コード例 #1
0
ファイル: Hangman.py プロジェクト: lunik1/DesertBot
    def _guess(self,
               message: IRCMessage) -> Union[IRCResponse, List[IRCResponse]]:
        channel = message.replyTo.lower()
        if channel not in self.gameStates:
            return IRCResponse(
                ResponseType.Say,
                '[Hangman] no game running, use {}hangman start to begin!'.
                format(self.bot.commandChar), message.replyTo)

        responses = []
        gs = self.gameStates[channel]

        guess = message.parameters.lower()
        # single letter
        if len(guess) == 1:
            try:
                correct = gs.guessLetter(guess)
            except (AlreadyGuessedException, InvalidCharacterException) as e:
                return self._exceptionFormatter(e, message.replyTo)
        # whole phrase
        else:
            try:
                correct = gs.guessPhrase(guess)
            except (WrongPhraseLengthException,
                    PhraseMismatchesGuessesException,
                    PhraseUsesKnownBadLettersException) as e:
                return self._exceptionFormatter(e, message.replyTo)

        user = message.user.nick
        # split the username with a zero-width space
        # hopefully this kills client highlighting on nick mentions
        # user = user[:1] + '\u200b' + user[1:]
        # try a tiny arrow instead, some clients actually render zero-width spaces
        colUser = user[:1] + '\u034e' + user[1:]
        if correct:
            colUser = colour(A.normal[A.fg.green[colUser]])
        else:
            colUser = colour(A.normal[A.fg.red[colUser]])
        responses.append(
            IRCResponse(ResponseType.Say,
                        '{} - {}'.format(gs.render(),
                                         colUser), message.replyTo))

        if gs.finished:
            if correct:
                responses.append(
                    IRCResponse(ResponseType.Say,
                                '[Hangman] Congratulations {}!'.format(user),
                                message.replyTo))
            else:
                responses.append(
                    IRCResponse(
                        ResponseType.Say,
                        '[Hangman] {} blew up the bomb! The {} was {}'.format(
                            user, gs.wOrP(), gs.phrase), message.replyTo))
            self._stop(message, suppressMessage=True)

        return responses
コード例 #2
0
ファイル: Hangman.py プロジェクト: lunik1/DesertBot
 def _renderGuesses(self):
     colouredGuesses = []
     for g in self.guesses:
         if g in self.phrase:
             colouredGuesses.append(colour(A.bold[A.fg.green[g]]))
         else:
             colouredGuesses.append(colour(A.fg.red[g]))
     reset = colour(A.normal[''])
     return '[{}{}]'.format(''.join(colouredGuesses), reset)
コード例 #3
0
ファイル: Hangman.py プロジェクト: DesertBot/DesertBot
 def _renderGuesses(self):
     colouredGuesses = []
     for g in self.guesses:
         if g in self.phrase:
             colouredGuesses.append(colour(A.bold[A.fg.green[g]]))
         else:
             colouredGuesses.append(colour(A.fg.red[g]))
     reset = colour(A.normal[''])
     return '[{}{}]'.format(''.join(colouredGuesses), reset)
コード例 #4
0
ファイル: Twitter.py プロジェクト: Tantusar/DesertBot
    def follow(self, _: IRCMessage, url: str) -> [str, None]:
        match = re.search(
            r'twitter\.com/(?P<tweeter>[^/]+)/status(es)?/(?P<tweetID>[0-9]+)',
            url)
        if not match:
            return

        tweeter = match.group('tweeter')
        tweetID = match.group('tweetID')
        url = 'https://twitter.com/{}/status/{}'.format(tweeter, tweetID)
        response = self.bot.moduleHandler.runActionUntilValue('fetch-url', url)

        soup = BeautifulSoup(response.content, 'lxml')

        tweet = soup.find(class_='permalink-tweet')

        displayName = tweet['data-name']
        user = tweet.find(class_='username').text

        reply = tweet.find(class_='ReplyingToContextBelowAuthor')
        if reply:
            reply = 'r' + reply.text.strip()[1:]

        tweetText = tweet.find(class_='tweet-text')

        tweetTimeText = tweet.find(class_='client-and-actions').text.strip()
        try:
            tweetTimeText = time.strptime(tweetTimeText, '%I:%M %p - %d %b %Y')
            tweetTimeText = time.strftime('%Y/%m/%d %H:%M', tweetTimeText)
        except ValueError:
            pass
        tweetTimeText = re.sub(r'[\r\n\s]+', u' ', tweetTimeText)

        links = tweetText.find_all('a', {'data-expanded-url': True})
        for link in links:
            link.string = ' ' + link['data-expanded-url']

        embeddedLinks = tweetText.find_all('a', {'data-pre-embedded': 'true'})
        for link in embeddedLinks:
            link.string = ' ' + link['href']

        text = string.unescapeXHTML(tweetText.text)
        graySplitter = colour(A.normal[' ', A.fg.gray['|'], ' '])
        text = re.sub('[\r\n]+', graySplitter, text)

        formatString = str(
            colour(A.normal[A.fg.gray['[{time}]'], A.bold[
                ' {name} ({user})',
                A.normal[A.fg.gray[' {reply}']] if reply else '', ':'],
                            ' {text}']))

        return formatString.format(time=tweetTimeText,
                                   name=displayName,
                                   user=user,
                                   reply=reply,
                                   text=text), url
コード例 #5
0
ファイル: Hangman.py プロジェクト: DesertBot/DesertBot
    def _guess(self, message: IRCMessage) -> Union[IRCResponse, List[IRCResponse]]:
        channel = message.replyTo.lower()
        if channel not in self.gameStates:
            return IRCResponse(ResponseType.Say,
                               '[Hangman] no game running, use {}hangman start to begin!'
                               .format(self.bot.commandChar),
                               message.replyTo)

        responses = []
        gs = self.gameStates[channel]

        guess = message.parameters.lower()
        # single letter
        if len(guess) == 1:
            try:
                correct = gs.guessLetter(guess)
            except (AlreadyGuessedException,
                    InvalidCharacterException) as e:
                return self._exceptionFormatter(e, message.replyTo)
        # whole phrase
        else:
            try:
                correct = gs.guessPhrase(guess)
            except (WrongPhraseLengthException,
                    PhraseMismatchesGuessesException,
                    PhraseUsesKnownBadLettersException) as e:
                return self._exceptionFormatter(e, message.replyTo)

        user = message.user.nick
        # split the username with a zero-width space
        # hopefully this kills client highlighting on nick mentions
        # user = user[:1] + '\u200b' + user[1:]
        # try a tiny arrow instead, some clients actually render zero-width spaces
        colUser = user[:1] + '\u034e' + user[1:]
        if correct:
            colUser = colour(A.normal[A.fg.green[colUser]])
        else:
            colUser = colour(A.normal[A.fg.red[colUser]])
        responses.append(IRCResponse(ResponseType.Say,
                                     '{} - {}'.format(gs.render(), colUser),
                                     message.replyTo))

        if gs.finished:
            if correct:
                responses.append(IRCResponse(ResponseType.Say,
                                             '[Hangman] Congratulations {}!'.format(user),
                                             message.replyTo))
            else:
                responses.append(IRCResponse(ResponseType.Say,
                                             '[Hangman] {} blew up the bomb! The {} was {}'
                                             .format(user, gs.wOrP(), gs.phrase),
                                             message.replyTo))
            self._stop(message, suppressMessage=True)

        return responses
コード例 #6
0
ファイル: Twitter.py プロジェクト: DesertBot/DesertBot
    def follow(self, _: IRCMessage, url: str) -> [str, None]:
        match = re.search(r'twitter\.com/(?P<tweeter>[^/]+)/status(es)?/(?P<tweetID>[0-9]+)', url)
        if not match:
            return

        tweeter = match.group('tweeter')
        tweetID = match.group('tweetID')
        url = 'https://twitter.com/{}/status/{}'.format(tweeter, tweetID)
        response = self.bot.moduleHandler.runActionUntilValue('fetch-url', url)

        soup = BeautifulSoup(response.content, 'lxml')

        tweet = soup.find(class_='permalink-tweet')

        displayName = tweet['data-name']
        user = tweet.find(class_='username').text

        reply = tweet.find(class_='ReplyingToContextBelowAuthor')
        if reply:
            reply = 'r' + reply.text.strip()[1:]

        tweetText = tweet.find(class_='tweet-text')

        tweetTimeText = tweet.find(class_='client-and-actions').text.strip()
        try:
            tweetTimeText = time.strptime(tweetTimeText, '%I:%M %p - %d %b %Y')
            tweetTimeText = time.strftime('%Y/%m/%d %H:%M', tweetTimeText)
        except ValueError:
            pass
        tweetTimeText = re.sub(r'[\r\n\s]+', u' ', tweetTimeText)

        links = tweetText.find_all('a', {'data-expanded-url': True})
        for link in links:
            link.string = ' ' + link['data-expanded-url']

        embeddedLinks = tweetText.find_all('a', {'data-pre-embedded': 'true'})
        for link in embeddedLinks:
            link.string = ' ' + link['href']

        text = string.unescapeXHTML(tweetText.text)
        graySplitter = colour(A.normal[' ', A.fg.gray['|'], ' '])
        text = re.sub('[\r\n]+', graySplitter, text)

        formatString = str(colour(A.normal[A.fg.gray['[{time}]'],
                                           A.bold[' {name} ({user})',
                                                  A.normal[A.fg.gray[' {reply}']] if reply else '',
                                                  ':'],
                                           ' {text}']))

        return formatString.format(time=tweetTimeText,
                                   name=displayName,
                                   user=user,
                                   reply=reply,
                                   text=text), url
コード例 #7
0
ファイル: Mixer.py プロジェクト: lunik1/DesertBot
    def follow(self, _: IRCMessage, url: str) -> [str, None]:
        # Heavily based on Didero's DideRobot code for the same
        # https://github.com/Didero/DideRobot/blob/06629fc3c8bddf8f729ce2d27742ff999dfdd1f6/commands/urlTitleFinder.py#L37
        match = re.search(r'mixer\.com/(?P<mixerChannel>[^/]+)/?(\s|$)', url)
        if not match:
            return
        channel = match.group('mixerChannel')

        if self.mixerClientID is None:
            return '[Mixer Client ID not found]'

        chanData = {}
        channelOnline = False
        mixerHeaders = {
            'Accept': 'application/json',
            'Client-ID': self.mixerClientID
        }
        url = 'https://mixer.com/api/v1/channels/{}'.format(channel)
        response = self.bot.moduleHandler.runActionUntilValue(
            'fetch-url', url, extraHeaders=mixerHeaders)

        streamData = response.json()

        if len(streamData) > 0 and 'online' in streamData:
            chanData = streamData
            channelOnline = streamData['online']
        else:
            return

        output = []
        if channelOnline:
            name = colour(A.normal[A.fg.green['{}'.format(
                chanData['user']['username'])]])
        else:
            name = colour(A.normal[A.fg.red['{}'.format(
                chanData['user']['username'])]])
        output.append(name)
        graySplitter = colour(A.normal[' ', A.fg.gray['|'], ' '])
        title = ' "{}"'.format(
            re.sub(r'[\r\n]+', graySplitter, chanData['name'].strip()))
        output.append(title)
        game = colour(A.normal[A.fg.gray[', playing '],
                               '{}'.format(chanData['type']['name'])])
        output.append(game)
        if chanData['audience'] == "18+":
            mature = colour(A.normal[A.fg.lightRed[' [Mature]']])
            output.append(mature)
        if channelOnline:
            viewers = streamData['viewersCurrent']
            status = colour(A.normal[A.fg.green[
                ' (Live with {0:,d} viewers)'.format(viewers)]])
        else:
            status = colour(A.normal[A.fg.red[' (Offline)']])
        output.append(status)

        return ''.join(output), 'https://mixer.com/{}'.format(channel)
