1
This repository has been archived on 2022-11-04. You can view files and clone it, but cannot push or open issues or pull requests.
fwzookeeper_helper/vendor/github.com/Arman92/go-tdlib/tdlib.go

365 lines
12 KiB
Go
Raw Normal View History

2018-12-04 08:32:11 +04:00
package tdlib
//#cgo linux CFLAGS: -I/usr/local/include
//#cgo darwin CFLAGS: -I/usr/local/include
//#cgo windows CFLAGS: -IC:/src/td -IC:/src/td/build
//#cgo linux LDFLAGS: -L/usr/local/lib -ltdjson_static -ltdjson_private -ltdclient -ltdcore -ltdactor -ltddb -ltdsqlite -ltdnet -ltdutils -lstdc++ -lssl -lcrypto -ldl -lz -lm
//#cgo darwin LDFLAGS: -L/usr/local/lib -L/usr/local/opt/openssl/lib -ltdjson_static -ltdjson_private -ltdclient -ltdcore -ltdactor -ltddb -ltdsqlite -ltdnet -ltdutils -lstdc++ -lssl -lcrypto -ldl -lz -lm
//#cgo windows LDFLAGS: -LC:/src/td/build/Debug -ltdjson
//#include <stdlib.h>
//#include <td/telegram/td_json_client.h>
//#include <td/telegram/td_log.h>
import "C"
import (
"encoding/json"
"errors"
"fmt"
"math/rand"
"reflect"
"sync"
"time"
"unsafe"
)
// UpdateData alias for use in UpdateMsg
type UpdateData map[string]interface{}
// UpdateMsg is used to unmarshal recieved json strings into
type UpdateMsg struct {
Data UpdateData
Raw []byte
}
// EventFilterFunc used to filter out unwanted messages in receiver channels
type EventFilterFunc func(msg *TdMessage) bool
// EventReceiver used to retreive updates from tdlib to user
type EventReceiver struct {
Instance TdMessage
Chan chan TdMessage
FilterFunc EventFilterFunc
}
// Client is the Telegram TdLib client
type Client struct {
Client unsafe.Pointer
Config Config
rawUpdates chan UpdateMsg
receivers []EventReceiver
waiters sync.Map
receiverLock *sync.Mutex
}
// Config holds tdlibParameters
type Config struct {
APIID string // Application identifier for Telegram API access, which can be obtained at https://my.telegram.org --- must be non-empty..
APIHash string // Application identifier hash for Telegram API access, which can be obtained at https://my.telegram.org --- must be non-empty..
SystemLanguageCode string // IETF language tag of the user's operating system language; must be non-empty.
DeviceModel string // Model of the device the application is being run on; must be non-empty.
SystemVersion string // Version of the operating system the application is being run on; must be non-empty.
ApplicationVersion string // Application version; must be non-empty.
// Optional fields
UseTestDataCenter bool // if set to true, the Telegram test environment will be used instead of the production environment.
DatabaseDirectory string // The path to the directory for the persistent database; if empty, the current working directory will be used.
FileDirectory string // The path to the directory for storing files; if empty, database_directory will be used.
UseFileDatabase bool // If set to true, information about downloaded and uploaded files will be saved between application restarts.
UseChatInfoDatabase bool // If set to true, the library will maintain a cache of users, basic groups, supergroups, channels and secret chats. Implies use_file_database.
UseMessageDatabase bool // If set to true, the library will maintain a cache of chats and messages. Implies use_chat_info_database.
UseSecretChats bool // If set to true, support for secret chats will be enabled.
EnableStorageOptimizer bool // If set to true, old files will automatically be deleted.
IgnoreFileNames bool // If set to true, original file names will be ignored. Otherwise, downloaded files will be saved under names as close as possible to the original name.
}
// NewClient Creates a new instance of TDLib.
// Has two public fields:
// Client itself and RawUpdates channel
func NewClient(config Config) *Client {
// Seed rand with time
rand.Seed(time.Now().UnixNano())
client := Client{Client: C.td_json_client_create()}
client.receivers = make([]EventReceiver, 0, 1)
client.receiverLock = &sync.Mutex{}
client.Config = config
go func() {
for {
// get update
updateBytes := client.Receive(10)
var updateData UpdateData
json.Unmarshal(updateBytes, &updateData)
// does new update has @extra field?
if extra, hasExtra := updateData["@extra"].(string); hasExtra {
// trying to load update with this salt
if waiter, found := client.waiters.Load(extra); found {
// found? send it to waiter channel
waiter.(chan UpdateMsg) <- UpdateMsg{Data: updateData, Raw: updateBytes}
// trying to prevent memory leak
close(waiter.(chan UpdateMsg))
}
} else {
// does new updates has @type field?
if msgType, hasType := updateData["@type"]; hasType {
if client.rawUpdates != nil {
// if rawUpdates is initialized, send the update in rawUpdates channel
client.rawUpdates <- UpdateMsg{Data: updateData, Raw: updateBytes}
}
client.receiverLock.Lock()
for _, receiver := range client.receivers {
if msgType == receiver.Instance.MessageType() {
var newMsg TdMessage
newMsg = reflect.New(reflect.ValueOf(receiver.Instance).Elem().Type()).Interface().(TdMessage)
err := json.Unmarshal(updateBytes, &newMsg)
if err != nil {
fmt.Printf("Error unmarhaling to type %v", err)
}
if receiver.FilterFunc(&newMsg) {
receiver.Chan <- newMsg
}
}
}
client.receiverLock.Unlock()
}
}
}
}()
return &client
}
// GetRawUpdatesChannel creates a general channel that fetches every update comming from tdlib
func (client *Client) GetRawUpdatesChannel(capacity int) chan UpdateMsg {
client.rawUpdates = make(chan UpdateMsg, capacity)
return client.rawUpdates
}
// AddEventReceiver adds a new receiver to be subscribed in receiver channels
func (client *Client) AddEventReceiver(msgInstance TdMessage, filterFunc EventFilterFunc, channelCapacity int) EventReceiver {
receiver := EventReceiver{
Instance: msgInstance,
Chan: make(chan TdMessage, channelCapacity),
FilterFunc: filterFunc,
}
client.receiverLock.Lock()
defer client.receiverLock.Unlock()
client.receivers = append(client.receivers, receiver)
return receiver
}
// DestroyInstance Destroys the TDLib client instance.
// After this is called the client instance shouldn't be used anymore.
func (client *Client) DestroyInstance() {
C.td_json_client_destroy(client.Client)
}
// Send Sends request to the TDLib client.
// You can provide string or UpdateData.
func (client *Client) Send(jsonQuery interface{}) {
var query *C.char
switch jsonQuery.(type) {
case string:
query = C.CString(jsonQuery.(string))
case UpdateData:
jsonBytes, _ := json.Marshal(jsonQuery.(UpdateData))
query = C.CString(string(jsonBytes))
}
defer C.free(unsafe.Pointer(query))
C.td_json_client_send(client.Client, query)
}
// Receive Receives incoming updates and request responses from the TDLib client.
// You can provide string or UpdateData.
func (client *Client) Receive(timeout float64) []byte {
result := C.td_json_client_receive(client.Client, C.double(timeout))
return []byte(C.GoString(result))
}
// Execute Synchronously executes TDLib request.
// Only a few requests can be executed synchronously.
func (client *Client) Execute(jsonQuery interface{}) UpdateMsg {
var query *C.char
switch jsonQuery.(type) {
case string:
query = C.CString(jsonQuery.(string))
case UpdateData:
jsonBytes, _ := json.Marshal(jsonQuery.(UpdateData))
query = C.CString(string(jsonBytes))
}
defer C.free(unsafe.Pointer(query))
result := C.td_json_client_execute(client.Client, query)
var update UpdateData
json.Unmarshal([]byte(C.GoString(result)), &update)
return UpdateMsg{Data: update, Raw: []byte(C.GoString(result))}
}
// SetFilePath Sets the path to the file to where the internal TDLib log will be written.
// By default TDLib writes logs to stderr or an OS specific log.
// Use this method to write the log to a file instead.
func SetFilePath(path string) {
2019-10-08 05:51:49 +04:00
bytes, _ := json.Marshal(UpdateData{
"@type": "setLogStream",
"log_stream": UpdateData{
"@type": "logStreamFile",
"path": path,
"max_file_size": 10485760,
},
})
query := C.CString(string(bytes))
C.td_json_client_execute(nil, query)
C.free(unsafe.Pointer(query))
2018-12-04 08:32:11 +04:00
}
// SetLogVerbosityLevel Sets the verbosity level of the internal logging of TDLib.
// By default the TDLib uses a verbosity level of 5 for logging.
func SetLogVerbosityLevel(level int) {
2019-10-08 05:51:49 +04:00
bytes, _ := json.Marshal(UpdateData{
"@type": "setLogVerbosityLevel",
"new_verbosity_level": level,
})
query := C.CString(string(bytes))
C.td_json_client_execute(nil, query)
C.free(unsafe.Pointer(query))
2018-12-04 08:32:11 +04:00
}
// SendAndCatch Sends request to the TDLib client and catches the result in updates channel.
// You can provide string or UpdateData.
func (client *Client) SendAndCatch(jsonQuery interface{}) (UpdateMsg, error) {
var update UpdateData
switch jsonQuery.(type) {
case string:
// unmarshal JSON into map, we don't have @extra field, if user don't set it
json.Unmarshal([]byte(jsonQuery.(string)), &update)
case UpdateData:
update = jsonQuery.(UpdateData)
}
// letters for generating random string
letterBytes := "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
// generate random string for @extra field
b := make([]byte, 32)
for i := range b {
b[i] = letterBytes[rand.Intn(len(letterBytes))]
}
randomString := string(b)
// set @extra field
update["@extra"] = randomString
// create waiter chan and save it in Waiters
waiter := make(chan UpdateMsg, 1)
client.waiters.Store(randomString, waiter)
// send it through already implemented method
client.Send(update)
select {
// wait response from main loop in NewClient()
case response := <-waiter:
return response, nil
// or timeout
case <-time.After(10 * time.Second):
client.waiters.Delete(randomString)
return UpdateMsg{}, errors.New("timeout")
}
}
// Authorize is used to authorize the users
func (client *Client) Authorize() (AuthorizationState, error) {
state, err := client.GetAuthorizationState()
if err != nil {
return nil, err
}
if state.GetAuthorizationStateEnum() == AuthorizationStateWaitEncryptionKeyType {
ok, err := client.CheckDatabaseEncryptionKey(nil)
if ok == nil || err != nil {
return nil, err
}
} else if state.GetAuthorizationStateEnum() == AuthorizationStateWaitTdlibParametersType {
client.sendTdLibParams()
}
authState, err := client.GetAuthorizationState()
return authState, err
}
func (client *Client) sendTdLibParams() {
client.Send(UpdateData{
"@type": "setTdlibParameters",
"parameters": UpdateData{
"@type": "tdlibParameters",
"use_test_dc": client.Config.UseTestDataCenter,
"database_directory": client.Config.DatabaseDirectory,
"files_directory": client.Config.FileDirectory,
"use_file_database": client.Config.UseFileDatabase,
"use_chat_info_database": client.Config.UseChatInfoDatabase,
"use_message_database": client.Config.UseMessageDatabase,
"use_secret_chats": client.Config.UseSecretChats,
"api_id": client.Config.APIID,
"api_hash": client.Config.APIHash,
"system_language_code": client.Config.SystemLanguageCode,
"device_model": client.Config.DeviceModel,
"system_version": client.Config.SystemVersion,
"application_version": client.Config.ApplicationVersion,
"enable_storage_optimizer": client.Config.EnableStorageOptimizer,
"ignore_file_names": client.Config.IgnoreFileNames,
},
})
}
// SendPhoneNumber sends phone number to tdlib
func (client *Client) SendPhoneNumber(phoneNumber string) (AuthorizationState, error) {
_, err := client.SetAuthenticationPhoneNumber(phoneNumber, false, false)
if err != nil {
return nil, err
}
authState, err := client.GetAuthorizationState()
return authState, err
}
// SendAuthCode sends auth code to tdlib
func (client *Client) SendAuthCode(code string) (AuthorizationState, error) {
_, err := client.CheckAuthenticationCode(code, "", "")
if err != nil {
return nil, err
}
authState, err := client.GetAuthorizationState()
return authState, err
}
// SendAuthPassword sends two-step verification password (user defined)to tdlib
func (client *Client) SendAuthPassword(password string) (AuthorizationState, error) {
_, err := client.CheckAuthenticationPassword(password)
if err != nil {
return nil, err
}
authState, err := client.GetAuthorizationState()
return authState, err
}