working file uploads, starting to clean up code
parent
564e04b9a2
commit
059baffbfb
2
bot.go
2
bot.go
|
@ -52,6 +52,8 @@ func (plugin *ColonThree) GotCommand(message Message, args []string) {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
bot.sendMessage(NewMessage(message.Chat.Id, ":3"))
|
bot.sendMessage(NewMessage(message.Chat.Id, ":3"))
|
||||||
|
|
||||||
|
bot.sendPhoto(NewPhotoUpload(message.Chat.Id, "fox.png"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
363
methods.go
363
methods.go
|
@ -1,11 +1,15 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
|
"mime/multipart"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -29,173 +33,6 @@ type BotApi struct {
|
||||||
config BotConfig
|
config BotConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
type ApiResponse struct {
|
|
||||||
Ok bool `json:"ok"`
|
|
||||||
Result json.RawMessage `json:"result"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Update struct {
|
|
||||||
UpdateId int `json:"update_id"`
|
|
||||||
Message Message `json:"message"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type User struct {
|
|
||||||
Id int `json:"id"`
|
|
||||||
FirstName string `json:"first_name"`
|
|
||||||
LastName string `json:"last_name"`
|
|
||||||
UserName string `json:"username"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type GroupChat struct {
|
|
||||||
Id int `json:"id"`
|
|
||||||
Title string `json:"title"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type UserOrGroupChat struct {
|
|
||||||
Id int `json:"id"`
|
|
||||||
FirstName string `json:"first_name"`
|
|
||||||
LastName string `json:"last_name"`
|
|
||||||
UserName string `json:"username"`
|
|
||||||
Title string `json:"title"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Message struct {
|
|
||||||
MessageId int `json:"message_id"`
|
|
||||||
From User `json:"from"`
|
|
||||||
Date int `json:"date"`
|
|
||||||
Chat UserOrGroupChat `json:"chat"`
|
|
||||||
ForwardFrom User `json:"forward_from"`
|
|
||||||
ForwardDate int `json:"forward_date"`
|
|
||||||
ReplyToMessage *Message `json:"reply_to_message"`
|
|
||||||
Text string `json:"text"`
|
|
||||||
Audio Audio `json:"audio"`
|
|
||||||
Document Document `json:"document"`
|
|
||||||
Photo []PhotoSize `json:"photo"`
|
|
||||||
Sticker Sticker `json:"sticker"`
|
|
||||||
Video Video `json:"video"`
|
|
||||||
Contact Contact `json:"contact"`
|
|
||||||
Location Location `json:"location"`
|
|
||||||
NewChatParticipant User `json:"new_chat_participant"`
|
|
||||||
LeftChatParticipant User `json:"left_chat_participant"`
|
|
||||||
NewChatTitle string `json:"new_chat_title"`
|
|
||||||
NewChatPhoto string `json:"new_chat_photo"`
|
|
||||||
DeleteChatPhoto bool `json:"delete_chat_photo"`
|
|
||||||
GroupChatCreated bool `json:"group_chat_created"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type PhotoSize struct {
|
|
||||||
FileId string `json:"file_id"`
|
|
||||||
Width int `json:"width"`
|
|
||||||
Height int `json:"height"`
|
|
||||||
FileSize int `json:"file_size"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Audio struct {
|
|
||||||
FileId string `json:"file_id"`
|
|
||||||
Duration int `json:"duration"`
|
|
||||||
MimeType string `json:"mime_type"`
|
|
||||||
FileSize int `json:"file_size"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Document struct {
|
|
||||||
FileId string `json:"file_id"`
|
|
||||||
Thumb PhotoSize `json:"thumb"`
|
|
||||||
FileName string `json:"file_name"`
|
|
||||||
MimeType string `json:"mime_type"`
|
|
||||||
FileSize int `json:"file_size"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Sticker struct {
|
|
||||||
FileId string `json:"file_id"`
|
|
||||||
Width int `json:"width"`
|
|
||||||
Height int `json:"height"`
|
|
||||||
Thumb PhotoSize `json:"thumb"`
|
|
||||||
FileSize int `json:"file_size"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Video struct {
|
|
||||||
FileId string `json:"file_id"`
|
|
||||||
Width int `json:"width"`
|
|
||||||
Height int `json:"height"`
|
|
||||||
Duration int `json:"duration"`
|
|
||||||
Thumb PhotoSize `json:"thumb"`
|
|
||||||
MimeType string `json:"mime_type"`
|
|
||||||
FileSize int `json:"file_size"`
|
|
||||||
Caption string `json:"caption"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Contact struct {
|
|
||||||
PhoneNumber string `json:"phone_number"`
|
|
||||||
FirstName string `json:"first_name"`
|
|
||||||
LastName string `json:"last_name"`
|
|
||||||
UserId string `json:"user_id"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Location struct {
|
|
||||||
Longitude float32 `json:"longitude"`
|
|
||||||
Latitude float32 `json:"latitude"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type UserProfilePhotos struct {
|
|
||||||
TotalCount int `json:"total_count"`
|
|
||||||
Photos []PhotoSize `json:"photos"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ReplyKeyboardMarkup struct {
|
|
||||||
Keyboard map[string]map[string]string `json:"keyboard"`
|
|
||||||
ResizeKeyboard bool `json:"resize_keyboard"`
|
|
||||||
OneTimeKeyboard bool `json:"one_time_keyboard"`
|
|
||||||
Selective bool `json:"selective"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ReplyKeyboardHide struct {
|
|
||||||
HideKeyboard bool `json:"hide_keyboard"`
|
|
||||||
Selective bool `json:"selective"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ForceReply struct {
|
|
||||||
ForceReply bool `json:"force_reply"`
|
|
||||||
Selective bool `json:"force_reply"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type UpdateConfig struct {
|
|
||||||
Offset int
|
|
||||||
Limit int
|
|
||||||
Timeout int
|
|
||||||
}
|
|
||||||
|
|
||||||
type MessageConfig struct {
|
|
||||||
ChatId int
|
|
||||||
Text string
|
|
||||||
DisableWebPagePreview bool
|
|
||||||
ReplyToMessageId int
|
|
||||||
}
|
|
||||||
|
|
||||||
type ForwardConfig struct {
|
|
||||||
ChatId int
|
|
||||||
FromChatId int
|
|
||||||
MessageId int
|
|
||||||
}
|
|
||||||
|
|
||||||
type LocationConfig struct {
|
|
||||||
ChatId int
|
|
||||||
Latitude float64
|
|
||||||
Longitude float64
|
|
||||||
ReplyToMessageId int
|
|
||||||
ReplyMarkup interface{}
|
|
||||||
}
|
|
||||||
|
|
||||||
type ChatActionConfig struct {
|
|
||||||
ChatId int
|
|
||||||
Action string
|
|
||||||
}
|
|
||||||
|
|
||||||
type UserProfilePhotosConfig struct {
|
|
||||||
UserId int
|
|
||||||
Offset int
|
|
||||||
Limit int
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewBotApi(config BotConfig) *BotApi {
|
func NewBotApi(config BotConfig) *BotApi {
|
||||||
return &BotApi{
|
return &BotApi{
|
||||||
config: config,
|
config: config,
|
||||||
|
@ -211,7 +48,65 @@ func (bot *BotApi) makeRequest(endpoint string, params url.Values) (ApiResponse,
|
||||||
|
|
||||||
bytes, err := ioutil.ReadAll(resp.Body)
|
bytes, err := ioutil.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ApiResponse{}, nil
|
return ApiResponse{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if bot.config.debug {
|
||||||
|
log.Println(string(bytes[:]))
|
||||||
|
}
|
||||||
|
|
||||||
|
var apiResp ApiResponse
|
||||||
|
json.Unmarshal(bytes, &apiResp)
|
||||||
|
|
||||||
|
return apiResp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bot *BotApi) uploadFile(endpoint string, params map[string]string, fieldname string, filename string) (ApiResponse, error) {
|
||||||
|
var b bytes.Buffer
|
||||||
|
w := multipart.NewWriter(&b)
|
||||||
|
|
||||||
|
f, err := os.Open(filename)
|
||||||
|
if err != nil {
|
||||||
|
return ApiResponse{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
fw, err := w.CreateFormFile(fieldname, filename)
|
||||||
|
if err != nil {
|
||||||
|
return ApiResponse{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err = io.Copy(fw, f); err != nil {
|
||||||
|
return ApiResponse{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for key, val := range params {
|
||||||
|
if fw, err = w.CreateFormField(key); err != nil {
|
||||||
|
return ApiResponse{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err = fw.Write([]byte(val)); err != nil {
|
||||||
|
return ApiResponse{}, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Close()
|
||||||
|
|
||||||
|
req, err := http.NewRequest("POST", "https://api.telegram.org/bot"+bot.config.token+"/"+endpoint, &b)
|
||||||
|
if err != nil {
|
||||||
|
return ApiResponse{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Header.Set("Content-Type", w.FormDataContentType())
|
||||||
|
|
||||||
|
client := &http.Client{}
|
||||||
|
res, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return ApiResponse{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
bytes, err := ioutil.ReadAll(res.Body)
|
||||||
|
if err != nil {
|
||||||
|
return ApiResponse{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if bot.config.debug {
|
if bot.config.debug {
|
||||||
|
@ -287,6 +182,74 @@ func (bot *BotApi) forwardMessage(config ForwardConfig) (Message, error) {
|
||||||
return message, nil
|
return message, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (bot *BotApi) sendPhoto(config PhotoConfig) (Message, error) {
|
||||||
|
if config.UseExistingPhoto {
|
||||||
|
v := url.Values{}
|
||||||
|
v.Add("chat_id", strconv.Itoa(config.ChatId))
|
||||||
|
v.Add("photo", config.FileId)
|
||||||
|
if config.Caption != "" {
|
||||||
|
v.Add("caption", config.Caption)
|
||||||
|
}
|
||||||
|
if config.ReplyToMessageId != 0 {
|
||||||
|
v.Add("reply_to_message_id", strconv.Itoa(config.ChatId))
|
||||||
|
}
|
||||||
|
if config.ReplyMarkup != nil {
|
||||||
|
data, err := json.Marshal(config.ReplyMarkup)
|
||||||
|
if err != nil {
|
||||||
|
return Message{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
v.Add("reply_markup", string(data))
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := bot.makeRequest("sendPhoto", v)
|
||||||
|
if err != nil {
|
||||||
|
return Message{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var message Message
|
||||||
|
json.Unmarshal(resp.Result, &message)
|
||||||
|
|
||||||
|
if bot.config.debug {
|
||||||
|
log.Printf("sendPhoto req : %+v\n", v)
|
||||||
|
log.Printf("sendPhoto resp: %+v\n", message)
|
||||||
|
}
|
||||||
|
|
||||||
|
return message, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
params := make(map[string]string)
|
||||||
|
params["chat_id"] = strconv.Itoa(config.ChatId)
|
||||||
|
if config.Caption != "" {
|
||||||
|
params["caption"] = config.Caption
|
||||||
|
}
|
||||||
|
if config.ReplyToMessageId != 0 {
|
||||||
|
params["reply_to_message_id"] = strconv.Itoa(config.ReplyToMessageId)
|
||||||
|
}
|
||||||
|
if config.ReplyMarkup != nil {
|
||||||
|
data, err := json.Marshal(config.ReplyMarkup)
|
||||||
|
if err != nil {
|
||||||
|
return Message{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
params["reply_markup"] = string(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := bot.uploadFile("sendPhoto", params, "photo", config.FilePath)
|
||||||
|
if err != nil {
|
||||||
|
return Message{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var message Message
|
||||||
|
json.Unmarshal(resp.Result, &message)
|
||||||
|
|
||||||
|
if bot.config.debug {
|
||||||
|
log.Printf("sendPhoto resp: %+v\n", message)
|
||||||
|
}
|
||||||
|
|
||||||
|
return message, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (bot *BotApi) sendLocation(config LocationConfig) (Message, error) {
|
func (bot *BotApi) sendLocation(config LocationConfig) (Message, error) {
|
||||||
v := url.Values{}
|
v := url.Values{}
|
||||||
v.Add("chat_id", strconv.Itoa(config.ChatId))
|
v.Add("chat_id", strconv.Itoa(config.ChatId))
|
||||||
|
@ -392,6 +355,54 @@ func (bot *BotApi) setWebhook(v url.Values) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type UpdateConfig struct {
|
||||||
|
Offset int
|
||||||
|
Limit int
|
||||||
|
Timeout int
|
||||||
|
}
|
||||||
|
|
||||||
|
type MessageConfig struct {
|
||||||
|
ChatId int
|
||||||
|
Text string
|
||||||
|
DisableWebPagePreview bool
|
||||||
|
ReplyToMessageId int
|
||||||
|
}
|
||||||
|
|
||||||
|
type ForwardConfig struct {
|
||||||
|
ChatId int
|
||||||
|
FromChatId int
|
||||||
|
MessageId int
|
||||||
|
}
|
||||||
|
|
||||||
|
type PhotoConfig struct {
|
||||||
|
ChatId int
|
||||||
|
Caption string
|
||||||
|
ReplyToMessageId int
|
||||||
|
ReplyMarkup interface{}
|
||||||
|
UseExistingPhoto bool
|
||||||
|
FilePath string
|
||||||
|
FileId string
|
||||||
|
}
|
||||||
|
|
||||||
|
type LocationConfig struct {
|
||||||
|
ChatId int
|
||||||
|
Latitude float64
|
||||||
|
Longitude float64
|
||||||
|
ReplyToMessageId int
|
||||||
|
ReplyMarkup interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
type ChatActionConfig struct {
|
||||||
|
ChatId int
|
||||||
|
Action string
|
||||||
|
}
|
||||||
|
|
||||||
|
type UserProfilePhotosConfig struct {
|
||||||
|
UserId int
|
||||||
|
Offset int
|
||||||
|
Limit int
|
||||||
|
}
|
||||||
|
|
||||||
func NewMessage(chatId int, text string) MessageConfig {
|
func NewMessage(chatId int, text string) MessageConfig {
|
||||||
return MessageConfig{
|
return MessageConfig{
|
||||||
ChatId: chatId,
|
ChatId: chatId,
|
||||||
|
@ -409,6 +420,22 @@ func NewForward(chatId int, fromChatId int, messageId int) ForwardConfig {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func NewPhotoUpload(chatId int, filename string) PhotoConfig {
|
||||||
|
return PhotoConfig{
|
||||||
|
ChatId: chatId,
|
||||||
|
UseExistingPhoto: false,
|
||||||
|
FilePath: filename,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewPhotoShare(chatId int, fileId string) PhotoConfig {
|
||||||
|
return PhotoConfig{
|
||||||
|
ChatId: chatId,
|
||||||
|
UseExistingPhoto: true,
|
||||||
|
FileId: fileId,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func NewLocation(chatId int, latitude float64, longitude float64) LocationConfig {
|
func NewLocation(chatId int, latitude float64, longitude float64) LocationConfig {
|
||||||
return LocationConfig{
|
return LocationConfig{
|
||||||
ChatId: chatId,
|
ChatId: chatId,
|
||||||
|
|
|
@ -0,0 +1,134 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ApiResponse struct {
|
||||||
|
Ok bool `json:"ok"`
|
||||||
|
Result json.RawMessage `json:"result"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Update struct {
|
||||||
|
UpdateId int `json:"update_id"`
|
||||||
|
Message Message `json:"message"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type User struct {
|
||||||
|
Id int `json:"id"`
|
||||||
|
FirstName string `json:"first_name"`
|
||||||
|
LastName string `json:"last_name"`
|
||||||
|
UserName string `json:"username"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GroupChat struct {
|
||||||
|
Id int `json:"id"`
|
||||||
|
Title string `json:"title"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UserOrGroupChat struct {
|
||||||
|
Id int `json:"id"`
|
||||||
|
FirstName string `json:"first_name"`
|
||||||
|
LastName string `json:"last_name"`
|
||||||
|
UserName string `json:"username"`
|
||||||
|
Title string `json:"title"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Message struct {
|
||||||
|
MessageId int `json:"message_id"`
|
||||||
|
From User `json:"from"`
|
||||||
|
Date int `json:"date"`
|
||||||
|
Chat UserOrGroupChat `json:"chat"`
|
||||||
|
ForwardFrom User `json:"forward_from"`
|
||||||
|
ForwardDate int `json:"forward_date"`
|
||||||
|
ReplyToMessage *Message `json:"reply_to_message"`
|
||||||
|
Text string `json:"text"`
|
||||||
|
Audio Audio `json:"audio"`
|
||||||
|
Document Document `json:"document"`
|
||||||
|
Photo []PhotoSize `json:"photo"`
|
||||||
|
Sticker Sticker `json:"sticker"`
|
||||||
|
Video Video `json:"video"`
|
||||||
|
Contact Contact `json:"contact"`
|
||||||
|
Location Location `json:"location"`
|
||||||
|
NewChatParticipant User `json:"new_chat_participant"`
|
||||||
|
LeftChatParticipant User `json:"left_chat_participant"`
|
||||||
|
NewChatTitle string `json:"new_chat_title"`
|
||||||
|
NewChatPhoto string `json:"new_chat_photo"`
|
||||||
|
DeleteChatPhoto bool `json:"delete_chat_photo"`
|
||||||
|
GroupChatCreated bool `json:"group_chat_created"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type PhotoSize struct {
|
||||||
|
FileId string `json:"file_id"`
|
||||||
|
Width int `json:"width"`
|
||||||
|
Height int `json:"height"`
|
||||||
|
FileSize int `json:"file_size"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Audio struct {
|
||||||
|
FileId string `json:"file_id"`
|
||||||
|
Duration int `json:"duration"`
|
||||||
|
MimeType string `json:"mime_type"`
|
||||||
|
FileSize int `json:"file_size"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Document struct {
|
||||||
|
FileId string `json:"file_id"`
|
||||||
|
Thumb PhotoSize `json:"thumb"`
|
||||||
|
FileName string `json:"file_name"`
|
||||||
|
MimeType string `json:"mime_type"`
|
||||||
|
FileSize int `json:"file_size"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Sticker struct {
|
||||||
|
FileId string `json:"file_id"`
|
||||||
|
Width int `json:"width"`
|
||||||
|
Height int `json:"height"`
|
||||||
|
Thumb PhotoSize `json:"thumb"`
|
||||||
|
FileSize int `json:"file_size"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Video struct {
|
||||||
|
FileId string `json:"file_id"`
|
||||||
|
Width int `json:"width"`
|
||||||
|
Height int `json:"height"`
|
||||||
|
Duration int `json:"duration"`
|
||||||
|
Thumb PhotoSize `json:"thumb"`
|
||||||
|
MimeType string `json:"mime_type"`
|
||||||
|
FileSize int `json:"file_size"`
|
||||||
|
Caption string `json:"caption"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Contact struct {
|
||||||
|
PhoneNumber string `json:"phone_number"`
|
||||||
|
FirstName string `json:"first_name"`
|
||||||
|
LastName string `json:"last_name"`
|
||||||
|
UserId string `json:"user_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Location struct {
|
||||||
|
Longitude float32 `json:"longitude"`
|
||||||
|
Latitude float32 `json:"latitude"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UserProfilePhotos struct {
|
||||||
|
TotalCount int `json:"total_count"`
|
||||||
|
Photos []PhotoSize `json:"photos"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ReplyKeyboardMarkup struct {
|
||||||
|
Keyboard map[string]map[string]string `json:"keyboard"`
|
||||||
|
ResizeKeyboard bool `json:"resize_keyboard"`
|
||||||
|
OneTimeKeyboard bool `json:"one_time_keyboard"`
|
||||||
|
Selective bool `json:"selective"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ReplyKeyboardHide struct {
|
||||||
|
HideKeyboard bool `json:"hide_keyboard"`
|
||||||
|
Selective bool `json:"selective"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ForceReply struct {
|
||||||
|
ForceReply bool `json:"force_reply"`
|
||||||
|
Selective bool `json:"force_reply"`
|
||||||
|
}
|
Loading…
Reference in New Issue