feat!: discord replaced by matrix

This commit is contained in:
agatha 2024-09-15 18:31:30 -04:00
parent 544796e0ca
commit 2e5196c6b8
6 changed files with 141 additions and 31 deletions

2
.gitignore vendored
View File

@ -4,4 +4,4 @@ venv/
__pycache__/
*.py[cod]
config.py
config.json

View File

@ -6,6 +6,6 @@ COPY requirements.txt /app
RUN pip install -r requirements.txt
COPY stockbot-buyvm.py /app
COPY config.py /app
COPY config.json /app
CMD ["python", "stockbot-buyvm.py"]

View File

@ -2,9 +2,17 @@
Send alerts when [BuyVM](https://buyvm.net) has KVM slices in stock.
## Usage
1. Create a Discord Webhook and add it to `config.py`:
```python
DISCORD_WEBHOOK = '<discord webhook url>'
1. Create a JSON configuration file in `config.json`:
```json
{
"memory": [512, 1, 2, 4],
"matrix": {
"homeserver": "https://matrix.juggalol.com",
"username": "",
"password": "",
"room_id": ""
}
}
```
2. Build Docker container:

89
matrix.py Normal file
View File

@ -0,0 +1,89 @@
import markdown
from loguru import logger
from nio import AsyncClient, LoginResponse
class MatrixBot:
def __init__(self, config: dict):
self.config = config
self.client = AsyncClient(
homeserver=self.config['homeserver'],
user=self.config['username']
)
self.logged_in = False
async def ensure_logged_in(self):
if not self.logged_in:
try:
response = await self.client.login(password=self.config['password'])
if isinstance(response, LoginResponse):
self.logged_in = True
logger.info(f"Logged in as {self.config['username']}")
else:
logger.error(f"Failed to login as {self.config['username']}: {response}")
logger.error("Closing nio session")
await self.client.close()
except Exception as e:
logger.error(f"Exception during login: {e}")
await self.client.close()
raise
async def send_message(self, message: str):
await self.ensure_logged_in()
if not self.logged_in:
logger.error("Unable to send message, login failed")
return
try:
await self.client.room_send(
room_id=self.config['room_id'],
message_type="m.room.message",
content={
"msgtype": "m.text",
"body": message
}
)
logger.info("Message sent")
except Exception as e:
logger.error(f"Exception during sending message: {e}")
raise
async def send_markdown(self, message: str):
await self.ensure_logged_in()
if not self.logged_in:
logger.error("Unable to send message, login failed")
return
try:
# Convert message to markdown
html = markdown.markdown(message)
# Send markdown formatted message
await self.client.room_send(
room_id=self.config['room_id'],
message_type="m.room.message",
content={
"msgtype": "m.text",
"body": message,
"format": "org.matrix.custom.html",
"formatted_body": html
}
)
logger.info("Markdown message sent")
except Exception as e:
logger.error(f"Exception during sending markdown message: {e}")
raise
async def close(self):
if self.logged_in:
try:
await self.client.logout()
self.logged_in = False
logger.info(f"Logged out from {self.config['homeserver']}")
except Exception as e:
logger.error(f"Exception during logout: {e}")
finally:
await self.client.close() # Ensure the client is closed

View File

@ -1,3 +1,5 @@
beautifulsoup4
requests
loguru
matrix-nio
markdown

View File

@ -1,9 +1,10 @@
"""buyvm stock checker"""
import json
import asyncio
import requests
from bs4 import BeautifulSoup
from loguru import logger
from config import DISCORD_WEBHOOK
from matrix import MatrixBot
BASE_URL = 'https://my.frantech.ca/'
URLS = [
@ -14,13 +15,6 @@ URLS = [
]
def send_notification(payload):
try:
requests.post(DISCORD_WEBHOOK, json=payload)
except requests.RequestException as e:
logger.error(f'error sending notification: {str(e)}')
def get_url(url):
try:
response = requests.get(url)
@ -58,8 +52,17 @@ def get_packages(html):
return packages
def main():
def load_config(filename):
with open(filename) as f:
return json.load(f)
async def main():
logger.info('checking buyvm stocks')
config = load_config('config.json')
bot = MatrixBot(config['matrix'])
memory_filter = config.get('memory', [512, 1, 2, 4]) # Defaults to price <= $15.00
for url in URLS:
html = get_url(url)
@ -68,23 +71,31 @@ def main():
packages = get_packages(html)
for package in packages:
if package['qty'] > 0:
qty = package['qty']
memory = int(package['name'].split()[-1][:-2])
if qty > 0 and (memory in memory_filter):
logger.info(f"{package['name']}: {package['qty']} in stock")
send_notification({
"username": "stockbot-buyvm",
"embeds": [
{
"author": {
"name": "BuyVM",
},
"title": package['name'],
"url": package['url'],
"description": f"{package['qty']} in stock now!"
}
],
"content": "STOCK ALERT"
})
await bot.send_message(f"🚨 {package['name']}: {package['qty']} in stock 🚨\n{package['url']}")
await bot.close()
def main_with_shutdown():
loop = asyncio.get_event_loop()
main_task = loop.create_task(main())
try:
loop.run_until_complete(main_task)
except asyncio.CancelledError:
logger.info("Main task has been cancelled.")
finally:
pending_tasks = [t for t in asyncio.all_tasks(loop) if not t.done()]
if pending_tasks:
loop.run_until_complete(asyncio.gather(*pending_tasks, return_exceptions=True))
loop.run_until_complete(loop.shutdown_asyncgens())
loop.close()
if __name__ == '__main__':
main()
main_with_shutdown()