Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor and bugfixes #18

Merged
merged 13 commits into from
Aug 31, 2024
7 changes: 3 additions & 4 deletions Tests/video_system/youtube/test_youtube.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import logging
import os
import shutil
import pytest
import yt_dlp

from Tests.video_system.download_tester import DownloadTester
from src.downloader import DownloadFailedError
from src.Youtube import YoutubeDownloader

TEST_YOUTUBE_1 = "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
Expand All @@ -26,10 +25,10 @@ def remove_cache(self):
async def test_basic_download(self):
try:
videos = await YoutubeDownloader.download_video_from_link(TEST_YOUTUBE_1, DOWNLOAD_PATH)
except yt_dlp.DownloadError as e:
except DownloadFailedError as e:
assert e.msg
import warnings
if "Sign in" not in e.msg:
if "Sign in" not in str(e.with_traceback(None)):
raise e # re-raise the exception if it's not a sign in error
warnings.warn(e.msg)
return
Expand Down
8 changes: 6 additions & 2 deletions src/Helpers/logging_system.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,12 @@ def is_server(only_true_if_cloud: bool = True) -> bool:


if is_server(only_true_if_cloud=False):
google_client = google.cloud.logging.Client()
google_client.setup_logging(log_level=logging.DEBUG)
try:
google_client = google.cloud.logging.Client()
google_client.get_default_handler()
google_client.setup_logging(log_level=logging.DEBUG)
except Exception as e:
logging.error("Failed to setup google cloud logging", exc_info=e)
else:
format_string = '%(asctime)s: %(name)s - %(levelname)s - %(message)s in %(filename)s:%(lineno)d'
logging.basicConfig(level=logging.DEBUG, filename=f'{BOT_NAME}.log', filemode='w', format=format_string)
Expand Down
28 changes: 5 additions & 23 deletions src/Youtube.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import asyncio
import functools
import logging
import os
Expand All @@ -7,7 +6,7 @@
import yt_dlp

from Constants import MAX_VIDEO_DOWNLOAD_SIZE
from src.downloader import VIDEO_RETURN_TYPE, VideoDownloader, VideoFile
from src.downloader import VIDEO_RETURN_TYPE, AlternateVideoDownloader

ydl_opts = {
'format': 'bestaudio',
Expand Down Expand Up @@ -94,9 +93,9 @@ def youtube_download(video_url, progress_queue: LifoQueue, file_path_with_name):
def get_last_played_guilded() -> video_data_guild:
return last_played

class YoutubeDownloader(VideoDownloader):
@staticmethod
async def download_video_from_link(url: str, path: str | None = None) -> VIDEO_RETURN_TYPE:
class YoutubeDownloader(AlternateVideoDownloader):
@classmethod
async def download_video_from_link(cls, url: str, path: str | None = None) -> VIDEO_RETURN_TYPE:
if path is None:
path = os.path.join("downloads", "youtube")

Expand All @@ -111,21 +110,4 @@ async def download_video_from_link(url: str, path: str | None = None) -> VIDEO_R
'quiet': True,
}

with yt_dlp.YoutubeDL(costum_options) as ydl:
ydt = await asyncio.to_thread(ydl.extract_info, url, download=True)

if ydt is None:
return []

info = ydt.get("entries", [None])[0] or ydt
video_id = info["id"]
video_extension = info["ext"]
if video_id is None:
return []

if video_extension != "mp4":
logging.error("Got a non-mp4 file that is %s from this link: %s", video_extension, url)

file_path = os.path.join(path, f"{video_id}.{video_extension}")

return [VideoFile(file_path, info.get("title", None))]
return await cls._get_list_from_ydt(url, costum_options, path)
190 changes: 131 additions & 59 deletions src/download_commands.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
import asyncio
import logging
from typing import Optional
from typing import Optional, Type
import discord

from src.downloader import (
VIDEO_RETURN_TYPE,
AbstractClassUsedError,
DownloadFailedError,
NoVideoFoundError,
VideoDownloader,
)
from src.other import UnknownAlternateDownloader
from src.downloading_system import get_downloader
from src.downloading_system import get_downloader, get_url_from_text


