cha make social url rewrite now search urls instead of match in queries

- ShareToolBilibili now searches all the urls in query string, include b23.tv share url, av/BV id, video url, then rewrites them all and outputs.
- ShareToolTwitter now searches all the urls instead of matches in the query string.
- ShareToolXhs now uses searchUrls, instead of old match url or search share text. Also supported multiple urls match in one query.
This commit is contained in:
A.C.Sukazyo Eyre 2024-08-12 23:06:41 +08:00
parent 9c4e2e0ec8
commit 02819a9069
Signed by: Eyre_S
GPG Key ID: C17CE40291207874
15 changed files with 424 additions and 92 deletions

View File

@ -17,7 +17,10 @@
[//]: # ([Task Listing][todo] | [~~BBS~~][issues] | [Published][artifact]) [//]: # ([Task Listing][todo] | [~~BBS~~][issues] | [Published][artifact])
[badge_release_img]: https://img.shields.io/github/v/release/Eyre-S/Coeur-Morny-Cono?display_name=release&label=latest&color=#00fa9a [badge_release_img]: https://img.shields.io/github/v/release/Eyre-S/Coeur-Morny-Cono?display_name=release&label=latest&color=#00fa9a
[badge_release_target]: https://mvn.sukazyo.cc/#/releases/cc/sukazyo/morny-coeur [badge_release_target]: https://mvn.sukazyo.cc/#/releases/cc/sukazyo/morny-coeur
[badge_tests_img]: https://img.shields.io/github/actions/workflow/status/Eyre-S/Coeur-Morny-Cono/test?label=Tests&color=dark-green [//]: # (on branch master)
[badge_tests_img]: https://img.shields.io/github/actions/workflow/status/Eyre-S/Coeur-Morny-Cono/test.yml?branch=master&label=Tests
[//]: # (on branch 2.0.0)
[//]: # ([badge_tests_img]: https://img.shields.io/github/actions/workflow/status/Eyre-S/Coeur-Morny-Cono/test.yml?branch=2.0.0&label=Tests%20on%202.0.0)
[badge_snapshot_img]: https://img.shields.io/maven-metadata/v?metadataUrl=https%3A%2F%2Fmvn.sukazyo.cc%2Fsnapshots%2Fcc%2Fsukazyo%2Fmorny-coeur%2Fmaven-metadata.xml&label=snapshots&color=%231e90ff [badge_snapshot_img]: https://img.shields.io/maven-metadata/v?metadataUrl=https%3A%2F%2Fmvn.sukazyo.cc%2Fsnapshots%2Fcc%2Fsukazyo%2Fmorny-coeur%2Fmaven-metadata.xml&label=snapshots&color=%231e90ff
[badge_snapshot_target]: https://mvn.sukazyo.cc/#/snapshots/cc/sukazyo/morny-coeur [badge_snapshot_target]: https://mvn.sukazyo.cc/#/snapshots/cc/sukazyo/morny-coeur
[![GitHub release][badge_release_img]][badge_release_target] [![GitHub release][badge_release_img]][badge_release_target]
@ -46,12 +49,16 @@
[okhttp]: https://square.github.io/okhttp/ [okhttp]: https://square.github.io/okhttp/
[gson]: https://github.com/google/gson [gson]: https://github.com/google/gson
[scalatest]: https://scalatest.org/ [scalatest]: https://scalatest.org/
[messiva]: https://github.com/suk-ws/messiva
[resource-tools]: https://github.com/Eyre-S/ResourceTools
[sttp]: https://sttp.softwaremill.com/
[circe]: https://circe.github.io/circe/
[jsoup]: https://jsoup.org/
[cron-utils]: https://github.com/jmrozanec/cron-utils
[Java Telegram Bot API][tg4j] [Scala 3][scala] | [SpotBugs Annotations][spotbugs] | [ScalaTest] \
[messiva] | [resource-tools] \
[Java Telegram Bot API][tg4j] \
[okhttp] | [Gson][gson] [okhttp] | [sttp] | [Gson] | [circe] | [jsoup] | [cron-utils]
[Scala][scala] | [SpotBugs Annotations][spotbugs] | [ScalaTest][scalatest]
</div> </div>

View File

@ -7,6 +7,7 @@ plugins {
id 'com.github.johnrengelman.shadow' version '8.1.1' id 'com.github.johnrengelman.shadow' version '8.1.1'
id 'com.github.gmazzo.buildconfig' version '4.1.2' id 'com.github.gmazzo.buildconfig' version '4.1.2'
id 'org.ajoberstar.grgit' version '5.2.0' id 'org.ajoberstar.grgit' version '5.2.0'
id "me.champeau.jmh" version "0.7.2"
} }
import org.ajoberstar.grgit.Status import org.ajoberstar.grgit.Status
@ -93,6 +94,7 @@ dependencies {
implementation group: 'io.circe', name: scala('circe-core'), version: lib_circe_v implementation group: 'io.circe', name: scala('circe-core'), version: lib_circe_v
implementation group: 'io.circe', name: scala('circe-generic'), version: lib_circe_v implementation group: 'io.circe', name: scala('circe-generic'), version: lib_circe_v
implementation group: 'io.circe', name: scala('circe-parser'), version: lib_circe_v implementation group: 'io.circe', name: scala('circe-parser'), version: lib_circe_v
implementation group: 'io.circe', name: scala('circe-yaml-v12'), version: '0.16.0'
implementation group: 'org.jsoup', name: 'jsoup', version: lib_jsoup_v implementation group: 'org.jsoup', name: 'jsoup', version: lib_jsoup_v
implementation group: 'com.cronutils', name: 'cron-utils', version: lib_cron_utils_v implementation group: 'com.cronutils', name: 'cron-utils', version: lib_cron_utils_v

View File

@ -0,0 +1,42 @@
package cc.sukazyo.cono.morny.social.xhs;
import cc.sukazyo.cono.morny.extra.xhs.XHSLink;
import org.openjdk.jmh.annotations.*;
@State(Scope.Benchmark)
@Fork(1)
@Warmup(iterations = 5, time = 5)
@Measurement(iterations = 5, time = 5)
public class Benchmark_XHS_MatchOrSearch {
public String SHARE_TEXT_PC = "\"24 【这个长得很像晓美焰的角色到底是谁.. - 正义少女天使酱 | 小红书 - 你的生活指南】 \\uD83D\\uDE06 ghMU496vOiKgPGk \\uD83D\\uDE06 http://xhslink.com/59LkLS\"";
public String NORMAL_TEXTS_SHORT = ".map{ case x => ... }\n" + "相当于\n" + ".map{ f => f match { case x => ... } }";
public String NORMAL_TEXTS_LONG = "关于巴黎奥运开幕式,以下引用一些群友们的话:\n" + "\n" + "这次,跨性别者被禁止参赛的同时,变装皇后被拽上了舞台。马克龙政权真的有权挪用这些解放符号与否我不知道,但国内博主的时评却着实让我从另一个方角疯狂下头。\n" + "\n" + "这些时评每一条都在强调着「正确的秩序应当如何」但这一套关于「什么是正确」的概念反而是非常带有殖民色彩的几乎就是19世纪白人的主流世界观。比如在性的论述里对种族肤色的特别关切「杂交」、对非异性配偶的恐惧之类。这或许也解释了为什么同一群人基本上会认为「欧洲历史部分其实还可以」但是当代社会却让他们这么抓狂。这些社会焦虑反而很白人特色没什么中国本土感。底下那位大哥甚至能直接替基督教天主教被亵渎感到丢脸。\n" + "\n" + "从这个角度来分析的话类似的思想本质上或许不是排外反西方而是有点类似「崖山之后无中华」的精神在是还向往着当初被强制开国时候的19世纪列强社会却发现那个世界已经被颠覆了于是只好退而自诩真正现代文明之精神传人。这反而正是一种殖民病一种被殖民者对成为旧时代殖民者的强烈欲望觉得只有那样才算文明。 在巴黎奥运没有跨性别能够参与[1]的情况下,已有两位顺性别女性运动员被造谣为指派男性[2],鉴跨的最后是伤害所有人。\n" + "transphobia kills everyone\n" + "\n" + "[1] https://www.thenation.com/article/society/trans-athletes-paris-olympics/\n" + "[2] https://x.com/dw_chinese/status/1819189497613242683";
@Benchmark
public scala.collection.immutable.List<XHSLink.ShareLink> searchShareTexts_onMatch () {
return XHSLink.searchShareText(SHARE_TEXT_PC);
}
@Benchmark
public scala.collection.immutable.List<XHSLink.ShareLink> searchShareTexts_onMismatch_shortTexts () {
return XHSLink.searchShareText(NORMAL_TEXTS_SHORT);
}
@Benchmark
public scala.collection.immutable.List<XHSLink.ShareLink> searchShareTexts_onMismatch_longTexts () {
return XHSLink.searchShareText(NORMAL_TEXTS_LONG);
}
@Benchmark
public scala.collection.immutable.List<XHSLink.ShareLink> searchShareLinks_onMatch () {
return XHSLink.searchShareUrl(SHARE_TEXT_PC);
}
@Benchmark
public scala.collection.immutable.List<XHSLink.ShareLink> searchShareLinks_onMismatch_shortTexts () {
return XHSLink.searchShareUrl(NORMAL_TEXTS_SHORT);
}
@Benchmark
public scala.collection.immutable.List<XHSLink.ShareLink> searchShareLinks_onMismatch_longTexts () {
return XHSLink.searchShareUrl(NORMAL_TEXTS_LONG);
}
}

View File

@ -1,14 +1,12 @@
package cc.sukazyo.cono.morny.bot.query package cc.sukazyo.cono.morny.bot.query
import cc.sukazyo.cono.morny.MornyCoeur
import cc.sukazyo.cono.morny.util.tgapi.formatting.NamingUtils.inlineQueryId import cc.sukazyo.cono.morny.util.tgapi.formatting.NamingUtils.inlineQueryId
import cc.sukazyo.cono.morny.Log.{exceptionLog, logger}
import com.pengrad.telegrambot.model.Update import com.pengrad.telegrambot.model.Update
import com.pengrad.telegrambot.model.request.{InlineQueryResultArticle, InputTextMessageContent, ParseMode} import com.pengrad.telegrambot.model.request.{InlineQueryResultArticle, InputTextMessageContent, ParseMode}
import scala.language.postfixOps import scala.language.postfixOps
class ShareToolBilibili (using coeur: MornyCoeur) extends ITelegramQuery { class ShareToolBilibili extends ITelegramQuery {
private val TITLE_BILI_AV = "[bilibili] Share video / av" private val TITLE_BILI_AV = "[bilibili] Share video / av"
private val TITLE_BILI_BV = "[bilibili] Share video / BV" private val TITLE_BILI_BV = "[bilibili] Share video / BV"
@ -21,31 +19,26 @@ class ShareToolBilibili (using coeur: MornyCoeur) extends ITelegramQuery {
if (event.inlineQuery.query == null) return null if (event.inlineQuery.query == null) return null
if (event.inlineQuery.query isBlank) return null if (event.inlineQuery.query isBlank) return null
val content = event.inlineQuery.query
import cc.sukazyo.cono.morny.extra.BilibiliForms.* import cc.sukazyo.cono.morny.extra.BilibiliForms.*
val result: BiliVideoId = val results: List[(String, BiliVideoId)] =
try BiliVideoId.searchIn(content).map(x => (x.toString, x)) ++
parse_videoUrl(event.inlineQuery.query) BiliB23.searchIn(content).map(x => (x.toString, x.toVideoId))
catch case _: IllegalArgumentException =>
try
parse_videoUrl(destructB23Url(event.inlineQuery.query))
catch
case _: IllegalArgumentException =>
return null
case e: IllegalStateException =>
logger error exceptionLog(e)
coeur.daemons.reporter.exception(e)
return null
List( results.flatMap( (_, it) =>
InlineQueryUnit(InlineQueryResultArticle( List(
inlineQueryId(ID_PREFIX_BILI_AV + result.av), TITLE_BILI_AV + result.av, InlineQueryUnit(InlineQueryResultArticle(
InputTextMessageContent(formatShareHTML(result.avLink, result.toAvString)).parseMode(ParseMode HTML) inlineQueryId(ID_PREFIX_BILI_AV + it.av),
)), TITLE_BILI_AV + it.av,
InlineQueryUnit(InlineQueryResultArticle( InputTextMessageContent(formatShareHTML(it.avLink, it.toAvString)).parseMode(ParseMode HTML)
inlineQueryId(ID_PREFIX_BILI_BV + result.bv), TITLE_BILI_BV + result.bv, )),
InputTextMessageContent(formatShareHTML(result.bvLink, result.toBvString)).parseMode(ParseMode HTML) InlineQueryUnit(InlineQueryResultArticle(
)) inlineQueryId(ID_PREFIX_BILI_BV + it.bv),
TITLE_BILI_BV + it.bv,
InputTextMessageContent(formatShareHTML(it.bvLink, it.toBvString)).parseMode(ParseMode HTML)
))
)
) )
} }

View File

@ -20,22 +20,25 @@ class ShareToolTwitter extends ITelegramQuery {
if (event.inlineQuery.query == null) return null if (event.inlineQuery.query == null) return null
twitter.parseTweetUrl(event.inlineQuery.query) match def getQueryTweetId (prefix: String, tweet: TweetUrlInformation): String =
prefix + tweet.hashCode
case Some(TweetUrlInformation(_, _path_data, _, _, _, _)) => def getTweetName (title_prefix: String, tweet: TweetUrlInformation): String =
List( s"$title_prefix ${tweet.screenName}.${tweet.statusId}"
InlineQueryUnit(InlineQueryResultArticle(
inlineQueryId(ID_PREFIX_FX + event.inlineQuery.query), TITLE_FX,
s"https://fxtwitter.com/$_path_data"
)),
InlineQueryUnit(InlineQueryResultArticle(
inlineQueryId(ID_PREFIX_VX+event.inlineQuery.query), TITLE_VX,
s"https://vxtwitter.com/$_path_data"
))
)
case _ => null
twitter.guessTweetUrl(event.inlineQuery.query).flatMap(tweet =>
List(
InlineQueryUnit(InlineQueryResultArticle(
getQueryTweetId(ID_PREFIX_FX, tweet),
getTweetName(TITLE_FX, tweet),
s"https://fxtwitter.com/${tweet.statusPath}"
)),
InlineQueryUnit(InlineQueryResultArticle(
getQueryTweetId(ID_PREFIX_VX, tweet),
getTweetName(TITLE_VX, tweet),
s"https://vxtwitter.com/${tweet.statusPath}"
))
)
)
} }
} }

View File

@ -14,22 +14,22 @@ class ShareToolXhs extends ITelegramQuery {
if inlineQuery.query == null then return null if inlineQuery.query == null then return null
val content = inlineQuery.query val content = inlineQuery.query
val xhsLink: XHSLink = { def getTitle (xhsLink: XHSLink): String = {
XHSLink.matchUrl(content) match s"$TITLE [${xhsLink.exploreId}]"
case Some(matched) => matched match
case xhsLink: XHSLink => xhsLink
case shareLink: XHSLink.ShareLink =>
shareLink.getXhsLink
case None =>
XHSLink.searchShareText(content).map(_.getXhsLink) match
case Some(found) => found
case None => return null
} }
List( val xhsLinks: List[(String, XHSLink)] = {
XHSLink.searchUrls(content).map {
case xhsLink: XHSLink => (xhsLink.toString, xhsLink)
case shareLink: XHSLink.ShareLink =>
(shareLink.toString, shareLink.getXhsLink)
}
}
xhsLinks.map((uniqueId, xhsLink) =>
InlineQueryUnit(InlineQueryResultArticle( InlineQueryUnit(InlineQueryResultArticle(
ID+content.hashCode, ID+uniqueId,
TITLE, getTitle(xhsLink),
xhsLink.link xhsLink.link
)) ))
) )

View File

@ -7,6 +7,7 @@ import sttp.client3.{HttpError, SttpClientException}
import sttp.client3.okhttp.OkHttpSyncBackend import sttp.client3.okhttp.OkHttpSyncBackend
import sttp.model.Uri import sttp.model.Uri
import scala.runtime.stdLibPatches.Predef.assert as fromBv
import scala.util.matching.Regex import scala.util.matching.Regex
object BilibiliForms { object BilibiliForms {
@ -15,23 +16,80 @@ object BilibiliForms {
def withPart (part: Int|Null): BiliVideoId = BiliVideoId(av, bv, part) def withPart (part: Int|Null): BiliVideoId = BiliVideoId(av, bv, part)
def link (useFormat: BiliVideoId.Formats = BiliVideoId.Formats.AV): String = def link (useFormat: BiliVideoId.Formats = BiliVideoId.Formats.AV): String =
val useId: String = useFormat match val useId: String = useFormat match
case BiliVideoId.Formats.AV => avLink case BiliVideoId.Formats.AV => toAvString
case BiliVideoId.Formats.BV => bvLink case BiliVideoId.Formats.BV => toBvString
s"https://www.bilibili.com/video/$useId" + s"https://www.bilibili.com/video/$useId" +
(if part == null then "" else s"?p=$part") (if part == null then "" else s"?p=$part")
def avLink: String = link(BiliVideoId.Formats.AV) def avLink: String = link(BiliVideoId.Formats.AV)
def bvLink: String = link(BiliVideoId.Formats.BV) def bvLink: String = link(BiliVideoId.Formats.BV)
def toAvString: String = s"av$av" def toAvString: String = s"av$av"
def toBvString: String = s"BV$bv" def toBvString: String = s"BV$bv"
object BiliVideoId: object BiliVideoId {
enum Formats: enum Formats:
case AV, BV case AV, BV
def fromAv (av: Long): BiliVideoId = BiliVideoId(av, BiliTool.toBv(av)) def fromAv (av: Long): BiliVideoId = BiliVideoId(av, BiliTool.toBv(av))
def fromBv (bv: String): BiliVideoId = BiliVideoId(BiliTool.toAv(bv), bv) def fromBv (bv: String): BiliVideoId = BiliVideoId(BiliTool.toAv(bv), bv)
def searchIn (texts: String): List[BiliVideoId] =
REGEX_BILI_VIDEO findAllMatchIn texts map {
case Regex.Groups(_url_v, _url_av, _url_bv, _url_param, _url_v_part, _raw_av, _raw_bv) =>
parseBiliUrlParamsToBiliVideoId(_url_v, _url_av, _url_bv, _url_param, _url_v_part, _raw_av, _raw_bv)
case _ => throw IllegalArgumentException("Unexpected tokenize result in BiliVideoId.searchIn")
} toList
def matchUrl (url: String): BiliVideoId =
url match
case REGEX_BILI_VIDEO(_url_v, _url_av, _url_bv, _url_param, _url_v_part, _raw_av, _raw_bv) =>
BiliVideoId.parseBiliUrlParamsToBiliVideoId(_url_v, _url_av, _url_bv, _url_param, _url_v_part, _raw_av, _raw_bv)
case _ => throw IllegalArgumentException(s"not a valid Bilibili video link: $url")
private[BilibiliForms] def parseBiliUrlParamsToBiliVideoId (
_url_v: String, _url_av: String, _url_bv: String,
_url_param: String, _url_v_part: String,
_raw_av: String, _raw_bv: String
): BiliVideoId = {
val av = select(_url_av, _raw_av)
val bv = "1" + select(_url_bv, _raw_bv)
val part_part = if _url_param == null then null else
REGEX_BILI_V_PART_IN_URL_PARAM.findFirstMatchIn(_url_param) match
case Some(part) => part.group(1)
case None => null
val part: Int | Null = if part_part != null then part_part toInt else null
if (av == null) {
assert(bv != null)
BiliVideoId fromBv bv withPart part
} else {
val _av = av.toLong
BiliVideoId fromAv _av withPart part
}
}
}
private val REGEX_BILI_ID = "^((?:av|AV)(\\d{1,16})|(?:bv1|BV1)([A-HJ-NP-Za-km-z1-9]{9}))$"r case class BiliB23 (shareId: String):
def link: String = s"https://b23.tv/$shareId"
def toVideoId: BiliVideoId =
BilibiliForms.parse_videoUrl(BilibiliForms.destructB23Url(this.link))
object BiliB23 {
val REGEX_URL: Regex = "(?:https?://)?(?:www\\.)?b23\\.tv/(\\w{7})(?!\\w)" r
def searchIn (texts: String): List[BiliB23] =
(REGEX_URL findAllMatchIn texts).toList map {
case Regex.Groups(shareId) => BiliB23(shareId)
case _ => throw IllegalArgumentException("Unexpected tokenize result in BiliB23.searchIn")
}
}
private val REGEX_BILI_ID = "((?:av|AV)(\\d{1,16})|(?:bv1|BV1)([A-HJ-NP-Za-km-z1-9]{9}))"r
private val REGEX_BILI_V_PART_IN_URL_PARAM = "(?:&|^)p=(\\d+)"r private val REGEX_BILI_V_PART_IN_URL_PARAM = "(?:&|^)p=(\\d+)"r
private val REGEX_BILI_VIDEO: Regex = "^(?:(?:https?://)?(?:(?:www\\.)?bilibili\\.com(?:/s)?/video/|b23\\.tv/)((?:av|AV)(\\d{1,16})|(?:bv1|BV1)([A-HJ-NP-Za-km-z1-9]{9}))/?(?:\\?((?:p=(\\d+))?.*))?|(?:av|AV)(\\d{1,16})|(?:bv1|BV1)([A-HJ-NP-Za-km-z1-9]{9}))$" r private val REGEX_BILI_VIDEO: Regex = "(?:https?://)?(?:(?:www\\.)?bilibili\\.com(?:/s)?/video/|b23\\.tv/)((?:av|AV)(\\d{1,16})|(?:bv1|BV1)([A-HJ-NP-Za-km-z1-9]{9}))/?(?:\\?((?:p=(\\d+))?.*))?|(?:av|AV)(\\d{1,16})|(?:bv1|BV1)([A-HJ-NP-Za-km-z1-9]{9})"r
/** parse a Bilibili video link to a [[BiliVideoId]] format Bilibili Video Id. /** parse a Bilibili video link to a [[BiliVideoId]] format Bilibili Video Id.
* *
@ -46,27 +104,7 @@ object BilibiliForms {
*/ */
@throws[IllegalArgumentException] @throws[IllegalArgumentException]
def parse_videoUrl (url: String): BiliVideoId = def parse_videoUrl (url: String): BiliVideoId =
url match BiliVideoId.matchUrl(url)
case REGEX_BILI_VIDEO(_url_v, _url_av, _url_bv, _url_param, _url_v_part, _raw_av, _raw_bv) =>
val av = select(_url_av, _raw_av)
val bv = "1" + select(_url_bv, _raw_bv)
val part_part = if _url_param == null then null else
REGEX_BILI_V_PART_IN_URL_PARAM.findFirstMatchIn(_url_param) match
case Some(part) => part.group(1)
case None => null
val part: Int | Null = if part_part != null then part_part toInt else null
if (av == null) {
assert(bv != null)
BiliVideoId fromBv bv withPart part
} else {
val _av = av.toLong
BiliVideoId fromAv _av withPart part
}
case _ => throw IllegalArgumentException(s"not a valid Bilibili video link: $url")
private lazy val httpClient = OkHttpSyncBackend() private lazy val httpClient = OkHttpSyncBackend()

View File

@ -5,6 +5,8 @@ import sttp.client3.okhttp.OkHttpSyncBackend
import sttp.client3.{HttpError, RequestT, SttpClientException} import sttp.client3.{HttpError, RequestT, SttpClientException}
import sttp.model.Uri import sttp.model.Uri
import scala.util.matching.Regex.Groups
case class XHSLink (exploreId: String) { case class XHSLink (exploreId: String) {
def link = def link =
@ -26,18 +28,39 @@ object XHSLink {
case _ => None case _ => None
} }
def searchExplorerUrl (texts: String): List[XHSLink] = {
REGEX_EXPLORER_URL.findAllMatchIn(texts).map {
case Groups(explorerId) => XHSLink(explorerId)
case _ => throw IllegalArgumentException("Unexpected tokenize result in XHSLink.searchExplorerUrl")
}.toList
}
def matchShareUrl (url: String): Option[ShareLink] = { def matchShareUrl (url: String): Option[ShareLink] = {
url match url match
case REGEX_SHARE_URL(shareId) => Some(ShareLink(shareId)) case REGEX_SHARE_URL(shareId) => Some(ShareLink(shareId))
case _ => None case _ => None
} }
def searchShareUrl (texts: String): List[ShareLink] = {
REGEX_SHARE_URL.findAllMatchIn(texts).map {
case Groups(shareId) => ShareLink(shareId)
case _ => throw IllegalArgumentException("Unexpected tokenize result in XHSLink.searchShareUrl")
}.toList
}
def matchUrl (url: String): Option[XHSLink|ShareLink] = { def matchUrl (url: String): Option[XHSLink|ShareLink] = {
matchExplorerUrl(url) orElse matchShareUrl(url) matchExplorerUrl(url) orElse matchShareUrl(url)
} }
def searchShareText (texts: String): Option[ShareLink] = { def searchUrls (texts: String): List[XHSLink|ShareLink] = {
REGEX_SHARE_TEXTS.findFirstMatchIn(texts).map(x => ShareLink(x.group(2))) searchExplorerUrl(texts) ++ searchShareUrl(texts)
}
def searchShareText (texts: String): List[ShareLink] = {
REGEX_SHARE_TEXTS.findAllMatchIn(texts).map {
case Groups(shareId) => ShareLink(shareId)
case _ => throw IllegalArgumentException("Unexpected tokenize result in XHSLink.searchShareText")
}.toList
} }
case class ShareLink (shareId: String) { case class ShareLink (shareId: String) {

View File

@ -0,0 +1,66 @@
without_b23_url:
- content: |
【兔子把化妆品涂在了脸上】 https://www.bilibili.com/video/BV1mB4y1o7dP/?share_source=copy_web&vd_source=653acafde45a589accad0a1287a9c7d8d
with_links:
- raw: "https://www.bilibili.com/video/BV1mB4y1o7dP/?share_source=copy_web&vd_source=653acafde45a589accad0a1287a9c7d8d"
bv: "1mB4y1o7dP"
- content: |
b23.tv/BV1QT421D7Md
绘画型高松灯(确信
with_links:
- raw: "b23.tv/BV1QT421D7Md"
bv: "1QT421D7Md"
- content: |
“那些哈基米史上难以超越的经典镜头” (https://www.bilibili.com/video/av1056285333?p=1)
@松鼠撅鱼 (https://space.bilibili.com/94735961):
鬼畜调教 - 高燃混剪
〰〰〰〰〰〰〰〰〰〰
🔝> @松鼠撅鱼 (https://space.bilibili.com/94735961):
经典镜头离不开@魔法师学徒的玩具 ! 有喜欢神鹰黑手哥便携式飞行器,偏铝酸钠,抛瓦椅毛绒玩具的朋友看看,能不能做哈基米玩具就看他了!https://b23.tv/mall-6PV9N-14Vr1mkj6B6
with_links:
- raw: "https://www.bilibili.com/video/av1056285333?p=1"
bv: "1jH4y1c7R9"
- content: "av112936634811010"
with_links:
- raw: "av112936634811010"
bv: "1DHYjeBEMV"
- content: "BV1Pw4m1k7ot"
with_links:
- raw: "BV1Pw4m1k7ot"
bv: "1Pw4m1k7ot"
with_b23_url:
- content: |
【四十小时三转机全程廉航 纽约回国终极折磨之旅-哔哩哔哩】 https://b23.tv/H6TxLwN
with_links:
- raw: "https://b23.tv/H6TxLwN"
shareId: "H6TxLwN"
bv: "1e4421Q7Hy"
- content: "【2只鸡蛋就能做舒芙蕾欧姆蛋外加奶油培根白蘑菇-哔哩哔哩】 https://b23.tv/0kPCod3"
with_links:
- raw: "https://b23.tv/0kPCod3"
shareId: "0kPCod3"
bv: "1sJ4m1j7Vw"
- content: "https://b23.tv/EbD4M1n"
with_links:
- raw: "https://b23.tv/EbD4M1n"
shareId: "EbD4M1n"
bv: "11KbRebEgp"
- content: "⚡班长又出新歌唱到封号老c都顶唔顺⚡ https://b23.tv/igpw6X4"
with_links:
- raw: "https://b23.tv/igpw6X4"
shareId: "igpw6X4"
bv: "1RT421k7T1"
- content: |
b23.tv/BV1QT421D7Md
绘画型高松灯(确信
“那些哈基米史上难以超越的经典镜头” (https://www.bilibili.com/video/av1056285333?p=1)
@松鼠撅鱼 (https://space.bilibili.com/94735961):
鬼畜调教 - 高燃混剪
〰〰〰〰〰〰〰〰〰〰
🔝> @松鼠撅鱼 (https://space.bilibili.com/94735961):
经典镜头离不开@魔法师学徒的玩具 ! 有喜欢神鹰黑手哥便携式飞行器,偏铝酸钠,抛瓦椅毛绒玩具的朋友看看,能不能做哈基米玩具就看他了!https://b23.tv/mall-6PV9N-14Vr1mkj6B6
with_links:
- raw: "b23.tv/BV1QT421D7Md"
bv: "1QT421D7Md"
- raw: "https://www.bilibili.com/video/av1056285333?p=1"
bv: "1jH4y1c7R9"

View File

@ -0,0 +1,2 @@
normal_message:
- "你好,世界!"

View File

@ -4,11 +4,21 @@ import cc.sukazyo.restools.{ResourceDirectory, ResourcePackage}
import org.scalatest.freespec.AnyFreeSpec import org.scalatest.freespec.AnyFreeSpec
import org.scalatest.matchers.should import org.scalatest.matchers.should
abstract class MornyTests extends AnyFreeSpec with should.Matchers { object MornyTests extends MornyTests.Assets with MornyTests.Keywords {
val assets: ResourceDirectory = trait Assets {
ResourcePackage.get("assets_morny_tests").getDirectory("assets_morny_tests") val assetsPackage: ResourcePackage = ResourcePackage.get("assets_morny_tests")
val assets: ResourceDirectory = assetsPackage.getDirectory("assets_morny_tests")
}
val pending_val = "[not-implemented]" trait Keywords {
val pending_val = "[not-implemented]"
}
} }
abstract class MornyTests
extends AnyFreeSpec
with should.Matchers
with MornyTests.Assets
with MornyTests.Keywords

View File

@ -0,0 +1,31 @@
package cc.sukazyo.cono.morny.test.assets
import cc.sukazyo.cono.morny.test.MornyTests
import io.circe.Codec
import io.circe.generic.semiauto.deriveCodec
object BilibiliAssets {
case class MessagesWithUrls (
without_b23_url: List[MessageData],
with_b23_url: List[MessageData]
)
case class MessageData (
content: String,
with_links: List[InMessageLink]
)
case class InMessageLink (
raw: String,
bv: String,
shareId: Option[String]
)
implicit val codec_MessagesWithUrls: Codec[MessagesWithUrls] = deriveCodec
implicit val codec_MessageData: Codec[MessageData] = deriveCodec
implicit val codec_InMessageLink: Codec[InMessageLink] = deriveCodec
lazy val message_with_urls: MessagesWithUrls =
import io.circe.yaml.v12.parser
parser.parse(MornyTests.assets.getFile("bilibili", "messages_with_urls.yml").readString).toTry.get
.as[MessagesWithUrls].toTry.get
}

View File

@ -0,0 +1,17 @@
package cc.sukazyo.cono.morny.test.assets
import cc.sukazyo.cono.morny.test.MornyTests
import io.circe.Codec
import io.circe.generic.semiauto.deriveCodec
object RandomMessages {
case class TextMessage (normal_message: List[String])
implicit val codec_TextMessage: Codec[TextMessage] = deriveCodec
lazy val text_message: TextMessage =
import io.circe.yaml.v12.parser
parser.parse(MornyTests.assets.getFile("random_message", "text_message.yml").readString).toTry.get
.as[TextMessage].toTry.get
}

View File

@ -0,0 +1,81 @@
package cc.sukazyo.cono.morny.test.bot.query
import cc.sukazyo.cono.morny.bot.query.{InlineQueryUnit, ShareToolBilibili}
import cc.sukazyo.cono.morny.test.MornyTests
import cc.sukazyo.cono.morny.test.assets.{BilibiliAssets, RandomMessages}
import cc.sukazyo.cono.morny.test.assets.BilibiliAssets.InMessageLink
import com.pengrad.telegrambot.model.{InlineQuery, Update}
import com.pengrad.telegrambot.model.request.InlineQueryResultArticle
import org.scalatest.exceptions.TestFailedException
import org.scalatest.tagobjects.{Network, Slow}
class ShareToolBilibiliTest extends MornyTests {
def formatToQueryUpdate (message: String): Update =
val inlineQuery = InlineQuery()
val InlineQuery_query = classOf[InlineQuery].getDeclaredField("query")
InlineQuery_query.setAccessible(true)
InlineQuery_query.set(inlineQuery, message)
val update = Update()
val Update_inlineQuery = classOf[Update].getDeclaredField("inline_query")
Update_inlineQuery.setAccessible(true)
Update_inlineQuery.set(update, inlineQuery)
update
extension (ex: TestFailedException) {
def addedExtraInfo (message: String): TestFailedException =
ex.modifyMessage {
case Some(errLog) => Some(errLog + "\n" + message)
case None => Some(message)
}
}
def withInfos (info: =>String)(tests: =>Any): Any =
try tests
catch case ex: TestFailedException => throw ex.addedExtraInfo(info)
"when parsing texts" - {
def queryResultShouldContains (queryResult: List[InlineQueryUnit[?]], shouldContains: List[InMessageLink]): Any = {
val titles = queryResult.map(x => {
val rx = x.result.asInstanceOf[InlineQueryResultArticle]
val rx_title = classOf[InlineQueryResultArticle].getDeclaredField("title")
rx_title.setAccessible(true)
rx_title.get(rx).asInstanceOf[String]
})
for (shou <- shouldContains)
titles should contain(s"[bilibili] Share video / BV${shou.bv}")
}
"that contains a video id/url should contains it in result" in {
for (messageObject <- BilibiliAssets.message_with_urls.without_b23_url) {
withInfos(s"In Message:\n${messageObject.content.indent(2)}") {
queryResultShouldContains(
ShareToolBilibili().query(formatToQueryUpdate(messageObject.content)),
messageObject.with_links
)
}
}
}
"that contains a b23 video share url should contains it in result" taggedAs (Slow, Network) in {
for (messageObject <- BilibiliAssets.message_with_urls.with_b23_url) {
withInfos(s"In Message:\n${messageObject.content.indent(2)}") {
queryResultShouldContains(
ShareToolBilibili().query(formatToQueryUpdate(messageObject.content)),
messageObject.with_links
)
}
}
}
"that have no bilibili video url should returns null" in {
for (message <- RandomMessages.text_message.normal_message) { withInfos ("In Message:\n" + message.indent(2)) {
ShareToolBilibili().query(formatToQueryUpdate(message)) shouldEqual List.empty
}}
}
}
}

View File

@ -2,6 +2,7 @@ package cc.sukazyo.cono.morny.test.extra
import cc.sukazyo.cono.morny.extra.BilibiliForms.* import cc.sukazyo.cono.morny.extra.BilibiliForms.*
import cc.sukazyo.cono.morny.test.MornyTests import cc.sukazyo.cono.morny.test.MornyTests
import cc.sukazyo.cono.morny.test.assets.BilibiliAssets
import org.scalatest.prop.TableDrivenPropertyChecks import org.scalatest.prop.TableDrivenPropertyChecks
class BilibiliFormsTest extends MornyTests with TableDrivenPropertyChecks { class BilibiliFormsTest extends MornyTests with TableDrivenPropertyChecks {
@ -93,6 +94,22 @@ class BilibiliFormsTest extends MornyTests with TableDrivenPropertyChecks {
} }
"b23.tv share url" - {
"should be get" - {
"from searching in texts" in {
for (messageIt <- BilibiliAssets.message_with_urls.with_b23_url) {
BiliB23.searchIn(messageIt.content) shouldEqual messageIt.with_links
.map(_.shareId.map(BiliB23(_)).orNull)
.filterNot(_ == null)
}
}
}
}
// Due to this url is expirable, I have no energy to update links in time. // Due to this url is expirable, I have no energy to update links in time.
// So I decide to deprecate the tests. // So I decide to deprecate the tests.
// "while destruct b23.tv share link :" - { // "while destruct b23.tv share link :" - {