5 Commits

Author SHA1 Message Date
042f4bd330 fix build err 2024-04-04 19:44:56 +08:00
9aef71b09f fix build err 2024-04-04 18:56:48 +08:00
9cbe755520 fix missing elem-type for kritor 2024-04-04 18:51:58 +08:00
df02f9f872 fix #316 2024-03-28 19:37:10 +08:00
5cbb695a66 fix crash 2024-03-28 19:27:11 +08:00
4 changed files with 94 additions and 33 deletions

View File

@ -14,7 +14,10 @@ import moe.fuqiuluo.shamrock.tools.toast
import moe.fuqiuluo.shamrock.xposed.helper.AppTalker import moe.fuqiuluo.shamrock.xposed.helper.AppTalker
import mqq.app.MobileQQ import mqq.app.MobileQQ
import java.io.File import java.io.File
import java.util.Calendar
import java.util.Date import java.util.Date
import java.util.Timer
import java.util.TimerTask
internal enum class Level( internal enum class Level(
val id: Byte val id: Byte
@ -31,7 +34,29 @@ internal object LogCenter {
// 格式化时间 // 格式化时间
SimpleDateFormat("yyyy-MM-dd").format(Date()) SimpleDateFormat("yyyy-MM-dd").format(Date())
}_" }_"
private val LogFile = MobileQQ.getContext().getExternalFilesDir(null)!! private var LogFile = generateLogFile()
private val format = SimpleDateFormat("[HH:mm:ss] ")
private val timer = Timer()
init {
val now = Calendar.getInstance()
val tomorrowMidnight = Calendar.getInstance().apply {
add(Calendar.DAY_OF_YEAR, 1)
set(Calendar.HOUR_OF_DAY, 0)
set(Calendar.MINUTE, 0)
set(Calendar.SECOND, 0)
set(Calendar.MILLISECOND, 0)
}
val delay = tomorrowMidnight.timeInMillis - now.timeInMillis
timer.scheduleAtFixedRate(object : TimerTask() {
override fun run() {
LogFile = generateLogFile()
}
}, delay, 24 * 60 * 60 * 1000)
}
private fun generateLogFile() = MobileQQ.getContext().getExternalFilesDir(null)!!
.parentFile!!.resolve("Tencent/Shamrock/log").also { .parentFile!!.resolve("Tencent/Shamrock/log").also {
if (it.exists()) it.delete() if (it.exists()) it.delete()
it.mkdirs() it.mkdirs()
@ -49,8 +74,6 @@ internal object LogCenter {
return@let result return@let result
} }
private val format = SimpleDateFormat("[HH:mm:ss] ")
fun log(string: String, level: Level = Level.INFO, toast: Boolean = false) { fun log(string: String, level: Level = Level.INFO, toast: Boolean = false) {
if (!ShamrockConfig[DebugMode] && level == Level.DEBUG) { if (!ShamrockConfig[DebugMode] && level == Level.DEBUG) {
return return

View File

@ -39,7 +39,7 @@ class AntiDetection: IAction {
if (ShamrockConfig[AntiJvmTrace]) if (ShamrockConfig[AntiJvmTrace])
antiTrace() antiTrace()
antiMemoryWalking() antiMemoryWalking()
antiO3Report() //antiO3Report()
} }
private fun antiO3Report() { private fun antiO3Report() {

View File

@ -9,11 +9,13 @@ import qq.service.QQInterfaces
object SwitchStatus: IInteract, QQInterfaces() { object SwitchStatus: IInteract, QQInterfaces() {
override fun invoke(intent: Intent) { override fun invoke(intent: Intent) {
AppTalker.talk("switch_status") { if (app.isLogin) {
put("account", app.currentAccountUin) AppTalker.talk("switch_status") {
put("nickname", if (app is QQAppInterface) app.currentNickname else "unknown") put("account", app.currentAccountUin)
put("voice", NativeLoader.isVoiceLoaded) put("nickname", if (app is QQAppInterface) (app.currentNickname ?: "unknown") else "unknown")
put("core_version", ShamrockVersion) put("voice", NativeLoader.isVoiceLoaded)
put("core_version", ShamrockVersion)
}
} }
} }
} }

View File

@ -1,11 +1,13 @@
package qq.service.msg package qq.service.msg
import com.google.protobuf.ByteString
import com.tencent.mobileqq.qroute.QRoute import com.tencent.mobileqq.qroute.QRoute
import com.tencent.qqnt.kernel.nativeinterface.MsgConstant import com.tencent.qqnt.kernel.nativeinterface.MsgConstant
import com.tencent.qqnt.kernel.nativeinterface.MsgElement import com.tencent.qqnt.kernel.nativeinterface.MsgElement
import com.tencent.qqnt.kernel.nativeinterface.MsgRecord import com.tencent.qqnt.kernel.nativeinterface.MsgRecord
import com.tencent.qqnt.msg.api.IMsgService import com.tencent.qqnt.msg.api.IMsgService
import io.kritor.common.* import io.kritor.common.*
import io.kritor.common.Element.ElementType
import kotlinx.coroutines.suspendCancellableCoroutine import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.withTimeoutOrNull import kotlinx.coroutines.withTimeoutOrNull
import moe.fuqiuluo.shamrock.helper.ActionMsgException import moe.fuqiuluo.shamrock.helper.ActionMsgException
@ -55,11 +57,13 @@ private object MsgConvertor {
val text = element.textElement val text = element.textElement
val elem = Element.newBuilder() val elem = Element.newBuilder()
if (text.atType != MsgConstant.ATTYPEUNKNOWN) { if (text.atType != MsgConstant.ATTYPEUNKNOWN) {
elem.type = ElementType.AT
elem.setAt(AtElement.newBuilder().apply { elem.setAt(AtElement.newBuilder().apply {
this.uid = text.atNtUid this.uid = text.atNtUid
this.uin = ContactHelper.getUinByUidAsync(text.atNtUid).toLong() this.uin = ContactHelper.getUinByUidAsync(text.atNtUid).toLong()
}) })
} else { } else {
elem.type = ElementType.TEXT
elem.setText(TextElement.newBuilder().apply { elem.setText(TextElement.newBuilder().apply {
this.text = text.content this.text = text.content
}) })
@ -71,6 +75,7 @@ private object MsgConvertor {
val face = element.faceElement val face = element.faceElement
val elem = Element.newBuilder() val elem = Element.newBuilder()
if (face.faceType == 5) { if (face.faceType == 5) {
elem.type = ElementType.POKE
elem.setPoke(PokeElement.newBuilder().apply { elem.setPoke(PokeElement.newBuilder().apply {
this.id = face.vaspokeId this.id = face.vaspokeId
this.type = face.pokeType this.type = face.pokeType
@ -78,28 +83,43 @@ private object MsgConvertor {
}) })
} else { } else {
when (face.faceIndex) { when (face.faceIndex) {
114 -> elem.setBasketball(BasketballElement.newBuilder().apply { 114 -> {
this.id = face.resultId.ifNullOrEmpty { "0" }?.toInt() ?: 0 elem.type = ElementType.BASKETBALL
}) elem.setBasketball(BasketballElement.newBuilder().apply {
this.id = face.resultId.ifNullOrEmpty { "0" }?.toInt() ?: 0
})
}
358 -> elem.setDice(DiceElement.newBuilder().apply { 358 -> {
this.id = face.resultId.ifNullOrEmpty { "0" }?.toInt() ?: 0 elem.type = ElementType.DICE
}) elem.setDice(DiceElement.newBuilder().apply {
this.id = face.resultId.ifNullOrEmpty { "0" }?.toInt() ?: 0
})
}
359 -> elem.setRps(RpsElement.newBuilder().apply { 359 -> {
this.id = face.resultId.ifNullOrEmpty { "0" }?.toInt() ?: 0 elem.type = ElementType.RPS
}) elem.setRps(RpsElement.newBuilder().apply {
this.id = face.resultId.ifNullOrEmpty { "0" }?.toInt() ?: 0
})
}
394 -> elem.setFace(FaceElement.newBuilder().apply { 394 -> {
this.id = face.faceIndex elem.type = ElementType.FACE
this.isBig = face.faceType == 3 elem.setFace(FaceElement.newBuilder().apply {
this.result = face.resultId.ifNullOrEmpty { "1" }?.toInt() ?: 1 this.id = face.faceIndex
}) this.isBig = face.faceType == 3
this.result = face.resultId.ifNullOrEmpty { "1" }?.toInt() ?: 1
})
}
else -> elem.setFace(FaceElement.newBuilder().apply { else -> {
this.id = face.faceIndex elem.type = ElementType.FACE
this.isBig = face.faceType == 3 elem.setFace(FaceElement.newBuilder().apply {
}) this.id = face.faceIndex
this.isBig = face.faceType == 3
})
}
} }
} }
return Result.success(elem.build()) return Result.success(elem.build())
@ -134,8 +154,9 @@ private object MsgConvertor {
LogCenter.log({ "receive image: $image" }, Level.DEBUG) LogCenter.log({ "receive image: $image" }, Level.DEBUG)
val elem = Element.newBuilder() val elem = Element.newBuilder()
elem.type = ElementType.IMAGE
elem.setImage(ImageElement.newBuilder().apply { elem.setImage(ImageElement.newBuilder().apply {
this.fileMd5 = md5 this.file = ByteString.copyFromUtf8(md5)
this.fileUrl = when (record.chatType) { this.fileUrl = when (record.chatType) {
MsgConstant.KCHATTYPEDISC, MsgConstant.KCHATTYPEGROUP -> RichProtoSvc.getGroupPicDownUrl( MsgConstant.KCHATTYPEDISC, MsgConstant.KCHATTYPEGROUP -> RichProtoSvc.getGroupPicDownUrl(
originalUrl = originalUrl, originalUrl = originalUrl,
@ -190,6 +211,7 @@ private object MsgConvertor {
ptt.fileName.substring(5) ptt.fileName.substring(5)
else ptt.md5HexStr else ptt.md5HexStr
elem.type = ElementType.VOICE
elem.setVoice(VoiceElement.newBuilder().apply { elem.setVoice(VoiceElement.newBuilder().apply {
this.fileUrl = when (record.chatType) { this.fileUrl = when (record.chatType) {
MsgConstant.KCHATTYPEC2C -> RichProtoSvc.getC2CPttDownUrl("0", ptt.fileUuid) MsgConstant.KCHATTYPEC2C -> RichProtoSvc.getC2CPttDownUrl("0", ptt.fileUuid)
@ -201,7 +223,7 @@ private object MsgConvertor {
else -> throw UnsupportedOperationException("Not supported chat type: ${record.chatType}") else -> throw UnsupportedOperationException("Not supported chat type: ${record.chatType}")
} }
this.fileMd5 = md5 this.file = ByteString.copyFromUtf8(md5)
this.magic = ptt.voiceChangeType != MsgConstant.KPTTVOICECHANGETYPENONE this.magic = ptt.voiceChangeType != MsgConstant.KPTTVOICECHANGETYPENONE
}) })
@ -218,8 +240,9 @@ private object MsgConvertor {
it[it.size - 2].hex2ByteArray() it[it.size - 2].hex2ByteArray()
} }
} else video.fileName.split(".")[0].hex2ByteArray() } else video.fileName.split(".")[0].hex2ByteArray()
elem.type = ElementType.VIDEO
elem.setVideo(VideoElement.newBuilder().apply { elem.setVideo(VideoElement.newBuilder().apply {
this.fileMd5 = md5.toHexString() this.file = ByteString.copyFromUtf8(md5.toHexString())
this.fileUrl = when (record.chatType) { this.fileUrl = when (record.chatType) {
MsgConstant.KCHATTYPEGROUP -> RichProtoSvc.getGroupVideoDownUrl("0", md5, video.fileUuid) MsgConstant.KCHATTYPEGROUP -> RichProtoSvc.getGroupVideoDownUrl("0", md5, video.fileUuid)
MsgConstant.KCHATTYPEC2C -> RichProtoSvc.getC2CVideoDownUrl("0", md5, video.fileUuid) MsgConstant.KCHATTYPEC2C -> RichProtoSvc.getC2CVideoDownUrl("0", md5, video.fileUuid)
@ -233,6 +256,7 @@ private object MsgConvertor {
suspend fun convertMarketFace(record: MsgRecord, element: MsgElement): Result<Element> { suspend fun convertMarketFace(record: MsgRecord, element: MsgElement): Result<Element> {
val marketFace = element.marketFaceElement val marketFace = element.marketFaceElement
val elem = Element.newBuilder() val elem = Element.newBuilder()
elem.type = ElementType.MARKET_FACE
elem.setMarketFace(MarketFaceElement.newBuilder().apply { elem.setMarketFace(MarketFaceElement.newBuilder().apply {
this.id = marketFace.emojiId.lowercase() this.id = marketFace.emojiId.lowercase()
}) })
@ -245,6 +269,7 @@ private object MsgConvertor {
when (data["app"].asString) { when (data["app"].asString) {
"com.tencent.multimsg" -> { "com.tencent.multimsg" -> {
val info = data["meta"].asJsonObject["detail"].asJsonObject val info = data["meta"].asJsonObject["detail"].asJsonObject
elem.type = ElementType.FORWARD
elem.setForward(ForwardElement.newBuilder().apply { elem.setForward(ForwardElement.newBuilder().apply {
this.resId = info["resid"].asString this.resId = info["resid"].asString
this.uniseq = info["uniseq"].asString this.uniseq = info["uniseq"].asString
@ -257,6 +282,7 @@ private object MsgConvertor {
"com.tencent.troopsharecard" -> { "com.tencent.troopsharecard" -> {
val info = data["meta"].asJsonObject["contact"].asJsonObject val info = data["meta"].asJsonObject["contact"].asJsonObject
elem.type = ElementType.CONTACT
elem.setContact(ContactElement.newBuilder().apply { elem.setContact(ContactElement.newBuilder().apply {
this.scene = Scene.GROUP this.scene = Scene.GROUP
this.peer = info["jumpUrl"].asString.split("group_code=")[1] this.peer = info["jumpUrl"].asString.split("group_code=")[1]
@ -265,6 +291,7 @@ private object MsgConvertor {
"com.tencent.contact.lua" -> { "com.tencent.contact.lua" -> {
val info = data["meta"].asJsonObject["contact"].asJsonObject val info = data["meta"].asJsonObject["contact"].asJsonObject
elem.type = ElementType.CONTACT
elem.setContact(ContactElement.newBuilder().apply { elem.setContact(ContactElement.newBuilder().apply {
this.scene = Scene.FRIEND this.scene = Scene.FRIEND
this.peer = info["jumpUrl"].asString.split("uin=")[1] this.peer = info["jumpUrl"].asString.split("uin=")[1]
@ -273,6 +300,7 @@ private object MsgConvertor {
"com.tencent.map" -> { "com.tencent.map" -> {
val info = data["meta"].asJsonObject["Location.Search"].asJsonObject val info = data["meta"].asJsonObject["Location.Search"].asJsonObject
elem.type = ElementType.LOCATION
elem.setLocation(LocationElement.newBuilder().apply { elem.setLocation(LocationElement.newBuilder().apply {
this.lat = info["lat"].asString.toFloat() this.lat = info["lat"].asString.toFloat()
this.lon = info["lng"].asString.toFloat() this.lon = info["lng"].asString.toFloat()
@ -281,9 +309,12 @@ private object MsgConvertor {
}) })
} }
else -> elem.setJson(JsonElement.newBuilder().apply { else -> {
this.json = data.toString() elem.type = ElementType.JSON
}) elem.setJson(JsonElement.newBuilder().apply {
this.json = data.toString()
})
}
} }
return Result.success(elem.build()) return Result.success(elem.build())
} }
@ -291,6 +322,7 @@ private object MsgConvertor {
suspend fun convertReply(record: MsgRecord, element: MsgElement): Result<Element> { suspend fun convertReply(record: MsgRecord, element: MsgElement): Result<Element> {
val reply = element.replyElement val reply = element.replyElement
val elem = Element.newBuilder() val elem = Element.newBuilder()
elem.type = ElementType.REPLY
elem.setReply(ReplyElement.newBuilder().apply { elem.setReply(ReplyElement.newBuilder().apply {
val msgSeq = reply.replayMsgSeq val msgSeq = reply.replayMsgSeq
val contact = MessageHelper.generateContact(record) val contact = MessageHelper.generateContact(record)
@ -332,6 +364,7 @@ private object MsgConvertor {
else -> RichProtoSvc.getGroupFileDownUrl(record.peerUin, fileId, bizId) else -> RichProtoSvc.getGroupFileDownUrl(record.peerUin, fileId, bizId)
} }
val elem = Element.newBuilder() val elem = Element.newBuilder()
elem.type = ElementType.FILE
elem.setFile(FileElement.newBuilder().apply { elem.setFile(FileElement.newBuilder().apply {
this.name = fileName this.name = fileName
this.size = fileSize this.size = fileSize
@ -347,6 +380,7 @@ private object MsgConvertor {
suspend fun convertMarkdown(record: MsgRecord, element: MsgElement): Result<Element> { suspend fun convertMarkdown(record: MsgRecord, element: MsgElement): Result<Element> {
val markdown = element.markdownElement val markdown = element.markdownElement
val elem = Element.newBuilder() val elem = Element.newBuilder()
elem.type = ElementType.MARKDOWN
elem.setMarkdown(MarkdownElement.newBuilder().apply { elem.setMarkdown(MarkdownElement.newBuilder().apply {
this.markdown = markdown.content this.markdown = markdown.content
}) })
@ -356,6 +390,7 @@ private object MsgConvertor {
suspend fun convertBubbleFace(record: MsgRecord, element: MsgElement): Result<Element> { suspend fun convertBubbleFace(record: MsgRecord, element: MsgElement): Result<Element> {
val bubbleFace = element.faceBubbleElement val bubbleFace = element.faceBubbleElement
val elem = Element.newBuilder() val elem = Element.newBuilder()
elem.type = ElementType.BUBBLE_FACE
elem.setBubbleFace(BubbleFaceElement.newBuilder().apply { elem.setBubbleFace(BubbleFaceElement.newBuilder().apply {
this.id = bubbleFace.yellowFaceInfo.index this.id = bubbleFace.yellowFaceInfo.index
this.count = bubbleFace.faceCount ?: 1 this.count = bubbleFace.faceCount ?: 1
@ -366,6 +401,7 @@ private object MsgConvertor {
suspend fun convertInlineKeyboard(record: MsgRecord, element: MsgElement): Result<Element> { suspend fun convertInlineKeyboard(record: MsgRecord, element: MsgElement): Result<Element> {
val inlineKeyboard = element.inlineKeyboardElement val inlineKeyboard = element.inlineKeyboardElement
val elem = Element.newBuilder() val elem = Element.newBuilder()
elem.type = ElementType.BUTTON
elem.setButton(ButtonElement.newBuilder().apply { elem.setButton(ButtonElement.newBuilder().apply {
inlineKeyboard.rows.forEach { row -> inlineKeyboard.rows.forEach { row ->
this.addRows(ButtonRow.newBuilder().apply { this.addRows(ButtonRow.newBuilder().apply {