More suspend funs

This commit is contained in:
Ammar Githam 2021-05-31 22:16:18 +09:00
parent 538a1406a6
commit 741a997424
10 changed files with 250 additions and 292 deletions

View File

@ -88,9 +88,9 @@ import awais.instagrabber.repositories.requests.directmessages.ThreadIdOrUserIds
import awais.instagrabber.repositories.responses.Media; import awais.instagrabber.repositories.responses.Media;
import awais.instagrabber.repositories.responses.StoryStickerResponse; import awais.instagrabber.repositories.responses.StoryStickerResponse;
import awais.instagrabber.repositories.responses.directmessages.DirectThread; import awais.instagrabber.repositories.responses.directmessages.DirectThread;
import awais.instagrabber.repositories.responses.directmessages.DirectThreadBroadcastResponse;
import awais.instagrabber.utils.Constants; import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.CookieUtils; import awais.instagrabber.utils.CookieUtils;
import awais.instagrabber.utils.CoroutineUtilsKt;
import awais.instagrabber.utils.DownloadUtils; import awais.instagrabber.utils.DownloadUtils;
import awais.instagrabber.utils.TextUtils; import awais.instagrabber.utils.TextUtils;
import awais.instagrabber.utils.Utils; import awais.instagrabber.utils.Utils;
@ -231,32 +231,21 @@ public class StoryViewerFragment extends Fragment {
return; return;
} }
final DirectThread thread = response.body(); final DirectThread thread = response.body();
final Call<DirectThreadBroadcastResponse> request = directMessagesService.broadcastStoryReply( directMessagesService.broadcastStoryReply(
ThreadIdOrUserIds.of(thread.getThreadId()), ThreadIdOrUserIds.of(thread.getThreadId()),
input.getText().toString(), input.getText().toString(),
currentStory.getStoryMediaId(), currentStory.getStoryMediaId(),
String.valueOf(currentStory.getUserId()) String.valueOf(currentStory.getUserId()),
); CoroutineUtilsKt.getContinuation((directThreadBroadcastResponse, throwable) -> {
request.enqueue(new Callback<DirectThreadBroadcastResponse>() { if (throwable != null) {
@Override Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show();
public void onResponse(@NonNull final Call<DirectThreadBroadcastResponse> call, Log.e(TAG, "onFailure: ", throwable);
@NonNull final Response<DirectThreadBroadcastResponse> response) { return;
if (!response.isSuccessful()) { }
Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show(); Toast.makeText(context, R.string.answered_story, Toast.LENGTH_SHORT).show();
return; })
}
Toast.makeText(context, R.string.answered_story, Toast.LENGTH_SHORT).show();
}
@Override );
public void onFailure(@NonNull final Call<DirectThreadBroadcastResponse> call, @NonNull final Throwable t) {
try {
Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show();
Log.e(TAG, "onFailure: ", t);
} catch (Throwable ignored) {
}
}
});
} }
@Override @Override

View File

