summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTrumeet <yuuta@yuuta.moe>2022-04-01 21:13:31 -0700
committerTrumeet <yuuta@yuuta.moe>2022-04-01 21:13:31 -0700
commit318a1ef88bb5ea09ff4cf953908aef5c76735a46 (patch)
tree9cdd8be7679e6a336af7a82ca4947b3ffdac97b2
downloadksyxbot-318a1ef88bb5ea09ff4cf953908aef5c76735a46.tar
ksyxbot-318a1ef88bb5ea09ff4cf953908aef5c76735a46.tar.gz
ksyxbot-318a1ef88bb5ea09ff4cf953908aef5c76735a46.tar.bz2
ksyxbot-318a1ef88bb5ea09ff4cf953908aef5c76735a46.zip
First Commit
-rw-r--r--.gitignore76
-rw-r--r--.gitmodules3
-rw-r--r--.idea/.gitignore8
-rw-r--r--.idea/inspectionProfiles/Project_Default.xml6
-rw-r--r--.idea/ksyxbot.iml2
-rw-r--r--.idea/misc.xml4
-rw-r--r--.idea/modules.xml8
-rw-r--r--.idea/vcs.xml7
-rw-r--r--CMakeLists.txt20
-rw-r--r--arch/.gitignore5
-rw-r--r--arch/PKGBUILD73
-rw-r--r--arch/botd.conf3
-rw-r--r--arch/ksyxbotd.service13
-rw-r--r--arch/sysusers.conf1
-rw-r--r--arch/tmpfiles.conf2
-rw-r--r--botd.h29
-rw-r--r--cmdline.c91
-rw-r--r--db.c124
-rw-r--r--db.h22
-rw-r--r--log.c44
-rw-r--r--log.h50
-rw-r--r--logic.h15
-rw-r--r--main.c50
-rw-r--r--query.c155
-rw-r--r--refresh.c121
-rw-r--r--sql/0_1.sql10
-rw-r--r--store.c125
m---------td0
-rw-r--r--tdutils.c368
-rw-r--r--tdutils.h28
30 files changed, 1463 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..13566b8
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,8 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Editor-based HTTP Client requests
+/httpRequests/
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml
diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
new file mode 100644
index 0000000..03d9549
--- /dev/null
+++ b/.idea/inspectionProfiles/Project_Default.xml
@@ -0,0 +1,6 @@
+<component name="InspectionProjectProfileManager">
+ <profile version="1.0">
+ <option name="myName" value="Project Default" />
+ <inspection_tool class="Eslint" enabled="true" level="WARNING" enabled_by_default="true" />
+ </profile>
+</component> \ No newline at end of file
diff --git a/.idea/ksyxbot.iml b/.idea/ksyxbot.iml
new file mode 100644
index 0000000..f08604b
--- /dev/null
+++ b/.idea/ksyxbot.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/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..aa174e2
--- /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/ksyxbot.iml" filepath="$PROJECT_DIR$/.idea/ksyxbot.iml" />
+ </modules>
+ </component>
+</project> \ 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..7251ace
--- /dev/null
+++ b/CMakeLists.txt
@@ -0,0 +1,20 @@
+cmake_minimum_required(VERSION 3.21)
+project(ksyxbot VERSION 1.0 LANGUAGES C CXX)
+
+set(CMAKE_C_STANDARD 99)
+
+IF (CMAKE_BUILD_TYPE MATCHES Debug)
+ add_compile_definitions(TD_USE_ASAN)
+ENDIF (CMAKE_BUILD_TYPE MATCHES Debug)
+
+add_subdirectory(td)
+
+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)
+
+add_executable(ksyxbotd main.c tdutils.c tdutils.h log.c log.h botd.h db.c db.h cmdline.c store.c logic.h query.c refresh.c)
+target_include_directories(ksyxbotd PUBLIC "${PROJECT_BINARY_DIR}")
+target_link_libraries(ksyxbotd PRIVATE tdc tdsqlite m)
diff --git a/arch/.gitignore b/arch/.gitignore
new file mode 100644
index 0000000..8991b67
--- /dev/null
+++ b/arch/.gitignore
@@ -0,0 +1,5 @@
+*.zst
+pkg/
+src/
+td/
+ksyxbot/
diff --git a/arch/PKGBUILD b/arch/PKGBUILD
new file mode 100644
index 0000000..9918a4f
--- /dev/null
+++ b/arch/PKGBUILD
@@ -0,0 +1,73 @@
+# Maintainer: Yuuta Liang <yuuta@yuuta.moe>
+pkgname=ksyxbot-git
+pkgver=r11.546381f
+pkgrel=1
+pkgdesc="ksyx ksyx ksyx"
+arch=(x86_64)
+url="https://git.yuuta.moe/ksyxbot.git"
+license=('custom')
+groups=()
+depends=(zlib openssl)
+makedepends=('git')
+provides=("${pkgname%-git}")
+conflicts=("${pkgname%-git}")
+replaces=()
+backup=(etc/ksyxbot/botd.conf)
+options=()
+install=
+source=('ksyxbot::git+https://git.yuuta.moe/ksyxbot.git'
+'td::git+https://github.com/tdlib/td.git'
+'ksyxbotd.service'
+'sysusers.conf'
+'tmpfiles.conf'
+'botd.conf')
+noextract=()
+md5sums=('SKIP'
+ 'SKIP'
+ '36f708d68d52bac366a333628a1213a7'
+ '9d905a018be08f0ac09796e301949afd'
+ 'b47ae2adc14a370080941b535843b095'
+ '062bd08b22c915956e2fe655204c9427')
+
+pkgver() {
+ cd "$srcdir/${pkgname%-git}"
+ printf "r%s.%s" "$(git rev-list --count HEAD)" "$(git rev-parse --short HEAD)"
+}
+
+prepare() {
+ cd "$srcdir/${pkgname%-git}"
+ git submodule init
+ git config submodule.td.url $srcdir/td
+ git submodule update
+}
+
+build() {
+ cd "$srcdir/${pkgname%-git}"
+ mkdir -p cmake-build-release
+ cd cmake-build-release
+ cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX:PATH="$pkgdir/usr" ..
+ make ksyxbotd
+}
+
+check() {
+ cd "$srcdir/${pkgname%-git}"
+}
+
+package() {
+ cd "$srcdir/${pkgname%-git}"
+ mkdir -p "$pkgdir/etc/ksyxbot/"
+ chmod 700 "$pkgdir/etc/ksyxbot/"
+ install -Dm700 "$srcdir/default.conf" "$pkgdir/etc/ksyxbot/default.conf"
+ mkdir -p "$pkgdir/usr/lib/sysusers.d/"
+ install -Dm644 "$srcdir/sysusers.conf" "$pkgdir/usr/lib/sysusers.d/ksyxbot.conf"
+ mkdir -p "$pkgdir/var/lib/ksyxbot/"
+ chmod 700 "$pkgdir/var/lib/ksyxbot/"
+ mkdir -p "$pkgdir/usr/lib/tmpfiles.d/"
+ install -Dm644 "$srcdir/tmpfiles.conf" "$pkgdir/usr/lib/tmpfiles.d/ksyxbot.conf"
+ mkdir -p "$pkgdir/usr/lib/systemd/system/"
+ install -Dm644 "$srcdir/ksyxbotd.service" "$pkgdir/usr/lib/systemd/system/ksyxbotd.service"
+ mkdir -p "$pkgdir/usr/share/licenses/${pkgname%-git}/"
+ install -Dm644 "./LICENSE" "$pkgdir/usr/share/licenses/${pkgname%-git}/LICENSE"
+ mkdir -p "$pkgdir/usr/bin/"
+ install -Dm755 "./cmake-build-release/ksyxbotd" "$pkgdir/usr/bin/ksyxbotd"
+}
diff --git a/arch/botd.conf b/arch/botd.conf
new file mode 100644
index 0000000..d1fbef4
--- /dev/null
+++ b/arch/botd.conf
@@ -0,0 +1,3 @@
+TD_API_ID=
+TD_API_HASH=
+BOT_TOKEN=
diff --git a/arch/ksyxbotd.service b/arch/ksyxbotd.service
new file mode 100644
index 0000000..c71fe66
--- /dev/null
+++ b/arch/ksyxbotd.service
@@ -0,0 +1,13 @@
+[Unit]
+Description=ksyxbotd
+Documentation=man:ksyxbotd(1)
+After=network.target network-online.target nss-lookup.target
+
+[Service]
+Type=simple
+User=ksyxbot
+EnvironmentFile=/etc/ksyxbot/botd.conf
+ExecStart=/usr/bin/ksyxbotd --td_path /var/lib/ksyxbot/td/ --db_path /var/lib/ksyxbot/db.sql
+
+[Install]
+WantedBy=multi-user.target
diff --git a/arch/sysusers.conf b/arch/sysusers.conf
new file mode 100644
index 0000000..652de4f
--- /dev/null
+++ b/arch/sysusers.conf
@@ -0,0 +1 @@
+u ksyxbot - "ksyxbotd" /var/lib/ksyxbot/ /bin/nologin
diff --git a/arch/tmpfiles.conf b/arch/tmpfiles.conf
new file mode 100644
index 0000000..5702e68
--- /dev/null
+++ b/arch/tmpfiles.conf
@@ -0,0 +1,2 @@
+d /etc/ksyxbot/ 0700 ksyxbot nobody -
+d /var/lib/ksyxbot/ 0700 ksyxbot nobody -
diff --git a/botd.h b/botd.h
new file mode 100644
index 0000000..5bdd8fe
--- /dev/null
+++ b/botd.h
@@ -0,0 +1,29 @@
+/*
+ * Created by yuuta on 4/1/22.
+ */
+
+#ifndef KSYXBOT_BOTD_H
+#define KSYXBOT_BOTD_H
+
+#include <stdbool.h>
+
+#define ADMIN 2122005901
+#define CHANNEL -1001794945713
+
+extern int td;
+
+typedef struct cmd_s {
+ const char *bot_token;
+ int api_id;
+ const char *api_hash;
+ bool test_dc;
+ const char *td_path;
+ bool logout;
+ const char *db_path;
+} cmd_t;
+
+extern cmd_t cmd;
+
+void parse_cmdline(int argc, char **argv);
+
+#endif /* KSYXBOT_BOTD_H */
diff --git a/cmdline.c b/cmdline.c
new file mode 100644
index 0000000..778c7ae
--- /dev/null
+++ b/cmdline.c
@@ -0,0 +1,91 @@
+/*
+ * Created by yuuta on 4/1/22.
+ */
+
+#include "botd.h"
+#include "log.h"
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <limits.h>
+#include <inttypes.h>
+#include <errno.h>
+
+static inline void parse_api_id(const 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) {
+ LOGFEV("Invalid API Hash: %s.", 64, str);
+ }
+ cmd.api_id = (int) num;
+}
+
+void parse_cmdline(int argc, char **argv) {
+ for (int i = 1; i < argc; i++) {
+ const char *arg = argv[i];
+ if (arg[0] != '-' || arg[1] != '-')
+ LOGFEV("Unexpected argument: %s.", 64, arg);
+ const char *a = &arg[2];
+ if (!strcmp(a, "test")) {
+ LOGD("Test DC is in use.");
+ cmd.test_dc = true;
+ } else if (!strcmp(a, "logout")) {
+ cmd.logout = true;
+ } else if (!strcmp(a, "api_id")) {
+ if (i == (argc - 1)) LOGFEV("%s expects an argument.", 64, arg);
+ parse_api_id(argv[++i]);
+ } else if (!strcmp(a, "api_hash")) {
+ if (i == (argc - 1)) LOGFEV("%s expects an argument.", 64, arg);
+ cmd.api_hash = argv[++i];
+ } else if (!strcmp(a, "bot_token")) {
+ if (i == (argc - 1)) LOGFEV("%s expects an argument.", 64, arg);
+ cmd.bot_token = argv[++i];
+ } else if (!strcmp(a, "td_path")) {
+ if (i == (argc - 1)) LOGFEV("%s expects an argument.", 64, arg);
+ cmd.td_path = argv[++i];
+ } else if (!strcmp(a, "db_path")) {
+ if (i == (argc - 1)) LOGFEV("%s expects an argument.", 64, arg);
+ cmd.db_path = argv[++i];
+ } else if (!strcmp(a, "help")) {
+ printf("Usage: "
+ "[TD_API_ID=] "
+ "[TD_API_HASH=] "
+ "[TD_PATH=] "
+ "[BOT_TOKEN=] "
+ "[DB_PATH=] "
+ "%s "
+ "[--help] "
+ "[--logout] "
+ "[--test] "
+ "[--api_id TD_API_ID] "
+ "[--api_hash TD_API_HASH] "
+ "[--td_path TD_PATH] "
+ "[--bot_token BOT_TOKEN] "
+ "[--db_path DB_PATH] ",
+ argv[0]);
+ exit(0);
+ } else {
+ LOGFEV("Invalid command: %s. Use %s --help to view usage.",
+ 64,
+ arg,
+ argv[0]);
+ }
+ }
+
+ if (!cmd.api_hash && !(cmd.api_hash = getenv("TD_API_HASH")))
+ LOGFEV("Required environment variable %s is missing.", 64, "TD_API_HASH");
+ if (!cmd.bot_token && !(cmd.bot_token = getenv("BOT_TOKEN")))
+ LOGFEV("Required environment variable %s is missing.", 64, "BOT_TOKEN");
+ if (!cmd.td_path && !(cmd.td_path = getenv("TD_PATH")))
+ LOGFEV("Required environment variable %s is missing.", 64, "TD_PATH");
+ if (!cmd.db_path && !(cmd.db_path = getenv("DB_PATH")))
+ LOGFEV("Required environment variable %s is missing.", 64, "DB_PATH");
+ if (!cmd.api_id) {
+ const char *a;
+ if (!(a = getenv("TD_API_ID")))
+ LOGFEV("Required environment variable %s is missing.", 64, "TD_API_ID");
+ parse_api_id(a);
+ }
+} \ No newline at end of file
diff --git a/db.c b/db.c
new file mode 100644
index 0000000..ff9388c
--- /dev/null
+++ b/db.c
@@ -0,0 +1,124 @@
+//
+// Created by yuuta on 11/28/21.
+//
+
+#include "db.h"
+#include "log.h"
+#include "botd.h"
+
+#include <stdio.h>
+#include <sqlite3.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <stdbool.h>
+
+#ifndef SQL_PATH
+#define SQL_PATH "../sql/"
+#endif
+
+sqlite3 *db = NULL;
+
+void db_init(void) {
+ int r;
+ r = sqlite3_open(cmd.db_path, &db);
+ if (r) {
+ LOGFEV("Cannot open SQLite3 database: %s", r, sqlite3_errstr(r));
+ }
+ char *errmsg = NULL;
+ /* Enable foreign key support */
+ r = sqlite3_exec(db, "PRAGMA foreign_keys = ON;", NULL, NULL, &errmsg);
+ if (r) {
+ goto sql_err;
+ sql_err:
+ db_close();
+ LOGFEV("%s", r, errmsg);
+ }
+ /* Get user_version */
+ sqlite3_stmt *stmt;
+ r = sqlite3_prepare_v2(db, "PRAGMA user_version", -1, &stmt, 0);
+ if (r) {
+ goto sql_err;
+ }
+ r = sqlite3_step(stmt);
+ if (r != SQLITE_ROW) {
+ sqlite3_finalize(stmt);
+ goto sql_err;
+ }
+ const int current_var = sqlite3_column_int(stmt, 0);
+ r = sqlite3_finalize(stmt);
+ /* Upgrade one by one */
+ FILE *fd_sql = NULL;
+ char *path_sql = NULL;
+ const unsigned int path_len = strlen(SQL_PATH) + 20;
+ path_sql = calloc(path_len, sizeof(char));
+ if (path_sql == NULL) {
+ r = errno;
+ db_close();
+ LOGFEV("Cannot allocate memory: %s", r, strerror(r));
+ }
+ for (int i = current_var; true; i++) {
+ snprintf(path_sql, path_len - 1, "%s/%d_%d.sql", SQL_PATH, i, i + 1);
+ fd_sql = fopen(path_sql, "r");
+ if (fd_sql == NULL) {
+ if (errno != ENOENT) {
+ r = errno;
+ free(path_sql);
+ db_close();
+ LOGFEV("Cannot upgrade database to %d: %s", r, i + 1, strerror(r));
+ }
+ break;
+ }
+ char *sql_str = NULL;
+ char *line = NULL;
+ size_t len = 0;
+ ssize_t tread = 0;
+ ssize_t nread;
+ while ((nread = getline(&line, &len, fd_sql)) != -1) {
+ tread += nread;
+ if (sql_str == NULL) {
+ sql_str = calloc(nread + 1, sizeof(char));
+ if (sql_str == NULL) {
+ r = errno;
+ fclose(fd_sql);
+ free(line);
+ free(path_sql);
+ db_close();
+ LOGFEV("Cannot allocate memory: %s", r, strerror(r));
+ }
+ strcpy(sql_str, line);
+ } else {
+ char *newptr = realloc(sql_str, (tread + 1) * sizeof(char));
+ if (newptr == NULL) {
+ r = errno;
+ fclose(fd_sql);
+ free(line);
+ free(sql_str);
+ free(path_sql);
+ db_close();
+ LOGFEV("Cannot allocate memory: %s", r, strerror(r));
+ }
+ sql_str = newptr;
+ strcat(sql_str, line);
+ }
+ }
+ fclose(fd_sql);
+ free(line);
+ r = sqlite3_exec(db, sql_str, NULL, NULL, &errmsg);
+ if (r) {
+ free(sql_str);
+ db_close();
+ LOGFV("%s", errmsg);
+ break;
+ }
+ free(sql_str);
+ }
+ free(path_sql);
+}
+
+void db_close(void) {
+ if (db == NULL)
+ return;
+ sqlite3_close(db);
+ db = NULL;
+}
diff --git a/db.h b/db.h
new file mode 100644
index 0000000..269cd70
--- /dev/null
+++ b/db.h
@@ -0,0 +1,22 @@
+//
+// Created by yuuta on 11/28/21.
+//
+
+#ifndef _DB_H
+#define _DB_H
+
+#include <sqlite3.h>
+#include <stdio.h>
+#include <math.h>
+#include <stdlib.h>
+
+#define ULEN(X) (X == 0 ? 1 : (int)floor(log10(X)) + 1)
+#define LLEN(X) ((int)floor(log10(llabs(X))) + 1 + ((X < 0) ? 1 : 0))
+
+extern sqlite3 *db;
+
+void db_init(void);
+
+void db_close(void);
+
+#endif /* _DB_H */
diff --git a/log.c b/log.c
new file mode 100644
index 0000000..f3fa7bb
--- /dev/null
+++ b/log.c
@@ -0,0 +1,44 @@
+/*
+ * Created by yuuta on 1/1/22.
+ */
+
+#include "log.h"
+
+#include <stdio.h>
+#include <stdarg.h>
+#include <assert.h>
+
+void g_log(enum log_level level,
+ const char *file,
+ int line,
+ const char *format,
+ ...) {
+ FILE *stream = level <= log_warn ? stderr : stdout;
+ switch (level) {
+ case log_fetal:
+ fprintf(stream, "F");
+ break;
+ case log_error:
+ fprintf(stream, "E");
+ break;
+ case log_warn:
+ fprintf(stream, "W");
+ break;
+ case log_info:
+ fprintf(stream, "I");
+ break;
+ case log_debug:
+ fprintf(stream, "D");
+ break;
+ default:
+ fprintf(stderr, "Unknown log level: %d.\n", level);
+ assert(0);
+ }
+ fprintf(stream, "[%s:%d]: ",
+ file, line);
+ va_list list;
+ va_start(list, format);
+ vfprintf(stream, format, list);
+ va_end(list);
+ fprintf(stream, "\n");
+}
diff --git a/log.h b/log.h
new file mode 100644
index 0000000..cea79a6
--- /dev/null
+++ b/log.h
@@ -0,0 +1,50 @@
+/*
+ * Created by yuuta on 1/1/22.
+ */
+
+#ifndef _LOG_H
+#define _LOG_H
+
+enum log_level {
+ log_fetal = 1,
+ log_error = 2,
+ log_warn = 3,
+ log_info = 4,
+ log_debug = 5
+};
+
+void g_log(enum log_level level,
+ const char *file,
+ int line,
+ const char *format,
+ ...);
+
+#define LOGFE(X, code) \
+do { g_log(log_fetal, __FUNCTION__, __LINE__, X); \
+exit(code); } while (0)
+
+#define LOGFEV(X, code, ...) \
+do { g_log(log_fetal, __FUNCTION__, __LINE__, X, __VA_ARGS__); \
+exit(code); } while (0)
+
+#define LOGF(X) g_log(log_fetal, __FUNCTION__, __LINE__, X)
+
+#define LOGFV(X, ...) g_log(log_fetal, __FUNCTION__, __LINE__, X, __VA_ARGS__)
+
+#define LOGE(X) g_log(log_error, __FUNCTION__, __LINE__, X)
+
+#define LOGEV(X, ...) g_log(log_error, __FUNCTION__, __LINE__, X, __VA_ARGS__)
+
+#define LOGW(X) g_log(log_warn, __FUNCTION__, __LINE__, X)
+
+#define LOGWV(X, ...) g_log(log_warn, __FUNCTION__, __LINE__, X, __VA_ARGS__)
+
+#define LOGI(X) g_log(log_info, __FUNCTION__, __LINE__, X)
+
+#define LOGIV(X, ...) g_log(log_info, __FUNCTION__, __LINE__, X, __VA_ARGS__)
+
+#define LOGD(X) g_log(log_debug, __FUNCTION__, __LINE__, X)
+
+#define LOGDV(X, ...) g_log(log_debug, __FUNCTION__, __LINE__, X, __VA_ARGS__)
+
+#endif /* _LOG_H */
diff --git a/logic.h b/logic.h
new file mode 100644
index 0000000..d85a631
--- /dev/null
+++ b/logic.h
@@ -0,0 +1,15 @@
+/*
+ * Created by yuuta on 4/1/22.
+ */
+
+#ifndef KSYXBOT_LOGIC_H
+#define KSYXBOT_LOGIC_H
+
+#include "td/td/telegram/td_c_client.h"
+#include "tdutils.h"
+
+void store(struct TdMessage *msg);
+
+void refresh(struct TdMessage *msg);
+
+#endif /* KSYXBOT_LOGIC_H */
diff --git a/main.c b/main.c
new file mode 100644
index 0000000..806c8ef
--- /dev/null
+++ b/main.c
@@ -0,0 +1,50 @@
+#include "db.h"
+#include "log.h"
+#include "tdutils.h"
+#include "botd.h"
+#include "logic.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+cmd_t cmd = {
+ NULL,
+ 0,
+ NULL,
+ false,
+ NULL,
+ false
+};
+
+int post_auth() {
+ LOGI("OK");
+ return 0;
+}
+
+static void ate(void) {
+ db_close();
+ td_free();
+}
+
+int main(int argc, char **argv) {
+ atexit(&ate);
+ parse_cmdline(argc, argv);
+ db_init();
+ td_init();
+ td_loop();
+ return 0;
+}
+
+int handle_message(struct TdUpdateNewMessage *update) {
+ struct TdMessage *msg = update->message_;
+ if (msg->sender_id_->ID == CODE_MessageSenderUser &&
+ ((struct TdMessageSenderUser *) msg->sender_id_)->user_id_ == ADMIN) {
+ if (msg->content_->ID == CODE_MessageText &&
+ !strcmp(((struct TdMessageText *) msg->content_)->text_->text_, "/refresh")) {
+ refresh(msg);
+ return 0;
+ }
+ }
+ store(msg);
+ return 0;
+} \ No newline at end of file
diff --git a/query.c b/query.c
new file mode 100644
index 0000000..41aa1e8
--- /dev/null
+++ b/query.c
@@ -0,0 +1,155 @@
+/*
+ * Created by yuuta on 4/1/22.
+ */
+
+#include "tdutils.h"
+#include "logic.h"
+#include "log.h"
+#include "db.h"
+
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <errno.h>
+#include <assert.h>
+
+struct result {
+ char id[10];
+ /* Title in selection */
+ char *title;
+ /* Description in selection */
+ char *description;
+ /* Text after sending */
+ char *text;
+ char *url;
+};
+
+static void cb_answer(bool successful, struct TdObject *result, struct TdError *error, void *cb_arg) {
+ if (!successful) {
+ LOGEV("Cannot answer inline request: %s.",
+ error ? error->message_ : "(NULL)");
+ }
+}
+
+int handle_inline(struct TdUpdateNewInlineQuery *update) {
+ struct result results[10];
+ memset(results, 0, sizeof(results));
+ sqlite3_stmt *stmt;
+ int r;
+ if ((r = sqlite3_prepare_v2(db,
+ "SELECT id, t, url FROM says ORDER BY RANDOM() LIMIT 10;",
+ -1,
+ &stmt,
+ NULL))) {
+ goto sql_err;
+ sql_err:
+ snprintf(results[0].id, 10, "e%d", r);
+ char *msg = (char *) sqlite3_errstr(r);
+ LOGEV("Cannot query: %s.", msg);
+ results[0].title = "Error!";
+ results[0].text = msg;
+ results[0].description = msg;
+ goto answer;
+ }
+
+ int j = 0;
+ goto step;
+ step:
+ switch (r = sqlite3_step(stmt)) {
+ case SQLITE_ROW: {
+ char *t = (char *) sqlite3_column_text(stmt, 1);
+ char *url = (char *) sqlite3_column_text(stmt, 3);
+
+ char *url1 = NULL;
+ char *title = NULL;
+ char *t1 = NULL;
+
+ if (url && !(url1 = strdup(url))) {
+ LOGEV("Cannot allocate memory: %s.", strerror(errno));
+ break;
+ }
+ if (!(title = strdup(t))) {
+ LOGEV("Cannot allocate memory: %s.", strerror(errno));
+ break;
+ }
+ if (!(t1 = strdup(t))) {
+ LOGEV("Cannot allocate memory: %s.", strerror(errno));
+ break;
+ }
+ results[j].title = title;
+ snprintf(results[j].id, 10, "%d", sqlite3_column_int(stmt, 0));
+ results[j].text = t1;
+ results[j].url = url1;
+ results[j].description = url1;
+ if ((++j) > 9) break;
+ goto step;
+ }
+ case SQLITE_DONE: {
+ break;
+ }
+ default: {
+ goto sql_err;
+ }
+ }
+
+ sqlite3_finalize(stmt);
+
+ if (!j) {
+ results[0].title = "No data";
+ results[0].description = "No data is available. Check it out later.";
+ sprintf(results[0].id, "e_nodat");
+ results[0].text = "No data is available. Check it out later.\n\n"
+ "https://www.youtube.com/watch?v=dQw4w9WgXcQ";
+ }
+ assert(results[0].title);
+ goto answer;
+ answer:
+ {
+ struct TdInputInlineQueryResultArticle *results_inline[10]; /* No need to memset(). Var 'i' will assure that. */
+ int i;
+ for (i = 0; i < sizeof(results) / sizeof(struct result); i++) {
+ struct result res = results[i];
+ if (!res.title) {
+ break;
+ }
+ results_inline[i] = TdCreateObjectInputInlineQueryResultArticle(
+ res.id,
+ NULL,
+ false,
+ res.title,
+ res.description,
+ NULL,
+ 0,
+ 0,
+ NULL,
+ (struct TdInputMessageContent *) TdCreateObjectInputMessageText(
+ TdCreateObjectFormattedText(res.text,
+ (struct TdVectorTextEntity *) TdCreateObjectVectorObject(0,
+ NULL)),
+ true,
+ false
+ ));
+ }
+ td_send(TdCreateObjectAnswerInlineQuery(update->id_,
+ false,
+ (struct TdVectorInputInlineQueryResult *)
+ TdCreateObjectVectorObject(i,
+ (struct TdObject **) results_inline),
+ 0,
+ NULL,
+ NULL,
+ NULL),
+ &cb_answer,
+ NULL);
+ for (i = 0; i < sizeof(results) / sizeof(struct result); i++) {
+ struct result res = results[i];
+ if (!res.title) {
+ break;
+ }
+ free(res.title);
+ if (res.url) free(res.url);
+ free(res.text);
+ }
+ return r;
+ }
+}
diff --git a/refresh.c b/refresh.c
new file mode 100644
index 0000000..8146563
--- /dev/null
+++ b/refresh.c
@@ -0,0 +1,121 @@
+/*
+ * Created by yuuta on 4/1/22.
+ */
+
+#include "log.h"
+#include "logic.h"
+#include "botd.h"
+#include "tdutils.h"
+
+#include <stdio.h>
+#include <stdbool.h>
+
+struct refresh_session {
+ bool running;
+ long long chat_id;
+ long long message_id;
+ long long offset_message_id;
+ unsigned int progress;
+};
+
+static struct refresh_session current_session = {
+ false,
+ 0,
+ 0,
+ 0
+};
+
+static void refresh_get(void);
+
+static void send_msg(long long chat_id, long long message_id, char *text) {
+ td_send(TdCreateObjectSendMessage(chat_id,
+ 0,
+ message_id,
+ TdCreateObjectMessageSendOptions(false,
+ false,
+ true,
+ NULL),
+ NULL,
+ (struct TdInputMessageContent *)
+ TdCreateObjectInputMessageText(
+ TdCreateObjectFormattedText(text,
+ (struct TdVectorTextEntity *)
+ TdCreateObjectVectorObject(
+ 0, NULL)
+ ),
+ false,
+ false)),
+ NULL,
+ NULL);
+}
+
+static void send_administrative_msg(char *text) {
+ send_msg(current_session.chat_id, current_session.message_id, text);
+}
+
+static void cb_refresh_mlink(bool successful, struct TdObject *result, struct TdError *error, void *cb_arg) {
+ if (!successful) {
+ char msg[512];
+ snprintf(msg, 512, "Cannot get message link info: %d (%s)",
+ error ? error->code_ : -1,
+ error ? error->message_ : "NULL");
+ send_administrative_msg(msg);
+ current_session.running = false;
+ return;
+ }
+ struct TdMessageLinkInfo *info = (struct TdMessageLinkInfo *) result;
+ if (!info->message_) {
+ refresh_get();
+ return;
+ }
+ LOGDV("%lld", info->message_->id_);
+ store(info->message_);
+ refresh_get();
+}
+
+static void cb_refresh_link(bool successful, struct TdObject *result, struct TdError *error, void *cb_arg) {
+ if (!successful) {
+ char msg[512];
+ snprintf(msg, 512, "Cannot get link: %d (%s)",
+ error ? error->code_ : -1,
+ error ? error->message_ : "NULL");
+ send_administrative_msg(msg);
+ current_session.running = false;
+ return;
+ }
+ struct TdInternalLinkType *type = (struct TdInternalLinkType *) result;
+ if (type->ID != CODE_InternalLinkTypeMessage) {
+ refresh_get();
+ return;
+ }
+ struct TdInternalLinkTypeMessage *m = (struct TdInternalLinkTypeMessage *) type;
+ td_send(TdCreateObjectGetMessageLinkInfo(m->url_), &cb_refresh_mlink, NULL);
+}
+
+static void refresh_get(void) {
+ if (current_session.progress > 815) {
+ send_administrative_msg("Done.");
+ current_session.running = false;
+ return;
+ }
+ char url[64];
+ sprintf(url, "https://t.me/ksyxsays/%u", current_session.progress ++);
+ LOGDV("Get %s", url);
+ td_send(TdCreateObjectGetInternalLinkType(url),
+ &cb_refresh_link,
+ NULL);
+}
+
+void refresh(struct TdMessage *msg) {
+ if (current_session.running) {
+ send_msg(msg->chat_id_, msg->id_, "A refresh is already in progress.");
+ return;
+ }
+ current_session.running = true;
+ current_session.message_id = msg->id_;
+ current_session.chat_id = msg->chat_id_;
+ current_session.offset_message_id = 0;
+ current_session.progress = 1;
+ send_administrative_msg("Refreshing ...");
+ refresh_get();
+} \ No newline at end of file
diff --git a/sql/0_1.sql b/sql/0_1.sql
new file mode 100644
index 0000000..847b16d
--- /dev/null
+++ b/sql/0_1.sql
@@ -0,0 +1,10 @@
+-- Init
+CREATE TABLE "says"
+(
+ "id" INTEGER PRIMARY KEY,
+ "t" TEXT NOT NULL UNIQUE,
+ "url" TEXT,
+ "msg" INTEGER NOT NULL
+);
+
+PRAGMA user_version = 1; \ No newline at end of file
diff --git a/store.c b/store.c
new file mode 100644
index 0000000..f49f23b
--- /dev/null
+++ b/store.c
@@ -0,0 +1,125 @@
+/*
+ * Created by yuuta on 4/1/22.
+ */
+
+#include "log.h"
+#include "logic.h"
+#include "db.h"
+#include "botd.h"
+
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+/* Temp object between message callback and link callback */
+struct say {
+ long long msg_id;
+ long long chat_id;
+ char *text;
+};
+
+static void cb_link(bool successful, struct TdObject *result, struct TdError *error, void *cb_arg) {
+ struct say *s = cb_arg;
+ if (!successful) {
+ LOGEV("Cannot get link for %lld/%lld: %s (%d).",
+ s->chat_id, s->msg_id,
+ error ? error->message_ : "NULL",
+ error ? error->code_ : 0);
+ goto cleanup;
+ }
+ LOGDV("%s", s->text);
+ struct TdMessageLink *link = (struct TdMessageLink *) result;
+ int r;
+ sqlite3_stmt *stmt;
+ if ((r = sqlite3_prepare_v2(db,
+ "INSERT INTO says(t, url, msg) VALUES(?, ?, ?)",
+ -1,
+ &stmt,
+ NULL))) {
+ goto sql_err;
+ sql_err:
+ LOGEV("Cannot insert: %s.", sqlite3_errstr(r));
+ goto cleanup;
+ }
+ if ((r = sqlite3_bind_text(stmt, 1, s->text, (int) strlen(s->text), NULL))) {
+ goto sql_err;
+ }
+ if ((r = sqlite3_bind_text(stmt, 2, link->link_, (int) strlen(link->link_), NULL))) {
+ goto sql_err;
+ }
+ if ((r = sqlite3_bind_int64(stmt, 3, s->msg_id))) {
+ goto sql_err;
+ }
+ r = sqlite3_step(stmt);
+ sqlite3_finalize(stmt);
+ if (r != SQLITE_DONE && r != SQLITE_CONSTRAINT) {
+ goto sql_err;
+ }
+ goto cleanup;
+ cleanup:
+ free(s->text);
+ free(s);
+}
+
+static bool filter(struct TdMessage *msg) {
+ if (msg->chat_id_ != CHANNEL)
+ return false;
+ if (!msg->forward_info_ || !msg->forward_info_->origin_) {
+ return false;
+ }
+ struct TdMessageForwardOrigin *origin = msg->forward_info_->origin_;
+ switch (origin->ID) {
+ case CODE_MessageForwardOriginChannel: {
+ struct TdMessageForwardOriginChannel *chan =
+ (struct TdMessageForwardOriginChannel *) origin;
+ if (chan->chat_id_ != -1001304761546 &&
+ chan->chat_id_ != -1001565681839)
+ return false;
+ break;
+ }
+ case CODE_MessageForwardOriginHiddenUser: {
+ struct TdMessageForwardOriginHiddenUser *hid_user =
+ (struct TdMessageForwardOriginHiddenUser *) origin;
+ if (strcmp("ksyx", hid_user->sender_name_) != 0) return false;
+ break;
+ }
+ default: {
+ return false;
+ }
+ }
+
+ if (msg->content_->ID != CODE_MessageText) {
+ return false;
+ }
+ return true;
+}
+
+void store(struct TdMessage *msg) {
+ if (!filter(msg)) return;
+ struct TdMessageText *text = (struct TdMessageText *) msg->content_;
+ const char *t = text->text_->text_;
+ struct say *s;
+ if (!(s = malloc(sizeof(struct say)))) {
+ LOGEV("Cannot allocate memory: %s.", strerror(errno));
+ return;
+ }
+ memset(s, 0, sizeof(struct say));
+ s->chat_id = msg->chat_id_;
+ s->msg_id = msg->id_;
+ /* I'm just too lazy to manually free in each callback, so just strdup. */
+ if (!(s->text = strdup(t))) {
+ LOGEV("Cannot allocate memory: %s.", strerror(errno));
+ free(s);
+ return;
+ }
+ td_send(TdCreateObjectGetMessageLink(msg->chat_id_,
+ msg->id_,
+ 0,
+ false,
+ false),
+ &cb_link,
+ s);
+
+}
+
diff --git a/td b/td
new file mode 160000
+Subproject 461b740987101972cce65d1d5c996f455e4891e
diff --git a/tdutils.c b/tdutils.c
new file mode 100644
index 0000000..89fb3b2
--- /dev/null
+++ b/tdutils.c
@@ -0,0 +1,368 @@
+#include "tdutils.h"
+#include "botd.h"
+#include "log.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>
+#include <stdbool.h>
+
+struct td_callback {
+ struct td_callback *next;
+
+ long long request_id;
+
+ void (*cb)(bool, struct TdObject *, struct TdError *, void *);
+
+ void *cb_arg;
+};
+
+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;
+
+/**
+ * Used for sigwait(2).
+ */
+sigset_t set;
+
+static void *main_sighandler(void *arg) {
+ pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
+ int r;
+ 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;
+ tg_close();
+ goto cleanup;
+ default:
+ break;
+ }
+ }
+ cleanup:
+ pthread_exit(NULL);
+}
+
+static void sighandler_init() {
+ int r;
+ sigemptyset(&set);
+ sigaddset(&set, SIGTERM);
+ sigaddset(&set, SIGINT);
+ sigaddset(&set, SIGUSR1);
+ if ((r = pthread_sigmask(SIG_BLOCK, &set, NULL))) {
+ LOGFEV("pthread_sigmask", r, strerror(r));
+ }
+ if ((r = pthread_create(&thread_sighandler, NULL, &main_sighandler, NULL))) {
+ LOGFEV("pthread_create", r, strerror(r));
+ }
+ sighandler_setup = true;
+}
+
+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 *, void *), void *cb_arg) {
+ 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;
+ current_ptr->cb_arg = cb_arg;
+ 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 *, void *), void *cb_arg) {
+ 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, cb_arg))) {
+ 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, current_ptr->cb_arg);
+ 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;
+}
+
+void td_init() {
+ sighandler_init();
+ td = TdCClientCreateId();
+ TdDestroyObjectObject(TdCClientExecute((struct TdFunction *) TdCreateObjectSetLogVerbosityLevel(0)));
+ td_send(TdCreateObjectGetOption("version"), &fetal_cb, NULL);
+}
+
+void td_free() {
+ sighandler_close();
+ tdcb_free();
+}
+
+void tg_close() {
+ if (td == -1) return;
+ td_send(TdCreateObjectClose(), NULL, NULL);
+}
+
+void fetal_cb(bool successful, struct TdObject *result, struct TdError *error, void *cb_arg) {
+ if (!successful) {
+ tg_close();
+ }
+}
+
+static void auth(bool successful, struct TdObject *result, struct TdError *error, void *cb_arg) {
+ 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((char *) cmd.bot_token),
+ &auth,
+ NULL);
+}
+
+static int handle_auth(const struct TdUpdateAuthorizationState *update) {
+ switch (update->authorization_state_->ID) {
+ case CODE_AuthorizationStateWaitTdlibParameters:
+ td_send(TdCreateObjectSetTdlibParameters(TdCreateObjectTdlibParameters(
+ cmd.test_dc,
+ (char *) cmd.td_path,
+ NULL,
+ false,
+ false,
+ false,
+ false,
+ cmd.api_id,
+ (char *) cmd.api_hash,
+ "en",
+ "Desktop",
+ "0.0",
+ "Channel Helper Bot 1.0",
+ false,
+ true
+ )),
+ &fetal_cb,
+ NULL);
+ return 0;
+ case CODE_AuthorizationStateWaitPhoneNumber: {
+ if (cmd.logout) {
+ tg_close();
+ return 0;
+ }
+ auth(false, NULL, NULL, NULL);
+ return 0;
+ }
+ case CODE_AuthorizationStateReady: {
+ if (cmd.logout) {
+ td_send(TdCreateObjectLogOut(), &fetal_cb, NULL);
+ return 0;
+ }
+ return post_auth();
+ }
+ case CODE_AuthorizationStateWaitEncryptionKey: {
+ td_send(TdCreateObjectCheckDatabaseEncryptionKey(TdCreateObjectBytes((unsigned char *) {0x0}, 0)),
+ &fetal_cb, NULL);
+ return 0;
+ }
+ case CODE_AuthorizationStateLoggingOut: {
+ return 0;
+ }
+ /* Closed state is handled in the main loop. */
+ 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_UpdateNewInlineQuery:
+ return handle_inline((struct TdUpdateNewInlineQuery *) 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) {
+ closing = true;
+ 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..c0653d5
--- /dev/null
+++ b/tdutils.h
@@ -0,0 +1,28 @@
+#ifndef _TDUTILS_H
+#define _TDUTILS_H
+
+#include <td/telegram/td_c_client.h>
+#include <stdbool.h>
+
+extern long long my_id;
+extern bool closing;
+
+void td_init();
+
+void td_free();
+
+void td_loop();
+
+void tg_close();
+
+int td_send(void *func, void (*cb)(bool, struct TdObject *, struct TdError *, void *), void *cb_arg);
+
+void fetal_cb(bool successful, struct TdObject *result, struct TdError *error, void *cb_arg);
+
+int post_auth();
+
+int handle_message(struct TdUpdateNewMessage *update);
+
+int handle_inline(struct TdUpdateNewInlineQuery * update);
+
+#endif /* _TDUTILS_H */ \ No newline at end of file