Update snek core bot.

This commit is contained in:
retoor 2025-04-24 21:03:12 +02:00
parent 7c688c1c9b
commit a91d4e72ec
4 changed files with 143 additions and 96 deletions

View File

@ -42,23 +42,19 @@ class ExampleBot(Bot):
async def on_join(self, channel_uid): async def on_join(self, channel_uid):
print(f"I joined!") print(f"I joined!")
await self.send_message( await self.send_message(
channel_uid, channel_uid,
f"Hello, i'm actively part of the conversation in channel {channel_uid} now, you don't have to mention me anymore. " f"Hello, i'm actively part of the conversation in channel {channel_uid} now, you don't have to mention me anymore. ",
) )
async def on_leave(self, channel_uid): async def on_leave(self, channel_uid):
print(f"I left!!") print(f"I left!!")
await self.send_message( await self.send_message(
channel_uid, channel_uid, "I stop actively being part of the conversation now. Bye!"
"I stop actively being part of the conversation now. Bye!"
) )
async def on_ping(self,username, user_nick, channel_uid, message): async def on_ping(self, username, user_nick, channel_uid, message):
print(f"Ping from {user_nick} in channel {channel_uid}: {message}") print(f"Ping from {user_nick} in channel {channel_uid}: {message}")
await self.send_message( await self.send_message(channel_uid, "pong " + message)
channel_uid,
"pong " + message
)
async def on_own_message(self, data): async def on_own_message(self, data):
print(f"Received my own message: {data.message}") print(f"Received my own message: {data.message}")
@ -80,7 +76,9 @@ class ExampleBot(Bot):
) )
await self.send_message(channel_uid, result) await self.send_message(channel_uid, result)
else: else:
await self.send_message(channel_uid, f'Hey {user_nick}, Thanks for mentioning me "{message}".') await self.send_message(
channel_uid, f'Hey {user_nick}, Thanks for mentioning me "{message}".'
)
async def on_message(self, sender_username, sender_nick, channel_uid, message): async def on_message(self, sender_username, sender_nick, channel_uid, message):
print(f"Message from {sender_nick}: {message}") print(f"Message from {sender_nick}: {message}")
@ -98,7 +96,9 @@ class ExampleBot(Bot):
await self.send_message(channel_uid, result) await self.send_message(channel_uid, result)
bot = ExampleBot(url="ws://snek.molodetz.nl/rpc.ws", username="example", password="example") bot = ExampleBot(
url="ws://snek.molodetz.nl/rpc.ws", username="example", password="example"
)
asyncio.run(bot.run()) asyncio.run(bot.run())
``` ```

View File

@ -11,23 +11,19 @@ class ExampleBot(Bot):
async def on_join(self, channel_uid): async def on_join(self, channel_uid):
print(f"I joined!") print(f"I joined!")
await self.send_message( await self.send_message(
channel_uid, channel_uid,
f"Hello, i'm actively part of the conversation in channel {channel_uid} now, you don't have to mention me anymore. " f"Hello, i'm actively part of the conversation in channel {channel_uid} now, you don't have to mention me anymore. ",
) )
async def on_leave(self, channel_uid): async def on_leave(self, channel_uid):
print(f"I left!!") print(f"I left!!")
await self.send_message( await self.send_message(
channel_uid, channel_uid, "I stop actively being part of the conversation now. Bye!"
"I stop actively being part of the conversation now. Bye!"
) )
async def on_ping(self,username, user_nick, channel_uid, message): async def on_ping(self, username, user_nick, channel_uid, message):
print(f"Ping from {user_nick} in channel {channel_uid}: {message}") print(f"Ping from {user_nick} in channel {channel_uid}: {message}")
await self.send_message( await self.send_message(channel_uid, "pong " + message)
channel_uid,
"pong " + message
)
async def on_own_message(self, data): async def on_own_message(self, data):
print(f"Received my own message: {data.message}") print(f"Received my own message: {data.message}")
@ -49,7 +45,9 @@ class ExampleBot(Bot):
) )
await self.send_message(channel_uid, result) await self.send_message(channel_uid, result)
else: else:
await self.send_message(channel_uid, f'Hey {user_nick}, Thanks for mentioning me "{message}".') await self.send_message(
channel_uid, f'Hey {user_nick}, Thanks for mentioning me "{message}".'
)
async def on_message(self, sender_username, sender_nick, channel_uid, message): async def on_message(self, sender_username, sender_nick, channel_uid, message):
print(f"Message from {sender_nick}: {message}") print(f"Message from {sender_nick}: {message}")
@ -67,5 +65,7 @@ class ExampleBot(Bot):
await self.send_message(channel_uid, result) await self.send_message(channel_uid, result)
bot = ExampleBot(url="ws://snek.molodetz.nl/rpc.ws", username="example", password="xxxxxx") bot = ExampleBot(
url="ws://snek.molodetz.nl/rpc.ws", username="example", password="xxxxxx"
)
asyncio.run(bot.run()) asyncio.run(bot.run())

