add authorization policies
Introduce first-class policy helpers and composition through middleware Normalize chat context for more updates and add regression coverage Document the completed backlog item in changelog, TODO, and wiki
This commit is contained in:
+3
-243
@@ -6,7 +6,6 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"git.scuroneko.dev/scuroneko/laniakea/tgapi"
|
||||
)
|
||||
@@ -61,103 +60,6 @@ func (bot *Bot[T]) handle(parentCtx context.Context, u *tgapi.Update) {
|
||||
}
|
||||
}
|
||||
|
||||
func (bot *Bot[T]) handleMessage(update *tgapi.Update, ctx *MsgContext) {
|
||||
var msg *tgapi.Message
|
||||
if update.Message != nil {
|
||||
msg = update.Message
|
||||
} else if update.ChannelPost != nil {
|
||||
msg = update.ChannelPost
|
||||
} else {
|
||||
return
|
||||
}
|
||||
|
||||
var text string
|
||||
if len(msg.Text) > 0 {
|
||||
text = msg.Text
|
||||
} else if len(msg.Caption) > 0 {
|
||||
text = msg.Caption
|
||||
} else {
|
||||
return
|
||||
}
|
||||
|
||||
prefix, cmd, args := bot.parseCommand(text)
|
||||
if cmd == "" {
|
||||
return
|
||||
}
|
||||
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
|
||||
}
|
||||
plugin.executeCmd(cmd, ctx, bot.appData)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (bot *Bot[T]) handleCallback(update *tgapi.Update, ctx *MsgContext) {
|
||||
data, err := bot.decodePayload(update.CallbackQuery.Data)
|
||||
if err != nil {
|
||||
bot.logger.Errorln(err)
|
||||
return
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
plugin.executePayload(data.Command, ctx, bot.appData)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (bot *Bot[T]) handleUpdate(u *tgapi.Update, ctx *MsgContext) {
|
||||
for _, plugin := range bot.plugins {
|
||||
handler, ok := plugin.handlers[u.Type]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
pluginCtx := cloneMsgContext(ctx)
|
||||
if plugin.logger != nil {
|
||||
pluginCtx.Logger = plugin.logger
|
||||
}
|
||||
if !plugin.executeMiddlewares(pluginCtx, bot.appData) {
|
||||
continue
|
||||
}
|
||||
if err := handler(pluginCtx, bot.appData); err != nil {
|
||||
pluginCtx.error(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func cloneMsgContext(src *MsgContext) *MsgContext {
|
||||
cloned := *src
|
||||
if src.Args != nil {
|
||||
@@ -166,139 +68,6 @@ func cloneMsgContext(src *MsgContext) *MsgContext {
|
||||
return &cloned
|
||||
}
|
||||
|
||||
func (bot *Bot[T]) prepareUpdateCtx(u *tgapi.Update, ctx *MsgContext) {
|
||||
var from *tgapi.User
|
||||
switch u.Type {
|
||||
case tgapi.UpdateTypeMessage:
|
||||
if u.Message != nil {
|
||||
ctx.Msg = u.Message
|
||||
}
|
||||
case tgapi.UpdateTypeEditedMessage:
|
||||
if u.EditedMessage != nil {
|
||||
ctx.Msg = u.EditedMessage
|
||||
}
|
||||
case tgapi.UpdateTypeChannelPost:
|
||||
if u.ChannelPost != nil {
|
||||
ctx.Msg = u.ChannelPost
|
||||
}
|
||||
case tgapi.UpdateTypeEditedChannelPost:
|
||||
if u.EditedChannelPost != nil {
|
||||
ctx.Msg = u.EditedChannelPost
|
||||
}
|
||||
case tgapi.UpdateTypeBusinessMessage:
|
||||
if u.BusinessMessage != nil {
|
||||
ctx.Msg = u.BusinessMessage
|
||||
}
|
||||
case tgapi.UpdateTypeEditedBusinessMessage:
|
||||
if u.EditedBusinessMessage != nil {
|
||||
ctx.Msg = u.EditedBusinessMessage
|
||||
}
|
||||
case tgapi.UpdateTypeInlineQuery:
|
||||
if u.InlineQuery != nil {
|
||||
from = &u.InlineQuery.From
|
||||
}
|
||||
case tgapi.UpdateTypeChosenInlineResult:
|
||||
if u.ChosenInlineResult != nil {
|
||||
from = &u.ChosenInlineResult.From
|
||||
}
|
||||
case tgapi.UpdateTypeCallbackQuery:
|
||||
if u.CallbackQuery != nil {
|
||||
if u.CallbackQuery.Message != nil {
|
||||
ctx.Msg = u.CallbackQuery.Message
|
||||
ctx.CallbackMsgId = u.CallbackQuery.Message.MessageID
|
||||
}
|
||||
if u.CallbackQuery.InlineMessageID != nil {
|
||||
ctx.InlineMsgId = *u.CallbackQuery.InlineMessageID
|
||||
}
|
||||
ctx.CallbackQueryId = u.CallbackQuery.ID
|
||||
from = &u.CallbackQuery.From
|
||||
}
|
||||
case tgapi.UpdateTypeShippingQuery:
|
||||
if u.ShippingQuery != nil {
|
||||
from = &u.ShippingQuery.From
|
||||
}
|
||||
case tgapi.UpdateTypePreCheckoutQuery:
|
||||
if u.PreCheckoutQuery != nil {
|
||||
from = &u.PreCheckoutQuery.From
|
||||
}
|
||||
case tgapi.UpdateTypePurchasedPaidMedia:
|
||||
if u.PurchasedPaidMedia != nil {
|
||||
from = &u.PurchasedPaidMedia.From
|
||||
}
|
||||
case tgapi.UpdateTypeMyChatMember:
|
||||
if u.MyChatMember != nil {
|
||||
from = &u.MyChatMember.From
|
||||
}
|
||||
case tgapi.UpdateTypeChatMember:
|
||||
if u.ChatMember != nil {
|
||||
from = &u.ChatMember.From
|
||||
}
|
||||
case tgapi.UpdateTypeChatJoinRequest:
|
||||
if u.ChatJoinRequest != nil {
|
||||
from = &u.ChatJoinRequest.From
|
||||
}
|
||||
case tgapi.UpdateTypeBusinessConnection:
|
||||
if u.BusinessConnection != nil {
|
||||
from = &u.BusinessConnection.User
|
||||
}
|
||||
case tgapi.UpdateTypePollAnswer:
|
||||
if u.PollAnswer != nil {
|
||||
from = &u.PollAnswer.User
|
||||
}
|
||||
case tgapi.UpdateTypeMessageReaction:
|
||||
if u.MessageReaction != nil {
|
||||
from = u.MessageReaction.User
|
||||
}
|
||||
case tgapi.UpdateTypeChatBoost:
|
||||
if u.ChatBoost != nil {
|
||||
from = &u.ChatBoost.Boost.Source.User
|
||||
}
|
||||
case tgapi.UpdateTypeRemovedChatBoost:
|
||||
if u.RemovedChatBoost != nil {
|
||||
from = &u.RemovedChatBoost.Source.User
|
||||
}
|
||||
}
|
||||
if ctx.Msg != nil && from == nil {
|
||||
from = ctx.Msg.From
|
||||
}
|
||||
if from != nil {
|
||||
ctx.From = from
|
||||
ctx.FromID = from.ID
|
||||
}
|
||||
}
|
||||
|
||||
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 "", "", ""
|
||||
}
|
||||
|
||||
func encodeJsonPayload(d CallbackData) (string, error) {
|
||||
b, err := json.Marshal(d)
|
||||
if err != nil {
|
||||
@@ -306,11 +75,13 @@ func encodeJsonPayload(d CallbackData) (string, error) {
|
||||
}
|
||||
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 {
|
||||
@@ -321,15 +92,6 @@ func encodeBase64Payload(d CallbackData) (string, error) {
|
||||
return string(dst), nil
|
||||
}
|
||||
|
||||
// func encodePayload(payloadType BotPayloadType, d CallbackData) (string, error) {
|
||||
// switch payloadType {
|
||||
// case BotPayloadBase64:
|
||||
// return encodeBase64Payload(d)
|
||||
// case BotPayloadJson:
|
||||
// return encodeJsonPayload(d)
|
||||
// }
|
||||
// return "", ErrInvalidPayloadType
|
||||
// }
|
||||
func decodeBase64Payload(s string) (CallbackData, error) {
|
||||
b, err := base64.RawURLEncoding.DecodeString(s)
|
||||
if err != nil {
|
||||
@@ -337,6 +99,7 @@ func decodeBase64Payload(s string) (CallbackData, error) {
|
||||
}
|
||||
return decodeJsonPayload(string(b))
|
||||
}
|
||||
|
||||
func decodePayload(payloadType BotPayloadType, s string, strict bool) (CallbackData, BotPayloadType, error) {
|
||||
switch payloadType {
|
||||
case BotPayloadBase64:
|
||||
@@ -369,9 +132,6 @@ func decodePayload(payloadType BotPayloadType, s string, strict bool) (CallbackD
|
||||
return CallbackData{}, "", ErrInvalidPayloadType
|
||||
}
|
||||
|
||||
// func (bot *Bot[T]) encodePayload(d CallbackData) (string, error) {
|
||||
// return encodePayload(bot.payloadType, d)
|
||||
// }
|
||||
func (bot *Bot[T]) decodePayload(s string) (CallbackData, error) {
|
||||
data, decodedType, err := decodePayload(bot.payloadType, s, bot.strictPayloadType)
|
||||
if err != nil {
|
||||
|
||||
Reference in New Issue
Block a user