mirror of
https://github.com/Eyre-S/Coeur-Morny-Cono.git
synced 2024-11-22 11:14:55 +08:00
add event sources statistics,
This commit is contained in:
parent
5aa63de2a9
commit
456273be96
@ -68,9 +68,7 @@ lazy val root = (project in file("."))
|
|||||||
.withClassifier(Some("fat")),
|
.withClassifier(Some("fat")),
|
||||||
if (MornyProject.publishWithFatJar) {
|
if (MornyProject.publishWithFatJar) {
|
||||||
addArtifact(assembly / artifact, assembly)
|
addArtifact(assembly / artifact, assembly)
|
||||||
} else {
|
} else Nil,
|
||||||
Nil
|
|
||||||
},
|
|
||||||
if (System.getenv("DOCKER_BUILD") != null) {
|
if (System.getenv("DOCKER_BUILD") != null) {
|
||||||
assembly / assemblyJarName := {
|
assembly / assemblyJarName := {
|
||||||
sLog.value info "environment DOCKER_BUILD checked"
|
sLog.value info "environment DOCKER_BUILD checked"
|
||||||
|
@ -8,7 +8,7 @@ object MornyConfiguration {
|
|||||||
val MORNY_CODE_STORE = "https://github.com/Eyre-S/Coeur-Morny-Cono"
|
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 MORNY_COMMIT_PATH = "https://github.com/Eyre-S/Coeur-Morny-Cono/commit/%s"
|
||||||
|
|
||||||
val VERSION = "2.0.0-alpha11"
|
val VERSION = "2.0.0-alpha12"
|
||||||
val VERSION_DELTA: Option[String] = None
|
val VERSION_DELTA: Option[String] = None
|
||||||
val CODENAME = "guanggu"
|
val CODENAME = "guanggu"
|
||||||
|
|
||||||
|
@ -28,6 +28,17 @@ trait EventListener () {
|
|||||||
*/
|
*/
|
||||||
def atEventPost (using EventEnv): Unit = {}
|
def atEventPost (using EventEnv): Unit = {}
|
||||||
|
|
||||||
|
/** A overall event listener that can listen every types that supported
|
||||||
|
* by the bot API.
|
||||||
|
*
|
||||||
|
* This method will runs before the specific event listener methods.
|
||||||
|
*
|
||||||
|
* [[executeFilter]] will affect this method.
|
||||||
|
*
|
||||||
|
* @since 2.0.0
|
||||||
|
*/
|
||||||
|
def on (using EventEnv): Unit = {}
|
||||||
|
|
||||||
def onMessage (using EventEnv): Unit = {}
|
def onMessage (using EventEnv): Unit = {}
|
||||||
def onEditedMessage (using EventEnv): Unit = {}
|
def onEditedMessage (using EventEnv): Unit = {}
|
||||||
def onChannelPost (using EventEnv): Unit = {}
|
def onChannelPost (using EventEnv): Unit = {}
|
||||||
|
@ -48,6 +48,7 @@ class EventListenerManager (using coeur: MornyCoeur) extends UpdatesListener {
|
|||||||
|
|
||||||
private def runEventListener (i: EventListener)(using EventEnv): Unit = {
|
private def runEventListener (i: EventListener)(using EventEnv): Unit = {
|
||||||
try {
|
try {
|
||||||
|
i.on
|
||||||
updateThreadName("message")
|
updateThreadName("message")
|
||||||
if update.message ne null then i.onMessage
|
if update.message ne null then i.onMessage
|
||||||
updateThreadName("edited-message")
|
updateThreadName("edited-message")
|
||||||
|
@ -4,19 +4,22 @@ import cc.sukazyo.cono.morny.core.{MornyCoeur, MornyConfig}
|
|||||||
import cc.sukazyo.cono.morny.core.Log.{exceptionLog, logger}
|
import cc.sukazyo.cono.morny.core.Log.{exceptionLog, logger}
|
||||||
import cc.sukazyo.cono.morny.core.bot.api.{EventEnv, EventListener}
|
import cc.sukazyo.cono.morny.core.bot.api.{EventEnv, EventListener}
|
||||||
import cc.sukazyo.cono.morny.data.MornyInformation.getVersionAllFullTagHTML
|
import cc.sukazyo.cono.morny.data.MornyInformation.getVersionAllFullTagHTML
|
||||||
import cc.sukazyo.cono.morny.util.statistics.NumericStatistics
|
import cc.sukazyo.cono.morny.util.statistics.{NumericStatistics, UniqueCounter}
|
||||||
import cc.sukazyo.cono.morny.util.tgapi.event.EventRuntimeException
|
import cc.sukazyo.cono.morny.util.tgapi.event.EventRuntimeException
|
||||||
import cc.sukazyo.cono.morny.util.tgapi.formatting.TelegramFormatter.*
|
import cc.sukazyo.cono.morny.util.tgapi.formatting.TelegramFormatter.*
|
||||||
import cc.sukazyo.cono.morny.util.tgapi.formatting.TelegramParseEscape.escapeHtml as h
|
import cc.sukazyo.cono.morny.util.tgapi.formatting.TelegramParseEscape.escapeHtml as h
|
||||||
import cc.sukazyo.cono.morny.util.tgapi.TelegramExtensions.Bot.exec
|
import cc.sukazyo.cono.morny.util.tgapi.TelegramExtensions.Bot.exec
|
||||||
import cc.sukazyo.cono.morny.util.EpochDateTime.DurationMillis
|
import cc.sukazyo.cono.morny.util.EpochDateTime.DurationMillis
|
||||||
import cc.sukazyo.cono.morny.util.schedule.CronTask
|
import cc.sukazyo.cono.morny.util.schedule.CronTask
|
||||||
|
import cc.sukazyo.cono.morny.util.tgapi.TelegramExtensions.Update.{extractSourceChat, extractSourceUser}
|
||||||
|
import cc.sukazyo.cono.morny.util.CommonEncrypt.hashId
|
||||||
|
import cc.sukazyo.cono.morny.util.ConvertByteHex.toHex
|
||||||
import com.cronutils.builder.CronBuilder
|
import com.cronutils.builder.CronBuilder
|
||||||
import com.cronutils.model.Cron
|
import com.cronutils.model.Cron
|
||||||
import com.cronutils.model.definition.CronDefinitionBuilder
|
import com.cronutils.model.definition.CronDefinitionBuilder
|
||||||
import com.google.gson.GsonBuilder
|
import com.google.gson.GsonBuilder
|
||||||
|
import com.pengrad.telegrambot.model.{Chat, User}
|
||||||
import com.pengrad.telegrambot.model.request.ParseMode
|
import com.pengrad.telegrambot.model.request.ParseMode
|
||||||
import com.pengrad.telegrambot.model.User
|
|
||||||
import com.pengrad.telegrambot.request.{BaseRequest, SendMessage}
|
import com.pengrad.telegrambot.request.{BaseRequest, SendMessage}
|
||||||
import com.pengrad.telegrambot.response.BaseResponse
|
import com.pengrad.telegrambot.response.BaseResponse
|
||||||
import com.pengrad.telegrambot.TelegramException
|
import com.pengrad.telegrambot.TelegramException
|
||||||
@ -30,7 +33,7 @@ class MornyReport (using coeur: MornyCoeur) {
|
|||||||
logger info "Morny Report is disabled : report chat is set to -1"
|
logger info "Morny Report is disabled : report chat is set to -1"
|
||||||
|
|
||||||
private def executeReport[T <: BaseRequest[T, R], R<: BaseResponse] (report: T): Unit = {
|
private def executeReport[T <: BaseRequest[T, R], R<: BaseResponse] (report: T): Unit = {
|
||||||
if !enabled then return;
|
if !enabled then return
|
||||||
try {
|
try {
|
||||||
coeur.account exec report
|
coeur.account exec report
|
||||||
} catch case e: EventRuntimeException => {
|
} catch case e: EventRuntimeException => {
|
||||||
@ -148,11 +151,23 @@ class MornyReport (using coeur: MornyCoeur) {
|
|||||||
private var eventTotal = 0
|
private var eventTotal = 0
|
||||||
private var eventCanceled = 0
|
private var eventCanceled = 0
|
||||||
private val runningTime: NumericStatistics[DurationMillis] = NumericStatistics()
|
private val runningTime: NumericStatistics[DurationMillis] = NumericStatistics()
|
||||||
|
/** The event which is from a private chat (mostly message) */
|
||||||
|
private val event_from_private = UniqueCounter[String]()
|
||||||
|
/** The event which is from a group (message, or member join etc.) */
|
||||||
|
private val event_from_group = UniqueCounter[String]()
|
||||||
|
/** The event which is from a channel (message, or member join etc.) */
|
||||||
|
private val event_from_channel = UniqueCounter[String]()
|
||||||
|
/** The event which is from a user's action (inline queries etc. which have a executor but not belongs to a chat.) */
|
||||||
|
private val event_from_user_action = UniqueCounter[String]()
|
||||||
|
|
||||||
def reset (): Unit = {
|
def reset (): Unit = {
|
||||||
eventTotal = 0
|
eventTotal = 0
|
||||||
eventCanceled = 0
|
eventCanceled = 0
|
||||||
runningTime.reset()
|
runningTime.reset()
|
||||||
|
event_from_private.reset()
|
||||||
|
event_from_group.reset()
|
||||||
|
event_from_channel.reset()
|
||||||
|
event_from_user_action.reset()
|
||||||
}
|
}
|
||||||
|
|
||||||
private def runningTimeStatisticsHTML: String =
|
private def runningTimeStatisticsHTML: String =
|
||||||
@ -168,11 +183,16 @@ class MornyReport (using coeur: MornyCoeur) {
|
|||||||
|
|
||||||
def eventStatisticsHTML: String =
|
def eventStatisticsHTML: String =
|
||||||
import cc.sukazyo.cono.morny.util.UseMath.percentageOf as p
|
import cc.sukazyo.cono.morny.util.UseMath.percentageOf as p
|
||||||
|
import cc.sukazyo.cono.morny.util.tgapi.formatting.TelegramFormatter.ChatTypeTag.*
|
||||||
val processed = runningTime.count
|
val processed = runningTime.count
|
||||||
val canceled = eventCanceled
|
val canceled = eventCanceled
|
||||||
val ignored = eventTotal - processed - canceled
|
val ignored = eventTotal - processed - canceled
|
||||||
// language=html
|
// language=html
|
||||||
s""" - <i>total event received</i>: <code>$eventTotal</code>
|
s""" - <i>total event received</i>: <code>$eventTotal</code>
|
||||||
|
| - <i>from</i> <code>${event_from_channel.count}</code> <i>$CHANNEL channels</i>
|
||||||
|
| - <i>from</i> <code>${event_from_group.count}</code> <i>$SUPERGROUP groups/supergroups</i>
|
||||||
|
| - <i>from</i> <code>${event_from_private.count}</code> <i>$PRIVATE private chats</i>
|
||||||
|
| - <i>from</i> <code>${event_from_user_action.count}</code> <i>😼 user actions</i>
|
||||||
| - <i>event ignored</i>: (<code>${eventTotal p ignored}%</code>) <code>$ignored</code>
|
| - <i>event ignored</i>: (<code>${eventTotal p ignored}%</code>) <code>$ignored</code>
|
||||||
| - <i>event canceled</i>: (<code>${eventTotal p canceled}%</code>) <code>$canceled</code>
|
| - <i>event canceled</i>: (<code>${eventTotal p canceled}%</code>) <code>$canceled</code>
|
||||||
| - <i>event processed</i>: (<code>${eventTotal p processed}%</code>) <code>$processed</code>
|
| - <i>event processed</i>: (<code>${eventTotal p processed}%</code>) <code>$processed</code>
|
||||||
@ -186,6 +206,20 @@ class MornyReport (using coeur: MornyCoeur) {
|
|||||||
override def atEventPost (using event: EventEnv): Unit = {
|
override def atEventPost (using event: EventEnv): Unit = {
|
||||||
import event.*
|
import event.*
|
||||||
eventTotal += 1
|
eventTotal += 1
|
||||||
|
event.update.extractSourceChat match
|
||||||
|
case None =>
|
||||||
|
event.update.extractSourceUser match
|
||||||
|
case None =>
|
||||||
|
case Some(user) =>
|
||||||
|
event_from_user_action << hashId(user.id).toHex
|
||||||
|
case Some(chat) =>
|
||||||
|
chat.`type` match
|
||||||
|
case Chat.Type.Private =>
|
||||||
|
event_from_private << hashId(chat.id).toHex
|
||||||
|
case Chat.Type.group | Chat.Type.supergroup =>
|
||||||
|
event_from_group << hashId(chat.id).toHex
|
||||||
|
case Chat.Type.channel =>
|
||||||
|
event_from_channel << hashId(chat.id).toHex
|
||||||
event.state match
|
event.state match
|
||||||
case State.OK(from) =>
|
case State.OK(from) =>
|
||||||
val timeUsed = EventTimeUsed(System.currentTimeMillis - event.timeStartup)
|
val timeUsed = EventTimeUsed(System.currentTimeMillis - event.timeStartup)
|
||||||
|
@ -1,50 +1,20 @@
|
|||||||
package cc.sukazyo.cono.morny.tele_utils.event_hack
|
package cc.sukazyo.cono.morny.tele_utils.event_hack
|
||||||
|
|
||||||
import cc.sukazyo.cono.morny.core.Log.logger
|
|
||||||
import cc.sukazyo.cono.morny.core.MornyCoeur
|
import cc.sukazyo.cono.morny.core.MornyCoeur
|
||||||
import cc.sukazyo.cono.morny.core.bot.api.{EventEnv, EventListener}
|
import cc.sukazyo.cono.morny.core.bot.api.{EventEnv, EventListener}
|
||||||
import com.google.gson.GsonBuilder
|
import cc.sukazyo.cono.morny.util.tgapi.TelegramExtensions.Update.*
|
||||||
import com.pengrad.telegrambot.model.Update
|
import com.pengrad.telegrambot.model.Update
|
||||||
import com.pengrad.telegrambot.model.request.ParseMode
|
|
||||||
import com.pengrad.telegrambot.request.SendMessage
|
|
||||||
|
|
||||||
import scala.collection.mutable
|
|
||||||
import scala.language.postfixOps
|
import scala.language.postfixOps
|
||||||
|
|
||||||
class HackerEventHandler (using hacker: EventHacker)(using coeur: MornyCoeur) extends EventListener {
|
class HackerEventHandler (using hacker: EventHacker)(using coeur: MornyCoeur) extends EventListener {
|
||||||
|
|
||||||
private def trigger (chat_id: Long, from_id: Long)(using event: EventEnv): Unit =
|
override def on (using event: EventEnv): Unit =
|
||||||
given Update = event.update
|
given update: Update = event.update
|
||||||
if hacker.trigger(chat_id, from_id) then
|
if hacker.trigger(
|
||||||
|
update.extractSourceChat.map[Long](_.id).getOrElse(0),
|
||||||
|
update.extractSourceUser.map[Long](_.id).getOrElse(0)
|
||||||
|
) then
|
||||||
event.setEventOk
|
event.setEventOk
|
||||||
|
|
||||||
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 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)
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -95,4 +95,14 @@ object CommonEncrypt {
|
|||||||
case lx if lx endsWith ".base64.txt" => lx dropRight ".base64.txt".length
|
case lx if lx endsWith ".base64.txt" => lx dropRight ".base64.txt".length
|
||||||
case u => u
|
case u => u
|
||||||
|
|
||||||
|
/** Hash a [[Long]] id to [[Bin]] using [[MD5]] algorithm.
|
||||||
|
*
|
||||||
|
* For some privacy cases, this method can provide a standard way to hash a ID to a MD5 hash value.
|
||||||
|
*
|
||||||
|
* @param id The [[Long]] number typed id.
|
||||||
|
* @return The hash value of the id.
|
||||||
|
*/
|
||||||
|
def hashId (id: Long): Bin =
|
||||||
|
MD5(id.toString)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,27 @@
|
|||||||
|
package cc.sukazyo.cono.morny.util.statistics
|
||||||
|
|
||||||
|
import scala.collection.mutable
|
||||||
|
|
||||||
|
/** Count unique elements progressively.
|
||||||
|
*
|
||||||
|
* Use [[<<]] to add a element to this counter. Use [[count]] to get current
|
||||||
|
* count in this counter, and use [[reset()]] to reset this counter.
|
||||||
|
*
|
||||||
|
* Behind it is a [[scala.collection.mutable.Set]].
|
||||||
|
*
|
||||||
|
* @tparam T The element type.
|
||||||
|
*/
|
||||||
|
class UniqueCounter [T] {
|
||||||
|
|
||||||
|
private var set: mutable.Set[T] = mutable.Set.empty
|
||||||
|
|
||||||
|
def << (t: T): Unit =
|
||||||
|
set += t
|
||||||
|
|
||||||
|
def count: Int =
|
||||||
|
set.size
|
||||||
|
|
||||||
|
def reset(): Unit =
|
||||||
|
set.clear()
|
||||||
|
|
||||||
|
}
|
@ -29,6 +29,44 @@ object TelegramExtensions {
|
|||||||
|
|
||||||
}}
|
}}
|
||||||
|
|
||||||
|
object Update { extension (update: Update) {
|
||||||
|
|
||||||
|
def extractSourceChat: Option[Chat] =
|
||||||
|
if (update.message != null) Some(update.message.chat)
|
||||||
|
else if (update.editedMessage != null) Some(update.editedMessage.chat)
|
||||||
|
else if (update.channelPost != null) Some(update.channelPost.chat)
|
||||||
|
else if (update.editedChannelPost != null) Some(update.editedChannelPost.chat)
|
||||||
|
else if (update.inlineQuery != null) None
|
||||||
|
else if (update.chosenInlineResult != null) None
|
||||||
|
else if (update.callbackQuery != null) Some(update.callbackQuery.message.chat)
|
||||||
|
else if (update.shippingQuery != null) None
|
||||||
|
else if (update.preCheckoutQuery != null) None
|
||||||
|
else if (update.poll != null) None
|
||||||
|
else if (update.pollAnswer != null) None
|
||||||
|
else if (update.myChatMember != null) Some(update.myChatMember.chat)
|
||||||
|
else if (update.chatMember != null) Some(update.chatMember.chat)
|
||||||
|
else if (update.chatJoinRequest != null) Some(update.chatJoinRequest.chat)
|
||||||
|
else None
|
||||||
|
|
||||||
|
def extractSourceUser: Option[User] =
|
||||||
|
if (update.message != null) Some(update.message.from)
|
||||||
|
else if (update.editedMessage != null) Some(update.editedMessage.from)
|
||||||
|
else if (update.channelPost != null) None
|
||||||
|
else if (update.editedChannelPost != null) None
|
||||||
|
else if (update.inlineQuery != null) Some(update.inlineQuery.from)
|
||||||
|
else if (update.chosenInlineResult != null) Some(update.chosenInlineResult.from)
|
||||||
|
else if (update.callbackQuery != null) Some(update.callbackQuery.from)
|
||||||
|
else if (update.shippingQuery != null) Some(update.shippingQuery.from)
|
||||||
|
else if (update.preCheckoutQuery != null) Some(update.preCheckoutQuery.from)
|
||||||
|
else if (update.poll != null) None
|
||||||
|
else if (update.pollAnswer != null) Some(update.pollAnswer.user)
|
||||||
|
else if (update.myChatMember != null) Some(update.myChatMember.from)
|
||||||
|
else if (update.chatMember != null) Some(update.chatMember.from)
|
||||||
|
else if (update.chatJoinRequest != null) Some(update.chatJoinRequest.from)
|
||||||
|
else None
|
||||||
|
|
||||||
|
}}
|
||||||
|
|
||||||
object Chat { extension (chat: Chat) {
|
object Chat { extension (chat: Chat) {
|
||||||
|
|
||||||
def hasMember (user: User) (using TelegramBot): Boolean =
|
def hasMember (user: User) (using TelegramBot): Boolean =
|
||||||
|
@ -34,11 +34,26 @@ object TelegramFormatter {
|
|||||||
def id_tdLib: Long =
|
def id_tdLib: Long =
|
||||||
if chat.id < 0 then (chat.id - MASK_BOTAPI_ID)abs else chat.id
|
if chat.id < 0 then (chat.id - MASK_BOTAPI_ID)abs else chat.id
|
||||||
|
|
||||||
def typeTag: String = chat.`type` match
|
def typeTag: String =
|
||||||
case Type.Private => "🔒"
|
import ChatTypeTag.tag
|
||||||
case Type.group => "💭"
|
chat.`type`.tag
|
||||||
case Type.supergroup => "💬"
|
|
||||||
case Type.channel => "📢"
|
}
|
||||||
|
|
||||||
|
object ChatTypeTag {
|
||||||
|
|
||||||
|
inline val PRIVATE = "🔒"
|
||||||
|
inline val GROUP = "💭"
|
||||||
|
inline val SUPERGROUP = "💬"
|
||||||
|
inline val CHANNEL = "📢"
|
||||||
|
|
||||||
|
extension (t: Type) {
|
||||||
|
def tag: String = t match
|
||||||
|
case Type.Private => this.PRIVATE
|
||||||
|
case Type.group => this.GROUP
|
||||||
|
case Type.supergroup => this.SUPERGROUP
|
||||||
|
case Type.channel => this.CHANNEL
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user