예제 #1
0
    def __init__(self):
        """Initializes the PSBot class

        Setups up the commands, usernotes, and opens the websocket to the
        main pokemonshowdown server hosted on https://play.pokemonshowdown.com.
        """
        self.usernotes = MessageDatabase()
        PokemonShowdownBot.__init__(
            self, 'ws://sim.psim.us:8000/showdown/websocket',
            self.splitMessage)
        self.invoker = CommandInvoker()
예제 #2
0
 def __init__(self):
     self.do = Command
     self.gameCommands = GameCommands
     self.usernotes = MessageDatabase()
     PokemonShowdownBot.__init__(self,
                                 'ws://sim.smogon.com:8000/showdown/websocket',
                                 self.splitMessage)
예제 #3
0
    def __init__(self):
        """Initializes the PSBot class

        Setups up the commands, usernotes, and opens the websocket to the
        main pokemonshowdown server hosted on https://play.pokemonshowdown.com.
        """
        self.do = Command
        self.learn = CommandLearn
        self.usernotes = MessageDatabase()
        PokemonShowdownBot.__init__(self,
                                    'ws://sim.psim.us:8000/showdown/websocket',
                                    self.splitMessage)
예제 #4
0
class PSBot(PokemonShowdownBot):
    """Mainly a wrapper class for the Robot class, implementing required methods.

    This implements the major method required: splitMessage. Also manages the
    delegation of tasks to their respective handlers. Most of the underlying
    functionality and/or function calls can be found in the inherited class
    PokemonShowdownBot in robot.py.

    You should expect to be somewhat familiar with the PS protocols outlined here:
    https://github.com/Zarel/Pokemon-Showdown/blob/master/PROTOCOL.md

    For the sake of simplicity, we assume that the command char used is ~ in the documentation.
    Utility like functions are placed in the PokemonShowdownBot class, to make this handler
    class look cleaner.

    Attributes:
        do: Command method, handles command behaviour (i.e. ~git returns a url to this project)
        usernotes: MessageDatabase, which handles/logs all PMs sent from users
    """
    def __init__(self):
        """Initializes the PSBot class

        Setups up the commands, usernotes, and opens the websocket to the
        main pokemonshowdown server hosted on https://play.pokemonshowdown.com.
        """
        self.usernotes = MessageDatabase()
        PokemonShowdownBot.__init__(
            self, 'ws://sim.psim.us:8000/showdown/websocket',
            self.splitMessage)
        self.invoker = CommandInvoker()

    def splitMessage(self, ws, message):
        """ Decides bot behaviour wth the server based on the content from the websocket.

        This method is the modified splitMessage that is passed to the open
        websocket in the PokemonShowdownBot class. This method splits the string given
        by the websocket and delegates the tasks to the corresponding interfaces.

        Args:
            ws: websocket, websocket object we are receiving information from.
            message: string,  information given by the websocket.
        Returns:
            None.
        Raises:
            By default, nothing is raised. But handlers which this method delegates tasks to
            may produce exceptions, so best follow the path to the individual module.
        """
        if not message: return
        if '\n' not in message: self.parseMessage(message, '')

        room = ''
        msg = message.split('\n')
        if msg[0].startswith('>'):
            room = msg[0][1:]
        msg.pop(0)

        if room.startswith('battle-'):
            if room not in self.rooms:
                # Battle rooms don't need the same interface as chatrooms
                self.rooms[room] = True
            if 'deinit' in msg[0]:
                self.rooms.pop(room)
            # Go to battle handler instead of regular rooms
            # (I don't allow commands in battle rooms anyway)
            try:
                for m in msg:
                    self.bh.parse(room, m)
            except AttributeError as e:
                import traceback
                print('AttributeError: {}'.format(e))
                traceback.print_tb(e.__traceback__)
                print('MESSAGE THAT CAUSED IT:\n{}'.format(msg))
            return

        for m in msg:
            self.parseMessage(m, room)

    def handleJoin(self, room, message):
        """ Handles new users entering a room.

        Args:
            room: Room object, room this message was received from.
            message: string, string produced from user joining this room.
        Returns:
            None.
        Raises:
            None.
        """
        if self.userIsSelf(message[1:]):
            room.rank = message[0]
            room.doneLoading()
        user = User(message, message[0], self.isOwner(message))
        if not room.addUser(user):
            return self.takeAction(
                room.title, user, 'roomban',
                "You are blacklisted from this room, so please don't come here."
            )

        # If the user have a message waiting, tell them that in a pm
        if self.usernotes.shouldNotifyMessage(user.id):
            self.sendPm(user.id, self.usernotes.pendingMessages(user.id))

    def parseMessage(self, msg, roomName):
        """Parses the message given by a user and delegates the tasks further

        This is where we handle the parsing of all the non-battle related PS protocols.
        Tasks like user related queries (i.e. commands) are delegated to the Command method.
        And Showdown tournaments are handled in their own handler in the plugins module.
        Likewise for the MessageDatabase interface.

        Args:
            msg: String, string produced from interacting with the websocket connected
                 to the PS server. Example: "|c:|1467521329| bb8nu|random chat message".
            roomName: String, name of the room that the message came from.
        Returns:
            None.
        Raises:
            None.
        """
        if not msg.startswith('|'): return
        message = self.escapeMessage(msg).split('|')
        room = self.getRoom(roomName)

        # we got messages from a room we aren't in?
        if not room:
            if not roomName:
                room = Room('Empty')
            else:
                self.rooms[roomName] = Room(roomName)

        # Logging in
        if message[1] == 'challstr':
            print('{name}: Attempting to login...'.format(name=self.name))
            self.login(message[3], message[2])

        elif message[1] == 'updateuser':
            self.updateUser(message[2], message[3])

        # Challenges
        elif 'updatechallenges' in message[1]:
            challs = json.loads(message[2])
            if challs['challengesFrom']:
                opp = [
                    name for name, form in challs['challengesFrom'].items()
                ][0]
                format = challs['challengesFrom'][opp]
                if format in self.bh.supportedFormats:
                    team = self.bh.getRandomTeam(format)
                    self.send('|/utm {}'.format(team))
                    self.send('|/accept {name}'.format(name=opp))
                else:
                    self.sendPm(
                        opp,
                        "Sorry, I can't accept challenges in that format :(")
        elif 'updatesearch' in message[1]:
            # This gets sent before `updatechallenges` does when receiving a battle, but it's
            # not useful for anything, so just return straight away
            return

        # This is a safeguard for l and n in case that a moderation action happen
        elif 'unlink' == message[1] or 'uhtml' in message[
                1] or 'html' == message[1]:
            return

        # Room was left in some way other than through ~leave
        elif 'deinit' == message[1] and room:
            self.rooms.pop(room.title)

        elif 'popup' == message[1]:
            print(message[2].replace('||', '\n\t'))

        elif 'noinit' == message[1]:
            # we didn't join the room for some other reason (doesn't exist/roombanned)
            self.rooms.pop(room.title, None)

        # As long as the room have a roomintro (which even groupchats do now)
        # Roomintros are also the last thing that is sent when joining a room
        # so when this show up, assume the room is loaded
        elif 'raw' == message[1]:
            if message[2].startswith(
                    '<div class="infobox infobox-roomintro"><div class="infobox-limited">'
            ):
                room.doneLoading()

        # Joined new room
        elif 'users' in message[1]:
            for user in message[2].split(',')[1:]:
                room.addUser(User(user[1:], user[0], self.isOwner(user)))
            # If PS doesn't tell us we joined, this still give us our room rank
            room.rank = message[2][message[2].index(self.name) - 1]

        elif 'j' in message[1].lower():
            if room.loading: return
            self.handleJoin(room, message[2])

        elif 'l' == message[1].lower() or 'leave' == message[1].lower():
            if room.loading: return
            if self.userIsSelf(message[2][1:]):
                # This is just a failsafe in case the bot is forcibly removed from a room.
                # Any other memory release required is handeled by the room destruction
                if roomName in self.rooms:
                    self.rooms.pop(room.title)
                return
            userid = self.toId(message[2])
            room.removeUser(userid)
        elif 'n' in message[1].lower() and len(message[1]) < 3:
            if room.loading: return
            # Keep track of your own rank
            # When demoting / promoting a user the server sends a |N| message to update the userlist
            if self.userIsSelf(message[2][1:]):
                room.rank = message[2][0]
            newUser = User(message[2][1:], message[2][0],
                           self.isOwner(message[2]))
            room.renamedUser(self.toId(message[3]), newUser)
            self.handleJoin(room, message[2])

        # Chat messages
        elif 'c' in message[1].lower():
            if room.isHistory(message): return
            user = room.getUser(self.toId(message[3]))
            if not user: return
            if self.userIsSelf(user.id): return

            room.logChat(user, message[4], message[2])

            saidMessage = '|'.join(message[4:])
            if saidMessage.startswith(
                    self.commandchar) and saidMessage[1:] and (
                        saidMessage[1].isalpha() or saidMessage[1] == '!'):
                command = self.extractCommand(saidMessage)
                self.log('Command', saidMessage, user.id)

                res = self.invoker.execute(
                    self, command, saidMessage[len(command) + 1:].lstrip(),
                    user, room)
                if not res.text or res.text == 'NoAnswer': return

                if self.userHasPermission(user, self.details['broadcastrank']
                                          ) or res.ignoreBroadcastPermission:
                    if not res.ignoreEscaping:
                        res.text = self.escapeText(res.text)
                    self.reply(room.title, user, res.text, res.samePlace)

                elif res.canPmReply:
                    self.sendPm(user.id, self.escapeText(res.text))
                else:
                    self.sendPm(user.id,
                                'Please pm the command for a response.')

            # Test room punishments after commands
            anything = room.moderation.shouldAct(message[4], user, message[2])
            if anything and self.canPunish(room):
                action, reason = room.moderation.getAction(
                    room, user, anything, message[2])
                self.takeAction(room.title, user, action, reason)

            if type(room.activity) == Workshop:
                room.activity.logSession(room.title, user.rank + user.name,
                                         message[4])

        elif 'pm' in message[1].lower():
            user = User(message[2][1:], message[2][0],
                        self.isOwner(message[2]))
            if self.userIsSelf(user.id): return

            if message[4].startswith('/invite'):
                if not message[4][8:] == 'lobby':
                    if user.hasRank('+'):
                        self.joinRoom(message[4][8:])
                        self.log('Invite', message[4], user.id)
                    else:
                        self.sendPm(
                            user.id,
                            'Only global voices (+) and up can add me to rooms, sorry :('
                        )

            message[4] = '|'.join(message[4:])
            if message[4].startswith(self.commandchar) and message[4][1:] and (
                    message[4][1].isalpha() or message[4][1] == '!'):
                command = self.extractCommand(message[4])
                self.log('Command', message[4], user.id)
                params = message[4][len(command) +
                                    len(self.commandchar):].lstrip()

                response = self.invoker.execute(self, command, params, user,
                                                Room('pm'))

                if not response.text or response.text == 'NoAnswer': return
                self.sendPm(user.id, response.text)

        # Tournaments
        elif 'tournament' == message[1]:
            if 'create' in message[2]:
                room.createTour(self.ws, message[3], self.bh)

                if room.loading: return
                # Tour was created, join it if in supported formats
                if self.details[
                        'joinTours'] and room.tour.format in self.bh.supportedFormats:
                    room.tour.joinTour()
            elif 'end' == message[2]:
                if not room.loading:
                    winners, tier = room.getTourWinner(message[3])
                    if self.name in winners:
                        message = 'I won the {form} tournament :o'.format(
                            form=tier)
                        if len(winners) > 1:
                            winners.remove(self.name)
                            message += '\nCongratulations to {others} for also winning :)'.format(
                                others=', '.join(winners))
                        self.say(room.title, message)
                    else:
                        self.say(
                            room.title,
                            'Congratulations to {name} for winning :)'.format(
                                name=', '.join(winners)))
                room.endTour()
            elif 'forceend' in message[2]:
                room.endTour()
            else:
                # This is for general tournament updates
                if not room.tour or room.loading: return
                room.tour.onUpdate(message[2:])
