diff --git a/api.go b/api.go deleted file mode 100644 index aa9983e..0000000 --- a/api.go +++ /dev/null @@ -1,80 +0,0 @@ -package laniakea - -import ( - "bytes" - "encoding/json" - "fmt" - "io" - "net/http" - "strings" -) - -type ApiResponse[R any] struct { - Ok bool `json:"ok"` - Description string `json:"description,omitempty"` - Result R `json:"result,omitempty"` - ErrorCode int `json:"error_code,omitempty"` -} - -type TelegramRequest[R, P any] struct { - method string - params P -} - -func NewRequest[R, P any](method string, params P) TelegramRequest[R, P] { - return TelegramRequest[R, P]{method: method, params: params} -} -func (r TelegramRequest[R, P]) Do(bot *Bot) (*R, error) { - var buf bytes.Buffer - err := json.NewEncoder(&buf).Encode(r.params) - if err != nil { - return nil, err - } - - if bot.requestLogger != nil { - bot.requestLogger.Debugln(strings.ReplaceAll(fmt.Sprintf( - "POST https://api.telegram.org/bot%s/%s %s", - "", r.method, buf.String(), - ), "\n", "")) - } - - req, err := http.Post(fmt.Sprintf("https://api.telegram.org/bot%s/%s", bot.token, r.method), "application/json", &buf) - if err != nil { - return nil, err - } - defer req.Body.Close() - data, err := io.ReadAll(req.Body) - if err != nil { - return nil, err - } - - if bot.requestLogger != nil { - bot.requestLogger.Debugln(fmt.Sprintf("RES %s %s", r.method, string(data))) - } - - response := new(ApiResponse[R]) - err = json.Unmarshal(data, &response) - if err != nil { - return nil, err - } - - if !response.Ok { - return nil, fmt.Errorf("[%d] %s", response.ErrorCode, response.Description) - } - return &response.Result, nil -} - -func (b *Bot) GetFileByLink(link string) ([]byte, error) { - c := http.DefaultClient - u := fmt.Sprintf("https://api.telegram.org/file/bot%s/%s", b.token, link) - req, err := http.NewRequest("GET", u, nil) - if err != nil { - return nil, err - } - res, err := c.Do(req) - if err != nil { - return nil, err - } - defer res.Body.Close() - return io.ReadAll(res.Body) -} diff --git a/bot.go b/bot.go index 9295a9c..d3cc5e9 100644 --- a/bot.go +++ b/bot.go @@ -9,27 +9,20 @@ import ( "time" "git.nix13.pw/scuroneko/extypes" + "git.nix13.pw/scuroneko/laniakea/tgapi" "git.nix13.pw/scuroneko/slog" "github.com/redis/go-redis/v9" "github.com/vinovest/sqlx" "go.mongodb.org/mongo-driver/v2/mongo" ) -type ParseMode string - -const ( - ParseMDV2 ParseMode = "MarkdownV2" - ParseHTML ParseMode = "HTML" - ParseMD ParseMode = "Markdown" -) - type Bot struct { token string debug bool errorTemplate string logger *slog.Logger - requestLogger *slog.Logger + RequestLogger *slog.Logger plugins []Plugin middlewares []Middleware @@ -37,12 +30,20 @@ type Bot struct { runners []Runner dbContext *DatabaseContext + api *tgapi.Api + + dbWriterRequested extypes.Slice[*slog.Logger] updateOffset int - updateTypes []string - updateQueue *extypes.Queue[*Update] + updateTypes []tgapi.UpdateType + updateQueue *extypes.Queue[*tgapi.Update] } +func (b *Bot) GetUpdateOffset() int { return b.updateOffset } +func (b *Bot) SetUpdateOffset(offset int) { b.updateOffset = offset } +func (b *Bot) GetUpdateTypes() []tgapi.UpdateType { return b.updateTypes } +func (b *Bot) GetQueue() *extypes.Queue[*tgapi.Update] { return b.updateQueue } + type BotSettings struct { Token string Debug bool @@ -74,13 +75,15 @@ func LoadPrefixesFromEnv() []string { return strings.Split(prefixesS, ";") } func NewBot(settings *BotSettings) *Bot { - updateQueue := extypes.CreateQueue[*Update](256) + updateQueue := extypes.CreateQueue[*tgapi.Update](256) + api := tgapi.NewAPI(settings.Token) bot := &Bot{ updateOffset: 0, plugins: make([]Plugin, 0), debug: settings.Debug, errorTemplate: "%s", - prefixes: settings.Prefixes, updateTypes: make([]string, 0), runners: make([]Runner, 0), - updateQueue: updateQueue, - token: settings.Token, + prefixes: settings.Prefixes, updateTypes: make([]tgapi.UpdateType, 0), runners: make([]Runner, 0), + updateQueue: updateQueue, api: api, dbWriterRequested: make([]*slog.Logger, 0), + token: settings.Token, } + bot.dbWriterRequested = bot.dbWriterRequested.Push(api.Logger) if len(settings.ErrorTemplate) > 0 { bot.errorTemplate = settings.ErrorTemplate @@ -106,19 +109,19 @@ func NewBot(settings *BotSettings) *Bot { } if settings.UseRequestLogger { - bot.requestLogger = slog.CreateLogger().Level(level).Prefix("REQUESTS") - bot.requestLogger.AddWriter(bot.requestLogger.CreateJsonStdoutWriter()) + bot.RequestLogger = slog.CreateLogger().Level(level).Prefix("REQUESTS") + bot.RequestLogger.AddWriter(bot.RequestLogger.CreateJsonStdoutWriter()) if settings.WriteToFile { path := fmt.Sprintf("%s/requests.log", strings.TrimRight(settings.LoggerBasePath, "/")) - fileWriter, err := bot.requestLogger.CreateTextFileWriter(path) + fileWriter, err := bot.RequestLogger.CreateTextFileWriter(path) if err != nil { bot.logger.Fatal(err) } - bot.requestLogger.AddWriter(fileWriter) + bot.RequestLogger.AddWriter(fileWriter) } } - u, err := bot.GetMe() + u, err := api.GetMe() if err != nil { bot.logger.Fatal(err) } @@ -132,7 +135,7 @@ func (b *Bot) Close() { if err != nil { log.Println(err) } - err = b.requestLogger.Close() + err = b.RequestLogger.Close() if err != nil { log.Println(err) } @@ -147,8 +150,11 @@ type DatabaseContext struct { func (b *Bot) AddDatabaseLogger(writer func(db *DatabaseContext) slog.LoggerWriter) *Bot { w := writer(b.dbContext) b.logger.AddWriter(w) - if b.requestLogger != nil { - b.requestLogger.AddWriter(w) + if b.RequestLogger != nil { + b.RequestLogger.AddWriter(w) + } + for _, l := range b.dbWriterRequested { + l.AddWriter(w) } return b } @@ -157,12 +163,12 @@ func (b *Bot) DatabaseContext(ctx *DatabaseContext) *Bot { b.dbContext = ctx return b } -func (b *Bot) UpdateTypes(t ...string) *Bot { - b.updateTypes = make([]string, 0) +func (b *Bot) UpdateTypes(t ...tgapi.UpdateType) *Bot { + b.updateTypes = make([]tgapi.UpdateType, 0) b.updateTypes = append(b.updateTypes, t...) return b } -func (b *Bot) AddUpdateType(t ...string) *Bot { +func (b *Bot) AddUpdateType(t ...tgapi.UpdateType) *Bot { b.updateTypes = append(b.updateTypes, t...) return b } @@ -251,17 +257,11 @@ func (b *Bot) Run() { continue } - ctx := &MsgContext{Bot: b, Update: u} + ctx := &MsgContext{Bot: b, Update: *u, Api: b.api} for _, middleware := range b.middlewares { middleware.Execute(ctx, b.dbContext) } - for _, plugin := range b.plugins { - if plugin.UpdateListener != nil { - (*plugin.UpdateListener)(ctx, b.dbContext) - } - } - if u.CallbackQuery != nil { b.handleCallback(u, ctx) } else { diff --git a/go.mod b/go.mod index bf2cc14..40bdc50 100644 --- a/go.mod +++ b/go.mod @@ -1,9 +1,9 @@ module git.nix13.pw/scuroneko/laniakea -go 1.25.6 +go 1.25 require ( - git.nix13.pw/scuroneko/extypes v1.0.2 + git.nix13.pw/scuroneko/extypes v1.1.0 git.nix13.pw/scuroneko/slog v1.0.2 github.com/redis/go-redis/v9 v9.17.3 github.com/vinovest/sqlx v1.7.1 @@ -14,16 +14,17 @@ require ( github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/fatih/color v1.18.0 // indirect - github.com/klauspost/compress v1.18.3 // indirect + github.com/klauspost/compress v1.18.4 // indirect github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect - github.com/muir/sqltoken v0.2.1 // indirect + github.com/muir/list v1.2.1 // indirect + github.com/muir/sqltoken v0.3.0 // indirect github.com/xdg-go/pbkdf2 v1.0.0 // indirect github.com/xdg-go/scram v1.2.0 // indirect github.com/xdg-go/stringprep v1.0.4 // indirect github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect - golang.org/x/crypto v0.47.0 // indirect + golang.org/x/crypto v0.48.0 // indirect golang.org/x/sync v0.19.0 // indirect - golang.org/x/sys v0.40.0 // indirect - golang.org/x/text v0.33.0 // indirect + golang.org/x/sys v0.41.0 // indirect + golang.org/x/text v0.34.0 // indirect ) diff --git a/go.sum b/go.sum index e737cc0..a552309 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,7 @@ filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= -git.nix13.pw/scuroneko/extypes v1.0.2 h1:Qz1InLccaB9crXY33oGrSetPHePKfQAUqW/p/iYXmJk= -git.nix13.pw/scuroneko/extypes v1.0.2/go.mod h1:uZVs8Yo3RrYAG9dMad6qR6lsYY67t+459D9c65QAYAw= +git.nix13.pw/scuroneko/extypes v1.1.0 h1:kdAraybAqQgVhArVkVfrIi7KVEX8HgTr8mzbIZAAAqg= +git.nix13.pw/scuroneko/extypes v1.1.0/go.mod h1:uZVs8Yo3RrYAG9dMad6qR6lsYY67t+459D9c65QAYAw= git.nix13.pw/scuroneko/slog v1.0.2 h1:vZyUROygxC2d5FJHUQM/30xFEHY1JT/aweDZXA4rm2g= git.nix13.pw/scuroneko/slog v1.0.2/go.mod h1:3Qm2wzkR5KjwOponMfG7TcGSDjmYaFqRAmLvSPTuWJI= github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= @@ -20,8 +20,8 @@ github.com/go-sql-driver/mysql v1.9.0 h1:Y0zIbQXhQKmQgTp44Y1dp3wTXcn804QoTptLZT1 github.com/go-sql-driver/mysql v1.9.0/go.mod h1:pDetrLJeA3oMujJuvXc8RJoasr589B6A9fwzD3QMrqw= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/klauspost/compress v1.18.3 h1:9PJRvfbmTabkOX8moIpXPbMMbYN60bWImDDU7L+/6zw= -github.com/klauspost/compress v1.18.3/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4= +github.com/klauspost/compress v1.18.4 h1:RPhnKRAQ4Fh8zU2FY/6ZFDwTVTxgJ/EMydqSTzE9a2c= +github.com/klauspost/compress v1.18.4/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= @@ -30,8 +30,12 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y= github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= +github.com/muir/list v1.2.1 h1:lmF8fz2B1WbXkzHr/Eh0oWPJArDBzWqIifOwbA4gWSo= +github.com/muir/list v1.2.1/go.mod h1:v0l2f997MxCohQlD7PTejJqyYKwFVz/i3mTpDl4LAf0= github.com/muir/sqltoken v0.2.1 h1:19KvJrCj9aOMfU921hjnizWPlQmPTe+tb36zupOY2FA= github.com/muir/sqltoken v0.2.1/go.mod h1:sSlj5M0VqQ4OuedmxwWs1TmzzRXaH3DLf5ukzg6meIo= +github.com/muir/sqltoken v0.3.0 h1:3xbcqr80f3IA4OlwkOpdIHC4DTu6gsi1TwMqgYL4Dpg= +github.com/muir/sqltoken v0.3.0/go.mod h1:+OSmbGI22QcVZ6DCzlHT8EAzEq/mqtqedtPP91Le+3A= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/redis/go-redis/v9 v9.17.3 h1:fN29NdNrE17KttK5Ndf20buqfDZwGNgoUr9qjl1DQx4= @@ -55,6 +59,8 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8= golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A= +golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts= +golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= @@ -71,6 +77,8 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k= +golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -79,6 +87,8 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= +golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk= +golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= diff --git a/handler.go b/handler.go index c2f8877..c250277 100644 --- a/handler.go +++ b/handler.go @@ -3,9 +3,24 @@ package laniakea import ( "encoding/json" "strings" + + "git.nix13.pw/scuroneko/laniakea/tgapi" ) -func (b *Bot) handleMessage(update *Update, ctx *MsgContext) { +func (b *Bot) handle(u *tgapi.Update) { + ctx := &MsgContext{Bot: b, Update: *u, Api: b.api} + for _, middleware := range b.middlewares { + middleware.Execute(ctx, b.dbContext) + } + + if u.CallbackQuery != nil { + b.handleCallback(u, ctx) + } else { + b.handleMessage(u, ctx) + } +} + +func (b *Bot) handleMessage(update *tgapi.Update, ctx *MsgContext) { if update.Message == nil { return } @@ -35,6 +50,18 @@ func (b *Bot) handleMessage(update *Update, ctx *MsgContext) { if !strings.HasPrefix(text, cmd) { continue } + requestParts := strings.Split(text, " ") + cmdParts := strings.Split(cmd, " ") + isValid := true + for i, part := range cmdParts { + if part != requestParts[i] { + isValid = false + break + } + } + if !isValid { + continue + } ctx.Text = strings.TrimSpace(text[len(cmd):]) ctx.Args = strings.Split(ctx.Text, " ") @@ -48,7 +75,7 @@ func (b *Bot) handleMessage(update *Update, ctx *MsgContext) { } } -func (b *Bot) handleCallback(update *Update, ctx *MsgContext) { +func (b *Bot) handleCallback(update *tgapi.Update, ctx *MsgContext) { data := new(CallbackData) err := json.Unmarshal([]byte(update.CallbackQuery.Data), data) if err != nil { @@ -57,8 +84,8 @@ func (b *Bot) handleCallback(update *Update, ctx *MsgContext) { } ctx.FromID = update.CallbackQuery.From.ID - ctx.From = update.CallbackQuery.From - ctx.Msg = update.CallbackQuery.Message + ctx.From = &update.CallbackQuery.From + ctx.Msg = &update.CallbackQuery.Message ctx.CallbackMsgId = update.CallbackQuery.Message.MessageID ctx.CallbackQueryId = update.CallbackQuery.ID ctx.Args = data.Args diff --git a/keyboard.go b/keyboard.go index bdb9e35..01b400e 100644 --- a/keyboard.go +++ b/keyboard.go @@ -3,51 +3,111 @@ package laniakea import ( "encoding/json" "fmt" + + "git.nix13.pw/scuroneko/extypes" + "git.nix13.pw/scuroneko/laniakea/tgapi" ) +const ( + ButtonStyleDanger tgapi.KeyboardButtonStyle = "danger" + ButtonStyleSuccess tgapi.KeyboardButtonStyle = "success" + ButtonStylePrimary tgapi.KeyboardButtonStyle = "primary" +) + +type InlineKbButtonBuilder struct { + text string + iconCustomEmojiID string + style tgapi.KeyboardButtonStyle + url string + callbackData string +} + +func NewInlineKbButton(text string) InlineKbButtonBuilder { + return InlineKbButtonBuilder{text: text} +} +func (b InlineKbButtonBuilder) SetIconCustomEmojiId(id string) InlineKbButtonBuilder { + b.iconCustomEmojiID = id + return b +} +func (b InlineKbButtonBuilder) SetStyle(style tgapi.KeyboardButtonStyle) InlineKbButtonBuilder { + b.style = style + return b +} +func (b InlineKbButtonBuilder) SetUrl(url string) InlineKbButtonBuilder { + b.url = url + return b +} +func (b InlineKbButtonBuilder) SetCallbackData(cmd string, args ...any) InlineKbButtonBuilder { + b.callbackData = NewCallbackData(cmd, args...).ToJson() + return b +} + +func (b InlineKbButtonBuilder) build() tgapi.InlineKeyboardButton { + return tgapi.InlineKeyboardButton{ + Text: b.text, + URL: b.url, + Style: b.style, + IconCustomEmojiID: b.iconCustomEmojiID, + CallbackData: b.callbackData, + } +} + type InlineKeyboard struct { - CurrentLine []InlineKeyboardButton - Lines [][]InlineKeyboardButton + CurrentLine extypes.Slice[tgapi.InlineKeyboardButton] + Lines [][]tgapi.InlineKeyboardButton maxRow int } func NewInlineKeyboard(maxRow int) *InlineKeyboard { return &InlineKeyboard{ - CurrentLine: make([]InlineKeyboardButton, 0), - Lines: make([][]InlineKeyboardButton, 0), + CurrentLine: make(extypes.Slice[tgapi.InlineKeyboardButton], 0), + Lines: make([][]tgapi.InlineKeyboardButton, 0), maxRow: maxRow, } } -func (in *InlineKeyboard) append(button InlineKeyboardButton) *InlineKeyboard { - if len(in.CurrentLine) == in.maxRow { +func (in *InlineKeyboard) append(button tgapi.InlineKeyboardButton) *InlineKeyboard { + if in.CurrentLine.Len() == in.maxRow { in.AddLine() } - in.CurrentLine = append(in.CurrentLine, button) + in.CurrentLine = in.CurrentLine.Push(button) return in } + func (in *InlineKeyboard) AddUrlButton(text, url string) *InlineKeyboard { - return in.append(InlineKeyboardButton{Text: text, URL: url}) + return in.append(tgapi.InlineKeyboardButton{Text: text, URL: url}) +} +func (in *InlineKeyboard) AddUrlButtonStyle(text string, style tgapi.KeyboardButtonStyle, url string) *InlineKeyboard { + return in.append(tgapi.InlineKeyboardButton{Text: text, Style: style, URL: url}) } func (in *InlineKeyboard) AddCallbackButton(text string, cmd string, args ...any) *InlineKeyboard { - return in.append(InlineKeyboardButton{Text: text, CallbackData: NewCallbackData(cmd, args...).ToJson()}) + return in.append(tgapi.InlineKeyboardButton{ + Text: text, CallbackData: NewCallbackData(cmd, args...).ToJson(), + }) +} +func (in *InlineKeyboard) AddCallbackButtonStyle(text string, style tgapi.KeyboardButtonStyle, cmd string, args ...any) *InlineKeyboard { + return in.append(tgapi.InlineKeyboardButton{ + Text: text, Style: style, + CallbackData: NewCallbackData(cmd, args...).ToJson(), + }) +} +func (in *InlineKeyboard) AddButton(b InlineKbButtonBuilder) *InlineKeyboard { + return in.append(b.build()) } func (in *InlineKeyboard) AddLine() *InlineKeyboard { - if len(in.CurrentLine) == 0 { + if in.CurrentLine.Len() == 0 { return in } in.Lines = append(in.Lines, in.CurrentLine) - in.CurrentLine = make([]InlineKeyboardButton, 0) + in.CurrentLine = make(extypes.Slice[tgapi.InlineKeyboardButton], 0) return in } -func (in *InlineKeyboard) Get() InlineKeyboardMarkup { - if len(in.CurrentLine) > 0 { +func (in *InlineKeyboard) Get() *tgapi.ReplyMarkup { + if in.CurrentLine.Len() > 0 { in.Lines = append(in.Lines, in.CurrentLine) } - return InlineKeyboardMarkup{ - InlineKeyboard: in.Lines, - } + return &tgapi.ReplyMarkup{InlineKeyboard: in.Lines} } type CallbackData struct { diff --git a/methods.go b/methods.go index 81ef1b6..d2a1f10 100644 --- a/methods.go +++ b/methods.go @@ -3,241 +3,49 @@ package laniakea import ( "encoding/json" "fmt" + "io" + "net/http" + + "git.nix13.pw/scuroneko/laniakea/tgapi" ) -type EmptyParams struct{} - -var NoParams = EmptyParams{} - -type UpdateParams struct { - Offset int `json:"offset"` - Timeout int `json:"timeout"` - AllowedUpdates []string `json:"allowed_updates"` -} - -func (b *Bot) Updates() ([]*Update, error) { - params := UpdateParams{ - Offset: b.updateOffset, - Timeout: 30, - AllowedUpdates: b.updateTypes, +func (b *Bot) Updates() ([]tgapi.Update, error) { + offset := b.GetUpdateOffset() + params := tgapi.UpdateParams{ + Offset: Ptr(offset), + Timeout: Ptr(30), + AllowedUpdates: b.GetUpdateTypes(), } - req := NewRequest[[]*Update]("getUpdates", params) - res, err := req.Do(b) + updates, err := b.api.GetUpdates(params) if err != nil { - return []*Update{}, err + return nil, err } - updates := *res for _, u := range updates { - b.updateOffset = u.UpdateID + 1 - err = b.updateQueue.Enqueue(u) + b.SetUpdateOffset(u.UpdateID + 1) + err = b.GetQueue().Enqueue(&u) if err != nil { - return updates, err + return nil, err } - if b.debug && b.requestLogger != nil { + if b.RequestLogger != nil { j, err := json.Marshal(u) if err != nil { - b.logger.Error(err) + b.Logger().Error(err) } - b.requestLogger.Debugln(fmt.Sprintf("UPDATE %s", j)) + b.RequestLogger.Debugf("UPDATE %s\n", j) } } return updates, err } -func (b *Bot) GetMe() (*User, error) { - req := NewRequest[User, EmptyParams]("getMe", NoParams) - return req.Do(b) -} - -type SendMessageP struct { - BusinessConnectionID string `json:"business_connection_id,omitempty"` - ChatID int `json:"chat_id"` - MessageThreadID int `json:"message_thread_id,omitempty"` - ParseMode ParseMode `json:"parse_mode,omitempty"` - Text string `json:"text"` - 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"` - ReplyMarkup InlineKeyboardMarkup `json:"reply_markup,omitempty"` -} - -func (b *Bot) SendMessage(params *SendMessageP) (*Message, error) { - req := NewRequest[Message, SendMessageP]("sendMessage", *params) - return req.Do(b) -} - -type SendPhotoBaseP struct { - BusinessConnectionID string `json:"business_connection_id,omitempty"` - ChatID int `json:"chat_id"` - MessageThreadID int `json:"message_thread_id,omitempty"` - ParseMode ParseMode `json:"parse_mode,omitempty"` - Caption string `json:"caption,omitempty"` - CaptionEntities []*MessageEntity `json:"caption_entities,omitempty"` - ShowCaptionAboveMedia bool `json:"show_caption_above_media,omitempty"` - HasSpoiler bool `json:"has_spoiler,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"` - ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup,omitempty"` -} -type SendPhotoP struct { - BusinessConnectionID string `json:"business_connection_id,omitempty"` - ChatID int `json:"chat_id"` - MessageThreadID int `json:"message_thread_id,omitempty"` - ParseMode ParseMode `json:"parse_mode,omitempty"` - Caption string `json:"caption,omitempty"` - CaptionEntities []*MessageEntity `json:"caption_entities,omitempty"` - ShowCaptionAboveMedia bool `json:"show_caption_above_media,omitempty"` - HasSpoiler bool `json:"has_spoiler,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"` - ReplyMarkup InlineKeyboardMarkup `json:"reply_markup,omitempty"` - Photo string `json:"photo"` -} - -func (b *Bot) SendPhoto(params *SendPhotoP) (*Message, error) { - req := NewRequest[Message]("sendPhoto", params) - return req.Do(b) -} - -type EditMessageTextP struct { - BusinessConnectionID string `json:"business_connection_id,omitempty"` - ChatID int `json:"chat_id,omitempty"` - MessageID int `json:"message_id,omitempty"` - InlineMessageID string `json:"inline_message_id,omitempty"` - Text string `json:"text"` - ParseMode ParseMode `json:"parse_mode,omitempty"` - ReplyMarkup InlineKeyboardMarkup `json:"reply_markup,omitempty"` -} - -func (b *Bot) EditMessageText(params *EditMessageTextP) (*Message, error) { - req := NewRequest[Message]("editMessageText", params) - return req.Do(b) -} - -type EditMessageCaptionP struct { - BusinessConnectionID string `json:"business_connection_id,omitempty"` - ChatID int `json:"chat_id,omitempty"` - MessageID int `json:"message_id,omitempty"` - InlineMessageID string `json:"inline_message_id,omitempty"` - Caption string `json:"caption"` - ParseMode ParseMode `json:"parse_mode,omitempty"` - ReplyMarkup InlineKeyboardMarkup `json:"reply_markup,omitempty"` -} - -func (b *Bot) EditMessageCaption(params *EditMessageCaptionP) (*Message, error) { - req := NewRequest[Message]("editMessageCaption", params) - return req.Do(b) -} - -type DeleteMessageP struct { - ChatID int `json:"chat_id"` - MessageID int `json:"message_id"` -} - -func (b *Bot) DeleteMessage(params *DeleteMessageP) (bool, error) { - req := NewRequest[bool]("deleteMessage", params) - ok, err := req.Do(b) +func (b *Bot) GetFileByLink(link string) ([]byte, error) { + u := fmt.Sprintf("https://api.telegram.org/file/bot%s/%s", b.token, link) + res, err := http.Get(u) if err != nil { - return false, err + return nil, err } - return *ok, err -} - -type AnswerCallbackQueryP struct { - CallbackQueryID string `json:"callback_query_id"` - Text string `json:"text,omitempty"` - ShowAlert bool `json:"show_alert,omitempty"` - URL string `json:"url,omitempty"` - CacheTime int `json:"cache_time,omitempty"` -} - -func (b *Bot) AnswerCallbackQuery(params *AnswerCallbackQueryP) (bool, error) { - req := NewRequest[bool]("answerCallbackQuery", params) - ok, err := req.Do(b) - if err != nil { - return false, err - } - return *ok, err -} - -type GetFileP struct { - FileId string `json:"file_id"` -} - -func (b *Bot) GetFile(params *GetFileP) (*File, error) { - req := NewRequest[File]("getFile", params) - return req.Do(b) -} - -type SendChatActionP struct { - BusinessConnectionID string `json:"business_connection_id,omitempty"` - ChatID int `json:"chat_id"` - MessageThreadID int `json:"message_thread_id,omitempty"` - Action ChatActions `json:"action"` -} - -func (b *Bot) SendChatAction(params SendChatActionP) (bool, error) { - req := NewRequest[bool]("sendChatAction", params) - res, err := req.Do(b) - if err != nil { - return false, err - } - return *res, err -} - -type SetMessageReactionP struct { - ChatId int `json:"chat_id"` - MessageId int `json:"message_id"` - IsBig bool `json:"is_big,omitempty"` -} -type SetMessageReactionEmojiP struct { - SetMessageReactionP - Reaction []ReactionTypeEmoji `json:"reaction"` -} - -func (b *Bot) SetMessageReactionEmoji(params SetMessageReactionEmojiP) (bool, error) { - req := NewRequest[bool]("setMessageReaction", params) - res, err := req.Do(b) - if err != nil { - return false, err - } - return *res, err -} - -type SetMessageReactionCustomEmojiP struct { - SetMessageReactionP - Reaction []ReactionTypeCustomEmoji `json:"reaction"` -} - -func (b *Bot) SetMessageReactionCustom(params SetMessageReactionCustomEmojiP) (bool, error) { - req := NewRequest[bool]("setMessageReaction", params) - res, err := req.Do(b) - if err != nil { - return false, err - } - return *res, err -} - -type SetMessageReactionPaidP struct { - SetMessageReactionP -} - -func (b *Bot) SetMessageReactionPaid(params SetMessageReactionPaidP) (bool, error) { - req := NewRequest[bool]("setMessageReaction", params) - res, err := req.Do(b) - if err != nil { - return false, err - } - return *res, err + defer res.Body.Close() + return io.ReadAll(res.Body) } diff --git a/msg_context.go b/msg_context.go index e1d92bc..d19274f 100644 --- a/msg_context.go +++ b/msg_context.go @@ -1,12 +1,19 @@ package laniakea -import "fmt" +import ( + "fmt" + + "git.nix13.pw/scuroneko/laniakea/tgapi" + "git.nix13.pw/scuroneko/laniakea/utils" +) type MsgContext struct { - Bot *Bot - Msg *Message - Update *Update - From *User + Bot *Bot + Api *tgapi.Api + + Msg *tgapi.Message + Update tgapi.Update + From *tgapi.User CallbackMsgId int CallbackQueryId string FromID int @@ -24,18 +31,18 @@ type AnswerMessage struct { } func (ctx *MsgContext) edit(messageId int, text string, keyboard *InlineKeyboard) *AnswerMessage { - params := &EditMessageTextP{ + params := tgapi.EditMessageTextP{ MessageID: messageId, ChatID: ctx.Msg.Chat.ID, Text: text, - ParseMode: ParseMD, + ParseMode: tgapi.ParseMD, } if keyboard != nil { params.ReplyMarkup = keyboard.Get() } - msg, err := ctx.Bot.EditMessageText(params) + msg, _, err := ctx.Api.EditMessageText(params) if err != nil { - ctx.Bot.logger.Errorln(err) + ctx.Api.Logger.Errorln(err) return nil } return &AnswerMessage{ @@ -47,7 +54,7 @@ func (m *AnswerMessage) Edit(text string) *AnswerMessage { } func (ctx *MsgContext) EditCallback(text string, keyboard *InlineKeyboard) *AnswerMessage { if ctx.CallbackMsgId == 0 { - ctx.Bot.logger.Errorln("Can't edit non-callback update message") + ctx.Api.Logger.Errorln("Can't edit non-callback update message") return nil } @@ -58,18 +65,18 @@ func (ctx *MsgContext) EditCallbackf(format string, keyboard *InlineKeyboard, ar } func (ctx *MsgContext) editPhotoText(messageId int, text string, kb *InlineKeyboard) *AnswerMessage { - params := &EditMessageCaptionP{ + params := tgapi.EditMessageCaptionP{ ChatID: ctx.Msg.Chat.ID, MessageID: messageId, Caption: text, - ParseMode: ParseMD, + ParseMode: tgapi.ParseMD, } if kb != nil { params.ReplyMarkup = kb.Get() } - msg, err := ctx.Bot.EditMessageCaption(params) + msg, _, err := ctx.Api.EditMessageCaption(params) if err != nil { - ctx.Bot.logger.Errorln(err) + ctx.Api.Logger.Errorln(err) } return &AnswerMessage{ MessageID: msg.MessageID, ctx: ctx, Text: text, IsMedia: true, @@ -77,7 +84,7 @@ func (ctx *MsgContext) editPhotoText(messageId int, text string, kb *InlineKeybo } func (m *AnswerMessage) EditCaption(text string) *AnswerMessage { if m.MessageID == 0 { - m.ctx.Bot.logger.Errorln("Can't edit caption message, message id is zero") + m.ctx.Api.Logger.Errorln("Can't edit caption message, message id is zero") return m } return m.ctx.editPhotoText(m.MessageID, text, nil) @@ -87,18 +94,18 @@ func (m *AnswerMessage) EditCaptionKeyboard(text string, kb *InlineKeyboard) *An } func (ctx *MsgContext) answer(text string, keyboard *InlineKeyboard) *AnswerMessage { - params := &SendMessageP{ + params := tgapi.SendMessageP{ ChatID: ctx.Msg.Chat.ID, Text: text, - ParseMode: ParseMD, + ParseMode: tgapi.ParseMD, } if keyboard != nil { params.ReplyMarkup = keyboard.Get() } - msg, err := ctx.Bot.SendMessage(params) + msg, err := ctx.Api.SendMessage(params) if err != nil { - ctx.Bot.logger.Errorln(err) + ctx.Api.Logger.Errorln(err) return nil } return &AnswerMessage{ @@ -116,18 +123,18 @@ func (ctx *MsgContext) Keyboard(text string, kb *InlineKeyboard) *AnswerMessage } func (ctx *MsgContext) answerPhoto(photoId, text string, kb *InlineKeyboard) *AnswerMessage { - params := &SendPhotoP{ + params := tgapi.SendPhotoP{ ChatID: ctx.Msg.Chat.ID, Caption: text, - ParseMode: ParseMD, + ParseMode: tgapi.ParseMD, Photo: photoId, } if kb != nil { params.ReplyMarkup = kb.Get() } - msg, err := ctx.Bot.SendPhoto(params) + msg, err := ctx.Api.SendPhoto(params) if err != nil { - ctx.Bot.logger.Errorln(err) + ctx.Api.Logger.Errorln(err) return &AnswerMessage{ ctx: ctx, Text: text, IsMedia: true, } @@ -144,12 +151,12 @@ func (ctx *MsgContext) AnswerPhotoKeyboard(photoId, text string, kb *InlineKeybo } func (ctx *MsgContext) delete(messageId int) { - _, err := ctx.Bot.DeleteMessage(&DeleteMessageP{ + _, err := ctx.Api.DeleteMessage(tgapi.DeleteMessageP{ ChatID: ctx.Msg.Chat.ID, MessageID: messageId, }) if err != nil { - ctx.Bot.logger.Errorln(err) + ctx.Api.Logger.Errorln(err) } } func (m *AnswerMessage) Delete() { @@ -163,12 +170,12 @@ func (ctx *MsgContext) answerCallbackQuery(url, text string, showAlert bool) { if len(ctx.CallbackQueryId) == 0 { return } - _, err := ctx.Bot.AnswerCallbackQuery(&AnswerCallbackQueryP{ + _, err := ctx.Api.AnswerCallbackQuery(tgapi.AnswerCallbackQueryP{ CallbackQueryID: ctx.CallbackQueryId, Text: text, ShowAlert: showAlert, URL: url, }) if err != nil { - ctx.Bot.logger.Errorln(err) + ctx.Api.Logger.Errorln(err) } } func (ctx *MsgContext) AnswerCbQuery() { @@ -184,24 +191,24 @@ func (ctx *MsgContext) AnswerCbQueryUrl(u string) { ctx.answerCallbackQuery(u, "", false) } -func (ctx *MsgContext) SendAction(action ChatActions) { - _, err := ctx.Bot.SendChatAction(SendChatActionP{ +func (ctx *MsgContext) SendAction(action tgapi.ChatActionType) { + _, err := ctx.Api.SendChatAction(tgapi.SendChatActionP{ ChatID: ctx.Msg.Chat.ID, Action: action, }) if err != nil { - ctx.Bot.logger.Errorln(err) + ctx.Api.Logger.Errorln(err) } } func (ctx *MsgContext) error(err error) { - text := fmt.Sprintf(ctx.Bot.errorTemplate, EscapeMarkdown(err.Error())) + text := fmt.Sprintf(ctx.Bot.errorTemplate, utils.EscapeMarkdown(err.Error())) if ctx.CallbackQueryId != "" { ctx.answerCallbackQuery("", text, false) } else { ctx.answer(text, nil) } - ctx.Bot.logger.Errorln(err) + ctx.Bot.Logger().Errorln(err) } func (ctx *MsgContext) Error(err error) { ctx.error(err) diff --git a/plugins.go b/plugins.go index 86b5e96..d6768f9 100644 --- a/plugins.go +++ b/plugins.go @@ -9,72 +9,62 @@ import ( type CommandExecutor func(ctx *MsgContext, dbContext *DatabaseContext) type PluginBuilder struct { - name string - commands map[string]*CommandExecutor - payloads map[string]*CommandExecutor - updateListener *CommandExecutor - middlewares extypes.Slice[*PluginMiddleware] + name string + commands map[string]CommandExecutor + payloads map[string]CommandExecutor + middlewares extypes.Slice[*PluginMiddleware] } type Plugin struct { - Name string - Commands map[string]*CommandExecutor - Payloads map[string]*CommandExecutor - UpdateListener *CommandExecutor - Middlewares extypes.Slice[*PluginMiddleware] + Name string + Commands map[string]CommandExecutor + Payloads map[string]CommandExecutor + Middlewares extypes.Slice[*PluginMiddleware] } func NewPlugin(name string) *PluginBuilder { return &PluginBuilder{ name: name, - commands: make(map[string]*CommandExecutor), - payloads: make(map[string]*CommandExecutor), + commands: make(map[string]CommandExecutor), + payloads: make(map[string]CommandExecutor), } } func (p *PluginBuilder) Command(f CommandExecutor, cmd ...string) *PluginBuilder { for _, c := range cmd { - p.commands[c] = &f + p.commands[c] = f } return p } func (p *PluginBuilder) Payload(f CommandExecutor, payloads ...string) *PluginBuilder { for _, payload := range payloads { - p.payloads[payload] = &f + p.payloads[payload] = f } return p } -func (p *PluginBuilder) UpdateListener(listener CommandExecutor) *PluginBuilder { - p.updateListener = &listener - return p -} - -func (p *PluginBuilder) Middleware(middleware *PluginMiddleware) *PluginBuilder { +func (p *PluginBuilder) AddMiddleware(middleware *PluginMiddleware) *PluginBuilder { p.middlewares = p.middlewares.Push(middleware) return p } func (p *PluginBuilder) Build() Plugin { if len(p.commands) == 0 && len(p.payloads) == 0 { - log.Println("no command or payloads") + log.Printf("no command or payloads for %s", p.name) } return Plugin{ - Name: p.name, - Commands: p.commands, - Payloads: p.payloads, - UpdateListener: p.updateListener, - Middlewares: p.middlewares, + p.name, p.commands, + p.payloads, p.middlewares, } } func (p *Plugin) Execute(cmd string, ctx *MsgContext, dbContext *DatabaseContext) { - (*p.Commands[cmd])(ctx, dbContext) + (p.Commands[cmd])(ctx, dbContext) } func (p *Plugin) ExecutePayload(payload string, ctx *MsgContext, dbContext *DatabaseContext) { - (*p.Payloads[payload])(ctx, dbContext) + (p.Payloads[payload])(ctx, dbContext) } func (p *Plugin) executeMiddlewares(ctx *MsgContext, db *DatabaseContext) bool { @@ -102,14 +92,6 @@ type MiddlewareBuilder struct { func NewMiddleware(name string, executor CommandExecutor) *MiddlewareBuilder { return &MiddlewareBuilder{name: name, executor: executor, order: 0, async: false} } -func (m *MiddlewareBuilder) SetName(name string) *MiddlewareBuilder { - m.name = name - return m -} -func (m *MiddlewareBuilder) SetExecutor(executor CommandExecutor) *MiddlewareBuilder { - m.executor = executor - return m -} func (m *MiddlewareBuilder) SetOrder(order int) *MiddlewareBuilder { m.order = order return m diff --git a/runners.go b/runners.go index 64988ec..488a008 100644 --- a/runners.go +++ b/runners.go @@ -44,6 +44,7 @@ func (b *RunnerBuilder) Build() Runner { } func (b *Bot) ExecRunners() { + b.logger.Infoln("Executing runners...") for _, runner := range b.runners { if !runner.Onetime && !runner.Async { b.logger.Warnf("Runner %s not onetime, but sync\n", runner.Name) diff --git a/tgapi/api.go b/tgapi/api.go new file mode 100644 index 0000000..4cda187 --- /dev/null +++ b/tgapi/api.go @@ -0,0 +1,105 @@ +package tgapi + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io" + "net/http" + "strings" + "time" + + "git.nix13.pw/scuroneko/laniakea/utils" + "git.nix13.pw/scuroneko/slog" +) + +type Api struct { + token string + client *http.Client + Logger *slog.Logger +} + +func NewAPI(token string) *Api { + l := slog.CreateLogger().Level(utils.GetLoggerLevel()).Prefix("API") + l.AddWriter(l.CreateJsonStdoutWriter()) + client := &http.Client{Timeout: time.Second * 45} + return &Api{token, client, l} +} +func (api *Api) CloseApi() error { + return api.Logger.Close() +} + +type ApiResponse[R any] struct { + Ok bool `json:"ok"` + Description string `json:"description,omitempty"` + Result R `json:"result,omitempty"` + ErrorCode int `json:"error_code,omitempty"` +} + +type TelegramRequest[R, P any] struct { + method string + params P +} + +func NewRequest[R, P any](method string, params P) TelegramRequest[R, P] { + return TelegramRequest[R, P]{method: method, params: params} +} +func (r TelegramRequest[R, P]) DoWithContext(ctx context.Context, api *Api) (R, error) { + var zero R + data, err := json.Marshal(r.params) + if err != nil { + return zero, err + } + buf := bytes.NewBuffer(data) + + u := fmt.Sprintf("https://api.telegram.org/bot%s/%s", api.token, r.method) + if api.Logger != nil { + api.Logger.Debugln(strings.ReplaceAll(fmt.Sprintf( + "POST %s %s", u, buf.String(), + ), api.token, "")) + } + + req, err := http.NewRequestWithContext(ctx, "POST", u, buf) + if err != nil { + return zero, err + } + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Accept", "application/json") + req.Header.Set("User-Agent", fmt.Sprintf("Laniakea/%s", utils.VersionString)) + + res, err := api.client.Do(req) + if err != nil { + return zero, err + } + defer res.Body.Close() + if res.StatusCode != http.StatusOK { + return zero, fmt.Errorf("unexpected status code: %d", res.StatusCode) + } + + reader := io.LimitReader(res.Body, 10<<20) + data, err = io.ReadAll(reader) + if err != nil { + return zero, err + } + + if api.Logger != nil { + api.Logger.Debugln(fmt.Sprintf("RES %s %s", r.method, string(data))) + } + + var resp ApiResponse[R] + err = json.Unmarshal(data, &resp) + if err != nil { + return zero, err + } + + if !resp.Ok { + return zero, fmt.Errorf("[%d] %s", resp.ErrorCode, resp.Description) + } + return resp.Result, nil + +} +func (r TelegramRequest[R, P]) Do(api *Api) (R, error) { + ctx := context.Background() + return r.DoWithContext(ctx, api) +} diff --git a/tgapi/attachments_methods.go b/tgapi/attachments_methods.go new file mode 100644 index 0000000..2e38771 --- /dev/null +++ b/tgapi/attachments_methods.go @@ -0,0 +1,246 @@ +package tgapi + +type SendPhotoP struct { + BusinessConnectionID string `json:"business_connection_id,omitempty"` + ChatID int `json:"chat_id"` + MessageThreadID int `json:"message_thread_id,omitempty"` + DirectMessagesTopicID int `json:"direct_messages_topic_id,omitempty"` + + Photo string `json:"photo"` + Caption string `json:"caption,omitempty"` + ParseMode ParseMode `json:"parse_mode,omitempty"` + CaptionEntities []MessageEntity `json:"caption_entities,omitempty"` + + ShowCaptionAboveMedia bool `json:"show_caption_above_media,omitempty"` + HasSpoiler bool `json:"has_spoiler,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"` + + SuggestedPostParameters *SuggestedPostParameters `json:"suggested_post_parameters,omitempty"` + ReplyParameters *ReplyParameters `json:"reply_parameters,omitempty"` + ReplyMarkup *ReplyMarkup `json:"reply_markup,omitempty"` +} + +func (api *Api) SendPhoto(params SendPhotoP) (Message, error) { + req := NewRequest[Message]("sendPhoto", params) + return req.Do(api) +} + +type SendAudioP struct { + BusinessConnectionID string `json:"business_connection_id,omitempty"` + ChatID int `json:"chat_id"` + MessageThreadID int `json:"message_thread_id,omitempty"` + DirectMessagesTopicID int `json:"direct_messages_topic_id,omitempty"` + + Audio string `json:"audio"` + Caption string `json:"caption,omitempty"` + ParseMode ParseMode `json:"parse_mode,omitempty"` + CaptionEntities []MessageEntity `json:"caption_entities,omitempty"` + Duration int `json:"duration,omitempty"` + Performer string `json:"performer,omitempty"` + Title string `json:"title,omitempty"` + + DisableNotification bool `json:"disable_notification,omitempty"` + ProtectContent bool `json:"protect_content,omitempty"` + AllowPaidBroadcast bool `json:"allow_paid_broadcast,omitempty"` + MessageEffectID string `json:"message_effect_id,omitempty"` + + SuggestedPostParameters *SuggestedPostParameters `json:"suggested_post_parameters,omitempty"` + ReplyParameters *ReplyParameters `json:"reply_parameters,omitempty"` + ReplyMarkup *ReplyMarkup `json:"reply_markup,omitempty"` +} + +func (api *Api) SendAudio(params SendAudioP) (Message, error) { + req := NewRequest[Message]("sendAudio", params) + return req.Do(api) +} + +type SendDocumentP struct { + BusinessConnectionID string `json:"business_connection_id,omitempty"` + ChatID int `json:"chat_id"` + MessageThreadID int `json:"message_thread_id,omitempty"` + DirectMessagesTopicID int `json:"direct_messages_topic_id,omitempty"` + + Document string `json:"document"` + Caption string `json:"caption,omitempty"` + ParseMode ParseMode `json:"parse_mode,omitempty"` + CaptionEntities []MessageEntity `json:"caption_entities,omitempty"` + + DisableNotification bool `json:"disable_notification,omitempty"` + ProtectContent bool `json:"protect_content,omitempty"` + AllowPaidBroadcast bool `json:"allow_paid_broadcast,omitempty"` + MessageEffectID string `json:"message_effect_id,omitempty"` + + SuggestedPostParameters *SuggestedPostParameters `json:"suggested_post_parameters,omitempty"` + ReplyParameters *ReplyParameters `json:"reply_parameters,omitempty"` + ReplyMarkup *ReplyMarkup `json:"reply_markup,omitempty"` +} + +func (api *Api) SendDocument(params SendDocumentP) (Message, error) { + req := NewRequest[Message]("sendDocument", params) + return req.Do(api) +} + +type SendVideoP struct { + BusinessConnectionID string `json:"business_connection_id,omitempty"` + ChatID int `json:"chat_id"` + MessageThreadID int `json:"message_thread_id,omitempty"` + DirectMessagesTopicID int `json:"direct_messages_topic_id,omitempty"` + + Video string `json:"video"` + Duration int `json:"duration,omitempty"` + Width int `json:"width,omitempty"` + Height int `json:"height,omitempty"` + Cover int `json:"cover,omitempty"` + + StartTimestamp int `json:"start_timestamp,omitempty"` + Caption string `json:"caption,omitempty"` + ParseMode ParseMode `json:"parse_mode,omitempty"` + CaptionEntities []MessageEntity `json:"caption_entities,omitempty"` + + ShowCaptionAboveMedia bool `json:"show_caption_above_media,omitempty"` + HasSpoiler bool `json:"has_spoiler,omitempty"` + SupportsStreaming bool `json:"supports_streaming,omitempty"` + DisableNotification bool `json:"disable_notification,omitempty"` + ProtectContent bool `json:"protect_content,omitempty"` + AllowPaidBroadcast bool `json:"allow_paid_broadcast,omitempty"` + MessageEffectID string `json:"message_effect_id,omitempty"` + + SuggestedPostParameters *SuggestedPostParameters `json:"suggested_post_parameters,omitempty"` + ReplyParameters *ReplyParameters `json:"reply_parameters,omitempty"` + ReplyMarkup *ReplyMarkup `json:"reply_markup,omitempty"` +} + +func (api *Api) SendVideo(params SendVideoP) (Message, error) { + req := NewRequest[Message]("sendVideo", params) + return req.Do(api) +} + +type SendAnimationP struct { + BusinessConnectionID string `json:"business_connection_id,omitempty"` + ChatID int `json:"chat_id"` + MessageThreadID int `json:"message_thread_id,omitempty"` + DirectMessagesTopicID int `json:"direct_messages_topic_id,omitempty"` + + Animation string `json:"animation"` + Duration int `json:"duration,omitempty"` + Width int `json:"width,omitempty"` + Height int `json:"height,omitempty"` + + Caption string `json:"caption,omitempty"` + ParseMode ParseMode `json:"parse_mode,omitempty"` + CaptionEntities []MessageEntity `json:"caption_entities,omitempty"` + ShowCaptionAboveMedia bool `json:"show_caption_above_media,omitempty"` + HasSpoiler bool `json:"has_spoiler,omitempty"` + DisableNotification bool `json:"disable_notification,omitempty"` + ProtectContent bool `json:"protect_content,omitempty"` + AllowPaidBroadcast bool `json:"allow_paid_broadcast,omitempty"` + MessageEffectID string `json:"message_effect_id,omitempty"` + + SuggestedPostParameters *SuggestedPostParameters `json:"suggested_post_parameters,omitempty"` + ReplyParameters *ReplyParameters `json:"reply_parameters,omitempty"` + ReplyMarkup *ReplyMarkup `json:"reply_markup,omitempty"` +} + +func (api *Api) SendAnimation(params SendAnimationP) (Message, error) { + req := NewRequest[Message]("sendAnimation", params) + return req.Do(api) +} + +type SendVoiceP struct { + BusinessConnectionID string `json:"business_connection_id,omitempty"` + ChatID int `json:"chat_id"` + MessageThreadID int `json:"message_thread_id,omitempty"` + DirectMessagesTopicID int `json:"direct_messages_topic_id,omitempty"` + + Voice string `json:"voice"` + Caption string `json:"caption,omitempty"` + ParseMode ParseMode `json:"parse_mode,omitempty"` + CaptionEntities []MessageEntity `json:"caption_entities,omitempty"` + Duration int `json:"duration,omitempty"` + DisableNotification bool `json:"disable_notification,omitempty"` + ProtectContent bool `json:"protect_content,omitempty"` + AllowPaidBroadcast bool `json:"allow_paid_broadcast,omitempty"` + MessageEffectID string `json:"message_effect_id,omitempty"` + + SuggestedPostParameters *SuggestedPostParameters `json:"suggested_post_parameters,omitempty"` + ReplyParameters *ReplyParameters `json:"reply_parameters,omitempty"` + ReplyMarkup *ReplyMarkup `json:"reply_markup,omitempty"` +} + +func (api *Api) SendVoice(params *SendVoiceP) (Message, error) { + req := NewRequest[Message]("sendVoice", params) + return req.Do(api) +} + +type SendVideoNoteP struct { + BusinessConnectionID string `json:"business_connection_id,omitempty"` + ChatID int `json:"chat_id"` + MessageThreadID int `json:"message_thread_id,omitempty"` + DirectMessagesTopicID int `json:"direct_messages_topic_id,omitempty"` + + VideoNote string `json:"video_note"` + Duration int `json:"duration,omitempty"` + Length int `json:"length,omitempty"` + DisableNotification bool `json:"disable_notification,omitempty"` + ProtectContent bool `json:"protect_content,omitempty"` + AllowPaidBroadcast bool `json:"allow_paid_broadcast,omitempty"` + MessageEffectID string `json:"message_effect_id,omitempty"` + + SuggestedPostParameters *SuggestedPostParameters `json:"suggested_post_parameters,omitempty"` + ReplyParameters *ReplyParameters `json:"reply_parameters,omitempty"` + ReplyMarkup *ReplyMarkup `json:"reply_markup,omitempty"` +} + +func (api *Api) SendVideoNote(params SendVideoNoteP) (Message, error) { + req := NewRequest[Message]("sendVideoNote", params) + return req.Do(api) +} + +type SendPaidMediaP struct { + BusinessConnectionID string `json:"business_connection_id,omitempty"` + ChatID int `json:"chat_id"` + MessageThreadID int `json:"message_thread_id,omitempty"` + DirectMessagesTopicID int `json:"direct_messages_topic_id,omitempty"` + StarCount int `json:"star_count,omitempty"` + + Media []InputPaidMedia `json:"media"` + Payload string `json:"payload,omitempty"` + Caption string `json:"caption,omitempty"` + ParseMode ParseMode `json:"parse_mode,omitempty"` + CaptionEntities []MessageEntity `json:"caption_entities,omitempty"` + ShowCaptionAboveMedia bool `json:"show_caption_above_media,omitempty"` + DisableNotification bool `json:"disable_notification,omitempty"` + ProtectContent bool `json:"protect_content,omitempty"` + AllowPaidBroadcast bool `json:"allow_paid_broadcast,omitempty"` + + SuggestedPostParameters *SuggestedPostParameters `json:"suggested_post_parameters,omitempty"` + ReplyParameters *ReplyParameters `json:"reply_parameters,omitempty"` + ReplyMarkup *ReplyMarkup `json:"reply_markup,omitempty"` +} + +func (api *Api) SendPaidMedia(params SendPaidMediaP) (Message, error) { + req := NewRequest[Message]("sendPaidMedia", params) + return req.Do(api) +} + +type SendMediaGroupP struct { + BusinessConnectionID string `json:"business_connection_id,omitempty"` + ChatID int `json:"chat_id"` + MessageThreadID int `json:"message_thread_id,omitempty"` + DirectMessagesTopicID int `json:"direct_messages_topic_id,omitempty"` + + Media []InputMedia `json:"media"` + DisableNotification bool `json:"disable_notification,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"` +} + +func (api *Api) SendMediaGroup(params SendMediaGroupP) (Message, error) { + req := NewRequest[Message]("sendMediaGroup", params) + return req.Do(api) +} diff --git a/tgapi/attachments_types.go b/tgapi/attachments_types.go new file mode 100644 index 0000000..89dc0ef --- /dev/null +++ b/tgapi/attachments_types.go @@ -0,0 +1,59 @@ +package tgapi + +type InputMedia struct { + Type InputMediaType `json:"type"` + Media string `json:"media"` + + Caption *string `json:"caption,omitempty"` + ParseMode *ParseMode `json:"parse_mode,omitempty"` + CaptionEntities []MessageEntity `json:"caption_entities,omitempty"` + ShowCaptionAboveMedia *bool `json:"show_caption_above_media,omitempty"` + HasSpoiler *bool `json:"has_spoiler,omitempty"` + + Cover *string `json:"cover"` + StartTimestamp *int `json:"start_timestamp"` + Width *int `json:"width,omitempty"` + Height *int `json:"height,omitempty"` + Duration *int `json:"duration,omitempty"` + SupportsStreaming *bool `json:"supports_streaming,omitempty"` + + Performer *string `json:"performer,omitempty"` + Title *string `json:"title,omitempty"` +} + +type InputMediaType string + +const ( + InputMediaTypeAnimation InputMediaType = "animation" + InputMediaTypeDocument InputMediaType = "document" + InputMediaTypePhoto InputMediaType = "photo" + InputMediaTypeVideo InputMediaType = "video" + InputMediaTypeAudio InputMediaType = "audio" +) + +type InputPaidMediaType string + +const ( + InputPaidMediaTypeVideo InputPaidMediaType = "video" + InputPaidMediaTypePhoto InputPaidMediaType = "photo" +) + +type InputPaidMedia struct { + Type InputPaidMediaType `json:"type"` + Media string `json:"media"` + + Cover string `json:"cover"` + StartTimestamp int64 `json:"start_timestamp"` + Width int `json:"width"` + Height int `json:"height"` + Duration int `json:"duration"` + SupportsStreaming bool `json:"supports_streaming"` +} + +type PhotoSize struct { + FileID string `json:"file_id"` + FileUniqueID string `json:"file_unique_id"` + Width int `json:"width"` + Height int `json:"height"` + FileSize int `json:"file_size,omitempty"` +} diff --git a/tgapi/bot_methods.go b/tgapi/bot_methods.go new file mode 100644 index 0000000..05741ae --- /dev/null +++ b/tgapi/bot_methods.go @@ -0,0 +1,174 @@ +package tgapi + +type SetMyCommandsP struct { + Commands []BotCommand `json:"commands"` + Scope *BotCommandScope `json:"scope,omitempty"` + Language string `json:"language_code,omitempty"` +} + +func (api *Api) SetMyCommands(params SetMyCommandsP) (bool, error) { + req := NewRequest[bool]("setMyCommands", params) + return req.Do(api) +} + +type DeleteMyCommandsP struct { + Scope *BotCommandScope `json:"scope,omitempty"` + Language string `json:"language_code,omitempty"` +} + +func (api *Api) DeleteMyCommands(params DeleteMyCommandsP) (bool, error) { + req := NewRequest[bool]("deleteMyCommands", params) + return req.Do(api) +} + +type GetMyCommands struct { + Scope *BotCommandScope `json:"scope,omitempty"` + Language string `json:"language_code,omitempty"` +} + +func (api *Api) GetMyCommands(params GetMyCommands) ([]BotCommand, error) { + req := NewRequest[[]BotCommand]("getMyCommands", params) + return req.Do(api) +} + +type SetMyName struct { + Name string `json:"name"` + Language string `json:"language_code,omitempty"` +} + +func (api *Api) SetMyName(params SetMyName) (bool, error) { + req := NewRequest[bool]("setMyName", params) + return req.Do(api) +} + +type GetMyName struct { + Language string `json:"language_code,omitempty"` +} + +func (api *Api) GetMyName(params GetMyName) (BotName, error) { + req := NewRequest[BotName]("getMyName", params) + return req.Do(api) +} + +type SetMyDescription struct { + Description string `json:"description"` + Language string `json:"language_code,omitempty"` +} + +func (api *Api) SetMyDescription(params SetMyDescription) (bool, error) { + req := NewRequest[bool]("setMyDescription", params) + return req.Do(api) +} + +type GetMyDescription struct { + Language string `json:"language_code,omitempty"` +} + +func (api *Api) GetMyDescription(params GetMyDescription) (BotDescription, error) { + req := NewRequest[BotDescription]("getMyDescription", params) + return req.Do(api) +} + +type SetMyShortDescription struct { + ShortDescription string `json:"short_description,omitempty"` + Language string `json:"language_code,omitempty"` +} + +func (api *Api) SetMyShortDescription(params SetMyShortDescription) (bool, error) { + req := NewRequest[bool]("setMyShortDescription", params) + return req.Do(api) +} + +type GetMyShortDescription struct { + Language string `json:"language_code,omitempty"` +} + +func (api *Api) GetMyShortDescription(params GetMyShortDescription) (BotShortDescription, error) { + req := NewRequest[BotShortDescription]("getMyShortDescription", params) + return req.Do(api) +} + +type SetMyProfilePhotoP struct { + Photo InputProfilePhoto `json:"photo"` +} + +func (api *Api) SetMyProfilePhoto(params SetMyProfilePhotoP) (bool, error) { + req := NewRequest[bool]("setMyProfilePhoto", params) + return req.Do(api) +} +func (api *Api) RemoveMyProfilePhoto() (bool, error) { + req := NewRequest[bool]("removeMyProfilePhoto", NoParams) + return req.Do(api) +} + +type SetChatMenuButtonP struct { + ChatID int `json:"chat_id"` + MenuButton MenuButtonType `json:"menu_button"` +} + +func (api *Api) SetChatMenuButton(params SetChatMenuButtonP) (bool, error) { + req := NewRequest[bool]("setChatMenuButton", params) + return req.Do(api) +} + +type GetChatMenuButtonP struct { + ChatID int `json:"chat_id"` +} + +func (api *Api) GetChatMenuButton(params GetChatMenuButtonP) (BaseMenuButton, error) { + req := NewRequest[BaseMenuButton]("getChatMenuButton", params) + return req.Do(api) +} + +type SetMyDefaultAdministratorRightsP struct { + Rights *ChatAdministratorRights `json:"rights"` + ForChannels bool `json:"for_channels"` +} + +func (api *Api) SetMyDefaultAdministratorRights(params SetMyDefaultAdministratorRightsP) (bool, error) { + req := NewRequest[bool]("setMyDefaultAdministratorRights", params) + return req.Do(api) +} + +type GetMyDefaultAdministratorRightsP struct { + ForChannels bool `json:"for_channels"` +} + +func (api *Api) GetMyDefaultAdministratorRights(params GetMyDefaultAdministratorRightsP) (ChatAdministratorRights, error) { + req := NewRequest[ChatAdministratorRights]("getMyDefaultAdministratorRights", params) + return req.Do(api) +} + +func (api *Api) GetAvailableGifts() (Gifts, error) { + req := NewRequest[Gifts]("getAvailableGifts", NoParams) + return req.Do(api) +} + +type SendGiftP struct { + UserID int `json:"user_id,omitempty"` + ChatID int `json:"chat_id,omitempty"` + GiftID string `json:"gift_id"` + PayForUpgrade bool `json:"pay_for_upgrade"` + Text string `json:"text"` + TextParseMode ParseMode `json:"text_parse_mode,omitempty"` + TextEntities []MessageEntity `json:"text_entities,omitempty"` +} + +func (api *Api) SendGift(params SendGiftP) (bool, error) { + req := NewRequest[bool]("sendGift", params) + return req.Do(api) +} + +type GiftPremiumSubscriptionP struct { + UserID int `json:"user_id"` + MonthCount int `json:"month_count"` + StarCount int `json:"star_count"` + Text string `json:"text,omitempty"` + TextParseMode ParseMode `json:"text_parse_mode,omitempty"` + TextEntities []MessageEntity `json:"text_entities,omitempty"` +} + +func (api *Api) GiftPremiumSubscription(params GiftPremiumSubscriptionP) (bool, error) { + req := NewRequest[bool]("giftPremiumSubscription", params) + return req.Do(api) +} diff --git a/tgapi/bot_types.go b/tgapi/bot_types.go new file mode 100644 index 0000000..d73c30c --- /dev/null +++ b/tgapi/bot_types.go @@ -0,0 +1,64 @@ +package tgapi + +type BotCommand struct { + Command string `json:"command"` + Description string `json:"description"` +} +type BotCommandScopeType string + +const ( + BotCommandScopeDefaultType BotCommandScopeType = "default" + BotCommandScopePrivateType BotCommandScopeType = "all_private_chats" + BotCommandScopeGroupType BotCommandScopeType = "all_groups_chats" + BotCommandScopeAllChatAdministratorsType BotCommandScopeType = "all_chat_administrators" + BotCommandScopeChatType BotCommandScopeType = "chat" + BotCommandScopeChatAdministratorsType BotCommandScopeType = "chat_administrators" + BotCommandScopeChatMemberType BotCommandScopeType = "chat_member" +) + +type BotCommandScope struct { + Type BotCommandScopeType `json:"type"` + ChatID *int `json:"chat_id,omitempty"` + UserID *int `json:"user_id,omitempty"` +} + +type BotName struct { + Name string `json:"name"` +} +type BotDescription struct { + Description string `json:"description"` +} +type BotShortDescription struct { + ShortDescription string `json:"short_description"` +} + +const ( + InputProfilePhotoStaticType InputProfilePhotoType = "static" + InputProfilePhotoAnimatedType InputProfilePhotoType = "animated" +) + +type InputProfilePhotoType string +type InputProfilePhoto struct { + Type InputProfilePhotoType `json:"type"` + + // Static + Photo *string `json:"photo,omitempty"` + + // Animated + Animation *string `json:"animation,omitempty"` + MainFrameTimestamp *float64 `json:"main_frame_timestamp,omitempty"` +} + +const ( + MenuButtonCommandsType MenuButtonType = "commands" + MenuButtonWebAppType MenuButtonType = "web_app" + MenuButtonDefaultType MenuButtonType = "default" +) + +type MenuButtonType string +type BaseMenuButton struct { + Type MenuButtonType `json:"type"` + // WebApp + Text string `json:"text"` + WebApp WebAppInfo `json:"web_app"` +} diff --git a/tgapi/business_methods.go b/tgapi/business_methods.go new file mode 100644 index 0000000..ff6792c --- /dev/null +++ b/tgapi/business_methods.go @@ -0,0 +1,258 @@ +package tgapi + +type VerifyUserP struct { + UserID int `json:"user_id"` + CustomDescription string `json:"custom_description,omitempty"` +} + +func (api *Api) VerifyUser(params VerifyUserP) (bool, error) { + req := NewRequest[bool]("verifyUser", params) + return req.Do(api) +} + +type VerifyChatP struct { + ChatID int `json:"chat_id"` + CustomDescription string `json:"custom_description,omitempty"` +} + +func (api *Api) VerifyChat(params VerifyChatP) (bool, error) { + req := NewRequest[bool]("verifyChat", params) + return req.Do(api) +} + +type RemoveUserVerificationP struct { + UserID int `json:"user_id"` +} + +func (api *Api) RemoveUserVerification(params RemoveUserVerificationP) (bool, error) { + req := NewRequest[bool]("removeUserVerification", params) + return req.Do(api) +} + +type RemoveChatVerificationP struct { + ChatID int `json:"chat_id"` +} + +func (api *Api) RemoveChatVerification(params RemoveChatVerificationP) (bool, error) { + req := NewRequest[bool]("removeChatVerification", params) + return req.Do(api) +} + +type ReadBusinessMessageP struct { + BusinessConnectionID string `json:"business_connection_id"` + ChatID int `json:"chat_id"` + MessageID int `json:"message_id"` +} + +func (api *Api) ReadBusinessMessage(params ReadBusinessMessageP) (bool, error) { + req := NewRequest[bool]("readBusinessMessage", params) + return req.Do(api) +} + +type DeleteBusinessMessageP struct { + BusinessConnectionID string `json:"business_connection_id"` + MessageIDs []int `json:"message_ids"` +} + +func (api *Api) DeleteBusinessMessage(params DeleteBusinessMessageP) (bool, error) { + req := NewRequest[bool]("deleteBusinessMessage", params) + return req.Do(api) +} + +type SetBusinessAccountNameP struct { + BusinessConnectionID string `json:"business_connection_id"` + FirstName string `json:"first_name"` + LastName string `json:"last_name,omitempty"` +} + +func (api *Api) SetBusinessAccountName(params SetBusinessAccountNameP) (bool, error) { + req := NewRequest[bool]("setBusinessAccountName", params) + return req.Do(api) +} + +type SetBusinessAccountUsernameP struct { + BusinessConnectionID string `json:"business_connection_id"` + Username string `json:"username,omitempty"` +} + +func (api *Api) SetBusinessAccountUsername(params SetBusinessAccountUsernameP) (bool, error) { + req := NewRequest[bool]("setBusinessAccountUsername", params) + return req.Do(api) +} + +type SetBusinessAccountBioP struct { + BusinessConnectionID string `json:"business_connection_id"` + Bio string `json:"bio,omitempty"` +} + +func (api *Api) SetBusinessAccountBio(params SetBusinessAccountBioP) (bool, error) { + req := NewRequest[bool]("setBusinessAccountBio", params) + return req.Do(api) +} + +type SetBusinessAccountProfilePhoto struct { + BusinessConnectionID string `json:"business_connection_id"` + Photo InputProfilePhoto `json:"photo,omitempty"` + IsPublic bool `json:"is_public,omitempty"` +} + +func (api *Api) SetBusinessAccountProfilePhoto(params SetBusinessAccountProfilePhoto) (bool, error) { + req := NewRequest[bool]("setBusinessAccountProfilePhoto", params) + return req.Do(api) +} + +type RemoveBusinessAccountProfilePhotoP struct { + BusinessConnectionID string `json:"business_connection_id"` + IsPublic bool `json:"is_public,omitempty"` +} + +func (api *Api) RemoveBusinessAccountProfilePhoto(params RemoveBusinessAccountProfilePhotoP) (bool, error) { + req := NewRequest[bool]("removeBusinessAccountProfilePhoto", params) + return req.Do(api) +} + +type SetBusinessAccountGiftSettingsP struct { + BusinessConnectionID string `json:"business_connection_id"` + ShowGiftButton bool `json:"show_gift_button"` + AcceptedGiftTypes AcceptedGiftTypes `json:"accepted_gift_types"` +} + +func (api *Api) SetBusinessAccountGiftSettings(params SetBusinessAccountGiftSettingsP) (bool, error) { + req := NewRequest[bool]("setBusinessAccountGiftSettings", params) + return req.Do(api) +} + +type GetBusinessAccountStarBalanceP struct { + BusinessConnectionID string `json:"business_connection_id"` +} + +func (api *Api) GetBusinessAccountStarBalance(params GetBusinessAccountStarBalanceP) (StarAmount, error) { + req := NewRequest[StarAmount]("getBusinessAccountGiftSettings", params) + return req.Do(api) +} + +type TransferBusinessAccountStartP struct { + BusinessConnectionID string `json:"business_connection_id"` + StarCount int `json:"star_count"` +} + +func (api *Api) TransferBusinessAccountStart(params TransferBusinessAccountStartP) (bool, error) { + req := NewRequest[bool]("transferBusinessAccountStart", params) + return req.Do(api) +} + +type GetBusinessAccountGiftsP struct { + BusinessConnectionID string `json:"business_connection_id"` + ExcludeUnsaved bool `json:"exclude_unsaved,omitempty"` + ExcludeSaved bool `json:"exclude_saved,omitempty"` + ExcludeUnlimited bool `json:"exclude_unlimited,omitempty"` + ExcludeLimitedUpgradable bool `json:"exclude_limited_upgradable,omitempty"` + ExcludeLimitedNonUpgradable bool `json:"exclude_limited_non_upgradable,omitempty"` + ExcludeUnique bool `json:"exclude_unique,omitempty"` + ExcludeFromBlockchain bool `json:"exclude_from_blockchain,omitempty"` + SortByPrice bool `json:"sort_by_price,omitempty"` + Offset string `json:"offset,omitempty"` + Limit int `json:"limit,omitempty"` +} + +func (api *Api) GetBusinessAccountGifts(params GetBusinessAccountGiftsP) (OwnedGifts, error) { + req := NewRequest[OwnedGifts]("getBusinessAccountGifts", params) + return req.Do(api) +} + +type ConvertGiftToStarsP struct { + BusinessConnectionID string `json:"business_connection_id"` + OwnedGiftID string `json:"owned_gift_id"` +} + +func (api *Api) ConvertGiftToStars(params ConvertGiftToStarsP) (bool, error) { + req := NewRequest[bool]("convertGiftToStars", params) + return req.Do(api) +} + +type UpgradeGiftP struct { + BusinessConnectionID string `json:"business_connection_id"` + OwnedGiftID string `json:"owned_gift_id"` + KeepOriginalDetails bool `json:"keep_original_details,omitempty"` + StarCount int `json:"star_count,omitempty"` +} + +func (api *Api) UpgradeGift(params UpgradeGiftP) (bool, error) { + req := NewRequest[bool]("upgradeGift", params) + return req.Do(api) +} + +type TransferGiftP struct { + BusinessConnectionID string `json:"business_connection_id"` + OwnedGiftID string `json:"owned_gift_id"` + NewOwnerChatID int `json:"new_owner_chat_id"` + StarCount int `json:"star_count,omitempty"` +} + +func (api *Api) TransferGift(params TransferGiftP) (bool, error) { + req := NewRequest[bool]("transferGift", params) + return req.Do(api) +} + +type PostStoryP struct { + BusinessConnectionID string `json:"business_connection_id"` + Content InputStoryContent `json:"content"` + ActivePeriod int `json:"active_period"` + + Caption string `json:"caption,omitempty"` + ParseMode ParseMode `json:"parse_mode,omitempty"` + CaptionEntities []MessageEntity `json:"caption_entities,omitempty"` + Areas []StoryArea `json:"areas"` + + PostToChatPage bool `json:"post_to_chat_page,omitempty"` + ProtectContent bool `json:"protect_content,omitempty"` +} + +func (api *Api) PostStoryPhoto(params PostStoryP) (Story, error) { + req := NewRequest[Story]("postStory", params) + return req.Do(api) +} +func (api *Api) PostStoryVideo(params PostStoryP) (Story, error) { + req := NewRequest[Story]("postStory", params) + return req.Do(api) +} + +type RepostStoryP struct { + BusinessConnectionID string `json:"business_connection_id"` + FromChatID int `json:"from_chat_id"` + FromStoryID int `json:"from_story_id"` + ActivePeriod int `json:"active_period"` + PostToChatPage bool `json:"post_to_chat_page,omitempty"` + ProtectContent bool `json:"protect_content,omitempty"` +} + +func (api *Api) RepostStory(params RepostStoryP) (Story, error) { + req := NewRequest[Story]("repostStory", params) + return req.Do(api) +} + +type EditStoryP struct { + BusinessConnectionID string `json:"business_connection_id"` + StoryID int `json:"story_id"` + Content InputStoryContent `json:"content"` + + Caption string `json:"caption,omitempty"` + ParseMode ParseMode `json:"parse_mode,omitempty"` + CaptionEntities []MessageEntity `json:"caption_entities,omitempty"` + Areas []StoryArea `json:"areas,omitempty"` +} + +func (api *Api) EditStory(params EditStoryP) (Story, error) { + req := NewRequest[Story]("editStory", params) + return req.Do(api) +} + +type DeleteStoryP struct { + BusinessConnectionID string `json:"business_connection_id"` + StoryID int `json:"story_id"` +} + +func (api *Api) DeleteStory(params DeleteStoryP) (bool, error) { + req := NewRequest[bool]("deleteStory", params) + return req.Do(api) +} diff --git a/tgapi/business_types.go b/tgapi/business_types.go new file mode 100644 index 0000000..fb9770d --- /dev/null +++ b/tgapi/business_types.go @@ -0,0 +1,104 @@ +package tgapi + +type BusinessIntro struct { + Title string `json:"title,omitempty"` + Message string `json:"message,omitempty"` + Sticker *Sticker `json:"sticker,omitempty"` +} +type BusinessLocation struct { + Address string `json:"address"` + Location *Location `json:"location,omitempty"` +} +type BusinessOpeningHoursInterval struct { + OpeningMinute int `json:"opening_minute"` + ClosingMinute int `json:"closing_minute"` +} +type BusinessOpeningHours struct { + TimeZoneName string `json:"time_zone_name"` + OpeningHours []Birthdate `json:"opening_hours"` +} + +type BusinessBotRights struct { + CanReply *bool `json:"can_reply,omitempty"` + CanReadMessages *bool `json:"can_read_messages,omitempty"` + CanDeleteSentMessages *bool `json:"can_delete_sent_messages,omitempty"` + CanDeleteAllMessages *bool `json:"can_delete_all_messages,omitempty"` + CanEditName *bool `json:"can_edit_name,omitempty"` + CanEditBio *bool `json:"can_edit_bio,omitempty"` + CanEditProfilePhoto *bool `json:"can_edit_profile_photo,omitempty"` + CanEditUsername *bool `json:"can_edit_username,omitempty"` + CanChangeGiftSettings *bool `json:"can_change_gift_settings,omitempty"` + CanViewGiftsAndStars *bool `json:"can_view_gifts_and_stars,omitempty"` + CanConvertGiftsToStars *bool `json:"can_convert_gifts_to_stars,omitempty"` + CanTransferAndUpgradeGifts *bool `json:"can_transfer_and_upgrade_gifts,omitempty"` + CanTransferStars *bool `json:"can_transfer_stars,omitempty"` + CanManageStories *bool `json:"can_manage_stories,omitempty"` +} +type BusinessConnection struct { + ID string `json:"id"` + User User `json:"user"` + UserChatID int `json:"user_chat_id"` + Date int `json:"date"` + Rights *BusinessBotRights `json:"rights,omitempty"` + IsEnabled bool `json:"id_enabled"` +} + +const ( + InputStoryContentPhotoType InputStoryContentType = "photo" + InputStoryContentVideoType InputStoryContentType = "video" +) + +type InputStoryContentType string +type InputStoryContent struct { + Type InputStoryContentType `json:"type"` + // Photo + Photo *string `json:"photo,omitempty"` + + // Video + Video *string `json:"video,omitempty"` + Duration *float64 `json:"duration,omitempty"` + CoverFrameTimestamp *float64 `json:"cover_frame_timestamp,omitempty"` + IsAnimation *bool `json:"is_animation,omitempty"` +} + +type StoryAreaPosition struct { + XPercentage float64 `json:"x_percentage"` + YPercentage float64 `json:"y_percentage"` + WidthPercentage float64 `json:"width_percentage"` + HeightPercentage float64 `json:"height_percentage"` + RotationAngle float64 `json:"rotation_angle"` + CornerRadiusPercentage float64 `json:"corner_radius_percentage"` +} + +const ( + StoryAreaTypeLocationType StoryAreaTypeType = "location" + StoryAreaTypeReactionType StoryAreaTypeType = "suggested_reaction" + StoryAreaTypeLinkType StoryAreaTypeType = "link" + StoryAreaTypeWeatherType StoryAreaTypeType = "weather" + StoryAreaTypeUniqueGiftType StoryAreaTypeType = "unique_gift" +) + +type StoryAreaTypeType string +type StoryAreaType struct { + Type StoryAreaTypeType `json:"type"` + + Latitude *float64 `json:"latitude,omitempty"` + Longitude *float64 `json:"longitude,omitempty"` + Address *LocationAddress `json:"address,omitempty"` + + ReactionType *ReactionType `json:"reaction_type,omitempty"` + IsDark *bool `json:"is_dark,omitempty"` + IsFlipped *bool `json:"is_flipped,omitempty"` + + URL *string `json:"url,omitempty"` + + Temperature *float64 `json:"temperature,omitempty"` + Emoji *string `json:"emoji"` + BackgroundColor *int `json:"background_color"` + + Name *string `json:"name,omitempty"` +} +type StoryArea struct { + Position StoryAreaPosition `json:"position"` + Type StoryAreaType `json:"type"` +} diff --git a/tgapi/chat_methods.go b/tgapi/chat_methods.go new file mode 100644 index 0000000..f75bee5 --- /dev/null +++ b/tgapi/chat_methods.go @@ -0,0 +1,356 @@ +package tgapi + +type BanChatMemberP struct { + ChatID int `json:"chat_id"` + UserID int `json:"user_id"` + UntilDate int `json:"until_date,omitempty"` + RevokeMessages bool `json:"revoke_messages,omitempty"` +} + +func (api *Api) BanChatMember(params BanChatMemberP) (bool, error) { + req := NewRequest[bool]("banChatMember", params) + return req.Do(api) +} + +type UnbanChatMemberP struct { + ChatID int `json:"chat_id"` + UserID int `json:"user_id"` + OnlyIfBanned bool `json:"only_if_banned"` +} + +func (api *Api) UnbanChatMember(params UnbanChatMemberP) (bool, error) { + req := NewRequest[bool]("unbanChatMember", params) + return req.Do(api) +} + +type RestrictChatMemberP struct { + ChatID int `json:"chat_id"` + UserID int `json:"user_id"` + Permissions ChatPermissions `json:"permissions"` + UseIndependentChatPermissions bool `json:"use_independent_chat_permissions,omitempty"` + UntilDate int `json:"until_date,omitempty"` +} + +func (api *Api) RestrictChatMember(params RestrictChatMemberP) (bool, error) { + req := NewRequest[bool]("restrictChatMember", params) + return req.Do(api) +} + +type PromoteChatMember struct { + ChatID int `json:"chat_id"` + UserID int `json:"user_id"` + IsAnonymous bool `json:"is_anonymous,omitempty"` + + CanManageChat bool `json:"can_manage_chat,omitempty"` + CanDeleteMessages bool `json:"can_delete_messages,omitempty"` + CanManageVideoChats bool `json:"can_manage_video_chats,omitempty"` + CanRestrictMembers bool `json:"can_restrict_members,omitempty"` + CanPromoteMembers bool `json:"can_promote_members,omitempty"` + CanChangeInfo bool `json:"can_change_info,omitempty"` + CanInviteUsers bool `json:"can_invite_users,omitempty"` + CanPostStories bool `json:"can_post_stories,omitempty"` + CanEditStories bool `json:"can_edit_stories,omitempty"` + CanDeleteStories bool `json:"can_delete_stories,omitempty"` + CanPostMessages bool `json:"can_post_messages,omitempty"` + CanEditMessages bool `json:"can_edit_messages,omitempty"` + CanPinMessages bool `json:"can_pin_messages,omitempty"` + CanManageTopics bool `json:"can_manage_topics,omitempty"` + CanManageDirectMessages bool `json:"can_manage_direct_messages,omitempty"` +} + +func (api *Api) PromoteChatMember(params PromoteChatMember) (bool, error) { + req := NewRequest[bool]("promoteChatMember", params) + return req.Do(api) +} + +type SetChatAdministratorCustomTitleP struct { + ChatID int `json:"chat_id"` + UserID int `json:"user_id"` + CustomTitle string `json:"custom_title"` +} + +func (api *Api) SetChatAdministratorCustomTitle(params SetChatAdministratorCustomTitleP) (bool, error) { + req := NewRequest[bool]("setChatAdministratorCustomTitle", params) + return req.Do(api) +} + +type BanChatSenderChatP struct { + ChatID int `json:"chat_id"` + SenderChatID int `json:"sender_chat_id"` +} + +func (api *Api) BanChatSenderChat(params BanChatSenderChatP) (bool, error) { + req := NewRequest[bool]("banChatSenderChat", params) + return req.Do(api) +} + +type UnbanChatSenderChatP struct { + ChatID int `json:"chat_id"` + SenderChatID int `json:"sender_chat_id"` +} + +func (api *Api) UnbanChatSenderChat(params BanChatSenderChatP) (bool, error) { + req := NewRequest[bool]("unbanChatSenderChat", params) + return req.Do(api) +} + +type SetChatPermissionsP struct { + ChatID int `json:"chat_id"` + Permissions ChatPermissions `json:"permissions"` + UseIndependentChatPermissions bool `json:"use_independent_chat_permissions,omitempty"` +} + +func (api *Api) SetChatPermissions(params SetChatPermissionsP) (bool, error) { + req := NewRequest[bool]("setChatPermissions", params) + return req.Do(api) +} + +type ExportChatInviteLinkP struct { + ChatID int `json:"chat_id"` +} + +func (api *Api) ExportChatInviteLink(params ExportChatInviteLinkP) (string, error) { + req := NewRequest[string]("exportChatInviteLink", params) + return req.Do(api) +} + +type CreateChatInviteLinkP struct { + ChatID int `json:"chat_id"` + Name *string `json:"name,omitempty"` + ExpireDate int `json:"expire_date,omitempty"` + MemberLimit int `json:"member_limit,omitempty"` + CreatesJoinRequest int `json:"creates_join_request,omitempty"` +} + +func (api *Api) CreateChatInviteLink(params CreateChatInviteLinkP) (ChatInviteLink, error) { + req := NewRequest[ChatInviteLink]("createChatInviteLink", params) + return req.Do(api) +} + +type EditChatInviteLinkP struct { + ChatID int `json:"chat_id"` + InviteLink string `json:"invite_link"` + + Name string `json:"name,omitempty"` + ExpireDate int `json:"expire_date,omitempty"` + MemberLimit int `json:"member_limit,omitempty"` + CreatesJoinRequest int `json:"creates_join_request,omitempty"` +} + +func (api *Api) EditChatInviteLink(params EditChatInviteLinkP) (ChatInviteLink, error) { + req := NewRequest[ChatInviteLink]("editChatInviteLink", params) + return req.Do(api) +} + +type CreateChatSubscriptionInviteLinkP struct { + ChatID int `json:"chat_id"` + Name string `json:"name,omitempty"` + SubscriptionPeriod int `json:"subscription_period,omitempty"` + SubscriptionPrice int `json:"subscription_price,omitempty"` +} + +func (api *Api) CreateChatSubscriptionInviteLink(params CreateChatSubscriptionInviteLinkP) (ChatInviteLink, error) { + req := NewRequest[ChatInviteLink]("createChatSubscriptionInviteLink", params) + return req.Do(api) +} + +type EditChatSubscriptionInviteLinkP struct { + ChatID int `json:"chat_id"` + InviteLink string `json:"invite_link"` + Name string `json:"name,omitempty"` +} + +func (api *Api) EditChatSubscriptionInviteLink(params EditChatSubscriptionInviteLinkP) (ChatInviteLink, error) { + req := NewRequest[ChatInviteLink]("editChatSubscriptionInviteLink", params) + return req.Do(api) +} + +type RevokeChatInviteLinkP struct { + ChatID int `json:"chat_id"` + InviteLink string `json:"invite_link"` +} + +func (api *Api) RevokeChatInviteLink(params RevokeChatInviteLinkP) (ChatInviteLink, error) { + req := NewRequest[ChatInviteLink]("revokeChatInviteLink", params) + return req.Do(api) +} + +type ApproveChatJoinRequestP struct { + ChatID int `json:"chat_id"` + UserID int `json:"user_id"` +} + +func (api *Api) ApproveChatJoinRequest(params ApproveChatJoinRequestP) (bool, error) { + req := NewRequest[bool]("approveChatJoinRequest", params) + return req.Do(api) +} + +type DeclineChatJoinRequestP struct { + ChatID int `json:"chat_id"` + UserID int `json:"user_id"` +} + +func (api *Api) DeclineChatJoinRequest(params DeclineChatJoinRequestP) (bool, error) { + req := NewRequest[bool]("declineChatJoinRequest", params) + return req.Do(api) +} + +func (api *Api) SetChatPhoto() { + uploader := NewUploader(api) + defer uploader.Close() +} + +type DeleteChatPhotoP struct { + ChatID int `json:"chat_id"` +} + +func (api *Api) DeleteChatPhoto(params DeleteChatPhotoP) (bool, error) { + req := NewRequest[bool]("deleteChatPhoto", params) + return req.Do(api) +} + +type SetChatTitleP struct { + ChatID int `json:"chat_id"` + Title string `json:"title"` +} + +func (api *Api) SetChatTitle(params SetChatTitleP) (bool, error) { + req := NewRequest[bool]("setChatTitle", params) + return req.Do(api) +} + +type SetChatDescriptionP struct { + ChatID int `json:"chat_id"` + Description string `json:"description"` +} + +func (api *Api) SetChatDescription(params SetChatDescriptionP) (bool, error) { + req := NewRequest[bool]("setChatDescription", params) + return req.Do(api) +} + +type PinChatMessageP struct { + BusinessConnectionID *string `json:"business_connection_id,omitempty"` + ChatID int `json:"chat_id"` + MessageID int `json:"message_id"` + DisableNotification bool `json:"disable_notification,omitempty"` +} + +func (api *Api) PinChatMessage(params PinChatMessageP) (bool, error) { + req := NewRequest[bool]("pinChatMessage", params) + return req.Do(api) +} + +type UnpinChatMessageP struct { + BusinessConnectionID *string `json:"business_connection_id,omitempty"` + ChatID int `json:"chat_id"` + MessageID int `json:"message_id"` +} + +func (api *Api) UnpinChatMessage(params UnpinChatMessageP) (bool, error) { + req := NewRequest[bool]("unpinChatMessage", params) + return req.Do(api) +} + +type UnpinAllChatMessagesP struct { + ChatID int `json:"chat_id"` +} + +func (api *Api) UnpinAllChatMessages(params UnpinAllChatMessagesP) (bool, error) { + req := NewRequest[bool]("unpinAllChatMessages", params) + return req.Do(api) +} + +type LeaveChatP struct { + ChatID int `json:"chat_id"` +} + +func (api *Api) LeaveChat(params LeaveChatP) (bool, error) { + req := NewRequest[bool]("leaveChatP", params) + return req.Do(api) +} + +type GetChatP struct { + ChatID int `json:"chat_id"` +} + +func (api *Api) GetChatP(params GetChatP) (ChatFullInfo, error) { + req := NewRequest[ChatFullInfo]("getChatP", params) + return req.Do(api) +} + +type GetChatAdministratorsP struct { + ChatID int `json:"chat_id"` +} + +func (api *Api) GetChatAdministrators(params GetChatAdministratorsP) ([]ChatMember, error) { + req := NewRequest[[]ChatMember]("getChatAdministrators", params) + return req.Do(api) +} + +type GetChatMembersCountP struct { + ChatID int `json:"chat_id"` +} + +func (api *Api) GetChatMemberCount(params GetChatMembersCountP) (int, error) { + req := NewRequest[int]("getChatMemberCount", params) + return req.Do(api) +} + +type GetChatMemberP struct { + ChatID int `json:"chat_id"` + UserID int `json:"user_id"` +} + +func (api *Api) GetChatMember(params GetChatMemberP) (ChatMember, error) { + req := NewRequest[ChatMember]("getChatMember", params) + return req.Do(api) +} + +type SetChatStickerSetP struct { + ChatID int `json:"chat_id"` + StickerSetName string `json:"sticker_set_name"` +} + +func (api *Api) SetChatStickerSet(params SetChatStickerSetP) (bool, error) { + req := NewRequest[bool]("setChatStickerSet", params) + return req.Do(api) +} + +type DeleteChatStickerSetP struct { + ChatID int `json:"chat_id"` +} + +func (api *Api) DeleteChatStickerSet(params DeleteChatStickerSetP) (bool, error) { + req := NewRequest[bool]("deleteChatStickerSet", params) + return req.Do(api) +} + +type GetUserChatBoostsP struct { + ChatID int `json:"chat_id"` + UserID int `json:"user_id"` +} + +func (api *Api) GetUserChatBoosts(params GetUserChatBoostsP) (UserChatBoosts, error) { + req := NewRequest[UserChatBoosts]("getUserChatBoosts", params) + return req.Do(api) +} + +type GetChatGiftsP struct { + ChatID int `json:"chat_id"` + ExcludeUnsaved bool `json:"exclude_unsaved,omitempty"` + ExcludeSaved bool `json:"exclude_saved,omitempty"` + ExcludeUnlimited bool `json:"exclude_unlimited,omitempty"` + ExcludeLimitedUpgradable bool `json:"exclude_limited_upgradable,omitempty"` + ExcludeLimitedNonUpgradable bool `json:"exclude_limited_non_upgradable,omitempty"` + ExcludeUnique bool `json:"exclude_unique,omitempty"` + ExcludeFromBlockchain bool `json:"exclude_from_blockchain,omitempty"` + SortByPrice bool `json:"sort_by_price,omitempty"` + Offset string `json:"offset,omitempty"` + Limit int `json:"limit,omitempty"` +} + +func (api *Api) GetChatGifts(params GetChatGiftsP) (OwnedGifts, error) { + req := NewRequest[OwnedGifts]("getChatGifts", params) + return req.Do(api) +} diff --git a/tgapi/chat_types.go b/tgapi/chat_types.go new file mode 100644 index 0000000..7b1ea6e --- /dev/null +++ b/tgapi/chat_types.go @@ -0,0 +1,230 @@ +package tgapi + +type Chat struct { + ID int `json:"id"` + Type string `json:"type"` + Title *string `json:"title,omitempty"` + Username *string `json:"username,omitempty"` + FirstName *string `json:"first_name,omitempty"` + LastName *string `json:"last_name,omitempty"` + IsForum *bool `json:"is_forum,omitempty"` + IsDirectMessages *bool `json:"is_direct_messages,omitempty"` +} + +type ChatType string + +const ( + ChatTypePrivate ChatType = "private" + ChatTypeGroup ChatType = "group" + ChatTypeSupergroup ChatType = "supergroup" + ChatTypeChannel ChatType = "channel" +) + +type ChatFullInfo struct { + ID int `json:"id"` + Type ChatType `json:"type"` + Title string `json:"title"` + Username string `json:"username"` + FirstName string `json:"first_name"` + LastName string `json:"last_name"` + IsForum bool `json:"is_forum"` + IsDirectMessages bool `json:"is_direct_messages"` + AccentColorID int `json:"accent_color_id"` + MaxReactionCount int `json:"max_reaction_count"` + Photo *ChatPhoto `json:"photo,omitempty"` + ActiveUsernames []string `json:"active_usernames,omitempty"` + Birthdate *Birthdate `json:"birthdate,omitempty"` + + BusinessIntro *BusinessIntro `json:"business_intro,omitempty"` + BusinessLocation *BusinessLocation `json:"business_location,omitempty"` + BusinessOpeningHours *BusinessOpeningHours `json:"business_opening_hours,omitempty"` + + PersonalChat *Chat `json:"personal_chat,omitempty"` + ParentChat *Chat `json:"parent_chat,omitempty"` + + AvailableReaction []ReactionType `json:"available_reaction,omitempty"` + + BackgroundCustomEmojiID *string `json:"background_custom_emoji_id,omitempty"` + ProfileAccentColorID *int `json:"profile_accent_color_id,omitempty"` + ProfileBackgroundCustomEmojiID *string `json:"profile_background_custom_emoji_id,omitempty"` + EmojiStatusCustomEmojiID *string `json:"emoji_status_custom_emoji_id,omitempty"` + EmojiStatusExpirationDate *int `json:"emoji_status_expiration_date,omitempty"` + + Bio *string `json:"bio,omitempty"` + HasPrivateForwards *bool `json:"has_private_forwards,omitempty"` + HasRestrictedVoiceAndVideoMessages *bool `json:"has_restricted_voice_and_video_messages,omitempty"` + JoinToSendMessages *bool `json:"join_to_send_messages,omitempty"` + JoinByRequest *bool `json:"join_by_request,omitempty"` + + Description *string `json:"description,omitempty"` + InviteLink *string `json:"invite_link,omitempty"` + PinnedMessage *Message `json:"pinned_message,omitempty"` + Permissions *ChatPermissions `json:"permissions,omitempty"` + AcceptedGiftTypes *AcceptedGiftTypes `json:"accepted_gift_types,omitempty"` + + CanSendPaidMedia *bool `json:"can_send_paid_media,omitempty"` + SlowModeDelay *int `json:"slow_mode_delay,omitempty"` + UnrestrictedBoostCount *int `json:"unrestricted_boost_count,omitempty"` + MessageAutoDeleteTime *int `json:"message_auto_delete_time,omitempty"` + HasAggressiveAntiSpamEnabled *bool `json:"has_aggressive_anti_spam_enabled,omitempty"` + HasHiddenMembers *bool `json:"has_hidden_members,omitempty"` + HasProtectedContent *bool `json:"has_protected_content,omitempty"` + HasVisibleHistory *bool `json:"has_visible_history,omitempty"` + StickerSetName *string `json:"sticker_set_name,omitempty"` + CanSetStickerSet *bool `json:"can_set_sticker_set,omitempty"` + CustomEmojiStickerSetName *string `json:"custom_emoji_sticker_set_name,omitempty"` + LinkedChatID *int `json:"linked_chat_id,omitempty"` + + Location *ChatLocation `json:"location,omitempty"` + Rating *UserRating `json:"rating,omitempty"` + FirstProfileAudio *Audio `json:"first_profile_audio,omitempty"` + UniqueGiftColors *UniqueGiftColors `json:"unique_gift_colors,omitempty"` + PaidMessageStarCount *int `json:"paid_message_star_count,omitempty"` +} + +type ChatPhoto struct { + SmallFileID string `json:"small_file_id"` + SmallFileUniqueID string `json:"small_file_unique_id"` + BigFileID string `json:"big_file_id"` + BigFileUniqueID string `json:"big_file_unique_id"` +} + +type ChatPermissions struct { + CanSendMessages bool `json:"can_send_messages"` + CanSendAudios bool `json:"can_send_audios"` + CanSendDocuments bool `json:"can_send_documents"` + CanSendPhotos bool `json:"can_send_photos"` + CanSendVideoNotes bool `json:"can_send_video_notes"` + CanSendVoiceNotes bool `json:"can_send_voice_notes"` + CanSendPolls bool `json:"can_send_polls"` + CanSendOtherMessages bool `json:"can_send_other_messages"` + CanAddWebPagePreview bool `json:"can_add_web_page_preview"` + CanChangeInfo bool `json:"can_change_info"` + CanInviteUsers bool `json:"can_invite_users"` + CanPinMessages bool `json:"can_pin_messages"` + CanManageTopics bool `json:"can_manage_topics"` +} +type ChatLocation struct { + Location Location `json:"location"` + Address string `json:"address"` +} +type ChatInviteLink struct { + InviteLink string `json:"invite_link"` + Creator User `json:"creator"` + CreateJoinRequest bool `json:"create_join_request"` + IsPrimary bool `json:"is_primary"` + IsRevoked bool `json:"is_revoked"` + + Name *string `json:"name,omitempty"` + ExpireDate *int `json:"expire_date,omitempty"` + MemberLimit *int `json:"member_limit,omitempty"` + PendingJoinRequestCount *int `json:"pending_join_request_count,omitempty"` + SubscriptionPeriod *int `json:"subscription_period,omitempty"` + SubscriptionPrice *int `json:"subscription_price,omitempty"` +} + +type ChatMemberStatusType string + +const ( + ChatMemberStatusOwner ChatMemberStatusType = "owner" + ChatMemberStatusAdministrator ChatMemberStatusType = "administrator" + ChatMemberStatusMember ChatMemberStatusType = "member" + ChatMemberStatusRestricted ChatMemberStatusType = "restricted" + ChatMemberStatusLeft ChatMemberStatusType = "left" + ChatMemberStatusBanned ChatMemberStatusType = "kicked" +) + +type ChatMember struct { + Status ChatMemberStatusType `json:"status"` + User User `json:"user"` + + // Owner + IsAnonymous *bool `json:"is_anonymous"` + CustomTitle *string `json:"custom_title,omitempty"` + + // Administrator + CanBeEdited *bool `json:"can_be_edited,omitempty"` + CanManageChat *bool `json:"can_manage_chat,omitempty"` + CanDeleteMessages *bool `json:"can_delete_messages,omitempty"` + CanManageVideoChats *bool `json:"can_manage_video_chats,omitempty"` + CanRestrictMembers *bool `json:"can_restrict_members,omitempty"` + CanPromoteMembers *bool `json:"can_promote_members,omitempty"` + CanChangeInfo *bool `json:"can_change_info,omitempty"` + CanInviteUsers *bool `json:"can_invite_users,omitempty"` + CanPostStories *bool `json:"can_post_stories,omitempty"` + CanEditStories *bool `json:"can_edit_stories,omitempty"` + CanDeleteStories *bool `json:"can_delete_stories,omitempty"` + + CanPostMessages *bool `json:"can_post_messages,omitempty"` + CanEditMessages *bool `json:"can_edit_messages,omitempty"` + CanPinMessages *bool `json:"can_pin_messages,omitempty"` + CanManageTopics *bool `json:"can_manage_topics,omitempty"` + CanManageDirectMessages *bool `json:"can_manage_direct_messages,omitempty"` + + // Member + UntilDate *int `json:"until_date,omitempty"` + + // Restricted + IsMember *bool `json:"is_member,omitempty"` + CanSendMessages *bool `json:"can_send_messages,omitempty"` + CanSendAudios *bool `json:"can_send_audios,omitempty"` + CanSendDocuments *bool `json:"can_send_documents,omitempty"` + CanSendVideos *bool `json:"can_send_videos,omitempty"` + CanSendVideoNotes *bool `json:"can_send_video_notes,omitempty"` + CanSendVoiceNotes *bool `json:"can_send_voice_notes,omitempty"` + CanSendPolls *bool `json:"can_send_polls,omitempty"` + CanSendOtherMessages *bool `json:"can_send_other_messages,omitempty"` + CanAddWebPagePreview *bool `json:"can_add_web_page_preview,omitempty"` +} + +type ChatBoostSource struct { + Source string `json:"source"` + User User `json:"user"` + + // Giveaway + GiveawayMessageID *int `json:"giveaway_message_id,omitempty"` + PrizeStarCount *int `json:"prize_star_count,omitempty"` + IsUnclaimed *bool `json:"is_unclaimed,omitempty"` +} + +type ChatBoost struct { + BoostID int `json:"boost_id"` + AddDate int `json:"add_date"` + ExpirationDate int `json:"expiration_date"` + Source ChatBoostSource `json:"source"` +} +type UserChatBoosts struct { + Boosts []ChatBoost `json:"boosts"` +} + +type ChatAdministratorRights struct { + IsAnonymous bool `json:"is_anonymous"` + CanManageChat bool `json:"can_manage_chat"` + CanDeleteMessages bool `json:"can_delete_messages"` + CanManageVideoChats bool `json:"can_manage_video_chats"` + CanRestrictMembers bool `json:"can_restrict_members"` + CanPromoteMembers bool `json:"can_promote_members"` + CanChangeInfo bool `json:"can_change_info"` + CanInviteUsers bool `json:"can_invite_users"` + CanPostStories bool `json:"can_post_stories"` + CanEditStories bool `json:"can_edit_stories"` + CanDeleteStories bool `json:"can_delete_stories"` + + CanPostMessages *bool `json:"can_post_messages,omitempty"` + CanEditMessages *bool `json:"can_edit_messages,omitempty"` + CanPinMessages *bool `json:"can_pin_messages,omitempty"` + CanManageTopics *bool `json:"can_manage_topics,omitempty"` + CanManageDirectMessages *bool `json:"can_manage_direct_messages,omitempty"` +} + +type ChatBoostUpdated struct { + Chat Chat `json:"chat"` + Boost ChatBoost `json:"boost"` +} + +type ChatBoostRemoved struct { + Chat Chat `json:"chat"` + BoostID string `json:"boost_id"` + RemoveDate int `json:"remove_date"` + Source ChatBoostSource `json:"source"` +} diff --git a/tgapi/forum_methods.go b/tgapi/forum_methods.go new file mode 100644 index 0000000..df2da3a --- /dev/null +++ b/tgapi/forum_methods.go @@ -0,0 +1,86 @@ +package tgapi + +type BaseForumTopicP struct { + ChatID int `json:"chat_id"` + MessageThreadID int `json:"message_thread_id"` +} + +func (api *Api) GetForumTopicIconSet() ([]Sticker, error) { + req := NewRequest[[]Sticker]("getForumTopicIconSet", NoParams) + return req.Do(api) +} + +type CreateForumTopicP struct { + ChatID int `json:"chat_id"` + Name string `json:"name"` + IconColor ForumTopicIconColor `json:"icon_color"` + IconCustomEmojiID string `json:"icon_custom_emoji_id"` +} + +func (api *Api) CreateForumTopic(params CreateForumTopicP) (ForumTopic, error) { + req := NewRequest[ForumTopic]("createForumTopic", params) + return req.Do(api) +} + +type EditForumTopicP struct { + BaseForumTopicP + Name string `json:"name"` + IconCustomEmojiID string `json:"icon_custom_emoji_id"` +} + +func (api *Api) EditForumTopic(params EditForumTopicP) (bool, error) { + req := NewRequest[bool]("editForumTopic", params) + return req.Do(api) +} + +func (api *Api) CloseForumTopic(params BaseForumTopicP) (bool, error) { + req := NewRequest[bool]("closeForumTopic", params) + return req.Do(api) +} +func (api *Api) ReopenForumTopic(params BaseForumTopicP) (bool, error) { + req := NewRequest[bool]("reopenForumTopic", params) + return req.Do(api) +} +func (api *Api) DeleteForumTopic(params BaseForumTopicP) (bool, error) { + req := NewRequest[bool]("deleteForumTopic", params) + return req.Do(api) +} +func (api *Api) UnpinAllForumTopicMessages(params BaseForumTopicP) (bool, error) { + req := NewRequest[bool]("unpinAllForumTopicMessages", params) + return req.Do(api) +} + +type BaseGeneralForumTopicP struct { + ChatID int `json:"chat_id"` +} + +type EditGeneralForumTopicP struct { + ChatID int `json:"chat_id"` + Name string `json:"name"` +} + +func (api *Api) EditGeneralForumTopic(params EditGeneralForumTopicP) (bool, error) { + req := NewRequest[bool]("editGeneralForumTopic", params) + return req.Do(api) +} + +func (api *Api) CloseGeneralForumTopic(params BaseGeneralForumTopicP) (bool, error) { + req := NewRequest[bool]("closeGeneralForumTopic", params) + return req.Do(api) +} +func (api *Api) ReopenGeneralForumTopic(params BaseGeneralForumTopicP) (bool, error) { + req := NewRequest[bool]("reopenGeneralForumTopic", params) + return req.Do(api) +} +func (api *Api) HideGeneralForumTopic(params BaseGeneralForumTopicP) (bool, error) { + req := NewRequest[bool]("hideGeneralForumTopic", params) + return req.Do(api) +} +func (api *Api) UnhideGeneralForumTopic(params BaseGeneralForumTopicP) (bool, error) { + req := NewRequest[bool]("unhideGeneralForumTopic", params) + return req.Do(api) +} +func (api *Api) UnpinAllGeneralForumTopicMessages(params BaseGeneralForumTopicP) (bool, error) { + req := NewRequest[bool]("unpinAllGeneralForumTopicMessages", params) + return req.Do(api) +} diff --git a/tgapi/forum_types.go b/tgapi/forum_types.go new file mode 100644 index 0000000..45a4661 --- /dev/null +++ b/tgapi/forum_types.go @@ -0,0 +1,15 @@ +package tgapi + +type ForumTopic struct { + MessageThreadID int `json:"message_thread_id"` + Name string `json:"name"` + IconColor int `json:"icon_color"` + IconCustomEmojiID string `json:"icon_custom_emoji_id,omitempty"` + IsNameImplicit bool `json:"is_name_implicit,omitempty"` +} + +type ForumTopicIconColor int + +const ( + ForumTopicIconColorBlue ForumTopicIconColor = 7322096 +) diff --git a/tgapi/messages_methods.go b/tgapi/messages_methods.go new file mode 100644 index 0000000..09fc098 --- /dev/null +++ b/tgapi/messages_methods.go @@ -0,0 +1,528 @@ +package tgapi + +type SendMessageP struct { + BusinessConnectionID string `json:"business_connection_id,omitempty"` + ChatID int `json:"chat_id"` + MessageThreadID int `json:"message_thread_id,omitempty"` + DirectMessagesTopicID int `json:"direct_messages_topic_id,omitempty"` + + Text string `json:"text"` + ParseMode ParseMode `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"` + + SuggestedPostParameters *SuggestedPostParameters `json:"suggested_post_parameters,omitempty"` + ReplyParameters *ReplyParameters `json:"reply_parameters,omitempty"` + ReplyMarkup *ReplyMarkup `json:"reply_markup,omitempty"` +} + +func (api *Api) SendMessage(params SendMessageP) (Message, error) { + req := NewRequest[Message, SendMessageP]("sendMessage", params) + return req.Do(api) +} + +type ForwardMessageP struct { + ChatID int `json:"chat_id"` + MessageThreadID int `json:"message_thread_id,omitempty"` + DirectMessagesTopicID int `json:"direct_messages_topic_id,omitempty"` + + MessageID int `json:"message_id,omitempty"` + FromChatID int `json:"from_chat_id,omitempty"` + VideoStartTimestamp int `json:"video_start_timestamp,omitempty"` + DisableNotification bool `json:"disable_notification,omitempty"` + ProtectContent bool `json:"protect_content,omitempty"` + + MessageEffectID string `json:"message_effect_id,omitempty"` + SuggestedPostParameters *SuggestedPostParameters `json:"suggested_post_parameters,omitempty"` +} + +func (api *Api) ForwardMessage(params ForwardMessageP) (Message, error) { + req := NewRequest[Message]("forwardMessage", params) + return req.Do(api) +} + +type ForwardMessagesP struct { + ChatID int `json:"chat_id"` + MessageThreadID int `json:"message_thread_id,omitempty"` + DirectMessagesTopicID int `json:"direct_messages_topic_id,omitempty"` + + FromChatID int `json:"from_chat_id,omitempty"` + MessageIDs []int `json:"message_ids,omitempty"` + DisableNotification bool `json:"disable_notification,omitempty"` + ProtectContent bool `json:"protect_content,omitempty"` +} + +func (api *Api) ForwardMessages(params ForwardMessagesP) ([]int, error) { + req := NewRequest[[]int]("forwardMessages", params) + return req.Do(api) +} + +type CopyMessageP struct { + ChatID int `json:"chat_id"` + MessageThreadID int `json:"message_thread_id,omitempty"` + DirectMessagesTopicID int `json:"direct_messages_topic_id,omitempty"` + + FromChatID int `json:"from_chat_id"` + MessageID int `json:"message_id"` + VideoStartTimestamp int `json:"video_start_timestamp,omitempty"` + Caption string `json:"caption,omitempty"` + ParseMode ParseMode `json:"parse_mode,omitempty"` + + CaptionEntities []MessageEntity `json:"caption_entities,omitempty"` + ShowCaptionAboveMedia bool `json:"show_caption_above_media,omitempty"` + DisableNotification bool `json:"disable_notification,omitempty"` + ProtectContent bool `json:"protect_content,omitempty"` + AllowPaidBroadcast bool `json:"allow_paid_broadcast,omitempty"` + MessageEffectID string `json:"message_effect_id,omitempty"` + + SuggestedPostParameters *SuggestedPostParameters `json:"suggested_post_parameters,omitempty"` + ReplyParameters *ReplyParameters `json:"reply_parameters,omitempty"` + ReplyMarkup *ReplyMarkup `json:"reply_markup,omitempty"` +} + +func (api *Api) CopyMessage(params CopyMessageP) (int, error) { + req := NewRequest[int]("copyMessage", params) + return req.Do(api) +} + +type CopyMessagesP struct { + ChatID int `json:"chat_id"` + MessageThreadID int `json:"message_thread_id,omitempty"` + DirectMessagesTopicID int `json:"direct_messages_topic_id,omitempty"` + + FromChatID int `json:"from_chat_id,omitempty"` + MessageIDs []int `json:"message_ids,omitempty"` + DisableNotification bool `json:"disable_notification,omitempty"` + ProtectContent bool `json:"protect_content,omitempty"` + RemoveCaption bool `json:"remove_caption,omitempty"` +} + +func (api *Api) CopyMessages(params CopyMessagesP) ([]int, error) { + req := NewRequest[[]int]("copyMessages", params) + return req.Do(api) +} + +type SendLocationP struct { + BusinessConnectionID int `json:"business_connection_id,omitempty"` + ChatID int `json:"chat_id"` + MessageThreadID int `json:"message_thread_id,omitempty"` + DirectMessagesTopicID int `json:"direct_messages_topic_id,omitempty"` + + Latitude float64 `json:"latitude"` + Longitude float64 `json:"longitude"` + HorizontalAccuracy float64 `json:"horizontal_accuracy,omitempty"` + LivePeriod int `json:"live_period,omitempty"` + Heading int `json:"heading,omitempty"` + ProximityAlertRadius int `json:"proximity_alert_radius,omitempty"` + + DisableNotification bool `json:"disable_notification,omitempty"` + ProtectContent bool `json:"protect_content,omitempty"` + AllowPaidBroadcast bool `json:"allow_paid_broadcast,omitempty"` + MessageEffectID string `json:"message_effect_id,omitempty"` + + SuggestedPostParameters *SuggestedPostParameters `json:"suggested_post_parameters,omitempty"` + ReplyParameters *ReplyParameters `json:"reply_parameters,omitempty"` + ReplyMarkup *ReplyMarkup `json:"reply_markup,omitempty"` +} + +func (api *Api) SendLocation(params SendLocationP) (Message, error) { + req := NewRequest[Message]("sendLocation", params) + return req.Do(api) +} + +type SendVenueP struct { + BusinessConnectionID int `json:"business_connection_id,omitempty"` + ChatID int `json:"chat_id"` + MessageThreadID int `json:"message_thread_id,omitempty"` + DirectMessagesTopicID int `json:"direct_messages_topic_id,omitempty"` + + Latitude float64 `json:"latitude"` + Longitude float64 `json:"longitude"` + Title string `json:"title"` + Address string `json:"address"` + FoursquareID string `json:"foursquare_id,omitempty"` + FoursquareType string `json:"foursquare_type,omitempty"` + GooglePlaceID string `json:"google_place_id,omitempty"` + GooglePlaceType string `json:"google_place_type,omitempty"` + + DisableNotification bool `json:"disable_notification,omitempty"` + ProtectContent bool `json:"protect_content,omitempty"` + AllowPaidBroadcast bool `json:"allow_paid_broadcast,omitempty"` + MessageEffectID string `json:"message_effect_id,omitempty"` + + SuggestedPostParameters *SuggestedPostParameters `json:"suggested_post_parameters,omitempty"` + ReplyParameters *ReplyParameters `json:"reply_parameters,omitempty"` + ReplyMarkup *ReplyMarkup `json:"reply_markup,omitempty"` +} + +func (api *Api) SendVenue(params SendVenueP) (Message, error) { + req := NewRequest[Message]("sendVenue", params) + return req.Do(api) +} + +type SendContactP struct { + BusinessConnectionID int `json:"business_connection_id,omitempty"` + ChatID int `json:"chat_id"` + MessageThreadID int `json:"message_thread_id,omitempty"` + DirectMessagesTopicID int `json:"direct_messages_topic_id,omitempty"` + + PhoneNumber string `json:"phone_number"` + FirstName string `json:"first_name"` + LastName string `json:"last_name,omitempty"` + Vcard string `json:"vcard"` + + DisableNotification bool `json:"disable_notification,omitempty"` + ProtectContent bool `json:"protect_content,omitempty"` + AllowPaidBroadcast bool `json:"allow_paid_broadcast,omitempty"` + MessageEffectID string `json:"message_effect_id,omitempty"` + + SuggestedPostParameters *SuggestedPostParameters `json:"suggested_post_parameters,omitempty"` + ReplyParameters *ReplyParameters `json:"reply_parameters,omitempty"` + ReplyMarkup *ReplyMarkup `json:"reply_markup,omitempty"` +} + +func (api *Api) SendContact(params SendContactP) (Message, error) { + req := NewRequest[Message]("sendContact", params) + return req.Do(api) +} + +type SendPollP struct { + BusinessConnectionID int `json:"business_connection_id,omitempty"` + ChatID int `json:"chat_id"` + MessageThreadID int `json:"message_thread_id,omitempty"` + + Question string `json:"question"` + QuestionParseMode ParseMode `json:"question_mode,omitempty"` + QuestionEntities []MessageEntity `json:"question_entities,omitempty"` + Options []InputPollOption `json:"options"` + IsAnonymous bool `json:"is_anonymous,omitempty"` + Type PollType `json:"type"` + AllowsMultipleAnswers bool `json:"allows_multiple_answers,omitempty"` + CorrectOptionID int `json:"correct_option_id,omitempty"` + Explanation string `json:"explanation,omitempty"` + ExplanationParseMode ParseMode `json:"explanation_parse_mode,omitempty"` + ExplanationEntities []MessageEntity `json:"explanation_entities,omitempty"` + OpenPeriod int `json:"open_period,omitempty"` + CloseDate int `json:"close_date"` + IsClosed bool `json:"is_closed,omitempty"` + + DisableNotification bool `json:"disable_notification,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"` + ReplyMarkup *ReplyMarkup `json:"reply_markup,omitempty"` +} + +func (api *Api) SendPoll(params SendPollP) (Message, error) { + req := NewRequest[Message]("sendPoll", params) + return req.Do(api) +} + +type SendChecklistP struct { + BusinessConnectionID int `json:"business_connection_id"` + ChatID int `json:"chat_id"` + Checklist InputChecklist `json:"checklist"` + + DisableNotification bool `json:"disable_notification,omitempty"` + ProtectContent bool `json:"protect_content,omitempty"` + MessageEffectID string `json:"message_effect_id,omitempty"` + + ReplyParameters *ReplyParameters `json:"reply_parameters,omitempty"` + ReplyMarkup *ReplyMarkup `json:"reply_markup,omitempty"` +} + +func (api *Api) SendChecklist(params SendChecklistP) (Message, error) { + req := NewRequest[Message]("sendChecklist", params) + return req.Do(api) +} + +type SendDiceP struct { + BusinessConnectionID int `json:"business_connection_id,omitempty"` + ChatID int `json:"chat_id"` + MessageThreadID int `json:"message_thread_id,omitempty"` + DirectMessagesTopicID int `json:"direct_messages_topic_id,omitempty"` + + Emoji string `json:"emoji,omitempty"` + + DisableNotification bool `json:"disable_notification,omitempty"` + ProtectContent bool `json:"protect_content,omitempty"` + AllowPaidBroadcast bool `json:"allow_paid_broadcast,omitempty"` + MessageEffectID string `json:"message_effect_id,omitempty"` + + SuggestedPostParameters *SuggestedPostParameters `json:"suggested_post_parameters,omitempty"` + ReplyParameters *ReplyParameters `json:"reply_parameters,omitempty"` + ReplyMarkup *ReplyMarkup `json:"reply_markup,omitempty"` +} + +func (api *Api) SendDice(params SendDiceP) (Message, error) { + req := NewRequest[Message]("sendDice", params) + return req.Do(api) +} + +type SendMessageDraftP struct { + ChatID int `json:"chat_id"` + MessageThreadID int `json:"message_thread_id,omitempty"` + DraftID int `json:"draft_id"` + Text string `json:"text"` + ParseMode ParseMode `json:"parse_mode,omitempty"` + Entities []MessageEntity `json:"entities,omitempty"` +} + +func (api *Api) SendMessageDraft(params SendMessageDraftP) (bool, error) { + req := NewRequest[bool]("sendMessageDraft", params) + return req.Do(api) +} + +type SendChatActionP struct { + BusinessConnectionID string `json:"business_connection_id,omitempty"` + ChatID int `json:"chat_id"` + MessageThreadID int `json:"message_thread_id,omitempty"` + Action ChatActionType `json:"action"` +} + +func (api *Api) SendChatAction(params SendChatActionP) (bool, error) { + req := NewRequest[bool]("sendChatAction", params) + return req.Do(api) +} + +type SetMessageReactionP struct { + ChatId int `json:"chat_id"` + MessageId int `json:"message_id"` + Reaction []ReactionType `json:"reaction"` + IsBig bool `json:"is_big,omitempty"` +} + +func (api *Api) SetMessageReaction(params SetMessageReactionP) (bool, error) { + req := NewRequest[bool]("setMessageReaction", params) + return req.Do(api) +} + +// Message update methods + +type EditMessageTextP struct { + BusinessConnectionID string `json:"business_connection_id,omitempty"` + ChatID int `json:"chat_id,omitempty"` + MessageID int `json:"message_id,omitempty"` + InlineMessageID string `json:"inline_message_id,omitempty"` + Text string `json:"text"` + ParseMode ParseMode `json:"parse_mode,omitempty"` + ReplyMarkup *ReplyMarkup `json:"reply_markup,omitempty"` +} + +// EditMessageText If inline message, first return will be zero-valued, and second will boolean +// Otherwise, first return will be Message, and second false +func (api *Api) EditMessageText(params EditMessageTextP) (Message, bool, error) { + var zero Message + if params.InlineMessageID != "" { + req := NewRequest[bool]("editMessageText", params) + res, err := req.Do(api) + return zero, res, err + } + req := NewRequest[Message]("editMessageText", params) + res, err := req.Do(api) + return res, false, err +} + +type EditMessageCaptionP struct { + BusinessConnectionID string `json:"business_connection_id,omitempty"` + ChatID int `json:"chat_id,omitempty"` + MessageID int `json:"message_id,omitempty"` + InlineMessageID string `json:"inline_message_id,omitempty"` + Caption string `json:"caption"` + ParseMode ParseMode `json:"parse_mode,omitempty"` + ReplyMarkup *ReplyMarkup `json:"reply_markup,omitempty"` +} + +// EditMessageCaption If inline message, first return will be zero-valued, and second will boolean +// Otherwise, first return will be Message, and second false +func (api *Api) EditMessageCaption(params EditMessageCaptionP) (Message, bool, error) { + var zero Message + if params.InlineMessageID != "" { + req := NewRequest[bool]("editMessageCaption", params) + res, err := req.Do(api) + return zero, res, err + } + req := NewRequest[Message]("editMessageCaption", params) + res, err := req.Do(api) + return res, false, err +} + +type EditMessageMediaP struct { + BusinessConnectionID string `json:"business_connection_id,omitempty"` + ChatID int `json:"chat_id,omitempty"` + MessageID int `json:"message_id,omitempty"` + InlineMessageID string `json:"inline_message_id,omitempty"` + Message InputMedia `json:"message"` + ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup,omitempty"` +} + +// EditMessageMedia If inline message, first return will be zero-valued, and second will boolean +// Otherwise, first return will be Message, and second false +func (api *Api) EditMessageMedia(params EditMessageMediaP) (Message, bool, error) { + var zero Message + if params.InlineMessageID != "" { + req := NewRequest[bool]("editMessageMedia", params) + res, err := req.Do(api) + return zero, res, err + } + req := NewRequest[Message]("editMessageMedia", params) + res, err := req.Do(api) + return res, false, err +} + +type EditMessageLiveLocationP struct { + BusinessConnectionID string `json:"business_connection_id,omitempty"` + ChatID int `json:"chat_id,omitempty"` + MessageID int `json:"message_id,omitempty"` + InlineMessageID string `json:"inline_message_id,omitempty"` + + Latitude float64 `json:"latitude"` + Longitude float64 `json:"longitude"` + LivePeriod int `json:"live_period,omitempty"` + HorizontalAccuracy float64 `json:"horizontal_accuracy,omitempty"` + Heading int `json:"heading,omitempty"` + ProximityAlertRadius int `json:"proximity_alert_radius,omitempty"` + ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup,omitempty"` +} + +// EditMessageLiveLocation If inline message, first return will be zero-valued, and second will boolean +// Otherwise, first return will be Message, and second false +func (api *Api) EditMessageLiveLocation(params EditMessageLiveLocationP) (Message, bool, error) { + var zero Message + if params.InlineMessageID != "" { + req := NewRequest[bool]("editMessageLiveLocation", params) + res, err := req.Do(api) + return zero, res, err + } + req := NewRequest[Message]("editMessageLiveLocation", params) + res, err := req.Do(api) + return res, false, err +} + +type StopMessageLiveLocationP struct { + BusinessConnectionID string `json:"business_connection_id,omitempty"` + ChatID int `json:"chat_id,omitempty"` + MessageID int `json:"message_id,omitempty"` + InlineMessageID string `json:"inline_message_id,omitempty"` + ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup,omitempty"` +} + +// StopMessageLiveLocation If inline message, first return will be zero-valued, and second will boolean +// Otherwise, first return will be Message, and second false +func (api *Api) StopMessageLiveLocation(params StopMessageLiveLocationP) (Message, bool, error) { + var zero Message + if params.InlineMessageID != "" { + req := NewRequest[bool]("stopMessageLiveLocation", params) + res, err := req.Do(api) + return zero, res, err + } + req := NewRequest[Message]("stopMessageLiveLocation", params) + res, err := req.Do(api) + return res, false, err +} + +type EditMessageChecklistP struct { + BusinessConnectionID string `json:"business_connection_id"` + ChatID int `json:"chat_id"` + MessageID int `json:"message_id"` + Checklist InputChecklist `json:"checklist"` + ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup,omitempty"` +} + +func (api *Api) EditMessageChecklist(params EditMessageChecklistP) (Message, error) { + req := NewRequest[Message]("editMessageChecklist", params) + return req.Do(api) +} + +type EditMessageReplyMarkupP struct { + BusinessConnectionID string `json:"business_connection_id,omitempty"` + ChatID int `json:"chat_id,omitempty"` + MessageID int `json:"message_id,omitempty"` + InlineMessageID string `json:"inline_message_id,omitempty"` + ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup,omitempty"` +} + +func (api *Api) EditMessageReplyMarkup(params EditMessageReplyMarkupP) (Message, bool, error) { + var zero Message + if params.InlineMessageID != "" { + req := NewRequest[bool]("editMessageReplyMarkup", params) + res, err := req.Do(api) + return zero, res, err + } + req := NewRequest[Message]("editMessageReplyMarkup", params) + res, err := req.Do(api) + return res, false, err +} + +type StopPollP struct { + BusinessConnectionID string `json:"business_connection_id,omitempty"` + ChatID int `json:"chat_id"` + MessageID int `json:"message_id"` + InlineMessageID string `json:"inline_message_id,omitempty"` +} + +func (api *Api) StopPoll(params StopPollP) (Poll, error) { + req := NewRequest[Poll]("stopPoll", params) + return req.Do(api) +} + +type ApproveSuggestedPostP struct { + ChatID int `json:"chat_id"` + MessageID int `json:"message_id"` + SendDate int `json:"send_date,omitempty"` +} + +func (api *Api) ApproveSuggestedPost(params ApproveSuggestedPostP) (bool, error) { + req := NewRequest[bool]("approveSuggestedPost", params) + return req.Do(api) +} + +type DeclineSuggestedPostP struct { + ChatID int `json:"chat_id"` + MessageID int `json:"message_id"` + Comment string `json:"comment,omitempty"` +} + +func (api *Api) DeclineSuggestedPost(params DeclineSuggestedPostP) (bool, error) { + req := NewRequest[bool]("declineSuggestedPost", params) + return req.Do(api) +} + +type DeleteMessageP struct { + ChatID int `json:"chat_id"` + MessageID int `json:"message_id"` +} + +func (api *Api) DeleteMessage(params DeleteMessageP) (bool, error) { + req := NewRequest[bool]("deleteMessage", params) + return req.Do(api) +} + +type DeleteMessagesP struct { + ChatID int `json:"chat_id"` + MessageIDs []int `json:"message_ids"` +} + +func (api *Api) DeleteMessages(params DeleteMessagesP) (bool, error) { + req := NewRequest[bool]("deleteMessages", params) + return req.Do(api) +} + +type AnswerCallbackQueryP struct { + CallbackQueryID string `json:"callback_query_id"` + Text string `json:"text,omitempty"` + ShowAlert bool `json:"show_alert,omitempty"` + URL string `json:"url,omitempty"` + CacheTime int `json:"cache_time,omitempty"` +} + +func (api *Api) AnswerCallbackQuery(params AnswerCallbackQueryP) (bool, error) { + req := NewRequest[bool]("answerCallbackQuery", params) + return req.Do(api) +} diff --git a/tgapi/messages_types.go b/tgapi/messages_types.go new file mode 100644 index 0000000..3662b20 --- /dev/null +++ b/tgapi/messages_types.go @@ -0,0 +1,229 @@ +package tgapi + +import "git.nix13.pw/scuroneko/extypes" + +type MessageReplyMarkup struct { + InlineKeyboard [][]InlineKeyboardButton `json:"inline_keyboard"` +} + +type Message struct { + MessageID int `json:"message_id"` + MessageThreadID int `json:"message_thread_id,omitempty"` + BusinessConnectionId string `json:"business_connection_id,omitempty"` + From *User `json:"from,omitempty"` + + SenderChat *Chat `json:"sender_chat,omitempty"` + SenderBoostCount int `json:"sender_boost_count,omitempty"` + SenderBusinessBot *User `json:"sender_business_bot,omitempty"` + Chat *Chat `json:"chat,omitempty"` + + IsTopicMessage bool `json:"is_topic_message,omitempty"` + IsAutomaticForward bool `json:"is_automatic_forward,omitempty"` + IsFromOffline bool `json:"is_from_offline,omitempty"` + IsPaidPost bool `json:"is_paid_post,omitempty"` + MediaGroupId string `json:"media_group_id,omitempty"` + AuthorSignature string `json:"author_signature,omitempty"` + PaidStarCount int `json:"paid_star_count,omitempty"` + ReplyToMessage *Message `json:"reply_to_message,omitempty"` + + Text string `json:"text"` + + Photo extypes.Slice[*PhotoSize] `json:"photo,omitempty"` + Caption string `json:"caption,omitempty"` + CaptionEntities []MessageEntity `json:"caption_entities,omitempty"` + + Date int `json:"date"` + EditDate int `json:"edit_date"` + + ReplyMarkup *MessageReplyMarkup `json:"reply_markup,omitempty"` + + Entities []MessageEntity `json:"entities,omitempty"` + LinkPreviewOptions *LinkPreviewOptions `json:"link_preview_options,omitempty"` + SuggestedPostInfo *SuggestedPostInfo `json:"suggested_post_info,omitempty"` + + EffectID string `json:"effect_id,omitempty"` +} + +type InaccessibleMessage struct { + Chat Chat `json:"chat"` + MessageID int `json:"message_id"` + Date int `json:"date"` +} + +type MaybeInaccessibleMessage interface{ Message | InaccessibleMessage } + +type MessageEntityType string + +const ( + MessageEntityMention MessageEntityType = "mention" + MessageEntityHashtag MessageEntityType = "hashtag" + MessageEntityCashtag MessageEntityType = "cashtag" + MessageEntityBotCommand MessageEntityType = "bot_command" + MessageEntityUrl MessageEntityType = "url" + MessageEntityEmail MessageEntityType = "email" + MessageEntityPhoneNumber MessageEntityType = "phone_number" + MessageEntityBold MessageEntityType = "bold" + MessageEntityItalic MessageEntityType = "italic" + MessageEntityUnderline MessageEntityType = "underline" + MessageEntityStrike MessageEntityType = "strikethrough" + MessageEntitySpoiler MessageEntityType = "spoiler" + MessageEntityBlockquote MessageEntityType = "blockquote" + MessageEntityExpandableBlockquote MessageEntityType = "expandable_blockquote" + MessageEntityCode MessageEntityType = "code" + MessageEntityPre MessageEntityType = "pre" + MessageEntityTextLink MessageEntityType = "text_link" + MessageEntityTextMention MessageEntityType = "text_mention" + MessageEntityCustomEmoji MessageEntityType = "custom_emoji" +) + +type MessageEntity struct { + Type MessageEntityType `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_position,omitempty"` + ChecklistTaskID int `json:"checklist_task_id,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 ReplyMarkup struct { + InlineKeyboard [][]InlineKeyboardButton `json:"inline_keyboard,omitempty"` + + Keyboard [][]int `json:"keyboard,omitempty"` + IsPersistent bool `json:"is_persistent,omitempty"` + ResizeKeyboard bool `json:"resize_keyboard,omitempty"` + OneTimeKeyboard bool `json:"one_time_keyboard,omitempty"` + InputFieldPlaceholder string `json:"input_field_placeholder,omitempty"` + Selective bool `json:"selective,omitempty"` + + RemoveKeyboard bool `json:"remove_keyboard,omitempty"` + + ForceReply bool `json:"force_reply,omitempty"` +} +type InlineKeyboardMarkup struct { + InlineKeyboard [][]InlineKeyboardButton `json:"inline_keyboard,omitempty"` +} + +type KeyboardButtonStyle string +type InlineKeyboardButton struct { + Text string `json:"text"` + URL string `json:"url,omitempty"` + CallbackData string `json:"callback_data,omitempty"` + Style KeyboardButtonStyle `json:"style,omitempty"` + IconCustomEmojiID string `json:"icon_custom_emoji_id,omitempty"` +} + +type ReplyKeyboardMarkup struct { + Keyboard [][]int `json:"keyboard"` +} + +type CallbackQuery struct { + ID string `json:"id"` + From User `json:"from"` + Message Message `json:"message"` + + Data string `json:"data"` +} + +type InputPollOption struct { + Text string `json:"text"` + TextParseMode ParseMode `json:"text_parse_mode,omitempty"` + TextEntities []*MessageEntity `json:"text_entities,omitempty"` +} +type PollType string + +const ( + PollTypeRegular PollType = "regular" + PollTypeQuiz PollType = "quiz" +) + +type InputChecklistTask struct { + ID int `json:"id"` + Text string `json:"text"` + ParseMode ParseMode `json:"parse_mode,omitempty"` + TextEntities []*MessageEntity `json:"text_entities,omitempty"` +} +type InputChecklist struct { + Title string `json:"title"` + ParseMode ParseMode `json:"parse_mode,omitempty"` + TitleEntities []*MessageEntity `json:"title_entities,omitempty"` + Tasks []InputChecklistTask `json:"tasks"` + OtherCanAddTasks bool `json:"other_can_add_tasks,omitempty"` + OtherCanMarkTasksAsDone bool `json:"other_can_mark_tasks_as_done,omitempty"` +} + +type ChatActionType string + +const ( + ChatActionTyping ChatActionType = "typing" + ChatActionUploadPhoto ChatActionType = "upload_photo" + ChatActionUploadVideo ChatActionType = "upload_video" + ChatActionUploadVoice ChatActionType = "upload_voice" + ChatActionUploadDocument ChatActionType = "upload_document" + ChatActionChooseSticker ChatActionType = "choose_sticker" + ChatActionFindLocation ChatActionType = "find_location" + ChatActionUploadVideoNone ChatActionType = "upload_video_none" +) + +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"` + // ReactionTypeEmoji + Emoji *string `json:"emoji,omitempty"` + // ReactionTypeCustomEmoji + CustomEmojiID *string `json:"custom_emoji_id,omitempty"` +} +type ReactionCount struct { + Type ReactionType `json:"type"` + TotalCount int `json:"total_count"` +} + +type SuggestedPostPrice struct { + Currency string `json:"currency"` + Amount int `json:"amount"` +} +type SuggestedPostInfo struct { + State string `json:"state"` //State of the suggested post. Currently, it can be one of “pending”, “approved”, “declined”. + Price SuggestedPostPrice `json:"price"` + SendDate int `json:"send_date"` +} +type SuggestedPostParameters struct { + Price SuggestedPostPrice `json:"price"` + SendDate int `json:"send_date"` +} diff --git a/tgapi/methods.go b/tgapi/methods.go new file mode 100644 index 0000000..9c20e88 --- /dev/null +++ b/tgapi/methods.go @@ -0,0 +1,46 @@ +package tgapi + +type ParseMode string + +const ( + ParseMDV2 ParseMode = "MarkdownV2" + ParseHTML ParseMode = "HTML" + ParseMD ParseMode = "Markdown" +) + +type EmptyParams struct{} + +var NoParams = EmptyParams{} + +type UpdateParams struct { + Offset *int `json:"offset,omitempty"` + Limit *int `json:"limit,omitempty"` + Timeout *int `json:"timeout,omitempty"` + AllowedUpdates []UpdateType `json:"allowed_updates"` +} + +func (api *Api) GetMe() (User, error) { + req := NewRequest[User, EmptyParams]("getMe", NoParams) + return req.Do(api) +} +func (api *Api) LogOut() (bool, error) { + req := NewRequest[bool, EmptyParams]("logOut", NoParams) + return req.Do(api) +} +func (api *Api) Close() (bool, error) { + req := NewRequest[bool, EmptyParams]("close", NoParams) + return req.Do(api) +} +func (api *Api) GetUpdates(params UpdateParams) ([]Update, error) { + req := NewRequest[[]Update]("getUpdates", params) + return req.Do(api) +} + +type GetFileP struct { + FileId string `json:"file_id"` +} + +func (api *Api) GetFile(params GetFileP) (File, error) { + req := NewRequest[File]("getFile", params) + return req.Do(api) +} diff --git a/tgapi/stickers_methods.go b/tgapi/stickers_methods.go new file mode 100644 index 0000000..99feeb8 --- /dev/null +++ b/tgapi/stickers_methods.go @@ -0,0 +1,166 @@ +package tgapi + +type SendStickerP struct { + BusinessConnectionID string `json:"business_connection_id,omitempty"` + ChatID int `json:"chat_id"` + MessageThreadID int `json:"message_thread_id,omitempty"` + DirectMessagesTopicID int `json:"direct_messages_topic_id,omitempty"` + + Sticker string `json:"sticker"` + Emoji string `json:"emoji,omitempty"` + DisableNotification bool `json:"disable_notification,omitempty"` + ProtectContent bool `json:"protect_content,omitempty"` + AllowPaidBroadcast bool `json:"allow_paid_broadcast,omitempty"` + MessageEffectID string `json:"message_effect_id,omitempty"` +} + +func (api *Api) SendSticker(params SendStickerP) (Message, error) { + req := NewRequest[Message]("sendSticker", params) + return req.Do(api) +} + +type GetStickerSetP struct { + Name string `json:"name"` +} + +func (api *Api) GetStickerSet(params GetStickerSetP) (StickerSet, error) { + req := NewRequest[StickerSet]("getStickerSet", params) + return req.Do(api) +} + +type GetCustomEmojiStickersP struct { + CustomEmojiIDs []string `json:"custom_emoji_ids"` +} + +func (api *Api) GetCustomEmojiStickers(params GetCustomEmojiStickersP) ([]Sticker, error) { + req := NewRequest[[]Sticker]("getCustomEmojiStickers", params) + return req.Do(api) +} + +type CreateNewStickerSetP struct { + UserID int `json:"user_id"` + Name string `json:"name"` + Title string `json:"title"` + + Stickers []InputSticker `json:"stickers"` + StickerType StickerType `json:"sticker_type,omitempty"` + NeedsRepainting bool `json:"needs_repainting,omitempty"` +} + +func (api *Api) CreateNewStickerSet(params CreateNewStickerSetP) (bool, error) { + req := NewRequest[bool]("createNewStickerSet", params) + return req.Do(api) +} + +type AddStickerToSetP struct { + UserID int `json:"user_id"` + Name string `json:"name"` + Sticker InputSticker `json:"sticker"` +} + +func (api *Api) AddStickerToSet(params AddStickerToSetP) (bool, error) { + req := NewRequest[bool]("addStickerToSet", params) + return req.Do(api) +} + +type SetStickerPositionInSetP struct { + Sticker string `json:"sticker"` + Position int `json:"position"` +} + +func (api *Api) SetStickerPosition(params SetStickerPositionInSetP) (bool, error) { + req := NewRequest[bool]("setStickerPosition", params) + return req.Do(api) +} + +type DeleteStickerFromSetP struct { + Sticker string `json:"sticker"` +} + +func (api *Api) DeleteStickerFromSet(params DeleteStickerFromSetP) (bool, error) { + req := NewRequest[bool]("deleteStickerFromSet", params) + return req.Do(api) +} + +type ReplaceStickerInSetP struct { + UserID int `json:"user_id"` + Name string `json:"name"` + OldSticker string `json:"old_sticker"` + Sticker InputSticker `json:"sticker"` +} + +func (api *Api) ReplaceStickerInSet(params ReplaceStickerInSetP) (bool, error) { + req := NewRequest[bool]("replaceStickerInSet", params) + return req.Do(api) +} + +type SetStickerEmojiListP struct { + Sticker string `json:"sticker"` + EmojiList []string `json:"emoji_list"` +} + +func (api *Api) SetStickerEmojiList(params SetStickerEmojiListP) (bool, error) { + req := NewRequest[bool]("setStickerEmojiList", params) + return req.Do(api) +} + +type SetStickerKeywordsP struct { + Sticker string `json:"sticker"` + Keywords []string `json:"keywords"` +} + +func (api *Api) SetStickerKeywords(params SetStickerKeywordsP) (bool, error) { + req := NewRequest[bool]("setStickerKeywords", params) + return req.Do(api) +} + +type SetStickerMaskPositionP struct { + Sticker string `json:"sticker"` + MaskPosition *MaskPosition `json:"mask_position,omitempty"` +} + +func (api *Api) SetStickerMaskPosition(params SetStickerMaskPositionP) (bool, error) { + req := NewRequest[bool]("setStickerMaskPosition", params) + return req.Do(api) +} + +type SetStickerSetTitleP struct { + Name string `json:"name"` + Title string `json:"title"` +} + +func (api *Api) SetStickerSetTitle(params SetStickerSetTitleP) (bool, error) { + req := NewRequest[bool]("setStickerSetTitle", params) + return req.Do(api) +} + +type SetStickerSetThumbnailP struct { + Name string `json:"name"` + UserID int `json:"user_id"` + Thumbnail string `json:"thumbnail"` + Format InputStickerFormat `json:"format"` +} + +func (api *Api) SetStickerSetThumbnail(params SetStickerSetThumbnailP) (bool, error) { + req := NewRequest[bool]("setStickerSetThumbnail", params) + return req.Do(api) +} + +type SetCustomEmojiStickerSetThumbnailP struct { + Name string `json:"name"` + CustomEmojiID string `json:"custom_emoji_id,omitempty"` +} + +func (api *Api) SetCustomEmojiStickerSetThumbnail(params SetStickerSetThumbnailP) (bool, error) { + req := NewRequest[bool]("setCustomEmojiStickerSetThumbnail", params) + return req.Do(api) +} + +type DeleteStickerSetP struct { + Name string `json:"name"` +} + +func (api *Api) DeleteStickerSet(params DeleteStickerSetP) (bool, error) { + req := NewRequest[bool]("deleteStickerSet", params) + return req.Do(api) +} diff --git a/tgapi/stickers_types.go b/tgapi/stickers_types.go new file mode 100644 index 0000000..299c492 --- /dev/null +++ b/tgapi/stickers_types.go @@ -0,0 +1,65 @@ +package tgapi + +type MaskPositionPoint string + +const ( + MaskPositionForehead MaskPositionPoint = "forehead" + MaskPositionEyes MaskPositionPoint = "eyes" + MaskPositionMouth MaskPositionPoint = "mouth" + MaskPositionChin MaskPositionPoint = "chin" +) + +type MaskPosition struct { + Point MaskPositionPoint `json:"point"` + XShift float32 `json:"x_shift"` + YShift float32 `json:"y_shift"` + Scale float32 `json:"scale"` +} + +type StickerType string + +const ( + StickerTypeRegular StickerType = "regular" + StickerTypeMask StickerType = "mask" + StickerTypeCustomEmoji StickerType = "custom_emoji" +) + +type Sticker struct { + FileId string `json:"file_id"` + FileUniqueId string `json:"file_unique_id"` + Type StickerType `json:"type"` + Width int `json:"width"` + Height int `json:"height"` + IsAnimated bool `json:"is_animated"` + IsVideo bool `json:"is_video"` + + Thumbnail *PhotoSize `json:"thumbnail,omitempty"` + Emoji *string `json:"emoji,omitempty"` + SetName *string `json:"set_name,omitempty"` + MaskPosition *MaskPosition `json:"mask_position,omitempty"` + CustomEmojiID *string `json:"custom_emoji_id,omitempty"` + NeedRepainting *bool `json:"need_repainting,omitempty"` + FileSize *int `json:"file_size,omitempty"` +} +type StickerSet struct { + Name string `json:"name"` + Title string `json:"title"` + StickerType StickerType `json:"sticker_type"` + Stickers []Sticker `json:"stickers"` + Thumbnail *PhotoSize `json:"thumbnail,omitempty"` +} +type InputStickerFormat string + +const ( + InputStickerFormatStatic InputStickerFormat = "static" + InputStickerFormatAnimated InputStickerFormat = "animated" + InputStickerFormatVideo InputStickerFormat = "video" +) + +type InputSticker struct { + Sticker string `json:"sticker"` + Format InputStickerFormat `json:"format"` + EmojiList []string `json:"emoji_list"` + MaskPosition *MaskPosition `json:"mask_position,omitempty"` + Keywords []string `json:"keywords,omitempty"` +} diff --git a/tgapi/types.go b/tgapi/types.go new file mode 100644 index 0000000..6143a63 --- /dev/null +++ b/tgapi/types.go @@ -0,0 +1,303 @@ +package tgapi + +type UpdateType string + +const ( + UpdateTypeMessage UpdateType = "message" + UpdateTypeEditedMessage UpdateType = "edited_message" + UpdateTypeChannelPost UpdateType = "channel_post" + UpdateTypeEditedChannelPost UpdateType = "edited_channel_post" + UpdateTypeMessageReaction UpdateType = "message_reaction" + UpdateTypeMessageReactionCount UpdateType = "message_reaction_count" + + UpdateTypeBusinessConnection UpdateType = "business_connection" + UpdateTypeBusinessMessage UpdateType = "business_message" + UpdateTypeEditedBusinessMessage UpdateType = "edited_business_message" + UpdateTypeDeletedBusinessMessage UpdateType = "deleted_business_message" + + UpdateTypeInlineQuery UpdateType = "inline_query" + UpdateTypeChosenInlineResult UpdateType = "chosen_inline_result" + UpdateTypeCallbackQuery UpdateType = "callback_query" + UpdateTypeShippingQuery UpdateType = "shipping_query" + UpdateTypePreCheckoutQuery UpdateType = "pre_checkout_query" + UpdateTypePurchasedPaidMedia UpdateType = "purchased_paid_media" + UpdateTypePoll UpdateType = "poll" + UpdateTypePollAnswer UpdateType = "poll_answer" + UpdateTypeMyChatMember UpdateType = "my_chat_member" + UpdateTypeChatMember UpdateType = "chat_member" + UpdateTypeChatJoinRequest UpdateType = "chat_join_request" + UpdateTypeChatBoost UpdateType = "chat_boost" + UpdateTypeRemovedChatBoost UpdateType = "removed_chat_boost" +) + +type Update struct { + UpdateID int `json:"update_id"` + Message *Message `json:"message,omitempty"` + 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 *InlineQuery `json:"inline_query,omitempty"` + ChosenInlineResult *ChosenInlineResult `json:"chosen_inline_result,omitempty"` + CallbackQuery *CallbackQuery `json:"callback_query,omitempty"` + ShippingQuery ShippingQuery `json:"shipping_query,omitempty"` + PreCheckoutQuery *PreCheckoutQuery `json:"pre_checkout_query,omitempty"` + PurchasedPaidMedia *PaidMediaPurchased `json:"purchased_paid_media,omitempty"` + + Poll *Poll `json:"poll,omitempty"` + PollAnswer *PollAnswer `json:"poll_answer,omitempty"` + MyChatMember *ChatMemberUpdated `json:"my_chat_member,omitempty"` + ChatMember *ChatMemberUpdated `json:"chat_member,omitempty"` + ChatJoinRequest *ChatJoinRequest `json:"chat_join_request,omitempty"` + ChatBoost *ChatBoostUpdated `json:"chat_boost,omitempty"` + RemovedChatBoost *ChatBoostRemoved `json:"removed_chat_boost,omitempty"` +} + +type InlineQuery struct { + ID string `json:"id"` + From User `json:"from"` + Query string `json:"query"` + Offset string `json:"offset"` + ChatType *ChatType `json:"chat_type,omitempty"` + Location *Location `json:"location,omitempty"` +} +type ChosenInlineResult struct { + ResultID string `json:"result_id"` + From User `json:"from"` + Location *Location `json:"location,omitempty"` + InlineMessageID string `json:"inline_message_id"` + Query string `json:"query"` +} + +type ShippingQuery struct { + ID string `json:"id"` + From User `json:"from"` + InvoicePayload string `json:"invoice_payload"` + ShippingAddress ShippingAddress `json:"shipping_address"` +} +type ShippingAddress struct { + CountryCode string `json:"country_code"` + State string `json:"state"` + City string `json:"city"` + StreetLine1 string `json:"street_line1"` + StreetLine2 string `json:"street_line2"` + PostCode string `json:"post_code"` +} + +type OrderInfo struct { + Name string `json:"name"` + PhoneNumber string `json:"phone_number"` + Email string `json:"email"` + ShippingAddress ShippingAddress `json:"shipping_address"` +} +type PreCheckoutQuery struct { + ID string `json:"id"` + From User `json:"from"` + Currency string `json:"currency"` + TotalAmount int `json:"total_amount"` + InvoicePayload string `json:"invoice_payload"` + ShippingOptionID string `json:"shipping_option_id"` + OrderInfo *OrderInfo `json:"order_info,omitempty"` +} + +type PaidMediaPurchased struct { + From User `json:"from"` + PaidMediaPayload string `json:"paid_media_payload"` +} + +type File struct { + FileId string `json:"file_id"` + FileUniqueID string `json:"file_unique_id"` + FileSize int `json:"file_size,omitempty"` + FilePath string `json:"file_path,omitempty"` +} + +type Audio struct { + FileID string `json:"file_id"` + FileUniqueID string `json:"file_unique_id"` + Duration int `json:"duration"` + + Performer string `json:"performer,omitempty"` + Title string `json:"title,omitempty"` + FileName string `json:"file_name,omitempty"` + MimeType string `json:"mime_type,omitempty"` + FileSize int `json:"file_size,omitempty"` + Thumbnail *PhotoSize `json:"thumbnail,omitempty"` +} + +type PollOption struct { + Text string `json:"text"` + TextEntities []MessageEntity `json:"text_entities"` + VoterCount int `json:"voter_count"` +} +type Poll struct { + ID string `json:"id"` + Question string `json:"question"` + QuestionEntities []MessageEntity `json:"question_entities"` + Options []PollOption `json:"options"` + TotalVoterCount int `json:"total_voter_count"` + IsClosed bool `json:"is_closed"` + IsAnonymous bool `json:"is_anonymous"` + Type PollType `json:"type"` + + AllowsMultipleAnswers bool `json:"allows_multiple_answers"` + CorrectOptionID *int `json:"correct_option_id,omitempty"` + Explanation *string `json:"explanation,omitempty"` + ExplanationEntities []MessageEntity `json:"explanation_entities,omitempty"` + OpenPeriod int `json:"open_period,omitempty"` + CloseDate int `json:"close_date,omitempty"` +} +type PollAnswer struct { + PollID string `json:"poll_id"` + VoterChat Chat `json:"voter_chat"` + User User `json:"user"` + OptionIDS []int `json:"option_ids"` +} +type ChatMemberUpdated struct { + Chat Chat `json:"chat"` + From User `json:"from"` + Date int64 `json:"date"` + OldChatMember ChatMember `json:"old_chat_member"` + NewChatMember ChatMember `json:"new_chat_member"` + InviteLink *ChatInviteLink `json:"invite_link,omitempty"` + ViaJoinRequest *bool `json:"via_join_request,omitempty"` + ViaChatFolderInviteLink *bool `json:"via_chat_folder_invite_link,omitempty"` +} + +type ChatJoinRequest struct { + Chat Chat `json:"chat"` + From User `json:"from"` + UserChatID int `json:"user_chat_id"` + Date int64 `json:"date"` + Bio *string `json:"bio,omitempty"` + InviteLink *ChatInviteLink `json:"invite_link,omitempty"` +} + +type Location struct { + Latitude float64 `json:"latitude"` + Longitude float64 `json:"longitude"` + HorizontalAccuracy float64 `json:"horizontal_accuracy"` + LivePeriod int `json:"live_period"` + Heading int `json:"heading"` + ProximityAlertRadius int `json:"proximity_alert_radius"` +} +type LocationAddress struct { + CountryCode string `json:"country_code"` + State *string `json:"state,omitempty"` + City *string `json:"city,omitempty"` + Street *string `json:"street,omitempty"` +} +type Venue struct { + Location Location `json:"location"` + Title string `json:"title"` + Address string `json:"address"` + FoursquareID string `json:"foursquare_id,omitempty"` + FoursquareType string `json:"foursquare_type,omitempty"` + GooglePlaceID string `json:"google_place_id,omitempty"` + GooglePlaceType string `json:"google_place_type,omitempty"` +} + +type WebAppInfo struct { + URL string `json:"url"` +} + +type StarAmount struct { + Amount int `json:"amount"` + NanostarAmount int `json:"nanostar_amount"` +} + +type Story struct { + Chat Chat `json:"chat"` + ID int `json:"id"` +} + +// Gifts + +type AcceptedGiftTypes struct { + UnlimitedGifts bool `json:"unlimited_gifts"` + LimitedGifts bool `json:"limited_gifts"` + UniqueGifts bool `json:"unique_gifts"` + PremiumSubscription bool `json:"premium_subscription"` + GiftsFromChannels bool `json:"gifts_from_channels"` +} + +type UniqueGiftColors struct { + ModelCustomEmojiID string `json:"model_custom_emoji_id"` + SymbolCustomEmojiID string `json:"symbol_custom_emoji_id"` + LightThemeMainColor int `json:"light_theme_main_color"` + LightThemeOtherColors []int `json:"light_theme_other_colors"` + DarkThemeMainColor int `json:"dark_theme_main_color"` + DarkThemeOtherColors []int `json:"dark_theme_other_colors"` +} + +type GiftBackground struct { + CenterColor int `json:"center_color"` + EdgeColor int `json:"edge_color"` + TextColor int `json:"text_color"` +} +type Gift struct { + ID string `json:"id"` + Sticker Sticker `json:"sticker"` + StarCount int `json:"star_count"` + UpdateStarCount *int `json:"update_star_count,omitempty"` + IsPremium *bool `json:"is_premium,omitempty"` + HasColors *bool `json:"has_colors,omitempty"` + TotalCount *int `json:"total_count,omitempty"` + RemainingCount *int `json:"remaining_count,omitempty"` + PersonalTotalCount *int `json:"personal_total_count,omitempty"` + PersonalRemainingCount *int `json:"personal_remaining_count,omitempty"` + Background GiftBackground `json:"background,omitempty"` + UniqueGiftVariantColor *int `json:"unique_gift_variant_color,omitempty"` + PublisherChat *Chat `json:"publisher_chat,omitempty"` +} +type Gifts struct { + Gifts []Gift `json:"gifts"` +} + +const ( + OwnedGiftRegularType OwnedGiftType = "regular" + OwnedGiftUniqueType OwnedGiftType = "unique" +) + +type OwnedGiftType string +type BaseOwnedGift struct { + Type OwnedGiftType `json:"type"` + OwnerGiftID *string `json:"owner_gift_id,omitempty"` + SendDate *int `json:"send_date,omitempty"` + IsSaved *bool `json:"is_saved,omitempty"` +} +type OwnedGiftRegular struct { + BaseOwnedGift + Gift Gift `json:"gift"` + SenderUser User `json:"sender_user,omitempty"` + Text string `json:"text,omitempty"` + Entities []MessageEntity `json:"entities,omitempty"` + IsPrivate *bool `json:"is_private,omitempty"` + CanBeUpgraded *bool `json:"can_be_upgraded,omitempty"` + WasRefunded *bool `json:"was_refunded,omitempty"` + ConvertStarCount *int `json:"convert_star_count,omitempty"` + PrepaidUpgradeStarCount *int `json:"prepaid_upgrade_star_count,omitempty"` + IsUpgradeSeparate *bool `json:"is_upgrade_separate,omitempty"` + UniqueGiftNumber *int `json:"unique_gift_number,omitempty"` +} +type OwnedGiftUnique struct { + BaseOwnedGift + CanBeTransferred *bool `json:"can_be_transferred,omitempty"` + TransferStarCount *int `json:"transfer_star_count,omitempty"` + NextTransferDate *int `json:"next_transfer_date,omitempty"` +} +type OwnedGifts struct { + TotalCount int `json:"total_count"` + Gifts []struct { + OwnedGiftRegular + OwnedGiftUnique + } `json:"gifts"` + NextOffset string `json:"next_offset"` +} diff --git a/tgapi/uploader.go b/tgapi/uploader.go new file mode 100644 index 0000000..7062503 --- /dev/null +++ b/tgapi/uploader.go @@ -0,0 +1,357 @@ +package tgapi + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io" + "mime/multipart" + "net/http" + "path/filepath" + + "git.nix13.pw/scuroneko/laniakea/utils" + "git.nix13.pw/scuroneko/slog" +) + +type Uploader struct { + api *Api + logger *slog.Logger +} + +func NewUploader(api *Api) *Uploader { + logger := slog.CreateLogger().Level(GetLoggerLevel()).Prefix("UPLOADER") + logger.AddWriter(logger.CreateJsonStdoutWriter()) + return &Uploader{api, logger} +} +func (u *Uploader) Close() error { + return u.logger.Close() +} + +type UploaderFileType string + +const ( + UploaderPhotoType UploaderFileType = "photo" + UploaderVideoType UploaderFileType = "video" + UploaderAudioType UploaderFileType = "audio" + UploaderDocumentType UploaderFileType = "document" + UploaderVoiceType UploaderFileType = "voice" + UploaderVideoNoteType UploaderFileType = "video_note" + UploaderThumbnailType UploaderFileType = "thumbnail" +) + +type UploaderFile struct { + filename string + data []byte + field UploaderFileType +} + +func NewUploaderFile(name string, data []byte) UploaderFile { + t := uploaderTypeByExt(name) + return UploaderFile{filename: name, data: data, field: t} +} + +// SetType used when auto-detect failed. I.e. you sending a voice message, but it detects as audio, or if you send audio with thumbnail +func (f UploaderFile) SetType(t UploaderFileType) UploaderFile { + f.field = t + return f +} + +type UploaderRequest[R, P any] struct { + method string + files []UploaderFile + params P +} + +func NewUploaderRequest[R, P any](method string, params P, files ...UploaderFile) UploaderRequest[R, P] { + return UploaderRequest[R, P]{method, files, params} +} +func (u UploaderRequest[R, P]) DoWithContext(ctx context.Context, up *Uploader) (R, error) { + var zero R + url := fmt.Sprintf("https://api.telegram.org/bot%s/%s", up.api.token, u.method) + + buf := bytes.NewBuffer(nil) + w := multipart.NewWriter(buf) + + for _, file := range u.files { + fw, err := w.CreateFormFile(string(file.field), file.filename) + if err != nil { + w.Close() + return zero, err + } + _, err = fw.Write(file.data) + if err != nil { + w.Close() + return zero, err + } + } + + err := utils.Encode(w, u.params) + if err != nil { + w.Close() + return zero, err + } + err = w.Close() + if err != nil { + return zero, err + } + + req, err := http.NewRequestWithContext(ctx, "POST", url, buf) + if err != nil { + return zero, err + } + req.Header.Set("Content-Type", w.FormDataContentType()) + req.Header.Set("User-Agent", fmt.Sprintf("Laniakea/%s", utils.VersionString)) + + up.logger.Debugln("UPLOADER REQ", u.method) + res, err := up.api.client.Do(req) + if err != nil { + return zero, err + } + defer res.Body.Close() + + body, err := io.ReadAll(res.Body) + if err != nil { + return zero, err + } + up.logger.Debugln("UPLOADER RES", u.method, string(body)) + if res.StatusCode != http.StatusOK { + return zero, fmt.Errorf("[%d] %s", res.StatusCode, string(body)) + } + + var response ApiResponse[R] + err = json.Unmarshal(body, &response) + if err != nil { + return zero, err + } + if !response.Ok { + return zero, fmt.Errorf("[%d] %s", response.ErrorCode, response.Description) + } + return response.Result, nil +} +func (u UploaderRequest[R, P]) Do(up *Uploader) (R, error) { + ctx := context.Background() + return u.DoWithContext(ctx, up) +} + +type UploadPhotoP struct { + BusinessConnectionID string `json:"business_connection_id,omitempty"` + ChatID int `json:"chat_id"` + MessageThreadID int `json:"message_thread_id,omitempty"` + DirectMessagesTopicID int `json:"direct_messages_topic_id,omitempty"` + + Caption string `json:"caption,omitempty"` + ParseMode ParseMode `json:"parse_mode,omitempty"` + CaptionEntities []MessageEntity `json:"caption_entities,omitempty"` + + ShowCaptionAboveMedia bool `json:"show_caption_above_media,omitempty"` + HasSpoiler bool `json:"has_spoiler,omitempty"` + DisableNotification bool `json:"disable_notification,omitempty"` + ProtectContent bool `json:"protect_content,omitempty"` + AllowPaidBroadcast bool `json:"allow_paid_broadcast,omitempty"` + MessageEffectID string `json:"message_effect_id,omitempty"` + + SuggestedPostParameters *SuggestedPostParameters `json:"suggested_post_parameters,omitempty"` + ReplyParameters *ReplyParameters `json:"reply_parameters,omitempty"` + ReplyMarkup *ReplyMarkup `json:"reply_markup,omitempty"` +} + +func (u *Uploader) UploadPhoto(params UploadPhotoP, file UploaderFile) (Message, error) { + req := NewUploaderRequest[Message]("sendPhoto", params, file) + return req.Do(u) +} + +type UploadAudioP struct { + BusinessConnectionID string `json:"business_connection_id,omitempty"` + ChatID int `json:"chat_id"` + MessageThreadID int `json:"message_thread_id,omitempty"` + DirectMessagesTopicID int `json:"direct_messages_topic_id,omitempty"` + + Caption string `json:"caption,omitempty"` + ParseMode ParseMode `json:"parse_mode,omitempty"` + CaptionEntities []MessageEntity `json:"caption_entities,omitempty"` + + Duration int `json:"duration,omitempty"` + Performer string `json:"performer,omitempty"` + Title string `json:"title,omitempty"` + + DisableNotification bool `json:"disable_notification,omitempty"` + ProtectContent bool `json:"protect_content,omitempty"` + AllowPaidBroadcast bool `json:"allow_paid_broadcast,omitempty"` + MessageEffectID string `json:"message_effect_id,omitempty"` + + SuggestedPostParameters *SuggestedPostParameters `json:"suggested_post_parameters,omitempty"` + ReplyParameters *ReplyParameters `json:"reply_parameters,omitempty"` + ReplyMarkup *ReplyMarkup `json:"reply_markup,omitempty"` +} + +func (u *Uploader) UploadAudio(params UploadAudioP, files ...UploaderFile) (Message, error) { + req := NewUploaderRequest[Message]("sendAudio", params, files...) + return req.Do(u) +} + +type UploadDocumentP struct { + BusinessConnectionID string `json:"business_connection_id,omitempty"` + ChatID int `json:"chat_id"` + MessageThreadID int `json:"message_thread_id,omitempty"` + DirectMessagesTopicID int `json:"direct_messages_topic_id,omitempty"` + + Caption string `json:"caption,omitempty"` + ParseMode ParseMode `json:"parse_mode,omitempty"` + CaptionEntities []MessageEntity `json:"caption_entities,omitempty"` + + DisableContentTypeDetection bool `json:"disable_content_type_detection,omitempty"` + DisableNotification bool `json:"disable_notification,omitempty"` + ProtectContent bool `json:"protect_content,omitempty"` + AllowPaidBroadcast bool `json:"allow_paid_broadcast,omitempty"` + MessageEffectID string `json:"message_effect_id,omitempty"` + + SuggestedPostParameters *SuggestedPostParameters `json:"suggested_post_parameters,omitempty"` + ReplyParameters *ReplyParameters `json:"reply_parameters,omitempty"` + ReplyMarkup *ReplyMarkup `json:"reply_markup,omitempty"` +} + +func (u *Uploader) UploadDocument(params UploadDocumentP, files ...UploaderFile) (Message, error) { + req := NewUploaderRequest[Message]("sendDocument", params, files...) + return req.Do(u) +} + +type UploadVideoP struct { + BusinessConnectionID string `json:"business_connection_id,omitempty"` + ChatID int `json:"chat_id"` + MessageThreadID int `json:"message_thread_id,omitempty"` + DirectMessagesTopicID int `json:"direct_messages_topic_id,omitempty"` + + Duration int `json:"duration,omitempty"` + Width int `json:"width,omitempty"` + Height int `json:"height,omitempty"` + + StartTimestamp int64 `json:"start_timestamp,omitempty"` + Caption string `json:"caption,omitempty"` + ParseMode ParseMode `json:"parse_mode,omitempty"` + CaptionEntities []MessageEntity `json:"caption_entities,omitempty"` + + ShowCaptionAboveMedia bool `json:"show_caption_above_media,omitempty"` + HasSpoiler bool `json:"has_spoiler,omitempty"` + SupportsStreaming bool `json:"supports_streaming,omitempty"` + DisableNotification bool `json:"disable_notification,omitempty"` + ProtectContent bool `json:"protect_content,omitempty"` + AllowPaidBroadcast bool `json:"allow_paid_broadcast,omitempty"` + MessageEffectID string `json:"message_effect_id,omitempty"` + + SuggestedPostParameters *SuggestedPostParameters `json:"suggested_post_parameters,omitempty"` + ReplyParameters *ReplyParameters `json:"reply_parameters,omitempty"` + ReplyMarkup *ReplyMarkup `json:"reply_markup,omitempty"` +} + +func (u *Uploader) UploadVideo(params UploadVideoP, files ...UploaderFile) (Message, error) { + req := NewUploaderRequest[Message]("sendVideo", params, files...) + return req.Do(u) +} + +type UploadAnimationP struct { + BusinessConnectionID string `json:"business_connection_id,omitempty"` + ChatID int `json:"chat_id"` + MessageThreadID int `json:"message_thread_id,omitempty"` + DirectMessagesTopicID int `json:"direct_messages_topic_id,omitempty"` + + Duration int `json:"duration,omitempty"` + Width int `json:"width,omitempty"` + Height int `json:"height,omitempty"` + + Caption string `json:"caption,omitempty"` + ParseMode ParseMode `json:"parse_mode,omitempty"` + CaptionEntities []MessageEntity `json:"caption_entities,omitempty"` + + ShowCaptionAboveMedia bool `json:"show_caption_above_media,omitempty"` + HasSpoiler bool `json:"has_spoiler,omitempty"` + DisableNotification bool `json:"disable_notification,omitempty"` + ProtectContent bool `json:"protect_content,omitempty"` + AllowPaidBroadcast bool `json:"allow_paid_broadcast,omitempty"` + MessageEffectID string `json:"message_effect_id,omitempty"` + + SuggestedPostParameters *SuggestedPostParameters `json:"suggested_post_parameters,omitempty"` + ReplyParameters *ReplyParameters `json:"reply_parameters,omitempty"` + ReplyMarkup *ReplyMarkup `json:"reply_markup,omitempty"` +} + +func (u *Uploader) UploadAnimation(params UploadAnimationP, files ...UploaderFile) (Message, error) { + req := NewUploaderRequest[Message]("sendAnimation", params, files...) + return req.Do(u) +} + +type UploadVoiceP struct { + BusinessConnectionID string `json:"business_connection_id,omitempty"` + ChatID int `json:"chat_id"` + MessageThreadID int `json:"message_thread_id,omitempty"` + DirectMessagesTopicID int `json:"direct_messages_topic_id,omitempty"` + + Caption string `json:"caption,omitempty"` + ParseMode ParseMode `json:"parse_mode,omitempty"` + CaptionEntities []MessageEntity `json:"caption_entities,omitempty"` + Duration int `json:"duration,omitempty"` + + DisableNotification bool `json:"disable_notification,omitempty"` + ProtectContent bool `json:"protect_content,omitempty"` + AllowPaidBroadcast bool `json:"allow_paid_broadcast,omitempty"` + MessageEffectID string `json:"message_effect_id,omitempty"` + + SuggestedPostParameters *SuggestedPostParameters `json:"suggested_post_parameters,omitempty"` + ReplyParameters *ReplyParameters `json:"reply_parameters,omitempty"` + ReplyMarkup *ReplyMarkup `json:"reply_markup,omitempty"` +} + +func (u *Uploader) UploadVoice(params UploadVoiceP, files ...UploaderFile) (Message, error) { + req := NewUploaderRequest[Message]("sendVoice", params, files...) + return req.Do(u) +} + +type UploadVideoNoteP struct { + BusinessConnectionID string `json:"business_connection_id,omitempty"` + ChatID int `json:"chat_id"` + MessageThreadID int `json:"message_thread_id,omitempty"` + DirectMessagesTopicID int `json:"direct_messages_topic_id,omitempty"` + + Duration int `json:"duration,omitempty"` + Length int `json:"length,omitempty"` + + DisableNotification bool `json:"disable_notification,omitempty"` + ProtectContent bool `json:"protect_content,omitempty"` + AllowPaidBroadcast bool `json:"allow_paid_broadcast,omitempty"` + MessageEffectID string `json:"message_effect_id,omitempty"` + + SuggestedPostParameters *SuggestedPostParameters `json:"suggested_post_parameters,omitempty"` + ReplyParameters *ReplyParameters `json:"reply_parameters,omitempty"` + ReplyMarkup *ReplyMarkup `json:"reply_markup,omitempty"` +} + +func (u *Uploader) UploadVideoNote(params UploadVideoNoteP, files ...UploaderFile) (Message, error) { + req := NewUploaderRequest[Message]("sendVideoNote", params, files...) + return req.Do(u) +} + +// setChatPhoto https://core.telegram.org/bots/api#setchatphoto +type UploadChatPhotoP struct { + ChatID int `json:"chat_id"` +} + +func (u *Uploader) UploadChatPhoto(params UploadChatPhotoP, photo UploaderFile) (Message, error) { + req := NewUploaderRequest[Message]("sendChatPhoto", params, photo) + return req.Do(u) +} + +func uploaderTypeByExt(filename string) UploaderFileType { + ext := filepath.Ext(filename) + switch ext { + case ".jpg", ".jpeg", ".png", ".webp", ".bmp": + return UploaderPhotoType + case ".mp4": + return UploaderVideoType + case ".mp3", ".m4a": + return UploaderAudioType + case ".ogg": + return UploaderVoiceType + default: + return UploaderDocumentType + } +} diff --git a/tgapi/users_methods.go b/tgapi/users_methods.go new file mode 100644 index 0000000..d254140 --- /dev/null +++ b/tgapi/users_methods.go @@ -0,0 +1,51 @@ +package tgapi + +type GetUserProfilePhotosP struct { + UserID int `json:"user_id"` + Offset int `json:"offset,omitempty"` + Limit int `json:"limit,omitempty"` +} + +func (api *Api) GetUserProfilePhotos(params GetUserProfilePhotosP) (UserProfilePhotos, error) { + req := NewRequest[UserProfilePhotos]("getUserProfilePhotos", params) + return req.Do(api) +} + +type GetUserProfileAudiosP struct { + UserID int `json:"user_id"` + Offset int `json:"offset,omitempty"` + Limit int `json:"limit,omitempty"` +} + +func (api *Api) GetUserProfileAudios(params GetUserProfileAudiosP) (UserProfileAudios, error) { + req := NewRequest[UserProfileAudios]("getUserProfileAudios", params) + return req.Do(api) +} + +type SetUserEmojiStatusP struct { + UserID int `json:"user_id"` + EmojiID string `json:"emoji_status_custom_emoji_id,omitempty"` + ExpirationDate int `json:"emoji_status_expiration_date,omitempty"` +} + +func (api *Api) SetUserEmojiStatus(params SetUserEmojiStatusP) (bool, error) { + req := NewRequest[bool]("setUserEmojiStatus", params) + return req.Do(api) +} + +type GetUserGiftsP struct { + UserID int `json:"user_id"` + ExcludeUnlimited bool `json:"exclude_unlimited,omitempty"` + ExcludeLimitedUpgradable bool `json:"exclude_limited_upgradable,omitempty"` + ExcludeLimitedNonUpgradable bool `json:"exclude_limited_non_upgradable,omitempty"` + ExcludeUnique bool `json:"exclude_unique,omitempty"` + ExcludeFromBlockchain bool `json:"exclude_from_blockchain,omitempty"` + SortByPrice bool `json:"sort_by_price,omitempty"` + Offset string `json:"offset,omitempty"` + Limit int `json:"limit,omitempty"` +} + +func (api *Api) GetUserGifts(params GetUserGiftsP) (OwnedGifts, error) { + req := NewRequest[OwnedGifts]("getUserGifts", params) + return req.Do(api) +} diff --git a/tgapi/users_types.go b/tgapi/users_types.go new file mode 100644 index 0000000..b826074 --- /dev/null +++ b/tgapi/users_types.go @@ -0,0 +1,40 @@ +package tgapi + +type User struct { + ID int `json:"id"` + IsBot bool `json:"is_bot"` + FirstName string `json:"first_name"` + LastName *string `json:"last_name,omitempty"` + Username *string `json:"username,omitempty"` + LanguageCode *string `json:"language_code,omitempty"` + IsPremium *bool `json:"is_premium,omitempty"` + AddedToAttachmentMenu *bool `json:"added_to_attachment_menu,omitempty"` + CanJoinGroups *bool `json:"can_join_groups,omitempty"` + CanReadAllGroupMessages *bool `json:"can_read_all_group_messages,omitempty"` + SupportsInlineQueries *bool `json:"supports_inline_queries,omitempty"` + CanConnectToBusiness *bool `json:"can_connect_to_business,omitempty"` + HasMainWebApp *bool `json:"has_main_web_app,omitempty"` + HasTopicsEnabled *bool `json:"has_topics_enabled,omitempty"` + AllowsUsersToCreateTopics *bool `json:"allows_users_to_create_topics,omitempty"` +} + +type UserProfilePhotos struct { + TotalCount int `json:"total_count"` + Photos [][]PhotoSize `json:"photos"` +} +type UserProfileAudios struct { + TotalCount int `json:"total_count"` + Audios []Audio `json:"audios"` +} + +type UserRating struct { + Level int `json:"level"` + Rating int `json:"rating"` + CurrentLevelRating int `json:"current_level_rating"` + NextLevelRating int `json:"next_level_rating"` +} +type Birthdate struct { + Day int `json:"day"` + Month int `json:"month"` + Year int `json:"year"` +} diff --git a/tgapi/utils.go b/tgapi/utils.go new file mode 100644 index 0000000..a982ea3 --- /dev/null +++ b/tgapi/utils.go @@ -0,0 +1,15 @@ +package tgapi + +import ( + "os" + + "git.nix13.pw/scuroneko/slog" +) + +func GetLoggerLevel() slog.LogLevel { + level := slog.FATAL + if os.Getenv("DEBUG") == "true" { + level = slog.DEBUG + } + return level +} diff --git a/types.go b/types.go deleted file mode 100644 index 974f144..0000000 --- a/types.go +++ /dev/null @@ -1,196 +0,0 @@ -package laniakea - -import "git.nix13.pw/scuroneko/extypes" - -type Update struct { - 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"` - CallbackQuery *CallbackQuery `json:"callback_query,omitempty"` - InlineQuery int - ChosenInlineResult int -} - -type User struct { - ID int `json:"id"` - IsBot bool `json:"is_bot"` - FirstName string `json:"first_name"` - LastName string `json:"last_name,omitempty"` - Username string `json:"username,omitempty"` - LanguageCode string `json:"language_code,omitempty"` - IsPremium bool `json:"is_premium,omitempty"` - AddedToAttachmentMenu bool `json:"added_to_attachment_menu,omitempty"` - CanJoinGroups bool `json:"can_join_groups,omitempty"` - CanReadAllGroupMessages bool `json:"can_read_all_group_messages,omitempty"` - SupportsInlineQueries bool `json:"supports_inline_queries,omitempty"` - CanConnectToBusiness bool `json:"can_connect_to_business,omitempty"` - HasMainWebApp bool `json:"has_main_web_app,omitempty"` -} - -type Chat struct { - ID int `json:"id"` - Type string `json:"type"` - Title string `json:"title,omitempty"` - Username string `json:"username,omitempty"` - FirstName string `json:"first_name,omitempty"` - LastName string `json:"last_name,omitempty"` - IsForum bool `json:"is_forum,omitempty"` -} - -type MessageReplyMarkup struct { - InlineKeyboard [][]InlineKeyboardButton `json:"inline_keyboard"` -} - -type Message struct { - MessageID int `json:"message_id"` - MessageThreadID int `json:"message_thread_id,omitempty"` - From *User `json:"from,omitempty"` - Chat *Chat `json:"chat,omitempty"` - Text string `json:"text"` - - Photo extypes.Slice[*PhotoSize] `json:"photo,omitempty"` - Caption string `json:"caption,omitempty"` - ReplyToMessage *Message `json:"reply_to_message"` - - ReplyMarkup *MessageReplyMarkup `json:"reply_markup,omitempty"` -} - -type InaccessableMessage struct { - Chat *Chat `json:"chat"` - MessageID int `json:"message_id"` - Date int `json:"date"` -} - -type MaybeInaccessibleMessage struct { -} - -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 PhotoSize struct { - FileID string `json:"file_id"` - FileUniqueID string `json:"file_unique_id"` - Width int `json:"width"` - Height int `json:"height"` - FileSize int `json:"file_size,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,omitempty"` -} - -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:"from"` - Message *Message `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"` -} - -type File struct { - FileId string `json:"file_id"` - FileUniqueID string `json:"file_unique_id"` - FileSize int `json:"file_size,omitempty"` - FilePath string `json:"file_path,omitempty"` -} - -type ChatActions string - -const ( - ChatActionTyping ChatActions = "typing" - ChatActionUploadPhoto ChatActions = "upload_photo" - ChatActionUploadVideo ChatActions = "upload_video" - ChatActionUploadVoice ChatActions = "upload_voice" - ChatActionUploadDocument ChatActions = "upload_document" - ChatActionChooseSticker ChatActions = "choose_sticker" - ChatActionFindLocation ChatActions = "find_location" - ChatActionUploadVideoNone ChatActions = "upload_video_none" -) diff --git a/uploader.go b/uploader.go deleted file mode 100644 index def36ce..0000000 --- a/uploader.go +++ /dev/null @@ -1,136 +0,0 @@ -package laniakea - -import ( - "bytes" - "encoding/json" - "fmt" - "io" - "mime/multipart" - "net/http" - "path/filepath" -) - -type Uploader struct { - bot *Bot -} - -func NewUploader(bot *Bot) *Uploader { - return &Uploader{bot: bot} -} - -type UploaderFileType string - -const ( - UploaderPhotoType UploaderFileType = "photo" - UploaderVideoType UploaderFileType = "video" - UploaderAudioType UploaderFileType = "audio" - UploaderDocumentType UploaderFileType = "document" - UploaderVoiceType UploaderFileType = "voice" - UploaderVideoNoteType UploaderFileType = "video_note" -) - -type UploaderFile struct { - filename string - data []byte - t UploaderFileType -} - -func NewUploaderFile(name string, data []byte) UploaderFile { - t := uploaderTypeByExt(name) - return UploaderFile{filename: name, data: data, t: t} -} - -// SetType used when auto-detect failed. I.e. you sending a voice message, but it detects as audio -func (f UploaderFile) SetType(t UploaderFileType) UploaderFile { - f.t = t - return f -} - -type UploaderRequest[R, P any] struct { - method string - file UploaderFile - params P -} - -func NewUploaderRequest[R, P any](method string, file UploaderFile, params P) UploaderRequest[R, P] { - return UploaderRequest[R, P]{method, file, params} -} - -func (u UploaderRequest[R, P]) Do(bot *Bot) (*R, error) { - url := fmt.Sprintf("https://api.telegram.org/bot%s/%s", bot.token, u.method) - - buf := bytes.NewBuffer(nil) - w := multipart.NewWriter(buf) - - fw, err := w.CreateFormFile(string(u.file.t), u.file.filename) - if err != nil { - w.Close() - return nil, err - } - _, err = fw.Write(u.file.data) - if err != nil { - w.Close() - return nil, err - } - err = Encode(w, u.params) - if err != nil { - w.Close() - return nil, err - } - err = w.Close() - if err != nil { - return nil, err - } - - req, err := http.NewRequest("POST", url, buf) - if err != nil { - return nil, err - } - req.Header.Set("Content-Type", w.FormDataContentType()) - bot.logger.Debugln("UPLOADER REQ", u.method) - res, err := http.DefaultClient.Do(req) - if err != nil { - return nil, err - } - defer res.Body.Close() - - body, err := io.ReadAll(res.Body) - if err != nil { - return nil, err - } - bot.logger.Debugln("UPLOADER RES", u.method, string(body)) - if res.StatusCode != http.StatusOK { - return nil, fmt.Errorf("[%d] %s", res.StatusCode, string(body)) - } - - response := new(ApiResponse[*R]) - err = json.Unmarshal(body, response) - if err != nil { - return nil, err - } - if !response.Ok { - return nil, fmt.Errorf("[%d] %s", response.ErrorCode, response.Description) - } - return response.Result, nil -} - -func (u *Uploader) UploadPhoto(file UploaderFile, params SendPhotoBaseP) (*Message, error) { - req := NewUploaderRequest[Message]("sendPhoto", file, params) - return req.Do(u.bot) -} - -func uploaderTypeByExt(filename string) UploaderFileType { - ext := filepath.Ext(filename) - switch ext { - case ".jpg", ".jpeg", ".png", ".webp", ".bmp": - return UploaderPhotoType - case ".mp4": - return UploaderVideoType - case ".mp3", ".m4a": - return UploaderAudioType - case ".ogg": - return UploaderVoiceType - default: - return UploaderDocumentType - } -} diff --git a/utils.go b/utils.go index 5bfad8d..debe5f1 100644 --- a/utils.go +++ b/utils.go @@ -1,75 +1,16 @@ package laniakea -import ( - "encoding/json" - "fmt" - "strings" -) +import "git.nix13.pw/scuroneko/laniakea/utils" -// MapToStruct unsafe function -func MapToStruct(m map[string]any, s any) error { - data, err := json.Marshal(m) - if err != nil { - return err +func Ptr[T any](v T) *T { return &v } + +func Val[T any](p *T, def T) T { + if p != nil { + return *p } - err = json.Unmarshal(data, s) - return err + return def } -// SliceToStruct unsafe function -func SliceToStruct(sl []any, s any) error { - data, err := json.Marshal(sl) - if err != nil { - return err - } - err = json.Unmarshal(data, s) - return err -} +const VersionString = utils.VersionString -// AnyToStruct unsafe function -func AnyToStruct(src, dest any) error { - data, err := json.Marshal(src) - if err != nil { - return err - } - err = json.Unmarshal(data, dest) - return err -} - -func MapToJson(m map[string]any) (string, error) { - data, err := json.Marshal(m) - return string(data), err -} - -func StructToMap(s any) (map[string]any, error) { - data, err := json.Marshal(s) - if err != nil { - return nil, err - } - m := make(map[string]any) - 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 -} - -func EscapeMarkdown(s string) string { - s = strings.ReplaceAll(s, "_", `\_`) - s = strings.ReplaceAll(s, "*", `\*`) - s = strings.ReplaceAll(s, "[", `\[`) - return strings.ReplaceAll(s, "`", "\\`") -} - -func EscapeMarkdownV2(s string) string { - symbols := []string{"_", "*", "[", "]", "(", ")", "~", "`", ">", "#", "+", "-", "=", "|", "{", "}", ".", "!"} - for _, symbol := range symbols { - s = strings.ReplaceAll(s, symbol, fmt.Sprintf("\\%s", symbol)) - } - return s -} +var EscapeMarkdown = utils.EscapeMarkdown diff --git a/multipart.go b/utils/multipart.go similarity index 99% rename from multipart.go rename to utils/multipart.go index e928fc3..89d2fc7 100644 --- a/multipart.go +++ b/utils/multipart.go @@ -1,4 +1,4 @@ -package laniakea +package utils import ( "encoding/json" @@ -78,7 +78,6 @@ func Encode[T any](w *multipart.Writer, req T) error { if err == nil { _, err = fw.Write([]byte(strconv.FormatBool(field.Bool()))) } - case reflect.Slice: if field.Type().Elem().Kind() == reflect.Uint8 && !field.IsNil() { filename := fieldType.Tag.Get("filename") diff --git a/utils/utils.go b/utils/utils.go new file mode 100644 index 0000000..c782c58 --- /dev/null +++ b/utils/utils.go @@ -0,0 +1,100 @@ +package utils + +import ( + "encoding/json" + "fmt" + "os" + "strings" + + "git.nix13.pw/scuroneko/slog" +) + +func GetLoggerLevel() slog.LogLevel { + level := slog.FATAL + if os.Getenv("DEBUG") == "true" { + level = slog.DEBUG + } + return level +} + +func Cast[A, B any](src A) (*B, error) { + m, err := StructToMap(src) + if err != nil { + return nil, err + } + + out := new(B) + err = MapToStruct(m, out) + if err != nil { + return nil, err + } + return out, nil +} + +// MapToStruct unsafe function +func MapToStruct(m map[string]any, s any) error { + data, err := json.Marshal(m) + if err != nil { + return err + } + err = json.Unmarshal(data, s) + return err +} + +// SliceToStruct unsafe function +func SliceToStruct(sl []any, s any) error { + data, err := json.Marshal(sl) + if err != nil { + return err + } + err = json.Unmarshal(data, s) + return err +} + +// AnyToStruct unsafe function +func AnyToStruct(src, dest any) error { + data, err := json.Marshal(src) + if err != nil { + return err + } + err = json.Unmarshal(data, dest) + return err +} + +func MapToJson(m map[string]any) (string, error) { + data, err := json.Marshal(m) + return string(data), err +} + +func StructToMap(s any) (map[string]any, error) { + data, err := json.Marshal(s) + if err != nil { + return nil, err + } + m := make(map[string]any) + 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 +} + +func EscapeMarkdown(s string) string { + s = strings.ReplaceAll(s, "_", `\_`) + s = strings.ReplaceAll(s, "*", `\*`) + s = strings.ReplaceAll(s, "[", `\[`) + return strings.ReplaceAll(s, "`", "\\`") +} + +func EscapeMarkdownV2(s string) string { + symbols := []string{"_", "*", "[", "]", "(", ")", "~", "`", ">", "#", "+", "-", "=", "|", "{", "}", ".", "!"} + for _, symbol := range symbols { + s = strings.ReplaceAll(s, symbol, fmt.Sprintf("\\%s", symbol)) + } + return s +} diff --git a/utils/version.go b/utils/version.go new file mode 100644 index 0000000..adc8290 --- /dev/null +++ b/utils/version.go @@ -0,0 +1,8 @@ +package utils + +const ( + VersionString = "0.5.0" + VersionMajor = 0 + VersionMinor = 5 + VersionPatch = 0 +) diff --git a/version.go b/version.go deleted file mode 100644 index 2661f98..0000000 --- a/version.go +++ /dev/null @@ -1,8 +0,0 @@ -package laniakea - -const ( - VersionString = "0.3.9" - VersionMajor = 0 - VersionMinor = 3 - VersionPatch = 9 -)