From de522b2dd6fe5937e0a5507630995f62682d2ad3 Mon Sep 17 00:00:00 2001 From: Eyre_S Date: Thu, 22 Aug 2024 16:52:56 +0800 Subject: [PATCH] add bilibili video metadata api --- .../cono/morny/bot/command/Testing.scala | 20 ++++++- .../cono/morny/extra/bilibili/XWebAPI.scala | 38 ++++++++++++ .../morny/extra/bilibili/XWebResponse.scala | 8 +++ .../cono/morny/extra/bilibili/XWebView.scala | 59 +++++++++++++++++++ .../cono/morny/util/circe/Ignore.scala | 15 +++++ 5 files changed, 139 insertions(+), 1 deletion(-) create mode 100644 src/main/scala/cc/sukazyo/cono/morny/extra/bilibili/XWebAPI.scala create mode 100644 src/main/scala/cc/sukazyo/cono/morny/extra/bilibili/XWebResponse.scala create mode 100644 src/main/scala/cc/sukazyo/cono/morny/extra/bilibili/XWebView.scala create mode 100644 src/main/scala/cc/sukazyo/cono/morny/util/circe/Ignore.scala diff --git a/src/main/scala/cc/sukazyo/cono/morny/bot/command/Testing.scala b/src/main/scala/cc/sukazyo/cono/morny/bot/command/Testing.scala index 949fa83..c18c611 100644 --- a/src/main/scala/cc/sukazyo/cono/morny/bot/command/Testing.scala +++ b/src/main/scala/cc/sukazyo/cono/morny/bot/command/Testing.scala @@ -1,11 +1,15 @@ package cc.sukazyo.cono.morny.bot.command import cc.sukazyo.cono.morny.MornyCoeur +import cc.sukazyo.cono.morny.extra.BilibiliForms +import cc.sukazyo.cono.morny.extra.bilibili.XWebAPI import cc.sukazyo.cono.morny.util.tgapi.InputCommand import cc.sukazyo.cono.morny.util.tgapi.TelegramExtensions.Bot.exec import com.pengrad.telegrambot.model.Update import com.pengrad.telegrambot.model.request.ParseMode -import com.pengrad.telegrambot.request.SendMessage +import com.pengrad.telegrambot.request.{SendMessage, SendPhoto} + +import cc.sukazyo.cono.morny.util.tgapi.formatting.TelegramParseEscape.escapeHtml as h import scala.language.postfixOps @@ -16,6 +20,20 @@ class Testing (using coeur: MornyCoeur) extends ISimpleCommand { override def execute (using command: InputCommand, event: Update): Unit = { + val video = BilibiliForms.parse_videoUrl(command.args.mkString(" ")) + val video_info = XWebAPI.get_view(video) + + coeur.account exec new SendPhoto( + event.message.chat.id, + video_info.data.pic + ).replyToMessageId(event.message.messageId) + .caption( + // language=html + s"""${h(video_info.data.title)} + | @${h(video_info.data.owner.name)} + |${h(video_info.data.desc)}""".stripMargin + ).parseMode(ParseMode.HTML) + coeur.account exec new SendMessage( event.message.chat.id, // language=html diff --git a/src/main/scala/cc/sukazyo/cono/morny/extra/bilibili/XWebAPI.scala b/src/main/scala/cc/sukazyo/cono/morny/extra/bilibili/XWebAPI.scala new file mode 100644 index 0000000..7639ef0 --- /dev/null +++ b/src/main/scala/cc/sukazyo/cono/morny/extra/bilibili/XWebAPI.scala @@ -0,0 +1,38 @@ +package cc.sukazyo.cono.morny.extra.bilibili + +import cc.sukazyo.cono.morny.extra.BilibiliForms.BiliVideoId +import cc.sukazyo.cono.morny.util.SttpPublic.mornyBasicRequest +import sttp.client3.{asString, RequestT} +import sttp.client3.okhttp.OkHttpSyncBackend +import sttp.model.Uri + +object XWebAPI { + + private val URL_BASE = "https://api.bilibili.com/x/web-interface" + + private lazy val http_client = OkHttpSyncBackend() + + def get_view (video: BiliVideoId)(using + http_client: sttp.client3.SttpBackend[sttp.client3.Identity, _] = http_client, + basic_request: RequestT[sttp.client3.Empty, Either[String, String], Any] = mornyBasicRequest + ): XWebResponse[XWebView] = { + + val request_url = Uri.unsafeParse(URL_BASE) + .addPath("view") + .addParams("aid" -> video.av.toString) + + val response = basic_request + .get(request_url) + .response(asString.getRight) + .send(http_client) + + val response_body = response.body + + io.circe.parser.parse(response_body) + .toTry.get + .as[XWebResponse[XWebView]] + .toTry.get + + } + +} diff --git a/src/main/scala/cc/sukazyo/cono/morny/extra/bilibili/XWebResponse.scala b/src/main/scala/cc/sukazyo/cono/morny/extra/bilibili/XWebResponse.scala new file mode 100644 index 0000000..8e16a01 --- /dev/null +++ b/src/main/scala/cc/sukazyo/cono/morny/extra/bilibili/XWebResponse.scala @@ -0,0 +1,8 @@ +package cc.sukazyo.cono.morny.extra.bilibili + +case class XWebResponse [T] ( + code: Int, + message: String, + ttl: Int, + data: T +) diff --git a/src/main/scala/cc/sukazyo/cono/morny/extra/bilibili/XWebView.scala b/src/main/scala/cc/sukazyo/cono/morny/extra/bilibili/XWebView.scala new file mode 100644 index 0000000..ea8875d --- /dev/null +++ b/src/main/scala/cc/sukazyo/cono/morny/extra/bilibili/XWebView.scala @@ -0,0 +1,59 @@ +package cc.sukazyo.cono.morny.extra.bilibili + +import cc.sukazyo.cono.morny.util.EpochDateTime.EpochSeconds +import cc.sukazyo.cono.morny.util.circe.Ignore +import io.circe.Codec + +case class XWebView ( + bvid: String, + aid: Long, + videos: Int, + tid: Int, + tname: String, + copyright: Int, + pic: String, + title: String, + pubdate: EpochSeconds, + ctime: EpochSeconds, + desc: String, + desc_v2: List[Ignore], + state: Int, + duration: Int, + forward: Option[Ignore], + mission_id: Option[Ignore], + redirect_url: Option[Ignore], + rights: Option[Ignore], + owner: XWebView.User, + stat: Ignore, + dynamic: String, + cid: Int, + dimension: Ignore, + premiere: Ignore, + teenage_mode: Int, + is_chargeable_season: Boolean, + is_story: Boolean, + no_cache: Boolean, + pages: List[Ignore], + subtitle: Ignore, + staff: Option[List[Ignore]], + is_season_display: Boolean, + use_grab: Option[Ignore], + honor_reply: Option[Ignore], + like_icon: Option[String], + argue_info: Option[Ignore] +) + +object XWebView { + + case class User ( + mid: Long, + name: String, + face: String, + ) + + import io.circe.generic.semiauto.deriveCodec + implicit val codec: Codec[XWebView] = deriveCodec + implicit val codec_User: Codec[User] = deriveCodec + implicit val codec_with_XWebResponse: Codec[XWebResponse[XWebView]] = deriveCodec + +} diff --git a/src/main/scala/cc/sukazyo/cono/morny/util/circe/Ignore.scala b/src/main/scala/cc/sukazyo/cono/morny/util/circe/Ignore.scala new file mode 100644 index 0000000..4414148 --- /dev/null +++ b/src/main/scala/cc/sukazyo/cono/morny/util/circe/Ignore.scala @@ -0,0 +1,15 @@ +package cc.sukazyo.cono.morny.util.circe + +import io.circe.{Codec, HCursor, Json} +import io.circe.Decoder.Result + +case class Ignore () + +object Ignore { + + implicit val codec_ignore: Codec[Ignore] = new Codec[Ignore] { + override def apply (c: HCursor): Result[Ignore] = Right(Ignore()) + override def apply (a: Ignore): Json = Json.Null + } + +}