Initial commit

This commit is contained in:
Emery Hemingway 2023-07-23 14:09:44 +01:00
commit 1f9f3fb03a
9 changed files with 219 additions and 0 deletions

2
.envrc Normal file
View File

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

3
Tuprules.tup Normal file
View File

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

33
message_types.prs Normal file
View File

@ -0,0 +1,33 @@
version 1 .
Attributes = {string: any ...:...} .
Resp = <resp @time RespItem> .
RespItem = NewChatItem / Attributes .
NewChatItem = <newChatItem {"chatItem": ChatItem0 }> .
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
} .

5
shell.nix Normal file
View File

@ -0,0 +1,5 @@
let
syndicate = builtins.getFlake "syndicate";
pkgs =
import <nixpkgs> { overlays = builtins.attrValues syndicate.overlays; };
in pkgs.nimPackages.syndicate_utils

6
simplex_bot_actor.nimble Normal file
View File

@ -0,0 +1,6 @@
bin = @["simplex_bot_actor"]
license = "Unlicense"
srcDir = "src"
version = "20230723"
requires: "nim", "syndicate", ws

4
src/Tupfile Normal file
View File

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

56
src/message_types.nim Normal file
View File

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

58
src/private/jsonhooks.nim Normal file
View File

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

52
src/simplex_bot_actor.nim Normal file
View File

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