diff --git a/gradle.properties b/gradle.properties
index a0939eb..f42f3ab 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -5,7 +5,7 @@ MORNY_ARCHIVE_NAME = morny-coeur
MORNY_CODE_STORE = https://github.com/Eyre-S/Coeur-Morny-Cono
MORNY_COMMIT_PATH = https://github.com/Eyre-S/Coeur-Morny-Cono/commit/%s
-VERSION = 1.3.0-dev11.1
+VERSION = 1.3.0-dev12
USE_DELTA = false
VERSION_DELTA =
diff --git a/src/main/scala/cc/sukazyo/cono/morny/bot/command/GetSocial.scala b/src/main/scala/cc/sukazyo/cono/morny/bot/command/GetSocial.scala
index fb2a0c3..0cb9bc7 100644
--- a/src/main/scala/cc/sukazyo/cono/morny/bot/command/GetSocial.scala
+++ b/src/main/scala/cc/sukazyo/cono/morny/bot/command/GetSocial.scala
@@ -2,16 +2,10 @@ package cc.sukazyo.cono.morny.bot.command
import cc.sukazyo.cono.morny.data.TelegramStickers
import cc.sukazyo.cono.morny.util.tgapi.InputCommand
import cc.sukazyo.cono.morny.MornyCoeur
-import cc.sukazyo.cono.morny.extra.{twitter, weibo}
-import cc.sukazyo.cono.morny.extra.twitter.{FXApi, TweetUrlInformation}
+import cc.sukazyo.cono.morny.bot.event.OnGetSocial
import cc.sukazyo.cono.morny.util.tgapi.TelegramExtensions.Bot.exec
-import cc.sukazyo.cono.morny.Log.{exceptionLog, logger}
-import cc.sukazyo.cono.morny.extra.weibo.StatusUrlInfo
import com.pengrad.telegrambot.model.Update
-import com.pengrad.telegrambot.model.request.{InputMedia, InputMediaPhoto, InputMediaVideo, ParseMode}
-import com.pengrad.telegrambot.request.{SendMediaGroup, SendMessage, SendSticker}
-import io.circe.{DecodingFailure, ParsingFailure}
-import sttp.client3.{HttpError, SttpClientException}
+import com.pengrad.telegrambot.request.SendSticker
class GetSocial (using coeur: MornyCoeur) extends ITelegramCommand {
@@ -30,111 +24,8 @@ class GetSocial (using coeur: MornyCoeur) extends ITelegramCommand {
if command.args.length < 1 then { do404(); return }
- var succeed = 0
- twitter.parseTweetUrl(command.args(0)) match
- case None =>
- case Some(TweetUrlInformation(_, _, screenName, statusId, _, _)) =>
- succeed += 1
- try {
- val api = FXApi.Fetch.status(Some(screenName), statusId)
- import cc.sukazyo.cono.morny.util.tgapi.formatting.TelegramParseEscape.escapeHtml as h
- api.tweet match
- case None =>
- coeur.account exec SendMessage(
- event.message.chat.id,
- // language=html
- s"""❌ Fix-Tweet ${api.code}
- |${h(api.message)}""".stripMargin
- ).replyToMessageId(event.message.messageId).parseMode(ParseMode.HTML)
- case Some(tweet) =>
- val content: String =
- // language=html
- s"""⚪️ ${h(tweet.author.name)} @${h(tweet.author.screen_name)}
- |
- |${h(tweet.text)}
- |
- |💬${tweet.replies} 🔗${tweet.retweets} ❤️${tweet.likes}
- |${h(tweet.created_at)}""".stripMargin
- tweet.media match
- case None =>
- coeur.account exec SendMessage(
- event.message.chat.id,
- content
- ).replyToMessageId(event.message.messageId).parseMode(ParseMode.HTML)
- case Some(media) =>
- val mediaGroup: List[InputMedia[?]] =
- (
- media.photos match
- case None => List.empty
- case Some(photos) => for i <- photos yield InputMediaPhoto(i.url)
- ) ::: (
- media.videos match
- case None => List.empty
- case Some(videos) => for i <- videos yield InputMediaVideo(i.url)
- )
- mediaGroup.head.caption(content)
- mediaGroup.head.parseMode(ParseMode.HTML)
- coeur.account exec SendMediaGroup(
- event.message.chat.id,
- mediaGroup:_*
- ).replyToMessageId(event.message.messageId)
- } catch case e: (SttpClientException|ParsingFailure|DecodingFailure) =>
- coeur.account exec SendSticker(
- event.message.chat.id,
- TelegramStickers.ID_NETWORK_ERR
- ).replyToMessageId(event.message.messageId)
- logger error
- "Error on requesting FixTweet API\n" + exceptionLog(e)
- coeur.daemons.reporter.exception(e, "Error on requesting FixTweet API")
-
- weibo.parseWeiboStatusUrl(command.args(0)) match
- case None =>
- case Some(StatusUrlInfo(_, id)) =>
- succeed += 1
- try {
- val api = weibo.MApi.Fetch.statuses_show(id)
- import cc.sukazyo.cono.morny.util.tgapi.formatting.TelegramParseEscape.{cleanupHtml as ch, escapeHtml as h}
- val content =
- // language=html
- s"""🔸${h(api.data.user.screen_name)}
- |
- |${ch(api.data.text)}
- |
- |${h(api.data.created_at)}""".stripMargin
- api.data.pics match
- case None =>
- coeur.account exec SendMessage(
- event.message.chat.id,
- content
- ).replyToMessageId(event.message.messageId).parseMode(ParseMode.HTML)
- case Some(pics) =>
-// val mediaGroup = pics.map(f =>
-// InputMediaPhoto(weibo.PicUrl(weibo.randomPicCdn, "large", f.pid).toUrl))
- val mediaGroup = pics.map(f => InputMediaPhoto(weibo.MApi.Fetch.pic(f.large.url)))
- mediaGroup.head.caption(content)
- mediaGroup.head.parseMode(ParseMode.HTML)
- coeur.account exec SendMediaGroup(
- event.message.chat.id,
- mediaGroup:_*
- ).replyToMessageId(event.message.messageId)
- } catch
- case e: HttpError[?] =>
- coeur.account exec SendMessage(
- event.message.chat.id,
- // language=html
- s"""Weibo Request Error ${e.statusCode}
- |
${e.body}
""".stripMargin
- ).replyToMessageId(event.message.messageId).parseMode(ParseMode.HTML)
- case e: (SttpClientException|ParsingFailure|DecodingFailure) =>
- coeur.account exec SendSticker(
- event.message.chat.id,
- TelegramStickers.ID_NETWORK_ERR
- ).replyToMessageId(event.message.messageId)
- logger error
- "Error on requesting Weibo m.API\n" + exceptionLog(e)
- coeur.daemons.reporter.exception(e, "Error on requesting Weibo m.API")
-
- if succeed == 0 then do404()
+ if !OnGetSocial.tryFetchSocial(command.args(0))(using event.message.chat.id, event.message.messageId) then
+ do404()
}
diff --git a/src/main/scala/cc/sukazyo/cono/morny/bot/event/MornyEventListeners.scala b/src/main/scala/cc/sukazyo/cono/morny/bot/event/MornyEventListeners.scala
index e3a6d1f..9e2d674 100644
--- a/src/main/scala/cc/sukazyo/cono/morny/bot/event/MornyEventListeners.scala
+++ b/src/main/scala/cc/sukazyo/cono/morny/bot/event/MornyEventListeners.scala
@@ -17,6 +17,7 @@ class MornyEventListeners (using manager: EventListenerManager) (using coeur: Mo
OnUserSlashAction(),
OnCallMe(),
OnCallMsgSend(),
+ OnGetSocial(),
OnMedicationNotifyApply(),
OnEventHackHandle()
)
diff --git a/src/main/scala/cc/sukazyo/cono/morny/bot/event/OnGetSocial.scala b/src/main/scala/cc/sukazyo/cono/morny/bot/event/OnGetSocial.scala
new file mode 100644
index 0000000..eeae553
--- /dev/null
+++ b/src/main/scala/cc/sukazyo/cono/morny/bot/event/OnGetSocial.scala
@@ -0,0 +1,93 @@
+package cc.sukazyo.cono.morny.bot.event
+
+import cc.sukazyo.cono.morny.MornyCoeur
+import cc.sukazyo.cono.morny.bot.api.{EventEnv, EventListener}
+import cc.sukazyo.cono.morny.bot.event.OnGetSocial.tryFetchSocial
+import cc.sukazyo.cono.morny.data.TelegramStickers
+import cc.sukazyo.cono.morny.extra.{twitter, weibo}
+import cc.sukazyo.cono.morny.util.tgapi.TelegramExtensions.Bot.exec
+import cc.sukazyo.cono.morny.Log.{exceptionLog, logger}
+import cc.sukazyo.cono.morny.data.social.{SocialTwitterParser, SocialWeiboParser}
+import com.pengrad.telegrambot.model.Chat
+import com.pengrad.telegrambot.model.request.ParseMode
+import com.pengrad.telegrambot.request.{SendMessage, SendSticker}
+
+class OnGetSocial (using coeur: MornyCoeur) extends EventListener {
+
+ override def onMessage (using event: EventEnv): Unit = {
+ import event.update.message as messageEvent
+
+ if messageEvent.chat.`type` != Chat.Type.Private then return;
+ if messageEvent.text == null then return;
+
+ if tryFetchSocial(messageEvent.text)(using messageEvent.chat.id, messageEvent.messageId) then
+ event.setEventOk
+
+ }
+
+}
+
+object OnGetSocial {
+
+ /** Try fetch from url from input and output fetched social content.
+ *
+ * @param text input text, maybe a social url.
+ * @param replyChat chat that should be output to.
+ * @param replyToMessage message that should be reply to.
+ * @param coeur [[MornyCoeur]] instance for executing Telegram function.
+ * @return [[true]] if fetched social content and sent something out.
+ */
+ def tryFetchSocial (text: String)(using replyChat: Long, replyToMessage: Int)(using coeur: MornyCoeur): Boolean = {
+ val _text = text.trim
+
+ var succeed = 0
+
+ import io.circe.{DecodingFailure, ParsingFailure}
+ import sttp.client3.{HttpError, SttpClientException}
+ import twitter.{FXApi, TweetUrlInformation}
+ import weibo.{MApi, StatusUrlInfo}
+ twitter.parseTweetUrl(_text) match
+ case None =>
+ case Some(TweetUrlInformation(_, _, screenName, statusId, _, _)) =>
+ succeed += 1
+ try {
+ val api = FXApi.Fetch.status(Some(screenName), statusId)
+ SocialTwitterParser.parseFXTweet(api).outputToTelegram
+ } catch case e: (SttpClientException | ParsingFailure | DecodingFailure) =>
+ coeur.account exec SendSticker(
+ replyChat,
+ TelegramStickers.ID_NETWORK_ERR
+ ).replyToMessageId(replyToMessage)
+ logger error
+ "Error on requesting FixTweet API\n" + exceptionLog(e)
+ coeur.daemons.reporter.exception(e, "Error on requesting FixTweet API")
+
+ weibo.parseWeiboStatusUrl(_text) match
+ case None =>
+ case Some(StatusUrlInfo(_, id)) =>
+ succeed += 1
+ try {
+ val api = MApi.Fetch.statuses_show(id)
+ SocialWeiboParser.parseMStatus(api).outputToTelegram
+ } catch
+ case e: HttpError[?] =>
+ coeur.account exec SendMessage(
+ replyChat,
+ // language=html
+ s"""Weibo Request Error ${e.statusCode}
+ |${e.body}
""".stripMargin
+ ).replyToMessageId(replyToMessage).parseMode(ParseMode.HTML)
+ case e: (SttpClientException | ParsingFailure | DecodingFailure) =>
+ coeur.account exec SendSticker(
+ replyChat,
+ TelegramStickers.ID_NETWORK_ERR
+ ).replyToMessageId(replyToMessage)
+ logger error
+ "Error on requesting Weibo m.API\n" + exceptionLog(e)
+ coeur.daemons.reporter.exception(e, "Error on requesting Weibo m.API")
+
+ succeed > 0
+
+ }
+
+}
diff --git a/src/main/scala/cc/sukazyo/cono/morny/bot/query/MornyQueries.scala b/src/main/scala/cc/sukazyo/cono/morny/bot/query/MornyQueries.scala
index 11c6a12..f0436ca 100644
--- a/src/main/scala/cc/sukazyo/cono/morny/bot/query/MornyQueries.scala
+++ b/src/main/scala/cc/sukazyo/cono/morny/bot/query/MornyQueries.scala
@@ -12,7 +12,8 @@ class MornyQueries (using MornyCoeur) {
RawText(),
MyInformation(),
ShareToolTwitter(),
- ShareToolBilibili()
+ ShareToolBilibili(),
+ ShareToolSocialContent()
)
def query (event: Update): List[InlineQueryUnit[_]] = {
diff --git a/src/main/scala/cc/sukazyo/cono/morny/bot/query/ShareToolSocialContent.scala b/src/main/scala/cc/sukazyo/cono/morny/bot/query/ShareToolSocialContent.scala
new file mode 100644
index 0000000..60912e7
--- /dev/null
+++ b/src/main/scala/cc/sukazyo/cono/morny/bot/query/ShareToolSocialContent.scala
@@ -0,0 +1,43 @@
+package cc.sukazyo.cono.morny.bot.query
+import cc.sukazyo.cono.morny.data.social.{SocialTwitterParser, SocialWeiboParser}
+import cc.sukazyo.cono.morny.extra.{twitter, weibo}
+import cc.sukazyo.cono.morny.extra.twitter.{FXApi, TweetUrlInformation}
+import cc.sukazyo.cono.morny.extra.weibo.{MApi, StatusUrlInfo}
+import com.pengrad.telegrambot.model.Update
+
+class ShareToolSocialContent extends ITelegramQuery {
+
+ override def query (event: Update): List[InlineQueryUnit[_]] | Null = {
+
+ val _queryRaw = event.inlineQuery.query
+ val query =
+ _queryRaw.trim match
+ case _startsWithTag if _startsWithTag startsWith "get " =>
+ (_startsWithTag drop 4)trim
+ case _endsWithTag if _endsWithTag endsWith " get" =>
+ (_endsWithTag dropRight 4)trim
+ case _ => return null
+
+ (
+ twitter.parseTweetUrl(query) match
+ case Some(TweetUrlInformation(_, statusPath, _, statusId, _, _)) =>
+ SocialTwitterParser.parseFXTweet(FXApi.Fetch.status(Some(statusPath), statusId))
+ .genInlineQueryResults(using
+ "morny/share/tweet/content", statusId,
+ "Twitter Tweet Content"
+ )
+ case None => Nil
+ ) ::: (
+ weibo.parseWeiboStatusUrl(query) match
+ case Some(StatusUrlInfo(_, id)) =>
+ SocialWeiboParser.parseMStatus(MApi.Fetch.statuses_show(id))
+ .genInlineQueryResults(using
+ "morny/share/weibo/status/content", id,
+ "Weibo Content"
+ )
+ case None => Nil
+ ) ::: Nil
+
+ }
+
+}
diff --git a/src/main/scala/cc/sukazyo/cono/morny/data/social/SocialContent.scala b/src/main/scala/cc/sukazyo/cono/morny/data/social/SocialContent.scala
new file mode 100644
index 0000000..84bcf80
--- /dev/null
+++ b/src/main/scala/cc/sukazyo/cono/morny/data/social/SocialContent.scala
@@ -0,0 +1,102 @@
+package cc.sukazyo.cono.morny.data.social
+
+import cc.sukazyo.cono.morny.data.social.SocialContent.{SocialMedia, SocialMediaType, SocialMediaWithUrl}
+import cc.sukazyo.cono.morny.data.social.SocialContent.SocialMediaType.{Photo, Video}
+import cc.sukazyo.cono.morny.MornyCoeur
+import cc.sukazyo.cono.morny.bot.query.InlineQueryUnit
+import cc.sukazyo.cono.morny.util.tgapi.TelegramExtensions.Bot.exec
+import cc.sukazyo.cono.morny.util.tgapi.formatting.NamingUtils.inlineQueryId
+import com.pengrad.telegrambot.model.request.*
+import com.pengrad.telegrambot.request.{SendMediaGroup, SendMessage}
+
+/** Model of social networks' status. for example twitter tweet or
+ * weibo status.
+ *
+ * Can be output to Telegram.
+ *
+ * @param text_html Formatted HTML output of the status that can be output
+ * directly to Telegram. Normally will contains metadata
+ * like status' author or like count etc.
+ * @param medias Status attachment medias.
+ * @param medias_mosaic Mosaic version of status medias. Will be used when
+ * the output API doesn't support multiple medias like
+ * Telegram inline API. This value is depends on the specific
+ * backend parser/formatter implementation.
+ * @param thumbnail Medias' thumbnail. Will be used when the output API required
+ * a thumbnail. This value is depends on the specific backend
+ * parser/formatter implementation.
+ */
+case class SocialContent (
+ text_html: String,
+ medias: List[SocialMedia],
+ medias_mosaic: Option[SocialMedia] = None,
+ thumbnail: Option[SocialMedia] = None
+) {
+
+ def thumbnailOrElse[T] (orElse: T): String | T =
+ thumbnail match
+ case Some(x) if x.isInstanceOf[SocialMediaWithUrl] && x.t == Photo =>
+ x.asInstanceOf[SocialMediaWithUrl].url
+ case _ => orElse
+
+ def outputToTelegram (using replyChat: Long, replyToMessage: Int)(using coeur: MornyCoeur): Unit = {
+ if medias isEmpty then
+ coeur.account exec
+ SendMessage(replyChat, text_html)
+ .parseMode(ParseMode.HTML)
+ .replyToMessageId(replyToMessage)
+ else
+ val mediaGroup = medias.map(f => f.genTelegramInputMedia)
+ mediaGroup.head.caption(text_html)
+ mediaGroup.head.parseMode(ParseMode.HTML)
+ coeur.account exec
+ SendMediaGroup(replyChat, mediaGroup: _*)
+ .replyToMessageId(replyToMessage)
+ }
+
+ def genInlineQueryResults (using id_head: String, id_param: Any, name: String): List[InlineQueryUnit[?]] = {
+ (
+ if (this.medias.length == 1) && (this.medias.head.t == Photo) && this.medias.head.isInstanceOf[SocialMediaWithUrl] then
+ InlineQueryUnit(InlineQueryResultPhoto(
+ inlineQueryId(s"[$id_head/photo/0]$id_param"),
+ this.medias.head.asInstanceOf[SocialMediaWithUrl].url,
+ thumbnailOrElse(this.medias.head.asInstanceOf[SocialMediaWithUrl].url)
+ ).title(s"$name").caption(text_html).parseMode(ParseMode.HTML)) :: Nil
+ else if (this.medias_mosaic nonEmpty) && (medias_mosaic.get.t == Photo) && medias_mosaic.get.isInstanceOf[SocialMediaWithUrl] then
+ InlineQueryUnit(InlineQueryResultPhoto(
+ inlineQueryId(s"[$id_head/photo/mosaic]$id_param"),
+ medias_mosaic.get.asInstanceOf[SocialMediaWithUrl].url,
+ thumbnailOrElse(medias_mosaic.get.asInstanceOf[SocialMediaWithUrl].url)
+ ).title(s"$name").caption(text_html).parseMode(ParseMode.HTML)) :: Nil
+ else
+ InlineQueryUnit(InlineQueryResultArticle(
+ inlineQueryId(s"[$id_head/text]$id_param"), s"$name",
+ InputTextMessageContent(this.text_html).parseMode(ParseMode.HTML)
+ )) :: Nil
+ ) ::: Nil
+ }
+
+}
+
+object SocialContent {
+
+ enum SocialMediaType:
+ case Photo
+ case Video
+ sealed trait SocialMedia(val t: SocialMediaType) {
+ def genTelegramInputMedia: InputMedia[?]
+ }
+ case class SocialMediaWithUrl (url: String)(t: SocialMediaType) extends SocialMedia(t) {
+ override def genTelegramInputMedia: InputMedia[_] =
+ t match
+ case Photo => InputMediaPhoto(url)
+ case Video => InputMediaVideo(url)
+ }
+ case class SocialMediaWithBytesData (data: Array[Byte])(t: SocialMediaType) extends SocialMedia(t) {
+ override def genTelegramInputMedia: InputMedia[_] =
+ t match
+ case Photo => InputMediaPhoto(data)
+ case Video => InputMediaVideo(data)
+ }
+
+}
diff --git a/src/main/scala/cc/sukazyo/cono/morny/data/social/SocialTwitterParser.scala b/src/main/scala/cc/sukazyo/cono/morny/data/social/SocialTwitterParser.scala
new file mode 100644
index 0000000..21e2da9
--- /dev/null
+++ b/src/main/scala/cc/sukazyo/cono/morny/data/social/SocialTwitterParser.scala
@@ -0,0 +1,52 @@
+package cc.sukazyo.cono.morny.data.social
+
+import cc.sukazyo.cono.morny.data.social.SocialContent.{SocialMedia, SocialMediaWithUrl}
+import cc.sukazyo.cono.morny.data.social.SocialContent.SocialMediaType.{Photo, Video}
+import cc.sukazyo.cono.morny.extra.twitter.FXApi
+import cc.sukazyo.cono.morny.util.tgapi.formatting.TelegramParseEscape.escapeHtml as h
+
+object SocialTwitterParser {
+
+ def parseFXTweet (api: FXApi): SocialContent = {
+ api.tweet match
+ case None =>
+ SocialContent(
+ // language=html
+ s"""❌ Fix-Tweet ${api.code}
+ |${h(api.message)}""".stripMargin,
+ Nil
+ )
+ case Some(tweet) =>
+ val content: String =
+ // language=html
+ s"""⚪️ ${h(tweet.author.name)} @${h(tweet.author.screen_name)}
+ |
+ |${h(tweet.text)}
+ |
+ |💬${tweet.replies} 🔗${tweet.retweets} ❤️${tweet.likes}
+ |${h(tweet.created_at)}""".stripMargin
+ tweet.media match
+ case None =>
+ SocialContent(content, Nil)
+ case Some(media) =>
+ val mediaGroup: List[SocialMedia] =
+ (
+ media.photos match
+ case None => List.empty
+ case Some(photos) => for i <- photos yield SocialMediaWithUrl(i.url)(Photo)
+ ) ::: (
+ media.videos match
+ case None => List.empty
+ case Some(videos) => for i <- videos yield SocialMediaWithUrl(i.url)(Video)
+ )
+ val thumbnail =
+ if media.videos.nonEmpty then
+ Some(SocialMediaWithUrl(media.videos.get.head.thumbnail_url)(Photo))
+ else None
+ val mediaMosaic = media.mosaic match
+ case Some(mosaic) => Some(SocialMediaWithUrl(mosaic.formats.jpeg)(Photo))
+ case None => None
+ SocialContent(content, mediaGroup, mediaMosaic, thumbnail)
+ }
+
+}
diff --git a/src/main/scala/cc/sukazyo/cono/morny/data/social/SocialWeiboParser.scala b/src/main/scala/cc/sukazyo/cono/morny/data/social/SocialWeiboParser.scala
new file mode 100644
index 0000000..44b9482
--- /dev/null
+++ b/src/main/scala/cc/sukazyo/cono/morny/data/social/SocialWeiboParser.scala
@@ -0,0 +1,29 @@
+package cc.sukazyo.cono.morny.data.social
+
+import cc.sukazyo.cono.morny.data.social.SocialContent.SocialMediaType.Photo
+import cc.sukazyo.cono.morny.data.social.SocialContent.SocialMediaWithBytesData
+import cc.sukazyo.cono.morny.extra.weibo.{genWeiboStatusUrl, MApi, MStatus, StatusUrlInfo}
+import cc.sukazyo.cono.morny.util.tgapi.formatting.TelegramParseEscape.{cleanupHtml as ch, escapeHtml as h}
+import io.circe.{DecodingFailure, ParsingFailure}
+import sttp.client3.{HttpError, SttpClientException}
+
+object SocialWeiboParser {
+
+ @throws[HttpError[?] | SttpClientException | ParsingFailure | DecodingFailure]
+ def parseMStatus (api: MApi[MStatus]): SocialContent = {
+ val content =
+ // language=html
+ s"""🔸${h(api.data.user.screen_name)}
+ |
+ |${ch(api.data.text)}
+ |
+ |${h(api.data.created_at)}""".stripMargin
+ api.data.pics match
+ case None =>
+ SocialContent(content, Nil)
+ case Some(pics) =>
+ val mediaGroup = pics.map(f => SocialMediaWithBytesData(MApi.Fetch.pic(f.large.url))(Photo))
+ SocialContent(content, mediaGroup)
+ }
+
+}