aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore76
-rw-r--r--.gitmodules3
-rw-r--r--.idea/.gitignore8
-rw-r--r--.idea/misc.xml4
-rw-r--r--.idea/modules.xml8
-rw-r--r--.idea/tmirror.iml2
-rw-r--r--.idea/vcs.xml7
-rw-r--r--CMakeLists.txt27
-rw-r--r--LICENSE13
-rw-r--r--README.md51
-rw-r--r--cmdline.c82
-rw-r--r--config.h.in7
-rw-r--r--handlers.c43
-rw-r--r--handlers.h8
-rw-r--r--main.c20
-rw-r--r--mutebot.h20
m---------td0
-rw-r--r--tdutils.c364
-rw-r--r--tdutils.h23
19 files changed, 766 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..f846957
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,76 @@
+cmake-build-debug/
+cmake-build-release/
+# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
+# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
+
+# User-specific stuff
+.idea/**/workspace.xml
+.idea/**/tasks.xml
+.idea/**/usage.statistics.xml
+.idea/**/dictionaries
+.idea/**/shelf
+
+# AWS User-specific
+.idea/**/aws.xml
+
+# Generated files
+.idea/**/contentModel.xml
+
+# Sensitive or high-churn files
+.idea/**/dataSources/
+.idea/**/dataSources.ids
+.idea/**/dataSources.local.xml
+.idea/**/sqlDataSources.xml
+.idea/**/dynamic.xml
+.idea/**/uiDesigner.xml
+.idea/**/dbnavigator.xml
+
+# Gradle
+.idea/**/gradle.xml
+.idea/**/libraries
+
+# Gradle and Maven with auto-import
+# When using Gradle or Maven with auto-import, you should exclude module files,
+# since they will be recreated, and may cause churn. Uncomment if using
+# auto-import.
+# .idea/artifacts
+# .idea/compiler.xml
+# .idea/jarRepositories.xml
+# .idea/modules.xml
+# .idea/*.iml
+# .idea/modules
+# *.iml
+# *.ipr
+
+# CMake
+cmake-build-*/
+
+# Mongo Explorer plugin
+.idea/**/mongoSettings.xml
+
+# File-based project format
+*.iws
+
+# IntelliJ
+out/
+
+# mpeltonen/sbt-idea plugin
+.idea_modules/
+
+# JIRA plugin
+atlassian-ide-plugin.xml
+
+# Cursive Clojure plugin
+.idea/replstate.xml
+
+# Crashlytics plugin (for Android Studio and IntelliJ)
+com_crashlytics_export_strings.xml
+crashlytics.properties
+crashlytics-build.properties
+fabric.properties
+
+# Editor-based Rest Client
+.idea/httpRequests
+
+# Android studio 3.1+ serialized cache file
+.idea/caches/build_file_checksums.ser
diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 0000000..f1273f5
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,3 @@
+[submodule "td"]
+ path = td
+ url = https://github.com/tdlib/td.git
diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000..73f69e0
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,8 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml
+# Editor-based HTTP Client requests
+/httpRequests/
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000..79b3c94
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+ <component name="CMakeWorkspace" PROJECT_DIR="$PROJECT_DIR$" />
+</project> \ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 0000000..c43cbd1
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+ <component name="ProjectModuleManager">
+ <modules>
+ <module fileurl="file://$PROJECT_DIR$/.idea/tmirror.iml" filepath="$PROJECT_DIR$/.idea/tmirror.iml" />
+ </modules>
+ </component>
+</project> \ No newline at end of file
diff --git a/.idea/tmirror.iml b/.idea/tmirror.iml
new file mode 100644
index 0000000..f08604b
--- /dev/null
+++ b/.idea/tmirror.iml
@@ -0,0 +1,2 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module classpath="CMake" type="CPP_MODULE" version="4" /> \ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..7dc97e3
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+ <component name="VcsDirectoryMappings">
+ <mapping directory="$PROJECT_DIR$" vcs="Git" />
+ <mapping directory="$PROJECT_DIR$/td" vcs="Git" />
+ </component>
+</project> \ No newline at end of file
diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644
index 0000000..e196ca8
--- /dev/null
+++ b/CMakeLists.txt
@@ -0,0 +1,27 @@
+cmake_minimum_required(VERSION 3.0)
+project(mutebot VERSION 1.0 LANGUAGES C CXX)
+
+set(CMAKE_C_STANDARD 11)
+
+IF (CMAKE_BUILD_TYPE MATCHES Debug)
+ add_compile_definitions(TD_USE_ASAN)
+ENDIF (CMAKE_BUILD_TYPE MATCHES Debug)
+add_subdirectory(td)
+
+configure_file(config.h.in config.h)
+
+set(CMAKE_C_FLAGS_DEBUG
+ "${CMAKE_C_FLAGS_DEBUG} -g3 -O0 -fsanitize=address")
+set(CMAKE_EXE_LINKER_FLAGS_DEBUG
+ "${CMAKE_EXE_LINKER_FLAGS_DEBUG} -fsanitize=address")
+
+add_definitions(-D_POSIX_C_SOURCE=200809L)
+set(MUTEBOT_SRC
+ main.c
+ cmdline.c
+ tdutils.c
+ handlers.c
+ )
+add_executable(mutebot ${MUTEBOT_SRC})
+target_include_directories(mutebot PUBLIC "${PROJECT_BINARY_DIR}")
+target_link_libraries(mutebot PRIVATE tdc)
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..456c488
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,13 @@
+ DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
+ Version 2, December 2004
+
+ Copyright (C) 2004 Sam Hocevar <sam@hocevar.net>
+
+ Everyone is permitted to copy and distribute verbatim or modified
+ copies of this license document, and changing it is allowed as long
+ as the name is changed.
+
+ DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. You just DO WHAT THE FUCK YOU WANT TO.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..6dfb7ff
--- /dev/null
+++ b/README.md
@@ -0,0 +1,51 @@
+# MuteBot
+
+Yet another simple Telegram bot that mutes new members in groups. Support joining by invite links, requests or @ usernames.
+
+It is built upon TDLib C interface using C11.
+
+*It may be the first project actually taking TDLib C interface into production. You can use this project as an example of how to use TDLib C interface.*
+
+## Building
+
+*Why do you need to spend half a hour, gigabytes of memory and gigabytes of disk to build TDLib again? Because TDLib doesn't support dynamic linking.*
+
+```shell
+$ mkdir cmake-build-release
+$ cd cmake-build-release
+$ cmake -DCMAKE_BUILD_TYPE=Release ..
+$ make -j mutebot
+```
+
+## Usage
+
+Obtain API ID and API Hash from [https://my.telegram.org](https://my.telegram.org).
+
+Obtain a bot token using BotFather.
+
+```
+$ export TD_API_ID <Your API ID>
+$ export TD_API_HASH <Your API Hash>
+$ export TD_BOT_TOKEN <Your Bot Token>
+$ ./mutebot
+```
+
+Alternatively, you can replace the following environment variables with -i -H and -T command line options, but they are not recommended, because some operating systems expose command line arguments to all users.
+
+### Configuration
+
+Currently, there are only a few options for you:
+
+* `-t`: Use test DC. (Default: false)
+* `-d`: Specify TDLib data directory. (Default: `./td_data/`)
+* `-l`: Logout of the current bot. See the following section.
+
+### Log out
+
+TDLib treats bots as normal accounts, so you need to log out if you want to change the bot token. To simplify your logout process, MuteBot will automatically logout your bot if you send SIGINT or SIGTERM to the daemon. Therefore, you can safely specify a new bot token at the next startup without worrying that it is still using the old one. However, you may still need to logout manually using the `-l` switch. It does nothing except for logging out the current bot session, if any.
+
+# License
+
+WTFPL
+
+by Yuuta Liang <yuuta@yuuta.moe> \ No newline at end of file
diff --git a/cmdline.c b/cmdline.c
new file mode 100644
index 0000000..b1c9fec
--- /dev/null
+++ b/cmdline.c
@@ -0,0 +1,82 @@
+#include "mutebot.h"
+#include <unistd.h>
+#include <stdio.h>
+#include <sysexits.h>
+#include <inttypes.h>
+#include <limits.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+struct cmdline cmd = {
+ "./td_data/",
+ false,
+ -1,
+ NULL,
+ NULL,
+ false
+};
+
+static inline int parse_api_id(char *str) {
+ char *endptr;
+ intmax_t num = strtoimax(str, &endptr, 10);
+ if (strcmp(endptr, "") != 0 || (num == INTMAX_MAX && errno == ERANGE) ||
+ num > INT_MAX || num < INT_MIN) {
+ fprintf(stderr, "Invalid API Hash: %s\n", optarg);
+ return EX_USAGE;
+ }
+ cmd.api_id = (int) num;
+ return 0;
+}
+
+int cmdline_init(int argc, char **argv) {
+ int opt;
+ while ((opt = getopt(argc, argv, "d:ti:H:T:l")) != -1) {
+ switch (opt) {
+ case 'd':
+ cmd.td_path = optarg;
+ break;
+ case 't':
+ cmd.test_dc = true;
+ break;
+ case 'i': {
+ int r = parse_api_id(optarg);
+ if (r) return r;
+ break;
+ }
+ case 'H':
+ cmd.api_hash = optarg;
+ break;
+ case 'T':
+ cmd.bot_token = optarg;
+ break;
+ case 'l':
+ cmd.logout = true;
+ break;
+ default:
+ fprintf(stderr, "Consult mutebot(1) for more details.\n");
+ return EX_USAGE;
+ }
+ }
+ if (cmd.api_id == -1 && getenv("TD_API_ID") != NULL) {
+ int r = parse_api_id(getenv("TD_API_ID"));
+ if (r) return r;
+ }
+ if (cmd.api_hash == NULL) cmd.api_hash = getenv("TD_API_HASH");
+ if (cmd.bot_token == NULL) cmd.bot_token = getenv("TD_BOT_TOKEN");
+
+ bool chkfail = false;
+ if (cmd.api_id == -1) {
+ fprintf(stderr, "You need to specify -i <API ID> or use TD_API_ID Environment Variable.\n");
+ chkfail = true;
+ }
+ if (cmd.api_hash == NULL) {
+ fprintf(stderr, "You need to specify -H <API Hash> or use TD_API_HASH Environment Variable.\n");
+ chkfail = true;
+ }
+ if (cmd.bot_token == NULL) {
+ fprintf(stderr, "You need to specify -T <BOT Token> or use TD_BOT_TOKEN Environment Variable.\n");
+ chkfail = true;
+ }
+ return chkfail ? EX_USAGE : EX_OK;
+}
diff --git a/config.h.in b/config.h.in
new file mode 100644
index 0000000..a5f7a08
--- /dev/null
+++ b/config.h.in
@@ -0,0 +1,7 @@
+#ifndef _CONFIG_H
+#define _CONFIG_H
+
+#define VER_MAJOR "@mutebot_VERSION_MAJOR@"
+#define VER_MINOR "@mutebot_VERSION_MINOR@"
+
+#endif /* _CONFIG_H */
diff --git a/handlers.c b/handlers.c
new file mode 100644
index 0000000..be2e653
--- /dev/null
+++ b/handlers.c
@@ -0,0 +1,43 @@
+#include "mutebot.h"
+#include "tdutils.h"
+
+#include <stdio.h>
+
+static void restrict_user(long long chat_id, long long uid) {
+ struct TdChatPermissions *permissions =
+ TdCreateObjectChatPermissions(0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0);
+ struct TdChatMemberStatus *status =
+ (struct TdChatMemberStatus *) TdCreateObjectChatMemberStatusRestricted(1,
+ 0,
+ permissions);
+ td_send(TdCreateObjectSetChatMemberStatus(chat_id,
+ (struct TdMessageSender *) TdCreateObjectMessageSenderUser(uid),
+ status),
+ NULL);
+}
+
+int handle_message(struct TdUpdateNewMessage *update) {
+ const struct TdMessage *message = update->message_;
+ if (message->sender_->ID == CODE_MessageSenderUser &&
+ (message->content_->ID == CODE_MessageChatJoinByRequest ||
+ message->content_->ID == CODE_MessageChatJoinByLink)) {
+ restrict_user(message->chat_id_, ((struct TdMessageSenderUser *) message->sender_)->user_id_);
+ return 0;
+ }
+ if (message->content_->ID != CODE_MessageChatAddMembers)
+ return 0;
+ struct TdMessageChatAddMembers *addMembers =
+ (struct TdMessageChatAddMembers *) message->content_;
+ for (int i = 0; i < addMembers->member_user_ids_->len; i++) {
+ const long long uid = addMembers->member_user_ids_->data[i];
+ restrict_user(message->chat_id_, uid);
+ }
+ return 0;
+} \ No newline at end of file
diff --git a/handlers.h b/handlers.h
new file mode 100644
index 0000000..ce03e79
--- /dev/null
+++ b/handlers.h
@@ -0,0 +1,8 @@
+#ifndef _MUTEBOT_HANDLERS_H
+#define _MUTEBOT_HANDLERS_H
+
+#include <td/telegram/td_c_client.h>
+
+int handle_message(struct TdUpdateNewMessage *update);
+
+#endif /* _MUTEBOT_HANDLERS_H */
diff --git a/main.c b/main.c
new file mode 100644
index 0000000..6fdba8b
--- /dev/null
+++ b/main.c
@@ -0,0 +1,20 @@
+#include "mutebot.h"
+#include "tdutils.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+
+int post_auth() {
+
+}
+
+int main(int argc, char **argv) {
+ int r = 0;
+ if ((r = cmdline_init(argc, argv))) goto cleanup;
+ if ((r = td_init())) goto cleanup;
+ td_loop();
+ goto cleanup;
+ cleanup:
+ td_free();
+ return r;
+}
diff --git a/mutebot.h b/mutebot.h
new file mode 100644
index 0000000..88ca854
--- /dev/null
+++ b/mutebot.h
@@ -0,0 +1,20 @@
+#ifndef _MUTEBOT_H
+#define _MUTEBOT_H
+
+#include <stdbool.h>
+#include "config.h"
+
+struct cmdline {
+ char *td_path;
+ bool test_dc;
+ int api_id;
+ char *api_hash;
+ char *bot_token;
+ bool logout;
+};
+
+extern struct cmdline cmd;
+
+int cmdline_init(int argc, char **argv);
+
+#endif /* _MUTEBOT_H */
diff --git a/td b/td
new file mode 160000
+Subproject 7c7ce5946b8d78e6c4d9f458dde0e60bad0b575
diff --git a/tdutils.c b/tdutils.c
new file mode 100644
index 0000000..fd1229a
--- /dev/null
+++ b/tdutils.c
@@ -0,0 +1,364 @@
+#include "mutebot.h"
+#include "tdutils.h"
+#include "handlers.h"
+
+#include <stdio.h>
+#include <td/telegram/td_c_client.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <string.h>
+#include <limits.h>
+#include <pthread.h>
+#include <signal.h>
+#include <stdatomic.h>
+
+struct td_callback {
+ struct td_callback *next;
+
+ long long request_id;
+
+ void (*cb)(bool, struct TdObject *, struct TdError *);
+};
+
+int td = -1;
+long long my_id = -1;
+static struct td_callback *cbs;
+
+/**
+ * Last request_id. Increased whenever sending a query.
+ *
+ * Write: any
+ * Read: any
+ */
+static atomic_llong last_req_id = 0;
+
+/**
+ * Set to true by main thread when received authorizationStateClosed or authorizationStateClosing.
+ * Stop accepting new queries.
+ *
+ * Write: main
+ * Read: any
+ */
+bool closing = false;
+
+static bool sighandler_setup = false;
+static pthread_t thread_sighandler;
+
+static void *main_sighandler(void *arg) {
+ pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
+ int r;
+ sigset_t *set = arg;
+ int sig;
+
+ while (true) {
+ r = sigwait(set, &sig);
+ if (r) {
+ fprintf(stderr, "Cannot call sigwait(): %d.\n", r);
+ goto cleanup;
+ }
+ switch (sig) {
+ case SIGINT:
+ case SIGTERM:
+ if (td == -1) goto cleanup;
+ td_send(TdCreateObjectLogOut(), NULL);
+ goto cleanup;
+ default:
+ break;
+ }
+ }
+ cleanup:
+ pthread_exit(NULL);
+}
+
+static int sighandler_init() {
+ int r;
+ sigset_t set;
+ sigemptyset(&set);
+ sigaddset(&set, SIGTERM);
+ sigaddset(&set, SIGINT);
+ sigaddset(&set, SIGUSR1);
+ r = pthread_sigmask(SIG_BLOCK, &set, NULL);
+ if (r) {
+ fprintf(stderr, "Cannot call pthread_sigmask(): %d\n", r);
+ goto cleanup;
+ }
+ r = pthread_create(&thread_sighandler, NULL, &main_sighandler, &set);
+ if (r) {
+ fprintf(stderr, "Cannot call pthread_create(): %d\n", r);
+ goto cleanup;
+ }
+ sighandler_setup = true;
+ goto cleanup;
+ cleanup:
+ return r;
+}
+
+static int sighandler_close() {
+ if (!sighandler_setup) return 0;
+ pthread_cancel(thread_sighandler);
+ int r = pthread_join(thread_sighandler, NULL);
+ if (!r) sighandler_setup = false;
+ return r;
+}
+
+static int tdcb_push(long long request_id, void (*cb)(bool, struct TdObject *, struct TdError *)) {
+ struct td_callback *current_ptr = malloc(sizeof(struct td_callback));
+ if (current_ptr == NULL) {
+ int r = errno;
+ fprintf(stderr, "Cannot allocate memory: %s\n", strerror(r));
+ return r;
+ }
+ current_ptr->next = NULL;
+ current_ptr->request_id = request_id;
+ current_ptr->cb = cb;
+ if (cbs == NULL) {
+ cbs = current_ptr;
+ } else {
+ current_ptr->next = cbs;
+ cbs = current_ptr;
+ }
+ return 0;
+}
+
+int td_send(void *func, void (*cb)(bool, struct TdObject *, struct TdError *)) {
+ if (closing) {
+ TdDestroyObjectFunction((struct TdFunction *)func);
+ return 0;
+ }
+ if (last_req_id == LLONG_MAX) last_req_id = 0;
+ last_req_id++;
+ int r;
+ if (cb != NULL && (r = tdcb_push(last_req_id, cb))) {
+ return r;
+ }
+ TdCClientSend(td, (struct TdRequest) {
+ last_req_id,
+ (struct TdFunction *) func
+ });
+ return 0;
+}
+
+static int tdcb_call(long long request_id, bool successful, struct TdObject *result, struct TdError *error) {
+ if (cbs == NULL) return 0;
+ struct td_callback *current_ptr = cbs;
+ bool node_found = false;
+ while (current_ptr != NULL) {
+ if (current_ptr->request_id == request_id) {
+ node_found = true;
+ current_ptr->cb(successful, result, error);
+ if (error != NULL &&
+ current_ptr->cb == &fetal_cb) {
+ /* The fetal_cb callback does not print anything */
+ /* because it does not know the request_id. */
+ fprintf(stderr, "Error: Request %lld: %s (%d)\n",
+ request_id,
+ error->message_,
+ error->code_);
+ }
+ break;
+ }
+ current_ptr = current_ptr->next;
+ }
+ if (node_found) {
+ /*
+ * The callback function may insert nodes to the link.
+ * Therefore, we do the iteration again after calling the function to
+ * delete the current one.
+ * Need a better implementation.
+ */
+ current_ptr = cbs;
+ struct td_callback *prev_ptr = NULL;
+ while (current_ptr != NULL) {
+ if (current_ptr->request_id == request_id) {
+ if (prev_ptr == NULL) cbs = current_ptr->next;
+ else prev_ptr->next = current_ptr->next;
+ free(current_ptr);
+ return 0;
+ }
+ prev_ptr = current_ptr;
+ current_ptr = current_ptr->next;
+ }
+ }
+ if (error != NULL) {
+ /* No callback found, Display default error message. */
+ fprintf(stderr, "Error: Request %lld: %s (%d)\n",
+ request_id,
+ error->message_,
+ error->code_);
+ }
+ return 0;
+}
+
+static void tdcb_free() {
+ struct td_callback *current_ptr = cbs;
+ while (current_ptr != NULL) {
+ struct td_callback *bak = current_ptr;
+ current_ptr = current_ptr->next;
+ free(bak);
+ }
+ cbs = NULL;
+}
+
+int td_init() {
+ int r;
+ if ((r = sighandler_init())) return r;
+ td = TdCClientCreateId();
+ TdDestroyObjectObject(TdCClientExecute((struct TdFunction *) TdCreateObjectSetLogVerbosityLevel(0)));
+ td_send(TdCreateObjectGetOption("version"), &fetal_cb);
+ return 0;
+}
+
+void td_free() {
+ sighandler_close();
+ tdcb_free();
+}
+
+void tg_close() {
+ if (td == -1) return;
+ td_send(TdCreateObjectClose(), NULL);
+}
+
+void fetal_cb(bool successful, struct TdObject *result, struct TdError *error) {
+ if (!successful) {
+ tg_close();
+ }
+}
+
+static void auth(bool successful, struct TdObject *result, struct TdError *error) {
+ if (closing || successful) return;
+ if (error != NULL) { /* error == NULL when caused from update handler */
+ fprintf(stderr, "Invalid bot token or API ID / Hash: %s (%d)\n",
+ error->message_,
+ error->code_);
+ tg_close();
+ return;
+ }
+ td_send(TdCreateObjectCheckAuthenticationBotToken(cmd.bot_token), &auth);
+}
+
+static int handle_auth(const struct TdUpdateAuthorizationState *update) {
+ switch (update->authorization_state_->ID) {
+ case CODE_AuthorizationStateWaitTdlibParameters:
+ td_send(TdCreateObjectSetTdlibParameters(TdCreateObjectTdlibParameters(
+ true,
+ cmd.td_path,
+ NULL,
+ false,
+ false,
+ false,
+ false,
+ cmd.api_id,
+ cmd.api_hash,
+ "en",
+ "Desktop",
+ "0.0",
+ "MuteBot "VER_MAJOR"."VER_MINOR,
+ false,
+ true
+ )),
+ &fetal_cb);
+ return 0;
+ case CODE_AuthorizationStateWaitPhoneNumber: {
+ if (cmd.logout) {
+ tg_close();
+ return 0;
+ }
+ auth(false, NULL, NULL);
+ return 0;
+ }
+ case CODE_AuthorizationStateReady: {
+ if (cmd.logout) {
+ td_send(TdCreateObjectLogOut(), &fetal_cb);
+ return 0;
+ }
+ return post_auth();
+ }
+ case CODE_AuthorizationStateWaitEncryptionKey: {
+ td_send(TdCreateObjectCheckDatabaseEncryptionKey(TdCreateObjectBytes((unsigned char *) {0x0}, 0)),
+ &fetal_cb);
+ return 0;
+ }
+ case CODE_AuthorizationStateLoggingOut: {
+ return 0;
+ }
+ case CODE_AuthorizationStateClosed:
+ case CODE_AuthorizationStateClosing: {
+ closing = true;
+ return 0;
+ }
+ case CODE_AuthorizationStateWaitOtherDeviceConfirmation: {
+ struct TdAuthorizationStateWaitOtherDeviceConfirmation *waitOtherDeviceConfirmation =
+ (struct TdAuthorizationStateWaitOtherDeviceConfirmation *)update->authorization_state_;
+ printf("Please scan the QR code of the following link using another Telegram seession:\n%s\n",
+ waitOtherDeviceConfirmation->link_);
+ return 0;
+ }
+ default: {
+ fprintf(stderr, "Unsupported authorization state: %d. Aborted.\n",
+ update->authorization_state_->ID);
+ tg_close();
+ return 0;
+ }
+ }
+}
+
+static int handle_option(const struct TdUpdateOption *update) {
+ if (!strcmp("my_id", update->name_) &&
+ update->value_->ID == CODE_OptionValueInteger) {
+ const struct TdOptionValueInteger *integer =
+ (struct TdOptionValueInteger *) update->value_;
+ my_id = integer->value_;
+ }
+ return 0;
+}
+
+static int handle_update(const struct TdUpdate *update) {
+ switch (update->ID) {
+ case CODE_UpdateAuthorizationState:
+ return handle_auth((struct TdUpdateAuthorizationState *) update);
+ case CODE_UpdateNewMessage:
+ return handle_message((struct TdUpdateNewMessage *) update);
+ case CODE_UpdateOption:
+ return handle_option((struct TdUpdateOption *) update);
+ default:
+ return 0;
+ }
+}
+
+void td_loop() {
+ struct TdResponse response;
+ while (1) {
+ response = TdCClientReceive(5);
+ struct TdObject *obj = response.object;
+ if (obj == NULL) continue;
+ const bool is_update = response.request_id == 0;
+ if (is_update &&
+ obj->ID == CODE_UpdateAuthorizationState &&
+ ((struct TdUpdate *) obj)->ID == CODE_UpdateAuthorizationState &&
+ ((struct TdUpdateAuthorizationState *) obj)->authorization_state_->ID ==
+ CODE_AuthorizationStateClosed) {
+ TdDestroyObjectObject(obj);
+ return;
+ }
+ if (is_update) {
+ handle_update((struct TdUpdate *) obj);
+ TdDestroyObjectObject(obj);
+ continue;
+ }
+ switch (obj->ID) {
+ case CODE_Ok:
+ tdcb_call(response.request_id, true, NULL, NULL);
+ break;
+ case CODE_Error: {
+ struct TdError *error =
+ (struct TdError *) obj;
+ tdcb_call(response.request_id, false, NULL, error);
+ break;
+ }
+ default:
+ tdcb_call(response.request_id, true, obj, NULL);
+ break;
+ }
+ TdDestroyObjectObject(obj);
+ }
+}
diff --git a/tdutils.h b/tdutils.h
new file mode 100644
index 0000000..7be3e26
--- /dev/null
+++ b/tdutils.h
@@ -0,0 +1,23 @@
+#ifndef _MUTEBOT_TDUTILS_H
+#define _MUTEBOT_TDUTILS_H
+
+#include <td/telegram/td_c_client.h>
+
+extern long long my_id;
+extern bool closing;
+
+int td_init();
+
+void td_free();
+
+void td_loop();
+
+void tg_close();
+
+int td_send(void *func, void (*cb)(bool, struct TdObject *, struct TdError *));
+
+void fetal_cb(bool successful, struct TdObject *result, struct TdError *error);
+
+int post_auth();
+
+#endif /* _MUTEBOT_TDUTILS_H */ \ No newline at end of file