Skip to content

Commit c9bae32

Browse files
committed
adapt to tim 3.5.5.3198
1 parent 3816266 commit c9bae32

File tree

4 files changed

+319
-3
lines changed

4 files changed

+319
-3
lines changed
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package cc.chenhe.qqnotifyevo.core
2+
3+
import cc.chenhe.qqnotifyevo.utils.Tag
4+
5+
/**
6+
* A [NotificationResolver] that call different implementations based on [Tag].
7+
*/
8+
class DelegateNotificationResolver : NotificationResolver {
9+
private val qqResolver by lazy { QQNotificationResolver() }
10+
private val timResolver by lazy { TimNotificationResolver() }
11+
12+
override fun resolveNotification(
13+
tag: Tag,
14+
title: String?,
15+
content: String?,
16+
ticker: String?
17+
): QQNotification? {
18+
return when (tag) {
19+
Tag.UNKNOWN -> null
20+
Tag.QQ -> qqResolver
21+
Tag.QQ_HD -> qqResolver
22+
Tag.QQ_LITE -> qqResolver
23+
Tag.TIM -> timResolver
24+
}?.run { resolveNotification(tag, title, content, ticker) }
25+
}
26+
}

app/src/main/java/cc/chenhe/qqnotifyevo/core/NotificationProcessor.kt

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ abstract class NotificationProcessor(context: Context, scope: CoroutineScope) {
6868

6969
private val avatarManager =
7070
AvatarManager.get(getAvatarDiskCacheDir(ctx), getAvatarCachePeriod(context))
71+
private val resolver: NotificationResolver = DelegateNotificationResolver()
7172

7273
init {
7374
scope.launch {
@@ -169,12 +170,9 @@ abstract class NotificationProcessor(context: Context, scope: CoroutineScope) {
169170
val original = sbn.notification ?: return null
170171
val tag = getTagFromPackageName(packageName)
171172
if (tag == Tag.UNKNOWN) {
172-
Timber.tag(TAG).d("Unknown tag, skip. pkgName=%s", packageName)
173173
return null
174174
}
175175

176-
val resolver: NotificationResolver = QQNotificationResolver()
177-
178176
return when (val r = resolver.resolveNotification(packageName, tag, sbn)) {
179177
is QQNotification.BindingAccountMessage -> {
180178
val conversation = addMessage(
Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
package cc.chenhe.qqnotifyevo.core
2+
3+
import cc.chenhe.qqnotifyevo.utils.Tag
4+
import timber.log.Timber
5+
6+
/**
7+
* For com.tencent.tim ver 3.5.5.3198 build 1328.
8+
*
9+
* Doesn't support QZone notifications because TIM failed to post anything about QZone.
10+
*/
11+
class TimNotificationResolver : NotificationResolver {
12+
companion object {
13+
private const val TAG = "TimNotificationResolver"
14+
15+
// 隐藏消息详情
16+
// title: TIM
17+
// ticker: 你收到了x条新消息
18+
// text: 你收到了x条新消息
19+
20+
private const val HIDE_MESSAGE_TITLE = "TIM"
21+
private val hideMsgTickerPattern = """^你收到了(?<num>\d+)条新消息$""".toRegex()
22+
23+
// 群聊消息
24+
// ------------- 单个消息
25+
// title: 群名
26+
// ticker: 昵称(群名):消息内容
27+
// text: [有关注的内容]昵称: 消息内容
28+
// ------------- 多个消息
29+
// title: 群名 (x条新消息)
30+
// ticker: 昵称(群名):消息内容
31+
// text: [有关注的内容]昵称: 消息内容
32+
33+
/**
34+
* 匹配群聊消息 Ticker.
35+
*
36+
* 限制:昵称不能包含英文括号 `()`.
37+
*/
38+
private val groupMsgPattern =
39+
"""^(?<nickname>.+?)\((?<group>.+?)\):(?<msg>[\s\S]+)$""".toRegex()
40+
41+
private val groupTitlePattern =
42+
"""^(?<group>.+?)(?: \((?<num>\d+)条新消息\))?$""".toRegex()
43+
44+
/**
45+
* 匹配群聊消息 Content.
46+
*/
47+
private val groupMsgContentPattern =
48+
"""^(?<sp>\[有关注的内容])?(?<nickname>.+?): (?<msg>[\s\S]+)$""".toRegex()
49+
50+
// 私聊消息
51+
// title: [特别关心]昵称 | [特别关心]昵称 (x条新消息)
52+
// ticker: 昵称: 消息内容
53+
// text: 消息内容
54+
55+
private val privateTitlePattern =
56+
"""^(?<sp>\[特别关心])?(?<nickname>.+?)(?: \((?<num>\d+)条新消息\))?$""".toRegex()
57+
58+
59+
// 关联QQ消息
60+
// title: 关联QQ号 | 关联QQ号 (x条新消息)
61+
// ticker: 关联QQ号-Sender:消息内容
62+
// text: Sender:消息内容
63+
64+
private val bindingTitlePattern =
65+
"""^关联QQ号(?: \((?<num>\d+)条新消息\))?$""".toRegex()
66+
67+
private val bindingTextPattern =
68+
"""^(?<nickname>.+?):(?<msg>[\s\S]+)$""".toRegex()
69+
}
70+
71+
override fun resolveNotification(
72+
tag: Tag,
73+
title: String?,
74+
content: String?,
75+
ticker: String?
76+
): QQNotification? {
77+
if (title.isNullOrEmpty() || content.isNullOrEmpty()) {
78+
return null
79+
}
80+
if (isHidden(title = title, ticker = ticker)) {
81+
return QQNotification.HiddenMessage(tag)
82+
}
83+
84+
if (ticker == null) {
85+
Timber.tag(TAG).i("Ticker is null, skip")
86+
return null
87+
}
88+
89+
tryResolveBindingMsg(tag, title, content)?.also { return it }
90+
tryResolveGroupMsg(tag, title, content, ticker)?.also { return it }
91+
tryResolvePrivateMsg(tag, title, content)?.also { return it }
92+
93+
return null
94+
}
95+
96+
private fun isHidden(title: String?, ticker: String?): Boolean {
97+
return title == HIDE_MESSAGE_TITLE && ticker != null
98+
&& hideMsgTickerPattern.matchEntire(ticker) != null
99+
}
100+
101+
private fun tryResolveGroupMsg(
102+
tag: Tag,
103+
title: String,
104+
content: String,
105+
ticker: String,
106+
): QQNotification? {
107+
if (content.isEmpty() || ticker.isEmpty()) {
108+
return null
109+
}
110+
val tickerGroups = groupMsgPattern.matchEntire(ticker)?.groups ?: return null
111+
val titleGroups = groupTitlePattern.matchEntire(title)?.groups ?: return null
112+
val contentGroups = groupMsgContentPattern.matchEntire(content)?.groups ?: return null
113+
val name = tickerGroups["nickname"]?.value ?: return null
114+
val groupName = titleGroups["group"]?.value ?: return null
115+
val text = contentGroups["msg"]?.value ?: return null
116+
val special = contentGroups["sp"]?.value != null
117+
val num = titleGroups["num"]?.value?.toIntOrNull()
118+
119+
return QQNotification.GroupMessage(
120+
tag = tag,
121+
groupName = groupName,
122+
nickname = name,
123+
message = text,
124+
special = special,
125+
num = num ?: 1,
126+
)
127+
}
128+
129+
private fun tryResolvePrivateMsg(tag: Tag, title: String, content: String): QQNotification? {
130+
if (title.isEmpty() || content.isEmpty()) {
131+
return null
132+
}
133+
val titleGroups = privateTitlePattern.matchEntire(title)?.groups ?: return null
134+
val special = titleGroups["sp"] != null
135+
val name = titleGroups["nickname"]?.value ?: return null
136+
val num = titleGroups["num"]?.value?.toIntOrNull()
137+
138+
return QQNotification.PrivateMessage(
139+
tag = tag,
140+
nickname = name,
141+
message = content,
142+
special = special,
143+
num = num ?: 1,
144+
)
145+
}
146+
147+
private fun tryResolveBindingMsg(
148+
tag: Tag,
149+
title: String,
150+
content: String
151+
): QQNotification? {
152+
val titleGroups = bindingTitlePattern.matchEntire(title)?.groups ?: return null
153+
val textGroups = bindingTextPattern.matchEntire(content)?.groups ?: return null
154+
155+
val sender = textGroups["nickname"]?.value ?: return null
156+
val text = textGroups["msg"]?.value ?: return null
157+
val num = titleGroups["num"]?.value?.toIntOrNull()
158+
return QQNotification.BindingAccountMessage(tag, sender, text, num ?: 1)
159+
}
160+
}
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
package cc.chenhe.qqnotifyevo.core
2+
3+
import cc.chenhe.qqnotifyevo.utils.Tag
4+
import io.kotest.matchers.booleans.shouldBeFalse
5+
import io.kotest.matchers.booleans.shouldBeTrue
6+
import io.kotest.matchers.equals.shouldBeEqual
7+
import io.kotest.matchers.nulls.shouldNotBeNull
8+
import io.kotest.matchers.types.shouldBeTypeOf
9+
import org.junit.Before
10+
import org.junit.Test
11+
12+
class TimNotificationResolverTest : BaseResolverTest() {
13+
private lateinit var resolver: TimNotificationResolver
14+
15+
@Before
16+
fun setup() {
17+
resolver = TimNotificationResolver()
18+
}
19+
20+
private fun resolve(data: NotificationData): QQNotification? {
21+
return resolver.resolveNotification(
22+
tag = Tag.TIM,
23+
title = data.title,
24+
content = data.content,
25+
ticker = data.ticker,
26+
)
27+
}
28+
29+
// 私聊消息 -––––--––––---––––---––––---––––---––––---––––
30+
31+
@Test
32+
fun private_normal() {
33+
val n = parse("""{"title":"咕咕咕","ticker":"咕咕咕: Hi","content":"Hi"}""")
34+
val r = resolve(n).shouldNotBeNull().shouldBeTypeOf<QQNotification.PrivateMessage>()
35+
r.nickname.shouldBeEqual("咕咕咕")
36+
r.message.shouldBeEqual(n.content!!)
37+
r.num.shouldBeEqual(1)
38+
r.special.shouldBeFalse()
39+
}
40+
41+
@Test
42+
fun private_special() {
43+
val n =
44+
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 "}""")
45+
val r = resolve(n).shouldNotBeNull().shouldBeTypeOf<QQNotification.PrivateMessage>()
46+
r.nickname.shouldBeEqual("咕咕咕")
47+
r.message.shouldBeEqual(n.content!!)
48+
r.num.shouldBeEqual(1)
49+
r.special.shouldBeTrue()
50+
}
51+
52+
@Test
53+
fun private_special_MultiMessage() {
54+
val n =
55+
parse("""{"title":"[特别关心]咕咕咕 (2条新消息)","ticker":"咕咕咕: &¥","content":"&¥"}""")
56+
val r = resolve(n).shouldNotBeNull().shouldBeTypeOf<QQNotification.PrivateMessage>()
57+
r.nickname.shouldBeEqual("咕咕咕")
58+
r.message.shouldBeEqual(n.content!!)
59+
r.num.shouldBeEqual(2)
60+
r.special.shouldBeTrue()
61+
}
62+
63+
// 群聊消息 -––––--––––---––––---––––---––––---––––---––––
64+
65+
@Test
66+
fun group_normal() {
67+
val n =
68+
parse("""{"title":"测试群","ticker":"咕咕咕(测试群):Xxx","content":"咕咕咕: Xxx"}""")
69+
val r = resolve(n).shouldNotBeNull().shouldBeTypeOf<QQNotification.GroupMessage>()
70+
r.groupName.shouldBeEqual("测试群")
71+
r.nickname.shouldBeEqual("咕咕咕")
72+
r.message.shouldBeEqual("Xxx")
73+
r.num.shouldBeEqual(1)
74+
r.special.shouldBeFalse()
75+
}
76+
77+
@Test
78+
fun group_multiMessage() {
79+
val n =
80+
parse("""{"title":"测试群 (2条新消息)","ticker":"咕咕咕(测试群):Yyy","content":"咕咕咕: Yyy"}""")
81+
val r = resolve(n).shouldNotBeNull().shouldBeTypeOf<QQNotification.GroupMessage>()
82+
r.groupName.shouldBeEqual("测试群")
83+
r.nickname.shouldBeEqual("咕咕咕")
84+
r.message.shouldBeEqual("Yyy")
85+
r.num.shouldBeEqual(2)
86+
r.special.shouldBeFalse()
87+
}
88+
89+
@Test
90+
fun group_special() {
91+
val n =
92+
parse("""{"title":"测试群","ticker":"咕咕咕(测试群):111","content":"[有关注的内容]咕咕咕: 111"}""")
93+
val r = resolve(n).shouldNotBeNull().shouldBeTypeOf<QQNotification.GroupMessage>()
94+
r.groupName.shouldBeEqual("测试群")
95+
r.nickname.shouldBeEqual("咕咕咕")
96+
r.message.shouldBeEqual("111")
97+
r.num.shouldBeEqual(1)
98+
r.special.shouldBeTrue()
99+
}
100+
101+
@Test
102+
fun group_special_multiMessage() {
103+
val n =
104+
parse("""{"title":"测试群 (2条新消息)","ticker":"咕咕咕(测试群):222","content":"[有关注的内容]咕咕咕: 222"}""")
105+
val r = resolve(n).shouldNotBeNull().shouldBeTypeOf<QQNotification.GroupMessage>()
106+
r.groupName.shouldBeEqual("测试群")
107+
r.nickname.shouldBeEqual("咕咕咕")
108+
r.message.shouldBeEqual("222")
109+
r.num.shouldBeEqual(2)
110+
r.special.shouldBeTrue()
111+
}
112+
113+
// 其他 -––––--––––---––––---––––---––––---––––---––––
114+
115+
@Test
116+
fun hidden() {
117+
val n =
118+
parse("""{"title":"TIM","ticker":"你收到了1条新消息","content":"你收到了1条新消息"}""")
119+
resolve(n).shouldNotBeNull().shouldBeTypeOf<QQNotification.HiddenMessage>()
120+
}
121+
122+
123+
@Test
124+
fun binding_multiMessage_multiLine() {
125+
val n =
126+
parse("""{"title":"关联QQ号 (2条新消息)","ticker":"关联QQ号-\/dev\/urandom:a\nb","content":"\/dev\/urandom:a\nb"}""")
127+
val r = resolve(n).shouldNotBeNull().shouldBeTypeOf<QQNotification.BindingAccountMessage>()
128+
r.sender.shouldBeEqual("/dev/urandom")
129+
r.message.shouldBeEqual("a\nb")
130+
r.num.shouldBeEqual(2)
131+
}
132+
}

0 commit comments

Comments
 (0)