FILE / ScuroNeko/Laniakea

msg_handler.go

Исходный файл и его история в репозитории.
FILE aa18da73d50a53e4e58e7eacbe148b2bc6bd60c0
Files
Laniakea/msg_handler.go
T
ScuroNeko aa18da73d5 Add plugin message fallback
Route unmatched messages through plugin fallback handlers

Add observer coverage and bump version to rc.15
2026-04-13 16:27:53 +03:00

298 lines
7.2 KiB
Go

package laniakea
import (
"strings"
"time"
"git.scuroneko.dev/scuroneko/laniakea/tgapi"
)
func (bot *Bot[T]) handleMessage(update *tgapi.Update, ctx *MsgContext) bool {
text, ok := messageText(update)
if !ok {
return false
}
prefix, cmd, args := bot.parseCommand(text)
if cmd == "" {
return bot.handleFallback(update, ctx)
}
ctx.Prefix = prefix
if strings.Contains(cmd, "@") {
botUsername := bot.username
if botUsername != "" && strings.HasSuffix(cmd, "@"+botUsername) {
cmd = cmd[:len(cmd)-len("@"+botUsername)] // убираем @botname
}
}
// Ищем команду по точному совпадению
for _, plugin := range bot.plugins {
if _, exists := plugin.commands[cmd]; exists {
ctx.Text = args
ctx.Args = strings.Fields(args) // Убирает лишние пробелы
if plugin.logger != nil {
ctx.Logger = plugin.logger
}
if !plugin.executeMiddlewares(ctx, bot.appData) {
return false
}
startTime := time.Now()
bot.safeEmitEvent(ctx.Context(), HandlerStartedEvent{
UpdateID: update.UpdateID,
UpdateType: update.Type,
Plugin: plugin.name,
HandlerKind: HandlerCommandKind,
HandlerName: cmd,
FromID: ctx.FromID,
ChatID: ctx.ChatID,
})
err := plugin.executeCmd(cmd, ctx, bot.appData)
handlerEndEvent := HandlerFinishedEvent{
UpdateID: update.UpdateID,
UpdateType: update.Type,
Plugin: plugin.name,
HandlerKind: HandlerCommandKind,
HandlerName: cmd,
FromID: ctx.FromID,
ChatID: ctx.ChatID,
Duration: time.Since(startTime),
}
var errorEvent *ErrorEvent = nil
if err != nil {
ctx.error(err)
handlerEndEvent.Err = err
handlerEndEvent.UserFacing = IsUserError(err)
errorEvent = &ErrorEvent{
UpdateID: update.UpdateID,
UpdateType: update.Type,
Plugin: plugin.name,
HandlerKind: HandlerCommandKind,
HandlerName: cmd,
FromID: ctx.FromID,
ChatID: ctx.ChatID,
Err: err,
UserFacing: handlerEndEvent.UserFacing,
}
}
bot.safeEmitEvent(ctx.Context(), handlerEndEvent)
if errorEvent != nil {
bot.safeEmitEvent(ctx.Context(), *errorEvent)
}
return true
}
}
return bot.handleFallback(update, ctx)
}
func (bot *Bot[T]) handleFallback(update *tgapi.Update, ctx *MsgContext) bool {
text, ok := messageText(update)
if !ok {
return false
}
prefix, _, _ := bot.parseCommand(text)
handled := false
for _, plugin := range bot.plugins {
if plugin.messageFallback == nil {
continue
}
pluginCtx := cloneMsgContext(ctx)
pluginCtx.Prefix = prefix
pluginCtx.Text = text
pluginCtx.Args = strings.Fields(text)
if plugin.logger != nil {
pluginCtx.Logger = plugin.logger
}
if !plugin.executeMiddlewares(pluginCtx, bot.appData) {
continue
}
startTime := time.Now()
bot.safeEmitEvent(pluginCtx.Context(), HandlerStartedEvent{
UpdateID: update.UpdateID,
UpdateType: update.Type,
Plugin: plugin.name,
HandlerKind: HandlerMessageKind,
HandlerName: "message_fallback",
FromID: pluginCtx.FromID,
ChatID: pluginCtx.ChatID,
})
err := plugin.messageFallback(pluginCtx, bot.appData)
endEvent := HandlerFinishedEvent{
UpdateID: update.UpdateID,
UpdateType: update.Type,
Plugin: plugin.name,
HandlerKind: HandlerMessageKind,
HandlerName: "message_fallback",
FromID: pluginCtx.FromID,
ChatID: pluginCtx.ChatID,
Duration: time.Since(startTime),
}
if err != nil {
endEvent.Err = err
endEvent.UserFacing = IsUserError(err)
}
bot.safeEmitEvent(pluginCtx.Context(), endEvent)
if err != nil {
pluginCtx.error(err)
bot.safeEmitEvent(pluginCtx.Context(), ErrorEvent{
UpdateID: update.UpdateID,
UpdateType: update.Type,
Plugin: plugin.name,
HandlerKind: HandlerMessageKind,
HandlerName: "message_fallback",
FromID: pluginCtx.FromID,
ChatID: pluginCtx.ChatID,
Err: err,
UserFacing: IsUserError(err),
})
}
handled = true
}
return handled
}
func messageText(update *tgapi.Update) (string, bool) {
var msg *tgapi.Message
if update.Message != nil {
msg = update.Message
} else if update.ChannelPost != nil {
msg = update.ChannelPost
} else {
return "", false
}
var text string
if len(msg.Text) > 0 {
text = msg.Text
} else if len(msg.Caption) > 0 {
text = msg.Caption
} else {
return "", false
}
return text, true
}
func (bot *Bot[T]) handleCallback(update *tgapi.Update, ctx *MsgContext) bool {
data, err := bot.decodePayload(update.CallbackQuery.Data)
if err != nil {
bot.logger.Errorln(err)
bot.safeEmitEvent(ctx.Context(), ErrorEvent{
UpdateID: update.UpdateID,
UpdateType: update.Type,
Plugin: "bot",
HandlerKind: HandlerPayloadKind,
HandlerName: "decodePayload",
FromID: ctx.FromID,
ChatID: ctx.ChatID,
Err: err,
UserFacing: false,
})
return false
}
ctx.Args = data.Args
for _, plugin := range bot.plugins {
_, ok := plugin.payloads[data.Command]
if !ok {
continue
}
ctx.Logger = plugin.logger
if ctx.Logger == nil {
ctx.Logger = bot.logger
}
if !plugin.executeMiddlewares(ctx, bot.appData) {
return false
}
startTime := time.Now()
bot.safeEmitEvent(ctx.Context(), HandlerStartedEvent{
UpdateID: update.UpdateID,
UpdateType: update.Type,
Plugin: plugin.name,
HandlerKind: HandlerPayloadKind,
HandlerName: data.Command,
FromID: ctx.FromID,
ChatID: ctx.ChatID,
})
err := plugin.executePayload(data.Command, ctx, bot.appData)
endEvent := HandlerFinishedEvent{
UpdateID: update.UpdateID,
UpdateType: update.Type,
Plugin: plugin.name,
HandlerKind: HandlerPayloadKind,
HandlerName: data.Command,
FromID: ctx.FromID,
ChatID: ctx.ChatID,
Duration: time.Since(startTime),
}
var errorEvent *ErrorEvent = nil
if err != nil {
ctx.error(err)
errorEvent = &ErrorEvent{
UpdateID: update.UpdateID,
UpdateType: update.Type,
Plugin: plugin.name,
HandlerKind: HandlerPayloadKind,
HandlerName: data.Command,
FromID: ctx.FromID,
ChatID: ctx.ChatID,
Err: err,
UserFacing: IsUserError(err),
}
endEvent.Err = err
endEvent.UserFacing = errorEvent.UserFacing
}
bot.safeEmitEvent(ctx.Context(), endEvent)
if errorEvent != nil {
bot.safeEmitEvent(ctx.Context(), *errorEvent)
}
return true
}
return false
}
func (bot *Bot[T]) checkPrefixes(text string) (string, bool) {
for _, prefix := range bot.prefixes {
if prefix == "" {
if bot.logger != nil {
bot.logger.Warnln("empty prefix is not allowed")
}
continue
}
if strings.HasPrefix(text, prefix) {
return prefix, true
}
}
return "", false
}
func (bot *Bot[T]) parseCommand(text string) (prefix, cmd, args string) {
if prefix, hasPrefix := bot.checkPrefixes(text); hasPrefix {
text = strings.TrimSpace(text[len(prefix):])
spaceIndex := strings.Index(text, " ")
var cmd string
var args string
if spaceIndex == -1 {
cmd = text
args = ""
} else {
cmd = text[:spaceIndex]
args = strings.TrimSpace(text[spaceIndex:])
}
return prefix, cmd, args
}
return "", "", ""
}