mirror of
https://github.com/Eyre-S/Coeur-Morny-Cono.git
synced 2024-11-23 19:47:38 +08:00
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:
parent
9c4e2e0ec8
commit
02819a9069
21
README.md
21
README.md
@ -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>
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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)
|
||||||
|
))
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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}"
|
||||||
|
))
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
))
|
))
|
||||||
)
|
)
|
||||||
|
@ -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()
|
||||||
|
|
||||||
|
@ -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) {
|
||||||
|
@ -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"
|
@ -0,0 +1,2 @@
|
|||||||
|
normal_message:
|
||||||
|
- "你好,世界!"
|
@ -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
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
}
|
@ -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
|
||||||
|
|
||||||
|
}
|
@ -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
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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 :" - {
|
||||||
|
Loading…
Reference in New Issue
Block a user