예제 #5
0
class PSBot(PokemonShowdownBot):
    def __init__(self):
        self.do = Command
        self.gameCommands = GameCommands
        self.usernotes = MessageDatabase()
        PokemonShowdownBot.__init__(self,
                                    'ws://sim.smogon.com:8000/showdown/websocket',
                                    self.splitMessage)

    def splitMessage(self, ws, message):
        if not message: return
        if '\n' not in message: self.parseMessage(message, '')

        room = ''
        msg = message.split('\n')
        if msg[0].startswith('>'):
            room = msg[0][1:]
        msg.pop(0)

        if room.startswith('battle-'):
            if room not in self.details['rooms']:
                # Battle rooms don't need the same interface as chatrooms
                self.details['rooms'][room] = True
            if 'leave|{me}'.format(me = self.details['user']) == msg[0]:
                self.details['rooms'].pop(room)
            # Go to battle handler instead of regular rooms
            # (we don't allow commands in battle rooms anyway)
            for m in msg:
                self.bh.parse(room, m)
            return

        for m in msg:
            self.parseMessage(m, room)

    def parseMessage(self, msg, room):
        if not msg.startswith('|'): return
        message = msg.split('|')
       
        # Logging in
        if message[1] == 'challstr':
            print('{name}: Attempting to login...'.format(name = self.details['user']))
            self.login(message[3], message[2])

        elif message[1] == 'updateuser':
            if self.details['user'] not in message[2]: return
            if message[3] not in '1':
                print('login failed, still guest')
                print('crashing now; have a nice day :)')
                exit()

            if self.details['avatar'] >= 0:
                self.send('|/avatar {num}'.format(num = self.details['avatar']))
            print('{name}: Successfully logged in.'.format(name = self.details['user']))
            for room in self.details['joinRooms']:
                name = [n for n in room][0] # joinRoom entry is a list of dicts
                self.joinRoom(name, room[name])

        # Challenges
        elif 'updatechallenges' in message[1]:
            challs = json.loads(message[2])
            if challs['challengesFrom']:
                opp = [name for name, form in challs['challengesFrom'].items()][0]
                if challs['challengesFrom'][opp] in supportedFormats:
                    self.send('|/accept {name}'.format(name = opp))
                else:
                    self.sendPm(opp, 'Sorry, I only accept challenges in Challenge Cup 1v1, Random Battles or Battle Factory :(')

        # This is a safeguard for l and n in case that a moderation action happen
        elif 'unlink' == message[1]:
            return

        # Joined new room
        elif 'users' in message[1]:
            users = ','.join([u[0]+re.sub(r'[^a-zA-z0-9,]', '',u[1:]).lower() for u in message[2].split(',') if message[2].split(',').index(u) > 0])
            self.getRoom(room).addUserlist(users)

        elif 'j' in message[1].lower():
            if self.userIsSelf(message[2][1:]):
                self.details['rooms'][room].rank = message[2][0]
                self.getRoom(room).doneLoading()
            user = re.sub(r'[^a-zA-z0-9]', '', message[2]).lower()
            self.details['rooms'][room].addUser(user, message[2][0])
            # If the user have a message waiting, send that message in a pm
            if self.usernotes.hasMessage(user):
                self.sendPm(user, self.usernotes.getMessage(user))
        elif 'l' in message[1].lower():
            if self.userIsSelf(message[2][1:]): return
            user = re.sub(r'[^a-zA-z0-9]', '', message[2]).lower()
            self.details['rooms'][room].removeUser(user)
        elif 'n' in message[1].lower() and len(message[1]) < 3:
            newName = message[2][0] + re.sub(r'[^a-zA-z0-9]', '', message[2]).lower()
            oldName = re.sub(r'[^a-zA-z0-9]', '', message[3]).lower()
            self.details['rooms'][room].renamedUser(oldName, newName)

 
        # Chat messages            
        elif 'c' in message[1].lower():
            user = {'name':re.sub(r'[^a-zA-z0-9]', '', message[3]).lower(),'group':message[3][0], 'unform': message[3][1:]}
            room = self.getRoom(room)
            if room.loading: return
            if user['name'] not in room.users: return
            if self.userIsSelf(user['unform']): return

            if room.moderate and moderation.canPunish(self, room.title):
                anything = moderation.shouldAct(message[4], user, room.title, message[2])   
                if anything:
                    action, reason = moderation.getAction(user, anything, message[2])
                    # If the current rank isn't allowed to roomban, keep hourmuting them
                    if action == 'roomban' and not moderation.canBan(self, room.title):
                        action = 'hourmute'
                    self.log(action, user['name'])
                    self.takeAction(room.title, user['name'], action, reason)


            if message[4].startswith(self.details['command']) and message[4][1:] and message[4][1].isalpha():            
                command = self.extractCommand(message[4])
                self.log(message[4], user['name'])

                response, samePlace = '', True
                # If the command was a chat game and permissions aren't met, kill the game (even if it just started)
                if command in self.gameCommands:
                    if not room.allowGames:
                        response = 'This room does not support chatgames.'
                    if 'new' in message[4] and command in ['hangman']:
                        response = "Please use Pm to start a hangman game."
                        
                if not response:
                    response, samePlace = self.do(self, command, room.title, message[4][len(command) + 1:].lstrip(), user)
                if response == 'NoAnswer': return

                if self.evalPermission(user) or command in self.gameCommands:
                    if response:
                        self.reply(room.title, user, self.escapeText(response), samePlace)
                    else:
                        self.reply(room.title, user, '{cmd} is not a valid command.'.format(cmd = command), samePlace)
                elif command in CanPmReplyCommands:
                    self.sendPm(user['name'], self.escapeText(response))
                else:
                    self.sendPm(user['name'], 'Please pm the commands for a response.')

        elif 'pm' in message[1].lower():
            user = {'name':re.sub(r'[^a-zA-z0-9]', '', message[2]).lower(),'group':message[2][0], 'unform': message[2][1:]}
            if self.userIsSelf(user['unform']): return

            if message[4].startswith('/invite'):
                if not message[4][8:] == 'lobby':
                    if self.Groups[user['group']] >= 1:
                        self.joinRoom(message[4][8:])
                        self.log(message[4], user['name'])
                    else:
                        self.sendPm(user['name'], 'Only global voices (+) and up can add me to rooms, sorry :(')

            if message[4].startswith(self.details['command']) and message[4][1:] and message[4][1].isalpha():
                command = self.extractCommand(message[4])
                self.log(message[4], user['name'])
                params = message[4][len(command) + len(self.details['command']):].lstrip()

                response, where = '', False
                if command in self.gameCommands:
                    if params.startswith('new,'):
                        room = params[len('new,'):].split(',')[0].replace(' ','')
                        if not self.getRoom(room).allowGames:
                            response = 'This room does not support chat games'
                        else:
                            user['group'] = self.getRoom(room).users[user['name']]
                            response, where = self.do(self, command, room, params, user)
                            self.reply(room, user, response, where)
                            return
                    elif params.startswith('score'):
                        response, where = self.do(self, command, 'pm', params, user)
                    else:
                        response = "Don't try to play games in pm please"
                if not response:
                    response, where = self.do(self, command, 'room', params, user)
                    
                if response:
                    self.sendPm(user['name'], response)
                else:
                    self.sendPm(user['name'], '{cmd} is not a valid command.'.format(cmd = command))

        # Tournaments
        elif 'tournament' == message[1]:
            if self.getRoom(room).loading: return
            if 'create' in message[2]:
                # Tour was created, join it if in supported formats
                if not self.details['joinTours']: return
                room = self.getRoom(room)
                if not room.tour:
                    room.createTour(self.ws)
                if room.tour and message[3] in supportedFormats:
                    room.tour.joinTour()
            elif 'end' == message[2]:
                if not self.getRoom(room).tour: return
                winner, tier = self.getRoom(room).tour.getWinner(message[3])
                if self.details['user'] in winner:
                    self.say(room, 'I won the {form} tournament :o'.format(form = tier))
                else:
                    self.say(room, 'Congratulations to {name} for winning :)'.format(name = ', '.join(winner)))
                self.getRoom(room).endTour()
            elif 'forceend' in message[2]:
                self.getRoom(room).endTour()
                self.say(room, "Aww, now nobody will win :(")
            else:
                if self.getRoom(room).tour:
                    self.getRoom(room).tour.onUpdate(message[2:])
