Initial version

This commit is contained in:
Guy Van Sanden 2025-08-30 13:13:59 +02:00
parent 262aef0cf7
commit bed40c8f50
2 changed files with 123 additions and 0 deletions

0
INSTALL.md Normal file
View File

View File

@ -0,0 +1,123 @@
#!/usr/bin/env python3
import asyncio
import json
import os
import uuid
import websockets
__version__ = '0.1.0'
# SimpleX business bot, invite a preset SimpleX user to an auto-created group
# AGPL 3.0 or later. Written by Taurix IT https://www.taurix.net
WS_URL = os.environ.get("SXC_WS", "ws://127.0.0.1:5225")
OPERATOR_ALIAS = os.environ.get("SXC_OPERATOR_ALIAS", "@Operator")
OPERATOR_ADDR = os.environ.get("SXC_OPERATOR_ADDR", "").strip()
def cmd(c: str) -> str:
return json.dumps({"corrId": str(uuid.uuid4()), "cmd": c}, separators=(",", ":"))
def q(s: str) -> str:
return '"' + s.replace('"', r'\"') + '"'
# Cache per-group info
groups = {} # gid -> {"name": str, "customerId": str, "invited": bool}
async def ensure_operator_contact(ws):
if OPERATOR_ADDR:
# Idempotent: fine to call on start
await ws.send(cmd(f"/c {OPERATOR_ADDR}"))
def extract_business_chat(resp: dict):
# Primary event in your logs
if resp.get("type") == "acceptingBusinessRequest":
gi = resp.get("groupInfo", {})
gname = gi.get("localDisplayName") or gi.get("groupProfile", {}).get("displayName")
gid = gi.get("groupId")
bchat = gi.get("businessChat") or {}
cust = bchat.get("customerId")
return gid, gname, cust
# Fallback from the first items burst
if resp.get("type") == "newChatItems":
for it in resp.get("chatItems", []):
ci = it.get("chatInfo", {})
if ci.get("type") == "group" and ci.get("businessChat"):
gi = ci.get("groupInfo", {})
gname = gi.get("localDisplayName") or gi.get("groupProfile", {}).get("displayName")
gid = gi.get("groupId")
cust = ci.get("businessChat", {}).get("customerId")
return gid, gname, cust
return None, None, None
def member_connected(resp: dict):
if resp.get("type") != "newChatItems":
return None, None
for it in resp.get("chatItems", []):
ci = it.get("chatInfo", {})
if ci.get("type") != "group":
continue
gid = ci.get("groupInfo", {}).get("groupId")
cont = it.get("content", {})
if cont.get("type") == "rcvGroupEvent" and cont.get("rcvGroupEvent", {}).get("type") == "memberConnected":
gm = it.get("chatItem", {}).get("groupMember") or {}
member_id = gm.get("memberId")
disp = (gm.get("memberProfile", {}) or {}).get("displayName") or gm.get("localDisplayName") or ""
return gid, (member_id, disp)
return None, None
async def invite_operator(ws, gid: int):
info = groups.get(gid)
if not info or info.get("invited"):
return
gname = info["name"]
await ws.send(cmd(f"/a {gname} {OPERATOR_ALIAS}"))
await ws.send(cmd(f"#{gname} Added {OPERATOR_ALIAS} to assist."))
info["invited"] = True
print(f"[invited] {OPERATOR_ALIAS} -> group {gname} (id {gid})")
async def run():
while True:
try:
async with websockets.connect(WS_URL, max_size=None) as ws:
print(f"[ok] connected {WS_URL}")
await ensure_operator_contact(ws)
await ws.send(cmd("/help"))
async for raw in ws:
# print(f"[frame] {raw}")
try:
msg = json.loads(raw)
except Exception:
continue
resp = msg.get("resp") or {}
rtype = resp.get("type")
# 1) See a business chat -> cache it and invite operator
gid, gname, cust = extract_business_chat(resp)
if isinstance(gid, int) and gname and cust:
if gid not in groups:
groups[gid] = {"name": gname, "customerId": cust, "invited": False}
print(f"[chat] business chat created gid={gid} name='{gname}' customerId={cust}")
await invite_operator(ws, gid)
# 2) Member joins
jgid, member = member_connected(resp)
if isinstance(jgid, int) and member:
mem_id, disp = member
info = groups.get(jgid)
if info:
if mem_id == info["customerId"]:
print(f"[join] customer connected to gid={jgid} as '{disp}'")
else:
print(f"[join] non-customer joined gid={jgid}: '{disp}'")
except Exception as e:
print(f"[ws] {e}; retrying in 2s")
await asyncio.sleep(2)
if __name__ == "__main__":
# env:
# export SXC_WS=ws://127.0.0.1:5225
# export SXC_OPERATOR_ALIAS="Taurix-Operator" # pick a unique alias
# export SXC_OPERATOR_ADDR="simplex://..." # optional
asyncio.run(run())