Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6fd482b58f | |||
| 913fa20e19 | |||
| c71aad0c79 | |||
| 90e2f38c18 |
15
api.go
15
api.go
@@ -63,3 +63,18 @@ func (r TelegramRequest[R, P]) Do(bot *Bot) (*R, error) {
|
|||||||
}
|
}
|
||||||
return &response.Result, nil
|
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)
|
||||||
|
}
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ func (b *Bot) handleCallback(update *Update, ctx *MsgContext) {
|
|||||||
ctx.From = update.CallbackQuery.From
|
ctx.From = update.CallbackQuery.From
|
||||||
ctx.Msg = update.CallbackQuery.Message
|
ctx.Msg = update.CallbackQuery.Message
|
||||||
ctx.CallbackMsgId = update.CallbackQuery.Message.MessageID
|
ctx.CallbackMsgId = update.CallbackQuery.Message.MessageID
|
||||||
|
ctx.CallbackQueryId = update.CallbackQuery.ID
|
||||||
ctx.Args = data.Args
|
ctx.Args = data.Args
|
||||||
|
|
||||||
for _, plugin := range b.plugins {
|
for _, plugin := range b.plugins {
|
||||||
|
|||||||
63
methods.go
63
methods.go
@@ -73,21 +73,36 @@ func (b *Bot) SendMessage(params *SendMessageP) (*Message, error) {
|
|||||||
return req.Do(b)
|
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 {
|
type SendPhotoP struct {
|
||||||
BusinessConnectionID string `json:"business_connection_id,omitempty"`
|
BusinessConnectionID string `json:"business_connection_id,omitempty"`
|
||||||
ChatID int `json:"chat_id"`
|
ChatID int `json:"chat_id"`
|
||||||
MessageThreadID int `json:"message_thread_id,omitempty"`
|
MessageThreadID int `json:"message_thread_id,omitempty"`
|
||||||
ParseMode ParseMode `json:"parse_mode,omitempty"`
|
ParseMode ParseMode `json:"parse_mode,omitempty"`
|
||||||
Photo string `json:"photo"`
|
|
||||||
Caption string `json:"caption,omitempty"`
|
Caption string `json:"caption,omitempty"`
|
||||||
CaptionEntities []*MessageEntity `json:"caption_entities,omitempty"`
|
CaptionEntities []*MessageEntity `json:"caption_entities,omitempty"`
|
||||||
ShowCaptionAboveMedia bool `json:"show_caption_above_media"`
|
ShowCaptionAboveMedia bool `json:"show_caption_above_media,omitempty"`
|
||||||
HasSpoiler bool `json:"has_spoiler"`
|
HasSpoiler bool `json:"has_spoiler,omitempty"`
|
||||||
DisableNotifications bool `json:"disable_notifications,omitempty"`
|
DisableNotifications bool `json:"disable_notifications,omitempty"`
|
||||||
ProtectContent bool `json:"protect_content,omitempty"`
|
ProtectContent bool `json:"protect_content,omitempty"`
|
||||||
AllowPaidBroadcast bool `json:"allow_paid_broadcast,omitempty"`
|
AllowPaidBroadcast bool `json:"allow_paid_broadcast,omitempty"`
|
||||||
MessageEffectID string `json:"message_effect_id,omitempty"`
|
MessageEffectID string `json:"message_effect_id,omitempty"`
|
||||||
ReplyMarkup InlineKeyboardMarkup `json:"reply_markup,omitempty"`
|
ReplyMarkup InlineKeyboardMarkup `json:"reply_markup,omitempty"`
|
||||||
|
Photo string `json:"photo"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Bot) SendPhoto(params *SendPhotoP) (*Message, error) {
|
func (b *Bot) SendPhoto(params *SendPhotoP) (*Message, error) {
|
||||||
@@ -138,3 +153,45 @@ func (b *Bot) DeleteMessage(params *DeleteMessageP) (bool, error) {
|
|||||||
}
|
}
|
||||||
return *ok, 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
|
||||||
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ type MsgContext struct {
|
|||||||
Update *Update
|
Update *Update
|
||||||
From *User
|
From *User
|
||||||
CallbackMsgId int
|
CallbackMsgId int
|
||||||
|
CallbackQueryId string
|
||||||
FromID int
|
FromID int
|
||||||
Prefix string
|
Prefix string
|
||||||
Text string
|
Text string
|
||||||
@@ -75,6 +76,10 @@ func (ctx *MsgContext) editPhotoText(messageId int, text string, kb *InlineKeybo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
func (m *AnswerMessage) EditCaption(text string) *AnswerMessage {
|
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")
|
||||||
|
return m
|
||||||
|
}
|
||||||
return m.ctx.editPhotoText(m.MessageID, text, nil)
|
return m.ctx.editPhotoText(m.MessageID, text, nil)
|
||||||
}
|
}
|
||||||
func (m *AnswerMessage) EditCaptionKeyboard(text string, kb *InlineKeyboard) *AnswerMessage {
|
func (m *AnswerMessage) EditCaptionKeyboard(text string, kb *InlineKeyboard) *AnswerMessage {
|
||||||
@@ -114,8 +119,8 @@ func (ctx *MsgContext) answerPhoto(photoId, text string, kb *InlineKeyboard) *An
|
|||||||
params := &SendPhotoP{
|
params := &SendPhotoP{
|
||||||
ChatID: ctx.Msg.Chat.ID,
|
ChatID: ctx.Msg.Chat.ID,
|
||||||
Caption: text,
|
Caption: text,
|
||||||
Photo: photoId,
|
|
||||||
ParseMode: ParseMD,
|
ParseMode: ParseMD,
|
||||||
|
Photo: photoId,
|
||||||
}
|
}
|
||||||
if kb != nil {
|
if kb != nil {
|
||||||
params.ReplyMarkup = kb.Get()
|
params.ReplyMarkup = kb.Get()
|
||||||
@@ -123,6 +128,9 @@ func (ctx *MsgContext) answerPhoto(photoId, text string, kb *InlineKeyboard) *An
|
|||||||
msg, err := ctx.Bot.SendPhoto(params)
|
msg, err := ctx.Bot.SendPhoto(params)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.Bot.logger.Errorln(err)
|
ctx.Bot.logger.Errorln(err)
|
||||||
|
return &AnswerMessage{
|
||||||
|
ctx: ctx, Text: text, IsMedia: true,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return &AnswerMessage{
|
return &AnswerMessage{
|
||||||
MessageID: msg.MessageID, ctx: ctx, Text: text, IsMedia: true,
|
MessageID: msg.MessageID, ctx: ctx, Text: text, IsMedia: true,
|
||||||
@@ -151,14 +159,50 @@ func (ctx *MsgContext) CallbackDelete() {
|
|||||||
ctx.delete(ctx.CallbackMsgId)
|
ctx.delete(ctx.CallbackMsgId)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ctx *MsgContext) Error(err error) {
|
func (ctx *MsgContext) answerCallbackQuery(url, text string, showAlert bool) {
|
||||||
_, sendErr := ctx.Bot.SendMessage(&SendMessageP{
|
if len(ctx.CallbackQueryId) == 0 {
|
||||||
ChatID: ctx.Msg.Chat.ID,
|
return
|
||||||
Text: fmt.Sprintf(ctx.Bot.errorTemplate, EscapeMarkdown(err.Error())),
|
}
|
||||||
|
_, err := ctx.Bot.AnswerCallbackQuery(&AnswerCallbackQueryP{
|
||||||
|
CallbackQueryID: ctx.CallbackQueryId,
|
||||||
|
Text: text, ShowAlert: showAlert, URL: url,
|
||||||
})
|
})
|
||||||
|
if err != nil {
|
||||||
ctx.Bot.logger.Errorln(err)
|
ctx.Bot.logger.Errorln(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func (ctx *MsgContext) AnswerCbQuery() {
|
||||||
|
ctx.answerCallbackQuery("", "", false)
|
||||||
|
}
|
||||||
|
func (ctx *MsgContext) AnswerCbQueryText(text string) {
|
||||||
|
ctx.answerCallbackQuery("", text, false)
|
||||||
|
}
|
||||||
|
func (ctx *MsgContext) AnswerCbQueryAlert(text string) {
|
||||||
|
ctx.answerCallbackQuery("", text, true)
|
||||||
|
}
|
||||||
|
func (ctx *MsgContext) AnswerCbQueryUrl(u string) {
|
||||||
|
ctx.answerCallbackQuery(u, "", false)
|
||||||
|
}
|
||||||
|
|
||||||
if sendErr != nil {
|
func (ctx *MsgContext) SendAction(action ChatActions) {
|
||||||
ctx.Bot.logger.Errorln(sendErr)
|
_, err := ctx.Bot.SendChatAction(SendChatActionP{
|
||||||
|
ChatID: ctx.Msg.Chat.ID, Action: action,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
ctx.Bot.logger.Errorln(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ctx *MsgContext) error(err error) {
|
||||||
|
text := fmt.Sprintf(ctx.Bot.errorTemplate, EscapeMarkdown(err.Error()))
|
||||||
|
|
||||||
|
if ctx.CallbackQueryId != "" {
|
||||||
|
ctx.answerCallbackQuery("", text, false)
|
||||||
|
} else {
|
||||||
|
ctx.answer(text, nil)
|
||||||
|
}
|
||||||
|
ctx.Bot.logger.Errorln(err)
|
||||||
|
}
|
||||||
|
func (ctx *MsgContext) Error(err error) {
|
||||||
|
ctx.error(err)
|
||||||
|
}
|
||||||
|
|||||||
131
multipart.go
Normal file
131
multipart.go
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
package laniakea
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"mime/multipart"
|
||||||
|
"reflect"
|
||||||
|
"slices"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Encode[T any](w *multipart.Writer, req T) error {
|
||||||
|
v := reflect.ValueOf(req)
|
||||||
|
if v.Kind() == reflect.Ptr {
|
||||||
|
v = v.Elem()
|
||||||
|
}
|
||||||
|
|
||||||
|
if v.Kind() != reflect.Struct {
|
||||||
|
return fmt.Errorf("req must be a struct")
|
||||||
|
}
|
||||||
|
|
||||||
|
t := v.Type()
|
||||||
|
for i := 0; i < v.NumField(); i++ {
|
||||||
|
field := v.Field(i)
|
||||||
|
fieldType := t.Field(i)
|
||||||
|
|
||||||
|
formTags := strings.Split(fieldType.Tag.Get("json"), ",")
|
||||||
|
fieldName := ""
|
||||||
|
if len(formTags) == 0 {
|
||||||
|
formTags = strings.Split(fieldType.Tag.Get("json"), ",")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(formTags) > 0 {
|
||||||
|
fieldName = formTags[0]
|
||||||
|
if fieldName == "-" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if slices.Index(formTags, "omitempty") >= 0 {
|
||||||
|
if field.IsZero() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fieldName = strings.ToLower(fieldType.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
fw io.Writer
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
switch field.Kind() {
|
||||||
|
case reflect.String:
|
||||||
|
if field.String() != "" {
|
||||||
|
fw, err = w.CreateFormField(fieldName)
|
||||||
|
if err == nil {
|
||||||
|
_, err = fw.Write([]byte(field.String()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||||
|
fw, err = w.CreateFormField(fieldName)
|
||||||
|
if err == nil {
|
||||||
|
_, err = fw.Write([]byte(strconv.FormatInt(field.Int(), 10)))
|
||||||
|
}
|
||||||
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||||
|
fw, err = w.CreateFormField(fieldName)
|
||||||
|
if err == nil {
|
||||||
|
_, err = fw.Write([]byte(strconv.FormatUint(field.Uint(), 10)))
|
||||||
|
}
|
||||||
|
case reflect.Float32, reflect.Float64:
|
||||||
|
fw, err = w.CreateFormField(fieldName)
|
||||||
|
if err == nil {
|
||||||
|
_, err = fw.Write([]byte(strconv.FormatFloat(field.Float(), 'f', -1, 64)))
|
||||||
|
}
|
||||||
|
case reflect.Bool:
|
||||||
|
fw, err = w.CreateFormField(fieldName)
|
||||||
|
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")
|
||||||
|
if filename == "" {
|
||||||
|
filename = fieldName
|
||||||
|
}
|
||||||
|
|
||||||
|
ext := ""
|
||||||
|
filename = filename + ext
|
||||||
|
|
||||||
|
fw, err = w.CreateFormFile(fieldName, filename)
|
||||||
|
if err == nil {
|
||||||
|
_, err = fw.Write(field.Bytes())
|
||||||
|
}
|
||||||
|
} else if !field.IsNil() {
|
||||||
|
// Handle slice of primitive values (as multiple form fields with the same name)
|
||||||
|
for j := 0; j < field.Len(); j++ {
|
||||||
|
elem := field.Index(j)
|
||||||
|
fw, err = w.CreateFormField(fieldName)
|
||||||
|
if err == nil {
|
||||||
|
switch elem.Kind() {
|
||||||
|
case reflect.String:
|
||||||
|
_, err = fw.Write([]byte(elem.String()))
|
||||||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||||
|
_, err = fw.Write([]byte(strconv.FormatInt(elem.Int(), 10)))
|
||||||
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||||
|
_, err = fw.Write([]byte(strconv.FormatUint(elem.Uint(), 10)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case reflect.Struct:
|
||||||
|
var jsonData []byte
|
||||||
|
jsonData, err = json.Marshal(field.Interface())
|
||||||
|
if err == nil {
|
||||||
|
fw, err = w.CreateFormField(fieldName)
|
||||||
|
if err == nil {
|
||||||
|
_, err = fw.Write(jsonData)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
70
slice.go
Normal file
70
slice.go
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
package laniakea
|
||||||
|
|
||||||
|
type Slice[T any] []T
|
||||||
|
|
||||||
|
func NewSliceFrom[T any](slice []T) Slice[T] {
|
||||||
|
s := make(Slice[T], len(slice))
|
||||||
|
copy(s[:], slice)
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
func (s Slice[T]) Len() int {
|
||||||
|
return len(s)
|
||||||
|
}
|
||||||
|
func (s Slice[T]) Cap() int {
|
||||||
|
return cap(s)
|
||||||
|
}
|
||||||
|
func (s Slice[T]) Get(index int) T {
|
||||||
|
return s[index]
|
||||||
|
}
|
||||||
|
func (s Slice[T]) Last() T {
|
||||||
|
return s.Get(s.Len() - 1)
|
||||||
|
}
|
||||||
|
func (s Slice[T]) Swap(i, j int) Slice[T] {
|
||||||
|
s[i], s[j] = s[j], s[i]
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
func (s Slice[T]) Filter(f func(e T) bool) Slice[T] {
|
||||||
|
out := make(Slice[T], 0)
|
||||||
|
for _, v := range s {
|
||||||
|
if f(v) {
|
||||||
|
out = append(out, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
func (s Slice[T]) Map(f func(e T) T) Slice[T] {
|
||||||
|
out := make(Slice[T], s.Len())
|
||||||
|
for i, v := range s {
|
||||||
|
out[i] = f(v)
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
func (s Slice[T]) Pop(index int) Slice[T] {
|
||||||
|
if index == 0 {
|
||||||
|
return s[1:]
|
||||||
|
}
|
||||||
|
out := make(Slice[T], s.Len()-index)
|
||||||
|
for i, e := range s {
|
||||||
|
if i == index {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
out[i] = e
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
func (s Slice[T]) Push(e T) Slice[T] {
|
||||||
|
return append(s, e)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Slice[T]) ToArray() []T {
|
||||||
|
out := make([]T, len(s))
|
||||||
|
copy(out, s)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
func (s Slice[T]) ToAnyArray() []any {
|
||||||
|
out := make([]any, len(s))
|
||||||
|
for i, v := range s {
|
||||||
|
out[i] = v
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
22
types.go
22
types.go
@@ -54,7 +54,7 @@ type Message struct {
|
|||||||
Chat *Chat `json:"chat,omitempty"`
|
Chat *Chat `json:"chat,omitempty"`
|
||||||
Text string `json:"text"`
|
Text string `json:"text"`
|
||||||
|
|
||||||
Photo []*PhotoSize `json:"photo,omitempty"`
|
Photo Slice[*PhotoSize] `json:"photo,omitempty"`
|
||||||
Caption string `json:"caption,omitempty"`
|
Caption string `json:"caption,omitempty"`
|
||||||
ReplyToMessage *Message `json:"reply_to_message"`
|
ReplyToMessage *Message `json:"reply_to_message"`
|
||||||
|
|
||||||
@@ -172,3 +172,23 @@ type ReactionCount struct {
|
|||||||
Type *ReactionType `json:"type"`
|
Type *ReactionType `json:"type"`
|
||||||
TotalCount int `json:"total_count"`
|
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"
|
||||||
|
)
|
||||||
|
|||||||
136
uploader.go
Normal file
136
uploader.go
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
4
utils.go
4
utils.go
@@ -41,12 +41,12 @@ func MapToJson(m map[string]any) (string, error) {
|
|||||||
return string(data), err
|
return string(data), err
|
||||||
}
|
}
|
||||||
|
|
||||||
func StructToMap(s interface{}) (map[string]interface{}, error) {
|
func StructToMap(s any) (map[string]any, error) {
|
||||||
data, err := json.Marshal(s)
|
data, err := json.Marshal(s)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
m := make(map[string]interface{})
|
m := make(map[string]any)
|
||||||
err = json.Unmarshal(data, &m)
|
err = json.Unmarshal(data, &m)
|
||||||
return m, err
|
return m, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
package laniakea
|
package laniakea
|
||||||
|
|
||||||
const (
|
const (
|
||||||
VersionString = "0.3.2"
|
VersionString = "0.3.7"
|
||||||
VersionMajor = 0
|
VersionMajor = 0
|
||||||
VersionMinor = 3
|
VersionMinor = 3
|
||||||
VersionPatch = 2
|
VersionPatch = 7
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user