예제 #1
0
    def __init__(self):
        self.session = Session()

        config_file = "config\\options.ini"
        self.config = self.open_config(config_file)
        self.bound_channel = None

        self.available_commands = {
            "help": self.help,
            "events": self.events,
            "rsvp": self.rsvp,
            "cancel": self.cancel,
            "suggest": self.suggest,
            "suggestions": self.suggestions,
            "vote": self.vote,
            "power": self.power,
            "ping": self.ping,
            # admin commands
            'create_event': self.create_event,
            'cancel_event': self.cancel_event,
            "start_vote": self.start_vote,
            "clear_suggestions": self.clear_suggestions,
            "clear_messages": self.clear_messages,
            "end_vote": self.end_vote
        }

        super().__init__()
예제 #2
0
파일: Member.py 프로젝트: dr1s/Tabletop-Bot
from sqlalchemy import Column, Integer, String

from bot.Base import Base, Session

session = Session()


class Member(Base):
    __tablename__ = 'members'

    id = Column(Integer(), primary_key=True)
    discord_id = Column(Integer(), index=True)  # This could be primary key, maybe in a future build
    name = Column(String(64), index=True)
    power = Column(Integer())
예제 #3
0
class TabletopBot(discord.Client):
    def __init__(self):
        self.session = Session()

        config_file = "config\\options.ini"
        self.config = self.open_config(config_file)
        self.bound_channel = None

        self.available_commands = {
            "help": self.help,
            "events": self.events,
            "rsvp": self.rsvp,
            "cancel": self.cancel,
            "suggest": self.suggest,
            "suggestions": self.suggestions,
            "vote": self.vote,
            "power": self.power,
            "ping": self.ping,
            # admin commands
            'create_event': self.create_event,
            'cancel_event': self.cancel_event,
            "start_vote": self.start_vote,
            "clear_suggestions": self.clear_suggestions,
            "clear_messages": self.clear_messages,
            "end_vote": self.end_vote
        }

        super().__init__()

    def run(self):
        try:
            self.loop.run_until_complete(
                self.start(self.config["_login_token"]))
        except ClientOSError:
            asyncio.sleep(60)
            self.run()

    @staticmethod
    def open_config(config_file):
        config_parser = configparser.ConfigParser()
        config_parser.read(config_file)

        # TODO Fall-backs
        config = {
            "_login_token": config_parser.get('Credentials', 'Token'),
            "owner_id": int(config_parser.get('Permissions', 'OwnerID')),
            "command_prefix": config_parser.get('Chat', 'CommandPrefix'),
            "bound_channels": int(config_parser.get('Chat', 'BindToChannels')),
            "mention_group_id": config_parser.get('Chat', 'MentionGroupID')
        }
        return config

    async def on_ready(self):
        print('Logged in as ' + self.user.name)
        self.bound_channel = self.get_channel(self.config['bound_channels'])
        print("Bound to: " + self.bound_channel.name)

        poll = self.session.query(GamePoll).first()
        if poll is not None and poll.active:
            time_left = (poll.finish_time - datetime.now()).total_seconds()
            if time_left <= 0:
                time_left = 0

            end_time = datetime.now() + timedelta(seconds=time_left)
            print("Voting over at: " + end_time.strftime("%c"))
            await asyncio.sleep(time_left)
            await self.finalize_vote()

    async def on_message(self, message):
        if message.channel.id != self.config['bound_channels']:
            return
        if len(message.content) < 1:
            return
        if message.content[0] != self.config["command_prefix"]:
            return

        print(str(message.author) + ": " + message.content)
        command = message.content[1:].split(" ")

        command_title = command[0].lower()

        try:
            await self.available_commands[command_title](message, command)
        except KeyError:
            await self.send_message_safe(self.bound_channel,
                                         "Not a valid command",
                                         0,
                                         delete=False)

    async def on_message_edit(self, before, after):
        if before.content != after.content:
            await self.on_message(after)

    async def ping(self, message, command):
        await self.send_message_safe(self.bound_channel,
                                     'Pong!',
                                     0,
                                     delete=False)

    async def events(self, message, command):
        all_events = self.session \
            .query(Event.id, Event.name, Event.date, Event.game_decided, Event.winning_game_id,
                   func.count(RSVP.id).label("count")) \
            .outerjoin(RSVP) \
            .filter(Event.date > datetime.now()) \
            .group_by(Event.id) \
            .all()
        if not all_events:
            message_to_send = "There are no events planned!"
            await self.send_message_safe(self.bound_channel, message_to_send,
                                         30)
            return
        message_to_send = "Upcoming Events:"
        for event in all_events:
            event_datetime = event.date.strftime("%c")
            if event.game_decided:
                winning_game = self.session.query(Game).filter(
                    Game.id == event.winning_game_id).first()
                message_to_send += "\n{0.id}) {0.name} at {1} with {0.count} attending ".format(
                    event, event_datetime)
                message_to_send += "playing " + winning_game.title + " | <" + winning_game.url + ">"
            else:
                message_to_send += "\n{0.id}) {0.name} at {1} with {0.count} attending.".format(
                    event, event_datetime)
        await self.send_message_safe(self.bound_channel,
                                     message_to_send,
                                     0,
                                     delete=False)

    async def suggest(self, message, command):
        try:
            bgg_query = command[1]
            bgg_query_long = command[1:]
        except IndexError:
            message_to_send = "The format needs to be !suggest [Suggestion]"
            await self.send_message_safe(self.bound_channel, message_to_send,
                                         30)
            return

        game_id = await self.get_game_id(bgg_query, bgg_query_long)
        game_database_entry = self.session.query(Game).filter(
            Game.bgg_id == game_id).first()
        if game_database_entry is None:
            game_info = self.generate_suggestion(game_id)
            game_database_entry = Game(
                bgg_id=game_info["id"],
                url=game_info["url"],
                title=game_info["game_title"],
                playtime=game_info["playtime"],
                description=game_info["description"],
                image_url=game_info["image_url"],
                best_players=game_info["best"],
                recommended_players=game_info["recommended"])
            self.session.add(game_database_entry)
            self.session.commit()
        else:
            game_info = game_database_entry.get_game_info()
        previous_suggestion = self.session.query(
            Suggestion.id, Member.name).join(Member).filter(
                Suggestion.game_id == game_database_entry.id).first()
        if previous_suggestion is not None:
            message_to_send = "This game has already been suggested by " + previous_suggestion.name
            print(message_to_send)
            await self.send_message_safe(self.bound_channel, message_to_send,
                                         30)
            return

        # make lowest possible vote_number
        results = self.session.query(Suggestion.vote_number).order_by(
            Suggestion.vote_number).all()
        open_vote_number = 1
        current_vote_numbers = []
        for result in results:
            current_vote_numbers.append(result.vote_number)

        while True:
            if open_vote_number in current_vote_numbers:
                open_vote_number += 1
                continue
            else:
                break

        member = self.get_member(message)

        self.session.add(
            Suggestion(author_id=member.id,
                       vote_number=open_vote_number,
                       game_id=game_database_entry.id,
                       number_lost=0))
        self.session.commit()

        await self.output_suggestion_game_info(game_info)

        poll_active = self.session.query(GamePoll.active).first()
        if poll_active:
            suggestions_list = []
            for suggestion in self.session.query(Suggestion.vote_number,
                                                 Game.title,
                                                 Game.url).join(Game).all():
                message_to_send = str(suggestion.vote_number) + ") " + suggestion.title + " | <" + \
                                  suggestion.url + ">"
                suggestions_list.append(message_to_send)

            combined_suggestions = "\n".join(suggestions_list)
            this_message = await self.send_message_safe(self.bound_channel,
                                                        combined_suggestions,
                                                        0,
                                                        delete=False)
            self.session.add(Message(message_id=this_message.id))
            self.session.commit()

    async def help(self, message, command):
        string_list = [
            "---Command List---\n", "!help", "Displays the Command List\n",
            "!events", "Displays all future events\n", "!rsvp [Event ID]",
            "RSVP to a specific Event, also allows you to vote\n",
            "!cancel [Event ID]", "Cancels a RSVP to an Event\n",
            "!suggest [suggestion]", "Suggest a board game to play",
            "[suggestion] can be a game title to search, a BoardGameGeek game url, "
            + "or the numerical game ID from the url\n", "!suggestions",
            "Display all the board games suggested so far\n", "!vote",
            "Vote on a game once voting has begun. If your game isn't picked, your voting power increases by one for"
            + " next time, otherwise it reset to one\n", "!power",
            "Display your current voting power\n", "!ping",
            "Check if the bot is running, it responds with pong\n",
            "---Owner Commands---",
            "!create_event [YYYY-MM-DD] [2359] [Event Name]",
            "Creates an event with a given date, time, and name\n",
            "!cancel_event [Event ID]", "Cancels the given event\n",
            "!start_vote [Event ID] [Hours]",
            "Begin voting on the selected event, ending in [Hours] hours\n",
            "!end_vote", "Immediately end voting\n", "!clear_suggestions",
            "Clear all Suggestions\n", "!clear_messages",
            "Delete the last 1000 messages in the channel that are not pinned"
        ]

        message_to_send = "\n".join(string_list)
        await self.send_message_safe(self.bound_channel,
                                     message_to_send,
                                     0,
                                     delete=False)
        return

    async def rsvp(self, message, command):
        try:
            event_id = command[1]
        except IndexError:
            message_to_send = "You need to event an Event ID"
            await self.send_message_safe(self.bound_channel, message_to_send,
                                         30)
            return

        # check if it is a real event
        this_event = self.session.query(Event).filter(
            Event.id == event_id).first()
        if this_event is None:
            message_to_send = "This is not a valid Event ID"
            await self.send_message_safe(self.bound_channel, message_to_send,
                                         30)
            return
        # check if already rsvp'd
        member = self.get_member(message)
        previous_rsvp = self.session.query(RSVP).filter(
            RSVP.member_id == member.id, RSVP.event_id == event_id).first()
        if previous_rsvp is not None:
            message_to_send = "You already RSVP'd, but now you can be sure!"
            await self.send_message_safe(self.bound_channel, message_to_send,
                                         30)
            return
        new_rsvp = RSVP(member_id=member.id, event_id=event_id)
        self.session.add(new_rsvp)
        self.session.commit()

        current_player_count = len(
            self.session.query(RSVP).filter(RSVP.event_id == event_id).all())
        if current_player_count == 1:
            count_message = "is currently 1 person"
        else:
            count_message = "are currently {0} people".format(
                current_player_count)
        message_to_send = "Ok I've got you down! There {0} attending".format(
            count_message)
        await self.send_message_safe(self.bound_channel,
                                     message_to_send,
                                     0,
                                     delete=False)
        return

    async def cancel(self, message, command):
        try:
            event_id = command[1]
        except IndexError:
            message_to_send = "You need to event an Event ID"
            await self.send_message_safe(self.bound_channel, message_to_send,
                                         30)
            return

        # check if it is a real event
        this_event = self.session.query(Event).filter(
            Event.id == event_id).first()
        if this_event is None:
            message_to_send = "This is not a valid Event ID"
            await self.send_message_safe(self.bound_channel, message_to_send,
                                         30)
            return

        # check if rsvp'd
        member = self.get_member(message)
        previous_rsvp = self.session.query(RSVP).filter(
            RSVP.member_id == member.id, RSVP.event_id == event_id).first()
        if previous_rsvp is None:
            message_to_send = "You never RSVP'd, so we know your aren't coming!"
            await self.send_message_safe(self.bound_channel, message_to_send,
                                         30)
            return

        self.session.delete(previous_rsvp)
        self.session.commit()

        current_player_count = len(
            self.session.query(RSVP).filter(RSVP.event_id == event_id).all())
        if current_player_count == 1:
            count_message = "1 person"
        else:
            count_message = "{0} people".format(current_player_count)
        message_to_send = "Sorry to hear you have to cancel! There is now {0} attending".format(
            count_message)
        await self.send_message_safe(self.bound_channel,
                                     message_to_send,
                                     0,
                                     delete=False)
        return

    async def suggestions(self, message, command):
        current_suggestions = self.session.query(Game).join(Suggestion).all()
        if not current_suggestions:
            message_to_send = "There are currently no suggestions!"
            await self.send_message_safe(self.bound_channel, message_to_send,
                                         60)
            return
        for suggestion in current_suggestions:
            await self.output_suggestion_game_info(suggestion.get_game_info())

    async def vote(self, message, command):
        this_poll = self.session.query(GamePoll).first()
        if not this_poll:
            message_to_send = "Voting has not yet begun!"
            await self.send_message_safe(self.bound_channel, message_to_send,
                                         10)
            return

        # check if rsvp
        this_member = self.get_member(message)
        this_rsvp = self.session.query(GamePoll).join(Event).join(RSVP).join(
            Member).filter(Member.id == this_member.id).first()
        if not this_rsvp:
            message_to_send = "You can't vote if you didn't RSVP!"
            await self.send_message_safe(self.bound_channel, message_to_send,
                                         10)
            return

        try:
            game_vote = command[1]
        except IndexError:
            message_to_send = "You didn't pick anything!"
            await self.send_message_safe(self.bound_channel, message_to_send,
                                         30)
            return

        if not game_vote.isdigit():
            message_to_send = "You need to vote using a number!"
            await self.send_message_safe(self.bound_channel, message_to_send,
                                         10)
            return
        if self.session.query(Suggestion).filter(
                Suggestion.vote_number == command[1]).first() is None:
            message_to_send = "Not a valid vote!"
            await self.send_message_safe(self.bound_channel, message_to_send,
                                         10)
            return

        # check if already voted, delete old vote
        old_vote = self.session.query(Vote).join(Member).filter(
            Vote.member_id == this_member.id).first()
        if old_vote is not None:
            self.session.delete(old_vote)
            self.session.commit()

        # add new vote
        suggestion = self.session.query(Suggestion).filter(
            Suggestion.vote_number == int(game_vote)).first()
        new_vote = Vote(member_id=this_member.id, suggestion_id=suggestion.id)
        self.session.add(new_vote)
        self.session.commit()

        # get current totals
        current_vote_totals = self.get_current_vote_totals()

        # display current totals
        message_list = []
        for this_vote_total in current_vote_totals:
            message_list.append(
                str(this_vote_total.vote_number) + ") " +
                this_vote_total.title + " votes: " +
                str(this_vote_total.vote_quantity))
        message_to_send = "\n".join(message_list)
        this_message = await self.send_message_safe(self.bound_channel,
                                                    message_to_send,
                                                    0,
                                                    delete=False)
        self.session.add(Message(message_id=this_message.id))

        self.session.commit()

    async def power(self, message, command):
        member = self.get_member(message)
        member_power = member.power
        if member_power == 1:
            count_message = "1 vote"
        else:
            count_message = str(member_power) + " votes"
        message_to_send = "Your vote currently counts as " + count_message
        await self.send_message_safe(self.bound_channel, message_to_send, 60)
        return

    async def start_vote(self, message, command):
        if message.author.id != self.config["owner_id"]:
            message_to_send = "You don't have permission to start_vote"
            await self.send_message_safe(self.bound_channel, message_to_send,
                                         10)
            return

        poll_active = self.session.query(GamePoll.active).first()
        if poll_active:
            message_to_send = "Voting has already begun!"
            await self.send_message_safe(self.bound_channel, message_to_send,
                                         10)
            await self.delete_message(message)
            return

        try:
            event_id = command[1]
            hours_string = command[2]
        except IndexError:
            message_to_send = "Needs to use the format !start_vote [Event ID] [Hours]"
            await self.send_message_safe(self.bound_channel, message_to_send,
                                         30)
            return

        if hours_string.isdigit() is False:
            message_to_send = "Need a numerical parameter for hours!"
            await self.send_message_safe(self.bound_channel, message_to_send,
                                         30)
            await self.delete_message(message)
            return

        if self.session.query(Suggestion).first() is None:
            message_to_send = "There are no suggestions!"
            await self.send_message_safe(self.bound_channel, message_to_send,
                                         30)
            await self.delete_message(message)
            return

        this_event = self.session.query(Event).filter(
            Event.id == event_id).first()
        if this_event is None:
            message_to_send = "Event with that ID not found!"
            await self.send_message_safe(self.bound_channel, message_to_send,
                                         30)
            await self.delete_message(message)
            return

        if this_event.game_decided:
            message_to_send = "This event has already selected a game!"
            await self.send_message_safe(self.bound_channel, message_to_send,
                                         30)
            await self.delete_message(message)
            return

        voting_duration = int(hours_string)
        voting_over = datetime.now() + timedelta(hours=voting_duration)

        self.session.add(
            GamePoll(active=True, finish_time=voting_over, event_id=event_id))

        event_rsvps = self.session \
            .query(Event.id, func.count(RSVP.id).label("count")) \
            .outerjoin(RSVP) \
            .filter(Event.id == event_id) \
            .group_by(Event.id) \
            .first()
        rsvp_count = event_rsvps.count

        directions_string = self.get_mention_group_string() + "\n" + \
            " It's time to vote on games for " + this_event.name + "!" + \
            " Use the !vote command followed by the game's id below (e.g. !vote 1).\n" + \
            " Voting ends at " + voting_over.strftime("%H:%M %Z on %m/%d") + "\n" + \
            " There are currently " + str(rsvp_count) + " attendees, so keep player counts in mind." + \
            " There will be multiple groups if there are enough players to do so." + \
            " RSVP count isn't finalized, as anyone can cancel or join last minute."
        directions_message = await self.send_message_safe(self.bound_channel,
                                                          directions_string,
                                                          0,
                                                          delete=False)
        self.session.add(Message(message_id=directions_message.id))

        suggestions_list = []
        for suggestion in self.session.query(Suggestion.vote_number,
                                             Game.title,
                                             Game.url).join(Game).all():
            message_to_send = str(suggestion.vote_number) + ") " + suggestion.title + " | <" + \
                              suggestion.url + ">"
            suggestions_list.append(message_to_send)

        combined_suggestions = "\n".join(suggestions_list)
        this_message = await self.send_message_safe(self.bound_channel,
                                                    combined_suggestions,
                                                    0,
                                                    delete=False)
        self.session.add(Message(message_id=this_message.id))
        self.session.commit()

        await self.delete_message(message)
        await asyncio.sleep((voting_duration * 60 * 60) - (5 * 60))

        message_to_send = self.get_mention_group_string(
        ) + " 5 minutes left to vote!"
        this_message = await self.send_message_safe(self.bound_channel,
                                                    message_to_send,
                                                    0,
                                                    delete=False)
        self.session.add(Message(message_id=this_message.id))
        self.session.commit()

        await asyncio.sleep(5 * 60)
        await self.finalize_vote()

    async def end_vote(self, message, command):
        if message.author.id != self.config["owner_id"]:
            message_to_send = "You don't have permission to delete_all"
            await self.send_message_safe(self.bound_channel, message_to_send,
                                         10)
            return

        await self.finalize_vote()

    async def create_event(self, message, command):
        if message.author.id != self.config["owner_id"]:
            message_to_send = "You don't have permission to delete_all"
            await self.send_message_safe(self.bound_channel, message_to_send,
                                         10)
            return
        try:
            date_string = command[1]
            time_string = command[2]
            name_string = " ".join(command[3:])
        except IndexError:
            message_to_send = "Not the correct format: !create_event YYYY-MM-DD 2359 event_name"
            await self.send_message_safe(self.bound_channel, message_to_send,
                                         30)
            return

        datetime_string = date_string + " " + time_string
        try:
            event_date_time = datetime.strptime(
                datetime_string, "%Y-%m-%d %H%M")  # YYYY-MM-DD 2359
        except ValueError:
            print(datetime_string)
            message_to_send = "Not a valid date and time format (YYYY-MM-DD 2359)"
            await self.send_message_safe(self.bound_channel, message_to_send,
                                         30)
            return

        if event_date_time < datetime.now():
            message_to_send = "Event can't be in the past!"
            await self.send_message_safe(self.bound_channel, message_to_send,
                                         30)
            return

        new_event = Event(date=event_date_time, name=name_string)
        self.session.add(new_event)
        self.session.commit()

        event_date_long = new_event.date.strftime("%A %B %d at %I:%M %p")
        event_time_delta = new_event.date - datetime.now()
        delta_days = event_time_delta.days
        days_string = "1 day" if delta_days == 1 else str(delta_days) + " days"
        delta_hours = int(event_time_delta.seconds / 3600)
        if delta_hours == 0:
            hours_string = ""
        elif delta_hours == 1:
            hours_string = " 1 hour"
        else:
            hours_string = " " + str(delta_hours) + " hours"
        if delta_days == 0 and delta_hours == 0:
            time_till_event_string = " less than an hour"
        else:
            time_till_event_string = days_string + hours_string
        message_to_send = self.get_mention_group_string() + "\n" + \
            "New Event Created: ({0.id}) {0.name}\n".format(new_event) + \
            "Event Date: {0}\n".format(event_date_long) + \
            "Time till Event: {0}.".format(time_till_event_string)
        await self.send_message_safe(self.bound_channel,
                                     message_to_send,
                                     0,
                                     delete=False)
        return

    async def cancel_event(self, message, command):
        if message.author.id != self.config["owner_id"]:
            message_to_send = "You don't have permission to cancel_event"
            await self.send_message_safe(self.bound_channel, message_to_send,
                                         10)
            return
        try:
            event_id = command[1]
        except IndexError:
            message_to_send = "You need to enter an event id!"
            await self.send_message_safe(self.bound_channel, message_to_send,
                                         30)
            return
        result = self.session.query(Event).filter(Event.id == event_id).first()
        self.session.delete(result)

        # delete associated GamePoll, Votes, Messages, and RSVPs
        game_poll = self.session.query(GamePoll).filter(
            GamePoll.event_id == event_id).first()
        if game_poll is not None:
            self.session.delete(game_poll)
            votes = self.session.query(Vote).all()
            for item in votes:
                self.session.delete(item)
            await self.delete_saved_messages()
        event_rsvps = self.session.query(RSVP).filter(
            RSVP.event_id == event_id).all()
        if event_rsvps is not None:
            for item in event_rsvps:
                self.session.delete(item)

        self.session.commit()
        message_to_send = "Done"
        await self.send_message_safe(self.bound_channel, message_to_send, 10)
        return

    async def clear_suggestions(self, message, command):
        if message.author.id != self.config["owner_id"]:
            message_to_send = "You don't have permission to delete_all"
            await self.send_message_safe(self.bound_channel, message_to_send,
                                         10)
            return
        for suggestion in self.session.query(Suggestion).all():
            self.session.delete(suggestion)
        self.session.commit()
        message_to_send = "Done"
        await self.send_message_safe(self.bound_channel, message_to_send, 10)
        return

    async def clear_messages(self, message, command):
        if message.author.id != self.config["owner_id"]:
            message_to_send = "You don't have permission to delete_all"
            await self.send_message_safe(self.bound_channel, message_to_send,
                                         10)
            return

        def is_pinned(m):
            for this_pinned_message in pinned_messages:
                if m.id == this_pinned_message.id:
                    return False
            return True

        pinned_messages = await message.channel.pins()
        await message.channel.purge(limit=1000, check=is_pinned)
        for item in self.session.query(Message).all():
            self.session.delete(item)
        self.session.commit()

    async def finalize_vote(self):
        current_vote_totals = self.get_current_vote_totals()
        if current_vote_totals is None:
            print("No vote totals")
            game_poll = self.session.query(GamePoll).first()
            if game_poll is None or game_poll.active is False:
                return
            self.session.delete(game_poll)
            for vote in self.session.query(Vote).all():
                self.session.delete(vote)
            # delete all messages related to this poll
            await self.delete_saved_messages()
            message_to_send = "Voting ended with no winner."
            await self.send_message_safe(self.bound_channel, message_to_send,
                                         30)
            return
        winner = current_vote_totals[0]

        # announce winner
        message_to_send = self.get_mention_group_string() + " " + winner.title + " won with " + \
            str(winner.vote_quantity)
        if winner.vote_quantity == 1:
            message_to_send += " vote!"
        else:
            message_to_send += " votes!"

        message_to_send += " | <" + winner.url + ">"

        await self.send_message_safe(self.bound_channel,
                                     message_to_send,
                                     0,
                                     delete=False)

        # delete all messages related to this poll
        await self.delete_saved_messages()

        # if your vote lost, increase vote power
        losing_voters = self.session.query(Member).join(Vote).filter(
            Vote.suggestion_id != winner.id).all()
        for voter in losing_voters:
            voter.power += 1

        # if suggestion won, reset vote power
        winning_voters = self.session.query(Member).join(Vote).filter(
            Vote.suggestion_id == winner.id).all()
        for item in winning_voters:
            item.power = 1

        # delete all votes
        for vote in self.session.query(Vote).all():
            self.session.delete(vote)
        self.session.commit()

        # delete suggestion if it has lost 5 or more times in a row
        for losing_suggestion in current_vote_totals[1:]:
            this_suggestion = self.session.query(Suggestion).filter(
                Suggestion.id == losing_suggestion.id).first()
            new_number_lost = this_suggestion.number_lost + 1
            if new_number_lost >= 5:
                self.session.delete(this_suggestion)
            else:
                this_suggestion.number_lost = new_number_lost
        self.session.commit()

        # update number_lost on winning suggestion
        winning_suggestion = self.session.query(Suggestion).filter(
            Suggestion.id == current_vote_totals[0].id).first()
        winning_suggestion.number_lost = 0
        self.session.commit()

        game_poll = self.session.query(GamePoll).first()
        if game_poll is None or game_poll.active is False:
            return

        # Update the event: The game has been decided
        this_event = self.session.query(Event).filter(
            Event.id == game_poll.event_id).first()
        if this_event is None:
            print("Missing event in finalize_vote")
        this_event.game_decided = True
        this_event.winning_game_id = winning_suggestion.game_id

        self.session.delete(game_poll)
        self.session.commit()

    def get_current_vote_totals(self):
        if self.session.query(Vote).first() is None:
            return None
        result = self.session.query(
            Suggestion.id,
            Suggestion.vote_number,
            Game.bgg_id.label("game_id"),
            Game.title,
            Game.url,
            func.sum(Member.power).label('vote_quantity')) \
            .join(Game) \
            .outerjoin(Vote).outerjoin(Member) \
            .group_by(Suggestion.id) \
            .order_by(desc('vote_quantity')) \
            .all()
        return result

    # gets member or creates one if they don't exist
    def get_member(self, message):
        member = self.session.query(Member).filter(
            Member.name == str(message.author)).first()
        if member is None:
            member = Member(discord_id=message.author.id,
                            name=str(message.author),
                            power=1)
            self.session.add(member)
            self.session.commit()
        return member

    async def output_suggestion_game_info(self, game_info):
        if len(game_info["description"]) > 2044:
            output_description = game_info["description"][0:2043] + "..."
        else:
            output_description = game_info["description"]

        embed = discord.Embed(title=game_info["game_title"],
                              type="rich",
                              url=game_info["url"])
        embed.set_image(url=game_info["image_url"])
        embed.add_field(name="Playtime", value=game_info["playtime"])
        embed.add_field(name="Recommended", value=game_info["recommended"])
        embed.add_field(name="Best with", value=game_info["best"] + " players")
        embed.set_footer(text=output_description)

        await self.send_message(self.bound_channel, content=None, embed=embed)

    @staticmethod
    def generate_suggestion(game_id):
        url_xml = "https://boardgamegeek.com/xmlapi/boardgame/" + game_id
        page = requests.get(url_xml)
        soup = BeautifulSoup(page.content, 'xml')
        # print(soup.prettify())

        description = html.unescape(soup.find("description").text).replace(
            "<br/>", "\n")
        image_url = soup.find("image").text

        suggested_players_poll = soup.find("poll",
                                           {"name": "suggested_numplayers"})
        results = suggested_players_poll.find_all("results")
        result_dictionary = {}
        for result in results:
            result_dictionary[result.attrs["numplayers"]] = {
                "Best":
                int(
                    result.find("result", {
                        "value": "Best"
                    }).attrs["numvotes"]),
                "Recommended":
                int(
                    result.find("result", {
                        "value": "Recommended"
                    }).attrs["numvotes"]),
                "Not Recommended":
                int(
                    result.find("result", {
                        "value": "Not Recommended"
                    }).attrs["numvotes"]),
            }

        result_conclusions = {
            "Best": {
                "number of players": 0,
                "votes": 0
            },
            "Recommended": {
                "min-title": "N/A",
                "min-value": 1000,
                "max-title": "N/A",
                "max-value": -1
            }
        }
        for number_of_players, suggestions in result_dictionary.items():
            # best first
            if suggestions["Best"] > result_conclusions["Best"]["votes"]:
                result_conclusions["Best"] = {
                    "number of players": number_of_players,
                    "votes": suggestions["Best"]
                }
            if suggestions["Best"] + suggestions["Recommended"] > suggestions[
                    "Not Recommended"]:
                if number_of_players.find("+") != -1:
                    result_conclusions["Recommended"][
                        "max-title"] = number_of_players
                    result_conclusions["Recommended"]["max-value"] = int(
                        number_of_players[:-1]) + 1
                else:
                    int_players = int(number_of_players)
                    if int_players > result_conclusions["Recommended"][
                            "max-value"]:
                        result_conclusions["Recommended"][
                            "max-value"] = int_players
                        result_conclusions["Recommended"][
                            "max-title"] = number_of_players
                    if int_players < result_conclusions["Recommended"][
                            "min-value"]:
                        result_conclusions["Recommended"][
                            "min-value"] = int_players
                        result_conclusions["Recommended"][
                            "min-title"] = number_of_players

        recommended_string = str(
            result_conclusions["Recommended"]["min-title"]) + "-" + str(
                result_conclusions["Recommended"]["max-title"]) + " Players"

        min_playtime = soup.find("minplaytime").text
        max_playtime = soup.find("maxplaytime").text
        if min_playtime == max_playtime:
            playtime = min_playtime + " minutes"
        else:
            playtime = min_playtime + "-" + max_playtime + " minutes"

        game_info = {
            "id": game_id,
            "url": "https://www.boardgamegeek.com/boardgame/" + game_id,
            "game_title": soup.find("name", {
                "primary": "true"
            }).text,
            "playtime": playtime,
            "description": description,
            "image_url": image_url,
            "best": result_conclusions["Best"]["number of players"],
            "recommended": recommended_string
        }

        return game_info

    async def send_message_safe(self,
                                channel,
                                output_string,
                                timeout,
                                delete=True):
        response_message = await self.send_message(channel, output_string)
        if delete:
            await asyncio.sleep(timeout)
            try:
                await self.delete_message(response_message)
            except discord.errors.NotFound:
                print("message already deleted")
            return
        else:
            return response_message

    async def send_message(self, channel, content=None, **kwargs):
        return await channel.send(content=content, **kwargs)

    @staticmethod
    async def delete_message(message):
        await message.delete()

    async def get_game_id(self, bgg_query, bgg_query_long):
        regex_url = re.search(
            r'^https://(?:www\.)?boardgamegeek\.com/boardgame/([\d]+)[\w\d-]*',
            bgg_query)
        if regex_url:
            game_id = regex_url.group(1)
        else:
            regex_game_id = re.fullmatch(r'[\d]*', bgg_query)
            if regex_game_id:
                game_id = bgg_query
            else:
                search_string = ""
                for part in bgg_query_long:
                    if search_string != "":
                        search_string += "%20"
                    search_string += part
                url_xml = "https://www.boardgamegeek.com/xmlapi/search?search=" + search_string
                page = requests.get(url_xml)
                soup = BeautifulSoup(page.content, 'xml')
                this_game = soup.boardgame
                if this_game is None:
                    message_to_send = "No game found!"
                    print(message_to_send)
                    await self.send_message_safe(self.bound_channel,
                                                 message_to_send, 30)
                    return
                game_id = this_game.attrs["objectid"]
        return game_id

    async def delete_saved_messages(self):
        message_objects_to_delete = self.session.query(Message).all()
        messages_to_delete = []
        for item in message_objects_to_delete:
            try:
                this_message = await self.get_message(self.bound_channel,
                                                      item.message_id)
            except discord.errors.NotFound:
                continue
            messages_to_delete.append(this_message)
            self.session.delete(item)

        number_messages_to_delete = len(messages_to_delete)
        if number_messages_to_delete >= 1:
            await self.delete_messages(messages_to_delete)
        # elif number_messages_to_delete == 1:
        #     await self.delete_message(self.messages_to_delete_after_vote[0])

    @staticmethod
    async def get_message(channel, id):
        return await channel.fetch_message(id)

    async def delete_messages(self, messages):
        await self.bound_channel.delete_messages(messages)

    def get_mention_group_string(self):
        mention_string = "<@&" + self.config['mention_group_id'] + ">"
        return mention_string