This commit is contained in:
Emery Hemingway 2023-10-28 12:14:16 +01:00
parent 19ce50b98b
commit 8e2845a81f
2 changed files with 17 additions and 0 deletions

3
README.md Normal file
View File

@ -0,0 +1,3 @@
# SimpleX bot actor
A [syndicated actor](https://syndicate-lang.org/) for collecting data from the the [SimpleX](https://simplex.chat/) websocket and reformatting as Syndicate assertions.

View File

@ -9,6 +9,7 @@ import syndicate, syndicate/relays
import ./schema/simple_types
func step(pr: Assertion; path: varargs[string]): Option[Assertion] =
## Convenience fuction for traversing levels of JSON hell.
result = some(pr)
var index = "".toSymbol(Cap)
for s in path:
@ -17,6 +18,8 @@ func step(pr: Assertion; path: varargs[string]): Option[Assertion] =
result = step(result.get, index)
proc sendCommand(turn: var Turn; ds: Cap; cmd: string) =
## Simplex websocket only accepts command
## strings in the format of the CLI client.
message(turn, ds, initRecord("send", Command(cmd: cmd).toPreserve))
proc `%`(bindings: sink openArray[(string, Pattern)]): Pattern =
@ -24,10 +27,13 @@ proc `%`(bindings: sink openArray[(string, Pattern)]): Pattern =
patterns.grabDictionary(bindings)
proc grabResp(obj: Pattern): Pattern =
## Grab the "resp" out of messages received on the websocket.
grabRecord("recv", %{ "resp": obj })
type
HandleTable = Table[Assertion, Handle]
## Table for mapping Simplex identifiers to Syndicate handles.
State = ref object
ds, websocket: Cap
contacts, groups, chats: HandleTable
@ -37,6 +43,8 @@ proc updateTable(turn: var Turn; state: State; table: var HandleTable; id, ass:
table[id] = replace(turn, state.ds, table.getOrDefault(id), ass)
proc extractImagePath(image: Option[Assertion]): string =
## Decode an image and return a content-addressed file-
## system path. Otherwise "/dev/null".
const prefix = "data:image/jpg;base64,"
if image.isNone:
result = "/dev/null"
@ -52,6 +60,7 @@ proc extractImagePath(image: Option[Assertion]): string =
writeFile(result, bin)
proc updateContact(turn: var Turn; state: State; id, attrs: Assertion) =
## Update the contact assertion held in the dataspace.
var
attrs = attrs
imagePath = attrs.step("profile", "image").extractImagePath
@ -59,6 +68,7 @@ proc updateContact(turn: var Turn; state: State; id, attrs: Assertion) =
updateTable(turn, state, state.contacts, id, initRecord("contact", attrs))
proc updateGroup(turn: var Turn; state: State; id, attrs: Assertion) =
## Update the group assertion held in the dataspace.
var
attrs = attrs
imagePath = attrs.step("groupProfile", "image").extractImagePath
@ -66,6 +76,7 @@ proc updateGroup(turn: var Turn; state: State; id, attrs: Assertion) =
updateTable(turn, state, state.groups, id, initRecord("group", attrs))
proc updateChat(turn: var Turn; state: State; ass: Assertion) =
## Update the chat assertion held in the dataspace.
var id: Option[Assertion]
var info = ass.step("chatInfo", "contact")
if info.isSome:
@ -82,6 +93,7 @@ proc updateChat(turn: var Turn; state: State; ass: Assertion) =
updateTable(turn, state, state.chats, id.get, initRecord("chat", ass))
proc bootChats(turn: var Turn; state: State) =
## Install observers and seed the dataspace.
let
chatPat = grabResp(%{ "chat": grab() })
chatsPat = grabResp(%{ "chats": grab() })
@ -94,8 +106,10 @@ proc bootChats(turn: var Turn; state: State) =
for chat in chats:
updateChat(turn, state, chat)
sendCommand(turn, state.websocket, "/chats")
# initial chats request to populate dataspace
type BootArgs {.preservesDictionary.} = object
# Arguments passed by the Syndicate server on stdin.
dataspace: Cap
websocket: Cap