From af382bfc34d761043a06f4bd446d58f5f43ab2fc Mon Sep 17 00:00:00 2001 From: Emery Hemingway Date: Tue, 21 May 2024 13:50:45 +0200 Subject: [PATCH] Sorta works --- .envrc | 2 + .gitignore | 1 + README.md | 64 +++++++++++- Tupfile | 3 + Tuprules.tup | 4 + config.prs | 8 ++ default.nix | 28 +++++ lock.json | 142 ++++++++++++++++++++++++++ src/Tupfile | 3 + src/telegram_actor.nim | 65 ++++++++++++ src/telegram_actor/Tupfile | 2 + src/telegram_actor/config.nim | 18 ++++ src/telegram_actor/td_json_client.nim | 32 ++++++ telegram_actor.nimble | 8 ++ 14 files changed, 379 insertions(+), 1 deletion(-) create mode 100644 .envrc create mode 100644 .gitignore create mode 100644 Tupfile create mode 100644 Tuprules.tup create mode 100644 config.prs create mode 100644 default.nix create mode 100644 lock.json create mode 100644 src/Tupfile create mode 100644 src/telegram_actor.nim create mode 100644 src/telegram_actor/Tupfile create mode 100644 src/telegram_actor/config.nim create mode 100644 src/telegram_actor/td_json_client.nim create mode 100644 telegram_actor.nimble diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..d324c24 --- /dev/null +++ b/.envrc @@ -0,0 +1,2 @@ +source_env .. +use nix diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8454dc7 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/nim.cfg diff --git a/README.md b/README.md index d5fbe27..6044cc5 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,65 @@ # telegram_actor -*NOT AN ENDORSEMENT OF TELEGRAM.* +A proxy for communicating with [TDLib](https://core.telegram.org/tdlib/docs/td__json__client_8h.html) over Syndicate using the JSON-serialized object API. + +## Do not use Telegram. + +Probably a front for gathering metadata, like Signal, except FSB. + +## Example config +``` +#!/usr/bin/env -S syndicate-server --config + +> + + + +let ?ds = dataspace + +? ?cap> [ + $cap +] + +$ds [ + ?? [ $log ! >>|: $v }> ] + ?? [ $log ! ] + ? ?v [ $log ! ] + ? [ + ! + + ?? [ + ! + ] + + ?? [ + ! + ] + + ?? [ + ! + + ] + + ] +] +``` diff --git a/Tupfile b/Tupfile new file mode 100644 index 0000000..eaaeaaf --- /dev/null +++ b/Tupfile @@ -0,0 +1,3 @@ +include_rules +: |> !nim_lk |> {lockfile} +: {lockfile} |> !nim_cfg |> | ./ diff --git a/Tuprules.tup b/Tuprules.tup new file mode 100644 index 0000000..704673f --- /dev/null +++ b/Tuprules.tup @@ -0,0 +1,4 @@ +include ../syndicate-nim/depends.tup +NIM = $(DIRENV) $(NIM) +NIM_FLAGS += --path:$(TUP_CWD)/../syndicate-nim/src +NIM_GROUPS += $(TUP_CWD)/ diff --git a/config.prs b/config.prs new file mode 100644 index 0000000..7efda1d --- /dev/null +++ b/config.prs @@ -0,0 +1,8 @@ +version 1 . + +TelegramArguments = . + +# Assertion made when the client is ready to process messages. +TelegramReady = . diff --git a/default.nix b/default.nix new file mode 100644 index 0000000..8914209 --- /dev/null +++ b/default.nix @@ -0,0 +1,28 @@ +#{ +# pkgs ? import { }, +#}: + +let + + nixpkgs = builtins.fetchTarball { + url = "https://github.com/NixOS/nixpkgs/archive/2057814051972fa1453ddfb0d98badbea9b83c06.tar.gz"; + sha256 = "1a8jafyawg8mysv787q0cwghkza9kahbbniism7v2rcxas89b575"; + }; + pkgs = import nixpkgs { }; + + inherit (pkgs) + lib + buildNimPackage + pkg-config + tdlib + ; +in + +buildNimPackage { + pname = "telegram_actor"; + version = "unstable"; + src = if lib.inNixShell then null else lib.cleanSource ./.; + buildInputs = [ tdlib ]; + nativeBuildInputs = [ pkg-config ]; + lockFile = ./lock.json; +} diff --git a/lock.json b/lock.json new file mode 100644 index 0000000..f3b16ad --- /dev/null +++ b/lock.json @@ -0,0 +1,142 @@ +{ + "depends": [ + { + "method": "fetchzip", + "packages": [ + "bigints" + ], + "path": "/nix/store/jvrm392g8adfsgf36prgwkbyd7vh5jsw-source", + "rev": "86ea14d31eea9275e1408ca34e6bfe9c99989a96", + "sha256": "15pcpmnk1bnw3k8769rjzcpg00nahyrypwbxs88jnwr4aczp99j4", + "srcDir": "src", + "url": "https://github.com/ehmry/nim-bigints/archive/86ea14d31eea9275e1408ca34e6bfe9c99989a96.tar.gz" + }, + { + "method": "fetchzip", + "packages": [ + "cps" + ], + "path": "/nix/store/8gbhwni0akqskdb3qhn5nfgv6gkdz0vz-source", + "rev": "c90530ac57f98a842b7be969115c6ef08bdcc564", + "sha256": "0h8ghs2fqg68j3jdcg7grnxssmllmgg99kym2w0a3vlwca1zvr62", + "srcDir": "", + "url": "https://github.com/ehmry/cps/archive/c90530ac57f98a842b7be969115c6ef08bdcc564.tar.gz" + }, + { + "method": "fetchzip", + "packages": [ + "getdns" + ], + "path": "/nix/store/x9xmn7w4k6jg8nv5bnx148ibhnsfh362-source", + "rev": "c73cbe288d9f9480586b8fa87f6d794ffb6a6ce6", + "sha256": "1sbgx2x51szr22i72n7c8jglnfmr8m7y7ga0v85d58fwadiv7g6b", + "srcDir": "src", + "url": "https://git.sr.ht/~ehmry/getdns-nim/archive/c73cbe288d9f9480586b8fa87f6d794ffb6a6ce6.tar.gz" + }, + { + "method": "fetchzip", + "packages": [ + "hashlib" + ], + "path": "/nix/store/fav82xdbicvlk34nmcbl89zx99lr3mbs-source", + "rev": "f9455d4be988e14e3dc7933eb7cc7d7c4820b7ac", + "sha256": "1sx6j952lj98629qfgr7ds5aipyw9d6lldcnnqs205wpj4pkcjb3", + "srcDir": "", + "url": "https://github.com/ehmry/hashlib/archive/f9455d4be988e14e3dc7933eb7cc7d7c4820b7ac.tar.gz" + }, + { + "method": "fetchzip", + "packages": [ + "nimcrypto" + ], + "path": "/nix/store/m1qrs3hwcl5a3bkd8f7nbx5p5syid1dh-source", + "rev": "50e8a9b41d221b4eb9981358f65a9ea1a904b471", + "sha256": "0w3pwrg4mjpnzsm16jqg0862v3rgb3pdaxzjrrhwz1rbl04gxa6i", + "srcDir": "", + "url": "https://github.com/cheatfate/nimcrypto/archive/50e8a9b41d221b4eb9981358f65a9ea1a904b471.tar.gz" + }, + { + "method": "fetchzip", + "packages": [ + "npeg" + ], + "path": "/nix/store/xpn694ibgipj8xak3j4bky6b3k0vp7hh-source", + "rev": "ec0cc6e64ea4c62d2aa382b176a4838474238f8d", + "sha256": "1fi9ls3xl20bmv1ikillxywl96i9al6zmmxrbffx448gbrxs86kg", + "srcDir": "src", + "url": "https://github.com/zevv/npeg/archive/ec0cc6e64ea4c62d2aa382b176a4838474238f8d.tar.gz" + }, + { + "method": "fetchzip", + "packages": [ + "preserves" + ], + "path": "/nix/store/hzb7af7lbd4kgd5y4hbgxv1lswig36yj-source", + "rev": "fd498c6457cb9ad2f3179daa40da69eec00326dd", + "sha256": "182xvw04vjw83mlcrkwkip29b44h0v8dapg2014k9011h90mdsj4", + "srcDir": "src", + "url": "https://git.syndicate-lang.org/ehmry/preserves-nim/archive/fd498c6457cb9ad2f3179daa40da69eec00326dd.tar.gz" + }, + { + "method": "fetchzip", + "packages": [ + "stew" + ], + "path": "/nix/store/mqg8qzsbcc8xqabq2yzvlhvcyqypk72c-source", + "rev": "3c91b8694e15137a81ec7db37c6c58194ec94a6a", + "sha256": "17lfhfxp5nxvld78xa83p258y80ks5jb4n53152cdr57xk86y07w", + "srcDir": "", + "url": "https://github.com/status-im/nim-stew/archive/3c91b8694e15137a81ec7db37c6c58194ec94a6a.tar.gz" + }, + { + "method": "fetchzip", + "packages": [ + "syndicate" + ], + "path": "/nix/store/dw30cq9gxz3353zgaq4a36ajq6chvbwc-source", + "rev": "3a4dc1f13392830b587138199643d30fdbec8541", + "sha256": "1mbd17rjm1fsx7d0ckzyjih2nzdjqs52ck9wscqcg9nvf3ib5mvh", + "srcDir": "src", + "url": "https://git.syndicate-lang.org/ehmry/syndicate-nim/archive/3a4dc1f13392830b587138199643d30fdbec8541.tar.gz" + }, + { + "method": "fetchzip", + "packages": [ + "sys" + ], + "path": "/nix/store/syhxsjlsdqfap0hk4qp3s6kayk8cqknd-source", + "rev": "4ef3b624db86e331ba334e705c1aa235d55b05e1", + "sha256": "1q4qgw4an4mmmcbx48l6xk1jig1vc8p9cq9dbx39kpnb0890j32q", + "srcDir": "src", + "url": "https://github.com/ehmry/nim-sys/archive/4ef3b624db86e331ba334e705c1aa235d55b05e1.tar.gz" + }, + { + "method": "fetchzip", + "packages": [ + "taps" + ], + "path": "/nix/store/6y14ia52kr7jyaa0izx37mlablmq9s65-source", + "rev": "8c8572cd971d1283e6621006b310993c632da247", + "sha256": "1dp166bv9x773jmfqppg5i3v3rilgff013vb11yzwcid9l7s3iy8", + "srcDir": "src", + "url": "https://git.sr.ht/~ehmry/nim_taps/archive/8c8572cd971d1283e6621006b310993c632da247.tar.gz" + }, + { + "date": "2024-04-02T15:38:57+01:00", + "deepClone": false, + "fetchLFS": false, + "fetchSubmodules": true, + "hash": "sha256-iZb9aAgYr4FGkqfIg49QWiCqeizIi047kFhugHiP8o0=", + "leaveDotGit": false, + "method": "git", + "packages": [ + "solo5_dispatcher" + ], + "path": "/nix/store/sf5dgj2ljvahcm6my7d61ibda51vnrii-solo5_dispatcher", + "rev": "a7a894a96a2221284012800e6fd32923d83d20bd", + "sha256": "13gjixw80vjqj0xlx2y85ixal82sa27q7j57j9383bqq11lgv5l9", + "srcDir": "pkg", + "url": "https://git.sr.ht/~ehmry/solo5_dispatcher" + } + ] +} diff --git a/src/Tupfile b/src/Tupfile new file mode 100644 index 0000000..cce0260 --- /dev/null +++ b/src/Tupfile @@ -0,0 +1,3 @@ +include_rules +: foreach *.nim | $(SYNDICATE_PROTOCOL) ./ |> !nim_bin |> {bin} +: foreach {bin} |> !assert_built |> diff --git a/src/telegram_actor.nim b/src/telegram_actor.nim new file mode 100644 index 0000000..2bd1f9e --- /dev/null +++ b/src/telegram_actor.nim @@ -0,0 +1,65 @@ +# SPDX-FileCopyrightText: ☭ Emery Hemingway +# SPDX-License-Identifier: Unlicense + +import + std/[options, streams, tables, times], + preserves, syndicate, syndicate/relays, + ./telegram_actor/[config, td_json_client] + +discard td_execute("""{"@type":"setLogVerbosityLevel", "new_verbosity_level":0}""") + # Disable log vomit to stderr, this isn't a GTK application. + +type State = ref object + facet: Facet + subscribers: Table[int, Cap] + buffer: StringStream + +let state = State( buffer: newStringStream() ) + # Global state object + +let actor = bootActor("main") do (turn: Turn): + state.facet = turn.facet + resolveEnvironment(turn) do (turn: Turn; ds: Cap): + + during(turn, ds, TelegramArguments.grabTypeFlat) do (ds: Cap): + # Extern actors assert themselves here and a ClientId + # is used to isolate their conversations with tdlib. + let client = td_create_client_id() + state.subscribers[client.int] = ds + + onMessage(turn, ds, grabRecord("send", grab())) do (v: Value): + state.buffer.setPosition 0 + state.buffer.writeText(v, textJson) + state.buffer.data.setLen state.buffer.getPosition + td_send(client, state.buffer.data) + # TODO: enforce that the subscriber has not set "@client_id"? + + publish(turn, ds, TelegramReady()) + # Assert to the subscriber that the messages + # it sends will be processed. + + do: + state.subscribers.del client.int + +proc recv(state: State; id: int; recvMsg: Value) = + let subscriber = state.subscribers.getOrDefault id + if not subscriber.isNil: + queueTurn(state.facet) do (turn: Turn): + message(turn, subscriber, recvMsg) + +while actor.running: + # Poll Syndicate and Telegram + const + syndicateTimeout = some initDuration(seconds = 4) + telegramTimeout = 4.0 + syndicate.runOnce(syndicateTimeout) + let cstr = td_receive(telegramTimeout) + if not cstr.isNil: + var + pr = parsePreserves $cstr + clientId: Value + if pr.pop("@client_id".toPreserves, clientId) and clientId.isInteger: + # Pop the client_id out of messages so it isn't exposed to the subscriber. + recv(state, clientId.register, initRecord("recv", pr)) + else: + stderr.writeLine "discarding message without @client_id: ", cstr diff --git a/src/telegram_actor/Tupfile b/src/telegram_actor/Tupfile new file mode 100644 index 0000000..f405409 --- /dev/null +++ b/src/telegram_actor/Tupfile @@ -0,0 +1,2 @@ +include_rules +: foreach ../../*.prs |> !preserves_schema_nim |> %B.nim | ../ diff --git a/src/telegram_actor/config.nim b/src/telegram_actor/config.nim new file mode 100644 index 0000000..179dcf5 --- /dev/null +++ b/src/telegram_actor/config.nim @@ -0,0 +1,18 @@ + +import + preserves + +type + TelegramReady* {.preservesRecord: "telegram-ready".} = object + + TelegramArgumentsField0* {.preservesDictionary.} = object + `dataspace`* {.preservesEmbedded.}: Value + + TelegramArguments* {.preservesRecord: "telegram-client".} = object + `field0`*: TelegramArgumentsField0 + +proc `$`*(x: TelegramReady | TelegramArguments): string = + `$`(toPreserves(x)) + +proc encode*(x: TelegramReady | TelegramArguments): seq[byte] = + encode(toPreserves(x)) diff --git a/src/telegram_actor/td_json_client.nim b/src/telegram_actor/td_json_client.nim new file mode 100644 index 0000000..f8f7a4f --- /dev/null +++ b/src/telegram_actor/td_json_client.nim @@ -0,0 +1,32 @@ +# SPDX-FileCopyrightText: ☭ Emery Hemingway +# SPDX-License-Identifier: Unlicense + +{.passC: staticExec("pkg-config --cflags tdjson").} +{.passL: staticExec("pkg-config --libs tdjson").} + +{.pragma: import_td, importc, header: "".} + +type + ClientId* = distinct cint + Client* = distinct pointer + LogMessageCallback* = proc (verbosity_level: cint; message: cstring) {.cdecl.} + +proc td_create_client_id*(): ClientId {.import_td.} + +proc td_send*(client_id: ClientId; request: cstring) {.import_td.} + +proc td_receive*(timeout: cdouble): cstring {.import_td.} + +proc td_execute*(request: cstring): cstring {.import_td.} + +proc td_set_log_message_callback*(max_verbosity_level: cint; callback: LogMessageCallback) {.import_td.} + +proc td_json_client_create*(): Client {.import_td, deprecated.} + +proc td_json_client_send*(client: Client; request: cstring) {.import_td, deprecated.} + +proc td_json_client_receive*(client: Client; timeout: cdouble): cstring {.import_td, deprecated.} + +proc td_json_client_execute*(client: Client; request: cstring): cstring {.import_td, deprecated.} + +proc td_json_client_destroy*(client: Client) {.import_td, deprecated.} diff --git a/telegram_actor.nimble b/telegram_actor.nimble new file mode 100644 index 0000000..5680395 --- /dev/null +++ b/telegram_actor.nimble @@ -0,0 +1,8 @@ +version = "20240521" +author = "Emery Hemingway" +description = "Telegram client as a Syndicated Actor" +license = "Unlicense" +srcDir = "src" +bin = @["telegram_actor"] + +requires "syndicate"