import json import sys import re import logging import discord import asyncio from datetime import datetime, timezone from config import Config from logger import logger from tinydb import Query import matplotlib.pyplot as plt import pandas as pd import marketMonitor from db import MarketDB, PlayerDB, _hist, getDF, NotificationDB, getMonitor TOKEN = Config["Tokens"]["BoobyLegendsEcon"]["Token"] def getPlayerCard(name): today=datetime.utcnow().strftime('%Y-%m-%d') thisCard=PlayerDB.search(Query().fragment({'date':today,'name':name}))[0] return thisCard def histGrid(name,columns=None): logger.debug(f"histGrid(name={name},columns={columns})") MAX_RECORD_COUNT=25 df = getDF(name) footer="" dfSize=len(df) if dfSize > MAX_RECORD_COUNT: df.drop(df.head(dfSize-MAX_RECORD_COUNT).index,inplace=True) footer=f"\n{dfSize-MAX_RECORD_COUNT} more..." if columns is None: cols = ['date','cost','cost-min','cost-max'] else: cols = columns return "```" + df[cols].to_string(index=False, col_space=6, na_rep="-") + footer + "```" def todayGrid(level=None,tier=None,columns=None): logger.debug(f"todayGrid(level={level},tier={tier},columns={columns})") MAX_RECORD_COUNT=21 df = getDF(aDate=datetime.utcnow().strftime('%Y-%m-%d')) if level is not None: df=df[df['level']==level.lower()] if tier is not None: df=df[df['tier']==tier.upper()] footer="" dfSize=len(df) if dfSize > MAX_RECORD_COUNT: df.drop(df.tail(dfSize-MAX_RECORD_COUNT).index,inplace=True) footer=f"\n{dfSize-MAX_RECORD_COUNT} more..." if columns is None: cols = ['name','cost','cost-min','cost-max'] else: cols = columns return "```" + df[cols].to_string(index=False, col_space=6, na_rep="-") + footer + "```" def save_notification(card, threshold, author): author_id = str(author.id) NotificationDB.insert({'card': card, 'threshold': threshold, 'author': author_id}) def delete_notification(card, threshold, author): query = (Query()['card'] == card) & (Query()['threshold'] == threshold) & (Query()['author'] == str(author.id)) result = NotificationDB.remove(query) return len(result) > 0 async def periodic_check(client): while True: try: logger.debug("Starting periodic check of notifications.") notifications = NotificationDB.all() if len(NotificationDB) > 0: marketMonitor.main() logger.debug(f"Found {len(notifications)} notifications to check.") for notification in notifications: card = notification['card'] threshold = notification['threshold'] author = await client.fetch_user(notification['author']) logger.debug(f"Checking card: {card} for threshold: {threshold} by {author}") card_cost = getMonitor(card) if card_cost and int(card_cost) < threshold: retstr = f"{author.mention} The cost of {card} is below {threshold}!" logger.debug(retstr) await author.send(retstr) delete_notification(card, threshold, author) await asyncio.sleep(240) # Check every 4 minutes except Exception as e: logger.error(f"An error occurred in periodic_check: {e}") def load_notifications(client): notifications = NotificationDB.all() logger.debug(f"Loading {len(notifications)} notifications.") for notification in notifications: card = notification['card'] threshold = notification['threshold'] author = notification['author'] logger.debug(f"Loaded notification for card: {card} with threshold: {threshold} for author: {author}.") async def notify_below_threshold(card, threshold, author, client): try: logger.info(f"Notification requested for: {card} if cost is below {threshold}") marketMonitor.main() card_cost = getMonitor(card) print(f"{card} costs {card_cost}") if card_cost is not None: card_cost = int(card_cost) logger.debug(f"Card cost for {card}: {card_cost}") if card_cost < threshold: retstr = f"{author.mention} The cost of {card} is already below {threshold}!" return retstr # Return immediately when condition is met else: retstr = f"I'll let you know if the cost of {card} drops below {threshold}! (remove notify request with **!delnotify {card} {threshold}**)" save_notification(card, threshold, author) return retstr # Return initial message if condition not met else: retstr = f"Could not find data for {card}." logger.debug(retstr) except IndexError: retstr = f"Could not find data for {card}." logger.debug(retstr) except Exception as e: logger.error(f"An error occurred: {e}") def postPlot(card=None, columns=['cost', 'cost-min', 'cost-max']): df = getDF(card).plot(x='date', y=columns) plt.title(str(card)) plt.xticks(rotation=45) plt.xlabel("Date") plt.ylabel("Cost") plt.savefig(f"plots/{card}.png") with open(f"plots/{card}.png", 'rb') as f: picture = discord.File(f) return picture def getData(): d=MarketDB.all() df=pd.DataFrame(d) df.sort_values(by=['card-num','date'], ascending=[True,True], inplace=True) df.to_csv('plots/BoobyLegendsMarket.csv',index=False) with open(f"plots/BoobyLegendsMarket.csv", 'rb') as f: data = discord.File(f) return data async def parse(input, author): parts = input.strip().split() logger.debug(input) logger.debug(parts) AuthorName = str(author) if len(parts) == 0 or parts[0][0] not in ['!', '/', '\\']: logger.debug("Not a command") return None try: command = parts[0][1:].upper() logger.debug("Command: " + command) # Process different commands if command == "HISTORY": name = ' '.join(parts[1:]) logger.info("History for: " + name) history = histGrid(name.strip()) retstr = "{Author} requested history for **{name}**:\n{history}".format( Author=AuthorName, name=name, history=history ) logger.debug(retstr) elif command == "TODAY": level = parts[1].lower() if len(parts) > 1 else None tier = parts[2].upper() if len(parts) > 2 else None logger.info(f"Today {level} {tier}") history = todayGrid(level, tier) retstr = "{Author} requested today's Costs for Today:\n{history}".format( Author=AuthorName, history=history ) logger.debug(retstr) elif command == "PLOT": name = ' '.join(parts[1:]) logger.info("Plot for: " + name) retstr = postPlot(card=name.strip()) logger.debug(f"Plot created for {name}") elif command == "GETDATA": logger.info("Getting Data Dump") retstr = getData() logger.debug("Data Dump requested") elif command == "LOGLEVEL": if parts[1].upper() == "DEBUG": logger.setLevel(logging.DEBUG) elif parts[1].upper() == "INFO": logger.setLevel(logging.INFO) retstr = f"LogLevel set to {parts[1].upper()}" logger.debug(retstr) elif command == "HELP": retstr = ''' My keywords are **!**, **/**, or **\\** Show a Card's Historical cost with: **!History Eva Elfie** Show the current day's average prices with:**!Today** You can also filter this by Level or Level and tier: **!Today rare** or **!Today common S** You can get a graph plotted with the historical values for a star by: **!Plot Natasha Nice** Get a Data Dump of the current data set with: **!GetData** Get notified when a card's cost drops below a certain threshold with: **!Notify Eva Elfie 100** ''' logger.debug("Help command requested") elif command == "NOTIFY": try: card = ' '.join(parts[1:-1]) threshold = int(parts[-1]) if isinstance(author, str): author = await client.fetch_user(author) response = await notify_below_threshold(card, threshold, author, client) return response except ValueError: retstr = "Please provide a valid threshold number." logger.error("Invalid threshold number provided") return retstr except IndexError: retstr = "Please provide both a card name and a threshold number." logger.error("Card name and threshold not provided") return retstr elif command == "DELNOTIFY": try: card = ' '.join(parts[1:-1]) threshold = int(parts[-1]) if isinstance(author, str): author = await client.fetch_user(author) if delete_notification(card, threshold, author): retstr = f"Notification for {card} at threshold {threshold} has been removed." else: retstr = f"No notification found for {card} at threshold {threshold} by {author.mention}." logger.info(retstr) return retstr except ValueError: retstr = "Please provide a valid threshold number." logger.error("Invalid threshold number provided") return retstr except IndexError: retstr = "Please provide both a card name and a threshold number." logger.error("Card name and threshold not provided") return retstr else: retstr = f"{AuthorName}, your command '{command}' was not understood." logger.debug(f"Unknown command: {command}") except Exception as e: logger.error(f"An error occurred: {e}") retstr = None return retstr ######################################################################### # Main Program ######################################################################### intents = discord.Intents.default() intents.message_content = True client = discord.Client(intents=intents) @client.event async def on_message(message): # Check if the message is from the specific channel if str(message.channel.id) != "1187032500657201162": return if message.author == client.user: return output = await parse(message.content, message.author) if output is not None: if isinstance(output, str): await message.channel.send(output) else: await message.channel.send(file=output) @client.event async def on_ready(): print('Logged in as') print(client.user.name) print(client.user.id) print('------') load_notifications(client) # Load and log notifications on startup client.loop.create_task(periodic_check(client)) # Start the periodic check task client.run(TOKEN)