Initial Code

This commit is contained in:
c0de 2022-09-23 17:40:31 -05:00
parent f96618d2d1
commit 20ca7befdf
9 changed files with 629 additions and 629 deletions

4
.gitignore vendored
View File

@ -1,2 +1,2 @@
.idea/*
__pycache__/
.idea/*
__pycache__/

View File

@ -1,51 +1,51 @@
# Names of Configurations
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
DATABASE_USERNAME = 'database_username'
DATABASE_PASSWORD = 'database_password'
DATABASE_HOST = 'database_host'
DATABASE_PORT = 'database_port'
DATABASE_NAME = 'database_name'
SEASON_1_SPREADSHEET_ID = 's1_spreadsheet_id'
SEASON_2_SPREADSHEET_ID = 's2_spreadsheet_id'
PLAYER_SPREADSHEET = 'player_spreadsheet'
'''
Main source for configurations fetched from a startup configuration file. Includes the ability to fetch all, or fetch
one configuration once the file is loaded.
You'll find the names of these configs above as constants that can be used throughout the rest of this repository
'''
class Configs():
configs = {}
def __init__(self, config_file_path):
self.config_file_path = config_file_path
self.__load_configs__()
'''
Fetches a single configuration by the name of that configuration.
Returns None if that configuration does not exist
'''
def get_config_by_name(self, name):
try:
return Configs.configs[name]
except KeyError:
return None
'''
Fetches all configurations and returns them as a dictionary of config_key -> config_value
'''
def get_all_configs(self):
return Configs.configs
'''
Performs the initial load of configurations from a startup configuration file
'''
def __load_configs__(self):
Configs.configs = {}
config_file = open(self.config_file_path, 'r')
for line in config_file:
split_line = line.split('=')
# Names of Configurations
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
DATABASE_USERNAME = 'ghost_user'
DATABASE_PASSWORD = 'root'
DATABASE_HOST = '192.168.0.11'
DATABASE_PORT = '5432'
DATABASE_NAME = 'ghostball'
SEASON_1_SPREADSHEET_ID = 's1_spreadsheet_id'
SEASON_2_SPREADSHEET_ID = 's2_spreadsheet_id'
PLAYER_SPREADSHEET = 'player_spreadsheet'
'''
Main source for configurations fetched from a startup configuration file. Includes the ability to fetch all, or fetch
one configuration once the file is loaded.
You'll find the names of these configs above as constants that can be used throughout the rest of this repository
'''
class Configs():
configs = {}
def __init__(self, config_file_path):
self.config_file_path = config_file_path
self.__load_configs__()
'''
Fetches a single configuration by the name of that configuration.
Returns None if that configuration does not exist
'''
def get_config_by_name(self, name):
try:
return Configs.configs[name]
except KeyError:
return None
'''
Fetches all configurations and returns them as a dictionary of config_key -> config_value
'''
def get_all_configs(self):
return Configs.configs
'''
Performs the initial load of configurations from a startup configuration file
'''
def __load_configs__(self):
Configs.configs = {}
config_file = open(self.config_file_path, 'r')
for line in config_file:
split_line = line.split('=')
Configs.configs[split_line[0]] = split_line[1].strip('\n')

View File

@ -1,28 +1,28 @@
from sqlalchemy import Column, String, Integer, ForeignKey, Date
from sqlalchemy.dialects.postgresql import UUID
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import relationship
Base = declarative_base()
class Play(Base):
__tablename__ = 'play'
play_id = Column(String, nullable=False, primary_key=True)
pitch_value = Column(Integer, nullable=True)
creation_date = Column(Date, nullable=False)
server_id = Column(String, nullable=False)
guesses = relationship(lambda : Guess)
class Guess(Base):
__FAKE_VALUE__ = -5000
__tablename__ = 'guess'
member_id = Column(String, nullable=False, primary_key=True)
play_id = Column(UUID, ForeignKey(Play.play_id), nullable=False, primary_key=True)
guessed_number = Column(Integer, nullable=False)
member_name = Column(String, nullable=False)
difference = Column(Integer)
play = relationship("Play", back_populates="guesses")
from sqlalchemy import Column, String, Integer, ForeignKey, Date
from sqlalchemy.dialects.postgresql import UUID
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import relationship
Base = declarative_base()
class Play(Base):
__tablename__ = 'play'
play_id = Column(String, nullable=False, primary_key=True)
pitch_value = Column(Integer, nullable=True)
creation_date = Column(Date, nullable=False)
server_id = Column(String, nullable=False)
guesses = relationship(lambda : Guess)
class Guess(Base):
__FAKE_VALUE__ = -5000
__tablename__ = 'guess'
member_id = Column(String, nullable=False, primary_key=True)
play_id = Column(UUID, ForeignKey(Play.play_id), nullable=False, primary_key=True)
guessed_number = Column(Integer, nullable=False)
member_name = Column(String, nullable=False)
difference = Column(Integer)
play = relationship("Play", back_populates="guesses")

View File

@ -1,123 +1,123 @@
from copy import deepcopy
from src.main.database_module.database_classes.db_classes import Guess
from src.main.db_session import DatabaseSession
MEMBER_ID = 'member_id'
PLAY_ID = 'play_id'
GUESSED_NUMBER = 'guessed_number'
DIFFERENCE = 'difference'
MEMBER_NAME = 'member_name'
class GuessDAO():
db_string = None
session = None
Session = None
engine = None
_database_session = None
def __init__(self):
self._database_session = DatabaseSession()
def insert(self, guess_info, allow_update=False):
session = self._database_session.get_or_create_session()
guess = Guess(
member_id=guess_info[MEMBER_ID],
play_id = guess_info[PLAY_ID],
guessed_number = guess_info[GUESSED_NUMBER],
member_name = guess_info[MEMBER_NAME]
)
existing_guess = self.__convert_all__(session\
.query(Guess)\
.filter(Guess.member_id == guess_info[MEMBER_ID], Guess.play_id == guess_info[PLAY_ID]))
if len(existing_guess) == 0:
session.add(guess)
session.commit()
return True
elif allow_update:
session\
.query(Guess)\
.filter(Guess.member_id == guess_info[MEMBER_ID], Guess.play_id == guess_info[PLAY_ID], Guess.member_name == guess_info[MEMBER_NAME])\
.update({Guess.guessed_number: guess_info[GUESSED_NUMBER]})
return True
else:
return False
'''
Converts the database object into a Dictionary, so that the database object is not passed out of the
datastore layer.
'''
def __convert_all__(self, games):
converted_games = []
for game in games:
game_dict = {}
for column in game.__dict__:
game_dict[column] = str(getattr(game, column))
converted_games.append(deepcopy(game_dict))
return converted_games
def set_differences(self, pitch_value, play_id):
session = self._database_session.get_or_create_session()
games_to_update = self.__convert_all__(session.query(Guess).filter(Guess.play_id == play_id))
for game in games_to_update:
difference = self.calculate_difference(pitch_value, game[GUESSED_NUMBER])
session.query(Guess).filter(Guess.member_id == game[MEMBER_ID], Guess.play_id == game[PLAY_ID]).update({Guess.difference: difference})
session.commit()
def calculate_difference(self, pitch_value, guess_value):
pitched_number = int(pitch_value)
possible_value = abs(int(guess_value) - pitched_number)
if possible_value > 500:
return 1000 - possible_value
else:
return possible_value
def fetch_closest(self, num_to_fetch):
session = self._database_session.get_or_create_session()
return self.__convert_all__(
session\
.query(Guess)\
.order_by(Guess.difference)\
.limit(num_to_fetch)
)
def refresh(self):
self._database_session.__create_new_session__() # I know, I know. It's fine.
def get_all_guesses_for_plays(self, play_ids):
session = self._database_session.get_or_create_session()
return self.__convert_all__(
session
.query(Guess)
.filter(Guess.play_id.in_(play_ids))
)
def get_closest_on_play(self, play):
session = self._database_session.get_or_create_session()
# TODO: Make this a MAX query for ties
converted_guesses = self.__convert_all__(
session
.query(Guess)
.filter(Guess.play_id == play)
.order_by(Guess.difference)
.limit(1)
)
if len(converted_guesses) > 1:
raise AssertionError("More than one best guess! Can't continue!")
elif len(converted_guesses) == 0:
return None
else:
from copy import deepcopy
from src.main.database_module.database_classes.db_classes import Guess
from src.main.db_session import DatabaseSession
MEMBER_ID = 'member_id'
PLAY_ID = 'play_id'
GUESSED_NUMBER = 'guessed_number'
DIFFERENCE = 'difference'
MEMBER_NAME = 'member_name'
class GuessDAO():
db_string = None
session = None
Session = None
engine = None
_database_session = None
def __init__(self):
self._database_session = DatabaseSession()
def insert(self, guess_info, allow_update=False):
session = self._database_session.get_or_create_session()
guess = Guess(
member_id=guess_info[MEMBER_ID],
play_id = guess_info[PLAY_ID],
guessed_number = guess_info[GUESSED_NUMBER],
member_name = guess_info[MEMBER_NAME]
)
existing_guess = self.__convert_all__(session\
.query(Guess)\
.filter(Guess.member_id == guess_info[MEMBER_ID], Guess.play_id == guess_info[PLAY_ID]))
if len(existing_guess) == 0:
session.add(guess)
session.commit()
return True
elif allow_update:
session\
.query(Guess)\
.filter(Guess.member_id == guess_info[MEMBER_ID], Guess.play_id == guess_info[PLAY_ID], Guess.member_name == guess_info[MEMBER_NAME])\
.update({Guess.guessed_number: guess_info[GUESSED_NUMBER]})
return True
else:
return False
'''
Converts the database object into a Dictionary, so that the database object is not passed out of the
datastore layer.
'''
def __convert_all__(self, games):
converted_games = []
for game in games:
game_dict = {}
for column in game.__dict__:
game_dict[column] = str(getattr(game, column))
converted_games.append(deepcopy(game_dict))
return converted_games
def set_differences(self, pitch_value, play_id):
session = self._database_session.get_or_create_session()
games_to_update = self.__convert_all__(session.query(Guess).filter(Guess.play_id == play_id))
for game in games_to_update:
difference = self.calculate_difference(pitch_value, game[GUESSED_NUMBER])
session.query(Guess).filter(Guess.member_id == game[MEMBER_ID], Guess.play_id == game[PLAY_ID]).update({Guess.difference: difference})
session.commit()
def calculate_difference(self, pitch_value, guess_value):
pitched_number = int(pitch_value)
possible_value = abs(int(guess_value) - pitched_number)
if possible_value > 500:
return 1000 - possible_value
else:
return possible_value
def fetch_closest(self, num_to_fetch):
session = self._database_session.get_or_create_session()
return self.__convert_all__(
session\
.query(Guess)\
.order_by(Guess.difference)\
.limit(num_to_fetch)
)
def refresh(self):
self._database_session.__create_new_session__() # I know, I know. It's fine.
def get_all_guesses_for_plays(self, play_ids):
session = self._database_session.get_or_create_session()
return self.__convert_all__(
session
.query(Guess)
.filter(Guess.play_id.in_(play_ids))
)
def get_closest_on_play(self, play):
session = self._database_session.get_or_create_session()
# TODO: Make this a MAX query for ties
converted_guesses = self.__convert_all__(
session
.query(Guess)
.filter(Guess.play_id == play)
.order_by(Guess.difference)
.limit(1)
)
if len(converted_guesses) > 1:
raise AssertionError("More than one best guess! Can't continue!")
elif len(converted_guesses) == 0:
return None
else:
return converted_guesses[0]

View File

@ -1,96 +1,96 @@
from copy import deepcopy
from sqlalchemy.sql.expression import and_
from src.main.db_session import DatabaseSession
from src.main.database_module.database_classes.db_classes import Play
import datetime
PLAY_ID = 'play_id'
PITCH_VALUE = 'pitch_value'
CREATION_DATE = 'creation_date'
SERVER_ID = 'server_id'
class PlayDAO():
db_string = None
session = None
Session = None
engine = None
_database_session = None
def __init__(self):
self._database_session = DatabaseSession()
def insert(self, play_info):
session = self._database_session.get_or_create_session()
play = Play(
play_id = play_info[PLAY_ID],
pitch_value = play_info[PITCH_VALUE] if PITCH_VALUE in play_info else None,
creation_date = play_info[CREATION_DATE],
server_id = play_info[SERVER_ID]
)
session.add(play)
session.commit()
def get_play_by_id(self, input_id):
session = self._database_session.get_or_create_session()
return self.__convert_all__(session.query(Play).filter(Play.play_id == input_id))
def get_all_plays_after(self, timestamp, input_server_id):
session = self._database_session.get_or_create_session()
return self.__convert_all__(session.query(Play).filter(and_(Play.server_id == str(input_server_id), Play.creation_date > timestamp)))
def get_all_plays_on_server(self, input_server_id, earliest_timestamp):
session = self._database_session.get_or_create_session()
converted_datetime = datetime.datetime.fromtimestamp(earliest_timestamp / 1000.0)
return self.__convert_all__(session.query(Play).filter(and_(Play.server_id == str(input_server_id), Play.creation_date > converted_datetime)))
'''
Checks to see if there is a play that is currently active or not
'''
def is_active_play(self, server_id):
return self.get_active_play(server_id) != None
def get_active_play(self, input_server_id):
session = self._database_session.get_or_create_session()
plays = self.__convert_all__(session.query(Play).filter(and_(Play.pitch_value == None, Play.server_id == str(input_server_id))))
if len(plays) > 1:
raise AssertionError("More than one active play! Can't continue!")
elif len(plays) == 0:
return None
else:
return plays[0]
def resolve_play(self, input_pitch, input_server_id):
session = self._database_session.get_or_create_session()
active_id = self.get_active_play(input_server_id)
session\
.query(Play)\
.filter(and_(Play.pitch_value == None, Play.server_id == str(input_server_id)))\
.update({Play.pitch_value: input_pitch})
session.commit()
return active_id
def refresh(self):
self._database_session.__create_new_session__() # I know, I know. It's fine.
'''
Converts the database object into a Dictionary, so that the database object is not passed out of the
datastore layer.
'''
def __convert_all__(self, plays):
converted_plays = []
for play in plays:
play_dict = {}
for column in play.__dict__:
play_dict[column] = str(getattr(play, column))
converted_plays.append(deepcopy(play_dict))
return converted_plays
from copy import deepcopy
from sqlalchemy.sql.expression import and_
from src.main.db_session import DatabaseSession
from src.main.database_module.database_classes.db_classes import Play
import datetime
PLAY_ID = 'play_id'
PITCH_VALUE = 'pitch_value'
CREATION_DATE = 'creation_date'
SERVER_ID = 'server_id'
class PlayDAO():
db_string = None
session = None
Session = None
engine = None
_database_session = None
def __init__(self):
self._database_session = DatabaseSession()
def insert(self, play_info):
session = self._database_session.get_or_create_session()
play = Play(
play_id = play_info[PLAY_ID],
pitch_value = play_info[PITCH_VALUE] if PITCH_VALUE in play_info else None,
creation_date = play_info[CREATION_DATE],
server_id = play_info[SERVER_ID]
)
session.add(play)
session.commit()
def get_play_by_id(self, input_id):
session = self._database_session.get_or_create_session()
return self.__convert_all__(session.query(Play).filter(Play.play_id == input_id))
def get_all_plays_after(self, timestamp, input_server_id):
session = self._database_session.get_or_create_session()
return self.__convert_all__(session.query(Play).filter(and_(Play.server_id == str(input_server_id), Play.creation_date > timestamp)))
def get_all_plays_on_server(self, input_server_id, earliest_timestamp):
session = self._database_session.get_or_create_session()
converted_datetime = datetime.datetime.fromtimestamp(earliest_timestamp / 1000.0)
return self.__convert_all__(session.query(Play).filter(and_(Play.server_id == str(input_server_id), Play.creation_date > converted_datetime)))
'''
Checks to see if there is a play that is currently active or not
'''
def is_active_play(self, server_id):
return self.get_active_play(server_id) != None
def get_active_play(self, input_server_id):
session = self._database_session.get_or_create_session()
plays = self.__convert_all__(session.query(Play).filter(and_(Play.pitch_value == None, Play.server_id == str(input_server_id))))
if len(plays) > 1:
raise AssertionError("More than one active play! Can't continue!")
elif len(plays) == 0:
return None
else:
return plays[0]
def resolve_play(self, input_pitch, input_server_id):
session = self._database_session.get_or_create_session()
active_id = self.get_active_play(input_server_id)
session\
.query(Play)\
.filter(and_(Play.pitch_value == None, Play.server_id == str(input_server_id)))\
.update({Play.pitch_value: input_pitch})
session.commit()
return active_id
def refresh(self):
self._database_session.__create_new_session__() # I know, I know. It's fine.
'''
Converts the database object into a Dictionary, so that the database object is not passed out of the
datastore layer.
'''
def __convert_all__(self, plays):
converted_plays = []
for play in plays:
play_dict = {}
for column in play.__dict__:
play_dict[column] = str(getattr(play, column))
converted_plays.append(deepcopy(play_dict))
return converted_plays

View File

@ -1,49 +1,49 @@
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
import sys
import sqlite3
sys.path.append('../../../../../src')
from src.main.configs import Configs, DATABASE_USERNAME, DATABASE_PASSWORD, DATABASE_HOST, DATABASE_NAME
'''
Stores a database session for use throughout the application. Must be initialized at startup before any database calls
are made and AFTER the Configurations are setup.
This shouldn't need to be touched after startup. To use, see the sqlalchemy docs...or just start by calling Session()
and then use it to handle the necessary CRUD operations.
You should NOT instantiate this in any method except the main application runner
'''
class DatabaseSession():
_session = None
def __init__(self):
self.__create_new_session__()
def __create_new_session__(self):
if self._session is not None:
self._session.close()
config_map = Configs.configs
db_string = self._pgsql_conn_string_(config_map)
Session = sessionmaker(create_engine(db_string))
self._session = Session()
return self._session
def get_or_create_session(self):
try:
self._session.connection()
return self._session
except: # The linter can scream all it wants, this makes sense. If it's this broke, we want a new one anyway.
return self.__create_new_session__()
# Look, this kinda sucks. But it's for fun and friends and I'm doing it quick and dirty.
def _pgsql_conn_string_(self, config_map):
return 'postgresql://%s:%s@%s/%s' % \
(config_map[DATABASE_USERNAME], config_map[DATABASE_PASSWORD], config_map[DATABASE_HOST], config_map[DATABASE_NAME])
def _sqlite_conn_string(self, config_map):
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
import sys
import sqlite3
sys.path.append('../../../../../../src')
from src.main.configs import Configs, DATABASE_USERNAME, DATABASE_PASSWORD, DATABASE_HOST, DATABASE_NAME
'''
Stores a database session for use throughout the application. Must be initialized at startup before any database calls
are made and AFTER the Configurations are setup.
This shouldn't need to be touched after startup. To use, see the sqlalchemy docs...or just start by calling Session()
and then use it to handle the necessary CRUD operations.
You should NOT instantiate this in any method except the main application runner
'''
class DatabaseSession():
_session = None
def __init__(self):
self.__create_new_session__()
def __create_new_session__(self):
if self._session is not None:
self._session.close()
config_map = Configs.configs
db_string = self._pgsql_conn_string_(config_map)
Session = sessionmaker(create_engine(db_string))
self._session = Session()
return self._session
def get_or_create_session(self):
try:
self._session.connection()
return self._session
except: # The linter can scream all it wants, this makes sense. If it's this broke, we want a new one anyway.
return self.__create_new_session__()
# Look, this kinda sucks. But it's for fun and friends and I'm doing it quick and dirty.
def _pgsql_conn_string_(self, config_map):
return 'postgresql://%s:%s@%s/%s' % \
(config_map[DATABASE_USERNAME], config_map[DATABASE_PASSWORD], config_map[DATABASE_HOST], config_map[DATABASE_NAME])
def _sqlite_conn_string(self, config_map):
return "sqlite:///ghostball.db"

View File

@ -1,220 +1,220 @@
import sys
import discord
from discord.utils import get
import uuid
import datetime
import dateparser
from src.main.configs import Configs
from src.main.database_module.guess_dao import GuessDAO, GUESSED_NUMBER, MEMBER_ID, MEMBER_NAME, DIFFERENCE
from src.main.services.points_service import PointsService
from src.main.database_module.play_dao import PlayDAO, PLAY_ID, CREATION_DATE, SERVER_ID
from src.main.db_session import DatabaseSession
from src.main.discord_module.leaderboard_config import LeaderboardConfig
play_dao = None
guess_dao = None
points_service = PointsService()
bot = discord.Client()
@bot.event
async def on_ready():
print('Logged in as')
print(bot.user.name)
print(bot.user.id)
print('------')
@bot.event
async def on_message(message):
if message.author == bot.user:
return
content = message.content
server_id = message.guild.id
'''
Sets up the next set of guesses.
'''
if content.startswith('!ghostball'):
if play_dao.is_active_play(server_id):
await message.channel.send("There's already an active play. Could you close that one first, please?")
else:
generated_play_id = uuid.uuid4()
play_object = {PLAY_ID: generated_play_id, CREATION_DATE: datetime.datetime.now(), SERVER_ID: server_id}
play_dao.insert(play_object)
await message.channel.send("@flappy ball, pitch is in! Send me your guesses with a !guess command.")
if content.startswith("!guess"):
guess_value = None
try:
guess_value = __parse_guess__(content)
except ValueError:
await message.channel.send("That number is not between 1 and 1000. We're still in MLN so don't try to cheat.")
return
if guess_value is None:
await message.channel.send("I don't know what you did but I'm pretty sure you're tyring to break the bot so please stop.")
return
if not play_dao.is_active_play(server_id):
await message.channel.send("Hey, there's no active play! Start one up first with !ghostball.")
else:
play = play_dao.get_active_play(server_id)
guess_object = {PLAY_ID: play['play_id'],
MEMBER_ID: str(message.author.id),
GUESSED_NUMBER: guess_value,
MEMBER_NAME: str(message.author.name)}
if guess_dao.insert(guess_object, allow_update=True):
await message.add_reaction(emoji="\N{THUMBS UP SIGN}")
# Closes off the active play to be ready for the next set
if content.startswith('!resolve'):
# try:
pitch_number, batter_id, batter_guess, has_batter = __parse_resolve_play__(content)
if args is None:
await message.channel.send("Hey " + "<@" + str(message.author.id) + ">, I'm not sure what you meant. "
"You need real, numeric, values for this command to work. "
"Use !resolve <pitch number> <optional batter> <optional swing number>"
" and try again.")
# Check if we have an active play
if not play_dao.is_active_play(server_id):
await message.channel.send("You confused me. There's no active play so I have nothing to close!")
else:
if has_batter:
referenced_member_id = batter_id[3:-1]
play = play_dao.get_active_play(server_id)
guess_object = {PLAY_ID: play['play_id'],
MEMBER_ID: str(referenced_member_id),
GUESSED_NUMBER: batter_guess,
MEMBER_NAME: bot.get_user(int(referenced_member_id)).name}
guess_dao.insert(guess_object, True)
play = play_dao.resolve_play(pitch_number, server_id)
guess_dao.set_differences(pitch_number, play['play_id'])
guesses = points_service.fetch_sorted_guesses_by_play(guess_dao, play['play_id'])
response_message = "Closed this play! Here are the results:\n"
response_message += "PLAYER --- DIFFERENCE --- POINTS GAINED\n"
for guess in guesses:
response_message += guess[1] + " --- " + str(guess[2]) + " --- " + str(guess[3]) + "\n"
if len(guesses) < 2:
response_message += "Not enough people participated to give best and worst awards. Stop being lazy."
else:
response_message += "\nCongrats to <@" + str(guesses[0][0]) + "> for being the closest! \n"
response_message += "And tell <@" + str(guesses[-1][0]) + "> they suck."
await message.channel.send(response_message)
if content.startswith("!points"):
try:
timestamp = __parse_points_message__(content)
except:
await message.channel.send("You gave me a timestamp that was so bad, the best date handling library in the"
" world of software couldn't figure out what you meant. That's...impressive. Now"
" fix your shit and try again.")
return
points_by_user = points_service.fetch_points(timestamp, server_id, play_dao, guess_dao)
response = "Here are the top guessers by points as per your request..."
for user in points_by_user:
if str(user[2]) != '0':
response += "\n" + str(user[1]) + " : " + str(user[2])
await message.channel.send(response)
# Refresh Postgres connection
if content.startswith('!restart'):
play_dao.refresh()
guess_dao.refresh()
if content.startswith('!help'):
help_message = __get_help_message__()
recipient = await bot.fetch_user(message.author.id)
await recipient.send(help_message)
def __get_help_message__():
# Start message with person who asked for help
help_message = "Hey! I can be instructed to do any number of things! Use the following commands: \n" \
"!guess <NUMBER> --- This will add your guess to the currently active play. " \
"I will give you a thumbs up if everything worked!\n" \
"!ghostball --- Starts a new play. I'll let you know if this didn't work for some reason!\n" \
"!help --- You just asked for this. If you ask for it again, I'll repeat myself.\n" \
"!resolve <PITCH_NUMBER> <OPTIONAL --- BATTER by @-mention> <OPTIONAL - ACTUAL SWING NUMBER> --- " \
"Uses the pitch number and real swing number to figure out who was closest and ends the active play." \
"If you include the batter and their swing number, they will get credit for how well they did!\n" \
"!points <OPTIONAL --- TIMESTAMP> Fetches all plays since your requested time, or the beginning of the unvierse " \
"if none given. Will currently always dump all players - top X coming soon...\n" \
"!restart --- If the bot looks broken, this will take a shot at fixing it. It won't answer your commands " \
"for about 3 seconds after you do this! BE CAREFUL! ONLY USE IN AN EMERGENCY!\n" \
"<PING KALI IF YOU'RE CONFUSED, ANGRY, OR WANT TO GEEK OUT ABOUT BRAVELY DEFAULT!>\n"
return help_message
def __parse_leaderboard_message__(message_content):
return LeaderboardConfig(message_content)
def __parse_points_message__(message_content):
pieces = message_content.split(' ')
if len(pieces) > 1:
try:
timestamp = dateparser.parse(pieces[1])
except:
raise RuntimeError("Unable to parse timestamp!")
else:
timestamp = dateparser.parse("1970-01-01")
return timestamp
def __parse_guess__(message_content):
pieces = message_content.split(' ')
try:
guess_value = pieces[1]
guess_as_int = int(guess_value)
if guess_as_int > 1000 or guess_as_int < 1:
raise ValueError("Number not between 1 and 1000 inclusive")
else:
return guess_value
except TypeError:
return None
def __parse_resolve_play__(message_content):
pieces = message_content.split()
try:
if len(pieces) == 2:
return pieces[1], None, None, False
elif len(pieces) == 4:
return pieces[1], pieces[2], pieces[3], True
else:
print("Illegal resolution command")
return None, None
except TypeError:
return None, None
if __name__ == '__main__':
args = sys.argv
token = args[1]
file_path = args[2]
configs = Configs(file_path)
databaseSession = DatabaseSession()
play_dao = PlayDAO()
guess_dao = GuessDAO()
bot.run(token)
import sys
import discord
from discord.utils import get
import uuid
import datetime
import dateparser
from src.main.configs import Configs
from src.main.database_module.guess_dao import GuessDAO, GUESSED_NUMBER, MEMBER_ID, MEMBER_NAME, DIFFERENCE
from src.main.services.points_service import PointsService
from src.main.database_module.play_dao import PlayDAO, PLAY_ID, CREATION_DATE, SERVER_ID
from src.main.db_session import DatabaseSession
from src.main.discord_module.leaderboard_config import LeaderboardConfig
play_dao = None
guess_dao = None
points_service = PointsService()
bot = discord.Client()
@bot.event
async def on_ready():
print('Logged in as')
print(bot.user.name)
print(bot.user.id)
print('------')
@bot.event
async def on_message(message):
if message.author == bot.user:
return
content = message.content
server_id = message.guild.id
'''
Sets up the next set of guesses.
'''
if content.startswith('!ghostball'):
if play_dao.is_active_play(server_id):
await message.channel.send("There's already an active play. Could you close that one first, please?")
else:
generated_play_id = uuid.uuid4()
play_object = {PLAY_ID: generated_play_id, CREATION_DATE: datetime.datetime.now(), SERVER_ID: server_id}
play_dao.insert(play_object)
await message.channel.send("@flappy ball, pitch is in! Send me your guesses with a !guess command.")
if content.startswith("!guess"):
guess_value = None
try:
guess_value = __parse_guess__(content)
except ValueError:
await message.channel.send("That number is not between 1 and 1000. We're still in MLN so don't try to cheat.")
return
if guess_value is None:
await message.channel.send("I don't know what you did but I'm pretty sure you're tyring to break the bot so please stop.")
return
if not play_dao.is_active_play(server_id):
await message.channel.send("Hey, there's no active play! Start one up first with !ghostball.")
else:
play = play_dao.get_active_play(server_id)
guess_object = {PLAY_ID: play['play_id'],
MEMBER_ID: str(message.author.id),
GUESSED_NUMBER: guess_value,
MEMBER_NAME: str(message.author.name)}
if guess_dao.insert(guess_object, allow_update=True):
await message.add_reaction(emoji="\N{THUMBS UP SIGN}")
# Closes off the active play to be ready for the next set
if content.startswith('!resolve'):
# try:
pitch_number, batter_id, batter_guess, has_batter = __parse_resolve_play__(content)
if args is None:
await message.channel.send("Hey " + "<@" + str(message.author.id) + ">, I'm not sure what you meant. "
"You need real, numeric, values for this command to work. "
"Use !resolve <pitch number> <optional batter> <optional swing number>"
" and try again.")
# Check if we have an active play
if not play_dao.is_active_play(server_id):
await message.channel.send("You confused me. There's no active play so I have nothing to close!")
else:
if has_batter:
referenced_member_id = batter_id[3:-1]
play = play_dao.get_active_play(server_id)
guess_object = {PLAY_ID: play['play_id'],
MEMBER_ID: str(referenced_member_id),
GUESSED_NUMBER: batter_guess,
MEMBER_NAME: bot.get_user(int(referenced_member_id)).name}
guess_dao.insert(guess_object, True)
play = play_dao.resolve_play(pitch_number, server_id)
guess_dao.set_differences(pitch_number, play['play_id'])
guesses = points_service.fetch_sorted_guesses_by_play(guess_dao, play['play_id'])
response_message = "Closed this play! Here are the results:\n"
response_message += "PLAYER --- DIFFERENCE --- POINTS GAINED\n"
for guess in guesses:
response_message += guess[1] + " --- " + str(guess[2]) + " --- " + str(guess[3]) + "\n"
if len(guesses) < 2:
response_message += "Not enough people participated to give best and worst awards. Stop being lazy."
else:
response_message += "\nCongrats to <@" + str(guesses[0][0]) + "> for being the closest! \n"
response_message += "And tell <@" + str(guesses[-1][0]) + "> they suck."
await message.channel.send(response_message)
if content.startswith("!points"):
try:
timestamp = __parse_points_message__(content)
except:
await message.channel.send("You gave me a timestamp that was so bad, the best date handling library in the"
" world of software couldn't figure out what you meant. That's...impressive. Now"
" fix your shit and try again.")
return
points_by_user = points_service.fetch_points(timestamp, server_id, play_dao, guess_dao)
response = "Here are the top guessers by points as per your request..."
for user in points_by_user:
if str(user[2]) != '0':
response += "\n" + str(user[1]) + " : " + str(user[2])
await message.channel.send(response)
# Refresh Postgres connection
if content.startswith('!restart'):
play_dao.refresh()
guess_dao.refresh()
if content.startswith('!help'):
help_message = __get_help_message__()
recipient = await bot.fetch_user(message.author.id)
await recipient.send(help_message)
def __get_help_message__():
# Start message with person who asked for help
help_message = "Hey! I can be instructed to do any number of things! Use the following commands: \n" \
"!guess <NUMBER> --- This will add your guess to the currently active play. " \
"I will give you a thumbs up if everything worked!\n" \
"!ghostball --- Starts a new play. I'll let you know if this didn't work for some reason!\n" \
"!help --- You just asked for this. If you ask for it again, I'll repeat myself.\n" \
"!resolve <PITCH_NUMBER> <OPTIONAL --- BATTER by @-mention> <OPTIONAL - ACTUAL SWING NUMBER> --- " \
"Uses the pitch number and real swing number to figure out who was closest and ends the active play." \
"If you include the batter and their swing number, they will get credit for how well they did!\n" \
"!points <OPTIONAL --- TIMESTAMP> Fetches all plays since your requested time, or the beginning of the unvierse " \
"if none given. Will currently always dump all players - top X coming soon...\n" \
"!restart --- If the bot looks broken, this will take a shot at fixing it. It won't answer your commands " \
"for about 3 seconds after you do this! BE CAREFUL! ONLY USE IN AN EMERGENCY!\n" \
"<PING KALI IF YOU'RE CONFUSED, ANGRY, OR WANT TO GEEK OUT ABOUT BRAVELY DEFAULT!>\n"
return help_message
def __parse_leaderboard_message__(message_content):
return LeaderboardConfig(message_content)
def __parse_points_message__(message_content):
pieces = message_content.split(' ')
if len(pieces) > 1:
try:
timestamp = dateparser.parse(pieces[1])
except:
raise RuntimeError("Unable to parse timestamp!")
else:
timestamp = dateparser.parse("1970-01-01")
return timestamp
def __parse_guess__(message_content):
pieces = message_content.split(' ')
try:
guess_value = pieces[1]
guess_as_int = int(guess_value)
if guess_as_int > 1000 or guess_as_int < 1:
raise ValueError("Number not between 1 and 1000 inclusive")
else:
return guess_value
except TypeError:
return None
def __parse_resolve_play__(message_content):
pieces = message_content.split()
try:
if len(pieces) == 2:
return pieces[1], None, None, False
elif len(pieces) == 4:
return pieces[1], pieces[2], pieces[3], True
else:
print("Illegal resolution command")
return None, None
except TypeError:
return None, None
if __name__ == '__main__':
args = sys.argv
token = args[1]
file_path = args[2]
configs = Configs(file_path)
databaseSession = DatabaseSession()
play_dao = PlayDAO()
guess_dao = GuessDAO()
bot.run(token)

View File

@ -1,15 +1,15 @@
class LeaderboardConfig():
closest = True
def __init__(self, message_content):
pieces = message_content.split(' ')
if len(pieces) == 1:
return
if pieces[1] == 'average':
self.closest = False
def should_sort_by_pure_closest(self):
return self.closest
def should_sort_by_best_average(self):
return not self.closest
class LeaderboardConfig():
closest = True
def __init__(self, message_content):
pieces = message_content.split(' ')
if len(pieces) == 1:
return
if pieces[1] == 'average':
self.closest = False
def should_sort_by_pure_closest(self):
return self.closest
def should_sort_by_best_average(self):
return not self.closest

View File

@ -1,48 +1,48 @@
class PointsService():
_point_table = [(5,25),(25, 75), (75, 50), (150, 25)]
def fetch_points(self, timestamp, server_id, play_dao, guess_dao):
plays = play_dao.get_all_plays_after(timestamp, server_id)
all_guesses = guess_dao.get_all_guesses_for_plays(x['play_id'] for x in plays)
# Build a dictionary of each member and their total points
totals_by_player = {}
for guess in all_guesses:
if guess['member_id'] in totals_by_player:
totals_by_player[guess['member_id']]['points'] += self.__get_points_for_diff__(guess['difference'])
else:
totals_by_player[guess['member_id']] = {}
totals_by_player[guess['member_id']]['points'] = self.__get_points_for_diff__(guess['difference'])
totals_by_player[guess['member_id']]['member_name'] = guess['member_name']
# And now pull those numbers out into a list and sort them
sorted_players = []
for player in totals_by_player:
sorted_players.append([player,
totals_by_player[player]['member_name'],
totals_by_player[player]['points']])
sorted_players.sort(key=lambda x: x[2], reverse=True)
return sorted_players
def fetch_sorted_guesses_by_play(self, guess_dao, play_id):
all_guesses = guess_dao.get_all_guesses_for_plays([play_id])
player_list = []
for guess in all_guesses:
player_list.append([guess['member_id'], guess['member_name'], int(guess['difference']), self.__get_points_for_diff__(guess['difference'])])
player_list.sort(key=lambda x: x[2])
return player_list
# Iterates through the point table, which we assume is sorted, and gets the points
def __get_points_for_diff__(self, diff):
if diff == 'None':
return 0
for i in range(0, len(self._point_table)):
if int(diff) < self._point_table[i][0]:
return self._point_table[i][1]
return 0
class PointsService():
_point_table = [(5,25),(25, 75), (75, 50), (150, 25)]
def fetch_points(self, timestamp, server_id, play_dao, guess_dao):
plays = play_dao.get_all_plays_after(timestamp, server_id)
all_guesses = guess_dao.get_all_guesses_for_plays(x['play_id'] for x in plays)
# Build a dictionary of each member and their total points
totals_by_player = {}
for guess in all_guesses:
if guess['member_id'] in totals_by_player:
totals_by_player[guess['member_id']]['points'] += self.__get_points_for_diff__(guess['difference'])
else:
totals_by_player[guess['member_id']] = {}
totals_by_player[guess['member_id']]['points'] = self.__get_points_for_diff__(guess['difference'])
totals_by_player[guess['member_id']]['member_name'] = guess['member_name']
# And now pull those numbers out into a list and sort them
sorted_players = []
for player in totals_by_player:
sorted_players.append([player,
totals_by_player[player]['member_name'],
totals_by_player[player]['points']])
sorted_players.sort(key=lambda x: x[2], reverse=True)
return sorted_players
def fetch_sorted_guesses_by_play(self, guess_dao, play_id):
all_guesses = guess_dao.get_all_guesses_for_plays([play_id])
player_list = []
for guess in all_guesses:
player_list.append([guess['member_id'], guess['member_name'], int(guess['difference']), self.__get_points_for_diff__(guess['difference'])])
player_list.sort(key=lambda x: x[2])
return player_list
# Iterates through the point table, which we assume is sorted, and gets the points
def __get_points_for_diff__(self, diff):
if diff == 'None':
return 0
for i in range(0, len(self._point_table)):
if int(diff) < self._point_table[i][0]:
return self._point_table[i][1]
return 0