mirror of
https://github.com/Cog-Creators/Red-DiscordBot.git
synced 2025-11-06 03:08:55 -05:00
[Streams] Support Youtube stream schedules (#4615)
* Catch scheduled livestreams * Announce scheduled streams and starts * Add setting, fix bugs * Add new dependency * Black reformat * Fix the duplicated messages bug * Do not send messages for schedules * Format embed
This commit is contained in:
parent
13ca9a6c2e
commit
6060da0f87
@ -58,6 +58,7 @@ class Streams(commands.Cog):
|
||||
"live_message_mention": False,
|
||||
"live_message_nomention": False,
|
||||
"ignore_reruns": False,
|
||||
"ignore_schedule": False,
|
||||
}
|
||||
|
||||
role_defaults = {"mention": False}
|
||||
@ -223,9 +224,9 @@ class Streams(commands.Cog):
|
||||
apikey = await self.bot.get_shared_api_tokens("youtube")
|
||||
is_name = self.check_name_or_id(channel_id_or_name)
|
||||
if is_name:
|
||||
stream = YoutubeStream(name=channel_id_or_name, token=apikey)
|
||||
stream = YoutubeStream(name=channel_id_or_name, token=apikey, config=self.config)
|
||||
else:
|
||||
stream = YoutubeStream(id=channel_id_or_name, token=apikey)
|
||||
stream = YoutubeStream(id=channel_id_or_name, token=apikey, config=self.config)
|
||||
await self.check_online(ctx, stream)
|
||||
|
||||
@commands.command()
|
||||
@ -398,7 +399,7 @@ class Streams(commands.Cog):
|
||||
is_yt = _class.__name__ == "YoutubeStream"
|
||||
is_twitch = _class.__name__ == "TwitchStream"
|
||||
if is_yt and not self.check_name_or_id(channel_name):
|
||||
stream = _class(id=channel_name, token=token)
|
||||
stream = _class(id=channel_name, token=token, config=self.config)
|
||||
elif is_twitch:
|
||||
await self.maybe_renew_twitch_bearer_token()
|
||||
stream = _class(
|
||||
@ -645,6 +646,19 @@ class Streams(commands.Cog):
|
||||
await self.config.guild(guild).ignore_reruns.set(True)
|
||||
await ctx.send(_("Streams of type 'rerun' will no longer send an alert."))
|
||||
|
||||
@streamset.command(name="ignoreschedule")
|
||||
@commands.guild_only()
|
||||
async def ignore_schedule(self, ctx: commands.Context):
|
||||
"""Toggle excluding YouTube streams schedules from alerts."""
|
||||
guild = ctx.guild
|
||||
current_setting = await self.config.guild(guild).ignore_schedule()
|
||||
if current_setting:
|
||||
await self.config.guild(guild).ignore_schedule.set(False)
|
||||
await ctx.send(_("Streams schedules will be included in alerts."))
|
||||
else:
|
||||
await self.config.guild(guild).ignore_schedule.set(True)
|
||||
await ctx.send(_("Streams schedules will no longer send an alert."))
|
||||
|
||||
async def add_or_remove(self, ctx: commands.Context, stream):
|
||||
if ctx.channel.id not in stream.channels:
|
||||
stream.channels.append(ctx.channel.id)
|
||||
@ -705,6 +719,16 @@ class Streams(commands.Cog):
|
||||
pass
|
||||
await asyncio.sleep(await self.config.refresh_timer())
|
||||
|
||||
async def _send_stream_alert(
|
||||
self, stream, channel: discord.TextChannel, embed: discord.Embed, content: str = None
|
||||
):
|
||||
m = await channel.send(
|
||||
content,
|
||||
embed=embed,
|
||||
allowed_mentions=discord.AllowedMentions(roles=True, everyone=True),
|
||||
)
|
||||
stream._messages_cache.append(m)
|
||||
|
||||
async def check_streams(self):
|
||||
for stream in self.streams:
|
||||
with contextlib.suppress(Exception):
|
||||
@ -712,9 +736,12 @@ class Streams(commands.Cog):
|
||||
if stream.__class__.__name__ == "TwitchStream":
|
||||
await self.maybe_renew_twitch_bearer_token()
|
||||
embed, is_rerun = await stream.is_online()
|
||||
elif stream.__class__.__name__ == "YoutubeStream":
|
||||
embed, is_schedule = await stream.is_online()
|
||||
else:
|
||||
embed = await stream.is_online()
|
||||
is_rerun = False
|
||||
is_schedule = False
|
||||
except OfflineStream:
|
||||
if not stream._messages_cache:
|
||||
continue
|
||||
@ -739,7 +766,14 @@ class Streams(commands.Cog):
|
||||
ignore_reruns = await self.config.guild(channel.guild).ignore_reruns()
|
||||
if ignore_reruns and is_rerun:
|
||||
continue
|
||||
|
||||
ignore_schedules = await self.config.guild(channel.guild).ignore_schedule()
|
||||
if ignore_schedules and is_schedule:
|
||||
continue
|
||||
if is_schedule:
|
||||
# skip messages and mentions
|
||||
await self._send_stream_alert(stream, channel, embed)
|
||||
await self.save_streams()
|
||||
continue
|
||||
await set_contextual_locales_from_guild(self.bot, channel.guild)
|
||||
|
||||
mention_str, edited_roles = await self._get_mention_str(
|
||||
@ -780,13 +814,7 @@ class Streams(commands.Cog):
|
||||
str(stream.name), mass_mentions=True, formatting=True
|
||||
)
|
||||
)
|
||||
|
||||
m = await channel.send(
|
||||
content,
|
||||
embed=embed,
|
||||
allowed_mentions=discord.AllowedMentions(roles=True, everyone=True),
|
||||
)
|
||||
stream._messages_cache.append(m)
|
||||
await self._send_stream_alert(stream, channel, embed, content)
|
||||
if edited_roles:
|
||||
for role in edited_roles:
|
||||
await role.edit(mentionable=False)
|
||||
@ -855,6 +883,8 @@ class Streams(commands.Cog):
|
||||
raw_stream["token"] = token.get("client_id")
|
||||
raw_stream["bearer"] = self.ttv_bearer_cache.get("access_token", None)
|
||||
else:
|
||||
if _class.__name__ == "YoutubeStream":
|
||||
raw_stream["config"] = self.config
|
||||
raw_stream["token"] = token
|
||||
streams.append(_class(**raw_stream))
|
||||
|
||||
|
||||
@ -1,9 +1,12 @@
|
||||
import contextlib
|
||||
import json
|
||||
import logging
|
||||
from dateutil.parser import parse as parse_time
|
||||
from random import choice
|
||||
from string import ascii_letters
|
||||
from datetime import datetime, timedelta
|
||||
import xml.etree.ElementTree as ET
|
||||
from typing import ClassVar, Optional, List
|
||||
from typing import ClassVar, Optional, List, Tuple
|
||||
|
||||
import aiohttp
|
||||
import discord
|
||||
@ -17,7 +20,7 @@ from .errors import (
|
||||
YoutubeQuotaExceeded,
|
||||
)
|
||||
from redbot.core.i18n import Translator
|
||||
from redbot.core.utils.chat_formatting import humanize_number
|
||||
from redbot.core.utils.chat_formatting import humanize_number, humanize_timedelta
|
||||
|
||||
TWITCH_BASE_URL = "https://api.twitch.tv"
|
||||
TWITCH_ID_ENDPOINT = TWITCH_BASE_URL + "/helix/users"
|
||||
@ -86,6 +89,7 @@ class YoutubeStream(Stream):
|
||||
def __init__(self, **kwargs):
|
||||
self.id = kwargs.pop("id", None)
|
||||
self._token = kwargs.pop("token", None)
|
||||
self._config = kwargs.pop("config")
|
||||
self.not_livestreams: List[str] = []
|
||||
self.livestreams: List[str] = []
|
||||
|
||||
@ -128,8 +132,11 @@ class YoutubeStream(Stream):
|
||||
if (
|
||||
stream_data
|
||||
and stream_data != "None"
|
||||
and stream_data.get("actualStartTime", None) is not None
|
||||
and stream_data.get("actualEndTime", None) is None
|
||||
and (
|
||||
stream_data.get("actualStartTime", None) is not None
|
||||
or stream_data.get("scheduledStartTime", None) is not None
|
||||
)
|
||||
):
|
||||
if video_id not in self.livestreams:
|
||||
self.livestreams.append(data["items"][0]["id"])
|
||||
@ -143,24 +150,52 @@ class YoutubeStream(Stream):
|
||||
# info from the RSS ... but incase you don't wanna deal with fully rewritting the
|
||||
# code for this part, as this is only a 2 quota query.
|
||||
if self.livestreams:
|
||||
params = {"key": self._token["api_key"], "id": self.livestreams[-1], "part": "snippet"}
|
||||
params = {
|
||||
"key": self._token["api_key"],
|
||||
"id": self.livestreams[-1],
|
||||
"part": "snippet,liveStreamingDetails",
|
||||
}
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.get(YOUTUBE_VIDEOS_ENDPOINT, params=params) as r:
|
||||
data = await r.json()
|
||||
return self.make_embed(data)
|
||||
return await self.make_embed(data)
|
||||
raise OfflineStream()
|
||||
|
||||
def make_embed(self, data):
|
||||
async def make_embed(self, data):
|
||||
vid_data = data["items"][0]
|
||||
video_url = "https://youtube.com/watch?v={}".format(vid_data["id"])
|
||||
title = vid_data["snippet"]["title"]
|
||||
thumbnail = vid_data["snippet"]["thumbnails"]["medium"]["url"]
|
||||
channel_title = vid_data["snippet"]["channelTitle"]
|
||||
embed = discord.Embed(title=title, url=video_url)
|
||||
is_schedule = False
|
||||
if vid_data["liveStreamingDetails"]["scheduledStartTime"] is not None:
|
||||
if "actualStartTime" not in vid_data["liveStreamingDetails"]:
|
||||
start_time = parse_time(vid_data["liveStreamingDetails"]["scheduledStartTime"])
|
||||
start_in = start_time.replace(tzinfo=None) - datetime.now()
|
||||
embed.description = _("This stream will start in {time}").format(
|
||||
time=humanize_timedelta(
|
||||
timedelta=timedelta(minutes=start_in.total_seconds() // 60)
|
||||
) # getting rid of seconds
|
||||
)
|
||||
embed.timestamp = start_time
|
||||
is_schedule = True
|
||||
else:
|
||||
# repost message
|
||||
to_remove = []
|
||||
for message in self._messages_cache:
|
||||
if message.embeds[0].description is discord.Embed.Empty:
|
||||
continue
|
||||
with contextlib.suppress(Exception):
|
||||
autodelete = await self._config.guild(message.guild).autodelete()
|
||||
if autodelete:
|
||||
await message.delete()
|
||||
to_remove.append(message.id)
|
||||
self._messages_cache = [x for x in self._messages_cache if x.id not in to_remove]
|
||||
embed.set_author(name=channel_title)
|
||||
embed.set_image(url=rnd(thumbnail))
|
||||
embed.colour = 0x9255A5
|
||||
return embed
|
||||
return embed, is_schedule
|
||||
|
||||
async def fetch_id(self):
|
||||
return await self._fetch_channel_resource("id")
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user