FILE / ScuroNeko/Laniakea
handler.go
Исходный файл и его история в репозитории.
Golang lint / lint (push) Successful in 1m11s
(refactor): API initialism names (tests): logger format coverage (doc): updated public names
190 lines
4.7 KiB
Go
190 lines
4.7 KiB
Go
package laniakea
|
|
|
|
import (
|
|
"context"
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"time"
|
|
|
|
"git.scuroneko.dev/scuroneko/laniakea/tgapi"
|
|
)
|
|
|
|
// ErrInvalidPayloadType is returned when callback payload encoding type is unknown.
|
|
var ErrInvalidPayloadType = errors.New("invalid payload type")
|
|
|
|
func (bot *Bot[T]) handle(parentCtx context.Context, u *tgapi.Update) {
|
|
defer func() {
|
|
if r := recover(); r != nil {
|
|
bot.logger.Errorln(fmt.Sprintf("panic in handle: %v", r))
|
|
}
|
|
}()
|
|
startTime := time.Now()
|
|
|
|
ctx, cancel := context.WithCancel(parentCtx)
|
|
defer cancel()
|
|
|
|
msgCtx := &MsgContext{
|
|
Update: *u, API: bot.api,
|
|
Logger: bot.logger,
|
|
errorTemplate: bot.errorTemplate,
|
|
l10n: bot.l10n,
|
|
draftProvider: bot.draftProvider,
|
|
sceneRuntime: bot,
|
|
observer: bot.observer,
|
|
payloadType: bot.payloadType,
|
|
ctx: ctx,
|
|
}
|
|
bot.prepareUpdateCtx(u, msgCtx)
|
|
bot.safeEmitEvent(ctx, UpdateReceivedEvent{
|
|
UpdateID: u.UpdateID,
|
|
UpdateType: u.Type,
|
|
FromID: msgCtx.FromID,
|
|
ChatID: msgCtx.ChatID,
|
|
})
|
|
|
|
for _, middleware := range bot.middlewares {
|
|
if !middleware.Execute(msgCtx, bot.appData) {
|
|
return
|
|
}
|
|
}
|
|
|
|
sceneHandled, err := bot.tryHandleScene(msgCtx)
|
|
if err != nil {
|
|
bot.logger.Errorln(err)
|
|
bot.safeEmitEvent(ctx, UpdateHandledEvent{
|
|
UpdateID: u.UpdateID,
|
|
UpdateType: u.Type,
|
|
FromID: msgCtx.FromID,
|
|
ChatID: msgCtx.ChatID,
|
|
Duration: time.Since(startTime),
|
|
Handled: false,
|
|
})
|
|
bot.safeEmitEvent(ctx, ErrorEvent{
|
|
UpdateID: u.UpdateID,
|
|
UpdateType: u.Type,
|
|
Plugin: "bot",
|
|
HandlerKind: HandlerSceneKind,
|
|
HandlerName: "tryHandleScene",
|
|
FromID: msgCtx.FromID,
|
|
ChatID: msgCtx.ChatID,
|
|
Err: err,
|
|
UserFacing: false,
|
|
})
|
|
return
|
|
}
|
|
if sceneHandled {
|
|
bot.safeEmitEvent(ctx, UpdateHandledEvent{
|
|
UpdateID: u.UpdateID,
|
|
UpdateType: u.Type,
|
|
FromID: msgCtx.FromID,
|
|
ChatID: msgCtx.ChatID,
|
|
Duration: time.Since(startTime),
|
|
Handled: true,
|
|
})
|
|
return
|
|
}
|
|
|
|
handled := false
|
|
switch u.Type {
|
|
case tgapi.UpdateTypeMessage, tgapi.UpdateTypeChannelPost:
|
|
handled = bot.handleMessage(u, msgCtx)
|
|
case tgapi.UpdateTypeCallbackQuery:
|
|
handled = bot.handleCallback(u, msgCtx)
|
|
default:
|
|
handled = bot.handleUpdate(u, msgCtx)
|
|
}
|
|
bot.safeEmitEvent(ctx, UpdateHandledEvent{
|
|
UpdateID: u.UpdateID,
|
|
UpdateType: u.Type,
|
|
FromID: msgCtx.FromID,
|
|
ChatID: msgCtx.ChatID,
|
|
Duration: time.Since(startTime),
|
|
Handled: handled,
|
|
})
|
|
}
|
|
|
|
func cloneMsgContext(src *MsgContext) *MsgContext {
|
|
cloned := *src
|
|
if src.Args != nil {
|
|
cloned.Args = append([]string(nil), src.Args...)
|
|
}
|
|
return &cloned
|
|
}
|
|
|
|
func encodeJSONPayload(d CallbackData) (string, error) {
|
|
b, err := json.Marshal(d)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return string(b), nil
|
|
}
|
|
|
|
func decodeJSONPayload(s string) (CallbackData, error) {
|
|
var data CallbackData
|
|
err := json.Unmarshal([]byte(s), &data)
|
|
return data, err
|
|
}
|
|
|
|
func encodeBase64Payload(d CallbackData) (string, error) {
|
|
data, err := encodeJSONPayload(d)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
dst := make([]byte, base64.RawURLEncoding.EncodedLen(len([]byte(data))))
|
|
base64.RawURLEncoding.Encode(dst, []byte(data))
|
|
return string(dst), nil
|
|
}
|
|
|
|
func decodeBase64Payload(s string) (CallbackData, error) {
|
|
b, err := base64.RawURLEncoding.DecodeString(s)
|
|
if err != nil {
|
|
return CallbackData{}, err
|
|
}
|
|
return decodeJSONPayload(string(b))
|
|
}
|
|
|
|
func decodePayload(payloadType BotPayloadType, s string, strict bool) (CallbackData, BotPayloadType, error) {
|
|
switch payloadType {
|
|
case BotPayloadBase64:
|
|
data, err := decodeBase64Payload(s)
|
|
if err == nil {
|
|
return data, BotPayloadBase64, nil
|
|
}
|
|
if strict {
|
|
return CallbackData{}, "", fmt.Errorf("%w: expected %s", ErrPayloadTypeMismatch, BotPayloadBase64)
|
|
}
|
|
data, err = decodeJSONPayload(s)
|
|
if err != nil {
|
|
return CallbackData{}, "", err
|
|
}
|
|
return data, BotPayloadJSON, nil
|
|
case BotPayloadJSON:
|
|
data, err := decodeJSONPayload(s)
|
|
if err == nil {
|
|
return data, BotPayloadJSON, nil
|
|
}
|
|
if strict {
|
|
return CallbackData{}, "", fmt.Errorf("%w: expected %s", ErrPayloadTypeMismatch, BotPayloadJSON)
|
|
}
|
|
data, err = decodeBase64Payload(s)
|
|
if err != nil {
|
|
return CallbackData{}, "", err
|
|
}
|
|
return data, BotPayloadBase64, nil
|
|
}
|
|
return CallbackData{}, "", ErrInvalidPayloadType
|
|
}
|
|
|
|
func (bot *Bot[T]) decodePayload(s string) (CallbackData, error) {
|
|
data, decodedType, err := decodePayload(bot.payloadType, s, bot.strictPayloadType)
|
|
if err != nil {
|
|
return CallbackData{}, err
|
|
}
|
|
if decodedType == BotPayloadBase64 && bot.debug && bot.logger != nil {
|
|
bot.logger.Debugf("decoded callback payload base64->json: raw=%q json=%s", s, data.ToJSON())
|
|
}
|
|
return data, nil
|
|
}
|