Sorta works

This commit is contained in:
Emery Hemingway 2024-05-21 13:50:45 +02:00
parent 9cbc397181
commit 20b9bb37fd
14 changed files with 385 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,71 @@
# 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.
> It is unsafe as it does not have end-to-end encryption enabled by default for one to one messaging, and it does not have end-to-end-encryption at all for group messaging. It has received a great deal of scrutiny for various design choices including extremely strange cryptographic choices that are suspected of being intentional weaknesses but no smoking gun has been reported.
>
> …
>
> Of note is that some prominent self-identified members of the Internet Freedom social milieu in the United States of America are extremely hostile to Telegram. Usually these critics are strong proponents of Signal and often their criticism appears technically sound.
>
> …
## 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"