BarInsta/app/src/main/java/awais/instagrabber/viewmodels/StoryFragmentViewModel.kt

499 lines
18 KiB
Kotlin
Raw Normal View History

package awais.instagrabber.viewmodels
import android.net.Uri
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import awais.instagrabber.R
import awais.instagrabber.managers.DirectMessagesManager
import awais.instagrabber.models.Resource
import awais.instagrabber.models.Resource.Companion.error
import awais.instagrabber.models.Resource.Companion.loading
import awais.instagrabber.models.Resource.Companion.success
import awais.instagrabber.models.enums.BroadcastItemType
import awais.instagrabber.models.enums.FavoriteType
import awais.instagrabber.models.enums.MediaItemType
import awais.instagrabber.models.enums.StoryPaginationType
import awais.instagrabber.repositories.requests.StoryViewerOptions
import awais.instagrabber.repositories.responses.Media
import awais.instagrabber.repositories.responses.directmessages.RankedRecipient
import awais.instagrabber.repositories.responses.stories.*
import awais.instagrabber.utils.Constants
import awais.instagrabber.utils.Utils
import awais.instagrabber.utils.getCsrfTokenFromCookie
import awais.instagrabber.utils.getUserIdFromCookie
import awais.instagrabber.webservices.MediaRepository
import awais.instagrabber.webservices.StoriesRepository
import com.google.common.collect.ImmutableList
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
class StoryFragmentViewModel : ViewModel() {
// large data
private val currentStory = MutableLiveData<Story?>()
private val currentMedia = MutableLiveData<StoryMedia>()
// small data
private val storyTitle = MutableLiveData<String>()
private val date = MutableLiveData<String>()
private val type = MutableLiveData<MediaItemType>()
private val poll = MutableLiveData<PollSticker>()
private val quiz = MutableLiveData<QuizSticker>()
private val question = MutableLiveData<QuestionSticker>()
private val slider = MutableLiveData<SliderSticker>()
private val swipeUp = MutableLiveData<String>()
private val linkedPost = MutableLiveData<String>()
private val appAttribution = MutableLiveData<StoryAppAttribution>()
private val reelMentions = MutableLiveData<List<Triple<String, String?, FavoriteType>>>()
// process
private val currentIndex = MutableLiveData<Int>()
private val pagination = MutableLiveData(StoryPaginationType.DO_NOTHING)
private val options = MutableLiveData<Triple<List<Pair<Int, Int>>, String?, String?>>()
private val seen = MutableLiveData<Triple<String, Long, Long>>()
// utils
private var messageManager: DirectMessagesManager? = null
private val cookie = Utils.settingsHelper.getString(Constants.COOKIE)
private val deviceId = Utils.settingsHelper.getString(Constants.DEVICE_UUID)
private val csrfToken = getCsrfTokenFromCookie(cookie)
private val userId = getUserIdFromCookie(cookie)
private val storiesRepository: StoriesRepository by lazy { StoriesRepository.getInstance() }
private val mediaRepository: MediaRepository by lazy { MediaRepository.getInstance() }
// for highlights ONLY
2021-07-08 20:51:46 +00:00
val highlights = MutableLiveData<List<Story>?>()
/* set functions */
fun setStory(story: Story) {
currentStory.postValue(story)
storyTitle.postValue(story.title ?: story.user?.username)
if (story.broadcast != null) {
date.postValue(story.dateTime)
type.postValue(MediaItemType.MEDIA_TYPE_LIVE)
pagination.postValue(StoryPaginationType.DO_NOTHING)
2021-07-08 20:51:46 +00:00
return
}
if (story.items == null || story.items.size == 0) {
pagination.postValue(StoryPaginationType.ERROR)
return
}
}
fun setMedia(index: Int) {
if (currentStory.value?.items == null) return
if (index < 0 || index >= currentStory.value!!.items!!.size) {
pagination.postValue(if (index < 0) StoryPaginationType.BACKWARD else StoryPaginationType.FORWARD)
return
}
currentIndex.postValue(index)
val story: Story? = currentStory.value
val media = story!!.items!!.get(index)
currentMedia.postValue(media)
date.postValue(media.date)
type.postValue(media.type)
initStickers(media)
}
fun setSingleMedia(media: StoryMedia) {
currentStory.postValue(null)
currentIndex.postValue(0)
currentMedia.postValue(media)
date.postValue(media.date)
type.postValue(media.type)
}
private fun initStickers(media: StoryMedia) {
val builder = ImmutableList.builder<Pair<Int, Int>>()
var linkedText: String? = null
var appText: String? = null
if (setMentions(media)) builder.add(Pair(R.id.mentions, R.string.story_mentions))
if (setQuiz(media)) builder.add(Pair(R.id.quiz, R.string.story_quiz))
if (setQuestion(media)) builder.add(Pair(R.id.question, R.string.story_question))
if (setPoll(media)) builder.add(Pair(R.id.poll, R.string.story_poll))
if (setSlider(media)) builder.add(Pair(R.id.slider, R.string.story_slider))
if (setLinkedPost(media)) builder.add(Pair(R.id.viewStoryPost, R.string.view_post))
if (setStoryCta(media)) {
linkedText = media.linkText
builder.add(Pair(R.id.swipeUp, 0))
}
if (setStoryAppAttribution(media)) {
appText = media.storyAppAttribution!!.appActionText
builder.add(Pair(R.id.spotify, 0))
}
options.postValue(Triple(builder.build(), linkedText, appText))
}
private fun setMentions(media: StoryMedia): Boolean {
val mentions: MutableList<Triple<String, String?, FavoriteType>> = mutableListOf()
if (media.reelMentions != null)
mentions.addAll(media.reelMentions.map{
Triple("@" + it.user?.username, it.user?.username, FavoriteType.USER)
})
if (media.storyHashtags != null)
mentions.addAll(media.storyHashtags.map{
Triple("#" + it.hashtag?.name, it.hashtag?.name, FavoriteType.HASHTAG)
})
if (media.storyLocations != null)
mentions.addAll(media.storyLocations.map{
Triple(it.location?.name ?: "", it.location?.pk?.toString(10), FavoriteType.LOCATION)
})
reelMentions.postValue(mentions.filterNot { it.second.isNullOrEmpty() } .distinct())
return !mentions.isEmpty()
}
private fun setPoll(media: StoryMedia): Boolean {
poll.postValue(media.storyPolls?.get(0)?.pollSticker ?: return false)
return true
}
private fun setQuiz(media: StoryMedia): Boolean {
quiz.postValue(media.storyQuizs?.get(0)?.quizSticker ?: return false)
return true
}
private fun setQuestion(media: StoryMedia): Boolean {
val questionSticker = media.storyQuestions?.get(0)?.questionSticker ?: return false
if (questionSticker.questionType.equals("music")) return false
question.postValue(questionSticker)
return true
}
private fun setSlider(media: StoryMedia): Boolean {
slider.postValue(media.storySliders?.get(0)?.sliderSticker ?: return false)
return true
}
private fun setLinkedPost(media: StoryMedia): Boolean {
linkedPost.postValue(media.storyFeedMedia?.get(0)?.mediaId ?: return false)
return true
}
private fun setStoryCta(media: StoryMedia): Boolean {
val webUri = media.storyCta?.get(0)?.links?.get(0)?.webUri ?: return false
val parsedUri = Uri.parse(webUri)
val cleanUri = if (parsedUri.host.equals("l.instagram.com")) parsedUri.getQueryParameter("u")
else null
swipeUp.postValue(if (cleanUri != null && Uri.parse(cleanUri).scheme?.startsWith("http") == true) cleanUri
else webUri)
return true
}
private fun setStoryAppAttribution(media: StoryMedia): Boolean {
appAttribution.postValue(media.storyAppAttribution ?: return false)
return true
}
/* get functions */
fun getCurrentStory(): LiveData<Story?> {
return currentStory
}
fun getCurrentIndex(): LiveData<Int> {
return currentIndex
}
fun getCurrentMedia(): LiveData<StoryMedia> {
return currentMedia
}
fun getPagination(): LiveData<StoryPaginationType> {
return pagination
}
fun getDate(): LiveData<String> {
return date
}
fun getTitle(): LiveData<String> {
return storyTitle
}
fun getType(): LiveData<MediaItemType> {
return type
}
fun getMedia(): LiveData<StoryMedia> {
return currentMedia
}
fun getMention(index: Int): Triple<String, String?, FavoriteType>? {
return reelMentions.value?.get(index)
}
fun getMentionTexts(): Array<String> {
return reelMentions.value!!.map { it.first } .toTypedArray()
}
fun getPoll(): LiveData<PollSticker> {
return poll
}
fun getQuestion(): LiveData<QuestionSticker> {
return question
}
fun getQuiz(): LiveData<QuizSticker> {
return quiz
}
fun getSlider(): LiveData<SliderSticker> {
return slider
}
fun getLinkedPost(): LiveData<Resource<Media?>> {
val data = MutableLiveData<Resource<Media?>>()
data.postValue(loading(null))
val postId = linkedPost.value
if (postId == null) data.postValue(error("No post ID supplied", null))
else viewModelScope.launch(Dispatchers.IO) {
try {
val media = mediaRepository.fetch(postId.toLong())
data.postValue(success(media))
}
catch (e: Exception) {
data.postValue(error(e.message, null))
}
}
return data
}
fun getSwipeUp(): String? {
return swipeUp.value
}
fun getAppAttribution(): String? {
return appAttribution.value?.url
}
fun getOptions(): LiveData<Triple<List<Pair<Int, Int>>, String?, String?>> {
return options
}
/* action functions */
fun answerPoll(w: Int): LiveData<Resource<Any?>> {
val data = MutableLiveData<Resource<Any?>>()
data.postValue(loading(null))
viewModelScope.launch(Dispatchers.IO) {
try {
val oldPoll: PollSticker = poll.value!!
val response = storiesRepository.respondToPoll(
csrfToken!!,
userId,
deviceId,
currentMedia.value!!.pk,
oldPoll.pollId,
w
)
if (!"ok".equals(response.status))
throw Exception("Instagram returned status \"" + response.status + "\"")
val tally = oldPoll.tallies.get(w)
val newTally = tally.copy(count = tally.count + 1)
val newTallies = oldPoll.tallies.toMutableList()
newTallies.set(w, newTally)
poll.postValue(oldPoll.copy(viewerVote = w, tallies = newTallies.toList()))
data.postValue(success(null))
}
catch (e: Exception) {
data.postValue(error(e.message, null))
}
}
return data
}
fun answerQuiz(w: Int): LiveData<Resource<Any?>> {
val data = MutableLiveData<Resource<Any?>>()
data.postValue(loading(null))
viewModelScope.launch(Dispatchers.IO) {
try {
val oldQuiz = quiz.value!!
val response = storiesRepository.respondToQuiz(
csrfToken!!,
userId,
deviceId,
currentMedia.value!!.pk,
oldQuiz.quizId,
w
)
if (!"ok".equals(response.status))
throw Exception("Instagram returned status \"" + response.status + "\"")
val tally = oldQuiz.tallies.get(w)
val newTally = tally.copy(count = tally.count + 1)
val newTallies = oldQuiz.tallies.toMutableList()
newTallies.set(w, newTally)
quiz.postValue(oldQuiz.copy(viewerAnswer = w, tallies = newTallies.toList()))
data.postValue(success(null))
}
catch (e: Exception) {
data.postValue(error(e.message, null))
}
}
return data
}
fun answerQuestion(a: String): LiveData<Resource<Any?>> {
val data = MutableLiveData<Resource<Any?>>()
data.postValue(loading(null))
viewModelScope.launch(Dispatchers.IO) {
try {
val response = storiesRepository.respondToQuestion(
csrfToken!!,
userId,
deviceId,
currentMedia.value!!.pk,
question.value!!.questionId,
a
)
if (!"ok".equals(response.status))
throw Exception("Instagram returned status \"" + response.status + "\"")
data.postValue(success(null))
}
catch (e: Exception) {
data.postValue(error(e.message, null))
}
}
return data
}
fun answerSlider(a: Double): LiveData<Resource<Any?>> {
val data = MutableLiveData<Resource<Any?>>()
data.postValue(loading(null))
viewModelScope.launch(Dispatchers.IO) {
try {
val oldSlider = slider.value!!
val response = storiesRepository.respondToSlider(
csrfToken!!,
userId,
deviceId,
currentMedia.value!!.pk,
oldSlider.sliderId,
a
)
if (!"ok".equals(response.status))
throw Exception("Instagram returned status \"" + response.status + "\"")
val newVoteCount = (oldSlider.sliderVoteCount ?: 0) + 1
val newAverage = if (oldSlider.sliderVoteAverage == null) a
else (oldSlider.sliderVoteAverage * oldSlider.sliderVoteCount!! + a) / newVoteCount
slider.postValue(oldSlider.copy(viewerCanVote = false,
sliderVoteCount = newVoteCount,
viewerVote = a,
sliderVoteAverage = newAverage))
data.postValue(success(null))
}
catch (e: Exception) {
data.postValue(error(e.message, null))
}
}
return data
}
fun reply(a: String): LiveData<Resource<Any?>>? {
if (messageManager == null) {
messageManager = DirectMessagesManager
}
return messageManager?.replyToStory(
currentStory.value?.user?.pk,
currentStory.value?.id,
currentMedia.value?.id,
a,
viewModelScope
)
}
fun shareDm(result: RankedRecipient) {
if (messageManager == null) {
messageManager = DirectMessagesManager
}
val mediaId = currentMedia.value?.id ?: return
val reelId = currentStory.value?.id ?: return
messageManager?.sendMedia(result, mediaId, reelId, BroadcastItemType.STORY, viewModelScope)
}
fun shareDm(recipients: Set<RankedRecipient>) {
if (messageManager == null) {
messageManager = DirectMessagesManager
}
val mediaId = currentMedia.value?.id ?: return
val reelId = currentStory.value?.id ?: return
messageManager?.sendMedia(recipients, mediaId, reelId, BroadcastItemType.STORY, viewModelScope)
}
fun paginate(backward: Boolean) {
var index = currentIndex.value!!
index = if (backward) index - 1 else index + 1
if (index < 0 || index >= currentStory.value!!.items!!.size) skip(backward)
setMedia(index)
}
fun skip(backward: Boolean) {
pagination.postValue(if (backward) StoryPaginationType.BACKWARD else StoryPaginationType.FORWARD)
}
fun fetchStory(fetchOptions: StoryViewerOptions?): LiveData<Resource<Any?>> {
val data = MutableLiveData<Resource<Any?>>()
data.postValue(loading(null))
viewModelScope.launch(Dispatchers.IO) {
try {
val story = storiesRepository.getStories(fetchOptions!!)
setStory(story!!)
data.postValue(success(null))
} catch (e: Exception) {
data.postValue(error(e.message, null))
}
}
return data
}
fun fetchHighlights(id: Long) {
viewModelScope.launch(Dispatchers.IO) {
try {
val result = storiesRepository.fetchHighlights(id)
highlights.postValue(result)
} catch (e: Exception) {
}
}
}
fun fetchSingleMedia(mediaId: Long): LiveData<Resource<Any?>> {
val data = MutableLiveData<Resource<Any?>>()
data.postValue(loading(null))
viewModelScope.launch(Dispatchers.IO) {
try {
val storyMedia = storiesRepository.fetch(mediaId)
setSingleMedia(storyMedia!!)
data.postValue(success(null))
} catch (e: Exception) {
data.postValue(error(e.message, null))
}
}
return data
}
2021-07-08 20:51:46 +00:00
fun markAsSeen(storyMedia: StoryMedia): LiveData<Resource<Story?>> {
val data = MutableLiveData<Resource<Story?>>()
data.postValue(loading(null))
2021-07-09 00:40:59 +00:00
val oldStory = currentStory.value!!
if (oldStory.seen != null && oldStory.seen >= storyMedia.takenAt) data.postValue(success(null))
else viewModelScope.launch(Dispatchers.IO) {
2021-07-08 20:51:46 +00:00
try {
storiesRepository.seen(
csrfToken!!,
userId,
deviceId,
storyMedia.id,
storyMedia.takenAt,
System.currentTimeMillis() / 1000
)
val newStory = oldStory.copy(seen = storyMedia.takenAt)
data.postValue(success(newStory))
} catch (e: Exception) {
data.postValue(error(e.message, null))
}
}
return data
}
}