package awais.instagrabber.webservices import awais.instagrabber.repositories.DirectMessagesService import awais.instagrabber.repositories.requests.directmessages.* import awais.instagrabber.repositories.responses.directmessages.* import awais.instagrabber.repositories.responses.giphy.GiphyGif import awais.instagrabber.utils.TextUtils.extractUrls import awais.instagrabber.utils.Utils import org.json.JSONArray import java.util.* open class DirectMessagesRepository(private val service: DirectMessagesService) { suspend fun fetchInbox( cursor: String?, seqId: Long, ): DirectInboxResponse { val queryMap = mutableMapOf( "visual_message_return_type" to "unseen", "thread_message_limit" to 10.toString(), "persistentBadging" to true.toString(), "limit" to 10.toString(), ) if (!cursor.isNullOrBlank()) { queryMap["cursor"] = cursor queryMap["direction"] = "older" } if (seqId != 0L) { queryMap["seq_id"] = seqId.toString() } return service.fetchInbox(queryMap) } suspend fun fetchThread( threadId: String, cursor: String?, ): DirectThreadFeedResponse { val queryMap = mutableMapOf( "visual_message_return_type" to "unseen", "limit" to 20.toString(), "direction" to "older", ) if (!cursor.isNullOrBlank()) { queryMap["cursor"] = cursor } return service.fetchThread(threadId, queryMap) } suspend fun fetchUnseenCount(): DirectBadgeCount = service.fetchUnseenCount() suspend fun broadcastText( csrfToken: String, userId: Long, deviceUuid: String, clientContext: String, threadIdsOrUserIds: ThreadIdsOrUserIds, text: String, repliedToItemId: String?, repliedToClientContext: String?, ): DirectThreadBroadcastResponse { val urls = extractUrls(text) if (urls.isNotEmpty()) { return broadcastLink( csrfToken, userId, deviceUuid, clientContext, threadIdsOrUserIds, text, urls, repliedToItemId, repliedToClientContext ) } val broadcastOptions = TextBroadcastOptions(clientContext, threadIdsOrUserIds, text) if (!repliedToItemId.isNullOrBlank() && !repliedToClientContext.isNullOrBlank()) { broadcastOptions.repliedToItemId = repliedToItemId broadcastOptions.repliedToClientContext = repliedToClientContext } return broadcast(csrfToken, userId, deviceUuid, broadcastOptions) } private suspend fun broadcastLink( csrfToken: String, userId: Long, deviceUuid: String, clientContext: String, threadIdsOrUserIds: ThreadIdsOrUserIds, linkText: String, urls: List, repliedToItemId: String?, repliedToClientContext: String?, ): DirectThreadBroadcastResponse { val broadcastOptions = LinkBroadcastOptions(clientContext, threadIdsOrUserIds, linkText, urls) if (!repliedToItemId.isNullOrBlank() && !repliedToClientContext.isNullOrBlank()) { broadcastOptions.repliedToItemId = repliedToItemId broadcastOptions.repliedToClientContext = repliedToClientContext } return broadcast(csrfToken, userId, deviceUuid, broadcastOptions) } suspend fun broadcastPhoto( csrfToken: String, userId: Long, deviceUuid: String, clientContext: String, threadIdsOrUserIds: ThreadIdsOrUserIds, uploadId: String, ): DirectThreadBroadcastResponse = broadcast(csrfToken, userId, deviceUuid, PhotoBroadcastOptions(clientContext, threadIdsOrUserIds, true, uploadId)) suspend fun broadcastVideo( csrfToken: String, userId: Long, deviceUuid: String, clientContext: String, threadIdsOrUserIds: ThreadIdsOrUserIds, uploadId: String, videoResult: String, sampled: Boolean, ): DirectThreadBroadcastResponse = broadcast(csrfToken, userId, deviceUuid, VideoBroadcastOptions(clientContext, threadIdsOrUserIds, videoResult, uploadId, sampled)) suspend fun broadcastVoice( csrfToken: String, userId: Long, deviceUuid: String, clientContext: String, threadIdsOrUserIds: ThreadIdsOrUserIds, uploadId: String, waveform: List, samplingFreq: Int, ): DirectThreadBroadcastResponse = broadcast(csrfToken, userId, deviceUuid, VoiceBroadcastOptions(clientContext, threadIdsOrUserIds, uploadId, waveform, samplingFreq)) suspend fun broadcastStoryReply( csrfToken: String, userId: Long, deviceUuid: String, threadIdsOrUserIds: ThreadIdsOrUserIds, text: String, mediaId: String, reelId: String, ): DirectThreadBroadcastResponse = broadcast(csrfToken, userId, deviceUuid, StoryReplyBroadcastOptions(UUID.randomUUID().toString(), threadIdsOrUserIds, text, mediaId, reelId)) suspend fun broadcastReaction( csrfToken: String, userId: Long, deviceUuid: String, clientContext: String, threadIdsOrUserIds: ThreadIdsOrUserIds, itemId: String, emoji: String?, delete: Boolean, ): DirectThreadBroadcastResponse = broadcast(csrfToken, userId, deviceUuid, ReactionBroadcastOptions(clientContext, threadIdsOrUserIds, itemId, emoji, delete)) suspend fun broadcastAnimatedMedia( csrfToken: String, userId: Long, deviceUuid: String, clientContext: String, threadIdsOrUserIds: ThreadIdsOrUserIds, giphyGif: GiphyGif, ): DirectThreadBroadcastResponse = broadcast(csrfToken, userId, deviceUuid, AnimatedMediaBroadcastOptions(clientContext, threadIdsOrUserIds, giphyGif)) suspend fun broadcastMediaShare( csrfToken: String, userId: Long, deviceUuid: String, clientContext: String, threadIdsOrUserIds: ThreadIdsOrUserIds, mediaId: String, childId: String?, ): DirectThreadBroadcastResponse = broadcast(csrfToken, userId, deviceUuid, MediaShareBroadcastOptions(clientContext, threadIdsOrUserIds, mediaId, childId)) suspend fun broadcastProfile( csrfToken: String, userId: Long, deviceUuid: String, clientContext: String, threadIdsOrUserIds: ThreadIdsOrUserIds, profileId: String, ): DirectThreadBroadcastResponse = broadcast(csrfToken, userId, deviceUuid, ProfileBroadcastOptions(clientContext, threadIdsOrUserIds, profileId)) private suspend fun broadcast( csrfToken: String, userId: Long, deviceUuid: String, broadcastOptions: BroadcastOptions, ): DirectThreadBroadcastResponse { require(broadcastOptions.clientContext.isNotBlank()) { "Broadcast requires a valid client context value" } val form = mutableMapOf( "_csrftoken" to csrfToken, "_uid" to userId.toString(10), "__uuid" to deviceUuid, "client_context" to broadcastOptions.clientContext, "mutation_token" to broadcastOptions.clientContext, ) val threadIds = broadcastOptions.threadIds val userIds = broadcastOptions.userIds require(!userIds.isNullOrEmpty() || !threadIds.isNullOrEmpty()) { "Either pass a list of thread ids or a list of lists of user ids" } if (!threadIds.isNullOrEmpty()) { form["thread_ids"] = JSONArray(threadIds).toString() } if (!userIds.isNullOrEmpty()) { form["recipient_users"] = JSONArray(userIds).toString() } val repliedToItemId = broadcastOptions.repliedToItemId val repliedToClientContext = broadcastOptions.repliedToClientContext if (!repliedToItemId.isNullOrBlank() && !repliedToClientContext.isNullOrBlank()) { form["replied_to_item_id"] = repliedToItemId form["replied_to_client_context"] = repliedToClientContext } form.putAll(broadcastOptions.formMap) form["action"] = "send_item" // val signedForm = Utils.sign(form) return service.broadcast(broadcastOptions.itemType.value, form) } suspend fun addUsers( csrfToken: String, deviceUuid: String, threadId: String, userIds: Collection, ): DirectThreadDetailsChangeResponse { val form = mapOf( "_csrftoken" to csrfToken, "_uuid" to deviceUuid, "user_ids" to JSONArray(userIds).toString(), ) return service.addUsers(threadId, form) } suspend fun removeUsers( csrfToken: String, deviceUuid: String, threadId: String, userIds: Collection, ): String { val form = mapOf( "_csrftoken" to csrfToken, "_uuid" to deviceUuid, "user_ids" to JSONArray(userIds).toString(), ) return service.removeUsers(threadId, form) } suspend fun updateTitle( csrfToken: String, deviceUuid: String, threadId: String, title: String, ): DirectThreadDetailsChangeResponse { val form = mapOf( "_csrftoken" to csrfToken, "_uuid" to deviceUuid, "title" to title, ) return service.updateTitle(threadId, form) } suspend fun addAdmins( csrfToken: String, deviceUuid: String, threadId: String, userIds: Collection, ): String { val form = mapOf( "_csrftoken" to csrfToken, "_uuid" to deviceUuid, "user_ids" to JSONArray(userIds).toString(), ) return service.addAdmins(threadId, form) } suspend fun removeAdmins( csrfToken: String, deviceUuid: String, threadId: String, userIds: Collection, ): String { val form = mapOf( "_csrftoken" to csrfToken, "_uuid" to deviceUuid, "user_ids" to JSONArray(userIds).toString(), ) return service.removeAdmins(threadId, form) } suspend fun deleteItem( csrfToken: String, deviceUuid: String, threadId: String, itemId: String, ): String { val form = mapOf( "_csrftoken" to csrfToken, "_uuid" to deviceUuid, ) return service.deleteItem(threadId, itemId, form) } suspend fun rankedRecipients( mode: String?, showThreads: Boolean?, query: String?, ): RankedRecipientsResponse { // String correctedMode = mode; // if (TextUtils.isEmpty(mode) || (!mode.equals("raven") && !mode.equals("reshare"))) { // correctedMode = "raven"; // } val queryMap = mutableMapOf() if (!mode.isNullOrBlank()) { queryMap["mode"] = mode } if (!query.isNullOrBlank()) { queryMap["query"] = query } if (showThreads != null) { queryMap["showThreads"] = showThreads.toString() } return service.rankedRecipients(queryMap) } suspend fun forward( toThreadId: String, itemType: String, fromThreadId: String, itemId: String, ): DirectThreadBroadcastResponse { val form = mapOf( "action" to "forward_item", "thread_id" to toThreadId, "item_type" to itemType, "forwarded_from_thread_id" to fromThreadId, "forwarded_from_thread_item_id" to itemId, ) return service.forward(form) } suspend fun createThread( csrfToken: String, userId: Long, deviceUuid: String, userIds: List, threadTitle: String?, ): DirectThread { val userIdStringList = userIds.map { it.toString() } val form = mutableMapOf( "_csrftoken" to csrfToken, "_uuid" to deviceUuid, "_uid" to userId, "recipient_users" to JSONArray(userIdStringList).toString(), ) if (!threadTitle.isNullOrBlank()) { form["thread_title"] = threadTitle } val signedForm = Utils.sign(form) return service.createThread(signedForm) } suspend fun mute( csrfToken: String, deviceUuid: String, threadId: String, ): String { val form = mapOf( "_csrftoken" to csrfToken, "_uuid" to deviceUuid ) return service.mute(threadId, form) } suspend fun unmute( csrfToken: String, deviceUuid: String, threadId: String, ): String { val form = mapOf( "_csrftoken" to csrfToken, "_uuid" to deviceUuid, ) return service.unmute(threadId, form) } suspend fun muteMentions( csrfToken: String, deviceUuid: String, threadId: String, ): String { val form = mapOf( "_csrftoken" to csrfToken, "_uuid" to deviceUuid, ) return service.muteMentions(threadId, form) } suspend fun unmuteMentions( csrfToken: String, deviceUuid: String, threadId: String, ): String { val form = mapOf( "_csrftoken" to csrfToken, "_uuid" to deviceUuid, ) return service.unmuteMentions(threadId, form) } suspend fun participantRequests( threadId: String, pageSize: Int, cursor: String? = null, ): DirectThreadParticipantRequestsResponse { return service.participantRequests(threadId, pageSize, cursor) } suspend fun approveParticipantRequests( csrfToken: String, deviceUuid: String, threadId: String, userIds: List, ): DirectThreadDetailsChangeResponse { val form = mapOf( "_csrftoken" to csrfToken, "_uuid" to deviceUuid, "user_ids" to JSONArray(userIds).toString(), // "share_join_chat_story" to String.valueOf(true) ) return service.approveParticipantRequests(threadId, form) } suspend fun declineParticipantRequests( csrfToken: String, deviceUuid: String, threadId: String, userIds: List, ): DirectThreadDetailsChangeResponse { val form = mapOf( "_csrftoken" to csrfToken, "_uuid" to deviceUuid, "user_ids" to JSONArray(userIds).toString(), ) return service.declineParticipantRequests(threadId, form) } suspend fun approvalRequired( csrfToken: String, deviceUuid: String, threadId: String, ): DirectThreadDetailsChangeResponse { val form = mapOf( "_csrftoken" to csrfToken, "_uuid" to deviceUuid, ) return service.approvalRequired(threadId, form) } suspend fun approvalNotRequired( csrfToken: String, deviceUuid: String, threadId: String, ): DirectThreadDetailsChangeResponse { val form = mapOf( "_csrftoken" to csrfToken, "_uuid" to deviceUuid, ) return service.approvalNotRequired(threadId, form) } suspend fun leave( csrfToken: String, deviceUuid: String, threadId: String, ): DirectThreadDetailsChangeResponse { val form = mapOf( "_csrftoken" to csrfToken, "_uuid" to deviceUuid, ) return service.leave(threadId, form) } suspend fun end( csrfToken: String, deviceUuid: String, threadId: String, ): DirectThreadDetailsChangeResponse { val form = mapOf( "_csrftoken" to csrfToken, "_uuid" to deviceUuid, ) return service.end(threadId, form) } suspend fun fetchPendingInbox(cursor: String?, seqId: Long): DirectInboxResponse { val queryMap = mutableMapOf( "visual_message_return_type" to "unseen", "thread_message_limit" to 20.toString(), "persistentBadging" to true.toString(), "limit" to 10.toString(), ) if (!cursor.isNullOrBlank()) { queryMap["cursor"] = cursor queryMap["direction"] = "older" } if (seqId != 0L) { queryMap["seq_id"] = seqId.toString() } return service.fetchPendingInbox(queryMap) } suspend fun approveRequest( csrfToken: String, deviceUuid: String, threadId: String, ): String { val form = mapOf( "_csrftoken" to csrfToken, "_uuid" to deviceUuid, ) return service.approveRequest(threadId, form) } suspend fun declineRequest( csrfToken: String, deviceUuid: String, threadId: String, ): String { val form = mapOf( "_csrftoken" to csrfToken, "_uuid" to deviceUuid, ) return service.declineRequest(threadId, form) } suspend fun markAsSeen( csrfToken: String, deviceUuid: String, threadId: String, directItem: DirectItem, ): DirectItemSeenResponse? { val itemId = directItem.itemId ?: return null val form = mapOf( "_csrftoken" to csrfToken, "_uuid" to deviceUuid, "use_unified_inbox" to "true", "action" to "mark_seen", "thread_id" to threadId, "item_id" to itemId, ) return service.markItemSeen(threadId, itemId, form) } companion object { @Volatile private var INSTANCE: DirectMessagesRepository? = null fun getInstance(): DirectMessagesRepository { return INSTANCE ?: synchronized(this) { val service: DirectMessagesService = RetrofitFactory.retrofit.create(DirectMessagesService::class.java) DirectMessagesRepository(service).also { INSTANCE = it } } } } }