Sorta works
This commit is contained in:
parent
9cbc397181
commit
af382bfc34
|
@ -0,0 +1 @@
|
||||||
|
/nim.cfg
|
64
README.md
64
README.md
|
@ -1,3 +1,65 @@
|
||||||
# telegram_actor
|
# 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
|
||||||
|
}>
|
||||||
|
|
||||||
|
]
|
||||||
|
|
||||||
|
]
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
include_rules
|
||||||
|
: |> !nim_lk |> {lockfile}
|
||||||
|
: {lockfile} |> !nim_cfg |> | ./<lock>
|
|
@ -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>
|
|
@ -0,0 +1,8 @@
|
||||||
|
version 1 .
|
||||||
|
|
||||||
|
TelegramArguments = <telegram-client {
|
||||||
|
dataspace: #:any
|
||||||
|
}>.
|
||||||
|
|
||||||
|
# Assertion made when the client is ready to process messages.
|
||||||
|
TelegramReady = <telegram-ready> .
|
|
@ -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;
|
||||||
|
}
|
|
@ -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"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
include_rules
|
||||||
|
: foreach *.nim | $(SYNDICATE_PROTOCOL) ./<schema> |> !nim_bin |> {bin}
|
||||||
|
: foreach {bin} |> !assert_built |>
|
|
@ -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
|
|
@ -0,0 +1,2 @@
|
||||||
|
include_rules
|
||||||
|
: foreach ../../*.prs |> !preserves_schema_nim |> %B.nim | ../<schema>
|
|
@ -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))
|
|
@ -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.}
|
|
@ -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"
|
Loading…
Reference in New Issue