diff --git a/gradle.properties b/gradle.properties index 8a198fb..0756bc7 100644 --- a/gradle.properties +++ b/gradle.properties @@ -5,7 +5,7 @@ MORNY_ARCHIVE_NAME = morny-coeur MORNY_CODE_STORE = https://github.com/Eyre-S/Coeur-Morny-Cono MORNY_COMMIT_PATH = https://github.com/Eyre-S/Coeur-Morny-Cono/commit/%s -VERSION = 1.0.0-RC5 +VERSION = 1.0.0-RC6 USE_DELTA = false VERSION_DELTA = diff --git a/src/main/scala/cc/sukazyo/cono/morny/MornyConfig.java b/src/main/scala/cc/sukazyo/cono/morny/MornyConfig.java index 692706d..61e5826 100644 --- a/src/main/scala/cc/sukazyo/cono/morny/MornyConfig.java +++ b/src/main/scala/cc/sukazyo/cono/morny/MornyConfig.java @@ -167,16 +167,16 @@ public class MornyConfig { @Nullable public String telegramBotApiServer4File = null; @Nullable public String telegramBotKey = null; @Nullable public String telegramBotUsername = null; - public long trustedMaster = 793274677L; - public long trustedChat = -1001541451710L; + public long trustedMaster = -1L; + public long trustedChat = -1L; public boolean eventIgnoreOutdated = false; public long eventOutdatedTimestamp = -1; public boolean commandLoginRefresh = false; public boolean commandLogoutClear = false; @Nonnull public final Set dinnerTrustedReaders = new HashSet<>(); - public long dinnerChatId = -1001707106392L; - public long reportToChat = -1001650050443L; - public long medicationNotifyToChat = -1001729016815L; + public long dinnerChatId = -1L; + public long reportToChat = -1L; + public long medicationNotifyToChat = -1L; @Nonnull public ZoneOffset medicationTimerUseTimezone = ZoneOffset.UTC; @Nonnull public final Set medicationNotifyAt = new HashSet<>(); diff --git a/src/main/scala/cc/sukazyo/cono/morny/MornyTrusted.scala b/src/main/scala/cc/sukazyo/cono/morny/MornyTrusted.scala index c95bde2..1400db6 100644 --- a/src/main/scala/cc/sukazyo/cono/morny/MornyTrusted.scala +++ b/src/main/scala/cc/sukazyo/cono/morny/MornyTrusted.scala @@ -2,11 +2,15 @@ package cc.sukazyo.cono.morny import cc.sukazyo.cono.morny.util.tgapi.TelegramExtensions.{LimboChat, LimboUser} import cc.sukazyo.cono.morny.util.tgapi.TelegramExtensions.Chat.* +import cc.sukazyo.cono.morny.Log.logger import com.pengrad.telegrambot.model.ChatMember.Status import com.pengrad.telegrambot.TelegramBot 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." + def isTrusted (userId: Long): Boolean = given TelegramBot = coeur.account if userId == config.trustedMaster then true diff --git a/src/main/scala/cc/sukazyo/cono/morny/bot/event/OnCallMe.scala b/src/main/scala/cc/sukazyo/cono/morny/bot/event/OnCallMe.scala index ba0bf7d..244ee66 100644 --- a/src/main/scala/cc/sukazyo/cono/morny/bot/event/OnCallMe.scala +++ b/src/main/scala/cc/sukazyo/cono/morny/bot/event/OnCallMe.scala @@ -21,35 +21,45 @@ class OnCallMe (using coeur: MornyCoeur) extends EventListener { if update.message.chat.`type` != (Chat.Type Private) then return false //noinspection ScalaUnnecessaryParentheses - (update.message.text toLowerCase) match - case "steam" | "sbeam" | "sdeam" => - requestItem(update.message.from, "STEAM LIBRARY") - case "hana paresu" | "花宫" | "内群" => - requestItem(update.message.from, "Hana Paresu") - case "dinner" | "lunch" | "breakfast" | "meal" | "eating" | "安妮今天吃什么" => - requestLastDinner(update.message) - case cc if cc startsWith "cc::" => - requestCustom(update.message) - case _ => - return false + val success = if me == -1 then false else + (update.message.text toLowerCase) match + case "steam" | "sbeam" | "sdeam" => + requestItem(update.message.from, "STEAM LIBRARY") + case "hana paresu" | "花宫" | "内群" => + requestItem(update.message.from, "Hana Paresu") + case "dinner" | "lunch" | "breakfast" | "meal" | "eating" | "安妮今天吃什么" => + requestLastDinner(update.message) + case cc if cc startsWith "cc::" => + requestCustom(update.message) + case _ => + return false + + if success then + coeur.account exec SendSticker( + update.message.chat.id, + TelegramStickers ID_SENT + ).replyToMessageId(update.message.messageId) + else + coeur.account exec SendSticker( + update.message.chat.id, + TelegramStickers ID_501 + ).replyToMessageId(update.message.messageId) - coeur.account exec SendSticker( - update.message.chat.id, - TelegramStickers ID_SENT - ).replyToMessageId(update.message.messageId) true } - private def requestItem (user: User, itemHTML: String, extra: String|Null = null): Unit = + private def requestItem (user: User, itemHTML: String, extra: String|Null = null): Boolean = coeur.account exec SendMessage( me, s"""request $itemHTML |from ${user.fullnameRefHTML}${if extra == null then "" else "\n"+extra}""" .stripMargin ).parseMode(ParseMode HTML) + true - private def requestLastDinner (req: Message): Unit = { + 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) { @@ -86,8 +96,9 @@ class OnCallMe (using coeur: MornyCoeur) extends EventListener { ) } - private def requestCustom (message: Message): Unit = + private def requestCustom (message: Message): Boolean = requestItem(message.from, "[???]") coeur.account exec ForwardMessage(me, message.chat.id, message.messageId) + true } diff --git a/src/main/scala/cc/sukazyo/cono/morny/bot/event/OnUserSlashAction.scala b/src/main/scala/cc/sukazyo/cono/morny/bot/event/OnUserSlashAction.scala index 163f893..eb3e119 100644 --- a/src/main/scala/cc/sukazyo/cono/morny/bot/event/OnUserSlashAction.scala +++ b/src/main/scala/cc/sukazyo/cono/morny/bot/event/OnUserSlashAction.scala @@ -31,7 +31,7 @@ class OnUserSlashAction (using coeur: MornyCoeur) extends EventListener { // scala, those commented code is removed permanently. // these message, here to remember the old DP7. - val actions = UniversalCommand(text) + val actions = UniversalCommand.Lossy(text) actions(0) = actions(0) substring 1 actions(0) diff --git a/src/main/scala/cc/sukazyo/cono/morny/daemon/MedicationTimer.scala b/src/main/scala/cc/sukazyo/cono/morny/daemon/MedicationTimer.scala index f01671a..3c61872 100644 --- a/src/main/scala/cc/sukazyo/cono/morny/daemon/MedicationTimer.scala +++ b/src/main/scala/cc/sukazyo/cono/morny/daemon/MedicationTimer.scala @@ -27,6 +27,12 @@ class MedicationTimer (using coeur: MornyCoeur) extends Thread { private var lastNotify_messageId: Option[Int] = None override def run (): Unit = { + + if ((notify_toChat == -1) || (notify_atHour isEmpty)) { + logger info "Medication Timer disabled : related param is not complete set" + return + } + logger info "Medication Timer started." while (!this.isInterrupted) { try { @@ -47,6 +53,7 @@ class MedicationTimer (using coeur: MornyCoeur) extends Thread { coeur.daemons.reporter.exception(e) } logger info "Medication Timer stopped." + } private def sendNotification(): Unit = { diff --git a/src/main/scala/cc/sukazyo/cono/morny/daemon/MornyReport.scala b/src/main/scala/cc/sukazyo/cono/morny/daemon/MornyReport.scala index 3528ff1..00ff369 100644 --- a/src/main/scala/cc/sukazyo/cono/morny/daemon/MornyReport.scala +++ b/src/main/scala/cc/sukazyo/cono/morny/daemon/MornyReport.scala @@ -15,7 +15,12 @@ import com.pengrad.telegrambot.response.BaseResponse 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" + private def executeReport[T <: BaseRequest[T, R], R<: BaseResponse] (report: T): Unit = { + if !enabled then return; try { coeur.account exec report } catch case e: EventRuntimeException.ActionFailed => { @@ -70,8 +75,7 @@ class MornyReport (using coeur: MornyCoeur) { ).parseMode(ParseMode HTML)) } - //noinspection ScalaWeakerAccess - def sectionConfigFields (config: MornyConfig): String = { + private def sectionConfigFields (config: MornyConfig): String = { val echo = StringBuilder() for (field <- config.getClass.getFields) { // language=html @@ -100,17 +104,18 @@ class MornyReport (using coeur: MornyCoeur) { } def reportCoeurExit (): Unit = { - val causedTag = coeur.exitReason match + def _causedTag = coeur.exitReason match + case Some(_exitReason) => _exitReason match + case u: User => u.fullnameRefHTML + case a => /*language=html*/ s"${h(a.toString)}" case None => "UNKNOWN reason" - case u: Some[User] => u.get.fullnameRefHTML - case a: Some[_] => /*language=html*/ s"${h(a.get.toString)}" executeReport(SendMessage( coeur.config.reportToChat, // language=html s"""▌Morny Exited |from user @${coeur.username} | - |by: $causedTag""" + |by: $_causedTag""" .stripMargin ).parseMode(ParseMode HTML)) } diff --git a/src/main/scala/cc/sukazyo/cono/morny/data/TelegramStickers.java b/src/main/scala/cc/sukazyo/cono/morny/data/TelegramStickers.java index 10a017a..51aa33f 100644 --- a/src/main/scala/cc/sukazyo/cono/morny/data/TelegramStickers.java +++ b/src/main/scala/cc/sukazyo/cono/morny/data/TelegramStickers.java @@ -21,6 +21,7 @@ public class TelegramStickers { public static final String ID_SAVED = "CAACAgEAAx0CSQh32gABBExuYdB_G0srfhQldRWkBYxWzCOv4-IAApooAAJ4_MYFcjuNZszfQcQjBA"; public static final String ID_PROGYNOVA = "CAACAgUAAxkBAAICm2KEuL7UQqNP7vSPCg2DHJIND6UsAAKLAwACH4WSBszIo722aQ3jJAQ"; public static final String ID_NETWORK_ERR = "CAACAgEAAxkBAAID0WNJgNEkD726KW4vZeFlw0FlVVyNAAIXJgACePzGBb50o7O1RbxoKgQ"; + public static final String ID_501 = "CAACAgEAAxkBAAIHbGUhJ8zm2Sb_c0YU-DYQ6xb-ZDtaAAKdJwACePzGBTOftDZL6X7vMAQ"; @Nonnull public static Map map () { diff --git a/src/main/scala/cc/sukazyo/cono/morny/util/UniversalCommand.scala b/src/main/scala/cc/sukazyo/cono/morny/util/UniversalCommand.scala index 9aa6e5c..00f4a0f 100644 --- a/src/main/scala/cc/sukazyo/cono/morny/util/UniversalCommand.scala +++ b/src/main/scala/cc/sukazyo/cono/morny/util/UniversalCommand.scala @@ -7,9 +7,15 @@ import scala.util.boundary * @todo docs * @todo maybe there can have some encapsulation */ -object UniversalCommand { +object UniversalCommand { - def apply (input: String): Array[String] = { + opaque type StrictMode = Boolean + //noinspection ScalaWeakerAccess + val strict: StrictMode = true + //noinspection ScalaWeakerAccess + val lossy: StrictMode = false + + def apply (using strict: StrictMode = strict)(input: String): Array[String] = { val builder = ArrayBuffer.empty[String] @@ -38,26 +44,29 @@ object UniversalCommand { val _inside_tag = input(i) boundary { while (true) { i=i+1 - if (i >= input.length) throw IllegalArgumentException("UniversalCommand: unclosed quoted text") + if (i >= input.length) + if strict then throw IllegalArgumentException("UniversalCommand: unclosed quoted text") + else boundary.break() if (input(i) == _inside_tag) boundary.break() - else if (input(i) isUnsupported) + else if (input(i) isUnsupported) && strict then throw IllegalArgumentException("UniversalCommand: unsupported new-line") - else if (input(i) isQuote) + else if (input(i) isQuote) && strict then throw IllegalArgumentException("UniversalCommand: mixed \" and ' used") else if (input(i) isEscapeChar) - if (i+1 >= input.length) throw IllegalArgumentException("UniversalCommand: \\ in the end") - if (input(i+1) escapableInQuote) + if (i+1 >= input.length) && strict then + throw IllegalArgumentException("UniversalCommand: \\ in the end") + if ((i+1 < input.length) && (input(i+1) escapableInQuote)) i=i+1 arg += input(i) else arg += input(i) }} - } else if (input(i) isUnsupported) { + } else if ((input(i) isUnsupported) && strict) { throw IllegalArgumentException("UniversalCommand: unsupported new-line") } else if (input(i) isEscapeChar) { - if (i + 1 >= input.length) throw IllegalArgumentException("UniversalCommand: \\ in the end") - if (input(i+1) escapable) + if (i + 1 >= input.length) && strict then throw IllegalArgumentException("UniversalCommand: \\ in the end") + if ((i+1 < input.length) && (input(i+1) escapable)) i=i+1 arg += input(i) } else { @@ -71,4 +80,7 @@ object UniversalCommand { } + object Lossy: + def apply (input: String): Array[String] = UniversalCommand(using lossy)(input) + } diff --git a/src/main/scala/cc/sukazyo/cono/morny/util/tgapi/InputCommand.scala b/src/main/scala/cc/sukazyo/cono/morny/util/tgapi/InputCommand.scala index f6c7e26..209415a 100644 --- a/src/main/scala/cc/sukazyo/cono/morny/util/tgapi/InputCommand.scala +++ b/src/main/scala/cc/sukazyo/cono/morny/util/tgapi/InputCommand.scala @@ -25,7 +25,8 @@ object InputCommand { ) } + //noinspection NoTailRecursionAnnotation def apply (input: String): InputCommand = - InputCommand(UniversalCommand(input)) + InputCommand(UniversalCommand.Lossy(input)) } diff --git a/src/test/scala/cc/sukazyo/cono/morny/test/utils/UniversalCommandTest.scala b/src/test/scala/cc/sukazyo/cono/morny/test/utils/UniversalCommandTest.scala index 28a11c5..abf5010 100644 --- a/src/test/scala/cc/sukazyo/cono/morny/test/utils/UniversalCommandTest.scala +++ b/src/test/scala/cc/sukazyo/cono/morny/test/utils/UniversalCommandTest.scala @@ -9,50 +9,93 @@ class UniversalCommandTest extends MornyTests with Matchers with TableDrivenProp "while formatting command from String :" - { import cc.sukazyo.cono.morny.util.UniversalCommand as Cmd + import cc.sukazyo.cono.morny.util.UniversalCommand.Lossy as Lmd + def whileLossy (info: String): String = "in lossy mode " + info + def whileStrict (info: String): String = "in strict mode" + info raw"args should be separated by (\u0020) ascii-space" in: - Cmd("a b c delta e") shouldEqual Array("a", "b", "c", "delta", "e"); + Cmd("a b c delta e") shouldEqual Array("a", "b", "c", "delta", "e") + Lmd("a b c delta e") shouldEqual Array("a", "b", "c", "delta", "e") "args should not be separated by non-ascii spaces" in: - Cmd("tests ダタ セト") shouldEqual Array("tests", "ダタ セト"); + Cmd("tests ダタ セト") shouldEqual Array("tests", "ダタ セト") + Lmd("tests ダタ セト") shouldEqual Array("tests", "ダタ セト") "multiple ascii-spaces should not generate empty arg in middle" in: - Cmd("tests some of data") shouldEqual Array("tests", "some", "of", "data"); + Cmd("tests some of data") shouldEqual Array("tests", "some", "of", "data") + Lmd("tests some of data") shouldEqual Array("tests", "some", "of", "data") """texts and ascii-spaces in '' should grouped in one arg""" in: - Cmd("""tests 'data set'""") shouldEqual Array("tests", "data set"); + Cmd("""tests 'data set'""") shouldEqual Array("tests", "data set") + Lmd("""tests 'data set'""") shouldEqual Array("tests", "data set") """texts and ascii-spaces in "" should grouped in one arg""" in : - Cmd("""tests "data set"""") shouldEqual Array("tests", "data set"); - """mixed ' and " should throws IllegalArgumentsException""" in: - an [IllegalArgumentException] should be thrownBy Cmd("""tests "data set' "of it'"""); - "with ' not closed should throws IllegalArgumentException" in: - an [IllegalArgumentException] should be thrownBy Cmd("""use 'it """); + Cmd("""tests "data set"""") shouldEqual Array("tests", "data set") + Lmd("""tests "data set"""") shouldEqual Array("tests", "data set") + """texts nested '' should grouped in one arg""" in : + Cmd("""tests some:'data set'.message is""") shouldEqual Array("tests", "some:data set.message", "is") + Lmd("""tests some:'data set'.message is""") shouldEqual Array("tests", "some:data set.message", "is") + """texts and "" nested '' should grouped in one arg""" in : + Cmd("""tests "arg1 x":'arg2 y' param""") shouldEqual Array("tests", "arg1 x:arg2 y", "param") + Lmd("""tests "arg1 x":"arg2 y" param""") shouldEqual Array("tests", "arg1 x:arg2 y", "param") + "with ' not closed" - { + whileStrict("should throws IllegalArgumentException") in: + an[IllegalArgumentException] should be thrownBy Cmd("""use 'it """) + whileLossy("should be cut at end") in: + Lmd("use 'it ") shouldEqual Array("use", "it ") + } + """mixed ' and """" - { + whileStrict("should throws IllegalArgumentsException") in: + an[IllegalArgumentException] should be thrownBy Cmd("""tests "data set' "of it'""") + whileLossy("should be seen as a normal character") in: + Lmd("""tests "data set' "of it'""") shouldEqual Array("tests", "data set' of", "it") + } raw"\ should escape itself" in: - Cmd(raw"input \\data") shouldEqual Array("input", "\\data"); + Cmd(raw"input \\data") shouldEqual Array("input", "\\data") + Lmd(raw"input \\data") shouldEqual Array("input", "\\data") raw"\ should escape ascii-space, makes it processed as a normal character" in: - Cmd(raw"input data\ set") shouldEqual Array("input", "data set"); + Cmd(raw"input data\ set") shouldEqual Array("input", "data set") + Lmd(raw"input data\ set") shouldEqual Array("input", "data set") raw"\ should escape ascii-space, makes it can be an arg body" in: - Cmd(raw"input \ some-thing") shouldEqual Array("input", " ", "some-thing"); + Cmd(raw"input \ some-thing") shouldEqual Array("input", " ", "some-thing") + Lmd(raw"input \ some-thing") shouldEqual Array("input", " ", "some-thing") raw"""\ should escape "", makes it processed as a normal character""" in : - Cmd(raw"""use \"inputted""") shouldEqual Array("use", "\"inputted"); + Cmd(raw"""use \"inputted""") shouldEqual Array("use", "\"inputted") + Lmd(raw"""use \"inputted""") shouldEqual Array("use", "\"inputted") raw"\ should escape '', makes it processed as a normal character" in: - Cmd(raw"use \'inputted") shouldEqual Array("use", "'inputted"); + Cmd(raw"use \'inputted") shouldEqual Array("use", "'inputted") + Lmd(raw"use \'inputted") shouldEqual Array("use", "'inputted") raw"\ should escape itself which inside a quoted scope" in: - Cmd(raw"use 'quoted \\ body'") shouldEqual Array("use", "quoted \\ body"); + Cmd(raw"use 'quoted \\ body'") shouldEqual Array("use", "quoted \\ body") + Lmd(raw"use 'quoted \\ body'") shouldEqual Array("use", "quoted \\ body") raw"""\ should escape " which inside a "" scope""" in: - Cmd(raw"""in "quoted \" body" body""") shouldEqual Array("in", "quoted \" body", "body"); + Cmd(raw"""in "quoted \" body" body""") shouldEqual Array("in", "quoted \" body", "body") + Lmd(raw"""in "quoted \" body" body""") shouldEqual Array("in", "quoted \" body", "body") raw"""\ should escape ' which inside a "" scope""" in : - Cmd(raw"""in "not-quoted \' body" body""") shouldEqual Array("in", "not-quoted ' body", "body"); + Cmd(raw"""in "not-quoted \' body" body""") shouldEqual Array("in", "not-quoted ' body", "body") + Lmd(raw"""in "not-quoted \' body" body""") shouldEqual Array("in", "not-quoted ' body", "body") raw"""\ should escape ' which inside a '' scope""" in : - Cmd(raw"""in 'quoted \' body' body""") shouldEqual Array("in", "quoted ' body", "body"); + Cmd(raw"""in 'quoted \' body' body""") shouldEqual Array("in", "quoted ' body", "body") + Lmd(raw"""in 'quoted \' body' body""") shouldEqual Array("in", "quoted ' body", "body") raw"""\ should escape " which inside a ' scope""" in : - Cmd(raw"""in 'not-quoted \" body' body""") shouldEqual Array("in", "not-quoted \" body", "body"); + Cmd(raw"""in 'not-quoted \" body' body""") shouldEqual Array("in", "not-quoted \" body", "body") + Lmd(raw"""in 'not-quoted \" body' body""") shouldEqual Array("in", "not-quoted \" body", "body") raw"\ should not escape ascii-space which inside a quoted scope" in: - Cmd(raw"""'quoted \ do not escape' did""") shouldEqual Array(raw"quoted \ do not escape", "did"); - raw"with \ in the end should throws IllegalArgumentException" in: - an [IllegalArgumentException] should be thrownBy Cmd("something error!\\"); + Cmd(raw"""'quoted \ do not escape' did""") shouldEqual Array(raw"quoted \ do not escape", "did") + Lmd(raw"""'quoted \ do not escape' did""") shouldEqual Array(raw"quoted \ do not escape", "did") + raw"with \ in the end" - { + whileStrict("should throws IllegalArgumentException") in: + an [IllegalArgumentException] should be thrownBy Cmd("something error!\\") + whileLossy("should seen as normal char") in: + Lmd("something error!\\") shouldEqual Array("something", "error!\\") + } + + + "with multi-line input" - { + whileStrict("should throws IllegalArgumentException") in: + an [IllegalArgumentException] should be thrownBy Cmd("something will\nhave a new line") + whileLossy("should keep new-line char origin like") in: + Lmd("something will\nhave a new line") shouldEqual Array("something", "will\nhave", "a", "new", "line") + } - "with multi-line input should throws IllegalArgumentException" in: - an [IllegalArgumentException] should be thrownBy Cmd("something will\nhave a new line"); val example_special_character = Table( "char", @@ -68,6 +111,8 @@ class UniversalCommandTest extends MornyTests with Matchers with TableDrivenProp s"input with special character ($char) should keep origin like" in { Cmd(s"$char dataset data[$char]contains parsed") shouldEqual Array(char, "dataset", s"data[$char]contains", "parsed") + Lmd(s"$char dataset data[$char]contains parsed") shouldEqual + Array(char, "dataset", s"data[$char]contains", "parsed") } } }