Skip to content

Commit

Permalink
adapt to tim 3.5.5.3198
Browse files Browse the repository at this point in the history
  • Loading branch information
ichenhe committed Nov 12, 2023
1 parent 3816266 commit c9bae32
Show file tree
Hide file tree
Showing 4 changed files with 319 additions and 3 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package cc.chenhe.qqnotifyevo.core

import cc.chenhe.qqnotifyevo.utils.Tag

/**
* A [NotificationResolver] that call different implementations based on [Tag].
*/
class DelegateNotificationResolver : NotificationResolver {
private val qqResolver by lazy { QQNotificationResolver() }
private val timResolver by lazy { TimNotificationResolver() }

override fun resolveNotification(
tag: Tag,
title: String?,
content: String?,
ticker: String?
): QQNotification? {
return when (tag) {
Tag.UNKNOWN -> null
Tag.QQ -> qqResolver
Tag.QQ_HD -> qqResolver
Tag.QQ_LITE -> qqResolver
Tag.TIM -> timResolver
}?.run { resolveNotification(tag, title, content, ticker) }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ abstract class NotificationProcessor(context: Context, scope: CoroutineScope) {

private val avatarManager =
AvatarManager.get(getAvatarDiskCacheDir(ctx), getAvatarCachePeriod(context))
private val resolver: NotificationResolver = DelegateNotificationResolver()

init {
scope.launch {
Expand Down Expand Up @@ -169,12 +170,9 @@ abstract class NotificationProcessor(context: Context, scope: CoroutineScope) {
val original = sbn.notification ?: return null
val tag = getTagFromPackageName(packageName)
if (tag == Tag.UNKNOWN) {
Timber.tag(TAG).d("Unknown tag, skip. pkgName=%s", packageName)
return null
}

val resolver: NotificationResolver = QQNotificationResolver()

return when (val r = resolver.resolveNotification(packageName, tag, sbn)) {
is QQNotification.BindingAccountMessage -> {
val conversation = addMessage(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
package cc.chenhe.qqnotifyevo.core

import cc.chenhe.qqnotifyevo.utils.Tag
import timber.log.Timber

/**
* For com.tencent.tim ver 3.5.5.3198 build 1328.
*
* Doesn't support QZone notifications because TIM failed to post anything about QZone.
*/
class TimNotificationResolver : NotificationResolver {
companion object {
private const val TAG = "TimNotificationResolver"

// 隐藏消息详情
// title: TIM
// ticker: 你收到了x条新消息
// text: 你收到了x条新消息

private const val HIDE_MESSAGE_TITLE = "TIM"
private val hideMsgTickerPattern = """^你收到了(?<num>\d+)条新消息$""".toRegex()

// 群聊消息
// ------------- 单个消息
// title: 群名
// ticker: 昵称(群名):消息内容
// text: [有关注的内容]昵称: 消息内容
// ------------- 多个消息
// title: 群名 (x条新消息)
// ticker: 昵称(群名):消息内容
// text: [有关注的内容]昵称: 消息内容

/**
* 匹配群聊消息 Ticker.
*
* 限制:昵称不能包含英文括号 `()`.
*/
private val groupMsgPattern =
"""^(?<nickname>.+?)\((?<group>.+?)\):(?<msg>[\s\S]+)$""".toRegex()

private val groupTitlePattern =
"""^(?<group>.+?)(?: \((?<num>\d+)条新消息\))?$""".toRegex()

/**
* 匹配群聊消息 Content.
*/
private val groupMsgContentPattern =
"""^(?<sp>\[有关注的内容])?(?<nickname>.+?): (?<msg>[\s\S]+)$""".toRegex()

// 私聊消息
// title: [特别关心]昵称 | [特别关心]昵称 (x条新消息)
// ticker: 昵称: 消息内容
// text: 消息内容

private val privateTitlePattern =
"""^(?<sp>\[特别关心])?(?<nickname>.+?)(?: \((?<num>\d+)条新消息\))?$""".toRegex()


// 关联QQ消息
// title: 关联QQ号 | 关联QQ号 (x条新消息)
// ticker: 关联QQ号-Sender:消息内容
// text: Sender:消息内容

private val bindingTitlePattern =
"""^关联QQ号(?: \((?<num>\d+)条新消息\))?$""".toRegex()

private val bindingTextPattern =
"""^(?<nickname>.+?):(?<msg>[\s\S]+)$""".toRegex()
}

override fun resolveNotification(
tag: Tag,
title: String?,
content: String?,
ticker: String?
): QQNotification? {
if (title.isNullOrEmpty() || content.isNullOrEmpty()) {
return null
}
if (isHidden(title = title, ticker = ticker)) {
return QQNotification.HiddenMessage(tag)
}

if (ticker == null) {
Timber.tag(TAG).i("Ticker is null, skip")
return null
}

tryResolveBindingMsg(tag, title, content)?.also { return it }
tryResolveGroupMsg(tag, title, content, ticker)?.also { return it }
tryResolvePrivateMsg(tag, title, content)?.also { return it }

return null
}

private fun isHidden(title: String?, ticker: String?): Boolean {
return title == HIDE_MESSAGE_TITLE && ticker != null
&& hideMsgTickerPattern.matchEntire(ticker) != null
}

private fun tryResolveGroupMsg(
tag: Tag,
title: String,
content: String,
ticker: String,
): QQNotification? {
if (content.isEmpty() || ticker.isEmpty()) {
return null
}
val tickerGroups = groupMsgPattern.matchEntire(ticker)?.groups ?: return null
val titleGroups = groupTitlePattern.matchEntire(title)?.groups ?: return null
val contentGroups = groupMsgContentPattern.matchEntire(content)?.groups ?: return null
val name = tickerGroups["nickname"]?.value ?: return null
val groupName = titleGroups["group"]?.value ?: return null
val text = contentGroups["msg"]?.value ?: return null
val special = contentGroups["sp"]?.value != null
val num = titleGroups["num"]?.value?.toIntOrNull()

return QQNotification.GroupMessage(
tag = tag,
groupName = groupName,
nickname = name,
message = text,
special = special,
num = num ?: 1,
)
}

private fun tryResolvePrivateMsg(tag: Tag, title: String, content: String): QQNotification? {
if (title.isEmpty() || content.isEmpty()) {
return null
}
val titleGroups = privateTitlePattern.matchEntire(title)?.groups ?: return null
val special = titleGroups["sp"] != null
val name = titleGroups["nickname"]?.value ?: return null
val num = titleGroups["num"]?.value?.toIntOrNull()

return QQNotification.PrivateMessage(
tag = tag,
nickname = name,
message = content,
special = special,
num = num ?: 1,
)
}

private fun tryResolveBindingMsg(
tag: Tag,
title: String,
content: String
): QQNotification? {
val titleGroups = bindingTitlePattern.matchEntire(title)?.groups ?: return null
val textGroups = bindingTextPattern.matchEntire(content)?.groups ?: return null

val sender = textGroups["nickname"]?.value ?: return null
val text = textGroups["msg"]?.value ?: return null
val num = titleGroups["num"]?.value?.toIntOrNull()
return QQNotification.BindingAccountMessage(tag, sender, text, num ?: 1)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
package cc.chenhe.qqnotifyevo.core

import cc.chenhe.qqnotifyevo.utils.Tag
import io.kotest.matchers.booleans.shouldBeFalse
import io.kotest.matchers.booleans.shouldBeTrue
import io.kotest.matchers.equals.shouldBeEqual
import io.kotest.matchers.nulls.shouldNotBeNull
import io.kotest.matchers.types.shouldBeTypeOf
import org.junit.Before
import org.junit.Test

class TimNotificationResolverTest : BaseResolverTest() {
private lateinit var resolver: TimNotificationResolver

@Before
fun setup() {
resolver = TimNotificationResolver()
}

private fun resolve(data: NotificationData): QQNotification? {
return resolver.resolveNotification(
tag = Tag.TIM,
title = data.title,
content = data.content,
ticker = data.ticker,
)
}

// 私聊消息 -––––--––––---––––---––––---––––---––––---––––

@Test
fun private_normal() {
val n = parse("""{"title":"咕咕咕","ticker":"咕咕咕: Hi","content":"Hi"}""")
val r = resolve(n).shouldNotBeNull().shouldBeTypeOf<QQNotification.PrivateMessage>()
r.nickname.shouldBeEqual("咕咕咕")
r.message.shouldBeEqual(n.content!!)
r.num.shouldBeEqual(1)
r.special.shouldBeFalse()
}

@Test
fun private_special() {
val n =
parse("""{"title":"[特别关心]咕咕咕","ticker":"咕咕咕: In memory of the days with another developer cs\nAnd I’m sorry ","content":"In memory of the days with another developer cs\nAnd I’m sorry "}""")
val r = resolve(n).shouldNotBeNull().shouldBeTypeOf<QQNotification.PrivateMessage>()
r.nickname.shouldBeEqual("咕咕咕")
r.message.shouldBeEqual(n.content!!)
r.num.shouldBeEqual(1)
r.special.shouldBeTrue()
}

@Test
fun private_special_MultiMessage() {
val n =
parse("""{"title":"[特别关心]咕咕咕 (2条新消息)","ticker":"咕咕咕: &¥","content":"&¥"}""")
val r = resolve(n).shouldNotBeNull().shouldBeTypeOf<QQNotification.PrivateMessage>()
r.nickname.shouldBeEqual("咕咕咕")
r.message.shouldBeEqual(n.content!!)
r.num.shouldBeEqual(2)
r.special.shouldBeTrue()
}

// 群聊消息 -––––--––––---––––---––––---––––---––––---––––

@Test
fun group_normal() {
val n =
parse("""{"title":"测试群","ticker":"咕咕咕(测试群):Xxx","content":"咕咕咕: Xxx"}""")
val r = resolve(n).shouldNotBeNull().shouldBeTypeOf<QQNotification.GroupMessage>()
r.groupName.shouldBeEqual("测试群")
r.nickname.shouldBeEqual("咕咕咕")
r.message.shouldBeEqual("Xxx")
r.num.shouldBeEqual(1)
r.special.shouldBeFalse()
}

@Test
fun group_multiMessage() {
val n =
parse("""{"title":"测试群 (2条新消息)","ticker":"咕咕咕(测试群):Yyy","content":"咕咕咕: Yyy"}""")
val r = resolve(n).shouldNotBeNull().shouldBeTypeOf<QQNotification.GroupMessage>()
r.groupName.shouldBeEqual("测试群")
r.nickname.shouldBeEqual("咕咕咕")
r.message.shouldBeEqual("Yyy")
r.num.shouldBeEqual(2)
r.special.shouldBeFalse()
}

@Test
fun group_special() {
val n =
parse("""{"title":"测试群","ticker":"咕咕咕(测试群):111","content":"[有关注的内容]咕咕咕: 111"}""")
val r = resolve(n).shouldNotBeNull().shouldBeTypeOf<QQNotification.GroupMessage>()
r.groupName.shouldBeEqual("测试群")
r.nickname.shouldBeEqual("咕咕咕")
r.message.shouldBeEqual("111")
r.num.shouldBeEqual(1)
r.special.shouldBeTrue()
}

@Test
fun group_special_multiMessage() {
val n =
parse("""{"title":"测试群 (2条新消息)","ticker":"咕咕咕(测试群):222","content":"[有关注的内容]咕咕咕: 222"}""")
val r = resolve(n).shouldNotBeNull().shouldBeTypeOf<QQNotification.GroupMessage>()
r.groupName.shouldBeEqual("测试群")
r.nickname.shouldBeEqual("咕咕咕")
r.message.shouldBeEqual("222")
r.num.shouldBeEqual(2)
r.special.shouldBeTrue()
}

// 其他 -––––--––––---––––---––––---––––---––––---––––

@Test
fun hidden() {
val n =
parse("""{"title":"TIM","ticker":"你收到了1条新消息","content":"你收到了1条新消息"}""")
resolve(n).shouldNotBeNull().shouldBeTypeOf<QQNotification.HiddenMessage>()
}


@Test
fun binding_multiMessage_multiLine() {
val n =
parse("""{"title":"关联QQ号 (2条新消息)","ticker":"关联QQ号-\/dev\/urandom:a\nb","content":"\/dev\/urandom:a\nb"}""")
val r = resolve(n).shouldNotBeNull().shouldBeTypeOf<QQNotification.BindingAccountMessage>()
r.sender.shouldBeEqual("/dev/urandom")
r.message.shouldBeEqual("a\nb")
r.num.shouldBeEqual(2)
}
}

0 comments on commit c9bae32

Please sign in to comment.