diff --git a/gradle.properties b/gradle.properties
index a21d2a6..595b471 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -5,19 +5,19 @@ 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.1.1
+VERSION = 1.2.0
 
 USE_DELTA = false
 VERSION_DELTA =
 
-CODENAME = nanchang
+CODENAME = xiongan
 
 # dependencies
 
 lib_spotbugs_v = 4.7.3
 lib_scalamodule_xml_v = 2.2.0
 
-lib_messiva_v = 0.1.1
+lib_messiva_v = 0.2.0
 lib_resourcetools_v = 0.2.2
 
 lib_javatelegramapi_v = 6.2.0
diff --git a/settings.gradle b/settings.gradle
index 92f8302..b7b457e 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -1,2 +1,2 @@
-rootProject.name = 'Coeur Morny Cono'
+rootProject.name = "Coeur Morny Cono"
 
diff --git a/src/main/scala/cc/sukazyo/cono/morny/Log.scala b/src/main/scala/cc/sukazyo/cono/morny/Log.scala
index 5ef60b3..c8dd452 100644
--- a/src/main/scala/cc/sukazyo/cono/morny/Log.scala
+++ b/src/main/scala/cc/sukazyo/cono/morny/Log.scala
@@ -1,25 +1,27 @@
 package cc.sukazyo.cono.morny
 
+import cc.sukazyo.cono.morny.internal.logging.{MornyFormatterConsole, MornyLoggerBase}
 import cc.sukazyo.messiva.appender.ConsoleAppender
 import cc.sukazyo.messiva.formatter.SimpleFormatter
