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
+ }
+
+}