def get_oauth_token(scopes): with open("keys.json") as f: keys = json.load(f) t = int(time.time()) header = json.dumps({"alg":"RS256", "typ":"JWT"}).encode("utf-8") claim = json.dumps({ "iss": keys["client_email"], "scope": " ".join(scopes), "aud": "https://accounts.google.com/o/oauth2/token", "iat": t, "exp": t+60*60, }).encode("utf-8") data = base64_encode(header) + b'.' + base64_encode(claim) key = RSA.importKey(keys["private_key"]) h = SHA256.new(data) signer = PKCS1_v1_5.new(key) signature = signer.sign(h) jwt = (data + b'.' + base64_encode(signature)).decode("utf-8") data = {"grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer", "assertion": jwt} ret = json.loads((yield from utils.http_request_coro("https://accounts.google.com/o/oauth2/token", data, "POST"))) if "error" in ret: raise Exception(ret["error"]) return ret
def get_display_name(nick): try: data = yield from utils.http_request_coro("https://api.twitch.tv/kraken/users/%s" % nick) data = json.loads(data) return data['display_name'] except: return nick
def extract_new_channels(loop): data = yield from utils.http_request_coro( googlecalendar.EVENTS_URL % urllib.parse.quote(googlecalendar.CALENDAR_FAN), {"key": config["google_key"], "maxResults": 25000}, ) data = json.loads(data) channels = set() for event in data["items"]: if "location" in event: for token in event["location"].split(): url = urllib.parse.urlparse(token) if url.scheme == "": url = urllib.parse.urlparse("https://" + token) if url.netloc in {"www.twitch.tv", "twitch.tv"}: try: channel = url.path.split("/")[1].lower() except IndexError: continue channels.add(channel) follows = yield from twitch.get_follows_channels() old_channels = {channel["channel"]["name"] for channel in follows} old_channels.add(config["channel"]) yield from asyncio.gather( *map(twitch.follow_channel, channels.difference(old_channels)), loop=loop, return_exceptions=True )
def get_videos(channel=None, offset=0, limit=10, broadcasts=False, hls=False): channel = channel or config["channel"] data = yield from utils.http_request_coro( "https://api.twitch.tv/kraken/channels/%s/videos" % channel, data={"offset": offset, "limit": limit, "broadcasts": "true" if broadcasts else "false", "hls": hls}, ) return json.loads(data)["videos"]
def unfollow_channel(target, user=None): if user is None: user = config["username"] headers = {"Authorization": "OAuth %s" % storage.data["twitch_oauth"][user]} yield from utils.http_request_coro( "https://api.twitch.tv/kraken/users/%s/follows/channels/%s" % (user, target), method="DELETE", headers=headers )
def follow_channel(target, user=None): if user is None: user = config["username"] headers = { "Authorization": "OAuth %s" % storage.data['twitch_oauth'][user], } yield from utils.http_request_coro("https://api.twitch.tv/kraken/users/%s/follows/channels/%s" % (user, target), data={"notifications": "false"}, method="PUT", headers=headers)
def get_follows_channels(username=None): if username is None: username = config["username"] url = "https://api.twitch.tv/kraken/users/%s/follows/channels" % username follows = [] total = 1 while len(follows) < total: data = yield from utils.http_request_coro(url) data = json.loads(data) total = data["_total"] follows += data["follows"] url = data["_links"]["next"] return follows
def get_streams_followed(username=None): if username is None: username = config["username"] url = "https://api.twitch.tv/kraken/streams/followed" headers = {"Authorization": "OAuth %s" % storage.data["twitch_oauth"][username]} streams = [] total = 1 while len(streams) < total: data = yield from utils.http_request_coro(url, headers=headers) data = json.loads(data) total = data["_total"] streams += data["streams"] url = data["_links"]["next"] return streams
def get_twitch_emotes_undocumented(): # This endpoint is not documented, however `/chat/emoticons` might be deprecated soon. data = yield from utils.http_request_coro("https://api.twitch.tv/kraken/chat/emoticon_images") data = json.loads(data)["emoticons"] emotesets = {} for emote in data: regex = emote["code"] regex = regex.replace(r"\<\;", "<").replace(r"\>\;", ">").replace(r"\"\;", '"').replace(r"\&\;", "&") if re_just_words.match(regex): regex = r"\b%s\b" % regex emotesets.setdefault(emote["emoticon_set"], {})[emote["code"]] = { "regex": re.compile("(%s)" % regex), "html": '<img src="https://static-cdn.jtvnw.net/emoticons/v1/%s/1.0" alt="{0}" title="{0}">' % emote["id"] } return emotesets
def add_rows_to_spreadsheet(spreadsheet, rows): token = yield from get_oauth_token(["https://spreadsheets.google.com/feeds"]) headers = {"Authorization": "%(token_type)s %(access_token)s" % token} url = "https://spreadsheets.google.com/feeds/worksheets/%s/private/full" % spreadsheet tree = xml.dom.minidom.parseString((yield from utils.http_request_coro(url, headers=headers))) worksheet = next(iter(tree.getElementsByTagName("entry"))) list_feed = find_schema(worksheet, "http://schemas.google.com/spreadsheets/2006#listfeed") if list_feed is None: raise Exception("List feed missing.") list_feed = xml.dom.minidom.parseString((yield from utils.http_request_coro(list_feed, headers=headers))) post_url = find_schema(list_feed, "http://schemas.google.com/g/2005#post") if post_url is None: raise Exception("POST URL missing.") for row in rows: doc = xml.dom.minidom.getDOMImplementation().createDocument(None, "entry", None) root = doc.documentElement root.setAttribute("xmlns", "http://www.w3.org/2005/Atom") root.setAttribute("xmlns:gsx", "http://schemas.google.com/spreadsheets/2006/extended") for column, value in row: root.appendChild(new_field(doc, column, value)) headers["Content-Type"] = "application/atom+xml" yield from utils.http_request_coro(post_url, headers=headers, data=doc.toxml(), method="POST")
def get_subscribers(channel=None, count=5, offset=None, latest=True): if channel is None: channel = config["channel"] if channel not in storage.data["twitch_oauth"]: return None headers = {"Authorization": "OAuth %s" % storage.data["twitch_oauth"][channel]} data = {"limit": count, "direction": "desc" if latest else "asc"} if offset is not None: data["offset"] = offset res = yield from utils.http_request_coro( "https://api.twitch.tv/kraken/channels/%s/subscriptions" % channel, headers=headers, data=data ) subscriber_data = json.loads(res) return [ (sub["user"]["display_name"], sub["user"].get("logo"), sub["created_at"]) for sub in subscriber_data["subscriptions"] ]
def get_twitch_emotes(): data = yield from utils.http_request_coro("https://api.twitch.tv/kraken/chat/emoticons") data = json.loads(data)['emoticons'] emotesets = {} for emote in data: regex = emote['regex'] if regex == r"\:-?[\\/]": # Don't match :/ inside URLs regex = r"\:-?[\\/](?![\\/])" regex = regex.replace(r"\<\;", "<").replace(r"\>\;", ">").replace(r"\"\;", '"').replace(r"\&\;", "&") if re_just_words.match(regex): regex = r"\b%s\b" % regex regex = re.compile("(%s)" % regex) for image in emote['images']: html = '<img src="%s" width="%d" height="%d" alt="{0}" title="{0}">' % (image['url'], image['width'], image['height']) emotesets.setdefault(image.get("emoticon_set"), {})[emote['regex']] = { "regex": regex, "html": html, } return emotesets
def get_subscribers(channel=None, count=5, offset=None, latest=True): if channel is None: channel = config['channel'] if channel not in storage.data['twitch_oauth']: return None headers = { "Authorization": "OAuth %s" % storage.data['twitch_oauth'][channel], } data = { "limit": count, "direction": "desc" if latest else "asc", } if offset is not None: data['offset'] = offset res = yield from utils.http_request_coro("https://api.twitch.tv/kraken/channels/%s/subscriptions" % channel, headers=headers, data=data) subscriber_data = json.loads(res) return [ (sub['user']['display_name'], sub['user'].get('logo'), sub['created_at']) for sub in subscriber_data['subscriptions'] ]