more methods

This commit is contained in:
ScuroNeko 2024-12-17 09:23:53 +03:00
parent 8f96244634
commit 186ad8e95b
8 changed files with 295 additions and 72 deletions

106
bot.go
View File

@ -18,12 +18,14 @@ type Bot struct {
prefixes []string
updateOffset int
updateTypes []string
updateQueue *Queue[*Update]
}
type MsgContext struct {
Bot *Bot
Msg *Message
Update *Update
FromID int
Prefix string
Text string
@ -31,11 +33,11 @@ type MsgContext struct {
func NewBot(token string) *Bot {
logger := CreateLogger()
logger.SetLevel(DEBUG)
logger.Level(DEBUG)
updateQueue := CreateQueue[*Update](256)
bot := &Bot{
updateOffset: 0, plugins: make([]*Plugin, 0), debug: false,
prefixes: make([]string, 0),
prefixes: make([]string, 0), updateTypes: make([]string, 0),
updateQueue: updateQueue,
token: token, logger: logger,
}
@ -43,8 +45,19 @@ func NewBot(token string) *Bot {
return bot
}
func (b *Bot) AddPrefixes(prefixes ...string) {
func (b *Bot) UpdateTypes(t ...string) *Bot {
b.updateTypes = make([]string, 0)
b.updateTypes = append(b.updateTypes, t...)
return b
}
func (b *Bot) AddUpdateType(t ...string) *Bot {
b.updateTypes = append(b.updateTypes, t...)
return b
}
func (b *Bot) AddPrefixes(prefixes ...string) *Bot {
b.prefixes = append(b.prefixes, prefixes...)
return b
}
func (b *Bot) Run() {
@ -58,9 +71,11 @@ func (b *Bot) Run() {
return
}
b.logger.Info("Bot running. Press CTRL+C to exit.")
go func() {
for {
_, err := b.GetUpdates()
_, err := b.Updates()
if err != nil {
b.logger.Error(err)
}
@ -68,44 +83,86 @@ func (b *Bot) Run() {
}()
for {
queue := b.GetQueue()
queue := b.updateQueue
if queue.IsEmpty() {
continue
}
u := queue.Dequeue()
b.handleMessage(u)
if u.CallbackQuery != nil {
b.handleCallback(u)
} else {
b.handleMessage(u)
}
}
}
// {"callback_query":{"chat_instance":"6202057960757700762","data":"aboba","from":{"first_name":"scuroneko","id":314834933,"is_bot":false,"language_code":"ru","username":"scuroneko"},"id":"1352205741990111553","message":{"chat":{"first_name":"scuroneko","id":314834933,"type":"private","username":"scuroneko"},"date":1734338107,"from":{"first_name":"Kurumi","id":7718900880,"is_bot":true,"username":"kurumi_game_bot"},"message_id":19,"reply_markup":{"inline_keyboard":[[{"callback_data":"aboba","text":"Test"},{"callback_data":"another","text":"Another"}]]},"text":"Aboba"}},"update_id":350979488}
func (b *Bot) handleMessage(update *Update) {
ctx := &MsgContext{
Bot: b,
Update: update,
}
for _, plugin := range b.plugins {
if plugin.UpdateListener != nil {
(*plugin.UpdateListener)(ctx)
}
}
if update.Message == nil {
return
}
ctx.FromID = update.Message.From.ID
ctx.Msg = update.Message
text := strings.TrimSpace(update.Message.Text)
prefix, hasPrefix := b.checkPrefixes(text)
if !hasPrefix {
return
}
ctx.Prefix = prefix
text = strings.TrimSpace(text[len(prefix):])
for _, plugin := range b.plugins {
for cmd, _ := range plugin.Commands {
// Check every command
for cmd := range plugin.Commands {
if !strings.HasPrefix(text, cmd) {
continue
}
ctx := &MsgContext{
Bot: b,
FromID: update.Message.From.ID,
Msg: update.Message,
Prefix: prefix,
Text: text[len(cmd):],
}
ctx.Text = text[len(cmd):]
plugin.Execute(cmd, ctx)
}
}
}
func (b *Bot) handleCallback(update *Update) {
ctx := &MsgContext{
Bot: b,
Update: update,
}
for _, plugin := range b.plugins {
if plugin.UpdateListener != nil {
(*plugin.UpdateListener)(ctx)
}
}
for _, plugin := range b.plugins {
for payload := range plugin.Payloads {
if !strings.HasPrefix(update.CallbackQuery.Data, payload) {
continue
}
plugin.ExecutePayload(payload, ctx)
}
}
}
func (b *Bot) checkPrefixes(text string) (string, bool) {
for _, prefix := range b.prefixes {
if strings.HasPrefix(text, prefix) {
@ -115,16 +172,14 @@ func (b *Bot) checkPrefixes(text string) (string, bool) {
return "", false
}
func (b *Bot) AddPlugins(plugin ...*Plugin) {
func (b *Bot) AddPlugins(plugin ...*Plugin) *Bot {
b.plugins = append(b.plugins, plugin...)
return b
}
func (b *Bot) SetDebug(debug bool) {
func (b *Bot) Debug(debug bool) *Bot {
b.debug = debug
}
func (b *Bot) GetQueue() *Queue[*Update] {
return b.updateQueue
return b
}
func (ctx *MsgContext) Answer(text string) {
@ -137,6 +192,10 @@ func (ctx *MsgContext) Answer(text string) {
}
}
func (b *Bot) Logger() *Logger {
return b.logger
}
type ApiResponse struct {
Ok bool `json:"ok"`
Result map[string]interface{} `json:"result,omitempty"`
@ -160,7 +219,12 @@ func (b *Bot) request(methodName string, params map[string]interface{}) (map[str
}
if b.debug {
b.logger.Debug(fmt.Sprintf("POST https://api.telegram.org/bot%s/%s %s", "<TOKEN>", methodName, string(buf.Bytes())))
b.logger.Debug(fmt.Sprintf(
"POST https://api.telegram.org/bot%s/%s %s",
"<TOKEN>",
methodName,
string(buf.Bytes()),
))
}
r, err := http.Post(fmt.Sprintf("https://api.telegram.org/bot%s/%s", b.token, methodName), "application/json", &buf)
if err != nil {

2
go.mod
View File

@ -1,4 +1,4 @@
module git.nix13.pw/ScuroNeko/Laniakea
module git.nix13.pw/ScuroNeko/laniakea
go 1.23.4

4
keyboard.go Normal file
View File

@ -0,0 +1,4 @@
package laniakea
type InlineKeyboard struct {
}

View File

@ -2,7 +2,6 @@ package laniakea
import (
"fmt"
"net/http"
"runtime"
"strings"
"time"
@ -40,17 +39,6 @@ var (
DEBUG LogLevel = LogLevel{n: 4, t: "debug", c: color.FgGreen}
)
var (
GET LogLevel = LogLevel{n: 0, t: "get", c: color.FgWhite}
POST LogLevel = LogLevel{n: 0, t: "post", c: color.FgBlue}
PUT LogLevel = LogLevel{n: 0, t: "put", c: color.FgYellow}
DELETE LogLevel = LogLevel{n: 0, t: "delete", c: color.FgHiRed}
)
var methodLevelMap = map[string]LogLevel{
"get": GET, "post": POST, "put": PUT, "delete": DELETE,
}
func CreateLogger() *Logger {
return &Logger{
prefix: "LOG",
@ -60,41 +48,34 @@ func CreateLogger() *Logger {
}
}
func (l *Logger) GetHTTPLogger(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
level := methodLevelMap[r.Method]
color.New(level.c).Println(l.buildString(methodLevelMap[strings.ToLower(r.Method)], "Hello"))
next.ServeHTTP(w, r)
})
}
func (l *Logger) SetPrefix(prefix string) {
func (l *Logger) Prefix(prefix string) {
l.prefix = prefix
}
func (l *Logger) SetLevel(level LogLevel) {
func (l *Logger) Level(level LogLevel) {
l.level = level
}
func (l *Logger) SetPrintTraceback(b bool) {
func (l *Logger) PrintTraceback(b bool) {
l.printTraceback = b
}
func (l *Logger) Info(m interface{}) {
func (l *Logger) Info(m ...any) {
l.print(INFO, m)
}
func (l *Logger) Warn(m interface{}) {
func (l *Logger) Warn(m ...any) {
l.print(WARN, m)
}
func (l *Logger) Error(m interface{}) {
func (l *Logger) Error(m ...any) {
l.print(ERROR, m)
}
func (l *Logger) Fatal(m interface{}) {
func (l *Logger) Fatal(m ...any) {
l.print(FATAL, m)
panic(m)
}
func (l *Logger) Debug(m interface{}) {
func (l *Logger) Debug(m ...any) {
l.print(DEBUG, m)
}
@ -127,7 +108,7 @@ func (l *Logger) formatTraceback(mt *MethodTraceback) string {
return fmt.Sprintf("%s:%s:%d", mt.filename, mt.Method, mt.line)
}
func (l *Logger) buildString(level LogLevel, m interface{}) string {
func (l *Logger) buildString(level LogLevel, m []any) string {
args := []string{
fmt.Sprintf("[%s]", l.prefix),
fmt.Sprintf("[%s]", strings.ToUpper(level.t)),
@ -141,12 +122,20 @@ func (l *Logger) buildString(level LogLevel, m interface{}) string {
args = append(args, fmt.Sprintf("[%s]", l.formatTime(time.Now())))
}
return fmt.Sprintf("%s %v", strings.Join(args, " "), m)
msg := Map(m, func(el any) string {
return fmt.Sprintf("%v", el)
})
return fmt.Sprintf("%s %v", strings.Join(args, " "), strings.Join(msg, " "))
}
func (l *Logger) print(level LogLevel, m interface{}) {
func (l *Logger) print(level LogLevel, m []any) {
if l.level.n < level.n {
return
}
color.New(level.c).Print(l.buildString(level, m))
if level == INFO {
color.New(level.c).Println(l.buildString(level, m))
} else {
color.New(level.c).Print(l.buildString(level, m))
}
}

View File

@ -4,10 +4,11 @@ import "fmt"
var NO_PARAMS = make(map[string]interface{})
func (b *Bot) GetUpdates() ([]*Update, error) {
func (b *Bot) Updates() ([]*Update, error) {
params := make(map[string]interface{})
params["offset"] = b.updateOffset
params["timeout"] = 30
params["allowed_updates"] = b.updateTypes
data, err := b.request("getUpdates", params)
if err != nil {
@ -38,9 +39,31 @@ func (b *Bot) GetUpdates() ([]*Update, error) {
return res, err
}
func (b *Bot) GetMe() (*User, error) {
data, err := b.request("getMe", NO_PARAMS)
if err != nil {
return nil, err
}
user := new(User)
err = MapToStruct(data, user)
return user, err
}
type SendMessageP struct {
ChatID int `json:"chat_id"`
Text string `json:"text"`
BusinessConnectionID string `json:"business_connection_id,omitempty"`
ChatID int `json:"chat_id"`
MessageThreadID int `json:"message_thread_id,omitempty"`
Text string `json:"text"`
ParseMode string `json:"parse_mode,omitempty"`
Entities []*MessageEntity `json:"entities,omitempty"`
LinkPreviewOptions *LinkPreviewOptions `json:"link_preview_options,omitempty"`
DisableNotifications bool `json:"disable_notifications,omitempty"`
ProtectContent bool `json:"protect_content,omitempty"`
AllowPaidBroadcast bool `json:"allow_paid_broadcast,omitempty"`
MessageEffectID string `json:"message_effect_id,omitempty"`
ReplyParameters *ReplyParameters `json:"reply_parameters,omitempty"`
InlineKeyboardMarkup *InlineKeyboardMarkup `json:"reply_markup,omitempty"`
// ReplyKeyboardMarkup *ReplyKeyboardMarkup `json:"reply_markup,omitempty"`
}
func (b *Bot) SendMessage(params *SendMessageP) (*Message, error) {

View File

@ -3,31 +3,43 @@ package laniakea
type CommandExecutor func(ctx *MsgContext)
type PluginBuilder struct {
name string
commands map[string]*CommandExecutor
payload string
name string
commands map[string]*CommandExecutor
payloads map[string]*CommandExecutor
updateListener *CommandExecutor
}
type Plugin struct {
Name string
Commands map[string]*CommandExecutor
Payload string
Name string
Commands map[string]*CommandExecutor
Payloads map[string]*CommandExecutor
UpdateListener *CommandExecutor
}
func NewPlugin(name string) *PluginBuilder {
return &PluginBuilder{
name: name,
commands: make(map[string]*CommandExecutor),
payloads: make(map[string]*CommandExecutor),
}
}
func (p *PluginBuilder) Command(cmd string, f CommandExecutor) *PluginBuilder {
p.commands[cmd] = &f
func (p *PluginBuilder) Command(f CommandExecutor, cmd ...string) *PluginBuilder {
for _, c := range cmd {
p.commands[c] = &f
}
return p
}
func (p *PluginBuilder) Payload(payload string) *PluginBuilder {
p.payload = payload
func (p *PluginBuilder) Payload(f CommandExecutor, payloads ...string) *PluginBuilder {
for _, payload := range payloads {
p.payloads[payload] = &f
}
return p
}
func (p *PluginBuilder) UpdateListener(listener CommandExecutor) *PluginBuilder {
p.updateListener = &listener
return p
}
@ -36,9 +48,10 @@ func (p *PluginBuilder) Build() *Plugin {
return nil
}
plugin := &Plugin{
Name: p.name,
Commands: p.commands,
Payload: p.payload,
Name: p.name,
Commands: p.commands,
Payloads: p.payloads,
UpdateListener: p.updateListener,
}
return plugin
}
@ -46,3 +59,7 @@ func (p *PluginBuilder) Build() *Plugin {
func (p *Plugin) Execute(cmd string, ctx *MsgContext) {
(*p.Commands[cmd])(ctx)
}
func (p *Plugin) ExecutePayload(payload string, ctx *MsgContext) {
(*p.Payloads[payload])(ctx)
}

122
types.go
View File

@ -1,8 +1,20 @@
package laniakea
type Update struct {
UpdateID int `json:"update_id"`
Message *Message `json:"message"`
UpdateID int `json:"update_id"`
Message *Message `json:"message"`
EditedMessage *Message `json:"edited_message,omitempty"`
ChannelPost *Message `json:"channel_post,omitempty"`
EditedChannelPost *Message `json:"edited_channel_post,omitempty"`
BusinessConnection *BusinessConnection `json:"business_connection,omitempty"`
BusinessMessage *Message `json:"business_message,omitempty"`
EditedBusinessMessage *Message `json:"edited_business_message,omitempty"`
DeletedBusinessMessage *Message `json:"deleted_business_messages,omitempty"`
MessageReaction *MessageReactionUpdated `json:"message_reaction,omitempty"`
MessageReactionCount *MessageReactionCountUpdated `json:"message_reaction_count,omitempty"`
InlineQuery int
ChosenInlineResult int
CallbackQuery *CallbackQuery `json:"callback_query,omitempty"`
}
type User struct {
@ -38,3 +50,109 @@ type Message struct {
Chat *Chat `json:"chat,omitempty"`
Text string `json:"text"`
}
type InaccessableMessage struct {
Chat *Chat `json:"chat"`
MessageID int `json:"message_id"`
Date int `json:"date"`
}
type MaybeInaccessibleMessage struct {
Message
InaccessableMessage
}
type MessageEntity struct {
Type string `json:"type"`
Offset int `json:"offset"`
Length int `json:"length"`
URL string `json:"url,omitempty"`
User *User `json:"user,omitempty"`
Language string `json:"language,omitempty"`
CustomEmojiID string `json:"custom_emoji_id,omitempty"`
}
type ReplyParameters struct {
MessageID int `json:"message_id"`
ChatID int `json:"chat_id,omitempty"`
AllowSendingWithoutReply bool `json:"allow_sending_without_reply,omitempty"`
Quote string `json:"quote,omitempty"`
QuoteParsingMode string `json:"quote_parsing_mode,omitempty"`
QuoteEntities []*MessageEntity `json:"quote_entities,omitempty"`
QuotePosition int `json:"quote_postigin,omitempty"`
}
type LinkPreviewOptions struct {
IsDisabled bool `json:"is_disabled,omitempty"`
URL string `json:"url,omitempty"`
PreferSmallMedia bool `json:"prefer_small_media,omitempty"`
PreferLargeMedia bool `json:"prefer_large_media,omitempty"`
ShowAboveText bool `json:"show_above_text,omitempty"`
}
type InlineKeyboardMarkup struct {
InlineKeyboard [][]*InlineKeyboardButton `json:"inline_keyboard"`
}
type InlineKeyboardButton struct {
Text string `json:"text"`
URL string `json:"url,omitempty"`
CallbackData string `json:"callback_data,omitempty"`
}
type ReplyKeyboardMarkup struct {
Keyboard [][]int `json:"keyboard"`
}
type CallbackQuery struct {
ID string `json:"id"`
From *User `json:"user"`
Message *MaybeInaccessibleMessage `json:"message"`
Data string `json:"data"`
}
type BusinessConnection struct {
ID string `json:"id"`
User *User `json:"user"`
UserChatID int `json:"user_chat_id"`
Date int `json:"date"`
CanReply bool `json:"can_reply"`
IsEnabled bool `json:"id_enabled"`
}
type MessageReactionUpdated struct {
Chat *Chat `json:"chat"`
MessageID int `json:"message_id"`
User *User `json:"user,omitempty"`
ActorChat *Chat `json:"actor_chat"`
Date int `json:"date"`
OldReaction []*ReactionType `json:"old_reaction"`
NewReaction []*ReactionType `json:"new_reaction"`
}
type MessageReactionCountUpdated struct {
Chat *Chat `json:"chat"`
MessageID int `json:"message_id"`
Date int `json:"date"`
Reactions []*ReactionCount `json:"reactions"`
}
type ReactionType struct {
Type string `json:"type"`
}
type ReactionTypeEmoji struct {
ReactionType
Emoji string `json:"emoji"`
}
type ReactionTypeCustomEmoji struct {
ReactionType
CustomEmojiID string `json:"custom_emoji_id"`
}
type ReactionTypePaid struct {
ReactionType
}
type ReactionCount struct {
Type *ReactionType `json:"type"`
TotalCount int `json:"total_count"`
}

View File

@ -25,3 +25,11 @@ func StructToMap(s interface{}) (map[string]interface{}, error) {
err = json.Unmarshal(data, &m)
return m, err
}
func Map[T, V any](ts []T, fn func(T) V) []V {
result := make([]V, len(ts))
for i, t := range ts {
result[i] = fn(t)
}
return result
}