From 9c433ba0ab8bdc01d87bd027342b2ba2e9d5ad2b Mon Sep 17 00:00:00 2001 From: Eyre_S Date: Thu, 12 Oct 2023 18:10:11 +0800 Subject: [PATCH 1/4] use EventEnv as event encapsulate instead of Update - make MornyOnTelegramCommand provides InputCommand - change OnUniMeowTrigger consume InputCommand --- gradle.properties | 2 +- .../sukazyo/cono/morny/bot/api/EventEnv.scala | 43 +++++++++++++ .../cono/morny/bot/api/EventListener.scala | 30 +++++---- .../morny/bot/api/EventListenerManager.scala | 46 +++++++------- .../morny/bot/event/MornyOnInlineQuery.scala | 10 +-- .../bot/event/MornyOnTelegramCommand.scala | 15 +++-- .../MornyOnUpdateTimestampOffsetLock.scala | 15 ++--- .../cono/morny/bot/event/OnCallMe.scala | 13 ++-- .../cono/morny/bot/event/OnCallMsgSend.scala | 39 ++++++------ .../morny/bot/event/OnEventHackHandle.scala | 61 ++++++++++--------- .../bot/event/OnMedicationNotifyApply.scala | 16 ++--- .../morny/bot/event/OnQuestionMarkReply.scala | 17 +++--- .../morny/bot/event/OnUniMeowTrigger.scala | 25 +++++--- .../cono/morny/bot/event/OnUserRandom.scala | 35 +++++------ .../morny/bot/event/OnUserSlashAction.scala | 17 +++--- src/test/scala/live/LiveMain.scala | 7 ++- 16 files changed, 228 insertions(+), 163 deletions(-) create mode 100644 src/main/scala/cc/sukazyo/cono/morny/bot/api/EventEnv.scala diff --git a/gradle.properties b/gradle.properties index a21d2a6..9f87499 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.1.1 +VERSION = 1.1.1.xiongan-dev1 USE_DELTA = false VERSION_DELTA = diff --git a/src/main/scala/cc/sukazyo/cono/morny/bot/api/EventEnv.scala b/src/main/scala/cc/sukazyo/cono/morny/bot/api/EventEnv.scala new file mode 100644 index 0000000..ec34522 --- /dev/null +++ b/src/main/scala/cc/sukazyo/cono/morny/bot/api/EventEnv.scala @@ -0,0 +1,43 @@ +package cc.sukazyo.cono.morny.bot.api + +import com.pengrad.telegrambot.model.Update + +import scala.collection.mutable +import scala.reflect.ClassTag +import scala.util.boundary + +class EventEnv ( + + val update: Update + +) { + + private var _isOk: Int = 0 + private val variables: mutable.HashMap[Class[?], Any] = mutable.HashMap.empty + + def isEventOk: Boolean = _isOk > 0 + + //noinspection UnitMethodIsParameterless + def setEventOk: Unit = + _isOk = _isOk + 1 + + def provide (i: Any): Unit = + variables += (i.getClass -> i) + + def use [T] (t: Class[T]): ConsumeProvider[T] = ConsumeProvider(t) + + class ConsumeProvider[T] (t: Class[T]) { + def consume (consumer: T => Unit): ConsumeResult = { + variables get t match + case Some(i) => consumer(i.asInstanceOf[T]); ConsumeResult(true) + case None => ConsumeResult(false) + } + } + + class ConsumeResult (success: Boolean) { + def onfail (processor: => Unit): Unit = { + if !success then processor + } + } + +} diff --git a/src/main/scala/cc/sukazyo/cono/morny/bot/api/EventListener.scala b/src/main/scala/cc/sukazyo/cono/morny/bot/api/EventListener.scala index 0a3e1ac..3c2fbb9 100644 --- a/src/main/scala/cc/sukazyo/cono/morny/bot/api/EventListener.scala +++ b/src/main/scala/cc/sukazyo/cono/morny/bot/api/EventListener.scala @@ -1,22 +1,20 @@ package cc.sukazyo.cono.morny.bot.api -import com.pengrad.telegrambot.model.Update - trait EventListener () { - def onMessage (using Update): Boolean = false - def onEditedMessage (using Update): Boolean = false - def onChannelPost (using Update): Boolean = false - def onEditedChannelPost (using Update): Boolean = false - def onInlineQuery (using Update): Boolean = false - def onChosenInlineResult (using Update): Boolean = false - def onCallbackQuery (using Update): Boolean = false - def onShippingQuery (using Update): Boolean = false - def onPreCheckoutQuery (using Update): Boolean = false - def onPoll (using Update): Boolean = false - def onPollAnswer (using Update): Boolean = false - def onMyChatMemberUpdated (using Update): Boolean = false - def onChatMemberUpdated (using Update): Boolean = false - def onChatJoinRequest (using Update): Boolean = false + def onMessage (using EventEnv): Unit = {} + def onEditedMessage (using EventEnv): Unit = {} + def onChannelPost (using EventEnv): Unit = {} + def onEditedChannelPost (using EventEnv): Unit = {} + def onInlineQuery (using EventEnv): Unit = {} + def onChosenInlineResult (using EventEnv): Unit = {} + def onCallbackQuery (using EventEnv): Unit = {} + def onShippingQuery (using EventEnv): Unit = {} + def onPreCheckoutQuery (using EventEnv): Unit = {} + def onPoll (using EventEnv): Unit = {} + def onPollAnswer (using EventEnv): Unit = {} + def onMyChatMemberUpdated (using EventEnv): Unit = {} + def onChatMemberUpdated (using EventEnv): Unit = {} + def onChatJoinRequest (using EventEnv): Unit = {} } diff --git a/src/main/scala/cc/sukazyo/cono/morny/bot/api/EventListenerManager.scala b/src/main/scala/cc/sukazyo/cono/morny/bot/api/EventListenerManager.scala index 5448204..de846ab 100644 --- a/src/main/scala/cc/sukazyo/cono/morny/bot/api/EventListenerManager.scala +++ b/src/main/scala/cc/sukazyo/cono/morny/bot/api/EventListenerManager.scala @@ -9,6 +9,7 @@ import com.pengrad.telegrambot.UpdatesListener import scala.collection.mutable import scala.language.postfixOps +import scala.util.boundary /** Contains a [[mutable.Queue]] of [[EventListener]], and delivery telegram [[Update]]. * @@ -23,46 +24,43 @@ class EventListenerManager (using coeur: MornyCoeur) extends UpdatesListener { def register (listeners: EventListener*): Unit = this.listeners ++= listeners - private class EventRunner (using event: Update) extends Thread { - this setName s"evt-${event.updateId()}-nn" + private class EventRunner (using update: Update) extends Thread { + this setName s"upd-${update.updateId()}-nn" private def updateThreadName (t: String): Unit = - this setName s"evt-${event.updateId()}-$t" + this setName s"upd-${update.updateId()}-$t" override def run (): Unit = { - for (i <- listeners) { - object status: - var _status = 0 - def isOk: Boolean = _status > 0 - def check (u: Boolean): Unit = if u then _status = _status + 1 + given env: EventEnv = EventEnv(update) + boundary { for (i <- listeners) { try { updateThreadName("message") - if event.message ne null then status check i.onMessage + if update.message ne null then i.onMessage updateThreadName("edited-message") - if event.editedMessage ne null then status check i.onEditedMessage + if update.editedMessage ne null then i.onEditedMessage updateThreadName("channel-post") - if event.channelPost ne null then status check i.onChannelPost + if update.channelPost ne null then i.onChannelPost updateThreadName("edited-channel-post") - if event.editedChannelPost ne null then status check i.onEditedChannelPost + if update.editedChannelPost ne null then i.onEditedChannelPost updateThreadName("inline-query") - if event.inlineQuery ne null then status check i.onInlineQuery + if update.inlineQuery ne null then i.onInlineQuery updateThreadName("chosen-inline-result") - if event.chosenInlineResult ne null then status check i.onChosenInlineResult + if update.chosenInlineResult ne null then i.onChosenInlineResult updateThreadName("callback-query") - if event.callbackQuery ne null then status check i.onCallbackQuery + if update.callbackQuery ne null then i.onCallbackQuery updateThreadName("shipping-query") - if event.shippingQuery ne null then status check i.onShippingQuery + if update.shippingQuery ne null then i.onShippingQuery updateThreadName("pre-checkout-query") - if event.preCheckoutQuery ne null then status check i.onPreCheckoutQuery + if update.preCheckoutQuery ne null then i.onPreCheckoutQuery updateThreadName("poll") - if event.poll ne null then status check i.onPoll + if update.poll ne null then i.onPoll updateThreadName("poll-answer") - if event.pollAnswer ne null then status check i.onPollAnswer + if update.pollAnswer ne null then i.onPollAnswer updateThreadName("my-chat-member") - if event.myChatMember ne null then status check i.onMyChatMemberUpdated + if update.myChatMember ne null then i.onMyChatMemberUpdated updateThreadName("chat-member") - if event.chatMember ne null then status check i.onChatMemberUpdated + if update.chatMember ne null then i.onChatMemberUpdated updateThreadName("chat-join-request") - if event.chatJoinRequest ne null then status check i.onChatJoinRequest + if update.chatJoinRequest ne null then i.onChatJoinRequest } catch case e => { val errorMessage = StringBuilder() errorMessage ++= "Event throws unexpected exception:\n" @@ -77,8 +75,8 @@ class EventListenerManager (using coeur: MornyCoeur) extends UpdatesListener { logger error errorMessage.toString coeur.daemons.reporter.exception(e, "on event running") } - if (status isOk) return - } + if env.isEventOk then boundary.break() + }} } } diff --git a/src/main/scala/cc/sukazyo/cono/morny/bot/event/MornyOnInlineQuery.scala b/src/main/scala/cc/sukazyo/cono/morny/bot/event/MornyOnInlineQuery.scala index 38b8f95..e079dd0 100644 --- a/src/main/scala/cc/sukazyo/cono/morny/bot/event/MornyOnInlineQuery.scala +++ b/src/main/scala/cc/sukazyo/cono/morny/bot/event/MornyOnInlineQuery.scala @@ -1,7 +1,7 @@ package cc.sukazyo.cono.morny.bot.event import cc.sukazyo.cono.morny.MornyCoeur -import cc.sukazyo.cono.morny.bot.api.EventListener +import cc.sukazyo.cono.morny.bot.api.{EventEnv, EventListener} import cc.sukazyo.cono.morny.bot.query.{InlineQueryUnit, MornyQueries} import cc.sukazyo.cono.morny.util.tgapi.TelegramExtensions.Bot.exec import com.pengrad.telegrambot.model.Update @@ -14,7 +14,8 @@ import scala.reflect.ClassTag class MornyOnInlineQuery (using queryManager: MornyQueries) (using coeur: MornyCoeur) extends EventListener { - override def onInlineQuery (using update: Update): Boolean = { + override def onInlineQuery (using event: EventEnv): Unit = { + import event.update val results: List[InlineQueryUnit[_]] = queryManager query update @@ -27,12 +28,13 @@ class MornyOnInlineQuery (using queryManager: MornyQueries) (using coeur: MornyC resultAnswers += r.result } - if (results isEmpty) return false + if (results isEmpty) return; coeur.account exec AnswerInlineQuery( update.inlineQuery.id, resultAnswers toArray:_* ).cacheTime(cacheTime).isPersonal(isPersonal) - true + + event.setEventOk } diff --git a/src/main/scala/cc/sukazyo/cono/morny/bot/event/MornyOnTelegramCommand.scala b/src/main/scala/cc/sukazyo/cono/morny/bot/event/MornyOnTelegramCommand.scala index 541070b..66c0f66 100644 --- a/src/main/scala/cc/sukazyo/cono/morny/bot/event/MornyOnTelegramCommand.scala +++ b/src/main/scala/cc/sukazyo/cono/morny/bot/event/MornyOnTelegramCommand.scala @@ -1,6 +1,6 @@ package cc.sukazyo.cono.morny.bot.event -import cc.sukazyo.cono.morny.bot.api.EventListener +import cc.sukazyo.cono.morny.bot.api.{EventEnv, EventListener} import cc.sukazyo.cono.morny.Log.logger import cc.sukazyo.cono.morny.MornyCoeur import cc.sukazyo.cono.morny.bot.command.MornyCommands @@ -9,7 +9,8 @@ import com.pengrad.telegrambot.model.{Message, Update} class MornyOnTelegramCommand (using commandManager: MornyCommands) (using coeur: MornyCoeur) extends EventListener { - override def onMessage (using update: Update): Boolean = { + override def onMessage (using event: EventEnv): Unit = { + given update: Update = event.update def _isCommandMessage(message: Message): Boolean = if message.text eq null then false @@ -17,17 +18,19 @@ class MornyOnTelegramCommand (using commandManager: MornyCommands) (using coeur: else if message.text startsWith "/ " then false else true - if !_isCommandMessage(update.message) then return false + if !_isCommandMessage(update.message) then return val inputCommand = InputCommand(update.message.text drop 1) + event provide inputCommand + logger trace ":provided InputCommand for event" + if (!(inputCommand.command matches "^\\w+$")) logger debug "not command" - false else if ((inputCommand.target ne null) && (inputCommand.target != coeur.username)) logger debug "not morny command" - false else logger debug "is command" - commandManager.execute(using inputCommand) + if commandManager.execute(using inputCommand) then + event.setEventOk } diff --git a/src/main/scala/cc/sukazyo/cono/morny/bot/event/MornyOnUpdateTimestampOffsetLock.scala b/src/main/scala/cc/sukazyo/cono/morny/bot/event/MornyOnUpdateTimestampOffsetLock.scala index fc9f575..ae44432 100644 --- a/src/main/scala/cc/sukazyo/cono/morny/bot/event/MornyOnUpdateTimestampOffsetLock.scala +++ b/src/main/scala/cc/sukazyo/cono/morny/bot/event/MornyOnUpdateTimestampOffsetLock.scala @@ -1,17 +1,18 @@ package cc.sukazyo.cono.morny.bot.event -import cc.sukazyo.cono.morny.bot.api.EventListener +import cc.sukazyo.cono.morny.bot.api.{EventEnv, EventListener} import cc.sukazyo.cono.morny.MornyCoeur import com.pengrad.telegrambot.model.Update class MornyOnUpdateTimestampOffsetLock (using coeur: MornyCoeur) extends EventListener { - private def isOutdated (timestamp: Int): Boolean = - coeur.config.eventIgnoreOutdated && (timestamp < (coeur.coeurStartTimestamp/1000)) + private def checkOutdated (timestamp: Int)(using event: EventEnv): Unit = + if coeur.config.eventIgnoreOutdated && (timestamp < (coeur.coeurStartTimestamp/1000)) then + event.setEventOk - override def onMessage (using update: Update): Boolean = isOutdated(update.message.date) - override def onEditedMessage (using update: Update): Boolean = isOutdated(update.editedMessage.date) - override def onChannelPost (using update: Update): Boolean = isOutdated(update.channelPost.date) - override def onEditedChannelPost (using update: Update): Boolean = isOutdated(update.editedChannelPost.date) + override def onMessage (using event: EventEnv): Unit = checkOutdated(event.update.message.date) + override def onEditedMessage (using event: EventEnv): Unit = checkOutdated(event.update.editedMessage.date) + override def onChannelPost (using event: EventEnv): Unit = checkOutdated(event.update.channelPost.date) + override def onEditedChannelPost (using event: EventEnv): Unit = checkOutdated(event.update.editedChannelPost.date) } diff --git a/src/main/scala/cc/sukazyo/cono/morny/bot/event/OnCallMe.scala b/src/main/scala/cc/sukazyo/cono/morny/bot/event/OnCallMe.scala index 244ee66..4f80a69 100644 --- a/src/main/scala/cc/sukazyo/cono/morny/bot/event/OnCallMe.scala +++ b/src/main/scala/cc/sukazyo/cono/morny/bot/event/OnCallMe.scala @@ -1,7 +1,7 @@ package cc.sukazyo.cono.morny.bot.event import cc.sukazyo.cono.morny.MornyCoeur -import cc.sukazyo.cono.morny.bot.api.EventListener +import cc.sukazyo.cono.morny.bot.api.{EventEnv, EventListener} import cc.sukazyo.cono.morny.data.TelegramStickers import cc.sukazyo.cono.morny.util.tgapi.formatting.TelegramFormatter.* import cc.sukazyo.cono.morny.util.tgapi.TelegramExtensions.Bot.exec @@ -15,10 +15,11 @@ class OnCallMe (using coeur: MornyCoeur) extends EventListener { private val me = coeur.config.trustedMaster - override def onMessage (using update: Update): Boolean = { + override def onMessage (using event: EventEnv): Unit = { + import event.update - if update.message.text == null then return false - if update.message.chat.`type` != (Chat.Type Private) then return false + if update.message.text == null then return; + if update.message.chat.`type` != (Chat.Type Private) then return //noinspection ScalaUnnecessaryParentheses val success = if me == -1 then false else @@ -32,7 +33,7 @@ class OnCallMe (using coeur: MornyCoeur) extends EventListener { case cc if cc startsWith "cc::" => requestCustom(update.message) case _ => - return false + return; if success then coeur.account exec SendSticker( @@ -45,7 +46,7 @@ class OnCallMe (using coeur: MornyCoeur) extends EventListener { TelegramStickers ID_501 ).replyToMessageId(update.message.messageId) - true + event.setEventOk } diff --git a/src/main/scala/cc/sukazyo/cono/morny/bot/event/OnCallMsgSend.scala b/src/main/scala/cc/sukazyo/cono/morny/bot/event/OnCallMsgSend.scala index db920c0..00a2ef8 100644 --- a/src/main/scala/cc/sukazyo/cono/morny/bot/event/OnCallMsgSend.scala +++ b/src/main/scala/cc/sukazyo/cono/morny/bot/event/OnCallMsgSend.scala @@ -1,6 +1,6 @@ package cc.sukazyo.cono.morny.bot.event -import cc.sukazyo.cono.morny.bot.api.EventListener +import cc.sukazyo.cono.morny.bot.api.{EventEnv, EventListener} import cc.sukazyo.cono.morny.MornyCoeur import cc.sukazyo.cono.morny.data.TelegramStickers import cc.sukazyo.cono.morny.util.tgapi.TelegramExtensions.Bot.exec @@ -52,26 +52,28 @@ class OnCallMsgSend (using coeur: MornyCoeur) extends EventListener { case _ => null } - override def onMessage (using update: Update): Boolean = { + override def onMessage (using event: EventEnv): Unit = { + import event.update val message = update.message - if message.chat.`type` != Chat.Type.Private then return false - if message.text eq null then return false - if !(message.text startsWith "*msg") then return false + if message.chat.`type` != Chat.Type.Private then return; + if message.text eq null then return; + if !(message.text startsWith "*msg") then return; if (!(coeur.trusted isTrusted message.from.id)) coeur.account exec SendSticker( message.chat.id, TelegramStickers ID_403 ).replyToMessageId(message.messageId) - return true + event.setEventOk + return; if (message.text == "*msgsend") { - if (message.replyToMessage eq null) return answer404 + if (message.replyToMessage eq null) { answer404; return } val messageToSend = MessageToSend from message.replyToMessage - if ((messageToSend eq null) || (messageToSend.message eq null)) return answer404 + if ((messageToSend eq null) || (messageToSend.message eq null)) { answer404; return } val sendResponse = coeur.account execute messageToSend.toSendMessage() if (sendResponse isOk) { @@ -89,20 +91,21 @@ class OnCallMsgSend (using coeur: MornyCoeur) extends EventListener { ).replyToMessageId(update.message.messageId).parseMode(ParseMode HTML) } - return true + event.setEventOk + return } val messageToSend: MessageToSend = val raw: Message = if (message.text == "*msg") - if message.replyToMessage eq null then return answer404 + if message.replyToMessage eq null then { answer404; return } else message.replyToMessage else if (message.text startsWith "*msg") message - else return answer404 + else { answer404; return } val _toSend = MessageToSend from raw - if _toSend eq null then return answer404 + if _toSend eq null then { answer404; return } else _toSend val targetChatResponse = coeur.account execute GetChat(messageToSend.targetId) @@ -128,7 +131,7 @@ class OnCallMsgSend (using coeur: MornyCoeur) extends EventListener { ).parseMode(ParseMode HTML).replyToMessageId(update.message.messageId) } - if messageToSend.message eq null then return true + if messageToSend.message eq null then { answer404; return } val testSendResponse = coeur.account execute messageToSend.toSendMessage(update.message.chat.id).replyToMessageId(update.message.messageId) if (!(testSendResponse isOk)) @@ -140,15 +143,15 @@ class OnCallMsgSend (using coeur: MornyCoeur) extends EventListener { .stripMargin ).parseMode(ParseMode HTML).replyToMessageId(update.message.messageId) - true + event.setEventOk } - private def answer404 (using update: Update): Boolean = + private def answer404 (using event: EventEnv): Unit = coeur.account exec SendSticker( - update.message.chat.id, + event.update.message.chat.id, TelegramStickers ID_404 - ).replyToMessageId(update.message.messageId) - true + ).replyToMessageId(event.update.message.messageId) + event.setEventOk } diff --git a/src/main/scala/cc/sukazyo/cono/morny/bot/event/OnEventHackHandle.scala b/src/main/scala/cc/sukazyo/cono/morny/bot/event/OnEventHackHandle.scala index d6bc1f6..13f78ae 100644 --- a/src/main/scala/cc/sukazyo/cono/morny/bot/event/OnEventHackHandle.scala +++ b/src/main/scala/cc/sukazyo/cono/morny/bot/event/OnEventHackHandle.scala @@ -1,6 +1,6 @@ package cc.sukazyo.cono.morny.bot.event -import cc.sukazyo.cono.morny.bot.api.EventListener +import cc.sukazyo.cono.morny.bot.api.{EventEnv, EventListener} import cc.sukazyo.cono.morny.Log.logger import cc.sukazyo.cono.morny.MornyCoeur import com.google.gson.GsonBuilder @@ -13,35 +13,38 @@ import scala.language.postfixOps class OnEventHackHandle (using coeur: MornyCoeur) extends EventListener { - import coeur.daemons.eventHack.trigger + private def trigger (chat_id: Long, from_id: Long)(using event: EventEnv): Unit = + given Update = event.update + if coeur.daemons.eventHack.trigger(chat_id, from_id) then + event.setEventOk - override def onMessage (using update: Update): Boolean = - trigger(update.message.chat.id, update.message.from.id) - override def onEditedMessage (using update: Update): Boolean = - trigger(update.editedMessage.chat.id, update.editedMessage.from.id) - override def onChannelPost (using update: Update): Boolean = - trigger(update.channelPost.chat.id, 0) - override def onEditedChannelPost (using update: Update): Boolean = - trigger(update.editedChannelPost.chat.id, 0) - override def onInlineQuery (using update: Update): Boolean = - trigger(0, update.inlineQuery.from.id) - override def onChosenInlineResult (using update: Update): Boolean = - trigger(0, update.chosenInlineResult.from.id) - override def onCallbackQuery (using update: Update): Boolean = - trigger(0, update.callbackQuery.from.id) - override def onShippingQuery (using update: Update): Boolean = - trigger(0, update.shippingQuery.from.id) - override def onPreCheckoutQuery (using update: Update): Boolean = - trigger(0, update.preCheckoutQuery.from.id) - override def onPoll (using update: Update): Boolean = + override def onMessage (using event: EventEnv): Unit = + trigger(event.update.message.chat.id, event.update.message.from.id) + override def onEditedMessage (using event: EventEnv): Unit = + trigger(event.update.editedMessage.chat.id, event.update.editedMessage.from.id) + override def onChannelPost (using event: EventEnv): Unit = + trigger(event.update.channelPost.chat.id, 0) + override def onEditedChannelPost (using event: EventEnv): Unit = + trigger(event.update.editedChannelPost.chat.id, 0) + override def onInlineQuery (using event: EventEnv): Unit = + trigger(0, event.update.inlineQuery.from.id) + override def onChosenInlineResult (using event: EventEnv): Unit = + trigger(0, event.update.chosenInlineResult.from.id) + override def onCallbackQuery (using event: EventEnv): Unit = + trigger(0, event.update.callbackQuery.from.id) + override def onShippingQuery (using event: EventEnv): Unit = + trigger(0, event.update.shippingQuery.from.id) + override def onPreCheckoutQuery (using event: EventEnv): Unit = + trigger(0, event.update.preCheckoutQuery.from.id) + override def onPoll (using event: EventEnv): Unit = trigger(0, 0) - override def onPollAnswer (using update: Update): Boolean = - trigger(0, update.pollAnswer.user.id) - override def onMyChatMemberUpdated (using update: Update): Boolean = - trigger(update.myChatMember.chat.id, update.myChatMember.from.id) - override def onChatMemberUpdated (using update: Update): Boolean = - trigger(update.chatMember.chat.id, update.chatMember.from.id) - override def onChatJoinRequest (using update: Update): Boolean = - trigger(update.chatJoinRequest.chat.id, update.chatJoinRequest.from.id) + override def onPollAnswer (using event: EventEnv): Unit = + trigger(0, event.update.pollAnswer.user.id) + override def onMyChatMemberUpdated (using event: EventEnv): Unit = + trigger(event.update.myChatMember.chat.id, event.update.myChatMember.from.id) + override def onChatMemberUpdated (using event: EventEnv): Unit = + trigger(event.update.chatMember.chat.id, event.update.chatMember.from.id) + override def onChatJoinRequest (using event: EventEnv): Unit = + trigger(event.update.chatJoinRequest.chat.id, event.update.chatJoinRequest.from.id) } diff --git a/src/main/scala/cc/sukazyo/cono/morny/bot/event/OnMedicationNotifyApply.scala b/src/main/scala/cc/sukazyo/cono/morny/bot/event/OnMedicationNotifyApply.scala index 25b29c1..ed24c3b 100644 --- a/src/main/scala/cc/sukazyo/cono/morny/bot/event/OnMedicationNotifyApply.scala +++ b/src/main/scala/cc/sukazyo/cono/morny/bot/event/OnMedicationNotifyApply.scala @@ -1,21 +1,21 @@ package cc.sukazyo.cono.morny.bot.event -import cc.sukazyo.cono.morny.bot.api.EventListener +import cc.sukazyo.cono.morny.bot.api.{EventEnv, EventListener} import cc.sukazyo.cono.morny.MornyCoeur import cc.sukazyo.cono.morny.daemon.{MedicationTimer, MornyDaemons} import com.pengrad.telegrambot.model.{Message, Update} class OnMedicationNotifyApply (using coeur: MornyCoeur) extends EventListener { - override def onEditedMessage (using event: Update): Boolean = - editedMessageProcess(event.editedMessage) - override def onEditedChannelPost (using event: Update): Boolean = - editedMessageProcess(event.editedChannelPost) + override def onEditedMessage (using event: EventEnv): Unit = + editedMessageProcess(event.update.editedMessage) + override def onEditedChannelPost (using event: EventEnv): Unit = + editedMessageProcess(event.update.editedChannelPost) - private def editedMessageProcess (edited: Message): Boolean = { - if edited.chat.id != coeur.config.medicationNotifyToChat then return false + private def editedMessageProcess (edited: Message)(using event: EventEnv): Unit = { + if edited.chat.id != coeur.config.medicationNotifyToChat then return; coeur.daemons.medicationTimer.refreshNotificationWrite(edited) - true + event.setEventOk } } diff --git a/src/main/scala/cc/sukazyo/cono/morny/bot/event/OnQuestionMarkReply.scala b/src/main/scala/cc/sukazyo/cono/morny/bot/event/OnQuestionMarkReply.scala index a56e42d..516499e 100644 --- a/src/main/scala/cc/sukazyo/cono/morny/bot/event/OnQuestionMarkReply.scala +++ b/src/main/scala/cc/sukazyo/cono/morny/bot/event/OnQuestionMarkReply.scala @@ -1,6 +1,6 @@ package cc.sukazyo.cono.morny.bot.event -import cc.sukazyo.cono.morny.bot.api.EventListener +import cc.sukazyo.cono.morny.bot.api.{EventEnv, EventListener} import cc.sukazyo.cono.morny.MornyCoeur import cc.sukazyo.cono.morny.bot.event.OnQuestionMarkReply.isAllMessageMark import cc.sukazyo.cono.morny.util.tgapi.TelegramExtensions.Bot.exec @@ -12,19 +12,20 @@ import scala.util.boundary class OnQuestionMarkReply (using coeur: MornyCoeur) extends EventListener { - override def onMessage (using event: Update): Boolean = { + override def onMessage (using event: EventEnv): Unit = { + import event.update - if event.message.text eq null then return false + if update.message.text eq null then return import cc.sukazyo.cono.morny.util.UseMath.over import cc.sukazyo.cono.morny.util.UseRandom.chance_is - if (1 over 8) chance_is false then return false - if !isAllMessageMark(using event.message.text) then return false + if (1 over 8) chance_is false then return; + if !isAllMessageMark(using update.message.text) then return; coeur.account exec SendMessage( - event.message.chat.id, event.message.text - ).replyToMessageId(event.message.messageId) - true + update.message.chat.id, update.message.text + ).replyToMessageId(update.message.messageId) + event.setEventOk } diff --git a/src/main/scala/cc/sukazyo/cono/morny/bot/event/OnUniMeowTrigger.scala b/src/main/scala/cc/sukazyo/cono/morny/bot/event/OnUniMeowTrigger.scala index d1c97da..6667a15 100644 --- a/src/main/scala/cc/sukazyo/cono/morny/bot/event/OnUniMeowTrigger.scala +++ b/src/main/scala/cc/sukazyo/cono/morny/bot/event/OnUniMeowTrigger.scala @@ -1,23 +1,28 @@ package cc.sukazyo.cono.morny.bot.event -import cc.sukazyo.cono.morny.bot.api.EventListener +import cc.sukazyo.cono.morny.bot.api.{EventEnv, EventListener} import cc.sukazyo.cono.morny.bot.command.MornyCommands import cc.sukazyo.cono.morny.util.tgapi.InputCommand +import cc.sukazyo.cono.morny.Log.logger import cc.sukazyo.cono.morny.MornyCoeur import com.pengrad.telegrambot.model.Update class OnUniMeowTrigger (using commands: MornyCommands) (using coeur: MornyCoeur) extends EventListener { - override def onMessage (using update: Update): Boolean = { + override def onMessage (using event: EventEnv): Unit = { - if update.message.text eq null then return false - var ok = false - for ((name, command) <- commands.commands_uni) - val _name = "/"+name - if (_name == update.message.text) - command.execute(using InputCommand(_name)) - ok = true - ok + event use classOf[InputCommand] consume { input => + logger trace s"got input command {$input} from event-context" + + for ((name, command_instance) <- commands.commands_uni) { + logger trace s"checking uni-meow $name" + if (name == input.command) + logger trace "checked" + command_instance.execute(using input, event.update) + event.setEventOk + } + + } onfail { logger trace "not command (for uni-meow)" } } diff --git a/src/main/scala/cc/sukazyo/cono/morny/bot/event/OnUserRandom.scala b/src/main/scala/cc/sukazyo/cono/morny/bot/event/OnUserRandom.scala index caa2dc8..10bd70a 100644 --- a/src/main/scala/cc/sukazyo/cono/morny/bot/event/OnUserRandom.scala +++ b/src/main/scala/cc/sukazyo/cono/morny/bot/event/OnUserRandom.scala @@ -1,7 +1,7 @@ package cc.sukazyo.cono.morny.bot.event import cc.sukazyo.cono.morny.MornyCoeur -import cc.sukazyo.cono.morny.bot.api.EventListener +import cc.sukazyo.cono.morny.bot.api.{EventEnv, EventListener} import cc.sukazyo.cono.morny.util.tgapi.TelegramExtensions.Bot.exec import com.pengrad.telegrambot.model.Update import com.pengrad.telegrambot.request.SendMessage @@ -17,10 +17,11 @@ class OnUserRandom (using coeur: MornyCoeur) { private val USER_OR_QUERY = "^(.+)(?:还是|or)(.+)$" r private val USER_IF_QUERY = "^(.+)(?:吗\\?|?|\\?|吗?)$" r - override def onMessage (using update: Update): Boolean = { + override def onMessage (using event: EventEnv): Unit = { + import event.update - if update.message.text == null then return false - if !(update.message.text startsWith "/") then return false + if update.message.text == null then return; + if !(update.message.text startsWith "/") then return import cc.sukazyo.cono.morny.util.UseRandom.rand_half val query = update.message.text substring 1 @@ -29,17 +30,17 @@ class OnUserRandom (using coeur: MornyCoeur) { if rand_half then _con1 else _con2 case USER_IF_QUERY(_con) => // for capability with [[OnQuestionMarkReply]] - if OnQuestionMarkReply.isAllMessageMark(using _con) then return false + if OnQuestionMarkReply.isAllMessageMark(using _con) then return; (if rand_half then "不" else "") + _con case _ => null - //noinspection DuplicatedCode - if result == null then return false + if result == null then return; coeur.account exec SendMessage( - update.message.chat.id, result + update.message.chat.id, + result ).replyToMessageId(update.message.messageId) - true + event.setEventOk } @@ -51,23 +52,23 @@ class OnUserRandom (using coeur: MornyCoeur) { private val word_pattern = "^([\\w\\W]*)?(?:尊嘟假嘟|(?:O\\.o|o\\.O))$"r private val keywords = Array("尊嘟假嘟", "O.o", "o.O") - override def onMessage (using event: Update): Boolean = { + override def onMessage (using event: EventEnv): Unit = { + import event.update - if event.message.text == null then return false + if update.message.text == null then return var result: String|Null = null import cc.sukazyo.cono.morny.util.UseRandom.rand_half for (k <- keywords) - if event.message.text endsWith k then + if update.message.text endsWith k then result = if rand_half then "尊嘟" else "假嘟" - //noinspection DuplicatedCode - if result == null then return false + if result == null then return; coeur.account exec SendMessage( - event.message.chat.id, + update.message.chat.id, result - ).replyToMessageId(event.message.messageId) - true + ).replyToMessageId(update.message.messageId) + event.setEventOk } diff --git a/src/main/scala/cc/sukazyo/cono/morny/bot/event/OnUserSlashAction.scala b/src/main/scala/cc/sukazyo/cono/morny/bot/event/OnUserSlashAction.scala index eb3e119..7e42632 100644 --- a/src/main/scala/cc/sukazyo/cono/morny/bot/event/OnUserSlashAction.scala +++ b/src/main/scala/cc/sukazyo/cono/morny/bot/event/OnUserSlashAction.scala @@ -1,7 +1,7 @@ package cc.sukazyo.cono.morny.bot.event import cc.sukazyo.cono.morny.MornyCoeur -import cc.sukazyo.cono.morny.bot.api.EventListener +import cc.sukazyo.cono.morny.bot.api.{EventEnv, EventListener} import cc.sukazyo.cono.morny.util.tgapi.formatting.TelegramFormatter.* import cc.sukazyo.cono.morny.util.tgapi.formatting.TelegramParseEscape.escapeHtml as h import cc.sukazyo.cono.morny.util.UniversalCommand @@ -16,10 +16,11 @@ class OnUserSlashAction (using coeur: MornyCoeur) extends EventListener { private val TG_FORMAT = "^\\w+(@\\w+)?$"r - override def onMessage (using update: Update): Boolean = { + override def onMessage (using event: EventEnv): Unit = { + import event.update val text = update.message.text - if text == null then return false + if text == null then return; if (text startsWith "/") { @@ -39,14 +40,14 @@ class OnUserSlashAction (using coeur: MornyCoeur) extends EventListener { actions(0) match // ignore Telegram command like case TG_FORMAT(_) => - return false + return; // ignore Path link - case x if x contains "/" => return false + case x if x contains "/" => return; case _ => val isHardParse = actions(0) isBlank def hp_len(i: Int) = if isHardParse then i+1 else i - if isHardParse && actions.length < 2 then return false + if isHardParse && actions.length < 2 then return val v_verb = actions(hp_len(0)) val hasObject = actions.length != hp_len(1) val v_object = @@ -70,9 +71,9 @@ class OnUserSlashAction (using coeur: MornyCoeur) extends EventListener { if hasObject then h(v_object+" ") else "" ) ).parseMode(ParseMode HTML).replyToMessageId(update.message.messageId) - true + event.setEventOk - } else false + } } diff --git a/src/test/scala/live/LiveMain.scala b/src/test/scala/live/LiveMain.scala index 2b7de09..f877dc3 100644 --- a/src/test/scala/live/LiveMain.scala +++ b/src/test/scala/live/LiveMain.scala @@ -1,9 +1,14 @@ package live +import cc.sukazyo.cono.morny.bot.api.EventEnv import cc.sukazyo.cono.morny.test.utils.BiliToolTest @main def LiveMain (args: String*): Unit = { - org.scalatest.run(BiliToolTest()) + val env: EventEnv = EventEnv(null) + + env provide "abcdefg" + + env use classOf[String] consume { (str: String) => println(str) } onfail { println("no str found in the env") } } From 79206dd13b2f984027090da292e534d01f2b74ed Mon Sep 17 00:00:00 2001 From: Eyre_S Date: Sun, 15 Oct 2023 21:14:54 +0800 Subject: [PATCH 2/4] new logger level and formatter - add MornyLogLevels with new log level NOTICE(notice) and ATTION(attention) - make mechanic and morny (but not coeur) related INFO to NOTICE, warn to ATTION - add MornyFormatterConsole to support the formatted time display, and some other formatter changed - now startup key output will hide key except the starting and ending 4 chars - change the function definition of consume in EventEnv - removed src/test/scala/live/LiveMain - and added gitignore for src/test/scala/live --- gradle.properties | 4 ++-- settings.gradle | 2 +- .../scala/cc/sukazyo/cono/morny/Log.scala | 16 +++++++------ .../cc/sukazyo/cono/morny/MornyCoeur.scala | 5 ++-- .../cc/sukazyo/cono/morny/ServerMain.scala | 7 ++++++ .../sukazyo/cono/morny/bot/api/EventEnv.scala | 14 ++++------- .../morny/bot/command/MornyCommands.scala | 4 ++-- .../morny/bot/command/MornyManagers.scala | 8 +++---- .../morny/bot/event/OnUniMeowTrigger.scala | 3 +-- .../cono/morny/daemon/MedicationTimer.scala | 8 +++---- .../cono/morny/daemon/MornyDaemons.scala | 8 +++---- .../internal/logging/IMornyLogLevelImpl.scala | 12 ++++++++++ .../logging/MornyFormatterConsole.scala | 24 +++++++++++++++++++ .../internal/logging/MornyLogLevels.scala | 13 ++++++++++ .../internal/logging/MornyLoggerBase.scala | 22 +++++++++++++++++ .../cono/morny/util/StringEnsure.scala | 3 +++ src/test/scala/.gitignore | 1 + src/test/scala/live/LiveMain.scala | 14 ----------- 18 files changed, 116 insertions(+), 52 deletions(-) create mode 100644 src/main/scala/cc/sukazyo/cono/morny/internal/logging/IMornyLogLevelImpl.scala create mode 100644 src/main/scala/cc/sukazyo/cono/morny/internal/logging/MornyFormatterConsole.scala create mode 100644 src/main/scala/cc/sukazyo/cono/morny/internal/logging/MornyLogLevels.scala create mode 100644 src/main/scala/cc/sukazyo/cono/morny/internal/logging/MornyLoggerBase.scala create mode 100644 src/test/scala/.gitignore delete mode 100644 src/test/scala/live/LiveMain.scala diff --git a/gradle.properties b/gradle.properties index 9f87499..d78eb7a 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.1.1.xiongan-dev1 +VERSION = 1.1.1.xiongan-dev2 USE_DELTA = false VERSION_DELTA = @@ -17,7 +17,7 @@ CODENAME = nanchang lib_spotbugs_v = 4.7.3 lib_scalamodule_xml_v = 2.2.0 -lib_messiva_v = 0.1.1 +lib_messiva_v = 0.2.0 lib_resourcetools_v = 0.2.2 lib_javatelegramapi_v = 6.2.0 diff --git a/settings.gradle b/settings.gradle index 92f8302..b7b457e 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,2 +1,2 @@ -rootProject.name = 'Coeur Morny Cono' +rootProject.name = "Coeur Morny Cono" diff --git a/src/main/scala/cc/sukazyo/cono/morny/Log.scala b/src/main/scala/cc/sukazyo/cono/morny/Log.scala index 5ef60b3..c8dd452 100644 --- a/src/main/scala/cc/sukazyo/cono/morny/Log.scala +++ b/src/main/scala/cc/sukazyo/cono/morny/Log.scala @@ -1,25 +1,27 @@ package cc.sukazyo.cono.morny +import cc.sukazyo.cono.morny.internal.logging.{MornyFormatterConsole, MornyLoggerBase} import cc.sukazyo.messiva.appender.ConsoleAppender import cc.sukazyo.messiva.formatter.SimpleFormatter -import cc.sukazyo.messiva.log.LogLevel +import cc.sukazyo.messiva.log.LogLevels import cc.sukazyo.messiva.logger.Logger import java.io.{PrintWriter, StringWriter} object Log { - val logger: Logger = Logger( + val logger: MornyLoggerBase = MornyLoggerBase( ConsoleAppender( - SimpleFormatter() + MornyFormatterConsole() ) - ).minLevel(LogLevel.INFO) + ) + logger minLevel LogLevels.INFO - def debug: Boolean = logger.levelSetting.minLevel.level <= LogLevel.DEBUG.level + def debug: Boolean = logger.levelSetting.minLevel.level <= LogLevels.DEBUG.level def debug(is: Boolean): Unit = - if is then logger.minLevel(LogLevel.ALL) - else logger.minLevel(LogLevel.INFO) + if is then logger.minLevel(LogLevels.ALL) + else logger.minLevel(LogLevels.INFO) def exceptionLog (e: Throwable): String = val stackTrace = StringWriter() diff --git a/src/main/scala/cc/sukazyo/cono/morny/MornyCoeur.scala b/src/main/scala/cc/sukazyo/cono/morny/MornyCoeur.scala index 905489f..e9337cf 100644 --- a/src/main/scala/cc/sukazyo/cono/morny/MornyCoeur.scala +++ b/src/main/scala/cc/sukazyo/cono/morny/MornyCoeur.scala @@ -27,7 +27,8 @@ class MornyCoeur (using val config: MornyConfig) { logger info "Coeur starting..." - logger info s"args key:\n ${config.telegramBotKey}" + import cc.sukazyo.cono.morny.util.StringEnsure.deSensitive + logger info s"args key:\n ${config.telegramBotKey deSensitive 4}" if config.telegramBotUsername ne null then logger info s"login as:\n ${config.telegramBotUsername}" @@ -92,7 +93,7 @@ class MornyCoeur (using val config: MornyConfig) { def saveDataAll(): Unit = { // nothing to do - logger info "done all save action." + logger notice "done all save action." } private def exitCleanup (): Unit = { diff --git a/src/main/scala/cc/sukazyo/cono/morny/ServerMain.scala b/src/main/scala/cc/sukazyo/cono/morny/ServerMain.scala index 86a0c0a..e4f7870 100644 --- a/src/main/scala/cc/sukazyo/cono/morny/ServerMain.scala +++ b/src/main/scala/cc/sukazyo/cono/morny/ServerMain.scala @@ -5,11 +5,15 @@ import cc.sukazyo.cono.morny.MornyConfig.CheckFailure import cc.sukazyo.cono.morny.util.CommonFormat import java.time.ZoneOffset +import java.util.TimeZone import scala.collection.mutable.ArrayBuffer import scala.language.postfixOps object ServerMain { + val tz: TimeZone = TimeZone getDefault + val tz_offset: ZoneOffset = ZoneOffset ofTotalSeconds (tz.getRawOffset/1000) + private val THREAD_MORNY_INIT: String = "morny-init" def main (args: Array[String]): Unit = { @@ -135,6 +139,9 @@ object ServerMain { |- Morny ${MornySystem.CODENAME toUpperCase} |- <${MornySystem.getJarMD5}> [${BuildConfig.CODE_TIMESTAMP}]""".stripMargin + // due to [[MornyFormatterConsole]] will use a localized time, it will output to the log + logger info s"logging time will use time-zone ${tz.getID} ($tz_offset)" + /// /// Check Coeur arguments /// finally start Coeur Program diff --git a/src/main/scala/cc/sukazyo/cono/morny/bot/api/EventEnv.scala b/src/main/scala/cc/sukazyo/cono/morny/bot/api/EventEnv.scala index ec34522..6cc7dea 100644 --- a/src/main/scala/cc/sukazyo/cono/morny/bot/api/EventEnv.scala +++ b/src/main/scala/cc/sukazyo/cono/morny/bot/api/EventEnv.scala @@ -3,8 +3,6 @@ package cc.sukazyo.cono.morny.bot.api import com.pengrad.telegrambot.model.Update import scala.collection.mutable -import scala.reflect.ClassTag -import scala.util.boundary class EventEnv ( @@ -24,14 +22,10 @@ class EventEnv ( def provide (i: Any): Unit = variables += (i.getClass -> i) - def use [T] (t: Class[T]): ConsumeProvider[T] = ConsumeProvider(t) - - class ConsumeProvider[T] (t: Class[T]) { - def consume (consumer: T => Unit): ConsumeResult = { - variables get t match - case Some(i) => consumer(i.asInstanceOf[T]); ConsumeResult(true) - case None => ConsumeResult(false) - } + def consume [T] (t: Class[T]) (consumer: T => Unit): ConsumeResult = { + variables get t match + case Some(i) => consumer(i.asInstanceOf[T]); ConsumeResult(true) + case None => ConsumeResult(false) } class ConsumeResult (success: Boolean) { diff --git a/src/main/scala/cc/sukazyo/cono/morny/bot/command/MornyCommands.scala b/src/main/scala/cc/sukazyo/cono/morny/bot/command/MornyCommands.scala index 0121b0c..e924d7e 100644 --- a/src/main/scala/cc/sukazyo/cono/morny/bot/command/MornyCommands.scala +++ b/src/main/scala/cc/sukazyo/cono/morny/bot/command/MornyCommands.scala @@ -91,14 +91,14 @@ class MornyCommands (using coeur: MornyCoeur) { val listing = commands_toTelegramList automaticTGListRemove() coeur.account exec SetMyCommands(listing:_*) - logger info + logger notice s"""automatic updated telegram command list : |${commandsTelegramList_toString(listing)}""".stripMargin } def automaticTGListRemove (): Unit = { coeur.account exec DeleteMyCommands() - logger info "cleaned up command list" + logger notice "cleaned up command list" } private def commandsTelegramList_toString (list: Array[BotCommand]): String = diff --git a/src/main/scala/cc/sukazyo/cono/morny/bot/command/MornyManagers.scala b/src/main/scala/cc/sukazyo/cono/morny/bot/command/MornyManagers.scala index 5a143c0..aea4127 100644 --- a/src/main/scala/cc/sukazyo/cono/morny/bot/command/MornyManagers.scala +++ b/src/main/scala/cc/sukazyo/cono/morny/bot/command/MornyManagers.scala @@ -31,7 +31,7 @@ class MornyManagers (using coeur: MornyCoeur) { event.message.chat.id, TelegramStickers ID_EXIT ).replyToMessageId(event.message.messageId) - logger info s"Morny exited by user ${user toLogTag}" + logger attention s"Morny exited by user ${user toLogTag}" coeur.exit(0, user) } else { @@ -40,7 +40,7 @@ class MornyManagers (using coeur: MornyCoeur) { event.message.chat.id, TelegramStickers ID_403 ).replyToMessageId(event.message.messageId) - logger info s"403 exit caught from user ${user toLogTag}" + logger attention s"403 exit caught from user ${user toLogTag}" coeur.daemons.reporter.unauthenticatedAction("/exit", user) } @@ -62,7 +62,7 @@ class MornyManagers (using coeur: MornyCoeur) { if (coeur.trusted isTrusted user.id) { - logger info s"call save from command by ${user toLogTag}" + logger attention s"call save from command by ${user toLogTag}" coeur.saveDataAll() coeur.account exec SendSticker( event.message.chat.id, @@ -75,7 +75,7 @@ class MornyManagers (using coeur: MornyCoeur) { event.message.chat.id, TelegramStickers ID_403 ).replyToMessageId(event.message.messageId) - logger info s"403 save caught from user ${user toLogTag}" + logger attention s"403 save caught from user ${user toLogTag}" coeur.daemons.reporter.unauthenticatedAction("/save", user) } diff --git a/src/main/scala/cc/sukazyo/cono/morny/bot/event/OnUniMeowTrigger.scala b/src/main/scala/cc/sukazyo/cono/morny/bot/event/OnUniMeowTrigger.scala index 6667a15..ccf140f 100644 --- a/src/main/scala/cc/sukazyo/cono/morny/bot/event/OnUniMeowTrigger.scala +++ b/src/main/scala/cc/sukazyo/cono/morny/bot/event/OnUniMeowTrigger.scala @@ -5,13 +5,12 @@ import cc.sukazyo.cono.morny.bot.command.MornyCommands import cc.sukazyo.cono.morny.util.tgapi.InputCommand import cc.sukazyo.cono.morny.Log.logger import cc.sukazyo.cono.morny.MornyCoeur -import com.pengrad.telegrambot.model.Update class OnUniMeowTrigger (using commands: MornyCommands) (using coeur: MornyCoeur) extends EventListener { override def onMessage (using event: EventEnv): Unit = { - event use classOf[InputCommand] consume { input => + event.consume (classOf[InputCommand]) { input => logger trace s"got input command {$input} from event-context" for ((name, command_instance) <- commands.commands_uni) { diff --git a/src/main/scala/cc/sukazyo/cono/morny/daemon/MedicationTimer.scala b/src/main/scala/cc/sukazyo/cono/morny/daemon/MedicationTimer.scala index 6e0ed09..4840418 100644 --- a/src/main/scala/cc/sukazyo/cono/morny/daemon/MedicationTimer.scala +++ b/src/main/scala/cc/sukazyo/cono/morny/daemon/MedicationTimer.scala @@ -30,11 +30,11 @@ class MedicationTimer (using coeur: MornyCoeur) extends Thread { override def run (): Unit = { if ((notify_toChat == -1) || (notify_atHour isEmpty)) { - logger info "Medication Timer disabled : related param is not complete set" + logger notice "Medication Timer disabled : related param is not complete set" return } - logger info "Medication Timer started." + logger notice "Medication Timer started." while (!this.isInterrupted) { try { val next_time = calcNextRoutineTimestamp(System.currentTimeMillis, use_timeZone, notify_atHour) @@ -47,7 +47,7 @@ class MedicationTimer (using coeur: MornyCoeur) extends Thread { } catch case _: InterruptedException => interrupt() - logger info "MedicationTimer was interrupted, will be exit now" + logger notice "MedicationTimer was interrupted, will be exit now" case ill: IllegalArgumentException => logger warn "MedicationTimer will not work due to: " + ill.getMessage interrupt() @@ -58,7 +58,7 @@ class MedicationTimer (using coeur: MornyCoeur) extends Thread { .stripMargin coeur.daemons.reporter.exception(e) } - logger info "Medication Timer stopped." + logger notice "Medication Timer stopped." } diff --git a/src/main/scala/cc/sukazyo/cono/morny/daemon/MornyDaemons.scala b/src/main/scala/cc/sukazyo/cono/morny/daemon/MornyDaemons.scala index b748480..44c0edc 100644 --- a/src/main/scala/cc/sukazyo/cono/morny/daemon/MornyDaemons.scala +++ b/src/main/scala/cc/sukazyo/cono/morny/daemon/MornyDaemons.scala @@ -11,18 +11,18 @@ class MornyDaemons (using val coeur: MornyCoeur) { def start (): Unit = { - logger info "ALL Morny Daemons starting..." + logger notice "ALL Morny Daemons starting..." // TrackerDataManager.init(); medicationTimer.start() - logger info "Morny Daemons started." + logger notice "Morny Daemons started." } def stop (): Unit = { - logger.info("stopping All Morny Daemons...") + logger notice "stopping All Morny Daemons..." // TrackerDataManager.DAEMON.interrupt(); medicationTimer.interrupt() @@ -31,7 +31,7 @@ class MornyDaemons (using val coeur: MornyCoeur) { catch case e: InterruptedException => e.printStackTrace(System.out) - logger.info("stopped ALL Morny Daemons.") + logger notice "stopped ALL Morny Daemons." } } diff --git a/src/main/scala/cc/sukazyo/cono/morny/internal/logging/IMornyLogLevelImpl.scala b/src/main/scala/cc/sukazyo/cono/morny/internal/logging/IMornyLogLevelImpl.scala new file mode 100644 index 0000000..80d2847 --- /dev/null +++ b/src/main/scala/cc/sukazyo/cono/morny/internal/logging/IMornyLogLevelImpl.scala @@ -0,0 +1,12 @@ +package cc.sukazyo.cono.morny.internal.logging + +import cc.sukazyo.messiva.log.Message + +trait IMornyLogLevelImpl { + + def notice (message: String): Unit + def notice (message: Message): Unit + def attention (message: String): Unit + def attention (message: Message): Unit + +} diff --git a/src/main/scala/cc/sukazyo/cono/morny/internal/logging/MornyFormatterConsole.scala b/src/main/scala/cc/sukazyo/cono/morny/internal/logging/MornyFormatterConsole.scala new file mode 100644 index 0000000..2d9514a --- /dev/null +++ b/src/main/scala/cc/sukazyo/cono/morny/internal/logging/MornyFormatterConsole.scala @@ -0,0 +1,24 @@ +package cc.sukazyo.cono.morny.internal.logging + +import cc.sukazyo.cono.morny.util.CommonFormat.formatDate +import cc.sukazyo.cono.morny.ServerMain +import cc.sukazyo.messiva.formatter.ILogFormatter +import cc.sukazyo.messiva.log.Log + +import java.time.{ZoneId, ZoneOffset} +import java.util.TimeZone + +class MornyFormatterConsole extends ILogFormatter { + + override def format (log: Log): String = + val message = StringBuilder() + val dt = formatDate(log.timestamp, ServerMain.tz_offset) + val prompt_heading = s"[$dt][${log.thread.getName}]" + val prompt_newline = "'" * prompt_heading.length + val prompt_levelTag = s"${log.level.tag}::: " + message ++= prompt_heading ++= prompt_levelTag ++= log.message.message(0) + for (line <- log.message.message drop 1) + message += '\n' ++= prompt_newline ++= prompt_levelTag ++= line + message toString + +} diff --git a/src/main/scala/cc/sukazyo/cono/morny/internal/logging/MornyLogLevels.scala b/src/main/scala/cc/sukazyo/cono/morny/internal/logging/MornyLogLevels.scala new file mode 100644 index 0000000..acaf7b8 --- /dev/null +++ b/src/main/scala/cc/sukazyo/cono/morny/internal/logging/MornyLogLevels.scala @@ -0,0 +1,13 @@ +package cc.sukazyo.cono.morny.internal.logging + +import cc.sukazyo.messiva.log.ILogLevel + +enum MornyLogLevels ( + override val level: Float, + override val tag: String +) extends ILogLevel { + + case NOTICE extends MornyLogLevels(0.2f, "NOTICE") + case ATTENTION extends MornyLogLevels(0.3f, "ATTION") + +} diff --git a/src/main/scala/cc/sukazyo/cono/morny/internal/logging/MornyLoggerBase.scala b/src/main/scala/cc/sukazyo/cono/morny/internal/logging/MornyLoggerBase.scala new file mode 100644 index 0000000..5d4b143 --- /dev/null +++ b/src/main/scala/cc/sukazyo/cono/morny/internal/logging/MornyLoggerBase.scala @@ -0,0 +1,22 @@ +package cc.sukazyo.cono.morny.internal.logging + +import cc.sukazyo.messiva.appender.IAppender +import cc.sukazyo.messiva.log.{Log, Message} +import cc.sukazyo.messiva.logger.Logger + +class MornyLoggerBase extends Logger with IMornyLogLevelImpl { + + def this (appends: IAppender*) = + this() + this.appends.addAll(java.util.List.of(appends:_*)) + + override def notice (message: String): Unit = + pushToAllAppender(Log(1, new Message(message), MornyLogLevels.NOTICE)) + override def notice (message: Message): Unit = + pushToAllAppender(Log(1, message, MornyLogLevels.NOTICE)) + override def attention (message: String): Unit = + pushToAllAppender(Log(1, new Message(message), MornyLogLevels.ATTENTION)) + override def attention (message: Message): Unit = + pushToAllAppender(Log(1, message, MornyLogLevels.ATTENTION)) + +} diff --git a/src/main/scala/cc/sukazyo/cono/morny/util/StringEnsure.scala b/src/main/scala/cc/sukazyo/cono/morny/util/StringEnsure.scala index c56dd16..3fd795e 100644 --- a/src/main/scala/cc/sukazyo/cono/morny/util/StringEnsure.scala +++ b/src/main/scala/cc/sukazyo/cono/morny/util/StringEnsure.scala @@ -12,6 +12,9 @@ object StringEnsure { } else str } + def deSensitive (keepStart: Int = 2, keepEnd: Int = 4, sensitive_cover: Char = '*'): String = + (str take keepStart) + (sensitive_cover.toString*(str.length-keepStart-keepEnd)) + (str takeRight keepEnd) + } } diff --git a/src/test/scala/.gitignore b/src/test/scala/.gitignore new file mode 100644 index 0000000..e23fe64 --- /dev/null +++ b/src/test/scala/.gitignore @@ -0,0 +1 @@ +live diff --git a/src/test/scala/live/LiveMain.scala b/src/test/scala/live/LiveMain.scala deleted file mode 100644 index f877dc3..0000000 --- a/src/test/scala/live/LiveMain.scala +++ /dev/null @@ -1,14 +0,0 @@ -package live - -import cc.sukazyo.cono.morny.bot.api.EventEnv -import cc.sukazyo.cono.morny.test.utils.BiliToolTest - -@main def LiveMain (args: String*): Unit = { - - val env: EventEnv = EventEnv(null) - - env provide "abcdefg" - - env use classOf[String] consume { (str: String) => println(str) } onfail { println("no str found in the env") } - -} From 60dbcef140f9bdf3cf424556f98077c506bf17db Mon Sep 17 00:00:00 2001 From: Eyre_S Date: Tue, 17 Oct 2023 14:16:29 +0800 Subject: [PATCH 3/4] add urlencode/decode for /encrypt, add b23.tv parse for InlineBilibiliShare --- gradle.properties | 4 +- .../cono/morny/bot/command/Encryptor.scala | 38 ++++-- .../cono/morny/bot/command/Testing.scala | 1 - .../morny/bot/query/ShareToolBilibili.scala | 87 ++++++-------- .../cono/morny/data/BilibiliForms.scala | 84 +++++++++++++ .../cono/morny/data/BilibiliFormsTest.scala | 110 ++++++++++++++++++ 6 files changed, 260 insertions(+), 64 deletions(-) create mode 100644 src/main/scala/cc/sukazyo/cono/morny/data/BilibiliForms.scala create mode 100644 src/test/scala/cc/sukazyo/cono/morny/test/cc/sukazyo/cono/morny/data/BilibiliFormsTest.scala diff --git a/gradle.properties b/gradle.properties index d78eb7a..0945157 100644 --- a/gradle.properties +++ b/gradle.properties @@ -5,12 +5,12 @@ 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.1.1.xiongan-dev2 +VERSION = 1.2.0-alpha1 USE_DELTA = false VERSION_DELTA = -CODENAME = nanchang +CODENAME = xiongan # dependencies diff --git a/src/main/scala/cc/sukazyo/cono/morny/bot/command/Encryptor.scala b/src/main/scala/cc/sukazyo/cono/morny/bot/command/Encryptor.scala index 07b1c5a..b63dde4 100644 --- a/src/main/scala/cc/sukazyo/cono/morny/bot/command/Encryptor.scala +++ b/src/main/scala/cc/sukazyo/cono/morny/bot/command/Encryptor.scala @@ -2,6 +2,7 @@ package cc.sukazyo.cono.morny.bot.command import cc.sukazyo.cono.morny.Log.logger import cc.sukazyo.cono.morny.MornyCoeur +import cc.sukazyo.cono.morny.bot.command.ICommandAlias.ListedAlias import cc.sukazyo.cono.morny.data.TelegramStickers import cc.sukazyo.cono.morny.util.tgapi.InputCommand import cc.sukazyo.cono.morny.util.CommonEncrypt @@ -13,6 +14,7 @@ import com.pengrad.telegrambot.model.request.ParseMode import com.pengrad.telegrambot.request.{GetFile, SendDocument, SendMessage, SendSticker} import java.io.IOException +import java.net.{URLDecoder, URLEncoder} import java.util.Base64 import scala.language.postfixOps @@ -20,7 +22,7 @@ import scala.language.postfixOps class Encryptor (using coeur: MornyCoeur) extends ITelegramCommand { override val name: String = "encrypt" - override val aliases: Array[ICommandAlias] | Null = null + override val aliases: Array[ICommandAlias] | Null = Array(ListedAlias("enc")) override val paramRule: String = "[algorithm|(l)] [(uppercase)]" override val description: String = "通过指定算法加密回复的内容 (目前只支持文本)" @@ -135,6 +137,12 @@ class Encryptor (using coeur: MornyCoeur) extends ITelegramCommand { def genResult_hash (source: XEncryptable, processor: Array[Byte]=>Array[Byte]): EXHash = val hashed = processor(source asByteArray) toHex; EXHash(if mod_uppercase then hashed toUpperCase else hashed) + //noinspection UnitMethodIsParameterless + def echo_unsupported: Unit = + coeur.account exec SendSticker( + event.message.chat.id, + TelegramStickers ID_404 + ).replyToMessageId(event.message.messageId) val result: EXHash|EXFile|EXText = args(0) match case "base64" | "b64" | "base64url" | "base64u" | "b64u" => val _tool_b64 = @@ -154,21 +162,27 @@ class Encryptor (using coeur: MornyCoeur) extends ITelegramCommand { _tool_b64d.decode, CommonEncrypt.lint_base64FileName ) } catch case _: IllegalArgumentException => - coeur.account exec SendSticker( - event.message.chat.id, - TelegramStickers ID_404 // todo: is here better erro notify? - ).replyToMessageId(event.message.messageId) + echo_unsupported return + case "urlencoder" | "urlencode" | "urlenc" | "url" => + input match + case x: XText => + EXText(URLEncoder.encode(x.data, ENCRYPT_STANDARD_CHARSET)) + case _: XFile => echo_unsupported; return; + case "urldecoder" | "urldecode" | "urldec" | "urld" => + input match + case _: XFile => echo_unsupported; return; + case x: XText => + try { EXText(URLDecoder.decode(x.data, ENCRYPT_STANDARD_CHARSET)) } + catch case _: IllegalArgumentException => + echo_unsupported + return case "md5" => genResult_hash(input, MD5) case "sha1" => genResult_hash(input, SHA1) case "sha256" => genResult_hash(input, SHA256) case "sha512" => genResult_hash(input, SHA512) case _ => - coeur.account exec SendSticker( - event.message.chat.id, - TelegramStickers ID_404 - ).replyToMessageId(event.message.messageId) - return; + echo_unsupported; return; // END BLOCK: encrypt // output @@ -203,6 +217,8 @@ class Encryptor (using coeur: MornyCoeur) extends ITelegramCommand { * '''__base64url__''', base64u, b64u
* '''__base64decode__''', base64d, b64d
* '''__base64url-decode__''', base64ud, b64ud
+ * '''urlencode''', urlencode, urlenc, url
+ * '''__urldecoder__''', urldecode, urldec, urld
* '''__sha1__'''
* '''__sha256__'''
* '''__sha512__'''
@@ -218,6 +234,8 @@ class Encryptor (using coeur: MornyCoeur) extends ITelegramCommand { |base64url, base64u, b64u |base64decode, base64d, b64d |base64url-decode, base64ud, b64ud + |urlencoder, urlencode, urlenc, url + |urldecoder, urldecode, urldec, urld |sha1 |sha256 |sha512 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 b7a1393..949fa83 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 @@ -7,7 +7,6 @@ import com.pengrad.telegrambot.model.Update import com.pengrad.telegrambot.model.request.ParseMode import com.pengrad.telegrambot.request.SendMessage -import javax.annotation.{Nonnull, Nullable} import scala.language.postfixOps class Testing (using coeur: MornyCoeur) extends ISimpleCommand { diff --git a/src/main/scala/cc/sukazyo/cono/morny/bot/query/ShareToolBilibili.scala b/src/main/scala/cc/sukazyo/cono/morny/bot/query/ShareToolBilibili.scala index f73fed6..85128db 100644 --- a/src/main/scala/cc/sukazyo/cono/morny/bot/query/ShareToolBilibili.scala +++ b/src/main/scala/cc/sukazyo/cono/morny/bot/query/ShareToolBilibili.scala @@ -1,16 +1,15 @@ package cc.sukazyo.cono.morny.bot.query -import cc.sukazyo.cono.morny.Log.logger +import cc.sukazyo.cono.morny.MornyCoeur import cc.sukazyo.cono.morny.util.tgapi.formatting.NamingUtils.inlineQueryId -import cc.sukazyo.cono.morny.util.BiliTool -import cc.sukazyo.cono.morny.util.UseSelect.select +import cc.sukazyo.cono.morny.Log.{exceptionLog, logger} import com.pengrad.telegrambot.model.Update import com.pengrad.telegrambot.model.request.{InlineQueryResultArticle, InputTextMessageContent, ParseMode} import scala.language.postfixOps import scala.util.matching.Regex -class ShareToolBilibili extends ITelegramQuery { +class ShareToolBilibili (using coeur: MornyCoeur) extends ITelegramQuery { private val TITLE_BILI_AV = "[bilibili] Share video / av" private val TITLE_BILI_BV = "[bilibili] Share video / BV" @@ -23,54 +22,40 @@ class ShareToolBilibili extends ITelegramQuery { override def query (event: Update): List[InlineQueryUnit[_]] | Null = { if (event.inlineQuery.query == null) return null + if (event.inlineQuery.query isBlank) return null - event.inlineQuery.query match - case REGEX_BILI_VIDEO(_url_v, _url_av, _url_bv, _url_param, _url_v_part, _raw_av, _raw_bv) => - - logger debug - s"""====== Share Tool Bilibili Catch ok - |1: ${_url_v} - |2: ${_url_av} - |3: ${_url_bv} - |4: ${_url_param} - |5: ${_url_v_part} - |6: ${_raw_av} - |7: ${_raw_bv}""" - .stripMargin - - var av = select(_url_av, _raw_av) - var bv = select(_url_bv, _raw_bv) - logger trace s"catch id av[$av] bv[$bv]" - val part: Int|Null = if (_url_v_part!=null) _url_v_part toInt else null - logger trace s"catch video part[$part]" - - if (av == null) { - assert (bv != null) - av = BiliTool.toAv(bv) toString; - logger trace s"converted bv[$av] to av[$av]" - } else { - bv = BiliTool.toBv(av toLong) - logger trace s"converted av[$av] to bv[$bv]" - } - - val id_av = s"av$av" - val id_bv = s"BV$bv" - val linkParams = if (part!=null) s"?p=$part" else "" - val link_av = LINK_PREFIX + id_av + linkParams - val link_bv = LINK_PREFIX + id_bv + linkParams - - List( - InlineQueryUnit(InlineQueryResultArticle( - inlineQueryId(ID_PREFIX_BILI_AV+av), TITLE_BILI_AV+av, - InputTextMessageContent(SHARE_FORMAT_HTML.format(link_av, id_av)).parseMode(ParseMode HTML) - )), - InlineQueryUnit(InlineQueryResultArticle( - inlineQueryId(ID_PREFIX_BILI_BV + bv), TITLE_BILI_BV + bv, - InputTextMessageContent(SHARE_FORMAT_HTML.format(link_bv, id_bv)).parseMode(ParseMode HTML) - )) - ) - - case _ => null + import cc.sukazyo.cono.morny.data.BilibiliForms.* + val result: BiliVideoId = + try + parse_videoUrl(event.inlineQuery.query) + 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; + + val av = result.av + val bv = result.bv + val id_av = s"av$av" + val id_bv = s"BV$bv" + val linkParams = if (result.part != null) s"?p=${result.part}" else "" + val link_av = LINK_PREFIX + id_av + linkParams + val link_bv = LINK_PREFIX + id_bv + linkParams + List( + InlineQueryUnit(InlineQueryResultArticle( + inlineQueryId(ID_PREFIX_BILI_AV + av), TITLE_BILI_AV + av, + InputTextMessageContent(SHARE_FORMAT_HTML.format(link_av, id_av)).parseMode(ParseMode HTML) + )), + InlineQueryUnit(InlineQueryResultArticle( + inlineQueryId(ID_PREFIX_BILI_BV + bv), TITLE_BILI_BV + bv, + InputTextMessageContent(SHARE_FORMAT_HTML.format(link_bv, id_bv)).parseMode(ParseMode HTML) + )) + ) } diff --git a/src/main/scala/cc/sukazyo/cono/morny/data/BilibiliForms.scala b/src/main/scala/cc/sukazyo/cono/morny/data/BilibiliForms.scala new file mode 100644 index 0000000..7bf78a4 --- /dev/null +++ b/src/main/scala/cc/sukazyo/cono/morny/data/BilibiliForms.scala @@ -0,0 +1,84 @@ +package cc.sukazyo.cono.morny.data + +import cc.sukazyo.cono.morny.util.BiliTool +import cc.sukazyo.cono.morny.util.UseSelect.select +import okhttp3.{HttpUrl, OkHttpClient, Request} + +import java.io.IOException +import scala.util.matching.Regex +import scala.util.Using + +object BilibiliForms { + + case class BiliVideoId (av: Long, bv: String, part: Int|Null = null) + + private val REGEX_BILI_ID = "^((?:av|AV)(\\d{1,12})|(?:bv|BV)([A-HJ-NP-Za-km-z1-9]{10}))$"r + private val REGEX_BILI_VIDEO: Regex = "^(?:(?:https?://)?(?:www\\.)?bilibili\\.com(?:/s)?/video/((?:av|AV)(\\d{1,12})|(?:bv|BV)([A-HJ-NP-Za-km-z1-9]{10}))/?(\\?(?:p=(\\d+))?.*)?|(?:av|AV)(\\d{1,12})|(?:bv|BV)([A-HJ-NP-Za-km-z1-9]{10}))$" r + + /** parse a Bilibili video link to a [[BiliVideoId]] format Bilibili Video Id + * + * @param url the Bilibili video link -- should be a valid link with av/BV, + * can take some tracking params (will be ignored), can be a search + * result link (have `s/` path). + * @throws IllegalArgumentException when the link is not the valid bilibili video link + * @return the [[BiliVideoId]] contains raw or converted av id, and raw or converted bv id, + * and video part id. + */ + @throws[IllegalArgumentException] + def parse_videoUrl (url: String): BiliVideoId = + url match + 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 = select(_url_bv, _raw_bv) + val part: Int | Null = if (_url_v_part != null) _url_v_part toInt else null + if (av == null) { + assert(bv != null) + BiliVideoId(BiliTool.toAv(bv), bv, part) + } else { + val _av = av.toLong + BiliVideoId(_av, BiliTool.toBv(_av), part) + } + case _ => throw IllegalArgumentException(s"not a valid Bilibili video link: $url") + + private val httpClient = OkHttpClient + .Builder() + .followSslRedirects(true) + .followRedirects(false) + .build() + + /** get the bilibili video url from b23.tv share url. + * + * result url can be used in [[parse_videoUrl]] + * + * @param url b23.tv share url + * @throws IllegalArgumentException the input `url` is not a b23.tv url + * @throws IllegalStateException some exception occurred when getting information from remote + * host, or failed to parse the information got + * @return bilibili video url with tracking params + */ + @throws[IllegalStateException|IllegalArgumentException] + def destructB23Url (url: String): String = + val _url: HttpUrl = HttpUrl.parse( + if url startsWith "http://" then url.replaceFirst("http://", "https://") else url + ) + if _url == null then throw IllegalArgumentException("not a valid url: " + url) + if _url.host != "b23.tv" then throw IllegalArgumentException(s"not a b23 share link: $url") + if (!_url.pathSegments.isEmpty) && _url.pathSegments.get(0).matches(REGEX_BILI_ID.regex) then + throw IllegalArgumentException(s"is a b23 video link: $url ; (use parse_videoUrl directly)") + val result: Option[String] = + try { + Using(httpClient.newCall(Request.Builder().url(_url).build).execute()) { response => + if response.isRedirect then + val _u = response header "Location" + if _u != null then + Some(_u) + else throw IllegalStateException("unable to get b23.tv redir location from: " + response) + else throw IllegalStateException("unable to get b23.tv redir location from: " + response) + }.get + } catch case e: IOException => + throw IllegalStateException("get b23.tv failed.", e) + result match + case Some(_result) => _result + case None => throw IllegalStateException("unable to parse from b23.tv .") + +} diff --git a/src/test/scala/cc/sukazyo/cono/morny/test/cc/sukazyo/cono/morny/data/BilibiliFormsTest.scala b/src/test/scala/cc/sukazyo/cono/morny/test/cc/sukazyo/cono/morny/data/BilibiliFormsTest.scala new file mode 100644 index 0000000..9108053 --- /dev/null +++ b/src/test/scala/cc/sukazyo/cono/morny/test/cc/sukazyo/cono/morny/data/BilibiliFormsTest.scala @@ -0,0 +1,110 @@ +package cc.sukazyo.cono.morny.test.cc.sukazyo.cono.morny.data + +import cc.sukazyo.cono.morny.data.BilibiliForms.* +import cc.sukazyo.cono.morny.test.MornyTests +import org.scalatest.prop.TableDrivenPropertyChecks + +class BilibiliFormsTest extends MornyTests with TableDrivenPropertyChecks { + + "while parsing bilibili video link :" - { + + "raw avXXX should be parsed" in: + parse_videoUrl("av455017605") shouldEqual BiliVideoId(455017605L, "1Q541167Qg") + "raw BVXXX should be parsed" in: + parse_videoUrl("BV1T24y197V2") shouldEqual BiliVideoId(688730800L, "1T24y197V2") + "raw id without av/BV prefix should not be parsed" in: + an[IllegalArgumentException] should be thrownBy parse_videoUrl("1T24y197V2") + an[IllegalArgumentException] should be thrownBy parse_videoUrl("455017605") + "av/bv prefix can be either uppercase or lowercase" in: + parse_videoUrl("bv1T24y197V2") shouldEqual BiliVideoId(688730800L, "1T24y197V2") + parse_videoUrl("AV455017605") shouldEqual BiliVideoId(455017605L, "1Q541167Qg") + + "av/bv bilibili.com link should be parsed" in: + parse_videoUrl("https://www.bilibili.com/video/AV455017605") shouldEqual + BiliVideoId(455017605L, "1Q541167Qg") + parse_videoUrl("https://www.bilibili.com/video/bv1T24y197V2") shouldEqual + BiliVideoId(688730800L, "1T24y197V2") + "bilibili.com link can have protocol http:// or https://" in: + parse_videoUrl("http://www.bilibili.com/video/AV455017605") shouldEqual + BiliVideoId(455017605L, "1Q541167Qg") + "bilibili.com link can omit protocol http or https" in : + parse_videoUrl("www.bilibili.com/video/AV455017605") shouldEqual + BiliVideoId(455017605L, "1Q541167Qg") + "bilibili.com link can omit www. prefix" in : + parse_videoUrl("bilibili.com/video/AV455017605") shouldEqual + BiliVideoId(455017605L, "1Q541167Qg") + parse_videoUrl("https://bilibili.com/video/AV455017605") shouldEqual + BiliVideoId(455017605L, "1Q541167Qg") + "bilibili.com link can be search result link (with /s path prefix)" in : + parse_videoUrl("bilibili.com/s/video/AV455017605") shouldEqual + BiliVideoId(455017605L, "1Q541167Qg") + parse_videoUrl("https://www.bilibili.com/s/video/AV455017605") shouldEqual + BiliVideoId(455017605L, "1Q541167Qg") + "bilibili.com link can only be video link" in : + an[IllegalArgumentException] should be thrownBy parse_videoUrl("bilibili.com/s/media/AV455017605") + an[IllegalArgumentException] should be thrownBy parse_videoUrl("https://www.bilibili.com/media/AV455017605") + an[IllegalArgumentException] should be thrownBy parse_videoUrl("https://www.bilibili.com/AV455017605") + "bilibili.com link can take parameters" in : + parse_videoUrl("https://www.bilibili.com/video/av455017605?vd_source=123456") shouldEqual + BiliVideoId(455017605L, "1Q541167Qg") + parse_videoUrl("bilibili.com/video/AV455017605?mid=12hdowhAID82EQ&289EHD8AHDOIWU8=r2aur9%3Bi0%3AJ%7BRQJH%28QJ.%5BropWG%3AKR%24%28O%7BGR") shouldEqual + BiliVideoId(455017605L, "1Q541167Qg") + "video part within bilibili.com link params should be parsed" in : + parse_videoUrl("https://www.bilibili.com/video/BV1Q541167Qg?p=1") shouldEqual + BiliVideoId(455017605L, "1Q541167Qg", 1) + parse_videoUrl("https://www.bilibili.com/video/av455017605?p=1&vd_source=123456") shouldEqual + BiliVideoId(455017605L, "1Q541167Qg", 1) + // todo: implement it +// parse_videoUrl("bilibili.com/video/AV455017605?mid=12hdowhAI&p=5&x=D82EQ&289EHD8AHDOIWU8=r2aur9%3Bi0%3AJ%7BRQJH%28QJ.%5BropWG%3AKR%24%28O%7BGR") shouldEqual +// BiliVideoId(455017605L, "1Q541167Qg", 5) + + "av id with more than 12 digits should not be parsed" in : + an[IllegalArgumentException] should be thrownBy parse_videoUrl("av4550176087554") + an[IllegalArgumentException] should be thrownBy parse_videoUrl("bilibili.com/video/av4550176087554") + an[IllegalArgumentException] should be thrownBy parse_videoUrl("av455017608755634345565341256") + "av id with 0 digits should not be parsed" in : + an[IllegalArgumentException] should be thrownBy parse_videoUrl("av") + an[IllegalArgumentException] should be thrownBy parse_videoUrl("bilibili.com/video/av") + "BV id with not 10 digits should not be parsed" in : + an[IllegalArgumentException] should be thrownBy parse_videoUrl("BV123456789") + an[IllegalArgumentException] should be thrownBy parse_videoUrl("BV12345678") + an[IllegalArgumentException] should be thrownBy parse_videoUrl("bilibili.com/video/BV12345678901") + + "url which is not bilibili link should not be parsed" in: + an[IllegalArgumentException] should be thrownBy parse_videoUrl("https://www.pilipili.com/video/av123456") + an[IllegalArgumentException] should be thrownBy parse_videoUrl("https://pilipili.com/video/av123456") + an[IllegalArgumentException] should be thrownBy parse_videoUrl("https://blilblil.com/video/av123456") + an[IllegalArgumentException] should be thrownBy parse_videoUrl("https://bilibili.cc/video/av123456") + an[IllegalArgumentException] should be thrownBy parse_videoUrl("https://vxbilibili.com/video/av123456") + an[IllegalArgumentException] should be thrownBy parse_videoUrl("https://bilibiliexc.com/video/av123456") + an[IllegalArgumentException] should be thrownBy parse_videoUrl("b23.tv/av123456") // todo: support it + an[IllegalArgumentException] should be thrownBy parse_videoUrl("C# does not have type erasure. C# has actual generic types deeply baked into the runtime.\n\n好文明") + + } + + "while destruct b23.tv share link :" - { + + val examples = Table( + ("b23_link", "bilibili_video_link"), + ("https://b23.tv/iiCldvZ", "https://www.bilibili.com/video/BV1Gh411P7Sh?buvid=XY6F25B69BE9CF469FF5B917D012C93E95E72&is_story_h5=false&mid=wD6DQnYivIG5pfA3sAGL6A%3D%3D&p=1&plat_id=114&share_from=ugc&share_medium=android&share_plat=android&share_session_id=8081015b-1210-4dea-a665-6746b4850fcd&share_source=COPY&share_tag=s_i×tamp=1689605644&unique_k=iiCldvZ&up_id=19977489"), + ("http://b23.tv/3ymowwx", "https://www.bilibili.com/video/BV15Y411n754?p=1&share_medium=android_i&share_plat=android&share_source=COPY&share_tag=s_i×tamp=1650293889&unique_k=3ymowwx") + ) + + "not b23.tv link is not supported" in: + an[IllegalArgumentException] should be thrownBy destructB23Url("sukazyo.cc/2xhUHO2e") + an[IllegalArgumentException] should be thrownBy destructB23Url("https://sukazyo.cc/2xhUHO2e") + an[IllegalArgumentException] should be thrownBy destructB23Url("长月烬明澹台烬心理分析向解析(一)因果之锁,渡魔之路") + an[IllegalArgumentException] should be thrownBy destructB23Url("https://b23.tvb/JDo2eaD") + an[IllegalArgumentException] should be thrownBy destructB23Url("https://ab23.tv/JDo2eaD") + "b23.tv/avXXX video link is not supported" in: + an[IllegalArgumentException] should be thrownBy destructB23Url("https://b23.tv/av123456") + an[IllegalArgumentException] should be thrownBy destructB23Url("https://b23.tv/BV1Q541167Qg") + + forAll (examples) { (origin, result) => + s"b23 link $origin should be destructed to $result" in: + destructB23Url(origin) shouldEqual result + } + + } + +} From 40bdbec1ecdb814fcac488b547f1d3cbb70795fc Mon Sep 17 00:00:00 2001 From: Eyre_S Date: Tue, 17 Oct 2023 18:49:57 +0800 Subject: [PATCH 4/4] add tests for BilibiliForms, support b23 video link and better v-part parse --- gradle.properties | 2 +- .../cono/morny/data/BilibiliForms.scala | 19 +++++++++++++++---- .../cono/morny/data/BilibiliFormsTest.scala | 17 ++++++++++++----- 3 files changed, 28 insertions(+), 10 deletions(-) diff --git a/gradle.properties b/gradle.properties index 0945157..de9eac3 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.2.0-alpha1 +VERSION = 1.2.0-alpha2 USE_DELTA = false VERSION_DELTA = diff --git a/src/main/scala/cc/sukazyo/cono/morny/data/BilibiliForms.scala b/src/main/scala/cc/sukazyo/cono/morny/data/BilibiliForms.scala index 7bf78a4..962c98b 100644 --- a/src/main/scala/cc/sukazyo/cono/morny/data/BilibiliForms.scala +++ b/src/main/scala/cc/sukazyo/cono/morny/data/BilibiliForms.scala @@ -13,13 +13,16 @@ object BilibiliForms { case class BiliVideoId (av: Long, bv: String, part: Int|Null = null) private val REGEX_BILI_ID = "^((?:av|AV)(\\d{1,12})|(?:bv|BV)([A-HJ-NP-Za-km-z1-9]{10}))$"r - private val REGEX_BILI_VIDEO: Regex = "^(?:(?:https?://)?(?:www\\.)?bilibili\\.com(?:/s)?/video/((?:av|AV)(\\d{1,12})|(?:bv|BV)([A-HJ-NP-Za-km-z1-9]{10}))/?(\\?(?:p=(\\d+))?.*)?|(?:av|AV)(\\d{1,12})|(?:bv|BV)([A-HJ-NP-Za-km-z1-9]{10}))$" 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,12})|(?:bv|BV)([A-HJ-NP-Za-km-z1-9]{10}))/?(?:\\?((?:p=(\\d+))?.*))?|(?:av|AV)(\\d{1,12})|(?:bv|BV)([A-HJ-NP-Za-km-z1-9]{10}))$" 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. * * @param url the Bilibili video link -- should be a valid link with av/BV, * can take some tracking params (will be ignored), can be a search * result link (have `s/` path). + * Or, it can also be a b23 video link: starts with b23.tv hostname with + * no www. prefix, and no /video/ path. * @throws IllegalArgumentException when the link is not the valid bilibili video link * @return the [[BiliVideoId]] contains raw or converted av id, and raw or converted bv id, * and video part id. @@ -28,9 +31,16 @@ object BilibiliForms { def parse_videoUrl (url: String): BiliVideoId = url match 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 = select(_url_bv, _raw_bv) - val part: Int | Null = if (_url_v_part != null) _url_v_part toInt else null + + val part_part = if (_url_param == null) 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) part_part toInt else null + if (av == null) { assert(bv != null) BiliVideoId(BiliTool.toAv(bv), bv, part) @@ -38,6 +48,7 @@ object BilibiliForms { val _av = av.toLong BiliVideoId(_av, BiliTool.toBv(_av), part) } + case _ => throw IllegalArgumentException(s"not a valid Bilibili video link: $url") private val httpClient = OkHttpClient @@ -50,7 +61,7 @@ object BilibiliForms { * * result url can be used in [[parse_videoUrl]] * - * @param url b23.tv share url + * @param url b23.tv share url. * @throws IllegalArgumentException the input `url` is not a b23.tv url * @throws IllegalStateException some exception occurred when getting information from remote * host, or failed to parse the information got diff --git a/src/test/scala/cc/sukazyo/cono/morny/test/cc/sukazyo/cono/morny/data/BilibiliFormsTest.scala b/src/test/scala/cc/sukazyo/cono/morny/test/cc/sukazyo/cono/morny/data/BilibiliFormsTest.scala index 9108053..93388f5 100644 --- a/src/test/scala/cc/sukazyo/cono/morny/test/cc/sukazyo/cono/morny/data/BilibiliFormsTest.scala +++ b/src/test/scala/cc/sukazyo/cono/morny/test/cc/sukazyo/cono/morny/data/BilibiliFormsTest.scala @@ -3,6 +3,7 @@ package cc.sukazyo.cono.morny.test.cc.sukazyo.cono.morny.data import cc.sukazyo.cono.morny.data.BilibiliForms.* import cc.sukazyo.cono.morny.test.MornyTests import org.scalatest.prop.TableDrivenPropertyChecks +import org.scalatest.tagobjects.{Network, Slow} class BilibiliFormsTest extends MornyTests with TableDrivenPropertyChecks { @@ -54,9 +55,8 @@ class BilibiliFormsTest extends MornyTests with TableDrivenPropertyChecks { BiliVideoId(455017605L, "1Q541167Qg", 1) parse_videoUrl("https://www.bilibili.com/video/av455017605?p=1&vd_source=123456") shouldEqual BiliVideoId(455017605L, "1Q541167Qg", 1) - // todo: implement it -// parse_videoUrl("bilibili.com/video/AV455017605?mid=12hdowhAI&p=5&x=D82EQ&289EHD8AHDOIWU8=r2aur9%3Bi0%3AJ%7BRQJH%28QJ.%5BropWG%3AKR%24%28O%7BGR") shouldEqual -// BiliVideoId(455017605L, "1Q541167Qg", 5) + parse_videoUrl("bilibili.com/video/AV455017605?mid=12hdowhAI&p=5&x=D82EQ&289EHD8AHDOIWU8=r2aur9%3Bi0%3AJ%7BRQJH%28QJ.%5BropWG%3AKR%24%28O%7BGR") shouldEqual + BiliVideoId(455017605L, "1Q541167Qg", 5) "av id with more than 12 digits should not be parsed" in : an[IllegalArgumentException] should be thrownBy parse_videoUrl("av4550176087554") @@ -77,9 +77,16 @@ class BilibiliFormsTest extends MornyTests with TableDrivenPropertyChecks { an[IllegalArgumentException] should be thrownBy parse_videoUrl("https://bilibili.cc/video/av123456") an[IllegalArgumentException] should be thrownBy parse_videoUrl("https://vxbilibili.com/video/av123456") an[IllegalArgumentException] should be thrownBy parse_videoUrl("https://bilibiliexc.com/video/av123456") - an[IllegalArgumentException] should be thrownBy parse_videoUrl("b23.tv/av123456") // todo: support it an[IllegalArgumentException] should be thrownBy parse_videoUrl("C# does not have type erasure. C# has actual generic types deeply baked into the runtime.\n\n好文明") + "url which is a b23 video link should be parsed" in: + parse_videoUrl("https://b23.tv/av688730800") shouldEqual BiliVideoId(688730800L, "1T24y197V2") + parse_videoUrl("http://b23.tv/BV1T24y197V2") shouldEqual BiliVideoId(688730800L, "1T24y197V2") + parse_videoUrl("b23.tv/BV1T24y197V2") shouldEqual BiliVideoId(688730800L, "1T24y197V2") + "b23 video link should not take www. or /video prefix" in: + an[IllegalArgumentException] should be thrownBy parse_videoUrl("https://www.b23.tv/av123456") + an[IllegalArgumentException] should be thrownBy parse_videoUrl("https://b23.tv/video/av123456") + } "while destruct b23.tv share link :" - { @@ -101,7 +108,7 @@ class BilibiliFormsTest extends MornyTests with TableDrivenPropertyChecks { an[IllegalArgumentException] should be thrownBy destructB23Url("https://b23.tv/BV1Q541167Qg") forAll (examples) { (origin, result) => - s"b23 link $origin should be destructed to $result" in: + s"b23 link $origin should be destructed to $result" taggedAs (Slow, Network) in: destructB23Url(origin) shouldEqual result }