2026-01-29 01:50:41 +07:00
|
|
|
import std/options
|
2026-02-09 06:37:47 -08:00
|
|
|
import std/sequtils
|
2026-01-29 01:38:08 +07:00
|
|
|
import results
|
|
|
|
|
import bindings
|
|
|
|
|
|
|
|
|
|
type
|
|
|
|
|
LibChat* = object
|
|
|
|
|
handle: ContextHandle
|
|
|
|
|
buffer_size: int
|
|
|
|
|
|
|
|
|
|
PayloadResult* = object
|
|
|
|
|
address*: string
|
|
|
|
|
data*: seq[uint8]
|
|
|
|
|
|
|
|
|
|
## Create a new conversations context
|
2026-02-19 17:25:42 -08:00
|
|
|
proc newConversationsContext*(name: string): LibChat =
|
2026-01-29 01:38:08 +07:00
|
|
|
|
2026-02-19 17:25:42 -08:00
|
|
|
result.handle = create_context(name.toReprCString)
|
2026-01-29 01:38:08 +07:00
|
|
|
result.buffer_size = 256
|
|
|
|
|
if result.handle.isNil:
|
|
|
|
|
raise newException(IOError, "Failed to create context")
|
|
|
|
|
|
2026-02-19 17:25:42 -08:00
|
|
|
## Get the friendly name of this context's installation
|
|
|
|
|
proc getInstallationName*(ctx: LibChat): string =
|
|
|
|
|
if ctx.handle == nil:
|
|
|
|
|
return ""
|
|
|
|
|
let name = installation_name(ctx.handle)
|
|
|
|
|
result = $name
|
|
|
|
|
|
2026-01-29 01:38:08 +07:00
|
|
|
## Destroy the context and free resources
|
|
|
|
|
proc destroy*(ctx: var LibChat) =
|
|
|
|
|
|
|
|
|
|
if not ctx.handle.isNil:
|
|
|
|
|
destroy_context(ctx.handle)
|
|
|
|
|
ctx.handle = nil
|
|
|
|
|
|
|
|
|
|
## Helper proc to create buffer of sufficient size
|
|
|
|
|
proc getBuffer*(ctx: LibChat): seq[byte] =
|
|
|
|
|
newSeq[byte](ctx.buffer_size)
|
|
|
|
|
|
|
|
|
|
## Generate a Introduction Bundle
|
2026-02-09 06:37:47 -08:00
|
|
|
proc createIntroductionBundle*(ctx: LibChat): Result[seq[byte], string] =
|
2026-01-29 01:38:08 +07:00
|
|
|
if ctx.handle == nil:
|
|
|
|
|
return err("Context handle is nil")
|
|
|
|
|
|
2026-02-09 06:37:47 -08:00
|
|
|
let res = create_intro_bundle(ctx.handle)
|
2026-02-25 20:09:55 +01:00
|
|
|
defer: destroy_intro_result(res)
|
2026-01-29 01:38:08 +07:00
|
|
|
|
2026-02-09 06:37:47 -08:00
|
|
|
if res.error_code != ErrNone:
|
2026-02-25 20:09:55 +01:00
|
|
|
return err("Failed to create intro bundle: " & $res.error_code)
|
2026-01-29 01:38:08 +07:00
|
|
|
|
2026-02-09 06:37:47 -08:00
|
|
|
return ok(res.intro_bytes.toSeq())
|
2026-01-29 01:38:08 +07:00
|
|
|
|
|
|
|
|
## Create a Private Convo
|
2026-02-09 06:37:47 -08:00
|
|
|
proc createNewPrivateConvo*(ctx: LibChat, bundle: seq[byte], content: seq[byte]): Result[(string, seq[PayloadResult]), string] =
|
2026-01-29 01:38:08 +07:00
|
|
|
if ctx.handle == nil:
|
|
|
|
|
return err("Context handle is nil")
|
|
|
|
|
|
|
|
|
|
if bundle.len == 0:
|
|
|
|
|
return err("bundle is zero length")
|
|
|
|
|
if content.len == 0:
|
|
|
|
|
return err("content is zero length")
|
|
|
|
|
|
2026-02-25 20:09:55 +01:00
|
|
|
let res = bindings.create_new_private_convo(ctx.handle, bundle.toSlice(), content.toSlice())
|
|
|
|
|
defer: destroy_convo_result(res)
|
2026-01-29 01:38:08 +07:00
|
|
|
|
|
|
|
|
if res.error_code != 0:
|
2026-02-25 20:09:55 +01:00
|
|
|
return err("Failed to create private convo: " & $res.error_code)
|
2026-01-29 01:38:08 +07:00
|
|
|
|
|
|
|
|
var payloads = newSeq[PayloadResult](res.payloads.len)
|
|
|
|
|
for i in 0 ..< res.payloads.len:
|
|
|
|
|
let p = res.payloads[int(i)]
|
2026-02-25 20:09:55 +01:00
|
|
|
payloads[int(i)] = PayloadResult(address: $p.address, data: p.data.toSeq())
|
2026-01-29 01:38:08 +07:00
|
|
|
|
2026-02-25 20:09:55 +01:00
|
|
|
return ok(($res.convo_id, payloads))
|
2026-01-29 01:38:08 +07:00
|
|
|
|
2026-02-28 03:16:10 -08:00
|
|
|
proc listConversations*(ctx: LibChat): Result[seq[string], string] =
|
|
|
|
|
if ctx.handle == nil:
|
|
|
|
|
return err("Context handle is nil")
|
|
|
|
|
let res = bindings.list_conversations(ctx.handle)
|
|
|
|
|
|
|
|
|
|
if res.error_code != 0:
|
|
|
|
|
result = err("Failed to list conversations: " & $res.error_code)
|
|
|
|
|
destroy_list_result(res)
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
ok(res.convo_ids.toSeq())
|
|
|
|
|
|
2026-01-29 01:50:41 +07:00
|
|
|
## Send content to an existing conversation
|
2026-02-06 23:58:23 +07:00
|
|
|
proc sendContent*(ctx: LibChat, convoId: string, content: seq[byte]): Result[seq[PayloadResult], string] =
|
2026-01-29 01:50:41 +07:00
|
|
|
if ctx.handle == nil:
|
|
|
|
|
return err("Context handle is nil")
|
|
|
|
|
|
|
|
|
|
if content.len == 0:
|
|
|
|
|
return err("content is zero length")
|
|
|
|
|
|
2026-02-25 20:09:55 +01:00
|
|
|
let res = bindings.send_content(ctx.handle, convoId.toReprCString, content.toSlice())
|
|
|
|
|
defer: destroy_send_content_result(res)
|
2026-01-29 01:50:41 +07:00
|
|
|
|
|
|
|
|
if res.error_code != 0:
|
2026-02-25 20:09:55 +01:00
|
|
|
return err("Failed to send content: " & $res.error_code)
|
2026-01-29 01:50:41 +07:00
|
|
|
|
2026-02-25 20:09:55 +01:00
|
|
|
let payloads = res.payloads.toSeq().mapIt(PayloadResult(address: $it.address, data: it.data.toSeq()))
|
2026-01-29 01:50:41 +07:00
|
|
|
return ok(payloads)
|
|
|
|
|
|
|
|
|
|
type
|
|
|
|
|
ContentResult* = object
|
|
|
|
|
conversationId*: string
|
|
|
|
|
data*: seq[uint8]
|
2026-02-18 20:01:47 +01:00
|
|
|
isNewConvo*: bool
|
2026-01-29 01:50:41 +07:00
|
|
|
|
|
|
|
|
## Handle an incoming payload and decrypt content
|
|
|
|
|
proc handlePayload*(ctx: LibChat, payload: seq[byte]): Result[Option[ContentResult], string] =
|
|
|
|
|
if ctx.handle == nil:
|
|
|
|
|
return err("Context handle is nil")
|
|
|
|
|
|
|
|
|
|
if payload.len == 0:
|
|
|
|
|
return err("payload is zero length")
|
|
|
|
|
|
2026-02-25 20:09:55 +01:00
|
|
|
let res = bindings.handle_payload(ctx.handle, payload.toSlice())
|
|
|
|
|
defer: destroy_handle_payload_result(res)
|
2026-01-29 01:50:41 +07:00
|
|
|
|
2026-02-09 06:37:47 -08:00
|
|
|
if res.error_code != ErrNone:
|
|
|
|
|
return err("Failed to handle payload: " & $res.error_code)
|
2026-01-29 01:50:41 +07:00
|
|
|
|
2026-02-09 06:37:47 -08:00
|
|
|
let content = res.content.toSeq()
|
|
|
|
|
if content.len == 0:
|
2026-01-29 01:50:41 +07:00
|
|
|
return ok(none(ContentResult))
|
|
|
|
|
|
|
|
|
|
return ok(some(ContentResult(
|
2026-02-09 06:37:47 -08:00
|
|
|
conversationId: $res.convo_id,
|
2026-02-18 20:01:47 +01:00
|
|
|
data: content,
|
|
|
|
|
isNewConvo: res.is_new_convo
|
2026-01-29 01:50:41 +07:00
|
|
|
)))
|