package awais.instagrabber.webservices import awais.instagrabber.fragments.settings.PreferenceKeys import awais.instagrabber.models.StoryModel import awais.instagrabber.repositories.StoriesService import awais.instagrabber.repositories.requests.StoryViewerOptions import awais.instagrabber.repositories.responses.stories.ArchiveResponse import awais.instagrabber.repositories.responses.stories.Story import awais.instagrabber.repositories.responses.stories.StoryStickerResponse import awais.instagrabber.utils.ResponseBodyUtils import awais.instagrabber.utils.TextUtils.isEmpty import awais.instagrabber.utils.Utils import awais.instagrabber.webservices.RetrofitFactory.retrofit import org.json.JSONArray import org.json.JSONObject import java.util.* open class StoriesRepository(private val service: StoriesService) { suspend fun fetch(mediaId: Long): StoryModel { val response = service.fetch(mediaId) val itemJson = JSONObject(response).getJSONArray("items").getJSONObject(0) return ResponseBodyUtils.parseStoryItem(itemJson, false, null) } suspend fun getFeedStories(): List { val response = service.getFeedStories() val result: MutableList = mutableListOf() if (response?.broadcasts != null) { val length = response.broadcasts.size for (i in 0 until length) { val broadcast = response.broadcasts.get(i) result.add( Story( broadcast.id, broadcast.publishedTime, 1, 0L, broadcast.broadcastOwner, broadcast.muted, false, // unclear null, null, null, null, broadcast ) ) } } if (response?.tray != null) result.addAll(response.tray) return sort(result.toList()) } open suspend fun fetchHighlights(profileId: Long): List { val response = service.fetchHighlights(profileId) val highlightModels = response?.tray ?: listOf() return highlightModels } suspend fun fetchArchive(maxId: String): ArchiveResponse? { val form = mutableMapOf( "include_suggested_highlights" to "false", "is_in_archive_home" to "true", "include_cover" to "1", ) if (!isEmpty(maxId)) { form["max_id"] = maxId // NOT TESTED } return service.fetchArchive(form) } open suspend fun getUserStory(options: StoryViewerOptions): List { val url = buildUrl(options) ?: return emptyList() val response = service.getUserStory(url) val isLocOrHashtag = options.type == StoryViewerOptions.Type.LOCATION || options.type == StoryViewerOptions.Type.HASHTAG val isHighlight = options.type == StoryViewerOptions.Type.HIGHLIGHT || options.type == StoryViewerOptions.Type.STORY_ARCHIVE var data: JSONObject? = JSONObject(response) data = if (!isHighlight) { data?.optJSONObject(if (isLocOrHashtag) "story" else "reel") } else { data?.getJSONObject("reels")?.optJSONObject(options.name) } var username: String? = null if (data != null && !isLocOrHashtag) { username = data.getJSONObject("user").getString("username") } val media: JSONArray? = data?.optJSONArray("items") return if (media?.length() ?: 0 > 0 && media?.optJSONObject(0) != null) { val mediaLen = media.length() val models: MutableList = ArrayList() for (i in 0 until mediaLen) { data = media.getJSONObject(i) models.add(ResponseBodyUtils.parseStoryItem(data, isLocOrHashtag, username)) } models } else emptyList() } private suspend fun respondToSticker( csrfToken: String, userId: Long, deviceUuid: String, storyId: String, stickerId: String, action: String, arg1: String, arg2: String, ): StoryStickerResponse { val form = mapOf( "_csrftoken" to csrfToken, "_uid" to userId, "_uuid" to deviceUuid, "mutation_token" to UUID.randomUUID().toString(), "client_context" to UUID.randomUUID().toString(), "radio_type" to "wifi-none", arg1 to arg2, ) val signedForm = Utils.sign(form) return service.respondToSticker(storyId, stickerId, action, signedForm) } suspend fun respondToQuestion( csrfToken: String, userId: Long, deviceUuid: String, storyId: String, stickerId: String, answer: String, ): StoryStickerResponse = respondToSticker(csrfToken, userId, deviceUuid, storyId, stickerId, "story_question_response", "response", answer) suspend fun respondToQuiz( csrfToken: String, userId: Long, deviceUuid: String, storyId: String, stickerId: String, answer: Int, ): StoryStickerResponse { return respondToSticker(csrfToken, userId, deviceUuid, storyId, stickerId, "story_quiz_answer", "answer", answer.toString()) } suspend fun respondToPoll( csrfToken: String, userId: Long, deviceUuid: String, storyId: String, stickerId: String, answer: Int, ): StoryStickerResponse = respondToSticker(csrfToken, userId, deviceUuid, storyId, stickerId, "story_poll_vote", "vote", answer.toString()) suspend fun respondToSlider( csrfToken: String, userId: Long, deviceUuid: String, storyId: String, stickerId: String, answer: Double, ): StoryStickerResponse = respondToSticker(csrfToken, userId, deviceUuid, storyId, stickerId, "story_slider_vote", "vote", answer.toString()) suspend fun seen( csrfToken: String, userId: Long, deviceUuid: String, storyMediaId: String, takenAt: Long, seenAt: Long, ): String { val reelsForm = mapOf(storyMediaId to listOf(takenAt.toString() + "_" + seenAt)) val form = mutableMapOf( "_csrftoken" to csrfToken, "_uid" to userId, "_uuid" to deviceUuid, "container_module" to "feed_timeline", "reels" to reelsForm, ) val signedForm = Utils.sign(form) val queryMap = mapOf( "reel" to "1", "live_vod" to "0", ) return service.seen(queryMap, signedForm) } private fun buildUrl(options: StoryViewerOptions): String? { val builder = StringBuilder() builder.append("https://i.instagram.com/api/v1/") val type = options.type var id: String? = null when (type) { StoryViewerOptions.Type.HASHTAG -> { builder.append("tags/") id = options.name } StoryViewerOptions.Type.LOCATION -> { builder.append("locations/") id = options.id.toString() } StoryViewerOptions.Type.USER -> { builder.append("feed/user/") id = options.id.toString() } StoryViewerOptions.Type.HIGHLIGHT, StoryViewerOptions.Type.STORY_ARCHIVE -> { builder.append("feed/reels_media/?user_ids=") id = options.name } StoryViewerOptions.Type.STORY -> { } else -> { } } if (id == null) { return null } builder.append(id) if (type != StoryViewerOptions.Type.HIGHLIGHT && type != StoryViewerOptions.Type.STORY_ARCHIVE) { builder.append("/story/") } return builder.toString() } private fun sort(list: List): List { val listCopy = ArrayList(list) listCopy.sortWith { o1, o2 -> if (o1.latestReelMedia == null || o2.latestReelMedia == null) return@sortWith 0 else when (Utils.settingsHelper.getString(PreferenceKeys.STORY_SORT)) { "1" -> return@sortWith o2.latestReelMedia.compareTo(o1.latestReelMedia) "2" -> return@sortWith o1.latestReelMedia.compareTo(o2.latestReelMedia) else -> return@sortWith 0 } } return listCopy } companion object { @Volatile private var INSTANCE: StoriesRepository? = null fun getInstance(): StoriesRepository { return INSTANCE ?: synchronized(this) { val service: StoriesService = retrofit.create(StoriesService::class.java) StoriesRepository(service).also { INSTANCE = it } } } } }