youtube download
This commit is contained in:
parent
71ff713417
commit
0749f742c3
9
.flake8
Normal file
9
.flake8
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
[flake8]
|
||||||
|
ignore =
|
||||||
|
E501,
|
||||||
|
E731,
|
||||||
|
W503
|
||||||
|
exclude =
|
||||||
|
.git,
|
||||||
|
__pycache__,
|
||||||
|
__init__.py
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,2 +1,3 @@
|
|||||||
__pycache__
|
__pycache__
|
||||||
config.toml
|
config.toml
|
||||||
|
download
|
||||||
|
2
.isort.cfg
Normal file
2
.isort.cfg
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
[settings]
|
||||||
|
profile = black
|
0
Dockerfile
Normal file
0
Dockerfile
Normal file
32
__main__.py
32
__main__.py
@ -1,11 +1,13 @@
|
|||||||
from toml import TomlDecodeError
|
from os import mkdir, path
|
||||||
from discord import Bot, Intents
|
|
||||||
|
from discord import Bot, Intents
|
||||||
|
from toml import TomlDecodeError
|
||||||
|
|
||||||
from config import Config
|
|
||||||
from logger import Logger
|
|
||||||
from framework import Redis, Youtube
|
|
||||||
from cog import Greetings, Music
|
from cog import Greetings, Music
|
||||||
from service import QueueManager
|
from config import Config
|
||||||
|
from framework import Downloader, Redis, Spotify, Youtube
|
||||||
|
from logger import Logger
|
||||||
|
from service import FileManager, QueueManager
|
||||||
from usecase import Sources
|
from usecase import Sources
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
@ -19,11 +21,14 @@ if __name__ == "__main__":
|
|||||||
print("Config/KeyError : %s" % error)
|
print("Config/KeyError : %s" % error)
|
||||||
exit(2)
|
exit(2)
|
||||||
|
|
||||||
|
if not path.isdir(config.downloadDirectory):
|
||||||
|
mkdir(config.downloadDirectory)
|
||||||
|
|
||||||
# Set Logger
|
# Set Logger
|
||||||
logger = Logger(config.logging)()
|
logger = Logger(config.logging)()
|
||||||
|
|
||||||
# Redis Client
|
# Redis Client
|
||||||
redis = Redis(logger, config.redis)
|
redis = Redis(logger, config.redis, config.appName)
|
||||||
|
|
||||||
# Queue Manager
|
# Queue Manager
|
||||||
queueManager = QueueManager(redis)
|
queueManager = QueueManager(redis)
|
||||||
@ -32,10 +37,19 @@ if __name__ == "__main__":
|
|||||||
bot = Bot(intents=Intents.default())
|
bot = Bot(intents=Intents.default())
|
||||||
|
|
||||||
# Youtube Client
|
# Youtube Client
|
||||||
youtube = Youtube(bot.loop, config.youtube)
|
youtube = Youtube(bot.loop, config.youtube, config.downloadDirectory)
|
||||||
|
|
||||||
|
# Spotify Client
|
||||||
|
spotify = Spotify(bot.loop, config.spotify)
|
||||||
|
|
||||||
|
# Downloader
|
||||||
|
downloader = Downloader(bot.loop)
|
||||||
|
|
||||||
|
# File Manager
|
||||||
|
fileManager = FileManager(youtube, downloader, config.downloadDirectory)
|
||||||
|
|
||||||
# Sources
|
# Sources
|
||||||
sources = Sources(youtube)
|
sources = Sources(fileManager, youtube, spotify)
|
||||||
|
|
||||||
# Add Cogs
|
# Add Cogs
|
||||||
bot.add_cog(Greetings(bot, logger, redis))
|
bot.add_cog(Greetings(bot, logger, redis))
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
from discord import Member, Bot, Cog, ApplicationContext
|
|
||||||
from discord.commands import slash_command
|
|
||||||
from logging import Logger
|
from logging import Logger
|
||||||
|
|
||||||
|
from discord import ApplicationContext, Bot, Cog, default_permissions, slash_command
|
||||||
|
|
||||||
from framework.redis import Redis
|
from framework.redis import Redis
|
||||||
|
|
||||||
|
|
||||||
@ -12,6 +12,7 @@ class Greetings(Cog):
|
|||||||
self.redis = redis
|
self.redis = redis
|
||||||
|
|
||||||
@slash_command()
|
@slash_command()
|
||||||
|
@default_permissions(administrator=True)
|
||||||
async def redis_set(self, context: ApplicationContext, key: str, value: str):
|
async def redis_set(self, context: ApplicationContext, key: str, value: str):
|
||||||
self.logger.info(f"redis set {value} at {key}")
|
self.logger.info(f"redis set {value} at {key}")
|
||||||
await self.redis.set(key, value)
|
await self.redis.set(key, value)
|
||||||
@ -19,6 +20,7 @@ class Greetings(Cog):
|
|||||||
await context.respond(f"redis set {value} at {key}")
|
await context.respond(f"redis set {value} at {key}")
|
||||||
|
|
||||||
@slash_command()
|
@slash_command()
|
||||||
|
@default_permissions(administrator=True)
|
||||||
async def redis_get(self, context: ApplicationContext, key: str):
|
async def redis_get(self, context: ApplicationContext, key: str):
|
||||||
self.logger.info(f"redis get {key}")
|
self.logger.info(f"redis get {key}")
|
||||||
value = await self.redis.get(key)
|
value = await self.redis.get(key)
|
||||||
|
24
cog/music.py
24
cog/music.py
@ -1,8 +1,7 @@
|
|||||||
from discord import Bot, Cog, ApplicationContext
|
|
||||||
from discord.commands import slash_command
|
|
||||||
|
|
||||||
from entity import Entry
|
|
||||||
from logging import Logger
|
from logging import Logger
|
||||||
|
|
||||||
|
from discord import ApplicationContext, Bot, Cog, slash_command
|
||||||
|
|
||||||
from service import QueueManager
|
from service import QueueManager
|
||||||
from usecase import Sources
|
from usecase import Sources
|
||||||
|
|
||||||
@ -19,17 +18,8 @@ class Music(Cog):
|
|||||||
@slash_command(name="play")
|
@slash_command(name="play")
|
||||||
async def play(self, context: ApplicationContext, query: str):
|
async def play(self, context: ApplicationContext, query: str):
|
||||||
async with self.queueManager(context.guild_id) as queue:
|
async with self.queueManager(context.guild_id) as queue:
|
||||||
test = await self.sources.processQuery(query)
|
interaction = await context.respond(f"searching {query} ...")
|
||||||
|
entries = await self.sources.processQuery(interaction, query)
|
||||||
|
queue.add(entries)
|
||||||
|
|
||||||
queue.add(
|
await interaction.edit_original_response(content=entries[0].title.name)
|
||||||
Entry(
|
|
||||||
title="title",
|
|
||||||
artist="artist",
|
|
||||||
album="album",
|
|
||||||
thumbnail="thumb",
|
|
||||||
link="fdsdfsd",
|
|
||||||
requesterId=context.author.id,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
await context.respond(str(test))
|
|
||||||
|
@ -13,6 +13,13 @@ class YoutubeConfig:
|
|||||||
self.region: str = youtube_config["region"]
|
self.region: str = youtube_config["region"]
|
||||||
|
|
||||||
|
|
||||||
|
class SpotifyConfig:
|
||||||
|
def __init__(self, spotify_config) -> None:
|
||||||
|
self.clientId: str = spotify_config["clientId"]
|
||||||
|
self.clientSecret: str = spotify_config["clientSecret"]
|
||||||
|
self.region: str = spotify_config["region"]
|
||||||
|
|
||||||
|
|
||||||
class LoggingConfig:
|
class LoggingConfig:
|
||||||
def __init__(self, logging_config) -> None:
|
def __init__(self, logging_config) -> None:
|
||||||
self.level: str = logging_config["level"]
|
self.level: str = logging_config["level"]
|
||||||
@ -29,8 +36,10 @@ class Config:
|
|||||||
def __init__(self, config_path: str) -> None:
|
def __init__(self, config_path: str) -> None:
|
||||||
self._config = toml.load(config_path)
|
self._config = toml.load(config_path)
|
||||||
self.appName: str = self._config["appName"]
|
self.appName: str = self._config["appName"]
|
||||||
|
self.downloadDirectory: str = self._config["downloadDirectory"]
|
||||||
self.discord = DiscordConfig(self._config["discord"])
|
self.discord = DiscordConfig(self._config["discord"])
|
||||||
self.youtube = YoutubeConfig(self._config["youtube"])
|
self.youtube = YoutubeConfig(self._config["youtube"])
|
||||||
|
self.spotify = SpotifyConfig(self._config["spotify"])
|
||||||
self.logging = LoggingConfig(self._config["logging"])
|
self.logging = LoggingConfig(self._config["logging"])
|
||||||
self.redis = RedisConfig(self._config["redis"])
|
self.redis = RedisConfig(self._config["redis"])
|
||||||
|
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
from .queue import Queue
|
from .album import Album
|
||||||
|
from .artist import Artist
|
||||||
from .entry import Entry
|
from .entry import Entry
|
||||||
from .file import File
|
from .file import File
|
||||||
from .playlist import Playlist
|
from .playlist import Playlist
|
||||||
|
from .queue import Queue
|
||||||
|
from .title import Title
|
||||||
|
4
entity/album.py
Normal file
4
entity/album.py
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
class Album:
|
||||||
|
def __init__(self, name: str, url: str) -> None:
|
||||||
|
self.name = name
|
||||||
|
self.url = url
|
4
entity/artist.py
Normal file
4
entity/artist.py
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
class Artist:
|
||||||
|
def __init__(self, name: str, url: str) -> None:
|
||||||
|
self.name = name
|
||||||
|
self.url = url
|
@ -1,24 +1,27 @@
|
|||||||
from .playlist import Playlist
|
from .album import Album
|
||||||
|
from .artist import Artist
|
||||||
from .file import File
|
from .file import File
|
||||||
|
from .playlist import Playlist
|
||||||
|
from .title import Title
|
||||||
|
|
||||||
|
|
||||||
class Entry:
|
class Entry:
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
title: str,
|
title: Title,
|
||||||
artist: str,
|
artist: Artist,
|
||||||
album: str,
|
duration: int,
|
||||||
thumbnail: str,
|
thumbnail: str,
|
||||||
link: str,
|
|
||||||
requesterId: int,
|
requesterId: int,
|
||||||
|
album: Album | None = None,
|
||||||
playlist: Playlist | None = None,
|
playlist: Playlist | None = None,
|
||||||
file: File | None = None,
|
source: File | str | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
self.title = title
|
self.title = title
|
||||||
self.artist = artist
|
self.artist = artist
|
||||||
self.album = album
|
self.album = album
|
||||||
|
self.duration = duration
|
||||||
self.thumbnail = thumbnail
|
self.thumbnail = thumbnail
|
||||||
self.link = link
|
|
||||||
self.requester = requesterId
|
self.requester = requesterId
|
||||||
self.playlist = playlist
|
self.playlist = playlist
|
||||||
self.file = file
|
self.source = source
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
class File:
|
class File:
|
||||||
def __init__(self, name: str, path: str, size: int) -> None:
|
def __init__(self, name: str, size: int) -> None:
|
||||||
self.name = name
|
self.name = name
|
||||||
self.path = path
|
|
||||||
self.size = size
|
self.size = size
|
||||||
|
@ -1,4 +1,8 @@
|
|||||||
|
from .artist import Artist
|
||||||
|
|
||||||
|
|
||||||
class Playlist:
|
class Playlist:
|
||||||
def __init__(self, title: str, url: str) -> None:
|
def __init__(self, name: str, url: str, owner: Artist) -> None:
|
||||||
self.title = title
|
self.name = name
|
||||||
self.url = url
|
self.url = url
|
||||||
|
self.owner = owner
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
from .entry import Entry
|
from .entry import Entry
|
||||||
from .playlist import Playlist
|
|
||||||
|
|
||||||
|
|
||||||
class Queue:
|
class Queue:
|
||||||
@ -7,11 +6,8 @@ class Queue:
|
|||||||
self._entries: list[Entry] = []
|
self._entries: list[Entry] = []
|
||||||
self.cursor = 0
|
self.cursor = 0
|
||||||
|
|
||||||
def add(self, entry: Entry) -> None:
|
def add(self, entries: list[Entry]) -> None:
|
||||||
self._entries.append(entry)
|
for entry in entries:
|
||||||
|
|
||||||
def addPlalist(self, playlist: Playlist) -> None:
|
|
||||||
for entry in playlist:
|
|
||||||
self._entries.append(entry)
|
self._entries.append(entry)
|
||||||
|
|
||||||
def remove(self, index: int, recursive: bool) -> None:
|
def remove(self, index: int, recursive: bool) -> None:
|
||||||
|
4
entity/title.py
Normal file
4
entity/title.py
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
class Title:
|
||||||
|
def __init__(self, name: str, url: str) -> None:
|
||||||
|
self.name = name
|
||||||
|
self.url = url
|
@ -1,2 +1,4 @@
|
|||||||
|
from .downloader import Downloader
|
||||||
from .redis import Redis
|
from .redis import Redis
|
||||||
|
from .spotify import Spotify
|
||||||
from .youtube import Youtube
|
from .youtube import Youtube
|
||||||
|
28
framework/downloader.py
Normal file
28
framework/downloader.py
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
from asyncio import AbstractEventLoop
|
||||||
|
from os import stat
|
||||||
|
from urllib.request import urlretrieve
|
||||||
|
|
||||||
|
from entity import File
|
||||||
|
|
||||||
|
|
||||||
|
class Downloader:
|
||||||
|
def __init__(self, loop: AbstractEventLoop) -> None:
|
||||||
|
self.loop = loop
|
||||||
|
|
||||||
|
def progress(self, progress, block_num: int, block_size: int, total_size: int):
|
||||||
|
self.loop.create_task(progress(block_num * block_size, total_size))
|
||||||
|
|
||||||
|
async def download(self, url: str, fileName: str, progress=None) -> File:
|
||||||
|
await self.loop.run_in_executor(
|
||||||
|
None,
|
||||||
|
lambda: urlretrieve(
|
||||||
|
url,
|
||||||
|
fileName,
|
||||||
|
lambda block_num, block_size, total_size: self.progress(
|
||||||
|
progress, block_num, block_size, total_size
|
||||||
|
)
|
||||||
|
if progress is not None
|
||||||
|
else None,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
return File(name=fileName, size=stat(fileName).st_size)
|
@ -1,13 +1,14 @@
|
|||||||
from logging import Logger
|
|
||||||
from redis.asyncio.lock import Lock
|
|
||||||
import redis.asyncio as redis
|
|
||||||
import pickle
|
import pickle
|
||||||
|
from logging import Logger
|
||||||
|
|
||||||
|
import redis.asyncio as redis
|
||||||
|
from redis.asyncio.lock import Lock
|
||||||
|
|
||||||
from config import RedisConfig
|
from config import RedisConfig
|
||||||
|
|
||||||
|
|
||||||
class Redis:
|
class Redis:
|
||||||
def __init__(self, logger: Logger, config: RedisConfig) -> None:
|
def __init__(self, logger: Logger, config: RedisConfig, rootKeyName: str) -> None:
|
||||||
self._client = redis.Redis(
|
self._client = redis.Redis(
|
||||||
host=config.host,
|
host=config.host,
|
||||||
port=config.port,
|
port=config.port,
|
||||||
@ -15,6 +16,7 @@ class Redis:
|
|||||||
auto_close_connection_pool=False,
|
auto_close_connection_pool=False,
|
||||||
)
|
)
|
||||||
self._locks: dict[str, Lock] = {}
|
self._locks: dict[str, Lock] = {}
|
||||||
|
self.rootKeyName = rootKeyName
|
||||||
self.logger = logger
|
self.logger = logger
|
||||||
|
|
||||||
async def _get_lock(self, key) -> Lock:
|
async def _get_lock(self, key) -> Lock:
|
||||||
@ -23,23 +25,23 @@ class Redis:
|
|||||||
return self._locks[key]
|
return self._locks[key]
|
||||||
|
|
||||||
async def acquire(self, key: str) -> None:
|
async def acquire(self, key: str) -> None:
|
||||||
lock = await self._get_lock(f"djembe:queue:{key}")
|
lock = await self._get_lock(f"{self.rootKeyName}:queue:{key}")
|
||||||
await lock.acquire()
|
await lock.acquire()
|
||||||
|
|
||||||
async def release(self, key: str) -> None:
|
async def release(self, key: str) -> None:
|
||||||
lock = await self._get_lock(f"djembe:queue:{key}")
|
lock = await self._get_lock(f"{self.rootKeyName}:queue:{key}")
|
||||||
await lock.release()
|
await lock.release()
|
||||||
|
|
||||||
async def get(self, key: str):
|
async def get(self, key: str):
|
||||||
self.logger.info(f"get value {key} from redis")
|
self.logger.info(f"get value {key} from redis")
|
||||||
value = await self._client.get(f"djembe:queue:{key}")
|
value = await self._client.get(f"{self.rootKeyName}:queue:{key}")
|
||||||
if value:
|
if value:
|
||||||
return pickle.loads(value)
|
return pickle.loads(value)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
async def set(self, key: str, value) -> None:
|
async def set(self, key: str, value) -> None:
|
||||||
self.logger.info(f"set value {key} to redis")
|
self.logger.info(f"set value {key} to redis")
|
||||||
await self._client.set(f"djembe:queue:{key}", pickle.dumps(value))
|
await self._client.set(f"{self.rootKeyName}:queue:{key}", pickle.dumps(value))
|
||||||
|
|
||||||
async def close(self) -> None:
|
async def close(self) -> None:
|
||||||
await self._client.close()
|
await self._client.close()
|
||||||
|
@ -0,0 +1,25 @@
|
|||||||
|
from asyncio import AbstractEventLoop
|
||||||
|
|
||||||
|
import spotipy
|
||||||
|
from spotipy import MemoryCacheHandler
|
||||||
|
from spotipy.oauth2 import SpotifyClientCredentials
|
||||||
|
|
||||||
|
from config import SpotifyConfig
|
||||||
|
|
||||||
|
|
||||||
|
class Spotify:
|
||||||
|
def __init__(self, loop: AbstractEventLoop, config: SpotifyConfig) -> None:
|
||||||
|
self.loop = loop
|
||||||
|
self.client = spotipy.Spotify(
|
||||||
|
auth_manager=SpotifyClientCredentials(
|
||||||
|
client_id=config.clientId,
|
||||||
|
client_secret=config.clientSecret,
|
||||||
|
cache_handler=MemoryCacheHandler(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
self.region = config.region
|
||||||
|
|
||||||
|
async def getTrack(self, url: str):
|
||||||
|
return await self.loop.run_in_executor(
|
||||||
|
None, lambda: self.client.track(url, self.region)
|
||||||
|
)
|
@ -1,25 +1,79 @@
|
|||||||
from yt_dlp import YoutubeDL
|
import re
|
||||||
from youtubesearchpython.__future__ import VideosSearch
|
|
||||||
from asyncio import AbstractEventLoop
|
from asyncio import AbstractEventLoop
|
||||||
|
from os import path, stat
|
||||||
|
|
||||||
|
from youtubesearchpython.__future__ import VideosSearch
|
||||||
|
from yt_dlp import YoutubeDL
|
||||||
|
|
||||||
from config import YoutubeConfig
|
from config import YoutubeConfig
|
||||||
|
from entity import File
|
||||||
|
|
||||||
|
|
||||||
class Youtube:
|
class Youtube:
|
||||||
def __init__(self, loop: AbstractEventLoop, config: YoutubeConfig) -> None:
|
def __init__(
|
||||||
self.params = {}
|
self, loop: AbstractEventLoop, config: YoutubeConfig, downloadDirectory: str
|
||||||
self.client = YoutubeDL(self.params)
|
) -> None:
|
||||||
self.loop = loop
|
self.loop = loop
|
||||||
self.config = config
|
self.params = {
|
||||||
|
"format": "bestaudio/best",
|
||||||
|
"outtmpl": path.join(downloadDirectory, "%(id)s"),
|
||||||
|
"restrictfilenames": True,
|
||||||
|
"noplaylist": True,
|
||||||
|
"ignoreerrors": True,
|
||||||
|
"logtostderr": False,
|
||||||
|
"verbose": False,
|
||||||
|
"quiet": True,
|
||||||
|
"no_warnings": True,
|
||||||
|
"noprogress": True,
|
||||||
|
}
|
||||||
|
self.client = YoutubeDL(self.params)
|
||||||
|
self.language = config.language
|
||||||
|
self.region = config.region
|
||||||
|
self.get_id_regex = re.compile(
|
||||||
|
r"https:\/\/www\.youtube\.com\/watch\?v=(?P<id>[\w-]*)"
|
||||||
|
)
|
||||||
|
|
||||||
async def searchVideo(self, query: str):
|
async def searchVideo(self, query: str):
|
||||||
videosSearch = VideosSearch(
|
videosSearch = VideosSearch(
|
||||||
query, limit=1, language=self.config.language, region=self.config.region
|
query, limit=1, language=self.language, region=self.region
|
||||||
)
|
)
|
||||||
return await videosSearch.next()
|
return await videosSearch.next()
|
||||||
|
|
||||||
async def get_data(self, url: str) -> str:
|
async def fetchData(self, url: str):
|
||||||
data = await self.loop.run_in_executor(
|
info = await self.loop.run_in_executor(
|
||||||
None, lambda: self.client.extract_info(url, download=False)
|
None, lambda: self.client.extract_info(url, download=False)
|
||||||
)
|
)
|
||||||
print(data)
|
return self.client.sanitize_info(info)
|
||||||
|
|
||||||
|
def getId(self, url: str) -> str | None:
|
||||||
|
match = self.get_id_regex.search(url)
|
||||||
|
|
||||||
|
if match is None or "id" not in match.groupdict():
|
||||||
|
return None
|
||||||
|
|
||||||
|
return match.groupdict()["id"]
|
||||||
|
|
||||||
|
def downloadProgress(self, progress, status):
|
||||||
|
if status["status"] == "downloading":
|
||||||
|
self.loop.create_task(
|
||||||
|
progress(
|
||||||
|
int(status["downloaded_bytes"]),
|
||||||
|
int(
|
||||||
|
status["total_bytes"]
|
||||||
|
if "total_bytes" in status
|
||||||
|
else status["total_bytes_estimate"]
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
async def download(self, url: str, fileName: str, progress=None) -> None:
|
||||||
|
if progress is not None:
|
||||||
|
progress_hook = lambda status: self.downloadProgress(progress, status)
|
||||||
|
self.client.add_progress_hook(progress_hook)
|
||||||
|
|
||||||
|
await self.loop.run_in_executor(None, lambda: self.client.download(url))
|
||||||
|
|
||||||
|
if progress is not None:
|
||||||
|
self.client._progress_hooks.remove(progress_hook)
|
||||||
|
|
||||||
|
return File(name=fileName, size=stat(fileName).st_size)
|
||||||
|
@ -1,6 +1,11 @@
|
|||||||
py-cord==2.4.1
|
|
||||||
PyNaCl==1.5.0
|
PyNaCl==1.5.0
|
||||||
toml==0.10.2
|
py-cord==2.4.1
|
||||||
redis==4.5.4
|
redis==4.5.4
|
||||||
|
spotipy==2.23.0
|
||||||
|
toml==0.10.2
|
||||||
youtube-search-python==1.6.6
|
youtube-search-python==1.6.6
|
||||||
yt-dlp==2023.3.4
|
yt-dlp==2023.3.4
|
||||||
|
|
||||||
|
black==23.3.0
|
||||||
|
flake8==6.0.0
|
||||||
|
isort==5.12.0
|
||||||
|
@ -1 +1,2 @@
|
|||||||
|
from .fileManager import FileManager
|
||||||
from .queueManager import QueueManager
|
from .queueManager import QueueManager
|
||||||
|
61
service/fileManager.py
Normal file
61
service/fileManager.py
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
import re
|
||||||
|
from os import path
|
||||||
|
from time import time
|
||||||
|
|
||||||
|
from discord import Interaction
|
||||||
|
|
||||||
|
from entity import Entry
|
||||||
|
from framework import Downloader, Youtube
|
||||||
|
|
||||||
|
|
||||||
|
class FileManager:
|
||||||
|
def __init__(
|
||||||
|
self, youtube: Youtube, downloader: Downloader, downloadDirectory: str
|
||||||
|
) -> None:
|
||||||
|
self.youtube = youtube
|
||||||
|
self.downloader = downloader
|
||||||
|
self.downloadDirectory = downloadDirectory
|
||||||
|
self.youtubeVideoRegex = re.compile(
|
||||||
|
r"(https:\/\/)?(www|music)\.youtube\.com\/(watch\?v=|shorts\/)\w*"
|
||||||
|
)
|
||||||
|
self.progressLastUpdate = 0
|
||||||
|
|
||||||
|
def getFileName(self, entryId: str) -> str:
|
||||||
|
return path.join(self.downloadDirectory, entryId)
|
||||||
|
|
||||||
|
async def downloadProgress(
|
||||||
|
self,
|
||||||
|
interaction: Interaction,
|
||||||
|
entry: Entry,
|
||||||
|
current_size: int,
|
||||||
|
total_size: int,
|
||||||
|
):
|
||||||
|
if time() - self.progressLastUpdate > 1:
|
||||||
|
self.progressLastUpdate = time()
|
||||||
|
await interaction.edit_original_response(
|
||||||
|
content=f"Downloading {entry.title.name} [%.2f/%.2f Mo]"
|
||||||
|
% (current_size / 1024 / 1024, total_size / 1024 / 1024)
|
||||||
|
)
|
||||||
|
|
||||||
|
async def download(self, interaction: Interaction, entries: list[Entry]):
|
||||||
|
for entry in entries:
|
||||||
|
entryId = self.youtube.getId(entry.title.url)
|
||||||
|
|
||||||
|
fileName = self.getFileName(entryId)
|
||||||
|
if not path.isfile(fileName) and isinstance(entry.source, str):
|
||||||
|
if self.youtubeVideoRegex.match(entry.source) is not None:
|
||||||
|
entry.source = await self.youtube.download(
|
||||||
|
entry.source,
|
||||||
|
fileName,
|
||||||
|
lambda current_size, total_size: self.downloadProgress(
|
||||||
|
interaction, entry, current_size, total_size
|
||||||
|
),
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
entry.source = await self.downloader.download(
|
||||||
|
entry.source,
|
||||||
|
fileName,
|
||||||
|
lambda current_size, total_size: self.downloadProgress(
|
||||||
|
interaction, entry, current_size, total_size
|
||||||
|
),
|
||||||
|
)
|
@ -11,11 +11,11 @@ class QueueManager:
|
|||||||
|
|
||||||
@asynccontextmanager
|
@asynccontextmanager
|
||||||
async def __call__(self, guildId: int) -> Queue:
|
async def __call__(self, guildId: int) -> Queue:
|
||||||
#await self.acquire(guildId)
|
# await self.acquire(guildId)
|
||||||
queue = await self.get(guildId)
|
queue = await self.get(guildId)
|
||||||
yield queue
|
yield queue
|
||||||
await self.save(guildId)
|
await self.save(guildId)
|
||||||
#await self.release(guildId)
|
# await self.release(guildId)
|
||||||
|
|
||||||
async def acquire(self, guildId: int) -> None:
|
async def acquire(self, guildId: int) -> None:
|
||||||
await self.redis.acquire(guildId)
|
await self.redis.acquire(guildId)
|
||||||
|
@ -1,11 +1,132 @@
|
|||||||
|
import json
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from framework import Youtube
|
from discord import Interaction
|
||||||
|
|
||||||
|
from entity import Album, Artist, Entry, Playlist, Title
|
||||||
|
from framework import Spotify, Youtube
|
||||||
|
from service import FileManager
|
||||||
|
|
||||||
|
|
||||||
class Sources:
|
class Sources:
|
||||||
def __init__(self, youtube: Youtube) -> None:
|
def __init__(
|
||||||
self.youtube = youtube
|
self, fileManager: FileManager, youtube: Youtube, spotify: Spotify
|
||||||
|
) -> None:
|
||||||
|
self.fileManager = fileManager
|
||||||
|
|
||||||
async def processQuery(self, query: str):
|
# youtube
|
||||||
return await self.youtube.searchVideo(query)
|
self.youtube = youtube
|
||||||
|
self.youtube_video_regex = re.compile(
|
||||||
|
r"(https:\/\/)?(www|music)\.youtube\.com\/(watch\?v=|shorts\/)\w*"
|
||||||
|
)
|
||||||
|
self.youtube_video_short_regex = re.compile(r"(https:\/\/)?youtu\.be\/\w*")
|
||||||
|
self.youtube_playlist_regex = re.compile(
|
||||||
|
r"(https:\/\/)?www\.youtube\.com\/playlist\?list=\w*"
|
||||||
|
)
|
||||||
|
|
||||||
|
# spotify
|
||||||
|
self.spotify = spotify
|
||||||
|
self.spotify_track_regex = re.compile(
|
||||||
|
r"(https:\/\/)?(open.spotify.com\/|spotify:)track(\/|:)\w*"
|
||||||
|
)
|
||||||
|
self.spotify_playlist_regex = re.compile(
|
||||||
|
r"(https:\/\/)?(open.spotify.com\/|spotify:)playlist(\/|:)\w*"
|
||||||
|
)
|
||||||
|
self.spotify_album_regex = re.compile(
|
||||||
|
r"(https:\/\/)?(open.spotify.com\/|spotify:)album(\/|:)\w*"
|
||||||
|
)
|
||||||
|
self.spotify_artist_regex = re.compile(
|
||||||
|
r"(https:\/\/)?(open.spotify.com\/|spotify:)artist(\/|:)\w*"
|
||||||
|
)
|
||||||
|
|
||||||
|
async def processQuery(
|
||||||
|
self, interaction: Interaction, query: str
|
||||||
|
) -> list[Entry] | None:
|
||||||
|
if (
|
||||||
|
self.youtube_video_regex.match(query) is not None
|
||||||
|
or self.youtube_video_short_regex.match(query) is not None
|
||||||
|
):
|
||||||
|
data = await self.youtube.fetchData(query)
|
||||||
|
if data is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
with open("search_result.json", "w") as file:
|
||||||
|
file.write(json.dumps(data))
|
||||||
|
|
||||||
|
entry = Entry(
|
||||||
|
title=Title(name=data["title"], url=data["webpage_url"]),
|
||||||
|
artist=Artist(name=data["channel"], url=data["channel_url"]),
|
||||||
|
duration=data["duration"],
|
||||||
|
thumbnail=data["thumbnail"],
|
||||||
|
requesterId=interaction.user.id,
|
||||||
|
source=data["webpage_url"],
|
||||||
|
)
|
||||||
|
|
||||||
|
await self.fileManager.download(interaction, [entry])
|
||||||
|
|
||||||
|
return [entry]
|
||||||
|
|
||||||
|
if self.youtube_playlist_regex.match(query) is not None:
|
||||||
|
data = await self.youtube.getData(query)
|
||||||
|
if data is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
with open("search_result.json", "w") as file:
|
||||||
|
file.write(json.dumps(data))
|
||||||
|
|
||||||
|
playlist = Playlist(
|
||||||
|
title=data["title"],
|
||||||
|
url=data["webpage_url"],
|
||||||
|
owner=Artist(name=data["channel"], url=data["channel_url"]),
|
||||||
|
)
|
||||||
|
|
||||||
|
return [
|
||||||
|
Entry(
|
||||||
|
title=Title(name=data["title"], url=data["webpage_url"]),
|
||||||
|
artist=Artist(name=data["channel"], url=data["channel_url"]),
|
||||||
|
duration=data["duration"],
|
||||||
|
thumbnail=data["thumbnail"],
|
||||||
|
requesterId=interaction.user.id,
|
||||||
|
playlist=playlist,
|
||||||
|
source=data["url"],
|
||||||
|
)
|
||||||
|
for data in data["entries"]
|
||||||
|
]
|
||||||
|
|
||||||
|
if self.spotify_track_regex.match(query) is not None:
|
||||||
|
data = await self.spotify.getTrack(query)
|
||||||
|
|
||||||
|
with open("spotify_track_result.json", "w") as file:
|
||||||
|
file.write(json.dumps(data))
|
||||||
|
|
||||||
|
return [
|
||||||
|
Entry(
|
||||||
|
title=Title(
|
||||||
|
name=data["name"], url=data["external_urls"]["spotify"]
|
||||||
|
),
|
||||||
|
artist=Artist(
|
||||||
|
name=data["artists"][0]["name"],
|
||||||
|
url=data["artists"][0]["external_urls"]["spotify"],
|
||||||
|
),
|
||||||
|
album=Album(
|
||||||
|
name=data["album"]["name"],
|
||||||
|
url=data["album"]["external_urls"]["spotify"],
|
||||||
|
),
|
||||||
|
duration=0,
|
||||||
|
thumbnail=data["album"]["images"][0]["url"],
|
||||||
|
requesterId=interaction.user.id,
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
if (
|
||||||
|
self.spotify_playlist_regex.match(query) is not None
|
||||||
|
or self.spotify_album_regex.match(query) is not None
|
||||||
|
or self.spotify_artist_regex.match(query) is not None
|
||||||
|
):
|
||||||
|
return
|
||||||
|
|
||||||
|
return await self.processQuery(interaction, await self.search(query))
|
||||||
|
|
||||||
|
async def search(self, query) -> str:
|
||||||
|
result = await self.youtube.searchVideo(query)
|
||||||
|
return result["result"][0]["link"]
|
||||||
|
Loading…
x
Reference in New Issue
Block a user