Initial version
This commit is contained in:
parent
262aef0cf7
commit
bed40c8f50
0
INSTALL.md
Normal file
0
INSTALL.md
Normal file
123
src/simplex_customerservicebot.py
Normal file
123
src/simplex_customerservicebot.py
Normal 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())
|
Loading…
x
Reference in New Issue
Block a user