diff --git a/modules-available/chatparser.ini b/modules-available/chatparser.ini new file mode 100644 index 0000000..d1db6ca --- /dev/null +++ b/modules-available/chatparser.ini @@ -0,0 +1,12 @@ + +[chatparser] +; Comma seperated list of servers to operate on, leave empty for all +servers = +; YouTube API key +youtube_api_key = + +; Amazon API stuff +amazon_access_key = +amazon_secret_key = +amazon_assoc_tag = +amazon_region = \ No newline at end of file diff --git a/modules/chatparser.py b/modules/chatparser.py new file mode 100644 index 0000000..3d44489 --- /dev/null +++ b/modules/chatparser.py @@ -0,0 +1,196 @@ +#!/usr/bin/env python + +import re +import urllib + +import amazon +import isodate +import requests +from amazon.api import AmazonAPI +from bs4 import BeautifulSoup + +from mumo_module import (commaSeperatedIntegers, + MumoModule) + +# YT +youtube_api_url = "https://www.googleapis.com/youtube/v3/videos?part=contentDetails%2C+snippet%2Cstatistics&id={0}&fields=pageInfo(totalResults)%2Citems(snippet(title)%2CcontentDetails(duration)%2Cstatistics(likeCount%2CdislikeCount))&key={1}" +youtube_regex = re.compile( + "(https?://)?(www\.)?(youtube|youtu|youtube-nocookie)\.(com|be)/(watch\?v=|embed/|v/|.+\?v=)?([^&=%\?]{11})") + +# STEAM +steam_regex = re.compile("(http|https)://store.steampowered.com/app/([0-9]+)[/\[]?") +steam_genre_regex = re.compile("Genre:.*?(.+?)") + +# AMAZON +amazon_regex = re.compile("ama?zo?n\.(?:de|com)/.*(?:dp|gp/product)/([A-Z0-9]{10})(?:(?:/+)|$)?") +# TODO: Make regex more general (de|com) + +# Commands +move_regex = re.compile("^moveall (.*)$") +tsp_cmd_regex = re.compile("^!(.+?)(?: (.+?))?(?: (.*))?$") + +# Stuff +headers = { + 'User-Agent': "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.49 Safari/537.36" +} + + +class chatparser(MumoModule): + default_config = {'chatparser': ( + ('servers', commaSeperatedIntegers, []), + ('youtube_api_key', str, ''), + ('amazon_access_key', str, ''), + ('amazon_secret_key', str, ''), + ('amazon_assoc_tag', str, ''), + ('amazon_region', str, '') + ) + } + + def __init__(self, name, manager, configuration=None): + MumoModule.__init__(self, name, manager, configuration) + self.murmur = manager.getMurmurModule() + self.youtube_api_key = self.cfg().chatparser.youtube_api_key + self.amazon_access_key = self.cfg().chatparser.amazon_access_key + self.amazon_secret_key = self.cfg().chatparser.youtube_api_key + self.amazon_assoc_tag = self.cfg().chatparser.youtube_api_key + self.amazon_region = self.cfg().chatparser.amazon_region + + self.amazon_api = AmazonAPI(self.amazon_access_key, self.amazon_secret_key, + self.amazon_assoc_tag, region=self.amazon_region) + + # Load steam appid <-> game list + r = requests.get("http://api.steampowered.com/ISteamApps/GetAppList/v0002/?format=json") + + if r.status_code == 200: + self.glist = r.json() + self.log().info("Got {} apps from steam".format(len(self.glist["applist"]["apps"]))) + else: + self.glist = None + + def connected(self): + manager = self.manager() + log = self.log() + log.debug("Register for Server callbacks") + + servers = self.cfg().chatparser.servers + if not servers: + servers = manager.SERVERS_ALL + + manager.subscribeServerCallbacks(self, servers) + + def disconnected(self): + pass + + def sendMessage(self, server, user, message, msg): + if message.channels: + server.sendMessageChannel(user.channel, False, msg) + else: + server.sendMessage(user.session, msg) + server.sendMessage(message.sessions[0], msg) + + # + # --- Server callback functions + # + + def userTextMessage(self, server, user, message, current=None): + if len(message.sessions) == 1 or (len(message.channels) == 1 and message.channels[0] == user.channel): + yt = youtube_regex.search(message.text) + + if yt: + self.parse_youtube(yt.group(6), user, server, message) + return + + stm = steam_regex.search(message.text) + + if stm: + self.parse_steam(int(stm.group(2)), user, server, message) + return + + amazon = amazon_regex.search(message.text) + + if amazon: + self.parse_amazon(amazon.group(1), user, server, message) + + def parse_youtube(self, yid, user, server, message): + ytparse = requests.get(youtube_api_url.format(yid, self.youtube_api_key)) + + self.log().info("YT: %s", yid) + if ytparse.status_code == 200: + if ytparse.json()["pageInfo"]["totalResults"] > 0: + v = ytparse.json()["items"][0] + t = isodate.parse_duration(v["contentDetails"]["duration"]) + try: + rate = "%.2f %%" % ( + (((float(v["statistics"]["dislikeCount"]) / float( + v["statistics"]["likeCount"])) - 1.0) * -1.0) * 100.0) + except ZeroDivisionError: + rate = "No rating possible" + self.sendMessage(server, user, message, + '{0} | {1} | {2}'.format( + v["snippet"]["title"], t, rate, yid)) + + def parse_steam(self, appid, user, server, message): + self.log().info("STEAM: %s", str(appid)) + + steampage = requests.get("http://store.steampowered.com/app/" + str(appid) + "/?l=en", headers=headers) + price = None + genre = None + + if steampage.status_code == 200: + soap = BeautifulSoup(steampage.text, "html.parser") + sprice = soap.find("div", {"class": "game_purchase_price price"}) + + if sprice: + price = sprice.get_text().strip() + else: + price = "Unkown Price" + + reg_genre = steam_genre_regex.search(steampage.text) + + if reg_genre: + genre = reg_genre.group(1) + else: + genre = "Unkown Genre" + + if self.glist: + for game in self.glist["applist"]["apps"]: + if appid == game["appid"]: + self.sendMessage(server, user, message, + '{1} | {2} | {3}'.format( + appid, game["name"], genre, price)) + return + + def parse_amazon(self, item_id, user, server, message): + self.log().info("AMAZON: %s", item_id) + try: + product = self.amazon_api.lookup(ItemId=item_id) + except urllib.error.HTTPError as e: + self.log().warning("[AMAZON] request failure: %s", e) + product = None + except amazon.api.AsinNotFound as e2: + self.log().warning("[AMAZON] Article not found: %s", e2) + product = None + + if product: + self.sendMessage(server, user, message, + '{1} | {2} € | {3}'.format( + item_id, product.title, product.price_and_currency[0], + product.get_attribute("ProductGroup"))) + + def userConnected(self, server, state, context=None): + pass + + def userDisconnected(self, server, state, context=None): + pass + + def userStateChanged(self, server, state, context=None): + pass + + def channelCreated(self, server, state, context=None): + pass + + def channelRemoved(self, server, state, context=None): + pass + + def channelStateChanged(self, server, state, context=None): + pass