Compare commits
44
Commits
25a5eca313
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e5f550c1b9 | ||
|
|
3d77653cbc | ||
|
|
c26f636e9d | ||
|
|
f47e803f69 | ||
|
|
4ed7c60346 | ||
|
|
c0e94d4199 | ||
|
|
06530eb225 | ||
|
|
fcd53a6b85 | ||
|
|
a73df33f05 | ||
|
|
99492ec73b | ||
|
|
a326406c90 | ||
|
|
04db6f4590 | ||
|
|
ad8c6bf308 | ||
|
|
8930c40f43 | ||
|
|
746debccd7 | ||
|
|
df7794a6a9 | ||
|
|
0adb5836d3 | ||
|
|
33a2d201da | ||
|
|
eeb4593854 | ||
|
|
4cbb7d17e3 | ||
|
|
476041c0ab | ||
|
|
c1421d0c22 | ||
|
|
bedd2345a4 | ||
|
|
de5b20bed9 | ||
|
|
4d09fad32f | ||
|
|
cfbd5a1ce4 | ||
|
|
e078ef918b | ||
|
|
85922db224 | ||
|
|
05410b484e | ||
|
|
5a94f2d041 | ||
|
|
ffc347030f | ||
|
|
e88b154510 | ||
|
|
dfb893c0cd | ||
|
|
103142acad | ||
|
|
abd8bcb7ee | ||
|
|
7bb4691ab4 | ||
|
|
cf47bd65d3 | ||
|
|
4f4ae87b7a | ||
|
|
756e26eb42 | ||
|
|
e85f7d748f | ||
|
|
158df199cc | ||
|
|
3b5cb74d94 | ||
|
|
3628ab98f5 | ||
|
|
6654901f37 |
@@ -1,83 +1,81 @@
|
|||||||
# 🐍 SnekBot: Your Instant Chat Companion 🚀
|
# SnekBot: Your Instant Chat Companion
|
||||||
|
|
||||||
## 🔥 Create Your Own Bot in 5 Minutes Flat!
|
## Create Your Own Bot in Minutes
|
||||||
|
|
||||||
### Why SnekBot?
|
### Overview
|
||||||
- 💨 Lightning-fast setup
|
SnekBot is designed for rapid deployment and customization, providing a fully asynchronous and production-ready chat bot solution. It is built to handle network issues effectively, ensuring a reliable user experience.
|
||||||
- 🤖 Fully async and production-ready
|
|
||||||
- 🌈 Super flexible and easy to customize
|
|
||||||
- 🛡️ Handles network issues like a boss
|
|
||||||
|
|
||||||
### Prerequisites
|
### Prerequisites
|
||||||
- Python 3.8+ (because we're modern like that)
|
- Python 3.8 or higher
|
||||||
- A sense of adventure 🏴☠️
|
- Basic understanding of Python programming
|
||||||
|
|
||||||
### Quick Installation Magic ✨
|
### Installation Instructions
|
||||||
|
|
||||||
#### 1. Prep Your Environment
|
#### 1. Prepare Your Environment
|
||||||
```bash
|
```bash
|
||||||
# Ubuntu/Debian users, get ready!
|
# For Ubuntu/Debian users:
|
||||||
sudo apt install python3 python3-venv python3-pip -y
|
sudo apt install python3 python3-venv python3-pip -y
|
||||||
|
|
||||||
# Create your bot's magical realm
|
# Create a virtual environment for your bot
|
||||||
python3 -m venv venv
|
python3 -m venv venv
|
||||||
source venv/bin/activate
|
source venv/bin/activate
|
||||||
```
|
```
|
||||||
|
|
||||||
#### 2. Summon SnekBot
|
#### 2. Install SnekBot
|
||||||
```bash
|
```bash
|
||||||
pip install git+https://molodetz.nl/retoor/snekbot.git
|
pip install git+https://molodetz.nl/retoor/snekbot.git
|
||||||
```
|
```
|
||||||
|
|
||||||
### 🤖 Bot Creation Wizard
|
### Bot Development
|
||||||
|
To create your bot, use the following template:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
import asyncio
|
import asyncio
|
||||||
from snekbot.bot import Bot
|
from snekbot.bot import Bot
|
||||||
|
|
||||||
class CoolSnekBot(Bot):
|
class CustomSnekBot(Bot):
|
||||||
async def on_join(self, channel_uid):
|
async def on_join(self, channel_uid):
|
||||||
await self.send_message(
|
await self.send_message(
|
||||||
channel_uid,
|
channel_uid,
|
||||||
"Yo! I'm here to make this chat awesome! 🎉"
|
"Hello! I am here to assist you."
|
||||||
)
|
)
|
||||||
|
|
||||||
async def on_message(self, sender_username, sender_nick, channel_uid, message):
|
async def on_message(self, sender_username, sender_nick, channel_uid, message):
|
||||||
message = message.lower()
|
message = message.lower()
|
||||||
if "hello" in message:
|
if "hello" in message:
|
||||||
await self.send_message(channel_uid, f"Hi there, {sender_nick}! 👋")
|
await self.send_message(channel_uid, f"Greetings, {sender_nick}!")
|
||||||
elif "bye" in message:
|
elif "bye" in message:
|
||||||
await self.send_message(channel_uid, f"Catch you later, {sender_nick}! 🤙")
|
await self.send_message(channel_uid, f"Goodbye, {sender_nick}!")
|
||||||
|
|
||||||
# Launch your bot into the wild!
|
# Initialize your bot
|
||||||
bot = CoolSnekBot(
|
bot = CustomSnekBot(
|
||||||
url="wss://your-snek-instance.com/rpc.ws",
|
url="wss://your-snek-instance.com/rpc.ws",
|
||||||
username="your_awesome_bot",
|
username="your_bot_username",
|
||||||
password="super_secret_password"
|
password="your_secure_password"
|
||||||
)
|
)
|
||||||
asyncio.run(bot.run())
|
asyncio.run(bot.run())
|
||||||
```
|
```
|
||||||
|
|
||||||
### 🚀 Run Your Bot
|
### Running Your Bot
|
||||||
```bash
|
```bash
|
||||||
python your_awesome_bot.py
|
python your_bot_script.py
|
||||||
```
|
```
|
||||||
|
|
||||||
### Event Handlers You Can Override
|
### Event Handlers
|
||||||
- `on_join`: When bot enters a channel
|
You can override the following event handlers:
|
||||||
- `on_leave`: When bot exits a channel
|
- `on_join`: Triggered when the bot joins a channel
|
||||||
- `on_ping`: Respond to ping messages
|
- `on_leave`: Triggered when the bot leaves a channel
|
||||||
- `on_mention`: Handle direct mentions
|
- `on_ping`: Responds to ping messages
|
||||||
- `on_message`: Catch and respond to general messages
|
- `on_mention`: Handles direct mentions
|
||||||
|
- `on_message`: Processes incoming messages
|
||||||
|
|
||||||
### 💡 Pro Tips
|
### Additional Information
|
||||||
- Add `logging.basicConfig(level=logging.DEBUG)` for detailed logs
|
- For detailed logging, include `logging.basicConfig(level=logging.DEBUG)` in your code.
|
||||||
- The bot automatically reconnects if connection drops
|
- The bot is designed to automatically reconnect in case of connection drops.
|
||||||
- Customize to your heart's content!
|
- Feel free to customize the bot to meet your specific requirements.
|
||||||
|
|
||||||
### Contributing
|
### Contribution Guidelines
|
||||||
Got cool ideas? PRs are welcome! 🤝
|
Contributions are welcome. Please submit pull requests for any enhancements or bug fixes.
|
||||||
|
|
||||||
### License
|
### License
|
||||||
MIT - Go wild, have fun! 🎈
|
This project is licensed under the MIT License.
|
||||||
|
|
||||||
+1
-1
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "snekbot"
|
name = "snekbot"
|
||||||
version = "1.0.0"
|
version = "1.1.0"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
description = "Bot API for Snek chat"
|
description = "Bot API for Snek chat"
|
||||||
authors = [
|
authors = [
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
Metadata-Version: 2.4
|
Metadata-Version: 2.4
|
||||||
Name: snekbot
|
Name: snekbot
|
||||||
Version: 1.0.0
|
Version: 1.1.0
|
||||||
Summary: Bot API for Snek chat
|
Summary: Bot API for Snek chat
|
||||||
Author-email: retoor <retoor@molodetz.nl>
|
Author-email: retoor <retoor@molodetz.nl>
|
||||||
Keywords: chat,snek,molodetz,bot
|
Keywords: chat,snek,molodetz,bot
|
||||||
@@ -10,130 +10,84 @@ License-File: LICENSE.txt
|
|||||||
Requires-Dist: aiohttp
|
Requires-Dist: aiohttp
|
||||||
Dynamic: license-file
|
Dynamic: license-file
|
||||||
|
|
||||||
# Snekbot API
|
# SnekBot: Your Instant Chat Companion
|
||||||
|
|
||||||
This is the Snekbot API. This document describes how to create a bot responding to "hello", "bye" and "@username-of-bot".
|
## Create Your Own Bot in Minutes
|
||||||
|
|
||||||
## 5 minute tutorial
|
### Overview
|
||||||
|
SnekBot is designed for rapid deployment and customization, providing a fully asynchronous and production-ready chat bot solution. It is built to handle network issues effectively, ensuring a reliable user experience.
|
||||||
|
|
||||||
Literally.
|
### Prerequisites
|
||||||
|
- Python 3.8 or higher
|
||||||
|
- Basic understanding of Python programming
|
||||||
|
|
||||||
### Installation
|
### Installation Instructions
|
||||||
#### Requirements:
|
|
||||||
Python:
|
|
||||||
- python3
|
|
||||||
- python3-venv
|
|
||||||
- python3-pip
|
|
||||||
Use apt or your package manager to install these packages. There is a big chance your system already has them.
|
|
||||||
|
|
||||||
For Debian (Ubuntu): `sudo apt install python3 python3-venv python3-pip -y`
|
#### 1. Prepare Your Environment
|
||||||
|
```bash
|
||||||
|
# For Ubuntu/Debian users:
|
||||||
|
sudo apt install python3 python3-venv python3-pip -y
|
||||||
|
|
||||||
#### Environment
|
# Create a virtual environment for your bot
|
||||||
- `python3 -m venv venv`
|
python3 -m venv venv
|
||||||
- `source venv/bin/activate`
|
source venv/bin/activate
|
||||||
- `pip install git+https://molodetz.nl/retoor/snekbot.git`
|
```
|
||||||
|
|
||||||
#### Create account
|
#### 2. Install SnekBot
|
||||||
Create regular user account for your bot. You need this later in your script.
|
```bash
|
||||||
Make sure you have this information right now:
|
pip install git+https://molodetz.nl/retoor/snekbot.git
|
||||||
- bot username
|
```
|
||||||
- bot password
|
|
||||||
- bot url (wss://your-snek-instance.com/rpc.ws)
|
|
||||||
|
|
||||||
#### Create a file
|
### Bot Development
|
||||||
Open a file ending with the `.py` extension and paste this content. Replace the authentication details on the bottom lines with the one of the account you just created.
|
To create your bot, use the following template:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
import asyncio
|
import asyncio
|
||||||
|
|
||||||
from snekbot.bot import Bot
|
from snekbot.bot import Bot
|
||||||
|
|
||||||
|
class CustomSnekBot(Bot):
|
||||||
class ExampleBot(Bot):
|
|
||||||
|
|
||||||
async def on_join(self, channel_uid):
|
async def on_join(self, channel_uid):
|
||||||
await super().on_join(channel_uid)
|
|
||||||
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. ",
|
"Hello! I am here to assist you."
|
||||||
)
|
|
||||||
|
|
||||||
async def on_leave(self, channel_uid):
|
|
||||||
await super().on_leave(channel_uid)
|
|
||||||
print(f"I left!!")
|
|
||||||
await self.send_message(
|
|
||||||
channel_uid, "I stop actively being part of the conversation now. Bye!"
|
|
||||||
)
|
|
||||||
|
|
||||||
async def on_ping(self, username, user_nick, channel_uid, message):
|
|
||||||
print(f"Ping from {user_nick} in channel {channel_uid}: {message}")
|
|
||||||
await self.send_message(channel_uid, "pong " + message)
|
|
||||||
|
|
||||||
async def on_own_message(self, channel_uid, data):
|
|
||||||
print(f"Received my own message: {data.message}")
|
|
||||||
|
|
||||||
async def on_mention(self, username, user_nick, channel_uid, message):
|
|
||||||
|
|
||||||
message = message[len(self.username) + 2 :]
|
|
||||||
print(f"Mention from {user_nick}: {message}")
|
|
||||||
|
|
||||||
if "source" in message:
|
|
||||||
with open(__file__) as f:
|
|
||||||
result = f.read()
|
|
||||||
result = result.replace(f'"{self.username}"', '"example username"')
|
|
||||||
result = result.replace(self.password, "example password")
|
|
||||||
result = (
|
|
||||||
"This is the actual source code running me now. Fresh from the bakery:\n\n```python\n"
|
|
||||||
+ result
|
|
||||||
+ "\n```"
|
|
||||||
)
|
|
||||||
await self.send_message(channel_uid, result)
|
|
||||||
else:
|
|
||||||
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}")
|
|
||||||
if not self.has_joined(channel_uid):
|
|
||||||
print(f"Probably not for me since i'm not mentioned and not joined yet")
|
|
||||||
return
|
|
||||||
message = message.lower()
|
message = message.lower()
|
||||||
result = None
|
|
||||||
if "hello" in message:
|
if "hello" in message:
|
||||||
result = f"Hi @{sender_nick}"
|
await self.send_message(channel_uid, f"Greetings, {sender_nick}!")
|
||||||
elif "bye" in message:
|
elif "bye" in message:
|
||||||
result = f"Bye @{sender_nick}"
|
await self.send_message(channel_uid, f"Goodbye, {sender_nick}!")
|
||||||
|
|
||||||
if result:
|
# Initialize your bot
|
||||||
await self.send_message(channel_uid, result)
|
bot = CustomSnekBot(
|
||||||
|
url="wss://your-snek-instance.com/rpc.ws",
|
||||||
|
username="your_bot_username",
|
||||||
bot = ExampleBot(
|
password="your_secure_password"
|
||||||
url="ws://snek.molodetz.nl/rpc.ws", username="example", password="example"
|
|
||||||
)
|
)
|
||||||
asyncio.run(bot.run())
|
asyncio.run(bot.run())
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Run the bot
|
### Running Your Bot
|
||||||
Make sure you have (still) activated your virtual env.
|
|
||||||
```bash
|
```bash
|
||||||
python [your-script].py
|
python your_bot_script.py
|
||||||
```
|
```
|
||||||
If you get the error 'python not found' or 'aiohttp not found', run `source .venv/bin/activate` again and run `python [your script].py` again.
|
|
||||||
|
|
||||||
#### Debugging
|
### Event Handlers
|
||||||
Add `import logging` and `logging.BasicConfig(level=logging.DEBUG)`.
|
You can override the following event handlers:
|
||||||
|
- `on_join`: Triggered when the bot joins a channel
|
||||||
|
- `on_leave`: Triggered when the bot leaves a channel
|
||||||
|
- `on_ping`: Responds to ping messages
|
||||||
|
- `on_mention`: Handles direct mentions
|
||||||
|
- `on_message`: Processes incoming messages
|
||||||
|
|
||||||
#### Summary
|
### Additional Information
|
||||||
The `ExampleBot` class inherits from a base `Bot` class and implements several event handlers:
|
- For detailed logging, include `logging.basicConfig(level=logging.DEBUG)` in your code.
|
||||||
- `on_join`: Sends a welcome message when the bot joins a channel.
|
- The bot is designed to automatically reconnect in case of connection drops.
|
||||||
- `on_leave`: Sends a goodbye message when the bot leaves.
|
- Feel free to customize the bot to meet your specific requirements.
|
||||||
- `on_ping`: Responds with "pong" when it receives a ping message.
|
|
||||||
- `on_own_message`: Logs messages sent by the bot itself.
|
|
||||||
- `on_mention`: Handles mentions; if "source" is in the message, it replies with its own source code, with sensitive data disguised.
|
|
||||||
- `on_message`: Responds to "hello" and "bye" messages if the bot has joined the channel.
|
|
||||||
The bot will be instantiated and runs asynchronously. It will survive server deploys and network outages. If such issue occurs, it will try to reconnect within a second like nothing happened. It's production ready.
|
|
||||||
|
|
||||||
|
### Contribution Guidelines
|
||||||
|
Contributions are welcome. Please submit pull requests for any enhancements or bug fixes.
|
||||||
|
|
||||||
|
### License
|
||||||
|
This project is licensed under the MIT License.
|
||||||
|
|||||||
+28
-6
@@ -33,6 +33,7 @@ class Bot:
|
|||||||
self.rpc = None
|
self.rpc = None
|
||||||
self.ws = None
|
self.ws = None
|
||||||
self.joined = set()
|
self.joined = set()
|
||||||
|
self.semaphore = asyncio.Semaphore(1)
|
||||||
|
|
||||||
async def on_init(self):
|
async def on_init(self):
|
||||||
logger.debug("Bot initialized.")
|
logger.debug("Bot initialized.")
|
||||||
@@ -76,8 +77,8 @@ class Bot:
|
|||||||
def has_joined(self, channel_uid):
|
def has_joined(self, channel_uid):
|
||||||
return channel_uid in self.joined
|
return channel_uid in self.joined
|
||||||
|
|
||||||
async def send_message(self, channel_uid, message):
|
async def send_message(self, channel_uid, message,final=True):
|
||||||
await self.rpc.send_message(channel_uid, message)
|
await self.rpc.send_message(channel_uid, message,final)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
async def get_channel(self, channel_uid=None, refresh=False):
|
async def get_channel(self, channel_uid=None, refresh=False):
|
||||||
@@ -89,7 +90,7 @@ class Bot:
|
|||||||
|
|
||||||
async def get_channels(self, refresh=False):
|
async def get_channels(self, refresh=False):
|
||||||
if refresh or not self._channels:
|
if refresh or not self._channels:
|
||||||
self._channels = await (await self.rpc.get_channels())()
|
self._channels = await self.rpc.get_channels()
|
||||||
return self._channels
|
return self._channels
|
||||||
|
|
||||||
async def run_once(self):
|
async def run_once(self):
|
||||||
@@ -101,8 +102,8 @@ class Bot:
|
|||||||
rpc = RPC(self.ws)
|
rpc = RPC(self.ws)
|
||||||
self.rpc = rpc
|
self.rpc = rpc
|
||||||
|
|
||||||
await (await rpc.login(self.username, self.password))()
|
await rpc.login(self.username, self.password)
|
||||||
self.user = await (await rpc.get_user(None))()
|
self.user = await rpc.get_user(None)
|
||||||
logger.debug("Logged in as: " + self.user["username"])
|
logger.debug("Logged in as: " + self.user["username"])
|
||||||
|
|
||||||
if is_initial:
|
if is_initial:
|
||||||
@@ -115,17 +116,38 @@ class Bot:
|
|||||||
await self.on_idle()
|
await self.on_idle()
|
||||||
|
|
||||||
message = None
|
message = None
|
||||||
|
data = None
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
|
async with self.semaphore:
|
||||||
data = await rpc.receive()
|
data = await rpc.receive()
|
||||||
|
if not data:
|
||||||
|
return
|
||||||
|
|
||||||
|
event = "?"
|
||||||
|
try:
|
||||||
|
event = data.event
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
|
|
||||||
try:
|
try:
|
||||||
message = data.message.strip()
|
message = data.message.strip()
|
||||||
|
event = "message"
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if event == "?":
|
||||||
|
continue
|
||||||
|
elif event == "message":
|
||||||
|
if not data.is_final:
|
||||||
continue
|
continue
|
||||||
else:
|
|
||||||
break
|
break
|
||||||
|
|
||||||
|
try:
|
||||||
|
await getattr(self, "on_" + data.event)(**data.data)
|
||||||
|
except AttributeError:
|
||||||
|
logger.debug("Not implemented event: " + event)
|
||||||
|
|
||||||
if data.username == self.user["username"]:
|
if data.username == self.user["username"]:
|
||||||
await self.on_own_message(data.channel_uid, message)
|
await self.on_own_message(data.channel_uid, message)
|
||||||
elif message.startswith("ping"):
|
elif message.startswith("ping"):
|
||||||
|
|||||||
+27
-42
@@ -7,13 +7,13 @@
|
|||||||
|
|
||||||
# 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.
|
# 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 asyncio
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
import pathlib
|
import pathlib
|
||||||
import subprocess
|
import subprocess
|
||||||
import uuid
|
import uuid
|
||||||
|
import asyncio
|
||||||
import aiohttp
|
import aiohttp
|
||||||
|
|
||||||
logger = logging.getLogger("snekbot.rpc")
|
logger = logging.getLogger("snekbot.rpc")
|
||||||
@@ -22,8 +22,6 @@ 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):
|
|
||||||
self.list = msg
|
|
||||||
self.__dict__.update(msg)
|
self.__dict__.update(msg)
|
||||||
|
|
||||||
def __iter__(self):
|
def __iter__(self):
|
||||||
@@ -53,13 +51,12 @@ class RPC:
|
|||||||
def __init__(self, ws):
|
def __init__(self, ws):
|
||||||
self.ws = ws
|
self.ws = ws
|
||||||
self.current_call_id = None
|
self.current_call_id = None
|
||||||
|
self.queue = asyncio.Queue()
|
||||||
async def echo(self, data):
|
self.semaphore = asyncio.Semaphore(1)
|
||||||
logger.debug("Schedule for retry: " + str(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):
|
||||||
|
no_response = kwargs.pop("_no_response", False)
|
||||||
self.current_call_id = str(uuid.uuid4())
|
self.current_call_id = str(uuid.uuid4())
|
||||||
payload = {
|
payload = {
|
||||||
"method": name,
|
"method": name,
|
||||||
@@ -69,46 +66,36 @@ class RPC:
|
|||||||
}
|
}
|
||||||
await self.ws.send_json(payload)
|
await self.ws.send_json(payload)
|
||||||
|
|
||||||
async def returner():
|
async def poller():
|
||||||
|
|
||||||
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 data.get("callId") == self.current_call_id:
|
||||||
await self.echo(data)
|
self.current_call_id = None
|
||||||
continue
|
|
||||||
return self.Response(data)
|
return self.Response(data)
|
||||||
|
await self.queue.put(data)
|
||||||
|
|
||||||
return returner
|
if no_response:
|
||||||
|
return True
|
||||||
|
async with self.semaphore:
|
||||||
|
return await poller()
|
||||||
|
|
||||||
return method
|
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 as ex:
|
|
||||||
logger.error(ex)
|
|
||||||
|
|
||||||
return response
|
|
||||||
|
|
||||||
async def receive(self):
|
async def receive(self):
|
||||||
|
popped = []
|
||||||
|
while not self.queue.empty():
|
||||||
|
msg = await self.queue.get()
|
||||||
|
if self.current_call_id == msg.get("callId"):
|
||||||
|
self.current_call_id = None
|
||||||
|
return self.Response(msg)
|
||||||
|
popped.append(msg)
|
||||||
|
for m in popped:
|
||||||
|
await self.queue.put(m)
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
|
async with self.semaphore:
|
||||||
try:
|
try:
|
||||||
msg = await self.ws.receive()
|
msg = await self.ws.receive()
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
@@ -121,12 +108,10 @@ class RPC:
|
|||||||
logger.exception("WebSocket error.")
|
logger.exception("WebSocket error.")
|
||||||
break
|
break
|
||||||
elif msg.type == aiohttp.WSMsgType.TEXT:
|
elif msg.type == aiohttp.WSMsgType.TEXT:
|
||||||
|
|
||||||
if (
|
if (
|
||||||
self.current_call_id
|
msg.json().get("callId") != self.current_call_id
|
||||||
and not msg.json().get("callId") != self.current_call_id
|
|
||||||
):
|
):
|
||||||
await self.echo(msg.json())
|
await self.queue.put(msg.json())
|
||||||
continue
|
continue
|
||||||
try:
|
try:
|
||||||
response = self.Response(msg.json())
|
response = self.Response(msg.json())
|
||||||
|
|||||||
Reference in New Issue
Block a user