def _convert_paths_to_discord_files(paths: list[str]) -> list[discord.File]:
return [discord.File(path) for path in paths]
Expand All @@ -16,80 +24,155 @@ def _get_shortest_punctuation_index(caption: str) -> int | None:
comma = caption.find(",")
question_mark = caption.find("?")
exclamation_mark = caption.find("!")
filtered_list = list(filter(lambda x: x != -1, [dot, comma, question_mark, exclamation_mark]))
filtered_list = list(
filter(lambda x: x != -1, [dot, comma, question_mark, exclamation_mark])
)
if len(filtered_list) == 0:
return None
return min(filtered_list)


def _get_shortened_caption(caption: str) -> str:
# check if we have a punctuation mark in the caption
caption = caption.split("\n")[0]

punctuation_index = _get_shortest_punctuation_index(caption)
if punctuation_index:
return caption[:punctuation_index + 1]
return caption[: punctuation_index + 1]
return caption[:100]


def _get_view(shortened_caption: str, caption: str):
view = discord.ui.View()
button = discord.ui.Button(label="🔽\nExpand", style=discord.ButtonStyle.secondary)

async def callback(interaction: discord.Interaction):
revert_view = discord.ui.View()
button = discord.ui.Button(label="🔼\nShorten", style=discord.ButtonStyle.secondary)
button = discord.ui.Button(
label="🔼\nShorten", style=discord.ButtonStyle.secondary
)

async def callback(interaction: discord.Interaction):
await interaction.response.edit_message(content=shortened_caption, view=view)
await interaction.response.edit_message(
content=shortened_caption, view=view
)

button.callback = callback
revert_view.add_item(button)
await interaction.response.edit_message(content=caption, view=revert_view)

button.callback = callback
view.add_item(button)
return view


def _get_caption_and_view(
real_caption: str, include_title: Optional[bool]
) -> tuple[Optional[str], discord.ui.View]:
shortened_caption = _get_shortened_caption(real_caption) + " ***...***"
view = discord.utils.MISSING

if include_title is False:
caption = None

elif include_title is True:
caption = real_caption

elif len(shortened_caption) < len(real_caption):
view = _get_view(shortened_caption, real_caption)
caption = shortened_caption
else:
caption = real_caption

return caption, view


async def get_details(
downloader: Type[VideoDownloader], url: str, interaction: discord.Interaction
) -> Optional[VIDEO_RETURN_TYPE]:
try:
return await downloader.download_video_from_link(url)
except DownloadFailedError:
await interaction.followup.send(
"Video indirilirken başarısız olundu, hata raporu alındı. Lütfen daha sonra tekrar deneyin",
ephemeral=True,
)
logging.exception("Failed Downloading Link: %s", url, exc_info=True)
return
except NoVideoFoundError:
await interaction.followup.send(
"Linkte bir video bulamadım, linkte **video** olduğuna emin misin?",
ephemeral=True,
)
logging.exception("Couldn't find link on url %s", url, exc_info=True)
return
except AbstractClassUsedError:
await interaction.followup.send(
"Bir şeyler ÇOK ters gitti, hata raporu alındı.", ephemeral=True
)
logging.exception(
"An abstract class was used, this should not happen", exc_info=True
)
return
except Exception as e:
await interaction.followup.send(
"Bilinmeyen bir hata oluştu, lütfen tekrar deneyin", ephemeral=True
)
raise e


async def _convert_to_discord_files(
interaction: discord.Interaction, attachments: VIDEO_RETURN_TYPE
) -> list[discord.File]:
try:
file_paths = [attachment.path for attachment in attachments]
return _convert_paths_to_discord_files(file_paths)
except Exception as e:
await interaction.followup.send(
"Bilinmeyen bir hata oluştu, lütfen tekrar deneyin", ephemeral=True
)
raise e # re-raise the exception so we can see what went wrong


async def download_video_command(
interaction: discord.Interaction,
url: str,
is_ephemeral: bool = False,
include_title: bool | None = None,
):
url = get_url_from_text(url)

async def download_video_command(interaction: discord.Interaction, url: str, is_ephemeral: bool = False, include_title: bool | None = None):
downloader = get_downloader(url)

if downloader is None:
logging.info("Found an unsupported link: %s", url)
await interaction.response.defer(ephemeral=True)
return await try_unknown_link(interaction, url, include_title)

await interaction.response.defer(ephemeral=is_ephemeral)