@ -12,7 +12,6 @@ import awais.instagrabber.models.Resource.Companion.success
import awais.instagrabber.repositories.requests.directmessages.ThreadIdOrUserIds.Companion.of import awais.instagrabber.repositories.requests.directmessages.ThreadIdOrUserIds.Companion.of
import awais.instagrabber.repositories.responses.User import awais.instagrabber.repositories.responses.User
import awais.instagrabber.repositories.responses.directmessages.DirectThread import awais.instagrabber.repositories.responses.directmessages.DirectThread
import awais.instagrabber.repositories.responses.directmessages.DirectThreadBroadcastResponse
import awais.instagrabber.repositories.responses.directmessages.RankedRecipient import awais.instagrabber.repositories.responses.directmessages.RankedRecipient
import awais.instagrabber.utils.Constants import awais.instagrabber.utils.Constants
import awais.instagrabber.utils.Utils import awais.instagrabber.utils.Utils
@ -21,6 +20,8 @@ import awais.instagrabber.utils.getUserIdFromCookie
import awais.instagrabber.webservices.DirectMessagesService import awais.instagrabber.webservices.DirectMessagesService
import awais.instagrabber.webservices.DirectMessagesService.Companion.getInstance import awais.instagrabber.webservices.DirectMessagesService.Companion.getInstance
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import retrofit2.Call import retrofit2.Call
import retrofit2.Callback import retrofit2.Callback
import retrofit2.Response import retrofit2.Response
@ -137,7 +138,7 @@ object DirectMessagesManager {
// create thread and forward // create thread and forward
createThread(recipient.user.pk) { (threadId) -> createThread(recipient.user.pk) { (threadId) ->
val threadIdTemp = threadId ?: return@createThread val threadIdTemp = threadId ?: return@createThread
sendMedia(threadIdTemp, mediaId) { sendMedia(threadIdTemp, mediaId, scope) {
if (refreshInbox) { if (refreshInbox) {
inboxManager.refresh(scope) inboxManager.refresh(scope)
} }
@ -149,7 +150,7 @@ object DirectMessagesManager {
// just forward // just forward
val thread = recipient.thread val thread = recipient.thread
val threadId = thread.threadId ?: return val threadId = thread.threadId ?: return
sendMedia(threadId, mediaId) { sendMedia(threadId, mediaId, scope) {
if (refreshInbox) { if (refreshInbox) {
inboxManager.refresh(scope) inboxManager.refresh(scope)
} }
@ -160,55 +161,26 @@ object DirectMessagesManager {
private fun sendMedia( private fun sendMedia(
threadId: String, threadId: String,
mediaId: String, mediaId: String,
scope: CoroutineScope,
callback: (() -> Unit)?, callback: (() -> Unit)?,
): LiveData<Resource<Any?>> { ): LiveData<Resource<Any?>> {
val data = MutableLiveData<Resource<Any?>>() val data = MutableLiveData<Resource<Any?>>()
data.postValue(loading(null)) data.postValue(loading(null))
val request = service.broadcastMediaShare( scope.launch(Dispatchers.IO) {
UUID.randomUUID().toString(), try {
of(threadId), service.broadcastMediaShare(
mediaId UUID.randomUUID().toString(),
) of(threadId),
request.enqueue(object : Callback<DirectThreadBroadcastResponse?> { mediaId
override fun onResponse( )
call: Call<DirectThreadBroadcastResponse?>, data.postValue(success(Any()))
response: Response<DirectThreadBroadcastResponse?>, callback?.invoke()
) { } catch (e: Exception) {
if (response.isSuccessful) { Log.e(TAG, "sendMedia: ", e)
data.postValue(success(Any())) data.postValue(error(e.message, null))
callback?.invoke()
return
}
val errorBody = response.errorBody()
if (errorBody != null) {
try {
val string = errorBody.string()
val msg = String.format(Locale.US,
"onResponse: url: %s, responseCode: %d, errorBody: %s",
call.request().url().toString(),
response.code(),
string)
Log.e(TAG, msg)
data.postValue(error(msg, null))
} catch (e: IOException) {
Log.e(TAG, "onResponse: ", e)
data.postValue(error(e.message, null))
}
callback?.invoke()
return
}
val msg = "onResponse: request was not successful and response error body was null"
Log.e(TAG, msg)
data.postValue(error(msg, null))
callback?.invoke() callback?.invoke()
} }
}
override fun onFailure(call: Call<DirectThreadBroadcastResponse?>, t: Throwable) {
Log.e(TAG, "onFailure: ", t)
data.postValue(error(t.message, null))
callback?.invoke()
}
})
return data return data
} }

View File

@ -21,8 +21,6 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import retrofit2.Call import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
import java.util.* import java.util.*
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
@ -66,49 +64,23 @@ class InboxManager private constructor(private val pending: Boolean) {
inbox.postValue(error(e.message, currentDirectInbox)) inbox.postValue(error(e.message, currentDirectInbox))
hasOlder = false hasOlder = false
} }
// inboxRequest?.enqueue(object : Callback<DirectInboxResponse?> {
// override fun onResponse(call: Call<DirectInboxResponse?>, response: Response<DirectInboxResponse?>) {
// val body = response.body()
// if (body == null) {
// Log.e(TAG, "parseInboxResponse: Response is null")
// inbox.postValue(error(R.string.generic_null_response, currentDirectInbox))
// hasOlder = false
// return
// }
//
// }
//
// override fun onFailure(call: Call<DirectInboxResponse?>, t: Throwable) {
// Log.e(TAG, "Failed fetching dm inbox", t)
// inbox.postValue(error(t.message, currentDirectInbox))
// hasOlder = false
// }
// })
} }
} }
fun fetchUnseenCount() { fun fetchUnseenCount(scope: CoroutineScope) {
val unseenCountResource = unseenCount.value val unseenCountResource = unseenCount.value
if (unseenCountResource != null && unseenCountResource.status === Resource.Status.LOADING) return if (unseenCountResource != null && unseenCountResource.status === Resource.Status.LOADING) return
stopCurrentUnseenCountRequest() stopCurrentUnseenCountRequest()
unseenCount.postValue(loading(currentUnseenCount)) unseenCount.postValue(loading(currentUnseenCount))
unseenCountRequest = service.fetchUnseenCount() scope.launch(Dispatchers.IO) {
unseenCountRequest?.enqueue(object : Callback<DirectBadgeCount?> { try {
override fun onResponse(call: Call<DirectBadgeCount?>, response: Response<DirectBadgeCount?>) { val directBadgeCount = service.fetchUnseenCount()
val directBadgeCount = response.body()
if (directBadgeCount == null) {
Log.e(TAG, "onResponse: directBadgeCount Response is null")
unseenCount.postValue(error(R.string.dms_inbox_error_null_count, currentUnseenCount))
return
}
unseenCount.postValue(success(directBadgeCount.badgeCount)) unseenCount.postValue(success(directBadgeCount.badgeCount))
} catch (e: Exception) {
Log.e(TAG, "Failed fetching unseen count", e)
unseenCount.postValue(error(e.message, currentUnseenCount))
} }
}
override fun onFailure(call: Call<DirectBadgeCount?>, t: Throwable) {
Log.e(TAG, "Failed fetching unseen count", t)
unseenCount.postValue(error(t.message, currentUnseenCount))
}
})
} }
fun refresh(scope: CoroutineScope) { fun refresh(scope: CoroutineScope) {
@ -117,7 +89,7 @@ class InboxManager private constructor(private val pending: Boolean) {
hasOlder = true hasOlder = true
fetchInbox(scope) fetchInbox(scope)
if (!pending) { if (!pending) {
fetchUnseenCount() fetchUnseenCount(scope)
} }
} }
@ -350,9 +322,5 @@ class InboxManager private constructor(private val pending: Boolean) {
val threads = inbox?.threads ?: emptyList() val threads = inbox?.threads ?: emptyList()
ImmutableList.sortedCopyOf(THREAD_COMPARATOR, threads) ImmutableList.sortedCopyOf(THREAD_COMPARATOR, threads)
}) })
// fetchInbox()
if (!pending) {
fetchUnseenCount()
}
} }
} }

View File

