diff --git a/Makefile b/Makefile index a5b8661..d04d811 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -all: clessy-broker clessy-mods clessy-stats +all: tg-broker clessy-mods clessy-stats deps: go get github.com/boltdb/bolt/... @@ -8,10 +8,10 @@ deps: go get github.com/disintegration/imaging install-tg: - go install github.com/hamcha/clessy/tg + go get -u github.com/hamcha/tg -clessy-broker: install-tg - go build -o clessy-broker github.com/hamcha/clessy/broker +tg-broker: install-tg + go get -u github.com/hamcha/tg/cmd/tg-broker clessy-mods: install-tg go build -o clessy-mods github.com/hamcha/clessy/mods @@ -23,4 +23,4 @@ clessy-stats-import: install-tg go build -o clessy-stats-import github.com/hamcha/clessy/stats-import clean: - rm -f clessy-broker clessy-mods clessy-stats \ No newline at end of file + rm -f clessy-mods clessy-stats \ No newline at end of file diff --git a/broker/action.go b/broker/action.go deleted file mode 100644 index 826b6a6..0000000 --- a/broker/action.go +++ /dev/null @@ -1,27 +0,0 @@ -package main - -import ( - "net" - - "github.com/hamcha/clessy/tg" -) - -func executeClientCommand(action tg.ClientCommand, client net.Conn) { - switch action.Type { - case tg.CmdSendTextMessage: - data := *(action.TextMessageData) - api.SendTextMessage(data) - case tg.CmdGetFile: - data := *(action.FileRequestData) - api.GetFile(data, client, *action.Callback) - case tg.CmdSendPhoto: - data := *(action.PhotoData) - api.SendPhoto(data) - case tg.CmdForwardMessage: - data := *(action.ForwardMessageData) - api.ForwardMessage(data) - case tg.CmdSendChatAction: - data := *(action.ChatActionData) - api.SendChatAction(data) - } -} diff --git a/broker/clients.go b/broker/clients.go deleted file mode 100644 index d93687f..0000000 --- a/broker/clients.go +++ /dev/null @@ -1,79 +0,0 @@ -package main - -import ( - "bufio" - "encoding/json" - "fmt" - "log" - "net" - - "github.com/hamcha/clessy/tg" -) - -var clients []net.Conn - -func startClientsServer(bind string) { - listener, err := net.Listen("tcp", bind) - assert(err) - - // Accept loop - for { - c, err := listener.Accept() - if err != nil { - log.Printf("Can't accept client: %s\n", err.Error()) - continue - } - clients = append(clients, c) - go handleClient(c) - } -} - -func handleClient(c net.Conn) { - b := bufio.NewReader(c) - defer c.Close() - - // Start reading messages - buf := make([]byte, 0) - for { - bytes, isPrefix, err := b.ReadLine() - if err != nil { - break - } - buf = append(buf, bytes...) - if isPrefix { - continue - } - - // Get command - var cmd tg.ClientCommand - err = json.Unmarshal(buf, &cmd) - if err != nil { - log.Printf("[handleClient] Can't parse JSON: %s\r\n", err.Error()) - log.Printf("%s\n", string(buf)) - continue - } - - // Empty buffer - buf = []byte{} - - executeClientCommand(cmd, c) - } - removeCon(c) -} - -func removeCon(c net.Conn) { - for i, con := range clients { - if c == con { - clients = append(clients[:i], clients[i+1:]...) - } - } -} - -func broadcast(message string) { - for _, c := range clients { - _, err := fmt.Fprintln(c, message) - if err != nil { - removeCon(c) - } - } -} diff --git a/broker/main.go b/broker/main.go deleted file mode 100644 index df2469e..0000000 --- a/broker/main.go +++ /dev/null @@ -1,57 +0,0 @@ -package main - -import ( - "encoding/json" - "flag" - "log" - "net/http" - "os" -) - -// The Config data (parsed from JSON) -type Config struct { - BindServer string /* Address:Port to bind for Telegram */ - BindClients string /* Address:Port to bind for clients */ - Token string /* Telegram bot token */ - BaseURL string /* Base URL for webhook */ - WebhookURL string /* Webhook URL */ -} - -func assert(err error) { - if err != nil { - panic(err) - } -} - -var api *Telegram - -func main() { - cfgpath := flag.String("config", "config.json", "Path to configuration file") - flag.Parse() - - file, err := os.Open(*cfgpath) - assert(err) - - var config Config - err = json.NewDecoder(file).Decode(&config) - assert(err) - - // Create Telegram API object - api = mkAPI(config.Token) - - // Setup webhook handler - go func() { - log.Println("Starting webserver..") - http.HandleFunc(config.WebhookURL, webhook) - err := http.ListenAndServe(config.BindServer, nil) - assert(err) - }() - - // Register webhook @ Telegram - log.Println("Registering webhook..") - api.SetWebhook(config.BaseURL + config.WebhookURL) - - // Create server for clients - log.Println("Starting clients server..") - startClientsServer(config.BindClients) -} diff --git a/broker/telegram.go b/broker/telegram.go deleted file mode 100644 index cafcc03..0000000 --- a/broker/telegram.go +++ /dev/null @@ -1,220 +0,0 @@ -package main - -import ( - "bytes" - "encoding/base64" - "encoding/json" - "errors" - "fmt" - "io/ioutil" - "log" - "mime/multipart" - "net" - "net/http" - "net/url" - "strconv" - - "github.com/hamcha/clessy/tg" -) - -// APIEndpoint is Telegram's current Bot API base url endpoint -const APIEndpoint = "https://api.telegram.org/" - -// Telegram is the API client for the Telegram Bot API -type Telegram struct { - Token string -} - -// mkAPI creates a Telegram instance from a Bot API token -func mkAPI(token string) *Telegram { - tg := new(Telegram) - tg.Token = token - return tg -} - -// SetWebhook sets the webhook address so that Telegram knows where to send updates -func (t Telegram) SetWebhook(webhook string) { - resp, err := http.PostForm(t.apiURL("setWebhook"), url.Values{"url": {webhook}}) - if !checkerr("SetWebhook/http.PostForm", err) { - defer resp.Body.Close() - var result tg.APIResponse - err = json.NewDecoder(resp.Body).Decode(&result) - if err != nil { - log.Println("[SetWebhook] Could not read reply: " + err.Error()) - return - } - if result.Ok { - log.Println("Webhook successfully set!") - } else { - log.Printf("[SetWebhook] Error setting webhook (errcode %d): %s\n", *(result.ErrCode), *(result.Description)) - panic(errors.New("Cannot set webhook")) - } - } -} - -// SendTextMessage sends an HTML-styled text message to a specified chat -func (t Telegram) SendTextMessage(data tg.ClientTextMessageData) { - postdata := url.Values{ - "chat_id": {strconv.FormatInt(data.ChatID, 10)}, - "text": {data.Text}, - "parse_mode": {"HTML"}, - } - if data.ReplyID != nil { - postdata["reply_to_message_id"] = []string{strconv.FormatInt(*(data.ReplyID), 10)} - } - - _, err := http.PostForm(t.apiURL("sendMessage"), postdata) - checkerr("SendTextMessage/http.PostForm", err) -} - -func (t Telegram) SendPhoto(data tg.ClientPhotoData) { - // Decode photo from b64 - photolen := base64.StdEncoding.DecodedLen(len(data.Bytes)) - photobytes := make([]byte, photolen) - decoded, err := base64.StdEncoding.Decode(photobytes, []byte(data.Bytes)) - if checkerr("SendPhoto/base64.Decode", err) { - return - } - - // Write file into multipart buffer - body := new(bytes.Buffer) - writer := multipart.NewWriter(body) - part, err := writer.CreateFormFile("photo", data.Filename) - if checkerr("SendPhoto/multipart.CreateFormFile", err) { - return - } - part.Write(photobytes[0:decoded]) - - // Write other fields - writer.WriteField("chat_id", strconv.FormatInt(data.ChatID, 10)) - - if data.ReplyID != nil { - writer.WriteField("reply_to_message_id", strconv.FormatInt(*data.ReplyID, 10)) - } - - if data.Caption != "" { - writer.WriteField("caption", data.Caption) - } - - err = writer.Close() - if checkerr("SendPhoto/writer.Close", err) { - return - } - - // Create HTTP client and execute request - client := &http.Client{} - req, err := http.NewRequest("POST", t.apiURL("sendPhoto"), body) - if checkerr("SendPhoto/http.NewRequest", err) { - return - } - - req.Header.Add("Content-Type", writer.FormDataContentType()) - - _, err = client.Do(req) - checkerr("SendPhoto/http.Do", err) -} - -func (t Telegram) ForwardMessage(data tg.ClientForwardMessageData) { - postdata := url.Values{ - "chat_id": {strconv.FormatInt(data.ChatID, 10)}, - "from_chat_id": {strconv.FormatInt(data.FromChatID, 10)}, - "message_id": {strconv.FormatInt(data.MessageID, 10)}, - } - - _, err := http.PostForm(t.apiURL("forwardMessage"), postdata) - checkerr("ForwardMessage/http.PostForm", err) -} - -func (t Telegram) SendChatAction(data tg.ClientChatActionData) { - postdata := url.Values{ - "chat_id": {strconv.FormatInt(data.ChatID, 10)}, - "action": {string(data.Action)}, - } - - _, err := http.PostForm(t.apiURL("sendChatAction"), postdata) - checkerr("SendChatAction/http.PostForm", err) -} - -// GetFile sends a "getFile" API call to Telegram's servers and fetches the file -// specified afterward. The file will be then send back to the client that requested it -// with the specified callback id. -func (t Telegram) GetFile(data tg.FileRequestData, client net.Conn, callback int) { - fail := func(msg string) { - errmsg, _ := json.Marshal(tg.BrokerUpdate{ - Type: tg.BError, - Error: &msg, - Callback: &callback, - }) - fmt.Fprintln(client, string(errmsg)) - } - - postdata := url.Values{ - "file_id": {data.FileID}, - } - resp, err := http.PostForm(t.apiURL("getFile"), postdata) - if checkerr("GetFile/post", err) { - fail("Server didn't like my request") - return - } - defer resp.Body.Close() - - var filespecs = struct { - Ok bool `json:"ok"` - Result *tg.APIFile `json:"result,omitempty"` - }{} - err = json.NewDecoder(resp.Body).Decode(&filespecs) - if checkerr("GetFile/json.Decode", err) { - fail("Server sent garbage (or error)") - return - } - if filespecs.Result == nil { - fail("Server didn't send a file info, does the file exist?") - return - } - result := *filespecs.Result - - path := APIEndpoint + "file/bot" + t.Token + "/" + *result.Path - fileresp, err := http.Get(path) - if checkerr("GetFile/get", err) { - fail("Could not retrieve file from Telegram's servers") - return - } - defer fileresp.Body.Close() - - rawdata, err := ioutil.ReadAll(fileresp.Body) - if checkerr("GetFile/ioutil.ReadAll", err) { - fail("Could not read file data") - return - } - - rawlen := len(rawdata) - if rawlen != *result.Size { - // ??? - log.Printf("[GetFile] WARN ?? Downloaded file does not match provided filesize: %d != %d\n", rawlen, *result.Size) - } - b64data := base64.StdEncoding.EncodeToString(rawdata) - - clientmsg, err := json.Marshal(tg.BrokerUpdate{ - Type: tg.BFile, - Bytes: &b64data, - Callback: &callback, - }) - if checkerr("GetFile/json.Marshal", err) { - fail("Could not serialize reply JSON") - return - } - - fmt.Fprintln(client, string(clientmsg)) -} - -func (t Telegram) apiURL(method string) string { - return APIEndpoint + "bot" + t.Token + "/" + method -} - -func checkerr(method string, err error) bool { - if err != nil { - log.Printf("[%s] Error: %s\n", method, err.Error()) - return true - } - return false -} diff --git a/broker/webhook.go b/broker/webhook.go deleted file mode 100644 index 8e999d1..0000000 --- a/broker/webhook.go +++ /dev/null @@ -1,33 +0,0 @@ -package main - -import ( - "encoding/json" - "log" - "net/http" - - "github.com/hamcha/clessy/tg" -) - -func webhook(rw http.ResponseWriter, req *http.Request) { - defer req.Body.Close() - - // Re-encode request to ensure conformity - var update tg.APIUpdate - err := json.NewDecoder(req.Body).Decode(&update) - if err != nil { - log.Println("[webhook] Received incorrect request: " + err.Error()) - return - } - - data, err := json.Marshal(tg.BrokerUpdate{ - Type: tg.BMessage, - Message: &(update.Message), - Callback: nil, - }) - if err != nil { - log.Println("[webhook] Cannot re-encode json (??) : " + err.Error()) - return - } - - broadcast(string(data)) -} diff --git a/config.json.sample b/config.json.sample deleted file mode 100644 index da4887c..0000000 --- a/config.json.sample +++ /dev/null @@ -1,7 +0,0 @@ -{ - "BindServer" : ":7313", - "BindClients": "127.0.0.1:7314", - "Token" : "Bot token here", - "BaseURL" : "https://my.bot.host", - "WebhookURL" : "/secret_url_here" -} \ No newline at end of file diff --git a/friday/main.go b/friday/main.go index 01bc969..a95d880 100644 --- a/friday/main.go +++ b/friday/main.go @@ -10,7 +10,7 @@ import ( "strconv" "strings" - "github.com/hamcha/clessy/tg" + "github.com/hamcha/tg" ) const KYMURL = "http://knowyourmeme.com/memes/it-s-finally-a-friday/photos/page/2" diff --git a/mods/dantes.go b/mods-toupdate/dantes.go similarity index 98% rename from mods/dantes.go rename to mods-toupdate/dantes.go index 9af4a7b..6cdf22b 100644 --- a/mods/dantes.go +++ b/mods-toupdate/dantes.go @@ -4,7 +4,7 @@ import ( "math/rand" "strings" - "github.com/hamcha/clessy/tg" + "github.com/hamcha/tg" ) var dantes_frasiacaso = []string{ diff --git a/mods-toupdate/roll.go b/mods-toupdate/roll.go new file mode 100644 index 0000000..11e8294 --- /dev/null +++ b/mods-toupdate/roll.go @@ -0,0 +1,21 @@ +package main + +import ( + "strings" + + "github.com/hamcha/tg" +) + +func roll(broker *tg.Broker, update tg.APIMessage) { + if isCommand(update, "roll") { + parts := strings.Split(*(update.Text), " ") + if len(parts) < 2 { + broker.SendTextMessage(update.Chat, "Sintassi\n/roll cosa1 [cosa2] ..\n\nCose supportate\nNumero intero casuale (es. 300, 10-20, 34-100)\nLancio di dadi (es. d6, 2d20, 100d12+10)\nUtente casuale (\"user\")", &update.MessageID) + return + } + } +} + +func rolltoken(token string) { + +} diff --git a/mods/talk.go b/mods-toupdate/talk.go similarity index 99% rename from mods/talk.go rename to mods-toupdate/talk.go index 3fb4205..c0099c8 100644 --- a/mods/talk.go +++ b/mods-toupdate/talk.go @@ -11,7 +11,7 @@ import ( "strings" "time" - "github.com/hamcha/clessy/tg" + "github.com/hamcha/tg" ) type QRequest struct { diff --git a/mods/macro.go b/mods/macro.go index 1993cdf..de7632c 100644 --- a/mods/macro.go +++ b/mods/macro.go @@ -8,7 +8,7 @@ import ( "strings" "time" - "github.com/hamcha/clessy/tg" + "github.com/hamcha/tg" ) type Macro struct { diff --git a/mods/main.go b/mods/main.go index 3c333bd..64b0b4e 100644 --- a/mods/main.go +++ b/mods/main.go @@ -8,7 +8,7 @@ import ( "strings" "time" - "github.com/hamcha/clessy/tg" + "github.com/hamcha/tg" ) type Mod struct { @@ -47,10 +47,12 @@ var mods = map[string]Mod{ OnInit: proverbio_init, OnMessage: proverbio_message, }, - "talk": { - OnInit: talk_init, - OnMessage: talk_message, - }, + /* + "talk": { + OnInit: inittalk, + OnMessage: talk, + }, + */ "stt": { OnInit: stt_init, OnMessage: stt_message, diff --git a/mods/memegen.go b/mods/memegen.go index b95823f..3d894e8 100644 --- a/mods/memegen.go +++ b/mods/memegen.go @@ -13,7 +13,7 @@ import ( "strings" "github.com/golang/freetype" - "github.com/hamcha/clessy/tg" + "github.com/hamcha/tg" "github.com/llgcode/draw2d" "github.com/llgcode/draw2d/draw2dimg" ) diff --git a/mods/metafora.go b/mods/metafora.go index da6b117..5fd83fb 100644 --- a/mods/metafora.go +++ b/mods/metafora.go @@ -3,7 +3,7 @@ package main import ( "math/rand" - "github.com/hamcha/clessy/tg" + "github.com/hamcha/tg" ) var metaactions = []string{ diff --git a/mods/proverbio.go b/mods/proverbio.go index e263180..28e79b3 100644 --- a/mods/proverbio.go +++ b/mods/proverbio.go @@ -8,7 +8,7 @@ import ( "log" - "github.com/hamcha/clessy/tg" + "github.com/hamcha/tg" ) type ProverbioData struct { diff --git a/mods/remind.go b/mods/remind.go index 3665a00..6b08f13 100644 --- a/mods/remind.go +++ b/mods/remind.go @@ -10,7 +10,7 @@ import ( "time" "unicode" - "github.com/hamcha/clessy/tg" + "github.com/hamcha/tg" ) var remindpath *string diff --git a/mods/search.go b/mods/search.go index 70c2750..f1d055a 100644 --- a/mods/search.go +++ b/mods/search.go @@ -9,7 +9,7 @@ import ( "net/url" "strings" - "github.com/hamcha/clessy/tg" + "github.com/hamcha/tg" ) type SearchResult struct { diff --git a/mods/snapchat.go b/mods/snapchat.go index 4b134ef..f6d4e1a 100644 --- a/mods/snapchat.go +++ b/mods/snapchat.go @@ -18,7 +18,7 @@ import ( "time" "github.com/golang/freetype" - "github.com/hamcha/clessy/tg" + "github.com/hamcha/tg" "github.com/llgcode/draw2d" "github.com/llgcode/draw2d/draw2dimg" ) diff --git a/mods/stt.go b/mods/stt.go index 6553e6f..5ced77a 100644 --- a/mods/stt.go +++ b/mods/stt.go @@ -8,7 +8,7 @@ import ( "encoding/base64" speech "cloud.google.com/go/speech/apiv1" - "github.com/hamcha/clessy/tg" + "github.com/hamcha/tg" "google.golang.org/api/option" speechpb "google.golang.org/genproto/googleapis/cloud/speech/v1" ) diff --git a/mods/unsplash.go b/mods/unsplash.go index 9162263..1ff84f2 100644 --- a/mods/unsplash.go +++ b/mods/unsplash.go @@ -13,7 +13,7 @@ import ( "strings" "github.com/golang/freetype" - "github.com/hamcha/clessy/tg" + "github.com/hamcha/tg" "github.com/llgcode/draw2d" "github.com/llgcode/draw2d/draw2dimg" diff --git a/mods/viaggi.go b/mods/viaggi.go index 4127c57..ff358c0 100644 --- a/mods/viaggi.go +++ b/mods/viaggi.go @@ -10,7 +10,7 @@ import ( "strconv" "strings" - "github.com/hamcha/clessy/tg" + "github.com/hamcha/tg" ) const viaggiurl = "http://free.rome2rio.com/api/1.2/json/Search?key=X5JMLHNc&languageCode=IT¤cyCode=EUR" diff --git a/mspa/main.go b/mspa/main.go index 85ef8fa..dd5aada 100644 --- a/mspa/main.go +++ b/mspa/main.go @@ -9,7 +9,7 @@ import ( "strings" "time" - "github.com/hamcha/clessy/tg" + "github.com/hamcha/tg" ) func assert(err error) { diff --git a/stats/main.go b/stats/main.go index a71d1b3..6a39754 100644 --- a/stats/main.go +++ b/stats/main.go @@ -4,7 +4,7 @@ import ( "flag" "github.com/boltdb/bolt" - "github.com/hamcha/clessy/tg" + "github.com/hamcha/tg" ) func assert(err error) { @@ -42,6 +42,5 @@ func main() { go startWebServer(*webBind) - _, err = tg.CreateBrokerClient(*brokerAddr, process) - assert(err) + assert(tg.CreateBrokerClient(*brokerAddr, process)) } diff --git a/stats/stats.go b/stats/stats.go index fcfc53b..1470224 100644 --- a/stats/stats.go +++ b/stats/stats.go @@ -9,7 +9,7 @@ import ( "time" "github.com/boltdb/bolt" - "github.com/hamcha/clessy/tg" + "github.com/hamcha/tg" ) const ( diff --git a/stats/users.go b/stats/users.go index 444fad6..7bd2744 100644 --- a/stats/users.go +++ b/stats/users.go @@ -5,7 +5,7 @@ import ( "strings" "github.com/boltdb/bolt" - "github.com/hamcha/clessy/tg" + "github.com/hamcha/tg" ) var users map[string]string diff --git a/tg/api.go b/tg/api.go deleted file mode 100644 index 274d291..0000000 --- a/tg/api.go +++ /dev/null @@ -1,154 +0,0 @@ -package tg - -// APIUser represents the "User" JSON structure -type APIUser struct { - UserID int64 `json:"id"` - FirstName string `json:"first_name"` - LastName string `json:"last_name,omitempty"` - Username string `json:"username,omitempty"` -} - -// ChatType defines the type of chat -type ChatType string - -const ( - // ChatTypePrivate is a private chat (between user and bot) - ChatTypePrivate ChatType = "private" - - // ChatTypeGroup is a group chat (<100 members) - ChatTypeGroup ChatType = "group" - - // ChatTypeSupergroup is a supergroup chat (>=100 members) - ChatTypeSupergroup ChatType = "supergroup" - - // ChatTypeChannel is a channel (Read-only) - ChatTypeChannel ChatType = "channel" -) - -// APIChat represents the "Chat" JSON structure -type APIChat struct { - ChatID int64 `json:"id"` - Type ChatType `json:"type"` - Title *string `json:"title,omitempty"` - Username *string `json:"username,omitempty"` - FirstName *string `json:"first_name,omitempty"` - LastName *string `json:"last_name,omitempty"` -} - -// APIMessage represents the "Message" JSON structure -type APIMessage struct { - MessageID int64 `json:"message_id"` - User APIUser `json:"from"` - Time int64 `json:"date"` - Chat *APIChat `json:"chat"` - FwdUser *APIUpdate `json:"forward_from,omitempty"` - FwdTime *int `json:"forward_date,omitempty"` - ReplyTo *APIMessage `json:"reply_to_message,omitempty"` - Text *string `json:"text,omitempty"` - Audio *APIAudio `json:"audio,omitempty"` - Document *APIDocument `json:"document,omitempty"` - Photo []APIPhotoSize `json:"photo,omitempty"` - Sticker *APISticker `json:"sticker,omitempty"` - Video *APIVideo `json:"video,omitempty"` - Voice *APIVoice `json:"voice,omitempty"` - Caption *string `json:"caption,omitempty"` - Contact *APIContact `json:"contact,omitempty"` - Location *APILocation `json:"location,omitempty"` - NewUser *APIUser `json:"new_chat_partecipant,omitempty"` - LeftUser *APIUser `json:"left_chat_partecipant,omitempty"` - PhotoDeleted *bool `json:"delete_chat_photo,omitempty"` - GroupCreated *bool `json:"group_chat_created,omitempty"` - SupergroupCreated *bool `json:"supergroup_chat_created,omitempty"` - ChannelCreated *bool `json:"channel_chat_created,omitempty"` - GroupToSuper *int64 `json:"migrate_to_chat_id,omitempty"` - GroupFromSuper *int64 `json:"migrate_from_chat_id,omitempty"` -} - -// APIPhotoSize represents the "PhotoSize" JSON structure -type APIPhotoSize struct { - FileID string `json:"file_id"` - Width int `json:"width"` - Height int `json:"height"` - FileSize *int `json:"file_size,omitempty"` -} - -// APIAudio represents the "Audio" JSON structure -type APIAudio struct { - FileID string `json:"file_id"` - Duration int `json:"duration"` - Performer *string `json:"performer,omitempty"` - Title *string `json:"title,omitempty"` - MimeType *string `json:"mime_type,omitempty"` - FileSize *int `json:"file_size,omitempty"` -} - -// APIDocument represents the "Document" JSON structure -type APIDocument struct { - FileID string `json:"file_id"` - Thumbnail *APIPhotoSize `json:"thumb,omitempty"` - Filename string `json:"file_name"` - MimeType *string `json:"mime_type,omitempty"` - FileSize *int `json:"file_size,omitempty"` -} - -// APISticker represents the "Sticker" JSON structure -type APISticker struct { - FileID string `json:"file_id"` - Width int `json:"width"` - Height int `json:"height"` - Thumbnail *APIPhotoSize `json:"thumb,omitempty"` - FileSize *int `json:"file_size,omitempty"` -} - -// APIVideo represents the "Video" JSON structure -type APIVideo struct { - FileID string `json:"file_id"` - Width int `json:"width"` - Height int `json:"height"` - Duration int `json:"duration"` - Thumbnail *APIPhotoSize `json:"thumb,omitempty"` - MimeType *string `json:"mime_type,omitempty"` - FileSize *int `json:"file_size,omitempty"` -} - -// APIVoice represents the "Voice" JSON structure -type APIVoice struct { - FileID string `json:"file_id"` - Duration int `json:"duration"` - MimeType *string `json:"mime_type,omitempty"` - FileSize *int `json:"file_size,omitempty"` -} - -// APIContact represents the "Contact" JSON structure -type APIContact struct { - PhoneNumber string `json:"phone_number"` - FirstName string `json:"first_name"` - LastName *string `json:"last_name,omitempty"` - UserID *int64 `json:"user_id,omitempty"` -} - -// APILocation represents the "Location" JSON structure -type APILocation struct { - Longitude float64 `json:"longitude"` - Latitude float64 `json:"latitude"` -} - -// APIUpdate represents the "Update" JSON structure -type APIUpdate struct { - UpdateID int64 `json:"update_id"` - Message APIMessage `json:"message"` -} - -// APIFile represents the "File" JSON structure -type APIFile struct { - FileID string `json:"file_id"` - Size *int `json:"file_size,omitempty"` - Path *string `json:"file_path,omitempty"` -} - -// APIResponse represents a response from the Telegram API -type APIResponse struct { - Ok bool `json:"ok"` - ErrCode *int `json:"error_code,omitempty"` - Description *string `json:"description,omitempty"` -} diff --git a/tg/broker.go b/tg/broker.go deleted file mode 100644 index 8ed3e3e..0000000 --- a/tg/broker.go +++ /dev/null @@ -1,166 +0,0 @@ -package tg - -import ( - "encoding/base64" - "encoding/json" - "fmt" - "log" - "net" -) - -// Broker is a broker connection handler with callback management functions -type Broker struct { - Socket net.Conn - Callbacks []BrokerCallback - - cbFree int -} - -// ConnectToBroker creates a Broker connection -func ConnectToBroker(brokerAddr string) (*Broker, error) { - sock, err := net.Dial("tcp", brokerAddr) - if err != nil { - return nil, err - } - - broker := new(Broker) - broker.Socket = sock - broker.Callbacks = make([]BrokerCallback, 0) - broker.cbFree = 0 - return broker, nil -} - -// Close closes a broker connection -func (b *Broker) Close() { - b.Socket.Close() -} - -// SendTextMessage sends a HTML-styles text message to a chat. -// A reply_to message ID can be specified as optional parameter. -func (b *Broker) SendTextMessage(chat *APIChat, text string, original *int64) { - b.sendCmd(ClientCommand{ - Type: CmdSendTextMessage, - TextMessageData: &ClientTextMessageData{ - Text: text, - ChatID: chat.ChatID, - ReplyID: original, - }, - }) -} - -// SendPhoto sends a photo with an optional caption to a chat. -// A reply_to message ID can be specified as optional parameter. -func (b *Broker) SendPhoto(chat *APIChat, data []byte, filename string, caption string, original *int64) { - b.sendCmd(ClientCommand{ - Type: CmdSendPhoto, - PhotoData: &ClientPhotoData{ - ChatID: chat.ChatID, - Filename: filename, - Bytes: base64.StdEncoding.EncodeToString(data), - Caption: caption, - ReplyID: original, - }, - }) -} - -// ForwardMessage forwards a message between chats. -func (b *Broker) ForwardMessage(chat *APIChat, message APIMessage) { - b.sendCmd(ClientCommand{ - Type: CmdForwardMessage, - ForwardMessageData: &ClientForwardMessageData{ - ChatID: chat.ChatID, - FromChatID: message.Chat.ChatID, - MessageID: message.MessageID, - }, - }) -} - -// SendChatAction sets a chat action for 5 seconds or less (canceled at first message sent) -func (b *Broker) SendChatAction(chat *APIChat, action ChatAction) { - b.sendCmd(ClientCommand{ - Type: CmdSendChatAction, - ChatActionData: &ClientChatActionData{ - ChatID: chat.ChatID, - Action: action, - }, - }) -} - -// GetFile sends a file retrieval request to the Broker. -// This function is asynchronous as data will be delivered to the given callback. -func (b *Broker) GetFile(fileID string, fn BrokerCallback) int { - cid := b.RegisterCallback(fn) - b.sendCmd(ClientCommand{ - Type: CmdGetFile, - FileRequestData: &FileRequestData{ - FileID: fileID, - }, - Callback: &cid, - }) - return cid -} - -// RegisterCallback assigns a callback ID to the given callback and puts it on the callback list. -// This function should never be called by clients. -func (b *Broker) RegisterCallback(fn BrokerCallback) int { - cblen := len(b.Callbacks) - // List is full, append to end - if b.cbFree == cblen { - b.Callbacks = append(b.Callbacks, fn) - b.cbFree++ - return cblen - } - // List is not full, use empty slot and find next one - id := b.cbFree - b.Callbacks[id] = fn - next := b.cbFree + 1 - for ; next < cblen; next++ { - if b.Callbacks[next] == nil { - break - } - } - b.cbFree = next - return id -} - -// RemoveCallback removes a callback from the callback list by ID. -// This function should never be called by clients. -func (b *Broker) RemoveCallback(id int) { - b.Callbacks[id] = nil - if id < b.cbFree { - b.cbFree = id - } - b.resizeCbArray() -} - -// SpliceCallback retrieves a callback by ID and removes it from the list. -// This function should never be called by clients. -func (b *Broker) SpliceCallback(id int) BrokerCallback { - defer b.RemoveCallback(id) - return b.Callbacks[id] -} - -func (b *Broker) sendCmd(cmd ClientCommand) { - data, err := json.Marshal(cmd) - if err != nil { - log.Printf("[sendCmd] JSON Encode error: %s\n", err.Error()) - } - fmt.Fprintln(b.Socket, string(data)) -} - -func (b *Broker) resizeCbArray() { - var i int - cut := false - for i = len(b.Callbacks); i > 0; i-- { - if b.Callbacks[i-1] != nil { - break - } - cut = true - } - if cut { - b.Callbacks = b.Callbacks[0:i] - if b.cbFree > i { - b.cbFree = i - } - } -} diff --git a/tg/client.go b/tg/client.go deleted file mode 100644 index af3002b..0000000 --- a/tg/client.go +++ /dev/null @@ -1,64 +0,0 @@ -package tg - -import ( - "bufio" - "encoding/json" - "io" - "log" -) - -// UpdateHandler is an update handler for webhook updates -type UpdateHandler func(broker *Broker, message APIMessage) - -// BrokerCallback is a callback for broker responses to client requests -type BrokerCallback func(broker *Broker, update BrokerUpdate) - -// CreateBrokerClient creates a connection to a broker and sends all webhook updates to a given function. -// This is the intended way to create clients, please refer to examples for how to make a simple client. -func CreateBrokerClient(brokerAddr string, updateFn UpdateHandler) error { - broker, err := ConnectToBroker(brokerAddr) - if err != nil { - return err - } - defer broker.Close() - - return RunBrokerClient(broker, updateFn) -} - -// RunBrokerClient is a slimmer version of CreateBrokerClient for who wants to keep its own broker connection -func RunBrokerClient(broker *Broker, updateFn UpdateHandler) error { - in := bufio.NewReader(broker.Socket) - var buf []byte - for { - bytes, isPrefix, err := in.ReadLine() - if err != nil { - break - } - buf = append(buf, bytes...) - - if isPrefix { - continue - } - - var update BrokerUpdate - err = json.Unmarshal(buf, &update) - if err != nil { - log.Printf("[tg - CreateBrokerClient] ERROR reading JSON: %s\r\n", err.Error()) - log.Printf("%s\n", string(buf)) - continue - } - - if update.Callback == nil { - // It's a generic message: dispatch to UpdateHandler - go updateFn(broker, *(update.Message)) - } else { - // It's a response to a request: retrieve callback and call it - go broker.SpliceCallback(*(update.Callback))(broker, update) - } - - // Empty buffer - buf = []byte{} - } - - return io.EOF -} diff --git a/tg/client_test.go b/tg/client_test.go deleted file mode 100644 index 5cd4dd9..0000000 --- a/tg/client_test.go +++ /dev/null @@ -1,16 +0,0 @@ -package tg_test - -// This example creates a basic client that connects to a broker and checks for message containing greetings. -// If it finds a greeting message it will greet back the user (using the reply_to parameter) -func ExampleCreateBrokerClient() { - CreateBrokerClient("localhost:7314", func(broker *Broker, message APIMessage) { - // Check if it's a text message - if message.Text != nil { - // Check that it's a greeting - if *(message.Text) == "hello" || *(message.Text) == "hi" { - // Reply with a greeting! - broker.SendTextMessage(message.Chat, "Hello!", message.MessageID) - } - } - }) -} diff --git a/tg/command.go b/tg/command.go deleted file mode 100644 index 021108c..0000000 --- a/tg/command.go +++ /dev/null @@ -1,103 +0,0 @@ -package tg - -// BrokerUpdateType distinguishes update types coming from the broker -type BrokerUpdateType string - -const ( - // BMessage is a message update (mostly webhook updates) - BMessage BrokerUpdateType = "message" - - // BFile is a file retrieval response update - BFile BrokerUpdateType = "file" - - // BError is an error the broker occurred while fulfilling a request - BError BrokerUpdateType = "error" -) - -// BrokerUpdate is what is sent by the broker as update -type BrokerUpdate struct { - Type BrokerUpdateType - Callback *int `json:",omitempty"` - Error *string `json:",omitempty"` - Message *APIMessage `json:",omitempty"` - Bytes *string `json:",omitempty"` -} - -// ClientCommandType distinguishes requests sent by clients to the broker -type ClientCommandType string - -const ( - // CmdSendTextMessage requests the broker to send a text message to a chat - CmdSendTextMessage ClientCommandType = "sendText" - - // CmdSendPhoto requests the broker to send a photo to a chat - CmdSendPhoto ClientCommandType = "sendPhoto" - - // CmdForwardMessage requests the broker to forward a message between chats - CmdForwardMessage ClientCommandType = "forwardMessage" - - // CmdGetFile requests the broker to get a file from Telegram - CmdGetFile ClientCommandType = "getFile" - - // CmdSendChatAction requests the broker to set a chat action (typing, etc.) - CmdSendChatAction ClientCommandType = "sendChatAction" -) - -// ClientTextMessageData is the required data for a CmdSendTextMessage request -type ClientTextMessageData struct { - ChatID int64 - Text string - ReplyID *int64 `json:",omitempty"` -} - -// ClientPhotoData is the required data for a CmdSendPhoto request -type ClientPhotoData struct { - ChatID int64 - Bytes string - Filename string - Caption string `json:",omitempty"` - ReplyID *int64 `json:",omitempty"` -} - -// ClientForwardMessageData is the required data for a CmdForwardMessage request -type ClientForwardMessageData struct { - ChatID int64 - FromChatID int64 - MessageID int64 -} - -// ClientChatActionData is the required data for a CmdSendChatAction request -type ClientChatActionData struct { - ChatID int64 - Action ChatAction -} - -// ChatAction is the action name for CmdSendChatAction requests -type ChatAction string - -const ( - ActionTyping ChatAction = "typing" - ActionUploadingPhoto ChatAction = "upload_photo" - ActionRecordingVideo ChatAction = "record_video" - ActionUploadingVideo ChatAction = "upload_video" - ActionRecordingAudio ChatAction = "record_audio" - ActionUploadingAudio ChatAction = "upload_audio" - ActionUploadingDocument ChatAction = "upload_document" - ActionFindingLocation ChatAction = "find_location" -) - -// FileRequestData is the required data for a CmdGetFile request -type FileRequestData struct { - FileID string -} - -// ClientCommand is a request sent by clients to the broker -type ClientCommand struct { - Type ClientCommandType - TextMessageData *ClientTextMessageData `json:",omitempty"` - PhotoData *ClientPhotoData `json:",omitempty"` - ForwardMessageData *ClientForwardMessageData `json:",omitempty"` - ChatActionData *ClientChatActionData `json:",omitempty"` - FileRequestData *FileRequestData `json:",omitempty"` - Callback *int `json:",omitempty"` -}