2020-10-17 10:07:03 +00:00
|
|
|
package awais.instagrabber.webservices;
|
|
|
|
|
|
|
|
import android.util.Log;
|
|
|
|
|
|
|
|
import androidx.annotation.NonNull;
|
|
|
|
|
|
|
|
import org.json.JSONArray;
|
|
|
|
import org.json.JSONException;
|
|
|
|
import org.json.JSONObject;
|
|
|
|
|
|
|
|
import java.util.ArrayList;
|
|
|
|
import java.util.Collections;
|
|
|
|
import java.util.HashMap;
|
|
|
|
import java.util.List;
|
|
|
|
import java.util.Map;
|
|
|
|
|
2021-03-20 17:37:17 +00:00
|
|
|
import awais.instagrabber.models.enums.FollowingType;
|
2020-12-20 18:35:16 +00:00
|
|
|
import awais.instagrabber.repositories.GraphQLRepository;
|
2021-01-07 12:36:33 +00:00
|
|
|
import awais.instagrabber.repositories.responses.FriendshipStatus;
|
2020-12-26 18:14:08 +00:00
|
|
|
import awais.instagrabber.repositories.responses.GraphQLUserListFetchResponse;
|
2021-03-20 17:37:17 +00:00
|
|
|
import awais.instagrabber.repositories.responses.Hashtag;
|
2021-01-07 12:36:33 +00:00
|
|
|
import awais.instagrabber.repositories.responses.Media;
|
2020-10-27 11:33:21 +00:00
|
|
|
import awais.instagrabber.repositories.responses.PostsFetchResponse;
|
2021-01-07 12:36:33 +00:00
|
|
|
import awais.instagrabber.repositories.responses.User;
|
2020-10-17 10:07:03 +00:00
|
|
|
import awais.instagrabber.utils.Constants;
|
|
|
|
import awais.instagrabber.utils.ResponseBodyUtils;
|
|
|
|
import awais.instagrabber.utils.TextUtils;
|
|
|
|
import retrofit2.Call;
|
|
|
|
import retrofit2.Callback;
|
|
|
|
import retrofit2.Response;
|
|
|
|
import retrofit2.Retrofit;
|
|
|
|
|
2020-12-20 18:35:16 +00:00
|
|
|
public class GraphQLService extends BaseService {
|
|
|
|
private static final String TAG = "GraphQLService";
|
2021-01-07 12:36:33 +00:00
|
|
|
// private static final boolean loadFromMock = false;
|
2020-10-17 10:07:03 +00:00
|
|
|
|
2020-12-20 18:35:16 +00:00
|
|
|
private final GraphQLRepository repository;
|
2020-10-17 10:07:03 +00:00
|
|
|
|
2020-12-20 18:35:16 +00:00
|
|
|
private static GraphQLService instance;
|
2020-10-17 10:07:03 +00:00
|
|
|
|
2020-12-20 18:35:16 +00:00
|
|
|
private GraphQLService() {
|
2020-10-17 10:07:03 +00:00
|
|
|
final Retrofit retrofit = getRetrofitBuilder()
|
|
|
|
.baseUrl("https://www.instagram.com")
|
|
|
|
.build();
|
2020-12-20 18:35:16 +00:00
|
|
|
repository = retrofit.create(GraphQLRepository.class);
|
2020-10-17 10:07:03 +00:00
|
|
|
}
|
|
|
|
|
2020-12-20 18:35:16 +00:00
|
|
|
public static GraphQLService getInstance() {
|
2020-10-17 10:07:03 +00:00
|
|
|
if (instance == null) {
|
2020-12-20 18:35:16 +00:00
|
|
|
instance = new GraphQLService();
|
2020-10-17 10:07:03 +00:00
|
|
|
}
|
|
|
|
return instance;
|
|
|
|
}
|
|
|
|
|
2020-12-20 18:35:16 +00:00
|
|
|
private void fetch(final String queryHash,
|
|
|
|
final String variables,
|
|
|
|
final String arg1,
|
|
|
|
final String arg2,
|
2021-03-22 01:35:18 +00:00
|
|
|
final User backup,
|
2020-12-20 18:35:16 +00:00
|
|
|
final ServiceCallback<PostsFetchResponse> callback) {
|
2020-10-17 10:07:03 +00:00
|
|
|
final Map<String, String> queryMap = new HashMap<>();
|
2020-12-20 18:35:16 +00:00
|
|
|
queryMap.put("query_hash", queryHash);
|
|
|
|
queryMap.put("variables", variables);
|
2020-10-17 10:07:03 +00:00
|
|
|
final Call<String> request = repository.fetch(queryMap);
|
|
|
|
request.enqueue(new Callback<String>() {
|
|
|
|
@Override
|
|
|
|
public void onResponse(@NonNull final Call<String> call, @NonNull final Response<String> response) {
|
|
|
|
try {
|
|
|
|
// Log.d(TAG, "onResponse: body: " + response.body());
|
2021-03-22 01:35:18 +00:00
|
|
|
final PostsFetchResponse postsFetchResponse = parsePostResponse(response, arg1, arg2, backup);
|
2020-10-17 10:07:03 +00:00
|
|
|
if (callback != null) {
|
2020-10-27 11:33:21 +00:00
|
|
|
callback.onSuccess(postsFetchResponse);
|
2020-10-17 10:07:03 +00:00
|
|
|
}
|
|
|
|
} catch (JSONException e) {
|
|
|
|
Log.e(TAG, "onResponse", e);
|
|
|
|
if (callback != null) {
|
|
|
|
callback.onFailure(e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void onFailure(@NonNull final Call<String> call, @NonNull final Throwable t) {
|
|
|
|
if (callback != null) {
|
|
|
|
callback.onFailure(t);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
2020-12-20 18:35:16 +00:00
|
|
|
}
|
|
|
|
|
2021-01-08 12:49:48 +00:00
|
|
|
public void fetchLocationPosts(final long locationId,
|
2020-12-20 18:35:16 +00:00
|
|
|
final String maxId,
|
|
|
|
final ServiceCallback<PostsFetchResponse> callback) {
|
|
|
|
fetch("36bd0f2bf5911908de389b8ceaa3be6d",
|
2021-01-07 12:36:33 +00:00
|
|
|
"{\"id\":\"" + locationId + "\"," +
|
|
|
|
"\"first\":25," +
|
|
|
|
"\"after\":\"" + (maxId == null ? "" : maxId) + "\"}",
|
|
|
|
Constants.EXTRAS_LOCATION,
|
|
|
|
"edge_location_to_media",
|
2021-03-22 01:35:18 +00:00
|
|
|
null,
|
2021-01-07 12:36:33 +00:00
|
|
|
callback);
|
2020-12-20 18:35:16 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
public void fetchHashtagPosts(@NonNull final String tag,
|
|
|
|
final String maxId,
|
|
|
|
final ServiceCallback<PostsFetchResponse> callback) {
|
|
|
|
fetch("9b498c08113f1e09617a1703c22b2f32",
|
2021-01-07 12:36:33 +00:00
|
|
|
"{\"tag_name\":\"" + tag + "\"," +
|
|
|
|
"\"first\":25," +
|
|
|
|
"\"after\":\"" + (maxId == null ? "" : maxId) + "\"}",
|
|
|
|
Constants.EXTRAS_HASHTAG,
|
|
|
|
"edge_hashtag_to_media",
|
2021-03-22 01:35:18 +00:00
|
|
|
null,
|
2021-01-07 12:36:33 +00:00
|
|
|
callback);
|
2020-12-20 18:35:16 +00:00
|
|
|
}
|
|
|
|
|
2021-01-07 12:36:33 +00:00
|
|
|
public void fetchProfilePosts(final long profileId,
|
2020-12-20 18:35:16 +00:00
|
|
|
final int postsPerPage,
|
|
|
|
final String maxId,
|
2021-03-22 01:35:18 +00:00
|
|
|
final User backup,
|
2020-12-20 18:35:16 +00:00
|
|
|
final ServiceCallback<PostsFetchResponse> callback) {
|
|
|
|
fetch("18a7b935ab438c4514b1f742d8fa07a7",
|
2021-01-07 12:36:33 +00:00
|
|
|
"{\"id\":\"" + profileId + "\"," +
|
|
|
|
"\"first\":" + postsPerPage + "," +
|
|
|
|
"\"after\":\"" + (maxId == null ? "" : maxId) + "\"}",
|
|
|
|
Constants.EXTRAS_USER,
|
|
|
|
"edge_owner_to_timeline_media",
|
2021-03-22 01:35:18 +00:00
|
|
|
backup,
|
2021-01-07 12:36:33 +00:00
|
|
|
callback);
|
2020-12-20 18:35:16 +00:00
|
|
|
}
|
2020-10-17 10:07:03 +00:00
|
|
|
|
2021-01-07 12:36:33 +00:00
|
|
|
public void fetchTaggedPosts(final long profileId,
|
2020-12-20 18:35:16 +00:00
|
|
|
final int postsPerPage,
|
|
|
|
final String maxId,
|
|
|
|
final ServiceCallback<PostsFetchResponse> callback) {
|
|
|
|
fetch("31fe64d9463cbbe58319dced405c6206",
|
2021-01-07 12:36:33 +00:00
|
|
|
"{\"id\":\"" + profileId + "\"," +
|
|
|
|
"\"first\":" + postsPerPage + "," +
|
|
|
|
"\"after\":\"" + (maxId == null ? "" : maxId) + "\"}",
|
|
|
|
Constants.EXTRAS_USER,
|
|
|
|
"edge_user_to_photos_of_you",
|
2021-03-22 01:35:18 +00:00
|
|
|
null,
|
2021-01-07 12:36:33 +00:00
|
|
|
callback);
|
2020-10-17 10:07:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
@NonNull
|
2021-03-22 01:35:18 +00:00
|
|
|
private PostsFetchResponse parsePostResponse(@NonNull final Response<String> response,
|
|
|
|
@NonNull final String arg1,
|
|
|
|
@NonNull final String arg2,
|
|
|
|
final User backup)
|
2021-01-07 12:36:33 +00:00
|
|
|
throws JSONException {
|
2020-10-17 10:07:03 +00:00
|
|
|
if (TextUtils.isEmpty(response.body())) {
|
|
|
|
Log.e(TAG, "parseResponse: feed response body is empty with status code: " + response.code());
|
2020-10-27 11:33:21 +00:00
|
|
|
return new PostsFetchResponse(Collections.emptyList(), false, null);
|
2020-10-17 10:07:03 +00:00
|
|
|
}
|
2021-03-22 01:35:18 +00:00
|
|
|
return parseResponseBody(response.body(), arg1, arg2, backup);
|
2020-10-17 10:07:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
@NonNull
|
2021-03-22 01:35:18 +00:00
|
|
|
private PostsFetchResponse parseResponseBody(@NonNull final String body,
|
|
|
|
@NonNull final String arg1,
|
|
|
|
@NonNull final String arg2,
|
|
|
|
final User backup)
|
2020-10-17 10:07:03 +00:00
|
|
|
throws JSONException {
|
2021-01-07 12:36:33 +00:00
|
|
|
final List<Media> items = new ArrayList<>();
|
2020-10-17 10:07:03 +00:00
|
|
|
final JSONObject timelineFeed = new JSONObject(body)
|
|
|
|
.getJSONObject("data")
|
2020-12-20 18:35:16 +00:00
|
|
|
.getJSONObject(arg1)
|
|
|
|
.getJSONObject(arg2);
|
2020-10-17 10:07:03 +00:00
|
|
|
final String endCursor;
|
|
|
|
final boolean hasNextPage;
|
|
|
|
|
|
|
|
final JSONObject pageInfo = timelineFeed.getJSONObject("page_info");
|
|
|
|
if (pageInfo.has("has_next_page")) {
|
|
|
|
hasNextPage = pageInfo.getBoolean("has_next_page");
|
|
|
|
endCursor = hasNextPage ? pageInfo.getString("end_cursor") : null;
|
|
|
|
} else {
|
|
|
|
hasNextPage = false;
|
|
|
|
endCursor = null;
|
|
|
|
}
|
|
|
|
|
|
|
|
final JSONArray feedItems = timelineFeed.getJSONArray("edges");
|
|
|
|
|
|
|
|
for (int i = 0; i < feedItems.length(); ++i) {
|
2020-11-13 19:19:26 +00:00
|
|
|
final JSONObject itemJson = feedItems.optJSONObject(i);
|
|
|
|
if (itemJson == null) {
|
2020-10-17 10:07:03 +00:00
|
|
|
continue;
|
|
|
|
}
|
2021-03-22 01:35:18 +00:00
|
|
|
final Media media = ResponseBodyUtils.parseGraphQLItem(itemJson, backup);
|
2021-01-07 12:36:33 +00:00
|
|
|
if (media != null) {
|
|
|
|
items.add(media);
|
2020-11-13 23:37:19 +00:00
|
|
|
}
|
2020-10-17 10:07:03 +00:00
|
|
|
}
|
2021-01-07 12:36:33 +00:00
|
|
|
return new PostsFetchResponse(items, hasNextPage, endCursor);
|
2020-10-17 10:07:03 +00:00
|
|
|
}
|
2020-12-26 18:14:08 +00:00
|
|
|
|
|
|
|
public void fetchCommentLikers(final String commentId,
|
2021-01-07 12:36:33 +00:00
|
|
|
final String endCursor,
|
|
|
|
final ServiceCallback<GraphQLUserListFetchResponse> callback) {
|
2020-12-26 18:14:08 +00:00
|
|
|
final Map<String, String> queryMap = new HashMap<>();
|
|
|
|
queryMap.put("query_hash", "5f0b1f6281e72053cbc07909c8d154ae");
|
|
|
|
queryMap.put("variables", "{\"comment_id\":\"" + commentId + "\"," +
|
|
|
|
"\"first\":30," +
|
|
|
|
"\"after\":\"" + (endCursor == null ? "" : endCursor) + "\"}");
|
|
|
|
final Call<String> request = repository.fetch(queryMap);
|
|
|
|
request.enqueue(new Callback<String>() {
|
|
|
|
@Override
|
|
|
|
public void onResponse(@NonNull final Call<String> call, @NonNull final Response<String> response) {
|
|
|
|
final String rawBody = response.body();
|
|
|
|
if (rawBody == null) {
|
2021-01-07 12:36:33 +00:00
|
|
|
Log.e(TAG, "Error occurred while fetching gql comment likes of " + commentId);
|
2020-12-26 18:14:08 +00:00
|
|
|
callback.onSuccess(null);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
try {
|
|
|
|
final JSONObject body = new JSONObject(rawBody);
|
|
|
|
final String status = body.getString("status");
|
|
|
|
final JSONObject data = body.getJSONObject("data").getJSONObject("comment").getJSONObject("edge_liked_by");
|
|
|
|
final JSONObject pageInfo = data.getJSONObject("page_info");
|
|
|
|
final String endCursor = pageInfo.getBoolean("has_next_page") ? pageInfo.getString("end_cursor") : null;
|
|
|
|
final JSONArray users = data.getJSONArray("edges");
|
|
|
|
final int usersLen = users.length();
|
2021-01-07 12:36:33 +00:00
|
|
|
final List<User> userModels = new ArrayList<>();
|
2020-12-26 18:14:08 +00:00
|
|
|
for (int j = 0; j < usersLen; ++j) {
|
|
|
|
final JSONObject userObject = users.getJSONObject(j).getJSONObject("node");
|
2021-01-07 12:36:33 +00:00
|
|
|
userModels.add(new User(
|
|
|
|
userObject.getLong("id"),
|
2020-12-26 18:14:08 +00:00
|
|
|
userObject.getString("username"),
|
|
|
|
userObject.optString("full_name"),
|
2021-01-07 12:36:33 +00:00
|
|
|
userObject.optBoolean("is_private"),
|
2020-12-26 18:14:08 +00:00
|
|
|
userObject.getString("profile_pic_url"),
|
2021-01-07 12:36:33 +00:00
|
|
|
null,
|
|
|
|
new FriendshipStatus(
|
|
|
|
false,
|
|
|
|
false,
|
|
|
|
false,
|
|
|
|
false,
|
|
|
|
false,
|
|
|
|
false,
|
|
|
|
false,
|
|
|
|
false,
|
|
|
|
false,
|
|
|
|
false
|
|
|
|
),
|
|
|
|
userObject.optBoolean("is_verified"),
|
|
|
|
false,
|
|
|
|
false,
|
|
|
|
false,
|
|
|
|
false,
|
|
|
|
null,
|
|
|
|
null,
|
|
|
|
0,
|
|
|
|
0,
|
|
|
|
0,
|
|
|
|
0,
|
|
|
|
null,
|
|
|
|
null,
|
|
|
|
0,
|
2021-01-27 02:21:48 +00:00
|
|
|
null,
|
2021-03-05 15:50:10 +00:00
|
|
|
null,
|
|
|
|
null,
|
2021-03-05 19:06:27 +00:00
|
|
|
null,
|
2021-03-17 00:24:19 +00:00
|
|
|
null,
|
2021-01-07 12:36:33 +00:00
|
|
|
null
|
|
|
|
));
|
|
|
|
// userModels.add(new ProfileModel(userObject.optBoolean("is_private"),
|
|
|
|
// false,
|
|
|
|
// userObject.optBoolean("is_verified"),
|
|
|
|
// userObject.getString("id"),
|
|
|
|
// userObject.getString("username"),
|
|
|
|
// userObject.optString("full_name"),
|
|
|
|
// null, null,
|
|
|
|
// userObject.getString("profile_pic_url"),
|
|
|
|
// null, 0, 0, 0, false, false, false, false, false));
|
2020-12-26 18:14:08 +00:00
|
|
|
}
|
|
|
|
callback.onSuccess(new GraphQLUserListFetchResponse(endCursor, status, userModels));
|
|
|
|
} catch (JSONException e) {
|
|
|
|
Log.e(TAG, "onResponse", e);
|
|
|
|
if (callback != null) {
|
|
|
|
callback.onFailure(e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void onFailure(@NonNull final Call<String> call, @NonNull final Throwable t) {
|
|
|
|
if (callback != null) {
|
|
|
|
callback.onFailure(t);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
2021-01-23 19:17:56 +00:00
|
|
|
|
|
|
|
public void fetchUser(final String username,
|
|
|
|
final ServiceCallback<User> callback) {
|
|
|
|
final Call<String> request = repository.getUser(username);
|
|
|
|
request.enqueue(new Callback<String>() {
|
|
|
|
@Override
|
|
|
|
public void onResponse(@NonNull final Call<String> call, @NonNull final Response<String> response) {
|
|
|
|
final String rawBody = response.body();
|
|
|
|
if (rawBody == null) {
|
|
|
|
Log.e(TAG, "Error occurred while fetching gql user of " + username);
|
|
|
|
callback.onSuccess(null);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
try {
|
|
|
|
final JSONObject body = new JSONObject(rawBody);
|
|
|
|
final JSONObject userJson = body.getJSONObject("graphql")
|
|
|
|
.getJSONObject(Constants.EXTRAS_USER);
|
|
|
|
|
|
|
|
boolean isPrivate = userJson.getBoolean("is_private");
|
|
|
|
final long id = userJson.optLong(Constants.EXTRAS_ID, 0);
|
|
|
|
final JSONObject timelineMedia = userJson.getJSONObject("edge_owner_to_timeline_media");
|
|
|
|
// if (timelineMedia.has("edges")) {
|
|
|
|
// final JSONArray edges = timelineMedia.getJSONArray("edges");
|
|
|
|
// }
|
|
|
|
|
|
|
|
String url = userJson.optString("external_url");
|
|
|
|
if (TextUtils.isEmpty(url)) url = null;
|
|
|
|
|
|
|
|
callback.onSuccess(new User(
|
|
|
|
id,
|
|
|
|
username,
|
|
|
|
userJson.getString("full_name"),
|
|
|
|
isPrivate,
|
|
|
|
userJson.getString("profile_pic_url_hd"),
|
|
|
|
null,
|
|
|
|
new FriendshipStatus(
|
|
|
|
userJson.optBoolean("followed_by_viewer"),
|
|
|
|
userJson.optBoolean("follows_viewer"),
|
|
|
|
userJson.optBoolean("blocked_by_viewer"),
|
|
|
|
false,
|
|
|
|
isPrivate,
|
|
|
|
userJson.optBoolean("has_requested_viewer"),
|
|
|
|
userJson.optBoolean("requested_by_viewer"),
|
|
|
|
false,
|
|
|
|
userJson.optBoolean("restricted_by_viewer"),
|
|
|
|
false
|
|
|
|
),
|
|
|
|
userJson.getBoolean("is_verified"),
|
|
|
|
false,
|
|
|
|
false,
|
|
|
|
false,
|
|
|
|
false,
|
|
|
|
null,
|
|
|
|
null,
|
|
|
|
timelineMedia.getLong("count"),
|
|
|
|
userJson.getJSONObject("edge_followed_by").getLong("count"),
|
|
|
|
userJson.getJSONObject("edge_follow").getLong("count"),
|
|
|
|
0,
|
|
|
|
userJson.getString("biography"),
|
|
|
|
url,
|
|
|
|
0,
|
2021-01-27 02:21:48 +00:00
|
|
|
null,
|
2021-03-05 15:50:10 +00:00
|
|
|
null,
|
|
|
|
null,
|
2021-03-05 19:06:27 +00:00
|
|
|
null,
|
2021-03-17 00:24:19 +00:00
|
|
|
null,
|
2021-01-23 19:17:56 +00:00
|
|
|
null));
|
|
|
|
} catch (JSONException e) {
|
|
|
|
Log.e(TAG, "onResponse", e);
|
|
|
|
if (callback != null) {
|
|
|
|
callback.onFailure(e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void onFailure(@NonNull final Call<String> call, @NonNull final Throwable t) {
|
|
|
|
if (callback != null) {
|
|
|
|
callback.onFailure(t);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
2021-03-20 17:37:17 +00:00
|
|
|
|
|
|
|
public void fetchTag(final String tag,
|
|
|
|
final ServiceCallback<Hashtag> callback) {
|
|
|
|
final Call<String> request = repository.getTag(tag);
|
|
|
|
request.enqueue(new Callback<String>() {
|
|
|
|
@Override
|
|
|
|
public void onResponse(@NonNull final Call<String> call, @NonNull final Response<String> response) {
|
|
|
|
final String rawBody = response.body();
|
|
|
|
if (rawBody == null) {
|
|
|
|
Log.e(TAG, "Error occurred while fetching gql tag of " + tag);
|
|
|
|
callback.onSuccess(null);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
try {
|
|
|
|
final JSONObject body = new JSONObject(rawBody)
|
|
|
|
.getJSONObject("graphql")
|
|
|
|
.getJSONObject(Constants.EXTRAS_HASHTAG);
|
|
|
|
final JSONObject timelineMedia = body.getJSONObject("edge_hashtag_to_media");
|
|
|
|
callback.onSuccess(new Hashtag(
|
|
|
|
body.getString(Constants.EXTRAS_ID),
|
|
|
|
body.getString("name"),
|
|
|
|
timelineMedia.getLong("count"),
|
2021-03-22 20:48:35 +00:00
|
|
|
body.optBoolean("is_following") ? FollowingType.FOLLOWING : FollowingType.NOT_FOLLOWING,
|
|
|
|
null));
|
2021-03-20 17:37:17 +00:00
|
|
|
} catch (JSONException e) {
|
|
|
|
Log.e(TAG, "onResponse", e);
|
|
|
|
if (callback != null) {
|
|
|
|
callback.onFailure(e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void onFailure(@NonNull final Call<String> call, @NonNull final Throwable t) {
|
|
|
|
if (callback != null) {
|
|
|
|
callback.onFailure(t);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
2020-10-17 10:07:03 +00:00
|
|
|
}
|