View File

@ -12,15 +12,16 @@
# MIT License # MIT License
import asyncio import asyncio
import logging
import traceback
import aiohttp import aiohttp
from snekbot.rpc import RPC from snekbot.rpc import RPC
import logging
logger = logging.getLogger("snekbot") logger = logging.getLogger("snekbot")
class Bot: class Bot:
def __init__(self, username, password, url="wss://snek.molodetz.nl/rpc.ws"): def __init__(self, username, password, url="wss://snek.molodetz.nl/rpc.ws"):
@ -33,20 +34,41 @@ class Bot:
self.ws = None self.ws = None
self.joined = set() self.joined = set()
async def on_join(self, channel_uid):
self.joined.add(channel_uid)
logger.debug("Joined channel: " + channel_uid)
async def on_leave(self, channel_uid):
self.joined.remove(channel_uid)
logger.debug("Left channel: " + channel_uid)
async def on_mention(self, username, user_nick, channel_uid, message):
logger.debug("Received mention from " + username + ": " + message)
async def on_idle(self):
logger.debug("Bot is idle.")
async def on_ping(self, username, user_nick, channel_uid, message):
logger.debug("Received ping from " + username + ": " + message)
async def on_own_message(self, channel_uid, message):
logger.debug("Received own message: " + message)
async def on_message(self, username, user_nick, channel_uid, message):
logger.debug("Received message from " + username + ": " + message)
async def run(self, reconnect=True): async def run(self, reconnect=True):
while True: while True:
try: try:
await self.run_once() await self.run_once()
except Exception as ex: except:
print(ex) traceback.print_exc()
await asyncio.sleep(1) await asyncio.sleep(1)
if not reconnect: if not reconnect:
break break
def has_joined(self, channel_uid): def has_joined(self, channel_uid):
return channel_uid in self.joined return channel_uid in self.joined
@ -55,57 +77,71 @@ class Bot:
await self.rpc.send_message(channel_uid, message) await self.rpc.send_message(channel_uid, message)
return True return True
async def get_channels(self):
return await (await self.rpc.get_channels())()
async def run_once(self): async def run_once(self):
async with aiohttp.ClientSession() as session: async with aiohttp.ClientSession() as session:
async with session.ws_connect(self.url) as ws: async with session.ws_connect(self.url) as ws:
self.ws = ws self.ws = ws
self.rpc = RPC(ws) rpc = RPC(self.ws)
rpc = self.rpc self.rpc = rpc
success = await (await rpc.login(self.username, self.password))() await (await rpc.login(self.username, self.password))()
print(success) self.channels = await self.get_channels()
self.channels = await (await rpc.get_channels())()
for channel in self.channels: for channel in self.channels:
logger.debug("Found channel: " + channel["name"]) logger.debug("Found channel: " + channel["name"])
self.user = (await (await rpc.get_user(None))()) self.user = await (await rpc.get_user(None))()
logger.debug("Logged in as: " + self.user["username"]) logger.debug("Logged in as: " + self.user["username"])
while True: while True:
await self.on_idle()
data = await rpc.receive()
if data is None:
break
try: try:
try: message = data.message.strip()
await self.on_idle() except AttributeError:
except AttributeError: continue
pass
data = await rpc.receive() if data.username == self.user["username"]:
await self.on_own_message(data.channel_uid, message)
if data is None: elif message.startswith("ping"):
break await self.on_ping(
data.username,
try: data.user_nick,
message = data.message.strip() data.channel_uid,
except AttributeError: data.message.lstrip("ping ").strip(),
continue )
elif any(
if data.username == self.user['username']: [
await self.on_own_message(data.channel_uid, message) "@" + self.user["nick"] + " join" in data.message,
elif message.startswith("ping"): "@" + self.user["username"] + " join" in data.message,
await self.on_ping(data.username, data.user_nick, data.channel_uid, data.message.lstrip("ping ").strip()) ]
elif any([ ):
"@" + self.user['nick'] + " join" in data.message, await self.on_join(data.channel_uid)
"@" + self.user['username'] + " join" in data.message]): elif any(
self.joined.add(data.channel_uid) [
await self.on_join(data.channel_uid) "@" + self.user["nick"] + " leave" in data.message,
elif any([ "@" + self.user["username"] + " leave" in data.message,
"@" + self.user['nick'] + " leave" in data.message, ]
"@" + self.user['username'] + " leave" in data.message]): ):
self.joined.remove(data.channel_uid) await self.on_leave(data.channel_uid)
await self.on_leave(data.channel_uid) elif (
elif "@" + self.user['nick'] in data.message or "@" + self.user['username'] in data.message: "@" + self.user["nick"] in data.message
await self.on_mention(data.username, data.user_nick, data.channel_uid, data.message) or "@" + self.user["username"] in data.message
else: ):
await self.on_message(data.username, data.user_nick, data.channel_uid, data.message) await self.on_mention(
except AttributeError as ex: data.username,
logger.exception(ex) data.user_nick,
raise data.channel_uid,
except Exception as ex: data.message,
raise ex )
else:
await self.on_message(
data.username,
data.user_nick,
data.channel_uid,
data.message,
)

