123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305 |
- 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)
|