add coeur lifecycles, add MornyCoeur.externalContext

This commit is contained in:
A.C.Sukazyo Eyre 2023-12-21 14:19:20 +08:00
parent 341d4cd851
commit 6b961a3de3
Signed by: Eyre_S
GPG Key ID: C17CE40291207874
14 changed files with 309 additions and 141 deletions

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-alpha3"
val VERSION = "2.0.0-alpha5"
val VERSION_DELTA: Option[String] = None
val CODENAME = "guanggu"

View File

@ -1,19 +1,19 @@
package cc.sukazyo.cono.morny
import cc.sukazyo.cono.morny.bot.command.MornyCommands
import cc.sukazyo.cono.morny.bot.command.MornyCommandManager
import cc.sukazyo.cono.morny.daemon.MornyDaemons
import cc.sukazyo.cono.morny.Log.{exceptionLog, logger}
import cc.sukazyo.cono.morny.MornyCoeur.{TestRun, THREAD_SERVER_EXIT}
import cc.sukazyo.cono.morny.MornyCoeur.*
import cc.sukazyo.cono.morny.bot.api.EventListenerManager
import cc.sukazyo.cono.morny.bot.event.{MornyEventListeners, MornyOnInlineQuery, MornyOnTelegramCommand, MornyOnUpdateTimestampOffsetLock}
import cc.sukazyo.cono.morny.bot.query.MornyQueries
import cc.sukazyo.cono.morny.bot.event.{MornyOnInlineQuery, MornyOnTelegramCommand, MornyOnUpdateTimestampOffsetLock}
import cc.sukazyo.cono.morny.bot.query.MornyQueryManager
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 com.pengrad.telegrambot.TelegramBot
import com.pengrad.telegrambot.request.GetMe
import scala.annotation.unused
import scala.util.boundary
import scala.util.boundary.break
@ -23,15 +23,65 @@ object MornyCoeur {
object TestRun
case class OnInitializingPreContext (
externalContext: GivenContext,
coeurStartupTimes: EpochMillis,
account: TelegramBot,
username: String,
userid: Long,
tasks: Scheduler,
trusted: MornyTrusted,
givenCxt: GivenContext
)
case class OnInitializingContext (
externalContext: GivenContext,
coeurStartupTimes: EpochMillis,
account: TelegramBot,
username: String,
userid: Long,
tasks: Scheduler,
trusted: MornyTrusted,
eventManager: EventListenerManager,
commandManager: MornyCommandManager,
queryManager: MornyQueryManager,
givenCxt: GivenContext
)
case class OnInitializingPostContext (
externalContext: GivenContext,
coeurStartupTimes: EpochMillis,
account: TelegramBot,
username: String,
userid: Long,
tasks: Scheduler,
trusted: MornyTrusted,
eventManager: EventListenerManager,
commandManager: MornyCommandManager,
queryManager: MornyQueryManager,
givenCxt: GivenContext
)
case class OnStartingContext (
givenCxt: GivenContext
)
case class OnStartingPostContext (
givenCxt: GivenContext
)
}
class MornyCoeur (using val config: MornyConfig)(testRun: Boolean = false) {
class MornyCoeur (modules: List[MornyModule])(using val config: MornyConfig)(testRun: Boolean = false) {
given MornyCoeur = this
val externalContext: GivenContext = GivenContext()
///>>> BLOCK START instance configure & startup stage 1
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}"
@ -44,6 +94,7 @@ class MornyCoeur (using val config: MornyConfig)(testRun: Boolean = false) {
logger error "Login to bot failed."
System exit -1
throw RuntimeException()
initializeContext << __loginResult
///<<< BLOCK END instance configure & startup stage 1
@ -71,19 +122,29 @@ class MornyCoeur (using val config: MornyConfig)(testRun: Boolean = false) {
/** current Morny's [[MornyTrusted]] instance */
val trusted: MornyTrusted = MornyTrusted()
modules.foreach(it => it.onInitializingPre(OnInitializingPreContext(
externalContext,
coeurStartTimestamp, account, username, userid, tasks, trusted,
initializeContext)))
val daemons: MornyDaemons = MornyDaemons()
//noinspection ScalaWeakerAccess
initializeContext << daemons
val eventManager: EventListenerManager = EventListenerManager()
eventManager register MornyOnUpdateTimestampOffsetLock()
val commands: MornyCommands = MornyCommands()
//noinspection ScalaWeakerAccess
val queries: MornyQueries = MornyQueries()
val commands: MornyCommandManager = MornyCommandManager()
val queries: MornyQueryManager = MornyQueryManager()
eventManager register MornyOnTelegramCommand(using commands)
eventManager register MornyOnInlineQuery(using queries)
//noinspection ScalaUnusedSymbol
val events: MornyEventListeners = MornyEventListeners(using eventManager)
// Coeur Initializing Event
modules.foreach(it => it.onInitializing(OnInitializingContext(
externalContext,
coeurStartTimestamp, account, username, userid, tasks, trusted,
eventManager, commands, queries,
initializeContext)))
eventManager register daemons.reporter.EventStatistics.EventInfoCatcher
@unused
val watchDog: WatchDog = WatchDog("watch-dog", 1000, 1500, { (consumed, _) =>
import cc.sukazyo.cono.morny.util.CommonFormat.formatDuration as f
logger warn
@ -91,6 +152,12 @@ class MornyCoeur (using val config: MornyConfig)(testRun: Boolean = false) {
| current tick takes ${f(consumed)} to complete.""".stripMargin
tasks.notifyIt()
})
initializeContext / this << watchDog
modules.foreach(it => it.onInitializingPost(OnInitializingPostContext(
externalContext,
coeurStartTimestamp, account, username, userid, tasks, trusted,
eventManager, commands, queries,
initializeContext)))
///>>> BLOCK START instance configure & startup stage 2
@ -102,6 +169,8 @@ class MornyCoeur (using val config: MornyConfig)(testRun: Boolean = false) {
configure_exitCleanup()
// put things that need to cleanup when exit below
// so that it will be correctly cleanup when normal run and will not execute in testRun.
modules.foreach(it => it.onStarting(OnStartingContext(
initializeContext)))
daemons.start()
logger info "start telegram event listening"
import com.pengrad.telegrambot.TelegramException
@ -150,18 +219,21 @@ class MornyCoeur (using val config: MornyConfig)(testRun: Boolean = false) {
}
})
modules.foreach(it => it.onStartingPost(OnStartingPostContext(
initializeContext)))
if config.commandLoginRefresh then
logger info "resetting telegram command list"
commands.automaticTGListUpdate()
daemons.reporter.reportCoeurMornyLogin()
initializeContext = null
logger info "Coeur start complete."
///<<< BLOCK END instance configure & startup stage 2
def saveDataAll(): Unit = {
// nothing to do
modules.foreach(it => it.onRoutineSavingData)
logger notice "done all save action."
}
@ -169,6 +241,7 @@ class MornyCoeur (using val config: MornyConfig)(testRun: Boolean = false) {
daemons.reporter.reportCoeurExit()
account.shutdown()
logger info "stopped bot account"
modules.foreach(it => it.onExit)
daemons.stop()
tasks.waitForStop()
logger info s"morny tasks stopped: remains ${tasks.amount} tasks not be executed"

View File

@ -0,0 +1,99 @@
package cc.sukazyo.cono.morny
import cc.sukazyo.cono.morny.MornyCoeur.OnInitializingContext
class MornyCoreModule extends MornyModule {
override val id: String = "cc.sukazyo.cono.morny.bot"
override val name: String = "Morny Coeur - Core (for refactor temporary)"
override val version: String = MornySystem.VERSION
override val description: String | Null =
"""Core module of Morny Coeur.
|
|Exists for temporary use, when refactor completed it should be replaced
|by all other small modules that provide different functionality.
|""".stripMargin
override def onInitializing (using MornyCoeur)(cxt: OnInitializingContext): Unit = {
import cc.sukazyo.cono.morny.bot.command.*
import cc.sukazyo.cono.morny.bot.event.*
import cc.sukazyo.cono.morny.bot.query.*
import cxt.*
val $OnUserRandom = OnUserRandom()
eventManager.register(
// ACTIVITY_RECORDER
// KUOHUANHUAN_NEED_SLEEP
OnUniMeowTrigger(using commandManager),
$OnUserRandom.RandomSelect,
//noinspection NonAsciiCharacters
$OnUserRandom.尊嘟假嘟,
OnQuestionMarkReply(),
OnUserSlashAction(),
OnCallMe(),
OnCallMsgSend(),
OnGetSocial(),
OnMedicationNotifyApply(),
OnEventHackHandle()
)
val $MornyHellos = MornyHellos()
val $IP186Query = IP186Query()
val $MornyInformation = MornyInformation()
val $MornyInformationOlds = MornyInformationOlds(using $MornyInformation)
val $MornyManagers = MornyManagers()
//noinspection NonAsciiCharacters
val $喵呜 = 喵呜()
//noinspection NonAsciiCharacters
val $创 = ()
commandManager.register(
$MornyHellos.On,
$MornyHellos.Hello,
MornyInfoOnStart(),
GetUsernameAndId(),
EventHack(),
Nbnhhsh(),
$IP186Query.IP,
$IP186Query.Whois,
Encryptor(),
MornyOldJrrp(),
GetSocial(),
$MornyManagers.SaveData,
$MornyInformation,
$MornyInformationOlds.Version,
$MornyInformationOlds.Runtime,
$MornyManagers.Exit,
Testing(),
DirectMsgClear(),
//noinspection NonAsciiCharacters
私わね(),
//noinspection NonAsciiCharacters
$喵呜.Progynova,
//noinspection NonAsciiCharacters
$创.Chuang
)
//noinspection NonAsciiCharacters
commandManager.registerForUni(
$喵呜.抱抱,
$喵呜.揉揉,
$喵呜.贴贴,
$喵呜.蹭蹭
)
queryManager.register(
RawText(),
MyInformation(),
ShareToolTwitter(),
ShareToolBilibili(),
ShareToolSocialContent()
)
}
}

View File

@ -0,0 +1,24 @@
package cc.sukazyo.cono.morny
import cc.sukazyo.cono.morny.MornyCoeur.*
trait MornyModule {
val id: String
val name: String
val version: String
val description: String|Null
def onInitializingPre (using MornyCoeur)(cxt: OnInitializingPreContext): Unit = {}
def onInitializing (using MornyCoeur)(cxt: OnInitializingContext): Unit = {}
def onInitializingPost (using MornyCoeur)(cxt: OnInitializingPostContext): Unit = {}
def onStarting (using MornyCoeur)(cxt: OnStartingContext): Unit = {}
def onStartingPost (using MornyCoeur)(cxt: OnStartingPostContext): Unit = {}
def onRoutineSavingData (using MornyCoeur): Unit = {}
def onExit (using MornyCoeur): Unit = {}
}

View File

@ -158,7 +158,9 @@ object ServerMain {
Thread.currentThread setName THREAD_MORNY_INIT
try
MornyCoeur(using config build)(
MornyCoeur(
MornyCoreModule() :: Nil
)(using config build)(
testRun = mode_testRun
)
catch {

View File

@ -1,6 +1,7 @@
package cc.sukazyo.cono.morny.bot.api
import cc.sukazyo.cono.morny.util.EpochDateTime.EpochMillis
import cc.sukazyo.cono.morny.util.GivenContext
import cc.sukazyo.messiva.utils.StackUtils
import com.pengrad.telegrambot.model.Update
@ -19,7 +20,7 @@ class EventEnv (
case CANCELED (_from: StackTraceElement) extends State with StateSource(_from)
private val _status: mutable.ListBuffer[State] = mutable.ListBuffer.empty
private val variables: mutable.HashMap[Class[?], Any] = mutable.HashMap.empty
val givenCxt: GivenContext = GivenContext()
val timeStartup: EpochMillis = System.currentTimeMillis
def isEventOk: Boolean = _status.lastOption match
@ -42,24 +43,4 @@ class EventEnv (
def status: List[State] =
_status.toList
def provide (i: Any): Unit =
variables += (i.getClass -> i)
def consume [T] (t: Class[T]) (consumer: T => Unit): ConsumeResult = {
variables get t match
case Some(i) => consumer(i.asInstanceOf[T]); ConsumeResult(true)
case None => ConsumeResult(false)
}
def consume [T: ClassTag] (consumer: T => Unit): ConsumeResult =
variables get classTag[T].runtimeClass match
case Some(i) => consumer(i.asInstanceOf[T]); ConsumeResult(true)
case None => ConsumeResult(false)
class ConsumeResult (success: Boolean) {
def onfail (processor: => Unit): Unit = {
if !success then processor
}
}
}

View File

@ -8,69 +8,26 @@ import cc.sukazyo.cono.morny.util.tgapi.TelegramExtensions.Bot.exec
import com.pengrad.telegrambot.model.{BotCommand, DeleteMyCommands, Update}
import com.pengrad.telegrambot.request.{SendSticker, SetMyCommands}
import scala.collection.{mutable, SeqMap}
import scala.collection.mutable
import scala.collection.mutable.ArrayBuffer
import scala.language.postfixOps
class MornyCommands (using coeur: MornyCoeur) {
class MornyCommandManager (using coeur: MornyCoeur) {
private type CommandMap = SeqMap[String, ISimpleCommand]
private def CommandMap (commands: ISimpleCommand*): CommandMap =
val stash = mutable.SeqMap.empty[String, ISimpleCommand]
private type CommandMap = mutable.SeqMap[String, ISimpleCommand]
private val commands: CommandMap = mutable.SeqMap.empty
def register [T <: ISimpleCommand] (commands: T*): Unit =
for (i <- commands)
stash += (i.name -> i)
this.commands += (i.name -> i)
for (alias <- i.aliases)
stash += (alias.name -> i)
stash
this.commands += (alias.name -> i)
private val $MornyHellos = MornyHellos()
private val $IP186Query = IP186Query()
private val $MornyInformation = MornyInformation()
private val $MornyInformationOlds = MornyInformationOlds(using $MornyInformation)
private val $MornyManagers = MornyManagers()
//noinspection NonAsciiCharacters
private val $喵呜 = 喵呜()
//noinspection NonAsciiCharacters
private val $创 = ()
private val commands: CommandMap = CommandMap(
$MornyHellos.On,
$MornyHellos.Hello,
MornyInfoOnStart(),
GetUsernameAndId(),
EventHack(),
Nbnhhsh(),
$IP186Query.IP,
$IP186Query.Whois,
Encryptor(),
MornyOldJrrp(),
GetSocial(),
$MornyManagers.SaveData,
$MornyInformation,
$MornyInformationOlds.Version,
$MornyInformationOlds.Runtime,
$MornyManagers.Exit,
Testing(),
DirectMsgClear(),
//noinspection NonAsciiCharacters
私わね(),
//noinspection NonAsciiCharacters
$喵呜.Progynova,
//noinspection NonAsciiCharacters
$创.Chuang
)
//noinspection NonAsciiCharacters
val commands_uni: CommandMap = CommandMap(
$喵呜.抱抱,
$喵呜.揉揉,
$喵呜.贴贴,
$喵呜.蹭蹭
)
private[bot] val commands_uni: CommandMap = mutable.SeqMap.empty
def registerForUni [T <: ISimpleCommand] (commands: T*): Unit =
for (i <- commands)
this.commands_uni += (i.name -> i)
for (alias <- i.aliases)
this.commands_uni += (alias.name -> i)
def execute (using command: InputCommand, event: Update): Boolean = {
if (commands contains command.command)

View File

@ -1,25 +0,0 @@
package cc.sukazyo.cono.morny.bot.event
import cc.sukazyo.cono.morny.bot.api.EventListenerManager
import cc.sukazyo.cono.morny.MornyCoeur
class MornyEventListeners (using manager: EventListenerManager) (using coeur: MornyCoeur) {
private val $OnUserRandom = OnUserRandom()
manager.register(
// ACTIVITY_RECORDER
// KUOHUANHUAN_NEED_SLEEP
OnUniMeowTrigger(using coeur.commands),
$OnUserRandom.RandomSelect,
//noinspection NonAsciiCharacters
$OnUserRandom.尊嘟假嘟,
OnQuestionMarkReply(),
OnUserSlashAction(),
OnCallMe(),
OnCallMsgSend(),
OnGetSocial(),
OnMedicationNotifyApply(),
OnEventHackHandle()
)
}

View File

@ -2,9 +2,8 @@ package cc.sukazyo.cono.morny.bot.event
import cc.sukazyo.cono.morny.MornyCoeur
import cc.sukazyo.cono.morny.bot.api.{EventEnv, EventListener}
import cc.sukazyo.cono.morny.bot.query.{InlineQueryUnit, MornyQueries}
import cc.sukazyo.cono.morny.bot.query.{InlineQueryUnit, MornyQueryManager}
import cc.sukazyo.cono.morny.util.tgapi.TelegramExtensions.Bot.exec
import com.pengrad.telegrambot.model.Update
import com.pengrad.telegrambot.model.request.InlineQueryResult
import com.pengrad.telegrambot.request.AnswerInlineQuery
@ -12,7 +11,7 @@ import scala.collection.mutable.ListBuffer
import scala.language.postfixOps
import scala.reflect.ClassTag
class MornyOnInlineQuery (using queryManager: MornyQueries) (using coeur: MornyCoeur) extends EventListener {
class MornyOnInlineQuery (using queryManager: MornyQueryManager) (using coeur: MornyCoeur) extends EventListener {
override def onInlineQuery (using event: EventEnv): Unit = {
import event.update

View File

@ -3,14 +3,15 @@ package cc.sukazyo.cono.morny.bot.event
import cc.sukazyo.cono.morny.bot.api.{EventEnv, EventListener}
import cc.sukazyo.cono.morny.Log.logger
import cc.sukazyo.cono.morny.MornyCoeur
import cc.sukazyo.cono.morny.bot.command.MornyCommands
import cc.sukazyo.cono.morny.bot.command.MornyCommandManager
import cc.sukazyo.cono.morny.util.tgapi.InputCommand
import com.pengrad.telegrambot.model.{Message, Update}
class MornyOnTelegramCommand (using commandManager: MornyCommands) (using coeur: MornyCoeur) extends EventListener {
class MornyOnTelegramCommand (using commandManager: MornyCommandManager) (using coeur: MornyCoeur) extends EventListener {
override def onMessage (using event: EventEnv): Unit = {
given update: Update = event.update
import event.*
given Update = update
def _isCommandMessage(message: Message): Boolean =
if message.text eq null then false
@ -20,7 +21,7 @@ class MornyOnTelegramCommand (using commandManager: MornyCommands) (using coeur:
if !_isCommandMessage(update.message) then return
val inputCommand = InputCommand(update.message.text drop 1)
event provide inputCommand
givenCxt << inputCommand
logger trace ":provided InputCommand for event"
if (!(inputCommand.command matches "^\\w+$"))
@ -30,7 +31,7 @@ class MornyOnTelegramCommand (using commandManager: MornyCommands) (using coeur:
else
logger debug "is command"
if commandManager.execute(using inputCommand) then
event.setEventOk
setEventOk
}

View File

@ -1,15 +1,16 @@
package cc.sukazyo.cono.morny.bot.event
import cc.sukazyo.cono.morny.bot.api.{EventEnv, EventListener}
import cc.sukazyo.cono.morny.bot.command.MornyCommands
import cc.sukazyo.cono.morny.bot.command.MornyCommandManager
import cc.sukazyo.cono.morny.util.tgapi.InputCommand
import cc.sukazyo.cono.morny.Log.logger
class OnUniMeowTrigger (using commands: MornyCommands) extends EventListener {
class OnUniMeowTrigger (using commands: MornyCommandManager) extends EventListener {
override def onMessage (using event: EventEnv): Unit = {
import event.*
event.consume[InputCommand] { input =>
givenCxt >> { (input: InputCommand) =>
logger trace s"got input command {$input} from event-context"
for ((name, command_instance) <- commands.commands_uni) {
@ -20,7 +21,7 @@ class OnUniMeowTrigger (using commands: MornyCommands) extends EventListener {
event.setEventOk
}
} onfail { logger trace "not command (for uni-meow)" }
} || { logger trace "not command (for uni-meow)" }
}

View File

@ -4,21 +4,19 @@ import cc.sukazyo.cono.morny.bot.query
import cc.sukazyo.cono.morny.MornyCoeur
import com.pengrad.telegrambot.model.Update
import scala.collection.mutable
import scala.collection.mutable.ListBuffer
class MornyQueries (using MornyCoeur) {
class MornyQueryManager (using MornyCoeur) {
private val queryInstances = Set[ITelegramQuery](
RawText(),
MyInformation(),
ShareToolTwitter(),
ShareToolBilibili(),
ShareToolSocialContent()
)
private val queries = mutable.Queue.empty[ITelegramQuery]
def register (queries: ITelegramQuery*): Unit =
this.queries ++= queries
def query (event: Update): List[InlineQueryUnit[_]] = {
val results = ListBuffer[InlineQueryUnit[_]]()
for (instance <- queryInstances) {
for (instance <- queries) {
val r = instance query event
if (r != null) results ++= r
}

View File

@ -184,12 +184,12 @@ class MornyReport (using coeur: MornyCoeur) {
//noinspection ScalaWeakerAccess
case class EventTimeUsed (it: DurationMillis)
override def atEventPost (using event: EventEnv): Unit = {
import event.State
import event.*
eventTotal += 1
event.state match
case State.OK(from) =>
val timeUsed = EventTimeUsed(System.currentTimeMillis - event.timeStartup)
event provide timeUsed
givenCxt << timeUsed
logger debug
s"""event done with OK
| with time consumed ${timeUsed.it}ms

View File

@ -0,0 +1,58 @@
package cc.sukazyo.cono.morny.util
import scala.annotation.targetName
import scala.collection.mutable
import scala.reflect.{classTag, ClassTag}
class GivenContext {
private type ImplicitsMap [T <: Any] = mutable.HashMap[Class[?], T]
private val variables: ImplicitsMap[Any] = mutable.HashMap.empty
private val variablesWithOwner: ImplicitsMap[ImplicitsMap[Any]] = mutable.HashMap.empty
def provide (i: Any): Unit =
variables += (i.getClass -> i)
def << (i: Any): Unit =
this.provide(i)
def >>[T: ClassTag] (consumer: T => Unit): ConsumeResult =
this.use[T](consumer)
def use [T: ClassTag] (consumer: T => Unit): ConsumeResult =
variables get classTag[T].runtimeClass match
case Some(i) => consumer(i.asInstanceOf[T]); ConsumeResult(true)
case None => ConsumeResult(false)
def consume [T: ClassTag] (consume: T => Unit): ConsumeResult =
this.use[T](consume)
@targetName("ownedBy")
def / [O: ClassTag] (owner: O): OwnedContext[O] =
OwnedContext[O]()
def ownedBy [O: ClassTag]: OwnedContext[O] =
OwnedContext[O]()
class OwnedContext [O: ClassTag] {
def provide (i: Any): Unit =
(variablesWithOwner getOrElseUpdate (classTag[O].runtimeClass, mutable.HashMap.empty))
.addOne(i.getClass -> i)
def << (i: Any): Unit =
this.provide(i)
def >> [T: ClassTag] (consumer: T => Unit): ConsumeResult =
this.use[T](consumer)
def use [T: ClassTag] (consumer: T => Unit): ConsumeResult =
variablesWithOwner(classTag[O].runtimeClass) get classTag[T].runtimeClass match
case Some(i) => consumer(i.asInstanceOf[T]); ConsumeResult(true)
case None => ConsumeResult(false)
}
class ConsumeResult (success: Boolean) {
@targetName("orElse")
def || (processor: => Unit): Unit = {
if !success then processor
}
}
}