mirror of
https://github.com/Eyre-S/Coeur-Morny-Cono.git
synced 2024-11-25 04:27:41 +08:00
change /tweet to /get and added support for weibo content
This commit is contained in:
parent
d602e1b366
commit
a9767ec1b0
@ -92,6 +92,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: 'org.jsoup', name: 'jsoup', version: '1.16.2'
|
||||||
implementation group: 'com.cronutils', name: 'cron-utils', version: lib_cron_utils_v
|
implementation group: 'com.cronutils', name: 'cron-utils', version: lib_cron_utils_v
|
||||||
|
|
||||||
// used for disable slf4j
|
// used for disable slf4j
|
||||||
|
@ -5,7 +5,7 @@ MORNY_ARCHIVE_NAME = morny-coeur
|
|||||||
MORNY_CODE_STORE = https://github.com/Eyre-S/Coeur-Morny-Cono
|
MORNY_CODE_STORE = https://github.com/Eyre-S/Coeur-Morny-Cono
|
||||||
MORNY_COMMIT_PATH = https://github.com/Eyre-S/Coeur-Morny-Cono/commit/%s
|
MORNY_COMMIT_PATH = https://github.com/Eyre-S/Coeur-Morny-Cono/commit/%s
|
||||||
|
|
||||||
VERSION = 1.3.0-dev10
|
VERSION = 1.3.0-dev11
|
||||||
|
|
||||||
USE_DELTA = false
|
USE_DELTA = false
|
||||||
VERSION_DELTA =
|
VERSION_DELTA =
|
||||||
|
@ -1,20 +1,23 @@
|
|||||||
package cc.sukazyo.cono.morny.bot.command
|
package cc.sukazyo.cono.morny.bot.command
|
||||||
import cc.sukazyo.cono.morny.data.{twitter, TelegramStickers}
|
import cc.sukazyo.cono.morny.data.{twitter, weibo, TelegramStickers}
|
||||||
import cc.sukazyo.cono.morny.util.tgapi.InputCommand
|
import cc.sukazyo.cono.morny.util.tgapi.InputCommand
|
||||||
import cc.sukazyo.cono.morny.MornyCoeur
|
import cc.sukazyo.cono.morny.MornyCoeur
|
||||||
import cc.sukazyo.cono.morny.data.twitter.{FXApi, TweetUrlInformation}
|
import cc.sukazyo.cono.morny.data.twitter.{FXApi, TweetUrlInformation}
|
||||||
import cc.sukazyo.cono.morny.util.tgapi.TelegramExtensions.Bot.exec
|
import cc.sukazyo.cono.morny.util.tgapi.TelegramExtensions.Bot.exec
|
||||||
import cc.sukazyo.cono.morny.Log.{exceptionLog, logger}
|
import cc.sukazyo.cono.morny.Log.{exceptionLog, logger}
|
||||||
|
import cc.sukazyo.cono.morny.data.weibo.StatusUrlInfo
|
||||||
import com.pengrad.telegrambot.model.Update
|
import com.pengrad.telegrambot.model.Update
|
||||||
import com.pengrad.telegrambot.model.request.{InputMedia, InputMediaPhoto, InputMediaVideo, ParseMode}
|
import com.pengrad.telegrambot.model.request.{InputMedia, InputMediaPhoto, InputMediaVideo, ParseMode}
|
||||||
import com.pengrad.telegrambot.request.{SendMediaGroup, SendMessage, SendSticker}
|
import com.pengrad.telegrambot.request.{SendMediaGroup, SendMessage, SendSticker}
|
||||||
|
import io.circe.{DecodingFailure, ParsingFailure}
|
||||||
|
import sttp.client3.{HttpError, SttpClientException}
|
||||||
|
|
||||||
class Tweet (using coeur: MornyCoeur) extends ITelegramCommand {
|
class GetSocial (using coeur: MornyCoeur) extends ITelegramCommand {
|
||||||
|
|
||||||
override val name: String = "tweet"
|
override val name: String = "get"
|
||||||
override val aliases: Array[ICommandAlias] | Null = null
|
override val aliases: Array[ICommandAlias] | Null = null
|
||||||
override val paramRule: String = "<tweet-url>"
|
override val paramRule: String = "<tweet-url|weibo-status-url>"
|
||||||
override val description: String = "获取 Twitter(X) Tweet 内容"
|
override val description: String = "从社交媒体分享链接获取其内容"
|
||||||
|
|
||||||
override def execute (using command: InputCommand, event: Update): Unit = {
|
override def execute (using command: InputCommand, event: Update): Unit = {
|
||||||
|
|
||||||
@ -26,9 +29,11 @@ class Tweet (using coeur: MornyCoeur) extends ITelegramCommand {
|
|||||||
|
|
||||||
if command.args.length < 1 then { do404(); return }
|
if command.args.length < 1 then { do404(); return }
|
||||||
|
|
||||||
|
var succeed = 0
|
||||||
twitter.parseTweetUrl(command.args(0)) match
|
twitter.parseTweetUrl(command.args(0)) match
|
||||||
case None => do404()
|
case None =>
|
||||||
case Some(TweetUrlInformation(_, _, screenName, statusId, _, _)) =>
|
case Some(TweetUrlInformation(_, _, screenName, statusId, _, _)) =>
|
||||||
|
succeed += 1
|
||||||
try {
|
try {
|
||||||
val api = FXApi.Fetch.status(Some(screenName), statusId)
|
val api = FXApi.Fetch.status(Some(screenName), statusId)
|
||||||
import cc.sukazyo.cono.morny.util.tgapi.formatting.TelegramParseEscape.escapeHtml as h
|
import cc.sukazyo.cono.morny.util.tgapi.formatting.TelegramParseEscape.escapeHtml as h
|
||||||
@ -72,15 +77,64 @@ class Tweet (using coeur: MornyCoeur) extends ITelegramCommand {
|
|||||||
event.message.chat.id,
|
event.message.chat.id,
|
||||||
mediaGroup:_*
|
mediaGroup:_*
|
||||||
).replyToMessageId(event.message.messageId)
|
).replyToMessageId(event.message.messageId)
|
||||||
} catch case e: Exception =>
|
} catch case e: (SttpClientException|ParsingFailure|DecodingFailure) =>
|
||||||
coeur.account exec SendSticker(
|
coeur.account exec SendSticker(
|
||||||
event.message.chat.id,
|
event.message.chat.id,
|
||||||
TelegramStickers.ID_NETWORK_ERR
|
TelegramStickers.ID_NETWORK_ERR
|
||||||
).replyToMessageId(event.message.messageId)
|
).replyToMessageId(event.message.messageId)
|
||||||
logger attention
|
logger error
|
||||||
"Error on requesting FixTweet API\n" + exceptionLog(e)
|
"Error on requesting FixTweet API\n" + exceptionLog(e)
|
||||||
coeur.daemons.reporter.exception(e, "Error on requesting FixTweet API")
|
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"""🔸<b><a href="${api.data.user.profile_url}">${h(api.data.user.screen_name)}</a></b>
|
||||||
|
|
|
||||||
|
|${ch(api.data.text)}
|
||||||
|
|
|
||||||
|
|<i><a href="${weibo.genWeiboStatusUrl(StatusUrlInfo(api.data.user.id.toString, api.data.id))}">${h(api.data.created_at)}</a></i>""".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 <code>${e.statusCode}</code>
|
||||||
|
|<pre><code>${e.body}</code></pre>""".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()
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -44,7 +44,7 @@ class MornyCommands (using coeur: MornyCoeur) {
|
|||||||
$IP186Query.Whois,
|
$IP186Query.Whois,
|
||||||
Encryptor(),
|
Encryptor(),
|
||||||
MornyOldJrrp(),
|
MornyOldJrrp(),
|
||||||
Tweet(),
|
GetSocial(),
|
||||||
|
|
||||||
$MornyManagers.SaveData,
|
$MornyManagers.SaveData,
|
||||||
$MornyInformation,
|
$MornyInformation,
|
||||||
|
@ -91,18 +91,16 @@ object FXApi {
|
|||||||
@throws[SttpClientException|ParsingFailure|DecodingFailure]
|
@throws[SttpClientException|ParsingFailure|DecodingFailure]
|
||||||
def status (screen_name: Option[String], id: String, translate_to: Option[String] = None): FXApi =
|
def status (screen_name: Option[String], id: String, translate_to: Option[String] = None): FXApi =
|
||||||
val get = mornyBasicRequest
|
val get = mornyBasicRequest
|
||||||
.header(SttpPublic.Headers.UserAgent.MORNY_CURRENT)
|
|
||||||
.get(uri_status(screen_name, id, translate_to))
|
.get(uri_status(screen_name, id, translate_to))
|
||||||
.response(asString)
|
.response(asString)
|
||||||
.send(httpClient)
|
.send(httpClient)
|
||||||
val body = get.body match
|
val body = get.body match
|
||||||
case Left(error) => error
|
case Left(error) => error
|
||||||
case Right(success) => success
|
case Right(success) => success
|
||||||
parser.parse(body) match
|
parser.parse(body)
|
||||||
case Left(error) => throw error
|
.toTry.get
|
||||||
case Right(value) => value.as[FXApi] match
|
.as[FXApi]
|
||||||
case Left(error) => throw error
|
.toTry.get
|
||||||
case Right(value) => value
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
66
src/main/scala/cc/sukazyo/cono/morny/data/weibo/MApi.scala
Normal file
66
src/main/scala/cc/sukazyo/cono/morny/data/weibo/MApi.scala
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
package cc.sukazyo.cono.morny.data.weibo
|
||||||
|
|
||||||
|
case class MApi [D] (
|
||||||
|
ok: Int,
|
||||||
|
data: D
|
||||||
|
)
|
||||||
|
|
||||||
|
object MApi {
|
||||||
|
|
||||||
|
object CirceADTs {
|
||||||
|
import io.circe.Decoder
|
||||||
|
import io.circe.generic.semiauto.deriveDecoder
|
||||||
|
given Decoder[MUser] = deriveDecoder
|
||||||
|
given given_Decoder_largeType_getType: Decoder[MPic.largeType.geoType] = deriveDecoder
|
||||||
|
given Decoder[MPic.largeType] = deriveDecoder
|
||||||
|
given Decoder[MPic.geoType] = deriveDecoder
|
||||||
|
given Decoder[MPic] = deriveDecoder
|
||||||
|
given Decoder[MStatus] = deriveDecoder
|
||||||
|
given Decoder[MApi[MStatus]] = deriveDecoder
|
||||||
|
}
|
||||||
|
|
||||||
|
object Fetch {
|
||||||
|
|
||||||
|
import cc.sukazyo.cono.morny.util.SttpPublic
|
||||||
|
import cc.sukazyo.cono.morny.util.SttpPublic.mornyBasicRequest
|
||||||
|
import io.circe.{parser, DecodingFailure, ParsingFailure}
|
||||||
|
import sttp.client3.{HttpError, SttpClientException, UriContext}
|
||||||
|
import sttp.client3.okhttp.OkHttpSyncBackend
|
||||||
|
|
||||||
|
val uri_base = uri"https://m.weibo.cn/"
|
||||||
|
val uri_statuses_show =
|
||||||
|
(id: String) => uri"$uri_base/statuses/show?id=$id"
|
||||||
|
|
||||||
|
private val httpClient = OkHttpSyncBackend()
|
||||||
|
|
||||||
|
@throws[HttpError[_]|SttpClientException|ParsingFailure|DecodingFailure]
|
||||||
|
def statuses_show (id: String): MApi[MStatus] =
|
||||||
|
import sttp.client3.asString
|
||||||
|
import MApi.CirceADTs.given
|
||||||
|
val response = mornyBasicRequest
|
||||||
|
.get(uri_statuses_show(id))
|
||||||
|
.response(asString.getRight)
|
||||||
|
.send(httpClient)
|
||||||
|
parser.parse(response.body)
|
||||||
|
.toTry.get
|
||||||
|
.as[MApi[MStatus]]
|
||||||
|
.toTry.get
|
||||||
|
|
||||||
|
@throws[HttpError[_] | SttpClientException | ParsingFailure | DecodingFailure]
|
||||||
|
def pic (picUrl: String): Array[Byte] =
|
||||||
|
import sttp.client3.*
|
||||||
|
import sttp.model.{MediaType, Uri}
|
||||||
|
mornyBasicRequest
|
||||||
|
.acceptEncoding(MediaType.ImageJpeg.toString)
|
||||||
|
.get(Uri.unsafeParse(picUrl))
|
||||||
|
.response(asByteArray.getRight)
|
||||||
|
.send(httpClient)
|
||||||
|
.body
|
||||||
|
|
||||||
|
// @throws[HttpError[_] | SttpClientException | ParsingFailure | DecodingFailure]
|
||||||
|
// def pic (info: PicUrl): Array[Byte] =
|
||||||
|
// pic(info.toUrl)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
33
src/main/scala/cc/sukazyo/cono/morny/data/weibo/MPic.scala
Normal file
33
src/main/scala/cc/sukazyo/cono/morny/data/weibo/MPic.scala
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
package cc.sukazyo.cono.morny.data.weibo
|
||||||
|
|
||||||
|
case class MPic (
|
||||||
|
pid: String,
|
||||||
|
url: String,
|
||||||
|
size: String,
|
||||||
|
geo: MPic.geoType,
|
||||||
|
large: MPic.largeType
|
||||||
|
)
|
||||||
|
|
||||||
|
object MPic {
|
||||||
|
|
||||||
|
case class geoType (
|
||||||
|
// width: Int,
|
||||||
|
// height: Int,
|
||||||
|
croped: Boolean
|
||||||
|
)
|
||||||
|
|
||||||
|
case class largeType (
|
||||||
|
size: String,
|
||||||
|
url: String,
|
||||||
|
geo: largeType.geoType
|
||||||
|
)
|
||||||
|
|
||||||
|
object largeType {
|
||||||
|
case class geoType (
|
||||||
|
// width: String,
|
||||||
|
// height: String,
|
||||||
|
croped: Boolean
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,92 @@
|
|||||||
|
package cc.sukazyo.cono.morny.data.weibo
|
||||||
|
|
||||||
|
case class MStatus (
|
||||||
|
|
||||||
|
id: String,
|
||||||
|
mid: String,
|
||||||
|
bid: String,
|
||||||
|
|
||||||
|
created_at: String,
|
||||||
|
text: String,
|
||||||
|
raw_text: Option[String],
|
||||||
|
|
||||||
|
user: MUser,
|
||||||
|
|
||||||
|
retweeted_status: Option[MStatus],
|
||||||
|
|
||||||
|
pic_ids: List[String],
|
||||||
|
pics: Option[List[MPic]],
|
||||||
|
thumbnail_pic: Option[String],
|
||||||
|
bmiddle_pic: Option[String],
|
||||||
|
original_pic: Option[String],
|
||||||
|
|
||||||
|
// visible: Nothing,
|
||||||
|
// created_at: String,
|
||||||
|
// id: String,
|
||||||
|
// mid: String,
|
||||||
|
// bid: String,
|
||||||
|
// can_edit: Boolean,
|
||||||
|
// show_additional_indication: Int,
|
||||||
|
// text: String,
|
||||||
|
// textLength: Option[Int],
|
||||||
|
// source: String,
|
||||||
|
// favorited: Boolean,
|
||||||
|
// pic_ids: List[String],
|
||||||
|
// pic_focus_point: Option[List[Nothing]],
|
||||||
|
// falls_pic_focus_point: Option[List[Nothing]],
|
||||||
|
// pic_rectangle_object: Option[List[Nothing]],
|
||||||
|
// pic_flag: Option[Int],
|
||||||
|
// thumbnail_pic: Option[String],
|
||||||
|
// bmiddle_pic: Option[String],
|
||||||
|
// original_pic: Option[String],
|
||||||
|
// is_paid: Boolean,
|
||||||
|
// mblog_vip_type: Int,
|
||||||
|
// user: Nothing,
|
||||||
|
// picStatus: Option[String],
|
||||||
|
// retweeted_status: Option[Nothing],
|
||||||
|
// reposts_count: Int,
|
||||||
|
// comments_count: Int,
|
||||||
|
// reprint_cmt_count: Int,
|
||||||
|
// attitudes_count: Int,
|
||||||
|
// pending_approval_count: Int,
|
||||||
|
// isLongText: Boolean,
|
||||||
|
// show_mlevel: Int,
|
||||||
|
// topic_id: Option[String],
|
||||||
|
// sync_mblog: Option[Boolean],
|
||||||
|
// is_imported_topic: Option[Boolean],
|
||||||
|
// darwin_tags: List[Nothing],
|
||||||
|
// ad_marked: Boolean,
|
||||||
|
// mblogtype: Int,
|
||||||
|
// item_category: String,
|
||||||
|
// rid: String,
|
||||||
|
// number_display_strategy: Nothing,
|
||||||
|
// content_auth: Int,
|
||||||
|
// safe_tags: Option[Int],
|
||||||
|
// comment_manage_info: Nothing,
|
||||||
|
// repost_type: Option[Int],
|
||||||
|
// pic_num: Int,
|
||||||
|
// jump_type: Option[Int],
|
||||||
|
// hot_page: Nothing,
|
||||||
|
// new_comment_style: Int,
|
||||||
|
// ab_switcher: Int,
|
||||||
|
// mlevel: Int,
|
||||||
|
// region_name: String,
|
||||||
|
// region_opt: 1,
|
||||||
|
// page_info: Option[Nothing],
|
||||||
|
// pics: Option[List[Nothing]],
|
||||||
|
// raw_text: Option[String],
|
||||||
|
// buttons: List[Nothing],
|
||||||
|
// status_title: Option[String],
|
||||||
|
// ok: Int,
|
||||||
|
|
||||||
|
|
||||||
|
// pid: Long,
|
||||||
|
// pidstr: String,
|
||||||
|
// pic_types: String,
|
||||||
|
// alchemy_params: Nothing,
|
||||||
|
// ad_state: Int,
|
||||||
|
// cardid: String,
|
||||||
|
// hide_flag: Int,
|
||||||
|
// mark: String,
|
||||||
|
// more_info_type: Int,
|
||||||
|
)
|
13
src/main/scala/cc/sukazyo/cono/morny/data/weibo/MUser.scala
Normal file
13
src/main/scala/cc/sukazyo/cono/morny/data/weibo/MUser.scala
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
package cc.sukazyo.cono.morny.data.weibo
|
||||||
|
|
||||||
|
case class MUser (
|
||||||
|
|
||||||
|
id: Long,
|
||||||
|
screen_name: String,
|
||||||
|
profile_url: String,
|
||||||
|
profile_image_url: Option[String],
|
||||||
|
avatar_hd: Option[String],
|
||||||
|
description: Option[String],
|
||||||
|
cover_image_phone: Option[String],
|
||||||
|
|
||||||
|
)
|
@ -0,0 +1,40 @@
|
|||||||
|
package cc.sukazyo.cono.morny.data
|
||||||
|
|
||||||
|
package object weibo {
|
||||||
|
|
||||||
|
/** Information in weibo status url.
|
||||||
|
*
|
||||||
|
* @param uid Status owner's user id. should be a number.
|
||||||
|
* @param id Status id. Should be unique in the whole weibo.com
|
||||||
|
* globe. Maybe a number format mid, or a base58-like
|
||||||
|
* bid.
|
||||||
|
*/
|
||||||
|
case class StatusUrlInfo (
|
||||||
|
uid: String,
|
||||||
|
id: String
|
||||||
|
)
|
||||||
|
|
||||||
|
// case class PicUrl (
|
||||||
|
// cdn: String,
|
||||||
|
// mode: String,
|
||||||
|
// pid: String
|
||||||
|
// ) {
|
||||||
|
// def toUrl: String =
|
||||||
|
// s"https://$cdn.singimg.cn/$mode/$pid.jpg"
|
||||||
|
// }
|
||||||
|
|
||||||
|
private val REGEX_WEIBO_STATUS_URL = "^(?:https?://)?((?:www\\.|m.)?weibo\\.(?:com|cn))/(\\d+)/([0-9a-zA-Z]+)/?(?:\\?([\\w&=-]+))?$"r
|
||||||
|
|
||||||
|
def parseWeiboStatusUrl (url: String): Option[StatusUrlInfo] =
|
||||||
|
url match
|
||||||
|
case REGEX_WEIBO_STATUS_URL(_, uid, id, _) => Some(StatusUrlInfo(uid, id))
|
||||||
|
case _ => None
|
||||||
|
|
||||||
|
def genWeiboStatusUrl (url: StatusUrlInfo): String =
|
||||||
|
s"https://weibo.com/${url.uid}/${url.id}"
|
||||||
|
|
||||||
|
// def randomPicCdn: String =
|
||||||
|
// import scala.util.Random
|
||||||
|
// s"wx${Random.nextInt(4)+1}"
|
||||||
|
|
||||||
|
}
|
@ -21,8 +21,10 @@ object TelegramExtensions {
|
|||||||
if onError_message isEmpty then response.errorCode toString else onError_message,
|
if onError_message isEmpty then response.errorCode toString else onError_message,
|
||||||
response
|
response
|
||||||
)
|
)
|
||||||
} catch case e: RuntimeException =>
|
} catch
|
||||||
throw EventRuntimeException.ClientFailed(e)
|
case e: EventRuntimeException.ActionFailed => throw e
|
||||||
|
case e: RuntimeException =>
|
||||||
|
throw EventRuntimeException.ClientFailed(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
}}
|
}}
|
||||||
|
@ -1,5 +1,11 @@
|
|||||||
package cc.sukazyo.cono.morny.util.tgapi.formatting
|
package cc.sukazyo.cono.morny.util.tgapi.formatting
|
||||||
|
|
||||||
|
import org.jsoup.Jsoup
|
||||||
|
import org.jsoup.nodes.Node
|
||||||
|
|
||||||
|
import scala.collection.mutable
|
||||||
|
import scala.jdk.CollectionConverters.*
|
||||||
|
|
||||||
object TelegramParseEscape {
|
object TelegramParseEscape {
|
||||||
|
|
||||||
def escapeHtml (input: String): String =
|
def escapeHtml (input: String): String =
|
||||||
@ -9,4 +15,55 @@ object TelegramParseEscape {
|
|||||||
process = process.replaceAll(">", ">")
|
process = process.replaceAll(">", ">")
|
||||||
process
|
process
|
||||||
|
|
||||||
|
def cleanupHtml (input: String): String =
|
||||||
|
import org.jsoup.nodes.*
|
||||||
|
val source = Jsoup.parse(input)
|
||||||
|
val x = cleanupHtml(source.body.childNodes.asScala.toSeq)
|
||||||
|
val doc = Document("")
|
||||||
|
doc.outputSettings
|
||||||
|
.prettyPrint(false)
|
||||||
|
x.map(f => doc.appendChild(f))
|
||||||
|
x.mkString("")
|
||||||
|
|
||||||
|
// def toHtmlRaw (input: Node): String =
|
||||||
|
// import org.jsoup.nodes.*
|
||||||
|
// input match
|
||||||
|
// case text: TextNode => text.getWholeText
|
||||||
|
// case _: (DataNode | XmlDeclaration | DocumentType | Comment) => ""
|
||||||
|
// case elem: Element => elem.childNodes.asScala.map(f => toHtmlRaw(f)).mkString("")
|
||||||
|
|
||||||
|
def cleanupHtml (input: Seq[Node]): List[Node] =
|
||||||
|
val result = mutable.ListBuffer.empty[Node]
|
||||||
|
for (i <- input) {
|
||||||
|
import org.jsoup.nodes.*
|
||||||
|
def produceChildNodes (curr: Element): Element =
|
||||||
|
val newOne = Element(curr.tagName)
|
||||||
|
curr.attributes.forEach(attr => newOne.attr(attr.getKey, attr.getValue))
|
||||||
|
for (i <- cleanupHtml(curr.childNodes.asScala.toSeq)) newOne.appendChild(i)
|
||||||
|
newOne
|
||||||
|
i match
|
||||||
|
case text_cdata: CDataNode => result += CDataNode(text_cdata.text)
|
||||||
|
case text: TextNode => result += TextNode(text.getWholeText)
|
||||||
|
case _: (DataNode | XmlDeclaration | DocumentType | Comment) =>
|
||||||
|
case elem: Element => elem match
|
||||||
|
case _: Document => // should not exists here
|
||||||
|
case _: FormElement => // ignored due to Telegram do not support form
|
||||||
|
case elem => elem.tagName match
|
||||||
|
case "a"|"b"|"strong"|"i"|"em"|"u"|"ins"|"s"|"strike"|"del"|"tg-spoiler"|"code"|"pre" =>
|
||||||
|
result += produceChildNodes(elem)
|
||||||
|
case "br" =>
|
||||||
|
result += TextNode("\n")
|
||||||
|
case "tg-emoji" =>
|
||||||
|
if elem.attributes.hasKey("emoji-id") then
|
||||||
|
result += produceChildNodes(elem)
|
||||||
|
else
|
||||||
|
result += TextNode(elem.text)
|
||||||
|
case "img" =>
|
||||||
|
if elem.attributes hasKey "alt" then
|
||||||
|
result += TextNode(s"[${elem attr "alt"}]")
|
||||||
|
case _ =>
|
||||||
|
for (i <- cleanupHtml(elem.childNodes.asScala.toSeq)) result += i
|
||||||
|
}
|
||||||
|
result.toList
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user