コード例 #8
0
ファイル: Twitch.py プロジェクト: DesertBot/DesertBot
    def follow(self, _: IRCMessage, url: str) -> [str, None]:
        # Heavily based on Didero's DideRobot code for the same
        # https://github.com/Didero/DideRobot/blob/06629fc3c8bddf8f729ce2d27742ff999dfdd1f6/commands/urlTitleFinder.py#L37
        match = re.search(r'twitch\.tv/(?P<twitchChannel>[^/]+)/?(\s|$)', url)
        if not match:
            return
        channel = match.group('twitchChannel')

        if self.twitchClientID is None:
            return '[Twitch Client ID not found]'

        chanData = {}
        channelOnline = False
        twitchHeaders = {'Accept': 'application/vnd.twitchtv.v3+json',
                         'Client-ID': self.twitchClientID}
        url = 'https://api.twitch.tv/kraken/streams/{}'.format(channel)
        response = self.bot.moduleHandler.runActionUntilValue('fetch-url', url,
                                                              extraHeaders=twitchHeaders)

        streamData = response.json()

        if 'stream' in streamData and streamData['stream'] is not None:
            chanData = streamData['stream']['channel']
            channelOnline = True
        elif 'error' not in streamData:
            url = 'https://api.twitch.tv/kraken/channels/{}'.format(channel)
            response = self.bot.moduleHandler.runActionUntilValue('fetch-url', url,
                                                                  extraHeaders=twitchHeaders)
            chanData = response.json()

        if len(chanData) == 0:
            return

        output = []
        if channelOnline:
            name = colour(A.normal[A.fg.green['{}'.format(chanData['display_name'])]])
        else:
            name = colour(A.normal[A.fg.red['{}'.format(chanData['display_name'])]])
        output.append(name)
        graySplitter = colour(A.normal[' ', A.fg.gray['|'], ' '])
        title = ' "{}"'.format(re.sub(r'[\r\n]+', graySplitter, chanData['status'].strip()))
        output.append(title)
        if chanData['game'] is not None:
            game = colour(A.normal[A.fg.gray[', playing '], '{}'.format(chanData['game'])])
            output.append(game)
        if chanData['mature']:
            mature = colour(A.normal[A.fg.lightRed[' [Mature]']])
            output.append(mature)
        if channelOnline:
            viewers = streamData['stream']['viewers']
            status = colour(A.normal[A.fg.green[' (Live with {0:,d} viewers)'.format(viewers)]])
        else:
            status = colour(A.normal[A.fg.red[' (Offline)']])
        output.append(status)

        return ''.join(output), 'https://twitch.tv/{}'.format(channel)
コード例 #9
0
ファイル: Mixer.py プロジェクト: DesertBot/DesertBot
    def follow(self, _: IRCMessage, url: str) -> [str, None]:
        # Heavily based on Didero's DideRobot code for the same
        # https://github.com/Didero/DideRobot/blob/06629fc3c8bddf8f729ce2d27742ff999dfdd1f6/commands/urlTitleFinder.py#L37
        match = re.search(r'mixer\.com/(?P<mixerChannel>[^/]+)/?(\s|$)', url)
        if not match:
            return
        channel = match.group('mixerChannel')

        if self.mixerClientID is None:
            return '[Mixer Client ID not found]'

        chanData = {}
        channelOnline = False
        mixerHeaders = {'Accept': 'application/json',
                        'Client-ID': self.mixerClientID}
        url = 'https://mixer.com/api/v1/channels/{}'.format(channel)
        response = self.bot.moduleHandler.runActionUntilValue('fetch-url', url,
                                                              extraHeaders=mixerHeaders)

        streamData = response.json()

        if len(streamData) > 0 and 'online' in streamData:
            chanData = streamData
            channelOnline = streamData['online']
        else:
            return

        output = []
        if channelOnline:
            name = colour(A.normal[A.fg.green['{}'.format(chanData['user']['username'])]])
        else:
            name = colour(A.normal[A.fg.red['{}'.format(chanData['user']['username'])]])
        output.append(name)
        graySplitter = colour(A.normal[' ', A.fg.gray['|'], ' '])
        title = ' "{}"'.format(re.sub(r'[\r\n]+', graySplitter, chanData['name'].strip()))
        output.append(title)
        game = colour(A.normal[A.fg.gray[', playing '], '{}'.format(chanData['type']['name'])])
        output.append(game)
        if chanData['audience'] == "18+":
            mature = colour(A.normal[A.fg.lightRed[' [Mature]']])
            output.append(mature)
        if channelOnline:
            viewers = streamData['viewersCurrent']
            status = colour(A.normal[A.fg.green[' (Live with {0:,d} viewers)'.format(viewers)]])
        else:
            status = colour(A.normal[A.fg.red[' (Offline)']])
        output.append(status)

        return ''.join(output), 'https://mixer.com/{}'.format(channel)
コード例 #10
0
    def execute(self, message: IRCMessage):
        if len(message.parameterList) < 3:
            return IRCResponse(ResponseType.Say, self.help(None),
                               message.replyTo)

        try:
            amount = float(message.parameterList[0])
            offset = 1
        except ValueError:
            amount = 1.0
            offset = 0

        ccFrom = message.parameterList[offset].upper()
        ccTo = message.parameterList[offset + 2:]
        ccTo = ",".join(ccTo)
        ccTo = ccTo.upper()

        url = "https://api.exchangeratesapi.io/latest"
        params = {
            'base': ccFrom,
            'symbols': ccTo,
        }
        response = self.bot.moduleHandler.runActionUntilValue('fetch-url',
                                                              url,
                                                              params=params)
        if response is None:
            return IRCResponse(
                ResponseType.Say,
                "Sorry, the currency API returned no data. Check your currencies!",
                message.replyTo)
        j = response.json()
        rates = j['rates']

        if not rates:
            return IRCResponse(
                ResponseType.Say,
                "Some or all of those currencies weren't recognized!",
                message.replyTo)

        data = []
        for curr, rate in rates.items():
            data.append("{:.2f} {}".format(rate * amount, curr))

        graySplitter = colour(A.normal[' ', A.fg.gray['|'], ' '])
        return IRCResponse(ResponseType.Say, graySplitter.join(data),
                           message.replyTo)
コード例 #11
0
ファイル: Currency.py プロジェクト: DesertBot/DesertBot
    def execute(self, message: IRCMessage):
        if len(message.parameterList) < 3:
            return IRCResponse(ResponseType.Say, self.help(None), message.replyTo)

        try:
            amount = float(message.parameterList[0])
            offset = 1
        except ValueError:
            amount = 1.0
            offset = 0

        ccFrom = message.parameterList[offset].upper()
        ccTo = message.parameterList[offset + 2:]
        ccTo = ",".join(ccTo)
        ccTo = ccTo.upper()

        url = "https://api.exchangeratesapi.io/latest"
        params = {
            'base': ccFrom,
            'symbols': ccTo,
            }
        response = self.bot.moduleHandler.runActionUntilValue('fetch-url', url, params=params)
        if response is None:
            return IRCResponse(ResponseType.Say,
                               "Sorry, the currency API returned no data. Check your currencies!",
                               message.replyTo)
        j = response.json()
        rates = j['rates']

        if not rates:
            return IRCResponse(ResponseType.Say,
                               "Some or all of those currencies weren't recognized!",
                               message.replyTo)

        data = []
        for curr, rate in rates.items():
            data.append("{:.2f} {}".format(rate*amount, curr))

        graySplitter = colour(A.normal[' ', A.fg.gray['|'], ' '])
        return IRCResponse(ResponseType.Say, graySplitter.join(data), message.replyTo)
コード例 #12
0
ファイル: Rainbow.py プロジェクト: lunik1/DesertBot
    def execute(self, message: IRCMessage):
        if len(message.parameterList) == 0:
            return IRCResponse(ResponseType.Say,
                               "You didn't give me any text to rainbow!",
                               message.replyTo)

        if message.command == 'rainbow':
            fg = True
        else:
            fg = False

        startPos = 0
        try:
            colList = [int(n) for n in message.parameterList[0].split(',')]
            startPos = len(message.parameterList[0]) + 1
        except ValueError:
            if fg:
                colList = [4, 8, 9, 11, 12, 13]
            else:
                colList = [5, 7, 8, 3, 10, 2, 6]

        if not message.parameters[startPos:]:
            return IRCResponse(ResponseType.Say,
                               "You didn't give me any text to rainbow after the colours!",
                               message.replyTo)

        outputMessage = u''

        if fg:
            for i, c in enumerate(message.parameters[startPos:]):
                outputMessage += self.colours[colList[i % len(colList)]] + c
        else:
            for i, c in enumerate(message.parameters[startPos:]):
                outputMessage += self.bgcolours[colList[i % len(colList)]] + c

        outputMessage += colour(A.normal[''])

        return IRCResponse(ResponseType.Say, outputMessage, message.replyTo)
コード例 #13
0
ファイル: Rainbow.py プロジェクト: lunik1/DesertBot
class Rainbow(BotCommand):
    def triggers(self):
        return ['rainbow', 'rrainbow']

    def help(self, query):
        return ('rainbow <text>'
                ' - outputs the specified text with rainbow colours;'
                ' rrainbow uses background colours')

    colours = [colour(A.fg.white['']),         # 0
               colour(A.fg.black['']),         # 1
               colour(A.fg.blue['']),          # 2
               colour(A.fg.green['']),         # 3
               colour(A.fg.lightRed['']),      # 4
               colour(A.fg.red['']),           # 5
               colour(A.fg.magenta['']),       # 6
               colour(A.fg.orange['']),        # 7
               colour(A.fg.yellow['']),        # 8
               colour(A.fg.lightGreen['']),    # 9
               colour(A.fg.cyan['']),          # 10
               colour(A.fg.lightCyan['']),     # 11
               colour(A.fg.lightBlue['']),     # 12
               colour(A.fg.lightMagenta['']),  # 13
               colour(A.fg.gray['']),          # 14
               colour(A.fg.lightGray['']),     # 15
               ]

    bgcolours = [colour(A.bg.white['']),         # 0
                 colour(A.bg.black['']),         # 1
                 colour(A.bg.blue['']),          # 2
                 colour(A.bg.green['']),         # 3
                 colour(A.bg.lightRed['']),      # 4
                 colour(A.bg.red['']),           # 5
                 colour(A.bg.magenta['']),       # 6
                 colour(A.bg.orange['']),        # 7
                 colour(A.bg.yellow['']),        # 8
                 colour(A.bg.lightGreen['']),    # 9
                 colour(A.bg.cyan['']),          # 10
                 colour(A.bg.lightCyan['']),     # 11
                 colour(A.bg.lightBlue['']),     # 12
                 colour(A.bg.lightMagenta['']),  # 13
                 colour(A.bg.gray['']),          # 14
                 colour(A.bg.lightGray['']),     # 15
                 ]

    def execute(self, message: IRCMessage):
        if len(message.parameterList) == 0:
            return IRCResponse(ResponseType.Say,
                               "You didn't give me any text to rainbow!",
                               message.replyTo)

        if message.command == 'rainbow':
            fg = True
        else:
            fg = False

        startPos = 0
        try:
            colList = [int(n) for n in message.parameterList[0].split(',')]
            startPos = len(message.parameterList[0]) + 1
        except ValueError:
            if fg:
                colList = [4, 8, 9, 11, 12, 13]
            else:
                colList = [5, 7, 8, 3, 10, 2, 6]

        if not message.parameters[startPos:]:
            return IRCResponse(ResponseType.Say,
                               "You didn't give me any text to rainbow after the colours!",
                               message.replyTo)

        outputMessage = u''

        if fg:
            for i, c in enumerate(message.parameters[startPos:]):
                outputMessage += self.colours[colList[i % len(colList)]] + c
        else:
            for i, c in enumerate(message.parameters[startPos:]):
                outputMessage += self.bgcolours[colList[i % len(colList)]] + c

        outputMessage += colour(A.normal[''])

        return IRCResponse(ResponseType.Say, outputMessage, message.replyTo)
コード例 #14
0
    def follow(self, _: IRCMessage, url: str) -> [str, None]:
        match = re.search(r'itch\.io/', url)
        if not match:
            return

        response = self.bot.moduleHandler.runActionUntilValue('fetch-url', url)
        soup = BeautifulSoup(response.content, 'lxml')

        if not soup.find('body', {'data-page_name': 'view_game'}):
            return

        gameMetaInfo = soup.find(class_='game_info_panel_widget')
        if not gameMetaInfo:
            return

        # extract infobox information
        def extractInfo(namePattern: str):
            r = re.compile(namePattern)
            nameMatch = gameMetaInfo.find(text=r)
            if not nameMatch:
                return
            value = nameMatch.parent.parent.find_all('td')[-1]
            return value

        updated = extractInfo(r'^\s*Updated\s*$')
        updated = updated.abbr['title'] if updated else None

        published = extractInfo(r'^\s*Published\s*$')
        published = published.abbr['title'] if published else None

        status = extractInfo(r'^\s*Status\s*$')
        status = status.text.strip() if status else None

        platforms = extractInfo(r'^\s*Platforms\s*$')
        platforms = platforms.text.strip() if platforms else None

        rating = extractInfo(r'^\s*Rating\s*$')
        rating_stars = rating.find(
            class_='star_value')['content'] if rating else None
        rating_count = rating.find(
            class_='rating_count')['content'] if rating else None

        author = extractInfo(r'^\s*Author\s*$')
        author = author.text.strip() if author else None

        genre = extractInfo(r'^\s*Genre\s*$')
        genre = genre.text.strip() if genre else None

        # extract json information
        gameInfo = soup.find_all('script',
                                 {'type': 'application/ld+json'})[-1].text
        gameInfo = json.loads(gameInfo)

        title = gameInfo['name']
        description = gameInfo['description']

        if 'offers' in gameInfo:
            price = gameInfo['offers']['price']
            currency = gameInfo['offers']['priceCurrency']
            if gameInfo['offers']['priceValidUntil']:
                pass  # fetch sale info (original price, percentage discount, end time)

        # build the output
        output = []

        output.append(colour(A.normal[title, A.fg.gray[' by '], author]))

        if genre:
            output.append(colour(A.normal['Genre: ', genre]))

        outStatus = status
        if published:
            outStatus += ', published ' + published
        if updated:
            outStatus += ', last updated ' + updated
        output.append(colour(A.normal[outStatus]))
        if rating:
            output.append(
                colour(A.normal['Rating: ', rating_stars, '/5',
                                A.fg.gray[' (', rating_count, ' ratings)']]))
        if price:
            output.append(colour(A.normal[price, ' ',
                                          currency]))  # todo: sale stuff
        else:
            output.append('Free')

        if platforms:
            output.append(platforms)
        if description:
            output.append(description)

        graySplitter = colour(A.normal[' ', A.fg.gray['|'], ' '])
        response = graySplitter.join(output)

        return response, url
コード例 #15
0
    def follow(self, _: IRCMessage, url: str) -> [str, None]:
        match = re.search(
            r'twitter\.com/(?P<tweeter>[^/]+)/status(es)?/(?P<tweetID>[0-9]+)',
            url)
        if not match:
            return

        if not self.token:
            self.getToken()

            if not self.token:
                return

        # tweeter = match.group('tweeter')
        tweetID = match.group('tweetID')

        url = 'https://api.twitter.com/1.1/statuses/show.json'
        headers = {'Authorization': f'{self.tokenType} {self.token}'}
        params = {'id': tweetID, 'tweet_mode': 'extended'}
        response = self.mhRunActionUntilValue('fetch-url',
                                              url,
                                              params=params,
                                              extraHeaders=headers)
        j = response.json()

        # replace retweets with the original tweet
        if 'retweeted_status' in j:
            j = j['retweeted_status']

        displayName = j['user']['name']
        user = j['user']['screen_name']

        if j['in_reply_to_screen_name'] and j[
                'in_reply_to_screen_name'] != user:
            reply = f"replying to @{j['in_reply_to_screen_name']}"
        else:
            reply = None

        tweetText = j['full_text']

        # replace twitter shortened links with real urls
        for url in j['entities']['urls']:
            tweetText = tweetText.replace(url['url'], url['expanded_url'])

        # replace twitter shortened embedded media links with real urls
        if 'media' in j['entities']:
            mediaDict = {}
            for media in j['extended_entities']['media']:
                if media['url'] not in mediaDict:
                    mediaDict[media['url']] = [media['media_url_https']]
                else:
                    mediaDict[media['url']].append(media['media_url_https'])
            for media, mediaURLs in mediaDict.items():
                splitter = ' · '
                mediaString = splitter.join(mediaURLs)
                tweetText = tweetText.replace(media, mediaString)

        # unescape html entities to their unicode equivalents
        tweetText = html.unescape(tweetText)

        # Thu Jan 30 16:44:15 +0000 2020
        tweetTimeText = j['created_at']
        tweetTimeText = time.strptime(tweetTimeText, '%a %b %d %H:%M:%S %z %Y')
        tweetTimeText = time.strftime('%Y/%m/%d %H:%M UTC', tweetTimeText)

        graySplitter = colour(A.normal[' ', A.fg.gray['|'], ' '])
        text = re.sub('[\r\n]+', graySplitter, tweetText)

        formatString = str(
            colour(A.normal[A.fg.gray['[{time}]'], A.bold[
                ' {name} (@{user})',
                A.normal[A.fg.gray[' {reply}']] if reply else '', ':'],
                            ' {text}']))

        return formatString.format(time=tweetTimeText,
                                   name=displayName,
                                   user=user,
                                   reply=reply,
                                   text=text), url
コード例 #16
0
ファイル: Sub.py プロジェクト: DesertBot/DesertBot
    def execute(self, message: IRCMessage):
        subString = self._mangleEscapes(message.parameters)

        try:
            segments = list(self._parseSubcommandTree(subString))
        except UnbalancedBracesException as e:
            red = colour(A.bold[A.fg.lightRed['']])
            normal = colour(A.normal[''])
            error = (subString[:e.column]
                     + red + subString[e.column]
                     + normal + subString[e.column+1:])
            error = self._unmangleEscapes(error, False)
            return [IRCResponse(ResponseType.Say,
                                "Sub Error: {}".format(e.message),
                                message.replyTo),
                    IRCResponse(ResponseType.Say, error, message.replyTo)]

        prevLevel = -1
        responseStack = []
        extraVars = {}
        metadata = {}

        for segment in segments:
            (level, command, start, end) = segment

            # We've finished executing subcommands at the previous depth,
            # so replace subcommands with their output at the current depth
            if level < prevLevel:
                command = self._substituteResponses(command, responseStack, level, extraVars, start)

            # Replace any extraVars in the command
            for var, value in extraVars.items():
                command = re.sub(r'\$\b{}\b'.format(re.escape(var)), '{}'.format(value), command)

            # Build a new message out of this segment
            inputMessage = IRCMessage(message.type, message.user, message.channel,
                                      self.bot.commandChar + command.lstrip(),
                                      self.bot,
                                      metadata=metadata)

            # Execute the constructed message
            if inputMessage.command.lower() in self.bot.moduleHandler.mappedTriggers:
                module = self.bot.moduleHandler.mappedTriggers[inputMessage.command.lower()]
                response = module.execute(inputMessage)
                """@type : IRCResponse"""
            else:
                return IRCResponse(ResponseType.Say,
                                   "'{}' is not a recognized command trigger"
                                   .format(inputMessage.command),
                                   message.replyTo)

            # Push the response onto the stack
            responseStack.append((level, response.response, start, end))
            # Update the extraVars dict
            extraVars.update(response.ExtraVars)
            metadata = self._recursiveMerge(metadata, response.Metadata)

            prevLevel = level

        responseString = self._substituteResponses(subString, responseStack, -1, extraVars, -1)
        responseString = self._unmangleEscapes(responseString)
        return IRCResponse(ResponseType.Say, responseString, message.replyTo,
                           extraVars=extraVars, metadata=metadata)
コード例 #17
0
ファイル: YouTube.py プロジェクト: lunik1/DesertBot
    def follow(self, _: IRCMessage, url: str) -> [str, None]:
        match = re.search(
            r'(youtube\.com/watch.+v=|youtu\.be/)(?P<videoID>[^&#\?]{11})',
            url)
        if not match:
            return
        videoID = match.group('videoID')

        if self.youtubeKey is None:
            return '[YouTube API key not found]', None

        url = 'https://www.googleapis.com/youtube/v3/videos'
        fields = ('items('
                  'id,'
                  'snippet('
                  'title,'
                  'description,'
                  'channelTitle,'
                  'liveBroadcastContent'
                  '),'
                  'contentDetails(duration),'
                  'statistics(viewCount),'
                  'liveStreamingDetails(scheduledStartTime)'
                  ')')
        parts = 'snippet,contentDetails,statistics,liveStreamingDetails'
        params = {
            'id': videoID,
            'fields': fields,
            'part': parts,
            'key': self.youtubeKey,
        }

        response = self.bot.moduleHandler.runActionUntilValue('fetch-url',
                                                              url,
                                                              params=params)
        j = response.json()

        if 'items' not in j:
            return None

        data = []

        vid = j['items'][0]

        title = vid['snippet']['title']
        data.append(title)
        channel = vid['snippet']['channelTitle']
        data.append(channel)
        if vid['snippet']['liveBroadcastContent'] == 'none':
            length = parse_duration(
                vid['contentDetails']['duration']).total_seconds()
            m, s = divmod(int(length), 60)
            h, m = divmod(m, 60)
            if h > 0:
                length = '{0:02d}:{1:02d}:{2:02d}'.format(h, m, s)
            else:
                length = '{0:02d}:{1:02d}'.format(m, s)

            data.append(length)
        elif vid['snippet']['liveBroadcastContent'] == 'upcoming':
            startTime = vid['liveStreamingDetails']['scheduledStartTime']
            startDateTime = dateutil.parser.parse(startTime)
            now = datetime.datetime.now(dateutil.tz.tzutc())
            delta = startDateTime - now
            timespan = string.deltaTimeToString(delta, 'm')
            timeString = colour(A.normal['Live in ',
                                         A.fg.cyan[A.bold[timespan]]])
            data.append(timeString)
            pass  # time till stream starts, indicate it's upcoming
        elif vid['snippet']['liveBroadcastContent'] == 'live':
            status = str(colour(A.normal[A.fg.red[A.bold['{} Live']]]))
            status = status.format('●')
            data.append(status)
        else:
            pass  # if we're here, wat

        views = int(vid['statistics']['viewCount'])
        data.append('{:,}'.format(views))

        description = vid['snippet']['description']
        if not description:
            description = '<no description available>'
        description = re.sub('(\n|\s)+', ' ', description)
        limit = 150
        if len(description) > limit:
            description = '{} ...'.format(description[:limit].rsplit(' ',
                                                                     1)[0])
        data.append(description)

        graySplitter = colour(A.normal[' ', A.fg.gray['|'], ' '])
        return graySplitter.join(data), 'http://youtu.be/{}'.format(videoID)
コード例 #18
0
ファイル: Kickstarter.py プロジェクト: lunik1/DesertBot
    def follow(self, _: IRCMessage, url: str) -> [str, None]:
        ksMatch = re.search(
            r'kickstarter\.com/projects/(?P<ksID>[^/]+/[^/&#\?]+)', url)
        if not ksMatch:
            return
        ksID = ksMatch.group('ksID')
        url = 'https://www.kickstarter.com/projects/{}/description'.format(
            ksID)
        response = self.bot.moduleHandler.runActionUntilValue('fetch-url', url)

        soup = BeautifulSoup(response.content, 'lxml')

        output = []

        state = soup.find(id='main_content')

        pageStructureChanged = '[Kickstarter changed their page structure again :S ({})]'
        if not state:
            return pageStructureChanged.format('#main_content'), None

        if 'Campaign-state-canceled' in state['class']:
            state = 'cancelled'
            campaignState = colour(A.normal[A.fg.red['Cancelled']])
        elif 'Campaign-state-suspended' in state['class']:
            state = 'suspended'
            campaignState = colour(A.normal[A.fg.blue['Suspended']])
        elif 'Campaign-state-failed' in state['class']:
            state = 'failed'
            campaignState = colour(A.normal[A.fg.red['Failed']])
        elif 'Campaign-state-successful' in state['class']:
            state = 'successful'
            campaignState = colour(A.normal[A.fg.green['Successful']])
        elif 'Campaign-state-live' in state['class']:
            state = 'live'
        else:
            return '[Kickstarter state {!r} not recognised]'.format(
                state['class']), None

        if state in ['live', 'cancelled', 'suspended']:
            data = soup.find(attrs={'data-initial': True})
            if not data:
                return pageStructureChanged.format(
                    '{} data-initial'.format(state)), None

            data = json.loads(data['data-initial'])
            data = data['project']

            shorturl = data['projectShortLink']

            title = data['name']
            if data['creator']:
                creator = data['creator']['name']
            else:
                creator = None

            backerCount = int(data['backersCount'])

            pledged = float(data['pledged']['amount'])
            goal = float(data['goal']['amount'])
            currency = data['goal']['currency']
            percentage = float(data['percentFunded'])

            if state == 'live':
                deadline = int(data['deadlineAt'])
                deadline = datetime.datetime.fromtimestamp(
                    deadline, timezone.utc)
                now = datetime.datetime.now(timezone.utc)
                remaining = deadline - now
                remaining = remaining.total_seconds()
                remaining = remaining / 3600

                days = math.floor(remaining / 24)
                hours = remaining % 24

                campaignState = 'Duration: {0:.0f} days {1:.1f} hours to go'.format(
                    days, hours)
        else:
            # Successful
            pattern = re.compile(
                r'\n\s*window\.current_project\s*=\s*"(?P<data>\{.*?\})";\n')
            script = soup.find("script", text=pattern)
            if not script:
                return pageStructureChanged.format(
                    'non-live script pattern'), None

            data = pattern.search(script.text).group('data')
            data = html.unescape(data)
            data = json.loads(data)

            shorturl = data['urls']['web']['project_short']

            title = data['name']
            creator = data['creator']['name']

            backerCount = int(data['backers_count'])

            pledged = float(data['pledged'])
            goal = float(data['goal'])
            currency = data['currency']
            percentage = (pledged / goal) * 100

        if creator is not None:
            name = str(colour(A.normal['{}', A.fg.gray[' by '],
                                       '{}'])).format(title, creator)
        else:
            name = title
        output.append(name)

        if backerCount is not None:
            output.append('Backers: {:,d}'.format(backerCount))

        if backerCount > 0:
            pledgePerBacker = pledged / backerCount
        else:
            pledgePerBacker = 0

        if percentage >= 100:
            percentageString = A.fg.green['({2:,.0f}% funded)']
        else:
            percentageString = A.fg.red['({2:,.0f}% funded)']

        pledgePerBackerString = A.fg.gray['{3:,.0f}/backer']

        pledgedString = colour(A.normal['Pledged: {0:,.0f}', A.fg.gray['/'],
                                        '{1:,.0f} {4} ', percentageString, ' ',
                                        pledgePerBackerString])
        output.append(
            pledgedString.format(pledged, goal, percentage, pledgePerBacker,
                                 currency))

        output.append(campaignState)

        graySplitter = colour(A.normal[' ', A.fg.gray['|'], ' '])
        return graySplitter.join(output), shorturl
コード例 #19
0
ファイル: YouTube.py プロジェクト: DesertBot/DesertBot
    def follow(self, _: IRCMessage, url: str) -> [str, None]:
        match = re.search(r'(youtube\.com/watch.+v=|youtu\.be/)(?P<videoID>[^&#\?]{11})', url)
        if not match:
            return
        videoID = match.group('videoID')

        if self.youtubeKey is None:
            return '[YouTube API key not found]', None

        url = 'https://www.googleapis.com/youtube/v3/videos'
        fields = ('items('
                    'id,'
                    'snippet('
                      'title,'
                      'description,'
                      'channelTitle,'
                      'liveBroadcastContent'
                    '),'
                    'contentDetails(duration),'
                    'statistics(viewCount),'
                    'liveStreamingDetails(scheduledStartTime)'
                  ')')
        parts = 'snippet,contentDetails,statistics,liveStreamingDetails'
        params = {
            'id': videoID,
            'fields': fields,
            'part': parts,
            'key': self.youtubeKey,
        }

        response = self.bot.moduleHandler.runActionUntilValue('fetch-url',
                                                              url,
                                                              params=params)
        j = response.json()

        if 'items' not in j:
            return None

        data = []

        vid = j['items'][0]

        title = vid['snippet']['title']
        data.append(title)
        channel = vid['snippet']['channelTitle']
        data.append(channel)
        if vid['snippet']['liveBroadcastContent'] == 'none':
            length = parse_duration(vid['contentDetails']['duration']).total_seconds()
            m, s = divmod(int(length), 60)
            h, m = divmod(m, 60)
            if h > 0:
                length = '{0:02d}:{1:02d}:{2:02d}'.format(h, m, s)
            else:
                length = '{0:02d}:{1:02d}'.format(m, s)

            data.append(length)
        elif vid['snippet']['liveBroadcastContent'] == 'upcoming':
            startTime = vid['liveStreamingDetails']['scheduledStartTime']
            startDateTime = dateutil.parser.parse(startTime)
            now = datetime.datetime.now(dateutil.tz.tzutc())
            delta = startDateTime - now
            timespan = string.deltaTimeToString(delta, 'm')
            timeString = colour(A.normal['Live in ', A.fg.cyan[A.bold[timespan]]])
            data.append(timeString)
            pass  # time till stream starts, indicate it's upcoming
        elif vid['snippet']['liveBroadcastContent'] == 'live':
            status = str(colour(A.normal[A.fg.red[A.bold['{} Live']]]))
            status = status.format('●')
            data.append(status)
        else:
            pass  # if we're here, wat

        views = int(vid['statistics']['viewCount'])
        data.append('{:,}'.format(views))

        description = vid['snippet']['description']
        if not description:
            description = '<no description available>'
        description = re.sub('(\n|\s)+', ' ', description)
        limit = 150
        if len(description) > limit:
            description = '{} ...'.format(description[:limit].rsplit(' ', 1)[0])
        data.append(description)

        graySplitter = colour(A.normal[' ', A.fg.gray['|'], ' '])
        return graySplitter.join(data), 'http://youtu.be/{}'.format(videoID)
コード例 #20
0
ファイル: Etymology.py プロジェクト: DesertBot/DesertBot
    def execute(self, message: IRCMessage):
        if not message.parameterList:
            return IRCResponse(ResponseType.Say,
                               "You didn't give a word! Usage: {}".format(self.help(None)),
                               message.replyTo)

        mh = self.bot.moduleHandler

        searchURL = 'https://www.etymonline.com/search'
        query = message.parameterList[0]
        if len(message.parameterList) > 1:
            try:
                index = int(message.parameterList[1]) - 1
                if index < 0:
                    index = 0
            except ValueError:
                return IRCResponse(ResponseType.Say,
                                   'Index {!r} is not an integer! Usage: {}'
                                   .format(message.parameterList[1], self.help(None)),
                                   message.replyTo)
        else:
            index = 0

        results = mh.runActionUntilValue('fetch-url', searchURL,
                                         params={'q': query})

        soup = BeautifulSoup(results.content, 'lxml')
        words = soup.find_all(class_='word--C9UPa')
        if not words:
            return IRCResponse(ResponseType.Say,
                               'No results found for {!r}'.format(query),
                               message.replyTo)

        totalResults = soup.find(class_='searchList__pageCount--2jQdB').text
        totalResults = int(re.sub(r'[^\d]', '', totalResults))

        if index >= totalResults:
            index = totalResults - 1
        displayIndex = '{}/{}'.format(index + 1, totalResults)
        if index >= len(words):
            results = mh.runActionUntilValue('fetch-url', searchURL,
                                             params={'q': query, 'page': index // len(words) + 1})
            index %= len(words)
            soup = BeautifulSoup(results.content, 'lxml')
            words = soup.find_all(class_='word--C9UPa')
            if index >= len(words):
                index = len(words) - 1

        word = words[index].find(class_='word__name--TTbAA')
        word = word.text

        defn = words[index].find(class_='word__defination--2q7ZH')
        defn = ' '.join(defn.text.splitlines())
        limit = 500
        if len(defn) > limit:
            defn = '{} ...'.format(defn[:limit].rsplit(' ', 1)[0])

        wordURL = 'https://www.etymonline.com{}'.format(words[index]['href'])
        url = mh.runActionUntilValue('shorten-url', wordURL)

        response = colour(A.normal[A.bold['{}: '.format(word)],
                                   defn,
                                   A.fg.gray[' | {} | '.format(displayIndex)],
                                   url])

        return IRCResponse(ResponseType.Say, response, message.replyTo)
コード例 #21
0
ファイル: Urban.py プロジェクト: DesertBot/DesertBot
    def execute(self, message: IRCMessage):
        if len(message.parameterList) == 0:
            return IRCResponse(ResponseType.Say,
                               "You didn't give a word! Usage: {0}".format(self.help),
                               message.replyTo)

        search = quote(message.parameters)

        url = 'http://api.urbandictionary.com/v0/define?term={0}'.format(search)

        response = self.bot.moduleHandler.runActionUntilValue('fetch-url', url)

        j = response.json()

        if len(j['list']) == 0:
            return IRCResponse(ResponseType.Say,
                               "No entry found for '{0}'".format(message.parameters),
                               message.replyTo)

        graySplitter = colour(A.normal[' ', A.fg.gray['|'], ' '])

        defn = j['list'][0]

        word = defn['word']

        definition = defn['definition']
        definition = graySplitter.join([s.strip() for s in definition.strip().splitlines() if s])

        example = defn['example']
        example = graySplitter.join([s.strip() for s in example.strip().splitlines() if s])

        author = defn['author']

        up = defn['thumbs_up']
        down = defn['thumbs_down']

        more = 'http://{}.urbanup.com/'.format(word.replace(' ', '-'))

        if word.lower() != message.parameters.lower():
            word = "{0} (Contains '{1}')".format(word, message.parameters)

        defFormatString = str(colour(A.normal[A.bold["{0}:"], " {1}"]))
        exampleFormatString = str(colour(A.normal[A.bold["Example(s):"], " {0}"]))
        byFormatString = str(colour(A.normal["{0}",
                                             graySplitter,
                                             A.fg.lightGreen["+{1}"],
                                             A.fg.gray["/"],
                                             A.fg.lightRed["-{2}"],
                                             graySplitter,
                                             "More defs: {3}"]))
        responses = [IRCResponse(ResponseType.Say,
                                 defFormatString.format(word, definition),
                                 message.replyTo),
                     IRCResponse(ResponseType.Say,
                                 exampleFormatString.format(example),
                                 message.replyTo),
                     IRCResponse(ResponseType.Say,
                                 byFormatString.format(author, up, down, more),
                                 message.replyTo)]

        return responses
コード例 #22
0
    def execute(self, message: IRCMessage):
        if not message.parameterList:
            return IRCResponse(
                ResponseType.Say,
                "You didn't give a word! Usage: {}".format(self.help(None)),
                message.replyTo)

        mh = self.bot.moduleHandler

        searchURL = 'https://www.etymonline.com/search'
        query = message.parameterList[0]
        if len(message.parameterList) > 1:
            try:
                index = int(message.parameterList[1]) - 1
                if index < 0:
                    index = 0
            except ValueError:
                return IRCResponse(
                    ResponseType.Say,
                    'Index {!r} is not an integer! Usage: {}'.format(
                        message.parameterList[1],
                        self.help(None)), message.replyTo)
        else:
            index = 0

        results = mh.runActionUntilValue('fetch-url',
                                         searchURL,
                                         params={'q': query})

        soup = BeautifulSoup(results.content, 'lxml')
        words = soup.find_all(class_='word--C9UPa')
        if not words:
            return IRCResponse(ResponseType.Say,
                               'No results found for {!r}'.format(query),
                               message.replyTo)

        totalResults = soup.find(class_='searchList__pageCount--2jQdB').text
        totalResults = int(re.sub(r'[^\d]', '', totalResults))

        if index >= totalResults:
            index = totalResults - 1
        displayIndex = '{}/{}'.format(index + 1, totalResults)
        if index >= len(words):
            results = mh.runActionUntilValue('fetch-url',
                                             searchURL,
                                             params={
                                                 'q': query,
                                                 'page':
                                                 index // len(words) + 1
                                             })
            index %= len(words)
            soup = BeautifulSoup(results.content, 'lxml')
            words = soup.find_all(class_='word--C9UPa')
            if index >= len(words):
                index = len(words) - 1

        word = words[index].find(class_='word__name--TTbAA')
        word = word.text

        defn = words[index].find(class_='word__defination--2q7ZH')
        defn = ' '.join(defn.text.splitlines())
        limit = 500
        if len(defn) > limit:
            defn = '{} ...'.format(defn[:limit].rsplit(' ', 1)[0])

        wordURL = 'https://www.etymonline.com{}'.format(words[index]['href'])
        url = mh.runActionUntilValue('shorten-url', wordURL)

        response = colour(A.normal[A.bold['{}: '.format(word)], defn,
                                   A.fg.gray[' | {} | '.format(displayIndex)],
                                   url])

        return IRCResponse(ResponseType.Say, response, message.replyTo)
コード例 #23
0
ファイル: WolframAlpha.py プロジェクト: DesertBot/DesertBot
    def execute(self, message: IRCMessage) -> Union[IRCResponse, List[IRCResponse]]:
        if not self.apiKey:
            return IRCResponse(ResponseType.Say, "No API key found.", message.replyTo)

        if len(message.parameterList) == 0:
            return IRCResponse(ResponseType.Say, "You didn't give me a search query.", message.replyTo)

        params = {
            'input': message.parameters,
            'output': 'json',
            'appid': self.apiKey,
            'podindex': '5,4,3,2,1'
        }
        result = self.bot.moduleHandler.runActionUntilValue("fetch-url", self.waBaseURL, params)
        if not result or 'queryresult' not in result.json():
            output = 'No Wolfram Alpha data could be found at this moment. Try again later.'
        else:
            j = result.json()['queryresult']
            if 'error' in j and j['error'] != False:
                if 'msg' in j['error']:
                    output = f"Wolfram Alpha returned an error: {j['error']['msg']}"
                else:
                    output = 'Wolfram Alpha returned an unknown error'

            elif 'success' not in j or j['success'] == False:
                output = 'No results found.'
                didyoumeans = []
                if 'didyoumeans' in j:
                    tmpList = []
                    if isinstance(j['didyoumeans'], dict):
                        tmpList.append(j['didyoumeans'])
                    else:
                        tmpList = j['didyoumeans']

                    for didyoumean in tmpList:
                        if didyoumean['level'] != 'low':
                            didyoumeans.append(didyoumean['val'])
                if len(didyoumeans) > 0:
                    output = f"{output} Did you mean {' or '.join(didyoumeans)}?"
            else:
                result = None
                for pod in j['pods'][1:]:
                    if 'input' in [pod['id'].lower(), pod['title'].lower()]:
                        continue
                    for subpod in pod['subpods']:
                        if 'plaintext' not in subpod or subpod['plaintext'].startswith('\n'):
                            continue
                        plaintext = subpod['plaintext'].replace('\n', ' | ').strip()
                        if not plaintext:
                            continue # Probably an image
                        result = plaintext
                        break

                    if result:
                        break

                output = result if result else 'No relevant information was found'
                url = f'http://www.wolframalpha.com/input/?i={quote_plus(message.parameters)}'
                shortenedUrl = self.bot.moduleHandler.runActionUntilValue('shorten-url', url)
                if not shortenedUrl:
                    shortenedUrl = url
                output = f'{output} | {shortenedUrl}'
            graySplitter = colour(A.normal['', A.fg.gray['|'], ''])
            output = re.sub('(\| )+', '| ', re.sub(' +', ' ', output)).replace('|', graySplitter)
            return IRCResponse(ResponseType.Say, output, message.replyTo)
コード例 #24
0
    def execute(self,
                message: IRCMessage) -> Union[IRCResponse, List[IRCResponse]]:
        if not self.apiKey:
            return IRCResponse(ResponseType.Say, "No API key found.",
                               message.replyTo)

        if len(message.parameterList) == 0:
            return IRCResponse(ResponseType.Say,
                               "You didn't give me a search query.",
                               message.replyTo)

        params = {
            'input': message.parameters,
            'output': 'json',
            'appid': self.apiKey,
            'podindex': '5,4,3,2,1'
        }
        result = self.bot.moduleHandler.runActionUntilValue(
            "fetch-url", self.waBaseURL, params)
        if not result or 'queryresult' not in result.json():
            output = 'No Wolfram Alpha data could be found at this moment. Try again later.'
        else:
            j = result.json()['queryresult']
            if 'error' in j and j['error'] != False:
                if 'msg' in j['error']:
                    output = f"Wolfram Alpha returned an error: {j['error']['msg']}"
                else:
                    output = 'Wolfram Alpha returned an unknown error'

            elif 'success' not in j or j['success'] == False:
                output = 'No results found.'
                didyoumeans = []
                if 'didyoumeans' in j:
                    tmpList = []
                    if isinstance(j['didyoumeans'], dict):
                        tmpList.append(j['didyoumeans'])
                    else:
                        tmpList = j['didyoumeans']

                    for didyoumean in tmpList:
                        if didyoumean['level'] != 'low':
                            didyoumeans.append(didyoumean['val'])
                if len(didyoumeans) > 0:
                    output = f"{output} Did you mean {' or '.join(didyoumeans)}?"
            else:
                result = None
                for pod in j['pods'][1:]:
                    if 'input' in [pod['id'].lower(), pod['title'].lower()]:
                        continue
                    for subpod in pod['subpods']:
                        if 'plaintext' not in subpod or subpod[
                                'plaintext'].startswith('\n'):
                            continue
                        plaintext = subpod['plaintext'].replace('\n',
                                                                ' | ').strip()
                        if not plaintext:
                            continue  # Probably an image
                        result = plaintext
                        break

                    if result:
                        break

                output = result if result else 'No relevant information was found'
                url = f'http://www.wolframalpha.com/input/?i={quote_plus(message.parameters)}'
                shortenedUrl = self.bot.moduleHandler.runActionUntilValue(
                    'shorten-url', url)
                if not shortenedUrl:
                    shortenedUrl = url
                output = f'{output} | {shortenedUrl}'
            graySplitter = colour(A.normal['', A.fg.gray['|'], ''])
            output = re.sub('(\| )+', '| ',
                            re.sub(' +', ' ',
                                   output)).replace('|', graySplitter)
            return IRCResponse(ResponseType.Say, output, message.replyTo)
コード例 #25
0
    def execute(self, message: IRCMessage):
        subString = self._mangleEscapes(message.parameters)

        try:
            segments = list(self._parseSubcommandTree(subString))
        except UnbalancedBracesException as e:
            red = colour(A.bold[A.fg.lightRed['']])
            normal = colour(A.normal[''])
            error = (subString[:e.column] + red + subString[e.column] +
                     normal + subString[e.column + 1:])
            error = self._unmangleEscapes(error, False)
            return [
                IRCResponse(ResponseType.Say,
                            "Sub Error: {}".format(e.message),
                            message.replyTo),
                IRCResponse(ResponseType.Say, error, message.replyTo)
            ]

        prevLevel = -1
        responseStack = []
        extraVars = {}
        metadata = {}

        for segment in segments:
            (level, command, start, end) = segment

            # We've finished executing subcommands at the previous depth,
            # so replace subcommands with their output at the current depth
            if level < prevLevel:
                command = self._substituteResponses(command, responseStack,
                                                    level, extraVars, start)

            # Replace any extraVars in the command
            for var, value in extraVars.items():
                command = re.sub(r'\$\b{}\b'.format(re.escape(var)),
                                 '{}'.format(value), command)

            # Build a new message out of this segment
            inputMessage = IRCMessage(message.type,
                                      message.user,
                                      message.channel,
                                      self.bot.commandChar + command.lstrip(),
                                      self.bot,
                                      metadata=metadata)

            # Execute the constructed message
            if inputMessage.command.lower(
            ) in self.bot.moduleHandler.mappedTriggers:
                module = self.bot.moduleHandler.mappedTriggers[
                    inputMessage.command.lower()]
                response = module.execute(inputMessage)
                """@type : IRCResponse"""
            else:
                return IRCResponse(
                    ResponseType.Say,
                    "'{}' is not a recognized command trigger".format(
                        inputMessage.command), message.replyTo)

            # Push the response onto the stack
            responseStack.append((level, response.response, start, end))
            # Update the extraVars dict
            extraVars.update(response.ExtraVars)
            metadata = self._recursiveMerge(metadata, response.Metadata)

            prevLevel = level

        responseString = self._substituteResponses(subString, responseStack,
                                                   -1, extraVars, -1)
        responseString = self._unmangleEscapes(responseString)
        return IRCResponse(ResponseType.Say,
                           responseString,
                           message.replyTo,
                           extraVars=extraVars,
                           metadata=metadata)
コード例 #26
0
ファイル: Kickstarter.py プロジェクト: DesertBot/DesertBot
    def follow(self, _: IRCMessage, url: str) -> [str, None]:
        ksMatch = re.search(r'kickstarter\.com/projects/(?P<ksID>[^/]+/[^/&#\?]+)', url)
        if not ksMatch:
            return
        ksID = ksMatch.group('ksID')
        url = 'https://www.kickstarter.com/projects/{}/description'.format(ksID)
        response = self.bot.moduleHandler.runActionUntilValue('fetch-url', url)

        soup = BeautifulSoup(response.content, 'lxml')

        output = []

        state = soup.find(id='main_content')

        pageStructureChanged = '[Kickstarter changed their page structure again :S ({})]'
        if not state:
            return pageStructureChanged.format('#main_content'), None

        if 'Campaign-state-canceled' in state['class']:
            state = 'cancelled'
            campaignState = colour(A.normal[A.fg.red['Cancelled']])
        elif 'Campaign-state-suspended' in state['class']:
            state = 'suspended'
            campaignState = colour(A.normal[A.fg.blue['Suspended']])
        elif 'Campaign-state-failed' in state['class']:
            state = 'failed'
            campaignState = colour(A.normal[A.fg.red['Failed']])
        elif 'Campaign-state-successful' in state['class']:
            state = 'successful'
            campaignState = colour(A.normal[A.fg.green['Successful']])
        elif 'Campaign-state-live' in state['class']:
            state = 'live'
        else:
            return '[Kickstarter state {!r} not recognised]'.format(state['class']), None

        if state in ['live', 'cancelled', 'suspended']:
            data = soup.find(attrs={'data-initial': True})
            if not data:
                return pageStructureChanged.format('{} data-initial'.format(state)), None

            data = json.loads(data['data-initial'])
            data = data['project']

            shorturl = data['projectShortLink']

            title = data['name']
            if data['creator']:
                creator = data['creator']['name']
            else:
                creator = None

            backerCount = int(data['backersCount'])

            pledged = float(data['pledged']['amount'])
            goal = float(data['goal']['amount'])
            currency = data['goal']['currency']
            percentage = float(data['percentFunded'])

            if state == 'live':
                deadline = int(data['deadlineAt'])
                deadline = datetime.datetime.fromtimestamp(deadline, timezone.utc)
                now = datetime.datetime.now(timezone.utc)
                remaining = deadline - now
                remaining = remaining.total_seconds()
                remaining = remaining / 3600

                days = math.floor(remaining/24)
                hours = remaining % 24

                campaignState = 'Duration: {0:.0f} days {1:.1f} hours to go'.format(days, hours)
        else:
            # Successful
            pattern = re.compile(r'\n\s*window\.current_project\s*=\s*"(?P<data>\{.*?\})";\n')
            script = soup.find("script", text=pattern)
            if not script:
                return pageStructureChanged.format('non-live script pattern'), None

            data = pattern.search(script.text).group('data')
            data = html.unescape(data)
            data = json.loads(data)

            shorturl = data['urls']['web']['project_short']

            title = data['name']
            creator = data['creator']['name']

            backerCount = int(data['backers_count'])

            pledged = float(data['pledged'])
            goal = float(data['goal'])
            currency = data['currency']
            percentage = (pledged / goal) * 100

        if creator is not None:
            name = str(colour(A.normal['{}', A.fg.gray[' by '], '{}'])).format(title, creator)
        else:
            name = title
        output.append(name)

        if backerCount is not None:
            output.append('Backers: {:,d}'.format(backerCount))

        if backerCount > 0:
            pledgePerBacker = pledged / backerCount
        else:
            pledgePerBacker = 0

        if percentage >= 100:
            percentageString = A.fg.green['({2:,.0f}% funded)']
        else:
            percentageString = A.fg.red['({2:,.0f}% funded)']

        pledgePerBackerString = A.fg.gray['{3:,.0f}/backer']

        pledgedString = colour(A.normal['Pledged: {0:,.0f}',
                                        A.fg.gray['/'],
                                        '{1:,.0f} {4} ',
                                        percentageString,
                                        ' ',
                                        pledgePerBackerString])
        output.append(pledgedString.format(pledged,
                                           goal,
                                           percentage,
                                           pledgePerBacker,
                                           currency))

        output.append(campaignState)

        graySplitter = colour(A.normal[' ', A.fg.gray['|'], ' '])
        return graySplitter.join(output), shorturl
コード例 #27
0
    def execute(self, message: IRCMessage):
        if len(message.parameterList) == 0:
            return IRCResponse(
                ResponseType.Say,
                "You didn't give a word! Usage: {0}".format(self.help),
                message.replyTo)

        search = quote(message.parameters)

        url = 'http://api.urbandictionary.com/v0/define?term={0}'.format(
            search)

        response = self.bot.moduleHandler.runActionUntilValue('fetch-url', url)

        j = response.json()

        if len(j['list']) == 0:
            return IRCResponse(
                ResponseType.Say,
                "No entry found for '{0}'".format(message.parameters),
                message.replyTo)

        graySplitter = colour(A.normal[' ', A.fg.gray['|'], ' '])

        defn = j['list'][0]

        word = defn['word']

        definition = defn['definition']
        definition = graySplitter.join(
            [s.strip() for s in definition.strip().splitlines() if s])

        example = defn['example']
        example = graySplitter.join(
            [s.strip() for s in example.strip().splitlines() if s])

        author = defn['author']

        up = defn['thumbs_up']
        down = defn['thumbs_down']

        more = 'http://{}.urbanup.com/'.format(word.replace(' ', '-'))

        if word.lower() != message.parameters.lower():
            word = "{0} (Contains '{1}')".format(word, message.parameters)

        defFormatString = str(colour(A.normal[A.bold["{0}:"], " {1}"]))
        exampleFormatString = str(
            colour(A.normal[A.bold["Example(s):"], " {0}"]))
        byFormatString = str(
            colour(A.normal["{0}", graySplitter, A.fg.lightGreen["+{1}"],
                            A.fg.gray["/"], A.fg.lightRed["-{2}"],
                            graySplitter, "More defs: {3}"]))
        responses = [
            IRCResponse(ResponseType.Say,
                        defFormatString.format(word, definition),
                        message.replyTo),
            IRCResponse(ResponseType.Say, exampleFormatString.format(example),
                        message.replyTo),
            IRCResponse(ResponseType.Say,
                        byFormatString.format(author, up, down, more),
                        message.replyTo)
        ]

        return responses
コード例 #28
0
ファイル: ItchIO.py プロジェクト: DesertBot/DesertBot
    def follow(self, _: IRCMessage, url: str) -> [str, None]:
        match = re.search(r'itch\.io/', url)
        if not match:
            return

        response = self.bot.moduleHandler.runActionUntilValue('fetch-url', url)
        soup = BeautifulSoup(response.content, 'lxml')

        if not soup.find('body', {'data-page_name': 'view_game'}):
            return

        gameMetaInfo = soup.find(class_='game_info_panel_widget')
        if not gameMetaInfo:
            return

        # extract infobox information
        def extractInfo(namePattern: str):
            r = re.compile(namePattern)
            nameMatch = gameMetaInfo.find(text=r)
            if not nameMatch:
                return
            value = nameMatch.parent.parent.find_all('td')[-1]
            return value

        updated = extractInfo(r'^\s*Updated\s*$')
        updated = updated.abbr['title'] if updated else None

        published = extractInfo(r'^\s*Published\s*$')
        published = published.abbr['title'] if published else None

        status = extractInfo(r'^\s*Status\s*$')
        status = status.text.strip() if status else None

        platforms = extractInfo(r'^\s*Platforms\s*$')
        platforms = platforms.text.strip() if platforms else None

        rating = extractInfo(r'^\s*Rating\s*$')
        rating_stars = rating.find(class_='star_value')['content'] if rating else None
        rating_count = rating.find(class_='rating_count')['content'] if rating else None

        author = extractInfo(r'^\s*Author\s*$')
        author = author.text.strip() if author else None

        genre = extractInfo(r'^\s*Genre\s*$')
        genre = genre.text.strip() if genre else None

        # extract json information
        gameInfo = soup.find_all('script', {'type': 'application/ld+json'})[-1].text
        gameInfo = json.loads(gameInfo)

        title = gameInfo['name']
        description = gameInfo['description']

        if 'offers' in gameInfo:
            price = gameInfo['offers']['price']
            currency = gameInfo['offers']['priceCurrency']
            if gameInfo['offers']['priceValidUntil']:
                pass  # fetch sale info (original price, percentage discount, end time)

        # build the output
        output = []

        output.append(colour(A.normal[title, A.fg.gray[' by '], author]))

        if genre:
            output.append(colour(A.normal['Genre: ', genre]))

        outStatus = status
        if published:
            outStatus += ', published ' + published
        if updated:
            outStatus += ', last updated ' + updated
        output.append(colour(A.normal[outStatus]))
        if rating:
            output.append(colour(A.normal['Rating: ', rating_stars, '/5',
                                          A.fg.gray[' (', rating_count, ' ratings)']]))
        if price:
            output.append(colour(A.normal[price, ' ', currency]))  # todo: sale stuff
        else:
            output.append('Free')

        if platforms:
            output.append(platforms)
        if description:
            output.append(description)

        graySplitter = colour(A.normal[' ', A.fg.gray['|'], ' '])
        response = graySplitter.join(output)

        return response, url
コード例 #29
0
ファイル: Imgur.py プロジェクト: lunik1/DesertBot
    def follow(self, _: IRCMessage, origUrl: str) -> [str, None]:
        match = re.search(r'(i\.)?imgur\.com/(?P<imgurID>[^\.]+)', origUrl)
        if not match:
            return
        origImgurID = match.group('imgurID')

        if self.imgurClientID is None:
            return '[imgur Client ID not found]', None

        albumLink = False
        if origImgurID.startswith('a/'):
            imgurID = origImgurID.replace('a/', '')
            url = 'https://api.imgur.com/3/album/{}'.format(imgurID)
            albumLink = True
        elif origImgurID.startswith('gallery/'):
            imgurID = origImgurID.replace('gallery/', '')
            url = 'https://api.imgur.com/3/gallery/{}'.format(imgurID)
        else:
            imgurID = origImgurID
            url = 'https://api.imgur.com/3/image/{}'.format(origImgurID)

        headers = {'Authorization': 'Client-ID {}'.format(self.imgurClientID)}

        mh = self.bot.moduleHandler
        response = mh.runActionUntilValue('fetch-url', url, extraHeaders=headers)

        if not response:
            return

        j = response.json()

        imageData = j['data']

        if not imageData['title']:
            if imageData['section']:
                # subreddit galleries have a different endpoint with better data.
                #  we don't know if it's a subreddit gallery image until we fetch it,
                #  so we're stuck with this double-lookup. oh well.
                url = ('https://api.imgur.com/3/gallery/r/{}/{}'
                       .format(imageData['section'], imgurID))
                response = mh.runActionUntilValue('fetch-url', url, extraHeaders=headers)
                if not response:
                    return
                j = response.json()
                imageData = j['data']
            else:
                # fallback to the html page title if no other title was found.
                #  this should always result in <No Title> now as all the endpoints are covered,
                #  but they may add new ones so we're leaving this here just in case.
                titleUrl = 'https://imgur.com/{}'.format(origImgurID)
                response = mh.runActionUntilValue('fetch-url', titleUrl)
                title = mh.runActionUntilValue('get-html-title', response.content)
                imageData['title'] = title.replace(' - Imgur', '')
                if imageData['title'] in ['imgur: the simple image sharer',
                                          'Imgur: The magic of the Internet']:
                    imageData['title'] = None

        data = []
        if imageData['title']:
            data.append(imageData['title'])
        else:
            data.append('<No Title>')
        if imageData['nsfw']:
            data.append('\x034\x02NSFW!\x0F')
        if albumLink:
            data.append('Album: {} Images'.format(imageData['images_count']))
        else:
            if 'is_album' in imageData and imageData['is_album']:
                data.append('Album: {:,d} Images'.format(len(imageData['images'])))
            else:
                if imageData['animated']:
                    data.append('\x032\x02Animated!\x0F')
                data.append('{:,d}x{:,d}'.format(imageData['width'], imageData['height']))
                data.append('Size: {:,d}kb'.format(int(imageData['size']/1024)))
        data.append('Views: {:,d}'.format(imageData['views']))

        graySplitter = colour(A.normal[' ', A.fg.gray['|'], ' '])
        return graySplitter.join(data), '[no imgur url]'
コード例 #30
0
    def followURL(self,
                  _: IRCMessage,
                  url: str,
                  showContents: bool = False) -> [str, None]:
        # check this is actually a Mastodon instance we're looking at
        hostname = urlparse(url).hostname
        endpoint = 'https://{domain}/api/v1/instance'.format(domain=hostname)
        endpointResponse = self.bot.moduleHandler.runActionUntilValue(
            'fetch-url', endpoint)
        if not endpointResponse:
            return
        try:
            endpointJSON = endpointResponse.json()
        except json.decoder.JSONDecodeError:
            return
        if 'uri' not in endpointJSON:
            return

        response = self.bot.moduleHandler.runActionUntilValue(
            'fetch-url', '{}/embed'.format(url))
        if not response:
            return

        soup = BeautifulSoup(response.content, 'lxml')

        toot = soup.find(class_='entry')
        if not toot:
            # presumably not a toot, ignore
            return

        date = toot.find(class_='dt-published')['value']
        date = dateutil.parser.parse(date)
        date = date.astimezone(dateutil.tz.UTC)
        date = date.strftime('%Y/%m/%d %H:%M')

        name = toot.find(class_='p-name')
        name = self.translateEmojo(name).text.strip()
        user = toot.find(class_='display-name__account').text.strip()

        user = '******'.format(name, user)

        content = toot.find(class_='status__content')
        summary = content.find(class_='p-summary')
        if summary:
            summary = self.translateEmojo(summary).text.strip()
        text = content.find(class_='e-content')
        text = self.translateEmojo(text)
        # if there's no p tag, add one wrapping everything
        if not text.find_all('p'):
            text_children = list(text.children)
            wrapper_p = soup.new_tag('p')
            text.clear()
            text.append(wrapper_p)
            for child in text_children:
                wrapper_p.append(child)
        # replace <br /> tags with a newline
        for br in text.find_all("br"):
            br.replace_with('\n')
        # then replace consecutive <p> tags with a double newline
        lines = [line.text for line in text.find_all('p')]
        text = '\n\n'.join(lines)

        # strip empty lines, strip leading/ending whitespace,
        # and replace newlines with gray pipes
        graySplitter = colour(A.normal[' ', A.fg.gray['|'], ' '])
        lines = [l.strip() for l in text.splitlines() if l.strip()]
        text = graySplitter.join(lines)

        media = toot.find('div', {'data-component': 'MediaGallery'})
        if media:
            media = json.loads(media['data-props'])
            media = media['media']
            numMedia = len(media)
            if numMedia == 1:
                medType = media[0]['type']
                #size = media[0]['meta']['original']['size']
                description = media[0]['description']
                description = ': {}'.format(description) if description else ''
                media = '(attached {medType}{description})'.format(
                    medType=medType, description=description)
            else:
                media = '({} media attached)'.format(numMedia)

        formatString = str(
            colour(A.normal[A.fg.gray['[{date}]'], A.bold[' {user}:'],
                            A.fg.red[' [{summary}]'] if summary else '',
                            ' {text}' if not summary or showContents else '',
                            A.fg.gray[' {media}'] if media else '']))

        return formatString.format(date=date,
                                   user=user,
                                   summary=summary,
                                   text=text,
                                   media=media), ''
コード例 #31
0
ファイル: Steam.py プロジェクト: lunik1/DesertBot
    def follow(self, _: IRCMessage, url: str) -> [str, None]:
        match = re.search(
            r'store\.steampowered\.com/(?P<steamType>(app|sub))/(?P<steamID>[0-9]+)',
            url)
        if not match:
            return

        steamType = match.group('steamType')
        steamId = match.group('steamID')

        steamType = {'app': 'app', 'sub': 'package'}[steamType]
        params = '{0}details/?{0}ids={1}&cc=US&l=english&v=1'.format(
            steamType, steamId)
        url = 'http://store.steampowered.com/api/{}'.format(params)
        response = self.bot.moduleHandler.runActionUntilValue('fetch-url', url)

        j = response.json()
        if not j[steamId]['success']:
            return  # failure

        appData = j[steamId]['data']

        data = []

        # name
        if 'developers' in appData:
            developers = ', '.join(appData['developers'])
            name = colour(A.normal[appData['name'], A.fg.gray[' by '],
                                   developers])
        else:
            name = appData['name']
        data.append(name)

        # package contents (might need to trim this...)
        if 'apps' in appData:
            appNames = [app['name'] for app in appData['apps']]
            apps = 'Package containing: {}'.format(', '.join(appNames))
            data.append(apps)

        # genres
        if 'genres' in appData:
            genres = ', '.join(
                [genre['description'] for genre in appData['genres']])
            data.append('Genres: ' + genres)

        # release date
        releaseDate = appData['release_date']
        if not releaseDate['coming_soon']:
            if releaseDate['date']:
                data.append('Released: ' + releaseDate['date'])
        else:
            upcomingDate = A.fg.cyan[A.bold[str(releaseDate['date'])]]
            data.append(colour(A.normal['To Be Released: ', upcomingDate]))

        # metacritic
        # http://www.metacritic.com/faq#item32
        # (Why is the breakdown of green, yellow, and red scores different for games?)
        if 'metacritic' in appData:
            metaScore = appData['metacritic']['score']
            if metaScore < 50:
                metacritic = colour(A.fg.red[str(metaScore)])
            elif metaScore < 75:
                metacritic = colour(A.fg.orange[str(metaScore)])
            else:
                metacritic = colour(A.fg.green[str(metaScore)])
            data.append('Metacritic: {}'.format(metacritic))

        # dlc count
        if 'dlc' in appData:
            dlc = 'DLC: {}'.format(len(appData['dlc']))
            data.append(dlc)

        # prices
        if 'is_free' in appData:
            if appData['is_free']:
                free = colour(A.fg.cyan['Free'])
                data.append(free)

        priceField = {'app': 'price_overview', 'package': 'price'}[steamType]
        if priceField in appData:
            prices = {
                'USD': appData[priceField],
                'GBP': self.getSteamPrice(steamType, steamId, 'GB'),
                'EUR': self.getSteamPrice(steamType, steamId, 'FR'),
                'AUD': self.getSteamPrice(steamType, steamId, 'AU')
            }

            currencies = {
                'USD': '$',
                'GBP': '\u00A3',
                'EUR': '\u20AC',
                'AUD': 'AU$'
            }

            # filter out AUD if same as USD (most are)
            if not prices['AUD'] or prices['AUD']['final'] == prices['USD'][
                    'final']:
                del prices['AUD']

            # filter out any missing prices
            prices = {key: val for key, val in prices.items() if val}
            priceList = [
                '{}{:,.2f}'.format(currencies[val['currency']],
                                   val['final'] / 100.0)
                for val in prices.values()
            ]
            priceString = '/'.join(priceList)
            if prices['USD']['discount_percent'] > 0:
                discount = ' ({}% sale!)'.format(
                    prices['USD']['discount_percent'])
                priceString += colour(A.fg.green[A.bold[discount]])

            data.append(priceString)

        # platforms
        if 'platforms' in appData:
            platforms = appData['platforms']
            platformArray = []
            if platforms['windows']:
                platformArray.append('Win')
            else:
                platformArray.append('---')
            if platforms['mac']:
                platformArray.append('Mac')
            else:
                platformArray.append('---')
            if platforms['linux']:
                platformArray.append('Lin')
            else:
                platformArray.append('---')
            data.append('/'.join(platformArray))

        # description
        if 'short_description' in appData and appData[
                'short_description'] is not None:
            limit = 100
            description = appData['short_description']
            if len(description) > limit:
                description = '{} ...'.format(description[:limit].rsplit(
                    ' ', 1)[0])
            data.append(description)

        url = ('http://store.steampowered.com/{}/{}'.format({
            'app': 'app',
            'package': 'sub'
        }[steamType], steamId))
        graySplitter = colour(A.normal[' ', A.fg.gray['|'], ' '])
        return graySplitter.join(data), url
コード例 #32
0
ファイル: Steam.py プロジェクト: DesertBot/DesertBot
    def follow(self, _: IRCMessage, url: str) -> [str, None]:
        match = re.search(r'store\.steampowered\.com/(?P<steamType>(app|sub))/(?P<steamID>[0-9]+)',
                          url)
        if not match:
            return

        steamType = match.group('steamType')
        steamId = match.group('steamID')

        steamType = {'app': 'app', 'sub': 'package'}[steamType]
        params = '{0}details/?{0}ids={1}&cc=US&l=english&v=1'.format(steamType, steamId)
        url = 'http://store.steampowered.com/api/{}'.format(params)
        response = self.bot.moduleHandler.runActionUntilValue('fetch-url', url)

        j = response.json()
        if not j[steamId]['success']:
            return  # failure

        appData = j[steamId]['data']

        data = []

        # name
        if 'developers' in appData:
            developers = ', '.join(appData['developers'])
            name = colour(A.normal[appData['name'], A.fg.gray[' by '], developers])
        else:
            name = appData['name']
        data.append(name)

        # package contents (might need to trim this...)
        if 'apps' in appData:
            appNames = [app['name'] for app in appData['apps']]
            apps = 'Package containing: {}'.format(', '.join(appNames))
            data.append(apps)

        # genres
        if 'genres' in appData:
            genres = ', '.join([genre['description'] for genre in appData['genres']])
            data.append('Genres: ' + genres)

        # release date
        releaseDate = appData['release_date']
        if not releaseDate['coming_soon']:
            if releaseDate['date']:
                data.append('Released: ' + releaseDate['date'])
        else:
            upcomingDate = A.fg.cyan[A.bold[str(releaseDate['date'])]]
            data.append(colour(A.normal['To Be Released: ', upcomingDate]))

        # metacritic
        # http://www.metacritic.com/faq#item32
        # (Why is the breakdown of green, yellow, and red scores different for games?)
        if 'metacritic' in appData:
            metaScore = appData['metacritic']['score']
            if metaScore < 50:
                metacritic = colour(A.fg.red[str(metaScore)])
            elif metaScore < 75:
                metacritic = colour(A.fg.orange[str(metaScore)])
            else:
                metacritic = colour(A.fg.green[str(metaScore)])
            data.append('Metacritic: {}'.format(metacritic))

        # dlc count
        if 'dlc' in appData:
            dlc = 'DLC: {}'.format(len(appData['dlc']))
            data.append(dlc)

        # prices
        if 'is_free' in appData:
            if appData['is_free']:
                free = colour(A.fg.cyan['Free'])
                data.append(free)

        priceField = {'app': 'price_overview', 'package': 'price'}[steamType]
        if priceField in appData:
            prices = {'USD': appData[priceField],
                      'GBP': self.getSteamPrice(steamType, steamId, 'GB'),
                      'EUR': self.getSteamPrice(steamType, steamId, 'FR'),
                      'AUD': self.getSteamPrice(steamType, steamId, 'AU')}

            currencies = {'USD': '$',
                          'GBP': '\u00A3',
                          'EUR': '\u20AC',
                          'AUD': 'AU$'}

            # filter out AUD if same as USD (most are)
            if not prices['AUD'] or prices['AUD']['final'] == prices['USD']['final']:
                del prices['AUD']

            # filter out any missing prices
            prices = {key: val for key, val in prices.items() if val}
            priceList = ['{}{:,.2f}'.format(currencies[val['currency']], val['final'] / 100.0)
                         for val in prices.values()]
            priceString = '/'.join(priceList)
            if prices['USD']['discount_percent'] > 0:
                discount = ' ({}% sale!)'.format(prices['USD']['discount_percent'])
                priceString += colour(A.fg.green[A.bold[discount]])

            data.append(priceString)

        # platforms
        if 'platforms' in appData:
            platforms = appData['platforms']
            platformArray = []
            if platforms['windows']:
                platformArray.append('Win')
            else:
                platformArray.append('---')
            if platforms['mac']:
                platformArray.append('Mac')
            else:
                platformArray.append('---')
            if platforms['linux']:
                platformArray.append('Lin')
            else:
                platformArray.append('---')
            data.append('/'.join(platformArray))

        # description
        if 'short_description' in appData and appData['short_description'] is not None:
            limit = 100
            description = appData['short_description']
            if len(description) > limit:
                description = '{} ...'.format(description[:limit].rsplit(' ', 1)[0])
            data.append(description)

        url = ('http://store.steampowered.com/{}/{}'
               .format({'app': 'app', 'package': 'sub'}[steamType],
                       steamId))
        graySplitter = colour(A.normal[' ', A.fg.gray['|'], ' '])
        return graySplitter.join(data), url
コード例 #33
0
ファイル: string.py プロジェクト: lunik1/DesertBot
from base64 import b64decode, b64encode
from collections import OrderedDict
from html.entities import name2codepoint
from datetime import timedelta
from dateutil.parser import parse
from enum import Enum
import re

from twisted.words.protocols.irc import assembleFormattedText as colour, attributes as A

graySplitter = colour(A.normal[' ', A.fg.gray['|'], ' '])


def isNumber(s: str) -> bool:
    """returns True if string s can be cast to a number, False otherwise"""
    try:
        float(s)
        return True
    except ValueError:
        return False


# From this SO answer: http://stackoverflow.com/a/6043797/331047
def splitUTF8(s: str, n: int) -> str:
    """Split UTF-8 s into chunks of maximum byte length n"""
    while len(s) > n:
        k = n
        while (ord(s[k]) & 0xc0) == 0x80:
            k -= 1
        yield s[:k]
        s = s[k:]
コード例 #34
0
ファイル: Twitch.py プロジェクト: lunik1/DesertBot
    def follow(self, _: IRCMessage, url: str) -> [str, None]:
        # Heavily based on Didero's DideRobot code for the same
        # https://github.com/Didero/DideRobot/blob/06629fc3c8bddf8f729ce2d27742ff999dfdd1f6/commands/urlTitleFinder.py#L37
        match = re.search(r'twitch\.tv/(?P<twitchChannel>[^/]+)/?(\s|$)', url)
        if not match:
            return
        channel = match.group('twitchChannel')

        if self.twitchClientID is None:
            return '[Twitch Client ID not found]'

        chanData = {}
        channelOnline = False
        twitchHeaders = {
            'Accept': 'application/vnd.twitchtv.v3+json',
            'Client-ID': self.twitchClientID
        }
        url = 'https://api.twitch.tv/kraken/streams/{}'.format(channel)
        response = self.bot.moduleHandler.runActionUntilValue(
            'fetch-url', url, extraHeaders=twitchHeaders)

        streamData = response.json()

        if 'stream' in streamData and streamData['stream'] is not None:
            chanData = streamData['stream']['channel']
            channelOnline = True
        elif 'error' not in streamData:
            url = 'https://api.twitch.tv/kraken/channels/{}'.format(channel)
            response = self.bot.moduleHandler.runActionUntilValue(
                'fetch-url', url, extraHeaders=twitchHeaders)
            chanData = response.json()

        if len(chanData) == 0:
            return

        output = []
        if channelOnline:
            name = colour(A.normal[A.fg.green['{}'.format(
                chanData['display_name'])]])
        else:
            name = colour(A.normal[A.fg.red['{}'.format(
                chanData['display_name'])]])
        output.append(name)
        graySplitter = colour(A.normal[' ', A.fg.gray['|'], ' '])
        title = ' "{}"'.format(
            re.sub(r'[\r\n]+', graySplitter, chanData['status'].strip()))
        output.append(title)
        if chanData['game'] is not None:
            game = colour(A.normal[A.fg.gray[', playing '],
                                   '{}'.format(chanData['game'])])
            output.append(game)
        if chanData['mature']:
            mature = colour(A.normal[A.fg.lightRed[' [Mature]']])
            output.append(mature)
        if channelOnline:
            viewers = streamData['stream']['viewers']
            status = colour(A.normal[A.fg.green[
                ' (Live with {0:,d} viewers)'.format(viewers)]])
        else:
            status = colour(A.normal[A.fg.red[' (Offline)']])
        output.append(status)

        return ''.join(output), 'https://twitch.tv/{}'.format(channel)
コード例 #35
0
ファイル: string.py プロジェクト: DesertBot/DesertBot
from base64 import b64decode, b64encode
from collections import OrderedDict
from html.entities import name2codepoint
from datetime import timedelta
from dateutil.parser import parse
import re

from twisted.words.protocols.irc import assembleFormattedText as colour, attributes as A


graySplitter = colour(A.normal[' ', A.fg.gray['|'], ' '])


def isNumber(s: str) -> bool:
    """returns True if string s can be cast to a number, False otherwise"""
    try:
        float(s)
        return True
    except ValueError:
        return False


# From this SO answer: http://stackoverflow.com/a/6043797/331047
def splitUTF8(s: str, n: int) -> str:
    """Split UTF-8 s into chunks of maximum byte length n"""
    while len(s) > n:
        k = n
        while (ord(s[k]) & 0xc0) == 0x80:
            k -= 1
        yield s[:k]
        s = s[k:]