Sorta works

This commit is contained in:
Emery Hemingway 2024-05-21 13:50:45 +02:00
parent 9cbc397181
commit af382bfc34
14 changed files with 379 additions and 1 deletions

2
.envrc Normal file
View File

@ -0,0 +1,2 @@
source_env ..
use nix

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/nim.cfg

View File

@ -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
<require-service <daemon telegram_actor>>
<daemon telegram_actor {
argv: [ "/bin/telegram_actor" ]
restart: never
protocol: application/syndicate
}>
let ?ds = dataspace
? <service-object <daemon telegram_actor> ?cap> [
$cap <telegram-client { dataspace: $ds }>
]
$ds [
?? <send ?v> [ $log ! <log "-" { |>>>|: $v }> ]
?? <recv ?v> [ $log ! <log "-" { |<<<|: $v }> ]
? ?v [ $log ! <log "-" { assertion: $v }> ]
? <telegram-ready> [
! <send {"@type": "getOption", "name":"version"}>
?? <recv {"@type": "updateAuthorizationState", "authorization_state": {"@type": "authorizationStateWaitTdlibParameters"}}> [
! <send {
"@type":"setTdlibParameters"
"database_directory":"tdlib"
"use_message_database":true
"use_secret_chats":true
"api_id": $api_id
"api_hash": $api_hash
"system_language_code":"en"
"device_model":"Desktop"
"application_version":"1.0"
}>
]
?? <recv {"@type": "updateAuthorizationState", "authorization_state": {"@type": "authorizationStateWaitPhoneNumber"}}> [
! <send {
"@type":"setAuthenticationPhoneNumber"
"phone_number": $phone_number
}>
]
?? <recv {"@type": "updateAuthorizationState", "authorization_state": {"@type": "authorizationStateWaitCode"}}> [
! <send {
"@type":"checkAuthenticationCode"
"code": $login_code
}>
]
]
]
```

3
Tupfile Normal file
View File

@ -0,0 +1,3 @@
include_rules
: |> !nim_lk |> {lockfile}
: {lockfile} |> !nim_cfg |> | ./<lock>

4
Tuprules.tup Normal file
View File

@ -0,0 +1,4 @@
include ../syndicate-nim/depends.tup
NIM = $(DIRENV) $(NIM)
NIM_FLAGS += --path:$(TUP_CWD)/../syndicate-nim/src
NIM_GROUPS += $(TUP_CWD)/<lock>

8
config.prs Normal file
View File

@ -0,0 +1,8 @@
version 1 .
TelegramArguments = <telegram-client {
dataspace: #:any
}>.
# Assertion made when the client is ready to process messages.
TelegramReady = <telegram-ready> .

28
default.nix Normal file
View File

@ -0,0 +1,28 @@
#{
# pkgs ? import <nixpkgs> { },
#}:
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;
}

142
lock.json Normal file
View File

@ -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"
}
]
}

3
src/Tupfile Normal file
View File

@ -0,0 +1,3 @@
include_rules
: foreach *.nim | $(SYNDICATE_PROTOCOL) ./<schema> |> !nim_bin |> {bin}
: foreach {bin} |> !assert_built |>

65
src/telegram_actor.nim Normal file
View File

@ -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

View File

@ -0,0 +1,2 @@
include_rules
: foreach ../../*.prs |> !preserves_schema_nim |> %B.nim | ../<schema>

View File

@ -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))

View File

@ -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: "<td/telegram/td_json_client.h>".}
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.}

8
telegram_actor.nimble Normal file
View File

@ -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"