Shamrock: 实现联系人服务

Signed-off-by: 白池 <whitechi73@outlook.com>
This commit is contained in:
白池 2024-03-11 12:30:42 +08:00
parent a528030cbb
commit c16f9d543c
5 changed files with 173 additions and 6 deletions

2
kritor

@ -1 +1 @@
Subproject commit 505d80b2eec8d5f226ed28390fb15f4ca41b4c70
Subproject commit 201e91e73225ce3f1ec098c06a5cf6a717d913e5

View File

@ -42,7 +42,7 @@ object AuthInterceptor: ServerInterceptor {
}
}
private fun getAllTicket(): List<String> {
fun getAllTicket(): List<String> {
val result = arrayListOf<String>()
val activeTicketName = ActiveTicket.name()
var index = 0

View File

@ -1,10 +1,16 @@
package kritor.service
import io.grpc.Status
import io.grpc.StatusRuntimeException
import io.kritor.AuthCode
import io.kritor.AuthReq
import io.kritor.AuthRsp
import io.kritor.AuthenticationGrpcKt
import io.kritor.GetAuthStateReq
import io.kritor.GetAuthStateRsp
import io.kritor.authRsp
import io.kritor.getAuthStateRsp
import kritor.auth.AuthInterceptor
import moe.fuqiuluo.shamrock.config.ActiveTicket
import moe.fuqiuluo.shamrock.config.ShamrockConfig
import qq.service.QQInterfaces
@ -46,4 +52,15 @@ object Authentication: AuthenticationGrpcKt.AuthenticationCoroutineImplBase() {
msg = "Invalid ticket"
}
}
@Grpc("Authentication", "GetAuthState")
override suspend fun getAuthState(request: GetAuthStateReq): GetAuthStateRsp {
if (request.account != QQInterfaces.app.account) {
throw StatusRuntimeException(Status.CANCELLED.withDescription("No such account"))
}
return getAuthStateRsp {
isRequiredAuth = AuthInterceptor.getAllTicket().isNotEmpty()
}
}
}

View File

@ -1,18 +1,37 @@
package kritor.service
import android.os.Bundle
import com.tencent.mobileqq.profilecard.api.IProfileCardBlacklistApi
import com.tencent.mobileqq.profilecard.api.IProfileProtocolConst.*
import com.tencent.mobileqq.profilecard.api.IProfileProtocolService
import com.tencent.mobileqq.qroute.QRoute
import io.grpc.Status
import io.grpc.StatusRuntimeException
import io.kritor.AuthCode
import io.kritor.authRsp
import io.kritor.contact.ContactServiceGrpcKt
import io.kritor.contact.GetUidRequest
import io.kritor.contact.GetUidResponse
import io.kritor.contact.GetUinByUidRequest
import io.kritor.contact.GetUinByUidResponse
import io.kritor.contact.IsBlackListUserRequest
import io.kritor.contact.IsBlackListUserResponse
import io.kritor.contact.ProfileCard
import io.kritor.contact.ProfileCardRequest
import io.kritor.contact.SetProfileCardRequest
import io.kritor.contact.SetProfileCardResponse
import io.kritor.contact.StrangerExt
import io.kritor.contact.StrangerInfo
import io.kritor.contact.StrangerInfoRequest
import io.kritor.contact.profileCard
import moe.fuqiuluo.shamrock.config.ActiveTicket
import moe.fuqiuluo.shamrock.config.ShamrockConfig
import io.kritor.contact.strangerInfo
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.withTimeoutOrNull
import qq.service.QQInterfaces
import qq.service.contact.ContactHelper
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
object ContactService: ContactServiceGrpcKt.ContactServiceCoroutineImplBase() {
@Grpc("ContactService", "GetProfileCard")
override suspend fun getProfileCard(request: ProfileCardRequest): ProfileCard {
val uin = if (request.hasUin()) request.uin
else ContactHelper.getUinByUidAsync(request.uid).toLong()
@ -44,4 +63,96 @@ object ContactService: ContactServiceGrpcKt.ContactServiceCoroutineImplBase() {
.withDescription("logic failed")
)
}
@Grpc("ContactService", "GetStrangerInfo")
override suspend fun getStrangerInfo(request: StrangerInfoRequest): StrangerInfo {
val userId = request.uin
val info = ContactHelper.refreshAndGetProfileCard(userId).onFailure {
throw StatusRuntimeException(Status.INTERNAL
.withCause(it)
.withDescription("Unable to fetch stranger info")
)
}.getOrThrow()
return strangerInfo {
this.uid = ContactHelper.getUidByUinAsync(userId)
this.uin = (info.uin ?: "0").toLong()
this.name = info.strNick ?: ""
this.level = info.iQQLevel
this.loginDay = info.lLoginDays.toInt()
this.voteCnt = info.lVoteCount.toInt()
this.qid = info.qid ?: ""
this.isSchoolVerified = info.schoolVerifiedFlag
this.ext = StrangerExt.newBuilder()
.setBigVip(info.bBigClubVipOpen == 1.toByte())
.setHollywoodVip(info.bHollywoodVipOpen == 1.toByte())
.setQqVip(info.bQQVipOpen == 1.toByte())
.setSuperVip(info.bSuperQQOpen == 1.toByte())
.setVoted(info.bVoted == 1.toByte())
.build().toByteString()
}
}
@Grpc("ContactService", "GetUid")
override suspend fun getUid(request: GetUidRequest): GetUidResponse {
return GetUidResponse.newBuilder().apply {
request.uinList.forEach {
putUid(it, ContactHelper.getUidByUinAsync(it))
}
}.build()
}
@Grpc("ContactService", "GetUinByUid")
override suspend fun getUinByUid(request: GetUinByUidRequest): GetUinByUidResponse {
return GetUinByUidResponse.newBuilder().apply {
request.uidList.forEach {
putUin(it, ContactHelper.getUinByUidAsync(it).toLong())
}
}.build()
}
@Grpc("ContactService", "SetProfileCard")
override suspend fun setProfileCard(request: SetProfileCardRequest): SetProfileCardResponse {
val bundle = Bundle()
val service = QQInterfaces.app
.getRuntimeService(IProfileProtocolService::class.java, "all")
if (request.hasNickName()) {
bundle.putString(KEY_NICK, request.nickName)
}
if (request.hasCompany()) {
bundle.putString(KEY_COMPANY, request.company)
}
if (request.hasEmail()) {
bundle.putString(KEY_EMAIL, request.email)
}
if (request.hasCollege()) {
bundle.putString(KEY_COLLEGE, request.college)
}
if (request.hasPersonalNote()) {
bundle.putString(KEY_PERSONAL_NOTE, request.personalNote)
}
if (request.hasBirthday()) {
bundle.putInt(KEY_BIRTHDAY, request.birthday)
}
if (request.hasAge()) {
bundle.putInt(KEY_AGE, request.age)
}
service.setProfileDetail(bundle)
return super.setProfileCard(request)
}
@Grpc("ContactService", "IsBlackListUser")
override suspend fun isBlackListUser(request: IsBlackListUserRequest): IsBlackListUserResponse {
val blacklistApi = QRoute.api(IProfileCardBlacklistApi::class.java)
val isBlack = withTimeoutOrNull(5000) {
suspendCancellableCoroutine { continuation ->
blacklistApi.isBlackOrBlackedUin(request.uin.toString()) {
continuation.resume(it)
}
}
} ?: false
return IsBlackListUserResponse.newBuilder().setIsBlackListUser(isBlack).build()
}
}

View File

@ -0,0 +1,39 @@
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"