-import cc.sukazyo.messiva.log.LogLevel
+import cc.sukazyo.messiva.log.LogLevels
 import cc.sukazyo.messiva.logger.Logger
 
 import java.io.{PrintWriter, StringWriter}
 
 object Log {
 	
-	val logger: Logger = Logger(
+	val logger: MornyLoggerBase = MornyLoggerBase(
 		ConsoleAppender(
-			SimpleFormatter()
+			MornyFormatterConsole()
 		)
-	).minLevel(LogLevel.INFO)
+	)
+	logger minLevel LogLevels.INFO
 	
-	def debug: Boolean = logger.levelSetting.minLevel.level <= LogLevel.DEBUG.level
+	def debug: Boolean = logger.levelSetting.minLevel.level <= LogLevels.DEBUG.level
 	
 	def debug(is: Boolean): Unit =
-		if is then logger.minLevel(LogLevel.ALL)
-		else logger.minLevel(LogLevel.INFO)
+		if is then logger.minLevel(LogLevels.ALL)
+		else logger.minLevel(LogLevels.INFO)
 	
 	def exceptionLog (e: Throwable): String =
 		val stackTrace = StringWriter()
diff --git a/src/main/scala/cc/sukazyo/cono/morny/MornyCoeur.scala b/src/main/scala/cc/sukazyo/cono/morny/MornyCoeur.scala
index 905489f..e9337cf 100644
--- a/src/main/scala/cc/sukazyo/cono/morny/MornyCoeur.scala
+++ b/src/main/scala/cc/sukazyo/cono/morny/MornyCoeur.scala
@@ -27,7 +27,8 @@ class MornyCoeur (using val config: MornyConfig) {
 	
 	logger info "Coeur starting..."
 	
-	logger info s"args key:\n  ${config.telegramBotKey}"
+	import cc.sukazyo.cono.morny.util.StringEnsure.deSensitive
+	logger info s"args key:\n  ${config.telegramBotKey deSensitive 4}"
 	if config.telegramBotUsername ne null then
 		logger info s"login as:\n  ${config.telegramBotUsername}"
 	
@@ -92,7 +93,7 @@ class MornyCoeur (using val config: MornyConfig) {
 	
 	def saveDataAll(): Unit = {
 		// nothing to do
-		logger info "done all save action."
+		logger notice "done all save action."
 	}
 	
 	private def exitCleanup (): Unit = {
diff --git a/src/main/scala/cc/sukazyo/cono/morny/ServerMain.scala b/src/main/scala/cc/sukazyo/cono/morny/ServerMain.scala
index 86a0c0a..e4f7870 100644
--- a/src/main/scala/cc/sukazyo/cono/morny/ServerMain.scala
+++ b/src/main/scala/cc/sukazyo/cono/morny/ServerMain.scala
@@ -5,11 +5,15 @@ import cc.sukazyo.cono.morny.MornyConfig.CheckFailure
 import cc.sukazyo.cono.morny.util.CommonFormat
 
 import java.time.ZoneOffset
+import java.util.TimeZone
 import scala.collection.mutable.ArrayBuffer
 import scala.language.postfixOps
 
 object ServerMain {
 	
+	val tz: TimeZone = TimeZone getDefault
+	val tz_offset: ZoneOffset = ZoneOffset ofTotalSeconds (tz.getRawOffset/1000)
+	
 	private val THREAD_MORNY_INIT: String = "morny-init"
 	
 	def main (args: Array[String]): Unit = {
@@ -135,6 +139,9 @@ object ServerMain {
 				   |- Morny ${MornySystem.CODENAME toUpperCase}
 				   |- <${MornySystem.getJarMD5}> [${BuildConfig.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)"
+		
 		///
 		/// Check Coeur arguments
 		/// finally start Coeur Program
diff --git a/src/main/scala/cc/sukazyo/cono/morny/bot/api/EventEnv.scala b/src/main/scala/cc/sukazyo/cono/morny/bot/api/EventEnv.scala
new file mode 100644
index 0000000..6cc7dea
--- /dev/null
+++ b/src/main/scala/cc/sukazyo/cono/morny/bot/api/EventEnv.scala
@@ -0,0 +1,37 @@
+package cc.sukazyo.cono.morny.bot.api
+
+import com.pengrad.telegrambot.model.Update
+
+import scala.collection.mutable
+
+class EventEnv (
+	
+	val update: Update
+	
+) {
+	
+	private var _isOk: Int = 0
+	private val variables: mutable.HashMap[Class[?], Any] = mutable.HashMap.empty
+	
+	def isEventOk: Boolean = _isOk > 0
+	
+	//noinspection UnitMethodIsParameterless
+	def setEventOk: Unit =
+		_isOk =  _isOk + 1
+	
+	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)
+	}
+	
+	class ConsumeResult (success: Boolean) {
+		def onfail (processor: => Unit): Unit = {
+			if !success then processor
+		}
+	}
+	
+}
diff --git a/src/main/scala/cc/sukazyo/cono/morny/bot/api/EventListener.scala b/src/main/scala/cc/sukazyo/cono/morny/bot/api/EventListener.scala
index 0a3e1ac..3c2fbb9 100644
--- a/src/main/scala/cc/sukazyo/cono/morny/bot/api/EventListener.scala
+++ b/src/main/scala/cc/sukazyo/cono/morny/bot/api/EventListener.scala
@@ -1,22 +1,20 @@
 package cc.sukazyo.cono.morny.bot.api
 
-import com.pengrad.telegrambot.model.Update
-
 trait EventListener () {
 	
-	def onMessage (using Update): Boolean = false
-	def onEditedMessage (using Update): Boolean = false
-	def onChannelPost (using Update): Boolean = false
-	def onEditedChannelPost (using Update): Boolean = false
-	def onInlineQuery (using Update): Boolean = false
-	def onChosenInlineResult (using Update): Boolean = false
-	def onCallbackQuery (using Update): Boolean = false
-	def onShippingQuery (using Update): Boolean = false
-	def onPreCheckoutQuery (using Update): Boolean = false
-	def onPoll (using Update): Boolean = false
-	def onPollAnswer (using Update): Boolean = false
-	def onMyChatMemberUpdated (using Update): Boolean = false
-	def onChatMemberUpdated (using Update): Boolean = false
-	def onChatJoinRequest (using Update): Boolean = false
+	def onMessage (using EventEnv): Unit = {}
+	def onEditedMessage (using EventEnv): Unit = {}
+	def onChannelPost (using EventEnv): Unit = {}
+	def onEditedChannelPost (using EventEnv): Unit = {}
+	def onInlineQuery (using EventEnv): Unit = {}
+	def onChosenInlineResult (using EventEnv): Unit = {}
+	def onCallbackQuery (using EventEnv): Unit = {}
+	def onShippingQuery (using EventEnv): Unit = {}
+	def onPreCheckoutQuery (using EventEnv): Unit = {}
+	def onPoll (using EventEnv): Unit = {}
+	def onPollAnswer (using EventEnv): Unit = {}
+	def onMyChatMemberUpdated (using EventEnv): Unit = {}
+	def onChatMemberUpdated (using EventEnv): Unit = {}
+	def onChatJoinRequest (using EventEnv): Unit = {}
 	
 }
diff --git a/src/main/scala/cc/sukazyo/cono/morny/bot/api/EventListenerManager.scala b/src/main/scala/cc/sukazyo/cono/morny/bot/api/EventListenerManager.scala
index 5448204..de846ab 100644
--- a/src/main/scala/cc/sukazyo/cono/morny/bot/api/EventListenerManager.scala
+++ b/src/main/scala/cc/sukazyo/cono/morny/bot/api/EventListenerManager.scala
@@ -9,6 +9,7 @@ import com.pengrad.telegrambot.UpdatesListener
 
 import scala.collection.mutable
 import scala.language.postfixOps
+import scala.util.boundary
 
 /** Contains a [[mutable.Queue]] of [[EventListener]], and delivery telegram [[Update]].
   *
@@ -23,46 +24,43 @@ class EventListenerManager (using coeur: MornyCoeur) extends UpdatesListener {
 	def register (listeners: EventListener*): Unit =
 		this.listeners ++= listeners
 	
-	private class EventRunner (using event: Update) extends Thread {
-		this setName s"evt-${event.updateId()}-nn"
+	private class EventRunner (using update: Update) extends Thread {
+		this setName s"upd-${update.updateId()}-nn"
 		private def updateThreadName (t: String): Unit =
-			this setName s"evt-${event.updateId()}-$t"
+			this setName s"upd-${update.updateId()}-$t"
 		
 		override def run (): Unit = {
-			for (i <- listeners) {
-				object status:
-					var _status = 0
-					def isOk: Boolean = _status > 0
-					def check (u: Boolean): Unit = if u then _status = _status + 1
+			given env: EventEnv = EventEnv(update)
+			boundary { for (i <- listeners) {
 				try {
 					updateThreadName("message")
-					if event.message ne null then status check i.onMessage
+					if update.message ne null then i.onMessage
 					updateThreadName("edited-message")
-					if event.editedMessage ne null then status check i.onEditedMessage
+					if update.editedMessage ne null then i.onEditedMessage
 					updateThreadName("channel-post")
-					if event.channelPost ne null then status check i.onChannelPost
+					if update.channelPost ne null then i.onChannelPost
 					updateThreadName("edited-channel-post")
-					if event.editedChannelPost ne null then status check i.onEditedChannelPost
+					if update.editedChannelPost ne null then i.onEditedChannelPost
 					updateThreadName("inline-query")
-					if event.inlineQuery ne null then status check i.onInlineQuery
+					if update.inlineQuery ne null then i.onInlineQuery
 					updateThreadName("chosen-inline-result")
-					if event.chosenInlineResult ne null then status check i.onChosenInlineResult
+					if update.chosenInlineResult ne null then i.onChosenInlineResult
 					updateThreadName("callback-query")
-					if event.callbackQuery ne null then status check i.onCallbackQuery
+					if update.callbackQuery ne null then i.onCallbackQuery
 					updateThreadName("shipping-query")
-					if event.shippingQuery ne null then status check i.onShippingQuery
+					if update.shippingQuery ne null then i.onShippingQuery
 					updateThreadName("pre-checkout-query")
-					if event.preCheckoutQuery ne null then status check i.onPreCheckoutQuery
+					if update.preCheckoutQuery ne null then i.onPreCheckoutQuery
 					updateThreadName("poll")
-					if event.poll ne null then status check i.onPoll
+					if update.poll ne null then i.onPoll
 					updateThreadName("poll-answer")
-					if event.pollAnswer ne null then status check i.onPollAnswer
+					if update.pollAnswer ne null then i.onPollAnswer
 					updateThreadName("my-chat-member")
-					if event.myChatMember ne null then status check i.onMyChatMemberUpdated
+					if update.myChatMember ne null then i.onMyChatMemberUpdated
 					updateThreadName("chat-member")
-					if event.chatMember ne null then status check i.onChatMemberUpdated
+					if update.chatMember ne null then i.onChatMemberUpdated
 					updateThreadName("chat-join-request")
-					if event.chatJoinRequest ne null then status check i.onChatJoinRequest
+					if update.chatJoinRequest ne null then i.onChatJoinRequest
 				} catch case e => {
 					val errorMessage = StringBuilder()
 					errorMessage ++= "Event throws unexpected exception:\n"
@@ -77,8 +75,8 @@ class EventListenerManager (using coeur: MornyCoeur) extends UpdatesListener {
 					logger error errorMessage.toString
 					coeur.daemons.reporter.exception(e, "on event running")
 				}
-				if (status isOk) return
-			}
+				if env.isEventOk then boundary.break()
+			}}
 		}
 		
 	}
diff --git a/src/main/scala/cc/sukazyo/cono/morny/bot/command/Encryptor.scala b/src/main/scala/cc/sukazyo/cono/morny/bot/command/Encryptor.scala
index 07b1c5a..b63dde4 100644
--- a/src/main/scala/cc/sukazyo/cono/morny/bot/command/Encryptor.scala
+++ b/src/main/scala/cc/sukazyo/cono/morny/bot/command/Encryptor.scala
@@ -2,6 +2,7 @@ package cc.sukazyo.cono.morny.bot.command
 
 import cc.sukazyo.cono.morny.Log.logger
 import cc.sukazyo.cono.morny.MornyCoeur
+import cc.sukazyo.cono.morny.bot.command.ICommandAlias.ListedAlias
 import cc.sukazyo.cono.morny.data.TelegramStickers
 import cc.sukazyo.cono.morny.util.tgapi.InputCommand
 import cc.sukazyo.cono.morny.util.CommonEncrypt
@@ -13,6 +14,7 @@ import com.pengrad.telegrambot.model.request.ParseMode
 import com.pengrad.telegrambot.request.{GetFile, SendDocument, SendMessage, SendSticker}
 
 import java.io.IOException
+import java.net.{URLDecoder, URLEncoder}
 import java.util.Base64
 import scala.language.postfixOps
 
@@ -20,7 +22,7 @@ import scala.language.postfixOps
 class Encryptor (using coeur: MornyCoeur) extends ITelegramCommand {
 	
 	override val name: String = "encrypt"
-	override val aliases: Array[ICommandAlias] | Null = null
+	override val aliases: Array[ICommandAlias] | Null = Array(ListedAlias("enc"))
 	override val paramRule: String = "[algorithm|(l)] [(uppercase)]"
 	override val description: String = "通过指定算法加密回复的内容 (目前只支持文本)"
 	
@@ -135,6 +137,12 @@ class Encryptor (using coeur: MornyCoeur) extends ITelegramCommand {
 		def genResult_hash (source: XEncryptable, processor: Array[Byte]=>Array[Byte]): EXHash =
 			val hashed = processor(source asByteArray) toHex;
 			EXHash(if mod_uppercase then hashed toUpperCase else hashed)
+		//noinspection UnitMethodIsParameterless
+		def echo_unsupported: Unit =
+			coeur.account exec SendSticker(
+				event.message.chat.id,
+				TelegramStickers ID_404
+			).replyToMessageId(event.message.messageId)
 		val result: EXHash|EXFile|EXText = args(0) match
 			case "base64" | "b64" | "base64url" | "base64u" | "b64u" =>
 				val _tool_b64 =
@@ -154,21 +162,27 @@ class Encryptor (using coeur: MornyCoeur) extends ITelegramCommand {
 					_tool_b64d.decode,
 					CommonEncrypt.lint_base64FileName
 				) } catch case _: IllegalArgumentException =>
-					coeur.account exec SendSticker(
-						event.message.chat.id,
-						TelegramStickers ID_404 // todo: is here better erro notify?
-					).replyToMessageId(event.message.messageId)
+					echo_unsupported
 					return
+			case "urlencoder" | "urlencode" | "urlenc" | "url" =>
+				input match
+					case x: XText =>
+						EXText(URLEncoder.encode(x.data, ENCRYPT_STANDARD_CHARSET))
+					case _: XFile => echo_unsupported; return;
+			case "urldecoder" | "urldecode" | "urldec" | "urld" =>
+				input match
+					case _: XFile => echo_unsupported; return;
+					case x: XText =>
+						try { EXText(URLDecoder.decode(x.data, ENCRYPT_STANDARD_CHARSET)) }
+						catch case _: IllegalArgumentException =>
+								echo_unsupported
+								return
 			case "md5" => genResult_hash(input, MD5)
 			case "sha1" => genResult_hash(input, SHA1)
 			case "sha256" => genResult_hash(input, SHA256)
 			case "sha512" => genResult_hash(input, SHA512)
 			case _ =>
-				coeur.account exec SendSticker(
-					event.message.chat.id,
-					TelegramStickers ID_404
-				).replyToMessageId(event.message.messageId)
-				return;
+				echo_unsupported; return;
 		// END BLOCK: encrypt
 		
 		// output
@@ -203,6 +217,8 @@ class Encryptor (using coeur: MornyCoeur) extends ITelegramCommand {
 	  * 	'''__base64url__''', base64u, b64u<br>
 	  * 	'''__base64decode__''', base64d, b64d<br>
 	  * 	'''__base64url-decode__''', base64ud, b64ud<br>
+	  *     '''urlencode''', urlencode, urlenc, url<br>
+	  *     '''__urldecoder__''', urldecode, urldec, urld<br>
 	  * 	'''__sha1__'''<br>
 	  * 	'''__sha256__'''<br>
 	  * 	'''__sha512__'''<br>
@@ -218,6 +234,8 @@ class Encryptor (using coeur: MornyCoeur) extends ITelegramCommand {
 			   |<b><u>base64url</u></b>, base64u, b64u
 			   |<b><u>base64decode</u></b>, base64d, b64d
 			   |<b><u>base64url-decode</u></b>, base64ud, b64ud
+			   |<b><u>urlencoder</u></b>, urlencode, urlenc, url
+			   |<b><u>urldecoder</u></b>, urldecode, urldec, urld
 			   |<b><u>sha1</u></b>
 			   |<b><u>sha256</u></b>
 			   |<b><u>sha512</u></b>
diff --git a/src/main/scala/cc/sukazyo/cono/morny/bot/command/MornyCommands.scala b/src/main/scala/cc/sukazyo/cono/morny/bot/command/MornyCommands.scala
index 0121b0c..e924d7e 100644
--- a/src/main/scala/cc/sukazyo/cono/morny/bot/command/MornyCommands.scala
+++ b/src/main/scala/cc/sukazyo/cono/morny/bot/command/MornyCommands.scala
@@ -91,14 +91,14 @@ class MornyCommands (using coeur: MornyCoeur) {
 		val listing = commands_toTelegramList
 		automaticTGListRemove()
 		coeur.account exec SetMyCommands(listing:_*)
-		logger info
+		logger notice
 				s"""automatic updated telegram command list :
 				   |${commandsTelegramList_toString(listing)}""".stripMargin
 	}
 	
 	def automaticTGListRemove (): Unit = {
 		coeur.account exec DeleteMyCommands()
-		logger info "cleaned up command list"
+		logger notice "cleaned up command list"
 	}
 	
 	private def commandsTelegramList_toString (list: Array[BotCommand]): String =
diff --git a/src/main/scala/cc/sukazyo/cono/morny/bot/command/MornyManagers.scala b/src/main/scala/cc/sukazyo/cono/morny/bot/command/MornyManagers.scala
index 5a143c0..aea4127 100644
--- a/src/main/scala/cc/sukazyo/cono/morny/bot/command/MornyManagers.scala
+++ b/src/main/scala/cc/sukazyo/cono/morny/bot/command/MornyManagers.scala
@@ -31,7 +31,7 @@ class MornyManagers (using coeur: MornyCoeur) {
 					event.message.chat.id,
 					TelegramStickers ID_EXIT
 				).replyToMessageId(event.message.messageId)
-				logger info s"Morny exited by user ${user toLogTag}"
+				logger attention s"Morny exited by user ${user toLogTag}"
 				coeur.exit(0, user)
 				
 			} else {
@@ -40,7 +40,7 @@ class MornyManagers (using coeur: MornyCoeur) {
 					event.message.chat.id,
 					TelegramStickers ID_403
 				).replyToMessageId(event.message.messageId)
-				logger info s"403 exit caught from user ${user toLogTag}"
+				logger attention s"403 exit caught from user ${user toLogTag}"
 				coeur.daemons.reporter.unauthenticatedAction("/exit", user)
 				
 			}
@@ -62,7 +62,7 @@ class MornyManagers (using coeur: MornyCoeur) {
 			
 			if (coeur.trusted isTrusted user.id) {
 				
-				logger info s"call save from command by ${user toLogTag}"
+				logger attention s"call save from command by ${user toLogTag}"
 				coeur.saveDataAll()
 				coeur.account exec SendSticker(
 					event.message.chat.id,
@@ -75,7 +75,7 @@ class MornyManagers (using coeur: MornyCoeur) {
 					event.message.chat.id,
 					TelegramStickers ID_403
 				).replyToMessageId(event.message.messageId)
-				logger info s"403 save caught from user ${user toLogTag}"
+				logger attention s"403 save caught from user ${user toLogTag}"
 				coeur.daemons.reporter.unauthenticatedAction("/save", user)
 				
 			}
diff --git a/src/main/scala/cc/sukazyo/cono/morny/bot/command/Testing.scala b/src/main/scala/cc/sukazyo/cono/morny/bot/command/Testing.scala
index b7a1393..949fa83 100644
--- a/src/main/scala/cc/sukazyo/cono/morny/bot/command/Testing.scala
+++ b/src/main/scala/cc/sukazyo/cono/morny/bot/command/Testing.scala
@@ -7,7 +7,6 @@ import com.pengrad.telegrambot.model.Update
 import com.pengrad.telegrambot.model.request.ParseMode
 import com.pengrad.telegrambot.request.SendMessage
 
-import javax.annotation.{Nonnull, Nullable}
 import scala.language.postfixOps
 
 class Testing (using coeur: MornyCoeur) extends ISimpleCommand {
diff --git a/src/main/scala/cc/sukazyo/cono/morny/bot/event/MornyOnInlineQuery.scala b/src/main/scala/cc/sukazyo/cono/morny/bot/event/MornyOnInlineQuery.scala
index 38b8f95..e079dd0 100644
--- a/src/main/scala/cc/sukazyo/cono/morny/bot/event/MornyOnInlineQuery.scala
+++ b/src/main/scala/cc/sukazyo/cono/morny/bot/event/MornyOnInlineQuery.scala
@@ -1,7 +1,7 @@
 package cc.sukazyo.cono.morny.bot.event
 
 import cc.sukazyo.cono.morny.MornyCoeur
-import cc.sukazyo.cono.morny.bot.api.EventListener
+import cc.sukazyo.cono.morny.bot.api.{EventEnv, EventListener}
 import cc.sukazyo.cono.morny.bot.query.{InlineQueryUnit, MornyQueries}
 import cc.sukazyo.cono.morny.util.tgapi.TelegramExtensions.Bot.exec
 import com.pengrad.telegrambot.model.Update
@@ -14,7 +14,8 @@ import scala.reflect.ClassTag
 
 class MornyOnInlineQuery (using queryManager: MornyQueries) (using coeur: MornyCoeur) extends EventListener {
 	
-	override def onInlineQuery (using update: Update): Boolean = {
+	override def onInlineQuery (using event: EventEnv): Unit = {
+		import event.update
 		
 		val results: List[InlineQueryUnit[_]] = queryManager query update
 		
@@ -27,12 +28,13 @@ class MornyOnInlineQuery (using queryManager: MornyQueries) (using coeur: MornyC
 			resultAnswers += r.result
 		}
 		
-		if (results isEmpty) return false
+		if (results isEmpty) return;
 		
 		coeur.account exec AnswerInlineQuery(
 			update.inlineQuery.id, resultAnswers toArray:_*
 		).cacheTime(cacheTime).isPersonal(isPersonal)
-		true
+		
+		event.setEventOk
 		
 	}
 	
diff --git a/src/main/scala/cc/sukazyo/cono/morny/bot/event/MornyOnTelegramCommand.scala b/src/main/scala/cc/sukazyo/cono/morny/bot/event/MornyOnTelegramCommand.scala
index 541070b..66c0f66 100644
--- a/src/main/scala/cc/sukazyo/cono/morny/bot/event/MornyOnTelegramCommand.scala
+++ b/src/main/scala/cc/sukazyo/cono/morny/bot/event/MornyOnTelegramCommand.scala
@@ -1,6 +1,6 @@
 package cc.sukazyo.cono.morny.bot.event
 
-import cc.sukazyo.cono.morny.bot.api.EventListener
+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
@@ -9,7 +9,8 @@ import com.pengrad.telegrambot.model.{Message, Update}
 
 class MornyOnTelegramCommand (using commandManager: MornyCommands) (using coeur: MornyCoeur) extends EventListener {
 	
-	override def onMessage (using update: Update): Boolean = {
+	override def onMessage (using event: EventEnv): Unit = {
+		given update: Update = event.update
 		
 		def _isCommandMessage(message: Message): Boolean =
 			if message.text eq null then false
@@ -17,17 +18,19 @@ class MornyOnTelegramCommand (using commandManager: MornyCommands) (using coeur:
 			else if message.text startsWith "/ " then false
 			else true
 		
-		if !_isCommandMessage(update.message) then return false
+		if !_isCommandMessage(update.message) then return
 		val inputCommand = InputCommand(update.message.text drop 1)
+		event provide inputCommand
+		logger trace ":provided InputCommand for event"
+		
 		if (!(inputCommand.command matches "^\\w+$"))
 			logger debug "not command"
-			false
 		else if ((inputCommand.target ne null) && (inputCommand.target != coeur.username))
 			logger debug "not morny command"
-			false
 		else
 			logger debug "is command"
-			commandManager.execute(using inputCommand)
+			if commandManager.execute(using inputCommand) then
+				event.setEventOk
 		
 	}
 	
diff --git a/src/main/scala/cc/sukazyo/cono/morny/bot/event/MornyOnUpdateTimestampOffsetLock.scala b/src/main/scala/cc/sukazyo/cono/morny/bot/event/MornyOnUpdateTimestampOffsetLock.scala
index fc9f575..ae44432 100644
--- a/src/main/scala/cc/sukazyo/cono/morny/bot/event/MornyOnUpdateTimestampOffsetLock.scala
+++ b/src/main/scala/cc/sukazyo/cono/morny/bot/event/MornyOnUpdateTimestampOffsetLock.scala
@@ -1,17 +1,18 @@
 package cc.sukazyo.cono.morny.bot.event
 
-import cc.sukazyo.cono.morny.bot.api.EventListener
+import cc.sukazyo.cono.morny.bot.api.{EventEnv, EventListener}
 import cc.sukazyo.cono.morny.MornyCoeur
 import com.pengrad.telegrambot.model.Update
 
 class MornyOnUpdateTimestampOffsetLock (using coeur: MornyCoeur) extends EventListener {
 	
-	private def isOutdated (timestamp: Int): Boolean =
-		coeur.config.eventIgnoreOutdated && (timestamp < (coeur.coeurStartTimestamp/1000))
+	private def checkOutdated (timestamp: Int)(using event: EventEnv): Unit =
+		if coeur.config.eventIgnoreOutdated && (timestamp < (coeur.coeurStartTimestamp/1000)) then
+			event.setEventOk
 	
-	override def onMessage (using update: Update): Boolean = isOutdated(update.message.date)
-	override def onEditedMessage (using update: Update): Boolean = isOutdated(update.editedMessage.date)
-	override def onChannelPost (using update: Update): Boolean = isOutdated(update.channelPost.date)
-	override def onEditedChannelPost (using update: Update): Boolean = isOutdated(update.editedChannelPost.date)
+	override def onMessage (using event: EventEnv): Unit = checkOutdated(event.update.message.date)
+	override def onEditedMessage (using event: EventEnv): Unit = checkOutdated(event.update.editedMessage.date)
+	override def onChannelPost (using event: EventEnv): Unit = checkOutdated(event.update.channelPost.date)
+	override def onEditedChannelPost (using event: EventEnv): Unit = checkOutdated(event.update.editedChannelPost.date)
 	
 }
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 244ee66..4f80a69 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
@@ -1,7 +1,7 @@
 package cc.sukazyo.cono.morny.bot.event
 
 import cc.sukazyo.cono.morny.MornyCoeur
-import cc.sukazyo.cono.morny.bot.api.EventListener
+import cc.sukazyo.cono.morny.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
@@ -15,10 +15,11 @@ class OnCallMe (using coeur: MornyCoeur) extends EventListener {
 	
 	private val me = coeur.config.trustedMaster
 	
-	override def onMessage (using update: Update): Boolean = {
+	override def onMessage (using event: EventEnv): Unit = {
+		import event.update
 		
-		if update.message.text == null then return false
-		if update.message.chat.`type` != (Chat.Type Private) then return false
+		if update.message.text == null then return;
+		if update.message.chat.`type` != (Chat.Type Private) then return
 		
 		//noinspection ScalaUnnecessaryParentheses
 		val success = if me == -1 then false else
@@ -32,7 +33,7 @@ class OnCallMe (using coeur: MornyCoeur) extends EventListener {
 				case cc if cc startsWith "cc::" =>
 					requestCustom(update.message)
 				case _ =>
-					return false
+					return;
 		
 		if success then
 			coeur.account exec SendSticker(
@@ -45,7 +46,7 @@ class OnCallMe (using coeur: MornyCoeur) extends EventListener {
 				TelegramStickers ID_501
 			).replyToMessageId(update.message.messageId)
 		
-		true
+		event.setEventOk
 		
 	}
 	
diff --git a/src/main/scala/cc/sukazyo/cono/morny/bot/event/OnCallMsgSend.scala b/src/main/scala/cc/sukazyo/cono/morny/bot/event/OnCallMsgSend.scala
index db920c0..00a2ef8 100644
--- a/src/main/scala/cc/sukazyo/cono/morny/bot/event/OnCallMsgSend.scala
+++ b/src/main/scala/cc/sukazyo/cono/morny/bot/event/OnCallMsgSend.scala
@@ -1,6 +1,6 @@
 package cc.sukazyo.cono.morny.bot.event
 
-import cc.sukazyo.cono.morny.bot.api.EventListener
+import cc.sukazyo.cono.morny.bot.api.{EventEnv, EventListener}
 import cc.sukazyo.cono.morny.MornyCoeur
 import cc.sukazyo.cono.morny.data.TelegramStickers
 import cc.sukazyo.cono.morny.util.tgapi.TelegramExtensions.Bot.exec
@@ -52,26 +52,28 @@ class OnCallMsgSend (using coeur: MornyCoeur) extends EventListener {
 				case _ => null
 		}
 	
-	override def onMessage (using update: Update): Boolean = {
+	override def onMessage (using event: EventEnv): Unit = {
+		import event.update
 		
 		val message = update.message
 		
-		if message.chat.`type` != Chat.Type.Private then return false
-		if message.text eq null then return false
-		if !(message.text startsWith "*msg") then return false
+		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(
 				message.chat.id,
 				TelegramStickers ID_403
 			).replyToMessageId(message.messageId)
-			return true
+			event.setEventOk
+			return;
 		
 		if (message.text == "*msgsend") {
 			
-			if (message.replyToMessage eq null) return answer404
+			if (message.replyToMessage eq null) { answer404; return }
 			val messageToSend = MessageToSend from message.replyToMessage
-			if ((messageToSend eq null) || (messageToSend.message eq null)) return answer404
+			if ((messageToSend eq null) || (messageToSend.message eq null)) { answer404; return }
 			val sendResponse = coeur.account execute messageToSend.toSendMessage()
 			
 			if (sendResponse isOk) {
@@ -89,20 +91,21 @@ class OnCallMsgSend (using coeur: MornyCoeur) extends EventListener {
 				).replyToMessageId(update.message.messageId).parseMode(ParseMode HTML)
 			}
 			
-			return true
+			event.setEventOk
+			return
 			
 		}
 		
 		val messageToSend: MessageToSend =
 			val raw: Message =
 				if (message.text == "*msg")
-					if message.replyToMessage eq null then return answer404
+					if message.replyToMessage eq null then { answer404; return }
 					else message.replyToMessage
 				else if (message.text startsWith "*msg")
 					message
-				else return answer404
+				else { answer404; return }
 			val _toSend = MessageToSend from raw
-			if _toSend eq null then return answer404
+			if _toSend eq null then { answer404; return }
 			else _toSend
 		
 		val targetChatResponse = coeur.account execute GetChat(messageToSend.targetId)
@@ -128,7 +131,7 @@ class OnCallMsgSend (using coeur: MornyCoeur) extends EventListener {
 			).parseMode(ParseMode HTML).replyToMessageId(update.message.messageId)
 		}
 		
-		if messageToSend.message eq null then return true
+		if messageToSend.message eq null then { answer404; return }
 		val testSendResponse = coeur.account execute
 			messageToSend.toSendMessage(update.message.chat.id).replyToMessageId(update.message.messageId)
 		if (!(testSendResponse isOk))
@@ -140,15 +143,15 @@ class OnCallMsgSend (using coeur: MornyCoeur) extends EventListener {
 				.stripMargin
 			).parseMode(ParseMode HTML).replyToMessageId(update.message.messageId)
 		
-		true
+		event.setEventOk
 		
 	}
 	
-	private def answer404 (using update: Update): Boolean =
+	private def answer404 (using event: EventEnv): Unit =
 		coeur.account exec SendSticker(
-			update.message.chat.id,
+			event.update.message.chat.id,
 			TelegramStickers ID_404
-		).replyToMessageId(update.message.messageId)
-		true
+		).replyToMessageId(event.update.message.messageId)
+		event.setEventOk
 	
 }
diff --git a/src/main/scala/cc/sukazyo/cono/morny/bot/event/OnEventHackHandle.scala b/src/main/scala/cc/sukazyo/cono/morny/bot/event/OnEventHackHandle.scala
index d6bc1f6..13f78ae 100644
--- a/src/main/scala/cc/sukazyo/cono/morny/bot/event/OnEventHackHandle.scala
+++ b/src/main/scala/cc/sukazyo/cono/morny/bot/event/OnEventHackHandle.scala
@@ -1,6 +1,6 @@
 package cc.sukazyo.cono.morny.bot.event
 
-import cc.sukazyo.cono.morny.bot.api.EventListener
+import cc.sukazyo.cono.morny.bot.api.{EventEnv, EventListener}
 import cc.sukazyo.cono.morny.Log.logger
 import cc.sukazyo.cono.morny.MornyCoeur
 import com.google.gson.GsonBuilder
@@ -13,35 +13,38 @@ import scala.language.postfixOps
 
 class OnEventHackHandle (using coeur: MornyCoeur) extends EventListener {
 	
-	import coeur.daemons.eventHack.trigger
+	private def trigger (chat_id: Long, from_id: Long)(using event: EventEnv): Unit =
+		given Update = event.update
+		if coeur.daemons.eventHack.trigger(chat_id, from_id) then
+			event.setEventOk
 	
-	override def onMessage (using update: Update): Boolean =
-		trigger(update.message.chat.id, update.message.from.id)
-	override def onEditedMessage (using update: Update): Boolean =
-		trigger(update.editedMessage.chat.id, update.editedMessage.from.id)
-	override def onChannelPost (using update: Update): Boolean =
-		trigger(update.channelPost.chat.id, 0)
-	override def onEditedChannelPost (using update: Update): Boolean =
-		trigger(update.editedChannelPost.chat.id, 0)
-	override def onInlineQuery (using update: Update): Boolean =
-		trigger(0, update.inlineQuery.from.id)
-	override def onChosenInlineResult (using update: Update): Boolean =
-		trigger(0, update.chosenInlineResult.from.id)
-	override def onCallbackQuery (using update: Update): Boolean =
-		trigger(0, update.callbackQuery.from.id)
-	override def onShippingQuery (using update: Update): Boolean =
-		trigger(0, update.shippingQuery.from.id)
-	override def onPreCheckoutQuery (using update: Update): Boolean =
-		trigger(0, update.preCheckoutQuery.from.id)
-	override def onPoll (using update: Update): Boolean =
+	override def onMessage (using event: EventEnv): Unit =
+		trigger(event.update.message.chat.id, event.update.message.from.id)
+	override def onEditedMessage (using event: EventEnv): Unit =
+		trigger(event.update.editedMessage.chat.id, event.update.editedMessage.from.id)
+	override def onChannelPost (using event: EventEnv): Unit =
+		trigger(event.update.channelPost.chat.id, 0)
+	override def onEditedChannelPost (using event: EventEnv): Unit =
+		trigger(event.update.editedChannelPost.chat.id, 0)
+	override def onInlineQuery (using event: EventEnv): Unit =
+		trigger(0, event.update.inlineQuery.from.id)
+	override def onChosenInlineResult (using event: EventEnv): Unit =
+		trigger(0, event.update.chosenInlineResult.from.id)
+	override def onCallbackQuery (using event: EventEnv): Unit =
+		trigger(0, event.update.callbackQuery.from.id)
+	override def onShippingQuery (using event: EventEnv): Unit =
+		trigger(0, event.update.shippingQuery.from.id)
+	override def onPreCheckoutQuery (using event: EventEnv): Unit =
+		trigger(0, event.update.preCheckoutQuery.from.id)
+	override def onPoll (using event: EventEnv): Unit =
 		trigger(0, 0)
-	override def onPollAnswer (using update: Update): Boolean =
-		trigger(0, update.pollAnswer.user.id)
-	override def onMyChatMemberUpdated (using update: Update): Boolean =
-		trigger(update.myChatMember.chat.id, update.myChatMember.from.id)
-	override def onChatMemberUpdated (using update: Update): Boolean =
-		trigger(update.chatMember.chat.id, update.chatMember.from.id)
-	override def onChatJoinRequest (using update: Update): Boolean =
-		trigger(update.chatJoinRequest.chat.id, update.chatJoinRequest.from.id)
+	override def onPollAnswer (using event: EventEnv): Unit =
+		trigger(0, event.update.pollAnswer.user.id)
+	override def onMyChatMemberUpdated (using event: EventEnv): Unit =
+		trigger(event.update.myChatMember.chat.id, event.update.myChatMember.from.id)
+	override def onChatMemberUpdated (using event: EventEnv): Unit =
+		trigger(event.update.chatMember.chat.id, event.update.chatMember.from.id)
+	override def onChatJoinRequest (using event: EventEnv): Unit =
+		trigger(event.update.chatJoinRequest.chat.id, event.update.chatJoinRequest.from.id)
 	
 }
diff --git a/src/main/scala/cc/sukazyo/cono/morny/bot/event/OnMedicationNotifyApply.scala b/src/main/scala/cc/sukazyo/cono/morny/bot/event/OnMedicationNotifyApply.scala
index 25b29c1..ed24c3b 100644
--- a/src/main/scala/cc/sukazyo/cono/morny/bot/event/OnMedicationNotifyApply.scala
+++ b/src/main/scala/cc/sukazyo/cono/morny/bot/event/OnMedicationNotifyApply.scala
@@ -1,21 +1,21 @@
 package cc.sukazyo.cono.morny.bot.event
 
-import cc.sukazyo.cono.morny.bot.api.EventListener
+import cc.sukazyo.cono.morny.bot.api.{EventEnv, EventListener}
 import cc.sukazyo.cono.morny.MornyCoeur
 import cc.sukazyo.cono.morny.daemon.{MedicationTimer, MornyDaemons}
 import com.pengrad.telegrambot.model.{Message, Update}
 
 class OnMedicationNotifyApply (using coeur: MornyCoeur) extends EventListener {
 	
-	override def onEditedMessage (using event: Update): Boolean =
-		editedMessageProcess(event.editedMessage)
-	override def onEditedChannelPost (using event: Update): Boolean =
-		editedMessageProcess(event.editedChannelPost)
+	override def onEditedMessage (using event: EventEnv): Unit =
+		editedMessageProcess(event.update.editedMessage)
+	override def onEditedChannelPost (using event: EventEnv): Unit =
+		editedMessageProcess(event.update.editedChannelPost)
 	
-	private def editedMessageProcess (edited: Message): Boolean = {
-		if edited.chat.id != coeur.config.medicationNotifyToChat then return false
+	private def editedMessageProcess (edited: Message)(using event: EventEnv): Unit = {
+		if edited.chat.id != coeur.config.medicationNotifyToChat then return;
 		coeur.daemons.medicationTimer.refreshNotificationWrite(edited)
-		true
+		event.setEventOk
 	}
 	
 }
diff --git a/src/main/scala/cc/sukazyo/cono/morny/bot/event/OnQuestionMarkReply.scala b/src/main/scala/cc/sukazyo/cono/morny/bot/event/OnQuestionMarkReply.scala
index a56e42d..516499e 100644
--- a/src/main/scala/cc/sukazyo/cono/morny/bot/event/OnQuestionMarkReply.scala
+++ b/src/main/scala/cc/sukazyo/cono/morny/bot/event/OnQuestionMarkReply.scala
@@ -1,6 +1,6 @@
 package cc.sukazyo.cono.morny.bot.event
 
-import cc.sukazyo.cono.morny.bot.api.EventListener
+import cc.sukazyo.cono.morny.bot.api.{EventEnv, EventListener}
 import cc.sukazyo.cono.morny.MornyCoeur
 import cc.sukazyo.cono.morny.bot.event.OnQuestionMarkReply.isAllMessageMark
 import cc.sukazyo.cono.morny.util.tgapi.TelegramExtensions.Bot.exec
@@ -12,19 +12,20 @@ import scala.util.boundary
 
 class OnQuestionMarkReply (using coeur: MornyCoeur) extends EventListener {
 	
-	override def onMessage (using event: Update): Boolean = {
+	override def onMessage (using event: EventEnv): Unit = {
+		import event.update
 		
-		if event.message.text eq null then return false
+		if update.message.text eq null then return
 		
 		import cc.sukazyo.cono.morny.util.UseMath.over
 		import cc.sukazyo.cono.morny.util.UseRandom.chance_is
-		if (1 over 8) chance_is false then return false
-		if !isAllMessageMark(using event.message.text) then return false
+		if (1 over 8) chance_is false then return;
+		if !isAllMessageMark(using update.message.text) then return;
 		
 		coeur.account exec SendMessage(
-			event.message.chat.id, event.message.text
-		).replyToMessageId(event.message.messageId)
-		true
+			update.message.chat.id, update.message.text
+		).replyToMessageId(update.message.messageId)
+		event.setEventOk
 		
 	}
 	
diff --git a/src/main/scala/cc/sukazyo/cono/morny/bot/event/OnUniMeowTrigger.scala b/src/main/scala/cc/sukazyo/cono/morny/bot/event/OnUniMeowTrigger.scala
index d1c97da..ccf140f 100644
--- a/src/main/scala/cc/sukazyo/cono/morny/bot/event/OnUniMeowTrigger.scala
+++ b/src/main/scala/cc/sukazyo/cono/morny/bot/event/OnUniMeowTrigger.scala
@@ -1,23 +1,27 @@
 package cc.sukazyo.cono.morny.bot.event
 
-import cc.sukazyo.cono.morny.bot.api.EventListener
+import cc.sukazyo.cono.morny.bot.api.{EventEnv, EventListener}
 import cc.sukazyo.cono.morny.bot.command.MornyCommands
 import cc.sukazyo.cono.morny.util.tgapi.InputCommand
+import cc.sukazyo.cono.morny.Log.logger
 import cc.sukazyo.cono.morny.MornyCoeur
-import com.pengrad.telegrambot.model.Update
 
 class OnUniMeowTrigger (using commands: MornyCommands) (using coeur: MornyCoeur) extends EventListener {
 	
-	override def onMessage (using update: Update): Boolean = {
+	override def onMessage (using event: EventEnv): Unit = {
 		
-		if update.message.text eq null then return false
-		var ok = false
-		for ((name, command) <- commands.commands_uni)
-			val _name = "/"+name
-			if (_name == update.message.text)
-				command.execute(using InputCommand(_name))
-				ok = true
-		ok
+		event.consume (classOf[InputCommand]) { input =>
+			logger trace s"got input command {$input} from event-context"
+			
+			for ((name, command_instance) <- commands.commands_uni) {
+				logger trace s"checking uni-meow $name"
+				if (name == input.command)
+					logger trace "checked"
+					command_instance.execute(using input, event.update)
+					event.setEventOk
+			}
+			
+		} onfail { logger trace "not command (for uni-meow)" }
 		
 	}
 	
diff --git a/src/main/scala/cc/sukazyo/cono/morny/bot/event/OnUserRandom.scala b/src/main/scala/cc/sukazyo/cono/morny/bot/event/OnUserRandom.scala
index caa2dc8..10bd70a 100644
--- a/src/main/scala/cc/sukazyo/cono/morny/bot/event/OnUserRandom.scala
+++ b/src/main/scala/cc/sukazyo/cono/morny/bot/event/OnUserRandom.scala
@@ -1,7 +1,7 @@
 package cc.sukazyo.cono.morny.bot.event
 
 import cc.sukazyo.cono.morny.MornyCoeur
-import cc.sukazyo.cono.morny.bot.api.EventListener
+import cc.sukazyo.cono.morny.bot.api.{EventEnv, EventListener}
 import cc.sukazyo.cono.morny.util.tgapi.TelegramExtensions.Bot.exec
 import com.pengrad.telegrambot.model.Update
 import com.pengrad.telegrambot.request.SendMessage
@@ -17,10 +17,11 @@ class OnUserRandom (using coeur: MornyCoeur) {
 		private val USER_OR_QUERY = "^(.+)(?:还是|or)(.+)$" r
 		private val USER_IF_QUERY = "^(.+)(?:吗\\?|?|\\?|吗?)$" r
 		
-		override def onMessage (using update: Update): Boolean = {
+		override def onMessage (using event: EventEnv): Unit = {
+			import event.update
 			
-			if update.message.text == null then return false
-			if !(update.message.text startsWith "/") then return false
+			if update.message.text == null 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
@@ -29,17 +30,17 @@ class OnUserRandom (using coeur: MornyCoeur) {
 					if rand_half then _con1 else _con2
 				case USER_IF_QUERY(_con) =>
 					// for capability with [[OnQuestionMarkReply]]
-					if OnQuestionMarkReply.isAllMessageMark(using _con) then return false
+					if OnQuestionMarkReply.isAllMessageMark(using _con) then return;
 					(if rand_half then "不" else "") + _con
 				case _ => null
 			
-			//noinspection DuplicatedCode
-			if result == null then return false
+			if result == null then return;
 			
 			coeur.account exec SendMessage(
-				update.message.chat.id, result
+				update.message.chat.id,
+				result
 			).replyToMessageId(update.message.messageId)
-			true
+			event.setEventOk
 			
 		}
 		
@@ -51,23 +52,23 @@ class OnUserRandom (using coeur: MornyCoeur) {
 		private val word_pattern = "^([\\w\\W]*)?(?:尊嘟假嘟|(?:O\\.o|o\\.O))$"r
 		private val keywords = Array("尊嘟假嘟", "O.o", "o.O")
 		
-		override def onMessage (using event: Update): Boolean = {
+		override def onMessage (using event: EventEnv): Unit = {
+			import event.update
 			
-			if event.message.text == null then return false
+			if update.message.text == null then return
 			
 			var result: String|Null = null
 			import cc.sukazyo.cono.morny.util.UseRandom.rand_half
 			for (k <- keywords)
-				if event.message.text endsWith k then
+				if update.message.text endsWith k then
 					result = if rand_half then "尊嘟" else "假嘟"
-			//noinspection DuplicatedCode
-			if result == null then return false
+			if result == null then return;
 			
 			coeur.account exec SendMessage(
-				event.message.chat.id,
+				update.message.chat.id,
 				result
-			).replyToMessageId(event.message.messageId)
-			true
+			).replyToMessageId(update.message.messageId)
+			event.setEventOk
 			
 		}
 		
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 eb3e119..7e42632 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
@@ -1,7 +1,7 @@
 package cc.sukazyo.cono.morny.bot.event
 
 import cc.sukazyo.cono.morny.MornyCoeur
-import cc.sukazyo.cono.morny.bot.api.EventListener
+import cc.sukazyo.cono.morny.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
@@ -16,10 +16,11 @@ class OnUserSlashAction (using coeur: MornyCoeur) extends EventListener {
 	
 	private val TG_FORMAT = "^\\w+(@\\w+)?$"r
 	
-	override def onMessage (using update: Update): Boolean = {
+	override def onMessage (using event: EventEnv): Unit = {
 		
+		import event.update
 		val text = update.message.text
-		if text == null then return false
+		if text == null then return;
 		
 		if (text startsWith "/") {
 			
@@ -39,14 +40,14 @@ class OnUserSlashAction (using coeur: MornyCoeur) extends EventListener {
 			actions(0) match
 				// ignore Telegram command like
 				case TG_FORMAT(_) =>
-					return false
+					return;
 				// ignore Path link
-				case x if x contains "/" => return false
+				case x if x contains "/" => return;
 				case _ =>
 			
 			val isHardParse = actions(0) isBlank
 			def hp_len(i: Int) = if isHardParse then i+1 else i
-			if isHardParse && actions.length < 2 then return false
+			if isHardParse && actions.length < 2 then return
 			val v_verb = actions(hp_len(0))
 			val hasObject = actions.length != hp_len(1)
 			val v_object =
@@ -70,9 +71,9 @@ class OnUserSlashAction (using coeur: MornyCoeur) extends EventListener {
 					if hasObject then h(v_object+" ") else ""
 				)
 			).parseMode(ParseMode HTML).replyToMessageId(update.message.messageId)
-			true
+			event.setEventOk
 			
-		} else false
+		}
 		
 	}
 	
diff --git a/src/main/scala/cc/sukazyo/cono/morny/bot/query/ShareToolBilibili.scala b/src/main/scala/cc/sukazyo/cono/morny/bot/query/ShareToolBilibili.scala
index f73fed6..85128db 100644
--- a/src/main/scala/cc/sukazyo/cono/morny/bot/query/ShareToolBilibili.scala
+++ b/src/main/scala/cc/sukazyo/cono/morny/bot/query/ShareToolBilibili.scala
@@ -1,16 +1,15 @@
 package cc.sukazyo.cono.morny.bot.query
 
-import cc.sukazyo.cono.morny.Log.logger
+import cc.sukazyo.cono.morny.MornyCoeur
 import cc.sukazyo.cono.morny.util.tgapi.formatting.NamingUtils.inlineQueryId
-import cc.sukazyo.cono.morny.util.BiliTool
-import cc.sukazyo.cono.morny.util.UseSelect.select
+import cc.sukazyo.cono.morny.Log.{exceptionLog, logger}
 import com.pengrad.telegrambot.model.Update
 import com.pengrad.telegrambot.model.request.{InlineQueryResultArticle, InputTextMessageContent, ParseMode}
 
 import scala.language.postfixOps
 import scala.util.matching.Regex
 
-class ShareToolBilibili extends ITelegramQuery {
+class ShareToolBilibili (using coeur: MornyCoeur) extends ITelegramQuery {
 	
 	private val TITLE_BILI_AV = "[bilibili] Share video / av"
 	private val TITLE_BILI_BV = "[bilibili] Share video / BV"
@@ -23,54 +22,40 @@ class ShareToolBilibili extends ITelegramQuery {
 	override def query (event: Update): List[InlineQueryUnit[_]] | Null = {
 		
 		if (event.inlineQuery.query == null) return null
+		if (event.inlineQuery.query isBlank) return null
 		
-		event.inlineQuery.query match
-			case REGEX_BILI_VIDEO(_url_v, _url_av, _url_bv, _url_param, _url_v_part, _raw_av, _raw_bv) =>
-				
-				logger debug
-						s"""====== Share Tool Bilibili Catch ok
-						   |1: ${_url_v}
-						   |2: ${_url_av}
-						   |3: ${_url_bv}
-						   |4: ${_url_param}
-						   |5: ${_url_v_part}
-						   |6: ${_raw_av}
-						   |7: ${_raw_bv}"""
-						.stripMargin
-				
-				var av = select(_url_av, _raw_av)
-				var bv = select(_url_bv, _raw_bv)
-				logger trace s"catch id av[$av] bv[$bv]"
-				val part: Int|Null = if (_url_v_part!=null) _url_v_part toInt else null
-				logger trace s"catch video part[$part]"
-				
-				if (av == null) {
-					assert (bv != null)
-					av = BiliTool.toAv(bv) toString;
-					logger trace s"converted bv[$av] to av[$av]"
-				} else {
-					bv = BiliTool.toBv(av toLong)
-					logger trace s"converted av[$av] to bv[$bv]"
-				}
-				
-				val id_av = s"av$av"
-				val id_bv = s"BV$bv"
-				val linkParams = if (part!=null) s"?p=$part" else ""
-				val link_av = LINK_PREFIX + id_av + linkParams
-				val link_bv = LINK_PREFIX + id_bv + linkParams
-				
-				List(
-					InlineQueryUnit(InlineQueryResultArticle(
-						inlineQueryId(ID_PREFIX_BILI_AV+av), TITLE_BILI_AV+av,
-						InputTextMessageContent(SHARE_FORMAT_HTML.format(link_av, id_av)).parseMode(ParseMode HTML)
-					)),
-					InlineQueryUnit(InlineQueryResultArticle(
-						inlineQueryId(ID_PREFIX_BILI_BV + bv), TITLE_BILI_BV + bv,
-						InputTextMessageContent(SHARE_FORMAT_HTML.format(link_bv, id_bv)).parseMode(ParseMode HTML)
-					))
-				)
-				
-			case _ => null
+		import cc.sukazyo.cono.morny.data.BilibiliForms.*
+		val result: BiliVideoId =
+			try
+				parse_videoUrl(event.inlineQuery.query)
+			catch case _: IllegalArgumentException =>
+				try
+					parse_videoUrl(destructB23Url(event.inlineQuery.query))
+				catch
+					case _: IllegalArgumentException =>
+						return null;
+					case e: IllegalStateException =>
+						logger error exceptionLog(e)
+						coeur.daemons.reporter.exception(e)
+						return null;
+		
+		val av = result.av
+		val bv = result.bv
+		val id_av = s"av$av"
+		val id_bv = s"BV$bv"
+		val linkParams = if (result.part != null) s"?p=${result.part}" else ""
+		val link_av = LINK_PREFIX + id_av + linkParams
+		val link_bv = LINK_PREFIX + id_bv + linkParams
+		List(
+			InlineQueryUnit(InlineQueryResultArticle(
+				inlineQueryId(ID_PREFIX_BILI_AV + av), TITLE_BILI_AV + av,
+				InputTextMessageContent(SHARE_FORMAT_HTML.format(link_av, id_av)).parseMode(ParseMode HTML)
+			)),
+			InlineQueryUnit(InlineQueryResultArticle(
+				inlineQueryId(ID_PREFIX_BILI_BV + bv), TITLE_BILI_BV + bv,
+				InputTextMessageContent(SHARE_FORMAT_HTML.format(link_bv, id_bv)).parseMode(ParseMode HTML)
+			))
+		)
 		
 	}
 	
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 6e0ed09..4840418 100644
--- a/src/main/scala/cc/sukazyo/cono/morny/daemon/MedicationTimer.scala
+++ b/src/main/scala/cc/sukazyo/cono/morny/daemon/MedicationTimer.scala
@@ -30,11 +30,11 @@ class MedicationTimer (using coeur: MornyCoeur) extends Thread {
 	override def run (): Unit = {
 		
 		if ((notify_toChat == -1) || (notify_atHour isEmpty)) {
-			logger info "Medication Timer disabled : related param is not complete set"
+			logger notice "Medication Timer disabled : related param is not complete set"
 			return
 		}
 		
-		logger info "Medication Timer started."
+		logger notice "Medication Timer started."
 		while (!this.isInterrupted) {
 			try {
 				val next_time = calcNextRoutineTimestamp(System.currentTimeMillis, use_timeZone, notify_atHour)
@@ -47,7 +47,7 @@ class MedicationTimer (using coeur: MornyCoeur) extends Thread {
 			} catch
 				case _: InterruptedException =>
 					interrupt()
-					logger info "MedicationTimer was interrupted, will be exit now"
+					logger notice "MedicationTimer was interrupted, will be exit now"
 				case ill: IllegalArgumentException =>
 					logger warn "MedicationTimer will not work due to: " + ill.getMessage
 					interrupt()
@@ -58,7 +58,7 @@ class MedicationTimer (using coeur: MornyCoeur) extends Thread {
 							.stripMargin
 					coeur.daemons.reporter.exception(e)
 		}
-		logger info "Medication Timer stopped."
+		logger notice "Medication Timer stopped."
 		
 	}
 	
diff --git a/src/main/scala/cc/sukazyo/cono/morny/daemon/MornyDaemons.scala b/src/main/scala/cc/sukazyo/cono/morny/daemon/MornyDaemons.scala
index b748480..44c0edc 100644
--- a/src/main/scala/cc/sukazyo/cono/morny/daemon/MornyDaemons.scala
+++ b/src/main/scala/cc/sukazyo/cono/morny/daemon/MornyDaemons.scala
@@ -11,18 +11,18 @@ class MornyDaemons (using val coeur: MornyCoeur) {
 	
 	def start (): Unit = {
 		
-		logger info "ALL Morny Daemons starting..."
+		logger notice "ALL Morny Daemons starting..."
 		
 		//		TrackerDataManager.init();
 		medicationTimer.start()
 		
-		logger info "Morny Daemons started."
+		logger notice "Morny Daemons started."
 		
 	}
 	
 	def stop (): Unit = {
 		
-		logger.info("stopping All Morny Daemons...")
+		logger notice "stopping All Morny Daemons..."
 		
 		//		TrackerDataManager.DAEMON.interrupt();
 		medicationTimer.interrupt()
@@ -31,7 +31,7 @@ class MornyDaemons (using val coeur: MornyCoeur) {
 		catch case e: InterruptedException =>
 			e.printStackTrace(System.out)
 		
-		logger.info("stopped ALL Morny Daemons.")
+		logger notice "stopped ALL Morny Daemons."
 	}
 	
 }
diff --git a/src/main/scala/cc/sukazyo/cono/morny/data/BilibiliForms.scala b/src/main/scala/cc/sukazyo/cono/morny/data/BilibiliForms.scala
new file mode 100644
index 0000000..962c98b
--- /dev/null
+++ b/src/main/scala/cc/sukazyo/cono/morny/data/BilibiliForms.scala
@@ -0,0 +1,95 @@
+package cc.sukazyo.cono.morny.data
+
+import cc.sukazyo.cono.morny.util.BiliTool
+import cc.sukazyo.cono.morny.util.UseSelect.select
+import okhttp3.{HttpUrl, OkHttpClient, Request}
+
+import java.io.IOException
+import scala.util.matching.Regex
+import scala.util.Using
+
+object BilibiliForms {
+	
+	case class BiliVideoId (av: Long, bv: String, part: Int|Null = null)
+	
+	private val REGEX_BILI_ID = "^((?:av|AV)(\\d{1,12})|(?:bv|BV)([A-HJ-NP-Za-km-z1-9]{10}))$"r
+	private val REGEX_BILI_V_PART_IN_URL_PARAM = "(?:&|^)p=(\\d+)"r
+	private val REGEX_BILI_VIDEO: Regex = "^(?:(?:https?://)?(?:(?:www\\.)?bilibili\\.com(?:/s)?/video/|b23\\.tv/)((?:av|AV)(\\d{1,12})|(?:bv|BV)([A-HJ-NP-Za-km-z1-9]{10}))/?(?:\\?((?:p=(\\d+))?.*))?|(?:av|AV)(\\d{1,12})|(?:bv|BV)([A-HJ-NP-Za-km-z1-9]{10}))$" r
+	
+	/** parse a Bilibili video link to a [[BiliVideoId]] format Bilibili Video Id.
+	  *
+	  * @param url the Bilibili video link -- should be a valid link with av/BV,
+	  *            can take some tracking params (will be ignored), can be a search
+	  *            result link (have `s/` path).
+	  *            Or, it can also be a b23 video link: starts with b23.tv hostname with
+	  *            no www. prefix, and no /video/ path.
+	  * @throws IllegalArgumentException when the link is not the valid bilibili video link
+	  * @return the [[BiliVideoId]] contains raw or converted av id, and raw or converted bv id,
+	  *         and video part id.
+	  */
+	@throws[IllegalArgumentException]
+	def parse_videoUrl (url: String): BiliVideoId =
+		url match
+			case REGEX_BILI_VIDEO(_url_v, _url_av, _url_bv, _url_param, _url_v_part, _raw_av, _raw_bv) =>
+				
+				val av = select(_url_av, _raw_av)
+				val bv = select(_url_bv, _raw_bv)
+				
+				val part_part = if (_url_param == null) null else
+					REGEX_BILI_V_PART_IN_URL_PARAM.findFirstMatchIn(_url_param) match
+						case Some(part) => part.group(1)
+						case None => null
+				val part: Int | Null = if (part_part != null) part_part toInt else null
+				
+				if (av == null) {
+					assert(bv != null)
+					BiliVideoId(BiliTool.toAv(bv), bv, part)
+				} else {
+					val _av = av.toLong
+					BiliVideoId(_av, BiliTool.toBv(_av), part)
+				}
+				
+			case _ => throw IllegalArgumentException(s"not a valid Bilibili video link: $url")
+	
+	private val httpClient = OkHttpClient
+		.Builder()
+		.followSslRedirects(true)
+		.followRedirects(false)
+		.build()
+	
+	/** get the bilibili video url from b23.tv share url.
+	  *
+	  * result url can be used in [[parse_videoUrl]]
+	  *
+	  * @param url b23.tv share url.
+	  * @throws IllegalArgumentException the input `url` is not a b23.tv url
+	  * @throws IllegalStateException some exception occurred when getting information from remote
+	  *                               host, or failed to parse the information got
+	  * @return bilibili video url with tracking params
+	  */
+	@throws[IllegalStateException|IllegalArgumentException]
+	def destructB23Url (url: String): String =
+		val _url: HttpUrl = HttpUrl.parse(
+			if url startsWith "http://" then url.replaceFirst("http://", "https://") else url
+		)
+		if _url == null then throw IllegalArgumentException("not a valid url: " + url)
+		if _url.host != "b23.tv" then throw IllegalArgumentException(s"not a b23 share link: $url")
+		if (!_url.pathSegments.isEmpty) && _url.pathSegments.get(0).matches(REGEX_BILI_ID.regex) then
+			throw IllegalArgumentException(s"is a b23 video link: $url ; (use parse_videoUrl directly)")
+		val result: Option[String] =
+			try {
+				Using(httpClient.newCall(Request.Builder().url(_url).build).execute()) { response =>
+					if response.isRedirect then
+						val _u = response header "Location"
+						if _u != null then
+							Some(_u)
+						else throw IllegalStateException("unable to get b23.tv redir location from: " + response)
+					else throw IllegalStateException("unable to get b23.tv redir location from: " + response)
+				}.get
+			} catch case e: IOException =>
+				throw IllegalStateException("get b23.tv failed.", e)
+		result match
+			case Some(_result) => _result
+			case None => throw IllegalStateException("unable to parse from b23.tv .")
+	
+}
diff --git a/src/main/scala/cc/sukazyo/cono/morny/internal/logging/IMornyLogLevelImpl.scala b/src/main/scala/cc/sukazyo/cono/morny/internal/logging/IMornyLogLevelImpl.scala
new file mode 100644
index 0000000..80d2847
--- /dev/null
+++ b/src/main/scala/cc/sukazyo/cono/morny/internal/logging/IMornyLogLevelImpl.scala
@@ -0,0 +1,12 @@
+package cc.sukazyo.cono.morny.internal.logging
+
+import cc.sukazyo.messiva.log.Message
+
+trait IMornyLogLevelImpl {
+	
+	def notice (message: String): Unit
+	def notice (message: Message): Unit
+	def attention (message: String): Unit
+	def attention (message: Message): Unit
+	
+}
diff --git a/src/main/scala/cc/sukazyo/cono/morny/internal/logging/MornyFormatterConsole.scala b/src/main/scala/cc/sukazyo/cono/morny/internal/logging/MornyFormatterConsole.scala
new file mode 100644
index 0000000..2d9514a
--- /dev/null
+++ b/src/main/scala/cc/sukazyo/cono/morny/internal/logging/MornyFormatterConsole.scala
@@ -0,0 +1,24 @@
+package cc.sukazyo.cono.morny.internal.logging
+
+import cc.sukazyo.cono.morny.util.CommonFormat.formatDate
+import cc.sukazyo.cono.morny.ServerMain
+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 =
+		val message = StringBuilder()
+		val dt = formatDate(log.timestamp, ServerMain.tz_offset)
+		val prompt_heading = s"[$dt][${log.thread.getName}]"
+		val prompt_newline = "'" * prompt_heading.length
+		val prompt_levelTag = s"${log.level.tag}::: "
+		message             ++= prompt_heading ++= prompt_levelTag ++= log.message.message(0)
+		for (line <- log.message.message drop 1)
+			message += '\n' ++= prompt_newline ++= prompt_levelTag ++= line
+		message toString
+	
+}
diff --git a/src/main/scala/cc/sukazyo/cono/morny/internal/logging/MornyLogLevels.scala b/src/main/scala/cc/sukazyo/cono/morny/internal/logging/MornyLogLevels.scala
new file mode 100644
index 0000000..acaf7b8
--- /dev/null
+++ b/src/main/scala/cc/sukazyo/cono/morny/internal/logging/MornyLogLevels.scala
@@ -0,0 +1,13 @@
+package cc.sukazyo.cono.morny.internal.logging
+
+import cc.sukazyo.messiva.log.ILogLevel
+
+enum MornyLogLevels (
+	override val level: Float,
+	override val tag: String
+) extends ILogLevel {
+	
+	case NOTICE    extends MornyLogLevels(0.2f, "NOTICE")
+	case ATTENTION extends MornyLogLevels(0.3f, "ATTION")
+	
+}
diff --git a/src/main/scala/cc/sukazyo/cono/morny/internal/logging/MornyLoggerBase.scala b/src/main/scala/cc/sukazyo/cono/morny/internal/logging/MornyLoggerBase.scala
new file mode 100644
index 0000000..5d4b143
--- /dev/null
+++ b/src/main/scala/cc/sukazyo/cono/morny/internal/logging/MornyLoggerBase.scala
@@ -0,0 +1,22 @@
+package cc.sukazyo.cono.morny.internal.logging
+
+import cc.sukazyo.messiva.appender.IAppender
+import cc.sukazyo.messiva.log.{Log, Message}
+import cc.sukazyo.messiva.logger.Logger
+
+class MornyLoggerBase extends Logger with IMornyLogLevelImpl {
+	
+	def this (appends: IAppender*) =
+		this()
+		this.appends.addAll(java.util.List.of(appends:_*))
+	
+	override def notice (message: String): Unit =
+		pushToAllAppender(Log(1, new Message(message), MornyLogLevels.NOTICE))
+	override def notice (message: Message): Unit =
+		pushToAllAppender(Log(1, message, MornyLogLevels.NOTICE))
+	override def attention (message: String): Unit =
+		pushToAllAppender(Log(1, new Message(message), MornyLogLevels.ATTENTION))
+	override def attention (message: Message): Unit =
+		pushToAllAppender(Log(1, message, MornyLogLevels.ATTENTION))
+	
+}
diff --git a/src/main/scala/cc/sukazyo/cono/morny/util/StringEnsure.scala b/src/main/scala/cc/sukazyo/cono/morny/util/StringEnsure.scala
index c56dd16..3fd795e 100644
--- a/src/main/scala/cc/sukazyo/cono/morny/util/StringEnsure.scala
+++ b/src/main/scala/cc/sukazyo/cono/morny/util/StringEnsure.scala
@@ -12,6 +12,9 @@ object StringEnsure {
 			} else str
 		}
 		
+		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)
+		
 	}
 	
 }
