[Streams] Remove communities support (#2223)

See [this blog post](https://blog.twitch.tv/introducing-tags-and-new-categories-33744ef7b04f), communities on Twitch have been discontinued.
This commit is contained in:
palmtree5 2019-02-20 21:37:28 -09:00 committed by Toby Harradine
parent b65466cebd
commit 77a0a67029
3 changed files with 3 additions and 268 deletions

View File

@ -6,10 +6,6 @@ class StreamNotFound(StreamsError):
pass pass
class CommunityNotFound(StreamsError):
pass
class APIError(StreamsError): class APIError(StreamsError):
pass pass
@ -24,7 +20,3 @@ class InvalidYoutubeCredentials(StreamsError):
class OfflineStream(StreamsError): class OfflineStream(StreamsError):
pass pass
class OfflineCommunity(StreamsError):
pass

View File

@ -11,7 +11,6 @@ from .streamtypes import (
HitboxStream, HitboxStream,
MixerStream, MixerStream,
PicartoStream, PicartoStream,
TwitchCommunity,
YoutubeStream, YoutubeStream,
) )
from .errors import ( from .errors import (
@ -19,8 +18,6 @@ from .errors import (
StreamNotFound, StreamNotFound,
APIError, APIError,
InvalidYoutubeCredentials, InvalidYoutubeCredentials,
CommunityNotFound,
OfflineCommunity,
StreamsError, StreamsError,
InvalidTwitchCredentials, InvalidTwitchCredentials,
) )
@ -39,7 +36,7 @@ _ = Translator("Streams", __file__)
@cog_i18n(_) @cog_i18n(_)
class Streams(commands.Cog): class Streams(commands.Cog):
global_defaults = {"tokens": {}, "streams": [], "communities": []} global_defaults = {"tokens": {}, "streams": []}
guild_defaults = {"autodelete": False, "mention_everyone": False, "mention_here": False} guild_defaults = {"autodelete": False, "mention_everyone": False, "mention_here": False}
@ -58,7 +55,6 @@ class Streams(commands.Cog):
self.bot: Red = bot self.bot: Red = bot
self.streams: List[Stream] = [] self.streams: List[Stream] = []
self.communities: List[TwitchCommunity] = []
self.task: Optional[asyncio.Task] = None self.task: Optional[asyncio.Task] = None
self.yt_cid_pattern = re.compile("^UC[-_A-Za-z0-9]{21}[AQgw]$") self.yt_cid_pattern = re.compile("^UC[-_A-Za-z0-9]{21}[AQgw]$")
@ -73,7 +69,6 @@ class Streams(commands.Cog):
"""Should be called straight after cog instantiation.""" """Should be called straight after cog instantiation."""
await self.move_api_keys() await self.move_api_keys()
self.streams = await self.load_streams() self.streams = await self.load_streams()
self.communities = await self.load_communities()
self.task = self.bot.loop.create_task(self._stream_alerts()) self.task = self.bot.loop.create_task(self._stream_alerts())
@ -178,11 +173,6 @@ class Streams(commands.Cog):
return return
await self.stream_alert(ctx, TwitchStream, channel_name.lower()) await self.stream_alert(ctx, TwitchStream, channel_name.lower())
@_twitch.command(name="community")
async def twitch_alert_community(self, ctx: commands.Context, community: str):
"""Toggle alerts in this channel for a Twitch community."""
await self.community_alert(ctx, TwitchCommunity, community.lower())
@streamalert.command(name="youtube") @streamalert.command(name="youtube")
async def youtube_alert(self, ctx: commands.Context, channel_name_or_id: str): async def youtube_alert(self, ctx: commands.Context, channel_name_or_id: str):
"""Toggle alerts in this channel for a YouTube stream.""" """Toggle alerts in this channel for a YouTube stream."""
@ -303,34 +293,6 @@ class Streams(commands.Cog):
await self.add_or_remove(ctx, stream) await self.add_or_remove(ctx, stream)
async def community_alert(self, ctx: commands.Context, _class, community_name):
community = self.get_community(_class, community_name)
if not community:
token = await self.bot.db.api_tokens.get_raw(_class.token_name, default=None)
community = _class(name=community_name, token=token)
try:
await community.get_community_streams()
except InvalidTwitchCredentials:
await ctx.send(
_(
"The Twitch token is either invalid or has not been set. See "
"`{prefix}streamset twitchtoken`."
).format(prefix=ctx.prefix)
)
return
except CommunityNotFound:
await ctx.send(_("That community doesn't seem to exist."))
return
except APIError:
await ctx.send(
_("Something went wrong whilst trying to contact the stream service's API.")
)
return
except OfflineCommunity:
pass
await self.add_or_remove_community(ctx, community)
@commands.group() @commands.group()
@checks.mod() @checks.mod()
async def streamset(self, ctx: commands.Context): async def streamset(self, ctx: commands.Context):
@ -393,9 +355,7 @@ class Streams(commands.Cog):
await ctx.send(_("`@\u200beveryone` will no longer be mentioned for stream alerts.")) await ctx.send(_("`@\u200beveryone` will no longer be mentioned for stream alerts."))
else: else:
await self.db.guild(guild).mention_everyone.set(True) await self.db.guild(guild).mention_everyone.set(True)
await ctx.send( await ctx.send(_("When a stream is live, `@\u200beveryone` will be mentioned."))
_("When a stream or community is live, `@\u200beveryone` will be mentioned.")
)
@mention.command(aliases=["here"]) @mention.command(aliases=["here"])
@commands.guild_only() @commands.guild_only()
@ -408,9 +368,7 @@ class Streams(commands.Cog):
await ctx.send(_("`@\u200bhere` will no longer be mentioned for stream alerts.")) await ctx.send(_("`@\u200bhere` will no longer be mentioned for stream alerts."))
else: else:
await self.db.guild(guild).mention_here.set(True) await self.db.guild(guild).mention_here.set(True)
await ctx.send( await ctx.send(_("When a stream is live, `@\u200bhere` will be mentioned."))
_("When a stream or community is live, `@\u200bhere` will be mentioned.")
)
@mention.command() @mention.command()
@commands.guild_only() @commands.guild_only()
@ -470,29 +428,6 @@ class Streams(commands.Cog):
await self.save_streams() await self.save_streams()
async def add_or_remove_community(self, ctx: commands.Context, community):
if ctx.channel.id not in community.channels:
community.channels.append(ctx.channel.id)
if community not in self.communities:
self.communities.append(community)
await ctx.send(
_(
"I'll send a notification in this channel when a "
"channel is live in the {community.name} community."
).format(community=community)
)
else:
community.channels.remove(ctx.channel.id)
if not community.channels:
self.communities.remove(community)
await ctx.send(
_(
"I won't send notifications about channels streaming "
"in the {community.name} community in this channel anymore."
).format(community=community)
)
await self.save_communities()
def get_stream(self, _class, name): def get_stream(self, _class, name):
for stream in self.streams: for stream in self.streams:
# if isinstance(stream, _class) and stream.name == name: # if isinstance(stream, _class) and stream.name == name:
@ -510,11 +445,6 @@ class Streams(commands.Cog):
elif stream.type == _class.__name__ and stream.name.lower() == name.lower(): elif stream.type == _class.__name__ and stream.name.lower() == name.lower():
return stream return stream
def get_community(self, _class, name):
for community in self.communities:
if community.type == _class.__name__ and community.name.lower() == name.lower():
return community
@staticmethod @staticmethod
async def check_exists(stream): async def check_exists(stream):
try: try:
@ -533,10 +463,6 @@ class Streams(commands.Cog):
await self.check_streams() await self.check_streams()
except asyncio.CancelledError: except asyncio.CancelledError:
pass pass
try:
await self.check_communities()
except asyncio.CancelledError:
pass
await asyncio.sleep(CHECK_DELAY) await asyncio.sleep(CHECK_DELAY)
async def check_streams(self): async def check_streams(self):
@ -600,52 +526,6 @@ class Streams(commands.Cog):
mentions.append(role.mention) mentions.append(role.mention)
return " ".join(mentions), edited_roles return " ".join(mentions), edited_roles
async def check_communities(self):
for community in self.communities:
with contextlib.suppress(Exception):
try:
stream_list = await community.get_community_streams()
except CommunityNotFound:
print(
_("The Community {community.name} was not found!").format(
community=community
)
)
continue
except OfflineCommunity:
if not community._messages_cache:
continue
for message in community._messages_cache:
with contextlib.suppress(Exception):
autodelete = await self.db.guild(message.guild).autodelete()
if autodelete:
await message.delete()
community._messages_cache.clear()
await self.save_communities()
else:
for channel in community.channels:
chn = self.bot.get_channel(channel)
streams = await self.filter_streams(stream_list, chn)
emb = await community.make_embed(streams)
chn_msg = [m for m in community._messages_cache if m.channel == chn]
if not chn_msg:
mentions, roles = await self._get_mention_str(chn.guild)
if mentions:
msg = await chn.send(mentions, embed=emb)
else:
msg = await chn.send(embed=emb)
community._messages_cache.append(msg)
if roles:
for role in roles:
await role.edit(mentionable=False)
await self.save_communities()
else:
chn_msg = sorted(chn_msg, key=lambda x: x.created_at, reverse=True)[0]
community._messages_cache.remove(chn_msg)
await chn_msg.edit(embed=emb)
community._messages_cache.append(chn_msg)
await self.save_communities()
async def filter_streams(self, streams: list, channel: discord.TextChannel) -> list: async def filter_streams(self, streams: list, channel: discord.TextChannel) -> list:
filtered = [] filtered = []
for stream in streams: for stream in streams:
@ -683,34 +563,6 @@ class Streams(commands.Cog):
return streams return streams
async def load_communities(self):
communities = []
for raw_community in await self.db.communities():
_class = getattr(_streamtypes, raw_community["type"], None)
if not _class:
continue
raw_msg_cache = raw_community["messages"]
raw_community["_messages_cache"] = []
for raw_msg in raw_msg_cache:
chn = self.bot.get_channel(raw_msg["channel"])
if chn is not None:
try:
msg = await chn.get_message(raw_msg["message"])
except discord.HTTPException:
pass
else:
raw_community["_messages_cache"].append(msg)
token = await self.bot.db.api_tokens.get_raw(_class.token_name, default=None)
communities.append(_class(token=token, **raw_community))
# issue 1191 extended resolution: Remove this after suitable period
# Fast dedupe below
seen = set()
seen_add = seen.add
return [x for x in communities if not (x.name.lower() in seen or seen_add(x.name.lower()))]
# return communities
async def save_streams(self): async def save_streams(self):
raw_streams = [] raw_streams = []
for stream in self.streams: for stream in self.streams:
@ -718,13 +570,6 @@ class Streams(commands.Cog):
await self.db.streams.set(raw_streams) await self.db.streams.set(raw_streams)
async def save_communities(self):
raw_communities = []
for community in self.communities:
raw_communities.append(community.export())
await self.db.communities.set(raw_communities)
def __unload(self): def __unload(self):
if self.task: if self.task:
self.task.cancel() self.task.cancel()

View File

@ -2,8 +2,6 @@ from .errors import (
StreamNotFound, StreamNotFound,
APIError, APIError,
OfflineStream, OfflineStream,
CommunityNotFound,
OfflineCommunity,
InvalidYoutubeCredentials, InvalidYoutubeCredentials,
InvalidTwitchCredentials, InvalidTwitchCredentials,
) )
@ -30,106 +28,6 @@ def rnd(url):
return url + "?rnd=" + "".join([choice(ascii_letters) for i in range(6)]) return url + "?rnd=" + "".join([choice(ascii_letters) for i in range(6)])
class TwitchCommunity:
token_name = "twitch"
def __init__(self, **kwargs):
self.name = kwargs.pop("name")
self.id = kwargs.pop("id", None)
self.channels = kwargs.pop("channels", [])
self._messages_cache = kwargs.pop("_messages_cache", [])
self._token = kwargs.pop("token", None)
self.type = self.__class__.__name__
async def get_community_id(self):
headers = {
"Accept": "application/vnd.twitchtv.v5+json",
"Client-ID": str(self._token["client_id"]),
}
params = {"name": self.name}
async with aiohttp.ClientSession() as session:
async with session.get(
TWITCH_COMMUNITIES_ENDPOINT, headers=headers, params=params
) as r:
data = await r.json()
if r.status == 200:
return data["_id"]
elif r.status == 400:
raise InvalidTwitchCredentials()
elif r.status == 404:
raise CommunityNotFound()
else:
raise APIError()
async def get_community_streams(self):
if not self.id:
try:
self.id = await self.get_community_id()
except CommunityNotFound:
raise
headers = {
"Accept": "application/vnd.twitchtv.v5+json",
"Client-ID": str(self._token["client_id"]),
}
params = {"community_id": self.id, "limit": 100}
url = TWITCH_BASE_URL + "/kraken/streams"
async with aiohttp.ClientSession() as session:
async with session.get(url, headers=headers, params=params) as r:
data = await r.json()
if r.status == 200:
if data["_total"] == 0:
raise OfflineCommunity()
else:
return data["streams"]
elif r.status == 400:
raise InvalidTwitchCredentials()
elif r.status == 404:
raise CommunityNotFound()
else:
raise APIError()
async def make_embed(self, streams: list) -> discord.Embed:
headers = {
"Accept": "application/vnd.twitchtv.v5+json",
"Client-ID": str(self._token["client_id"]),
}
async with aiohttp.ClientSession() as session:
async with session.get(
"{}/{}".format(TWITCH_COMMUNITIES_ENDPOINT, self.id), headers=headers
) as r:
data = await r.json()
avatar = data["avatar_image_url"]
title = "Channels currently streaming to {}".format(data["display_name"])
url = "https://www.twitch.tv/communities/{}".format(self.name)
embed = discord.Embed(title=title, url=url)
embed.set_image(url=avatar)
if len(streams) >= 10:
stream_list = sample(streams, 10)
else:
stream_list = streams
for stream in stream_list:
name = "[{}]({})".format(stream["channel"]["display_name"], stream["channel"]["url"])
embed.add_field(name=stream["channel"]["status"], value=name, inline=False)
embed.color = 0x6441A4
return embed
def export(self):
data = {}
for k, v in self.__dict__.items():
if not k.startswith("_"):
data[k] = v
data["messages"] = []
for m in self._messages_cache:
data["messages"].append({"channel": m.channel.id, "message": m.id})
return data
def __repr__(self):
return "<{0.__class__.__name__}: {0.name}>".format(self)
class Stream: class Stream:
token_name: ClassVar[Optional[str]] = None token_name: ClassVar[Optional[str]] = None