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 //#include //#include 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) { query := C.CString(path) defer C.free(unsafe.Pointer(query)) C.td_set_log_file_path(query) } // 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) { C.td_set_log_verbosity_level(C.int(level)) } // 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 }