@ -10,11 +10,11 @@ import androidx.lifecycle.Transformations.distinctUntilChanged
import androidx.lifecycle.Transformations.map import androidx.lifecycle.Transformations.map
import awais.instagrabber.R import awais.instagrabber.R
import awais.instagrabber.customviews.emoji.Emoji import awais.instagrabber.customviews.emoji.Emoji
import awais.instagrabber.models.enums.DirectItemType.Companion.getName
import awais.instagrabber.models.Resource import awais.instagrabber.models.Resource
import awais.instagrabber.models.Resource.Companion.error import awais.instagrabber.models.Resource.Companion.error
import awais.instagrabber.models.Resource.Companion.loading import awais.instagrabber.models.Resource.Companion.loading
import awais.instagrabber.models.Resource.Companion.success import awais.instagrabber.models.Resource.Companion.success
import awais.instagrabber.models.enums.DirectItemType
import awais.instagrabber.repositories.requests.UploadFinishOptions import awais.instagrabber.repositories.requests.UploadFinishOptions
import awais.instagrabber.repositories.requests.VideoOptions import awais.instagrabber.repositories.requests.VideoOptions
import awais.instagrabber.repositories.requests.directmessages.ThreadIdOrUserIds import awais.instagrabber.repositories.requests.directmessages.ThreadIdOrUserIds
@ -400,7 +400,7 @@ class ThreadManager private constructor(
return temp return temp
} }
fun sendText(text: String): LiveData<Resource<Any?>> { fun sendText(text: String, scope: CoroutineScope): LiveData<Resource<Any?>> {
val data = MutableLiveData<Resource<Any?>>() val data = MutableLiveData<Resource<Any?>>()
val userId = getCurrentUserId(data) ?: return data val userId = getCurrentUserId(data) ?: return data
val clientContext = UUID.randomUUID().toString() val clientContext = UUID.randomUUID().toString()
@ -412,29 +412,36 @@ class ThreadManager private constructor(
data.postValue(loading(directItem)) data.postValue(loading(directItem))
val repliedToItemId = replyToItemValue?.itemId val repliedToItemId = replyToItemValue?.itemId
val repliedToClientContext = replyToItemValue?.clientContext val repliedToClientContext = replyToItemValue?.clientContext
val request = service.broadcastText( scope.launch(Dispatchers.IO) {
clientContext, try {
threadIdOrUserIds, val response = service.broadcastText(
text, clientContext,
repliedToItemId, threadIdOrUserIds,
repliedToClientContext text,
) repliedToItemId,
enqueueRequest(request, data, directItem) repliedToClientContext
)
parseResponse(response, data, directItem)
} catch (e: Exception) {
data.postValue(error(e.message, directItem))
Log.e(TAG, "sendText: ", e)
}
}
return data return data
} }
fun sendUri(entry: MediaController.MediaEntry): LiveData<Resource<Any?>> { fun sendUri(entry: MediaController.MediaEntry, scope: CoroutineScope): LiveData<Resource<Any?>> {
val data = MutableLiveData<Resource<Any?>>() val data = MutableLiveData<Resource<Any?>>()
val uri = Uri.fromFile(File(entry.path)) val uri = Uri.fromFile(File(entry.path))
if (!entry.isVideo) { if (!entry.isVideo) {
sendPhoto(data, uri, entry.width, entry.height) sendPhoto(data, uri, entry.width, entry.height, scope)
return data return data
} }
sendVideo(data, uri, entry.size, entry.duration, entry.width, entry.height) sendVideo(data, uri, entry.size, entry.duration, entry.width, entry.height, scope)
return data return data
} }
fun sendUri(uri: Uri): LiveData<Resource<Any?>> { fun sendUri(uri: Uri, scope: CoroutineScope): LiveData<Resource<Any?>> {
val data = MutableLiveData<Resource<Any?>>() val data = MutableLiveData<Resource<Any?>>()
val mimeType = Utils.getMimeType(uri, contentResolver) val mimeType = Utils.getMimeType(uri, contentResolver)
if (isEmpty(mimeType)) { if (isEmpty(mimeType)) {
@ -443,16 +450,16 @@ class ThreadManager private constructor(
} }
val isPhoto = mimeType != null && mimeType.startsWith("image") val isPhoto = mimeType != null && mimeType.startsWith("image")
if (isPhoto) { if (isPhoto) {
sendPhoto(data, uri) sendPhoto(data, uri, scope)
return data return data
} }
if (mimeType != null && mimeType.startsWith("video")) { if (mimeType != null && mimeType.startsWith("video")) {
sendVideo(data, uri) sendVideo(data, uri, scope)
} }
return data return data
} }
fun sendAnimatedMedia(giphyGif: GiphyGif): LiveData<Resource<Any?>> { fun sendAnimatedMedia(giphyGif: GiphyGif, scope: CoroutineScope): LiveData<Resource<Any?>> {
val data = MutableLiveData<Resource<Any?>>() val data = MutableLiveData<Resource<Any?>>()
val userId = getCurrentUserId(data) ?: return data val userId = getCurrentUserId(data) ?: return data
val clientContext = UUID.randomUUID().toString() val clientContext = UUID.randomUUID().toString()
@ -460,12 +467,19 @@ class ThreadManager private constructor(
directItem.isPending = true directItem.isPending = true
addItems(0, listOf(directItem)) addItems(0, listOf(directItem))
data.postValue(loading(directItem)) data.postValue(loading(directItem))
val request = service.broadcastAnimatedMedia( scope.launch(Dispatchers.IO) {
clientContext, try {
threadIdOrUserIds, val request = service.broadcastAnimatedMedia(
giphyGif clientContext,
) threadIdOrUserIds,
enqueueRequest(request, data, directItem) giphyGif
)
parseResponse(request, data, directItem)
} catch (e: Exception) {
data.postValue(error(e.message, directItem))
Log.e(TAG, "sendAnimatedMedia: ", e)
}
}
return data return data
} }
@ -476,6 +490,7 @@ class ThreadManager private constructor(
samplingFreq: Int, samplingFreq: Int,
duration: Long, duration: Long,
byteLength: Long, byteLength: Long,
scope: CoroutineScope,
) { ) {
if (duration > 60000) { if (duration > 60000) {
// instagram does not allow uploading audio longer than 60 secs for Direct messages // instagram does not allow uploading audio longer than 60 secs for Direct messages
@ -502,14 +517,21 @@ class ThreadManager private constructor(
uploadFinishRequest.enqueue(object : Callback<String?> { uploadFinishRequest.enqueue(object : Callback<String?> {
override fun onResponse(call: Call<String?>, response: Response<String?>) { override fun onResponse(call: Call<String?>, response: Response<String?>) {
if (response.isSuccessful) { if (response.isSuccessful) {
val request = service.broadcastVoice( scope.launch(Dispatchers.IO) {
clientContext, try {
threadIdOrUserIds, val request = service.broadcastVoice(
uploadDmVoiceOptions.uploadId, clientContext,
waveform, threadIdOrUserIds,
samplingFreq uploadDmVoiceOptions.uploadId,
) waveform,
enqueueRequest(request, data, directItem) samplingFreq
)
parseResponse(request, data, directItem)
} catch (e: Exception) {
data.postValue(error(e.message, directItem))
Log.e(TAG, "sendVoice: ", e)
}
}
return return
} }
if (response.errorBody() != null) { if (response.errorBody() != null) {
@ -537,6 +559,7 @@ class ThreadManager private constructor(
fun sendReaction( fun sendReaction(
item: DirectItem, item: DirectItem,
emoji: Emoji, emoji: Emoji,
scope: CoroutineScope,
): LiveData<Resource<Any?>> { ): LiveData<Resource<Any?>> {
val data = MutableLiveData<Resource<Any?>>() val data = MutableLiveData<Resource<Any?>>()
val userId = getCurrentUserId(data) val userId = getCurrentUserId(data)
@ -557,18 +580,24 @@ class ThreadManager private constructor(
data.postValue(error("itemId is null", null)) data.postValue(error("itemId is null", null))
return data return data
} }
val request = service.broadcastReaction( scope.launch(Dispatchers.IO) {
clientContext, try {
threadIdOrUserIds, service.broadcastReaction(
itemId, clientContext,
emojiUnicode, threadIdOrUserIds,
false itemId,
) emojiUnicode,
handleBroadcastReactionRequest(data, item, request) false
)
} catch (e: Exception) {
data.postValue(error(e.message, null))
Log.e(TAG, "sendReaction: ", e)
}
}
return data return data
} }
fun sendDeleteReaction(itemId: String): LiveData<Resource<Any?>> { fun sendDeleteReaction(itemId: String, scope: CoroutineScope): LiveData<Resource<Any?>> {
val data = MutableLiveData<Resource<Any?>>() val data = MutableLiveData<Resource<Any?>>()
val item = getItem(itemId) val item = getItem(itemId)
if (item == null) { if (item == null) {
@ -588,8 +617,14 @@ class ThreadManager private constructor(
data.postValue(error("itemId is null", null)) data.postValue(error("itemId is null", null))
return data return data
} }
val request = service.broadcastReaction(clientContext, threadIdOrUserIds, itemId1, null, true) scope.launch(Dispatchers.IO) {
handleBroadcastReactionRequest(data, item, request) try {
service.broadcastReaction(clientContext, threadIdOrUserIds, itemId1, null, true)
} catch (e: Exception) {
data.postValue(error(e.message, null))
Log.e(TAG, "sendDeleteReaction: ", e)
}
}
return data return data
} }
@ -660,7 +695,7 @@ class ThreadManager private constructor(
data.postValue(error("item type is null", null)) data.postValue(error("item type is null", null))
return data return data
} }
val itemTypeName = getName(itemType) val itemTypeName = DirectItemType.getName(itemType)
if (itemTypeName == null) { if (itemTypeName == null) {
Log.e(TAG, "forward: itemTypeName was null!") Log.e(TAG, "forward: itemTypeName was null!")
data.postValue(error("itemTypeName is null", null)) data.postValue(error("itemTypeName is null", null))
@ -798,6 +833,7 @@ class ThreadManager private constructor(
private fun sendPhoto( private fun sendPhoto(
data: MutableLiveData<Resource<Any?>>, data: MutableLiveData<Resource<Any?>>,
uri: Uri, uri: Uri,
scope: CoroutineScope,
) { ) {
try { try {
val dimensions = BitmapUtils.decodeDimensions(contentResolver, uri) val dimensions = BitmapUtils.decodeDimensions(contentResolver, uri)
@ -805,7 +841,7 @@ class ThreadManager private constructor(
data.postValue(error("Decoding dimensions failed", null)) data.postValue(error("Decoding dimensions failed", null))
return return
} }
sendPhoto(data, uri, dimensions.first, dimensions.second) sendPhoto(data, uri, dimensions.first, dimensions.second, scope)
} catch (e: IOException) { } catch (e: IOException) {
data.postValue(error(e.message, null)) data.postValue(error(e.message, null))
Log.e(TAG, "sendPhoto: ", e) Log.e(TAG, "sendPhoto: ", e)
@ -817,6 +853,7 @@ class ThreadManager private constructor(
uri: Uri, uri: Uri,
width: Int, width: Int,
height: Int, height: Int,
scope: CoroutineScope,
) { ) {
val userId = getCurrentUserId(data) ?: return val userId = getCurrentUserId(data) ?: return
val clientContext = UUID.randomUUID().toString() val clientContext = UUID.randomUUID().toString()
@ -829,8 +866,15 @@ class ThreadManager private constructor(
if (handleInvalidResponse(data, response)) return if (handleInvalidResponse(data, response)) return
val response1 = response.response ?: return val response1 = response.response ?: return
val uploadId = response1.optString("upload_id") val uploadId = response1.optString("upload_id")
val request = service.broadcastPhoto(clientContext, threadIdOrUserIds, uploadId) scope.launch(Dispatchers.IO) {
enqueueRequest(request, data, directItem) try {
val response2 = service.broadcastPhoto(clientContext, threadIdOrUserIds, uploadId)
parseResponse(response2, data, directItem)
} catch (e: Exception) {
data.postValue(error(e.message, null))
Log.e(TAG, "sendPhoto: ", e)
}
}
} }
override fun onFailure(t: Throwable) { override fun onFailure(t: Throwable) {
@ -843,6 +887,7 @@ class ThreadManager private constructor(
private fun sendVideo( private fun sendVideo(
data: MutableLiveData<Resource<Any?>>, data: MutableLiveData<Resource<Any?>>,
uri: Uri, uri: Uri,
scope: CoroutineScope,
) { ) {
MediaUtils.getVideoInfo(contentResolver, uri, object : OnInfoLoadListener<VideoInfo?> { MediaUtils.getVideoInfo(contentResolver, uri, object : OnInfoLoadListener<VideoInfo?> {
override fun onLoad(info: VideoInfo?) { override fun onLoad(info: VideoInfo?) {
@ -850,7 +895,7 @@ class ThreadManager private constructor(
data.postValue(error("Could not get the video info", null)) data.postValue(error("Could not get the video info", null))
return return
} }
sendVideo(data, uri, info.size, info.duration, info.width, info.height) sendVideo(data, uri, info.size, info.duration, info.width, info.height, scope)
} }
override fun onFailure(t: Throwable) { override fun onFailure(t: Throwable) {
@ -866,6 +911,7 @@ class ThreadManager private constructor(
duration: Long, duration: Long,
width: Int, width: Int,
height: Int, height: Int,
scope: CoroutineScope,
) { ) {
if (duration > 60000) { if (duration > 60000) {
// instagram does not allow uploading videos longer than 60 secs for Direct messages // instagram does not allow uploading videos longer than 60 secs for Direct messages
@ -892,14 +938,21 @@ class ThreadManager private constructor(
uploadFinishRequest.enqueue(object : Callback<String?> { uploadFinishRequest.enqueue(object : Callback<String?> {
override fun onResponse(call: Call<String?>, response: Response<String?>) { override fun onResponse(call: Call<String?>, response: Response<String?>) {
if (response.isSuccessful) { if (response.isSuccessful) {
val request = service.broadcastVideo( scope.launch(Dispatchers.IO) {
clientContext, try {
threadIdOrUserIds, val response1 = service.broadcastVideo(
uploadDmVideoOptions.uploadId, clientContext,
"", threadIdOrUserIds,
true uploadDmVideoOptions.uploadId,
) "",
enqueueRequest(request, data, directItem) true
)
parseResponse(response1, data, directItem)
} catch (e: Exception) {
data.postValue(error(e.message, null))
Log.e(TAG, "sendVideo: ", e)
}
}
return return
} }
if (response.errorBody() != null) { if (response.errorBody() != null) {
@ -924,64 +977,36 @@ class ThreadManager private constructor(
}) })
} }
private fun enqueueRequest( private fun parseResponse(
request: Call<DirectThreadBroadcastResponse?>, response: DirectThreadBroadcastResponse,
data: MutableLiveData<Resource<Any?>>, data: MutableLiveData<Resource<Any?>>,
directItem: DirectItem, directItem: DirectItem,
) { ) {
request.enqueue(object : Callback<DirectThreadBroadcastResponse?> { val payloadClientContext: String?
override fun onResponse( val timestamp: Long
call: Call<DirectThreadBroadcastResponse?>, val itemId: String?
response: Response<DirectThreadBroadcastResponse?>, val payload = response.payload
) { if (payload == null) {
if (response.isSuccessful) { val messageMetadata = response.messageMetadata
val broadcastResponse = response.body() if (messageMetadata == null || messageMetadata.isEmpty()) {
if (broadcastResponse == null) { data.postValue(success(directItem))
data.postValue(error(R.string.generic_null_response, directItem)) return
Log.e(TAG, "enqueueRequest: onResponse: response body is null")
return
}
val payloadClientContext: String?
val timestamp: Long
val itemId: String?
val payload = broadcastResponse.payload
if (payload == null) {
val messageMetadata = broadcastResponse.messageMetadata
if (messageMetadata == null || messageMetadata.isEmpty()) {
data.postValue(success(directItem))
return
}
val (clientContext, itemId1, timestamp1) = messageMetadata[0]
payloadClientContext = clientContext
itemId = itemId1
timestamp = timestamp1
} else {
payloadClientContext = payload.clientContext
timestamp = payload.timestamp
itemId = payload.itemId
}
if (payloadClientContext == null) {
data.postValue(error("clientContext in response was null", null))
return
}
updateItemSent(payloadClientContext, timestamp, itemId)
data.postValue(success(directItem))
return
}
if (response.errorBody() != null) {
handleErrorBody(call, response, data)
}
data.postValue(error(R.string.generic_failed_request, directItem))
} }
val (clientContext, itemId1, timestamp1) = messageMetadata[0]
override fun onFailure( payloadClientContext = clientContext
call: Call<DirectThreadBroadcastResponse?>, itemId = itemId1
t: Throwable, timestamp = timestamp1
) { } else {
data.postValue(error(t.message, directItem)) payloadClientContext = payload.clientContext
Log.e(TAG, "enqueueRequest: onFailure: ", t) timestamp = payload.timestamp
} itemId = payload.itemId
}) }
if (payloadClientContext == null) {
data.postValue(error("clientContext in response was null", null))
return
}
updateItemSent(payloadClientContext, timestamp, itemId)
data.postValue(success(directItem))
} }
private fun updateItemSent( private fun updateItemSent(
@ -1054,38 +1079,6 @@ class ThreadManager private constructor(
.firstOrNull() .firstOrNull()
} }
private fun handleBroadcastReactionRequest(
data: MutableLiveData<Resource<Any?>>,
item: DirectItem,
request: Call<DirectThreadBroadcastResponse?>,
) {
request.enqueue(object : Callback<DirectThreadBroadcastResponse?> {
override fun onResponse(
call: Call<DirectThreadBroadcastResponse?>,
response: Response<DirectThreadBroadcastResponse?>,
) {
if (!response.isSuccessful) {
if (response.errorBody() != null) {
handleErrorBody(call, response, data)
return
}
data.postValue(error(R.string.generic_failed_request, item))
return
}
val body = response.body()
if (body == null) {
data.postValue(error(R.string.generic_null_response, item))
}
// otherwise nothing to do? maybe update the timestamp in the emoji?
}
override fun onFailure(call: Call<DirectThreadBroadcastResponse?>, t: Throwable) {
data.postValue(error(t.message, item))
Log.e(TAG, "enqueueRequest: onFailure: ", t)
}
})
}
private fun stopCurrentRequest() { private fun stopCurrentRequest() {
chatsRequest?.let { chatsRequest?.let {
if (it.isExecuted || it.isCanceled) return if (it.isExecuted || it.isCanceled) return
@ -1583,7 +1576,7 @@ class ThreadManager private constructor(
}) })
} }
fun markAsSeen(directItem: DirectItem): LiveData<Resource<Any?>> { fun markAsSeen(directItem: DirectItem, scope: CoroutineScope): LiveData<Resource<Any?>> {
val data = MutableLiveData<Resource<Any?>>() val data = MutableLiveData<Resource<Any?>>()
data.postValue(loading(null)) data.postValue(loading(null))
val request = service.markAsSeen(threadId, directItem) val request = service.markAsSeen(threadId, directItem)
@ -1606,7 +1599,7 @@ class ThreadManager private constructor(
data.postValue(error(R.string.generic_null_response, null)) data.postValue(error(R.string.generic_null_response, null))
return return
} }
inboxManager.fetchUnseenCount() inboxManager.fetchUnseenCount(scope)
val payload = seenResponse.payload ?: return val payload = seenResponse.payload ?: return
val timestamp = payload.timestamp val timestamp = payload.timestamp
val thread = thread.value ?: return val thread = thread.value ?: return

View File

@ -1,46 +1,62 @@
package awais.instagrabber.models.enums package awais.instagrabber.models.enums
import com.google.gson.annotations.SerializedName import com.google.gson.annotations.SerializedName
import java.io.Serializable import java.io.Serializable
import java.util.*
enum class DirectItemType(val id: Int) : Serializable { enum class DirectItemType(val id: Int) : Serializable {
UNKNOWN(0), UNKNOWN(0),
@SerializedName("text") @SerializedName("text")
TEXT(1), TEXT(1),
@SerializedName("like") @SerializedName("like")
LIKE(2), LIKE(2),
@SerializedName("link") @SerializedName("link")
LINK(3), LINK(3),
@SerializedName("media") @SerializedName("media")
MEDIA(4), MEDIA(4),
@SerializedName("raven_media") @SerializedName("raven_media")
RAVEN_MEDIA(5), RAVEN_MEDIA(5),
@SerializedName("profile") @SerializedName("profile")
PROFILE(6), PROFILE(6),
@SerializedName("video_call_event") @SerializedName("video_call_event")
VIDEO_CALL_EVENT(7), VIDEO_CALL_EVENT(7),
@SerializedName("animated_media") @SerializedName("animated_media")
ANIMATED_MEDIA(8), ANIMATED_MEDIA(8),
@SerializedName("voice_media") @SerializedName("voice_media")
VOICE_MEDIA(9), VOICE_MEDIA(9),
@SerializedName("media_share") @SerializedName("media_share")
MEDIA_SHARE(10), MEDIA_SHARE(10),
@SerializedName("reel_share") @SerializedName("reel_share")
REEL_SHARE(11), REEL_SHARE(11),
@SerializedName("action_log") @SerializedName("action_log")
ACTION_LOG(12), ACTION_LOG(12),
@SerializedName("placeholder") @SerializedName("placeholder")
PLACEHOLDER(13), PLACEHOLDER(13),
@SerializedName("story_share") @SerializedName("story_share")
STORY_SHARE(14), STORY_SHARE(14),
@SerializedName("clip") @SerializedName("clip")
CLIP(15), // media_share but reel CLIP(15), // media_share but reel
@SerializedName("felix_share") @SerializedName("felix_share")
FELIX_SHARE(16), // media_share but igtv FELIX_SHARE(16), // media_share but igtv
@SerializedName("location") @SerializedName("location")
LOCATION(17), LOCATION(17),
@SerializedName("xma") @SerializedName("xma")
XMA(18); // self avatar stickers XMA(18); // self avatar stickers
@ -52,7 +68,6 @@ enum class DirectItemType(val id: Int) : Serializable {
return map[id] return map[id]
} }
@JvmStatic
fun getName(directItemType: DirectItemType): String? { fun getName(directItemType: DirectItemType): String? {
when (directItemType) { when (directItemType) {
TEXT -> return "text" TEXT -> return "text"
@ -72,12 +87,12 @@ enum class DirectItemType(val id: Int) : Serializable {
CLIP -> return "clip" CLIP -> return "clip"
FELIX_SHARE -> return "felix_share" FELIX_SHARE -> return "felix_share"
LOCATION -> return "location" LOCATION -> return "location"
else -> return null
} }
return null
} }
init { init {
for (type in DirectItemType.values()) { for (type in values()) {
map[type.id] = type map[type.id] = type
} }
} }

View File

@ -18,14 +18,14 @@ interface DirectMessagesRepository {
): DirectThreadFeedResponse ): DirectThreadFeedResponse
@GET("/api/v1/direct_v2/get_badge_count/?no_raven=1") @GET("/api/v1/direct_v2/get_badge_count/?no_raven=1")
fun fetchUnseenCount(): Call<DirectBadgeCount?> suspend fun fetchUnseenCount(): DirectBadgeCount
@FormUrlEncoded @FormUrlEncoded
@POST("/api/v1/direct_v2/threads/broadcast/{item}/") @POST("/api/v1/direct_v2/threads/broadcast/{item}/")
fun broadcast( suspend fun broadcast(
@Path("item") item: String, @Path("item") item: String,
@FieldMap signedForm: Map<String, String>, @FieldMap signedForm: Map<String, String>,
): Call<DirectThreadBroadcastResponse?> ): DirectThreadBroadcastResponse
@FormUrlEncoded @FormUrlEncoded
@POST("/api/v1/direct_v2/threads/{threadId}/add_user/") @POST("/api/v1/direct_v2/threads/{threadId}/add_user/")

View File

@ -0,0 +1,19 @@
package awais.instagrabber.utils
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers
import java.util.function.BiConsumer
import kotlin.coroutines.Continuation
import kotlin.coroutines.CoroutineContext
@JvmOverloads
fun <R> getContinuation(onFinished: BiConsumer<R?, Throwable?>, dispatcher: CoroutineDispatcher = Dispatchers.Default): Continuation<R> {
return object : Continuation<R> {
override val context: CoroutineContext
get() = dispatcher
override fun resumeWith(result: Result<R>) {
onFinished.accept(result.getOrNull(), result.exceptionOrNull())
}
}
}

View File

@ -32,5 +32,6 @@ class DirectInboxViewModel : ViewModel() {
init { init {
inboxManager.fetchInbox(viewModelScope) inboxManager.fetchInbox(viewModelScope)
inboxManager.fetchUnseenCount(viewModelScope)
} }
} }

View File

@ -74,15 +74,15 @@ class DirectThreadViewModel(
} }
fun sendText(text: String): LiveData<Resource<Any?>> { fun sendText(text: String): LiveData<Resource<Any?>> {
return threadManager.sendText(text) return threadManager.sendText(text, viewModelScope)
} }
fun sendUri(entry: MediaController.MediaEntry): LiveData<Resource<Any?>> { fun sendUri(entry: MediaController.MediaEntry): LiveData<Resource<Any?>> {
return threadManager.sendUri(entry) return threadManager.sendUri(entry, viewModelScope)
} }
fun sendUri(uri: Uri): LiveData<Resource<Any?>> { fun sendUri(uri: Uri): LiveData<Resource<Any?>> {
return threadManager.sendUri(uri) return threadManager.sendUri(uri, viewModelScope)
} }
fun startRecording(): LiveData<Resource<Any?>> { fun startRecording(): LiveData<Resource<Any?>> {
@ -106,12 +106,15 @@ class DirectThreadViewModel(
MediaUtils.getVoiceInfo(contentResolver, uri, object : OnInfoLoadListener<VideoInfo?> { MediaUtils.getVoiceInfo(contentResolver, uri, object : OnInfoLoadListener<VideoInfo?> {
override fun onLoad(videoInfo: VideoInfo?) { override fun onLoad(videoInfo: VideoInfo?) {
if (videoInfo == null) return if (videoInfo == null) return
threadManager.sendVoice(data, threadManager.sendVoice(
data,
uri, uri,
result.waveform, result.waveform,
result.samplingFreq, result.samplingFreq,
videoInfo.duration, videoInfo.duration,
videoInfo.size) videoInfo.size,
viewModelScope,
)
} }
override fun onFailure(t: Throwable) { override fun onFailure(t: Throwable) {
@ -133,11 +136,11 @@ class DirectThreadViewModel(
} }
fun sendReaction(item: DirectItem, emoji: Emoji): LiveData<Resource<Any?>> { fun sendReaction(item: DirectItem, emoji: Emoji): LiveData<Resource<Any?>> {
return threadManager.sendReaction(item, emoji) return threadManager.sendReaction(item, emoji, viewModelScope)
} }
fun sendDeleteReaction(itemId: String): LiveData<Resource<Any?>> { fun sendDeleteReaction(itemId: String): LiveData<Resource<Any?>> {
return threadManager.sendDeleteReaction(itemId) return threadManager.sendDeleteReaction(itemId, viewModelScope)
} }
fun unsend(item: DirectItem): LiveData<Resource<Any?>> { fun unsend(item: DirectItem): LiveData<Resource<Any?>> {
@ -145,7 +148,7 @@ class DirectThreadViewModel(
} }
fun sendAnimatedMedia(giphyGif: GiphyGif): LiveData<Resource<Any?>> { fun sendAnimatedMedia(giphyGif: GiphyGif): LiveData<Resource<Any?>> {
return threadManager.sendAnimatedMedia(giphyGif) return threadManager.sendAnimatedMedia(giphyGif, viewModelScope)
} }
fun getUser(userId: Long): User? { fun getUser(userId: Long): User? {
@ -197,7 +200,7 @@ class DirectThreadViewModel(
return successEventResObjectLiveData return successEventResObjectLiveData
} }
} }
return threadManager.markAsSeen(directItem) return threadManager.markAsSeen(directItem, viewModelScope)
} }
private val successEventResObjectLiveData: MutableLiveData<Resource<Any?>> private val successEventResObjectLiveData: MutableLiveData<Resource<Any?>>

View File

@ -53,17 +53,15 @@ class DirectMessagesService private constructor(
return repository.fetchThread(threadId, queryMap) return repository.fetchThread(threadId, queryMap)
} }
fun fetchUnseenCount(): Call<DirectBadgeCount?> { suspend fun fetchUnseenCount(): DirectBadgeCount = repository.fetchUnseenCount()
return repository.fetchUnseenCount()
}
fun broadcastText( suspend fun broadcastText(
clientContext: String, clientContext: String,
threadIdOrUserIds: ThreadIdOrUserIds, threadIdOrUserIds: ThreadIdOrUserIds,
text: String, text: String,
repliedToItemId: String?, repliedToItemId: String?,
repliedToClientContext: String?, repliedToClientContext: String?,
): Call<DirectThreadBroadcastResponse?> { ): DirectThreadBroadcastResponse {
val urls = extractUrls(text) val urls = extractUrls(text)
if (urls.isNotEmpty()) { if (urls.isNotEmpty()) {
return broadcastLink(clientContext, threadIdOrUserIds, text, urls, repliedToItemId, repliedToClientContext) return broadcastLink(clientContext, threadIdOrUserIds, text, urls, repliedToItemId, repliedToClientContext)
@ -76,14 +74,14 @@ class DirectMessagesService private constructor(
return broadcast(broadcastOptions) return broadcast(broadcastOptions)
} }
private fun broadcastLink( private suspend fun broadcastLink(
clientContext: String, clientContext: String,
threadIdOrUserIds: ThreadIdOrUserIds, threadIdOrUserIds: ThreadIdOrUserIds,
linkText: String, linkText: String,
urls: List<String>, urls: List<String>,
repliedToItemId: String?, repliedToItemId: String?,
repliedToClientContext: String?, repliedToClientContext: String?,
): Call<DirectThreadBroadcastResponse?> { ): DirectThreadBroadcastResponse {
val broadcastOptions = LinkBroadcastOptions(clientContext, threadIdOrUserIds, linkText, urls) val broadcastOptions = LinkBroadcastOptions(clientContext, threadIdOrUserIds, linkText, urls)
if (!repliedToItemId.isNullOrBlank() && !repliedToClientContext.isNullOrBlank()) { if (!repliedToItemId.isNullOrBlank() && !repliedToClientContext.isNullOrBlank()) {
broadcastOptions.repliedToItemId = repliedToItemId broadcastOptions.repliedToItemId = repliedToItemId
@ -92,70 +90,70 @@ class DirectMessagesService private constructor(
return broadcast(broadcastOptions) return broadcast(broadcastOptions)
} }
fun broadcastPhoto( suspend fun broadcastPhoto(
clientContext: String, clientContext: String,
threadIdOrUserIds: ThreadIdOrUserIds, threadIdOrUserIds: ThreadIdOrUserIds,
uploadId: String, uploadId: String,
): Call<DirectThreadBroadcastResponse?> { ): DirectThreadBroadcastResponse {
return broadcast(PhotoBroadcastOptions(clientContext, threadIdOrUserIds, true, uploadId)) return broadcast(PhotoBroadcastOptions(clientContext, threadIdOrUserIds, true, uploadId))
} }
fun broadcastVideo( suspend fun broadcastVideo(
clientContext: String, clientContext: String,
threadIdOrUserIds: ThreadIdOrUserIds, threadIdOrUserIds: ThreadIdOrUserIds,
uploadId: String, uploadId: String,
videoResult: String, videoResult: String,
sampled: Boolean, sampled: Boolean,
): Call<DirectThreadBroadcastResponse?> { ): DirectThreadBroadcastResponse {
return broadcast(VideoBroadcastOptions(clientContext, threadIdOrUserIds, videoResult, uploadId, sampled)) return broadcast(VideoBroadcastOptions(clientContext, threadIdOrUserIds, videoResult, uploadId, sampled))
} }
fun broadcastVoice( suspend fun broadcastVoice(
clientContext: String, clientContext: String,
threadIdOrUserIds: ThreadIdOrUserIds, threadIdOrUserIds: ThreadIdOrUserIds,
uploadId: String, uploadId: String,
waveform: List<Float>, waveform: List<Float>,
samplingFreq: Int, samplingFreq: Int,
): Call<DirectThreadBroadcastResponse?> { ): DirectThreadBroadcastResponse {
return broadcast(VoiceBroadcastOptions(clientContext, threadIdOrUserIds, uploadId, waveform, samplingFreq)) return broadcast(VoiceBroadcastOptions(clientContext, threadIdOrUserIds, uploadId, waveform, samplingFreq))
} }
fun broadcastStoryReply( suspend fun broadcastStoryReply(
threadIdOrUserIds: ThreadIdOrUserIds, threadIdOrUserIds: ThreadIdOrUserIds,
text: String, text: String,
mediaId: String, mediaId: String,
reelId: String, reelId: String,
): Call<DirectThreadBroadcastResponse?> { ): DirectThreadBroadcastResponse {
return broadcast(StoryReplyBroadcastOptions(UUID.randomUUID().toString(), threadIdOrUserIds, text, mediaId, reelId)) return broadcast(StoryReplyBroadcastOptions(UUID.randomUUID().toString(), threadIdOrUserIds, text, mediaId, reelId))
} }
fun broadcastReaction( suspend fun broadcastReaction(
clientContext: String, clientContext: String,
threadIdOrUserIds: ThreadIdOrUserIds, threadIdOrUserIds: ThreadIdOrUserIds,
itemId: String, itemId: String,
emoji: String?, emoji: String?,
delete: Boolean, delete: Boolean,
): Call<DirectThreadBroadcastResponse?> { ): DirectThreadBroadcastResponse {
return broadcast(ReactionBroadcastOptions(clientContext, threadIdOrUserIds, itemId, emoji, delete)) return broadcast(ReactionBroadcastOptions(clientContext, threadIdOrUserIds, itemId, emoji, delete))
} }
fun broadcastAnimatedMedia( suspend fun broadcastAnimatedMedia(
clientContext: String, clientContext: String,
threadIdOrUserIds: ThreadIdOrUserIds, threadIdOrUserIds: ThreadIdOrUserIds,
giphyGif: GiphyGif, giphyGif: GiphyGif,
): Call<DirectThreadBroadcastResponse?> { ): DirectThreadBroadcastResponse {
return broadcast(AnimatedMediaBroadcastOptions(clientContext, threadIdOrUserIds, giphyGif)) return broadcast(AnimatedMediaBroadcastOptions(clientContext, threadIdOrUserIds, giphyGif))
} }
fun broadcastMediaShare( suspend fun broadcastMediaShare(
clientContext: String, clientContext: String,
threadIdOrUserIds: ThreadIdOrUserIds, threadIdOrUserIds: ThreadIdOrUserIds,
mediaId: String, mediaId: String,
): Call<DirectThreadBroadcastResponse?> { ): DirectThreadBroadcastResponse {
return broadcast(MediaShareBroadcastOptions(clientContext, threadIdOrUserIds, mediaId)) return broadcast(MediaShareBroadcastOptions(clientContext, threadIdOrUserIds, mediaId))
} }
private fun broadcast(broadcastOptions: BroadcastOptions): Call<DirectThreadBroadcastResponse?> { private suspend fun broadcast(broadcastOptions: BroadcastOptions): DirectThreadBroadcastResponse {
require(!isEmpty(broadcastOptions.clientContext)) { "Broadcast requires a valid client context value" } require(!isEmpty(broadcastOptions.clientContext)) { "Broadcast requires a valid client context value" }
val form = mutableMapOf<String, Any>() val form = mutableMapOf<String, Any>()
val threadId = broadcastOptions.threadId val threadId = broadcastOptions.threadId