View File

@ -1,6 +1,6 @@
# Written by retoor@molodetz.nl # Written by retoor@molodetz.nl
# This code defines an RPC class that allows asynchronous communication over websockets, # This code defines an RPC class that allows asynchronous communication over websockets,
# including command execution and handling of asynchronous responses using Python's asyncio and websockets library. # including command execution and handling of asynchronous responses using Python's asyncio and websockets library.
# Uses aiohttp for asynchronous HTTP network communication. # Uses aiohttp for asynchronous HTTP network communication.
@ -9,22 +9,23 @@
import json import json
import subprocess import logging
import pathlib import pathlib
import aiohttp import subprocess
import uuid import uuid
import logging import aiohttp
logger = logging.getLogger("snekbot.rpc") logger = logging.getLogger("snekbot.rpc")
class RPC: class RPC:
class Response: class Response:
def __init__(self, msg): def __init__(self, msg):
if isinstance(msg, list): if isinstance(msg, list):
self.list = msg self.list = msg
self.__dict__.update(msg) self.__dict__.update(msg)
def __iter__(self): def __iter__(self):
for k in self.__dict__.get("data", []): for k in self.__dict__.get("data", []):
yield k yield k
@ -38,40 +39,47 @@ class RPC:
return self.__dict__[name] return self.__dict__[name]
except: except:
pass pass
return self.__dict__.get('data',{})[name] return self.__dict__.get("data", {})[name]
def __setitem__(self, name, value): def __setitem__(self, name, value):
if not name in self.__dict__.get('data',{}): if name not in self.__dict__.get("data", {}):
self.__dict__[name] = value self.__dict__[name] = value
else: else:
self.__dict__['data'][name] = value self.__dict__["data"][name] = value
def __str__(self): def __str__(self):
return json.dumps(self.__dict__, default=str, indent=2) return json.dumps(self.__dict__, default=str, indent=2)
def __init__(self, ws): def __init__(self, ws):
self.ws = ws self.ws = ws
self.current_call_id = None self.current_call_id = None
async def echo(self, data): async def echo(self, data):
logger.debug("Schedule for retry: " + str(data)) logger.debug("Schedule for retry: " + str(data))
await self.ws.send_json(dict(method="echo",args=[data])) await self.ws.send_json({"method": "echo", "args": [data]})
def __getattr__(self, name): def __getattr__(self, name):
async def method(*args, **kwargs): async def method(*args, **kwargs):
self.current_call_id = str(uuid.uuid4()) self.current_call_id = str(uuid.uuid4())
payload = dict(method=name, args=args, kwargs=kwargs, callId=self.current_call_id) payload = {
"method": name,
"args": args,
"kwargs": kwargs,
"callId": self.current_call_id,
}
await self.ws.send_json(payload) await self.ws.send_json(payload)
async def returner(): async def returner():
while True: while True:
response = await self.ws.receive() response = await self.ws.receive()
data = response.json() data = response.json()
if not data.get("callId") == self.current_call_id: if not data.get("callId") == self.current_call_id:
await self.echo(data) await self.echo(data)
continue continue
return self.Response(data) return self.Response(data)
return returner
return returner
return method return method
async def system(self, command): async def system(self, command):
@ -108,15 +116,18 @@ class RPC:
break break
if msg.type == aiohttp.WSMsgType.CLOSED: if msg.type == aiohttp.WSMsgType.CLOSED:
logger.exception("WebSocket closed.") logger.exception("WebSocket closed.")
break break
elif msg.type == aiohttp.WSMsgType.ERROR: elif msg.type == aiohttp.WSMsgType.ERROR:
logger.exception("WebSocket error.") logger.exception("WebSocket error.")
break break
elif msg.type == aiohttp.WSMsgType.TEXT: elif msg.type == aiohttp.WSMsgType.TEXT:
if self.current_call_id and not msg.json().get('callId') != self.current_call_id: if (
self.current_call_id
and not msg.json().get("callId") != self.current_call_id
):
await self.echo(msg.json()) await self.echo(msg.json())
continue continue
try: try:
response = self.Response(msg.json()) response = self.Response(msg.json())
self.current_call_id = None self.current_call_id = None
@ -126,5 +137,5 @@ class RPC:
break break
else: else:
logger.exception("Unexpected message type.") logger.exception("Unexpected message type.")
break break
return None return None