aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTrumeet <yuuta@yuuta.moe>2021-10-11 00:31:10 -0700
committerTrumeet <yuuta@yuuta.moe>2021-10-11 00:31:10 -0700
commit8e1fcfd0dd7dc8fa9f5450253d12e89dfafdd855 (patch)
tree2abe9bc29104bc518ba2c17afbe880ca1b6cb944
downloadeasy-tg-8e1fcfd0dd7dc8fa9f5450253d12e89dfafdd855.tar
easy-tg-8e1fcfd0dd7dc8fa9f5450253d12e89dfafdd855.tar.gz
easy-tg-8e1fcfd0dd7dc8fa9f5450253d12e89dfafdd855.tar.bz2
easy-tg-8e1fcfd0dd7dc8fa9f5450253d12e89dfafdd855.zip
First Commit
-rw-r--r--.gitignore2
-rw-r--r--README.md35
-rwxr-xr-xbuild5
-rw-r--r--easy-tg.c300
-rw-r--r--easy-tg.h103
-rw-r--r--example.c141
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
diff --git a/build b/build
new file mode 100755
index 0000000..9191837
--- /dev/null
+++ b/build
@@ -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;
+}