1
Fork 0
mirror of https://github.com/RYGhub/royalnet.git synced 2024-11-23 19:44:20 +00:00

WIP: new queue

This commit is contained in:
Steffo 2018-11-28 14:47:08 +00:00
parent a5f0df2597
commit cabc517861
2 changed files with 94 additions and 45 deletions

View file

@ -20,6 +20,7 @@ import datetime
import sqlalchemy.exc import sqlalchemy.exc
import coloredlogs import coloredlogs
import errors import errors
import math
logging.getLogger().disabled = True logging.getLogger().disabled = True
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -51,10 +52,6 @@ zalgo_middle = ['̕', '̛', '̀', '́', '͘', '̡', '̢', '̧', '̨', '̴', '̵'
# Init the event loop # Init the event loop
loop = asyncio.get_event_loop() loop = asyncio.get_event_loop()
# Init the config reader
config = configparser.ConfigParser()
config.read("config.ini")
# Radio messages # Radio messages
radio_messages = ["https://www.youtube.com/watch?v=3-yeK1Ck4yk", radio_messages = ["https://www.youtube.com/watch?v=3-yeK1Ck4yk",
"https://youtu.be/YcR7du_A1Vc", "https://youtu.be/YcR7du_A1Vc",
@ -141,20 +138,33 @@ else:
class Video: class Video:
"""A video to be played in the bot."""
def __init__(self, url: str = None, file: str = None, info: dict = None, enqueuer: discord.Member = None): def __init__(self, url: str = None, file: str = None, info: dict = None, enqueuer: discord.Member = None):
# Url of the video if it has to be downloaded
self.url = url self.url = url
# Filename of the downloaded video
if file is None and info is None: if file is None and info is None:
# Get it from the url hash
self.file = str(hash(url)) + ".opus" self.file = str(hash(url)) + ".opus"
elif info is not None: elif info is not None:
# Get it from the video title
self.file = "./opusfiles/" + re.sub(r'[/\\?*"<>|!:]', "_", info["title"]) + ".opus" self.file = "./opusfiles/" + re.sub(r'[/\\?*"<>|!:]', "_", info["title"]) + ".opus"
else: else:
# The filename was explicitly passed
self.file = file self.file = file
self.downloaded = False if file is None else True # Was the file already downloaded?
self.downloaded = (file is not None)
# Do we already have info on the video?
self.info = info self.info = info
# Who added the video to the queue?
self.enqueuer = enqueuer self.enqueuer = enqueuer
self.duration = None # How long is the video?
if info is not None:
self.duration = info.get("duration")
def __str__(self): def __str__(self):
"""Format the title to be used on Discord using Markdown."""
if self.info is None or "title" not in self.info: if self.info is None or "title" not in self.info:
return f"`{self.file}`" return f"`{self.file}`"
return f"_{self.info['title']}_" return f"_{self.info['title']}_"
@ -163,11 +173,12 @@ class Video:
return f"<discordbot.Video {str(self)}>" return f"<discordbot.Video {str(self)}>"
def plain_text(self): def plain_text(self):
"""Format the video title without any Markdown."""
if self.info is None or "title" not in self.info: if self.info is None or "title" not in self.info:
return self.file return self.file
return self.info['title'] return self.info['title']
async def download(self, progress_hooks: typing.List["function"] = None): def download(self, progress_hooks: typing.List["function"] = None):
# File already downloaded # File already downloaded
if self.downloaded: if self.downloaded:
raise errors.AlreadyDownloadedError() raise errors.AlreadyDownloadedError()
@ -176,10 +187,11 @@ class Video:
progress_hooks = [] progress_hooks = []
# Check if under max duration # Check if under max duration
self.duration = datetime.timedelta(seconds=self.info.get("duration", 0)) self.duration = datetime.timedelta(seconds=self.info.get("duration", 0))
if self.info is not None and self.duration.total_seconds() > int(config["YouTube"]["max_duration"]): # Refuse downloading if over YouTube max_duration
if self.info is not None and self.duration.total_seconds() > self.max_duration:
raise errors.DurationError() raise errors.DurationError()
# Download the file # Download the file
logger.info(f"Now downloading {repr(self)}.") logger.info(f"Downloading: {repr(self)}")
with youtube_dl.YoutubeDL({"noplaylist": True, with youtube_dl.YoutubeDL({"noplaylist": True,
"format": "best", "format": "best",
"postprocessors": [{ "postprocessors": [{
@ -189,8 +201,8 @@ class Video:
"outtmpl": self.file, "outtmpl": self.file,
"progress_hooks": progress_hooks, "progress_hooks": progress_hooks,
"quiet": True}) as ytdl: "quiet": True}) as ytdl:
await loop.run_in_executor(executor, functools.partial(ytdl.download, [self.url])) ytdl.download(self.url)
logger.info(f"Download of {repr(self)} complete.") logger.info(f"Download complete: {repr(self)}")
self.downloaded = True self.downloaded = True
def create_audio_source(self) -> discord.PCMVolumeTransformer: def create_audio_source(self) -> discord.PCMVolumeTransformer:
@ -201,6 +213,7 @@ class Video:
class SecretVideo(Video): class SecretVideo(Video):
"""A video to be played, but with a Zalgo'ed title."""
def __str__(self): def __str__(self):
final_string = "" final_string = ""
@ -220,12 +233,6 @@ class SecretVideo(Video):
final_string += letter final_string += letter
return final_string return final_string
def create_audio_source(self) -> discord.PCMVolumeTransformer:
# Check if the file has been downloaded
if not self.downloaded:
raise errors.FileNotDownloadedError()
return discord.PCMVolumeTransformer(discord.FFmpegPCMAudio(f"./opusfiles/{self.file}", **ffmpeg_settings))
def escape(message: str): def escape(message: str):
return message.replace("<", "&lt;").replace(">", "&gt;") return message.replace("<", "&lt;").replace(">", "&gt;")
@ -335,22 +342,61 @@ class RoyalDiscordBot(discord.Client):
"!resume": self.cmd_resume "!resume": self.cmd_resume
} }
self.video_queue: typing.List[Video] = [] self.video_queue: typing.List[Video] = []
self.now_playing = None self.now_playing: typing.Optional[Video] = None
self.radio_messages = True self.load_config("config.ini")
self.next_radio_message_in = int(config["Discord"]["radio_messages_every"])
self.inactivity_timer = 0 self.inactivity_timer = 0
def load_config(self, filename):
# Init the config reader
config = configparser.ConfigParser()
config.read("config.ini")
# Token
try:
self.token = config["Discord"]["bot_token"]
except KeyError, ValueError:
raise errors.InvalidConfigError("Missing Discord bot token.")
# Main channels, will be fully loaded when ready
try:
self.main_guild_id = int(config["Discord"]["server_id"])
self.main_channel_id = int(config["Discord"]["main_channel"]
except KeyError, ValueError:
raise errors.InvalidConfigError("Missing main guild and channel ids.")
# Max enqueable video duration
try:
self.max_duration = int(config["YouTube"].get("max_duration"))
except KeyError, ValueError:
logger.warning("Max video duration is not set, setting it to infinity.")
self.max_duration = math.inf
# Max videos to predownload
try:
self.max_videos_to_predownload = int(config["YouTube"]["predownload_videos"])
except KeyError, ValueError:
logger.warning("Max videos to predownload is not set, setting it to infinity.")
self.max_videos_to_predownload = None
# Radio messages
try:
self.radio_messages_enabled = True if config["Discord"]["radio_messages_enabled"] == "True" else False
self.radio_messages_every = int(config["Discord"]["radio_messages_every"])
self.radio_messages_next_in = self.radio_messages_every
except KeyError, ValueError:
logger.warning("Radio messages config error, disabling them.")
self.radio_messages_enabled = False
self.radio_messages_every = mathf.inf
self.radio_messages_next_in = mathf.inf
async def on_ready(self): async def on_ready(self):
# Get the main channel
self.main_channel = self.get_channel(int(config["Discord"]["main_channel"]))
if not isinstance(self.main_channel, discord.TextChannel):
raise errors.InvalidConfigError("The main channel is not a TextChannel!")
# Get the main guild # Get the main guild
self.main_guild = self.get_guild(int(config["Discord"]["server_id"])) self.main_guild = self.get_guild(self.main_guild_id)
if not isinstance(self.main_guild, discord.Guild): if not isinstance(self.main_guild, discord.Guild):
raise errors.InvalidConfigError("The main guild does not exist!") raise errors.InvalidConfigError("The main guild does not exist!")
# Get the main channel
self.main_channel = self.get_channel(self.main_channel_id)
if not isinstance(self.main_channel, discord.TextChannel):
raise errors.InvalidConfigError("The main channel is not a TextChannel!")
# Show yourself!
await self.change_presence(status=discord.Status.online, activity=None) await self.change_presence(status=discord.Status.online, activity=None)
logger.info("Bot is ready!") logger.info("Bot is ready!")
# Start the bot tasks
asyncio.ensure_future(self.queue_predownload_videos()) asyncio.ensure_future(self.queue_predownload_videos())
asyncio.ensure_future(self.queue_play_next_video()) asyncio.ensure_future(self.queue_play_next_video())
asyncio.ensure_future(self.inactivity_countdown()) asyncio.ensure_future(self.inactivity_countdown())
@ -504,12 +550,12 @@ class RoyalDiscordBot(discord.Client):
async def queue_predownload_videos(self): async def queue_predownload_videos(self):
while True: while True:
for index, video in enumerate(self.video_queue[:int(config["YouTube"]["predownload_videos"])].copy()): for index, video in enumerate(self.video_queue[:(None if self.max_videos_to_predownload == math.inf else self.max_videos_to_predownload].copy()):
if video.downloaded: if video.downloaded:
continue continue
try: try:
with async_timeout.timeout(int(config["YouTube"]["download_timeout"])): with async_timeout.timeout(int(config["YouTube"]["download_timeout"])):
await video.download() await loop.run_in_executor(executor, video.download)
except asyncio.TimeoutError: except asyncio.TimeoutError:
logger.warning(f"Video download took more than {config['YouTube']['download_timeout']}s:" logger.warning(f"Video download took more than {config['YouTube']['download_timeout']}s:"
f" {video.plain_text()}") f" {video.plain_text()}")
@ -520,7 +566,7 @@ class RoyalDiscordBot(discord.Client):
continue continue
except DurationError: except DurationError:
await self.main_channel.send(f"⚠️ {str(video)} dura più di" await self.main_channel.send(f"⚠️ {str(video)} dura più di"
f" {str(int(config['YouTube']['max_duration']) // 60)}" f" {self.max_duration // 60}"
f" minuti, quindi è stato rimosso dalla coda.") f" minuti, quindi è stato rimosso dalla coda.")
del self.video_queue[index] del self.video_queue[index]
continue continue
@ -560,7 +606,7 @@ class RoyalDiscordBot(discord.Client):
now_playing = self.video_queue[0] now_playing = self.video_queue[0]
try: try:
audio_source = now_playing.create_audio_source() audio_source = now_playing.create_audio_source()
except FileNotDownloadedError: except errors.FileNotDownloadedError:
continue continue
logger.info(f"Started playing {repr(now_playing)}.") logger.info(f"Started playing {repr(now_playing)}.")
voice_client.play(audio_source) voice_client.play(audio_source)
@ -750,15 +796,13 @@ class RoyalDiscordBot(discord.Client):
"Sintassi: `!play <url|ricercayoutube|nomefile>`") "Sintassi: `!play <url|ricercayoutube|nomefile>`")
return return
channel.typing() channel.typing()
# If the radio messages are enabled... self.next_radio_message_in -= 1
if self.radio_messages: if self.next_radio_message_in <= 0:
self.next_radio_message_in -= 1 radio_message = random.sample(radio_messages, 1)[0]
if self.next_radio_message_in <= 0: self.next_radio_message_in = self.radio_messages_every
radio_message = random.sample(radio_messages, 1)[0] await self.add_video_from_url(radio_message)
self.next_radio_message_in = int(config["Discord"]["radio_messages_every"]) await channel.send(f"📻 Aggiunto un messaggio radio, disattiva con `!radiomessages off`.")
await self.add_video_from_url(radio_message) logger.info(f"Radio message added to the queue.")
await channel.send(f"📻 Aggiunto un messaggio radio, disattiva con `!radiomessages off`.")
logger.info(f"Radio message added to the queue.")
# Parse the parameter as URL # Parse the parameter as URL
url = re.match(r"(?:https?://|ytsearch[0-9]*:).*", " ".join(params[1:]).strip("<>")) url = re.match(r"(?:https?://|ytsearch[0-9]*:).*", " ".join(params[1:]).strip("<>"))
if url is not None: if url is not None:
@ -931,19 +975,23 @@ class RoyalDiscordBot(discord.Client):
@command @command
async def cmd_radiomessages(self, channel: discord.TextChannel, author: discord.Member, params: typing.List[str]): async def cmd_radiomessages(self, channel: discord.TextChannel, author: discord.Member, params: typing.List[str]):
if not self.radio_messages_enabled:
await channel.send("⚠ I messaggi radio sono stati disabilitati dall'amministratore del bot.")
return
if len(params) < 2: if len(params) < 2:
self.radio_messages = not self.radio_messages await channel.send("⚠ Sintassi del comando non valida.\n"
"Sintassi: `!radiomessages <on|off>`")
else: else:
if params[1].lower() == "on": if params[1].lower() == "on":
self.radio_messages = True self.radio_messages_next_in = self.radio_messages_every
elif params[1].lower() == "off": elif params[1].lower() == "off":
self.radio_messages = False self.radio_messages_next_in = math.inf
else: else:
await channel.send("⚠ Sintassi del comando non valida.\n" await channel.send("⚠ Sintassi del comando non valida.\n"
"Sintassi: `!radiomessages [on|off]`") "Sintassi: `!radiomessages <on|off>`")
return return
logger.info(f"Radio messages status toggled to {self.radio_messages}.") logger.info(f"Radio messages status to {'enabled' if self.radio_messages.next_in < math.inf else 'disabled'}.")
await channel.send(f"📻 Messaggi radio **{'attivati' if self.radio_messages else 'disattivati'}**.") await channel.send(f"📻 Messaggi radio **{'attivati' if self.radio_messages.next_in < math.inf else 'disattivati'}**.")
@command @command
@requires_connected_voice_client @requires_connected_voice_client
@ -972,7 +1020,7 @@ def process(users_connection=None):
logger.info("Initializing Telegram-Discord connection...") logger.info("Initializing Telegram-Discord connection...")
asyncio.ensure_future(bot.feed_pipe(users_connection)) asyncio.ensure_future(bot.feed_pipe(users_connection))
logger.info("Logging in...") logger.info("Logging in...")
loop.run_until_complete(bot.login(config["Discord"]["bot_token"], bot=True)) loop.run_until_complete(bot.login(bot.token, bot=True))
logger.info("Connecting...") logger.info("Connecting...")
loop.run_until_complete(bot.connect()) loop.run_until_complete(bot.connect())
logger.info("Now stopping...") logger.info("Now stopping...")

View file

@ -15,6 +15,7 @@ bot_token =
server_id = server_id =
main_channel = main_channel =
afk_timer = 10 afk_timer = 10
radio_messages_enabled = True
radio_messages_every = 5 radio_messages_every = 5
[Telegram] [Telegram]