Browse Source
improve search
improve search
1. separate logged-in and anonymous endpoints 2. migrate to retrofit + gson, retire SuggestionsFetcher 3. prefixing search with @ or # will only return users or hashtags, respectively 4. add subtitles for locations (address) and hashtags (rough post count)renovate/org.robolectric-robolectric-4.x
Austin Huang
4 years ago
No known key found for this signature in database
GPG Key ID: 84C23AA04587A91F
10 changed files with 283 additions and 172 deletions
-
113app/src/main/java/awais/instagrabber/activities/MainActivity.java
-
26app/src/main/java/awais/instagrabber/adapters/SuggestionsAdapter.java
-
113app/src/main/java/awais/instagrabber/asyncs/SuggestionsFetcher.java
-
15app/src/main/java/awais/instagrabber/repositories/SearchRepository.java
-
11app/src/main/java/awais/instagrabber/repositories/responses/Hashtag.java
-
36app/src/main/java/awais/instagrabber/repositories/responses/search/Place.java
-
39app/src/main/java/awais/instagrabber/repositories/responses/search/SearchItem.java
-
48app/src/main/java/awais/instagrabber/repositories/responses/search/SearchResponse.java
-
4app/src/main/java/awais/instagrabber/webservices/GraphQLService.java
-
50app/src/main/java/awais/instagrabber/webservices/SearchService.java
@ -1,113 +0,0 @@ |
|||||
package awais.instagrabber.asyncs; |
|
||||
|
|
||||
import android.os.AsyncTask; |
|
||||
import android.util.Log; |
|
||||
|
|
||||
import org.json.JSONArray; |
|
||||
import org.json.JSONObject; |
|
||||
|
|
||||
import java.io.InterruptedIOException; |
|
||||
import java.net.HttpURLConnection; |
|
||||
import java.net.URL; |
|
||||
import java.util.ArrayList; |
|
||||
import java.util.Collections; |
|
||||
|
|
||||
import awais.instagrabber.BuildConfig; |
|
||||
import awais.instagrabber.interfaces.FetchListener; |
|
||||
import awais.instagrabber.models.SuggestionModel; |
|
||||
import awais.instagrabber.models.enums.SuggestionType; |
|
||||
import awais.instagrabber.utils.Constants; |
|
||||
import awais.instagrabber.utils.NetworkUtils; |
|
||||
import awais.instagrabber.utils.UrlEncoder; |
|
||||
|
|
||||
public final class SuggestionsFetcher extends AsyncTask<String, String, SuggestionModel[]> { |
|
||||
private final FetchListener<SuggestionModel[]> fetchListener; |
|
||||
|
|
||||
public SuggestionsFetcher(final FetchListener<SuggestionModel[]> fetchListener) { |
|
||||
this.fetchListener = fetchListener; |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
protected void onPreExecute() { |
|
||||
if (fetchListener != null) fetchListener.doBefore(); |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
protected SuggestionModel[] doInBackground(final String... params) { |
|
||||
SuggestionModel[] result = null; |
|
||||
try { |
|
||||
final HttpURLConnection conn = (HttpURLConnection) new URL("https://www.instagram.com/web/search/topsearch/?context=blended&count=50&query=" |
|
||||
+ UrlEncoder.encodeUrl(params[0])).openConnection(); |
|
||||
conn.setUseCaches(false); |
|
||||
conn.connect(); |
|
||||
|
|
||||
if (conn.getResponseCode() == HttpURLConnection.HTTP_OK) { |
|
||||
final JSONObject jsonObject = new JSONObject(NetworkUtils.readFromConnection(conn)); |
|
||||
conn.disconnect(); |
|
||||
|
|
||||
final JSONArray usersArray = jsonObject.getJSONArray("users"); |
|
||||
final JSONArray hashtagsArray = jsonObject.getJSONArray("hashtags"); |
|
||||
final JSONArray placesArray = jsonObject.getJSONArray("places"); |
|
||||
|
|
||||
final int usersLen = usersArray.length(); |
|
||||
final int hashtagsLen = hashtagsArray.length(); |
|
||||
final int placesLen = placesArray.length(); |
|
||||
|
|
||||
final ArrayList<SuggestionModel> suggestionModels = new ArrayList<>(usersLen + hashtagsLen); |
|
||||
for (int i = 0; i < hashtagsLen; i++) { |
|
||||
final JSONObject hashtagsArrayJSONObject = hashtagsArray.getJSONObject(i); |
|
||||
|
|
||||
final JSONObject hashtag = hashtagsArrayJSONObject.getJSONObject("hashtag"); |
|
||||
|
|
||||
suggestionModels.add(new SuggestionModel(false, |
|
||||
hashtag.getString(Constants.EXTRAS_NAME), |
|
||||
null, |
|
||||
hashtag.optString("profile_pic_url", Constants.DEFAULT_HASH_TAG_PIC), |
|
||||
SuggestionType.TYPE_HASHTAG, |
|
||||
hashtagsArrayJSONObject.optInt("position", suggestionModels.size() - 1))); |
|
||||
} |
|
||||
|
|
||||
for (int i = 0; i < placesLen; i++) { |
|
||||
final JSONObject placesArrayJSONObject = placesArray.getJSONObject(i); |
|
||||
|
|
||||
final JSONObject place = placesArrayJSONObject.getJSONObject("place"); |
|
||||
|
|
||||
// name |
|
||||
suggestionModels.add(new SuggestionModel(false, |
|
||||
place.getJSONObject("location").getString("pk"), // +"/"+place.getString("slug"), |
|
||||
place.getString("title"), |
|
||||
place.optString("profile_pic_url"), |
|
||||
SuggestionType.TYPE_LOCATION, |
|
||||
placesArrayJSONObject.optInt("position", suggestionModels.size() - 1))); |
|
||||
} |
|
||||
|
|
||||
for (int i = 0; i < usersLen; i++) { |
|
||||
final JSONObject usersArrayJSONObject = usersArray.getJSONObject(i); |
|
||||
|
|
||||
final JSONObject user = usersArrayJSONObject.getJSONObject(Constants.EXTRAS_USER); |
|
||||
|
|
||||
suggestionModels.add(new SuggestionModel(user.getBoolean("is_verified"), |
|
||||
user.getString(Constants.EXTRAS_USERNAME), |
|
||||
user.getString("full_name"), |
|
||||
user.getString("profile_pic_url"), |
|
||||
SuggestionType.TYPE_USER, |
|
||||
usersArrayJSONObject.optInt("position", suggestionModels.size() - 1))); |
|
||||
} |
|
||||
|
|
||||
suggestionModels.trimToSize(); |
|
||||
|
|
||||
Collections.sort(suggestionModels); |
|
||||
|
|
||||
result = suggestionModels.toArray(new SuggestionModel[0]); |
|
||||
} |
|
||||
} catch (final Exception e) { |
|
||||
if (BuildConfig.DEBUG && !(e instanceof InterruptedIOException)) Log.e("AWAISKING_APP", "", e); |
|
||||
} |
|
||||
return result; |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
protected void onPostExecute(final SuggestionModel[] result) { |
|
||||
if (fetchListener != null) fetchListener.onResult(result); |
|
||||
} |
|
||||
} |
|
@ -0,0 +1,15 @@ |
|||||
|
package awais.instagrabber.repositories; |
||||
|
|
||||
|
import java.util.Map; |
||||
|
|
||||
|
import awais.instagrabber.repositories.responses.search.SearchResponse; |
||||
|
|
||||
|
import retrofit2.Call; |
||||
|
import retrofit2.http.GET; |
||||
|
import retrofit2.http.QueryMap; |
||||
|
import retrofit2.http.Url; |
||||
|
|
||||
|
public interface SearchRepository { |
||||
|
@GET |
||||
|
Call<SearchResponse> search(@Url String url, @QueryMap(encoded = true) Map<String, String> queryParams); |
||||
|
} |
@ -0,0 +1,36 @@ |
|||||
|
package awais.instagrabber.repositories.responses.search; |
||||
|
|
||||
|
import awais.instagrabber.repositories.responses.Location; |
||||
|
|
||||
|
public class Place { |
||||
|
private final Location location; |
||||
|
private final String title; // those are repeated within location |
||||
|
private final String subtitle; // address |
||||
|
private final String slug; // browser only; for end of address |
||||
|
|
||||
|
public Place(final Location location, |
||||
|
final String title, |
||||
|
final String subtitle, |
||||
|
final String slug) { |
||||
|
this.location = location; |
||||
|
this.title = title; |
||||
|
this.subtitle = subtitle; |
||||
|
this.slug = slug; |
||||
|
} |
||||
|
|
||||
|
public Location getLocation() { |
||||
|
return location; |
||||
|
} |
||||
|
|
||||
|
public String getTitle() { |
||||
|
return title; |
||||
|
} |
||||
|
|
||||
|
public String getSubtitle() { |
||||
|
return subtitle; |
||||
|
} |
||||
|
|
||||
|
public String getSlug() { |
||||
|
return slug; |
||||
|
} |
||||
|
} |
@ -0,0 +1,39 @@ |
|||||
|
package awais.instagrabber.repositories.responses.search; |
||||
|
|
||||
|
import java.util.List; |
||||
|
|
||||
|
import awais.instagrabber.repositories.responses.Hashtag; |
||||
|
import awais.instagrabber.repositories.responses.User; |
||||
|
|
||||
|
public class SearchItem { |
||||
|
private final User user; |
||||
|
private final Place place; |
||||
|
private final Hashtag hashtag; |
||||
|
private final int position; |
||||
|
|
||||
|
public SearchItem(final User user, |
||||
|
final Place place, |
||||
|
final Hashtag hashtag, |
||||
|
final int position) { |
||||
|
this.user = user; |
||||
|
this.place = place; |
||||
|
this.hashtag = hashtag; |
||||
|
this.position = position; |
||||
|
} |
||||
|
|
||||
|
public User getUser() { |
||||
|
return user; |
||||
|
} |
||||
|
|
||||
|
public Place getPlace() { |
||||
|
return place; |
||||
|
} |
||||
|
|
||||
|
public Hashtag getHashtag() { |
||||
|
return hashtag; |
||||
|
} |
||||
|
|
||||
|
public int getPosition() { |
||||
|
return position; |
||||
|
} |
||||
|
} |
@ -0,0 +1,48 @@ |
|||||
|
package awais.instagrabber.repositories.responses.search; |
||||
|
|
||||
|
import java.util.List; |
||||
|
|
||||
|
import awais.instagrabber.repositories.responses.User; |
||||
|
|
||||
|
public class SearchResponse { |
||||
|
// app |
||||
|
private final List<SearchItem> list; |
||||
|
// browser |
||||
|
private final List<SearchItem> users; |
||||
|
private final List<SearchItem> places; |
||||
|
private final List<SearchItem> hashtags; |
||||
|
// universal |
||||
|
private final String status; |
||||
|
|
||||
|
public SearchResponse(final List<SearchItem> list, |
||||
|
final List<SearchItem> users, |
||||
|
final List<SearchItem> places, |
||||
|
final List<SearchItem> hashtags, |
||||
|
final String status) { |
||||
|
this.list = list; |
||||
|
this.users = users; |
||||
|
this.places = places; |
||||
|
this.hashtags = hashtags; |
||||
|
this.status = status; |
||||
|
} |
||||
|
|
||||
|
public List<SearchItem> getList() { |
||||
|
return list; |
||||
|
} |
||||
|
|
||||
|
public List<SearchItem> getUsers() { |
||||
|
return users; |
||||
|
} |
||||
|
|
||||
|
public List<SearchItem> getPlaces() { |
||||
|
return places; |
||||
|
} |
||||
|
|
||||
|
public List<SearchItem> getHashtags() { |
||||
|
return hashtags; |
||||
|
} |
||||
|
|
||||
|
public String getStatus() { |
||||
|
return status; |
||||
|
} |
||||
|
} |
@ -0,0 +1,50 @@ |
|||||
|
package awais.instagrabber.webservices; |
||||
|
|
||||
|
import androidx.annotation.NonNull; |
||||
|
|
||||
|
import com.google.common.collect.ImmutableMap; |
||||
|
|
||||
|
import awais.instagrabber.repositories.SearchRepository; |
||||
|
import awais.instagrabber.repositories.responses.search.SearchResponse; |
||||
|
import awais.instagrabber.utils.TextUtils; |
||||
|
import retrofit2.Call; |
||||
|
import retrofit2.Callback; |
||||
|
import retrofit2.Response; |
||||
|
import retrofit2.Retrofit; |
||||
|
|
||||
|
public class SearchService extends BaseService { |
||||
|
private static final String TAG = "LocationService"; |
||||
|
|
||||
|
private final SearchRepository repository; |
||||
|
|
||||
|
private static SearchService instance; |
||||
|
|
||||
|
private SearchService() { |
||||
|
final Retrofit retrofit = getRetrofitBuilder() |
||||
|
.baseUrl("https://www.instagram.com") |
||||
|
.build(); |
||||
|
repository = retrofit.create(SearchRepository.class); |
||||
|
} |
||||
|
|
||||
|
public static SearchService getInstance() { |
||||
|
if (instance == null) { |
||||
|
instance = new SearchService(); |
||||
|
} |
||||
|
return instance; |
||||
|
} |
||||
|
|
||||
|
public Call<SearchResponse> search(final boolean isLoggedIn, |
||||
|
final String query, |
||||
|
final String context) { |
||||
|
final ImmutableMap.Builder<String, String> builder = ImmutableMap.builder(); |
||||
|
builder.put("query", query); |
||||
|
// context is one of: "blended", "user", "place", "hashtag" |
||||
|
// note that "place" and "hashtag" can contain ONE user result, who knows why |
||||
|
builder.put("context", context); |
||||
|
builder.put("count", "50"); |
||||
|
return repository.search(isLoggedIn |
||||
|
? "https://i.instagram.com/api/v1/fbsearch/topsearch_flat/" |
||||
|
: "https://www.instagram.com/web/search/topsearch/", |
||||
|
builder.build()); |
||||
|
} |
||||
|
} |
Write
Preview
Loading…
Cancel
Save
Reference in new issue