diff --git a/src/test/scala/.gitignore b/src/test/scala/.gitignore
new file mode 100644
index 0000000..e23fe64
--- /dev/null
+++ b/src/test/scala/.gitignore
@@ -0,0 +1 @@
+live
diff --git a/src/test/scala/cc/sukazyo/cono/morny/test/cc/sukazyo/cono/morny/data/BilibiliFormsTest.scala b/src/test/scala/cc/sukazyo/cono/morny/test/cc/sukazyo/cono/morny/data/BilibiliFormsTest.scala
new file mode 100644
index 0000000..93388f5
--- /dev/null
+++ b/src/test/scala/cc/sukazyo/cono/morny/test/cc/sukazyo/cono/morny/data/BilibiliFormsTest.scala
@@ -0,0 +1,117 @@
+package cc.sukazyo.cono.morny.test.cc.sukazyo.cono.morny.data
+
+import cc.sukazyo.cono.morny.data.BilibiliForms.*
+import cc.sukazyo.cono.morny.test.MornyTests
+import org.scalatest.prop.TableDrivenPropertyChecks
+import org.scalatest.tagobjects.{Network, Slow}
+
+class BilibiliFormsTest extends MornyTests with TableDrivenPropertyChecks {
+	
+	"while parsing bilibili video link :" - {
+		
+		"raw avXXX should be parsed" in:
+			parse_videoUrl("av455017605") shouldEqual BiliVideoId(455017605L, "1Q541167Qg")
+		"raw BVXXX should be parsed" in:
+			parse_videoUrl("BV1T24y197V2") shouldEqual BiliVideoId(688730800L, "1T24y197V2")
+		"raw id without av/BV prefix should not be parsed" in:
+			an[IllegalArgumentException] should be thrownBy parse_videoUrl("1T24y197V2")
+			an[IllegalArgumentException] should be thrownBy parse_videoUrl("455017605")
+		"av/bv prefix can be either uppercase or lowercase" in:
+			parse_videoUrl("bv1T24y197V2") shouldEqual BiliVideoId(688730800L, "1T24y197V2")
+			parse_videoUrl("AV455017605") shouldEqual BiliVideoId(455017605L, "1Q541167Qg")
+		
+		"av/bv bilibili.com link should be parsed" in:
+			parse_videoUrl("https://www.bilibili.com/video/AV455017605") shouldEqual
+				BiliVideoId(455017605L, "1Q541167Qg")
+			parse_videoUrl("https://www.bilibili.com/video/bv1T24y197V2") shouldEqual
+				BiliVideoId(688730800L, "1T24y197V2")
+		"bilibili.com link can have protocol http:// or https://" in:
+			parse_videoUrl("http://www.bilibili.com/video/AV455017605") shouldEqual
+				BiliVideoId(455017605L, "1Q541167Qg")
+		"bilibili.com link can omit protocol http or https" in :
+			parse_videoUrl("www.bilibili.com/video/AV455017605") shouldEqual
+				BiliVideoId(455017605L, "1Q541167Qg")
+		"bilibili.com link can omit www. prefix" in :
+			parse_videoUrl("bilibili.com/video/AV455017605") shouldEqual
+				BiliVideoId(455017605L, "1Q541167Qg")
+			parse_videoUrl("https://bilibili.com/video/AV455017605") shouldEqual
+				BiliVideoId(455017605L, "1Q541167Qg")
+		"bilibili.com link can be search result link (with /s path prefix)" in :
+			parse_videoUrl("bilibili.com/s/video/AV455017605") shouldEqual
+				BiliVideoId(455017605L, "1Q541167Qg")
+			parse_videoUrl("https://www.bilibili.com/s/video/AV455017605") shouldEqual
+				BiliVideoId(455017605L, "1Q541167Qg")
+		"bilibili.com link can only be video link" in :
+			an[IllegalArgumentException] should be thrownBy parse_videoUrl("bilibili.com/s/media/AV455017605")
+			an[IllegalArgumentException] should be thrownBy parse_videoUrl("https://www.bilibili.com/media/AV455017605")
+			an[IllegalArgumentException] should be thrownBy parse_videoUrl("https://www.bilibili.com/AV455017605")
+		"bilibili.com link can take parameters" in :
+			parse_videoUrl("https://www.bilibili.com/video/av455017605?vd_source=123456") shouldEqual
+				BiliVideoId(455017605L, "1Q541167Qg")
+			parse_videoUrl("bilibili.com/video/AV455017605?mid=12hdowhAID82EQ&289EHD8AHDOIWU8=r2aur9%3Bi0%3AJ%7BRQJH%28QJ.%5BropWG%3AKR%24%28O%7BGR") shouldEqual
+				BiliVideoId(455017605L, "1Q541167Qg")
+		"video part within bilibili.com link params should be parsed" in :
+			parse_videoUrl("https://www.bilibili.com/video/BV1Q541167Qg?p=1") shouldEqual
+				BiliVideoId(455017605L, "1Q541167Qg", 1)
+			parse_videoUrl("https://www.bilibili.com/video/av455017605?p=1&vd_source=123456") shouldEqual
+				BiliVideoId(455017605L, "1Q541167Qg", 1)
+			parse_videoUrl("bilibili.com/video/AV455017605?mid=12hdowhAI&p=5&x=D82EQ&289EHD8AHDOIWU8=r2aur9%3Bi0%3AJ%7BRQJH%28QJ.%5BropWG%3AKR%24%28O%7BGR") shouldEqual
+				BiliVideoId(455017605L, "1Q541167Qg", 5)
+		
+		"av id with more than 12 digits should not be parsed" in :
+			an[IllegalArgumentException] should be thrownBy parse_videoUrl("av4550176087554")
+			an[IllegalArgumentException] should be thrownBy parse_videoUrl("bilibili.com/video/av4550176087554")
+			an[IllegalArgumentException] should be thrownBy parse_videoUrl("av455017608755634345565341256")
+		"av id with 0 digits should not be parsed" in :
+			an[IllegalArgumentException] should be thrownBy parse_videoUrl("av")
+			an[IllegalArgumentException] should be thrownBy parse_videoUrl("bilibili.com/video/av")
+		"BV id with not 10 digits should not be parsed" in :
+			an[IllegalArgumentException] should be thrownBy parse_videoUrl("BV123456789")
+			an[IllegalArgumentException] should be thrownBy parse_videoUrl("BV12345678")
+			an[IllegalArgumentException] should be thrownBy parse_videoUrl("bilibili.com/video/BV12345678901")
+		
+		"url which is not bilibili link should not be parsed" in:
+			an[IllegalArgumentException] should be thrownBy parse_videoUrl("https://www.pilipili.com/video/av123456")
+			an[IllegalArgumentException] should be thrownBy parse_videoUrl("https://pilipili.com/video/av123456")
+			an[IllegalArgumentException] should be thrownBy parse_videoUrl("https://blilblil.com/video/av123456")
+			an[IllegalArgumentException] should be thrownBy parse_videoUrl("https://bilibili.cc/video/av123456")
+			an[IllegalArgumentException] should be thrownBy parse_videoUrl("https://vxbilibili.com/video/av123456")
+			an[IllegalArgumentException] should be thrownBy parse_videoUrl("https://bilibiliexc.com/video/av123456")
+			an[IllegalArgumentException] should be thrownBy parse_videoUrl("C# does not have type erasure. C# has actual generic types deeply baked into the runtime.\n\n好文明")
+		
+		"url which is a b23 video link should be parsed" in:
+			parse_videoUrl("https://b23.tv/av688730800") shouldEqual BiliVideoId(688730800L, "1T24y197V2")
+			parse_videoUrl("http://b23.tv/BV1T24y197V2") shouldEqual BiliVideoId(688730800L, "1T24y197V2")
+			parse_videoUrl("b23.tv/BV1T24y197V2") shouldEqual BiliVideoId(688730800L, "1T24y197V2")
+		"b23 video link should not take www. or /video prefix" in:
+			an[IllegalArgumentException] should be thrownBy parse_videoUrl("https://www.b23.tv/av123456")
+			an[IllegalArgumentException] should be thrownBy parse_videoUrl("https://b23.tv/video/av123456")
+		
+	}
+	
+	"while destruct b23.tv share link :" - {
+		
+		val examples = Table(
+			("b23_link", "bilibili_video_link"),
+			("https://b23.tv/iiCldvZ", "https://www.bilibili.com/video/BV1Gh411P7Sh?buvid=XY6F25B69BE9CF469FF5B917D012C93E95E72&is_story_h5=false&mid=wD6DQnYivIG5pfA3sAGL6A%3D%3D&p=1&plat_id=114&share_from=ugc&share_medium=android&share_plat=android&share_session_id=8081015b-1210-4dea-a665-6746b4850fcd&share_source=COPY&share_tag=s_i&timestamp=1689605644&unique_k=iiCldvZ&up_id=19977489"),
+			("http://b23.tv/3ymowwx", "https://www.bilibili.com/video/BV15Y411n754?p=1&share_medium=android_i&share_plat=android&share_source=COPY&share_tag=s_i&timestamp=1650293889&unique_k=3ymowwx")
+		)
+		
+		"not b23.tv link is not supported" in:
+			an[IllegalArgumentException] should be thrownBy destructB23Url("sukazyo.cc/2xhUHO2e")
+			an[IllegalArgumentException] should be thrownBy destructB23Url("https://sukazyo.cc/2xhUHO2e")
+			an[IllegalArgumentException] should be thrownBy destructB23Url("长月烬明澹台烬心理分析向解析(一)因果之锁,渡魔之路")
+			an[IllegalArgumentException] should be thrownBy destructB23Url("https://b23.tvb/JDo2eaD")
+			an[IllegalArgumentException] should be thrownBy destructB23Url("https://ab23.tv/JDo2eaD")
+		"b23.tv/avXXX video link is not supported" in:
+			an[IllegalArgumentException] should be thrownBy destructB23Url("https://b23.tv/av123456")
+			an[IllegalArgumentException] should be thrownBy destructB23Url("https://b23.tv/BV1Q541167Qg")
+		
+		forAll (examples) { (origin, result) =>
+			s"b23 link $origin should be destructed to $result" taggedAs (Slow, Network) in:
+				destructB23Url(origin) shouldEqual result
+		}
+		
+	}
+	
+}
diff --git a/src/test/scala/live/LiveMain.scala b/src/test/scala/live/LiveMain.scala
deleted file mode 100644
index 2b7de09..0000000
--- a/src/test/scala/live/LiveMain.scala
+++ /dev/null
@@ -1,9 +0,0 @@
-package live
-
-import cc.sukazyo.cono.morny.test.utils.BiliToolTest
-
-@main def LiveMain (args: String*): Unit = {
-	
-	org.scalatest.run(BiliToolTest())
-	
-}