forked from melod1n/fast-messenger
fix: harden captcha and long poll parsing
This commit is contained in:
@@ -21,12 +21,10 @@ class VkGroupsMap(
|
|||||||
else map[abs(convo.id)]
|
else map[abs(convo.id)]
|
||||||
|
|
||||||
fun messageActionGroup(message: VkMessage): VkGroupDomain? =
|
fun messageActionGroup(message: VkMessage): VkGroupDomain? =
|
||||||
if (message.actionMemberId == null || message.actionMemberId!! >= 0) null
|
message.actionMemberId?.takeIf { it < 0 }?.let { map[abs(it)] }
|
||||||
else map[abs(message.actionMemberId!!)]
|
|
||||||
|
|
||||||
fun messageActionGroup(message: VkMessageData): VkGroupDomain? =
|
fun messageActionGroup(message: VkMessageData): VkGroupDomain? =
|
||||||
if (message.action?.memberId == null || message.action!!.memberId!! >= 0) null
|
message.action?.memberId?.takeIf { it < 0 }?.let { map[abs(it)] }
|
||||||
else map[abs(message.action!!.memberId!!)]
|
|
||||||
|
|
||||||
fun messageGroup(message: VkMessage): VkGroupDomain? =
|
fun messageGroup(message: VkMessage): VkGroupDomain? =
|
||||||
if (!message.isGroup()) null
|
if (!message.isGroup()) null
|
||||||
|
|||||||
@@ -20,12 +20,10 @@ class VkUsersMap(
|
|||||||
else map[convo.id]
|
else map[convo.id]
|
||||||
|
|
||||||
fun messageActionUser(message: VkMessage): VkUser? =
|
fun messageActionUser(message: VkMessage): VkUser? =
|
||||||
if (message.actionMemberId == null || message.actionMemberId!! <= 0) null
|
message.actionMemberId?.takeIf { it > 0 }?.let(map::get)
|
||||||
else map[message.actionMemberId]
|
|
||||||
|
|
||||||
fun messageActionUser(message: VkMessageData): VkUser? =
|
fun messageActionUser(message: VkMessageData): VkUser? =
|
||||||
if (message.action?.memberId == null || message.action!!.memberId!! <= 0) null
|
message.action?.memberId?.takeIf { it > 0 }?.let(map::get)
|
||||||
else map[message.action!!.memberId]
|
|
||||||
|
|
||||||
fun messageUser(message: VkMessage): VkUser? =
|
fun messageUser(message: VkMessage): VkUser? =
|
||||||
if (!message.isUser()) null
|
if (!message.isUser()) null
|
||||||
|
|||||||
@@ -21,8 +21,8 @@ import kotlinx.coroutines.Dispatchers
|
|||||||
import kotlinx.coroutines.async
|
import kotlinx.coroutines.async
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||||
import kotlin.coroutines.resume
|
import kotlin.coroutines.resume
|
||||||
import kotlin.coroutines.suspendCoroutine
|
|
||||||
|
|
||||||
internal class LongPollEventParser(
|
internal class LongPollEventParser(
|
||||||
private val coroutineScope: CoroutineScope,
|
private val coroutineScope: CoroutineScope,
|
||||||
@@ -35,7 +35,7 @@ internal class LongPollEventParser(
|
|||||||
val eventId = event.first().asInt()
|
val eventId = event.first().asInt()
|
||||||
|
|
||||||
when (val eventType = ApiEvent.parseOrNull(eventId)) {
|
when (val eventType = ApiEvent.parseOrNull(eventId)) {
|
||||||
null -> Log.d("LongPollEventParser", "parseNextUpdate: unknownEvent: $event")
|
null -> Unit
|
||||||
|
|
||||||
ApiEvent.MESSAGE_SET_FLAGS -> parseMessageSetFlags(eventType, event)
|
ApiEvent.MESSAGE_SET_FLAGS -> parseMessageSetFlags(eventType, event)
|
||||||
ApiEvent.MESSAGE_CLEAR_FLAGS -> parseMessageClearFlags(eventType, event)
|
ApiEvent.MESSAGE_CLEAR_FLAGS -> parseMessageClearFlags(eventType, event)
|
||||||
@@ -62,8 +62,6 @@ internal class LongPollEventParser(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun parseMessageSetFlags(eventType: ApiEvent, event: List<Any>) {
|
private fun parseMessageSetFlags(eventType: ApiEvent, event: List<Any>) {
|
||||||
Log.d("LongPollEventParser", "$eventType: $event")
|
|
||||||
|
|
||||||
val cmId = event[1].asLong()
|
val cmId = event[1].asLong()
|
||||||
val flags = event[2].asInt()
|
val flags = event[2].asInt()
|
||||||
val peerId = event[3].asLong()
|
val peerId = event[3].asLong()
|
||||||
@@ -128,8 +126,6 @@ internal class LongPollEventParser(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun parseMessageClearFlags(eventType: ApiEvent, event: List<Any>) {
|
private fun parseMessageClearFlags(eventType: ApiEvent, event: List<Any>) {
|
||||||
Log.d("LongPollEventParser", "$eventType: $event")
|
|
||||||
|
|
||||||
val cmId = event[1].asLong()
|
val cmId = event[1].asLong()
|
||||||
val flags = event[2].asInt()
|
val flags = event[2].asInt()
|
||||||
val peerId = event[3].asLong()
|
val peerId = event[3].asLong()
|
||||||
@@ -192,8 +188,6 @@ internal class LongPollEventParser(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun parseMessageNew(eventType: ApiEvent, event: List<Any>) {
|
private fun parseMessageNew(eventType: ApiEvent, event: List<Any>) {
|
||||||
Log.d("LongPollEventParser", "$eventType: $event")
|
|
||||||
|
|
||||||
val cmId = event[1].asLong()
|
val cmId = event[1].asLong()
|
||||||
val peerId = event[4].asLong()
|
val peerId = event[4].asLong()
|
||||||
|
|
||||||
@@ -223,8 +217,6 @@ internal class LongPollEventParser(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun parseMessageEdit(eventType: ApiEvent, event: List<Any>) {
|
private fun parseMessageEdit(eventType: ApiEvent, event: List<Any>) {
|
||||||
Log.d("LongPollEventParser", "$eventType: $event")
|
|
||||||
|
|
||||||
val cmId = event[1].asLong()
|
val cmId = event[1].asLong()
|
||||||
val peerId = event[3].asLong()
|
val peerId = event[3].asLong()
|
||||||
|
|
||||||
@@ -239,40 +231,28 @@ internal class LongPollEventParser(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun parseMessageReadIncoming(eventType: ApiEvent, event: List<Any>) {
|
private fun parseMessageReadIncoming(eventType: ApiEvent, event: List<Any>) {
|
||||||
Log.d("LongPollEventParser", "$eventType: $event")
|
dispatchMessageRead(
|
||||||
val peerId = event[1].asLong()
|
longPollEvent = LongPollEvent.INCOMING_MESSAGE_READ,
|
||||||
val cmId = event[2].asLong()
|
parsedEvent = LongPollParsedEvent.IncomingMessageRead(
|
||||||
val unreadCount = event[3].asInt()
|
peerId = event[1].asLong(),
|
||||||
|
cmId = event[2].asLong(),
|
||||||
dispatch(
|
unreadCount = event[3].asInt()
|
||||||
LongPollEvent.INCOMING_MESSAGE_READ,
|
|
||||||
LongPollParsedEvent.IncomingMessageRead(
|
|
||||||
peerId = peerId,
|
|
||||||
cmId = cmId,
|
|
||||||
unreadCount = unreadCount
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun parseMessageReadOutgoing(eventType: ApiEvent, event: List<Any>) {
|
private fun parseMessageReadOutgoing(eventType: ApiEvent, event: List<Any>) {
|
||||||
Log.d("LongPollEventParser", "$eventType: $event")
|
dispatchMessageRead(
|
||||||
val peerId = event[1].asLong()
|
longPollEvent = LongPollEvent.OUTGOING_MESSAGE_READ,
|
||||||
val cmId = event[2].asLong()
|
parsedEvent = LongPollParsedEvent.OutgoingMessageRead(
|
||||||
val unreadCount = event[3].asInt()
|
peerId = event[1].asLong(),
|
||||||
|
cmId = event[2].asLong(),
|
||||||
dispatch(
|
unreadCount = event[3].asInt()
|
||||||
LongPollEvent.OUTGOING_MESSAGE_READ,
|
|
||||||
LongPollParsedEvent.OutgoingMessageRead(
|
|
||||||
peerId = peerId,
|
|
||||||
cmId = cmId,
|
|
||||||
unreadCount = unreadCount
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun parseChatClearFlags(eventType: ApiEvent, event: List<Any>) {
|
private fun parseChatClearFlags(eventType: ApiEvent, event: List<Any>) {
|
||||||
Log.d("LongPollEventParser", "$eventType: $event")
|
|
||||||
|
|
||||||
val peerId = event[1].asLong()
|
val peerId = event[1].asLong()
|
||||||
val flags = event[2].asInt()
|
val flags = event[2].asInt()
|
||||||
|
|
||||||
@@ -284,23 +264,11 @@ internal class LongPollEventParser(
|
|||||||
parsedFlags.forEach { flag ->
|
parsedFlags.forEach { flag ->
|
||||||
when (flag) {
|
when (flag) {
|
||||||
ConvoFlags.ARCHIVED -> {
|
ConvoFlags.ARCHIVED -> {
|
||||||
val convo = loadConvo(
|
handleArchivedChat(
|
||||||
peerId = peerId,
|
peerId = peerId,
|
||||||
extended = true,
|
archived = false,
|
||||||
fields = VkConstants.ALL_FIELDS
|
eventsToSend = eventsToSend
|
||||||
) ?: return@forEach
|
|
||||||
|
|
||||||
val message = loadMessage(
|
|
||||||
peerId = peerId,
|
|
||||||
cmId = convo.lastCmId
|
|
||||||
)
|
)
|
||||||
|
|
||||||
val eventToSend = LongPollParsedEvent.ChatArchived(
|
|
||||||
convo = convo.copy(lastMessage = message),
|
|
||||||
archived = false
|
|
||||||
)
|
|
||||||
eventsToSend += eventToSend
|
|
||||||
dispatch(LongPollEvent.CHAT_ARCHIVED, eventToSend)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> Unit
|
else -> Unit
|
||||||
@@ -312,8 +280,6 @@ internal class LongPollEventParser(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun parseChatSetFlags(eventType: ApiEvent, event: List<Any>) {
|
private fun parseChatSetFlags(eventType: ApiEvent, event: List<Any>) {
|
||||||
Log.d("LongPollEventParser", "$eventType: $event")
|
|
||||||
|
|
||||||
val peerId = event[1].asLong()
|
val peerId = event[1].asLong()
|
||||||
val flags = event[2].asInt()
|
val flags = event[2].asInt()
|
||||||
|
|
||||||
@@ -325,23 +291,11 @@ internal class LongPollEventParser(
|
|||||||
parsedFlags.forEach { flag ->
|
parsedFlags.forEach { flag ->
|
||||||
when (flag) {
|
when (flag) {
|
||||||
ConvoFlags.ARCHIVED -> {
|
ConvoFlags.ARCHIVED -> {
|
||||||
val convo = loadConvo(
|
handleArchivedChat(
|
||||||
peerId = peerId,
|
peerId = peerId,
|
||||||
extended = true,
|
archived = true,
|
||||||
fields = VkConstants.ALL_FIELDS
|
eventsToSend = eventsToSend
|
||||||
) ?: return@forEach
|
|
||||||
|
|
||||||
val message = loadMessage(
|
|
||||||
peerId = peerId,
|
|
||||||
cmId = convo.lastCmId
|
|
||||||
)
|
)
|
||||||
|
|
||||||
val eventToSend = LongPollParsedEvent.ChatArchived(
|
|
||||||
convo = convo.copy(lastMessage = message),
|
|
||||||
archived = true
|
|
||||||
)
|
|
||||||
eventsToSend += eventToSend
|
|
||||||
dispatch(LongPollEvent.CHAT_ARCHIVED, eventToSend)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> Unit
|
else -> Unit
|
||||||
@@ -353,8 +307,6 @@ internal class LongPollEventParser(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun parseMessagesDeleted(eventType: ApiEvent, event: List<Any>) {
|
private fun parseMessagesDeleted(eventType: ApiEvent, event: List<Any>) {
|
||||||
Log.d("LongPollEventParser", "$eventType: $event")
|
|
||||||
|
|
||||||
val peerId = event[1].asLong()
|
val peerId = event[1].asLong()
|
||||||
val cmId = event[2].asLong()
|
val cmId = event[2].asLong()
|
||||||
|
|
||||||
@@ -368,8 +320,6 @@ internal class LongPollEventParser(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun parseChatMajorChanged(eventType: ApiEvent, event: List<Any>) {
|
private fun parseChatMajorChanged(eventType: ApiEvent, event: List<Any>) {
|
||||||
Log.d("LongPollEventParser", "$eventType: $event")
|
|
||||||
|
|
||||||
val peerId = event[1].asLong()
|
val peerId = event[1].asLong()
|
||||||
val majorId = event[2].asInt()
|
val majorId = event[2].asInt()
|
||||||
|
|
||||||
@@ -383,8 +333,6 @@ internal class LongPollEventParser(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun parseChatMinorChanged(eventType: ApiEvent, event: List<Any>) {
|
private fun parseChatMinorChanged(eventType: ApiEvent, event: List<Any>) {
|
||||||
Log.d("LongPollEventParser", "$eventType: $event")
|
|
||||||
|
|
||||||
val peerId = event[1].asLong()
|
val peerId = event[1].asLong()
|
||||||
val minorId = event[2].asInt()
|
val minorId = event[2].asInt()
|
||||||
|
|
||||||
@@ -398,8 +346,6 @@ internal class LongPollEventParser(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun parseInteraction(eventType: ApiEvent, event: List<Any>) {
|
private fun parseInteraction(eventType: ApiEvent, event: List<Any>) {
|
||||||
Log.d("LongPollEventParser", "$eventType: $event")
|
|
||||||
|
|
||||||
val interactionType = when (eventType) {
|
val interactionType = when (eventType) {
|
||||||
ApiEvent.TYPING -> InteractionType.Typing
|
ApiEvent.TYPING -> InteractionType.Typing
|
||||||
ApiEvent.AUDIO_MESSAGE_RECORDING -> InteractionType.VoiceMessage
|
ApiEvent.AUDIO_MESSAGE_RECORDING -> InteractionType.VoiceMessage
|
||||||
@@ -438,8 +384,6 @@ internal class LongPollEventParser(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun parseUnreadCounterUpdate(eventType: ApiEvent, event: List<Any>) {
|
private fun parseUnreadCounterUpdate(eventType: ApiEvent, event: List<Any>) {
|
||||||
Log.d("LongPollEventParser", "$eventType $event")
|
|
||||||
|
|
||||||
val unreadCount = event[1].asInt()
|
val unreadCount = event[1].asInt()
|
||||||
val unreadUnmutedCount = event[2].asInt()
|
val unreadUnmutedCount = event[2].asInt()
|
||||||
val showOnlyMuted = event[3].asInt() == 1
|
val showOnlyMuted = event[3].asInt() == 1
|
||||||
@@ -463,8 +407,6 @@ internal class LongPollEventParser(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun parseMessageUpdated(eventType: ApiEvent, event: List<Any>) {
|
private fun parseMessageUpdated(eventType: ApiEvent, event: List<Any>) {
|
||||||
Log.d("LongPollEventParser", "$eventType $event")
|
|
||||||
|
|
||||||
val cmId = event[1].asLong()
|
val cmId = event[1].asLong()
|
||||||
val peerId = event[4].asLong()
|
val peerId = event[4].asLong()
|
||||||
|
|
||||||
@@ -479,8 +421,6 @@ internal class LongPollEventParser(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun parseMessageCacheClear(eventType: ApiEvent, event: List<Any>) {
|
private fun parseMessageCacheClear(eventType: ApiEvent, event: List<Any>) {
|
||||||
Log.d("LongPollEventParser", "$eventType $event")
|
|
||||||
|
|
||||||
val messageId = event[1].asLong()
|
val messageId = event[1].asLong()
|
||||||
|
|
||||||
coroutineScope.launch(Dispatchers.IO) {
|
coroutineScope.launch(Dispatchers.IO) {
|
||||||
@@ -497,10 +437,10 @@ internal class LongPollEventParser(
|
|||||||
peerId: Long? = null,
|
peerId: Long? = null,
|
||||||
cmId: Long? = null,
|
cmId: Long? = null,
|
||||||
messageId: Long? = null
|
messageId: Long? = null
|
||||||
): VkMessage? = suspendCoroutine { continuation ->
|
): VkMessage? = suspendCancellableCoroutine { continuation ->
|
||||||
require((peerId != null && cmId != null) || messageId != null)
|
require((peerId != null && cmId != null) || messageId != null)
|
||||||
|
|
||||||
coroutineScope.launch(Dispatchers.IO) {
|
val job = coroutineScope.launch(Dispatchers.IO) {
|
||||||
messagesUseCase.getById(
|
messagesUseCase.getById(
|
||||||
peerCmIds = null,
|
peerCmIds = null,
|
||||||
peerId = peerId,
|
peerId = peerId,
|
||||||
@@ -525,14 +465,18 @@ internal class LongPollEventParser(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
continuation.invokeOnCancellation {
|
||||||
|
job.cancel()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun loadConvo(
|
private suspend fun loadConvo(
|
||||||
peerId: Long,
|
peerId: Long,
|
||||||
extended: Boolean = false,
|
extended: Boolean = false,
|
||||||
fields: String? = null
|
fields: String? = null
|
||||||
): VkConvo? = suspendCoroutine { continuation ->
|
): VkConvo? = suspendCancellableCoroutine { continuation ->
|
||||||
coroutineScope.launch(Dispatchers.IO) {
|
val job = coroutineScope.launch(Dispatchers.IO) {
|
||||||
convoUseCase.getById(
|
convoUseCase.getById(
|
||||||
peerIds = listOf(peerId),
|
peerIds = listOf(peerId),
|
||||||
extended = extended,
|
extended = extended,
|
||||||
@@ -554,5 +498,40 @@ internal class LongPollEventParser(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
continuation.invokeOnCancellation {
|
||||||
|
job.cancel()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun handleArchivedChat(
|
||||||
|
peerId: Long,
|
||||||
|
archived: Boolean,
|
||||||
|
eventsToSend: MutableList<LongPollParsedEvent>
|
||||||
|
) {
|
||||||
|
val convo = loadConvo(
|
||||||
|
peerId = peerId,
|
||||||
|
extended = true,
|
||||||
|
fields = VkConstants.ALL_FIELDS
|
||||||
|
) ?: return
|
||||||
|
|
||||||
|
val message = loadMessage(
|
||||||
|
peerId = peerId,
|
||||||
|
cmId = convo.lastCmId
|
||||||
|
)
|
||||||
|
|
||||||
|
val eventToSend = LongPollParsedEvent.ChatArchived(
|
||||||
|
convo = convo.copy(lastMessage = message),
|
||||||
|
archived = archived
|
||||||
|
)
|
||||||
|
eventsToSend += eventToSend
|
||||||
|
dispatch(LongPollEvent.CHAT_ARCHIVED, eventToSend)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun dispatchMessageRead(
|
||||||
|
longPollEvent: LongPollEvent,
|
||||||
|
parsedEvent: LongPollParsedEvent
|
||||||
|
) {
|
||||||
|
dispatch(longPollEvent, parsedEvent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,22 +22,35 @@ class OAuthUseCaseImpl(
|
|||||||
): Flow<State<AuthInfo>> = flow {
|
): Flow<State<AuthInfo>> = flow {
|
||||||
emit(State.Loading)
|
emit(State.Loading)
|
||||||
|
|
||||||
val newState = oAuthRepository.auth(
|
val newState = when (val authResult = oAuthRepository.auth(
|
||||||
login = login,
|
login = login,
|
||||||
password = password,
|
password = password,
|
||||||
forceSms = forceSms,
|
forceSms = forceSms,
|
||||||
validationCode = validationCode,
|
validationCode = validationCode,
|
||||||
captchaSid = captchaSid,
|
captchaSid = captchaSid,
|
||||||
captchaKey = captchaKey
|
captchaKey = captchaKey
|
||||||
).asState(
|
)) {
|
||||||
successMapper = {
|
is com.slack.eithernet.ApiResult.Success -> {
|
||||||
|
val value = authResult.value
|
||||||
|
val userId = value.userId
|
||||||
|
val accessToken = value.accessToken
|
||||||
|
val validationHash = value.validationHash
|
||||||
|
|
||||||
|
if (userId == null || accessToken == null || validationHash == null) {
|
||||||
|
State.Error.InternalError
|
||||||
|
} else {
|
||||||
|
State.Success(
|
||||||
AuthInfo(
|
AuthInfo(
|
||||||
userId = it.userId!!,
|
userId = userId,
|
||||||
accessToken = it.accessToken!!,
|
accessToken = accessToken,
|
||||||
validationHash = it.validationHash!!
|
validationHash = validationHash
|
||||||
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
}
|
||||||
|
|
||||||
|
else -> authResult.asState()
|
||||||
|
}
|
||||||
|
|
||||||
emit(newState)
|
emit(newState)
|
||||||
}
|
}
|
||||||
|
|||||||
+34
-65
@@ -1,90 +1,62 @@
|
|||||||
package dev.meloda.fast.network.interceptor
|
package dev.meloda.fast.network.interceptor
|
||||||
|
|
||||||
import android.util.Log
|
|
||||||
import dev.meloda.fast.common.extensions.listenValue
|
|
||||||
import dev.meloda.fast.datastore.AppSettings
|
import dev.meloda.fast.datastore.AppSettings
|
||||||
import dev.meloda.fast.datastore.CaptchaTokenResult
|
import dev.meloda.fast.datastore.CaptchaTokenResult
|
||||||
import kotlinx.coroutines.CoroutineScope
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.flow.first
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import kotlinx.coroutines.sync.Mutex
|
||||||
|
import kotlinx.coroutines.sync.withLock
|
||||||
|
import kotlinx.coroutines.withTimeout
|
||||||
import okhttp3.Interceptor
|
import okhttp3.Interceptor
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
import okhttp3.Response
|
import okhttp3.Response
|
||||||
import org.json.JSONObject
|
import org.json.JSONObject
|
||||||
import java.util.concurrent.Executors
|
import java.io.IOException
|
||||||
import java.util.concurrent.atomic.AtomicReference
|
import java.util.concurrent.atomic.AtomicReference
|
||||||
|
import kotlin.time.Duration.Companion.minutes
|
||||||
|
|
||||||
class Error14HandlingInterceptor(
|
class Error14HandlingInterceptor : Interceptor {
|
||||||
// private val domains: Set<String> = emptySet(),
|
|
||||||
) : Interceptor {
|
|
||||||
|
|
||||||
private val cookie = AtomicReference<String?>(null)
|
private val cookie = AtomicReference<String?>(null)
|
||||||
|
private val captchaMutex = Mutex()
|
||||||
private companion object {
|
|
||||||
private const val CAPTCHA_ERROR_CODE = 14
|
|
||||||
private const val CAPTCHA_ERROR_KIND = "need_captcha"
|
|
||||||
private val executor = Executors.newSingleThreadExecutor()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun intercept(chain: Interceptor.Chain): Response {
|
override fun intercept(chain: Interceptor.Chain): Response {
|
||||||
val request = chain.request().withCookie()
|
val request = chain.request().withCookie()
|
||||||
val response = chain.proceed(request)
|
val response = chain.proceed(request)
|
||||||
response.parseCookie()
|
response.parseCookie()
|
||||||
|
|
||||||
if (request.shouldSkipCaptcha()) return response
|
if (request.shouldSkipCaptcha()) return response
|
||||||
|
|
||||||
val redirectUri = response.getRedirectUri() ?: return response
|
val redirectUri = response.getRedirectUri() ?: return response
|
||||||
val token = passCaptchaAndGetToken(redirectUri)
|
val token = awaitCaptchaToken(redirectUri)
|
||||||
|
|
||||||
return chain.proceed(chain.request().withCookie().withSuccessToken(token))
|
return chain.proceed(chain.request().withCookie().withSuccessToken(token))
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun passCaptchaAndGetToken(redirectUri: String): String = synchronized(this) {
|
private fun awaitCaptchaToken(redirectUri: String): String = runBlocking(Dispatchers.IO) {
|
||||||
val tokenResult = AtomicReference<Result<String>>(Result.failure(Exception("No result")))
|
captchaMutex.withLock {
|
||||||
|
AppSettings.setCaptchaResult(CaptchaTokenResult.Initial)
|
||||||
executor.submit {
|
|
||||||
AppSettings.setCaptchaRedirectUri(redirectUri)
|
AppSettings.setCaptchaRedirectUri(redirectUri)
|
||||||
Log.d("Error14Interceptor", "passCaptchaAndGetToken: $redirectUri")
|
|
||||||
|
|
||||||
var job: Job? = null
|
try {
|
||||||
job = AppSettings.getCaptchaResultFlow()
|
withTimeout(CAPTCHA_TIMEOUT) {
|
||||||
.listenValue(CoroutineScope(Dispatchers.IO)) {
|
AppSettings.getCaptchaResultFlow()
|
||||||
Log.d("Error14Interceptor", "passCaptchaAndGetToken: $it")
|
.first { it != CaptchaTokenResult.Initial }
|
||||||
if (it != CaptchaTokenResult.Initial) {
|
.toToken()
|
||||||
synchronized(tokenResult) {
|
}
|
||||||
Log.d(
|
} finally {
|
||||||
"Error14Interceptor",
|
|
||||||
"passCaptchaAndGetToken: SYNCHRONIZED: $it"
|
|
||||||
)
|
|
||||||
tokenResult.set(wrapResult(it))
|
|
||||||
tokenResult.notifyAll()
|
|
||||||
job?.cancel()
|
|
||||||
Log.d(
|
|
||||||
"Error14Interceptor",
|
|
||||||
"passCaptchaAndGetToken: NULL RESULT"
|
|
||||||
)
|
|
||||||
AppSettings.setCaptchaResult(CaptchaTokenResult.Initial)
|
AppSettings.setCaptchaResult(CaptchaTokenResult.Initial)
|
||||||
AppSettings.setCaptchaRedirectUri(null)
|
AppSettings.setCaptchaRedirectUri(null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
synchronized(tokenResult) {
|
|
||||||
if (tokenResult.get().getOrNull() == null) {
|
|
||||||
tokenResult.wait()
|
|
||||||
}
|
|
||||||
|
|
||||||
Log.d("Error14Interceptor", "passCaptchaAndGetToken: GET VALUE")
|
private fun CaptchaTokenResult.toToken(): String = when (this) {
|
||||||
tokenResult.get().getOrThrow()
|
is CaptchaTokenResult.Success -> token
|
||||||
}
|
CaptchaTokenResult.Cancelled -> throw IOException("Captcha cancelled")
|
||||||
}
|
CaptchaTokenResult.Null -> throw IOException("Captcha result is empty")
|
||||||
|
CaptchaTokenResult.Initial -> throw IllegalStateException("Captcha result not ready")
|
||||||
private fun wrapResult(result: CaptchaTokenResult): Result<String> {
|
|
||||||
return when (result) {
|
|
||||||
// TODO: 03/05/2026, Danil Nikolaev: check again?
|
|
||||||
CaptchaTokenResult.Null -> Result.success("")
|
|
||||||
|
|
||||||
CaptchaTokenResult.Cancelled, CaptchaTokenResult.Initial -> Result.success("")
|
|
||||||
|
|
||||||
is CaptchaTokenResult.Success -> Result.success(result.token)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Request.withSuccessToken(token: String): Request {
|
private fun Request.withSuccessToken(token: String): Request {
|
||||||
@@ -133,13 +105,10 @@ class Error14HandlingInterceptor(
|
|||||||
private fun Request.withCookie(): Request {
|
private fun Request.withCookie(): Request {
|
||||||
return newBuilder().apply { cookie.get()?.let { addHeader("Cookie", it) } }.build()
|
return newBuilder().apply { cookie.get()?.let { addHeader("Cookie", it) } }.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private companion object {
|
||||||
|
private const val CAPTCHA_ERROR_CODE = 14
|
||||||
|
private const val CAPTCHA_ERROR_KIND = "need_captcha"
|
||||||
|
private val CAPTCHA_TIMEOUT = 10.minutes
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN", "NOTHING_TO_INLINE")
|
|
||||||
private inline fun Any.wait() = (this as Object).wait()
|
|
||||||
|
|
||||||
@Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN", "NOTHING_TO_INLINE")
|
|
||||||
private inline fun Any.notify() = (this as Object).notify()
|
|
||||||
|
|
||||||
@Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN", "NOTHING_TO_INLINE")
|
|
||||||
private inline fun Any.notifyAll() = (this as Object).notifyAll()
|
|
||||||
|
|||||||
Reference in New Issue
Block a user