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/* .idea/*
__pycache__/ __pycache__/

View File

@ -1,51 +1,51 @@
# Names of Configurations # Names of Configurations
from sqlalchemy import create_engine from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker from sqlalchemy.orm import sessionmaker
DATABASE_USERNAME = 'database_username' DATABASE_USERNAME = 'ghost_user'
DATABASE_PASSWORD = 'database_password' DATABASE_PASSWORD = 'root'
DATABASE_HOST = 'database_host' DATABASE_HOST = '192.168.0.11'
DATABASE_PORT = 'database_port' DATABASE_PORT = '5432'
DATABASE_NAME = 'database_name' DATABASE_NAME = 'ghostball'
SEASON_1_SPREADSHEET_ID = 's1_spreadsheet_id' SEASON_1_SPREADSHEET_ID = 's1_spreadsheet_id'
SEASON_2_SPREADSHEET_ID = 's2_spreadsheet_id' SEASON_2_SPREADSHEET_ID = 's2_spreadsheet_id'
PLAYER_SPREADSHEET = 'player_spreadsheet' PLAYER_SPREADSHEET = 'player_spreadsheet'
''' '''
Main source for configurations fetched from a startup configuration file. Includes the ability to fetch all, or fetch 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. 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 You'll find the names of these configs above as constants that can be used throughout the rest of this repository
''' '''
class Configs(): class Configs():
configs = {} configs = {}
def __init__(self, config_file_path): def __init__(self, config_file_path):
self.config_file_path = config_file_path self.config_file_path = config_file_path
self.__load_configs__() self.__load_configs__()
''' '''
Fetches a single configuration by the name of that configuration. Fetches a single configuration by the name of that configuration.
Returns None if that configuration does not exist Returns None if that configuration does not exist
''' '''
def get_config_by_name(self, name): def get_config_by_name(self, name):
try: try:
return Configs.configs[name] return Configs.configs[name]
except KeyError: except KeyError:
return None return None
''' '''
Fetches all configurations and returns them as a dictionary of config_key -> config_value Fetches all configurations and returns them as a dictionary of config_key -> config_value
''' '''
def get_all_configs(self): def get_all_configs(self):
return Configs.configs return Configs.configs
''' '''
Performs the initial load of configurations from a startup configuration file Performs the initial load of configurations from a startup configuration file
''' '''
def __load_configs__(self): def __load_configs__(self):
Configs.configs = {} Configs.configs = {}
config_file = open(self.config_file_path, 'r') config_file = open(self.config_file_path, 'r')
for line in config_file: for line in config_file:
split_line = line.split('=') split_line = line.split('=')
Configs.configs[split_line[0]] = split_line[1].strip('\n') 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 import Column, String, Integer, ForeignKey, Date
from sqlalchemy.dialects.postgresql import UUID from sqlalchemy.dialects.postgresql import UUID
from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import relationship from sqlalchemy.orm import relationship
Base = declarative_base() Base = declarative_base()
class Play(Base): class Play(Base):
__tablename__ = 'play' __tablename__ = 'play'
play_id = Column(String, nullable=False, primary_key=True) play_id = Column(String, nullable=False, primary_key=True)
pitch_value = Column(Integer, nullable=True) pitch_value = Column(Integer, nullable=True)
creation_date = Column(Date, nullable=False) creation_date = Column(Date, nullable=False)
server_id = Column(String, nullable=False) server_id = Column(String, nullable=False)
guesses = relationship(lambda : Guess) guesses = relationship(lambda : Guess)
class Guess(Base): class Guess(Base):
__FAKE_VALUE__ = -5000 __FAKE_VALUE__ = -5000
__tablename__ = 'guess' __tablename__ = 'guess'
member_id = Column(String, nullable=False, primary_key=True) member_id = Column(String, nullable=False, primary_key=True)
play_id = Column(UUID, ForeignKey(Play.play_id), nullable=False, primary_key=True) play_id = Column(UUID, ForeignKey(Play.play_id), nullable=False, primary_key=True)
guessed_number = Column(Integer, nullable=False) guessed_number = Column(Integer, nullable=False)
member_name = Column(String, nullable=False) member_name = Column(String, nullable=False)
difference = Column(Integer) difference = Column(Integer)
play = relationship("Play", back_populates="guesses") play = relationship("Play", back_populates="guesses")

View File

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

View File

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

View File

@ -1,49 +1,49 @@
from sqlalchemy import create_engine from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker from sqlalchemy.orm import sessionmaker
import sys import sys
import sqlite3 import sqlite3
sys.path.append('../../../../../src') sys.path.append('../../../../../../src')
from src.main.configs import Configs, DATABASE_USERNAME, DATABASE_PASSWORD, DATABASE_HOST, DATABASE_NAME 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 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. 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() 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. and then use it to handle the necessary CRUD operations.
You should NOT instantiate this in any method except the main application runner You should NOT instantiate this in any method except the main application runner
''' '''
class DatabaseSession(): class DatabaseSession():
_session = None _session = None
def __init__(self): def __init__(self):
self.__create_new_session__() self.__create_new_session__()
def __create_new_session__(self): def __create_new_session__(self):
if self._session is not None: if self._session is not None:
self._session.close() self._session.close()
config_map = Configs.configs config_map = Configs.configs
db_string = self._pgsql_conn_string_(config_map) db_string = self._pgsql_conn_string_(config_map)
Session = sessionmaker(create_engine(db_string)) Session = sessionmaker(create_engine(db_string))
self._session = Session() self._session = Session()
return self._session return self._session
def get_or_create_session(self): def get_or_create_session(self):
try: try:
self._session.connection() self._session.connection()
return self._session 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. 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__() return self.__create_new_session__()
# Look, this kinda sucks. But it's for fun and friends and I'm doing it quick and dirty. # 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): def _pgsql_conn_string_(self, config_map):
return 'postgresql://%s:%s@%s/%s' % \ return 'postgresql://%s:%s@%s/%s' % \
(config_map[DATABASE_USERNAME], config_map[DATABASE_PASSWORD], config_map[DATABASE_HOST], config_map[DATABASE_NAME]) (config_map[DATABASE_USERNAME], config_map[DATABASE_PASSWORD], config_map[DATABASE_HOST], config_map[DATABASE_NAME])
def _sqlite_conn_string(self, config_map): def _sqlite_conn_string(self, config_map):
return "sqlite:///ghostball.db" return "sqlite:///ghostball.db"

View File

@ -1,220 +1,220 @@
import sys import sys
import discord import discord
from discord.utils import get from discord.utils import get
import uuid import uuid
import datetime import datetime
import dateparser import dateparser
from src.main.configs import Configs 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.database_module.guess_dao import GuessDAO, GUESSED_NUMBER, MEMBER_ID, MEMBER_NAME, DIFFERENCE
from src.main.services.points_service import PointsService 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.database_module.play_dao import PlayDAO, PLAY_ID, CREATION_DATE, SERVER_ID
from src.main.db_session import DatabaseSession from src.main.db_session import DatabaseSession
from src.main.discord_module.leaderboard_config import LeaderboardConfig from src.main.discord_module.leaderboard_config import LeaderboardConfig
play_dao = None play_dao = None
guess_dao = None guess_dao = None
points_service = PointsService() points_service = PointsService()
bot = discord.Client() bot = discord.Client()
@bot.event @bot.event
async def on_ready(): async def on_ready():
print('Logged in as') print('Logged in as')
print(bot.user.name) print(bot.user.name)
print(bot.user.id) print(bot.user.id)
print('------') print('------')
@bot.event @bot.event
async def on_message(message): async def on_message(message):
if message.author == bot.user: if message.author == bot.user:
return return
content = message.content content = message.content
server_id = message.guild.id server_id = message.guild.id
''' '''
Sets up the next set of guesses. Sets up the next set of guesses.
''' '''
if content.startswith('!ghostball'): if content.startswith('!ghostball'):
if play_dao.is_active_play(server_id): 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?") await message.channel.send("There's already an active play. Could you close that one first, please?")
else: else:
generated_play_id = uuid.uuid4() generated_play_id = uuid.uuid4()
play_object = {PLAY_ID: generated_play_id, CREATION_DATE: datetime.datetime.now(), SERVER_ID: server_id} play_object = {PLAY_ID: generated_play_id, CREATION_DATE: datetime.datetime.now(), SERVER_ID: server_id}
play_dao.insert(play_object) play_dao.insert(play_object)
await message.channel.send("@flappy ball, pitch is in! Send me your guesses with a !guess command.") await message.channel.send("@flappy ball, pitch is in! Send me your guesses with a !guess command.")
if content.startswith("!guess"): if content.startswith("!guess"):
guess_value = None guess_value = None
try: try:
guess_value = __parse_guess__(content) guess_value = __parse_guess__(content)
except ValueError: 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.") await message.channel.send("That number is not between 1 and 1000. We're still in MLN so don't try to cheat.")
return return
if guess_value is None: 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.") 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 return
if not play_dao.is_active_play(server_id): 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.") await message.channel.send("Hey, there's no active play! Start one up first with !ghostball.")
else: else:
play = play_dao.get_active_play(server_id) play = play_dao.get_active_play(server_id)
guess_object = {PLAY_ID: play['play_id'], guess_object = {PLAY_ID: play['play_id'],
MEMBER_ID: str(message.author.id), MEMBER_ID: str(message.author.id),
GUESSED_NUMBER: guess_value, GUESSED_NUMBER: guess_value,
MEMBER_NAME: str(message.author.name)} MEMBER_NAME: str(message.author.name)}
if guess_dao.insert(guess_object, allow_update=True): if guess_dao.insert(guess_object, allow_update=True):
await message.add_reaction(emoji="\N{THUMBS UP SIGN}") await message.add_reaction(emoji="\N{THUMBS UP SIGN}")
# Closes off the active play to be ready for the next set # Closes off the active play to be ready for the next set
if content.startswith('!resolve'): if content.startswith('!resolve'):
# try: # try:
pitch_number, batter_id, batter_guess, has_batter = __parse_resolve_play__(content) pitch_number, batter_id, batter_guess, has_batter = __parse_resolve_play__(content)
if args is None: if args is None:
await message.channel.send("Hey " + "<@" + str(message.author.id) + ">, I'm not sure what you meant. " 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. " "You need real, numeric, values for this command to work. "
"Use !resolve <pitch number> <optional batter> <optional swing number>" "Use !resolve <pitch number> <optional batter> <optional swing number>"
" and try again.") " and try again.")
# Check if we have an active play # Check if we have an active play
if not play_dao.is_active_play(server_id): 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!") await message.channel.send("You confused me. There's no active play so I have nothing to close!")
else: else:
if has_batter: if has_batter:
referenced_member_id = batter_id[3:-1] referenced_member_id = batter_id[3:-1]
play = play_dao.get_active_play(server_id) play = play_dao.get_active_play(server_id)
guess_object = {PLAY_ID: play['play_id'], guess_object = {PLAY_ID: play['play_id'],
MEMBER_ID: str(referenced_member_id), MEMBER_ID: str(referenced_member_id),
GUESSED_NUMBER: batter_guess, GUESSED_NUMBER: batter_guess,
MEMBER_NAME: bot.get_user(int(referenced_member_id)).name} MEMBER_NAME: bot.get_user(int(referenced_member_id)).name}
guess_dao.insert(guess_object, True) guess_dao.insert(guess_object, True)
play = play_dao.resolve_play(pitch_number, server_id) play = play_dao.resolve_play(pitch_number, server_id)
guess_dao.set_differences(pitch_number, play['play_id']) guess_dao.set_differences(pitch_number, play['play_id'])
guesses = points_service.fetch_sorted_guesses_by_play(guess_dao, 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 = "Closed this play! Here are the results:\n"
response_message += "PLAYER --- DIFFERENCE --- POINTS GAINED\n" response_message += "PLAYER --- DIFFERENCE --- POINTS GAINED\n"
for guess in guesses: for guess in guesses:
response_message += guess[1] + " --- " + str(guess[2]) + " --- " + str(guess[3]) + "\n" response_message += guess[1] + " --- " + str(guess[2]) + " --- " + str(guess[3]) + "\n"
if len(guesses) < 2: if len(guesses) < 2:
response_message += "Not enough people participated to give best and worst awards. Stop being lazy." response_message += "Not enough people participated to give best and worst awards. Stop being lazy."
else: else:
response_message += "\nCongrats to <@" + str(guesses[0][0]) + "> for being the closest! \n" response_message += "\nCongrats to <@" + str(guesses[0][0]) + "> for being the closest! \n"
response_message += "And tell <@" + str(guesses[-1][0]) + "> they suck." response_message += "And tell <@" + str(guesses[-1][0]) + "> they suck."
await message.channel.send(response_message) await message.channel.send(response_message)
if content.startswith("!points"): if content.startswith("!points"):
try: try:
timestamp = __parse_points_message__(content) timestamp = __parse_points_message__(content)
except: except:
await message.channel.send("You gave me a timestamp that was so bad, the best date handling library in the" 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" " world of software couldn't figure out what you meant. That's...impressive. Now"
" fix your shit and try again.") " fix your shit and try again.")
return return
points_by_user = points_service.fetch_points(timestamp, server_id, play_dao, guess_dao) 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..." response = "Here are the top guessers by points as per your request..."
for user in points_by_user: for user in points_by_user:
if str(user[2]) != '0': if str(user[2]) != '0':
response += "\n" + str(user[1]) + " : " + str(user[2]) response += "\n" + str(user[1]) + " : " + str(user[2])
await message.channel.send(response) await message.channel.send(response)
# Refresh Postgres connection # Refresh Postgres connection
if content.startswith('!restart'): if content.startswith('!restart'):
play_dao.refresh() play_dao.refresh()
guess_dao.refresh() guess_dao.refresh()
if content.startswith('!help'): if content.startswith('!help'):
help_message = __get_help_message__() help_message = __get_help_message__()
recipient = await bot.fetch_user(message.author.id) recipient = await bot.fetch_user(message.author.id)
await recipient.send(help_message) await recipient.send(help_message)
def __get_help_message__(): def __get_help_message__():
# Start message with person who asked for help # 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" \ 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. " \ "!guess <NUMBER> --- This will add your guess to the currently active play. " \
"I will give you a thumbs up if everything worked!\n" \ "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" \ "!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" \ "!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> --- " \ "!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." \ "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" \ "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 " \ "!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" \ "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 " \ "!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" \ "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" "<PING KALI IF YOU'RE CONFUSED, ANGRY, OR WANT TO GEEK OUT ABOUT BRAVELY DEFAULT!>\n"
return help_message return help_message
def __parse_leaderboard_message__(message_content): def __parse_leaderboard_message__(message_content):
return LeaderboardConfig(message_content) return LeaderboardConfig(message_content)
def __parse_points_message__(message_content): def __parse_points_message__(message_content):
pieces = message_content.split(' ') pieces = message_content.split(' ')
if len(pieces) > 1: if len(pieces) > 1:
try: try:
timestamp = dateparser.parse(pieces[1]) timestamp = dateparser.parse(pieces[1])
except: except:
raise RuntimeError("Unable to parse timestamp!") raise RuntimeError("Unable to parse timestamp!")
else: else:
timestamp = dateparser.parse("1970-01-01") timestamp = dateparser.parse("1970-01-01")
return timestamp return timestamp
def __parse_guess__(message_content): def __parse_guess__(message_content):
pieces = message_content.split(' ') pieces = message_content.split(' ')
try: try:
guess_value = pieces[1] guess_value = pieces[1]
guess_as_int = int(guess_value) guess_as_int = int(guess_value)
if guess_as_int > 1000 or guess_as_int < 1: if guess_as_int > 1000 or guess_as_int < 1:
raise ValueError("Number not between 1 and 1000 inclusive") raise ValueError("Number not between 1 and 1000 inclusive")
else: else:
return guess_value return guess_value
except TypeError: except TypeError:
return None return None
def __parse_resolve_play__(message_content): def __parse_resolve_play__(message_content):
pieces = message_content.split() pieces = message_content.split()
try: try:
if len(pieces) == 2: if len(pieces) == 2:
return pieces[1], None, None, False return pieces[1], None, None, False
elif len(pieces) == 4: elif len(pieces) == 4:
return pieces[1], pieces[2], pieces[3], True return pieces[1], pieces[2], pieces[3], True
else: else:
print("Illegal resolution command") print("Illegal resolution command")
return None, None return None, None
except TypeError: except TypeError:
return None, None return None, None
if __name__ == '__main__': if __name__ == '__main__':
args = sys.argv args = sys.argv
token = args[1] token = args[1]
file_path = args[2] file_path = args[2]
configs = Configs(file_path) configs = Configs(file_path)
databaseSession = DatabaseSession() databaseSession = DatabaseSession()
play_dao = PlayDAO() play_dao = PlayDAO()
guess_dao = GuessDAO() guess_dao = GuessDAO()
bot.run(token) bot.run(token)

View File

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

View File

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