feat!: discord replaced by matrix
This commit is contained in:
		
							parent
							
								
									544796e0ca
								
							
						
					
					
						commit
						2e5196c6b8
					
				
							
								
								
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@ -4,4 +4,4 @@ venv/
 | 
				
			|||||||
__pycache__/
 | 
					__pycache__/
 | 
				
			||||||
*.py[cod]
 | 
					*.py[cod]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
config.py
 | 
					config.json
 | 
				
			||||||
 | 
				
			|||||||
@ -6,6 +6,6 @@ COPY requirements.txt /app
 | 
				
			|||||||
RUN pip install -r requirements.txt
 | 
					RUN pip install -r requirements.txt
 | 
				
			||||||
 | 
					
 | 
				
			||||||
COPY stockbot-buyvm.py /app
 | 
					COPY stockbot-buyvm.py /app
 | 
				
			||||||
COPY config.py /app
 | 
					COPY config.json /app
 | 
				
			||||||
 | 
					
 | 
				
			||||||
CMD ["python", "stockbot-buyvm.py"]
 | 
					CMD ["python", "stockbot-buyvm.py"]
 | 
				
			||||||
							
								
								
									
										14
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										14
									
								
								README.md
									
									
									
									
									
								
							@ -2,9 +2,17 @@
 | 
				
			|||||||
Send alerts when [BuyVM](https://buyvm.net) has KVM slices in stock.
 | 
					Send alerts when [BuyVM](https://buyvm.net) has KVM slices in stock.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Usage
 | 
					## Usage
 | 
				
			||||||
1. Create a Discord Webhook and add it to `config.py`:
 | 
					1. Create a JSON configuration file in `config.json`:
 | 
				
			||||||
```python
 | 
					```json
 | 
				
			||||||
DISCORD_WEBHOOK = '<discord webhook url>'
 | 
					{
 | 
				
			||||||
 | 
					  "memory": [512, 1, 2, 4],
 | 
				
			||||||
 | 
					  "matrix": {
 | 
				
			||||||
 | 
					    "homeserver": "https://matrix.juggalol.com",
 | 
				
			||||||
 | 
					    "username": "",
 | 
				
			||||||
 | 
					    "password": "",
 | 
				
			||||||
 | 
					    "room_id": ""
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
2. Build Docker container:
 | 
					2. Build Docker container:
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										89
									
								
								matrix.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										89
									
								
								matrix.py
									
									
									
									
									
										Normal 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
 | 
				
			||||||
@ -1,3 +1,5 @@
 | 
				
			|||||||
beautifulsoup4
 | 
					beautifulsoup4
 | 
				
			||||||
requests
 | 
					requests
 | 
				
			||||||
loguru
 | 
					loguru
 | 
				
			||||||
 | 
					matrix-nio
 | 
				
			||||||
 | 
					markdown
 | 
				
			||||||
 | 
				
			|||||||
@ -1,9 +1,10 @@
 | 
				
			|||||||
"""buyvm stock checker"""
 | 
					"""buyvm stock checker"""
 | 
				
			||||||
 | 
					import json
 | 
				
			||||||
 | 
					import asyncio
 | 
				
			||||||
import requests
 | 
					import requests
 | 
				
			||||||
from bs4 import BeautifulSoup
 | 
					from bs4 import BeautifulSoup
 | 
				
			||||||
from loguru import logger
 | 
					from loguru import logger
 | 
				
			||||||
 | 
					from matrix import MatrixBot
 | 
				
			||||||
from config import DISCORD_WEBHOOK
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
BASE_URL = 'https://my.frantech.ca/'
 | 
					BASE_URL = 'https://my.frantech.ca/'
 | 
				
			||||||
URLS = [
 | 
					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):
 | 
					def get_url(url):
 | 
				
			||||||
    try:
 | 
					    try:
 | 
				
			||||||
        response = requests.get(url)
 | 
					        response = requests.get(url)
 | 
				
			||||||
@ -58,8 +52,17 @@ def get_packages(html):
 | 
				
			|||||||
    return packages
 | 
					    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')
 | 
					    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:
 | 
					    for url in URLS:
 | 
				
			||||||
        html = get_url(url)
 | 
					        html = get_url(url)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -68,23 +71,31 @@ def main():
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        packages = get_packages(html)
 | 
					        packages = get_packages(html)
 | 
				
			||||||
        for package in packages:
 | 
					        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")
 | 
					                logger.info(f"{package['name']}: {package['qty']} in stock")
 | 
				
			||||||
                send_notification({
 | 
					                await bot.send_message(f"🚨 {package['name']}: {package['qty']} in stock 🚨\n{package['url']}")
 | 
				
			||||||
                    "username": "stockbot-buyvm",
 | 
					
 | 
				
			||||||
                    "embeds": [
 | 
					    await bot.close()
 | 
				
			||||||
                        {
 | 
					
 | 
				
			||||||
                            "author": {
 | 
					
 | 
				
			||||||
                                "name": "BuyVM",
 | 
					def main_with_shutdown():
 | 
				
			||||||
                            },
 | 
					    loop = asyncio.get_event_loop()
 | 
				
			||||||
                            "title": package['name'],
 | 
					    main_task = loop.create_task(main())
 | 
				
			||||||
                            "url": package['url'],
 | 
					
 | 
				
			||||||
                            "description": f"{package['qty']} in stock now!"
 | 
					    try:
 | 
				
			||||||
                        }
 | 
					        loop.run_until_complete(main_task)
 | 
				
			||||||
                    ],
 | 
					    except asyncio.CancelledError:
 | 
				
			||||||
                    "content": "STOCK ALERT"
 | 
					        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__':
 | 
					if __name__ == '__main__':
 | 
				
			||||||
    main()
 | 
					    main_with_shutdown()
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user