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\/)'
|
||||
stream = re.sub(regex, '', stream)
|
||||
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:
|
||||
await self.bot.say(stream + " is offline.")
|
||||
except StreamNotFound:
|
||||
@ -117,7 +118,7 @@ class Streams:
|
||||
stream = re.sub(regex, '', stream)
|
||||
channel = ctx.message.channel
|
||||
try:
|
||||
await self.twitch_online(stream)
|
||||
data = await self.fetch_twitch_ids(stream, raise_if_none=True)
|
||||
except StreamNotFound:
|
||||
await self.bot.say("That stream doesn't exist.")
|
||||
return
|
||||
@ -129,12 +130,11 @@ class Streams:
|
||||
"See `{}streamset twitchtoken`"
|
||||
"".format(ctx.prefix))
|
||||
return
|
||||
except OfflineStream:
|
||||
pass
|
||||
|
||||
enabled = self.enable_or_disable_if_active(self.twitch_streams,
|
||||
stream,
|
||||
channel)
|
||||
channel,
|
||||
_id=data[0]["_id"])
|
||||
|
||||
if enabled:
|
||||
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":
|
||||
raise OfflineStream()
|
||||
elif data["livestream"][0]["media_is_live"] == "1":
|
||||
data = self.hitbox_embed(data)
|
||||
return data
|
||||
return self.hitbox_embed(data)
|
||||
|
||||
raise APIError()
|
||||
|
||||
async def twitch_online(self, stream):
|
||||
session = aiohttp.ClientSession()
|
||||
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:
|
||||
data = await r.json(encoding='utf-8')
|
||||
@ -314,8 +316,7 @@ class Streams:
|
||||
if r.status == 200:
|
||||
if data["stream"] is None:
|
||||
raise OfflineStream()
|
||||
embed = self.twitch_embed(data)
|
||||
return embed
|
||||
return self.twitch_embed(data)
|
||||
elif r.status == 400:
|
||||
raise InvalidCredentials()
|
||||
elif r.status == 404:
|
||||
@ -330,8 +331,7 @@ class Streams:
|
||||
data = await r.json(encoding='utf-8')
|
||||
if r.status == 200:
|
||||
if data["online"] is True:
|
||||
data = self.beam_embed(data)
|
||||
return data
|
||||
return self.beam_embed(data)
|
||||
else:
|
||||
raise OfflineStream()
|
||||
elif r.status == 404:
|
||||
@ -339,6 +339,36 @@ class Streams:
|
||||
else:
|
||||
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):
|
||||
channel = data["stream"]["channel"]
|
||||
url = channel["url"]
|
||||
@ -396,7 +426,7 @@ class Streams:
|
||||
embed.set_footer(text="Playing: " + data["type"]["name"])
|
||||
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"""
|
||||
for i, s in enumerate(streams):
|
||||
if s["NAME"] != stream:
|
||||
@ -411,15 +441,29 @@ class Streams:
|
||||
streams[i]["CHANNELS"].append(channel.id)
|
||||
return True
|
||||
|
||||
streams.append({"CHANNELS": [channel.id],
|
||||
data = {"CHANNELS": [channel.id],
|
||||
"NAME": stream,
|
||||
"ALREADY_ONLINE": False})
|
||||
"ALREADY_ONLINE": False}
|
||||
|
||||
if _id:
|
||||
data["ID"] = _id
|
||||
|
||||
streams.append(data)
|
||||
|
||||
return True
|
||||
|
||||
async def stream_checker(self):
|
||||
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"):
|
||||
save = False
|
||||
|
||||
@ -428,10 +472,16 @@ class Streams:
|
||||
(self.beam_streams, self.beam_online))
|
||||
|
||||
for streams_list, parser in streams:
|
||||
if parser == self.twitch_online:
|
||||
_type = "ID"
|
||||
else:
|
||||
_type = "NAME"
|
||||
for stream in streams_list:
|
||||
key = (parser, stream["NAME"])
|
||||
if _type not in stream:
|
||||
continue
|
||||
key = (parser, stream[_type])
|
||||
try:
|
||||
embed = await parser(stream["NAME"])
|
||||
embed = await parser(stream[_type])
|
||||
except OfflineStream:
|
||||
if stream["ALREADY_ONLINE"]:
|
||||
stream["ALREADY_ONLINE"] = False
|
||||
@ -483,6 +533,28 @@ class Streams:
|
||||
"""Avoids Discord's caching"""
|
||||
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():
|
||||
if not os.path.exists("data/streams"):
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user