diff options
author | Trumeet <yuuta@yuuta.moe> | 2021-10-11 00:31:10 -0700 |
---|---|---|
committer | Trumeet <yuuta@yuuta.moe> | 2021-10-11 00:31:10 -0700 |
commit | 8e1fcfd0dd7dc8fa9f5450253d12e89dfafdd855 (patch) | |
tree | 2abe9bc29104bc518ba2c17afbe880ca1b6cb944 | |
download | easy-tg-8e1fcfd0dd7dc8fa9f5450253d12e89dfafdd855.tar easy-tg-8e1fcfd0dd7dc8fa9f5450253d12e89dfafdd855.tar.gz easy-tg-8e1fcfd0dd7dc8fa9f5450253d12e89dfafdd855.tar.bz2 easy-tg-8e1fcfd0dd7dc8fa9f5450253d12e89dfafdd855.zip |
First Commit
-rw-r--r-- | .gitignore | 2 | ||||
-rw-r--r-- | README.md | 35 | ||||
-rwxr-xr-x | build | 5 | ||||
-rw-r--r-- | easy-tg.c | 300 | ||||
-rw-r--r-- | easy-tg.h | 103 | ||||
-rw-r--r-- | example.c | 141 |
6 files changed, 586 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..25064ae --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +*.o +example diff --git a/README.md b/README.md new file mode 100644 index 0000000..8c6e9ba --- /dev/null +++ b/README.md @@ -0,0 +1,35 @@ +# easy-tg + +Easy-to-use helpers for TDLib JSON. + +**Not completely tested yet.** + +## Dependencies + +* tdlib + +* json-c + +* pthread (if you compile with `TG_ENABLE_PTHREAD`) + +## Functions + +* Automatically handles authorization state changes. + +* Various helper functions. + +## Supported platforms + +* Any platform that `json-c` supports, + +* Any platform that `tdjson` supports, and + +* Any platform that supports C99. + +## How to use + +See `example.c`. Put your API keys into `build` then run it. + +## License + +Copyright Yuuta Liang, Apache 2.0. Originally published at https://git.yuuta.moe/easy-tg.git @@ -0,0 +1,5 @@ +#!/bin/sh +set -ex +cc -o easy-td.o -std=c99 -I. -DTG_ENABLE_PTHREAD -DTG_DEBUG -fsanitize=address -rdynamic -O0 -g3 -c easy-tg.c +cc -o example.o -std=c99 -I. -DAPI_ID=114514 -DAPI_HASH="\"hhhhhAAAAA\"" -fsanitize=address -rdynamic -O0 -g3 -c example.c +cc -o example -std=c99 -lpthread -ltdjson -ljson-c -fsanitize=address easy-td.o example.o diff --git a/easy-tg.c b/easy-tg.c new file mode 100644 index 0000000..1d89a2b --- /dev/null +++ b/easy-tg.c @@ -0,0 +1,300 @@ +#include "easy-tg.h" + +#include <stdio.h> +#include <string.h> + +#include <td/telegram/td_json_client.h> +#include <json-c/json_object.h> +#include <json-c/json_tokener.h> + +void *td = NULL; +const char *tg_update_type = NULL; +json_object *tg_update = NULL; +int tg_errno = 0; +const void *tg_reg1 = NULL; +const void *tg_reg2 = NULL; +const void *tg_reg3 = NULL; +const void *tg_reg4 = NULL; +const void *tg_reg5 = NULL; +const void *tg_reg6 = NULL; + +#ifdef TG_ENABLE_PTHREAD +#include <pthread.h> +static pthread_mutex_t mutex_close = PTHREAD_MUTEX_INITIALIZER; +#endif + +json_object *tg_mkreq(const char *extra, const char *type, const char *args_key, json_object *args) +{ + json_object *root = json_object_new_object(); + json_object_object_add(root, "@extra", json_object_new_string(extra)); + json_object_object_add(root, "@type", json_object_new_string(type)); + if(args_key != NULL) json_object_object_add(root, args_key, args); + return root; +} + +int tg_send_raw(bool execute, const char *req) +{ +#ifdef TG_DEBUG + printf("> %s\n", req); +#endif + if(execute) + td_json_client_execute(td, req); + else + td_json_client_send(td, req); + return 0; +} + +int tg_send(bool execute, json_object *root, const bool dispose) +{ + int r = 0; + const char *req = json_object_to_json_string(root); + r = tg_send_raw(execute, req); + if(dispose) + { + json_object_put(root); + } + return r; +} + +int tg_login_phone( + const char *phone_number + ) +{ + int r = 0; + json_object *raw_req = json_object_new_string(phone_number); + tg_send(false, tg_mkreq(TG_REQ_LOGIN_PHONE, "setAuthenticationPhoneNumber", "phone_number", raw_req), true); + goto cleanup; +cleanup: + return r; +} + +int tg_login_code( + const char *code + ) +{ + int r = 0; + json_object *raw_req = json_object_new_string(code); + tg_send(false, tg_mkreq(TG_REQ_LOGIN_CODE, "checkAuthenticationCode", "code", raw_req), true); + goto cleanup; +cleanup: + return r; +} + +int tg_login_pass( + const char *pass + ) +{ + int r = 0; + json_object *raw_req = json_object_new_string(pass); + tg_send(false, tg_mkreq(TG_REQ_LOGIN_PASS, "checkAuthenticationPassword", "password", raw_req), true); + goto cleanup; +cleanup: + return r; +} + +int tg_login_enc_key( + const char *encryption_key + ) +{ + int r = 0; + json_object *raw_req = json_object_new_string(encryption_key); + tg_send(false, tg_mkreq("-1", "checkDatabaseEncryptionKey", "encryption_key", raw_req), true); + goto cleanup; +cleanup: + return r; +} + +int tg_set_params( + const bool use_test_dc, + const char *database_directory, + const char *files_directory, + const bool use_file_database, + const bool use_chat_info_database, + const bool use_message_database, + const bool use_secret_chats, + const int32_t api_id, + const char *api_hash, + const char *system_language_code, + const char *device_model, + const char *system_version, + const char *application_version, + const bool enable_storage_optimizer, + const bool ignore_file_names + ) +{ + int r = 0; + json_object *raw_req = json_object_new_object(); + json_object_object_add(raw_req, "use_test_dc", json_object_new_boolean(use_test_dc)); + json_object_object_add(raw_req, "database_directory", json_object_new_string(database_directory)); + if(files_directory != NULL) + json_object_object_add(raw_req, "files_directory", json_object_new_string(files_directory)); + json_object_object_add(raw_req, "use_file_database", json_object_new_boolean(use_file_database)); + json_object_object_add(raw_req, "use_message_database", json_object_new_boolean(use_message_database)); + json_object_object_add(raw_req, "use_secret_chats", json_object_new_boolean(use_secret_chats)); + json_object_object_add(raw_req, "api_id", json_object_new_int(api_id)); + json_object_object_add(raw_req, "api_hash", json_object_new_string(api_hash)); + json_object_object_add(raw_req, "system_language_code", json_object_new_string(system_language_code)); + json_object_object_add(raw_req, "device_model", json_object_new_string(device_model)); + json_object_object_add(raw_req, "system_version", json_object_new_string(system_version)); + json_object_object_add(raw_req, "application_version", json_object_new_string(application_version)); + json_object_object_add(raw_req, "use_secret_chats", json_object_new_boolean(use_secret_chats)); + json_object_object_add(raw_req, "enable_storage_optimizer", json_object_new_boolean(enable_storage_optimizer)); + json_object_object_add(raw_req, "ignore_file_names", json_object_new_boolean(ignore_file_names)); + tg_send(false, tg_mkreq(TG_REQ_SET_PARAMS, "setTdlibParameters", "parameters", raw_req), true); + goto cleanup; +cleanup: + return r; +} + +static int handle_update_error(const json_object *json) +{ + int r = TG_IGNORE; + int code = -1; + const char *msg = NULL; + const char *extra = NULL; + json_object *obj = NULL; + if(!json_object_object_get_ex(json, "code", &obj)) + goto cleanup; + code = json_object_get_int(obj); + if(!json_object_object_get_ex(json, "message", &obj)) + goto cleanup; + msg = json_object_get_string(obj); + if(json_object_object_get_ex(json, "@extra", &obj)) + extra = json_object_get_string(obj); + tg_errno = code; + tg_reg1 = extra; + tg_reg2 = msg; + r = TG_ERROR; + goto cleanup; +cleanup: + return r; +} + +static int handle_update_authorization_state(const json_object *json) +{ + int r = 0; + json_object *obj = NULL; + if(!json_object_object_get_ex(json, "authorization_state", &obj)) + goto cleanup; + json_object *type_obj = NULL; + if(!json_object_object_get_ex(obj, "@type", &type_obj)) + goto cleanup; + const char *type = json_object_get_string(type_obj); +#ifdef TG_DEBUG + printf("AUTH: %s\n", type); +#endif + if(!strcmp(type, "authorizationStateClosed")) + r = TG_CLOSED; + else if(!strcmp(type, "authorizationStateClosing")) + r = TG_CLOSING; + else if(!strcmp(type, "authorizationStateLoggingOut")) + r = TG_LOGGING_OUT; + else if(!strcmp(type, "authorizationStateWaitTdlibParameters")) + r = TG_PARAMS; + else if(!strcmp(type, "authorizationStateWaitEncryptionKey")) + r = TG_LOGIN_ENC; + else if(!strcmp(type, "authorizationStateWaitPhoneNumber")) + r = TG_LOGIN_PHONE; + else if(!strcmp(type, "authorizationStateWaitCode")) + r = TG_LOGIN_CODE; + else if(!strcmp(type, "authorizationStateWaitPassword")) + r = TG_LOGIN_PW; + else if(!strcmp(type, "authorizationStateWaitRegistration")) + r = TG_LOGIN_REG; + else if(!strcmp(type, "authorizationStateWaitOtherDeviceConfirmation")) + { + r = TG_LOGIN_QR; + if(json_object_object_get_ex(obj, "link", &obj)) + tg_reg1 = json_object_get_string(obj); + } + else if(!strcmp(type, "authorizationStateReady")) + r = TG_LOGGED_IN; + else + r = TG_RUN; + goto cleanup; +cleanup: + return r; +} + +int tg_init() +{ + int r = 0; + td = td_json_client_create(); + if(td == NULL) + { + r = TG_SYSERR; + goto cleanup; + } + tg_send_raw(true, "{ \"@extra\": \"log\", \"@type\": \"setLogVerbosityLevel\", \"@args\": { \"new_verbosity_level\": 0 } }"); + goto cleanup; +cleanup: + return r; +} + +int tg_destroy() +{ +#ifdef TG_ENABLE_PTHREAD + pthread_mutex_lock(&mutex_close); +#endif + if(td == NULL) + { + return 0; + } + td_json_client_destroy(td); + td = NULL; +#ifdef TG_ENABLE_PTHREAD + pthread_mutex_unlock(&mutex_close); +#endif + return 0; +} + +int tg_loop() +{ + if(tg_update != NULL) + { + json_object_put(tg_update); + tg_update = NULL; + tg_update_type = NULL; + tg_errno = 0; + tg_reg1 = NULL; + tg_reg2 = NULL; + tg_reg3 = NULL; + tg_reg4 = NULL; + tg_reg5 = NULL; + tg_reg6 = NULL; + } + const char *event = td_json_client_receive(td, 3); + int r = TG_IGNORE; + if(event == NULL) goto cleanup; +#ifdef TG_DEBUG + printf("< %s\n", event); +#endif + tg_update = json_tokener_parse(event); + json_object *obj = NULL; + if(!json_object_object_get_ex(tg_update, "@type", &obj)) + goto cleanup; + tg_update_type = json_object_get_string(obj); + if(!strcmp(tg_update_type, "updateAuthorizationState")) + r = handle_update_authorization_state(tg_update); + else if(!strcmp(tg_update_type, "error")) + r = handle_update_error(tg_update); + else + r = TG_RUN; + goto cleanup; +cleanup: + return r; +} + +int tg_close() +{ +#ifdef TG_ENABLE_PTHREAD + pthread_mutex_lock(&mutex_close); +#endif + if(td == NULL) return 0; + /* Using json object seems to be leaking */ + tg_send_raw(false, "{ \"@type\": \"close\" }"); +#ifdef TG_ENABLE_PTHREAD + pthread_mutex_unlock(&mutex_close); +#endif + return 0; +} diff --git a/easy-tg.h b/easy-tg.h new file mode 100644 index 0000000..fd66314 --- /dev/null +++ b/easy-tg.h @@ -0,0 +1,103 @@ +#ifndef _EASY_TG_H +#define _EASY_TG_H + +#define TG_IGNORE -1 /* Invalid update received. Just ignore it. */ +#define TG_RUN 0 /* Normal update received. */ +#define TG_CLOSED 1 /* TDLib closed. */ +#define TG_CLOSING 2 /* TDLib is closing. */ +#define TG_LOGGING_OUT 3 /* Logging out. */ +#define TG_LOGGED_IN 4 /* Login ready. */ +#define TG_LOGIN_PHONE 5 /* Need to input phone number. */ +#define TG_LOGIN_CODE 6 /* Need to input verification code. */ +#define TG_LOGIN_ENC 7 /* Need to provide encryption key. */ +#define TG_LOGIN_QR 8 /* Need to scan the QR code. r1 = link. */ +#define TG_LOGIN_PW 9 /* Need to input the password. */ +#define TG_LOGIN_REG 10 /* Need to register. */ +#define TG_PARAMS 11 /* Need to provide TDLib parameters. */ +#define TG_ERROR 12 /* TDLib returns an error. td_errno = code, r1 = extra, r2 = msg. */ +#define TG_SYSERR 13 /* EasyTD encounters an error. */ + +#define TG_REQ_SET_PARAMS "set_params" +#define TG_REQ_LOGIN_PHONE "login_phone" +#define TG_REQ_LOGIN_CODE "login_code" +#define TG_REQ_LOGIN_PASS "login_pass" + +#include <json-c/json_object.h> +#include <stdint.h> +#include <stdbool.h> + +extern void *td; +extern const char *tg_update_type; +extern json_object *tg_update; +extern int tg_errno; +extern const void *tg_reg1; +extern const void *tg_reg2; +extern const void *tg_reg3; +extern const void *tg_reg4; +extern const void *tg_reg5; +extern const void *tg_reg6; + +/* MT-Unsafe */ +int tg_init(); +/* MT-Unsafe */ +int tg_destroy(); +/* MT-Unsafe */ +int tg_loop(); +/* MT-Safe */ +int tg_close(); +/* MT-Safe */ +int tg_logout(); +/* MT-Safe */ +int tg_set_params( + const bool use_test_dc, + const char *database_directory, + const char *files_directory, + const bool use_file_database, + const bool use_message_database, + const bool use_chat_info_database, + const bool use_secret_chats, + const int32_t api_id, + const char *api_hash, + const char *system_language_code, + const char *device_model, + const char *system_version, + const char *application_version, + const bool enable_storage_optimizer, + const bool ignore_file_names + ); +/* MT-Safe */ +int tg_login_phone( + const char *phone_number + ); +/* MT-Safe */ +int tg_login_code( + const char *code + ); +/* MT-Safe */ +int tg_login_pass( + const char *password + ); +/* MT-Safe */ +int tg_login_enc_key( + const char *encryption_key + ); +/* MT-Safe */ +json_object *tg_mkreq( + const char *extra, + const char *type, + const char *args_key, + json_object *args + ); +/* MT-Safe */ +int tg_send( + bool execute, + json_object *req, + const bool dispose + ); +/* MT-Safe */ +int tg_send_raw( + bool execute, + const char *req + ); + +#endif /* _EASY_TG_H */ diff --git a/example.c b/example.c new file mode 100644 index 0000000..fb05d98 --- /dev/null +++ b/example.c @@ -0,0 +1,141 @@ +#define TG_DEBUG +#define TG_ENABLE_PTHREAD + +#include "easy-tg.h" +#include <stdio.h> +#include <unistd.h> +#include <string.h> + +/* In this example we are not closing TDLib properly. In a real-world program, you need to call tg_close() to make a close request to TDLib (any thread is OK), and wait for the TG_CLOSED response. */ + +static void read_input(const char *prompt, char *buf, unsigned int len) +{ + printf("%s", prompt); + ssize_t size = read(0, buf, len + 1); + if(size >= 1) buf[size - 1] = '\0'; + printf("Read: %s\n", buf); +} + +static void run() +{ + /* In each loop, the action our application needs to take is returned via return code. + * Other data, such as current update type and the whole JSON response, are stored in registers. + * There are nine registers: + * int tg_errno: In case of error, it will store the error code. + * const char *tg_update_type: Always stores the parsed type of update. + * json_object *tg_update: Always stores the whole tokenized JSON update. + * void *tg_reg1: Optional data field. + * void *tg_reg2: Optional data field. + * void *tg_reg3: Optional data field. + * void *tg_reg4: Optional data field. + * void *tg_reg5: Optional data field. + * void *tg_reg6: Optional data field. + * Each register has the lifetime from the end of tg_loop() to the next call of tg_loop(). + * After calling tg_loop() for the next time, all memory will be released and the registers will be reset to default values, which are 0 for int and NULL for pointers. + */ + printf("%s\n", tg_update_type); +} + +int main(int argc, char **argv) +{ + int r = 0; + r = tg_init(); + if(r) goto cleanup; /* Fetal errors encountered. */ + while(1) + { + switch(tg_loop()) + { + case TG_IGNORE: /* Invalid response from TDLib. Just ignore it. */ + break; + case TG_RUN: /* Non-login update received. Run our custom function. */ + run(); + break; + case TG_CLOSED: /* TDLib is shutting down. We have nothing to do except releasing resources. */ + goto destroy; + case TG_CLOSING: /* TDLib is closing. Currently we don't need to do nothing. */ + case TG_LOGGING_OUT: /* The account is logging out. No need for now. */ + break; + case TG_LOGGED_IN: /* Login ready. */ + printf("You are logged in!\n"); + break; + case TG_LOGIN_PHONE: /* We need to provide the phone number to login. */ + char phone[20]; + read_input("Phone number: ", phone, 19); + tg_login_phone(phone); + break; + case TG_LOGIN_CODE: /* We need to provide the verification code. */ + char code[7]; + read_input("Verification code: ", code, 6); + tg_login_code(code); + break; + case TG_LOGIN_ENC: /* During initialization, TDLib is asking us for database encryption key. */ + tg_login_enc_key(""); /* Just leave it empty for now. */ + break; + case TG_LOGIN_QR: /* QR login. Currently not supported in the demo. */ + /* The QR link is stored in register 1. */ + /* generate_qr((char*)tg_reg1); */ + printf("QR login not supported.\n"); + break; + case TG_LOGIN_PW: /* We need to type the password. */ + char pass[257]; + read_input("Password: ", pass, 256); + tg_login_pass(pass); + break; + case TG_LOGIN_REG: /* The phone number is verified but unknown. We need fill in the registration form. In this case, we will ask the user to register in the official client instead. */ + printf("Your phone number is not known.\n"); + break; + case TG_PARAMS: /* TDLib is not been setup and is asking you for parameters. */ + tg_set_params( + true, /* Use test datacenter */ + "/tmp/td", /* Database directory */ + NULL, /* Files directory */ + false, /* Use files database */ + false, /* Use message database */ + false, /* Use chat info database */ + false, /* Support secret chats */ + API_ID, /* API ID */ + API_HASH, /* API Hash */ + "en", /* System language code */ + "Desktop", /* Device model */ + "Windoge 114.514", /* System version */ + "Moe Bot", /* Application version */ + false, /* Enable storage optimizer */ + true /* Ignore file names */ + ); + break; + case TG_ERROR: /* TDLib returned an error. */ + /* Error code is stored in tg_errno; + * Request extra (ID) is stored in register 1; + * Error message is stored in register 2. + */ + /* Built-in request extras */ + if(!strcmp(TG_REQ_SET_PARAMS, tg_reg1)) + { + printf("Invalid TDLib parameters! %d (%s)\n", tg_errno, tg_reg2); + } else if(!strcmp(TG_REQ_LOGIN_PHONE, tg_reg1)) + { + printf("Invalid phone number! %d (%s)\n", tg_errno, tg_reg2); + } else if(!strcmp(TG_REQ_LOGIN_PASS, tg_reg1)) + { + printf("Invalid password! %d (%s)\n", tg_errno, tg_reg2); + } else + { + printf("Invalid request: %s! %d (%s)\n", tg_reg1, tg_errno, tg_reg2); + } + break; + case TG_SYSERR: /* Easy-TG encountered an error. */ + /* Error code is stored in tg_errno. */ + printf("Fetal: %d\n", tg_errno); + break; + default: + printf("???\n"); + break; + } + } + goto destroy; +destroy: + tg_destroy(); + goto cleanup; +cleanup: + return r; +} |