docs: add .gitignore, LICENSE, Makefile, README tutorial, example bot, pyproject.toml, and core snekbot modules

This commit is contained in:
2025-02-01 14:58:18 +00:00
commit 6654901f37
15 changed files with 441 additions and 0 deletions
+10
View File
@@ -0,0 +1,10 @@
Metadata-Version: 2.2
Name: snekbot
Version: 1.0.0
Summary: Bot API for Snek chat
Author-email: retoor <retoor@molodetz.nl>
Keywords: chat,snek,molodetz,bot
Requires-Python: >=3.12
Description-Content-Type: text/markdown
License-File: LICENSE.txt
Requires-Dist: aiohttp
+11
View File
@@ -0,0 +1,11 @@
LICENSE.txt
pyproject.toml
src/snekbot/__init__.py
src/snekbot/__main__.py
src/snekbot/bot.py
src/snekbot/rpc.py
src/snekbot.egg-info/PKG-INFO
src/snekbot.egg-info/SOURCES.txt
src/snekbot.egg-info/dependency_links.txt
src/snekbot.egg-info/requires.txt
src/snekbot.egg-info/top_level.txt
@@ -0,0 +1 @@
+1
View File
@@ -0,0 +1 @@
aiohttp
+1
View File
@@ -0,0 +1 @@
snekbot
View File
View File
+111
View File
@@ -0,0 +1,111 @@
# Written by retoor@molodetz.nl
# This script serves as a bot service that connects to a WebSocket server using `aiohttp` and `asyncio`.
# It logs in, retrieves channel information, processes incoming messages, and responds based on specified conditions. The bot can execute commands received from a specific user and respond to certain ping messages.
# Used external imports:
# - aiohttp: A library for handling asynchronous HTTP and WebSocket connections.
# - asyncio: A library for writing single-threaded concurrent code using coroutines.
# - agent from agent: Module to get an AI agent for communication.
# - RPC from rpc: Module to interact with the server using Remote Procedure Call method.
# MIT License
import asyncio
import aiohttp
from snekbot.rpc import RPC
class Bot:
def __init__(self, username, password, url="wss://snek.molodetz.nl/rpc.ws"):
self.url = url
self.username = username
self.password = password
self.user = None
self.channels = None
self.rpc = None
self.ws = None
self.join_conversation = False
async def run(self, reconnect=True):
while True:
try:
await self.run_once()
except Exception as ex:
print(ex)
await asyncio.sleep(1)
if not reconnect:
break
async def send_message(self, channel_uid, message):
await self.rpc.send_message(channel_uid, message)
return True
async def run_once(self):
async with aiohttp.ClientSession() as session:
async with session.ws_connect(self.url) as ws:
self.ws = ws
self.rpc = RPC(ws)
rpc = self.rpc
await (await rpc.login(self.username, self.password))()
try:
raise Exception(self.login_result.exception)
except:
pass
self.channels = await (await rpc.get_channels())()
self.user = (await (await rpc.get_user(None))()).data
self.join_conversation = False
while True:
print("Waiting for message...")
data = await rpc.receive()
if not data:
break
try:
pass
except:
continue
message = data["message"].strip()
if data.username == self.user["username"]:
try:
await self.on_own_message(data)
except Exception as ex:
print("Error", ex)
continue
elif message.startswith("ping"):
try:
await self.on_ping(data)
except Exception as ex:
print("Error:", ex)
continue
elif "@" + self.user["nick"] in data.message:
try:
await self.on_mention(data)
except Exception as ex:
print("Error:", ex)
continue
elif "@" + self.user["nick"] + " join" in data.message:
self.join_conversation = True
try:
await self.on_join(data)
except:
print("Error:", ex)
continue
elif "@" + self.user["nick"] + " leave" in data.message:
self.join_conversation = False
try:
await self.on_leave(data)
except:
print("Error:", ex)
continue
else:
try:
await self.on_message(data)
except Exception as ex:
print("Error:", ex)
+101
View File
@@ -0,0 +1,101 @@
# Written by retoor@molodetz.nl
# 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.
# Uses aiohttp for asynchronous HTTP network communication.
# MIT License Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
import json
import pathlib
import subprocess
import aiohttp
class RPC:
class Response:
def __init__(self, msg):
if isinstance(msg, list):
self.list = msg
self.__dict__.update(msg)
def __iter__(self):
for item in self.data:
yield item
async def __aiter__(self):
for item in self.data:
yield item
def __getitem__(self, name):
return self.__dict__[name]
def __setitem__(self, name, value):
self.__dict__[name] = value
def __str__(self):
return json.dumps(self.__dict__, default=str, indent=2)
def __init__(self, ws):
self.ws = ws
def __getattr__(self, name):
async def method(*args, **kwargs):
payload = {"method": name, "args": args}
try:
await self.ws.send_json(payload)
except Exception:
return None
async def returner():
response = await self.ws.receive()
return self.Response(response.json())
return returner
return method
async def system(self, command):
if isinstance(command, str):
command = command.split(" ")
path = pathlib.Path("output.txt")
with path.open("w+") as f:
try:
subprocess.run(command, stderr=f, stdout=f)
except Exception as ex:
print("Error running command:", ex)
return f"Error: {ex}"
response = None
with path.open("r") as f:
response = f.read()
try:
path.unlink()
except Exception:
pass
return response
async def receive(self):
while True:
try:
msg = await self.ws.receive()
except Exception:
break
if msg.type == aiohttp.WSMsgType.CLOSED:
break
elif msg.type == aiohttp.WSMsgType.ERROR:
break
elif msg.type == aiohttp.WSMsgType.TEXT:
try:
return self.Response(msg.json())
except Exception:
break
return None