diff --git a/.gitignore b/.gitignore index 6d3947a..77de716 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,5 @@ clessy-broker clessy-mods clessy-stats clessy-stats-import -stats-web/Chart.bundle.js \ No newline at end of file +stats-web/Chart.bundle.js +impact.ttf \ No newline at end of file diff --git a/broker/action.go b/broker/action.go index cbfb00d..1a2ceb9 100644 --- a/broker/action.go +++ b/broker/action.go @@ -1,13 +1,18 @@ package main import ( + "net" + "github.com/hamcha/clessy/tg" ) -func executeClientCommand(action tg.ClientCommand) { +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) } } diff --git a/broker/clients.go b/broker/clients.go index 1eabf26..2882528 100644 --- a/broker/clients.go +++ b/broker/clients.go @@ -45,7 +45,7 @@ func handleClient(c net.Conn) { log.Printf("%s\n", string(bytes)) continue } - executeClientCommand(cmd) + executeClientCommand(cmd, c) } removeCon(c) } diff --git a/broker/telegram.go b/broker/telegram.go index ea5ef35..4ffd403 100644 --- a/broker/telegram.go +++ b/broker/telegram.go @@ -1,9 +1,13 @@ package main import ( + "encoding/base64" "encoding/json" "errors" + "fmt" + "io/ioutil" "log" + "net" "net/http" "net/url" "strconv" @@ -61,13 +65,77 @@ func (t Telegram) SendTextMessage(data tg.ClientTextMessageData) { checkerr("SendTextMessage", 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 tg.APIFile + err = json.NewDecoder(resp.Body).Decode(&filespecs) + if checkerr("GetFile/json.Decode", err) { + fail("Server sent garbage (or error)") + return + } + + path := "https://api.telegram.org/file/bot" + t.Token + "/" + *filespecs.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 != *filespecs.Size { + // ??? + log.Printf("[GetFile] WARN ?? Downloaded file does not match provided filesize: %d != %d\n", rawlen, *filespecs.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("Received error with call to %s: %s\n", method, err.Error()) + log.Printf("[%s] Error: %s\n", method, err.Error()) return true } return false diff --git a/mods/main.go b/mods/main.go index 3cf7a5c..d3d4e58 100644 --- a/mods/main.go +++ b/mods/main.go @@ -15,6 +15,7 @@ func initmods() { func dispatch(broker *tg.Broker, update tg.APIMessage) { metafora(broker, update) viaggi(broker, update) + memegen(broker, update) } func isCommand(update tg.APIMessage, cmdname string) bool { diff --git a/tg/broker.go b/tg/broker.go index 575e393..6da6804 100644 --- a/tg/broker.go +++ b/tg/broker.go @@ -66,7 +66,13 @@ func (b *Broker) SendPhoto(chat *APIChat, data []byte, caption *string, original // 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) - // Make file request + b.sendCmd(ClientCommand{ + Type: CmdGetFile, + FileRequestData: &FileRequestData{ + FileID: fileID, + }, + Callback: &cid, + }) return cid } diff --git a/tg/command.go b/tg/command.go index 7ec9161..452cb4c 100644 --- a/tg/command.go +++ b/tg/command.go @@ -9,14 +9,18 @@ const ( // 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 - Message *APIMessage - Bytes *string + 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 @@ -37,27 +41,27 @@ const ( type ClientTextMessageData struct { ChatID int Text string - ReplyID *int + ReplyID *int `json:",omitempty"` } // ClientPhotoData is the required data for a CmdSendPhoto request type ClientPhotoData struct { ChatID int Bytes string - Caption *string - ReplyID *int + Caption *string `json:",omitempty"` + ReplyID *int `json:",omitempty"` } // FileRequestData is the required data for a CmdGetFile request type FileRequestData struct { - FileID int + FileID string } // ClientCommand is a request sent by clients to the broker type ClientCommand struct { Type ClientCommandType - TextMessageData *ClientTextMessageData - PhotoData *ClientPhotoData - FileRequestData *FileRequestData - Callback *int + TextMessageData *ClientTextMessageData `json:",omitempty"` + PhotoData *ClientPhotoData `json:",omitempty"` + FileRequestData *FileRequestData `json:",omitempty"` + Callback *int `json:",omitempty"` }