예제 #6
0
class PSBot(PokemonShowdownBot):
    def __init__(self):
        self.do = Command
        self.usernotes = MessageDatabase()
        PokemonShowdownBot.__init__(self,
                                    'ws://sim.smogon.com:8000/showdown/websocket',
                                    self.splitMessage)

    def splitMessage(self, ws, message):
        if not message: return
        if '\n' not in message: self.parseMessage(message, '')

        room = ''
        msg = message.split('\n')
        if msg[0].startswith('>'):
            room = msg[0][1:]
        msg.pop(0)

        if room.startswith('battle-'):
            if room not in self.details['rooms']:
                # Battle rooms don't need the same interface as chatrooms
                self.details['rooms'][room] = True
            if 'deinit' in msg[0]:
                self.details['rooms'].pop(room)
            # Go to battle handler instead of regular rooms
            # (we don't allow commands in battle rooms anyway)
            for m in msg:
                self.bh.parse(room, m)
            return

        for m in msg:
            self.parseMessage(m, room)

    def handleJoin(self, room, message):
        if self.userIsSelf(message[1:]):
            room.rank = message[0]
            room.doneLoading()
        user = re.sub(r'[^a-zA-z0-9]', '', message).lower()
        if moderation.shouldBan(self, user, room):
            self.takeAction(room.title, user, 'roomban', "You are blacklisted from this room, so please don't come here.")
            return
        room.addUser(user, message[0])
        # If the user have a message waiting, tell them that in a pm
        if self.usernotes.shouldNotifyMessage(user):
            self.sendPm(user, self.usernotes.pendingMessages(user))

    def parseMessage(self, msg, roomName):
        if not msg.startswith('|'): return
        message = msg.split('|')
        room = self.getRoom(roomName)

        # Logging in
        if message[1] == 'challstr':
            print('{name}: Attempting to login...'.format(name = self.details['user']))
            self.login(message[3], message[2])

        elif message[1] == 'updateuser':
            self.updateUser(message[2], message[3])

        # Challenges
        elif 'updatechallenges' in message[1]:
            challs = json.loads(message[2])
            if challs['challengesFrom']:
                opp = [name for name, form in challs['challengesFrom'].items()][0]
                if challs['challengesFrom'][opp] in supportedFormats:
                    self.send('|/accept {name}'.format(name = opp))
                else:
                    self.sendPm(opp, 'Sorry, I only accept challenges in Challenge Cup 1v1, Random Battles or Battle Factory :(')
        elif 'updatesearch' in message[1]:
            # This gets sent before `updatechallenges` does when recieving a battle, but it's
            # not useful for anything, so just return straight away
            return

        # This is a safeguard for l and n in case that a moderation action happen
        elif 'unlink' == message[1] or 'uhtml' in message[1] or 'html' == message[1]:
            return

        # As long as the room have a roomintro (whih even groupchats do now)
        # Roomintros are also the last thing that is sent when joining a room
        # so when this show up, assume the room is loaded
        elif 'raw' == message[1]:
            if message[2].startswith('<div class="infobox infobox-roomintro"><div class="infobox-limited">'):
                room.doneLoading()

        # Joined new room
        elif 'users' in message[1]:
            room.makeUserlist(message[2])
            # If PS doesn't tell us we joined, this still give us our room rank
            room.rank = message[2][message[2].index(self.details['user']) - 1]

        elif 'j' in message[1].lower():
            self.handleJoin(room, message[2])

        elif 'l' == message[1].lower() or 'leave' == message[1].lower():
            if self.userIsSelf(message[2][1:]):
                # This is just a failsafe in case the bot is forcibly removed from a room.
                # Any other memory release required is handeled by the room destruction
                if roomName in self.details['rooms']:
                    self.details['rooms'].pop(roomName)
                return
            user = re.sub(r'[^a-zA-z0-9]', '', message[2]).lower()
            room.removeUser(user)
        elif 'n' in message[1].lower() and len(message[1]) < 3:
            # Keep track of your own rank
            # When demoting / promoting a user the server sends a |N| message to update the userlist
            if message[2][1:] == self.details['user']:
                room.rank = message[2][0]
            newName = message[2][0] + re.sub(r'[^a-zA-z0-9]', '', message[2]).lower()
            oldName = re.sub(r'[^a-zA-z0-9]', '', message[3]).lower()
            room.renamedUser(oldName, newName)


        # Chat messages
        elif 'c' in message[1].lower():
            user = {'name':re.sub(r'[^a-zA-z0-9]', '', message[3]).lower(),'group':message[3][0], 'unform': message[3][1:]}
            if room.loading: return
            if user['name'] not in room.users: return
            if self.userIsSelf(user['unform']): return

            if room.moderate and self.canPunish(room):
                anything = moderation.shouldAct(message[4], user, room, message[2])
                if anything:
                    action, reason = moderation.getAction(self, room, user, anything, message[2])
                    self.log('Action', action, user['name'])
                    self.takeAction(room.title, user['name'], action, reason)


            if message[4].startswith(self.details['command']) and message[4][1:] and message[4][1].isalpha():
                command = self.extractCommand(message[4])
                self.log('Command', message[4], user['name'])

                response, samePlace = '', True
                # If the command was a chat game and permissions aren't met, kill the game (even if it just started)
                if command in GameCommands:
                    if not room.allowGames:
                        response = 'This room does not support chatgames.'

                if not response:
                    response, samePlace = self.do(self, command, room, message[4][len(command) + 1:].lstrip(), user)
                if response == 'NoAnswer': return

                if self.evalPermission(user) or command in IgnoreBroadcastPermission:
                    if command not in IgnoreEscaping:
                        response = self.escapeText(response)
                    self.reply(room.title, user, response, samePlace)

                elif command in CanPmReplyCommands:
                    self.sendPm(user['name'], self.escapeText(response))
                else:
                    self.sendPm(user['name'], 'Please pm the commands for a response.')

            if type(room.game) == Workshop:
                room.game.logSession(room.title, user['group'] + user['unform'], message[4])


        elif 'pm' in message[1].lower():
            user = {'name':re.sub(r'[^a-zA-z0-9]', '', message[2]).lower(),'group':message[2][0], 'unform': message[2][1:]}
            if self.userIsSelf(user['unform']): return

            if message[4].startswith('/invite'):
                if not message[4][8:] == 'lobby':
                    if self.Groups[user['group']] >= 1:
                        self.joinRoom(message[4][8:])
                        self.log('Invite', message[4], user['name'])
                    else:
                        self.sendPm(user['name'], 'Only global voices (+) and up can add me to rooms, sorry :(')

            if message[4].startswith(self.details['command']) and message[4][1:] and message[4][1].isalpha():
                command = self.extractCommand(message[4])
                self.log('Command', message[4], user['name'])
                params = message[4][len(command) + len(self.details['command']):].lstrip()

                response = ''
                if command in GameCommands:
                    if params.startswith('score'):
                        response, where = self.do(self, command, Room('pm'), params, user)
                    else:
                        response = "Don't try to play games in pm please"
                if not response:
                    response, where = self.do(self, command, Room('pm'), params, user)

                if response:
                    self.sendPm(user['name'], response)
                else:
                    self.sendPm(user['name'], '{cmd} is not a valid command.'.format(cmd = command))

        # Tournaments
        elif 'tournament' == message[1]:
            if room.loading: return
            if 'create' in message[2]:
                if not room.tour:
                    room.createTour(self.ws, message[3])
                # Tour was created, join it if in supported formats
                if not self.details['joinTours']: return
                if room.tour and room.tour.format in supportedFormats:
                    room.tour.joinTour()
            elif 'end' == message[2]:
                if not room.tour: return
                winner, tier = room.tour.getWinner(message[3])
                if self.details['user'] in winner:
                    self.say(room.title, 'I won the {form} tournament :o'.format(form = tier))
                else:
                    self.say(room.title, 'Congratulations to {name} for winning :)'.format(name = ', '.join(winner)))
                room.endTour()
            elif 'forceend' in message[2]:
                room.endTour()
            else:
                if room.tour:
                    room.tour.onUpdate(message[2:])
