Compare commits
5 Commits
Author | SHA1 | Date |
---|---|---|
Hamcha | 400e756356 | |
Hamcha | b27494aa52 | |
Hamcha | 9abe50bebd | |
Hamcha | ab9dc80b06 | |
Hamcha | bcfc5c2d64 |
|
@ -0,0 +1,7 @@
|
|||
ISC License
|
||||
|
||||
Copyright 2022 Hamcha
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
22
api.go
22
api.go
|
@ -2,10 +2,15 @@ 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"`
|
||||
UserID int64 `json:"id"`
|
||||
FirstName string `json:"first_name"`
|
||||
LastName string `json:"last_name,omitempty"`
|
||||
Username string `json:"username,omitempty"`
|
||||
IsBot bool `json:"is_bot"`
|
||||
LanguageCode string `json:"language_code,omitempty"`
|
||||
CanJoinGroups bool `json:"can_join_groups,omitempty"`
|
||||
CanReadAllGroupMessages bool `json:"can_read_all_group_messages,omitempty"`
|
||||
SupportsInlineQueries bool `json:"supports_inline_queries,omitempty"`
|
||||
}
|
||||
|
||||
// ChatType defines the type of chat
|
||||
|
@ -41,7 +46,7 @@ type APIMessage struct {
|
|||
User APIUser `json:"from"`
|
||||
Time int64 `json:"date"`
|
||||
Chat *APIChat `json:"chat"`
|
||||
FwdUser *APIUpdate `json:"forward_from,omitempty"`
|
||||
FwdUser *APIUser `json:"forward_from,omitempty"`
|
||||
FwdTime *int `json:"forward_date,omitempty"`
|
||||
ReplyTo *APIMessage `json:"reply_to_message,omitempty"`
|
||||
Text *string `json:"text,omitempty"`
|
||||
|
@ -227,7 +232,7 @@ type APIInputMediaPhoto struct {
|
|||
// APICallbackQuery is a callback query triggered by an inline button
|
||||
type APICallbackQuery struct {
|
||||
ID string `json:"id"`
|
||||
User APIAudio `json:"from"`
|
||||
User APIUser `json:"from"`
|
||||
Message *APIMessage `json:"message,omitempty"`
|
||||
InlineID *string `json:"inline_message_id,omitempty"`
|
||||
ChatID string `json:"chat_instance"`
|
||||
|
@ -243,3 +248,8 @@ type apiAlbumResponse struct {
|
|||
Ok bool `json:"ok"`
|
||||
Result []APIMessage `json:"result"`
|
||||
}
|
||||
|
||||
type apiUserResponse struct {
|
||||
Ok bool `json:"ok"`
|
||||
Result APIUser `json:"result"`
|
||||
}
|
||||
|
|
|
@ -2,7 +2,6 @@ package tg
|
|||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
|
@ -62,7 +61,7 @@ func (b *Broker) SendTextMessage(chat *APIChat, text string, extra *MessageOptio
|
|||
// 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, extra *MessageOptions) {
|
||||
photodata := &ClientPhotoData{
|
||||
photodata := &ClientPhotoDataNet{
|
||||
ChatID: chat.ChatID,
|
||||
Filename: filename,
|
||||
Bytes: base64.StdEncoding.EncodeToString(data),
|
||||
|
|
|
@ -13,10 +13,10 @@ func executeClientCommand(action tg.ClientCommand, client net.Conn) {
|
|||
api.SendTextMessage(data)
|
||||
case tg.CmdGetFile:
|
||||
data := *(action.FileRequestData)
|
||||
api.GetFile(data, client, *action.Callback)
|
||||
api.GetFileNet(data, client, *action.Callback)
|
||||
case tg.CmdSendPhoto:
|
||||
data := *(action.PhotoData)
|
||||
api.SendPhoto(data)
|
||||
api.SendPhotoNet(data)
|
||||
case tg.CmdForwardMessage:
|
||||
data := *(action.ForwardMessageData)
|
||||
api.ForwardMessage(data)
|
||||
|
|
11
command.go
11
command.go
|
@ -68,6 +68,15 @@ type ClientTextMessageData struct {
|
|||
|
||||
// ClientPhotoData is the required data for a CmdSendPhoto request
|
||||
type ClientPhotoData struct {
|
||||
ChatID int64
|
||||
Bytes []byte
|
||||
Filename string
|
||||
Caption string `json:",omitempty"`
|
||||
ReplyID *int64 `json:",omitempty"`
|
||||
}
|
||||
|
||||
// ClientPhotoDataNet is the required data for a CmdSendPhoto request over the wire
|
||||
type ClientPhotoDataNet struct {
|
||||
ChatID int64
|
||||
Bytes string
|
||||
Filename string
|
||||
|
@ -156,7 +165,7 @@ type FileRequestData struct {
|
|||
type ClientCommand struct {
|
||||
Type ClientCommandType
|
||||
TextMessageData *ClientTextMessageData `json:",omitempty"`
|
||||
PhotoData *ClientPhotoData `json:",omitempty"`
|
||||
PhotoData *ClientPhotoDataNet `json:",omitempty"`
|
||||
ForwardMessageData *ClientForwardMessageData `json:",omitempty"`
|
||||
ChatActionData *ClientChatActionData `json:",omitempty"`
|
||||
InlineQueryResults *InlineQueryResponse `json:",omitempty"`
|
||||
|
|
2
go.mod
2
go.mod
|
@ -1,3 +1,5 @@
|
|||
module git.fromouter.space/hamcha/tg
|
||||
|
||||
go 1.12
|
||||
|
||||
require github.com/json-iterator/go v1.1.12 // indirect
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
89
telegram.go
89
telegram.go
|
@ -3,7 +3,6 @@ package tg
|
|||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
|
@ -13,8 +12,12 @@ import (
|
|||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
)
|
||||
|
||||
var json = jsoniter.ConfigFastest
|
||||
|
||||
// APIEndpoint is Telegram's current Bot API base url endpoint
|
||||
const APIEndpoint = "https://api.telegram.org/"
|
||||
|
||||
|
@ -107,16 +110,24 @@ func (t Telegram) SendTextMessage(data ClientTextMessageData) (APIMessage, error
|
|||
return out.Result, err
|
||||
}
|
||||
|
||||
// SendPhoto sends a picture to a chat as a photo
|
||||
func (t Telegram) SendPhoto(data ClientPhotoData) (APIMessage, error) {
|
||||
// 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) {
|
||||
// SendPhotoNet decodes a network SendPhoto request and executes it
|
||||
func (t Telegram) SendPhotoNet(data ClientPhotoDataNet) (APIMessage, error) {
|
||||
byt, err := base64.StdEncoding.DecodeString(data.Bytes)
|
||||
if checkerr("SendPhotoNet/base64.Decode", err) {
|
||||
return APIMessage{}, err
|
||||
}
|
||||
|
||||
return t.SendPhoto(ClientPhotoData{
|
||||
ChatID: data.ChatID,
|
||||
Bytes: byt,
|
||||
Filename: data.Filename,
|
||||
Caption: data.Caption,
|
||||
ReplyID: data.ReplyID,
|
||||
})
|
||||
}
|
||||
|
||||
// SendPhoto sends a picture to a chat as a photo
|
||||
func (t Telegram) SendPhoto(data ClientPhotoData) (APIMessage, error) {
|
||||
// Write file into multipart buffer
|
||||
body := new(bytes.Buffer)
|
||||
writer := multipart.NewWriter(body)
|
||||
|
@ -124,7 +135,7 @@ func (t Telegram) SendPhoto(data ClientPhotoData) (APIMessage, error) {
|
|||
if checkerr("SendPhoto/multipart.CreateFormFile", err) {
|
||||
return APIMessage{}, err
|
||||
}
|
||||
part.Write(photobytes[0:decoded])
|
||||
part.Write(data.Bytes)
|
||||
|
||||
// Write other fields
|
||||
writer.WriteField("chat_id", strconv.FormatInt(data.ChatID, 10))
|
||||
|
@ -374,23 +385,13 @@ func (t Telegram) AnswerInlineQuery(data InlineQueryResponse) error {
|
|||
// 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 FileRequestData, client net.Conn, callback int) {
|
||||
fail := func(msg string) {
|
||||
errmsg, _ := json.Marshal(BrokerUpdate{
|
||||
Type: BError,
|
||||
Error: &msg,
|
||||
Callback: &callback,
|
||||
})
|
||||
fmt.Fprintln(client, string(errmsg))
|
||||
}
|
||||
|
||||
func (t Telegram) GetFile(data FileRequestData) ([]byte, error) {
|
||||
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
|
||||
return nil, fmt.Errorf("Server didn't like my request: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
|
@ -400,27 +401,23 @@ func (t Telegram) GetFile(data FileRequestData, client net.Conn, callback int) {
|
|||
}{}
|
||||
err = json.NewDecoder(resp.Body).Decode(&filespecs)
|
||||
if checkerr("GetFile/json.Decode", err) {
|
||||
fail("Server sent garbage (or error)")
|
||||
return
|
||||
return nil, fmt.Errorf("Server sent garbage (or error): %w", err)
|
||||
}
|
||||
if filespecs.Result == nil {
|
||||
fail("Server didn't send a file info, does the file exist?")
|
||||
return
|
||||
return nil, fmt.Errorf("Server didn't send a file info, does the file exist?")
|
||||
}
|
||||
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
|
||||
return nil, fmt.Errorf("Could not retrieve file from Telegram's servers: %w", err)
|
||||
}
|
||||
defer fileresp.Body.Close()
|
||||
|
||||
rawdata, err := ioutil.ReadAll(fileresp.Body)
|
||||
if checkerr("GetFile/ioutil.ReadAll", err) {
|
||||
fail("Could not read file data")
|
||||
return
|
||||
return nil, fmt.Errorf("Could not read file data: %w", err)
|
||||
}
|
||||
|
||||
rawlen := len(rawdata)
|
||||
|
@ -428,21 +425,51 @@ func (t Telegram) GetFile(data FileRequestData, client net.Conn, callback int) {
|
|||
// ???
|
||||
log.Printf("[GetFile] WARN ?? Downloaded file does not match provided filesize: %d != %d\n", rawlen, *result.Size)
|
||||
}
|
||||
b64data := base64.StdEncoding.EncodeToString(rawdata)
|
||||
return rawdata, nil
|
||||
}
|
||||
|
||||
func (t Telegram) GetFileNet(data FileRequestData, client net.Conn, callback int) {
|
||||
byt, err := t.GetFile(data)
|
||||
if err != nil {
|
||||
errstr := err.Error()
|
||||
errmsg, _ := json.Marshal(BrokerUpdate{
|
||||
Type: BError,
|
||||
Error: &errstr,
|
||||
Callback: &callback,
|
||||
})
|
||||
fmt.Fprintln(client, string(errmsg))
|
||||
}
|
||||
|
||||
b64data := base64.StdEncoding.EncodeToString(byt)
|
||||
clientmsg, err := json.Marshal(BrokerUpdate{
|
||||
Type: BFile,
|
||||
Bytes: &b64data,
|
||||
Callback: &callback,
|
||||
})
|
||||
if checkerr("GetFile/json.Marshal", err) {
|
||||
fail("Could not serialize reply JSON")
|
||||
errstr := "Could not serialize reply JSON"
|
||||
errmsg, _ := json.Marshal(BrokerUpdate{
|
||||
Type: BError,
|
||||
Error: &errstr,
|
||||
Callback: &callback,
|
||||
})
|
||||
fmt.Fprintln(client, string(errmsg))
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Fprintln(client, string(clientmsg))
|
||||
}
|
||||
|
||||
func (t Telegram) GetMe() (APIUser, error) {
|
||||
resp, err := http.Get(t.apiURL("getMe"))
|
||||
if checkerr("GetMe/get", err) {
|
||||
return APIUser{}, err
|
||||
}
|
||||
var result apiUserResponse
|
||||
err = json.NewDecoder(resp.Body).Decode(&result)
|
||||
return result.Result, err
|
||||
}
|
||||
|
||||
func (t Telegram) apiURL(method string) string {
|
||||
return APIEndpoint + "bot" + t.Token + "/" + method
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue