Add the rest
This commit is contained in:
51
src/py/bot_aux.py
Normal file
51
src/py/bot_aux.py
Normal file
@@ -0,0 +1,51 @@
|
||||
"""
|
||||
This module is the automated poster to discord.
|
||||
This module will post to the discord whenever the script is run, detailing the callouts for the current raid for the next seven days.
|
||||
This automation will be run on a daily basis, through a cron job + docker.
|
||||
|
||||
@author: Gabriella 'contrastellar' Agathon
|
||||
"""
|
||||
import argparse
|
||||
import os
|
||||
import discord
|
||||
import helper.db_helper
|
||||
|
||||
|
||||
DATABASE_CONN = None
|
||||
|
||||
intents = discord.Intents.default()
|
||||
intents.message_content = True
|
||||
intents.guild_messages = True
|
||||
intents.presences = False
|
||||
|
||||
client = discord.Client(intents=intents)
|
||||
|
||||
NUMBER_OF_DAYS = 7
|
||||
|
||||
parser: argparse.ArgumentParser = argparse.ArgumentParser(prog='callouts aux',
|
||||
description='The poster for the callouts bot functionality')
|
||||
|
||||
parser.add_argument('database')
|
||||
parser.add_argument('token')
|
||||
parser.add_argument('guild_id', type=int)
|
||||
parser.add_argument('channel_id', type=int)
|
||||
|
||||
args: argparse.Namespace = parser.parse_args()
|
||||
|
||||
@client.event
|
||||
async def on_ready():
|
||||
|
||||
print(f'{client.user} has connected.')
|
||||
print(args.guild_id)
|
||||
guild: discord.Guild = client.get_guild(args.guild_id)
|
||||
channel: discord.TextChannel = guild.get_channel(args.channel_id)
|
||||
callouts = DATABASE_CONN.query_callouts(NUMBER_OF_DAYS)
|
||||
formatted_callouts = DATABASE_CONN.formatted_list_of_callouts(callouts)
|
||||
output = f'Callouts for the next {NUMBER_OF_DAYS} days:\n' + formatted_callouts
|
||||
await channel.send(output)
|
||||
await client.close() # Another way to exit, a little bit cleaner than exit(0)
|
||||
return
|
||||
|
||||
DATABASE_CONN = helper.db_helper.DBHelper(args.database)
|
||||
TOKEN = open(args.token, encoding='utf-8').read()
|
||||
client.run(TOKEN)
|
||||
295
src/py/bot_core.py
Normal file
295
src/py/bot_core.py
Normal file
@@ -0,0 +1,295 @@
|
||||
# pylint: disable=consider-using-with, no-member
|
||||
"""
|
||||
This module is the listener to discord.
|
||||
This module will listen to the discord server for two things:
|
||||
1. the /schedule command -- which will report the current callouts for the next X days, where X is either supplied or is the default of 7
|
||||
2. the /callout command -- which will allow users to add a new scheduled callout
|
||||
3. a /ping command, to test the bot's current status!
|
||||
4. a /registercharacter command, to allow users to register their character's name independently of their server nickname
|
||||
5. a /checkcharname command, to allow users to verify their character's name
|
||||
6. a /remove_callout command, to allow users to remove callouts that are no longer necessary
|
||||
7. a /help command, to direct users to the github for this bot!
|
||||
|
||||
@author: Gabriella 'contrastellar' Agathon
|
||||
"""
|
||||
|
||||
import datetime
|
||||
import argparse
|
||||
import os
|
||||
import discord
|
||||
import psycopg2
|
||||
from discord.ext import commands
|
||||
import helper.db_helper
|
||||
|
||||
# module constants
|
||||
DAYS_FOR_CALLOUTS = 7
|
||||
CONTRASTELLAR = 181187505448681472
|
||||
|
||||
DATABASE_CONN: helper.db_helper.DBHelper = None
|
||||
|
||||
# psycopg2 'imports'
|
||||
UNIQUEVIOLATION: psycopg2.Error = psycopg2.errors.UniqueViolation
|
||||
INVALIDDATETIMEFORMAT: psycopg2.Error = psycopg2.errors.InvalidDatetimeFormat
|
||||
FOREIGNKEYVIOLATION: psycopg2.Error = psycopg2.errors.ForeignKeyViolation
|
||||
|
||||
# discord variables
|
||||
intents = discord.Intents.default()
|
||||
intents.message_content = True
|
||||
intents.guild_messages = True
|
||||
intents.presences = False
|
||||
|
||||
# client declaration
|
||||
client = commands.Bot(command_prefix='!', intents=intents)
|
||||
|
||||
# parser declaration
|
||||
parser: argparse.ArgumentParser = argparse.ArgumentParser(prog='callouts core',
|
||||
description='The listener for the callouts bot functionality')
|
||||
parser.add_argument('database')
|
||||
parser.add_argument('token')
|
||||
parser.add_argument('guild_id', type=int)
|
||||
parser.add_argument('channel_id', type=int)
|
||||
|
||||
|
||||
# utility methods
|
||||
def cleanup_invalidate() -> None:
|
||||
DATABASE_CONN.is_procedure_queued = False
|
||||
|
||||
|
||||
def delete_invalidate() -> None:
|
||||
DATABASE_CONN.is_unregister_queued = False
|
||||
|
||||
|
||||
# discord commands
|
||||
@client.event
|
||||
async def on_ready() -> None:
|
||||
await client.tree.sync()
|
||||
print(f'{client.user} has connected to Discord!')
|
||||
print(args.guild_id)
|
||||
if 'RAID_CALLOUTS_DEV' in os.environ:
|
||||
return
|
||||
|
||||
guild: discord.Guild = client.get_guild(args.guild_id)
|
||||
channel: discord.TextChannel = guild.get_channel(args.channel_id)
|
||||
output = f'The bot is now running!\nPlease message <@{CONTRASTELLAR}> with any errors!'
|
||||
await channel.send(output)
|
||||
return
|
||||
|
||||
@client.event
|
||||
async def on_error(interaction: discord.Interaction) -> None:
|
||||
delete_invalidate()
|
||||
cleanup_invalidate()
|
||||
output = "Something awful has happened. In all honesty you should never see this message. Reporting to <@{CONTRASTELLAR}>."
|
||||
await interaction.response.send_message(output)
|
||||
return
|
||||
|
||||
|
||||
# === slash commands are below here
|
||||
|
||||
@client.tree.command()
|
||||
async def registercharacter(interaction: discord.Interaction, character_name: str) -> None:
|
||||
delete_invalidate()
|
||||
cleanup_invalidate()
|
||||
user_id = interaction.user.id
|
||||
user_nick = interaction.user.display_name
|
||||
|
||||
try:
|
||||
DATABASE_CONN.register_char_name(user_id, character_name)
|
||||
except psycopg2.Error as e:
|
||||
char_name = DATABASE_CONN.return_char_name(user_id)
|
||||
await interaction.response.send_message(f'User {char_name} -- you have already registered a character!\n{e}')
|
||||
else:
|
||||
await interaction.response.send_message(f'{user_nick} -- you have registered your discord account with {character_name}!')
|
||||
return
|
||||
|
||||
|
||||
@client.tree.command()
|
||||
async def check_char_name(interaction: discord.Interaction) -> None:
|
||||
delete_invalidate()
|
||||
cleanup_invalidate()
|
||||
charname: str = DATABASE_CONN.return_char_name(interaction.user.id)
|
||||
|
||||
if charname == "":
|
||||
await interaction.response.send_message("You have not registered! Please do with `/registercharacter`")
|
||||
return
|
||||
if interaction.user.id == 151162055142014976:
|
||||
await interaction.response.send_message("You are: " + charname + "... in case you forgot.")
|
||||
return
|
||||
|
||||
await interaction.response.send_message("You are: " + charname)
|
||||
return
|
||||
|
||||
|
||||
@client.tree.command()
|
||||
async def remove_registration(interaction: discord.Interaction) -> None:
|
||||
delete_invalidate()
|
||||
cleanup_invalidate()
|
||||
await interaction.response.send_message("To remove your registration with the boss, please run the `/confirm_unregister` command\nPlease be aware that this will also remove all of your callouts from the bot! ***This is in an irreversable action!***")
|
||||
DATABASE_CONN.is_unregister_queued = True
|
||||
return
|
||||
|
||||
|
||||
@client.tree.command()
|
||||
async def validate_unregister(interaction: discord.Interaction) -> None:
|
||||
cleanup_invalidate()
|
||||
|
||||
user_id = interaction.user.id
|
||||
user_nick = interaction.user.nick
|
||||
|
||||
await interaction.response.defer(thinking=True)
|
||||
print(f"Removing {user_id} from the database!")
|
||||
|
||||
DATABASE_CONN.remove_registration(user_id, DATABASE_CONN.is_unregister_queued)
|
||||
|
||||
await interaction.followup.send(f"{user_nick}, you have been unregistered!")
|
||||
delete_invalidate()
|
||||
|
||||
|
||||
@client.tree.command()
|
||||
async def invalidate_unregister(interaction: discord.Interaction) -> None:
|
||||
cleanup_invalidate()
|
||||
delete_invalidate()
|
||||
print("User deletion has been invalidated! Aborting process!")
|
||||
|
||||
await interaction.response.send_message("Unregister has been invalidated!")
|
||||
|
||||
return
|
||||
|
||||
|
||||
@client.tree.command()
|
||||
async def ping(interaction: discord.Interaction) -> None:
|
||||
delete_invalidate()
|
||||
cleanup_invalidate()
|
||||
user_id = interaction.user.id
|
||||
await interaction.response.send_message(f'Pong! {user_id} -- the bot is active, please message contrastellar with issues!')
|
||||
return
|
||||
|
||||
|
||||
@client.tree.command()
|
||||
async def cleanup(interaction: discord.Interaction) -> None:
|
||||
delete_invalidate()
|
||||
cleanup_invalidate()
|
||||
number_to_be_affected: int = DATABASE_CONN.number_affected_in_cleanup()
|
||||
await interaction.response.send_message(f"Is the bot being weird or slow? You can try the `/validate_cleanup` command to clear out old database entries!\nBe warned that this is an admin-level command, and may have unintended side effects!\n{number_to_be_affected} rows will be affected by the `/validate_cleanup` command!\nThese entries are all in the past.")
|
||||
DATABASE_CONN.is_procedure_queued = True
|
||||
print("Bot has been primed for cleanup!")
|
||||
return
|
||||
|
||||
|
||||
@client.tree.command()
|
||||
async def validate_cleanup(interaction: discord.Interaction) -> None:
|
||||
delete_invalidate()
|
||||
user_nickname = interaction.user.nick
|
||||
await interaction.response.defer(thinking=True)
|
||||
print(f"{user_nickname} has called validate_cleanup!\n\nCalling now.")
|
||||
|
||||
number_rows_affected: int
|
||||
|
||||
try:
|
||||
number_rows_affected = DATABASE_CONN.call_cleanup(DATABASE_CONN.is_procedure_queued)
|
||||
except psycopg2.Error as e:
|
||||
print(e)
|
||||
await interaction.followup.send(f"Something happened! This message is to inform <@{CONTRASTELLAR}> of this error!\n`{e}`")
|
||||
return
|
||||
|
||||
print("cleanup should be complete. Setting queue variable to False")
|
||||
DATABASE_CONN.is_procedure_queued = False
|
||||
await interaction.followup.send(f"Database has been cleaned!\n\n{number_rows_affected} rows have been purged!")
|
||||
|
||||
return
|
||||
|
||||
@client.tree.command()
|
||||
async def invalidate_cleanup(interaction: discord.Interaction) -> None:
|
||||
delete_invalidate()
|
||||
cleanup_invalidate()
|
||||
|
||||
await interaction.response.defer(thinking=True)
|
||||
|
||||
print(f"{interaction.user.id} has called the invalidate command!")
|
||||
print("Cleanup has been invalidated!")
|
||||
await interaction.followup.send("The queued action has been cancelled!")
|
||||
|
||||
return
|
||||
|
||||
|
||||
@client.tree.command()
|
||||
async def callout(interaction: discord.Interaction, day: int, month: int, year: int, reason: str = '', fill: str = '') -> None:
|
||||
delete_invalidate()
|
||||
cleanup_invalidate()
|
||||
user_id = interaction.user.id
|
||||
user_nick = interaction.user.display_name
|
||||
|
||||
user_char_name = DATABASE_CONN.return_char_name(user_id)
|
||||
|
||||
today: datetime.date = datetime.date.today()
|
||||
callout_date: datetime.date = datetime.date(year=year, month=month, day=day)
|
||||
|
||||
if today > callout_date:
|
||||
await interaction.response.send_message(f'{user_char_name}, date in the past given. Please give a date for today or in the future!')
|
||||
return
|
||||
|
||||
if len(reason) > 512:
|
||||
await interaction.response.send_message(f'{user_char_name}, your reason was too long. Keep it to 512 characters or less.')
|
||||
return
|
||||
|
||||
try:
|
||||
DATABASE_CONN.add_callout(user_id=user_id, callout=callout_date, reason=reason, nickname=user_nick, char_name=user_char_name, potential_fill=fill)
|
||||
except UNIQUEVIOLATION:
|
||||
await interaction.response.send_message(f'{user_char_name} -- you have already added a callout for {callout_date} with reason: {reason}')
|
||||
except INVALIDDATETIMEFORMAT:
|
||||
await interaction.response.send_message(f'{user_char_name} -- please format the date as the following format: MM/DD/YYYY')
|
||||
except FOREIGNKEYVIOLATION:
|
||||
await interaction.response.send_message(f'{user_nick} -- please register with the bot using the following command!\n`/registercharacter`\n Please use your in-game name!')
|
||||
except helper.db_helper.DateTimeError:
|
||||
await interaction.response.send_message(f'{user_nick}, you\'re trying to submit a callout for a time in the past! Please verify that this is what you want to do!')
|
||||
except psycopg2.Error as e:
|
||||
await interaction.response.send_message(f'{user_nick} -- an error has occured!\nNotifying <@{CONTRASTELLAR}> of this error. Error is as follows --\n{e}')
|
||||
else:
|
||||
await interaction.response.send_message(f'{user_char_name} -- you added a callout for {callout_date} with reason: {reason}')
|
||||
await interaction.followup.send(f'{DATABASE_CONN.format_list_of_callouts(DATABASE_CONN.query_callouts(7))}')
|
||||
|
||||
|
||||
@client.tree.command()
|
||||
async def remove_callout(interaction: discord.Interaction, day: int, month: int, year: int) -> None:
|
||||
delete_invalidate()
|
||||
cleanup_invalidate()
|
||||
user_id = interaction.user.id
|
||||
user_char_name = DATABASE_CONN.return_char_name(user_id)
|
||||
callout_date: datetime.date = datetime.date(year=year, month=month, day=day)
|
||||
try:
|
||||
DATABASE_CONN.remove_callout(user_id=user_id, callout=callout_date)
|
||||
except psycopg2.Error:
|
||||
await interaction.response.send_message(f'{user_char_name} -- you have not added a callout for {callout_date}')
|
||||
else:
|
||||
await interaction.response.send_message(f'{user_char_name} removed a callout for {callout_date}')
|
||||
|
||||
|
||||
@client.tree.command()
|
||||
async def schedule(interaction: discord.Interaction, days: int = DAYS_FOR_CALLOUTS) -> None:
|
||||
delete_invalidate()
|
||||
cleanup_invalidate()
|
||||
await interaction.response.defer(thinking=True)
|
||||
callouts: list = DATABASE_CONN.query_callouts(days=days)
|
||||
callouts: str = DATABASE_CONN.formatted_list_of_callouts(callouts)
|
||||
await interaction.followup.send(f'Callouts for the next {days} days:\n{callouts}')
|
||||
return
|
||||
|
||||
@client.tree.command()
|
||||
async def self_callouts(interaction: discord.Interaction, days: int = 365) -> None:
|
||||
delete_invalidate()
|
||||
cleanup_invalidate()
|
||||
|
||||
uid = interaction.user.id
|
||||
await interaction.response.defer(thinking=True)
|
||||
callouts: list = DATABASE_CONN.query_self_callouts(user_id=uid, days=days)
|
||||
callouts: str = DATABASE_CONN.formatted_list_of_callouts(callouts)
|
||||
await interaction.followup.send(f'Callouts for the next **{days}** for user **{DATABASE_CONN.return_char_name(uid)}**:\n{callouts}')
|
||||
|
||||
|
||||
args: argparse.Namespace = parser.parse_args()
|
||||
|
||||
# To be used for reading/writing to the database
|
||||
# #will not handle the parsing of the returns from the db
|
||||
DATABASE_CONN = helper.db_helper.DBHelper(args.database)
|
||||
|
||||
TOKEN: str = open(args.token, encoding='utf-8').read()
|
||||
client.run(TOKEN)
|
||||
59
src/py/db_helper_tests.py
Normal file
59
src/py/db_helper_tests.py
Normal file
@@ -0,0 +1,59 @@
|
||||
"""
|
||||
Database functionality tests.
|
||||
|
||||
@author: Gabriella "Contrastellar" Agathon
|
||||
"""
|
||||
|
||||
import datetime
|
||||
import helper.db_helper
|
||||
|
||||
class TestClass():
|
||||
"""
|
||||
Test class for the database functionality.
|
||||
These tests should only be assertions of type, not of return values.
|
||||
"""
|
||||
DATABASE_CONN = helper.db_helper.DBHelper("xiv-database.ini")
|
||||
|
||||
def test_add_registration(self) -> None:
|
||||
registration = self.DATABASE_CONN.register_char_name(uid=1, char_name="test")
|
||||
assert registration is None
|
||||
|
||||
|
||||
def test_query_callouts(self) -> None:
|
||||
callouts = self.DATABASE_CONN.query_callouts(7)
|
||||
assert type(callouts) is list
|
||||
|
||||
|
||||
def test_add_callout(self) -> None:
|
||||
callout = self.DATABASE_CONN.add_callout(user_id=1, callout=datetime.date.today(), reason='test', nickname='test', char_name='test', potential_fill='test')
|
||||
assert callout is None
|
||||
|
||||
def test_self_query_callouts(self) -> None:
|
||||
self_callouts = self.DATABASE_CONN.query_self_callouts(user_id=1, days=365)
|
||||
assert self_callouts is not None
|
||||
|
||||
def test_callouts(self) -> None:
|
||||
callout = self.DATABASE_CONN.query_callouts(days=7)
|
||||
assert callout is not None
|
||||
|
||||
|
||||
def test_remove_callout(self) -> None:
|
||||
remove_callout = self.DATABASE_CONN.remove_callout(user_id=1, callout=datetime.date.today())
|
||||
assert remove_callout is None
|
||||
|
||||
|
||||
def test_char_name(self) -> None:
|
||||
char_name = self.DATABASE_CONN.return_char_name(uid=1)
|
||||
assert type(char_name) is str
|
||||
assert char_name == "test"
|
||||
|
||||
|
||||
def test_remove_registration(self) -> None:
|
||||
registration = self.DATABASE_CONN.remove_registration(uid=1, isOkay=True)
|
||||
assert registration is None
|
||||
|
||||
|
||||
def test_format_list_of_callouts(self) -> None:
|
||||
callouts = self.DATABASE_CONN.query_callouts(days=7)
|
||||
formatted_callouts = self.DATABASE_CONN.format_list_of_callouts(callouts=callouts)
|
||||
assert formatted_callouts.__class__ is str
|
||||
295
src/py/helper/db_helper.py
Normal file
295
src/py/helper/db_helper.py
Normal file
@@ -0,0 +1,295 @@
|
||||
"""
|
||||
The helper core of the raid-callouts bot.
|
||||
This module(s) will contain all of the helper functions for the bot
|
||||
|
||||
@author: Gabriella 'contrastellar' Agathon
|
||||
"""
|
||||
|
||||
import psycopg2
|
||||
import psycopg2.extensions
|
||||
from configparser import ConfigParser
|
||||
import datetime
|
||||
|
||||
|
||||
def load_config(filename='database.ini', section='postgresql'):
|
||||
"""
|
||||
Args:
|
||||
filename (str, optional): filename for the ini file. Defaults to 'database.ini'.
|
||||
section (str, optional): defines the section for the ini file to read from. Defaults to 'postgresql'.
|
||||
|
||||
Raises:
|
||||
Exception: Will raise an exception if the postgresql section is not found in the ini file.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary containing the parsed values from the ini file, to be used for the database connection.
|
||||
"""
|
||||
parser = ConfigParser()
|
||||
parser.read(filename)
|
||||
|
||||
# get section, default is postgresql
|
||||
config = {}
|
||||
if parser.has_section(section):
|
||||
params = parser.items(section)
|
||||
for param in params:
|
||||
config[param[0]] = param[1]
|
||||
|
||||
else:
|
||||
raise Exception('Section {0} not found in the {1} file'.format(section, filename))
|
||||
|
||||
return config
|
||||
|
||||
def connect_config(config) -> psycopg2.extensions.connection:
|
||||
""" Connect to the PostgreSQL database server """
|
||||
try:
|
||||
# connecting to the PostgreSQL server
|
||||
with psycopg2.connect(**config) as conn:
|
||||
print('Connected to the PostgreSQL server.')
|
||||
return conn
|
||||
|
||||
except (psycopg2.DatabaseError, Exception) as error:
|
||||
print(error)
|
||||
|
||||
finally:
|
||||
if conn is None:
|
||||
raise psycopg2.DatabaseError('Failed to connect to the PostgreSQL database')
|
||||
|
||||
class DateTimeError(Exception):
|
||||
def __init__(self, *args):
|
||||
super().__init__(*args)
|
||||
|
||||
|
||||
class DBHelper():
|
||||
"""
|
||||
The helper class for the raid-callouts bot.
|
||||
This class will contain all of the helper functions for the bot
|
||||
"""
|
||||
|
||||
_config: dict
|
||||
__CONN: psycopg2.extensions.connection = None
|
||||
is_procedure_queued: bool = False
|
||||
is_unregister_queued: bool = False
|
||||
|
||||
def __init__(self, filename = 'database.ini', section = 'postgresql') -> None:
|
||||
self._config = load_config(filename=filename, section=section)
|
||||
|
||||
|
||||
def __del__(self):
|
||||
"""
|
||||
Destructor for the DBHelper class
|
||||
No need to do anything here
|
||||
"""
|
||||
# self.__CONN.close()
|
||||
pass
|
||||
|
||||
|
||||
def query_callouts(self, days: int) -> list:
|
||||
"""This function will query the database for the callouts for the next X days, where X is defined by the days parameter.
|
||||
|
||||
Args:
|
||||
days int: number of days in the future to query for callouts
|
||||
|
||||
Returns:
|
||||
list: list of users + their callouts for the next X days
|
||||
"""
|
||||
self.__CONN = connect_config(self._config)
|
||||
self.__CONN.autocommit = True
|
||||
cursor = self.__CONN.cursor()
|
||||
# Weird query, but it grabs the callouts from the last day to the next X days.
|
||||
cursor.execute(f"SELECT * FROM newcallouts WHERE date >= NOW() - INTERVAL '1 day' AND date <= NOW() + INTERVAL '{days} days' ORDER BY date ASC;")
|
||||
self.__CONN.commit()
|
||||
|
||||
return cursor.fetchall()
|
||||
|
||||
def query_self_callouts(self, user_id: int, days: int = 365):
|
||||
self.__CONN = connect_config(self._config)
|
||||
self.__CONN.autocommit = True
|
||||
cursor = self.__CONN.cursor()
|
||||
cursor.execute(f"SELECT * FROM newcallouts WHERE date >= NOW() - INTERVAL '1 day' AND date <= NOW() + INTERVAL '{days} days' AND user_id = {user_id} ORDER BY date ASC;")
|
||||
self.__CONN.commit()
|
||||
|
||||
return cursor.fetchall()
|
||||
|
||||
|
||||
def add_callout(self, user_id: int, callout: datetime.date, reason: str, nickname: str, char_name: str, potential_fill: str) -> None:
|
||||
"""Add a callout to the database
|
||||
|
||||
Args:
|
||||
user_id (int): the Discord UUID of the user adding things to the db
|
||||
callout (datetime.date): The day of the callout
|
||||
reason (str): The reason of the callout
|
||||
nickname (str): The server(guild) nickname of the user who is making the callout
|
||||
char_name (str): The character name (as supplied from registration) of the user inserting a callout
|
||||
"""
|
||||
self.__CONN = connect_config(self._config)
|
||||
self.__CONN.autocommit = True
|
||||
cursor = self.__CONN.cursor()
|
||||
cursor.execute("INSERT INTO newcallouts (user_id, date, reason, nickname, charname, fill) VALUES (%s, %s, %s, %s, %s, %s)", (user_id, callout, reason, nickname, char_name, potential_fill))
|
||||
self.__CONN.commit()
|
||||
|
||||
return
|
||||
|
||||
|
||||
def remove_callout(self, user_id: int, callout: datetime.date) -> None:
|
||||
"""Remove a callout based on user + date, which form the primary key in the db
|
||||
|
||||
Args:
|
||||
user_id (int): The Discord UUID of the user removing something from the db
|
||||
callout (datetime.datetime): The date of the callout
|
||||
"""
|
||||
self.__CONN = connect_config(self._config)
|
||||
self.__CONN.autocommit = True
|
||||
cursor = self.__CONN.cursor()
|
||||
|
||||
cursor.execute("DELETE FROM newcallouts WHERE user_id = %s AND date = %s", (user_id, callout))
|
||||
self.__CONN.commit()
|
||||
|
||||
return
|
||||
|
||||
|
||||
def formatted_list_of_callouts(self, callouts: list) -> str:
|
||||
"""Format the python list of callouts.
|
||||
|
||||
Args:
|
||||
callouts (list): The list that needs to be formatted
|
||||
|
||||
Returns:
|
||||
str: The formatted list
|
||||
"""
|
||||
length = len(callouts)
|
||||
output: str = ''
|
||||
|
||||
# Quick and dirty way to say that there were no callouts found during the query
|
||||
if length == 0:
|
||||
return 'No callouts found for the requested timeframe'
|
||||
|
||||
for entry in callouts:
|
||||
|
||||
# this is a bit wonky, but we take the known constant width of each entry (4 columns)
|
||||
# then we use python's range function to turn "item" into an interator
|
||||
# Then we do some funky logic on the entry that we're iterating over
|
||||
# in order to get the proper formatting
|
||||
for item in range(5):
|
||||
if item == 0:
|
||||
# skip discord user ID always
|
||||
continue
|
||||
|
||||
elif item == 1:
|
||||
# handles the date displaying logic
|
||||
if datetime.date.today() == entry[1]:
|
||||
output += '**TODAY** • '
|
||||
else:
|
||||
output += f'**{entry[1]}** • '
|
||||
|
||||
elif item == 2:
|
||||
# in the database, this is actually the "reason" place
|
||||
# instead of doing that, we call the last column's value
|
||||
# which is the char name
|
||||
# this was requested by Yasu
|
||||
output += "**" + entry[4] + '** • '
|
||||
|
||||
elif item == 3:
|
||||
# Finally add the reason for the user's callout
|
||||
# two line breaks as Yasu requested
|
||||
output += entry[2] + ' '
|
||||
|
||||
elif item == 4:
|
||||
if entry[5] is not None:
|
||||
output += f'• potential fill -- {entry[5]}\n--\n'
|
||||
else:
|
||||
output += '\n--\n'
|
||||
|
||||
|
||||
output += "END OF MESSAGE"
|
||||
return output
|
||||
|
||||
|
||||
def format_list_of_callouts(self, callouts: list) -> str:
|
||||
"""Format the python list of callouts.
|
||||
|
||||
Args:
|
||||
callouts (list): The list that needs to be formatted
|
||||
|
||||
Returns:
|
||||
str: The formatted list
|
||||
"""
|
||||
return self.formatted_list_of_callouts(callouts=callouts)
|
||||
|
||||
|
||||
def register_char_name(self, uid: int, char_name: str) -> None:
|
||||
""" allows users to register their character name with the bot, allowing silly nicknames to be used independent of their
|
||||
character's name
|
||||
|
||||
Arguments:
|
||||
uid -- Discord User ID of the user to be registered
|
||||
char_name -- User-supplied character name, to be inserted into the table
|
||||
"""
|
||||
self.__CONN = connect_config(self._config)
|
||||
self.__CONN.autocommit = True
|
||||
cursor = self.__CONN.cursor()
|
||||
|
||||
cursor.execute("INSERT INTO charnames (uid, charname) VALUES (%s, %s)", (uid, char_name))
|
||||
|
||||
self.__CONN.commit()
|
||||
|
||||
return
|
||||
|
||||
|
||||
def return_char_name(self, uid: int) -> str:
|
||||
"""Utility method to return the character name based on a specific discord ID
|
||||
|
||||
Arguments:
|
||||
uid -- Discord User ID of the user to be queried
|
||||
|
||||
Returns:
|
||||
String; either character name or empty.
|
||||
"""
|
||||
self.__CONN = connect_config(self._config)
|
||||
self.__CONN.autocommit = True
|
||||
cursor = self.__CONN.cursor()
|
||||
|
||||
# was getting weird index error on this line due to tuples, so we're using an f-string
|
||||
cursor.execute(f"SELECT charname FROM charnames WHERE uid = {uid}")
|
||||
output: str = ""
|
||||
try:
|
||||
output = cursor.fetchone()[0]
|
||||
except TypeError:
|
||||
return ""
|
||||
else:
|
||||
return output
|
||||
|
||||
|
||||
def remove_registration(self, uid: int, isOkay: bool) -> None:
|
||||
self.__CONN = connect_config(self._config)
|
||||
self.__CONN.autocommit = True
|
||||
cursor = self.__CONN.cursor()
|
||||
|
||||
# need to remove all callouts!
|
||||
cursor.execute(f"DELETE FROM newcallouts WHERE user_id = {uid}")
|
||||
|
||||
cursor.execute(f"DELETE FROM charnames WHERE uid = {uid}")
|
||||
return
|
||||
|
||||
|
||||
def number_affected_in_cleanup(self) -> int:
|
||||
self.__CONN = connect_config(self._config)
|
||||
self.__CONN.autocommit = True
|
||||
cursor = self.__CONN.cursor()
|
||||
cursor.execute(f"SELECT count(*) FROM newcallouts WHERE date < NOW();")
|
||||
|
||||
return cursor.fetchone()[0]
|
||||
|
||||
|
||||
def call_cleanup(self, is_okay: bool) -> int:
|
||||
|
||||
number_to_be_affected = self.number_affected_in_cleanup()
|
||||
|
||||
if not is_okay:
|
||||
raise Exception("Not queued properly!")
|
||||
|
||||
self.__CONN = connect_config(self._config)
|
||||
self.__CONN.autocommit = True
|
||||
|
||||
cursor = self.__CONN.cursor()
|
||||
cursor.execute(f"CALL cleanup();")
|
||||
print("Cleanup was called!")
|
||||
return number_to_be_affected
|
||||
17
src/sql/current-table.sql
Normal file
17
src/sql/current-table.sql
Normal file
@@ -0,0 +1,17 @@
|
||||
-- Table: public.callouts
|
||||
|
||||
-- DROP TABLE IF EXISTS public.callouts;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS public.callouts
|
||||
(
|
||||
user_id bigint NOT NULL,
|
||||
date date NOT NULL,
|
||||
reason text COLLATE pg_catalog."default",
|
||||
nickname text COLLATE pg_catalog."default",
|
||||
CONSTRAINT callouts_pkey PRIMARY KEY (user_id, date)
|
||||
)
|
||||
|
||||
TABLESPACE pg_default;
|
||||
|
||||
ALTER TABLE IF EXISTS public.callouts
|
||||
OWNER to opossumbot;
|
||||
7
src/sql/initial-table.sql
Normal file
7
src/sql/initial-table.sql
Normal file
@@ -0,0 +1,7 @@
|
||||
create table absent_players (
|
||||
user_id int NOT NULL,
|
||||
user_name varchar(20) NOT NULL,
|
||||
date_ab date NOT NULL,
|
||||
reason_ab varchar(200),
|
||||
has_passed int DEFAULT 0);
|
||||
/
|
||||
13
src/sql/sqlscripts.sql
Normal file
13
src/sql/sqlscripts.sql
Normal file
@@ -0,0 +1,13 @@
|
||||
-- These are scripts that are formatted for accesing the table, more comments can be provided as needed
|
||||
|
||||
select count(user_id) from absent_players where user_id = 000000 and has_passed = 1;
|
||||
-- Select the number of times a user has been absent so far(replace user_ID)
|
||||
|
||||
select user_name, reason_ab from absent_players where date_ab = SYSDATE;
|
||||
-- select users name, reason they're absent today (reoplace date with today's date)
|
||||
|
||||
select user_name, date_ab, reason_ab from absent_players where has_passed = 0;
|
||||
-- selects all future players who are absent, the reason they are, and when they are.
|
||||
|
||||
delete from absent_players where user_ID = 00000 and date_ab = date and has_passed = 0;
|
||||
-- deletes a future absence from the table, cannot remove older absences.
|
||||
Reference in New Issue
Block a user