From 434756c165a9b43d750eb56e6a4eaff067f1bcbb Mon Sep 17 00:00:00 2001 From: Syfaro Date: Thu, 25 Jun 2015 00:34:05 -0500 Subject: [PATCH] initial commit, some api methods and structs for everything --- bot.go | 109 +++++++++++++ methods.go | 443 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 552 insertions(+) create mode 100644 bot.go create mode 100644 methods.go diff --git a/bot.go b/bot.go new file mode 100644 index 0000000..fd9f924 --- /dev/null +++ b/bot.go @@ -0,0 +1,109 @@ +package main + +import ( + "encoding/json" + "flag" + "io/ioutil" + "log" + "strconv" + "strings" + "time" +) + +type Config struct { + Token string `json:"token"` +} + +type Plugin interface { + GetCommand() string + GotCommand(Message, []string) +} + +type ColonThree struct { +} + +func (plugin *ColonThree) GetCommand() string { + return "/three" +} + +func (plugin *ColonThree) GotCommand(message Message, args []string) { + if len(args) > 0 { + n, err := strconv.Atoi(args[0]) + if err != nil { + msg := NewMessage(message.Chat.Id, "Bad number!") + msg.ReplyToMessageId = message.MessageId + + bot.sendMessage(msg) + + return + } + + if n > 5 { + msg := NewMessage(message.Chat.Id, "That's a bit much, no?") + msg.ReplyToMessageId = message.MessageId + + bot.sendMessage(msg) + + return + } + + for i := 0; i < n; i++ { + bot.sendMessage(NewMessage(message.Chat.Id, ":3")) + } + } else { + bot.sendMessage(NewMessage(message.Chat.Id, ":3")) + } +} + +var bot *BotApi + +func main() { + configPath := flag.String("config", "config.json", "path to config.json") + + flag.Parse() + + data, err := ioutil.ReadFile(*configPath) + if err != nil { + log.Panic(err) + } + + var cfg Config + json.Unmarshal(data, &cfg) + + bot = NewBotApi(BotConfig{ + token: cfg.Token, + debug: true, + }) + + plugins := []Plugin{&ColonThree{}} + + ticker := time.NewTicker(5 * time.Second) + + lastUpdate := 0 + + for range ticker.C { + updates, err := bot.getUpdates(NewUpdate(lastUpdate + 1)) + + if err != nil { + log.Panic(err) + } + + for _, update := range updates { + lastUpdate = update.UpdateId + + if update.Message.Text == "" { + continue + } + + for _, plugin := range plugins { + parts := strings.Split(update.Message.Text, " ") + + if plugin.GetCommand() == parts[0] { + parts = append(parts[:0], parts[1:]...) + + plugin.GotCommand(update.Message, parts) + } + } + } + } +} diff --git a/methods.go b/methods.go new file mode 100644 index 0000000..a04c1d8 --- /dev/null +++ b/methods.go @@ -0,0 +1,443 @@ +package main + +import ( + "encoding/json" + "io/ioutil" + "log" + "net/http" + "net/url" + "strconv" +) + +const ( + CHAT_TYPING = "typing" + CHAT_UPLOAD_PHOTO = "upload_photo" + CHAT_RECORD_VIDEO = "record_video" + CHAT_UPLOAD_VIDEO = "upload_video" + CHAT_RECORD_AUDIO = "record_audio" + CHAT_UPLOAD_AUDIO = "upload_audio" + CHAT_UPLOAD_DOCUMENT = "upload_document" + CHAT_FIND_LOCATION = "find_location" +) + +type BotConfig struct { + token string + debug bool +} + +type BotApi struct { + 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 { + return &BotApi{ + config: config, + } +} + +func (bot *BotApi) makeRequest(endpoint string, params url.Values) (ApiResponse, error) { + resp, err := http.PostForm("https://api.telegram.org/bot"+bot.config.token+"/"+endpoint, params) + defer resp.Body.Close() + if err != nil { + return ApiResponse{}, err + } + + bytes, err := ioutil.ReadAll(resp.Body) + if err != nil { + return ApiResponse{}, nil + } + + if bot.config.debug { + log.Println(string(bytes[:])) + } + + var apiResp ApiResponse + json.Unmarshal(bytes, &apiResp) + + return apiResp, nil +} + +func (bot *BotApi) getMe() (User, error) { + resp, err := bot.makeRequest("getMe", nil) + if err != nil { + return User{}, err + } + + var user User + json.Unmarshal(resp.Result, &user) + + if bot.config.debug { + log.Printf("getMe: %+v\n", user) + } + + return user, nil +} + +func (bot *BotApi) sendMessage(config MessageConfig) (Message, error) { + v := url.Values{} + v.Add("chat_id", strconv.Itoa(config.ChatId)) + v.Add("text", config.Text) + v.Add("disable_web_page_preview", strconv.FormatBool(config.DisableWebPagePreview)) + if config.ReplyToMessageId != 0 { + v.Add("reply_to_message_id", strconv.Itoa(config.ReplyToMessageId)) + } + + resp, err := bot.makeRequest("sendMessage", v) + if err != nil { + return Message{}, err + } + + var message Message + json.Unmarshal(resp.Result, &message) + + if bot.config.debug { + log.Printf("sendMessage req : %+v\n", v) + log.Printf("sendMessage resp: %+v\n", message) + } + + return message, nil +} + +func (bot *BotApi) forwardMessage(config ForwardConfig) (Message, error) { + v := url.Values{} + v.Add("chat_id", strconv.Itoa(config.ChatId)) + v.Add("from_chat_id", strconv.Itoa(config.FromChatId)) + v.Add("message_id", strconv.Itoa(config.MessageId)) + + resp, err := bot.makeRequest("forwardMessage", v) + if err != nil { + return Message{}, err + } + + var message Message + json.Unmarshal(resp.Result, &message) + + if bot.config.debug { + log.Printf("forwardMessage req : %+v\n", v) + log.Printf("forwardMessage resp: %+v\n", message) + } + + return message, nil +} + +func (bot *BotApi) sendLocation(config LocationConfig) (Message, error) { + v := url.Values{} + v.Add("chat_id", strconv.Itoa(config.ChatId)) + v.Add("latitude", strconv.FormatFloat(config.Latitude, 'f', 6, 64)) + v.Add("longitude", strconv.FormatFloat(config.Longitude, 'f', 6, 64)) + if config.ReplyToMessageId != 0 { + v.Add("reply_to_message_id", strconv.Itoa(config.ReplyToMessageId)) + } + 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("sendLocation", v) + if err != nil { + return Message{}, err + } + + var message Message + json.Unmarshal(resp.Result, &message) + + if bot.config.debug { + log.Printf("sendLocation req : %+v\n", v) + log.Printf("sendLocation resp: %+v\n", message) + } + + return message, nil +} + +func (bot *BotApi) sendChatAction(config ChatActionConfig) error { + v := url.Values{} + v.Add("chat_id", strconv.Itoa(config.ChatId)) + v.Add("action", config.Action) + + _, err := bot.makeRequest("sendChatAction", v) + if err != nil { + return err + } + + return nil +} + +func (bot *BotApi) getUserProfilePhotos(config UserProfilePhotosConfig) (UserProfilePhotos, error) { + v := url.Values{} + v.Add("user_id", strconv.Itoa(config.UserId)) + if config.Offset != 0 { + v.Add("offset", strconv.Itoa(config.Offset)) + } + if config.Limit != 0 { + v.Add("limit", strconv.Itoa(config.Limit)) + } + + resp, err := bot.makeRequest("getUserProfilePhotos", v) + if err != nil { + return UserProfilePhotos{}, err + } + + var profilePhotos UserProfilePhotos + json.Unmarshal(resp.Result, &profilePhotos) + + if bot.config.debug { + log.Printf("getUserProfilePhotos req : %+v\n", v) + log.Printf("getUserProfilePhotos resp: %+v\n", profilePhotos) + } + + return profilePhotos, nil +} + +func (bot *BotApi) getUpdates(config UpdateConfig) ([]Update, error) { + v := url.Values{} + if config.Offset > 0 { + v.Add("offset", strconv.Itoa(config.Offset)) + } + if config.Limit > 0 { + v.Add("limit", strconv.Itoa(config.Limit)) + } + if config.Timeout > 0 { + v.Add("timeout", strconv.Itoa(config.Timeout)) + } + + resp, err := bot.makeRequest("getUpdates", v) + if err != nil { + return []Update{}, err + } + + var updates []Update + json.Unmarshal(resp.Result, &updates) + + if bot.config.debug { + log.Printf("getUpdates: %+v\n", updates) + } + + return updates, nil +} + +func (bot *BotApi) setWebhook(v url.Values) error { + _, err := bot.makeRequest("setWebhook", v) + + return err +} + +func NewMessage(chatId int, text string) MessageConfig { + return MessageConfig{ + ChatId: chatId, + Text: text, + DisableWebPagePreview: false, + ReplyToMessageId: 0, + } +} + +func NewForward(chatId int, fromChatId int, messageId int) ForwardConfig { + return ForwardConfig{ + ChatId: chatId, + FromChatId: fromChatId, + MessageId: messageId, + } +} + +func NewLocation(chatId int, latitude float64, longitude float64) LocationConfig { + return LocationConfig{ + ChatId: chatId, + Latitude: latitude, + Longitude: longitude, + ReplyToMessageId: 0, + ReplyMarkup: nil, + } +} + +func NewChatAction(chatId int, action string) ChatActionConfig { + return ChatActionConfig{ + ChatId: chatId, + Action: action, + } +} + +func NewUserProfilePhotos(userId int) UserProfilePhotosConfig { + return UserProfilePhotosConfig{ + UserId: userId, + Offset: 0, + Limit: 0, + } +} + +func NewUpdate(offset int) UpdateConfig { + return UpdateConfig{ + Offset: offset, + Limit: 0, + Timeout: 0, + } +}