Shamrock: 实现Kritor核心服务

Signed-off-by: 白池 <whitechi73@outlook.com>
This commit is contained in:
白池 2024-03-11 12:48:11 +08:00
parent c16f9d543c
commit cb4268edef
4 changed files with 120 additions and 40 deletions

View File

@ -20,6 +20,7 @@ class KritorServer(
.intercept(AuthInterceptor) .intercept(AuthInterceptor)
.addService(Authentication) .addService(Authentication)
.addService(ContactService) .addService(ContactService)
.addService(KritorService)
.build()!! .build()!!
fun start(block: Boolean = false) { fun start(block: Boolean = false) {

View File

@ -0,0 +1,118 @@
package kritor.service
import android.util.Base64
import com.tencent.mobileqq.app.QQAppInterface
import io.grpc.Status
import io.grpc.StatusRuntimeException
import io.kritor.core.ClearCacheRequest
import io.kritor.core.ClearCacheResponse
import io.kritor.core.DownloadFileRequest
import io.kritor.core.DownloadFileResponse
import io.kritor.core.GetCurrentAccountRequest
import io.kritor.core.GetCurrentAccountResponse
import io.kritor.core.GetVersionRequest
import io.kritor.core.GetVersionResponse
import io.kritor.core.KritorServiceGrpcKt
import io.kritor.core.SwitchAccountRequest
import io.kritor.core.SwitchAccountResponse
import io.kritor.core.clearCacheResponse
import io.kritor.core.downloadFileResponse
import io.kritor.core.getCurrentAccountResponse
import io.kritor.core.getVersionResponse
import io.kritor.core.switchAccountResponse
import moe.fuqiuluo.shamrock.tools.ShamrockVersion
import moe.fuqiuluo.shamrock.utils.DownloadUtils
import moe.fuqiuluo.shamrock.utils.FileUtils
import moe.fuqiuluo.shamrock.utils.MD5
import moe.fuqiuluo.shamrock.utils.MMKVFetcher
import mqq.app.MobileQQ
import qq.service.QQInterfaces
import qq.service.QQInterfaces.Companion.app
import qq.service.contact.ContactHelper
import java.io.File
object KritorService: KritorServiceGrpcKt.KritorServiceCoroutineImplBase() {
@Grpc("KritorService", "GetVersion")
override suspend fun getVersion(request: GetVersionRequest): GetVersionResponse {
return getVersionResponse {
this.version = ShamrockVersion
this.appName = "Shamrock"
}
}
@Grpc("KritorService", "ClearCache")
override suspend fun clearCache(request: ClearCacheRequest): ClearCacheResponse {
FileUtils.clearCache()
MMKVFetcher.mmkvWithId("audio2silk")
.clear()
return clearCacheResponse {}
}
@Grpc("KritorService", "GetCurrentAccount")
override suspend fun getCurrentAccount(request: GetCurrentAccountRequest): GetCurrentAccountResponse {
return getCurrentAccountResponse {
this.accountName = if (app is QQAppInterface) app.currentNickname else "unknown"
this.accountUid = app.currentUid ?: ""
this.accountUin = (app.currentUin ?: "0").toLong()
}
}
@Grpc("KritorService", "DownloadFile")
override suspend fun downloadFile(request: DownloadFileRequest): DownloadFileResponse {
val headerMap = mutableMapOf(
"User-Agent" to "Shamrock"
)
if (request.hasHeaders()) {
request.headers.split("[\r\n]").forEach {
val pair = it.split("=")
if (pair.size >= 2) {
val (k, v) = pair
headerMap[k] = v
}
}
}
var tmp = FileUtils.getTmpFile("cache")
if (request.hasBase64()) {
val bytes = Base64.decode(request.base64, Base64.DEFAULT)
tmp.writeBytes(bytes)
} else if(request.hasUrl()) {
if(!DownloadUtils.download(
urlAdr = request.url,
dest = tmp,
headers = headerMap,
threadCount = if (request.hasThreadCnt()) request.threadCnt else 3
)) {
throw StatusRuntimeException(Status.INTERNAL.withDescription("download failed"))
}
}
tmp = if (!request.hasFileName()) FileUtils.renameByMd5(tmp)
else tmp.parentFile!!.resolve(request.fileName).also {
tmp.renameTo(it)
}
if (request.hasRootPath()) {
tmp = File(request.rootPath).resolve(tmp.name).also {
tmp.renameTo(it)
}
}
return downloadFileResponse {
this.fileMd5 = MD5.genFileMd5Hex(tmp.absolutePath)
this.fileAbsolutePath = tmp.absolutePath
}
}
@Grpc("KritorService", "SwitchAccount")
override suspend fun switchAccount(request: SwitchAccountRequest): SwitchAccountResponse {
val uin = if (request.hasAccountUin()) request.accountUin.toString()
else ContactHelper.getUinByUidAsync(request.accountUid)
val account = MobileQQ.getMobileQQ().allAccounts.firstOrNull { it.uin == uin }
?: throw StatusRuntimeException(Status.NOT_FOUND.withDescription("account not found"))
runCatching {
app.switchAccount(account, null)
}.onFailure {
throw StatusRuntimeException(Status.INTERNAL.withCause(it).withDescription("failed to switch account"))
}
return switchAccountResponse { }
}
}

View File

@ -33,7 +33,7 @@ object DownloadUtils {
threadCount: Int = MAX_THREAD, threadCount: Int = MAX_THREAD,
headers: Map<String, String> = mapOf() headers: Map<String, String> = mapOf()
): Boolean { ): Boolean {
var threadCnt = if(threadCount == 0) MAX_THREAD else threadCount var threadCnt = if(threadCount == 0 || threadCount < 0) MAX_THREAD else threadCount
val url = URL(urlAdr) val url = URL(urlAdr)
val connection = withContext(Dispatchers.IO) { url.openConnection() } as HttpURLConnection val connection = withContext(Dispatchers.IO) { url.openConnection() } as HttpURLConnection
headers.forEach { (k, v) -> headers.forEach { (k, v) ->

View File

@ -1,39 +0,0 @@
package qq.service.contact
const val CMD_GET_PROFILE_DETAIL = "OidbSvc.0x480_9_IMCore"
const val CMD_SET_PROFILE_DETAIL = "OidbSvc.0x4ff_9_IMCore"
const val KET_INTERESTS = "interests"
const val KEY_AGE = "age"
const val KEY_BIRTHDAY = "birthday"
const val KEY_COLLEGE = "college"
const val KEY_COMPANY = "company"
const val KEY_CONSTELLATION = "key_constellation"
const val KEY_EMAIL = "email"
const val KEY_HOMETOWN = "hometown"
const val KEY_HOMETOWN_DESC = "hometown_desc"
const val KEY_LOCATION = "location"
const val KEY_LOCATION_DESC = "location_desc"
const val KEY_LOCATION_NAME = "location_name"
const val KEY_NICK = "nick"
const val KEY_PARSE_PROFILE_LOCATION = "parse_profile_location"
const val KEY_PERSONAL_NOTE = "personalNote"
const val KEY_PROFESSION = "profession"
const val KEY_SEX = "sex"
const val PARAM_ADD_FRIEND_SOURCE = "addFriendSource"
const val PARAM_COME_FROM_TYPE = "comeFromType"
const val PARAM_GET_CONTROL = "getControl"
const val PARAM_IS_FRIEND = "isFriend"
const val PARAM_LOGIN_SIG = "loginSig"
const val PARAM_QZONE_FEED_TIMESTAMP = "qZoneFeedTimeStamp"
const val PARAM_QZONE_SEED = "qZoneSeed"
const val PARAM_REQ_0X5EB = "req0x5ebList"
const val PARAM_REQ_EXTEND = "reqExtendFriend"
const val PARAM_REQ_MEDAL = "reqMedalWall"
const val PARAM_REQ_SERVICES = "reqServiceList"
const val PARAM_REQ_TEMPLATE = "reqTemplate"
const val PARAM_SEARCH_NAME = "searchName"
const val PARAM_SECURE_SIG = "secureSig"
const val PARAM_SELF_UIN = "selfUin"
const val PARAM_TARGET_UIN = "targetUin"
const val PARAM_TROOP_CODE = "troopCode"
const val PARAM_TROOP_UIN = "troopUin"