-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
4 changed files
with
319 additions
and
3 deletions.
There are no files selected for viewing
26 changes: 26 additions & 0 deletions
26
app/src/main/java/cc/chenhe/qqnotifyevo/core/DelegateNotificationResolver.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) } | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
160 changes: 160 additions & 0 deletions
160
app/src/main/java/cc/chenhe/qqnotifyevo/core/TimNotificationResolver.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} | ||
} |
132 changes: 132 additions & 0 deletions
132
app/src/test/java/cc/chenhe/qqnotifyevo/core/TimNotificationResolverTest.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} | ||
} |