mirror of https://github.com/KokaKiwi/BarInsta
306 lines
11 KiB
Kotlin
306 lines
11 KiB
Kotlin
package awais.instagrabber.webservices
|
|
|
|
import android.util.Log
|
|
import awais.instagrabber.repositories.GraphQLService
|
|
import awais.instagrabber.repositories.responses.*
|
|
import awais.instagrabber.utils.Constants
|
|
import awais.instagrabber.utils.ResponseBodyUtils
|
|
import awais.instagrabber.utils.extensions.TAG
|
|
import org.json.JSONException
|
|
import org.json.JSONObject
|
|
import java.util.*
|
|
|
|
|
|
open class GraphQLRepository(private val service: GraphQLService) {
|
|
|
|
// TODO convert string response to a response class
|
|
private suspend fun fetch(
|
|
queryHash: String,
|
|
variables: String,
|
|
arg1: String,
|
|
arg2: String,
|
|
backup: User?,
|
|
): PostsFetchResponse {
|
|
val queryMap = mapOf(
|
|
"query_hash" to queryHash,
|
|
"variables" to variables,
|
|
)
|
|
val response = service.fetch(queryMap)
|
|
return parsePostResponse(response, arg1, arg2, backup)
|
|
}
|
|
|
|
suspend fun fetchLocationPosts(
|
|
locationId: Long,
|
|
maxId: String?,
|
|
): PostsFetchResponse = fetch(
|
|
"36bd0f2bf5911908de389b8ceaa3be6d",
|
|
"{\"id\":\"" + locationId + "\"," + "\"first\":25," + "\"after\":\"" + (maxId ?: "") + "\"}",
|
|
Constants.EXTRAS_LOCATION,
|
|
"edge_location_to_media",
|
|
null
|
|
)
|
|
|
|
suspend fun fetchHashtagPosts(
|
|
tag: String,
|
|
maxId: String?,
|
|
): PostsFetchResponse = fetch(
|
|
"9b498c08113f1e09617a1703c22b2f32",
|
|
"{\"tag_name\":\"" + tag + "\"," + "\"first\":25," + "\"after\":\"" + (maxId ?: "") + "\"}",
|
|
Constants.EXTRAS_HASHTAG,
|
|
"edge_hashtag_to_media",
|
|
null,
|
|
)
|
|
|
|
suspend fun fetchProfilePosts(
|
|
profileId: Long,
|
|
postsPerPage: Int,
|
|
maxId: String?,
|
|
backup: User?,
|
|
): PostsFetchResponse = fetch(
|
|
"02e14f6a7812a876f7d133c9555b1151",
|
|
"{\"id\":\"" + profileId + "\"," + "\"first\":" + postsPerPage + "," + "\"after\":\"" + (maxId ?: "") + "\"}",
|
|
Constants.EXTRAS_USER,
|
|
"edge_owner_to_timeline_media",
|
|
backup,
|
|
)
|
|
|
|
suspend fun fetchTaggedPosts(
|
|
profileId: Long,
|
|
postsPerPage: Int,
|
|
maxId: String?,
|
|
): PostsFetchResponse = fetch(
|
|
"31fe64d9463cbbe58319dced405c6206",
|
|
"{\"id\":\"" + profileId + "\"," + "\"first\":" + postsPerPage + "," + "\"after\":\"" + (maxId ?: "") + "\"}",
|
|
Constants.EXTRAS_USER,
|
|
"edge_user_to_photos_of_you",
|
|
null,
|
|
)
|
|
|
|
@Throws(JSONException::class)
|
|
private fun parsePostResponse(
|
|
response: String,
|
|
arg1: String,
|
|
arg2: String,
|
|
backup: User?,
|
|
): PostsFetchResponse {
|
|
if (response.isBlank()) {
|
|
Log.e(TAG, "parseResponse: feed response body is empty")
|
|
return PostsFetchResponse(emptyList(), false, null)
|
|
}
|
|
return parseResponseBody(response, arg1, arg2, backup)
|
|
}
|
|
|
|
@Throws(JSONException::class)
|
|
private fun parseResponseBody(
|
|
body: String,
|
|
arg1: String,
|
|
arg2: String,
|
|
backup: User?,
|
|
): PostsFetchResponse {
|
|
val items: MutableList<Media> = ArrayList()
|
|
val timelineFeed = JSONObject(body)
|
|
.getJSONObject("data")
|
|
.getJSONObject(arg1)
|
|
.getJSONObject(arg2)
|
|
val endCursor: String?
|
|
val hasNextPage: Boolean
|
|
val pageInfo = timelineFeed.getJSONObject("page_info")
|
|
if (pageInfo.has("has_next_page")) {
|
|
hasNextPage = pageInfo.getBoolean("has_next_page")
|
|
endCursor = if (hasNextPage) pageInfo.getString("end_cursor") else null
|
|
} else {
|
|
hasNextPage = false
|
|
endCursor = null
|
|
}
|
|
val feedItems = timelineFeed.getJSONArray("edges")
|
|
for (i in 0 until feedItems.length()) {
|
|
val itemJson = feedItems.optJSONObject(i) ?: continue
|
|
val media = ResponseBodyUtils.parseGraphQLItem(itemJson, backup)
|
|
if (media != null) {
|
|
items.add(media)
|
|
}
|
|
}
|
|
return PostsFetchResponse(items, hasNextPage, endCursor)
|
|
}
|
|
|
|
// TODO convert string response to a response class
|
|
suspend fun fetchCommentLikers(
|
|
commentId: String,
|
|
endCursor: String?,
|
|
): GraphQLUserListFetchResponse {
|
|
val queryMap = mapOf(
|
|
"query_hash" to "5f0b1f6281e72053cbc07909c8d154ae",
|
|
"variables" to "{\"comment_id\":\"" + commentId + "\"," + "\"first\":30," + "\"after\":\"" + (endCursor ?: "") + "\"}"
|
|
)
|
|
val response = service.fetch(queryMap)
|
|
val body = JSONObject(response)
|
|
val status = body.getString("status")
|
|
val data = body.getJSONObject("data").getJSONObject("comment").getJSONObject("edge_liked_by")
|
|
val pageInfo = data.getJSONObject("page_info")
|
|
val newEndCursor = if (pageInfo.getBoolean("has_next_page")) pageInfo.getString("end_cursor") else null
|
|
val users = data.getJSONArray("edges")
|
|
val usersLen = users.length()
|
|
val userModels: MutableList<User> = ArrayList()
|
|
for (j in 0 until usersLen) {
|
|
val userObject = users.getJSONObject(j).getJSONObject("node")
|
|
userModels.add(
|
|
User(
|
|
userObject.getLong("id"),
|
|
userObject.getString("username"),
|
|
userObject.optString("full_name"),
|
|
userObject.optBoolean("is_private"),
|
|
userObject.getString("profile_pic_url"),
|
|
userObject.optBoolean("is_verified")
|
|
)
|
|
)
|
|
}
|
|
return GraphQLUserListFetchResponse(newEndCursor, status, userModels)
|
|
}
|
|
|
|
suspend fun fetchComments(
|
|
shortCodeOrCommentId: String?,
|
|
root: Boolean,
|
|
cursor: String?,
|
|
): String {
|
|
val variables = mapOf(
|
|
(if (root) "shortcode" else "comment_id") to shortCodeOrCommentId,
|
|
"first" to 50,
|
|
"after" to (cursor ?: "")
|
|
)
|
|
val queryMap = mapOf(
|
|
"query_hash" to if (root) "bc3296d1ce80a24b1b6e40b1e72903f5" else "51fdd02b67508306ad4484ff574a0b62",
|
|
"variables" to JSONObject(variables).toString()
|
|
)
|
|
return service.fetch(queryMap)
|
|
}
|
|
|
|
// TODO convert string response to a response class
|
|
open suspend fun fetchUser(
|
|
username: String,
|
|
): User? {
|
|
val response = service.getUser(username)
|
|
try {
|
|
val body = JSONObject(
|
|
response
|
|
.split("<script type=\"text/javascript\">window._sharedData = ").get(1)
|
|
.split("</script>").get(0)
|
|
.trim().replace(Regex("\\};$"), "}")
|
|
)
|
|
val userJson = body
|
|
.getJSONObject("entry_data")
|
|
.getJSONArray("ProfilePage")
|
|
.getJSONObject(0)
|
|
.getJSONObject("graphql")
|
|
.getJSONObject(Constants.EXTRAS_USER)
|
|
val isPrivate = userJson.getBoolean("is_private")
|
|
val id = userJson.optLong(Constants.EXTRAS_ID, 0)
|
|
val timelineMedia = userJson.getJSONObject("edge_owner_to_timeline_media")
|
|
// if (timelineMedia.has("edges")) {
|
|
// final JSONArray edges = timelineMedia.getJSONArray("edges");
|
|
// }
|
|
var url: String? = userJson.optString("external_url")
|
|
if (url.isNullOrBlank()) url = null
|
|
return User(
|
|
id,
|
|
username,
|
|
userJson.getString("full_name"),
|
|
isPrivate,
|
|
userJson.getString("profile_pic_url_hd"),
|
|
userJson.getBoolean("is_verified"),
|
|
friendshipStatus = 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
|
|
),
|
|
mediaCount = timelineMedia.getLong("count"),
|
|
followerCount = userJson.getJSONObject("edge_followed_by").getLong("count"),
|
|
followingCount = userJson.getJSONObject("edge_follow").getLong("count"),
|
|
biography = userJson.getString("biography"),
|
|
externalUrl = url,
|
|
)
|
|
}
|
|
catch (e: Exception) {
|
|
Log.e(TAG, "fetchUser failed", e)
|
|
return null
|
|
}
|
|
}
|
|
|
|
// TODO convert string response to a response class
|
|
suspend fun fetchPost(
|
|
shortcode: String,
|
|
): Media {
|
|
val response = service.getPost(shortcode)
|
|
val body = JSONObject(response)
|
|
val media = body.getJSONObject("graphql").getJSONObject("shortcode_media")
|
|
return ResponseBodyUtils.parseGraphQLItem(media, null)
|
|
}
|
|
|
|
// TODO convert string response to a response class
|
|
suspend fun fetchTag(
|
|
tag: String,
|
|
): Hashtag {
|
|
val response = service.getTag(tag)
|
|
val body = JSONObject(response
|
|
.split("<script type=\"text/javascript\">window._sharedData = ").get(1)
|
|
.split("</script>").get(0)
|
|
.trim().replace(Regex("\\};$"), "}"))
|
|
.getJSONObject("entry_data")
|
|
.getJSONArray("TagPage")
|
|
.getJSONObject(0)
|
|
.getJSONObject("graphql")
|
|
.getJSONObject(Constants.EXTRAS_HASHTAG)
|
|
val timelineMedia = body.getJSONObject("edge_hashtag_to_media")
|
|
return Hashtag(
|
|
body.getString(Constants.EXTRAS_ID),
|
|
body.getString("name"),
|
|
timelineMedia.getLong("count"),
|
|
if (body.optBoolean("is_following")) 1 else 0,
|
|
null
|
|
)
|
|
}
|
|
|
|
// TODO convert string response to a response class
|
|
suspend fun fetchLocation(
|
|
locationId: Long,
|
|
): Location {
|
|
val response = service.getLocation(locationId)
|
|
val body = JSONObject(response
|
|
.split("<script type=\"text/javascript\">window._sharedData = ", "</script>").get(1)
|
|
.trim().replace(Regex("};$"), "}"))
|
|
.getJSONObject("entry_data")
|
|
.getJSONArray("LocationsPage")
|
|
.getJSONObject(0)
|
|
.getJSONObject("graphql")
|
|
.getJSONObject(Constants.EXTRAS_LOCATION)
|
|
// val timelineMedia = body.getJSONObject("edge_location_to_media")
|
|
val address = JSONObject(body.getString("address_json"))
|
|
return Location(
|
|
body.getLong(Constants.EXTRAS_ID),
|
|
body.getString("slug"),
|
|
body.getString("name"),
|
|
address.optString("street_address"),
|
|
address.optString("city_name"),
|
|
body.optDouble("lng", 0.0),
|
|
body.optDouble("lat", 0.0)
|
|
)
|
|
}
|
|
|
|
companion object {
|
|
@Volatile
|
|
private var INSTANCE: GraphQLRepository? = null
|
|
|
|
fun getInstance(): GraphQLRepository {
|
|
return INSTANCE ?: synchronized(this) {
|
|
val service = RetrofitFactory.retrofitWeb.create(GraphQLService::class.java)
|
|
GraphQLRepository(service).also { INSTANCE = it }
|
|
}
|
|
}
|
|
}
|
|
} |