358 lines
13 KiB
Go
358 lines
13 KiB
Go
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
|
|
}
|
|
}
|