feat: 获取 `z_id`
This commit is contained in:
parent
035102dcf3
commit
2ae4d728ee
|
|
@ -1,7 +1,7 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<litepal>
|
||||
<dbname value="tblite" />
|
||||
<version value="33" />
|
||||
<version value="35" />
|
||||
<list>
|
||||
<mapping class="com.huanchengfly.tieba.post.models.database.Account" />
|
||||
<mapping class="com.huanchengfly.tieba.post.models.database.Draft" />
|
||||
|
|
|
|||
|
|
@ -75,6 +75,8 @@ fun Any.toJson(): String = Gson().toJson(this)
|
|||
|
||||
fun String.toMD5(): String = MD5Util.toMd5(this)
|
||||
|
||||
fun ByteArray.toMD5(): String = MD5Util.toMd5(this)
|
||||
|
||||
fun Context.getColorCompat(@ColorRes id: Int): Int {
|
||||
return ContextCompat.getColor(this, id)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,19 @@
|
|||
package com.huanchengfly.tieba.post.api.models
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class SofireResponse(
|
||||
val data: String,
|
||||
@SerialName("request_id")
|
||||
@SerializedName("request_id")
|
||||
val requestId: Long,
|
||||
val skey: String
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class SofireResponseData(
|
||||
val token: String
|
||||
)
|
||||
|
|
@ -25,6 +25,7 @@ import com.huanchengfly.tieba.post.api.retrofit.interfaces.MiniTiebaApi
|
|||
import com.huanchengfly.tieba.post.api.retrofit.interfaces.NewTiebaApi
|
||||
import com.huanchengfly.tieba.post.api.retrofit.interfaces.OfficialProtobufTiebaApi
|
||||
import com.huanchengfly.tieba.post.api.retrofit.interfaces.OfficialTiebaApi
|
||||
import com.huanchengfly.tieba.post.api.retrofit.interfaces.SofireApi
|
||||
import com.huanchengfly.tieba.post.api.retrofit.interfaces.WebTiebaApi
|
||||
import com.huanchengfly.tieba.post.toJson
|
||||
import com.huanchengfly.tieba.post.utils.AccountUtil
|
||||
|
|
@ -224,6 +225,21 @@ object RetrofitTiebaApi {
|
|||
)
|
||||
}
|
||||
|
||||
val SOFIRE_API: SofireApi by lazy {
|
||||
Retrofit.Builder()
|
||||
.baseUrl("https://sofire.baidu.com/")
|
||||
.addCallAdapterFactory(DeferredCallAdapterFactory())
|
||||
.addCallAdapterFactory(FlowCallAdapterFactory.create())
|
||||
.addConverterFactory(NullOnEmptyConverterFactory())
|
||||
.addConverterFactory(gsonConverterFactory)
|
||||
.client(OkHttpClient.Builder().apply {
|
||||
// addInterceptor()
|
||||
connectionPool(connectionPool)
|
||||
}.build())
|
||||
.build()
|
||||
.create(SofireApi::class.java)
|
||||
}
|
||||
|
||||
private inline fun <reified T : Any> createJsonApi(
|
||||
baseUrl: String,
|
||||
vararg interceptors: Interceptor
|
||||
|
|
|
|||
|
|
@ -0,0 +1,21 @@
|
|||
package com.huanchengfly.tieba.post.api.retrofit.interfaces
|
||||
|
||||
import com.huanchengfly.tieba.post.api.models.SofireResponse
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import okhttp3.RequestBody
|
||||
import retrofit2.http.Body
|
||||
import retrofit2.http.HeaderMap
|
||||
import retrofit2.http.POST
|
||||
import retrofit2.http.Query
|
||||
import retrofit2.http.Url
|
||||
|
||||
interface SofireApi {
|
||||
@POST
|
||||
// @FormUrlEncoded
|
||||
fun post(
|
||||
@Url url: String,
|
||||
@Query("skey") skey: String,
|
||||
@Body body: RequestBody,
|
||||
@HeaderMap headers: Map<String, String>,
|
||||
): Flow<SofireResponse>
|
||||
}
|
||||
|
|
@ -25,6 +25,8 @@ data class Account @JvmOverloads constructor(
|
|||
var birthdayTime: String? = null,
|
||||
var constellation: String? = null,
|
||||
var loadSuccess: Boolean = false,
|
||||
var uuid: String? = "",
|
||||
var zid: String? = ""
|
||||
) : LitePalSupport() {
|
||||
val id: Int = 0
|
||||
}
|
||||
|
|
@ -12,13 +12,20 @@ import androidx.compose.runtime.mutableStateOf
|
|||
import androidx.compose.runtime.staticCompositionLocalOf
|
||||
import com.huanchengfly.tieba.post.R
|
||||
import com.huanchengfly.tieba.post.api.TiebaApi
|
||||
import com.huanchengfly.tieba.post.api.models.InitNickNameBean
|
||||
import com.huanchengfly.tieba.post.api.models.LoginBean
|
||||
import com.huanchengfly.tieba.post.arch.GlobalEvent
|
||||
import com.huanchengfly.tieba.post.arch.emitGlobalEvent
|
||||
import com.huanchengfly.tieba.post.models.database.Account
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.flow.zip
|
||||
import org.litepal.LitePal
|
||||
import org.litepal.LitePal.findAll
|
||||
import org.litepal.LitePal.where
|
||||
import org.litepal.extension.findAllAsync
|
||||
import org.litepal.extension.findFirst
|
||||
import java.util.UUID
|
||||
|
||||
object AccountUtil {
|
||||
const val TAG = "AccountUtil"
|
||||
|
|
@ -67,6 +74,11 @@ object AccountUtil {
|
|||
return currentAccount
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun <T> getAccountInfo(getter: Account.() -> T): T? {
|
||||
return currentAccount?.getter()
|
||||
}
|
||||
|
||||
fun newAccount(uid: String, account: Account, callback: (Boolean) -> Unit) {
|
||||
account.saveOrUpdateAsync("uid = ?", uid).listen {
|
||||
mutableAllAccountsState.value = findAll(Account::class.java)
|
||||
|
|
@ -80,7 +92,7 @@ object AccountUtil {
|
|||
|
||||
@JvmStatic
|
||||
fun getAccountInfoByUid(uid: String): Account? {
|
||||
return where("uid = ?", uid).findFirst(Account::class.java)
|
||||
return where("uid = ?", uid).findFirst<Account>()
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
|
|
@ -103,44 +115,23 @@ object AccountUtil {
|
|||
.putInt("now", id).commit()
|
||||
}
|
||||
|
||||
fun fetchAccountFlow(): Flow<Account> {
|
||||
return TiebaApi.getInstance()
|
||||
.initNickNameFlow()
|
||||
.zip(TiebaApi.getInstance().loginFlow()) { initNickNameBean, loginBean ->
|
||||
getLoginInfo()!!.apply {
|
||||
uid = loginBean.user.id
|
||||
name = loginBean.user.name
|
||||
nameShow = initNickNameBean.userInfo.nameShow
|
||||
portrait = loginBean.user.portrait
|
||||
tbs = loginBean.anti.tbs
|
||||
saveOrUpdate("uid = ?", loginBean.user.id)
|
||||
mutableAllAccountsState.value = findAll(Account::class.java)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun fetchAccountFlow(account: Account): Flow<Account> {
|
||||
return TiebaApi.getInstance()
|
||||
.initNickNameFlow(
|
||||
account.bduss,
|
||||
account.sToken
|
||||
)
|
||||
.zip(
|
||||
TiebaApi.getInstance().loginFlow(
|
||||
account.bduss,
|
||||
account.sToken
|
||||
)
|
||||
) { initNickNameBean, loginBean ->
|
||||
private fun updateAccount(
|
||||
account: Account,
|
||||
initNickNameBean: InitNickNameBean,
|
||||
loginBean: LoginBean,
|
||||
) {
|
||||
account.apply {
|
||||
uid = loginBean.user.id
|
||||
name = loginBean.user.name
|
||||
nameShow = initNickNameBean.userInfo.nameShow
|
||||
portrait = loginBean.user.portrait
|
||||
tbs = loginBean.anti.tbs
|
||||
saveOrUpdate("uid = ?", loginBean.user.id)
|
||||
mutableAllAccountsState.value = findAll(Account::class.java)
|
||||
if (uuid.isNullOrBlank()) uuid = UUID.randomUUID().toString()
|
||||
}
|
||||
}
|
||||
|
||||
fun fetchAccountFlow(account: Account = getLoginInfo()!!): Flow<Account> {
|
||||
return fetchAccountFlow(account.bduss, account.sToken, account.cookie)
|
||||
}
|
||||
|
||||
fun fetchAccountFlow(
|
||||
|
|
@ -154,14 +145,9 @@ object AccountUtil {
|
|||
getAccountInfoByUid(loginBean.user.id)?.apply {
|
||||
this.bduss = bduss
|
||||
this.sToken = sToken
|
||||
this.tbs = loginBean.anti.tbs
|
||||
this.name = loginBean.user.name
|
||||
this.nameShow = initNickNameBean.userInfo.nameShow
|
||||
this.portrait = loginBean.user.portrait
|
||||
this.cookie = cookie ?: getBdussCookie(bduss)
|
||||
saveOrUpdate("uid = ?", loginBean.user.id)
|
||||
}
|
||||
?: Account(
|
||||
updateAccount(this, initNickNameBean, loginBean)
|
||||
} ?: Account(
|
||||
loginBean.user.id,
|
||||
loginBean.user.name,
|
||||
bduss,
|
||||
|
|
@ -174,6 +160,20 @@ object AccountUtil {
|
|||
"0"
|
||||
)
|
||||
}
|
||||
.zip(SofireUtils.fetchZid()) { account, zid ->
|
||||
account.apply { this.zid = zid }
|
||||
}
|
||||
.onEach { account ->
|
||||
account.updateAllAsync("uid = ?", account.uid)
|
||||
.listen { rowAffected ->
|
||||
if (rowAffected > 0) {
|
||||
LitePal.findAllAsync<Account>()
|
||||
.listen {
|
||||
mutableAllAccountsState.value = it
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
|
|
|
|||
|
|
@ -0,0 +1,10 @@
|
|||
package com.huanchengfly.tieba.post.utils
|
||||
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.util.zip.GZIPOutputStream
|
||||
|
||||
fun String.gzipCompress(): ByteArray {
|
||||
val bos = ByteArrayOutputStream()
|
||||
GZIPOutputStream(bos).bufferedWriter(Charsets.UTF_8).use { it.write(this) }
|
||||
return bos.toByteArray()
|
||||
}
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
package com.huanchengfly.tieba.post.utils
|
||||
|
||||
import kotlin.experimental.xor
|
||||
|
||||
class RC442 {
|
||||
private var x: Int = 0
|
||||
private var y: Int = 0
|
||||
private var m: ByteArray = ByteArray(256)
|
||||
|
||||
fun setup(key: ByteArray, keyLen: Int = key.size) {
|
||||
var i: Int
|
||||
var j: Int
|
||||
var a: Int
|
||||
|
||||
for (i in 0 until 256) {
|
||||
m[i] = i.toByte()
|
||||
}
|
||||
|
||||
j = 0
|
||||
var k = 0
|
||||
|
||||
for (i in 0 until 256) {
|
||||
if (k >= keyLen)
|
||||
k = 0
|
||||
|
||||
a = m[i].toInt()
|
||||
j = (j + a + key[k].toInt()) and 0xFF
|
||||
m[i] = m[j]
|
||||
m[j] = a.toByte()
|
||||
k++
|
||||
}
|
||||
}
|
||||
|
||||
fun crypt(src: ByteArray, srcLen: Int = src.size): ByteArray {
|
||||
val dst = ByteArray(src.size)
|
||||
var x = this.x
|
||||
var y = this.y
|
||||
|
||||
for (i in 0 until srcLen) {
|
||||
x = (x + 1) and 0xFF
|
||||
val a = m[x].toInt()
|
||||
y = (y + a) and 0xFF
|
||||
val b = m[y].toInt()
|
||||
|
||||
m[x] = b.toByte()
|
||||
m[y] = a.toByte()
|
||||
|
||||
dst[i] = (src[i] xor m[(a + b) and 0xFF]) xor 42.toByte()
|
||||
}
|
||||
|
||||
this.x = x
|
||||
this.y = y
|
||||
|
||||
return dst
|
||||
}
|
||||
}
|
||||
|
||||
fun rc442Crypt(
|
||||
src: ByteArray,
|
||||
key: ByteArray,
|
||||
srcLen: Int = src.size,
|
||||
keyLen: Int = key.size
|
||||
): ByteArray {
|
||||
val rc442 = RC442()
|
||||
rc442.setup(key, keyLen)
|
||||
return rc442.crypt(src, srcLen)
|
||||
}
|
||||
|
|
@ -0,0 +1,92 @@
|
|||
package com.huanchengfly.tieba.post.utils
|
||||
|
||||
import android.util.Base64
|
||||
import com.huanchengfly.tieba.post.api.models.SofireResponseData
|
||||
import com.huanchengfly.tieba.post.api.retrofit.RetrofitTiebaApi
|
||||
import com.huanchengfly.tieba.post.toMD5
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.json.Json
|
||||
import okhttp3.MediaType.Companion.toMediaType
|
||||
import okhttp3.RequestBody.Companion.toRequestBody
|
||||
import java.security.MessageDigest
|
||||
import java.util.Locale
|
||||
import javax.crypto.Cipher
|
||||
import javax.crypto.spec.IvParameterSpec
|
||||
import javax.crypto.spec.SecretKeySpec
|
||||
import kotlin.random.Random
|
||||
|
||||
@Serializable
|
||||
data class SofireRequestBody(
|
||||
@SerialName("module_section")
|
||||
val moduleSection: List<Map<String, String>>
|
||||
)
|
||||
|
||||
fun generateRandomString(length: Int): String {
|
||||
val charPool: List<Char> = ('a'..'z') + ('A'..'Z') + ('0'..'9')
|
||||
return (1..length)
|
||||
.map { Random.nextInt(0, charPool.size) }
|
||||
.map(charPool::get)
|
||||
.joinToString("")
|
||||
}
|
||||
|
||||
object SofireUtils {
|
||||
const val DEFAULT_APP_KEY = "200033"
|
||||
const val DEFAULT_SECRET_KEY = "ea737e4f435b53786043369d2e5ace4f"
|
||||
|
||||
fun fetchZid(): Flow<String> {
|
||||
val appKey = DEFAULT_APP_KEY
|
||||
val secKey = DEFAULT_SECRET_KEY
|
||||
val cuid = "${UIDUtil.uUID.toMD5().uppercase()}|0"
|
||||
val cuidMd5 = cuid.toMD5().lowercase()
|
||||
val currTime = "${System.currentTimeMillis() / 1000}"
|
||||
val reqBody =
|
||||
Json.encodeToString(SofireRequestBody(listOf(mapOf("zid" to cuid)))).gzipCompress()
|
||||
val randomKey = generateRandomString(16)
|
||||
val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding")
|
||||
val iv = IvParameterSpec(0.toChar().toString().repeat(16).encodeToByteArray())
|
||||
cipher.init(Cipher.ENCRYPT_MODE, SecretKeySpec(randomKey.toByteArray(), "AES"), iv)
|
||||
val encBody = cipher.doFinal(reqBody)
|
||||
val reqBodyMd5Digest = MessageDigest.getInstance("MD5").apply { update(reqBody) }.digest()
|
||||
val finalBody =
|
||||
(encBody + reqBodyMd5Digest).toRequestBody("application/x-www-form-urlencoded".toMediaType())
|
||||
val headers = mapOf(
|
||||
"Pragma" to "no-cache",
|
||||
"Accept" to "*/*",
|
||||
"Accept-Language" to Locale.getDefault().language,
|
||||
"x-device-id" to cuidMd5,
|
||||
"x-client-src" to "src",
|
||||
"User-Agent" to "x6/$appKey/12.35.1.0/4.4.1.3",
|
||||
"x-sdk-ver" to "sofire/3.5.9.6",
|
||||
"x-plu-ver" to "x6/4.4.1.3",
|
||||
"x-app-ver" to "com.baidu.tieba/12.35.1.0",
|
||||
"x-api-ver" to "33"
|
||||
)
|
||||
val pathMd5 = listOf(appKey, currTime, secKey).joinToString("").toMD5().lowercase()
|
||||
val skey = Base64.encodeToString(
|
||||
rc442Crypt(randomKey.encodeToByteArray(), cuidMd5.encodeToByteArray(), 16, 32),
|
||||
Base64.DEFAULT
|
||||
)
|
||||
val url = "https://sofire.baidu.com/c/11/z/100/$appKey/$currTime/$pathMd5"
|
||||
return RetrofitTiebaApi.SOFIRE_API
|
||||
.post(url, skey, finalBody, headers)
|
||||
.map {
|
||||
val resSkey = rc442Crypt(
|
||||
Base64.decode(it.skey, Base64.DEFAULT),
|
||||
cuidMd5.encodeToByteArray(),
|
||||
16,
|
||||
32
|
||||
)
|
||||
cipher.init(Cipher.DECRYPT_MODE, SecretKeySpec(resSkey, "AES"), iv)
|
||||
val decode = Base64.decode(it.data, Base64.DEFAULT).dropLast(16).toByteArray()
|
||||
val json = Json { ignoreUnknownKeys = true }
|
||||
val decryptData = json.decodeFromString<SofireResponseData>(
|
||||
cipher.doFinal(decode).decodeToString()
|
||||
)
|
||||
decryptData.token
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue