def FollowTwitch(self, channel, message): # Heavily based on Didero's DideRobot code for the same # https://github.com/Didero/DideRobot/blob/06629fc3c8bddf8f729ce2d27742ff999dfdd1f6/commands/urlTitleFinder.py#L37 # TODO: other stats? chanData = {} channelOnline = False twitchHeaders = [('Accept', 'application/vnd.twitchtv.v2+json')] webPage = WebUtils.fetchURL(u'https://api.twitch.tv/kraken/streams/{0}'.format(channel), twitchHeaders) streamData = json.loads(webPage.body) if 'stream' in streamData and streamData['stream'] is not None: chanData = streamData['stream']['channel'] channelOnline = True elif 'error' not in streamData: webPage = WebUtils.fetchURL(u'https://api.twitch.tv/kraken/channels/{0}'.format(channel), twitchHeaders) chanData = json.loads(webPage.body) if len(chanData) > 0: if channelOnline: channelInfo = assembleFormattedText(A.fg.green['']) + '{0}'.format(chanData['display_name']) + assembleFormattedText(A.normal['']) else: channelInfo = assembleFormattedText(A.fg.red['']) + '{0}'.format(chanData['display_name']) + assembleFormattedText(A.normal['']) channelInfo += u' "{0}"'.format(re.sub('[\r\n]+', self.graySplitter, chanData['status'].strip())) if chanData['game'] is not None: channelInfo += assembleFormattedText(A.normal[A.fg.gray[', playing '], '{0}'.format(chanData['game'])]) if chanData['mature']: channelInfo += assembleFormattedText(A.normal[A.fg.lightRed[' [Mature]']]) if channelOnline: channelInfo += assembleFormattedText(A.normal[A.fg.green[' (Live with {0:,d} viewers)'.format(streamData['stream']['viewers'])]]) else: channelInfo += assembleFormattedText(A.normal[A.fg.red[' (Offline)']]) return IRCResponse(ResponseType.Say, channelInfo, message.ReplyTo)
def FollowTwitch(self, channel, message): # Heavily based on Didero's DideRobot code for the same # https://github.com/Didero/DideRobot/blob/06629fc3c8bddf8f729ce2d27742ff999dfdd1f6/commands/urlTitleFinder.py#L37 # TODO: other stats? if self.twitchClientID is None: return IRCResponse(ResponseType.Say, '[Twitch Client ID not found]', message.ReplyTo) chanData = {} channelOnline = False twitchHeaders = [('Accept', 'application/vnd.twitchtv.v3+json'), ('Client-ID', self.twitchClientID)] webPage = WebUtils.fetchURL( u'https://api.twitch.tv/kraken/streams/{}'.format(channel), twitchHeaders) streamData = json.loads(webPage.body) if 'stream' in streamData and streamData['stream'] is not None: chanData = streamData['stream']['channel'] channelOnline = True elif 'error' not in streamData: webPage = WebUtils.fetchURL( u'https://api.twitch.tv/kraken/channels/{}'.format(channel), twitchHeaders) chanData = json.loads(webPage.body) if len(chanData) > 0: if channelOnline: channelInfo = assembleFormattedText( A.fg.green['']) + u'{}'.format( chanData['display_name']) + assembleFormattedText( A.normal['']) else: channelInfo = assembleFormattedText( A.fg.red['']) + u'{}'.format( chanData['display_name']) + assembleFormattedText( A.normal['']) channelInfo += u' "{}"'.format( re.sub(r'[\r\n]+', self.graySplitter, chanData['status'].strip())) if chanData['game'] is not None: channelInfo += assembleFormattedText( A.normal[A.fg.gray[', playing '], u'{}'.format(chanData['game'])]) if chanData['mature']: channelInfo += assembleFormattedText( A.normal[A.fg.lightRed[' [Mature]']]) if channelOnline: channelInfo += assembleFormattedText( A.normal[A.fg.green[' (Live with {0:,d} viewers)'.format( streamData['stream']['viewers'])]]) else: channelInfo += assembleFormattedText( A.normal[A.fg.red[' (Offline)']]) return IRCResponse( ResponseType.Say, channelInfo, message.ReplyTo, {'urlfollowURL': 'https://twitch.tv/{}'.format(channel)})
def execute(self, message): """ @type message: IRCMessage """ if len(message.ParameterList) == 0: return IRCResponse(ResponseType.Say, "You didn't give a word! Usage: {0}".format(self.help), message.ReplyTo) word = message.Parameters webPage = WebUtils.fetchURL('http://www.etymonline.com/index.php?allowed_in_frame=0&search={0}'.format(word)) root = BeautifulSoup(webPage.body) etymTitle = root.find('dt') etymDef = root.find('dd') if not etymTitle or not etymDef: return IRCResponse(ResponseType.Say, "No etymology found for '{0}'".format(word), message.ReplyTo) response = "{0}: {1}".format(etymTitle.text.strip(), etymDef.text.strip()) return IRCResponse(ResponseType.Say, response, message.ReplyTo)
def execute(self, message): """ @type message: IRCMessage """ if len(message.ParameterList) < 3: return IRCResponse(ResponseType.Say, self.help, message.ReplyTo) try: amount = float(message.ParameterList[0]) offset = 1 except ValueError: amount = 1.0 offset = 0 ccFrom = message.ParameterList[offset] ccTo = message.ParameterList[offset+2:] ccTo = ",".join(ccTo) url = "https://api.fixer.io/latest?base={}&symbols={}" url = url.format(ccFrom, ccTo) response = WebUtils.fetchURL(url) jsonResponse = json.loads(response.body) rates = jsonResponse['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.iteritems(): data.append("{} {}".format(rate*amount, curr)) graySplitter = assembleFormattedText(A.normal[' ', A.fg.gray['|'], ' ']) return IRCResponse(ResponseType.Say, graySplitter.join(data), message.ReplyTo)
def execute(self, message): """ @type message: IRCMessage """ if len(message.ParameterList) > 0: if self.api_key is None: return IRCResponse(ResponseType.Say, "[Bing Maps API key not found]", message.ReplyTo) url = "http://dev.virtualearth.net/REST/v1/Locations?q={0}&key={1}".format(urllib.quote_plus(message.Parameters), self.api_key) page = WebUtils.fetchURL(url) result = json.loads(page.body) if result['resourceSets'][0]['estimatedTotal'] == 0: print result return IRCResponse(ResponseType.Say, "Couldn't find GPS coords for '{0}', sorry!".format(message.Parameters), message.ReplyTo) coords = result['resourceSets'][0]['resources'][0]['point']['coordinates'] return IRCResponse(ResponseType.Say, "GPS coords for '{0}' are: {1},{2}".format(message.Parameters, coords[0], coords[1]), message.ReplyTo) else: return IRCResponse(ResponseType.Say, "You didn't give an address to look up", message.ReplyTo)
def execute(self, message): """ @type message: IRCMessage """ if len(message.ParameterList) != 1: return IRCResponse(ResponseType.Say, self.help, message.ReplyTo) subreddit = message.ParameterList[0].lower() url = "https://api.imgur.com/3/gallery/r/{}/time/all/{}" url = url.format(subreddit, random.randint(0, 100)) response = WebUtils.fetchURL(url, self.headers) jsonResponse = json.loads(response.body) images = jsonResponse['data'] if not images: return IRCResponse(ResponseType.Say, "The subreddit '{}' doesn't seem to have any images posted to it (or it doesn't exist!)" .format(subreddit), message.ReplyTo) image = random.choice(images) data = [] if image['title'] is not None: data.append(image['title']) if image['nsfw']: data.append(u'\x034\x02NSFW!\x0F') if image['animated']: data.append(u'\x032\x02Animated!\x0F') data.append(image['link']) graySplitter = assembleFormattedText(A.normal[' ', A.fg.gray['|'], ' ']) return IRCResponse(ResponseType.Say, graySplitter.join(data), message.ReplyTo)
def execute(self, message): """ @type message: IRCMessage """ if len(message.ParameterList) > 0: if self.api_key is None: return IRCResponse(ResponseType.Say, "[Bing Maps API key not found]", message.ReplyTo) url = "http://dev.virtualearth.net/REST/v1/Locations?q={0}&key={1}".format( urllib.quote_plus(message.Parameters), self.api_key) page = WebUtils.fetchURL(url) result = json.loads(page.body) if result['resourceSets'][0]['estimatedTotal'] == 0: print result return IRCResponse( ResponseType.Say, "Couldn't find GPS coords for '{0}', sorry!".format( message.Parameters), message.ReplyTo) coords = result['resourceSets'][0]['resources'][0]['point'][ 'coordinates'] return IRCResponse( ResponseType.Say, "GPS coords for '{0}' are: {1},{2}".format( message.Parameters, coords[0], coords[1]), message.ReplyTo) else: return IRCResponse(ResponseType.Say, "You didn't give an address to look up", message.ReplyTo)
def FollowTwitter(self, tweeter, tweetID, message): webPage = WebUtils.fetchURL('https://twitter.com/{0}/status/{1}'.format(tweeter, tweetID)) soup = BeautifulSoup(webPage.body) tweet = soup.find(class_='permalink-tweet') user = tweet.find(class_='username').text tweetText = tweet.find(class_='tweet-text') tweetTimeText = tweet.find(class_='client-and-actions').text.strip() try: tweetTimeText = time.strftime('%Y/%m/%d %H:%M', time.strptime(tweetTimeText, '%I:%M %p - %d %b %Y')) except ValueError: pass 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 = StringUtils.unescapeXHTML(tweetText.text) text = re.sub('[\r\n]+', self.graySplitter, text) formatString = unicode(assembleFormattedText(A.normal[A.fg.gray['[{0}]'], A.bold[' {1}:'], ' {2}'])) return IRCResponse(ResponseType.Say, formatString.format(tweetTimeText, user, text), message.ReplyTo, {'urlfollowURL': 'https://twitter.com/{}/status/{}'.format(tweeter, tweetID)})
def execute(self, message): """ @type message: IRCMessage """ wtfsimfd = "http://whatthefuckshouldimakefordinner.com/{}" options = {'meat': 'index.php', 'veg': 'veg.php', 'drink': 'drinks.php'} option = 'meat' if len(message.ParameterList) > 0: option = message.ParameterList[0] if option in options: webPage = WebUtils.fetchURL(wtfsimfd.format(options[option])) soup = BeautifulSoup(webPage.body) phrase = soup.find('dl').text item = soup.find('a') link = WebUtils.shortenGoogl(item['href']) return IRCResponse(ResponseType.Say, u"{}... {} {}".format(phrase, item.text, link), message.ReplyTo) else: error = u"'{}' is not a recognized dinner type, please choose one of {}"\ .format(option, u'/'.join(options.keys())) return IRCResponse(ResponseType.Say, error, message.ReplyTo)
def execute(self, message): """ @type message: IRCMessage """ baseURL = "http://greywool.com/desertbus/{}/gifs/random.php" years = range(7, 11) if len(message.ParameterList) > 0: invalid = u"'{}' is not a valid year, valid years are {} to {}"\ .format(message.ParameterList[0], years[0], years[-1]) try: if len(message.ParameterList[0]) < 4: year = int(message.ParameterList[0]) else: raise ValueError except ValueError: return IRCResponse(ResponseType.Say, invalid, message.ReplyTo) if year not in years: return IRCResponse(ResponseType.Say, invalid, message.ReplyTo) else: year = random.choice(years) url = baseURL.format(year) webPage = WebUtils.fetchURL(url) link = webPage.body return IRCResponse(ResponseType.Say, u"Random DB{} gif: {}".format(year, link), message.ReplyTo)
def FollowTwitter(self, tweeter, tweetID, message): webPage = WebUtils.fetchURL('https://twitter.com/{0}/status/{1}'.format(tweeter, tweetID)) soup = BeautifulSoup(webPage.body) tweet = soup.find(class_='permalink-tweet') user = tweet.find(class_='username').text tweetText = tweet.find(class_='tweet-text') 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 = StringUtils.unescapeXHTML(tweetText.text) text = re.sub('[\r\n]+', self.graySplitter, text) formatString = unicode(assembleFormattedText(A.normal[A.bold['{0}:'], ' {1}'])) return IRCResponse(ResponseType.Say, formatString.format(user, text), message.ReplyTo)
def FollowTwitter(self, tweeter, tweetID, message): webPage = WebUtils.fetchURL('https://twitter.com/{0}/status/{1}'.format(tweeter, tweetID)) soup = BeautifulSoup(webPage.body) tweet = soup.find(class_='permalink-tweet') user = tweet.find(class_='username').text tweetText = tweet.find(class_='tweet-text') tweetTimeText = tweet.find(class_='client-and-actions').text.strip() try: tweetTimeText = time.strftime('%Y/%m/%d %H:%M', time.strptime(tweetTimeText, '%I:%M %p - %d %b %Y')) except ValueError: pass 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 = StringUtils.unescapeXHTML(tweetText.text) text = re.sub('[\r\n]+', self.graySplitter, text) formatString = unicode(assembleFormattedText(A.normal[A.fg.gray['[{0}]'], A.bold[' {1}:'], ' {2}'])) return IRCResponse(ResponseType.Say, formatString.format(tweetTimeText, user, text), message.ReplyTo, {'urlfollowURL': 'https://twitter.com/{}/status/{}'.format(tweeter, tweetID)})
def execute(self, message): """ @type message: IRCMessage """ url = "https://splatoon2.ink/data/schedules.json" response = WebUtils.fetchURL(url) jsonResponse = json.loads(response.body) if len(message.ParameterList) < 1: # do everything data = [] data += filter(None, [self._regular(jsonResponse, short=True)]) data += filter(None, [self._ranked(jsonResponse, short=True)]) data += filter(None, [self._league(jsonResponse, short=True)]) data += filter(None, [self._fest(jsonResponse, short=True)]) return IRCResponse(ResponseType.Say, self.graySplitter.join(data), message.ReplyTo) else: subCommands = { 'regular': self._regular, 'ranked': self._ranked, 'league': self._league, 'fest': self._fest } subCommand = message.ParameterList[0].lower() if subCommand in subCommands: return IRCResponse( ResponseType.Say, subCommands[subCommand](jsonResponse, short=False), message.ReplyTo) else: return IRCResponse(ResponseType.Say, self.help, message.ReplyTo)
def execute(self, message): """ @type message: IRCMessage """ baseURL = "http://greywool.com/desertbus/{}/gifs/random.php" years = range(7, 9) if len(message.ParameterList) > 0: invalid = u"'{}' is not a valid year, valid years are {} to {}"\ .format(message.ParameterList[0], years[0], years[-1]) try: year = int(message.ParameterList[0]) except ValueError: return IRCResponse(ResponseType.Say, invalid, message.ReplyTo) if year not in years: return IRCResponse(ResponseType.Say, invalid, message.ReplyTo) else: year = random.choice(years) url = baseURL.format(year) webPage = WebUtils.fetchURL(url) link = webPage.body return IRCResponse(ResponseType.Say, u"Random DB{} gif: {}".format(year, link), message.ReplyTo)
def FollowKickstarter(self, ksID, message): webPage = WebUtils.fetchURL('https://www.kickstarter.com/projects/{0}/'.format(ksID)) soup = BeautifulSoup(webPage.body) data = [] title = soup.find(class_='title') if title is not None: creator = soup.find(id='name') if creator is not None: data.append(assembleFormattedText(A.normal['{0}', A.fg.gray[' by '], '{1}']).format(title.h2.text.strip(), creator.text.strip())) else: data.append(title.h2.text.strip()) stats = soup.find(id='stats') backerCount = stats.find(id='backers_count') if backerCount is not None: data.append('Backers: {0:,}'.format(int(backerCount['data-backers-count']))) pledged = stats.find(id='pledged') if pledged is not None: if float(pledged['data-percent-raised']) >= 1.0: percentageString = A.fg.green['({3:,.0f}% funded)'] else: percentageString = A.fg.red['({3:,.0f}% funded)'] pledgedString = assembleFormattedText(A.normal['Pledged: {0:,.0f}', A.fg.gray['/'], '{1:,.0f} {2} ', percentageString]) data.append(pledgedString.format(float(pledged['data-pledged']), float(pledged['data-goal']), pledged.data['data-currency'], float(pledged['data-percent-raised']) * 100)) findState = soup.find(id='main_content') if 'Project-state-canceled' in findState['class']: data.append(assembleFormattedText(A.normal[A.fg.red['Cancelled']])) elif 'Project-state-failed' in findState['class']: data.append(assembleFormattedText(A.normal[A.fg.red['Failed']])) elif 'Project-state-successful' in findState['class']: data.append(assembleFormattedText(A.normal[A.fg.green['Successful']])) elif 'Project-state-live' in findState['class']: duration = stats.find(id='project_duration_data') if duration is not None: remaining = float(duration['data-hours-remaining']) days = math.floor(remaining/24) hours = remaining/24 - days data.append('Duration: {0:.0f} days {1:.1f} hours to go'.format(days, hours)) return IRCResponse(ResponseType.Say, self.graySplitter.join(data), message.ReplyTo)
def getSteamPrice(cls, appType, appId, region): webPage = WebUtils.fetchURL('http://store.steampowered.com/api/{0}details/?{0}ids={1}&cc={2}&l=english&v=1'.format(appType, appId, region)) priceField = {'app': 'price_overview', 'package': 'price'}[appType] response = json.loads(webPage.body) if 'data' not in response[appId]: return if region == 'AU': response[appId]['data'][priceField]['currency'] = 'AUD' return response[appId]['data'][priceField]
def execute(self, message): """ @type message: IRCMessage """ url = "http://greywool.com/desertbus/gifs/random.php" webPage = WebUtils.fetchURL(url) link = webPage.body return IRCResponse(ResponseType.Say, u"Random DB gif: {0}".format(link), message.ReplyTo)
class JQ(CommandInterface): triggers = ['jq'] help = "jq <url> <filter> - filters json returned by the given url, returning values. \ filter syntax here: https://stedolan.github.io/jq/manual/#Basicfilters" runInThread = True htmlParser = HTMLParser.HTMLParser() def execute(self, message): """ @type message: IRCMessage """ if len(message.ParameterList) < 2: return IRCResponse(ResponseType.Say, u"Not enough parameters, usage: {}".format(self.help), message.ReplyTo) url, jqfilter = (message.ParameterList[0], u" ".join(message.ParameterList[1:])) if not re.match(ur'^\w+://', url): url = u"http://{}".format(url) page = WebUtils.fetchURL(url) if page is None: return IRCResponse(ResponseType.Say, u"Problem fetching {}".format(url), message.ReplyTo) try: value = jq(jqfilter).transform(text=page.body) except ValueError as e: response = re.sub(ur'[\r\n]+', u' ', e.message) return IRCResponse(ResponseType.Say, response, message.ReplyTo) if value is None: return IRCResponse(ResponseType.Say, u"{} does not match a value".format(jqfilter), message.ReplyTo) if isinstance(value, dict): return IRCResponse(ResponseType.Say, u"{} matches a dict".format(jqfilter), message.ReplyTo) if isinstance(value, list): return IRCResponse(ResponseType.Say, u"{} matches a list".format(jqfilter), message.ReplyTo) # sanitize the value value = u'{}'.format(value) value = value.strip() value = re.sub(ur'[\r\n]+', u' ', value) value = re.sub(ur'\s+', u' ', value) value = self.htmlParser.unescape(value) return IRCResponse(ResponseType.Say, value, message.ReplyTo)
def execute(self, message): """ @type message: IRCMessage """ if len(message.ParameterList) == 0: return IRCResponse(ResponseType.Say, "You didn't give a URL! Usage: {0}".format(self.help), message.ReplyTo) url = message.Parameters webPage = WebUtils.fetchURL('http://www.downforeveryoneorjustme.com/{0}'.format(url)) root = BeautifulSoup(webPage.body) downText = root.find('div').text.splitlines()[1].strip() return IRCResponse(ResponseType.Say, downText, message.ReplyTo)
def FollowStandard(self, url, message): webPage = WebUtils.fetchURL(url) if webPage is None: return if webPage.responseUrl != url: return self.DispatchToFollows(webPage.responseUrl, message) title = self.GetTitle(webPage.body) if title is not None: return IRCResponse(ResponseType.Say, u'{0} (at {1})'.format(title, webPage.domain), message.ReplyTo) return
def execute(self, message): """ @type message: IRCMessage """ if len(message.ParameterList) == 0 or len(message.ParameterList) > 2: return IRCResponse(ResponseType.Say, self.help, message.ReplyTo) subreddit = message.ParameterList[0].lower() if len(message.ParameterList) == 2: try: if len(message.ParameterList[1]) < 20: topRange = int(message.ParameterList[1]) else: raise ValueError if topRange < 0: raise ValueError except ValueError: return IRCResponse(ResponseType.Say, "The range should be a positive integer!", message.ReplyTo) else: topRange = 100 url = "https://api.imgur.com/3/gallery/r/{}/time/all/{}" url = url.format(subreddit, random.randint(0, topRange)) response = WebUtils.fetchURL(url, self.headers) jsonResponse = json.loads(response.body) images = jsonResponse['data'] if not images: return IRCResponse(ResponseType.Say, "The subreddit '{}' doesn't seem to have any images posted to it (or it doesn't exist!)" .format(subreddit), message.ReplyTo) image = random.choice(images) data = [] if 'title' in image and image['title'] is not None: data.append(image['title']) if 'nsfw' in image and image['nsfw']: data.append(u'\x034\x02NSFW!\x0F') if 'animated' in image and image['animated']: data.append(u'\x032\x02Animated!\x0F') if 'gifv' in image: data.append(image['gifv']) else: data.append(image['link']) graySplitter = assembleFormattedText(A.normal[' ', A.fg.gray['|'], ' ']) return IRCResponse(ResponseType.Say, graySplitter.join(data), message.ReplyTo)
def execute(self, message): """ @type message: IRCMessage """ if len(message.ParameterList) == 0 or len(message.ParameterList) > 2: return IRCResponse(ResponseType.Say, self.help, message.ReplyTo) subreddit = message.ParameterList[0].lower() if len(message.ParameterList) == 2: try: topRange = int(message.ParameterList[1]) if topRange < 0: return IRCResponse(ResponseType.Say, "The range should be a positive integer!", message.ReplyTo) except ValueError: return IRCResponse(ResponseType.Say, "The range should be a positive integer!", message.ReplyTo) else: topRange = 100 url = "https://api.imgur.com/3/gallery/r/{}/time/all/{}" url = url.format(subreddit, random.randint(0, topRange)) response = WebUtils.fetchURL(url, self.headers) jsonResponse = json.loads(response.body) images = jsonResponse["data"] if not images: return IRCResponse( ResponseType.Say, "The subreddit '{}' doesn't seem to have any images posted to it (or it doesn't exist!)".format( subreddit ), message.ReplyTo, ) image = random.choice(images) data = [] if image["title"] is not None: data.append(image["title"]) if image["nsfw"]: data.append(u"\x034\x02NSFW!\x0F") if image["animated"]: data.append(u"\x032\x02Animated!\x0F") if "gifv" in image: data.append(image["gifv"]) else: data.append(image["link"]) graySplitter = assembleFormattedText(A.normal[" ", A.fg.gray["|"], " "]) return IRCResponse(ResponseType.Say, graySplitter.join(data), message.ReplyTo)
def _pizzaTracker(self, orderID): """ @type orderID: str """ steps = {6: u"{}'s pizza order has been placed", 7: u"{}'s pizza is being prepared", 5: u"{}'s pizza is in the oven", 8: u"{}'s pizza is sitting on a shelf, waiting for a driver", 9: u"{}'s pizza is out for delivery", 3: u"{}'s pizza has been delivered! Tracking stopped"} trackingDetails = self.trackers[orderID] trackURL = u'https://www.dominos.co.uk/pizzaTracker/getOrderDetails?id={}'.format(orderID) page = WebUtils.fetchURL(trackURL) if page is None: # tracking API didn't respond self._stopPizzaTracker(orderID) self.bot.sendResponse(IRCResponse(ResponseType.Say, u"The pizza tracking page linked by {} " u"had some kind of error, tracking stopped".format(trackingDetails.orderer), trackingDetails.channel.Name)) return j = json.loads(page.body) if j['customerName'] is None: self._stopPizzaTracker(orderID) self.bot.sendResponse(IRCResponse(ResponseType.Say, u"There are no pizza tracking details at the page linked by {}.".format(trackingDetails.orderer), trackingDetails.channel.Name)) return response = None step = j['statusId'] if step != trackingDetails.step: trackingDetails.step = step response = IRCResponse(ResponseType.Say, steps[step].format(trackingDetails.orderer), trackingDetails.channel.Name) if step == 3: self._stopPizzaTracker(orderID) if response is not None: self.bot.sendResponse(response)
def FollowStandard(self, url, message): webPage = WebUtils.fetchURL(url) if webPage is None: return if webPage.responseUrl != url: return self.DispatchToFollows(webPage.responseUrl, message) title = self.GetTitle(webPage.body) if title is not None: return IRCResponse(ResponseType.Say, u'{0} (at {1})'.format(title, webPage.domain), message.ReplyTo, {'urlfollowURL': url}) return
def execute(self, message): """ @type message: IRCMessage """ responses = [] for feedName, feedDeets in DataStore.LRRChecker.iteritems(): if feedDeets['lastCheck'] > datetime.datetime.utcnow( ) - datetime.timedelta(minutes=10): continue DataStore.LRRChecker[feedName][ 'lastCheck'] = datetime.datetime.utcnow() feedPage = WebUtils.fetchURL(feedDeets['url']) if feedPage is None: #TODO: log an error here that the feed likely no longer exists! continue root = ET.fromstring(feedPage.body) item = root.find('channel/item') if item is None: #TODO: log an error here that the feed likely no longer exists! continue title = DataStore.LRRChecker[feedName]['lastTitle'] = item.find( 'title').text link = DataStore.LRRChecker[feedName][ 'lastLink'] = WebUtils.shortenGoogl(item.find('link').text) newestDate = dparser.parse(item.find('pubDate').text, fuzzy=True, ignoretz=True) if newestDate > feedDeets['lastUpdate']: DataStore.LRRChecker[feedName]['lastUpdate'] = newestDate if feedDeets['suppress']: DataStore.LRRChecker[feedName]['suppress'] = False else: response = 'New {0}! Title: {1} | {2}'.format( feedName, title, link) responses.append( IRCResponse(ResponseType.Say, response, '#desertbus')) return responses
def _pizzaTracker(self, orderID): """ @type orderID: str """ steps = [u"{}'s pizza order has been placed", u"{}'s pizza is being prepared", u"{}'s pizza is in the oven", u"{}'s pizza is sitting on a shelf, waiting for a driver", u"{}'s pizza is out for delivery", u"{}'s pizza has been delivered! Tracking stopped"] trackingDetails = self.trackers[orderID] trackURL = u'http://www.dominos.co.uk/checkout/pizzaTrackeriFrame.aspx?id={}'.format(orderID) page = WebUtils.fetchURL(trackURL) if page is not None: root = BeautifulSoup(page.body) stepImg = root.find('img') if stepImg is not None and stepImg.has_attr('alt'): stepImgAlt = stepImg['alt'] stepMatch = re.search(r'^Step (?P<step>[0-9]+)$', stepImgAlt) if stepMatch: step = int(stepMatch.group('step')) if step > trackingDetails.step: trackingDetails.step = step self.bot.sendResponse(IRCResponse(ResponseType.Say, steps[step-1].format(trackingDetails.orderer), trackingDetails.channel.Name)) if step == 6: self._stopPizzaTracker(orderID) return # if we reach here the tracking page was invalid in some way self.bot.sendResponse(IRCResponse(ResponseType.Say, u"The pizza tracking page linked by {} " u"had some kind of error, tracking stopped".format(trackingDetails.orderer), trackingDetails.channel.Name)) self._stopPizzaTracker(orderID)
def FollowYouTube(self, videoID, message): if self.youtubeKey is None: return IRCResponse(ResponseType.Say, '[YouTube API key not found]', message.ReplyTo) fields = 'items(id,snippet(title,description,channelTitle),contentDetails(duration))' parts = 'snippet,contentDetails' url = 'https://www.googleapis.com/youtube/v3/videos?id={}&fields={}&part={}&key={}'.format(videoID, fields, parts, self.youtubeKey) webPage = WebUtils.fetchURL(url) webPage.body = webPage.body.decode('utf-8') j = json.loads(webPage.body) if 'items' not in j: return None title = j['items'][0]['snippet']['title'] description = j['items'][0]['snippet']['description'] channel = j['items'][0]['snippet']['channelTitle'] length = parse_duration(j["items"][0]["contentDetails"]["duration"]).total_seconds() m, s = divmod(int(length), 60) h, m = divmod(m, 60) if h > 0: length = u'{0:02d}:{1:02d}:{2:02d}'.format(h, m, s) else: length = u'{0:02d}:{1:02d}'.format(m, s) if not description: description = u'<no description available>' description = re.sub('(\n|\s)+', ' ', description) limit = 150 if len(description) > limit: description = u'{} ...'.format(description[:limit].rsplit(' ', 1)[0]) return IRCResponse(ResponseType.Say, self.graySplitter.join([title, length, channel, description]), message.ReplyTo, {'urlfollowURL': 'http://youtu.be/{}'.format(videoID)})
def execute(self, message): """ @type message: IRCMessage """ responses = [] for feedName, feedDeets in DataStore.LRRChecker.iteritems(): if feedDeets['lastCheck'] > datetime.datetime.utcnow() - datetime.timedelta(minutes=10): continue DataStore.LRRChecker[feedName]['lastCheck'] = datetime.datetime.utcnow() feedPage = WebUtils.fetchURL(feedDeets['url']) if feedPage is None: #TODO: log an error here that the feed likely no longer exists! continue root = ET.fromstring(feedPage.body) item = root.find('channel/item') if item is None: #TODO: log an error here that the feed likely no longer exists! continue title = DataStore.LRRChecker[feedName]['lastTitle'] = item.find('title').text link = DataStore.LRRChecker[feedName]['lastLink'] = WebUtils.shortenGoogl(item.find('link').text) newestDate = dparser.parse(item.find('pubDate').text, fuzzy=True, ignoretz=True) if newestDate > feedDeets['lastUpdate']: DataStore.LRRChecker[feedName]['lastUpdate'] = newestDate if feedDeets['suppress']: DataStore.LRRChecker[feedName]['suppress'] = False else: response = 'New {0}! Title: {1} | {2}'.format(feedName, title, link) responses.append(IRCResponse(ResponseType.Say, response, '#desertbus')) return responses
def FollowYouTube(self, videoID, message): if self.youtubeKey is None: return IRCResponse(ResponseType.Say, '[YouTube API key not found]', message.ReplyTo) url = 'https://gdata.youtube.com/feeds/api/videos/{0}?v=2&key={1}'.format(videoID, self.youtubeKey) webPage = WebUtils.fetchURL(url) webPage.body = webPage.body.decode('utf-8') titleMatch = re.search('<title>(?P<title>[^<]+?)</title>', webPage.body) if titleMatch: lengthMatch = re.search("<yt:duration seconds='(?P<length>[0-9]+?)'/>", webPage.body) descMatch = re.search("<media:description type='plain'>(?P<desc>[^<]+?)</media:description>", webPage.body) title = titleMatch.group('title') title = self.htmlParser.unescape(title) length = lengthMatch.group('length') m, s = divmod(int(length), 60) h, m = divmod(m, 60) if h > 0: length = u'{0:02d}:{1:02d}:{2:02d}'.format(h, m, s) else: length = u'{0:02d}:{1:02d}'.format(m, s) description = u'<no description available>' if descMatch: description = descMatch.group('desc') description = re.sub('<[^<]+?>', '', description) description = self.htmlParser.unescape(description) description = re.sub('\n+', ' ', description) description = re.sub('\s+', ' ', description) if len(description) > 150: description = description[:147] + u'...' return IRCResponse(ResponseType.Say, self.graySplitter.join([title, length, description]), message.ReplyTo) return
def execute(self, message): """ @type message: IRCMessage """ if len(message.ParameterList) < 3: return IRCResponse(ResponseType.Say, self.help, 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.fixer.io/latest?base={}&symbols={}" url = url.format(ccFrom, ccTo) response = WebUtils.fetchURL(url) jsonResponse = json.loads(response.body) rates = jsonResponse['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.iteritems(): data.append("{} {}".format(rate*amount, curr)) graySplitter = assembleFormattedText(A.normal[' ', A.fg.gray['|'], ' ']) return IRCResponse(ResponseType.Say, graySplitter.join(data), message.ReplyTo)
def execute(self, message): """ @type message: IRCMessage """ if len(message.ParameterList) != 1: return IRCResponse(ResponseType.Say, self.help, message.ReplyTo) subreddit = message.ParameterList[0].lower() url = "https://api.imgur.com/3/gallery/r/{}/time/all/{}" url = url.format(subreddit, random.randint(0, 100)) response = WebUtils.fetchURL(url, self.headers) jsonResponse = json.loads(response.body) images = jsonResponse['data'] if not images: return IRCResponse( ResponseType.Say, "The subreddit '{}' doesn't seem to have any images posted to it (or it doesn't exist!)" .format(subreddit), message.ReplyTo) image = random.choice(images) data = [] if image['title'] is not None: data.append(image['title']) if image['nsfw']: data.append(u'\x034\x02NSFW!\x0F') if image['animated']: data.append(u'\x032\x02Animated!\x0F') data.append(image['link']) graySplitter = assembleFormattedText(A.normal[' ', A.fg.gray['|'], ' ']) return IRCResponse(ResponseType.Say, graySplitter.join(data), message.ReplyTo)
def FollowSteam(self, steamAppId, message): webPage = WebUtils.fetchURL('http://store.steampowered.com/api/appdetails/?appids={0}&cc=US&l=english&v=1'.format(steamAppId)) response = json.loads(webPage.body) if not response[steamAppId]['success']: return # failure appData = response[steamAppId]['data'] data = [] # name data.append(assembleFormattedText(A.normal[appData['name'], A.fg.gray[' by '], u', '.join(appData['developers'])])) # genres data.append(u'Genres: ' + ', '.join([genre['description'] for genre in appData['genres']])) # release date releaseDate = appData['release_date'] if not releaseDate['coming_soon']: data.append(u'Release Date: ' + releaseDate['date']) else: data.append(assembleFormattedText(A.normal['Release Date: ', A.fg.cyan[str(releaseDate['date'])]])) # 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 = assembleFormattedText(A.normal[A.fg.red[str(metaScore)]]) elif metaScore < 75: metacritic = assembleFormattedText(A.normal[A.fg.yellow[str(metaScore)]]) else: metacritic = assembleFormattedText(A.normal[A.fg.green[str(metaScore)]]) data.append(u'Metacritic: {0}'.format(metacritic)) # prices if 'price_overview' in appData: prices = {'USD': appData['price_overview'], 'GBP': self.getSteamPrice(steamAppId, 'GB'), 'EUR': self.getSteamPrice(steamAppId, 'FR'), 'AUD': self.getSteamPrice(steamAppId, 'AU')} currencies = {'USD': u'$', 'GBP': u'\u00A3', 'EUR': u'\u20AC', 'AUD': u'AU$'} if prices['AUD']['final'] == prices['USD']['final']: del prices['AUD'] priceString = u'/'.join([currencies[val['currency']] + unicode(val['final'] / 100.0) for val in prices.values()]) if prices['USD']['discount_percent'] > 0: priceString += assembleFormattedText(A.normal[A.fg.green[' ({0}% sale!)'.format(prices['USD']['discount_percent'])]]) data.append(priceString) # description description = appData['about_the_game'] if description is not None: limit = 150 description = re.sub(r'(<[^>]+>|[\r\n\t])+', assembleFormattedText(A.normal[' ', A.fg.gray['>'], ' ']), description) if len(description) > limit: description = u'{0} ...'.format(description[:limit].rsplit(' ', 1)[0]) data.append(description) return IRCResponse(ResponseType.Say, self.graySplitter.join(data), message.ReplyTo)
def FollowKickstarter(self, ksID, message): webPage = WebUtils.fetchURL('https://www.kickstarter.com/projects/{}/'.format(ksID)) soup = BeautifulSoup(webPage.body) data = [] title = soup.find(class_='NS_projects__header') if title is not None: creator = soup.find(attrs={'data-modal-class': 'modal_project_by'}) if creator is not None: data.append(unicode(assembleFormattedText(A.normal['{0}', A.fg.gray[' by '], '{1}'])).format(title.h2.text.strip(), creator.text.strip())) else: data.append(title.h2.text.strip()) stats = soup.find(id='stats') # all of this is now in page javascript, extracting it will be a pain... if stats is not None: backerCount = stats.find(id='backers_count') if backerCount is not None: data.append('Backers: {0:,}'.format(int(backerCount['data-backers-count']))) pledged = stats.find(id='pledged') if pledged is not None: if float(pledged['data-percent-raised']) >= 1.0: percentageString = A.fg.green['({3:,.0f}% funded)'] else: percentageString = A.fg.red['({3:,.0f}% funded)'] if int(backerCount['data-backers-count']) > 0: pledgePerBacker = float(pledged['data-pledged']) / int(backerCount['data-backers-count']) else: pledgePerBacker = 0 pledgePerBackerString = A.fg.gray['{4:,.0f}/backer'] pledgedString = assembleFormattedText(A.normal['Pledged: {0:,.0f}', A.fg.gray['/'], '{1:,.0f} {2} ', percentageString, ' ', pledgePerBackerString]) data.append(pledgedString.format(float(pledged['data-pledged']), float(pledged['data-goal']), pledged.data['data-currency'], float(pledged['data-percent-raised']) * 100, pledgePerBacker)) findState = soup.find(id='main_content') if 'Project-state-canceled' in findState['class']: data.append(assembleFormattedText(A.normal[A.fg.red['Cancelled']])) elif 'Project-state-suspended' in findState['class']: data.append(assembleFormattedText(A.normal[A.fg.blue['Suspended']])) elif 'Project-state-failed' in findState['class']: data.append(assembleFormattedText(A.normal[A.fg.red['Failed']])) elif 'Project-state-successful' in findState['class']: data.append(assembleFormattedText(A.normal[A.fg.green['Successful']])) elif 'Project-state-live' in findState['class']: duration = stats.find(id='project_duration_data') if duration is not None: remaining = float(duration['data-hours-remaining']) days = math.floor(remaining/24) hours = remaining % 24 data.append('Duration: {0:.0f} days {1:.1f} hours to go'.format(days, hours)) return IRCResponse(ResponseType.Say, self.graySplitter.join(data), message.ReplyTo, {'urlfollowURL': 'https://www.kickstarter.com/projects/{}/'.format(ksID)})
def FollowImgur(self, imgurID, message): if self.imgurClientID is None: return IRCResponse(ResponseType.Say, '[imgur Client ID not found]', message.ReplyTo) if imgurID.startswith('gallery/'): imgurID = imgurID.replace('gallery/', '') albumLink = False if imgurID.startswith('a/'): imgurID = imgurID.replace('a/', '') url = 'https://api.imgur.com/3/album/{0}'.format(imgurID) albumLink = True else: url = 'https://api.imgur.com/3/image/{0}'.format(imgurID) headers = [('Authorization', 'Client-ID {0}'.format(self.imgurClientID))] webPage = WebUtils.fetchURL(url, headers) if webPage is None: url = 'https://api.imgur.com/3/gallery/{0}'.format(imgurID) webPage = WebUtils.fetchURL(url, headers) if webPage is None: return response = json.loads(webPage.body) imageData = response['data'] if imageData['title'] is None: url = 'https://api.imgur.com/3/gallery/{0}'.format(imgurID) webPage = WebUtils.fetchURL(url, headers) if webPage is not None: imageData = json.loads(webPage.body)['data'] if imageData['title'] is None: webPage = WebUtils.fetchURL('http://imgur.com/{0}'.format(imgurID)) imageData['title'] = self.GetTitle(webPage.body).replace(' - Imgur', '') if imageData['title'] == 'imgur: the simple image sharer': imageData['title'] = None data = [] if imageData['title'] is not None: data.append(imageData['title']) else: data.append(u'<No Title>') if imageData['nsfw']: data.append(u'\x034\x02NSFW!\x0F') if albumLink: data.append(u'Album: {0} Images'.format(imageData['images_count'])) else: if 'is_album' in imageData and imageData['is_album']: data.append(u'Album: {0:,d} Images'.format(len(imageData['images']))) else: if imageData[u'animated']: data.append(u'\x032\x02Animated!\x0F') data.append(u'{0:,d}x{1:,d}'.format(imageData['width'], imageData['height'])) data.append(u'Size: {0:,d}kb'.format(int(imageData['size'])/1024)) data.append(u'Views: {0:,d}'.format(imageData['views'])) return IRCResponse(ResponseType.Say, self.graySplitter.join(data), message.ReplyTo)
def FollowImgur(self, imgurID, message): if self.imgurClientID is None: return IRCResponse(ResponseType.Say, '[imgur Client ID not found]', message.ReplyTo) if imgurID.startswith('gallery/'): imgurID = imgurID.replace('gallery/', '') albumLink = False if imgurID.startswith('a/'): imgurID = imgurID.replace('a/', '') url = 'https://api.imgur.com/3/album/{0}'.format(imgurID) albumLink = True else: url = 'https://api.imgur.com/3/image/{0}'.format(imgurID) headers = [('Authorization', 'Client-ID {0}'.format(self.imgurClientID))] webPage = WebUtils.fetchURL(url, headers) if webPage is None: url = 'https://api.imgur.com/3/gallery/{0}'.format(imgurID) webPage = WebUtils.fetchURL(url, headers) if webPage is None: return response = json.loads(webPage.body) imageData = response['data'] if imageData['title'] is None: url = 'https://api.imgur.com/3/gallery/{0}'.format(imgurID) webPage = WebUtils.fetchURL(url, headers) if webPage is not None: imageData = json.loads(webPage.body)['data'] if imageData['title'] is None: webPage = WebUtils.fetchURL( 'http://imgur.com/{0}'.format(imgurID)) imageData['title'] = self.GetTitle(webPage.body).replace( ' - Imgur', '') if imageData['title'] == 'imgur: the simple image sharer': imageData['title'] = None data = [] if imageData['title'] is not None: data.append(imageData['title']) else: data.append(u'<No Title>') if imageData['nsfw']: data.append(u'\x034\x02NSFW!\x0F') if albumLink: data.append(u'Album: {0} Images'.format(imageData['images_count'])) else: if 'is_album' in imageData and imageData['is_album']: data.append(u'Album: {0:,d} Images'.format( len(imageData['images']))) else: if imageData[u'animated']: data.append(u'\x032\x02Animated!\x0F') data.append(u'{0:,d}x{1:,d}'.format(imageData['width'], imageData['height'])) data.append(u'Size: {0:,d}kb'.format( int(imageData['size']) / 1024)) data.append(u'Views: {0:,d}'.format(imageData['views'])) return IRCResponse( ResponseType.Say, self.graySplitter.join(data), message.ReplyTo, {'urlfollowURL': '[nope, imgur is too hard. also, pointless?]'})
def execute(self, message): """ @type message: IRCMessage """ searchTerm = 'http://gatherer.wizards.com/pages/search/default.aspx?name=' for param in message.ParameterList: searchTerm += '+[%s]' % param webPage = WebUtils.fetchURL(searchTerm) soup = BeautifulSoup(webPage.body) name = soup.find('div', {'id': 'ctl00_ctl00_ctl00_MainContent_SubContent_SubContent_nameRow'}) if name is None: searchResults = soup.find('div', {'id': 'ctl00_ctl00_ctl00_MainContent_SubContent_SubContent_searchResultsContainer'}) if searchResults is None: return IRCResponse(ResponseType.Say, 'No cards found: ' + searchTerm, message.ReplyTo) else: cardItems = searchResults.find_all(class_='cardItem') # potentially return first item here return IRCResponse(ResponseType.Say, '{0} cards found: {1}'.format(len(cardItems), searchTerm), message.ReplyTo) name = name.find('div', 'value').text.strip() types = u' | T: ' + soup.find('div', {'id': 'ctl00_ctl00_ctl00_MainContent_SubContent_SubContent_typeRow'}).find('div', 'value').text.strip() rarity = u' | R: ' + soup.find('div', {'id': 'ctl00_ctl00_ctl00_MainContent_SubContent_SubContent_rarityRow'}).find('div', 'value').text.strip() manaCost = soup.find('div', {'id': 'ctl00_ctl00_ctl00_MainContent_SubContent_SubContent_manaRow'}) if manaCost is not None: manaCost = unicode(manaCost.find('div', 'value')) manaCost = u' | MC: ' + self.translateSymbols(manaCost) manaCost = re.sub('<[^>]+?>', '', manaCost) manaCost = manaCost.replace('\n', '') else: manaCost = u'' convCost = soup.find('div', {'id': 'ctl00_ctl00_ctl00_MainContent_SubContent_SubContent_cmcRow'}) if convCost is not None: convCost = u' | CMC: ' + convCost.find('div', 'value').text.strip() else: convCost = u'' cardText = soup.find('div', {'id': 'ctl00_ctl00_ctl00_MainContent_SubContent_SubContent_textRow'}) if cardText is not None: cardTexts = cardText.find_all('div', 'cardtextbox') texts = [] for text in cardTexts: text = self.translateSymbols(text) text = re.sub('<[^>]+?>', '', text) texts.append(text) cardText = u' | CT: ' + u' > '.join(texts) else: cardText = u'' flavText = soup.find('div', {'id': 'ctl00_ctl00_ctl00_MainContent_SubContent_SubContent_FlavorText'}) if message.Command.endswith('f') and flavText is not None: flavTexts = flavText.find_all('div', 'cardtextbox') texts = [] for text in flavTexts: texts.append(unicode(text.text)) flavText = u' | FT: ' + ' > '.join(texts) else: flavText = u'' powTough = soup.find('div', {'id': 'ctl00_ctl00_ctl00_MainContent_SubContent_SubContent_ptRow'}) if powTough is not None: powTough = u' | P/T: ' + powTough.find('div', 'value').text.strip().replace(' ', '') else: powTough = u'' reply = name + manaCost + convCost + types + cardText + flavText + powTough + rarity return IRCResponse(ResponseType.Say, reply, message.ReplyTo)
def FollowYouTube(self, videoID, message): if self.youtubeKey is None: return IRCResponse(ResponseType.Say, '[YouTube API key not found]', message.ReplyTo) fields = 'items(id,snippet(title,description,channelTitle,liveBroadcastContent),contentDetails(duration),statistics(viewCount),liveStreamingDetails(scheduledStartTime))' parts = 'snippet,contentDetails,statistics,liveStreamingDetails' url = 'https://www.googleapis.com/youtube/v3/videos?id={}&fields={}&part={}&key={}'.format( videoID, fields, parts, self.youtubeKey) webPage = WebUtils.fetchURL(url) webPage.body = webPage.body.decode('utf-8') j = json.loads(webPage.body) 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 = u'{0:02d}:{1:02d}:{2:02d}'.format(h, m, s) else: length = u'{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 = StringUtils.deltaTimeToString(delta, 'm') timeString = assembleFormattedText( 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 = unicode( assembleFormattedText(A.normal[A.fg.red[A.bold['{} Live']]])) status = status.format(u'●') 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 = u'<no description available>' description = re.sub('(\n|\s)+', ' ', description) limit = 150 if len(description) > limit: description = u'{} ...'.format(description[:limit].rsplit(' ', 1)[0]) data.append(description) return IRCResponse( ResponseType.Say, self.graySplitter.join(data), message.ReplyTo, {'urlfollowURL': 'http://youtu.be/{}'.format(videoID)})
def FollowKickstarter(self, ksID, message): webPage = WebUtils.fetchURL( 'https://www.kickstarter.com/projects/{}/description'.format(ksID)) soup = BeautifulSoup(webPage.body) data = [] shorturl = soup.find(rel='shorturl')['href'] if shorturl is None: shorturl = 'https://www.kickstarter.com/projects/{}/'.format(ksID) title = soup.find(property='og:title') if title is not None: # live projects creator = soup.find(attrs={'data-modal-class': 'modal_project_by'}) # completed projects if creator is None or not creator.text: creator = soup.find( class_='green-dark', attrs={'data-modal-class': 'modal_project_by'}) if creator is not None: data.append( unicode( assembleFormattedText( A.normal['{0}', A.fg.gray[' by '], '{1}'])).format(title['content'].strip(), creator.text.strip())) else: data.append(title['content'].strip()) stats = soup.find(id='stats') # projects in progress if stats is not None: backerCount = soup.find(id='backers_count') if backerCount is not None: backerCount = int(backerCount['data-backers-count']) # completed projects else: backerCount = soup.find(class_='NS_campaigns__spotlight_stats') if backerCount is not None: backerCount = int( backerCount.b.text.strip().split()[0].replace(',', '')) data.append('Backers: {:,d}'.format(backerCount)) if stats is not None: pledgeData = soup.find(id='pledged') if pledgeData is not None: pledged = float(pledgeData['data-pledged']) goal = float(pledgeData['data-goal']) percentage = float(pledgeData['data-percent-raised']) if backerCount > 0: pledgePerBacker = pledged / backerCount else: pledgePerBacker = 0 else: money = soup.select('span.money') if money: pledgedString = money[1].text.strip() goalString = money[2].text.strip() pledged = float(re.sub(ur'[^0-9.]', u'', pledgedString)) goal = float(re.sub(ur'[^0-9.]', u'', goalString)) percentage = (pledged / goal) if backerCount > 0: pledgePerBacker = pledged / backerCount else: pledgePerBacker = 0 # no longer any way to get this? #currency = soup.select('span.money.no-code')[-1]['class'] #currency.remove('money') #currency.remove('no-code') #currency = currency[0].upper() if percentage >= 1.0: percentageString = A.fg.green['({2:,.0f}% funded)'] else: percentageString = A.fg.red['({2:,.0f}% funded)'] pledgePerBackerString = A.fg.gray['{3:,.0f}/backer'] pledgedString = assembleFormattedText(A.normal['Pledged: {0:,.0f}', A.fg.gray['/'], '{1:,.0f} ', percentageString, ' ', pledgePerBackerString]) data.append( pledgedString.format(pledged, goal, percentage * 100, pledgePerBacker)) findState = soup.find(id='main_content') if 'Campaign-state-canceled' in findState['class']: data.append(assembleFormattedText(A.normal[A.fg.red['Cancelled']])) elif 'Campaign-state-suspended' in findState['class']: data.append(assembleFormattedText( A.normal[A.fg.blue['Suspended']])) elif 'Campaign-state-failed' in findState['class']: data.append(assembleFormattedText(A.normal[A.fg.red['Failed']])) elif 'Campaign-state-successful' in findState['class']: data.append( assembleFormattedText(A.normal[A.fg.green['Successful']])) elif 'Campaign-state-live' in findState['class']: duration = soup.find(id='project_duration_data') if duration is not None: remaining = float(duration['data-hours-remaining']) days = math.floor(remaining / 24) hours = remaining % 24 data.append( 'Duration: {0:.0f} days {1:.1f} hours to go'.format( days, hours)) return IRCResponse(ResponseType.Say, self.graySplitter.join(data), message.ReplyTo, {'urlfollowURL': shorturl})
def _import(self, message): """import <url> [<alias(es)>] - imports all aliases from the given address, or only the listed aliases""" if message.User.Name not in GlobalVars.admins: return IRCResponse(ResponseType.Say, u"Only my admins may import aliases!", message.ReplyTo) if len(message.ParameterList) < 2: return IRCResponse(ResponseType.Say, u"You didn't give a url to import from!", message.ReplyTo) if len(message.ParameterList) > 2: onlyListed = True importList = [alias.lower() for alias in message.ParameterList[2:]] else: onlyListed = False url = message.ParameterList[1] try: page = WebUtils.fetchURL(url) except ValueError: return IRCResponse(ResponseType.Say, u"'{}' is not a valid URL".format(url), message.ReplyTo) if page is None: return IRCResponse(ResponseType.Say, u"Failed to open page at {}".format(url), message.ReplyTo) text = page.body text = UnicodeDammit(text).unicode_markup lines = text.splitlines() numAliases = 0 numHelpTexts = 0 for lineNumber, line in enumerate(lines): # Skip over blank lines if line == u"": continue splitLine = line.split() if splitLine[0].lower() != u"{}alias".format(self.bot.commandChar): return IRCResponse(ResponseType.Say, u"Line {} at {} does not begin with {}alias".format(lineNumber, url, self.bot.commandChar), message.ReplyTo) subCommand = splitLine[1].lower() if subCommand not in [u"add", u"help"]: return IRCResponse(ResponseType.Say, u"Line {} at {} is not an add or help command".format(lineNumber, url), message.ReplyTo) aliasName = splitLine[2].lower() aliasCommand = splitLine[3:] aliasCommand[0] = aliasCommand[0].lower() # Skip over aliases that weren't listed, if any were listed if onlyListed and aliasName not in importList: continue if subCommand == u"add": self._newAlias(aliasName, aliasCommand) numAliases += 1 elif subCommand == u"help": aliasHelp = u" ".join(splitLine[3:]) self.aliasHelpDict[aliasName] = aliasHelp numHelpTexts += 1 return IRCResponse(ResponseType.Say, u"Imported {} alias(es) and {} help string(s) from {}".format(numAliases, numHelpTexts, url), message.ReplyTo)
class Slurp(CommandInterface): triggers = ['slurp'] help = "slurp <attribute> <url> <css selector> - scrapes the given attribute from the tag selected at the given url" runInThread = True runInThread = True htmlParser = HTMLParser.HTMLParser() def execute(self, message): """ @type message: IRCMessage """ if len(message.ParameterList) < 3: return IRCResponse( ResponseType.Say, u"Not enough parameters, usage: {}".format(self.help), message.ReplyTo) prop, url, selector = (message.ParameterList[0], message.ParameterList[1], u" ".join(message.ParameterList[2:])) if not re.match(ur'^\w+://', url): url = u"http://{}".format(url) if 'slurp' in message.Metadata and url in message.Metadata['slurp']: soup = message.Metadata['slurp'][url] else: page = WebUtils.fetchURL(url) if page is None: return IRCResponse(ResponseType.Say, u"Problem fetching {}".format(url), message.ReplyTo) soup = BeautifulSoup(page.body) tag = soup.select_one(selector) if tag is None: return IRCResponse( ResponseType.Say, u"'{}' does not select a tag at {}".format(selector, url), message.ReplyTo) specials = {'tagname': tag.name, 'text': tag.text} if prop in specials: value = specials[prop] elif prop in tag.attrs: value = tag[prop] else: return IRCResponse( ResponseType.Say, u"The tag selected by '{}' ({}) does not have attribute '{}'". format(selector, tag.name, prop), message.ReplyTo) if not isinstance(value, basestring): value = u" ".join(value) # sanitize the value value = value.strip() value = re.sub(ur'[\r\n]+', u' ', value) value = re.sub(ur'\s+', u' ', value) value = self.htmlParser.unescape(value) return IRCResponse(ResponseType.Say, value, message.ReplyTo, extraVars={'slurpURL': url}, metadata={'slurp': { url: soup }})
def execute(self, message): """ @type 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 = urllib.quote(message.Parameters) url = 'http://api.urbandictionary.com/v0/define?term={0}'.format(search) webPage = WebUtils.fetchURL(url) response = json.loads(webPage.body) if len(response['list']) == 0: return IRCResponse(ResponseType.Say, "No entry found for '{0}'".format(search), message.ReplyTo) graySplitter = assembleFormattedText(A.normal[' ', A.fg.gray['|'], ' ']) defn = response['list'][0] word = defn['word'] definition = defn['definition'] definition = graySplitter.join([s.strip() for s in definition.strip().split('\r\n')]) example = defn['example'] example = graySplitter.join([s.strip() for s in example.strip().split('\r\n')]) author = defn['author'] up = defn['thumbs_up'] down = defn['thumbs_down'] more = 'http://{}.urbanup.com/'.format(word) if word.lower() != message.Parameters.lower(): word = "{0} (Contains '{0}')".format(word, message.Parameters) defFormatString = unicode(assembleFormattedText(A.normal[A.bold["{0}:"], " {1}"])) exampleFormatString = unicode(assembleFormattedText(A.normal[A.bold["Example(s):"], " {0}"])) byFormatString = unicode(assembleFormattedText(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
def execute(self, message): """ @type message: IRCMessage """ searchTerm = 'http://gatherer.wizards.com/pages/search/default.aspx?name=' for param in message.ParameterList: searchTerm += '+[%s]' % param webPage = WebUtils.fetchURL(searchTerm) soup = BeautifulSoup(webPage.body) name = soup.find('div', { 'id': 'ctl00_ctl00_ctl00_MainContent_SubContent_SubContent_nameRow' }) if name is None: searchResults = soup.find( 'div', { 'id': 'ctl00_ctl00_ctl00_MainContent_SubContent_SubContent_searchResultsContainer' }) if searchResults is None: return IRCResponse(ResponseType.Say, 'No cards found: ' + searchTerm, message.ReplyTo) else: cardItems = searchResults.find_all(class_='cardItem') # potentially return first item here return IRCResponse( ResponseType.Say, '{0} cards found: {1}'.format(len(cardItems), searchTerm), message.ReplyTo) name = name.find('div', 'value').text.strip() types = u' | T: ' + soup.find('div', { 'id': 'ctl00_ctl00_ctl00_MainContent_SubContent_SubContent_typeRow' }).find('div', 'value').text.strip() rarity = u' | R: ' + soup.find('div', { 'id': 'ctl00_ctl00_ctl00_MainContent_SubContent_SubContent_rarityRow' }).find('div', 'value').text.strip() manaCost = soup.find('div', { 'id': 'ctl00_ctl00_ctl00_MainContent_SubContent_SubContent_manaRow' }) if manaCost is not None: manaCost = unicode(manaCost.find('div', 'value')) manaCost = u' | MC: ' + self.translateSymbols(manaCost) manaCost = re.sub('<[^>]+?>', '', manaCost) manaCost = manaCost.replace('\n', '') else: manaCost = u'' convCost = soup.find('div', { 'id': 'ctl00_ctl00_ctl00_MainContent_SubContent_SubContent_cmcRow' }) if convCost is not None: convCost = u' | CMC: ' + convCost.find('div', 'value').text.strip() else: convCost = u'' cardText = soup.find('div', { 'id': 'ctl00_ctl00_ctl00_MainContent_SubContent_SubContent_textRow' }) if cardText is not None: cardTexts = cardText.find_all('div', 'cardtextbox') texts = [] for text in cardTexts: text = self.translateSymbols(text) text = re.sub('<[^>]+?>', '', text) texts.append(text) cardText = u' | CT: ' + u' > '.join(texts) else: cardText = u'' flavText = soup.find('div', { 'id': 'ctl00_ctl00_ctl00_MainContent_SubContent_SubContent_FlavorText' }) if message.Command.endswith('f') and flavText is not None: flavTexts = flavText.find_all('div', 'cardtextbox') texts = [] for text in flavTexts: texts.append(unicode(text.text)) flavText = u' | FT: ' + ' > '.join(texts) else: flavText = u'' powTough = soup.find('div', { 'id': 'ctl00_ctl00_ctl00_MainContent_SubContent_SubContent_ptRow' }) if powTough is not None: powTough = u' | P/T: ' + powTough.find( 'div', 'value').text.strip().replace(' ', '') else: powTough = u'' reply = name + manaCost + convCost + types + cardText + flavText + powTough + rarity return IRCResponse(ResponseType.Say, reply, message.ReplyTo)
def execute(self, message): """ @type 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 = urllib.quote(message.Parameters) url = 'http://api.urbandictionary.com/v0/define?term={0}'.format( search) webPage = WebUtils.fetchURL(url) response = json.loads(webPage.body) if len(response['list']) == 0: return IRCResponse( ResponseType.Say, "No entry found for '{0}'".format(message.Parameters), message.ReplyTo) graySplitter = assembleFormattedText(A.normal[' ', A.fg.gray['|'], ' ']) defn = response['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 = unicode( assembleFormattedText(A.normal[A.bold["{0}:"], " {1}"])) exampleFormatString = unicode( assembleFormattedText(A.normal[A.bold["Example(s):"], " {0}"])) byFormatString = unicode( assembleFormattedText(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
def getSteamPrice(cls, appId, region): webPage = WebUtils.fetchURL('http://store.steampowered.com/api/appdetails/?appids={0}&cc={1}&l=english&v=1'.format(appId, region)) response = json.loads(webPage.body) if region == 'AU': response[appId]['data']['price_overview']['currency'] = 'AUD' return response[appId]['data']['price_overview']
def _import(self, message): """import <url> [<alias(es)>] - imports all aliases from the given address, or only the listed aliases""" if message.User.Name not in GlobalVars.admins: return IRCResponse(ResponseType.Say, u"Only my admins may import aliases!", message.ReplyTo) if len(message.ParameterList) < 2: return IRCResponse(ResponseType.Say, u"You didn't give a url to import from!", message.ReplyTo) if len(message.ParameterList) > 2: onlyListed = True importList = [alias.lower() for alias in message.ParameterList[2:]] else: onlyListed = False url = message.ParameterList[1] try: page = WebUtils.fetchURL(url) except ValueError: return IRCResponse(ResponseType.Say, u"'{}' is not a valid URL".format(url), message.ReplyTo) if page is None: return IRCResponse(ResponseType.Say, u"Failed to open page at {}".format(url), message.ReplyTo) text = page.body text = UnicodeDammit(text).unicode_markup lines = text.splitlines() numAliases = 0 numHelpTexts = 0 for lineNumber, line in enumerate(lines): # Skip over blank lines if line == u"": continue splitLine = line.split() if splitLine[0].lower() != u"{}alias".format(self.bot.commandChar): return IRCResponse( ResponseType.Say, u"Line {} at {} does not begin with {}alias".format( lineNumber, url, self.bot.commandChar), message.ReplyTo) subCommand = splitLine[1].lower() if subCommand not in [u"add", u"help"]: return IRCResponse( ResponseType.Say, u"Line {} at {} is not an add or help command".format( lineNumber, url), message.ReplyTo) aliasName = splitLine[2].lower() aliasCommand = splitLine[3:] aliasCommand[0] = aliasCommand[0].lower() # Skip over aliases that weren't listed, if any were listed if onlyListed and aliasName not in importList: continue if subCommand == u"add": self._newAlias(aliasName, aliasCommand) numAliases += 1 elif subCommand == u"help": aliasHelp = u" ".join(splitLine[3:]) self.aliasHelpDict[aliasName] = aliasHelp numHelpTexts += 1 return IRCResponse( ResponseType.Say, u"Imported {} alias(es) and {} help string(s) from {}".format( numAliases, numHelpTexts, url), message.ReplyTo)
def FollowSteam(self, steamType, steamId, message): steamType = {'app': 'app', 'sub': 'package'}[steamType] webPage = WebUtils.fetchURL( 'http://store.steampowered.com/api/{0}details/?{0}ids={1}&cc=US&l=english&v=1' .format(steamType, steamId)) response = json.loads(webPage.body) if not response[steamId]['success']: return # failure appData = response[steamId]['data'] data = [] # name if 'developers' in appData: name = assembleFormattedText( A.normal[appData['name'], A.fg.gray[' by '], u', '.join(appData['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 = u'Package containing: {}'.format(u', '.join(appNames)) data.append(apps) # genres if 'genres' in appData: data.append(u'Genres: ' + ', '.join( [genre['description'] for genre in appData['genres']])) # release date releaseDate = appData['release_date'] if not releaseDate['coming_soon']: if releaseDate['date']: data.append(u'Released: ' + releaseDate['date']) else: data.append( assembleFormattedText( A.normal['To Be Released: ', A.fg.cyan[A.bold[str(releaseDate['date'])]]])) # 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 = assembleFormattedText( A.normal[A.fg.red[str(metaScore)]]) elif metaScore < 75: metacritic = assembleFormattedText( A.normal[A.fg.orange[str(metaScore)]]) else: metacritic = assembleFormattedText( A.normal[A.fg.green[str(metaScore)]]) data.append(u'Metacritic: {0}'.format(metacritic)) # prices 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': u'$', 'GBP': u'\u00A3', 'EUR': u'\u20AC', 'AUD': u'AU$' } 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.iteritems() if val} priceString = u'/'.join([ currencies[val['currency']] + unicode(val['final'] / 100.0) for val in prices.values() ]) if prices['USD']['discount_percent'] > 0: priceString += assembleFormattedText( A.normal[A.fg.green[A.bold[' ({0}% sale!)'.format( prices['USD']['discount_percent'])]]]) data.append(priceString) # platforms if 'platforms' in appData: platforms = appData['platforms'] platformArray = [] if platforms['windows']: platformArray.append(u'Win') else: platformArray.append(u'---') if platforms['mac']: platformArray.append(u'Mac') else: platformArray.append(u'---') if platforms['linux']: platformArray.append(u'Lin') else: platformArray.append(u'---') data.append(u'/'.join(platformArray)) # description if 'about_the_game' in appData and appData[ 'about_the_game'] is not None: limit = 100 description = re.sub( r'(<[^>]+>|[\r\n\t])+', assembleFormattedText(A.normal[' ', A.fg.gray['>'], ' ']), appData['about_the_game']) if len(description) > limit: description = u'{0} ...'.format(description[:limit].rsplit( ' ', 1)[0]) data.append(description) return IRCResponse( ResponseType.Say, self.graySplitter.join(data), message.ReplyTo, { 'urlfollowURL': 'http://store.steampowered.com/{}/{}'.format({ 'app': 'app', 'package': 'sub' }[steamType], steamId) })
def execute(self, message): """ @type 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 = urllib.quote(message.Parameters) url = 'http://www.urbandictionary.com/define.php?term={0}'.format(search) webPage = WebUtils.fetchURL(url) soup = BeautifulSoup(webPage.body) # replace link tags with their contents [a.unwrap() for a in soup.find_all('a')] box = soup.find('div', {'class': 'box'}) if not box: return IRCResponse(ResponseType.Say, "No entry found for '{0}'".format(search), message.ReplyTo) graySplitter = assembleFormattedText(A.normal[' ', A.fg.gray['|'], ' ']) word = box.find('div', {'class': 'word'}).text.strip() # 2014-01-28 really, urban dictionary? 'definition' to 'meaning'? what an important change! definition = box.find('div', {'class': 'meaning'}) if definition.br is not None: definition.br.replace_with('\n') definition = graySplitter.join([s.strip() for s in definition.text.strip().split('\n')]) example = box.find('div', {'class': 'example'}) if example.br is not None: example.br.replace_with('\n') example = graySplitter.join([s.strip() for s in example.text.strip().split('\n')]) author = box.find('div', {'class': 'contributor'}).text.strip().replace('\n', ' ') counts = box.find('div', {'class': 'thumbs-counts'}).find_all('span', {'class': 'count'}) up = counts[0].text down = counts[1].text if word.lower() != message.Parameters.lower(): word = "{0} (Contains '{0}')".format(word, message.Parameters) defFormatString = unicode(assembleFormattedText(A.normal[A.bold["{0}:"], " {1}"])) exampleFormatString = unicode(assembleFormattedText(A.normal[A.bold["Example(s):"], " {0}"])) byFormatString = unicode(assembleFormattedText(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, url), message.ReplyTo)] return responses
def FollowKickstarter(self, ksID, message): webPage = WebUtils.fetchURL('https://www.kickstarter.com/projects/{}/description'.format(ksID)) soup = BeautifulSoup(webPage.body) data = [] shorturl = soup.find(rel='shorturl')['href'] if shorturl is None: shorturl = 'https://www.kickstarter.com/projects/{}/'.format(ksID) title = soup.find(property='og:title') if title is not None: creator = soup.find(attrs={'data-modal-class': 'modal_project_by'}) if creator is not None: data.append(unicode(assembleFormattedText(A.normal['{0}', A.fg.gray[' by '], '{1}'])).format(title['content'].strip(), creator.text.strip())) else: data.append(title['content'].strip()) stats = soup.find(id='stats') # projects in progress if stats is not None: backerCount = stats.find(id='backers_count') if backerCount is not None: backerCount = int(backerCount['data-backers-count']) # completed projects else: backerCount = soup.find(class_='NS_projects__spotlight_stats') if backerCount is not None: backerCount = int(backerCount.b.text.strip().split()[0].replace(',', '')) data.append('Backers: {0:,}'.format(backerCount)) if stats is not None: pledgeData = stats.find(id='pledged') if pledgeData is not None: pledged = float(pledgeData['data-pledged']) goal = float(pledgeData['data-goal']) percentage = float(pledgeData['data-percent-raised']) if backerCount > 0: pledgePerBacker = pledged / backerCount else: pledgePerBacker = 0 currency = stats.find_all(attrs={'data-currency': True})[-1]['data-currency'] else: money = soup.select('span.money.no-code') if money: pledgedString = money[0].text.strip() goalString = money[1].text.strip() pledged = float(re.sub(ur'[^0-9.]', u'', pledgedString)) goal = float(re.sub(ur'[^0-9.]', u'', goalString)) percentage = (pledged / goal) if backerCount > 0: pledgePerBacker = pledged / backerCount else: pledgePerBacker = 0 currency = soup.select('span.money.no-code')[-1]['class'] currency.remove('money') currency.remove('no-code') currency = currency[0].upper() if percentage >= 1.0: percentageString = A.fg.green['({3:,.0f}% funded)'] else: percentageString = A.fg.red['({3:,.0f}% funded)'] pledgePerBackerString = A.fg.gray['{4:,.0f}/backer'] pledgedString = assembleFormattedText(A.normal['Pledged: {0:,.0f}', A.fg.gray['/'], '{1:,.0f} {2} ', percentageString, ' ', pledgePerBackerString]) data.append(pledgedString.format(pledged, goal, currency, #pledgedData.data['data-currency'], percentage * 100, pledgePerBacker)) findState = soup.find(id='main_content') if 'Project-state-canceled' in findState['class']: data.append(assembleFormattedText(A.normal[A.fg.red['Cancelled']])) elif 'Project-state-suspended' in findState['class']: data.append(assembleFormattedText(A.normal[A.fg.blue['Suspended']])) elif 'Project-state-failed' in findState['class']: data.append(assembleFormattedText(A.normal[A.fg.red['Failed']])) elif 'Project-state-successful' in findState['class']: data.append(assembleFormattedText(A.normal[A.fg.green['Successful']])) elif 'Project-state-live' in findState['class']: duration = stats.find(id='project_duration_data') if duration is not None: remaining = float(duration['data-hours-remaining']) days = math.floor(remaining/24) hours = remaining % 24 data.append('Duration: {0:.0f} days {1:.1f} hours to go'.format(days, hours)) return IRCResponse(ResponseType.Say, self.graySplitter.join(data), message.ReplyTo, {'urlfollowURL': shorturl})
def FollowSteam(self, steamType, steamId, message): steamType = {'app': 'app', 'sub': 'package'}[steamType] webPage = WebUtils.fetchURL('http://store.steampowered.com/api/{0}details/?{0}ids={1}&cc=US&l=english&v=1'.format(steamType, steamId)) response = json.loads(webPage.body) if not response[steamId]['success']: return # failure appData = response[steamId]['data'] data = [] # name if 'developers' in appData: name = assembleFormattedText(A.normal[appData['name'], A.fg.gray[' by '], u', '.join(appData['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 = u'Package containing: {}'.format(u', '.join(appNames)) data.append(apps) # genres if 'genres' in appData: data.append(u'Genres: ' + ', '.join([genre['description'] for genre in appData['genres']])) # release date releaseDate = appData['release_date'] if not releaseDate['coming_soon']: if releaseDate['date']: data.append(u'Release Date: ' + releaseDate['date']) else: data.append(assembleFormattedText(A.normal['Release Date: ', A.fg.cyan[str(releaseDate['date'])]])) # 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 = assembleFormattedText(A.normal[A.fg.red[str(metaScore)]]) elif metaScore < 75: metacritic = assembleFormattedText(A.normal[A.fg.yellow[str(metaScore)]]) else: metacritic = assembleFormattedText(A.normal[A.fg.green[str(metaScore)]]) data.append(u'Metacritic: {0}'.format(metacritic)) # prices 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': u'$', 'GBP': u'\u00A3', 'EUR': u'\u20AC', 'AUD': u'AU$'} 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.iteritems() if val} priceString = u'/'.join([currencies[val['currency']] + unicode(val['final'] / 100.0) for val in prices.values()]) if prices['USD']['discount_percent'] > 0: priceString += assembleFormattedText(A.normal[A.fg.green[' ({0}% sale!)'.format(prices['USD']['discount_percent'])]]) data.append(priceString) # description if 'about_the_game' in appData and appData['about_the_game'] is not None: limit = 150 description = re.sub(r'(<[^>]+>|[\r\n\t])+', assembleFormattedText(A.normal[' ', A.fg.gray['>'], ' ']), appData['about_the_game']) if len(description) > limit: description = u'{0} ...'.format(description[:limit].rsplit(' ', 1)[0]) data.append(description) return IRCResponse(ResponseType.Say, self.graySplitter.join(data), message.ReplyTo, {'urlfollowURL': 'http://store.steampowered.com/{}/{}'.format({'app': 'app', 'package': 'sub'}[steamType], steamId)})