try:
attachments = await downloader.download_video_from_link(url)
file_paths = [attachment.path for attachment in attachments]
discord_files = _convert_paths_to_discord_files(file_paths)
except Exception as e:
await interaction.followup.send("Bir şey ters gitti... lütfen tekrar deneyin", ephemeral=True)
raise e # re-raise the exception so we can see what went wrong
if len(attachments) == 0:
await interaction.followup.send("Videoyu Bulamadım, lütfen daha sonra tekrar deneyin ya da hatayı bildirin", ephemeral=True)
attachments = await get_details(downloader, url, interaction)
if attachments is None:
return
returned_content = " + ".join(filter(None, [attachment.caption for attachment in attachments]))
default_caption = f"Video{'s' if len(attachments) > 1 else ''} Downloaded"
caption = ""
view = discord.utils.MISSING
shortened_content = _get_shortened_caption(returned_content) + " ***...***"

if include_title is False or not returned_content:
caption = default_caption
discord_files = await _convert_to_discord_files(interaction, attachments)

elif include_title is True:
caption = returned_content
real_caption = (
attachments.caption or f"Video{'s' if len(attachments) > 1 else ''} Downloaded"
)
caption, view = _get_caption_and_view(real_caption, include_title)
caption = caption or discord.utils.MISSING

elif len(shortened_content) < len(returned_content):
view = _get_view(shortened_content, returned_content)
caption = shortened_content
else:
caption = returned_content
await interaction.followup.send(
caption, files=discord_files, ephemeral=is_ephemeral, view=view
)

await interaction.followup.send(caption, files=discord_files, ephemeral=is_ephemeral, view=view)

async def loading_animation(message: discord.WebhookMessage):
original_text = message.content
sleep_time = 0 # we don't actually need to sleep thanks to ``message.edit`` being async
sleep_time = (
0 # we don't actually need to sleep thanks to ``message.edit`` being async
)
while True:
await message.edit(content=original_text + ".", view=discord.ui.View())
await asyncio.sleep(sleep_time)
Expand All @@ -98,16 +181,23 @@ async def loading_animation(message: discord.WebhookMessage):
await message.edit(content=original_text + "...", view=discord.ui.View())
await asyncio.sleep(sleep_time)

async def try_unknown_link(interaction: discord.Interaction, url: str, include_title: Optional[bool] = None):
""" edits the sent message if the download is successful, otherwise sends an error message

async def try_unknown_link(
interaction: discord.Interaction, url: str, include_title: Optional[bool] = None
):
"""edits the sent message if the download is successful, otherwise sends an error message

Args:
interaction (discord.Interaction): the interaction to edit with ``interaction.response.edit_message``
url (str): the url to download the video from
"""

downloader = UnknownAlternateDownloader
sent_message = await interaction.followup.send("Bu link resmi olarak desteklenmiyor, yine de indirmeyi deniyorum", ephemeral=True, wait=True)
sent_message = await interaction.followup.send(
"Bu link resmi olarak desteklenmiyor, yine de indirmeyi deniyorum",
ephemeral=True,
wait=True,
)
loading_task = asyncio.create_task(loading_animation(sent_message))

try:
Expand All @@ -117,31 +207,13 @@ async def try_unknown_link(interaction: discord.Interaction, url: str, include_t
except Exception as e:
loading_task.cancel()
await sent_message.edit(content="Linki ne yazıkki indiremedim")
raise e # re-raise the exception so we can see what went wrong

if len(attachments) == 0:
loading_task.cancel()
await sent_message.edit(content="Videoyu Bulamadım, lütfen daha sonra tekrar deneyin ya da hatayı bildirin")
return

returned_content = " + ".join(filter(None, [attachment.caption for attachment in attachments]))
default_caption = f"Video{'s' if len(attachments) > 1 else ''} Downloaded"
caption = ""
view = discord.utils.MISSING
shortened_content = _get_shortened_caption(returned_content) + " ***...***"
raise e # re-raise the exception so we can see what went wrong


if include_title is False or not returned_content:
caption = default_caption

elif include_title is True:
caption = returned_content

elif len(shortened_content) < len(returned_content):
view = _get_view(shortened_content, returned_content)
caption = shortened_content
else:
caption = returned_content
real_caption = (
attachments.caption or f"Video{'s' if len(attachments) > 1 else ''} Downloaded"
)
caption, view = _get_caption_and_view(real_caption, include_title)
caption = caption or ""

loading_task.cancel()
await sent_message.edit(content=f"{url} downloaded")
Expand Down
Loading
Loading