예제 #7
0
class PSBot(PokemonShowdownBot):
    """Mainly a wrapper class for the Robot class, implementing required methods.

    This implements the major method required: splitMessage. Also manages the
    delegation of tasks to their respective handlers. Most of the underlying
    functionality and/or function calls can be found in the inherited class
    PokemonShowdownBot in robot.py.

    You should expect to be somewhat familiar with the PS protocols outlined here:
    https://github.com/Zarel/Pokemon-Showdown/blob/master/PROTOCOL.md

    For the sake of simplicity, we assume that the command char used is ~ in the documentation.
    Utility like functions are placed in the PokemonShowdownBot class, to make this handler
    class look cleaner.

    Attributes:
        do: Command method, handles command behaviour (i.e. ~git returns a url to this project)
        learn: Command method, sends data to commands which change behaviour
               (i.e. ~markov uses chat data to generate sentences).
        usernotes: MessageDatabase, which handles/logs all PMs sent from users
    """
    def __init__(self):
        """Initializes the PSBot class

        Setups up the commands, usernotes, and opens the websocket to the
        main pokemonshowdown server hosted on https://play.pokemonshowdown.com.
        """
        self.do = Command
        self.learn = CommandLearn
        self.usernotes = MessageDatabase()
        PokemonShowdownBot.__init__(self,
                                    'ws://sim.psim.us:8000/showdown/websocket',
                                    self.splitMessage)

    def splitMessage(self, ws, message):
        """ Decides bot behaviour wth the server based on the content from the websocket.

        This method is the modified splitMessage that is passed to the open
        websocket in the PokemonShowdownBot class. This method splits the string given
        by the websocket and delegates the tasks to the corresponding interfaces.

        Args:
            ws: websocket, websocket object we are receiving information from.
            message: string,  information given by the websocket.
        Returns:
            None.
        Raises:
            By default, nothing is raised. But handlers which this method delegates tasks to
            may produce exceptions, so best follow the path to the individual module.
        """
        if not message:
            return
        if '\n' not in message:
            self.parseMessage(message, '')

        room = ''
        msg = message.split('\n')
        if msg[0].startswith('>'):
            room = msg[0][1:]
        msg.pop(0)

        if room.startswith("battle-"):
            if room not in self.rooms:
                # Battle rooms don't need the same interface as chatrooms
                self.rooms[room] = True
            if "deinit" in msg[0]:
                self.rooms.pop(room)
            # Go to battle handler instead of regular rooms
            # (I don't allow commands in battle rooms anyway)
            try:
                for m in msg:
                    self.bh.parse(room, m)
            except AttributeError as e:
                import traceback
                print('AttributeError: {}'.format(e))
                traceback.print_tb(e.__traceback__)
                print('MESSAGE THAT CAUSED IT:\n{}'.format(msg))
            return
        # here we handle things like commands or saving user data
        for m in msg:
            self.parseMessage(m, room)

    def handleJoin(self, room, message):
        """ Handles new users entering a room.

        Args:
            room: Room object, room this message was received from.
            message: string, string produced from user joining this room.
        Returns:
            None.
        Raises:
            None.
        """
        if self.userIsSelf(message[1:]):
            room.rank = message[0]
            room.doneLoading()
        user = User(message, message[0], self.isOwner(message))
        if not room.addUser(user):
            return self.takeAction(room.title, user, 'roomban',
                                   "You are blacklisted from this room, so please don't come here.")

        # If the user have a message waiting, tell them that in a pm
        if self.usernotes.shouldNotifyMessage(user.id):
            self.sendPm(user.id, self.usernotes.pendingMessages(user.id))

    def parseMessage(self, msg, roomName):
        """Parses the message given by a user and delegates the tasks further

        This is where we handle the parsing of all the non-battle related PS protocols.
        Tasks like user related queries (i.e. commands) are delegated to the Command method.
        And Showdown tournaments are handled in their own handler in the plugins module.
        Likewise for the MessageDatabase interface.

        Args:
            msg: String, string produced from interacting with the websocket connected
                 to the PS server. Example: "|c:|1467521329| bb8nu|random chat message".
            roomName: String, name of the room that the message came from.
        Returns:
            None.
        Raises:
            None.
        """
        if not msg.startswith('|'):
            return
        message = self.escapeMessage(msg).split('|')
        room = Room('Empty') if not roomName else self.getRoom(roomName)

        # Logging in
        if message[1] == "challstr":
            print("{name}: Attempting to login...".format(name=self.name))
            self.login(message[3], message[2])

        elif message[1] == "updateuser":
            self.updateUser(message[2], message[3])

        # Challenges
        elif "updatechallenges" in message[1]:
            challs = json.loads(message[2])
            if challs['challengesFrom']:
                opp = [name for name, form in challs['challengesFrom'].items()][0]
                format = challs['challengesFrom'][opp]
                if format in self.bh.supportedFormats:
                    team = self.bh.getRandomTeam(format)
                    self.send('|/utm {}'.format(team))
                    self.send('|/accept {name}'.format(name=opp))
                else:
                    self.sendPm(opp, "Sorry, I can't accept challenges in that format :(")
        elif 'updatesearch' in message[1]:
            # This gets sent before `updatechallenges` does when receiving a battle, but it's
            # not useful for anything, so just return straight away
            return

        # This is a safeguard for l and n in case that a moderation action
        # happen
        elif("unlink" == message[1] or "uhtml" in message[1] or
             "html" == message[1]):
            return

        # Room was left in some way other than through ~leave
        elif 'deinit' == message[1]:
            self.rooms.pop(roomName)

        # As long as the room have a roomintro (which even groupchats do now)
        # Roomintros are also the last thing that is sent when joining a room
        # so when this show up, assume the room is loaded
        elif "raw" == message[1]:
            if message[2].startswith(('<div class="infobox infobox-roomintro">'
                                      '<div class="infobox-limited">')):
                room.doneLoading()

        # Joined new room
        elif 'users' in message[1]:
            for user in message[2].split(',')[1:]:
                room.addUser(User(user[1:], user[0], self.isOwner(user)))
            # If PS doesn't tell us we joined, this still give us our room rank
            room.rank = message[2][message[2].index(self.name) - 1]

        elif "j" in message[1].lower():
            self.handleJoin(room, message[2])

        elif "l" == message[1].lower() or "leave" == message[1].lower():
            if self.userIsSelf(message[2][1:]):
                # This is just a failsafe in case the bot is forcibly removed
                # from a room. Any other memory release required is handeled by
                # the room destruction
                if roomName in self.rooms:
                    self.rooms.pop(roomName)
                return
            userid = self.toId(message[2])
            room.removeUser(userid)
        elif "n" in message[1].lower() and len(message[1]) < 3:
            # Keep track of your own rank
            # When demoting / promoting a user the server sends a |N| message
            # to update the userlist
            if self.userIsSelf(message[2][1:]):
                room.rank = message[2][0]
            newUser = User(message[2][1:], message[2][0],
                           self.isOwner(message[2]))
            room.renamedUser(self.toId(message[3]), newUser)
            self.handleJoin(room, message[2])
        # Chat messages
        elif "c" in message[1].lower():

            if room.loading:
                return
            user = room.getUser(self.toId(message[3]))
            if not user:
                return
            if self.userIsSelf(user.id):
                return

            # perform moderation on user content
            room.logChat(user, message[2])
            saidMessage = '|'.join(message[4:])

            # this message follows the command structure i.e. '~send hi'
            if saidMessage.startswith(self.commandchar) and saidMessage[1:] and saidMessage[1].isalpha():
                command = self.extractCommand(saidMessage)
                self.log('Command', saidMessage, user.id)

                res = self.do(self, command, room, saidMessage[len(command) + 1:].lstrip(), user)
                if not res.text or res.text == 'NoAnswer':
                    return

                if self.userHasPermission(user, room.broadcast_rank) or res.ignoreBroadcastPermission:
                    if not res.ignoreEscaping:
                        res.text = self.escapeText(res.text)
                    self.reply(room.title, user, res.text, res.samePlace)

                elif not self.userHasPermission(user, room.broadcast_rank):
                    self.sendPm(user.id, 'You do not have broadcast rank, requires {}'.format(room.broadcast_rank))

                elif res.canPmReply:
                    self.sendPm(user.id, self.escapeText(res.text))
                else:
                    self.sendPm(user.id, 'Please pm the command for a response.')
            else:
                # this message should be considered to be any normal message found in chat
                self.learn(room, user, saidMessage)

            # Test room punishments after commands
            anything = room.moderation.shouldAct(message[4], user, message[2])
            if anything and self.canPunish(room):
                action, reason = room.moderation.getAction(room, user, anything, message[2])
                self.takeAction(room.title, user, action, reason)

            if type(room.activity) == Workshop:
                room.activity.logSession(room.title, user.rank + user.name, message[4])

        elif 'pm' in message[1].lower():
            user = User(message[2][1:], message[2][0], self.isOwner(message[2]))
            if self.userIsSelf(user.id):
                return
            if message[4].startswith("/invite"):
                if not message[4][8:] == "lobby":
                    if user.hasRank("&"):
                        self.joinRoom(message[4][8:])
                        self.log("Invite", message[4], user.id)
                    else:
                        self.sendPm(user.id, "Only global leaders (&) and up can add me to rooms, sorry :(")

            message[4] = '|'.join(message[4:])
            if message[4].startswith(self.commandchar) and message[4][1:] and message[4][1].isalpha():
                command = self.extractCommand(message[4])
                self.log('Command', message[4], user.id)
                params = message[4][len(command) + len(self.commandchar):]
                params = params.lstrip()
                response = self.do(self, command, Room('pm'), params, user)
                if not response.text or response.text == 'NoAnswer':
                    return
                self.sendPm(user.id, response.text)

        # Tournaments
        elif 'tournament' == message[1]:
            if 'create' in message[2]:
                room.createTour(self.ws, message[3], self.bh)

                if room.loading:
                    return
                # Tour was created, join it if in supported formats
                if details.joinTours and room.tour.format in self.bh.supportedFormats:
                    room.tour.joinTour()
            elif 'end' == message[2]:
                if not room.loading:
                    winner, tier = room.getTourWinner(message[3])
                    if self.name in winner:
                        self.say(room.title, 'I won the {form} tournament :o'.format(form=tier))
                    else:
                        self.say(room.title, 'Congratulations to {name} for winning :)'.format(name=', '.join(winner)))
                room.endTour()
            elif "forceend" in message[2]:
                room.endTour()
            else:
                # This is for general tournament updates
                if not room.tour or room.loading:
                    return
                room.tour.onUpdate(message[2:])