mirror of
https://github.com/Cog-Creators/Red-DiscordBot.git
synced 2025-11-08 12:18:54 -05:00
[Streams] Twitch API v5 (#718)
Twitch API v5 makes use of IDs instead of usernames. To migrate the current data, the cog will attempt to fetch and save all the missing stream IDs on loading (in chunks of 100)
This commit is contained in:
parent
6b725560f6
commit
719088d438
106
cogs/streams.py
106
cogs/streams.py
@ -71,7 +71,8 @@ class Streams:
|
|||||||
regex = r'^(https?\:\/\/)?(www\.)?(twitch\.tv\/)'
|
regex = r'^(https?\:\/\/)?(www\.)?(twitch\.tv\/)'
|
||||||
stream = re.sub(regex, '', stream)
|
stream = re.sub(regex, '', stream)
|
||||||
try:
|
try:
|
||||||
embed = await self.twitch_online(stream)
|
data = await self.fetch_twitch_ids(stream, raise_if_none=True)
|
||||||
|
embed = await self.twitch_online(data[0]["_id"])
|
||||||
except OfflineStream:
|
except OfflineStream:
|
||||||
await self.bot.say(stream + " is offline.")
|
await self.bot.say(stream + " is offline.")
|
||||||
except StreamNotFound:
|
except StreamNotFound:
|
||||||
@ -117,7 +118,7 @@ class Streams:
|
|||||||
stream = re.sub(regex, '', stream)
|
stream = re.sub(regex, '', stream)
|
||||||
channel = ctx.message.channel
|
channel = ctx.message.channel
|
||||||
try:
|
try:
|
||||||
await self.twitch_online(stream)
|
data = await self.fetch_twitch_ids(stream, raise_if_none=True)
|
||||||
except StreamNotFound:
|
except StreamNotFound:
|
||||||
await self.bot.say("That stream doesn't exist.")
|
await self.bot.say("That stream doesn't exist.")
|
||||||
return
|
return
|
||||||
@ -129,12 +130,11 @@ class Streams:
|
|||||||
"See `{}streamset twitchtoken`"
|
"See `{}streamset twitchtoken`"
|
||||||
"".format(ctx.prefix))
|
"".format(ctx.prefix))
|
||||||
return
|
return
|
||||||
except OfflineStream:
|
|
||||||
pass
|
|
||||||
|
|
||||||
enabled = self.enable_or_disable_if_active(self.twitch_streams,
|
enabled = self.enable_or_disable_if_active(self.twitch_streams,
|
||||||
stream,
|
stream,
|
||||||
channel)
|
channel,
|
||||||
|
_id=data[0]["_id"])
|
||||||
|
|
||||||
if enabled:
|
if enabled:
|
||||||
await self.bot.say("Alert activated. I will notify this channel "
|
await self.bot.say("Alert activated. I will notify this channel "
|
||||||
@ -298,15 +298,17 @@ class Streams:
|
|||||||
elif data["livestream"][0]["media_is_live"] == "0":
|
elif data["livestream"][0]["media_is_live"] == "0":
|
||||||
raise OfflineStream()
|
raise OfflineStream()
|
||||||
elif data["livestream"][0]["media_is_live"] == "1":
|
elif data["livestream"][0]["media_is_live"] == "1":
|
||||||
data = self.hitbox_embed(data)
|
return self.hitbox_embed(data)
|
||||||
return data
|
|
||||||
|
|
||||||
raise APIError()
|
raise APIError()
|
||||||
|
|
||||||
async def twitch_online(self, stream):
|
async def twitch_online(self, stream):
|
||||||
session = aiohttp.ClientSession()
|
session = aiohttp.ClientSession()
|
||||||
url = "https://api.twitch.tv/kraken/streams/" + stream
|
url = "https://api.twitch.tv/kraken/streams/" + stream
|
||||||
header = {'Client-ID': self.settings.get("TWITCH_TOKEN", "")}
|
header = {
|
||||||
|
'Client-ID': self.settings.get("TWITCH_TOKEN", ""),
|
||||||
|
'Accept': 'application/vnd.twitchtv.v5+json'
|
||||||
|
}
|
||||||
|
|
||||||
async with session.get(url, headers=header) as r:
|
async with session.get(url, headers=header) as r:
|
||||||
data = await r.json(encoding='utf-8')
|
data = await r.json(encoding='utf-8')
|
||||||
@ -314,8 +316,7 @@ class Streams:
|
|||||||
if r.status == 200:
|
if r.status == 200:
|
||||||
if data["stream"] is None:
|
if data["stream"] is None:
|
||||||
raise OfflineStream()
|
raise OfflineStream()
|
||||||
embed = self.twitch_embed(data)
|
return self.twitch_embed(data)
|
||||||
return embed
|
|
||||||
elif r.status == 400:
|
elif r.status == 400:
|
||||||
raise InvalidCredentials()
|
raise InvalidCredentials()
|
||||||
elif r.status == 404:
|
elif r.status == 404:
|
||||||
@ -330,8 +331,7 @@ class Streams:
|
|||||||
data = await r.json(encoding='utf-8')
|
data = await r.json(encoding='utf-8')
|
||||||
if r.status == 200:
|
if r.status == 200:
|
||||||
if data["online"] is True:
|
if data["online"] is True:
|
||||||
data = self.beam_embed(data)
|
return self.beam_embed(data)
|
||||||
return data
|
|
||||||
else:
|
else:
|
||||||
raise OfflineStream()
|
raise OfflineStream()
|
||||||
elif r.status == 404:
|
elif r.status == 404:
|
||||||
@ -339,6 +339,36 @@ class Streams:
|
|||||||
else:
|
else:
|
||||||
raise APIError()
|
raise APIError()
|
||||||
|
|
||||||
|
async def fetch_twitch_ids(self, *streams, raise_if_none=False):
|
||||||
|
def chunks(l):
|
||||||
|
for i in range(0, len(l), 100):
|
||||||
|
yield l[i:i + 100]
|
||||||
|
|
||||||
|
base_url = "https://api.twitch.tv/kraken/users?login="
|
||||||
|
header = {
|
||||||
|
'Client-ID': self.settings.get("TWITCH_TOKEN", ""),
|
||||||
|
'Accept': 'application/vnd.twitchtv.v5+json'
|
||||||
|
}
|
||||||
|
results = []
|
||||||
|
|
||||||
|
for streams_list in chunks(streams):
|
||||||
|
session = aiohttp.ClientSession()
|
||||||
|
url = base_url + ",".join(streams_list)
|
||||||
|
async with session.get(url, headers=header) as r:
|
||||||
|
data = await r.json()
|
||||||
|
if r.status == 200:
|
||||||
|
results.extend(data["users"])
|
||||||
|
elif r.status == 400:
|
||||||
|
raise InvalidCredentials()
|
||||||
|
else:
|
||||||
|
raise APIError()
|
||||||
|
await session.close()
|
||||||
|
|
||||||
|
if not results and raise_if_none:
|
||||||
|
raise StreamNotFound()
|
||||||
|
|
||||||
|
return results
|
||||||
|
|
||||||
def twitch_embed(self, data):
|
def twitch_embed(self, data):
|
||||||
channel = data["stream"]["channel"]
|
channel = data["stream"]["channel"]
|
||||||
url = channel["url"]
|
url = channel["url"]
|
||||||
@ -396,7 +426,7 @@ class Streams:
|
|||||||
embed.set_footer(text="Playing: " + data["type"]["name"])
|
embed.set_footer(text="Playing: " + data["type"]["name"])
|
||||||
return embed
|
return embed
|
||||||
|
|
||||||
def enable_or_disable_if_active(self, streams, stream, channel):
|
def enable_or_disable_if_active(self, streams, stream, channel, _id=None):
|
||||||
"""Returns True if enabled or False if disabled"""
|
"""Returns True if enabled or False if disabled"""
|
||||||
for i, s in enumerate(streams):
|
for i, s in enumerate(streams):
|
||||||
if s["NAME"] != stream:
|
if s["NAME"] != stream:
|
||||||
@ -411,15 +441,29 @@ class Streams:
|
|||||||
streams[i]["CHANNELS"].append(channel.id)
|
streams[i]["CHANNELS"].append(channel.id)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
streams.append({"CHANNELS": [channel.id],
|
data = {"CHANNELS": [channel.id],
|
||||||
"NAME": stream,
|
"NAME": stream,
|
||||||
"ALREADY_ONLINE": False})
|
"ALREADY_ONLINE": False}
|
||||||
|
|
||||||
|
if _id:
|
||||||
|
data["ID"] = _id
|
||||||
|
|
||||||
|
streams.append(data)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
async def stream_checker(self):
|
async def stream_checker(self):
|
||||||
CHECK_DELAY = 60
|
CHECK_DELAY = 60
|
||||||
|
|
||||||
|
try:
|
||||||
|
await self._migration_twitch_v5()
|
||||||
|
except InvalidCredentials:
|
||||||
|
print("Error during convertion of twitch usernames to IDs: "
|
||||||
|
"invalid token")
|
||||||
|
except Exception as e:
|
||||||
|
print("Error during convertion of twitch usernames to IDs: "
|
||||||
|
"{}".format(e))
|
||||||
|
|
||||||
while self == self.bot.get_cog("Streams"):
|
while self == self.bot.get_cog("Streams"):
|
||||||
save = False
|
save = False
|
||||||
|
|
||||||
@ -428,10 +472,16 @@ class Streams:
|
|||||||
(self.beam_streams, self.beam_online))
|
(self.beam_streams, self.beam_online))
|
||||||
|
|
||||||
for streams_list, parser in streams:
|
for streams_list, parser in streams:
|
||||||
|
if parser == self.twitch_online:
|
||||||
|
_type = "ID"
|
||||||
|
else:
|
||||||
|
_type = "NAME"
|
||||||
for stream in streams_list:
|
for stream in streams_list:
|
||||||
key = (parser, stream["NAME"])
|
if _type not in stream:
|
||||||
|
continue
|
||||||
|
key = (parser, stream[_type])
|
||||||
try:
|
try:
|
||||||
embed = await parser(stream["NAME"])
|
embed = await parser(stream[_type])
|
||||||
except OfflineStream:
|
except OfflineStream:
|
||||||
if stream["ALREADY_ONLINE"]:
|
if stream["ALREADY_ONLINE"]:
|
||||||
stream["ALREADY_ONLINE"] = False
|
stream["ALREADY_ONLINE"] = False
|
||||||
@ -483,6 +533,28 @@ class Streams:
|
|||||||
"""Avoids Discord's caching"""
|
"""Avoids Discord's caching"""
|
||||||
return "?rnd=" + "".join([choice(ascii_letters) for i in range(6)])
|
return "?rnd=" + "".join([choice(ascii_letters) for i in range(6)])
|
||||||
|
|
||||||
|
async def _migration_twitch_v5(self):
|
||||||
|
# Migration of old twitch streams to API v5
|
||||||
|
to_convert = []
|
||||||
|
for stream in self.twitch_streams:
|
||||||
|
if "ID" not in stream:
|
||||||
|
to_convert.append(stream["NAME"])
|
||||||
|
|
||||||
|
if not to_convert:
|
||||||
|
return
|
||||||
|
|
||||||
|
results = await self.fetch_twitch_ids(*to_convert)
|
||||||
|
|
||||||
|
for stream in self.twitch_streams:
|
||||||
|
for result in results:
|
||||||
|
if stream["NAME"].lower() == result["name"].lower():
|
||||||
|
stream["ID"] = result["_id"]
|
||||||
|
|
||||||
|
# We might as well delete the invalid / renamed ones
|
||||||
|
self.twitch_streams = [s for s in self.twitch_streams if "ID" in s]
|
||||||
|
|
||||||
|
dataIO.save_json("data/streams/twitch.json", self.twitch_streams)
|
||||||
|
|
||||||
|
|
||||||
def check_folders():
|
def check_folders():
|
||||||
if not os.path.exists("data/streams"):
|
if not os.path.exists("data/streams"):
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user