From 1f9f3fb03ac7b9c0b137c53158e2e7dc703f917e Mon Sep 17 00:00:00 2001 From: Emery Hemingway Date: Sun, 23 Jul 2023 14:09:44 +0100 Subject: [PATCH] Initial commit --- .envrc | 2 ++ Tuprules.tup | 3 ++ message_types.prs | 33 ++++++++++++++++++++++ shell.nix | 5 ++++ simplex_bot_actor.nimble | 6 ++++ src/Tupfile | 4 +++ src/message_types.nim | 56 +++++++++++++++++++++++++++++++++++++ src/private/jsonhooks.nim | 58 +++++++++++++++++++++++++++++++++++++++ src/simplex_bot_actor.nim | 52 +++++++++++++++++++++++++++++++++++ 9 files changed, 219 insertions(+) create mode 100644 .envrc create mode 100644 Tuprules.tup create mode 100644 message_types.prs create mode 100644 shell.nix create mode 100644 simplex_bot_actor.nimble create mode 100644 src/Tupfile create mode 100644 src/message_types.nim create mode 100644 src/private/jsonhooks.nim create mode 100644 src/simplex_bot_actor.nim 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/Tuprules.tup b/Tuprules.tup new file mode 100644 index 0000000..acc5cb1 --- /dev/null +++ b/Tuprules.tup @@ -0,0 +1,3 @@ +include ../syndicate-nim/depends.tup +NIM_FLAGS += --path:$(TUP_CWD)/../syndicate-nim/src +NIM_FLAGS += --path:$(TUP_CWD)/../nimble/ws/src diff --git a/message_types.prs b/message_types.prs new file mode 100644 index 0000000..3552334 --- /dev/null +++ b/message_types.prs @@ -0,0 +1,33 @@ +version 1 . + +Attributes = {string: any ...:...} . + +Resp = . + +RespItem = NewChatItem / Attributes . + +NewChatItem = . + +ChatItem0 = { + "chatInfo": ChatInfo + "user": User +} . + +ChatInfo = { + "chatInfo": ChatInfo + "chatItem": ChatItem1 +} . + +ChatItem1 = { + "content": Content +} . + +Content = Attributes . + +User = { + "activeUser": bool + "agentUserId": string + "localDisplayName": string + "userContactId": int + "userId": int +} . diff --git a/shell.nix b/shell.nix new file mode 100644 index 0000000..160e711 --- /dev/null +++ b/shell.nix @@ -0,0 +1,5 @@ +let + syndicate = builtins.getFlake "syndicate"; + pkgs = + import { overlays = builtins.attrValues syndicate.overlays; }; +in pkgs.nimPackages.syndicate_utils diff --git a/simplex_bot_actor.nimble b/simplex_bot_actor.nimble new file mode 100644 index 0000000..63170a9 --- /dev/null +++ b/simplex_bot_actor.nimble @@ -0,0 +1,6 @@ +bin = @["simplex_bot_actor"] +license = "Unlicense" +srcDir = "src" +version = "20230723" + +requires: "nim", "syndicate", ws diff --git a/src/Tupfile b/src/Tupfile new file mode 100644 index 0000000..4a51cbf --- /dev/null +++ b/src/Tupfile @@ -0,0 +1,4 @@ +include_rules +: foreach ../*.prs |> !preserves_schema_nim |> {schema} +: simplex_bot_actor.nim | $(SYNDICATE_PROTOCOL) {schema} |> !nim_bin |> {bin} +: {bin} |> !assert_built |> diff --git a/src/message_types.nim b/src/message_types.nim new file mode 100644 index 0000000..b1a4a15 --- /dev/null +++ b/src/message_types.nim @@ -0,0 +1,56 @@ + +import + preserves, std/tables + +type + Resp* {.preservesRecord: "resp".} = ref object + `time`*: RespItem + + RespItemKind* {.pure.} = enum + `NewChatItem`, `Attributes` + `RespItem`* {.preservesOr.} = ref object + case orKind*: RespItemKind + of RespItemKind.`NewChatItem`: + `newchatitem`*: NewChatItem + + of RespItemKind.`Attributes`: + `attributes`*: Attributes + + + Attributes* = Table[string, Preserve[void]] + NewChatItemField0* {.preservesDictionary.} = ref object + `"chatItem"`*: ChatItem0 + + NewChatItem* {.preservesRecord: "newChatItem".} = ref object + `field0`*: NewChatItemField0 + + ChatInfo* {.preservesDictionary.} = ref object + `"chatInfo"`*: ChatInfo + `"chatItem"`*: ChatItem1 + + Content* = Attributes + ChatItem0* {.preservesDictionary.} = ref object + `"chatInfo"`*: ChatInfo + `"user"`*: User + + User* {.preservesDictionary.} = object + `"activeUser"`*: bool + `"agentUserId"`*: string + `"localDisplayName"`*: string + `"userContactId"`*: BiggestInt + `"userId"`*: BiggestInt + + ChatItem1* {.preservesDictionary.} = object + `"content"`*: Content + +proc `$`*(x: Resp | RespItem | Attributes | NewChatItem | ChatInfo | Content | + ChatItem0 | + User | + ChatItem1): string = + `$`(toPreserve(x)) + +proc encode*(x: Resp | RespItem | Attributes | NewChatItem | ChatInfo | Content | + ChatItem0 | + User | + ChatItem1): seq[byte] = + encode(toPreserve(x)) diff --git a/src/private/jsonhooks.nim b/src/private/jsonhooks.nim new file mode 100644 index 0000000..6912cc3 --- /dev/null +++ b/src/private/jsonhooks.nim @@ -0,0 +1,58 @@ +# SPDX-FileCopyrightText: ☭ Emery Hemingway +# SPDX-License-Identifier: Unlicense + +import std/[base64, json, parseutils, tables] +import preserves + +proc toPreserveHook*(js: JsonNode; E: typedesc): Preserve[E] {.gcsafe.} = + case js.kind + of JString: + var off = js.str.skip("data:") + if off == 0: + result = Preserve[E](kind: pkString, string: js.str) + else: + var mime: string + off.inc js.str.parseUntil(mime, ';', off) + var dataOff = off + js.str.skip(";base64,", off) + if dataOff != off: + var data = cast[seq[byte]](substr(js.str, dataOff).decode) + result = initRecord("mime", mime.toSymbol(E), data.toPreserve(E)) + else: + result = Preserve[E](kind: pkString, string: js.str) + of JInt: + result = Preserve[E](kind: pkSignedInteger, int: js.num) + of JFloat: + result = Preserve[E](kind: pkDouble, double: js.fnum) + of JBool: + result = case js.bval + of false: toSymbol("false", E) + of true: toSymbol("true", E) + of JNull: + result = toSymbol("null", E) + of JArray: + result = Preserve[E](kind: pkSequence, + sequence: newSeq[Preserve[E]](js.elems.len)) + for i, e in js.elems: + result.sequence[i] = toPreserveHook(e, E) + of JObject: + if js.hasKey("type"): + var label = js.getOrDefault("type").getStr.toSymbol(E) + if js.len == 1: + result = initRecord(label) + elif js.len == 2: + for key, val in js.fields.pairs: + if key != "type": + result = initRecord(label, val.toPreserve(E)) + else: + var dict = Preserve[E](kind: pkDictionary) + for key, val in js.fields.pairs: + if key != "type": + dict[Preserve[E](kind: pkString, string: key)] = toPreserveHook(val, E) + result = initRecord(label, dict) + elif js.len == 1: + for key, val in js.fields.pairs: + result = initRecord(key, val.toPreserve(E)) + else: + result = Preserve[E](kind: pkDictionary) + for key, val in js.fields.pairs: + result[Preserve[E](kind: pkString, string: key)] = toPreserveHook(val, E) diff --git a/src/simplex_bot_actor.nim b/src/simplex_bot_actor.nim new file mode 100644 index 0000000..e9a76e4 --- /dev/null +++ b/src/simplex_bot_actor.nim @@ -0,0 +1,52 @@ +# SPDX-FileCopyrightText: ☭ Emery Hemingway +# SPDX-License-Identifier: Unlicense + +import std/[asyncdispatch, json, sequtils, streams, tables, uri] +import preserves, syndicate +import ws + +import private/jsonhooks + +type + Args {.preservesDictionary.} = object + dataspace: Ref + url: string + + Internal* {.preservesRecord: "internal".} = object + dataspace: Ref + +proc recvMessages(facet: Facet; ds: Ref; ws: WebSocket) = + var fut: Future[string] + proc recvMessage() {.gcsafe.} = + fut = receiveStrPacket ws + addCallback(fut, facet) do (turn: var Turn): + message(turn, ds, fut.read.parseJson) + recvMessage() + recvMessage() + +proc bootClient(turn: var Turn; ds: Ref; ws: WebSocket) = + let dumpStream = openFileStream("/tmp/simplex_bot_actor.log", fmWrite) + let intern = newDataspace(turn) + discard publish(turn, ds, Internal(dataspace: intern)) + recvMessages(turn.facet, intern, ws) + onMessage(turn, intern, grab()) do (msg: Assertion): + writeText(dumpStream, msg) + write(dumpStream, '\n') + flush(dumpStream) + +proc boot*(root: Ref; turn: var Turn) = + during(turn, root, ?Args) do (dataspace: Ref, url: string): + debugEcho "got dataspace ", dataspace, " and URL ", url + var ws: WebSocket + newWebSocket(url).addCallback(turn) do (turn: var Turn; s: WebSocket): + ws = s + debugecho "connected to ", url + bootClient(turn, dataspace, ws) + do: + close ws + +when isMainModule: + runActor("eris_actor") do (root: Ref; turn: var Turn): + # connectStdio(root, turn) + boot(root, turn) + discard publish(turn, root, Args(dataspace: root, url: "ws://127.0.0.1:5225/"))