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 / organizationName := "A.C. Sukazyo Eyre"
ThisBuild / scalaVersion := "3.3.1"
ThisBuild / scalaVersion := "3.4.0-RC4"
resolvers ++= Seq(
"-ws-releases" at "https://mvn.sukazyo.cc/releases"
@ -56,6 +56,20 @@ lazy val root = (project in file("."))
"-target", "17"
),
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 := {
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_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 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.data.TelegramStickers
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.request.ParseMode
import com.pengrad.telegrambot.request.{ForwardMessage, GetChat, SendMessage, SendSticker}
import scala.language.postfixOps
import com.pengrad.telegrambot.TelegramBot
class OnCallMe (using coeur: MornyCoeur) extends EventListener {
private given TelegramBot = coeur.account
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>")
case "dinner" | "lunch" | "breakfast" | "meal" | "eating" | "安妮今天吃什么" =>
requestLastDinner(update.message)
case cc if cc startsWith "cc::" =>
case cc if cc `startsWith` "cc::" =>
requestCustom(update.message)
case _ =>
return;
if success then
coeur.account exec SendSticker(
SendSticker(
update.message.chat.id,
TelegramStickers ID_SENT
).replyToMessageId(update.message.messageId)
.unsafeExecute
else
coeur.account exec SendSticker(
SendSticker(
update.message.chat.id,
TelegramStickers ID_501
).replyToMessageId(update.message.messageId)
.unsafeExecute
event.setEventOk
}
private def requestItem (user: User, itemHTML: String, extra: String|Null = null): Boolean =
coeur.account exec SendMessage(
SendMessage(
me,
s"""request $itemHTML
|from ${user.fullnameRefHTML}${if extra == null then "" else "\n"+extra}"""
.stripMargin
).parseMode(ParseMode HTML)
.unsafeExecute
true
private def requestLastDinner (req: Message): Boolean = {
if coeur.config.dinnerChatId == -1 then return false
var isAllowed = false
var lastDinnerData: Message|Null = null
if (coeur.trusted isTrusted_dinnerReader req.from.id) {
if (coeur.trusted isTrust4dinner req.from) {
// todo: have issues
// i dont want to test it anymore... it might be deprecated soon
lastDinnerData = (coeur.account exec GetChat(coeur.config.dinnerChatId)).chat.pinnedMessage
val sendResp = coeur.account exec ForwardMessage(
lastDinnerData = GetChat(coeur.config.dinnerChatId).unsafeExecute.chat.pinnedMessage
val sendResp = ForwardMessage(
req.from.id,
lastDinnerData.forwardFromChat.id,
lastDinnerData.forwardFromMessageId
)
).unsafeExecute
import cc.sukazyo.cono.morny.util.CommonFormat.{formatDate, formatDuration}
import cc.sukazyo.cono.morny.util.EpochDateTime.EpochMillis
import cc.sukazyo.cono.morny.util.tgapi.formatting.TelegramParseEscape.escapeHtml as h
def lastDinner_dateMillis: EpochMillis = EpochMillis fromEpochSeconds lastDinnerData.forwardDate
coeur.account exec SendMessage(
def lastDinner_dateMillis: EpochMillis = EpochMillis fromSeconds lastDinnerData.forwardDate
SendMessage(
req.from.id,
"<i>on</i> <code>%s [UTC+8]</code>\n- <code>%s</code> <i>before</i>".formatted(
h(formatDate(lastDinner_dateMillis, 8)),
h(formatDuration(System.currentTimeMillis - lastDinner_dateMillis))
)
).parseMode(ParseMode HTML).replyToMessageId(sendResp.message.messageId)
.unsafeExecute
isAllowed = true
} else {
coeur.account exec SendSticker(
SendSticker(
req.from.id,
TelegramStickers ID_403
).replyToMessageId(req.messageId)
.unsafeExecute
}
import Math.abs
requestItem(
@ -100,7 +105,8 @@ class OnCallMe (using coeur: MornyCoeur) extends EventListener {
private def requestCustom (message: Message): Boolean =
requestItem(message.from, "<u>[???]</u>")
coeur.account exec ForwardMessage(me, message.chat.id, message.messageId)
ForwardMessage(me, message.chat.id, message.messageId)
.unsafeExecute
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.bot.api.{EventEnv, EventListener}
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.request.ParseMode
import com.pengrad.telegrambot.request.{GetChat, SendMessage, SendSticker}
import com.pengrad.telegrambot.TelegramBot
import scala.collection.mutable.ArrayBuffer
import scala.language.postfixOps
import scala.util.matching.Regex
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
@ -25,12 +27,12 @@ class OnCallMsgSend (using coeur: MornyCoeur) extends EventListener {
def toSendMessage (target_override: Long|Null = null): SendMessage =
val useTarget = if target_override == null then targetId else target_override
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)
sendMessage
}
private object MessageToSend:
def from (raw: Message): MessageToSend = {
infix def from (raw: Message): MessageToSend = {
raw.text match
case REGEX_MSG_SENDREQ_DATA_HEAD(_target, _parseMode, _body) =>
val target = _target toLong
@ -57,15 +59,15 @@ class OnCallMsgSend (using coeur: MornyCoeur) extends EventListener {
val message = update.message
if message.chat.`type` != Chat.Type.Private then return;
if message.text eq null then return;
if !(message.text startsWith "*msg") then return;
if message.chat.`type` != Chat.Type.Private then return
if message.text eq null then return
if !(message.text `startsWith` "*msg") then return
if (!(coeur.trusted isTrusted message.from.id))
coeur.account exec SendSticker(
if (!(coeur.trusted isTrust message.from))
SendSticker(
message.chat.id,
TelegramStickers ID_403
).replyToMessageId(message.messageId)
).replyToMessageId(message.messageId).unsafeExecute
event.setEventOk
return;
@ -74,21 +76,23 @@ class OnCallMsgSend (using coeur: MornyCoeur) extends EventListener {
if (message.replyToMessage eq null) { answer404; return }
val messageToSend = MessageToSend from message.replyToMessage
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) {
coeur.account exec SendSticker(
SendSticker(
update.message.chat.id,
TelegramStickers ID_SENT
).replyToMessageId(update.message.messageId)
.unsafeExecute
} else {
coeur.account exec SendMessage(
SendMessage(
update.message.chat.id,
// language=html
s"""<b><u>${sendResponse.errorCode} FAILED</u></b>
|<code>${sendResponse.description}</code>"""
.stripMargin
).replyToMessageId(update.message.messageId).parseMode(ParseMode HTML)
.unsafeExecute
}
event.setEventOk
@ -101,14 +105,14 @@ class OnCallMsgSend (using coeur: MornyCoeur) extends EventListener {
if (message.text == "*msg")
if message.replyToMessage eq null then { answer404; return }
else message.replyToMessage
else if (message.text startsWith "*msg")
else if (message.text `startsWith` "*msg")
message
else { answer404; return }
val _toSend = MessageToSend from raw
if _toSend eq null then { answer404; return }
else _toSend
val targetChatResponse = coeur.account execute GetChat(messageToSend.targetId)
val targetChatResponse = GetChat(messageToSend.targetId).execute
if (targetChatResponse isOk) {
def getChatDescriptionHTML (chat: Chat): String =
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 ""}
|${chat.typeTag} <b>${h(chat.safe_name)}</b> ${chat.safe_linkHTML}"""
.stripMargin
coeur.account exec SendMessage(
SendMessage(
update.message.chat.id,
getChatDescriptionHTML(targetChatResponse.chat)
).parseMode(ParseMode HTML).replyToMessageId(update.message.messageId)
.unsafeExecute
} else {
coeur.account exec SendMessage(
SendMessage(
update.message.chat.id,
// language=html
s"""<b><u>${targetChatResponse.errorCode} FAILED</u></b>
|<code>${targetChatResponse.description}</code>"""
.stripMargin
).parseMode(ParseMode HTML).replyToMessageId(update.message.messageId)
.unsafeExecute
}
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)
.execute
if (!(testSendResponse isOk))
coeur.account exec SendMessage(
SendMessage(
update.message.chat.id,
// language=html
s"""<b><u>${testSendResponse.errorCode}</u> FAILED</b>
|<code>${testSendResponse.description}</code>"""
.stripMargin
).parseMode(ParseMode HTML).replyToMessageId(update.message.messageId)
.unsafeExecute
event.setEventOk
}
private def answer404 (using event: EventEnv): Unit =
coeur.account exec SendSticker(
SendSticker(
event.update.message.chat.id,
TelegramStickers ID_404
).replyToMessageId(event.update.message.messageId)
.unsafeExecute
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.log.LogLevels
import java.io.{PrintWriter, StringWriter}
object Log {
val logger: MornyLoggerBase = MornyLoggerBase(
@ -13,7 +11,7 @@ object Log {
MornyFormatterConsole()
)
)
logger minLevel LogLevels.INFO
logger.minLevel(LogLevels.INFO)
def debug: Boolean = logger.levelSetting.minLevel.level <= LogLevels.DEBUG.level
@ -21,9 +19,4 @@ object Log {
if is then logger.minLevel(LogLevels.ALL)
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 {
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 =>
throw RuntimeException("Cannot read MORNY_PREVIEW_IMAGE_ASCII from assets pack", e)

View File

@ -1,6 +1,6 @@
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.bot.api.{EventListenerManager, MornyCommandManager, MornyQueryManager}
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.time.WatchDog
import cc.sukazyo.cono.morny.util.GivenContext
import cc.sukazyo.cono.morny.util.UseThrowable.toLogString
import com.pengrad.telegrambot.TelegramBot
import com.pengrad.telegrambot.request.GetMe
@ -115,7 +116,7 @@ class MornyCoeur (modules: List[MornyModule])(using val config: MornyConfig)(tes
val externalContext: GivenContext = GivenContext()
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:
|${fmtTable(
("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
logger info "Coeur starting..."
logger `info` "Coeur starting..."
private var initializeContext = GivenContext()
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
logger info s"login as:\n ${config.telegramBotUsername}"
logger `info` s"login as:\n ${config.telegramBotUsername}"
private val __loginResult: LoginResult = login() match
case some: Some[LoginResult] => some.get
case None =>
logger error "Login to bot failed."
System exit -1
logger `error` "Login to bot failed."
System `exit` -1
throw RuntimeException()
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, _) =>
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?
| current tick takes ${f(consumed)} to complete.""".stripMargin
tasks.notifyIt()
@ -238,9 +239,9 @@ class MornyCoeur (modules: List[MornyModule])(using val config: MornyConfig)(tes
///>>> BLOCK START instance configure & startup stage 2
logger info "done initialize."
logger `info` "done initialize."
if testRun then
logger info "done test run, exiting."
logger `info` "done test run, exiting."
this.exit(0, TestRun)
// Coeur Starting Pre
@ -250,10 +251,10 @@ class MornyCoeur (modules: List[MornyModule])(using val config: MornyConfig)(tes
modules.foreach(it => it.onStarting(OnStartingContext(
initializeContext)))
logger info "start http server"
logger `info` "start http server"
val http: HttpServer = _httpServerContext.start
_httpServerContext = null
logger info "start telegram event listening"
logger `info` "start telegram event listening"
import com.pengrad.telegrambot.TelegramException
account.setUpdatesListener(eventManager, (e: TelegramException) => {
@ -267,10 +268,10 @@ class MornyCoeur (modules: List[MornyModule])(using val config: MornyConfig)(tes
if (e.response != null) {
import com.google.gson.GsonBuilder
logger error
logger `error`
s"""Failed get updates: ${e.getMessage}
| server responses:
|${GsonBuilder().setPrettyPrinting().create.toJson(e.response) indent 4}
|${GsonBuilder().setPrettyPrinting().create.toJson(e.response).indent(4)}
|""".stripMargin
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
log += s" caused by: ${current.getClass.getSimpleName}: ${current.getMessage}"
}
logger error Message(log mkString "\n")
logger `error` Message(log mkString "\n")
case e_other =>
logger error
logger `error`
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."))
}
@ -306,17 +307,17 @@ class MornyCoeur (modules: List[MornyModule])(using val config: MornyConfig)(tes
initializeContext)))
if config.commandLoginRefresh then
logger info "resetting telegram command list"
logger `info` "resetting telegram command list"
commands.automaticTGListUpdate()
initializeContext = null
logger info "Coeur start complete."
logger `info` "Coeur start complete."
///<<< BLOCK END instance configure & startup stage 2
def saveDataAll(): Unit = {
modules.foreach(it => it.onRoutineSavingData)
logger notice "done all save action."
logger `notice` "done all save action."
}
private def exitCleanup (): Unit = {
@ -325,9 +326,9 @@ class MornyCoeur (modules: List[MornyModule])(using val config: MornyConfig)(tes
modules.foreach(it => it.onExiting)
account.removeGetUpdatesListener()
logger info "stopped bot update listener"
logger `info` "stopped bot update listener"
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
if config.commandLogoutClear then
@ -335,10 +336,10 @@ class MornyCoeur (modules: List[MornyModule])(using val config: MornyConfig)(tes
modules.foreach(it => it.onExitingPost)
account.shutdown()
logger info "stopped bot account"
logger `info` "stopped bot account"
// Morny Exited
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 =
whileExit_reason = Some(reason)
System exit status
System `exit` status
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_file = config.telegramBotApiServer4File
if (api_bot ne null)
if api_bot endsWith "/" then api_bot = api_bot dropRight 1
if !(api_bot endsWith "/bot") then api_bot += "/bot"
if api_bot `endsWith` "/" then api_bot = api_bot dropRight 1
if !(api_bot `endsWith` "/bot") then api_bot += "/bot"
builder.apiUrl(api_bot)
if (api_file ne null)
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/" then api_file = api_file dropRight 1
if !(api_file `endsWith` "/file/bot") then api_file += "/file/bot"
builder.apiUrl(api_bot)
if ((api_bot ne null) || (api_file ne null))
logger info
logger `info`
s"""Telegram bot api set to:
|- bot: $api_bot
|- file: $api_file"""
@ -374,21 +375,22 @@ class MornyCoeur (modules: List[MornyModule])(using val config: MornyConfig)(tes
val account = builder build
logger info "Trying to login..."
logger `info` "Trying to login..."
boundary[Option[LoginResult]] {
for (i <- 0 to 3) {
if i > 0 then logger info "retrying..."
if i > 0 then logger `info` "retrying..."
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)
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)))
} catch
case r: boundary.Break[Option[LoginResult]] => throw r
case e =>
logger error
s"""${exceptionLog(e)}
logger `error`
s"""${e.toLogString}
|login failed"""
.stripMargin
}

View File

@ -1,10 +1,11 @@
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.util.EpochDateTime.EpochMillis
import cc.sukazyo.cono.morny.util.FileUtils
import cc.sukazyo.cono.morny.BuildConfig
import cc.sukazyo.cono.morny.util.UseThrowable.toLogString
import java.io.IOException
import java.net.URISyntaxException
@ -38,7 +39,7 @@ object MornySystem {
case _: (IOException|URISyntaxException) =>
"<non-jar-runtime>"
case n: NoSuchAlgorithmException =>
logger error exceptionLog(n)
logger `error` n.toLogString
// MornyReport.exception(n, "<coeur-md5/calculation-error>") // todo: will not implemented
"<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 com.pengrad.telegrambot.model.ChatMember.Status
import com.pengrad.telegrambot.TelegramBot
import com.pengrad.telegrambot.model.User
class MornyTrusted (using coeur: MornyCoeur)(using config: MornyConfig) {
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
if userId == config.trustedMaster then true
if user.id == config.trustedMaster then true
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 userId == config.trustedMaster then true
else config.dinnerTrustedReaders contains userId
/** If this user can be trusted to read the dinner messages.
* @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 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 {
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"
@ -54,7 +54,7 @@ object ServerMain {
case "--report-to" => i += 1; config.reportToChat = args(i) toLong
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 "--http-listen-port" | "-hp" =>
@ -67,8 +67,8 @@ object ServerMain {
config.medicationTimerUseTimezone = ZoneOffset.ofHours(args(i) toInt)
case "--medication-notify-times" | "-medt" =>
i += 1
for (u <- args(i) split ",") {
config.medicationNotifyAt add(u toInt)
for (u <- args(i) `split` ",") {
config.medicationNotifyAt `add` (u toInt)
}
case "--auto-cmd-list" | "-ca" => config.commandLoginRefresh = true
@ -87,8 +87,8 @@ object ServerMain {
var propToken: String = null
var propTokenKey: String = null
for (iKey <- MornyConfig.PROP_TOKEN_KEY) {
if ((System getenv iKey) != null) {
propToken = System getenv iKey
if ((System `getenv` iKey) != null) {
propToken = System `getenv` iKey
propTokenKey = iKey
}
}
@ -98,27 +98,27 @@ object ServerMain {
/// 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 (unknownArgs.nonEmpty) logger warn
if (unknownArgs.nonEmpty) logger `warn`
s"""Can't understand arg to some meaning
| ${unknownArgs mkString "\n "}"""
.stripMargin
if (deprecatedArgs.nonEmpty) logger warn
if (deprecatedArgs.nonEmpty) logger `warn`
s"""Those arguments have been deprecated:
| ${deprecatedArgs map((d, n) => s"$d : use $n instead") mkString "\n "}
|""".stripMargin
if (Log debug)
logger warn
logger `warn`
"""Debug log output enabled.
| It may lower your performance, make sure that you are not in production environment."""
.stripMargin
if (mode_echoVersion) {
logger info
logger `info`
s"""Morny Cono Version
|- version :
| Morny ${MornySystem.CODENAME toUpperCase}
@ -148,14 +148,14 @@ object ServerMain {
}
logger info
logger `info`
s"""ServerMain.java Loaded >>>
|- version ${MornySystem.VERSION_FULL}
|- Morny ${MornySystem.CODENAME toUpperCase}
|- <${MornySystem.getJarMD5}> [${MornySystem.CODE_TIMESTAMP}]""".stripMargin
// due to [[MornyFormatterConsole]] will use a localized time, it will output to the log
logger info s"logging time will use time-zone ${tz.getID} ($tz_offset)"
logger `info` s"logging time will use time-zone ${tz.getID} ($tz_offset)"
///
/// Check Coeur arguments
@ -164,10 +164,10 @@ object ServerMain {
if (propToken != null) {
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
MornyCoeur(

View File

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

View File

@ -1,12 +1,9 @@
package cc.sukazyo.cono.morny.core.bot.api
import cc.sukazyo.cono.morny.core.MornyCoeur
import com.pengrad.telegrambot.model.Update
import javax.annotation.Nullable
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.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.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.request.{SendSticker, SetMyCommands}
import com.pengrad.telegrambot.TelegramBot
import scala.collection.mutable
import scala.collection.mutable.ArrayBuffer
@ -17,14 +16,8 @@ import scala.language.postfixOps
object MornyCommandManager:
type CommandMap = mutable.SeqMap[String, ISimpleCommand]
class MornyCommandManager (using coeur: MornyCoeur) {
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)
class MornyCommandManager (using coeur: MornyCoeur) extends SimpleCommandManager {
private given TelegramBot = coeur.account
def execute (using command: InputCommand, event: Update): Boolean = {
if (commands contains command.command)
@ -36,25 +29,28 @@ class MornyCommandManager (using coeur: MornyCoeur) {
private def nonCommandExecutable (using command: InputCommand, event: Update): Boolean = {
if command.target eq null then false
else
coeur.account exec SendSticker(
SendSticker(
event.message.chat.id,
TelegramStickers ID_404
).replyToMessageId(event.message.messageId)
.unsafeExecute
true
}
def automaticTGListUpdate (): Unit = {
val listing = commands_toTelegramList
automaticTGListRemove()
coeur.account exec SetMyCommands(listing:_*)
logger notice
SetMyCommands(listing*)
.unsafeExecute
logger `notice`
s"""automatic updated telegram command list :
|${commandsTelegramList_toString(listing)}""".stripMargin
}
def automaticTGListRemove (): Unit = {
coeur.account exec DeleteMyCommands()
logger notice "cleaned up command list"
DeleteMyCommands()
.unsafeExecute
logger `notice` "cleaned up command list"
}
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]
infix def register (query: ITelegramQuery): Unit =
this.queries += query
def register (queries: ITelegramQuery*): Unit =
this.queries ++= queries
def query (event: Update): List[InlineQueryUnit[_]] = {
val results = ListBuffer[InlineQueryUnit[_]]()
def query (event: Update): List[InlineQueryUnit[?]] = {
val results = ListBuffer[InlineQueryUnit[?]]()
for (instance <- queries) {
val r = instance query event
val r = instance `query` event
if (r != null) results ++= r
}
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.data.TelegramStickers
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.request.{DeleteMessage, GetChatMember, SendSticker}
import com.pengrad.telegrambot.TelegramBot
import scala.language.postfixOps
class DirectMsgClear (using coeur: MornyCoeur) extends ISimpleCommand {
private given TelegramBot = coeur.account
override val name: String = "r"
override val aliases: List[ICommandAlias] = Nil
override def execute (using command: InputCommand, event: Update): Unit = {
logger debug "executing command /r"
if (event.message.replyToMessage == null) return;
logger trace "message is a reply"
if (event.message.replyToMessage.from.id != coeur.userid) return;
logger trace "message replied is from me"
if (System.currentTimeMillis/1000 - event.message.replyToMessage.date > 48*60*60) return;
logger trace "message is not outdated(48 hrs ago)"
logger `debug` "executing command /r"
if (event.message.replyToMessage == null) return
logger `trace` "message is a reply"
if (event.message.replyToMessage.from.id != coeur.userid) return
logger `trace` "message replied is from me"
if (System.currentTimeMillis/1000 - event.message.replyToMessage.date > 48*60*60) return
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:
// it does not work. due to the Telegram Bot API doesn't provide
// nested replyToMessage, so currently the trusted check by
@ -38,22 +40,22 @@ class DirectMsgClear (using coeur: MornyCoeur) extends ISimpleCommand {
if (isTrusted || _isReplyTrusted) {
coeur.account exec DeleteMessage(
DeleteMessage(
event.message.chat.id, event.message.replyToMessage.messageId
)
).unsafeExecute
def _isPrivate: Boolean = event.message.chat.`type` == Chat.Type.Private
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
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,
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.data.TelegramStickers
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.request.SendSticker
import com.pengrad.telegrambot.TelegramBot
import scala.language.postfixOps
class MornyHellos (using coeur: MornyCoeur) {
private given TelegramBot = coeur.account
object On extends ITelegramCommand {
@ -21,10 +23,11 @@ class MornyHellos (using coeur: MornyCoeur) {
override val description: String = "检查是否在线"
override def execute (using command: InputCommand, event: Update): Unit =
coeur.account exec SendSticker(
SendSticker(
event.message.chat.id,
TelegramStickers ID_ONLINE_STATUS_RETURN
).replyToMessageId(event.message.messageId)
.unsafeExecute
}
@ -36,10 +39,11 @@ class MornyHellos (using coeur: MornyCoeur) {
override val description: String = "打招呼"
override def execute (using command: InputCommand, event: Update): Unit =
coeur.account exec SendSticker(
SendSticker(
event.message.chat.id,
TelegramStickers ID_HELLO
).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.data.MornyInformation.{getAboutPic, getMornyAboutLinksHTML}
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.request.ParseMode
import com.pengrad.telegrambot.request.SendPhoto
import scala.language.postfixOps
import com.pengrad.telegrambot.TelegramBot
class MornyInfoOnStart (using coeur: MornyCoeur) extends ISimpleCommand {
private given TelegramBot = coeur.account
override val name: String = "start"
override val aliases: List[ICommandAlias] = Nil
override def execute (using command: InputCommand, event: Update): Unit = {
coeur.account exec new SendPhoto(
SendPhoto(
event.message.chat.id,
getAboutPic
).caption(
@ -32,6 +32,7 @@ class MornyInfoOnStart (using coeur: MornyCoeur) extends ISimpleCommand {
|你可以随时通过 /info 重新获得这些信息"""
.stripMargin
).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.tgapi.formatting.TelegramParseEscape.escapeHtml as h
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.request.ParseMode
import com.pengrad.telegrambot.request.{SendMessage, SendPhoto, SendSticker}
import com.pengrad.telegrambot.TelegramBot
import java.lang.System
import scala.language.postfixOps
// todo: maybe move some utils method outside
class MornyInformation (using coeur: MornyCoeur) extends ITelegramCommand {
private given TelegramBot = coeur.account
private case object Subs {
val STICKERS = "stickers"
@ -43,7 +43,7 @@ class MornyInformation (using coeur: MornyCoeur) extends ITelegramCommand {
val action: String = command.args(0)
action match {
case s if s startsWith Subs.STICKERS => echoStickers
case s if s `startsWith` Subs.STICKERS => echoStickers
case Subs.RUNTIME => echoRuntime
case Subs.VERSION | Subs.VERSION_2 => echoVersion
case Subs.TASKS => echoTasksStatus
@ -54,7 +54,7 @@ class MornyInformation (using coeur: MornyCoeur) extends ITelegramCommand {
}
private def echoInfo (chatId: Long, replyTo: Int): Unit = {
coeur.account exec new SendPhoto(
SendPhoto(
chatId,
getAboutPic
).caption(
@ -64,6 +64,7 @@ class MornyInformation (using coeur: MornyCoeur) extends ITelegramCommand {
|$getMornyAboutLinksHTML"""
.stripMargin
).parseMode(ParseMode HTML).replyToMessageId(replyTo)
.unsafeExecute
}
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 null
} else if (command.args.length == 1) {
if ((command.args(0) startsWith s"${Subs.STICKERS}.") || (command.args(0) startsWith s"${Subs.STICKERS}#")) {
command.args(0) substring Subs.STICKERS.length+1
if ((command.args(0) `startsWith` s"${Subs.STICKERS}.") || (command.args(0) `startsWith` s"${Subs.STICKERS}#")) {
command.args(0) `substring` Subs.STICKERS.length+1
} else null
} else null
if (mid == null) echo404
@ -87,7 +88,7 @@ class MornyInformation (using coeur: MornyCoeur) extends ITelegramCommand {
echoSticker(_key, _file_id)
else {
try {
val sticker = TelegramStickers getById mid
val sticker = TelegramStickers `getById` mid
echoSticker(sticker.getKey, sticker.getValue)
} catch case _: NoSuchFieldException => {
echo404
@ -99,15 +100,15 @@ class MornyInformation (using coeur: MornyCoeur) extends ITelegramCommand {
val send_mid = SendMessage(send_chat, mid)
val send_sticker = SendSticker(send_chat, file_id)
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)
coeur.account exec send_sticker
send_sticker.unsafeExecute
}
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 versionGitHTML = if (MornySystem.GIT_COMMIT nonEmpty) s"git $getVersionGitTagHTML" else ""
coeur.account exec new SendMessage(
SendMessage(
event.message.chat.id,
// language=html
s"""version:
@ -120,11 +121,12 @@ class MornyInformation (using coeur: MornyCoeur) extends ITelegramCommand {
|- <code>${h(formatDate(MornySystem.CODE_TIMESTAMP, 0))} [UTC]</code>
|""".stripMargin
).replyToMessageId(event.message.messageId).parseMode(ParseMode HTML)
.unsafeExecute
}
private[command] def echoRuntime (using event: Update): Unit = {
def sysprop (p: String): String = System.getProperty(p)
coeur.account exec new SendMessage(
SendMessage(
event.message.chat.id,
/* language=html */
s"""system:
@ -148,11 +150,12 @@ class MornyInformation (using coeur: MornyCoeur) extends ITelegramCommand {
|- [<code>${coeur.coeurStartTimestamp}</code>]"""
.stripMargin
).parseMode(ParseMode HTML).replyToMessageId(event.message.messageId)
.unsafeExecute
}
private def echoTasksStatus (using update: Update): Unit = {
// if !coeur.trusted.isTrusted(update.message.from.id) then return;
coeur.account exec SendMessage(
SendMessage(
update.message.chat.id,
// language=html
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>
|""".stripMargin
).parseMode(ParseMode.HTML).replyToMessageId(update.message.messageId)
.unsafeExecute
}
private def echoEventStatistics (using update: Update): Unit = {
coeur.externalContext >> { (reporter: MornyReport) =>
coeur.account exec SendMessage(
SendMessage(
update.message.chat.id,
// language=html
s"""<b>Event Statistics :</b>
|in today
|${reporter.EventStatistics.eventStatisticsHTML}""".stripMargin
).parseMode(ParseMode.HTML).replyToMessageId(update.message.messageId)
.unsafeExecute
} || {
echo404
}
}
private def echo404 (using event: Update): Unit =
coeur.account exec new SendSticker(
SendSticker(
event.message.chat.id,
TelegramStickers ID_404
).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.util.tgapi.InputCommand
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.request.SendSticker
import scala.language.postfixOps
import com.pengrad.telegrambot.TelegramBot
class MornyManagers (using coeur: MornyCoeur) {
private given TelegramBot = coeur.account
object Exit extends ITelegramCommand {
@ -27,22 +27,24 @@ class MornyManagers (using coeur: MornyCoeur) {
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,
TelegramStickers ID_EXIT
).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)
} else {
coeur.account exec SendSticker(
SendSticker(
event.message.chat.id,
TelegramStickers ID_403
).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))
}
@ -62,22 +64,24 @@ class MornyManagers (using coeur: MornyCoeur) {
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.account exec SendSticker(
SendSticker(
event.message.chat.id,
TelegramStickers ID_SAVED
).replyToMessageId(event.message.messageId)
.unsafeExecute
} else {
coeur.account exec SendSticker(
SendSticker(
event.message.chat.id,
TelegramStickers ID_403
).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))
}

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.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.request.AnswerInlineQuery
import com.pengrad.telegrambot.TelegramBot
import scala.collection.mutable.ListBuffer
import scala.language.postfixOps
import scala.reflect.ClassTag
class MornyOnInlineQuery (using queryManager: MornyQueryManager) (using coeur: MornyCoeur) extends EventListener {
private given TelegramBot = coeur.account
override def onInlineQuery (using event: EventEnv): Unit = {
import event.update
val results: List[InlineQueryUnit[_]] = queryManager query update
val results: List[InlineQueryUnit[?]] = queryManager `query` update
var cacheTime = Int.MaxValue
var isPersonal = InlineQueryUnit.defaults.IS_PERSONAL
val resultAnswers = ListBuffer[InlineQueryResult[_]]()
val resultAnswers = ListBuffer[InlineQueryResult[?]]()
for (r <- results) {
if (cacheTime > r.cacheTime) cacheTime = r.cacheTime
if (r isPersonal) isPersonal = true
resultAnswers += r.result
}
if (results isEmpty) return;
if (results isEmpty) return
coeur.account exec AnswerInlineQuery(
update.inlineQuery.id, resultAnswers toArray:_*
AnswerInlineQuery(
update.inlineQuery.id,
(resultAnswers toArray)*
).cacheTime(cacheTime).isPersonal(isPersonal)
.unsafeExecute
event.setEventOk

View File

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

View File

@ -20,7 +20,7 @@ class MornyOnUpdateTimestampOffsetLock (using coeur: MornyCoeur) extends EventLi
override def on (using event: EventEnv): Unit =
event.update.sourceTime match
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
case None =>

View File

@ -1,7 +1,7 @@
package cc.sukazyo.cono.morny.core.http.api
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}
trait HttpService4Api {
@ -13,7 +13,7 @@ trait HttpService4Api {
response.setMornyInternalErrorHeader(
e.getClass.getSimpleName,
e.getMessage,
exceptionLog(e)
e.toLogString
)
def setMornyInternalErrorHeader (
`Morny-Internal-Error-Type`: String,

View File

@ -2,6 +2,8 @@ package cc.sukazyo.cono.morny.core.http.api
trait MornyHttpServerContext {
infix def register4API (service: HttpService4Api): Unit
def register4API (service: HttpService4Api*): Unit
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.http.api.{HttpServer, HttpService4Api, MornyHttpServerContext}
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
@ -13,6 +14,9 @@ class MornyHttpServerContextImpl (using coeur: MornyCoeur) extends MornyHttpServ
private lazy val service_ui = ServiceUI()
override infix def register4API (service: HttpService4Api): Unit =
services_api += service
override def register4API (services: HttpService4Api*): Unit =
services_api ++= services
@ -31,9 +35,9 @@ class MornyHttpServerContextImpl (using coeur: MornyCoeur) extends MornyHttpServ
def errorHandler (t: Throwable, message: =>String): OptionT[IO, Unit] =
OptionT.liftF(IO {
logger error
logger `error`
s"""Unexpected exception occurred on Morny Http Server :
|${exceptionLog(t)}""".stripMargin
|${t.toLogString}""".stripMargin
})
val withErrorHandler = ErrorHandling.Recover.total(
ErrorAction.log(
@ -49,7 +53,7 @@ class MornyHttpServerContextImpl (using coeur: MornyCoeur) extends MornyHttpServ
.resource
val (_server, _shutdown_io) = server.allocated.unsafeRunSync() match
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):
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.log.Log
import java.time.{ZoneId, ZoneOffset}
import java.util.TimeZone
class MornyFormatterConsole extends ILogFormatter {
override def format (log: Log): String =

View File

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

View File

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

View File

@ -20,7 +20,7 @@ object TelegramImages {
@throws[AssetsException]
private def read (): Unit = {
Using ((MornyAssets.pack getResource assetsPath)read) { stream =>
Using ((MornyAssets.pack `getResource` assetsPath)read) { stream =>
try { this.cache = Some(stream.readAllBytes()) }
catch case e: IOException => {
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.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.request.ParseMode
import com.pengrad.telegrambot.request.{GetFile, SendDocument, SendMessage, SendSticker}
import com.pengrad.telegrambot.TelegramBot
import java.io.IOException
import java.net.{URLDecoder, URLEncoder}
import java.util.Base64
import scala.language.postfixOps
/** Provides Telegram Command __`/encrypt`__. */
class Encryptor (using coeur: MornyCoeur) extends ITelegramCommand {
private given TelegramBot = coeur.account
override val name: String = "encrypt"
override val aliases: List[ICommandAlias] = ListedAlias("enc") :: Nil
@ -33,7 +35,7 @@ class Encryptor (using coeur: MornyCoeur) extends ITelegramCommand {
val args = command.args
// 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)
return
@ -45,17 +47,18 @@ class Encryptor (using coeur: MornyCoeur) extends ITelegramCommand {
// so the algorithm will be and must be in the 2nd arg.
/** inner function: is input `arg` means mod-param ''uppercase'' */
def _is_mod_u(arg: String): Boolean =
if (arg equalsIgnoreCase "uppercase") return true
if (arg equalsIgnoreCase "u") return true
if (arg equalsIgnoreCase "upper") return true
if (arg `equalsIgnoreCase` "uppercase") return true
if (arg `equalsIgnoreCase` "u") return true
if (arg `equalsIgnoreCase` "upper") return true
false
val mod_uppercase = if (args.length > 1) {
if (args.length < 3 && _is_mod_u(args(1))) true
else
coeur.account exec SendSticker(
SendSticker(
event.message.chat.id,
TelegramStickers ID_404
).replyToMessageId(event.message.messageId)
.unsafeExecute
return
} else false
@ -73,15 +76,16 @@ class Encryptor (using coeur: MornyCoeur) extends ITelegramCommand {
val asByteArray: Array[Byte] = data
/** inner class: the [[XEncryptable]] implementation of [[String]] data */
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 _r = event.message.replyToMessage
if ((_r ne null) && (_r.document ne null)) {
try {XFile(
coeur.account getFileContent (coeur.account exec GetFile(_r.document.fileId)).file,
GetFile(_r.document.fileId).unsafeExecute
.file.getContent,
_r.document.fileName
)} 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"))
return
} 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.")
import cc.sukazyo.cono.morny.util.UseRandom.rand_id
XFile(
coeur.account getFileContent (coeur.account exec GetFile(_photo_origin.fileId)).file,
GetFile(_photo_origin.fileId).unsafeExecute
.file.getContent,
s"photo$rand_id.png"
)
} catch
case e: IOException =>
//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"))
return
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"))
return
} else if ((_r ne null) && (_r.text ne null)) {
XText(_r.text)
} else {
coeur.account exec SendMessage(
SendMessage(
event.message.chat.id,
"<i><u>null</u></i>"
).parseMode(ParseMode HTML).replyToMessageId(event.message.messageId)
.unsafeExecute
return
}
// END BLOCK: get input
@ -141,14 +147,15 @@ class Encryptor (using coeur: MornyCoeur) extends ITelegramCommand {
EXHash(if mod_uppercase then hashed toUpperCase else hashed)
//noinspection UnitMethodIsParameterless
def echo_unsupported: Unit =
coeur.account exec SendSticker(
SendSticker(
event.message.chat.id,
TelegramStickers ID_404
).replyToMessageId(event.message.messageId)
.unsafeExecute
val result: EXHash|EXFile|EXText = args(0) match
case "base64" | "b64" | "base64url" | "base64u" | "b64u" =>
val _tool_b64 =
if args(0) contains "u" then Base64.getUrlEncoder
if args(0) `contains` "u" then Base64.getUrlEncoder
else Base64.getEncoder
genResult_encrypt(
input,
@ -157,7 +164,7 @@ class Encryptor (using coeur: MornyCoeur) extends ITelegramCommand {
)
case "base64decode" | "base64d" | "b64d" | "base64url-decode" | "base64ud" | "b64ud" =>
val _tool_b64d =
if args(0) contains "u" then Base64.getUrlDecoder
if args(0) `contains` "u" then Base64.getUrlDecoder
else Base64.getDecoder
try { genResult_encrypt(
input,
@ -190,17 +197,19 @@ class Encryptor (using coeur: MornyCoeur) extends ITelegramCommand {
// output
result match
case _file: EXFile =>
coeur.account exec SendDocument(
SendDocument(
event.message.chat.id,
_file.result
).fileName(_file.resultName).replyToMessageId(event.message.messageId)
.unsafeExecute
case _text: EXTextLike =>
import cc.sukazyo.cono.morny.util.tgapi.formatting.TelegramParseEscape.escapeHtml as h
coeur.account exec SendMessage(
SendMessage(
event.message.chat.id,
// language=html
s"<pre><code>${h(_text.text)}</code></pre>"
).parseMode(ParseMode HTML).replyToMessageId(event.message.messageId)
.unsafeExecute
}
@ -231,7 +240,7 @@ class Encryptor (using coeur: MornyCoeur) extends ITelegramCommand {
* </blockquote>
*/
private def echoHelp(chat: Long, replyTo: Int): Unit =
coeur.account exec SendMessage(
SendMessage(
chat,
s"""<b><u>base64</u></b>, b64
|<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>"""
.stripMargin
).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.bot.api.{ICommandAlias, ITelegramCommand}
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.request.ParseMode
import com.pengrad.telegrambot.request.SendMessage
import com.pengrad.telegrambot.TelegramBot
import scala.language.postfixOps
class BotCommand (using coeur: MornyCoeur) {
private given TelegramBot = coeur.account
private enum Subs (val cmd: String):
case IP extends Subs("ip")
@ -35,18 +37,20 @@ class BotCommand (using coeur: MornyCoeur) {
if (command.args isEmpty)
if event.message.replyToMessage eq null then null else event.message.replyToMessage.text
else if (command.args.length > 1)
coeur.account exec SendMessage(
SendMessage(
event.message.chat.id,
"[Unavailable] Too much arguments."
).replyToMessageId(event.message.messageId)
.unsafeExecute
return
else command.args(0)
if (target eq null)
coeur.account exec new SendMessage(
SendMessage(
event.message.chat.id,
"[Unavailable] No ip defined."
).replyToMessageId(event.message.messageId)
.unsafeExecute
return;
@ -58,20 +62,22 @@ class BotCommand (using coeur: MornyCoeur) {
case Subs.WHOIS.cmd => IP186QueryHandler.query_whoisPretty(target)
case _ => throw IllegalArgumentException(s"Unknown 186-IP query method ${command.command}")
coeur.account exec SendMessage(
SendMessage(
event.message.chat.id,
s"""${h(response.url)}
|<code>${h(response.body)}</code>"""
.stripMargin
).parseMode(ParseMode HTML).replyToMessageId(event.message.messageId)
.unsafeExecute
} catch case e: Exception =>
coeur.account exec new SendMessage(
SendMessage(
event.message().chat().id(),
s"""[Exception] in query:
|<code>${h(e.getMessage)}</code>"""
.stripMargin
).parseMode(ParseMode.HTML).replyToMessageId(event.message().messageId())
.unsafeExecute
}

View File

@ -28,7 +28,7 @@ object IP186QueryHandler {
@throws[IOException]
def query_whoisPretty (domain: String): IP186Response =
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]
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.medication_timer.MedicationTimer.calcNextRoutineTimestamp
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.EpochDateTime.EpochMillis
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.request.{EditMessageText, SendMessage}
import com.pengrad.telegrambot.response.SendResponse
import com.pengrad.telegrambot.TelegramBot
import java.time.{Instant, ZonedDateTime, ZoneOffset}
import scala.collection.mutable.ArrayBuffer
import scala.language.implicitConversions
class MedicationTimer (using coeur: MornyCoeur) {
private given TelegramBot = coeur.account
private val NOTIFY_MESSAGE = "🍥⏲"
private val DAEMON_THREAD_NAME_DEF = "MedicationTimer"
@ -36,7 +38,7 @@ class MedicationTimer (using coeur: MornyCoeur) {
def calcNextSendTime: EpochMillis =
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
override def firstRoutineTimeMillis: EpochMillis =
@ -47,24 +49,24 @@ class MedicationTimer (using coeur: MornyCoeur) {
override def main: Unit = {
sendNotification()
logger info "medication notify sent."
logger `info` "medication notify sent."
}
}
def start(): Unit =
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;
coeur.tasks ++ scheduleTask
logger notice "Medication Timer started."
logger `notice` "Medication Timer started."
def stop(): Unit =
coeur.tasks % scheduleTask
logger notice "Medication Timer stopped."
logger `notice` "Medication Timer stopped."
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)
else lastNotify_messageId = None
}
@ -76,11 +78,11 @@ class MedicationTimer (using coeur: MornyCoeur) {
val entities = ArrayBuffer.empty[MessageEntity]
if edited.entities ne null then entities ++= edited.entities
entities += MessageEntity(MessageEntity.Type.italic, edited.text.length + "\n-- ".length, editTime.length)
coeur.account exec EditMessageText(
EditMessageText(
notify_toChat,
edited.messageId,
edited.text + s"\n-- $editTime --"
).entities(entities toArray:_*)
).entities((entities toArray)*).unsafeExecute
lastNotify_messageId = None
true
}
@ -105,7 +107,7 @@ object MedicationTimer {
}))
.instance
).nextExecution(
ZonedDateTime ofInstant (Instant ofEpochMilli baseTimeMillis, zone.normalized)
ZonedDateTime `ofInstant` (Instant `ofEpochMilli` baseTimeMillis, zone.normalized)
).get.toInstant.toEpochMilli
}

View File

@ -27,7 +27,7 @@ class ModuleMedicationTimer extends MornyInternalModule {
externalContext >> { (instance: MedicationTimer) =>
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) =>
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) =>
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 {
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 =
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.util.tgapi.formatting.TelegramFormatter.*
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.request.ParseMode
import com.pengrad.telegrambot.request.SendMessage
import com.pengrad.telegrambot.TelegramBot
class MornyOldJrrp (using coeur: MornyCoeur) extends ITelegramCommand {
private given TelegramBot = coeur.account
override val name: String = "jrrp"
override val aliases: List[ICommandAlias] = Nil
@ -26,11 +28,12 @@ class MornyOldJrrp (using coeur: MornyCoeur) extends ITelegramCommand {
case _ => "..."
import cc.sukazyo.cono.morny.util.tgapi.formatting.TelegramParseEscape.escapeHtml as h
coeur.account exec SendMessage(
SendMessage(
event.message.chat.id,
// language=html
f"${user.fullnameRefHTML} 在(utc的)今天的运气指数是———— <code>$jrrp%.2f%%</code> ${h(ending)}"
).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.bot.api.{ICommandAlias, ISimpleCommand}
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.request.ParseMode
import com.pengrad.telegrambot.request.SendMessage
import com.pengrad.telegrambot.TelegramBot
import scala.language.postfixOps
class Testing (using coeur: MornyCoeur) extends ISimpleCommand {
private given TelegramBot = coeur.account
override val name: String = "test"
override val aliases: List[ICommandAlias] = Nil
override def execute (using command: InputCommand, event: Update): Unit = {
coeur.account exec new SendMessage(
SendMessage(
event.message.chat.id,
// language=html
"<b>Just</b> a TEST command."
).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.util.tgapi.formatting.TelegramParseEscape.escapeHtml as h
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.request.ParseMode
import com.pengrad.telegrambot.request.{SendMessage, SendSticker}
import com.pengrad.telegrambot.TelegramBot
import sttp.client3.{HttpError, SttpClientException}
import scala.language.postfixOps
class CommandNbnhhsh (using coeur: MornyCoeur) extends ITelegramCommand {
private given TelegramBot = coeur.account
private val NBNHHSH_RESULT_HEAD_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)
event.message.replyToMessage.text
else
coeur.account exec SendSticker(
SendSticker(
event.message.chat.id,
TelegramStickers ID_404
).replyToMessageId(event.message.messageId)
.unsafeExecute
return;
try {
val queryResp = NbnhhshQuery sendGuess queryTarget
val queryResp = NbnhhshQuery `sendGuess` queryTarget
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) {
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_inputting = (_word.inputting ne null) && (_word.inputting nonEmpty)
if (_use_trans || _use_inputting)
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)
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)
logger trace s"**used [${_word.name}] inputting"
logger `trace` s"**used [${_word.name}] inputting"
if (_use_trans)
message += '\n'
message ++= " maybe:"
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>"
logger trace s"**done"
logger `trace` s"**done"
}
coeur.account exec SendMessage(
SendMessage(
event.message.chat.id,
message toString
).parseMode(ParseMode HTML).replyToMessageId(event.message.messageId)
.unsafeExecute
} catch case e: (HttpError[_] | SttpClientException) => {
coeur.account exec SendMessage(
SendMessage(
event.message.chat.id,
s"""[Exception] in query:
|${h(e.getMessage)}
|""".stripMargin
)
).unsafeExecute
}
}

View File

@ -20,7 +20,7 @@ object NbnhhshQuery {
private val httpClient = OkHttpSyncBackend()
@throws[HttpError[_]|SttpClientException]
@throws[HttpError[?]|SttpClientException]
def sendGuess (text: String): GuessResult = {
case class GuessRequest (text: String)
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.bot.api.{EventEnv, EventListener}
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.TelegramBot
import scala.language.postfixOps
import scala.util.boundary
import scala.util.boundary.break
class OnQuestionMarkReply (using coeur: MornyCoeur) extends EventListener {
private given TelegramBot = coeur.account
override def onMessage (using event: EventEnv): Unit = {
import event.update
@ -21,9 +23,10 @@ class OnQuestionMarkReply (using coeur: MornyCoeur) extends EventListener {
if (1 over 8) chance_is false then return;
if !isAllMessageMark(using update.message.text) then return;
coeur.account exec SendMessage(
SendMessage(
update.message.chat.id, update.message.text
).replyToMessageId(update.message.messageId)
.unsafeExecute
event.setEventOk
}
@ -40,7 +43,7 @@ object OnQuestionMarkReply {
boundary[Boolean] {
for (c <- text)
if !(QUESTION_MARKS contains c) then
boundary break false
break(false)
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.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.response.SendResponse
import com.pengrad.telegrambot.TelegramBot
import scala.language.postfixOps
class OnUserRandom (using coeur: MornyCoeur) {
private given TelegramBot = coeur.account
object RandomSelect extends EventListener {
@ -19,10 +20,10 @@ class OnUserRandom (using coeur: MornyCoeur) {
import event.update
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
val query = update.message.text substring 1
val query = update.message.text `substring` 1
val result: String | Null = query match
case USER_OR_QUERY(_con1, _con2) =>
if rand_half then _con1 else _con2
@ -34,10 +35,11 @@ class OnUserRandom (using coeur: MornyCoeur) {
if result == null then return;
coeur.account exec SendMessage(
SendMessage(
update.message.chat.id,
result
).replyToMessageId(update.message.messageId)
.unsafeExecute
event.setEventOk
}
@ -58,14 +60,15 @@ class OnUserRandom (using coeur: MornyCoeur) {
var result: String|Null = null
import cc.sukazyo.cono.morny.util.UseRandom.rand_half
for (k <- keywords)
if update.message.text endsWith k then
if update.message.text `endsWith` k then
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,
result
).replyToMessageId(update.message.messageId)
.unsafeExecute
event.setEventOk
}

View File

@ -27,10 +27,10 @@ class Module extends MornyInternalModule {
import cxt.*
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
} || {
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) =>
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) =>
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
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.data.MornyInformation.getVersionAllFullTagHTML
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.formatting.TelegramFormatter.*
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.schedule.CronTask
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.ConvertByteHex.toHex
import cc.sukazyo.cono.morny.util.UseThrowable.toLogString
import com.cronutils.builder.CronBuilder
import com.cronutils.model.Cron
import com.cronutils.model.definition.CronDefinitionBuilder
@ -30,25 +31,25 @@ class MornyReport (using coeur: MornyCoeur) {
private val enabled = coeur.config.reportToChat != -1
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 = {
if !enabled then return
try {
coeur.account exec report
report.unsafeExecute(using coeur.account)
} catch case e: EventRuntimeException => {
import EventRuntimeException.*
e match
case e: ActionFailed =>
logger warn
logger `warn`
s"""cannot execute report to telegram:
|${exceptionLog(e) indent 4}
|${e.toLogString `indent` 4}
| tg-api response:
|${(e.response toString) indent 4}""".stripMargin
|${(e.response toString) `indent` 4}""".stripMargin
case e: ClientFailed =>
logger error
logger `error`
s"""failed when report to telegram:
|${exceptionLog(e.getCause) indent 4}
|${e.getCause.toLogString `indent` 4}
|""".stripMargin
}
}
@ -69,7 +70,7 @@ class MornyReport (using coeur: MornyCoeur) {
// language=html
s"""<b>▌Coeur Unexpected Exception </b>
|${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
).parseMode(ParseMode HTML))
}
@ -120,9 +121,9 @@ class MornyReport (using coeur: MornyCoeur) {
case e: (IllegalAccessException|IllegalArgumentException|NullPointerException) =>
// language=html
echo ++= s": <i>${h("<read-error>")}</i>"
logger error
logger `error`
s"""error while reading config field ${field.getName}
|${exceptionLog(e)}""".stripMargin
|${e.toLogString}""".stripMargin
exception(e, s"error while reading config field ${field.getName}")
echo ++= "\n"
}
@ -224,14 +225,14 @@ class MornyReport (using coeur: MornyCoeur) {
case State.OK(from) =>
val timeUsed = EventTimeUsed(System.currentTimeMillis - event.timeStartup)
givenCxt << timeUsed
logger debug
logger `debug`
s"""event done with OK
| with time consumed ${timeUsed.it}ms
| by $from""".stripMargin
runningTime ++ timeUsed.it
case State.CANCELED(from) =>
eventCanceled += 1
logger debug
logger `debug`
s"""event done with CANCELED"
| by $from""".stripMargin
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.TelegramParseEscape.escapeHtml as h
import cc.sukazyo.cono.morny.util.UniversalCommand
import cc.sukazyo.cono.morny.util.tgapi.TelegramExtensions.Bot.exec
import com.pengrad.telegrambot.model.Update
import cc.sukazyo.cono.morny.util.tgapi.TelegramExtensions.Requests.unsafeExecute
import com.pengrad.telegrambot.model.request.ParseMode
import com.pengrad.telegrambot.request.SendMessage
import com.pengrad.telegrambot.TelegramBot
import scala.language.postfixOps
class OnUserSlashAction (using coeur: MornyCoeur) extends EventListener {
private given TelegramBot = coeur.account
private val TG_FORMAT = "^\\w+(@\\w+)?$"r
@ -22,7 +23,7 @@ class OnUserSlashAction (using coeur: MornyCoeur) extends EventListener {
val text = update.message.text
if text == null then return;
if (text startsWith "/") {
if (text `startsWith` "/") {
// there has to be some special conditions for DP7
// 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.
val actions = UniversalCommand.Lossy(text)
actions(0) = actions(0) substring 1
actions(0) = actions(0) `substring` 1
actions(0)
@ -42,7 +43,7 @@ class OnUserSlashAction (using coeur: MornyCoeur) extends EventListener {
case TG_FORMAT(_) =>
return;
// ignore Path link
case x if x contains "/" => return;
case x if x `contains` "/" => return;
case _ =>
val isHardParse = actions(0) isBlank
@ -52,7 +53,7 @@ class OnUserSlashAction (using coeur: MornyCoeur) extends EventListener {
val hasObject = actions.length != hp_len(1)
val v_object =
if hasObject then
actions slice(hp_len(1), actions.length) mkString " "
actions `slice` (hp_len(1), actions.length) `mkString` " "
else ""
val origin = update.message
val target =
@ -60,7 +61,7 @@ class OnUserSlashAction (using coeur: MornyCoeur) extends EventListener {
origin
else update.message.replyToMessage
coeur.account exec SendMessage(
SendMessage(
update.message.chat.id,
"%s %s%s %s %s!".format(
origin.sender_firstnameRefHTML,
@ -71,6 +72,7 @@ class OnUserSlashAction (using coeur: MornyCoeur) extends EventListener {
if hasObject then h(v_object+" ") else ""
)
).parseMode(ParseMode HTML).replyToMessageId(update.message.messageId)
.unsafeExecute
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.social_share.api.SocialContent.{SocialMedia, SocialMediaType, SocialMediaWithUrl}
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 com.pengrad.telegrambot.model.request.*
import com.pengrad.telegrambot.request.{SendMediaGroup, SendMessage}
import com.pengrad.telegrambot.TelegramBot
/** Model of social networks' status. for example twitter tweet or
* weibo status.
@ -44,18 +45,19 @@ case class SocialContent (
case _ => orElse
def outputToTelegram (using replyChat: Long, replyToMessage: Int)(using coeur: MornyCoeur): Unit = {
given TelegramBot = coeur.account
if medias isEmpty then
coeur.account exec
SendMessage(replyChat, text_html)
.parseMode(ParseMode.HTML)
.replyToMessageId(replyToMessage)
SendMessage(replyChat, text_html)
.parseMode(ParseMode.HTML)
.replyToMessageId(replyToMessage)
.unsafeExecute
else
val mediaGroup = medias.map(f => f.genTelegramInputMedia)
mediaGroup.head.caption(text_html)
mediaGroup.head.parseMode(ParseMode.HTML)
coeur.account exec
SendMediaGroup(replyChat, mediaGroup: _*)
.replyToMessageId(replyToMessage)
SendMediaGroup(replyChat, mediaGroup*)
.replyToMessageId(replyToMessage)
.unsafeExecute
}
def genInlineQueryResults (using id_head: String, id_param: Any, name: String): List[InlineQueryUnit[?]] = {
@ -99,13 +101,13 @@ object SocialContent {
def genTelegramInputMedia: InputMedia[?]
}
case class SocialMediaWithUrl (url: String)(t: SocialMediaType) extends SocialMedia(t) {
override def genTelegramInputMedia: InputMedia[_] =
override def genTelegramInputMedia: InputMedia[?] =
t match
case Photo => InputMediaPhoto(url)
case Video => InputMediaVideo(url)
}
case class SocialMediaWithBytesData (data: Array[Byte])(t: SocialMediaType) extends SocialMedia(t) {
override def genTelegramInputMedia: InputMedia[_] =
override def genTelegramInputMedia: InputMedia[?] =
t match
case Photo => InputMediaPhoto(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.social_share.event.OnGetSocial
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.request.SendSticker
import com.pengrad.telegrambot.TelegramBot
class GetSocial (using coeur: MornyCoeur) extends ITelegramCommand {
private given TelegramBot = coeur.account
override val name: String = "get"
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 = {
def do404 (): Unit =
coeur.account exec SendSticker(
SendSticker(
event.message.chat.id,
TelegramStickers.ID_404
).replyToMessageId(event.message.messageId())
.unsafeExecute
val content =
if command.args.length > 0 then

View File

@ -1,6 +1,6 @@
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.bot.api.{EventEnv, EventListener}
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.event.OnGetSocial.tryFetchSocial
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.Requests.unsafeExecute
import cc.sukazyo.cono.morny.util.UseThrowable.toLogString
import com.pengrad.telegrambot.model.Chat
import com.pengrad.telegrambot.model.request.ParseMode
import com.pengrad.telegrambot.request.{SendMessage, SendSticker}
import com.pengrad.telegrambot.TelegramBot
class OnGetSocial (using coeur: MornyCoeur) extends EventListener {
@ -80,36 +82,39 @@ object OnGetSocial {
val api = FXApi.Fetch.status(Some(url.screenName), url.statusId)
SocialTwitterParser.parseFXTweet(api).outputToTelegram
} catch case e: (SttpClientException | ParsingFailure | DecodingFailure) =>
coeur.account exec SendSticker(
SendSticker(
replyChat,
TelegramStickers.ID_NETWORK_ERR
).replyToMessageId(replyToMessage)
logger error
"Error on requesting FixTweet API\n" + exceptionLog(e)
).replyToMessageId(replyToMessage).unsafeExecute(using coeur.account)
logger `error`
"Error on requesting FixTweet API\n" + e.toLogString
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 =
import cc.sukazyo.cono.morny.social_share.external.weibo.MApi
import io.circe.{DecodingFailure, ParsingFailure}
import sttp.client3.{HttpError, SttpClientException}
given TelegramBot = coeur.account
try {
val api = MApi.Fetch.statuses_show(url.id)
SocialWeiboParser.parseMStatus(api).outputToTelegram
} catch
case e: HttpError[?] =>
coeur.account exec SendMessage(
SendMessage(
replyChat,
// language=html
s"""Weibo Request Error <code>${e.statusCode}</code>
|<pre><code>${e.body}</code></pre>""".stripMargin
).replyToMessageId(replyToMessage).parseMode(ParseMode.HTML)
.unsafeExecute
case e: (SttpClientException | ParsingFailure | DecodingFailure) =>
coeur.account exec SendSticker(
SendSticker(
replyChat,
TelegramStickers.ID_NETWORK_ERR
).replyToMessageId(replyToMessage)
logger error
"Error on requesting Weibo m.API\n" + exceptionLog(e)
.unsafeExecute
logger `error`
"Error on requesting Weibo m.API\n" + e.toLogString
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 = {
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) {
import Math.{floor, pow}
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")
else if uri.pathSegments.segments.size < 1 then
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)")
try {

View File

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

View File

@ -1,10 +1,11 @@
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.bot.api.{InlineQueryUnit, ITelegramQuery}
import cc.sukazyo.cono.morny.reporter.MornyReport
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.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 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 isBlank) return null
@ -36,7 +37,7 @@ class ShareToolBilibili (using coeur: MornyCoeur) extends ITelegramQuery {
case _: IllegalArgumentException =>
return null;
case e: IllegalStateException =>
logger error exceptionLog(e)
logger `error` e.toLogString
coeur.externalContext.consume[MornyReport](_.exception(e))
return null;

View File

@ -9,14 +9,14 @@ import com.pengrad.telegrambot.model.Update
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 query =
_queryRaw.trim match
case _startsWithTag if _startsWithTag startsWith "get " =>
case _startsWithTag if _startsWithTag `startsWith` "get " =>
(_startsWithTag drop 4)trim
case _endsWithTag if _endsWithTag endsWith " get" =>
case _endsWithTag if _endsWithTag `endsWith` " get" =>
(_endsWithTag dropRight 4)trim
case _ => return null

View File

@ -8,7 +8,6 @@ import com.pengrad.telegrambot.model.Update
import com.pengrad.telegrambot.model.request.InlineQueryResultArticle
import scala.language.postfixOps
import scala.util.matching.Regex
class ShareToolTwitter extends ITelegramQuery {
@ -17,7 +16,7 @@ class ShareToolTwitter extends ITelegramQuery {
private val TITLE_FX = "[tweet] Share as Fix-Tweet"
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

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.MornyCoeur
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.TelegramBot
import org.http4s.{HttpRoutes, MediaType}
import org.http4s.dsl.io.*
import org.http4s.headers.`Content-Type`
@ -17,10 +20,11 @@ class StickerService (using coeur: MornyCoeur) extends HttpService4Api {
case GET -> Root / "sticker" / "id" / id =>
try {
val response = coeur.account execute GetFile(id)
val response = GetFile(id).execute(using coeur.account)
if response.isOk then
try {
val file = coeur.account getFileContent response.file
given TelegramBot = coeur.account
val file = response.file.getContent
Ok(file)
} catch {
case e: IOException =>

View File

@ -12,7 +12,7 @@ class InlineRawText extends ITelegramQuery {
private val ID_PREFIX = "[morny/r/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

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.data.TelegramStickers
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.request.SendSticker
import com.pengrad.telegrambot.TelegramBot
import scala.language.postfixOps
class CommandEventHack (using hacker: EventHacker)(using coeur: MornyCoeur) extends ITelegramCommand {
private given TelegramBot = coeur.account
override val name: String = "event_hack"
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 ""
def done_ok =
coeur.account exec SendSticker(
SendSticker(
event.message.chat.id,
TelegramStickers ID_WAITING
).replyToMessageId(event.message.messageId)
.unsafeExecute
def done_forbiddenForAny =
coeur.account exec SendSticker(
SendSticker(
event.message.chat.id,
TelegramStickers ID_403
).replyToMessageId(event.message.messageId)
.unsafeExecute
def doRegister (t: HackType): Unit =
registerHack(
@ -43,7 +47,7 @@ class CommandEventHack (using hacker: EventHacker)(using coeur: MornyCoeur) exte
)
x_mode match
case "any" =>
if (coeur.trusted isTrusted event.message.from.id)
if (coeur.trusted isTrust event.message.from)
doRegister(HackType ANY)
done_ok
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.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.pengrad.telegrambot.model.Update
import com.pengrad.telegrambot.model.request.ParseMode
import com.pengrad.telegrambot.request.SendMessage
import com.pengrad.telegrambot.TelegramBot
import scala.collection.mutable
class EventHacker (using coeur: MornyCoeur) {
private given TelegramBot = coeur.account
private case class Hacker (from_chat: Long, from_message: Long):
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.ANY => "[[]]"
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 = {
logger debug s"got event signed {{$chat}}(($fromUser))"
logger `debug` s"got event signed {{$chat}}(($fromUser))"
val x: Hacker =
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 "[[]]" then (hackers remove "[[]]") get
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
coeur.account exec SendMessage(
SendMessage(
x.from_chat,
// language=html
s"<pre><code class='language-json'>${h(GsonBuilder().setPrettyPrinting().create.toJson(update))}</code></pre>"
).parseMode(ParseMode HTML).replyToMessageId(x.from_message toInt)
.unsafeExecute
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.util.tgapi.{InputCommand, Standardize}
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.request.ParseMode
import com.pengrad.telegrambot.request.{GetChatMember, SendMessage}
import scala.language.postfixOps
import com.pengrad.telegrambot.TelegramBot
class CommandGetUser (using coeur: MornyCoeur) extends ITelegramCommand {
private given TelegramBot = coeur.account
override val name: String = "user"
override val aliases: List[ICommandAlias] = Nil
@ -23,46 +23,51 @@ class CommandGetUser (using coeur: MornyCoeur) extends ITelegramCommand {
val args = command.args
if (args.length > 1)
coeur.account exec SendMessage(
SendMessage(
event.message.chat.id,
"[Unavailable] Too much arguments."
).replyToMessageId(event.message.messageId)
.unsafeExecute
return
val userId: Long =
if (args nonEmpty) {
try args(0) toLong
catch case e: NumberFormatException =>
coeur.account exec SendMessage(
SendMessage(
event.message.chat.id,
s"[Unavailable] ${e.getMessage}"
).replyToMessageId(event.message.messageId)
.unsafeExecute
return
} else if (event.message.replyToMessage eq null) event.message.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)
coeur.account exec SendMessage(
SendMessage(
event.message.chat.id,
"[Unavailable] user not found."
).replyToMessageId(event.message.messageId)
.unsafeExecute
return
val user = response.chatMember.user
if (user.id == Standardize.CHANNEL_SPEAKER_MAGIC_ID)
coeur.account exec SendMessage(
SendMessage(
event.message.chat.id,
"<code>$__channel_identify</code>"
).replyToMessageId(event.message.messageId)
.unsafeExecute
return;
coeur.account exec SendMessage(
SendMessage(
event.message.chat.id,
TelegramUserInformation getFormattedInformation user
TelegramUserInformation.getFormattedInformation(user)
).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 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
@ -21,7 +21,7 @@ class InlineMyInformation extends ITelegramQuery {
InlineQueryUnit(InlineQueryResultArticle(
inlineQueryId(ID_PREFIX), TITLE,
new InputTextMessageContent(
TelegramUserInformation getFormattedInformation event.inlineQuery.from
TelegramUserInformation.getFormattedInformation(event.inlineQuery.from)
).parseMode(ParseMode HTML)
)).isPersonal(true).cacheTime(10)
)

View File

@ -10,17 +10,17 @@ class BotEventUniMeowTrigger (using commands: UniMeowCommandManager) extends Eve
import event.*
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) {
logger trace s"checking uni-meow $name"
for ((name, command_instance) <- commands.getCommands) {
logger `trace` s"checking uni-meow $name"
if (name == input.command)
logger trace "checked"
logger `trace` "checked"
command_instance.execute(using input, event.update)
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
import cc.sukazyo.cono.morny.core.bot.api.ISimpleCommand
import cc.sukazyo.cono.morny.core.bot.api.MornyCommandManager.CommandMap
import cc.sukazyo.cono.morny.core.bot.api.SimpleCommandManager
import scala.collection.mutable
class UniMeowCommandManager {
class UniMeowCommandManager extends SimpleCommandManager{
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)
protected[uni_meow] def getCommands: this.commands.type = this.commands
}

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.bot.api.{ICommandAlias, ISimpleCommand}
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.request.SendMessage
import com.pengrad.telegrambot.TelegramBot
//noinspection NonAsciiCharacters
class (using coeur: MornyCoeur) {
private given TelegramBot = coeur.account
object Chuang extends ISimpleCommand {
@ -27,12 +29,13 @@ class 创 (using coeur: MornyCoeur) {
return;
val chuangText = .chuangText(text)
coeur.account exec SendMessage(
SendMessage(
event.message.chat.id,
chuangText
).entities(
MessageEntity(MessageEntity.Type.pre, 0, chuangText.length)
).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.data.TelegramStickers
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.request.ParseMode
import com.pengrad.telegrambot.request.{SendMessage, SendSticker}
import com.pengrad.telegrambot.TelegramBot
import javax.swing.text.html.HTML
import scala.annotation.unused
@ -15,6 +16,7 @@ import scala.language.postfixOps
//noinspection NonAsciiCharacters
class 喵呜 (using coeur: MornyCoeur) {
private given TelegramBot = coeur.account
object 抱抱 extends ISimpleCommand {
override val name: String = "抱抱"
@ -50,20 +52,22 @@ class 喵呜 (using coeur: MornyCoeur) {
override val paramRule: String = ""
override val description: String = "抽取一个神秘盒子"
override def execute (using command: InputCommand, event: Update): Unit = {
coeur.account exec new SendSticker(
SendSticker(
event.message.chat.id,
TelegramStickers ID_PROGYNOVA
).replyToMessageId(event.message.messageId)
.unsafeExecute
}
}
private def replyingSet (whileRec: String, whileNew: String)(using event: Update): Unit = {
val isNew = event.message.replyToMessage == null
val target = if (isNew) event.message else event.message.replyToMessage
coeur.account exec new SendMessage(
SendMessage(
event.message.chat.id,
if (isNew) whileNew else whileRec
).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.UseMath.over
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.request.SendMessage
import com.pengrad.telegrambot.TelegramBot
//noinspection NonAsciiCharacters
class 私わね (using coeur: MornyCoeur) extends ISimpleCommand {
private given TelegramBot = coeur.account
override val name: String = "me"
override val aliases: List[ICommandAlias] = Nil
@ -19,10 +21,11 @@ class 私わね (using coeur: MornyCoeur) extends ISimpleCommand {
if ((1 over 521) chance_is true) {
val text = "/打假"
coeur.account exec new SendMessage(
SendMessage(
event.message.chat.id,
text
).replyToMessageId(event.message.messageId)
.unsafeExecute
}
}

View File

@ -37,7 +37,7 @@ object CommonEncrypt {
private def hash (data: Bin)(using algorithm: String): Bin =
try {
MessageDigest.getInstance(algorithm) digest data
MessageDigest.getInstance(algorithm) `digest` data
} catch case n: NoSuchAlgorithmException =>
throw IllegalStateException(n)
@ -47,7 +47,7 @@ object CommonEncrypt {
*
* $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`. */
def SHA1 (data: Bin): Bin = hash(data)(using "sha1")
@ -55,7 +55,7 @@ object CommonEncrypt {
*
* $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`. */
def SHA256 (data: Bin): Bin = hash(data)(using "sha256")
@ -63,7 +63,7 @@ object CommonEncrypt {
*
* $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`. */
def SHA512 (data: Bin): Bin = hash(data)(using "sha512")
@ -71,7 +71,7 @@ object CommonEncrypt {
*
* $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.
*
@ -89,10 +89,10 @@ object CommonEncrypt {
* @return the file fullname removed the base64 file extension.
*/
def lint_base64FileName (encrypted: String): String = encrypted match
case i if i endsWith ".b64" => i dropRight ".b64".length
case ix if ix endsWith ".b64.txt" => ix dropRight ".b64.txt".length
case l if l endsWith ".base64" => l dropRight ".base64".length
case lx if lx endsWith ".base64.txt" => lx dropRight ".base64.txt".length
case i if i `endsWith` ".b64" => i dropRight ".b64".length
case ix if ix `endsWith` ".b64.txt" => ix dropRight ".b64.txt".length
case l if l `endsWith` ".base64" => l dropRight ".base64".length
case lx if lx `endsWith` ".base64.txt" => lx dropRight ".base64.txt".length
case u => u
/** Hash a [[Long]] id to [[Bin]] using [[MD5]] algorithm.

View File

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

View File

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

View File

@ -1,26 +1,17 @@
package cc.sukazyo.cono.morny.util
import cc.sukazyo.cono.morny.util.GivenContext.{ContextNotGivenException, FolderClass, RequestItemClass}
import cc.sukazyo.messiva.utils.StackUtils
import scala.annotation.targetName
import scala.collection.mutable
import scala.reflect.{classTag, ClassTag}
import scala.util.boundary
object GivenContext {
case class FolderClass (clazz: Option[Class[?]])
object FolderClass:
def default: FolderClass = FolderClass(None)
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
val requestItemClass: RequestItemClass,
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
* e.printStackTrace()
* }}}
*
* TODO: Tests
*
* @since 2.0.0
*/
//noinspection NoTargetNameAnnotationForOperatorLikeDefinition
class GivenContext {
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 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)
def << [T: ClassTag] (is: (Class[T], T)): Unit =
val (_, i) = is
@ -91,7 +87,7 @@ class GivenContext {
variables get t.clazz match
case Some(i) => Right(i.asInstanceOf[T])
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
case Left(_) => ConsumeFailed[U]()
case Right(i) => ConsumeSucceed[U](consumer(i))
@ -101,7 +97,7 @@ class GivenContext {
this.use[T].toTry.get
def >>[T: ClassTag, U] (consumer: T => U): ConsumeResult[U] =
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)
@targetName("ownedBy")
@ -112,7 +108,7 @@ class GivenContext {
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))
.addOne(classTag[T].runtimeClass -> i)
def << [T: ClassTag] (is: (Class[T], T)): Unit =
@ -129,7 +125,7 @@ class GivenContext {
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] =
use[T] match
case Left(_) => ConsumeFailed[U]()
case Right(i) => ConsumeSucceed[U](consumer(i))
@ -139,7 +135,7 @@ class GivenContext {
this.use[T].toTry.get
def >> [T: ClassTag, U] (consumer: T => U): ConsumeResult[U] =
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)
}

View File

@ -5,13 +5,68 @@ object StringEnsure {
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) {
val padding = paddingStr.toString * (size-str.length)
str + padding
} 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 =
(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
import cc.sukazyo.cono.morny.core.MornySystem
import sttp.client3.basicRequest
import sttp.client3.{basicRequest, RequestT}
import sttp.client3
import sttp.model.Header
object SttpPublic {
@ -17,13 +18,27 @@ object SttpPublic {
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 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
.header(Headers.UserAgent.MORNY_CURRENT, true)

View File

@ -7,7 +7,7 @@ object UseMath {
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) {
def percentageOf (another: Int): Int =
infix def percentageOf (another: Int): Int =
Math.round((another.toDouble/base)*100).toInt
}

View File

@ -10,7 +10,7 @@ import scala.util.Random
object UseRandom {
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
}
@ -19,7 +19,7 @@ object UseRandom {
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)
}

View File

@ -4,6 +4,7 @@ import cc.sukazyo.messiva.utils.StackUtils
import scala.reflect.{classTag, ClassTag}
import scala.util.boundary
import scala.util.boundary.break
object UseStacks {
@ -11,7 +12,7 @@ object UseStacks {
boundary {
for (stack <- StackUtils.getStackTrace(1)) {
if (!stack.getClassName.startsWith(classTag[T].runtimeClass.getName))
boundary break stack
break(stack)
}
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 =>
runtimeStatus = State.RUNNING
this setName readyToRun.name
this `setName` readyToRun.name
try {
readyToRun.main
} catch case _: (Exception | Error) => {}
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
// run. It is useless since the wait/notify changes.
@ -117,7 +117,7 @@ class Scheduler {
}
// currentRunning = null
this setName runnerName
this `setName` runnerName
case needToWaitMillis: EpochMillis =>
runtimeStatus = State.WAITING
@ -133,7 +133,7 @@ class Scheduler {
}
}
runtime setName runnerName
runtime `setName` runnerName
runtime.start()
/** Name of the scheduler runner.
@ -179,7 +179,7 @@ class Scheduler {
* succeed removed from task queue.
*/
def cancel (task: Task): Boolean =
taskList synchronized:
taskList.synchronized:
try taskList remove task
finally taskList.notifyAll()
@ -208,7 +208,7 @@ class Scheduler {
* to make the scheduler avoid fails when machine fall asleep or some else conditions.
*/
def notifyIt(): Unit =
taskList synchronized:
taskList.synchronized:
taskList.notifyAll()
/** 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 {
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
new InputCommand(
if _ex.length > 1 then _ex(1) else null,

View File

@ -1,12 +1,13 @@
package cc.sukazyo.cono.morny.util.tgapi
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.model.*
import com.pengrad.telegrambot.request.{BaseRequest, GetChatMember}
import com.pengrad.telegrambot.response.BaseResponse
import java.io.IOException
import scala.annotation.targetName
object TelegramExtensions {
@ -39,7 +40,7 @@ object TelegramExtensions {
@throws[EventRuntimeException]
def exec [T <: BaseRequest[T, R], R <: BaseResponse] (request: BaseRequest[T, R], onError_message: String = ""): R = {
try {
val response = bot execute request
val response = bot `execute` request
if response isOk then return response
throw EventRuntimeException.ActionFailed(
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,
* 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.
*
* @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.
*
* @since 1.0.0
@ -257,9 +261,14 @@ object TelegramExtensions {
* | [[ChatMember.Status.left]] | -3 |
* | [[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.
*
* @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 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.
@ -288,8 +297,8 @@ object TelegramExtensions {
case ChatMember.Status.kicked => KICKED
def apply (chatMember: ChatMember): UserPermissionLevel = apply(chatMember.status)
import Bot.*
val chatMember: ChatMember = (bot exec GetChatMember(chat.id, user.id)).chatMember
import Requests.execute
val chatMember: ChatMember = GetChatMember(chat.id, user.id).execute(using bot).chatMember
if chatMember eq null then false
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)
/** 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() {
override val id: java.lang.Long = _id
}

View File

@ -54,13 +54,13 @@ object TelegramParseEscape {
case "br" =>
result += TextNode("\n")
case "tg-emoji" =>
if elem.attributes.hasKey("emoji-id") then
if elem.attributes `hasKey` "emoji-id" then
result += produceChildNodes(elem)
else
result += TextNode(elem.text)
case "img" =>
if elem.attributes hasKey "alt" then
result += TextNode(s"[${elem attr "alt"}]")
if elem.attributes `hasKey` "alt" then
result += TextNode(s"[${elem.attr("alt")}]")
case _ =>
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)
private var previousTickTimeMillis: Option[EpochMillis] = None
this setName threadName
this setDaemon isDaemonIt
this `setName` threadName
this `setDaemon` isDaemonIt
this.start()

View File

@ -8,6 +8,8 @@ import scala.io.StdIn
@main def MornyCLI (): Unit = {
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) =>
val _curr = EpochMillis(current)
val _tz = ZoneOffset of useTimezone
val _tz = ZoneOffset `of` useTimezone
val _next = EpochMillis(nextNotifyTime)
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) =>
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) =>
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 {
Thread.currentThread setName "parent-thread"
Thread.currentThread `setName` "parent-thread"
val data = StringBuilder("")
val task = Task("some-task", 0L, {
Thread.sleep(100)