unset all function defaults config, fix universal command parse

unset the following defaults value:
  - master
  - trusted chat
  - dinner chat id
  - report to chat
  - medication notify to chat
and make the related function can be shutdown by the new -1 defaults:
  - MedicationTimer daemon will not run when medication-notify-to-chat or notify-at-hour is not set
  - MornyReport will do not report when report-to is not set
  - OnCallMe will send ID_501 when there's no master
  - OnCallMe.requestLastDinner will send ID_501 when there's no dinner-chat

fix when using universal command parse may throw exception
This commit is contained in:
A.C.Sukazyo Eyre 2023-10-07 22:07:00 +08:00
parent 985fde9aa2
commit 45a85e15f5
Signed by: Eyre_S
GPG Key ID: C17CE40291207874
11 changed files with 152 additions and 66 deletions

View File

@ -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 =

View File

@ -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<Long> 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<Integer> medicationNotifyAt = new HashSet<>();

View File

@ -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

View File

@ -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, "<b>STEAM LIBRARY</b>")
case "hana paresu" | "花宫" | "内群" =>
requestItem(update.message.from, "<b>Hana Paresu</b>")
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, "<b>STEAM LIBRARY</b>")
case "hana paresu" | "花宫" | "内群" =>
requestItem(update.message.from, "<b>Hana Paresu</b>")
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, "<u>[???]</u>")
coeur.account exec ForwardMessage(me, message.chat.id, message.messageId)
true
}

View File

@ -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)

View File

@ -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 = {

View File

@ -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"<code>${h(a.toString)}</code>"
case None => "UNKNOWN reason"
case u: Some[User] => u.get.fullnameRefHTML
case a: Some[_] => /*language=html*/ s"<code>${h(a.get.toString)}</code>"
executeReport(SendMessage(
coeur.config.reportToChat,
// language=html
s"""<b>▌Morny Exited</b>
|from user @${coeur.username}
|
|by: $causedTag"""
|by: $_causedTag"""
.stripMargin
).parseMode(ParseMode HTML))
}

View File

@ -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<String, String> map () {

View File

@ -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)
}

View File

@ -25,7 +25,8 @@ object InputCommand {
)
}
//noinspection NoTailRecursionAnnotation
def apply (input: String): InputCommand =
InputCommand(UniversalCommand(input))
InputCommand(UniversalCommand.Lossy(input))
}

View File

@ -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")
}
}
}