refactor bot execute method etc.

- change Log.exceptionLog(throwable) to an extension method throwable.toLogString in UseThrowable
- added SimpleCommandManager as the backend of both MornyCommandManager and UniMeowCommandManager
- added Request().execute and Request().unsafeExecute() extensions.
  - change all the request execute using those extensions.
- change some method with infix keyword.
- change MornyTrusted methods using User/Chat object instead of a bare id.
- update scala to 3.4.0-RC4 and fix infix warnings.
This commit is contained in:
A.C.Sukazyo Eyre 2024-02-15 22:26:39 +08:00
parent 9cc8b49459
commit 8d04d6529c
Signed by: Eyre_S
GPG Key ID: C17CE40291207874
85 changed files with 764 additions and 454 deletions

View File

@ -3,7 +3,7 @@ aether.AetherKeys.aetherOldVersionMethod := true
ThisBuild / organization := "cc.sukazyo" ThisBuild / organization := "cc.sukazyo"
ThisBuild / organizationName := "A.C. Sukazyo Eyre" ThisBuild / organizationName := "A.C. Sukazyo Eyre"
ThisBuild / scalaVersion := "3.3.1" ThisBuild / scalaVersion := "3.4.0-RC4"
resolvers ++= Seq( resolvers ++= Seq(
"-ws-releases" at "https://mvn.sukazyo.cc/releases" "-ws-releases" at "https://mvn.sukazyo.cc/releases"
@ -56,6 +56,20 @@ lazy val root = (project in file("."))
"-target", "17" "-target", "17"
), ),
autoAPIMappings := true, autoAPIMappings := true,
apiMappings ++= {
def mappingsFor(organization: String, names: List[String], location: String, revision: String => String = identity): Seq[(File, URL)] =
for {
entry: Attributed[File] <- (Compile / fullClasspath).value
module: ModuleID <- entry.get(moduleID.key)
if module.organization == organization
if names.exists(module.name.startsWith)
} yield entry.data -> url(location.format(revision(module.revision)))
val mappings: Seq[(File, URL)] = Seq(
mappingsFor("org.scala-lang", List("scala-library"), "https://scala-lang.org/api/%s/"),
mappingsFor("com.github.pengrad", "java-telegram-bot-api"::Nil, "https://jitpack.io/com/github/pengrad/java-telegram-bot-api/6.3.0/javadoc/"),
).flatten
mappings.toMap
},
assemblyMergeStrategy := { assemblyMergeStrategy := {
case module if module endsWith "module-info.class" => MergeStrategy.concat case module if module endsWith "module-info.class" => MergeStrategy.concat

View File

@ -8,7 +8,7 @@ object MornyConfiguration {
val MORNY_CODE_STORE = "https://github.com/Eyre-S/Coeur-Morny-Cono" val MORNY_CODE_STORE = "https://github.com/Eyre-S/Coeur-Morny-Cono"
val MORNY_COMMIT_PATH = "https://github.com/Eyre-S/Coeur-Morny-Cono/commit/%s" val MORNY_COMMIT_PATH = "https://github.com/Eyre-S/Coeur-Morny-Cono/commit/%s"
val VERSION = "2.0.0-alpha13" val VERSION = "2.0.0-alpha14"
val VERSION_DELTA: Option[String] = None val VERSION_DELTA: Option[String] = None
val CODENAME = "guanggu" val CODENAME = "guanggu"

View File

@ -4,14 +4,14 @@ import cc.sukazyo.cono.morny.core.MornyCoeur
import cc.sukazyo.cono.morny.core.bot.api.{EventEnv, EventListener} import cc.sukazyo.cono.morny.core.bot.api.{EventEnv, EventListener}
import cc.sukazyo.cono.morny.data.TelegramStickers import cc.sukazyo.cono.morny.data.TelegramStickers
import cc.sukazyo.cono.morny.util.tgapi.formatting.TelegramFormatter.* import cc.sukazyo.cono.morny.util.tgapi.formatting.TelegramFormatter.*
import cc.sukazyo.cono.morny.util.tgapi.TelegramExtensions.Bot.exec import cc.sukazyo.cono.morny.util.tgapi.TelegramExtensions.Requests.unsafeExecute
import com.pengrad.telegrambot.model.{Chat, Message, User} import com.pengrad.telegrambot.model.{Chat, Message, User}
import com.pengrad.telegrambot.model.request.ParseMode import com.pengrad.telegrambot.model.request.ParseMode
import com.pengrad.telegrambot.request.{ForwardMessage, GetChat, SendMessage, SendSticker} import com.pengrad.telegrambot.request.{ForwardMessage, GetChat, SendMessage, SendSticker}
import com.pengrad.telegrambot.TelegramBot
import scala.language.postfixOps
class OnCallMe (using coeur: MornyCoeur) extends EventListener { class OnCallMe (using coeur: MornyCoeur) extends EventListener {
private given TelegramBot = coeur.account
private val me = coeur.config.trustedMaster private val me = coeur.config.trustedMaster
@ -30,65 +30,70 @@ class OnCallMe (using coeur: MornyCoeur) extends EventListener {
requestItem(update.message.from, "<b>Hana Paresu</b>") requestItem(update.message.from, "<b>Hana Paresu</b>")
case "dinner" | "lunch" | "breakfast" | "meal" | "eating" | "安妮今天吃什么" => case "dinner" | "lunch" | "breakfast" | "meal" | "eating" | "安妮今天吃什么" =>
requestLastDinner(update.message) requestLastDinner(update.message)
case cc if cc startsWith "cc::" => case cc if cc `startsWith` "cc::" =>
requestCustom(update.message) requestCustom(update.message)
case _ => case _ =>
return; return;
if success then if success then
coeur.account exec SendSticker( SendSticker(
update.message.chat.id, update.message.chat.id,
TelegramStickers ID_SENT TelegramStickers ID_SENT
).replyToMessageId(update.message.messageId) ).replyToMessageId(update.message.messageId)
.unsafeExecute
else else
coeur.account exec SendSticker( SendSticker(
update.message.chat.id, update.message.chat.id,
TelegramStickers ID_501 TelegramStickers ID_501
).replyToMessageId(update.message.messageId) ).replyToMessageId(update.message.messageId)
.unsafeExecute
event.setEventOk event.setEventOk
} }
private def requestItem (user: User, itemHTML: String, extra: String|Null = null): Boolean = private def requestItem (user: User, itemHTML: String, extra: String|Null = null): Boolean =
coeur.account exec SendMessage( SendMessage(
me, me,
s"""request $itemHTML s"""request $itemHTML
|from ${user.fullnameRefHTML}${if extra == null then "" else "\n"+extra}""" |from ${user.fullnameRefHTML}${if extra == null then "" else "\n"+extra}"""
.stripMargin .stripMargin
).parseMode(ParseMode HTML) ).parseMode(ParseMode HTML)
.unsafeExecute
true true
private def requestLastDinner (req: Message): Boolean = { private def requestLastDinner (req: Message): Boolean = {
if coeur.config.dinnerChatId == -1 then return false if coeur.config.dinnerChatId == -1 then return false
var isAllowed = false var isAllowed = false
var lastDinnerData: Message|Null = null var lastDinnerData: Message|Null = null
if (coeur.trusted isTrusted_dinnerReader req.from.id) { if (coeur.trusted isTrust4dinner req.from) {
// todo: have issues // todo: have issues
// i dont want to test it anymore... it might be deprecated soon // i dont want to test it anymore... it might be deprecated soon
lastDinnerData = (coeur.account exec GetChat(coeur.config.dinnerChatId)).chat.pinnedMessage lastDinnerData = GetChat(coeur.config.dinnerChatId).unsafeExecute.chat.pinnedMessage
val sendResp = coeur.account exec ForwardMessage( val sendResp = ForwardMessage(
req.from.id, req.from.id,
lastDinnerData.forwardFromChat.id, lastDinnerData.forwardFromChat.id,
lastDinnerData.forwardFromMessageId lastDinnerData.forwardFromMessageId
) ).unsafeExecute
import cc.sukazyo.cono.morny.util.CommonFormat.{formatDate, formatDuration} import cc.sukazyo.cono.morny.util.CommonFormat.{formatDate, formatDuration}
import cc.sukazyo.cono.morny.util.EpochDateTime.EpochMillis import cc.sukazyo.cono.morny.util.EpochDateTime.EpochMillis
import cc.sukazyo.cono.morny.util.tgapi.formatting.TelegramParseEscape.escapeHtml as h import cc.sukazyo.cono.morny.util.tgapi.formatting.TelegramParseEscape.escapeHtml as h
def lastDinner_dateMillis: EpochMillis = EpochMillis fromEpochSeconds lastDinnerData.forwardDate def lastDinner_dateMillis: EpochMillis = EpochMillis fromSeconds lastDinnerData.forwardDate
coeur.account exec SendMessage( SendMessage(
req.from.id, req.from.id,
"<i>on</i> <code>%s [UTC+8]</code>\n- <code>%s</code> <i>before</i>".formatted( "<i>on</i> <code>%s [UTC+8]</code>\n- <code>%s</code> <i>before</i>".formatted(
h(formatDate(lastDinner_dateMillis, 8)), h(formatDate(lastDinner_dateMillis, 8)),
h(formatDuration(System.currentTimeMillis - lastDinner_dateMillis)) h(formatDuration(System.currentTimeMillis - lastDinner_dateMillis))
) )
).parseMode(ParseMode HTML).replyToMessageId(sendResp.message.messageId) ).parseMode(ParseMode HTML).replyToMessageId(sendResp.message.messageId)
.unsafeExecute
isAllowed = true isAllowed = true
} else { } else {
coeur.account exec SendSticker( SendSticker(
req.from.id, req.from.id,
TelegramStickers ID_403 TelegramStickers ID_403
).replyToMessageId(req.messageId) ).replyToMessageId(req.messageId)
.unsafeExecute
} }
import Math.abs import Math.abs
requestItem( requestItem(
@ -100,7 +105,8 @@ class OnCallMe (using coeur: MornyCoeur) extends EventListener {
private def requestCustom (message: Message): Boolean = private def requestCustom (message: Message): Boolean =
requestItem(message.from, "<u>[???]</u>") requestItem(message.from, "<u>[???]</u>")
coeur.account exec ForwardMessage(me, message.chat.id, message.messageId) ForwardMessage(me, message.chat.id, message.messageId)
.unsafeExecute
true true
} }

View File

@ -3,16 +3,18 @@ package cc.sukazyo.cono.morny.call_me
import cc.sukazyo.cono.morny.core.MornyCoeur import cc.sukazyo.cono.morny.core.MornyCoeur
import cc.sukazyo.cono.morny.core.bot.api.{EventEnv, EventListener} import cc.sukazyo.cono.morny.core.bot.api.{EventEnv, EventListener}
import cc.sukazyo.cono.morny.data.TelegramStickers import cc.sukazyo.cono.morny.data.TelegramStickers
import cc.sukazyo.cono.morny.util.tgapi.TelegramExtensions.Bot.exec import cc.sukazyo.cono.morny.util.tgapi.TelegramExtensions.Requests.*
import com.pengrad.telegrambot.model.{Chat, Message, MessageEntity} import com.pengrad.telegrambot.model.{Chat, Message, MessageEntity}
import com.pengrad.telegrambot.model.request.ParseMode import com.pengrad.telegrambot.model.request.ParseMode
import com.pengrad.telegrambot.request.{GetChat, SendMessage, SendSticker} import com.pengrad.telegrambot.request.{GetChat, SendMessage, SendSticker}
import com.pengrad.telegrambot.TelegramBot
import scala.collection.mutable.ArrayBuffer import scala.collection.mutable.ArrayBuffer
import scala.language.postfixOps import scala.language.postfixOps
import scala.util.matching.Regex import scala.util.matching.Regex
class OnCallMsgSend (using coeur: MornyCoeur) extends EventListener { class OnCallMsgSend (using coeur: MornyCoeur) extends EventListener {
private given TelegramBot = coeur.account
private val REGEX_MSG_SENDREQ_DATA_HEAD: Regex = "^\\*msg(-?\\d+)(\\*\\S+)?(?:\\n([\\s\\S]+))?$"r private val REGEX_MSG_SENDREQ_DATA_HEAD: Regex = "^\\*msg(-?\\d+)(\\*\\S+)?(?:\\n([\\s\\S]+))?$"r
@ -25,12 +27,12 @@ class OnCallMsgSend (using coeur: MornyCoeur) extends EventListener {
def toSendMessage (target_override: Long|Null = null): SendMessage = def toSendMessage (target_override: Long|Null = null): SendMessage =
val useTarget = if target_override == null then targetId else target_override val useTarget = if target_override == null then targetId else target_override
val sendMessage = SendMessage(useTarget, message) val sendMessage = SendMessage(useTarget, message)
if entities ne null then sendMessage.entities(entities:_*) if entities ne null then sendMessage.entities(entities*)
if parseMode ne null then sendMessage.parseMode(parseMode) if parseMode ne null then sendMessage.parseMode(parseMode)
sendMessage sendMessage
} }
private object MessageToSend: private object MessageToSend:
def from (raw: Message): MessageToSend = { infix def from (raw: Message): MessageToSend = {
raw.text match raw.text match
case REGEX_MSG_SENDREQ_DATA_HEAD(_target, _parseMode, _body) => case REGEX_MSG_SENDREQ_DATA_HEAD(_target, _parseMode, _body) =>
val target = _target toLong val target = _target toLong
@ -57,15 +59,15 @@ class OnCallMsgSend (using coeur: MornyCoeur) extends EventListener {
val message = update.message val message = update.message
if message.chat.`type` != Chat.Type.Private then return; if message.chat.`type` != Chat.Type.Private then return
if message.text eq null then return; if message.text eq null then return
if !(message.text startsWith "*msg") then return; if !(message.text `startsWith` "*msg") then return
if (!(coeur.trusted isTrusted message.from.id)) if (!(coeur.trusted isTrust message.from))
coeur.account exec SendSticker( SendSticker(
message.chat.id, message.chat.id,
TelegramStickers ID_403 TelegramStickers ID_403
).replyToMessageId(message.messageId) ).replyToMessageId(message.messageId).unsafeExecute
event.setEventOk event.setEventOk
return; return;
@ -74,21 +76,23 @@ class OnCallMsgSend (using coeur: MornyCoeur) extends EventListener {
if (message.replyToMessage eq null) { answer404; return } if (message.replyToMessage eq null) { answer404; return }
val messageToSend = MessageToSend from message.replyToMessage val messageToSend = MessageToSend from message.replyToMessage
if ((messageToSend eq null) || (messageToSend.message eq null)) { answer404; return } if ((messageToSend eq null) || (messageToSend.message eq null)) { answer404; return }
val sendResponse = coeur.account execute messageToSend.toSendMessage() val sendResponse = messageToSend.toSendMessage().execute
if (sendResponse isOk) { if (sendResponse isOk) {
coeur.account exec SendSticker( SendSticker(
update.message.chat.id, update.message.chat.id,
TelegramStickers ID_SENT TelegramStickers ID_SENT
).replyToMessageId(update.message.messageId) ).replyToMessageId(update.message.messageId)
.unsafeExecute
} else { } else {
coeur.account exec SendMessage( SendMessage(
update.message.chat.id, update.message.chat.id,
// language=html // language=html
s"""<b><u>${sendResponse.errorCode} FAILED</u></b> s"""<b><u>${sendResponse.errorCode} FAILED</u></b>
|<code>${sendResponse.description}</code>""" |<code>${sendResponse.description}</code>"""
.stripMargin .stripMargin
).replyToMessageId(update.message.messageId).parseMode(ParseMode HTML) ).replyToMessageId(update.message.messageId).parseMode(ParseMode HTML)
.unsafeExecute
} }
event.setEventOk event.setEventOk
@ -101,14 +105,14 @@ class OnCallMsgSend (using coeur: MornyCoeur) extends EventListener {
if (message.text == "*msg") if (message.text == "*msg")
if message.replyToMessage eq null then { answer404; return } if message.replyToMessage eq null then { answer404; return }
else message.replyToMessage else message.replyToMessage
else if (message.text startsWith "*msg") else if (message.text `startsWith` "*msg")
message message
else { answer404; return } else { answer404; return }
val _toSend = MessageToSend from raw val _toSend = MessageToSend from raw
if _toSend eq null then { answer404; return } if _toSend eq null then { answer404; return }
else _toSend else _toSend
val targetChatResponse = coeur.account execute GetChat(messageToSend.targetId) val targetChatResponse = GetChat(messageToSend.targetId).execute
if (targetChatResponse isOk) { if (targetChatResponse isOk) {
def getChatDescriptionHTML (chat: Chat): String = def getChatDescriptionHTML (chat: Chat): String =
import cc.sukazyo.cono.morny.util.tgapi.formatting.TelegramFormatter.* import cc.sukazyo.cono.morny.util.tgapi.formatting.TelegramFormatter.*
@ -117,41 +121,46 @@ class OnCallMsgSend (using coeur: MornyCoeur) extends EventListener {
s"""<i><u>${h(chat.id toString)}</u>@${h(chat.`type`.name)}</i>${if (chat.`type` != Chat.Type.Private) ":::" else ""} s"""<i><u>${h(chat.id toString)}</u>@${h(chat.`type`.name)}</i>${if (chat.`type` != Chat.Type.Private) ":::" else ""}
|${chat.typeTag} <b>${h(chat.safe_name)}</b> ${chat.safe_linkHTML}""" |${chat.typeTag} <b>${h(chat.safe_name)}</b> ${chat.safe_linkHTML}"""
.stripMargin .stripMargin
coeur.account exec SendMessage( SendMessage(
update.message.chat.id, update.message.chat.id,
getChatDescriptionHTML(targetChatResponse.chat) getChatDescriptionHTML(targetChatResponse.chat)
).parseMode(ParseMode HTML).replyToMessageId(update.message.messageId) ).parseMode(ParseMode HTML).replyToMessageId(update.message.messageId)
.unsafeExecute
} else { } else {
coeur.account exec SendMessage( SendMessage(
update.message.chat.id, update.message.chat.id,
// language=html // language=html
s"""<b><u>${targetChatResponse.errorCode} FAILED</u></b> s"""<b><u>${targetChatResponse.errorCode} FAILED</u></b>
|<code>${targetChatResponse.description}</code>""" |<code>${targetChatResponse.description}</code>"""
.stripMargin .stripMargin
).parseMode(ParseMode HTML).replyToMessageId(update.message.messageId) ).parseMode(ParseMode HTML).replyToMessageId(update.message.messageId)
.unsafeExecute
} }
if messageToSend.message eq null then { answer404; return } if messageToSend.message eq null then { answer404; return }
val testSendResponse = coeur.account execute val testSendResponse =
messageToSend.toSendMessage(update.message.chat.id).replyToMessageId(update.message.messageId) messageToSend.toSendMessage(update.message.chat.id).replyToMessageId(update.message.messageId)
.execute
if (!(testSendResponse isOk)) if (!(testSendResponse isOk))
coeur.account exec SendMessage( SendMessage(
update.message.chat.id, update.message.chat.id,
// language=html // language=html
s"""<b><u>${testSendResponse.errorCode}</u> FAILED</b> s"""<b><u>${testSendResponse.errorCode}</u> FAILED</b>
|<code>${testSendResponse.description}</code>""" |<code>${testSendResponse.description}</code>"""
.stripMargin .stripMargin
).parseMode(ParseMode HTML).replyToMessageId(update.message.messageId) ).parseMode(ParseMode HTML).replyToMessageId(update.message.messageId)
.unsafeExecute
event.setEventOk event.setEventOk
} }
private def answer404 (using event: EventEnv): Unit = private def answer404 (using event: EventEnv): Unit =
coeur.account exec SendSticker( SendSticker(
event.update.message.chat.id, event.update.message.chat.id,
TelegramStickers ID_404 TelegramStickers ID_404
).replyToMessageId(event.update.message.messageId) ).replyToMessageId(event.update.message.messageId)
.unsafeExecute
event.setEventOk event.setEventOk
} }

View File

@ -4,8 +4,6 @@ import cc.sukazyo.cono.morny.core.internal.logging.{MornyFormatterConsole, Morny
import cc.sukazyo.messiva.appender.ConsoleAppender import cc.sukazyo.messiva.appender.ConsoleAppender
import cc.sukazyo.messiva.log.LogLevels import cc.sukazyo.messiva.log.LogLevels
import java.io.{PrintWriter, StringWriter}
object Log { object Log {
val logger: MornyLoggerBase = MornyLoggerBase( val logger: MornyLoggerBase = MornyLoggerBase(
@ -13,7 +11,7 @@ object Log {
MornyFormatterConsole() MornyFormatterConsole()
) )
) )
logger minLevel LogLevels.INFO logger.minLevel(LogLevels.INFO)
def debug: Boolean = logger.levelSetting.minLevel.level <= LogLevels.DEBUG.level def debug: Boolean = logger.levelSetting.minLevel.level <= LogLevels.DEBUG.level
@ -21,9 +19,4 @@ object Log {
if is then logger.minLevel(LogLevels.ALL) if is then logger.minLevel(LogLevels.ALL)
else logger.minLevel(LogLevels.INFO) else logger.minLevel(LogLevels.INFO)
def exceptionLog (e: Throwable): String =
val stackTrace = StringWriter()
e printStackTrace PrintWriter(stackTrace)
stackTrace toString
} }

View File

@ -7,7 +7,7 @@ import java.io.IOException
object MornyAbout { object MornyAbout {
val MORNY_PREVIEW_IMAGE_ASCII: String = val MORNY_PREVIEW_IMAGE_ASCII: String =
try { MornyAssets.pack getResource "texts/server-hello.txt" readAsString } try { MornyAssets.pack `getResource` "texts/server-hello.txt" readAsString }
catch case e: IOException => catch case e: IOException =>
throw RuntimeException("Cannot read MORNY_PREVIEW_IMAGE_ASCII from assets pack", e) throw RuntimeException("Cannot read MORNY_PREVIEW_IMAGE_ASCII from assets pack", e)

View File

@ -1,6 +1,6 @@
package cc.sukazyo.cono.morny.core package cc.sukazyo.cono.morny.core
import cc.sukazyo.cono.morny.core.Log.{exceptionLog, logger} import cc.sukazyo.cono.morny.core.Log.logger
import cc.sukazyo.cono.morny.core.MornyCoeur.* import cc.sukazyo.cono.morny.core.MornyCoeur.*
import cc.sukazyo.cono.morny.core.bot.api.{EventListenerManager, MornyCommandManager, MornyQueryManager} import cc.sukazyo.cono.morny.core.bot.api.{EventListenerManager, MornyCommandManager, MornyQueryManager}
import cc.sukazyo.cono.morny.core.bot.event.{MornyOnInlineQuery, MornyOnTelegramCommand, MornyOnUpdateTimestampOffsetLock} import cc.sukazyo.cono.morny.core.bot.event.{MornyOnInlineQuery, MornyOnTelegramCommand, MornyOnUpdateTimestampOffsetLock}
@ -11,6 +11,7 @@ import cc.sukazyo.cono.morny.util.schedule.Scheduler
import cc.sukazyo.cono.morny.util.EpochDateTime.EpochMillis import cc.sukazyo.cono.morny.util.EpochDateTime.EpochMillis
import cc.sukazyo.cono.morny.util.time.WatchDog import cc.sukazyo.cono.morny.util.time.WatchDog
import cc.sukazyo.cono.morny.util.GivenContext import cc.sukazyo.cono.morny.util.GivenContext
import cc.sukazyo.cono.morny.util.UseThrowable.toLogString
import com.pengrad.telegrambot.TelegramBot import com.pengrad.telegrambot.TelegramBot
import com.pengrad.telegrambot.request.GetMe import com.pengrad.telegrambot.request.GetMe
@ -115,7 +116,7 @@ class MornyCoeur (modules: List[MornyModule])(using val config: MornyConfig)(tes
val externalContext: GivenContext = GivenContext() val externalContext: GivenContext = GivenContext()
import cc.sukazyo.cono.morny.util.dataview.Table.format as fmtTable import cc.sukazyo.cono.morny.util.dataview.Table.format as fmtTable
logger info logger `info`
s"""The following Modules have been added to current Morny: s"""The following Modules have been added to current Morny:
|${fmtTable( |${fmtTable(
("Module ID" :: "Module Name" :: "Module Version" :: Nil)::Nil ::: ("Module ID" :: "Module Name" :: "Module Version" :: Nil)::Nil :::
@ -125,19 +126,19 @@ class MornyCoeur (modules: List[MornyModule])(using val config: MornyConfig)(tes
///>>> BLOCK START instance configure & startup stage 1 ///>>> BLOCK START instance configure & startup stage 1
logger info "Coeur starting..." logger `info` "Coeur starting..."
private var initializeContext = GivenContext() private var initializeContext = GivenContext()
import cc.sukazyo.cono.morny.util.StringEnsure.deSensitive import cc.sukazyo.cono.morny.util.StringEnsure.deSensitive
logger info s"args key:\n ${config.telegramBotKey deSensitive 4}" logger `info` s"args key:\n ${config.telegramBotKey.deSensitive(4)}"
if config.telegramBotUsername ne null then if config.telegramBotUsername ne null then
logger info s"login as:\n ${config.telegramBotUsername}" logger `info` s"login as:\n ${config.telegramBotUsername}"
private val __loginResult: LoginResult = login() match private val __loginResult: LoginResult = login() match
case some: Some[LoginResult] => some.get case some: Some[LoginResult] => some.get
case None => case None =>
logger error "Login to bot failed." logger `error` "Login to bot failed."
System exit -1 System `exit` -1
throw RuntimeException() throw RuntimeException()
initializeContext << __loginResult initializeContext << __loginResult
@ -221,7 +222,7 @@ class MornyCoeur (modules: List[MornyModule])(using val config: MornyConfig)(tes
val watchDog: WatchDog = WatchDog("watch-dog", 1000, 1500, { (consumed, _) => val watchDog: WatchDog = WatchDog("watch-dog", 1000, 1500, { (consumed, _) =>
import cc.sukazyo.cono.morny.util.CommonFormat.formatDuration as f import cc.sukazyo.cono.morny.util.CommonFormat.formatDuration as f
logger warn logger `warn`
s"""Can't keep up! is the server overloaded or host machine fall asleep? s"""Can't keep up! is the server overloaded or host machine fall asleep?
| current tick takes ${f(consumed)} to complete.""".stripMargin | current tick takes ${f(consumed)} to complete.""".stripMargin
tasks.notifyIt() tasks.notifyIt()
@ -238,9 +239,9 @@ class MornyCoeur (modules: List[MornyModule])(using val config: MornyConfig)(tes
///>>> BLOCK START instance configure & startup stage 2 ///>>> BLOCK START instance configure & startup stage 2
logger info "done initialize." logger `info` "done initialize."
if testRun then if testRun then
logger info "done test run, exiting." logger `info` "done test run, exiting."
this.exit(0, TestRun) this.exit(0, TestRun)
// Coeur Starting Pre // Coeur Starting Pre
@ -250,10 +251,10 @@ class MornyCoeur (modules: List[MornyModule])(using val config: MornyConfig)(tes
modules.foreach(it => it.onStarting(OnStartingContext( modules.foreach(it => it.onStarting(OnStartingContext(
initializeContext))) initializeContext)))
logger info "start http server" logger `info` "start http server"
val http: HttpServer = _httpServerContext.start val http: HttpServer = _httpServerContext.start
_httpServerContext = null _httpServerContext = null
logger info "start telegram event listening" logger `info` "start telegram event listening"
import com.pengrad.telegrambot.TelegramException import com.pengrad.telegrambot.TelegramException
account.setUpdatesListener(eventManager, (e: TelegramException) => { account.setUpdatesListener(eventManager, (e: TelegramException) => {
@ -267,10 +268,10 @@ class MornyCoeur (modules: List[MornyModule])(using val config: MornyConfig)(tes
if (e.response != null) { if (e.response != null) {
import com.google.gson.GsonBuilder import com.google.gson.GsonBuilder
logger error logger `error`
s"""Failed get updates: ${e.getMessage} s"""Failed get updates: ${e.getMessage}
| server responses: | server responses:
|${GsonBuilder().setPrettyPrinting().create.toJson(e.response) indent 4} |${GsonBuilder().setPrettyPrinting().create.toJson(e.response).indent(4)}
|""".stripMargin |""".stripMargin
externalContext.consume[MornyReport](_.exception(e, "Failed get updates.")) externalContext.consume[MornyReport](_.exception(e, "Failed get updates."))
} }
@ -291,11 +292,11 @@ class MornyCoeur (modules: List[MornyModule])(using val config: MornyConfig)(tes
current = current.getCause current = current.getCause
log += s" caused by: ${current.getClass.getSimpleName}: ${current.getMessage}" log += s" caused by: ${current.getClass.getSimpleName}: ${current.getMessage}"
} }
logger error Message(log mkString "\n") logger `error` Message(log mkString "\n")
case e_other => case e_other =>
logger error logger `error`
s"""Failed get updates: s"""Failed get updates:
|${exceptionLog(e_other) indent 3}""".stripMargin |${e_other.toLogString `indent` 3}""".stripMargin
externalContext.consume[MornyReport](_.exception(e_other, "Failed get updates.")) externalContext.consume[MornyReport](_.exception(e_other, "Failed get updates."))
} }
@ -306,17 +307,17 @@ class MornyCoeur (modules: List[MornyModule])(using val config: MornyConfig)(tes
initializeContext))) initializeContext)))
if config.commandLoginRefresh then if config.commandLoginRefresh then
logger info "resetting telegram command list" logger `info` "resetting telegram command list"
commands.automaticTGListUpdate() commands.automaticTGListUpdate()
initializeContext = null initializeContext = null
logger info "Coeur start complete." logger `info` "Coeur start complete."
///<<< BLOCK END instance configure & startup stage 2 ///<<< BLOCK END instance configure & startup stage 2
def saveDataAll(): Unit = { def saveDataAll(): Unit = {
modules.foreach(it => it.onRoutineSavingData) modules.foreach(it => it.onRoutineSavingData)
logger notice "done all save action." logger `notice` "done all save action."
} }
private def exitCleanup (): Unit = { private def exitCleanup (): Unit = {
@ -325,9 +326,9 @@ class MornyCoeur (modules: List[MornyModule])(using val config: MornyConfig)(tes
modules.foreach(it => it.onExiting) modules.foreach(it => it.onExiting)
account.removeGetUpdatesListener() account.removeGetUpdatesListener()
logger info "stopped bot update listener" logger `info` "stopped bot update listener"
tasks.waitForStop() tasks.waitForStop()
logger info s"morny tasks stopped: remains ${tasks.amount} tasks not be executed" logger `info` s"morny tasks stopped: remains ${tasks.amount} tasks not be executed"
// Morny Exiting Post // Morny Exiting Post
if config.commandLogoutClear then if config.commandLogoutClear then
@ -335,10 +336,10 @@ class MornyCoeur (modules: List[MornyModule])(using val config: MornyConfig)(tes
modules.foreach(it => it.onExitingPost) modules.foreach(it => it.onExitingPost)
account.shutdown() account.shutdown()
logger info "stopped bot account" logger `info` "stopped bot account"
// Morny Exited // Morny Exited
modules.foreach(it => it.onExited) modules.foreach(it => it.onExited)
logger info "done exit cleanup\nMorny will EXIT now" logger `info` "done exit cleanup\nMorny will EXIT now"
} }
@ -348,7 +349,7 @@ class MornyCoeur (modules: List[MornyModule])(using val config: MornyConfig)(tes
def exit (status: Int, reason: AnyRef): Unit = def exit (status: Int, reason: AnyRef): Unit =
whileExit_reason = Some(reason) whileExit_reason = Some(reason)
System exit status System `exit` status
private case class LoginResult(account: TelegramBot, username: String, userid: Long) private case class LoginResult(account: TelegramBot, username: String, userid: Long)
@ -358,15 +359,15 @@ class MornyCoeur (modules: List[MornyModule])(using val config: MornyConfig)(tes
var api_bot = config.telegramBotApiServer var api_bot = config.telegramBotApiServer
var api_file = config.telegramBotApiServer4File var api_file = config.telegramBotApiServer4File
if (api_bot ne null) if (api_bot ne null)
if api_bot endsWith "/" then api_bot = api_bot dropRight 1 if api_bot `endsWith` "/" then api_bot = api_bot dropRight 1
if !(api_bot endsWith "/bot") then api_bot += "/bot" if !(api_bot `endsWith` "/bot") then api_bot += "/bot"
builder.apiUrl(api_bot) builder.apiUrl(api_bot)
if (api_file ne null) if (api_file ne null)
if api_file endsWith "/file/" then api_file = api_file dropRight 1 if api_file `endsWith` "/file/" then api_file = api_file dropRight 1
if !(api_file endsWith "/file/bot") then api_file += "/file/bot" if !(api_file `endsWith` "/file/bot") then api_file += "/file/bot"
builder.apiUrl(api_bot) builder.apiUrl(api_bot)
if ((api_bot ne null) || (api_file ne null)) if ((api_bot ne null) || (api_file ne null))
logger info logger `info`
s"""Telegram bot api set to: s"""Telegram bot api set to:
|- bot: $api_bot |- bot: $api_bot
|- file: $api_file""" |- file: $api_file"""
@ -374,21 +375,22 @@ class MornyCoeur (modules: List[MornyModule])(using val config: MornyConfig)(tes
val account = builder build val account = builder build
logger info "Trying to login..." logger `info` "Trying to login..."
boundary[Option[LoginResult]] { boundary[Option[LoginResult]] {
for (i <- 0 to 3) { for (i <- 0 to 3) {
if i > 0 then logger info "retrying..." if i > 0 then logger `info` "retrying..."
try { try {
val remote = (account execute GetMe()).user import cc.sukazyo.cono.morny.util.tgapi.TelegramExtensions.Requests.execute
val remote = GetMe().execute(using account).user
if ((config.telegramBotUsername ne null) && config.telegramBotUsername != remote.username) if ((config.telegramBotUsername ne null) && config.telegramBotUsername != remote.username)
throw RuntimeException(s"Required the bot @${config.telegramBotUsername} but @${remote.username} logged in") throw RuntimeException(s"Required the bot @${config.telegramBotUsername} but @${remote.username} logged in")
logger info s"Succeed logged in to @${remote.username}" logger `info` s"Succeed logged in to @${remote.username}"
break(Some(LoginResult(account, remote.username, remote.id))) break(Some(LoginResult(account, remote.username, remote.id)))
} catch } catch
case r: boundary.Break[Option[LoginResult]] => throw r case r: boundary.Break[Option[LoginResult]] => throw r
case e => case e =>
logger error logger `error`
s"""${exceptionLog(e)} s"""${e.toLogString}
|login failed""" |login failed"""
.stripMargin .stripMargin
} }

View File

@ -1,10 +1,11 @@
package cc.sukazyo.cono.morny.core package cc.sukazyo.cono.morny.core
import cc.sukazyo.cono.morny.core.Log.{exceptionLog, logger} import cc.sukazyo.cono.morny.core.Log.logger
import cc.sukazyo.cono.morny.core.internal.BuildConfigField import cc.sukazyo.cono.morny.core.internal.BuildConfigField
import cc.sukazyo.cono.morny.util.EpochDateTime.EpochMillis import cc.sukazyo.cono.morny.util.EpochDateTime.EpochMillis
import cc.sukazyo.cono.morny.util.FileUtils import cc.sukazyo.cono.morny.util.FileUtils
import cc.sukazyo.cono.morny.BuildConfig import cc.sukazyo.cono.morny.BuildConfig
import cc.sukazyo.cono.morny.util.UseThrowable.toLogString
import java.io.IOException import java.io.IOException
import java.net.URISyntaxException import java.net.URISyntaxException
@ -38,7 +39,7 @@ object MornySystem {
case _: (IOException|URISyntaxException) => case _: (IOException|URISyntaxException) =>
"<non-jar-runtime>" "<non-jar-runtime>"
case n: NoSuchAlgorithmException => case n: NoSuchAlgorithmException =>
logger error exceptionLog(n) logger `error` n.toLogString
// MornyReport.exception(n, "<coeur-md5/calculation-error>") // todo: will not implemented // MornyReport.exception(n, "<coeur-md5/calculation-error>") // todo: will not implemented
"<calculation-error>" "<calculation-error>"
} }

View File

@ -5,20 +5,33 @@ import cc.sukazyo.cono.morny.util.tgapi.TelegramExtensions.{LimboChat, LimboUser
import cc.sukazyo.cono.morny.util.tgapi.TelegramExtensions.Chat.* import cc.sukazyo.cono.morny.util.tgapi.TelegramExtensions.Chat.*
import com.pengrad.telegrambot.model.ChatMember.Status import com.pengrad.telegrambot.model.ChatMember.Status
import com.pengrad.telegrambot.TelegramBot import com.pengrad.telegrambot.TelegramBot
import com.pengrad.telegrambot.model.User
class MornyTrusted (using coeur: MornyCoeur)(using config: MornyConfig) { class MornyTrusted (using coeur: MornyCoeur)(using config: MornyConfig) {
if config.trustedMaster == -1 then if config.trustedMaster == -1 then
logger warn "You have not set your Morny's master.\n it may have some issues on controlling your bot." logger `warn` "You have not set your Morny's master.\n it may have some issues on controlling your bot."
def isTrusted (userId: Long): Boolean = /** If the user can be trusted.
* @since 2.0.0
* @param user The user that will be check if it is in this trust list. Only user's id will be used,
* so if you don't have a user instance, you can safely use [[LimboUser]].
* @return `true` if this user can be trusted, `false` otherwise.
*/
infix def isTrust (user: User): Boolean =
given TelegramBot = coeur.account given TelegramBot = coeur.account
if userId == config.trustedMaster then true if user.id == config.trustedMaster then true
else if config.trustedChat == -1 then false else if config.trustedChat == -1 then false
else LimboChat(config.trustedChat) memberHasPermission(LimboUser(userId), Status.administrator) else LimboChat(config.trustedChat).memberHasPermission(user, Status.administrator)
def isTrusted_dinnerReader (userId: Long): Boolean = /** If this user can be trusted to read the dinner messages.
if userId == config.trustedMaster then true * @since 2.0.0
else config.dinnerTrustedReaders contains userId * @param user The user that will be check if it is in this trust list. Only user's id will be used,
* so if you don't have a user instance, you can safely use [[LimboUser]].
* @return `true` if this user can read dinners, `false` otherwise.
*/
infix def isTrust4dinner (user: User): Boolean =
if user.id == config.trustedMaster then true
else config.dinnerTrustedReaders `contains` user.id
} }

View File

@ -11,7 +11,7 @@ import scala.collection.mutable.ArrayBuffer
object ServerMain { object ServerMain {
val tz: TimeZone = TimeZone getDefault val tz: TimeZone = TimeZone getDefault
val tz_offset: ZoneOffset = ZoneOffset ofTotalSeconds(tz.getRawOffset / 1000) val tz_offset: ZoneOffset = ZoneOffset `ofTotalSeconds` (tz.getRawOffset / 1000)
private val THREAD_MORNY_INIT: String = "morny-init" private val THREAD_MORNY_INIT: String = "morny-init"
@ -54,7 +54,7 @@ object ServerMain {
case "--report-to" => i += 1; config.reportToChat = args(i) toLong case "--report-to" => i += 1; config.reportToChat = args(i) toLong
case "--report-zone" => i += 1; config.reportZone = TimeZone.getTimeZone(args(i)) case "--report-zone" => i += 1; config.reportZone = TimeZone.getTimeZone(args(i))
case "--trusted-reader-dinner" | "-trsd" => i += 1; config.dinnerTrustedReaders add(args(i) toLong) case "--trusted-reader-dinner" | "-trsd" => i += 1; config.dinnerTrustedReaders `add` (args(i) toLong)
case "--dinner-chat" | "-chd" => i += 1; config.dinnerChatId = args(i) toLong case "--dinner-chat" | "-chd" => i += 1; config.dinnerChatId = args(i) toLong
case "--http-listen-port" | "-hp" => case "--http-listen-port" | "-hp" =>
@ -67,8 +67,8 @@ object ServerMain {
config.medicationTimerUseTimezone = ZoneOffset.ofHours(args(i) toInt) config.medicationTimerUseTimezone = ZoneOffset.ofHours(args(i) toInt)
case "--medication-notify-times" | "-medt" => case "--medication-notify-times" | "-medt" =>
i += 1 i += 1
for (u <- args(i) split ",") { for (u <- args(i) `split` ",") {
config.medicationNotifyAt add(u toInt) config.medicationNotifyAt `add` (u toInt)
} }
case "--auto-cmd-list" | "-ca" => config.commandLoginRefresh = true case "--auto-cmd-list" | "-ca" => config.commandLoginRefresh = true
@ -87,8 +87,8 @@ object ServerMain {
var propToken: String = null var propToken: String = null
var propTokenKey: String = null var propTokenKey: String = null
for (iKey <- MornyConfig.PROP_TOKEN_KEY) { for (iKey <- MornyConfig.PROP_TOKEN_KEY) {
if ((System getenv iKey) != null) { if ((System `getenv` iKey) != null) {
propToken = System getenv iKey propToken = System `getenv` iKey
propTokenKey = iKey propTokenKey = iKey
} }
} }
@ -98,27 +98,27 @@ object ServerMain {
/// process startup params - like startup mode /// process startup params - like startup mode
/// ///
if (showHello) logger info MornyAbout.MORNY_PREVIEW_IMAGE_ASCII if (showHello) logger `info` MornyAbout.MORNY_PREVIEW_IMAGE_ASCII
if (mode_echoHello) return if (mode_echoHello) return
if (unknownArgs.nonEmpty) logger warn if (unknownArgs.nonEmpty) logger `warn`
s"""Can't understand arg to some meaning s"""Can't understand arg to some meaning
| ${unknownArgs mkString "\n "}""" | ${unknownArgs mkString "\n "}"""
.stripMargin .stripMargin
if (deprecatedArgs.nonEmpty) logger warn if (deprecatedArgs.nonEmpty) logger `warn`
s"""Those arguments have been deprecated: s"""Those arguments have been deprecated:
| ${deprecatedArgs map((d, n) => s"$d : use $n instead") mkString "\n "} | ${deprecatedArgs map((d, n) => s"$d : use $n instead") mkString "\n "}
|""".stripMargin |""".stripMargin
if (Log debug) if (Log debug)
logger warn logger `warn`
"""Debug log output enabled. """Debug log output enabled.
| It may lower your performance, make sure that you are not in production environment.""" | It may lower your performance, make sure that you are not in production environment."""
.stripMargin .stripMargin
if (mode_echoVersion) { if (mode_echoVersion) {
logger info logger `info`
s"""Morny Cono Version s"""Morny Cono Version
|- version : |- version :
| Morny ${MornySystem.CODENAME toUpperCase} | Morny ${MornySystem.CODENAME toUpperCase}
@ -148,14 +148,14 @@ object ServerMain {
} }
logger info logger `info`
s"""ServerMain.java Loaded >>> s"""ServerMain.java Loaded >>>
|- version ${MornySystem.VERSION_FULL} |- version ${MornySystem.VERSION_FULL}
|- Morny ${MornySystem.CODENAME toUpperCase} |- Morny ${MornySystem.CODENAME toUpperCase}
|- <${MornySystem.getJarMD5}> [${MornySystem.CODE_TIMESTAMP}]""".stripMargin |- <${MornySystem.getJarMD5}> [${MornySystem.CODE_TIMESTAMP}]""".stripMargin
// due to [[MornyFormatterConsole]] will use a localized time, it will output to the log // 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)" logger `info` s"logging time will use time-zone ${tz.getID} ($tz_offset)"
/// ///
/// Check Coeur arguments /// Check Coeur arguments
@ -164,10 +164,10 @@ object ServerMain {
if (propToken != null) { if (propToken != null) {
config.telegramBotKey = propToken config.telegramBotKey = propToken
logger info s"Parameter <token> set by EnvVar $$$propTokenKey" logger `info` s"Parameter <token> set by EnvVar $$$propTokenKey"
} }
Thread.currentThread setName THREAD_MORNY_INIT Thread.currentThread `setName` THREAD_MORNY_INIT
try try
MornyCoeur( MornyCoeur(

View File

@ -1,15 +1,15 @@
package cc.sukazyo.cono.morny.core.bot.api package cc.sukazyo.cono.morny.core.bot.api
import cc.sukazyo.cono.morny.core.{Log, MornyCoeur} import cc.sukazyo.cono.morny.core.{Log, MornyCoeur}
import cc.sukazyo.cono.morny.core.Log.{exceptionLog, logger} import cc.sukazyo.cono.morny.core.Log.logger
import cc.sukazyo.cono.morny.reporter.MornyReport import cc.sukazyo.cono.morny.reporter.MornyReport
import cc.sukazyo.cono.morny.util.tgapi.event.EventRuntimeException import cc.sukazyo.cono.morny.util.tgapi.event.EventRuntimeException
import cc.sukazyo.cono.morny.util.UseThrowable.toLogString
import com.google.gson.GsonBuilder import com.google.gson.GsonBuilder
import com.pengrad.telegrambot.model.Update import com.pengrad.telegrambot.model.Update
import com.pengrad.telegrambot.UpdatesListener import com.pengrad.telegrambot.UpdatesListener
import scala.collection.mutable import scala.collection.mutable
import scala.language.postfixOps
/** Contains a [[scala.collection.mutable.Queue]] of [[EventListener]], and delivery telegram [[Update]]. /** Contains a [[scala.collection.mutable.Queue]] of [[EventListener]], and delivery telegram [[Update]].
* *
@ -21,13 +21,16 @@ class EventListenerManager (using coeur: MornyCoeur) extends UpdatesListener {
private val listeners = mutable.Queue.empty[EventListener] private val listeners = mutable.Queue.empty[EventListener]
infix def register (listener: EventListener): Unit =
this.listeners += listener
def register (listeners: EventListener*): Unit = def register (listeners: EventListener*): Unit =
this.listeners ++= listeners this.listeners ++= listeners
private class EventRunner (using update: Update) extends Thread { private class EventRunner (using update: Update) extends Thread {
this setName s"upd-${update.updateId()}-nn" this `setName` s"upd-${update.updateId()}-nn"
private def updateThreadName (t: String): Unit = private def updateThreadName (t: String): Unit =
this setName s"upd-${update.updateId()}-$t" this `setName` s"upd-${update.updateId()}-$t"
override def run (): Unit = { override def run (): Unit = {
@ -80,15 +83,15 @@ class EventListenerManager (using coeur: MornyCoeur) extends UpdatesListener {
} catch case e => { } catch case e => {
val errorMessage = StringBuilder() val errorMessage = StringBuilder()
errorMessage ++= "Event throws unexpected exception:\n" errorMessage ++= "Event throws unexpected exception:\n"
errorMessage ++= (exceptionLog(e) indent 4) errorMessage ++= (e.toLogString `indent` 4)
e match e match
case actionFailed: EventRuntimeException.ActionFailed => case actionFailed: EventRuntimeException.ActionFailed =>
errorMessage ++= "\ntg-api action: response track: " errorMessage ++= "\ntg-api action: response track: "
errorMessage ++= (GsonBuilder().setPrettyPrinting().create().toJson( errorMessage ++= (GsonBuilder().setPrettyPrinting().create().toJson(
actionFailed.response actionFailed.response
) indent 4) ++= "\n" ) `indent` 4) ++= "\n"
case _ => case _ =>
logger error errorMessage.toString logger `error` errorMessage.toString
coeur.externalContext.consume[MornyReport](_.exception(e, "on event running")) coeur.externalContext.consume[MornyReport](_.exception(e, "on event running"))
} }
} }

View File

@ -1,12 +1,9 @@
package cc.sukazyo.cono.morny.core.bot.api package cc.sukazyo.cono.morny.core.bot.api
import cc.sukazyo.cono.morny.core.MornyCoeur
import com.pengrad.telegrambot.model.Update import com.pengrad.telegrambot.model.Update
import javax.annotation.Nullable
trait ITelegramQuery { trait ITelegramQuery {
def query (event: Update): List[InlineQueryUnit[_]] | Null def query (event: Update): List[InlineQueryUnit[?]] | Null
} }

View File

@ -2,13 +2,12 @@ package cc.sukazyo.cono.morny.core.bot.api
import cc.sukazyo.cono.morny.core.Log.logger import cc.sukazyo.cono.morny.core.Log.logger
import cc.sukazyo.cono.morny.core.MornyCoeur import cc.sukazyo.cono.morny.core.MornyCoeur
import cc.sukazyo.cono.morny.core.bot.api.{ISimpleCommand, ITelegramCommand}
import cc.sukazyo.cono.morny.core.bot.api.MornyCommandManager.CommandMap
import cc.sukazyo.cono.morny.data.TelegramStickers import cc.sukazyo.cono.morny.data.TelegramStickers
import cc.sukazyo.cono.morny.util.tgapi.InputCommand import cc.sukazyo.cono.morny.util.tgapi.InputCommand
import cc.sukazyo.cono.morny.util.tgapi.TelegramExtensions.Bot.exec import cc.sukazyo.cono.morny.util.tgapi.TelegramExtensions.Requests.unsafeExecute
import com.pengrad.telegrambot.model.{BotCommand, DeleteMyCommands, Update} import com.pengrad.telegrambot.model.{BotCommand, DeleteMyCommands, Update}
import com.pengrad.telegrambot.request.{SendSticker, SetMyCommands} import com.pengrad.telegrambot.request.{SendSticker, SetMyCommands}
import com.pengrad.telegrambot.TelegramBot
import scala.collection.mutable import scala.collection.mutable
import scala.collection.mutable.ArrayBuffer import scala.collection.mutable.ArrayBuffer
@ -17,14 +16,8 @@ import scala.language.postfixOps
object MornyCommandManager: object MornyCommandManager:
type CommandMap = mutable.SeqMap[String, ISimpleCommand] type CommandMap = mutable.SeqMap[String, ISimpleCommand]
class MornyCommandManager (using coeur: MornyCoeur) { class MornyCommandManager (using coeur: MornyCoeur) extends SimpleCommandManager {
private given TelegramBot = coeur.account
private val commands: CommandMap = mutable.SeqMap.empty
def register [T <: ISimpleCommand] (commands: T*): Unit =
for (i <- commands)
this.commands += (i.name -> i)
for (alias <- i.aliases)
this.commands += (alias.name -> i)
def execute (using command: InputCommand, event: Update): Boolean = { def execute (using command: InputCommand, event: Update): Boolean = {
if (commands contains command.command) if (commands contains command.command)
@ -36,25 +29,28 @@ class MornyCommandManager (using coeur: MornyCoeur) {
private def nonCommandExecutable (using command: InputCommand, event: Update): Boolean = { private def nonCommandExecutable (using command: InputCommand, event: Update): Boolean = {
if command.target eq null then false if command.target eq null then false
else else
coeur.account exec SendSticker( SendSticker(
event.message.chat.id, event.message.chat.id,
TelegramStickers ID_404 TelegramStickers ID_404
).replyToMessageId(event.message.messageId) ).replyToMessageId(event.message.messageId)
.unsafeExecute
true true
} }
def automaticTGListUpdate (): Unit = { def automaticTGListUpdate (): Unit = {
val listing = commands_toTelegramList val listing = commands_toTelegramList
automaticTGListRemove() automaticTGListRemove()
coeur.account exec SetMyCommands(listing:_*) SetMyCommands(listing*)
logger notice .unsafeExecute
logger `notice`
s"""automatic updated telegram command list : s"""automatic updated telegram command list :
|${commandsTelegramList_toString(listing)}""".stripMargin |${commandsTelegramList_toString(listing)}""".stripMargin
} }
def automaticTGListRemove (): Unit = { def automaticTGListRemove (): Unit = {
coeur.account exec DeleteMyCommands() DeleteMyCommands()
logger notice "cleaned up command list" .unsafeExecute
logger `notice` "cleaned up command list"
} }
private def commandsTelegramList_toString (list: Array[BotCommand]): String = private def commandsTelegramList_toString (list: Array[BotCommand]): String =

View File

@ -10,13 +10,16 @@ class MornyQueryManager (using MornyCoeur) {
private val queries = mutable.Queue.empty[ITelegramQuery] private val queries = mutable.Queue.empty[ITelegramQuery]
infix def register (query: ITelegramQuery): Unit =
this.queries += query
def register (queries: ITelegramQuery*): Unit = def register (queries: ITelegramQuery*): Unit =
this.queries ++= queries this.queries ++= queries
def query (event: Update): List[InlineQueryUnit[_]] = { def query (event: Update): List[InlineQueryUnit[?]] = {
val results = ListBuffer[InlineQueryUnit[_]]() val results = ListBuffer[InlineQueryUnit[?]]()
for (instance <- queries) { for (instance <- queries) {
val r = instance query event val r = instance `query` event
if (r != null) results ++= r if (r != null) results ++= r
} }
results.result() results.result()

View File

@ -0,0 +1,21 @@
package cc.sukazyo.cono.morny.core.bot.api
import cc.sukazyo.cono.morny.core.bot.api.MornyCommandManager.CommandMap
import scala.collection.mutable
trait SimpleCommandManager {
protected val commands: CommandMap = mutable.SeqMap.empty
protected def doRegister [T <: ISimpleCommand](it: T): Unit =
this.commands += (it.name -> it)
for (alias <- it.aliases)
this.commands += (alias.name -> it)
infix def register [T <: ISimpleCommand] (command: T): Unit =
doRegister(command)
def register [T <: ISimpleCommand] (commands: T*): Unit =
for (command <- commands) doRegister(command)
}

View File

@ -5,28 +5,30 @@ import cc.sukazyo.cono.morny.core.MornyCoeur
import cc.sukazyo.cono.morny.core.bot.api.{ICommandAlias, ISimpleCommand} import cc.sukazyo.cono.morny.core.bot.api.{ICommandAlias, ISimpleCommand}
import cc.sukazyo.cono.morny.data.TelegramStickers import cc.sukazyo.cono.morny.data.TelegramStickers
import cc.sukazyo.cono.morny.util.tgapi.InputCommand import cc.sukazyo.cono.morny.util.tgapi.InputCommand
import cc.sukazyo.cono.morny.util.tgapi.TelegramExtensions.Bot.exec import cc.sukazyo.cono.morny.util.tgapi.TelegramExtensions.Requests.unsafeExecute
import com.pengrad.telegrambot.model.{Chat, Update} import com.pengrad.telegrambot.model.{Chat, Update}
import com.pengrad.telegrambot.request.{DeleteMessage, GetChatMember, SendSticker} import com.pengrad.telegrambot.request.{DeleteMessage, GetChatMember, SendSticker}
import com.pengrad.telegrambot.TelegramBot
import scala.language.postfixOps import scala.language.postfixOps
class DirectMsgClear (using coeur: MornyCoeur) extends ISimpleCommand { class DirectMsgClear (using coeur: MornyCoeur) extends ISimpleCommand {
private given TelegramBot = coeur.account
override val name: String = "r" override val name: String = "r"
override val aliases: List[ICommandAlias] = Nil override val aliases: List[ICommandAlias] = Nil
override def execute (using command: InputCommand, event: Update): Unit = { override def execute (using command: InputCommand, event: Update): Unit = {
logger debug "executing command /r" logger `debug` "executing command /r"
if (event.message.replyToMessage == null) return; if (event.message.replyToMessage == null) return
logger trace "message is a reply" logger `trace` "message is a reply"
if (event.message.replyToMessage.from.id != coeur.userid) return; if (event.message.replyToMessage.from.id != coeur.userid) return
logger trace "message replied is from me" logger `trace` "message replied is from me"
if (System.currentTimeMillis/1000 - event.message.replyToMessage.date > 48*60*60) return; if (System.currentTimeMillis/1000 - event.message.replyToMessage.date > 48*60*60) return
logger trace "message is not outdated(48 hrs ago)" logger `trace` "message is not outdated(48 hrs ago)"
val isTrusted = coeur.trusted isTrusted event.message.from.id val isTrusted = coeur.trusted isTrust event.message.from
// todo: // todo:
// it does not work. due to the Telegram Bot API doesn't provide // it does not work. due to the Telegram Bot API doesn't provide
// nested replyToMessage, so currently the trusted check by // nested replyToMessage, so currently the trusted check by
@ -38,22 +40,22 @@ class DirectMsgClear (using coeur: MornyCoeur) extends ISimpleCommand {
if (isTrusted || _isReplyTrusted) { if (isTrusted || _isReplyTrusted) {
coeur.account exec DeleteMessage( DeleteMessage(
event.message.chat.id, event.message.replyToMessage.messageId event.message.chat.id, event.message.replyToMessage.messageId
) ).unsafeExecute
def _isPrivate: Boolean = event.message.chat.`type` == Chat.Type.Private def _isPrivate: Boolean = event.message.chat.`type` == Chat.Type.Private
def _isPermission: Boolean = def _isPermission: Boolean =
(coeur.account exec GetChatMember(event.message.chat.id, event.message.from.id)) GetChatMember(event.message.chat.id, event.message.from.id).unsafeExecute
.chatMember.canDeleteMessages .chatMember.canDeleteMessages
if (_isPrivate || _isPermission) { if (_isPrivate || _isPermission) {
coeur.account exec DeleteMessage(event.message.chat.id, event.message.messageId) DeleteMessage(event.message.chat.id, event.message.messageId).unsafeExecute
} }
} else coeur.account exec SendSticker( } else SendSticker(
event.message.chat.id, event.message.chat.id,
TelegramStickers ID_403 TelegramStickers ID_403
).replyToMessageId(event.message.messageId) ).replyToMessageId(event.message.messageId).unsafeExecute
} }

View File

@ -5,13 +5,15 @@ import cc.sukazyo.cono.morny.core.bot.api.{ICommandAlias, ITelegramCommand}
import cc.sukazyo.cono.morny.core.bot.api.ICommandAlias.ListedAlias import cc.sukazyo.cono.morny.core.bot.api.ICommandAlias.ListedAlias
import cc.sukazyo.cono.morny.data.TelegramStickers import cc.sukazyo.cono.morny.data.TelegramStickers
import cc.sukazyo.cono.morny.util.tgapi.InputCommand import cc.sukazyo.cono.morny.util.tgapi.InputCommand
import cc.sukazyo.cono.morny.util.tgapi.TelegramExtensions.Bot.exec import cc.sukazyo.cono.morny.util.tgapi.TelegramExtensions.Requests.unsafeExecute
import com.pengrad.telegrambot.model.Update import com.pengrad.telegrambot.model.Update
import com.pengrad.telegrambot.request.SendSticker import com.pengrad.telegrambot.request.SendSticker
import com.pengrad.telegrambot.TelegramBot
import scala.language.postfixOps import scala.language.postfixOps
class MornyHellos (using coeur: MornyCoeur) { class MornyHellos (using coeur: MornyCoeur) {
private given TelegramBot = coeur.account
object On extends ITelegramCommand { object On extends ITelegramCommand {
@ -21,10 +23,11 @@ class MornyHellos (using coeur: MornyCoeur) {
override val description: String = "检查是否在线" override val description: String = "检查是否在线"
override def execute (using command: InputCommand, event: Update): Unit = override def execute (using command: InputCommand, event: Update): Unit =
coeur.account exec SendSticker( SendSticker(
event.message.chat.id, event.message.chat.id,
TelegramStickers ID_ONLINE_STATUS_RETURN TelegramStickers ID_ONLINE_STATUS_RETURN
).replyToMessageId(event.message.messageId) ).replyToMessageId(event.message.messageId)
.unsafeExecute
} }
@ -36,10 +39,11 @@ class MornyHellos (using coeur: MornyCoeur) {
override val description: String = "打招呼" override val description: String = "打招呼"
override def execute (using command: InputCommand, event: Update): Unit = override def execute (using command: InputCommand, event: Update): Unit =
coeur.account exec SendSticker( SendSticker(
event.message.chat.id, event.message.chat.id,
TelegramStickers ID_HELLO TelegramStickers ID_HELLO
).replyToMessageId(event.message.messageId) ).replyToMessageId(event.message.messageId)
.unsafeExecute
} }

View File

@ -4,21 +4,21 @@ import cc.sukazyo.cono.morny.core.MornyCoeur
import cc.sukazyo.cono.morny.core.bot.api.{ICommandAlias, ISimpleCommand} import cc.sukazyo.cono.morny.core.bot.api.{ICommandAlias, ISimpleCommand}
import cc.sukazyo.cono.morny.data.MornyInformation.{getAboutPic, getMornyAboutLinksHTML} import cc.sukazyo.cono.morny.data.MornyInformation.{getAboutPic, getMornyAboutLinksHTML}
import cc.sukazyo.cono.morny.util.tgapi.InputCommand import cc.sukazyo.cono.morny.util.tgapi.InputCommand
import cc.sukazyo.cono.morny.util.tgapi.TelegramExtensions.Bot.exec import cc.sukazyo.cono.morny.util.tgapi.TelegramExtensions.Requests.unsafeExecute
import com.pengrad.telegrambot.model.Update import com.pengrad.telegrambot.model.Update
import com.pengrad.telegrambot.model.request.ParseMode import com.pengrad.telegrambot.model.request.ParseMode
import com.pengrad.telegrambot.request.SendPhoto import com.pengrad.telegrambot.request.SendPhoto
import com.pengrad.telegrambot.TelegramBot
import scala.language.postfixOps
class MornyInfoOnStart (using coeur: MornyCoeur) extends ISimpleCommand { class MornyInfoOnStart (using coeur: MornyCoeur) extends ISimpleCommand {
private given TelegramBot = coeur.account
override val name: String = "start" override val name: String = "start"
override val aliases: List[ICommandAlias] = Nil override val aliases: List[ICommandAlias] = Nil
override def execute (using command: InputCommand, event: Update): Unit = { override def execute (using command: InputCommand, event: Update): Unit = {
coeur.account exec new SendPhoto( SendPhoto(
event.message.chat.id, event.message.chat.id,
getAboutPic getAboutPic
).caption( ).caption(
@ -32,6 +32,7 @@ class MornyInfoOnStart (using coeur: MornyCoeur) extends ISimpleCommand {
|你可以随时通过 /info 重新获得这些信息""" |你可以随时通过 /info 重新获得这些信息"""
.stripMargin .stripMargin
).parseMode(ParseMode HTML) ).parseMode(ParseMode HTML)
.unsafeExecute
} }

View File

@ -8,16 +8,16 @@ import cc.sukazyo.cono.morny.reporter.MornyReport
import cc.sukazyo.cono.morny.util.CommonFormat.{formatDate, formatDuration} import cc.sukazyo.cono.morny.util.CommonFormat.{formatDate, formatDuration}
import cc.sukazyo.cono.morny.util.tgapi.formatting.TelegramParseEscape.escapeHtml as h import cc.sukazyo.cono.morny.util.tgapi.formatting.TelegramParseEscape.escapeHtml as h
import cc.sukazyo.cono.morny.util.tgapi.InputCommand import cc.sukazyo.cono.morny.util.tgapi.InputCommand
import cc.sukazyo.cono.morny.util.tgapi.TelegramExtensions.Bot.exec import cc.sukazyo.cono.morny.util.tgapi.TelegramExtensions.Requests.unsafeExecute
import com.pengrad.telegrambot.model.Update import com.pengrad.telegrambot.model.Update
import com.pengrad.telegrambot.model.request.ParseMode import com.pengrad.telegrambot.model.request.ParseMode
import com.pengrad.telegrambot.request.{SendMessage, SendPhoto, SendSticker} import com.pengrad.telegrambot.request.{SendMessage, SendPhoto, SendSticker}
import com.pengrad.telegrambot.TelegramBot
import java.lang.System import java.lang.System
import scala.language.postfixOps
// todo: maybe move some utils method outside // todo: maybe move some utils method outside
class MornyInformation (using coeur: MornyCoeur) extends ITelegramCommand { class MornyInformation (using coeur: MornyCoeur) extends ITelegramCommand {
private given TelegramBot = coeur.account
private case object Subs { private case object Subs {
val STICKERS = "stickers" val STICKERS = "stickers"
@ -43,7 +43,7 @@ class MornyInformation (using coeur: MornyCoeur) extends ITelegramCommand {
val action: String = command.args(0) val action: String = command.args(0)
action match { action match {
case s if s startsWith Subs.STICKERS => echoStickers case s if s `startsWith` Subs.STICKERS => echoStickers
case Subs.RUNTIME => echoRuntime case Subs.RUNTIME => echoRuntime
case Subs.VERSION | Subs.VERSION_2 => echoVersion case Subs.VERSION | Subs.VERSION_2 => echoVersion
case Subs.TASKS => echoTasksStatus case Subs.TASKS => echoTasksStatus
@ -54,7 +54,7 @@ class MornyInformation (using coeur: MornyCoeur) extends ITelegramCommand {
} }
private def echoInfo (chatId: Long, replyTo: Int): Unit = { private def echoInfo (chatId: Long, replyTo: Int): Unit = {
coeur.account exec new SendPhoto( SendPhoto(
chatId, chatId,
getAboutPic getAboutPic
).caption( ).caption(
@ -64,6 +64,7 @@ class MornyInformation (using coeur: MornyCoeur) extends ITelegramCommand {
|$getMornyAboutLinksHTML""" |$getMornyAboutLinksHTML"""
.stripMargin .stripMargin
).parseMode(ParseMode HTML).replyToMessageId(replyTo) ).parseMode(ParseMode HTML).replyToMessageId(replyTo)
.unsafeExecute
} }
private def echoStickers (using command: InputCommand, event: Update): Unit = { private def echoStickers (using command: InputCommand, event: Update): Unit = {
@ -73,8 +74,8 @@ class MornyInformation (using coeur: MornyCoeur) extends ITelegramCommand {
else if (command.args.length == 2) command.args(1) else if (command.args.length == 2) command.args(1)
else null else null
} else if (command.args.length == 1) { } else if (command.args.length == 1) {
if ((command.args(0) startsWith s"${Subs.STICKERS}.") || (command.args(0) startsWith s"${Subs.STICKERS}#")) { if ((command.args(0) `startsWith` s"${Subs.STICKERS}.") || (command.args(0) `startsWith` s"${Subs.STICKERS}#")) {
command.args(0) substring Subs.STICKERS.length+1 command.args(0) `substring` Subs.STICKERS.length+1
} else null } else null
} else null } else null
if (mid == null) echo404 if (mid == null) echo404
@ -87,7 +88,7 @@ class MornyInformation (using coeur: MornyCoeur) extends ITelegramCommand {
echoSticker(_key, _file_id) echoSticker(_key, _file_id)
else { else {
try { try {
val sticker = TelegramStickers getById mid val sticker = TelegramStickers `getById` mid
echoSticker(sticker.getKey, sticker.getValue) echoSticker(sticker.getKey, sticker.getValue)
} catch case _: NoSuchFieldException => { } catch case _: NoSuchFieldException => {
echo404 echo404
@ -99,15 +100,15 @@ class MornyInformation (using coeur: MornyCoeur) extends ITelegramCommand {
val send_mid = SendMessage(send_chat, mid) val send_mid = SendMessage(send_chat, mid)
val send_sticker = SendSticker(send_chat, file_id) val send_sticker = SendSticker(send_chat, file_id)
if (send_replyTo != -1) send_mid.replyToMessageId(send_replyTo) if (send_replyTo != -1) send_mid.replyToMessageId(send_replyTo)
val result_send_mid = coeur.account exec send_mid val result_send_mid = send_mid.unsafeExecute
send_sticker.replyToMessageId(result_send_mid.message.messageId) send_sticker.replyToMessageId(result_send_mid.message.messageId)
coeur.account exec send_sticker send_sticker.unsafeExecute
} }
private[command] def echoVersion (using event: Update): Unit = { private[command] def echoVersion (using event: Update): Unit = {
val versionDeltaHTML = MornySystem.VERSION_DELTA match {case Some(d) => s"-δ<code>${h(d)}</code>" case None => ""} val versionDeltaHTML = MornySystem.VERSION_DELTA match {case Some(d) => s"-δ<code>${h(d)}</code>" case None => ""}
val versionGitHTML = if (MornySystem.GIT_COMMIT nonEmpty) s"git $getVersionGitTagHTML" else "" val versionGitHTML = if (MornySystem.GIT_COMMIT nonEmpty) s"git $getVersionGitTagHTML" else ""
coeur.account exec new SendMessage( SendMessage(
event.message.chat.id, event.message.chat.id,
// language=html // language=html
s"""version: s"""version:
@ -120,11 +121,12 @@ class MornyInformation (using coeur: MornyCoeur) extends ITelegramCommand {
|- <code>${h(formatDate(MornySystem.CODE_TIMESTAMP, 0))} [UTC]</code> |- <code>${h(formatDate(MornySystem.CODE_TIMESTAMP, 0))} [UTC]</code>
|""".stripMargin |""".stripMargin
).replyToMessageId(event.message.messageId).parseMode(ParseMode HTML) ).replyToMessageId(event.message.messageId).parseMode(ParseMode HTML)
.unsafeExecute
} }
private[command] def echoRuntime (using event: Update): Unit = { private[command] def echoRuntime (using event: Update): Unit = {
def sysprop (p: String): String = System.getProperty(p) def sysprop (p: String): String = System.getProperty(p)
coeur.account exec new SendMessage( SendMessage(
event.message.chat.id, event.message.chat.id,
/* language=html */ /* language=html */
s"""system: s"""system:
@ -148,11 +150,12 @@ class MornyInformation (using coeur: MornyCoeur) extends ITelegramCommand {
|- [<code>${coeur.coeurStartTimestamp}</code>]""" |- [<code>${coeur.coeurStartTimestamp}</code>]"""
.stripMargin .stripMargin
).parseMode(ParseMode HTML).replyToMessageId(event.message.messageId) ).parseMode(ParseMode HTML).replyToMessageId(event.message.messageId)
.unsafeExecute
} }
private def echoTasksStatus (using update: Update): Unit = { private def echoTasksStatus (using update: Update): Unit = {
// if !coeur.trusted.isTrusted(update.message.from.id) then return; // if !coeur.trusted.isTrusted(update.message.from.id) then return;
coeur.account exec SendMessage( SendMessage(
update.message.chat.id, update.message.chat.id,
// language=html // language=html
s"""<b>Coeur Task Scheduler:</b> s"""<b>Coeur Task Scheduler:</b>
@ -161,26 +164,29 @@ class MornyInformation (using coeur: MornyCoeur) extends ITelegramCommand {
| - <i>current runner status</i>: <code>${coeur.tasks.runnerState}</code> | - <i>current runner status</i>: <code>${coeur.tasks.runnerState}</code>
|""".stripMargin |""".stripMargin
).parseMode(ParseMode.HTML).replyToMessageId(update.message.messageId) ).parseMode(ParseMode.HTML).replyToMessageId(update.message.messageId)
.unsafeExecute
} }
private def echoEventStatistics (using update: Update): Unit = { private def echoEventStatistics (using update: Update): Unit = {
coeur.externalContext >> { (reporter: MornyReport) => coeur.externalContext >> { (reporter: MornyReport) =>
coeur.account exec SendMessage( SendMessage(
update.message.chat.id, update.message.chat.id,
// language=html // language=html
s"""<b>Event Statistics :</b> s"""<b>Event Statistics :</b>
|in today |in today
|${reporter.EventStatistics.eventStatisticsHTML}""".stripMargin |${reporter.EventStatistics.eventStatisticsHTML}""".stripMargin
).parseMode(ParseMode.HTML).replyToMessageId(update.message.messageId) ).parseMode(ParseMode.HTML).replyToMessageId(update.message.messageId)
.unsafeExecute
} || { } || {
echo404 echo404
} }
} }
private def echo404 (using event: Update): Unit = private def echo404 (using event: Update): Unit =
coeur.account exec new SendSticker( SendSticker(
event.message.chat.id, event.message.chat.id,
TelegramStickers ID_404 TelegramStickers ID_404
).replyToMessageId(event.message.messageId) ).replyToMessageId(event.message.messageId)
.unsafeExecute
} }

View File

@ -8,13 +8,13 @@ import cc.sukazyo.cono.morny.data.TelegramStickers
import cc.sukazyo.cono.morny.reporter.MornyReport import cc.sukazyo.cono.morny.reporter.MornyReport
import cc.sukazyo.cono.morny.util.tgapi.InputCommand import cc.sukazyo.cono.morny.util.tgapi.InputCommand
import cc.sukazyo.cono.morny.util.tgapi.formatting.TelegramFormatter.* import cc.sukazyo.cono.morny.util.tgapi.formatting.TelegramFormatter.*
import cc.sukazyo.cono.morny.util.tgapi.TelegramExtensions.Bot.exec import cc.sukazyo.cono.morny.util.tgapi.TelegramExtensions.Requests.unsafeExecute
import com.pengrad.telegrambot.model.Update import com.pengrad.telegrambot.model.Update
import com.pengrad.telegrambot.request.SendSticker import com.pengrad.telegrambot.request.SendSticker
import com.pengrad.telegrambot.TelegramBot
import scala.language.postfixOps
class MornyManagers (using coeur: MornyCoeur) { class MornyManagers (using coeur: MornyCoeur) {
private given TelegramBot = coeur.account
object Exit extends ITelegramCommand { object Exit extends ITelegramCommand {
@ -27,22 +27,24 @@ class MornyManagers (using coeur: MornyCoeur) {
val user = event.message.from val user = event.message.from
if (coeur.trusted isTrusted user.id) { if (coeur.trusted isTrust user) {
coeur.account exec SendSticker( SendSticker(
event.message.chat.id, event.message.chat.id,
TelegramStickers ID_EXIT TelegramStickers ID_EXIT
).replyToMessageId(event.message.messageId) ).replyToMessageId(event.message.messageId)
logger attention s"Morny exited by user ${user toLogTag}" .unsafeExecute
logger `attention` s"Morny exited by user ${user toLogTag}"
coeur.exit(0, user) coeur.exit(0, user)
} else { } else {
coeur.account exec SendSticker( SendSticker(
event.message.chat.id, event.message.chat.id,
TelegramStickers ID_403 TelegramStickers ID_403
).replyToMessageId(event.message.messageId) ).replyToMessageId(event.message.messageId)
logger attention s"403 exit caught from user ${user toLogTag}" .unsafeExecute
logger `attention` s"403 exit caught from user ${user toLogTag}"
coeur.externalContext.consume[MornyReport](_.unauthenticatedAction("/exit", user)) coeur.externalContext.consume[MornyReport](_.unauthenticatedAction("/exit", user))
} }
@ -62,22 +64,24 @@ class MornyManagers (using coeur: MornyCoeur) {
val user = event.message.from val user = event.message.from
if (coeur.trusted isTrusted user.id) { if (coeur.trusted isTrust user) {
logger attention s"call save from command by ${user toLogTag}" logger `attention` s"call save from command by ${user toLogTag}"
coeur.saveDataAll() coeur.saveDataAll()
coeur.account exec SendSticker( SendSticker(
event.message.chat.id, event.message.chat.id,
TelegramStickers ID_SAVED TelegramStickers ID_SAVED
).replyToMessageId(event.message.messageId) ).replyToMessageId(event.message.messageId)
.unsafeExecute
} else { } else {
coeur.account exec SendSticker( SendSticker(
event.message.chat.id, event.message.chat.id,
TelegramStickers ID_403 TelegramStickers ID_403
).replyToMessageId(event.message.messageId) ).replyToMessageId(event.message.messageId)
logger attention s"403 save caught from user ${user toLogTag}" .unsafeExecute
logger `attention` s"403 save caught from user ${user toLogTag}"
coeur.externalContext.consume[MornyReport](_.unauthenticatedAction("/save", user)) coeur.externalContext.consume[MornyReport](_.unauthenticatedAction("/save", user))
} }

View File

@ -2,35 +2,38 @@ package cc.sukazyo.cono.morny.core.bot.event
import cc.sukazyo.cono.morny.core.MornyCoeur import cc.sukazyo.cono.morny.core.MornyCoeur
import cc.sukazyo.cono.morny.core.bot.api.{EventEnv, EventListener, InlineQueryUnit, MornyQueryManager} import cc.sukazyo.cono.morny.core.bot.api.{EventEnv, EventListener, InlineQueryUnit, MornyQueryManager}
import cc.sukazyo.cono.morny.util.tgapi.TelegramExtensions.Bot.exec import cc.sukazyo.cono.morny.util.tgapi.TelegramExtensions.Requests.unsafeExecute
import com.pengrad.telegrambot.model.request.InlineQueryResult import com.pengrad.telegrambot.model.request.InlineQueryResult
import com.pengrad.telegrambot.request.AnswerInlineQuery import com.pengrad.telegrambot.request.AnswerInlineQuery
import com.pengrad.telegrambot.TelegramBot
import scala.collection.mutable.ListBuffer import scala.collection.mutable.ListBuffer
import scala.language.postfixOps
import scala.reflect.ClassTag import scala.reflect.ClassTag
class MornyOnInlineQuery (using queryManager: MornyQueryManager) (using coeur: MornyCoeur) extends EventListener { class MornyOnInlineQuery (using queryManager: MornyQueryManager) (using coeur: MornyCoeur) extends EventListener {
private given TelegramBot = coeur.account
override def onInlineQuery (using event: EventEnv): Unit = { override def onInlineQuery (using event: EventEnv): Unit = {
import event.update import event.update
val results: List[InlineQueryUnit[_]] = queryManager query update val results: List[InlineQueryUnit[?]] = queryManager `query` update
var cacheTime = Int.MaxValue var cacheTime = Int.MaxValue
var isPersonal = InlineQueryUnit.defaults.IS_PERSONAL var isPersonal = InlineQueryUnit.defaults.IS_PERSONAL
val resultAnswers = ListBuffer[InlineQueryResult[_]]() val resultAnswers = ListBuffer[InlineQueryResult[?]]()
for (r <- results) { for (r <- results) {
if (cacheTime > r.cacheTime) cacheTime = r.cacheTime if (cacheTime > r.cacheTime) cacheTime = r.cacheTime
if (r isPersonal) isPersonal = true if (r isPersonal) isPersonal = true
resultAnswers += r.result resultAnswers += r.result
} }
if (results isEmpty) return; if (results isEmpty) return
coeur.account exec AnswerInlineQuery( AnswerInlineQuery(
update.inlineQuery.id, resultAnswers toArray:_* update.inlineQuery.id,
(resultAnswers toArray)*
).cacheTime(cacheTime).isPersonal(isPersonal) ).cacheTime(cacheTime).isPersonal(isPersonal)
.unsafeExecute
event.setEventOk event.setEventOk

View File

@ -14,21 +14,21 @@ class MornyOnTelegramCommand (using commandManager: MornyCommandManager) (using
def _isCommandMessage(message: Message): Boolean = def _isCommandMessage(message: Message): Boolean =
if message.text eq null then false if message.text eq null then false
else if !(message.text startsWith "/") then false else if !(message.text `startsWith` "/") then false
else if message.text startsWith "/ " then false else if message.text `startsWith` "/ " then false
else true else true
if !_isCommandMessage(update.message) then return if !_isCommandMessage(update.message) then return
val inputCommand = InputCommand(update.message.text drop 1) val inputCommand = InputCommand(update.message.text drop 1)
givenCxt << inputCommand givenCxt << inputCommand
logger trace ":provided InputCommand for event" logger `trace` ":provided InputCommand for event"
if (!(inputCommand.command matches "^\\w+$")) if (!(inputCommand.command `matches` "^\\w+$"))
logger debug "not command" logger `debug` "not command"
else if ((inputCommand.target ne null) && (inputCommand.target != coeur.username)) else if ((inputCommand.target ne null) && (inputCommand.target != coeur.username))
logger debug "not morny command" logger `debug` "not morny command"
else else
logger debug "is command" logger `debug` "is command"
if commandManager.execute(using inputCommand) then if commandManager.execute(using inputCommand) then
setEventOk setEventOk

View File

@ -20,7 +20,7 @@ class MornyOnUpdateTimestampOffsetLock (using coeur: MornyCoeur) extends EventLi
override def on (using event: EventEnv): Unit = override def on (using event: EventEnv): Unit =
event.update.sourceTime match event.update.sourceTime match
case Some(timestamp) => case Some(timestamp) =>
if coeur.config.eventIgnoreOutdated && (EpochMillis.fromEpochSeconds(timestamp) < coeur.coeurStartTimestamp) then if coeur.config.eventIgnoreOutdated && ((EpochMillis fromSeconds timestamp) < coeur.coeurStartTimestamp) then
event.setEventCanceled event.setEventCanceled
case None => case None =>

View File

@ -1,7 +1,7 @@
package cc.sukazyo.cono.morny.core.http.api package cc.sukazyo.cono.morny.core.http.api
import cats.effect.IO import cats.effect.IO
import cc.sukazyo.cono.morny.core.Log.exceptionLog import cc.sukazyo.cono.morny.util.UseThrowable.toLogString
import org.http4s.{HttpRoutes, Response} import org.http4s.{HttpRoutes, Response}
trait HttpService4Api { trait HttpService4Api {
@ -13,7 +13,7 @@ trait HttpService4Api {
response.setMornyInternalErrorHeader( response.setMornyInternalErrorHeader(
e.getClass.getSimpleName, e.getClass.getSimpleName,
e.getMessage, e.getMessage,
exceptionLog(e) e.toLogString
) )
def setMornyInternalErrorHeader ( def setMornyInternalErrorHeader (
`Morny-Internal-Error-Type`: String, `Morny-Internal-Error-Type`: String,

View File

@ -2,6 +2,8 @@ package cc.sukazyo.cono.morny.core.http.api
trait MornyHttpServerContext { trait MornyHttpServerContext {
infix def register4API (service: HttpService4Api): Unit
def register4API (service: HttpService4Api*): Unit def register4API (service: HttpService4Api*): Unit
def start: HttpServer def start: HttpServer

View File

@ -3,7 +3,8 @@ package cc.sukazyo.cono.morny.core.http.internal
import cc.sukazyo.cono.morny.core.MornyCoeur import cc.sukazyo.cono.morny.core.MornyCoeur
import cc.sukazyo.cono.morny.core.http.api.{HttpServer, HttpService4Api, MornyHttpServerContext} import cc.sukazyo.cono.morny.core.http.api.{HttpServer, HttpService4Api, MornyHttpServerContext}
import cc.sukazyo.cono.morny.core.http.ServiceUI import cc.sukazyo.cono.morny.core.http.ServiceUI
import cc.sukazyo.cono.morny.core.Log.{exceptionLog, logger} import cc.sukazyo.cono.morny.core.Log.logger
import cc.sukazyo.cono.morny.util.UseThrowable.toLogString
import scala.collection.mutable import scala.collection.mutable
@ -13,6 +14,9 @@ class MornyHttpServerContextImpl (using coeur: MornyCoeur) extends MornyHttpServ
private lazy val service_ui = ServiceUI() private lazy val service_ui = ServiceUI()
override infix def register4API (service: HttpService4Api): Unit =
services_api += service
override def register4API (services: HttpService4Api*): Unit = override def register4API (services: HttpService4Api*): Unit =
services_api ++= services services_api ++= services
@ -31,9 +35,9 @@ class MornyHttpServerContextImpl (using coeur: MornyCoeur) extends MornyHttpServ
def errorHandler (t: Throwable, message: =>String): OptionT[IO, Unit] = def errorHandler (t: Throwable, message: =>String): OptionT[IO, Unit] =
OptionT.liftF(IO { OptionT.liftF(IO {
logger error logger `error`
s"""Unexpected exception occurred on Morny Http Server : s"""Unexpected exception occurred on Morny Http Server :
|${exceptionLog(t)}""".stripMargin |${t.toLogString}""".stripMargin
}) })
val withErrorHandler = ErrorHandling.Recover.total( val withErrorHandler = ErrorHandling.Recover.total(
ErrorAction.log( ErrorAction.log(
@ -49,7 +53,7 @@ class MornyHttpServerContextImpl (using coeur: MornyCoeur) extends MornyHttpServ
.resource .resource
val (_server, _shutdown_io) = server.allocated.unsafeRunSync() match val (_server, _shutdown_io) = server.allocated.unsafeRunSync() match
case (_1, _2) => (_1, _2) case (_1, _2) => (_1, _2)
logger notice s"Morny HTTP Server started at ${_server.baseUri}" logger `notice` s"Morny HTTP Server started at ${_server.baseUri}"
new HttpServer(using global): new HttpServer(using global):
val server: Server = _server val server: Server = _server

View File

@ -5,9 +5,6 @@ import cc.sukazyo.cono.morny.util.CommonFormat.formatDate
import cc.sukazyo.messiva.formatter.ILogFormatter import cc.sukazyo.messiva.formatter.ILogFormatter
import cc.sukazyo.messiva.log.Log import cc.sukazyo.messiva.log.Log
import java.time.{ZoneId, ZoneOffset}
import java.util.TimeZone
class MornyFormatterConsole extends ILogFormatter { class MornyFormatterConsole extends ILogFormatter {
override def format (log: Log): String = override def format (log: Log): String =

View File

@ -8,7 +8,7 @@ class MornyLoggerBase extends Logger with IMornyLogLevelImpl {
def this (appends: IAppender*) = def this (appends: IAppender*) =
this() this()
this.appends.addAll(java.util.List.of(appends:_*)) this.appends.addAll(java.util.List.of(appends*))
override def notice (message: String): Unit = override def notice (message: String): Unit =
pushToAllAppender(Log(1, new Message(message), MornyLogLevels.NOTICE)) pushToAllAppender(Log(1, new Message(message), MornyLogLevels.NOTICE))

View File

@ -13,7 +13,7 @@ object MornyInformation {
case None => "" case None => ""
case Some(commit) => case Some(commit) =>
val g = StringBuilder() val g = StringBuilder()
val cm = commit substring(0, 8) val cm = commit.substring(0, 8)
val cp = MornySystem.currentCodePath val cp = MornySystem.currentCodePath
if (cp == null) g ++= s"<code>$cm</code>" if (cp == null) g ++= s"<code>$cm</code>"
else g ++= s"<a href='$cp'>$cm</a>" else g ++= s"<a href='$cp'>$cm</a>"

View File

@ -20,7 +20,7 @@ object TelegramImages {
@throws[AssetsException] @throws[AssetsException]
private def read (): Unit = { private def read (): Unit = {
Using ((MornyAssets.pack getResource assetsPath)read) { stream => Using ((MornyAssets.pack `getResource` assetsPath)read) { stream =>
try { this.cache = Some(stream.readAllBytes()) } try { this.cache = Some(stream.readAllBytes()) }
catch case e: IOException => { catch case e: IOException => {
throw AssetsException(e) throw AssetsException(e)

View File

@ -10,18 +10,20 @@ import cc.sukazyo.cono.morny.util.tgapi.InputCommand
import cc.sukazyo.cono.morny.util.CommonEncrypt import cc.sukazyo.cono.morny.util.CommonEncrypt
import cc.sukazyo.cono.morny.util.CommonEncrypt.* import cc.sukazyo.cono.morny.util.CommonEncrypt.*
import cc.sukazyo.cono.morny.util.ConvertByteHex.toHex import cc.sukazyo.cono.morny.util.ConvertByteHex.toHex
import cc.sukazyo.cono.morny.util.tgapi.TelegramExtensions.Bot.exec import cc.sukazyo.cono.morny.util.tgapi.TelegramExtensions.File.getContent
import cc.sukazyo.cono.morny.util.tgapi.TelegramExtensions.Requests.unsafeExecute
import com.pengrad.telegrambot.model.{PhotoSize, Update} import com.pengrad.telegrambot.model.{PhotoSize, Update}
import com.pengrad.telegrambot.model.request.ParseMode import com.pengrad.telegrambot.model.request.ParseMode
import com.pengrad.telegrambot.request.{GetFile, SendDocument, SendMessage, SendSticker} import com.pengrad.telegrambot.request.{GetFile, SendDocument, SendMessage, SendSticker}
import com.pengrad.telegrambot.TelegramBot
import java.io.IOException import java.io.IOException
import java.net.{URLDecoder, URLEncoder} import java.net.{URLDecoder, URLEncoder}
import java.util.Base64 import java.util.Base64
import scala.language.postfixOps
/** Provides Telegram Command __`/encrypt`__. */ /** Provides Telegram Command __`/encrypt`__. */
class Encryptor (using coeur: MornyCoeur) extends ITelegramCommand { class Encryptor (using coeur: MornyCoeur) extends ITelegramCommand {
private given TelegramBot = coeur.account
override val name: String = "encrypt" override val name: String = "encrypt"
override val aliases: List[ICommandAlias] = ListedAlias("enc") :: Nil override val aliases: List[ICommandAlias] = ListedAlias("enc") :: Nil
@ -33,7 +35,7 @@ class Encryptor (using coeur: MornyCoeur) extends ITelegramCommand {
val args = command.args val args = command.args
// show a simple help page // show a simple help page
if ((args isEmpty) || ((args(0) equals "l") && (args.length == 1))) if ((args isEmpty) || ((args(0) `equals` "l") && (args.length == 1)))
echoHelp(event.message.chat.id, event.message.messageId) echoHelp(event.message.chat.id, event.message.messageId)
return return
@ -45,17 +47,18 @@ class Encryptor (using coeur: MornyCoeur) extends ITelegramCommand {
// so the algorithm will be and must be in the 2nd arg. // so the algorithm will be and must be in the 2nd arg.
/** inner function: is input `arg` means mod-param ''uppercase'' */ /** inner function: is input `arg` means mod-param ''uppercase'' */
def _is_mod_u(arg: String): Boolean = def _is_mod_u(arg: String): Boolean =
if (arg equalsIgnoreCase "uppercase") return true if (arg `equalsIgnoreCase` "uppercase") return true
if (arg equalsIgnoreCase "u") return true if (arg `equalsIgnoreCase` "u") return true
if (arg equalsIgnoreCase "upper") return true if (arg `equalsIgnoreCase` "upper") return true
false false
val mod_uppercase = if (args.length > 1) { val mod_uppercase = if (args.length > 1) {
if (args.length < 3 && _is_mod_u(args(1))) true if (args.length < 3 && _is_mod_u(args(1))) true
else else
coeur.account exec SendSticker( SendSticker(
event.message.chat.id, event.message.chat.id,
TelegramStickers ID_404 TelegramStickers ID_404
).replyToMessageId(event.message.messageId) ).replyToMessageId(event.message.messageId)
.unsafeExecute
return return
} else false } else false
@ -73,15 +76,16 @@ class Encryptor (using coeur: MornyCoeur) extends ITelegramCommand {
val asByteArray: Array[Byte] = data val asByteArray: Array[Byte] = data
/** inner class: the [[XEncryptable]] implementation of [[String]] data */ /** inner class: the [[XEncryptable]] implementation of [[String]] data */
case class XText (data: String) extends XEncryptable: case class XText (data: String) extends XEncryptable:
val asByteArray: Array[Byte] = data getBytes CommonEncrypt.ENCRYPT_STANDARD_CHARSET val asByteArray: Array[Byte] = data.getBytes(CommonEncrypt.ENCRYPT_STANDARD_CHARSET)
val input: XEncryptable = val input: XEncryptable =
val _r = event.message.replyToMessage val _r = event.message.replyToMessage
if ((_r ne null) && (_r.document ne null)) { if ((_r ne null) && (_r.document ne null)) {
try {XFile( try {XFile(
coeur.account getFileContent (coeur.account exec GetFile(_r.document.fileId)).file, GetFile(_r.document.fileId).unsafeExecute
.file.getContent,
_r.document.fileName _r.document.fileName
)} catch case e: IOException => )} catch case e: IOException =>
logger warn s"NetworkRequest error: TelegramFileAPI:\n\t${e.getMessage}" logger `warn` s"NetworkRequest error: TelegramFileAPI:\n\t${e.getMessage}"
coeur.externalContext.consume[MornyReport](_.exception(e, "NetworkRequest error: TelegramFileAPI")) coeur.externalContext.consume[MornyReport](_.exception(e, "NetworkRequest error: TelegramFileAPI"))
return return
} else if ((_r ne null) && (_r.photo ne null)) { } else if ((_r ne null) && (_r.photo ne null)) {
@ -96,26 +100,28 @@ class Encryptor (using coeur: MornyCoeur) extends ITelegramCommand {
if (_photo_origin eq null) throw IllegalArgumentException("no photo from api.") if (_photo_origin eq null) throw IllegalArgumentException("no photo from api.")
import cc.sukazyo.cono.morny.util.UseRandom.rand_id import cc.sukazyo.cono.morny.util.UseRandom.rand_id
XFile( XFile(
coeur.account getFileContent (coeur.account exec GetFile(_photo_origin.fileId)).file, GetFile(_photo_origin.fileId).unsafeExecute
.file.getContent,
s"photo$rand_id.png" s"photo$rand_id.png"
) )
} catch } catch
case e: IOException => case e: IOException =>
//noinspection DuplicatedCode //noinspection DuplicatedCode
logger warn s"NetworkRequest error: TelegramFileAPI:\n\t${e.getMessage}" logger `warn` s"NetworkRequest error: TelegramFileAPI:\n\t${e.getMessage}"
coeur.externalContext.consume[MornyReport](_.exception(e, "NetworkRequest error: TelegramFileAPI")) coeur.externalContext.consume[MornyReport](_.exception(e, "NetworkRequest error: TelegramFileAPI"))
return return
case e: IllegalArgumentException => case e: IllegalArgumentException =>
logger warn s"FileProcess error: PhotoSize:\n\t${e.getMessage}" logger `warn` s"FileProcess error: PhotoSize:\n\t${e.getMessage}"
coeur.externalContext.consume[MornyReport](_.exception(e, "FileProcess error: PhotoSize")) coeur.externalContext.consume[MornyReport](_.exception(e, "FileProcess error: PhotoSize"))
return return
} else if ((_r ne null) && (_r.text ne null)) { } else if ((_r ne null) && (_r.text ne null)) {
XText(_r.text) XText(_r.text)
} else { } else {
coeur.account exec SendMessage( SendMessage(
event.message.chat.id, event.message.chat.id,
"<i><u>null</u></i>" "<i><u>null</u></i>"
).parseMode(ParseMode HTML).replyToMessageId(event.message.messageId) ).parseMode(ParseMode HTML).replyToMessageId(event.message.messageId)
.unsafeExecute
return return
} }
// END BLOCK: get input // END BLOCK: get input
@ -141,14 +147,15 @@ class Encryptor (using coeur: MornyCoeur) extends ITelegramCommand {
EXHash(if mod_uppercase then hashed toUpperCase else hashed) EXHash(if mod_uppercase then hashed toUpperCase else hashed)
//noinspection UnitMethodIsParameterless //noinspection UnitMethodIsParameterless
def echo_unsupported: Unit = def echo_unsupported: Unit =
coeur.account exec SendSticker( SendSticker(
event.message.chat.id, event.message.chat.id,
TelegramStickers ID_404 TelegramStickers ID_404
).replyToMessageId(event.message.messageId) ).replyToMessageId(event.message.messageId)
.unsafeExecute
val result: EXHash|EXFile|EXText = args(0) match val result: EXHash|EXFile|EXText = args(0) match
case "base64" | "b64" | "base64url" | "base64u" | "b64u" => case "base64" | "b64" | "base64url" | "base64u" | "b64u" =>
val _tool_b64 = val _tool_b64 =
if args(0) contains "u" then Base64.getUrlEncoder if args(0) `contains` "u" then Base64.getUrlEncoder
else Base64.getEncoder else Base64.getEncoder
genResult_encrypt( genResult_encrypt(
input, input,
@ -157,7 +164,7 @@ class Encryptor (using coeur: MornyCoeur) extends ITelegramCommand {
) )
case "base64decode" | "base64d" | "b64d" | "base64url-decode" | "base64ud" | "b64ud" => case "base64decode" | "base64d" | "b64d" | "base64url-decode" | "base64ud" | "b64ud" =>
val _tool_b64d = val _tool_b64d =
if args(0) contains "u" then Base64.getUrlDecoder if args(0) `contains` "u" then Base64.getUrlDecoder
else Base64.getDecoder else Base64.getDecoder
try { genResult_encrypt( try { genResult_encrypt(
input, input,
@ -190,17 +197,19 @@ class Encryptor (using coeur: MornyCoeur) extends ITelegramCommand {
// output // output
result match result match
case _file: EXFile => case _file: EXFile =>
coeur.account exec SendDocument( SendDocument(
event.message.chat.id, event.message.chat.id,
_file.result _file.result
).fileName(_file.resultName).replyToMessageId(event.message.messageId) ).fileName(_file.resultName).replyToMessageId(event.message.messageId)
.unsafeExecute
case _text: EXTextLike => case _text: EXTextLike =>
import cc.sukazyo.cono.morny.util.tgapi.formatting.TelegramParseEscape.escapeHtml as h import cc.sukazyo.cono.morny.util.tgapi.formatting.TelegramParseEscape.escapeHtml as h
coeur.account exec SendMessage( SendMessage(
event.message.chat.id, event.message.chat.id,
// language=html // language=html
s"<pre><code>${h(_text.text)}</code></pre>" s"<pre><code>${h(_text.text)}</code></pre>"
).parseMode(ParseMode HTML).replyToMessageId(event.message.messageId) ).parseMode(ParseMode HTML).replyToMessageId(event.message.messageId)
.unsafeExecute
} }
@ -231,7 +240,7 @@ class Encryptor (using coeur: MornyCoeur) extends ITelegramCommand {
* </blockquote> * </blockquote>
*/ */
private def echoHelp(chat: Long, replyTo: Int): Unit = private def echoHelp(chat: Long, replyTo: Int): Unit =
coeur.account exec SendMessage( SendMessage(
chat, chat,
s"""<b><u>base64</u></b>, b64 s"""<b><u>base64</u></b>, b64
|<b><u>base64url</u></b>, base64u, b64u |<b><u>base64url</u></b>, base64u, b64u
@ -247,5 +256,6 @@ class Encryptor (using coeur: MornyCoeur) extends ITelegramCommand {
|<b><i>uppercase</i></b>, upper, u <i>(sha1/sha256/sha512/md5 only)</i>""" |<b><i>uppercase</i></b>, upper, u <i>(sha1/sha256/sha512/md5 only)</i>"""
.stripMargin .stripMargin
).replyToMessageId(replyTo).parseMode(ParseMode HTML) ).replyToMessageId(replyTo).parseMode(ParseMode HTML)
.unsafeExecute
} }

View File

@ -3,14 +3,16 @@ package cc.sukazyo.cono.morny.ip186
import cc.sukazyo.cono.morny.core.MornyCoeur import cc.sukazyo.cono.morny.core.MornyCoeur
import cc.sukazyo.cono.morny.core.bot.api.{ICommandAlias, ITelegramCommand} import cc.sukazyo.cono.morny.core.bot.api.{ICommandAlias, ITelegramCommand}
import cc.sukazyo.cono.morny.util.tgapi.InputCommand import cc.sukazyo.cono.morny.util.tgapi.InputCommand
import cc.sukazyo.cono.morny.util.tgapi.TelegramExtensions.Bot.exec import cc.sukazyo.cono.morny.util.tgapi.TelegramExtensions.Requests.unsafeExecute
import com.pengrad.telegrambot.model.Update import com.pengrad.telegrambot.model.Update
import com.pengrad.telegrambot.model.request.ParseMode import com.pengrad.telegrambot.model.request.ParseMode
import com.pengrad.telegrambot.request.SendMessage import com.pengrad.telegrambot.request.SendMessage
import com.pengrad.telegrambot.TelegramBot
import scala.language.postfixOps import scala.language.postfixOps
class BotCommand (using coeur: MornyCoeur) { class BotCommand (using coeur: MornyCoeur) {
private given TelegramBot = coeur.account
private enum Subs (val cmd: String): private enum Subs (val cmd: String):
case IP extends Subs("ip") case IP extends Subs("ip")
@ -35,18 +37,20 @@ class BotCommand (using coeur: MornyCoeur) {
if (command.args isEmpty) if (command.args isEmpty)
if event.message.replyToMessage eq null then null else event.message.replyToMessage.text if event.message.replyToMessage eq null then null else event.message.replyToMessage.text
else if (command.args.length > 1) else if (command.args.length > 1)
coeur.account exec SendMessage( SendMessage(
event.message.chat.id, event.message.chat.id,
"[Unavailable] Too much arguments." "[Unavailable] Too much arguments."
).replyToMessageId(event.message.messageId) ).replyToMessageId(event.message.messageId)
.unsafeExecute
return return
else command.args(0) else command.args(0)
if (target eq null) if (target eq null)
coeur.account exec new SendMessage( SendMessage(
event.message.chat.id, event.message.chat.id,
"[Unavailable] No ip defined." "[Unavailable] No ip defined."
).replyToMessageId(event.message.messageId) ).replyToMessageId(event.message.messageId)
.unsafeExecute
return; return;
@ -58,20 +62,22 @@ class BotCommand (using coeur: MornyCoeur) {
case Subs.WHOIS.cmd => IP186QueryHandler.query_whoisPretty(target) case Subs.WHOIS.cmd => IP186QueryHandler.query_whoisPretty(target)
case _ => throw IllegalArgumentException(s"Unknown 186-IP query method ${command.command}") case _ => throw IllegalArgumentException(s"Unknown 186-IP query method ${command.command}")
coeur.account exec SendMessage( SendMessage(
event.message.chat.id, event.message.chat.id,
s"""${h(response.url)} s"""${h(response.url)}
|<code>${h(response.body)}</code>""" |<code>${h(response.body)}</code>"""
.stripMargin .stripMargin
).parseMode(ParseMode HTML).replyToMessageId(event.message.messageId) ).parseMode(ParseMode HTML).replyToMessageId(event.message.messageId)
.unsafeExecute
} catch case e: Exception => } catch case e: Exception =>
coeur.account exec new SendMessage( SendMessage(
event.message().chat().id(), event.message().chat().id(),
s"""[Exception] in query: s"""[Exception] in query:
|<code>${h(e.getMessage)}</code>""" |<code>${h(e.getMessage)}</code>"""
.stripMargin .stripMargin
).parseMode(ParseMode.HTML).replyToMessageId(event.message().messageId()) ).parseMode(ParseMode.HTML).replyToMessageId(event.message().messageId())
.unsafeExecute
} }

View File

@ -28,7 +28,7 @@ object IP186QueryHandler {
@throws[IOException] @throws[IOException]
def query_whoisPretty (domain: String): IP186Response = def query_whoisPretty (domain: String): IP186Response =
val raw = query_whois(domain) val raw = query_whois(domain)
IP186Response(raw.url, raw.body substring(0, (raw.body indexOf "<<<")+3)) IP186Response(raw.url, raw.body `substring`(0, (raw.body `indexOf` "<<<")+3))
@throws[IOException] @throws[IOException]
private def commonQuery (requestPath: Uri): IP186Response = { private def commonQuery (requestPath: Uri): IP186Response = {

View File

@ -4,7 +4,7 @@ import cc.sukazyo.cono.morny.core.Log.logger
import cc.sukazyo.cono.morny.core.MornyCoeur import cc.sukazyo.cono.morny.core.MornyCoeur
import cc.sukazyo.cono.morny.medication_timer.MedicationTimer.calcNextRoutineTimestamp import cc.sukazyo.cono.morny.medication_timer.MedicationTimer.calcNextRoutineTimestamp
import cc.sukazyo.cono.morny.util.schedule.RoutineTask import cc.sukazyo.cono.morny.util.schedule.RoutineTask
import cc.sukazyo.cono.morny.util.tgapi.TelegramExtensions.Bot.exec import cc.sukazyo.cono.morny.util.tgapi.TelegramExtensions.Requests.unsafeExecute
import cc.sukazyo.cono.morny.util.CommonFormat import cc.sukazyo.cono.morny.util.CommonFormat
import cc.sukazyo.cono.morny.util.EpochDateTime.EpochMillis import cc.sukazyo.cono.morny.util.EpochDateTime.EpochMillis
import com.cronutils.builder.CronBuilder import com.cronutils.builder.CronBuilder
@ -13,12 +13,14 @@ import com.cronutils.model.time.ExecutionTime
import com.pengrad.telegrambot.model.{Message, MessageEntity} import com.pengrad.telegrambot.model.{Message, MessageEntity}
import com.pengrad.telegrambot.request.{EditMessageText, SendMessage} import com.pengrad.telegrambot.request.{EditMessageText, SendMessage}
import com.pengrad.telegrambot.response.SendResponse import com.pengrad.telegrambot.response.SendResponse
import com.pengrad.telegrambot.TelegramBot
import java.time.{Instant, ZonedDateTime, ZoneOffset} import java.time.{Instant, ZonedDateTime, ZoneOffset}
import scala.collection.mutable.ArrayBuffer import scala.collection.mutable.ArrayBuffer
import scala.language.implicitConversions import scala.language.implicitConversions
class MedicationTimer (using coeur: MornyCoeur) { class MedicationTimer (using coeur: MornyCoeur) {
private given TelegramBot = coeur.account
private val NOTIFY_MESSAGE = "🍥⏲" private val NOTIFY_MESSAGE = "🍥⏲"
private val DAEMON_THREAD_NAME_DEF = "MedicationTimer" private val DAEMON_THREAD_NAME_DEF = "MedicationTimer"
@ -36,7 +38,7 @@ class MedicationTimer (using coeur: MornyCoeur) {
def calcNextSendTime: EpochMillis = def calcNextSendTime: EpochMillis =
val next_time = calcNextRoutineTimestamp(System.currentTimeMillis, use_timeZone, notify_atHour) val next_time = calcNextRoutineTimestamp(System.currentTimeMillis, use_timeZone, notify_atHour)
logger info s"medication timer will send next notify at ${CommonFormat.formatDate(next_time, use_timeZone.getTotalSeconds / 60 / 60)} with $use_timeZone [$next_time]" logger `info` s"medication timer will send next notify at ${CommonFormat.formatDate(next_time, use_timeZone.getTotalSeconds / 60 / 60)} with $use_timeZone [$next_time]"
next_time next_time
override def firstRoutineTimeMillis: EpochMillis = override def firstRoutineTimeMillis: EpochMillis =
@ -47,24 +49,24 @@ class MedicationTimer (using coeur: MornyCoeur) {
override def main: Unit = { override def main: Unit = {
sendNotification() sendNotification()
logger info "medication notify sent." logger `info` "medication notify sent."
} }
} }
def start(): Unit = def start(): Unit =
if ((notify_toChat == -1) || (notify_atHour isEmpty)) if ((notify_toChat == -1) || (notify_atHour isEmpty))
logger notice "Medication Timer disabled : related param is not complete set" logger `notice` "Medication Timer disabled : related param is not complete set"
return; return;
coeur.tasks ++ scheduleTask coeur.tasks ++ scheduleTask
logger notice "Medication Timer started." logger `notice` "Medication Timer started."
def stop(): Unit = def stop(): Unit =
coeur.tasks % scheduleTask coeur.tasks % scheduleTask
logger notice "Medication Timer stopped." logger `notice` "Medication Timer stopped."
private def sendNotification(): Unit = { private def sendNotification(): Unit = {
val sendResponse: SendResponse = coeur.account exec SendMessage(notify_toChat, NOTIFY_MESSAGE) val sendResponse: SendResponse = SendMessage(notify_toChat, NOTIFY_MESSAGE).unsafeExecute
if sendResponse isOk then lastNotify_messageId = Some(sendResponse.message.messageId) if sendResponse isOk then lastNotify_messageId = Some(sendResponse.message.messageId)
else lastNotify_messageId = None else lastNotify_messageId = None
} }
@ -76,11 +78,11 @@ class MedicationTimer (using coeur: MornyCoeur) {
val entities = ArrayBuffer.empty[MessageEntity] val entities = ArrayBuffer.empty[MessageEntity]
if edited.entities ne null then entities ++= edited.entities if edited.entities ne null then entities ++= edited.entities
entities += MessageEntity(MessageEntity.Type.italic, edited.text.length + "\n-- ".length, editTime.length) entities += MessageEntity(MessageEntity.Type.italic, edited.text.length + "\n-- ".length, editTime.length)
coeur.account exec EditMessageText( EditMessageText(
notify_toChat, notify_toChat,
edited.messageId, edited.messageId,
edited.text + s"\n-- $editTime --" edited.text + s"\n-- $editTime --"
).entities(entities toArray:_*) ).entities((entities toArray)*).unsafeExecute
lastNotify_messageId = None lastNotify_messageId = None
true true
} }
@ -105,7 +107,7 @@ object MedicationTimer {
})) }))
.instance .instance
).nextExecution( ).nextExecution(
ZonedDateTime ofInstant (Instant ofEpochMilli baseTimeMillis, zone.normalized) ZonedDateTime `ofInstant` (Instant `ofEpochMilli` baseTimeMillis, zone.normalized)
).get.toInstant.toEpochMilli ).get.toInstant.toEpochMilli
} }

View File

@ -27,7 +27,7 @@ class ModuleMedicationTimer extends MornyInternalModule {
externalContext >> { (instance: MedicationTimer) => externalContext >> { (instance: MedicationTimer) =>
eventManager register OnMedicationNotifyApply(using instance) eventManager register OnMedicationNotifyApply(using instance)
} || { } || {
logger warn "There seems no Medication Timer instance is provided; skipped register events for it." logger `warn` "There seems no Medication Timer instance is provided; skipped register events for it."
} }
} }
@ -38,7 +38,7 @@ class ModuleMedicationTimer extends MornyInternalModule {
externalContext >> { (instance: MedicationTimer) => externalContext >> { (instance: MedicationTimer) =>
instance.start() instance.start()
} || { } || {
logger warn "There seems no Medication Timer instance is provided; skipped start it." logger `warn` "There seems no Medication Timer instance is provided; skipped start it."
} }
} }
@ -49,7 +49,7 @@ class ModuleMedicationTimer extends MornyInternalModule {
externalContext >> { (instance: MedicationTimer) => externalContext >> { (instance: MedicationTimer) =>
instance.stop() instance.stop()
} || { } || {
logger warn "There seems no Medication Timer instance need to be stop." logger `warn` "There seems no Medication Timer instance need to be stop."
} }
} }

View File

@ -8,7 +8,7 @@ import scala.language.postfixOps
object MornyJrrp { object MornyJrrp {
def jrrp_of_telegramUser (user: User, timestamp: EpochMillis): Double = def jrrp_of_telegramUser (user: User, timestamp: EpochMillis): Double =
jrrp_v_xmomi(user.id, EpochDays fromEpochMillis timestamp) * 100.0 jrrp_v_xmomi(user.id, EpochDays fromMillis timestamp) * 100.0
private def jrrp_v_xmomi (identifier: Long, dayStamp: EpochDays): Double = private def jrrp_v_xmomi (identifier: Long, dayStamp: EpochDays): Double =
import cc.sukazyo.cono.morny.util.CommonEncrypt.MD5 import cc.sukazyo.cono.morny.util.CommonEncrypt.MD5

View File

@ -4,12 +4,14 @@ import cc.sukazyo.cono.morny.core.MornyCoeur
import cc.sukazyo.cono.morny.core.bot.api.{ICommandAlias, ITelegramCommand} import cc.sukazyo.cono.morny.core.bot.api.{ICommandAlias, ITelegramCommand}
import cc.sukazyo.cono.morny.util.tgapi.formatting.TelegramFormatter.* import cc.sukazyo.cono.morny.util.tgapi.formatting.TelegramFormatter.*
import cc.sukazyo.cono.morny.util.tgapi.InputCommand import cc.sukazyo.cono.morny.util.tgapi.InputCommand
import cc.sukazyo.cono.morny.util.tgapi.TelegramExtensions.Bot.exec import cc.sukazyo.cono.morny.util.tgapi.TelegramExtensions.Requests.unsafeExecute
import com.pengrad.telegrambot.model.Update import com.pengrad.telegrambot.model.Update
import com.pengrad.telegrambot.model.request.ParseMode import com.pengrad.telegrambot.model.request.ParseMode
import com.pengrad.telegrambot.request.SendMessage import com.pengrad.telegrambot.request.SendMessage
import com.pengrad.telegrambot.TelegramBot
class MornyOldJrrp (using coeur: MornyCoeur) extends ITelegramCommand { class MornyOldJrrp (using coeur: MornyCoeur) extends ITelegramCommand {
private given TelegramBot = coeur.account
override val name: String = "jrrp" override val name: String = "jrrp"
override val aliases: List[ICommandAlias] = Nil override val aliases: List[ICommandAlias] = Nil
@ -26,11 +28,12 @@ class MornyOldJrrp (using coeur: MornyCoeur) extends ITelegramCommand {
case _ => "..." case _ => "..."
import cc.sukazyo.cono.morny.util.tgapi.formatting.TelegramParseEscape.escapeHtml as h import cc.sukazyo.cono.morny.util.tgapi.formatting.TelegramParseEscape.escapeHtml as h
coeur.account exec SendMessage( SendMessage(
event.message.chat.id, event.message.chat.id,
// language=html // language=html
f"${user.fullnameRefHTML} 在(utc的)今天的运气指数是———— <code>$jrrp%.2f%%</code> ${h(ending)}" f"${user.fullnameRefHTML} 在(utc的)今天的运气指数是———— <code>$jrrp%.2f%%</code> ${h(ending)}"
).replyToMessageId(event.message.messageId).parseMode(ParseMode HTML) ).replyToMessageId(event.message.messageId).parseMode(ParseMode HTML)
.unsafeExecute
} }

View File

@ -3,25 +3,28 @@ package cc.sukazyo.cono.morny.morny_misc
import cc.sukazyo.cono.morny.core.MornyCoeur import cc.sukazyo.cono.morny.core.MornyCoeur
import cc.sukazyo.cono.morny.core.bot.api.{ICommandAlias, ISimpleCommand} import cc.sukazyo.cono.morny.core.bot.api.{ICommandAlias, ISimpleCommand}
import cc.sukazyo.cono.morny.util.tgapi.InputCommand import cc.sukazyo.cono.morny.util.tgapi.InputCommand
import cc.sukazyo.cono.morny.util.tgapi.TelegramExtensions.Bot.exec import cc.sukazyo.cono.morny.util.tgapi.TelegramExtensions.Requests.unsafeExecute
import com.pengrad.telegrambot.model.Update import com.pengrad.telegrambot.model.Update
import com.pengrad.telegrambot.model.request.ParseMode import com.pengrad.telegrambot.model.request.ParseMode
import com.pengrad.telegrambot.request.SendMessage import com.pengrad.telegrambot.request.SendMessage
import com.pengrad.telegrambot.TelegramBot
import scala.language.postfixOps import scala.language.postfixOps
class Testing (using coeur: MornyCoeur) extends ISimpleCommand { class Testing (using coeur: MornyCoeur) extends ISimpleCommand {
private given TelegramBot = coeur.account
override val name: String = "test" override val name: String = "test"
override val aliases: List[ICommandAlias] = Nil override val aliases: List[ICommandAlias] = Nil
override def execute (using command: InputCommand, event: Update): Unit = { override def execute (using command: InputCommand, event: Update): Unit = {
coeur.account exec new SendMessage( SendMessage(
event.message.chat.id, event.message.chat.id,
// language=html // language=html
"<b>Just</b> a TEST command." "<b>Just</b> a TEST command."
).replyToMessageId(event.message.messageId).parseMode(ParseMode HTML) ).replyToMessageId(event.message.messageId).parseMode(ParseMode HTML)
.unsafeExecute
} }

View File

@ -6,15 +6,15 @@ import cc.sukazyo.cono.morny.core.bot.api.{ICommandAlias, ITelegramCommand}
import cc.sukazyo.cono.morny.data.TelegramStickers import cc.sukazyo.cono.morny.data.TelegramStickers
import cc.sukazyo.cono.morny.util.tgapi.formatting.TelegramParseEscape.escapeHtml as h import cc.sukazyo.cono.morny.util.tgapi.formatting.TelegramParseEscape.escapeHtml as h
import cc.sukazyo.cono.morny.util.tgapi.InputCommand import cc.sukazyo.cono.morny.util.tgapi.InputCommand
import cc.sukazyo.cono.morny.util.tgapi.TelegramExtensions.Bot.exec import cc.sukazyo.cono.morny.util.tgapi.TelegramExtensions.Requests.unsafeExecute
import com.pengrad.telegrambot.model.Update import com.pengrad.telegrambot.model.Update
import com.pengrad.telegrambot.model.request.ParseMode import com.pengrad.telegrambot.model.request.ParseMode
import com.pengrad.telegrambot.request.{SendMessage, SendSticker} import com.pengrad.telegrambot.request.{SendMessage, SendSticker}
import com.pengrad.telegrambot.TelegramBot
import sttp.client3.{HttpError, SttpClientException} import sttp.client3.{HttpError, SttpClientException}
import scala.language.postfixOps
class CommandNbnhhsh (using coeur: MornyCoeur) extends ITelegramCommand { class CommandNbnhhsh (using coeur: MornyCoeur) extends ITelegramCommand {
private given TelegramBot = coeur.account
private val NBNHHSH_RESULT_HEAD_HTML = private val NBNHHSH_RESULT_HEAD_HTML =
// language=html // language=html
@ -33,52 +33,54 @@ class CommandNbnhhsh (using coeur: MornyCoeur) extends ITelegramCommand {
else if (event.message.replyToMessage != null && event.message.replyToMessage.text != null) else if (event.message.replyToMessage != null && event.message.replyToMessage.text != null)
event.message.replyToMessage.text event.message.replyToMessage.text
else else
coeur.account exec SendSticker( SendSticker(
event.message.chat.id, event.message.chat.id,
TelegramStickers ID_404 TelegramStickers ID_404
).replyToMessageId(event.message.messageId) ).replyToMessageId(event.message.messageId)
.unsafeExecute
return; return;
try { try {
val queryResp = NbnhhshQuery sendGuess queryTarget val queryResp = NbnhhshQuery `sendGuess` queryTarget
val message = StringBuilder(NBNHHSH_RESULT_HEAD_HTML) val message = StringBuilder(NBNHHSH_RESULT_HEAD_HTML)
logger trace s"**nbnhhsh got len=${queryResp.words.length}" logger `trace` s"**nbnhhsh got len=${queryResp.words.length}"
for (_word <- queryResp.words) { for (_word <- queryResp.words) {
logger trace s"**start for ${_word.name}" logger `trace` s"**start for ${_word.name}"
val _use_trans = (_word.trans ne null) && (_word.trans nonEmpty) val _use_trans = (_word.trans ne null) && (_word.trans nonEmpty)
val _use_inputting = (_word.inputting ne null) && (_word.inputting nonEmpty) val _use_inputting = (_word.inputting ne null) && (_word.inputting nonEmpty)
if (_use_trans || _use_inputting) if (_use_trans || _use_inputting)
message ++= s"\n\n<b>[[ ${h(_word.name)} ]]</b>" message ++= s"\n\n<b>[[ ${h(_word.name)} ]]</b>"
logger trace s"**used [${_word.name}]" logger `trace` s"**used [${_word.name}]"
if (_use_trans) for (_trans <- _word.trans) if (_use_trans) for (_trans <- _word.trans)
message ++= s"\n* <i>${h(_trans)}</i>" message ++= s"\n* <i>${h(_trans)}</i>"
logger trace s"**used [${_word.name}] used `${_trans}``" logger `trace` s"**used [${_word.name}] used `${_trans}``"
if (_use_inputting) if (_use_inputting)
logger trace s"**used [${_word.name}] inputting" logger `trace` s"**used [${_word.name}] inputting"
if (_use_trans) if (_use_trans)
message += '\n' message += '\n'
message ++= " maybe:" message ++= " maybe:"
for (_inputting <- _word.inputting) for (_inputting <- _word.inputting)
logger trace s"**used [${_word.name}] used-i ${_inputting}" logger `trace` s"**used [${_word.name}] used-i ${_inputting}"
message ++= s"\n` <i>${h(_inputting)}</i>" message ++= s"\n` <i>${h(_inputting)}</i>"
logger trace s"**done" logger `trace` s"**done"
} }
coeur.account exec SendMessage( SendMessage(
event.message.chat.id, event.message.chat.id,
message toString message toString
).parseMode(ParseMode HTML).replyToMessageId(event.message.messageId) ).parseMode(ParseMode HTML).replyToMessageId(event.message.messageId)
.unsafeExecute
} catch case e: (HttpError[_] | SttpClientException) => { } catch case e: (HttpError[_] | SttpClientException) => {
coeur.account exec SendMessage( SendMessage(
event.message.chat.id, event.message.chat.id,
s"""[Exception] in query: s"""[Exception] in query:
|${h(e.getMessage)} |${h(e.getMessage)}
|""".stripMargin |""".stripMargin
) ).unsafeExecute
} }
} }

View File

@ -20,7 +20,7 @@ object NbnhhshQuery {
private val httpClient = OkHttpSyncBackend() private val httpClient = OkHttpSyncBackend()
@throws[HttpError[_]|SttpClientException] @throws[HttpError[?]|SttpClientException]
def sendGuess (text: String): GuessResult = { def sendGuess (text: String): GuessResult = {
case class GuessRequest (text: String) case class GuessRequest (text: String)
val http = mornyBasicRequest val http = mornyBasicRequest

View File

@ -3,13 +3,15 @@ package cc.sukazyo.cono.morny.randomize_somthing
import cc.sukazyo.cono.morny.core.MornyCoeur import cc.sukazyo.cono.morny.core.MornyCoeur
import cc.sukazyo.cono.morny.core.bot.api.{EventEnv, EventListener} import cc.sukazyo.cono.morny.core.bot.api.{EventEnv, EventListener}
import cc.sukazyo.cono.morny.randomize_somthing.OnQuestionMarkReply.isAllMessageMark import cc.sukazyo.cono.morny.randomize_somthing.OnQuestionMarkReply.isAllMessageMark
import cc.sukazyo.cono.morny.util.tgapi.TelegramExtensions.Bot.exec import cc.sukazyo.cono.morny.util.tgapi.TelegramExtensions.Requests.unsafeExecute
import com.pengrad.telegrambot.request.SendMessage import com.pengrad.telegrambot.request.SendMessage
import com.pengrad.telegrambot.TelegramBot
import scala.language.postfixOps
import scala.util.boundary import scala.util.boundary
import scala.util.boundary.break
class OnQuestionMarkReply (using coeur: MornyCoeur) extends EventListener { class OnQuestionMarkReply (using coeur: MornyCoeur) extends EventListener {
private given TelegramBot = coeur.account
override def onMessage (using event: EventEnv): Unit = { override def onMessage (using event: EventEnv): Unit = {
import event.update import event.update
@ -21,9 +23,10 @@ class OnQuestionMarkReply (using coeur: MornyCoeur) extends EventListener {
if (1 over 8) chance_is false then return; if (1 over 8) chance_is false then return;
if !isAllMessageMark(using update.message.text) then return; if !isAllMessageMark(using update.message.text) then return;
coeur.account exec SendMessage( SendMessage(
update.message.chat.id, update.message.text update.message.chat.id, update.message.text
).replyToMessageId(update.message.messageId) ).replyToMessageId(update.message.messageId)
.unsafeExecute
event.setEventOk event.setEventOk
} }
@ -40,7 +43,7 @@ object OnQuestionMarkReply {
boundary[Boolean] { boundary[Boolean] {
for (c <- text) for (c <- text)
if !(QUESTION_MARKS contains c) then if !(QUESTION_MARKS contains c) then
boundary break false break(false)
true true
} }
} }

View File

@ -2,13 +2,14 @@ package cc.sukazyo.cono.morny.randomize_somthing
import cc.sukazyo.cono.morny.core.MornyCoeur import cc.sukazyo.cono.morny.core.MornyCoeur
import cc.sukazyo.cono.morny.core.bot.api.{EventEnv, EventListener} import cc.sukazyo.cono.morny.core.bot.api.{EventEnv, EventListener}
import cc.sukazyo.cono.morny.util.tgapi.TelegramExtensions.Bot.exec import cc.sukazyo.cono.morny.util.tgapi.TelegramExtensions.Requests.unsafeExecute
import com.pengrad.telegrambot.request.SendMessage import com.pengrad.telegrambot.request.SendMessage
import com.pengrad.telegrambot.response.SendResponse import com.pengrad.telegrambot.TelegramBot
import scala.language.postfixOps import scala.language.postfixOps
class OnUserRandom (using coeur: MornyCoeur) { class OnUserRandom (using coeur: MornyCoeur) {
private given TelegramBot = coeur.account
object RandomSelect extends EventListener { object RandomSelect extends EventListener {
@ -19,10 +20,10 @@ class OnUserRandom (using coeur: MornyCoeur) {
import event.update import event.update
if update.message.text == null then return; if update.message.text == null then return;
if !(update.message.text startsWith "/") then return if !(update.message.text `startsWith` "/") then return
import cc.sukazyo.cono.morny.util.UseRandom.rand_half import cc.sukazyo.cono.morny.util.UseRandom.rand_half
val query = update.message.text substring 1 val query = update.message.text `substring` 1
val result: String | Null = query match val result: String | Null = query match
case USER_OR_QUERY(_con1, _con2) => case USER_OR_QUERY(_con1, _con2) =>
if rand_half then _con1 else _con2 if rand_half then _con1 else _con2
@ -34,10 +35,11 @@ class OnUserRandom (using coeur: MornyCoeur) {
if result == null then return; if result == null then return;
coeur.account exec SendMessage( SendMessage(
update.message.chat.id, update.message.chat.id,
result result
).replyToMessageId(update.message.messageId) ).replyToMessageId(update.message.messageId)
.unsafeExecute
event.setEventOk event.setEventOk
} }
@ -58,14 +60,15 @@ class OnUserRandom (using coeur: MornyCoeur) {
var result: String|Null = null var result: String|Null = null
import cc.sukazyo.cono.morny.util.UseRandom.rand_half import cc.sukazyo.cono.morny.util.UseRandom.rand_half
for (k <- keywords) for (k <- keywords)
if update.message.text endsWith k then if update.message.text `endsWith` k then
result = if rand_half then "尊嘟" else "假嘟" result = if rand_half then "尊嘟" else "假嘟"
if result == null then return; if result == null then return
coeur.account exec SendMessage( SendMessage(
update.message.chat.id, update.message.chat.id,
result result
).replyToMessageId(update.message.messageId) ).replyToMessageId(update.message.messageId)
.unsafeExecute
event.setEventOk event.setEventOk
} }

View File

@ -27,10 +27,10 @@ class Module extends MornyInternalModule {
import cxt.* import cxt.*
externalContext >> { (instance: MornyReport) => externalContext >> { (instance: MornyReport) =>
logger info "MornyReport will now collect your bot event statistics." logger `info` "MornyReport will now collect your bot event statistics."
eventManager register instance.EventStatistics.EventInfoCatcher eventManager register instance.EventStatistics.EventInfoCatcher
} || { } || {
logger warn "There seems no reporter instance is provided; skipped register events for it." logger `warn` "There seems no reporter instance is provided; skipped register events for it."
} }
} }
@ -40,7 +40,7 @@ class Module extends MornyInternalModule {
externalContext >> { (instance: MornyReport) => externalContext >> { (instance: MornyReport) =>
instance.start() instance.start()
} || { } || {
logger warn "There seems no reporter instance is provided; skipped start it." logger `warn` "There seems no reporter instance is provided; skipped start it."
} }
} }
@ -56,7 +56,7 @@ class Module extends MornyInternalModule {
externalContext >> { (instance: MornyReport) => externalContext >> { (instance: MornyReport) =>
instance.stop() instance.stop()
} || { } || {
logger warn "There seems no reporter instance need to be stop." logger `warn` "There seems no reporter instance need to be stop."
} }
} }

View File

@ -1,19 +1,20 @@
package cc.sukazyo.cono.morny.reporter package cc.sukazyo.cono.morny.reporter
import cc.sukazyo.cono.morny.core.{MornyCoeur, MornyConfig} import cc.sukazyo.cono.morny.core.{MornyCoeur, MornyConfig}
import cc.sukazyo.cono.morny.core.Log.{exceptionLog, logger} import cc.sukazyo.cono.morny.core.Log.logger
import cc.sukazyo.cono.morny.core.bot.api.{EventEnv, EventListener} import cc.sukazyo.cono.morny.core.bot.api.{EventEnv, EventListener}
import cc.sukazyo.cono.morny.data.MornyInformation.getVersionAllFullTagHTML import cc.sukazyo.cono.morny.data.MornyInformation.getVersionAllFullTagHTML
import cc.sukazyo.cono.morny.util.statistics.{NumericStatistics, UniqueCounter} import cc.sukazyo.cono.morny.util.statistics.{NumericStatistics, UniqueCounter}
import cc.sukazyo.cono.morny.util.tgapi.event.EventRuntimeException import cc.sukazyo.cono.morny.util.tgapi.event.EventRuntimeException
import cc.sukazyo.cono.morny.util.tgapi.formatting.TelegramFormatter.* 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.tgapi.formatting.TelegramParseEscape.escapeHtml as h
import cc.sukazyo.cono.morny.util.tgapi.TelegramExtensions.Bot.exec import cc.sukazyo.cono.morny.util.tgapi.TelegramExtensions.Requests.unsafeExecute
import cc.sukazyo.cono.morny.util.EpochDateTime.DurationMillis import cc.sukazyo.cono.morny.util.EpochDateTime.DurationMillis
import cc.sukazyo.cono.morny.util.schedule.CronTask import cc.sukazyo.cono.morny.util.schedule.CronTask
import cc.sukazyo.cono.morny.util.tgapi.TelegramExtensions.Update.{sourceChat, sourceUser} import cc.sukazyo.cono.morny.util.tgapi.TelegramExtensions.Update.{sourceChat, sourceUser}
import cc.sukazyo.cono.morny.util.CommonEncrypt.hashId import cc.sukazyo.cono.morny.util.CommonEncrypt.hashId
import cc.sukazyo.cono.morny.util.ConvertByteHex.toHex import cc.sukazyo.cono.morny.util.ConvertByteHex.toHex
import cc.sukazyo.cono.morny.util.UseThrowable.toLogString
import com.cronutils.builder.CronBuilder import com.cronutils.builder.CronBuilder
import com.cronutils.model.Cron import com.cronutils.model.Cron
import com.cronutils.model.definition.CronDefinitionBuilder import com.cronutils.model.definition.CronDefinitionBuilder
@ -30,25 +31,25 @@ class MornyReport (using coeur: MornyCoeur) {
private val enabled = coeur.config.reportToChat != -1 private val enabled = coeur.config.reportToChat != -1
if !enabled then if !enabled then
logger info "Morny Report is disabled : report chat is set to -1" logger `info` "Morny Report is disabled : report chat is set to -1"
private def executeReport[T <: BaseRequest[T, R], R<: BaseResponse] (report: T): Unit = { private def executeReport[T <: BaseRequest[T, R], R<: BaseResponse] (report: T): Unit = {
if !enabled then return if !enabled then return
try { try {
coeur.account exec report report.unsafeExecute(using coeur.account)
} catch case e: EventRuntimeException => { } catch case e: EventRuntimeException => {
import EventRuntimeException.* import EventRuntimeException.*
e match e match
case e: ActionFailed => case e: ActionFailed =>
logger warn logger `warn`
s"""cannot execute report to telegram: s"""cannot execute report to telegram:
|${exceptionLog(e) indent 4} |${e.toLogString `indent` 4}
| tg-api response: | tg-api response:
|${(e.response toString) indent 4}""".stripMargin |${(e.response toString) `indent` 4}""".stripMargin
case e: ClientFailed => case e: ClientFailed =>
logger error logger `error`
s"""failed when report to telegram: s"""failed when report to telegram:
|${exceptionLog(e.getCause) indent 4} |${e.getCause.toLogString `indent` 4}
|""".stripMargin |""".stripMargin
} }
} }
@ -69,7 +70,7 @@ class MornyReport (using coeur: MornyCoeur) {
// language=html // language=html
s"""<b>▌Coeur Unexpected Exception </b> s"""<b>▌Coeur Unexpected Exception </b>
|${if description ne null then h(description)+"\n" else ""} |${if description ne null then h(description)+"\n" else ""}
|<pre><code class="language-log">${h(exceptionLog(e))}</code></pre>$_tgErrFormat""" |<pre><code class="language-log">${h(e.toLogString)}</code></pre>$_tgErrFormat"""
.stripMargin .stripMargin
).parseMode(ParseMode HTML)) ).parseMode(ParseMode HTML))
} }
@ -120,9 +121,9 @@ class MornyReport (using coeur: MornyCoeur) {
case e: (IllegalAccessException|IllegalArgumentException|NullPointerException) => case e: (IllegalAccessException|IllegalArgumentException|NullPointerException) =>
// language=html // language=html
echo ++= s": <i>${h("<read-error>")}</i>" echo ++= s": <i>${h("<read-error>")}</i>"
logger error logger `error`
s"""error while reading config field ${field.getName} s"""error while reading config field ${field.getName}
|${exceptionLog(e)}""".stripMargin |${e.toLogString}""".stripMargin
exception(e, s"error while reading config field ${field.getName}") exception(e, s"error while reading config field ${field.getName}")
echo ++= "\n" echo ++= "\n"
} }
@ -224,14 +225,14 @@ class MornyReport (using coeur: MornyCoeur) {
case State.OK(from) => case State.OK(from) =>
val timeUsed = EventTimeUsed(System.currentTimeMillis - event.timeStartup) val timeUsed = EventTimeUsed(System.currentTimeMillis - event.timeStartup)
givenCxt << timeUsed givenCxt << timeUsed
logger debug logger `debug`
s"""event done with OK s"""event done with OK
| with time consumed ${timeUsed.it}ms | with time consumed ${timeUsed.it}ms
| by $from""".stripMargin | by $from""".stripMargin
runningTime ++ timeUsed.it runningTime ++ timeUsed.it
case State.CANCELED(from) => case State.CANCELED(from) =>
eventCanceled += 1 eventCanceled += 1
logger debug logger `debug`
s"""event done with CANCELED" s"""event done with CANCELED"
| by $from""".stripMargin | by $from""".stripMargin
case null => case null =>

View File

@ -5,14 +5,15 @@ import cc.sukazyo.cono.morny.core.bot.api.{EventEnv, EventListener}
import cc.sukazyo.cono.morny.util.tgapi.formatting.TelegramFormatter.* 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.tgapi.formatting.TelegramParseEscape.escapeHtml as h
import cc.sukazyo.cono.morny.util.UniversalCommand import cc.sukazyo.cono.morny.util.UniversalCommand
import cc.sukazyo.cono.morny.util.tgapi.TelegramExtensions.Bot.exec import cc.sukazyo.cono.morny.util.tgapi.TelegramExtensions.Requests.unsafeExecute
import com.pengrad.telegrambot.model.Update
import com.pengrad.telegrambot.model.request.ParseMode import com.pengrad.telegrambot.model.request.ParseMode
import com.pengrad.telegrambot.request.SendMessage import com.pengrad.telegrambot.request.SendMessage
import com.pengrad.telegrambot.TelegramBot
import scala.language.postfixOps import scala.language.postfixOps
class OnUserSlashAction (using coeur: MornyCoeur) extends EventListener { class OnUserSlashAction (using coeur: MornyCoeur) extends EventListener {
private given TelegramBot = coeur.account
private val TG_FORMAT = "^\\w+(@\\w+)?$"r private val TG_FORMAT = "^\\w+(@\\w+)?$"r
@ -22,7 +23,7 @@ class OnUserSlashAction (using coeur: MornyCoeur) extends EventListener {
val text = update.message.text val text = update.message.text
if text == null then return; if text == null then return;
if (text startsWith "/") { if (text `startsWith` "/") {
// there has to be some special conditions for DP7 // there has to be some special conditions for DP7
// due to I have left DP7, I closed those special // due to I have left DP7, I closed those special
@ -33,7 +34,7 @@ class OnUserSlashAction (using coeur: MornyCoeur) extends EventListener {
// these message, here to remember the old DP7. // these message, here to remember the old DP7.
val actions = UniversalCommand.Lossy(text) val actions = UniversalCommand.Lossy(text)
actions(0) = actions(0) substring 1 actions(0) = actions(0) `substring` 1
actions(0) actions(0)
@ -42,7 +43,7 @@ class OnUserSlashAction (using coeur: MornyCoeur) extends EventListener {
case TG_FORMAT(_) => case TG_FORMAT(_) =>
return; return;
// ignore Path link // ignore Path link
case x if x contains "/" => return; case x if x `contains` "/" => return;
case _ => case _ =>
val isHardParse = actions(0) isBlank val isHardParse = actions(0) isBlank
@ -52,7 +53,7 @@ class OnUserSlashAction (using coeur: MornyCoeur) extends EventListener {
val hasObject = actions.length != hp_len(1) val hasObject = actions.length != hp_len(1)
val v_object = val v_object =
if hasObject then if hasObject then
actions slice(hp_len(1), actions.length) mkString " " actions `slice` (hp_len(1), actions.length) `mkString` " "
else "" else ""
val origin = update.message val origin = update.message
val target = val target =
@ -60,7 +61,7 @@ class OnUserSlashAction (using coeur: MornyCoeur) extends EventListener {
origin origin
else update.message.replyToMessage else update.message.replyToMessage
coeur.account exec SendMessage( SendMessage(
update.message.chat.id, update.message.chat.id,
"%s %s%s %s %s!".format( "%s %s%s %s %s!".format(
origin.sender_firstnameRefHTML, origin.sender_firstnameRefHTML,
@ -71,6 +72,7 @@ class OnUserSlashAction (using coeur: MornyCoeur) extends EventListener {
if hasObject then h(v_object+" ") else "" if hasObject then h(v_object+" ") else ""
) )
).parseMode(ParseMode HTML).replyToMessageId(update.message.messageId) ).parseMode(ParseMode HTML).replyToMessageId(update.message.messageId)
.unsafeExecute
event.setEventOk event.setEventOk
} }

View File

@ -25,4 +25,3 @@ class ModuleSocialShare extends MornyInternalModule {
} }
} }

View File

@ -4,10 +4,11 @@ import cc.sukazyo.cono.morny.core.MornyCoeur
import cc.sukazyo.cono.morny.core.bot.api.InlineQueryUnit import cc.sukazyo.cono.morny.core.bot.api.InlineQueryUnit
import cc.sukazyo.cono.morny.social_share.api.SocialContent.{SocialMedia, SocialMediaType, SocialMediaWithUrl} import cc.sukazyo.cono.morny.social_share.api.SocialContent.{SocialMedia, SocialMediaType, SocialMediaWithUrl}
import cc.sukazyo.cono.morny.social_share.api.SocialContent.SocialMediaType.{Photo, Video} import cc.sukazyo.cono.morny.social_share.api.SocialContent.SocialMediaType.{Photo, Video}
import cc.sukazyo.cono.morny.util.tgapi.TelegramExtensions.Bot.exec import cc.sukazyo.cono.morny.util.tgapi.TelegramExtensions.Requests.unsafeExecute
import cc.sukazyo.cono.morny.util.tgapi.formatting.NamingUtils.inlineQueryId import cc.sukazyo.cono.morny.util.tgapi.formatting.NamingUtils.inlineQueryId
import com.pengrad.telegrambot.model.request.* import com.pengrad.telegrambot.model.request.*
import com.pengrad.telegrambot.request.{SendMediaGroup, SendMessage} import com.pengrad.telegrambot.request.{SendMediaGroup, SendMessage}
import com.pengrad.telegrambot.TelegramBot
/** Model of social networks' status. for example twitter tweet or /** Model of social networks' status. for example twitter tweet or
* weibo status. * weibo status.
@ -44,18 +45,19 @@ case class SocialContent (
case _ => orElse case _ => orElse
def outputToTelegram (using replyChat: Long, replyToMessage: Int)(using coeur: MornyCoeur): Unit = { def outputToTelegram (using replyChat: Long, replyToMessage: Int)(using coeur: MornyCoeur): Unit = {
given TelegramBot = coeur.account
if medias isEmpty then if medias isEmpty then
coeur.account exec
SendMessage(replyChat, text_html) SendMessage(replyChat, text_html)
.parseMode(ParseMode.HTML) .parseMode(ParseMode.HTML)
.replyToMessageId(replyToMessage) .replyToMessageId(replyToMessage)
.unsafeExecute
else else
val mediaGroup = medias.map(f => f.genTelegramInputMedia) val mediaGroup = medias.map(f => f.genTelegramInputMedia)
mediaGroup.head.caption(text_html) mediaGroup.head.caption(text_html)
mediaGroup.head.parseMode(ParseMode.HTML) mediaGroup.head.parseMode(ParseMode.HTML)
coeur.account exec SendMediaGroup(replyChat, mediaGroup*)
SendMediaGroup(replyChat, mediaGroup: _*)
.replyToMessageId(replyToMessage) .replyToMessageId(replyToMessage)
.unsafeExecute
} }
def genInlineQueryResults (using id_head: String, id_param: Any, name: String): List[InlineQueryUnit[?]] = { def genInlineQueryResults (using id_head: String, id_param: Any, name: String): List[InlineQueryUnit[?]] = {
@ -99,13 +101,13 @@ object SocialContent {
def genTelegramInputMedia: InputMedia[?] def genTelegramInputMedia: InputMedia[?]
} }
case class SocialMediaWithUrl (url: String)(t: SocialMediaType) extends SocialMedia(t) { case class SocialMediaWithUrl (url: String)(t: SocialMediaType) extends SocialMedia(t) {
override def genTelegramInputMedia: InputMedia[_] = override def genTelegramInputMedia: InputMedia[?] =
t match t match
case Photo => InputMediaPhoto(url) case Photo => InputMediaPhoto(url)
case Video => InputMediaVideo(url) case Video => InputMediaVideo(url)
} }
case class SocialMediaWithBytesData (data: Array[Byte])(t: SocialMediaType) extends SocialMedia(t) { case class SocialMediaWithBytesData (data: Array[Byte])(t: SocialMediaType) extends SocialMedia(t) {
override def genTelegramInputMedia: InputMedia[_] = override def genTelegramInputMedia: InputMedia[?] =
t match t match
case Photo => InputMediaPhoto(data) case Photo => InputMediaPhoto(data)
case Video => InputMediaVideo(data) case Video => InputMediaVideo(data)

View File

@ -5,11 +5,13 @@ import cc.sukazyo.cono.morny.core.bot.api.{ICommandAlias, ITelegramCommand}
import cc.sukazyo.cono.morny.data.TelegramStickers import cc.sukazyo.cono.morny.data.TelegramStickers
import cc.sukazyo.cono.morny.social_share.event.OnGetSocial import cc.sukazyo.cono.morny.social_share.event.OnGetSocial
import cc.sukazyo.cono.morny.util.tgapi.InputCommand import cc.sukazyo.cono.morny.util.tgapi.InputCommand
import cc.sukazyo.cono.morny.util.tgapi.TelegramExtensions.Bot.exec import cc.sukazyo.cono.morny.util.tgapi.TelegramExtensions.Requests.unsafeExecute
import com.pengrad.telegrambot.model.Update import com.pengrad.telegrambot.model.Update
import com.pengrad.telegrambot.request.SendSticker import com.pengrad.telegrambot.request.SendSticker
import com.pengrad.telegrambot.TelegramBot
class GetSocial (using coeur: MornyCoeur) extends ITelegramCommand { class GetSocial (using coeur: MornyCoeur) extends ITelegramCommand {
private given TelegramBot = coeur.account
override val name: String = "get" override val name: String = "get"
override val aliases: List[ICommandAlias] = Nil override val aliases: List[ICommandAlias] = Nil
@ -19,10 +21,11 @@ class GetSocial (using coeur: MornyCoeur) extends ITelegramCommand {
override def execute (using command: InputCommand, event: Update): Unit = { override def execute (using command: InputCommand, event: Update): Unit = {
def do404 (): Unit = def do404 (): Unit =
coeur.account exec SendSticker( SendSticker(
event.message.chat.id, event.message.chat.id,
TelegramStickers.ID_404 TelegramStickers.ID_404
).replyToMessageId(event.message.messageId()) ).replyToMessageId(event.message.messageId())
.unsafeExecute
val content = val content =
if command.args.length > 0 then if command.args.length > 0 then

View File

@ -1,6 +1,6 @@
package cc.sukazyo.cono.morny.social_share.event package cc.sukazyo.cono.morny.social_share.event
import cc.sukazyo.cono.morny.core.Log.{exceptionLog, logger} import cc.sukazyo.cono.morny.core.Log.logger
import cc.sukazyo.cono.morny.core.MornyCoeur import cc.sukazyo.cono.morny.core.MornyCoeur
import cc.sukazyo.cono.morny.core.bot.api.{EventEnv, EventListener} import cc.sukazyo.cono.morny.core.bot.api.{EventEnv, EventListener}
import cc.sukazyo.cono.morny.data.TelegramStickers import cc.sukazyo.cono.morny.data.TelegramStickers
@ -8,11 +8,13 @@ import cc.sukazyo.cono.morny.reporter.MornyReport
import cc.sukazyo.cono.morny.social_share.api.{SocialTwitterParser, SocialWeiboParser} import cc.sukazyo.cono.morny.social_share.api.{SocialTwitterParser, SocialWeiboParser}
import cc.sukazyo.cono.morny.social_share.event.OnGetSocial.tryFetchSocial import cc.sukazyo.cono.morny.social_share.event.OnGetSocial.tryFetchSocial
import cc.sukazyo.cono.morny.social_share.external.{twitter, weibo} import cc.sukazyo.cono.morny.social_share.external.{twitter, weibo}
import cc.sukazyo.cono.morny.util.tgapi.TelegramExtensions.Bot.exec
import cc.sukazyo.cono.morny.util.tgapi.TelegramExtensions.Message.textWithUrls import cc.sukazyo.cono.morny.util.tgapi.TelegramExtensions.Message.textWithUrls
import cc.sukazyo.cono.morny.util.tgapi.TelegramExtensions.Requests.unsafeExecute
import cc.sukazyo.cono.morny.util.UseThrowable.toLogString
import com.pengrad.telegrambot.model.Chat import com.pengrad.telegrambot.model.Chat
import com.pengrad.telegrambot.model.request.ParseMode import com.pengrad.telegrambot.model.request.ParseMode
import com.pengrad.telegrambot.request.{SendMessage, SendSticker} import com.pengrad.telegrambot.request.{SendMessage, SendSticker}
import com.pengrad.telegrambot.TelegramBot
class OnGetSocial (using coeur: MornyCoeur) extends EventListener { class OnGetSocial (using coeur: MornyCoeur) extends EventListener {
@ -80,36 +82,39 @@ object OnGetSocial {
val api = FXApi.Fetch.status(Some(url.screenName), url.statusId) val api = FXApi.Fetch.status(Some(url.screenName), url.statusId)
SocialTwitterParser.parseFXTweet(api).outputToTelegram SocialTwitterParser.parseFXTweet(api).outputToTelegram
} catch case e: (SttpClientException | ParsingFailure | DecodingFailure) => } catch case e: (SttpClientException | ParsingFailure | DecodingFailure) =>
coeur.account exec SendSticker( SendSticker(
replyChat, replyChat,
TelegramStickers.ID_NETWORK_ERR TelegramStickers.ID_NETWORK_ERR
).replyToMessageId(replyToMessage) ).replyToMessageId(replyToMessage).unsafeExecute(using coeur.account)
logger error logger `error`
"Error on requesting FixTweet API\n" + exceptionLog(e) "Error on requesting FixTweet API\n" + e.toLogString
coeur.externalContext.consume[MornyReport](_.exception(e, "Error on requesting FixTweet API")) coeur.externalContext.consume[MornyReport](_.exception(e, "Error on requesting FixTweet API"))
def tryFetchSocialOfWeibo (url: weibo.StatusUrlInfo)(using replyChat: Long, replyToMessage: Int)(using coeur: MornyCoeur): Unit = def tryFetchSocialOfWeibo (url: weibo.StatusUrlInfo)(using replyChat: Long, replyToMessage: Int)(using coeur: MornyCoeur): Unit =
import cc.sukazyo.cono.morny.social_share.external.weibo.MApi import cc.sukazyo.cono.morny.social_share.external.weibo.MApi
import io.circe.{DecodingFailure, ParsingFailure} import io.circe.{DecodingFailure, ParsingFailure}
import sttp.client3.{HttpError, SttpClientException} import sttp.client3.{HttpError, SttpClientException}
given TelegramBot = coeur.account
try { try {
val api = MApi.Fetch.statuses_show(url.id) val api = MApi.Fetch.statuses_show(url.id)
SocialWeiboParser.parseMStatus(api).outputToTelegram SocialWeiboParser.parseMStatus(api).outputToTelegram
} catch } catch
case e: HttpError[?] => case e: HttpError[?] =>
coeur.account exec SendMessage( SendMessage(
replyChat, replyChat,
// language=html // language=html
s"""Weibo Request Error <code>${e.statusCode}</code> s"""Weibo Request Error <code>${e.statusCode}</code>
|<pre><code>${e.body}</code></pre>""".stripMargin |<pre><code>${e.body}</code></pre>""".stripMargin
).replyToMessageId(replyToMessage).parseMode(ParseMode.HTML) ).replyToMessageId(replyToMessage).parseMode(ParseMode.HTML)
.unsafeExecute
case e: (SttpClientException | ParsingFailure | DecodingFailure) => case e: (SttpClientException | ParsingFailure | DecodingFailure) =>
coeur.account exec SendSticker( SendSticker(
replyChat, replyChat,
TelegramStickers.ID_NETWORK_ERR TelegramStickers.ID_NETWORK_ERR
).replyToMessageId(replyToMessage) ).replyToMessageId(replyToMessage)
logger error .unsafeExecute
"Error on requesting Weibo m.API\n" + exceptionLog(e) logger `error`
"Error on requesting Weibo m.API\n" + e.toLogString
coeur.externalContext.consume[MornyReport](_.exception(e, "Error on requesting Weibo m.API")) coeur.externalContext.consume[MornyReport](_.exception(e, "Error on requesting Weibo m.API"))
} }

View File

@ -108,12 +108,12 @@ object BiliTool {
*/ */
def toBv (av: Long): String = { def toBv (av: Long): String = {
val _av = (av^V_CONV_XOR)+V_CONV_ADD val _av = (av^V_CONV_XOR)+V_CONV_ADD
val bv = Array(BV_TEMPLATE:_*) val bv = Array(BV_TEMPLATE*)
for (i <- BV_TEMPLATE_FILTER.indices) { for (i <- BV_TEMPLATE_FILTER.indices) {
import Math.{floor, pow} import Math.{floor, pow}
bv(BV_TEMPLATE_FILTER(i)) = BV_TABLE( (floor(_av/(TABLE_INT**i)) % TABLE_INT) toInt ) bv(BV_TEMPLATE_FILTER(i)) = BV_TABLE( (floor(_av/(TABLE_INT**i)) % TABLE_INT) toInt )
} }
String copyValueOf bv String `copyValueOf` bv
} }
} }

View File

@ -72,7 +72,7 @@ object BilibiliForms {
IllegalArgumentException(s"not a b23.tv url: $uri") IllegalArgumentException(s"not a b23.tv url: $uri")
else if uri.pathSegments.segments.size < 1 then else if uri.pathSegments.segments.size < 1 then
throw IllegalArgumentException(s"empty b23.tv url: $uri") throw IllegalArgumentException(s"empty b23.tv url: $uri")
else if uri.pathSegments.segments.head.v matches REGEX_BILI_ID.regex then else if uri.pathSegments.segments.head.v `matches` REGEX_BILI_ID.regex then
throw IllegalArgumentException(s"is a b23 video link: $uri . (use parse_videoUrl instead)") throw IllegalArgumentException(s"is a b23 video link: $uri . (use parse_videoUrl instead)")
try { try {

View File

@ -33,7 +33,7 @@ object MApi {
private val httpClient = OkHttpSyncBackend() private val httpClient = OkHttpSyncBackend()
@throws[HttpError[_]|SttpClientException|ParsingFailure|DecodingFailure] @throws[HttpError[?]|SttpClientException|ParsingFailure|DecodingFailure]
def statuses_show (id: String): MApi[MStatus] = def statuses_show (id: String): MApi[MStatus] =
import sttp.client3.asString import sttp.client3.asString
import MApi.CirceADTs.given import MApi.CirceADTs.given
@ -46,7 +46,7 @@ object MApi {
.as[MApi[MStatus]] .as[MApi[MStatus]]
.toTry.get .toTry.get
@throws[HttpError[_] | SttpClientException | ParsingFailure | DecodingFailure] @throws[HttpError[?] | SttpClientException | ParsingFailure | DecodingFailure]
def pic (picUrl: String): Array[Byte] = def pic (picUrl: String): Array[Byte] =
import sttp.client3.* import sttp.client3.*
import sttp.model.{MediaType, Uri} import sttp.model.{MediaType, Uri}

View File

@ -1,10 +1,11 @@
package cc.sukazyo.cono.morny.social_share.query package cc.sukazyo.cono.morny.social_share.query
import cc.sukazyo.cono.morny.core.Log.{exceptionLog, logger} import cc.sukazyo.cono.morny.core.Log.logger
import cc.sukazyo.cono.morny.core.MornyCoeur import cc.sukazyo.cono.morny.core.MornyCoeur
import cc.sukazyo.cono.morny.core.bot.api.{InlineQueryUnit, ITelegramQuery} import cc.sukazyo.cono.morny.core.bot.api.{InlineQueryUnit, ITelegramQuery}
import cc.sukazyo.cono.morny.reporter.MornyReport import cc.sukazyo.cono.morny.reporter.MornyReport
import cc.sukazyo.cono.morny.util.tgapi.formatting.NamingUtils.inlineQueryId import cc.sukazyo.cono.morny.util.tgapi.formatting.NamingUtils.inlineQueryId
import cc.sukazyo.cono.morny.util.UseThrowable.toLogString
import com.pengrad.telegrambot.model.Update import com.pengrad.telegrambot.model.Update
import com.pengrad.telegrambot.model.request.{InlineQueryResultArticle, InputTextMessageContent, ParseMode} import com.pengrad.telegrambot.model.request.{InlineQueryResultArticle, InputTextMessageContent, ParseMode}
@ -20,7 +21,7 @@ class ShareToolBilibili (using coeur: MornyCoeur) extends ITelegramQuery {
private val LINK_PREFIX = "https://bilibili.com/video/" private val LINK_PREFIX = "https://bilibili.com/video/"
private val SHARE_FORMAT_HTML = "<a href='%s'>%s</a>" private val SHARE_FORMAT_HTML = "<a href='%s'>%s</a>"
override def query (event: Update): List[InlineQueryUnit[_]] | Null = { override def query (event: Update): List[InlineQueryUnit[?]] | Null = {
if (event.inlineQuery.query == null) return null if (event.inlineQuery.query == null) return null
if (event.inlineQuery.query isBlank) return null if (event.inlineQuery.query isBlank) return null
@ -36,7 +37,7 @@ class ShareToolBilibili (using coeur: MornyCoeur) extends ITelegramQuery {
case _: IllegalArgumentException => case _: IllegalArgumentException =>
return null; return null;
case e: IllegalStateException => case e: IllegalStateException =>
logger error exceptionLog(e) logger `error` e.toLogString
coeur.externalContext.consume[MornyReport](_.exception(e)) coeur.externalContext.consume[MornyReport](_.exception(e))
return null; return null;

View File

@ -9,14 +9,14 @@ import com.pengrad.telegrambot.model.Update
class ShareToolSocialContent extends ITelegramQuery { class ShareToolSocialContent extends ITelegramQuery {
override def query (event: Update): List[InlineQueryUnit[_]] | Null = { override def query (event: Update): List[InlineQueryUnit[?]] | Null = {
val _queryRaw = event.inlineQuery.query val _queryRaw = event.inlineQuery.query
val query = val query =
_queryRaw.trim match _queryRaw.trim match
case _startsWithTag if _startsWithTag startsWith "get " => case _startsWithTag if _startsWithTag `startsWith` "get " =>
(_startsWithTag drop 4)trim (_startsWithTag drop 4)trim
case _endsWithTag if _endsWithTag endsWith " get" => case _endsWithTag if _endsWithTag `endsWith` " get" =>
(_endsWithTag dropRight 4)trim (_endsWithTag dropRight 4)trim
case _ => return null case _ => return null

View File

@ -8,7 +8,6 @@ import com.pengrad.telegrambot.model.Update
import com.pengrad.telegrambot.model.request.InlineQueryResultArticle import com.pengrad.telegrambot.model.request.InlineQueryResultArticle
import scala.language.postfixOps import scala.language.postfixOps
import scala.util.matching.Regex
class ShareToolTwitter extends ITelegramQuery { class ShareToolTwitter extends ITelegramQuery {
@ -17,7 +16,7 @@ class ShareToolTwitter extends ITelegramQuery {
private val TITLE_FX = "[tweet] Share as Fix-Tweet" private val TITLE_FX = "[tweet] Share as Fix-Tweet"
private val ID_PREFIX_FX = "[morny/share/twitter/fxtwi]" private val ID_PREFIX_FX = "[morny/share/twitter/fxtwi]"
override def query (event: Update): List[InlineQueryUnit[_]] | Null = { override def query (event: Update): List[InlineQueryUnit[?]] | Null = {
if (event.inlineQuery.query == null) return null if (event.inlineQuery.query == null) return null

View File

@ -4,7 +4,10 @@ import cats.effect.IO
import cc.sukazyo.cono.morny.core.http.api.HttpService4Api import cc.sukazyo.cono.morny.core.http.api.HttpService4Api
import cc.sukazyo.cono.morny.core.MornyCoeur import cc.sukazyo.cono.morny.core.MornyCoeur
import cc.sukazyo.cono.morny.data.TelegramImages import cc.sukazyo.cono.morny.data.TelegramImages
import cc.sukazyo.cono.morny.util.tgapi.TelegramExtensions.File.getContent
import cc.sukazyo.cono.morny.util.tgapi.TelegramExtensions.Requests.execute
import com.pengrad.telegrambot.request.GetFile import com.pengrad.telegrambot.request.GetFile
import com.pengrad.telegrambot.TelegramBot
import org.http4s.{HttpRoutes, MediaType} import org.http4s.{HttpRoutes, MediaType}
import org.http4s.dsl.io.* import org.http4s.dsl.io.*
import org.http4s.headers.`Content-Type` import org.http4s.headers.`Content-Type`
@ -17,10 +20,11 @@ class StickerService (using coeur: MornyCoeur) extends HttpService4Api {
case GET -> Root / "sticker" / "id" / id => case GET -> Root / "sticker" / "id" / id =>
try { try {
val response = coeur.account execute GetFile(id) val response = GetFile(id).execute(using coeur.account)
if response.isOk then if response.isOk then
try { try {
val file = coeur.account getFileContent response.file given TelegramBot = coeur.account
val file = response.file.getContent
Ok(file) Ok(file)
} catch { } catch {
case e: IOException => case e: IOException =>

View File

@ -12,7 +12,7 @@ class InlineRawText extends ITelegramQuery {
private val ID_PREFIX = "[morny/r/text]" private val ID_PREFIX = "[morny/r/text]"
private val TITLE = "Raw Text" private val TITLE = "Raw Text"
override def query (event: Update): List[InlineQueryUnit[_]] | Null = { override def query (event: Update): List[InlineQueryUnit[?]] | Null = {
if (event.inlineQuery.query == null || (event.inlineQuery.query isBlank)) return null if (event.inlineQuery.query == null || (event.inlineQuery.query isBlank)) return null

View File

@ -4,13 +4,15 @@ import cc.sukazyo.cono.morny.core.MornyCoeur
import cc.sukazyo.cono.morny.core.bot.api.{ICommandAlias, ITelegramCommand} import cc.sukazyo.cono.morny.core.bot.api.{ICommandAlias, ITelegramCommand}
import cc.sukazyo.cono.morny.data.TelegramStickers import cc.sukazyo.cono.morny.data.TelegramStickers
import cc.sukazyo.cono.morny.util.tgapi.InputCommand import cc.sukazyo.cono.morny.util.tgapi.InputCommand
import cc.sukazyo.cono.morny.util.tgapi.TelegramExtensions.Bot.exec import cc.sukazyo.cono.morny.util.tgapi.TelegramExtensions.Requests.unsafeExecute
import com.pengrad.telegrambot.model.Update import com.pengrad.telegrambot.model.Update
import com.pengrad.telegrambot.request.SendSticker import com.pengrad.telegrambot.request.SendSticker
import com.pengrad.telegrambot.TelegramBot
import scala.language.postfixOps import scala.language.postfixOps
class CommandEventHack (using hacker: EventHacker)(using coeur: MornyCoeur) extends ITelegramCommand { class CommandEventHack (using hacker: EventHacker)(using coeur: MornyCoeur) extends ITelegramCommand {
private given TelegramBot = coeur.account
override val name: String = "event_hack" override val name: String = "event_hack"
override val aliases: List[ICommandAlias] = Nil override val aliases: List[ICommandAlias] = Nil
@ -24,15 +26,17 @@ class CommandEventHack (using hacker: EventHacker)(using coeur: MornyCoeur) exte
val x_mode = if (command.args nonEmpty) command.args(0) else "" val x_mode = if (command.args nonEmpty) command.args(0) else ""
def done_ok = def done_ok =
coeur.account exec SendSticker( SendSticker(
event.message.chat.id, event.message.chat.id,
TelegramStickers ID_WAITING TelegramStickers ID_WAITING
).replyToMessageId(event.message.messageId) ).replyToMessageId(event.message.messageId)
.unsafeExecute
def done_forbiddenForAny = def done_forbiddenForAny =
coeur.account exec SendSticker( SendSticker(
event.message.chat.id, event.message.chat.id,
TelegramStickers ID_403 TelegramStickers ID_403
).replyToMessageId(event.message.messageId) ).replyToMessageId(event.message.messageId)
.unsafeExecute
def doRegister (t: HackType): Unit = def doRegister (t: HackType): Unit =
registerHack( registerHack(
@ -43,7 +47,7 @@ class CommandEventHack (using hacker: EventHacker)(using coeur: MornyCoeur) exte
) )
x_mode match x_mode match
case "any" => case "any" =>
if (coeur.trusted isTrusted event.message.from.id) if (coeur.trusted isTrust event.message.from)
doRegister(HackType ANY) doRegister(HackType ANY)
done_ok done_ok
else done_forbiddenForAny else done_forbiddenForAny

View File

@ -2,15 +2,17 @@ package cc.sukazyo.cono.morny.tele_utils.event_hack
import cc.sukazyo.cono.morny.core.Log.logger import cc.sukazyo.cono.morny.core.Log.logger
import cc.sukazyo.cono.morny.core.MornyCoeur import cc.sukazyo.cono.morny.core.MornyCoeur
import cc.sukazyo.cono.morny.util.tgapi.TelegramExtensions.Bot.exec import cc.sukazyo.cono.morny.util.tgapi.TelegramExtensions.Requests.unsafeExecute
import com.google.gson.GsonBuilder import com.google.gson.GsonBuilder
import com.pengrad.telegrambot.model.Update import com.pengrad.telegrambot.model.Update
import com.pengrad.telegrambot.model.request.ParseMode import com.pengrad.telegrambot.model.request.ParseMode
import com.pengrad.telegrambot.request.SendMessage import com.pengrad.telegrambot.request.SendMessage
import com.pengrad.telegrambot.TelegramBot
import scala.collection.mutable import scala.collection.mutable
class EventHacker (using coeur: MornyCoeur) { class EventHacker (using coeur: MornyCoeur) {
private given TelegramBot = coeur.account
private case class Hacker (from_chat: Long, from_message: Long): private case class Hacker (from_chat: Long, from_message: Long):
override def toString: String = s"$from_chat/$from_message" override def toString: String = s"$from_chat/$from_message"
@ -28,22 +30,23 @@ class EventHacker (using coeur: MornyCoeur) {
case HackType.GROUP => s"{{$from_chat}}" case HackType.GROUP => s"{{$from_chat}}"
case HackType.ANY => "[[]]" case HackType.ANY => "[[]]"
hackers += (record -> Hacker(from_chat, from_message)) hackers += (record -> Hacker(from_chat, from_message))
logger debug s"add hacker track $record" logger `debug` s"add hacker track $record"
def trigger (chat: Long, fromUser: Long)(using update: Update): Boolean = { def trigger (chat: Long, fromUser: Long)(using update: Update): Boolean = {
logger debug s"got event signed {{$chat}}(($fromUser))" logger `debug` s"got event signed {{$chat}}(($fromUser))"
val x: Hacker = val x: Hacker =
if hackers contains s"(($fromUser))" then (hackers remove s"(($fromUser))") get if hackers contains s"(($fromUser))" then (hackers remove s"(($fromUser))") get
else if hackers contains s"{{$chat}}" then (hackers remove s"{{$chat}}") get else if hackers contains s"{{$chat}}" then (hackers remove s"{{$chat}}") get
else if hackers contains "[[]]" then (hackers remove "[[]]") get else if hackers contains "[[]]" then (hackers remove "[[]]") get
else return false else return false
logger debug s"hacked event by $x" logger `debug` s"hacked event by $x"
import cc.sukazyo.cono.morny.util.tgapi.formatting.TelegramParseEscape.escapeHtml as h import cc.sukazyo.cono.morny.util.tgapi.formatting.TelegramParseEscape.escapeHtml as h
coeur.account exec SendMessage( SendMessage(
x.from_chat, x.from_chat,
// language=html // language=html
s"<pre><code class='language-json'>${h(GsonBuilder().setPrettyPrinting().create.toJson(update))}</code></pre>" s"<pre><code class='language-json'>${h(GsonBuilder().setPrettyPrinting().create.toJson(update))}</code></pre>"
).parseMode(ParseMode HTML).replyToMessageId(x.from_message toInt) ).parseMode(ParseMode HTML).replyToMessageId(x.from_message toInt)
.unsafeExecute
true true
} }

View File

@ -4,14 +4,14 @@ import cc.sukazyo.cono.morny.core.MornyCoeur
import cc.sukazyo.cono.morny.core.bot.api.{ICommandAlias, ITelegramCommand} import cc.sukazyo.cono.morny.core.bot.api.{ICommandAlias, ITelegramCommand}
import cc.sukazyo.cono.morny.util.tgapi.{InputCommand, Standardize} import cc.sukazyo.cono.morny.util.tgapi.{InputCommand, Standardize}
import cc.sukazyo.cono.morny.util.tgapi.formatting.TelegramUserInformation import cc.sukazyo.cono.morny.util.tgapi.formatting.TelegramUserInformation
import cc.sukazyo.cono.morny.util.tgapi.TelegramExtensions.Bot.exec import cc.sukazyo.cono.morny.util.tgapi.TelegramExtensions.Requests.{execute, unsafeExecute}
import com.pengrad.telegrambot.model.Update import com.pengrad.telegrambot.model.Update
import com.pengrad.telegrambot.model.request.ParseMode import com.pengrad.telegrambot.model.request.ParseMode
import com.pengrad.telegrambot.request.{GetChatMember, SendMessage} import com.pengrad.telegrambot.request.{GetChatMember, SendMessage}
import com.pengrad.telegrambot.TelegramBot
import scala.language.postfixOps
class CommandGetUser (using coeur: MornyCoeur) extends ITelegramCommand { class CommandGetUser (using coeur: MornyCoeur) extends ITelegramCommand {
private given TelegramBot = coeur.account
override val name: String = "user" override val name: String = "user"
override val aliases: List[ICommandAlias] = Nil override val aliases: List[ICommandAlias] = Nil
@ -23,46 +23,51 @@ class CommandGetUser (using coeur: MornyCoeur) extends ITelegramCommand {
val args = command.args val args = command.args
if (args.length > 1) if (args.length > 1)
coeur.account exec SendMessage( SendMessage(
event.message.chat.id, event.message.chat.id,
"[Unavailable] Too much arguments." "[Unavailable] Too much arguments."
).replyToMessageId(event.message.messageId) ).replyToMessageId(event.message.messageId)
.unsafeExecute
return return
val userId: Long = val userId: Long =
if (args nonEmpty) { if (args nonEmpty) {
try args(0) toLong try args(0) toLong
catch case e: NumberFormatException => catch case e: NumberFormatException =>
coeur.account exec SendMessage( SendMessage(
event.message.chat.id, event.message.chat.id,
s"[Unavailable] ${e.getMessage}" s"[Unavailable] ${e.getMessage}"
).replyToMessageId(event.message.messageId) ).replyToMessageId(event.message.messageId)
.unsafeExecute
return return
} else if (event.message.replyToMessage eq null) event.message.from.id } else if (event.message.replyToMessage eq null) event.message.from.id
else event.message.replyToMessage.from.id else event.message.replyToMessage.from.id
val response = coeur.account execute GetChatMember(event.message.chat.id, userId) val response = GetChatMember(event.message.chat.id, userId).execute
if (response.chatMember eq null) if (response.chatMember eq null)
coeur.account exec SendMessage( SendMessage(
event.message.chat.id, event.message.chat.id,
"[Unavailable] user not found." "[Unavailable] user not found."
).replyToMessageId(event.message.messageId) ).replyToMessageId(event.message.messageId)
.unsafeExecute
return return
val user = response.chatMember.user val user = response.chatMember.user
if (user.id == Standardize.CHANNEL_SPEAKER_MAGIC_ID) if (user.id == Standardize.CHANNEL_SPEAKER_MAGIC_ID)
coeur.account exec SendMessage( SendMessage(
event.message.chat.id, event.message.chat.id,
"<code>$__channel_identify</code>" "<code>$__channel_identify</code>"
).replyToMessageId(event.message.messageId) ).replyToMessageId(event.message.messageId)
.unsafeExecute
return; return;
coeur.account exec SendMessage( SendMessage(
event.message.chat.id, event.message.chat.id,
TelegramUserInformation getFormattedInformation user TelegramUserInformation.getFormattedInformation(user)
).replyToMessageId(event.message.messageId()).parseMode(ParseMode HTML) ).replyToMessageId(event.message.messageId()).parseMode(ParseMode HTML)
.unsafeExecute
} }

View File

@ -13,7 +13,7 @@ class InlineMyInformation extends ITelegramQuery {
private val ID_PREFIX = "[morny/info/me]" private val ID_PREFIX = "[morny/info/me]"
private val TITLE = "My Account Information" private val TITLE = "My Account Information"
override def query (event: Update): List[InlineQueryUnit[_]] | Null = { override def query (event: Update): List[InlineQueryUnit[?]] | Null = {
if !((event.inlineQuery.query eq null) || (event.inlineQuery.query isEmpty)) then return null if !((event.inlineQuery.query eq null) || (event.inlineQuery.query isEmpty)) then return null
@ -21,7 +21,7 @@ class InlineMyInformation extends ITelegramQuery {
InlineQueryUnit(InlineQueryResultArticle( InlineQueryUnit(InlineQueryResultArticle(
inlineQueryId(ID_PREFIX), TITLE, inlineQueryId(ID_PREFIX), TITLE,
new InputTextMessageContent( new InputTextMessageContent(
TelegramUserInformation getFormattedInformation event.inlineQuery.from TelegramUserInformation.getFormattedInformation(event.inlineQuery.from)
).parseMode(ParseMode HTML) ).parseMode(ParseMode HTML)
)).isPersonal(true).cacheTime(10) )).isPersonal(true).cacheTime(10)
) )

View File

@ -10,17 +10,17 @@ class BotEventUniMeowTrigger (using commands: UniMeowCommandManager) extends Eve
import event.* import event.*
givenCxt >> { (input: InputCommand) => givenCxt >> { (input: InputCommand) =>
logger trace s"got input command {$input} from event-context" logger `trace` s"got input command {$input} from event-context"
for ((name, command_instance) <- commands.commands) { for ((name, command_instance) <- commands.getCommands) {
logger trace s"checking uni-meow $name" logger `trace` s"checking uni-meow $name"
if (name == input.command) if (name == input.command)
logger trace "checked" logger `trace` "checked"
command_instance.execute(using input, event.update) command_instance.execute(using input, event.update)
event.setEventOk event.setEventOk
} }
} || { logger trace "not command (for uni-meow)" } } || { logger `trace` "not command (for uni-meow)" }
} }

View File

@ -1,17 +1,9 @@
package cc.sukazyo.cono.morny.uni_meow package cc.sukazyo.cono.morny.uni_meow
import cc.sukazyo.cono.morny.core.bot.api.ISimpleCommand import cc.sukazyo.cono.morny.core.bot.api.SimpleCommandManager
import cc.sukazyo.cono.morny.core.bot.api.MornyCommandManager.CommandMap
import scala.collection.mutable class UniMeowCommandManager extends SimpleCommandManager{
class UniMeowCommandManager { protected[uni_meow] def getCommands: this.commands.type = this.commands
private[uni_meow] val commands: CommandMap = mutable.SeqMap.empty
def register [T <: ISimpleCommand] (commands: T*): Unit =
for (i <- commands)
this.commands += (i.name -> i)
for (alias <- i.aliases)
this.commands += (alias.name -> i)
} }

View File

@ -3,12 +3,14 @@ package cc.sukazyo.cono.morny.uni_meow
import cc.sukazyo.cono.morny.core.MornyCoeur import cc.sukazyo.cono.morny.core.MornyCoeur
import cc.sukazyo.cono.morny.core.bot.api.{ICommandAlias, ISimpleCommand} import cc.sukazyo.cono.morny.core.bot.api.{ICommandAlias, ISimpleCommand}
import cc.sukazyo.cono.morny.util.tgapi.InputCommand import cc.sukazyo.cono.morny.util.tgapi.InputCommand
import cc.sukazyo.cono.morny.util.tgapi.TelegramExtensions.Bot.* import cc.sukazyo.cono.morny.util.tgapi.TelegramExtensions.Requests.unsafeExecute
import com.pengrad.telegrambot.model.{MessageEntity, Update} import com.pengrad.telegrambot.model.{MessageEntity, Update}
import com.pengrad.telegrambot.request.SendMessage import com.pengrad.telegrambot.request.SendMessage
import com.pengrad.telegrambot.TelegramBot
//noinspection NonAsciiCharacters //noinspection NonAsciiCharacters
class (using coeur: MornyCoeur) { class (using coeur: MornyCoeur) {
private given TelegramBot = coeur.account
object Chuang extends ISimpleCommand { object Chuang extends ISimpleCommand {
@ -27,12 +29,13 @@ class 创 (using coeur: MornyCoeur) {
return; return;
val chuangText = .chuangText(text) val chuangText = .chuangText(text)
coeur.account exec SendMessage( SendMessage(
event.message.chat.id, event.message.chat.id,
chuangText chuangText
).entities( ).entities(
MessageEntity(MessageEntity.Type.pre, 0, chuangText.length) MessageEntity(MessageEntity.Type.pre, 0, chuangText.length)
).replyToMessageId(event.message.messageId) ).replyToMessageId(event.message.messageId)
.unsafeExecute
} }

View File

@ -4,10 +4,11 @@ import cc.sukazyo.cono.morny.core.MornyCoeur
import cc.sukazyo.cono.morny.core.bot.api.{ICommandAlias, ISimpleCommand, ITelegramCommand} import cc.sukazyo.cono.morny.core.bot.api.{ICommandAlias, ISimpleCommand, ITelegramCommand}
import cc.sukazyo.cono.morny.data.TelegramStickers import cc.sukazyo.cono.morny.data.TelegramStickers
import cc.sukazyo.cono.morny.util.tgapi.InputCommand import cc.sukazyo.cono.morny.util.tgapi.InputCommand
import cc.sukazyo.cono.morny.util.tgapi.TelegramExtensions.Bot.exec import cc.sukazyo.cono.morny.util.tgapi.TelegramExtensions.Requests.unsafeExecute
import com.pengrad.telegrambot.model.{Message, Update} import com.pengrad.telegrambot.model.{Message, Update}
import com.pengrad.telegrambot.model.request.ParseMode import com.pengrad.telegrambot.model.request.ParseMode
import com.pengrad.telegrambot.request.{SendMessage, SendSticker} import com.pengrad.telegrambot.request.{SendMessage, SendSticker}
import com.pengrad.telegrambot.TelegramBot
import javax.swing.text.html.HTML import javax.swing.text.html.HTML
import scala.annotation.unused import scala.annotation.unused
@ -15,6 +16,7 @@ import scala.language.postfixOps
//noinspection NonAsciiCharacters //noinspection NonAsciiCharacters
class 喵呜 (using coeur: MornyCoeur) { class 喵呜 (using coeur: MornyCoeur) {
private given TelegramBot = coeur.account
object 抱抱 extends ISimpleCommand { object 抱抱 extends ISimpleCommand {
override val name: String = "抱抱" override val name: String = "抱抱"
@ -50,20 +52,22 @@ class 喵呜 (using coeur: MornyCoeur) {
override val paramRule: String = "" override val paramRule: String = ""
override val description: String = "抽取一个神秘盒子" override val description: String = "抽取一个神秘盒子"
override def execute (using command: InputCommand, event: Update): Unit = { override def execute (using command: InputCommand, event: Update): Unit = {
coeur.account exec new SendSticker( SendSticker(
event.message.chat.id, event.message.chat.id,
TelegramStickers ID_PROGYNOVA TelegramStickers ID_PROGYNOVA
).replyToMessageId(event.message.messageId) ).replyToMessageId(event.message.messageId)
.unsafeExecute
} }
} }
private def replyingSet (whileRec: String, whileNew: String)(using event: Update): Unit = { private def replyingSet (whileRec: String, whileNew: String)(using event: Update): Unit = {
val isNew = event.message.replyToMessage == null val isNew = event.message.replyToMessage == null
val target = if (isNew) event.message else event.message.replyToMessage val target = if (isNew) event.message else event.message.replyToMessage
coeur.account exec new SendMessage( SendMessage(
event.message.chat.id, event.message.chat.id,
if (isNew) whileNew else whileRec if (isNew) whileNew else whileRec
).replyToMessageId(target.messageId).parseMode(ParseMode HTML) ).replyToMessageId(target.messageId).parseMode(ParseMode HTML)
.unsafeExecute
} }
} }

View File

@ -5,12 +5,14 @@ import cc.sukazyo.cono.morny.core.bot.api.{ICommandAlias, ISimpleCommand}
import cc.sukazyo.cono.morny.util.tgapi.InputCommand import cc.sukazyo.cono.morny.util.tgapi.InputCommand
import cc.sukazyo.cono.morny.util.UseMath.over import cc.sukazyo.cono.morny.util.UseMath.over
import cc.sukazyo.cono.morny.util.UseRandom.* import cc.sukazyo.cono.morny.util.UseRandom.*
import cc.sukazyo.cono.morny.util.tgapi.TelegramExtensions.Bot.exec import cc.sukazyo.cono.morny.util.tgapi.TelegramExtensions.Requests.unsafeExecute
import com.pengrad.telegrambot.model.Update import com.pengrad.telegrambot.model.Update
import com.pengrad.telegrambot.request.SendMessage import com.pengrad.telegrambot.request.SendMessage
import com.pengrad.telegrambot.TelegramBot
//noinspection NonAsciiCharacters //noinspection NonAsciiCharacters
class 私わね (using coeur: MornyCoeur) extends ISimpleCommand { class 私わね (using coeur: MornyCoeur) extends ISimpleCommand {
private given TelegramBot = coeur.account
override val name: String = "me" override val name: String = "me"
override val aliases: List[ICommandAlias] = Nil override val aliases: List[ICommandAlias] = Nil
@ -19,10 +21,11 @@ class 私わね (using coeur: MornyCoeur) extends ISimpleCommand {
if ((1 over 521) chance_is true) { if ((1 over 521) chance_is true) {
val text = "/打假" val text = "/打假"
coeur.account exec new SendMessage( SendMessage(
event.message.chat.id, event.message.chat.id,
text text
).replyToMessageId(event.message.messageId) ).replyToMessageId(event.message.messageId)
.unsafeExecute
} }
} }

View File

@ -37,7 +37,7 @@ object CommonEncrypt {
private def hash (data: Bin)(using algorithm: String): Bin = private def hash (data: Bin)(using algorithm: String): Bin =
try { try {
MessageDigest.getInstance(algorithm) digest data MessageDigest.getInstance(algorithm) `digest` data
} catch case n: NoSuchAlgorithmException => } catch case n: NoSuchAlgorithmException =>
throw IllegalStateException(n) throw IllegalStateException(n)
@ -47,7 +47,7 @@ object CommonEncrypt {
* *
* $WhenString2Bin * $WhenString2Bin
*/ */
def MD5 (data: String): Bin = hash(data getBytes ENCRYPT_STANDARD_CHARSET)(using "md5") def MD5 (data: String): Bin = hash(data.getBytes(ENCRYPT_STANDARD_CHARSET))(using "md5")
/** the [[https://en.wikipedia.org/wiki/SHA-1 SHA-1]] hash value of input [[Bin]] `data`. */ /** the [[https://en.wikipedia.org/wiki/SHA-1 SHA-1]] hash value of input [[Bin]] `data`. */
def SHA1 (data: Bin): Bin = hash(data)(using "sha1") def SHA1 (data: Bin): Bin = hash(data)(using "sha1")
@ -55,7 +55,7 @@ object CommonEncrypt {
* *
* $WhenString2Bin * $WhenString2Bin
*/ */
def SHA1 (data: String): Bin = hash(data getBytes ENCRYPT_STANDARD_CHARSET)(using "sha1") def SHA1 (data: String): Bin = hash(data.getBytes(ENCRYPT_STANDARD_CHARSET))(using "sha1")
/** the [[https://en.wikipedia.org/wiki/SHA-2 SHA-2/256]] hash value of input [[Bin]] `data`. */ /** the [[https://en.wikipedia.org/wiki/SHA-2 SHA-2/256]] hash value of input [[Bin]] `data`. */
def SHA256 (data: Bin): Bin = hash(data)(using "sha256") def SHA256 (data: Bin): Bin = hash(data)(using "sha256")
@ -63,7 +63,7 @@ object CommonEncrypt {
* *
* $WhenString2Bin * $WhenString2Bin
*/ */
def SHA256 (data: String): Bin = hash(data getBytes ENCRYPT_STANDARD_CHARSET)(using "sha256") def SHA256 (data: String): Bin = hash(data.getBytes(ENCRYPT_STANDARD_CHARSET))(using "sha256")
/** the [[https://en.wikipedia.org/wiki/SHA-2 SHA-2/512]] hash value of input [[Bin]] `data`. */ /** the [[https://en.wikipedia.org/wiki/SHA-2 SHA-2/512]] hash value of input [[Bin]] `data`. */
def SHA512 (data: Bin): Bin = hash(data)(using "sha512") def SHA512 (data: Bin): Bin = hash(data)(using "sha512")
@ -71,7 +71,7 @@ object CommonEncrypt {
* *
* $WhenString2Bin * $WhenString2Bin
*/ */
def SHA512 (data: String): Bin = hash(data getBytes ENCRYPT_STANDARD_CHARSET)(using "sha512") def SHA512 (data: String): Bin = hash(data.getBytes(ENCRYPT_STANDARD_CHARSET))(using "sha512")
/** Try get the filename before it got encrypted. /** Try get the filename before it got encrypted.
* *
@ -89,10 +89,10 @@ object CommonEncrypt {
* @return the file fullname removed the base64 file extension. * @return the file fullname removed the base64 file extension.
*/ */
def lint_base64FileName (encrypted: String): String = encrypted match def lint_base64FileName (encrypted: String): String = encrypted match
case i if i endsWith ".b64" => i dropRight ".b64".length case i if i `endsWith` ".b64" => i dropRight ".b64".length
case ix if ix endsWith ".b64.txt" => ix dropRight ".b64.txt".length case ix if ix `endsWith` ".b64.txt" => ix dropRight ".b64.txt".length
case l if l endsWith ".base64" => l dropRight ".base64".length case l if l `endsWith` ".base64" => l dropRight ".base64".length
case lx if lx endsWith ".base64.txt" => lx dropRight ".base64.txt".length case lx if lx `endsWith` ".base64.txt" => lx dropRight ".base64.txt".length
case u => u case u => u
/** Hash a [[Long]] id to [[Bin]] using [[MD5]] algorithm. /** Hash a [[Long]] id to [[Bin]] using [[MD5]] algorithm.

View File

@ -8,10 +8,14 @@ object EpochDateTime {
/** The UNIX Epoch Time in milliseconds. /** The UNIX Epoch Time in milliseconds.
* *
* aka. Milliseconds since 00:00:00 UTC on Thursday, 1 January 1970. * aka. Milliseconds since 00:00:00 UTC on Thursday, 1 January 1970.
*
* @since 1.1.1
*/ */
type EpochMillis = Long type EpochMillis = Long
object EpochMillis: object EpochMillis:
/** convert a localtime with timezone to epoch milliseconds /** convert a localtime with timezone to epoch milliseconds
*
* @since 1.1.1
* *
* @param time the local time in that timezone, should be formatted * @param time the local time in that timezone, should be formatted
* in [[DateTimeFormatter.ISO_DATE_TIME]] * in [[DateTimeFormatter.ISO_DATE_TIME]]
@ -22,7 +26,7 @@ object EpochDateTime {
def apply (time: String, zone: String): EpochMillis = { def apply (time: String, zone: String): EpochMillis = {
val formatter = DateTimeFormatter.ISO_DATE_TIME val formatter = DateTimeFormatter.ISO_DATE_TIME
val innerTime = LocalDateTime.parse(time, formatter) val innerTime = LocalDateTime.parse(time, formatter)
val instant = innerTime.toInstant(ZoneOffset of zone) val instant = innerTime.toInstant(ZoneOffset `of` zone)
instant.toEpochMilli instant.toEpochMilli
} }
def apply (time_zone: (String, String)): EpochMillis = def apply (time_zone: (String, String)): EpochMillis =
@ -33,8 +37,10 @@ object EpochDateTime {
* *
* Due to the missing accuracy, the converted EpochMillis will * Due to the missing accuracy, the converted EpochMillis will
* be always in 0ms aligned. * be always in 0ms aligned.
*
* @since 2.0.0
*/ */
def fromEpochSeconds (epochSeconds: EpochSeconds): EpochMillis = infix def fromSeconds (epochSeconds: EpochSeconds): EpochMillis =
epochSeconds.longValue * 1000L epochSeconds.longValue * 1000L
/** The UNIX Epoch Time in seconds. /** The UNIX Epoch Time in seconds.
@ -45,6 +51,8 @@ object EpochDateTime {
* *
* Notice that, currently, it stores using [[Int]] (also the implementation * Notice that, currently, it stores using [[Int]] (also the implementation
* method of Telegram), which will only store times before 2038-01-19 03:14:07. * method of Telegram), which will only store times before 2038-01-19 03:14:07.
*
* @since 1.3.0
*/ */
type EpochSeconds = Int type EpochSeconds = Int
@ -56,13 +64,20 @@ object EpochDateTime {
* *
* Notice that, currently, it stores using [[Short]] (also the implementation * Notice that, currently, it stores using [[Short]] (also the implementation
* method of Telegram), which will only store times before 2059-09-18. * method of Telegram), which will only store times before 2059-09-18.
*
* @since 1.3.0
*/ */
type EpochDays = Short type EpochDays = Short
object EpochDays: object EpochDays:
def fromEpochMillis (epochMillis: EpochMillis): EpochDays = /** Convert a [[EpochMillis]] to [[EpochDays]]. Will be loss of precision.
* @since 2.0.0
*/
infix def fromMillis (epochMillis: EpochMillis): EpochDays =
(epochMillis / (1000*60*60*24)).toShort (epochMillis / (1000*60*60*24)).toShort
/** Time duration/interval in milliseconds. */ /** Time duration/interval in milliseconds.
* @since 1.3.0
*/
type DurationMillis = Long type DurationMillis = Long
} }

View File

@ -18,7 +18,7 @@ object FileUtils {
Using (FileInputStream(path)) { stream => Using (FileInputStream(path)) { stream =>
len = stream.read(buffer) len = stream.read(buffer)
while (len != -1) while (len != -1)
algo update (buffer, 0, len) algo.update(buffer, 0, len)
len = stream.read(buffer) len = stream.read(buffer)
} }
import ConvertByteHex.toHex import ConvertByteHex.toHex

View File

@ -1,26 +1,17 @@
package cc.sukazyo.cono.morny.util package cc.sukazyo.cono.morny.util
import cc.sukazyo.cono.morny.util.GivenContext.{ContextNotGivenException, FolderClass, RequestItemClass} import cc.sukazyo.cono.morny.util.GivenContext.{ContextNotGivenException, FolderClass, RequestItemClass}
import cc.sukazyo.messiva.utils.StackUtils
import scala.annotation.targetName import scala.annotation.targetName
import scala.collection.mutable import scala.collection.mutable
import scala.reflect.{classTag, ClassTag} import scala.reflect.{classTag, ClassTag}
import scala.util.boundary
object GivenContext { object GivenContext {
case class FolderClass (clazz: Option[Class[?]]) case class FolderClass (clazz: Option[Class[?]])
object FolderClass: object FolderClass:
def default: FolderClass = FolderClass(None) def default: FolderClass = FolderClass(None)
case class RequestItemClass (clazz: Class[?]) case class RequestItemClass (clazz: Class[?])
private def lastNonGCStack: StackTraceElement =
boundary {
for (stack <- StackUtils.getStackTrace(0)) {
if (!stack.getClassName.startsWith(classOf[GivenContext].getName))
boundary break stack
}
StackTraceElement("unknown", "unknown", "unknown", -1)
}
class ContextNotGivenException (using class ContextNotGivenException (using
val requestItemClass: RequestItemClass, val requestItemClass: RequestItemClass,
val folderClass: FolderClass = FolderClass.default, val folderClass: FolderClass = FolderClass.default,
@ -69,7 +60,12 @@ object GivenContext {
* } catch case e: ContextNotGivenException => // if any of the above val is not available, it will catch the exception * } catch case e: ContextNotGivenException => // if any of the above val is not available, it will catch the exception
* e.printStackTrace() * e.printStackTrace()
* }}} * }}}
*
* TODO: Tests
*
* @since 2.0.0
*/ */
//noinspection NoTargetNameAnnotationForOperatorLikeDefinition
class GivenContext { class GivenContext {
private type ImplicitsMap [T <: Any] = mutable.HashMap[Class[?], T] private type ImplicitsMap [T <: Any] = mutable.HashMap[Class[?], T]
@ -77,7 +73,7 @@ class GivenContext {
private val variables: ImplicitsMap[Any] = mutable.HashMap.empty private val variables: ImplicitsMap[Any] = mutable.HashMap.empty
private val variablesWithOwner: ImplicitsMap[ImplicitsMap[Any]] = mutable.HashMap.empty private val variablesWithOwner: ImplicitsMap[ImplicitsMap[Any]] = mutable.HashMap.empty
def provide [T: ClassTag] (i: T): Unit = infix def provide [T: ClassTag] (i: T): Unit =
variables += (classTag[T].runtimeClass -> i) variables += (classTag[T].runtimeClass -> i)
def << [T: ClassTag] (is: (Class[T], T)): Unit = def << [T: ClassTag] (is: (Class[T], T)): Unit =
val (_, i) = is val (_, i) = is
@ -91,7 +87,7 @@ class GivenContext {
variables get t.clazz match variables get t.clazz match
case Some(i) => Right(i.asInstanceOf[T]) case Some(i) => Right(i.asInstanceOf[T])
case None => Left(ContextNotGivenException()) case None => Left(ContextNotGivenException())
def use [T: ClassTag, U] (consumer: T => U): ConsumeResult[U] = infix def use [T: ClassTag, U] (consumer: T => U): ConsumeResult[U] =
this.use[T] match this.use[T] match
case Left(_) => ConsumeFailed[U]() case Left(_) => ConsumeFailed[U]()
case Right(i) => ConsumeSucceed[U](consumer(i)) case Right(i) => ConsumeSucceed[U](consumer(i))
@ -101,7 +97,7 @@ class GivenContext {
this.use[T].toTry.get this.use[T].toTry.get
def >>[T: ClassTag, U] (consumer: T => U): ConsumeResult[U] = def >>[T: ClassTag, U] (consumer: T => U): ConsumeResult[U] =
this.use[T,U](consumer) this.use[T,U](consumer)
def consume [T: ClassTag] (consume: T => Any): ConsumeResult[Any] = infix def consume [T: ClassTag] (consume: T => Any): ConsumeResult[Any] =
this.use[T,Any](consume) this.use[T,Any](consume)
@targetName("ownedBy") @targetName("ownedBy")
@ -112,7 +108,7 @@ class GivenContext {
class OwnedContext [O: ClassTag] { class OwnedContext [O: ClassTag] {
def provide [T: ClassTag] (i: T): Unit = infix def provide [T: ClassTag] (i: T): Unit =
(variablesWithOwner getOrElseUpdate (classTag[O].runtimeClass, mutable.HashMap.empty)) (variablesWithOwner getOrElseUpdate (classTag[O].runtimeClass, mutable.HashMap.empty))
.addOne(classTag[T].runtimeClass -> i) .addOne(classTag[T].runtimeClass -> i)
def << [T: ClassTag] (is: (Class[T], T)): Unit = def << [T: ClassTag] (is: (Class[T], T)): Unit =
@ -129,7 +125,7 @@ class GivenContext {
case Some(i) => Right(i.asInstanceOf[T]) case Some(i) => Right(i.asInstanceOf[T])
case None => Left(ContextNotGivenException()) case None => Left(ContextNotGivenException())
case None => Left(ContextNotGivenException()) case None => Left(ContextNotGivenException())
def use [T: ClassTag, U] (consumer: T => U): ConsumeResult[U] = infix def use [T: ClassTag, U] (consumer: T => U): ConsumeResult[U] =
use[T] match use[T] match
case Left(_) => ConsumeFailed[U]() case Left(_) => ConsumeFailed[U]()
case Right(i) => ConsumeSucceed[U](consumer(i)) case Right(i) => ConsumeSucceed[U](consumer(i))
@ -139,7 +135,7 @@ class GivenContext {
this.use[T].toTry.get this.use[T].toTry.get
def >> [T: ClassTag, U] (consumer: T => U): ConsumeResult[U] = def >> [T: ClassTag, U] (consumer: T => U): ConsumeResult[U] =
this.use[T,U](consumer) this.use[T,U](consumer)
def consume [T: ClassTag] (consume: T => Any): ConsumeResult[Any] = infix def consume [T: ClassTag] (consume: T => Any): ConsumeResult[Any] =
this.use[T,Any](consume) this.use[T,Any](consume)
} }

View File

@ -5,13 +5,68 @@ object StringEnsure {
extension (str: String) { extension (str: String) {
def ensureSize(size: Int, paddingStr: Char = ' '): String = { /** Ensure the string have a length not smaller that the given length.
*
* If the length of the string is smaller than the given length, then the string will be padded
* with the given padding character to the right, until the length of the string is not smaller
* than the given length.
*
* For now, it does nothing if the length of the string is not smaller than the given length.
*
* @since 1.1.0
*
* @param size The minimum length that the string should have.
* @param paddingStr The character that will be used to pad the string. Defaults to `' '` (a space).
* @return A string that have been ensured to have a length not smaller than the given length.
*/
infix def ensureSize(size: Int, paddingStr: Char = ' '): String = {
if (str.length < size) { if (str.length < size) {
val padding = paddingStr.toString * (size-str.length) val padding = paddingStr.toString * (size-str.length)
str + padding str + padding
} else str } else str
} }
/** Replace the String with a given char, keeps the String length and characters only at the start
* and end.
*
* This method can be used to de-sensitive some sensitive information, such as phone number, email,
* etc. The default setting is to keep the first 2 characters and the last 4 characters, and replace
* the rest with '*'.
*
* Notice that this method have un-defined behavior when the length of the String is less than
* the character that will be kept, so change the character length that will be kept in your need.
*
* Examples:
* {{{
* scala> val someUserToken = "TOKEN_UV:V927c092FV$REFV[p':V<IE#*&@()U8eR)c"
* val someUserToken: String = TOKEN_UV:V927c092FV$REFV[p':V<IE#*&@()U8eR)c
*
* scala> someUserToken.deSensitive()
* val res1: String = TO**************************************eR)c
*
* scala> someUserToken.deSensitive(8, 4)
* val res2: String = TOKEN_UV********************************eR)c
*
* scala> someUserToken.deSensitive(10, 4, '?')
* val res3: String = TOKEN_UV:V??????????????????????????????eR)c
*
* scala> someUserToken.deSensitive(10, 4, '-')
* val res4: String = TOKEN_UV:V------------------------------eR)c
*
* scala> "short".deSensitive(5, 5, '-')
* val res5: String = shortshort
* }}}
*
* @since 1.2.0
*
* @param keepStart The characters length that need to be kept at the start of the String. Defaults
* to `2`.
* @param keepEnd The characters length that need to be kept at the end of the String. Defaults to
* `4`.
* @param sensitive_cover The character that will be used to cover the sensitive information.
* Defaults to `*`.
* @return A string that have been de-sensitive.
*/
def deSensitive (keepStart: Int = 2, keepEnd: Int = 4, sensitive_cover: Char = '*'): String = 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) (str take keepStart) + (sensitive_cover.toString*(str.length-keepStart-keepEnd)) + (str takeRight keepEnd)

View File

@ -1,7 +1,8 @@
package cc.sukazyo.cono.morny.util package cc.sukazyo.cono.morny.util
import cc.sukazyo.cono.morny.core.MornySystem import cc.sukazyo.cono.morny.core.MornySystem
import sttp.client3.basicRequest import sttp.client3.{basicRequest, RequestT}
import sttp.client3
import sttp.model.Header import sttp.model.Header
object SttpPublic { object SttpPublic {
@ -17,13 +18,27 @@ object SttpPublic {
private val key = "User-Agent" private val key = "User-Agent"
/** The default Morny User-Agent header.
*
* It follows the following template:
* `MornyCoeur / <morny-version>`
*
* @since 2.0.0
*/
val MORNY_CURRENT: Header = Header(key, s"MornyCoeur / ${MornySystem.VERSION}") val MORNY_CURRENT: Header = Header(key, s"MornyCoeur / ${MornySystem.VERSION}")
} }
} }
val mornyBasicRequest = /** The basic request template for Morny's HTTP requests.
*
* It is a expansion of [[sttp.client3.basicRequest]] with the following features:
* - A [[Headers.UserAgent.MORNY_CURRENT]] User-Agent header.
*
* @since 2.0.0
*/
val mornyBasicRequest: RequestT[client3.Empty, Either[String, String], Any] =
basicRequest basicRequest
.header(Headers.UserAgent.MORNY_CURRENT, true) .header(Headers.UserAgent.MORNY_CURRENT, true)

View File

@ -7,7 +7,7 @@ object UseMath {
extension (self: Int) { extension (self: Int) {
def over (other: Int): Double = self.toDouble / other infix def over (other: Int): Double = self.toDouble / other
} }
@ -17,7 +17,7 @@ object UseMath {
} }
extension (base: Int) { extension (base: Int) {
def percentageOf (another: Int): Int = infix def percentageOf (another: Int): Int =
Math.round((another.toDouble/base)*100).toInt Math.round((another.toDouble/base)*100).toInt
} }

View File

@ -10,7 +10,7 @@ import scala.util.Random
object UseRandom { object UseRandom {
class ChancePossibility[T <: Any] (val one: T) (using possibility: Double) { class ChancePossibility[T <: Any] (val one: T) (using possibility: Double) {
def nor[U] (another: U): T|U = infix def nor[U] (another: U): T|U =
if Random.nextDouble < possibility then one else another if Random.nextDouble < possibility then one else another
} }
@ -19,7 +19,7 @@ object UseRandom {
extension (num: Double) { extension (num: Double) {
def chance_is[T <: Any] (one: T): ChancePossibility[T] = infix def chance_is[T <: Any] (one: T): ChancePossibility[T] =
ChancePossibility(one)(using num) ChancePossibility(one)(using num)
} }

View File

@ -4,6 +4,7 @@ import cc.sukazyo.messiva.utils.StackUtils
import scala.reflect.{classTag, ClassTag} import scala.reflect.{classTag, ClassTag}
import scala.util.boundary import scala.util.boundary
import scala.util.boundary.break
object UseStacks { object UseStacks {
@ -11,7 +12,7 @@ object UseStacks {
boundary { boundary {
for (stack <- StackUtils.getStackTrace(1)) { for (stack <- StackUtils.getStackTrace(1)) {
if (!stack.getClassName.startsWith(classTag[T].runtimeClass.getName)) if (!stack.getClassName.startsWith(classTag[T].runtimeClass.getName))
boundary break stack break(stack)
} }
StackTraceElement("unknown", "unknown", "unknown", -1) StackTraceElement("unknown", "unknown", "unknown", -1)
} }

View File

@ -0,0 +1,16 @@
package cc.sukazyo.cono.morny.util
import java.io.{PrintWriter, StringWriter}
object UseThrowable {
extension (t: Throwable) {
def toLogString: String =
val stackTrace = StringWriter()
t `printStackTrace` PrintWriter(stackTrace)
stackTrace toString
}
}

View File

@ -93,14 +93,14 @@ class Scheduler {
case readyToRun: Task => case readyToRun: Task =>
runtimeStatus = State.RUNNING runtimeStatus = State.RUNNING
this setName readyToRun.name this `setName` readyToRun.name
try { try {
readyToRun.main readyToRun.main
} catch case _: (Exception | Error) => {} } catch case _: (Exception | Error) => {}
runtimeStatus = State.RUNNING_POST runtimeStatus = State.RUNNING_POST
this setName s"${readyToRun.name}#post" this `setName` s"${readyToRun.name}#post"
// this if is used for check if post effect need to be // this if is used for check if post effect need to be
// run. It is useless since the wait/notify changes. // run. It is useless since the wait/notify changes.
@ -117,7 +117,7 @@ class Scheduler {
} }
// currentRunning = null // currentRunning = null
this setName runnerName this `setName` runnerName
case needToWaitMillis: EpochMillis => case needToWaitMillis: EpochMillis =>
runtimeStatus = State.WAITING runtimeStatus = State.WAITING
@ -133,7 +133,7 @@ class Scheduler {
} }
} }
runtime setName runnerName runtime `setName` runnerName
runtime.start() runtime.start()
/** Name of the scheduler runner. /** Name of the scheduler runner.
@ -179,7 +179,7 @@ class Scheduler {
* succeed removed from task queue. * succeed removed from task queue.
*/ */
def cancel (task: Task): Boolean = def cancel (task: Task): Boolean =
taskList synchronized: taskList.synchronized:
try taskList remove task try taskList remove task
finally taskList.notifyAll() finally taskList.notifyAll()
@ -208,7 +208,7 @@ class Scheduler {
* to make the scheduler avoid fails when machine fall asleep or some else conditions. * to make the scheduler avoid fails when machine fall asleep or some else conditions.
*/ */
def notifyIt(): Unit = def notifyIt(): Unit =
taskList synchronized: taskList.synchronized:
taskList.notifyAll() taskList.notifyAll()
/** Stop the scheduler's runner, no matter how much task is not run yet. /** Stop the scheduler's runner, no matter how much task is not run yet.

View File

@ -16,7 +16,7 @@ class InputCommand private (
object InputCommand { object InputCommand {
def apply (input: Array[String]): InputCommand = { def apply (input: Array[String]): InputCommand = {
val _ex = if input.nonEmpty then input(0) split ("@", 2) else Array.empty[String] val _ex = if input.nonEmpty then input(0).split("@", 2) else Array.empty[String]
val _args = input drop 1 val _args = input drop 1
new InputCommand( new InputCommand(
if _ex.length > 1 then _ex(1) else null, if _ex.length > 1 then _ex(1) else null,

View File

@ -1,12 +1,13 @@
package cc.sukazyo.cono.morny.util.tgapi package cc.sukazyo.cono.morny.util.tgapi
import cc.sukazyo.cono.morny.util.tgapi.event.EventRuntimeException import cc.sukazyo.cono.morny.util.tgapi.event.EventRuntimeException
import cc.sukazyo.cono.morny.util.EpochDateTime.{EpochMillis, EpochSeconds} import cc.sukazyo.cono.morny.util.EpochDateTime.EpochSeconds
import com.pengrad.telegrambot.TelegramBot import com.pengrad.telegrambot.TelegramBot
import com.pengrad.telegrambot.model.* import com.pengrad.telegrambot.model.*
import com.pengrad.telegrambot.request.{BaseRequest, GetChatMember} import com.pengrad.telegrambot.request.{BaseRequest, GetChatMember}
import com.pengrad.telegrambot.response.BaseResponse import com.pengrad.telegrambot.response.BaseResponse
import java.io.IOException
import scala.annotation.targetName import scala.annotation.targetName
object TelegramExtensions { object TelegramExtensions {
@ -39,7 +40,7 @@ object TelegramExtensions {
@throws[EventRuntimeException] @throws[EventRuntimeException]
def exec [T <: BaseRequest[T, R], R <: BaseResponse] (request: BaseRequest[T, R], onError_message: String = ""): R = { def exec [T <: BaseRequest[T, R], R <: BaseResponse] (request: BaseRequest[T, R], onError_message: String = ""): R = {
try { try {
val response = bot execute request val response = bot `execute` request
if response isOk then return response if response isOk then return response
throw EventRuntimeException.ActionFailed( throw EventRuntimeException.ActionFailed(
if onError_message isEmpty then response.errorCode toString else onError_message, if onError_message isEmpty then response.errorCode toString else onError_message,
@ -229,11 +230,14 @@ object TelegramExtensions {
* It needs to execute a request getting chat member, so it requires a live [[TelegramBot]] instance, * It needs to execute a request getting chat member, so it requires a live [[TelegramBot]] instance,
* and the bot needs to be a member of this chat so that it can read the chat member. * and the bot needs to be a member of this chat so that it can read the chat member.
* *
* This method will only use the [[Chat]]'s [[Chat.id]], so it is safe to call on a [[LimboChat]].
*
* **Notice:** This method will execute a request to the Telegram Bot API, so it may be slow. * **Notice:** This method will execute a request to the Telegram Bot API, so it may be slow.
* *
* @see [[memberHasPermission]] * @see [[memberHasPermission]]
* *
* @param user The user that want to check if they are a member of this chat. * @param user The user that want to check if they are a member of this chat.Only its [[User.id]] will
* be used so it is safe using a [[LimboUser]].
* @return [[true]] when the user is a member of this chat, [[false]] otherwise. * @return [[true]] when the user is a member of this chat, [[false]] otherwise.
* *
* @since 1.0.0 * @since 1.0.0
@ -257,9 +261,14 @@ object TelegramExtensions {
* | [[ChatMember.Status.left]] | -3 | * | [[ChatMember.Status.left]] | -3 |
* | [[ChatMember.Status.kicked]] | -5 | * | [[ChatMember.Status.kicked]] | -5 |
* *
* This method will only use the [[Chat]]'s [[Chat.id]], so it is safe to call on a [[LimboChat]].
*
* **Notice:** This method will execute a request to the Telegram Bot API, so it may be slow. * **Notice:** This method will execute a request to the Telegram Bot API, so it may be slow.
* *
* @param user The user that wanted to check if they have the permission. * @since 1.0.0
*
* @param user The user that wanted to check if they have the permission. Only its [[User.id]] will
* be used so it is safe using a [[LimboUser]].
* @param permission The required permission level. * @param permission The required permission level.
* @param bot A live [[TelegramBot]] that will be used to execute the getChatMember request. It * @param bot A live [[TelegramBot]] that will be used to execute the getChatMember request. It
* should be a member of this chat so that can read the chat member for this method works. * should be a member of this chat so that can read the chat member for this method works.
@ -288,8 +297,8 @@ object TelegramExtensions {
case ChatMember.Status.kicked => KICKED case ChatMember.Status.kicked => KICKED
def apply (chatMember: ChatMember): UserPermissionLevel = apply(chatMember.status) def apply (chatMember: ChatMember): UserPermissionLevel = apply(chatMember.status)
import Bot.* import Requests.execute
val chatMember: ChatMember = (bot exec GetChatMember(chat.id, user.id)).chatMember val chatMember: ChatMember = GetChatMember(chat.id, user.id).execute(using bot).chatMember
if chatMember eq null then false if chatMember eq null then false
else UserPermissionLevel(chatMember) >= UserPermissionLevel(permission) else UserPermissionLevel(chatMember) >= UserPermissionLevel(permission)
@ -309,7 +318,65 @@ object TelegramExtensions {
}} }}
object File { extension (self: File) {
/** Alias of [[TelegramBot.getFileContent]] */
@throws[IOException]
def getContent (using bot: TelegramBot): Array[Byte] =
bot.getFileContent(self)
/** Alias of [[TelegramBot.getFullFilePath]] */
def getFullPath (using bot: TelegramBot): String =
bot.getFullFilePath(self)
}}
object Requests { extension [T<:BaseRequest[T,R], R<:BaseResponse] (self: BaseRequest[T,R]) {
/** Run this request with the given bot. Returns [[BaseResponse response]] if succeed, or throws
* [[EventRuntimeException]] if run failed.
*
* This method is an alias for [[Bot.exec]]
*
* @since 2.0.0
*/
@throws[EventRuntimeException]
def unsafeExecute (using bot: TelegramBot): R =
import Bot.exec
bot.exec(self)
/** Run this request with the given bot and returns no matter it succeed or not.
*
* Notice that if there's some client errors (like network error, etc.), this method will still
* throws an Exception following the [[TelegramBot.execute]]'s behavior.
*
* This method is an alias for the native [[TelegramBot.execute]].
*
* @since 2.0.0
*/
def execute (using bot: TelegramBot): R =
bot.execute(self)
}}
/** A [[User]] instance with only a [[User.id]] is defined.
*
* This is only for capabilities that some method need a [[User]] but only need its [[User.id]]. If
* you don't have a [[User]] instance for some reason, you can use this instead.
*
* Many methods may crashes on this class for this is highly mutilated. Use this only the method declares
* that using this is safe.
*
* @since 1.0.0
*/
class LimboUser (id: Long) extends User(id) class LimboUser (id: Long) extends User(id)
/** A [[Chat]] instance with only a [[Chat.id]] is defined.
*
* This is only for capabilities that some method need a [[Chat]] but only need its [[Chat.id]]. If
* you don't have a [[Chat]] instance for some reason, you can use this instead.
*
* Many methods may crashes on this class for this is highly mutilated. Use this only the method declares
* that using this is safe.
*
* @since 1.0.0
*/
class LimboChat (val _id: Long) extends Chat() { class LimboChat (val _id: Long) extends Chat() {
override val id: java.lang.Long = _id override val id: java.lang.Long = _id
} }

View File

@ -54,13 +54,13 @@ object TelegramParseEscape {
case "br" => case "br" =>
result += TextNode("\n") result += TextNode("\n")
case "tg-emoji" => case "tg-emoji" =>
if elem.attributes.hasKey("emoji-id") then if elem.attributes `hasKey` "emoji-id" then
result += produceChildNodes(elem) result += produceChildNodes(elem)
else else
result += TextNode(elem.text) result += TextNode(elem.text)
case "img" => case "img" =>
if elem.attributes hasKey "alt" then if elem.attributes `hasKey` "alt" then
result += TextNode(s"[${elem attr "alt"}]") result += TextNode(s"[${elem.attr("alt")}]")
case _ => case _ =>
for (i <- cleanupHtml(elem.childNodes.asScala.toSeq)) result += i for (i <- cleanupHtml(elem.childNodes.asScala.toSeq)) result += i
} }

View File

@ -9,8 +9,8 @@ trait WatchDog (val isDaemonIt: Boolean = true) extends Thread {
val overloadMillis: DurationMillis = tickSpeedMillis + (tickSpeedMillis/2) val overloadMillis: DurationMillis = tickSpeedMillis + (tickSpeedMillis/2)
private var previousTickTimeMillis: Option[EpochMillis] = None private var previousTickTimeMillis: Option[EpochMillis] = None
this setName threadName this `setName` threadName
this setDaemon isDaemonIt this `setDaemon` isDaemonIt
this.start() this.start()

View File

@ -8,6 +8,8 @@ import scala.io.StdIn
@main def MornyCLI (): Unit = { @main def MornyCLI (): Unit = {
print("$ java -jar morny-coeur-\"+MornySystem.VERSION_FULL+\".jar ") print("$ java -jar morny-coeur-\"+MornySystem.VERSION_FULL+\".jar ")
ServerMain main UniversalCommand(StdIn readLine) ServerMain.main(
UniversalCommand(StdIn readLine)
)
} }

View File

@ -26,7 +26,7 @@ class MedicationTimerTest extends MornyTests with TableDrivenPropertyChecks {
forAll(examples) { (current, notifyAt, useTimezone, nextNotifyTime) => forAll(examples) { (current, notifyAt, useTimezone, nextNotifyTime) =>
val _curr = EpochMillis(current) val _curr = EpochMillis(current)
val _tz = ZoneOffset of useTimezone val _tz = ZoneOffset `of` useTimezone
val _next = EpochMillis(nextNotifyTime) val _next = EpochMillis(nextNotifyTime)
s"at time [$_curr], and need to be notify at hours ${notifyAt.mkString(",")} with $_tz :" - { s"at time [$_curr], and need to be notify at hours ${notifyAt.mkString(",")} with $_tz :" - {

View File

@ -19,7 +19,7 @@ class EpochDateTimeTest extends MornyTests with TableDrivenPropertyChecks {
forAll(examples) { (epochSeconds, epochMillis) => forAll(examples) { (epochSeconds, epochMillis) =>
s"EpochSeconds($epochSeconds) should be converted to EpochMillis($epochMillis)" in { s"EpochSeconds($epochSeconds) should be converted to EpochMillis($epochMillis)" in {
(EpochMillis fromEpochSeconds epochSeconds) shouldEqual epochMillis (EpochMillis fromSeconds epochSeconds) shouldEqual epochMillis
} }
} }
@ -42,7 +42,7 @@ class EpochDateTimeTest extends MornyTests with TableDrivenPropertyChecks {
forAll(examples) { (epochMillis, epochDays) => forAll(examples) { (epochMillis, epochDays) =>
s"EpochMillis($epochMillis) should be converted to EpochDays($epochDays)" in { s"EpochMillis($epochMillis) should be converted to EpochDays($epochDays)" in {
(EpochDays fromEpochMillis epochMillis) shouldEqual epochDays (EpochDays fromMillis epochMillis) shouldEqual epochDays
} }
} }

View File

@ -32,7 +32,7 @@ class TaskBasicTest extends MornyTests {
"task can be sync executed by calling its main method." taggedAs Slow in { "task can be sync executed by calling its main method." taggedAs Slow in {
Thread.currentThread setName "parent-thread" Thread.currentThread `setName` "parent-thread"
val data = StringBuilder("") val data = StringBuilder("")
val task = Task("some-task", 0L, { val task = Task("some-task", 0L, {
Thread.sleep(100) Thread.sleep(100)