from __future__ import annotations
import random
from pathlib import Path
from typing import TYPE_CHECKING
import discord
from dacite import from_dict
from redbot.core.i18n import Translator
from redbot.core.utils.chat_formatting import humanize_number
from redbot.vendored.discord.ext import menus
from pylav.extension.red.ui.selectors.options.playlist import PlaylistOption
from pylav.logging import getLogger
from pylav.nodes.api.responses.track import Track as LavalinkTrack
from pylav.players.tracks.obj import Track
from pylav.storage.models.playlist import Playlist
from pylav.type_hints.bot import DISCORD_COG_TYPE
from pylav.type_hints.dict_typing import JSON_DICT_TYPE
if TYPE_CHECKING:
from pylav.extension.red.ui.menus.generic import PaginatingMenu
from pylav.extension.red.ui.menus.playlist import PlaylistPickerMenu
LOGGER = getLogger("PyLav.ext.red.ui.sources.playlist")
_ = Translator("PyLav", Path(__file__))
INF = float("inf")
ASCII_ORDER_SORT = "~" * 100
[docs]
class PlaylistPickerSource(menus.ListPageSource):
def __init__(self, guild_id: int, cog: DISCORD_COG_TYPE, pages: list[Playlist], message_str: str):
pages.sort(key=lambda p: p.id)
super().__init__(entries=pages, per_page=5)
self.message_str = message_str
self.per_page = 5
self.guild_id = guild_id
self.select_options: list[PlaylistOption] = []
self.cog = cog
self.select_mapping: dict[str, Playlist] = {}
[docs]
def get_starting_index_and_page_number(self, menu: PlaylistPickerMenu) -> tuple[int, int]:
page_num = menu.current_page
start = page_num * self.per_page
return start, page_num
[docs]
async def format_page(self, menu: PlaylistPickerMenu, playlists: list[Playlist]) -> discord.Embed | str:
idx_start, page_num = self.get_starting_index_and_page_number(menu)
page = await self.cog.pylav.construct_embed(messageable=menu.ctx, title=self.message_str)
total_number_of_entries = len(self.entries)
current_page = humanize_number(page_num + 1)
total_number_of_pages = humanize_number(self.get_max_pages())
match total_number_of_entries:
case 1:
message = _("Page 1 / 1 | 1 playlist")
case 0:
message = _("Page 1 / 1 | 0 playlists")
case __:
message = _(
"Page {current_page_variable_do_not_translate} / {total_number_of_pages_variable_do_not_translate} | {total_number_of_entries_variable_do_not_translate} playlists"
).format(
current_page_variable_do_not_translate=current_page,
total_number_of_pages_variable_do_not_translate=total_number_of_pages,
total_number_of_entries_variable_do_not_translate=humanize_number(total_number_of_entries),
)
page.set_footer(text=message)
return page
[docs]
async def get_page(self, page_number):
if page_number > self.get_max_pages():
page_number = 0
base = page_number * self.per_page
self.select_options.clear()
self.select_mapping.clear()
for i, playlist in enumerate(iter(self.entries[base : base + self.per_page]), start=base): # noqa: E203
self.select_options.append(await PlaylistOption.from_playlist(playlist=playlist, index=i, bot=self.cog.bot))
self.select_mapping[f"{playlist.id}"] = playlist
return self.entries[base : base + self.per_page] # noqa: E203
[docs]
def get_max_pages(self):
""":class:`int`: The maximum number of pages required to paginate this sequence"""
return self._max_pages or 1
[docs]
class TrackMappingSource(menus.ListPageSource):
def __init__(
self,
guild_id: int,
cog: DISCORD_COG_TYPE,
playlist: Playlist,
author: discord.abc.User,
entries: list[str],
per_page: int = 10,
):
super().__init__(entries=entries, per_page=per_page)
self.cog = cog
self.author = author
self.guild_id = guild_id
self.playlist = playlist
[docs]
def is_paginating(self) -> bool:
return True
[docs]
def get_starting_index_and_page_number(self, menu: PaginatingMenu) -> tuple[int, int]:
page_num = menu.current_page
start = page_num * self.per_page
return start, page_num
[docs]
async def format_page(self, menu: PaginatingMenu, tracks: list[str | JSON_DICT_TYPE]) -> discord.Embed:
start_index, page_num = self.get_starting_index_and_page_number(menu)
padding = len(str(start_index + len(tracks)))
queue_list = ""
player = self.cog.pylav.get_player(self.guild_id)
for track_idx, track in enumerate(tracks, start=start_index + 1):
if isinstance(track, str):
track = await Track.build_track(
node=random.choice(self.cog.pylav.node_manager.nodes),
requester=self.author.id,
data=track,
query=None,
player_instance=player,
)
else:
track = await Track.build_track(
node=random.choice(self.cog.pylav.node_manager.nodes),
requester=self.author.id,
data=from_dict(data_class=LavalinkTrack, data=track),
query=None,
player_instance=player,
)
track_description = await track.get_track_display_name(max_length=50, with_url=True)
diff = padding - len(str(track_idx))
queue_list += f"`{track_idx}.{' ' * diff}` {track_description}\n"
page = await self.cog.pylav.construct_embed(
title="{translation} __{name}__".format(
name=await self.playlist.fetch_name(), translation=discord.utils.escape_markdown(_("Tracks in"))
),
description=queue_list,
messageable=menu.ctx,
)
total_number_of_entries = len(self.entries)
current_page = humanize_number(page_num + 1)
total_number_of_pages = humanize_number(self.get_max_pages())
match total_number_of_entries:
case 1:
message = _("Page 1 / 1 | 1 track")
case 0:
message = _("Page 1 / 1 | 0 tracks")
case __:
message = _(
"Page {current_page_variable_do_not_translate} / {total_number_of_pages_variable_do_not_translate} | {total_number_of_entries_variable_do_not_translate} tracks"
).format(
current_page_variable_do_not_translate=current_page,
total_number_of_pages_variable_do_not_translate=total_number_of_pages,
total_number_of_entries_variable_do_not_translate=humanize_number(total_number_of_entries),
)
page.set_footer(text=message)
return page
[docs]
def get_max_pages(self):
""":class:`int`: The maximum number of pages required to paginate this sequence"""
return self._max_pages or 1
[docs]
class PlaylistListSource(menus.ListPageSource):
def __init__(self, cog: DISCORD_COG_TYPE, pages: list[Playlist]):
pages.sort(key=lambda p: p.id)
super().__init__(entries=pages, per_page=5)
self.cog = cog
[docs]
def get_starting_index_and_page_number(self, menu: PaginatingMenu) -> tuple[int, int]:
page_num = menu.current_page
start = page_num * self.per_page
return start, page_num
[docs]
async def format_page(self, menu: PaginatingMenu, playlists: list[Playlist]) -> discord.Embed | str:
idx_start, page_num = self.get_starting_index_and_page_number(menu)
plist = ""
space = "\N{EN SPACE}"
for i, playlist in enumerate(playlists, start=idx_start + 1):
scope_name = await playlist.get_scope_name(self.cog.bot)
author_name = await playlist.get_author_name(self.cog.bot) or _("Unknown")
is_same = scope_name == author_name
playlist_info = ("\n" + space * 4).join(
(
await playlist.get_name_formatted(with_url=True),
_("Identifier: {id_variable_do_not_translate}").format(id_variable_do_not_translate=playlist.id),
_("Tracks: {num_variable_do_not_translate}").format(
num_variable_do_not_translate=await playlist.size()
),
_("Author: {name_variable_do_not_translate}").format(name_variable_do_not_translate=author_name),
"\n"
if is_same
else _("Scope: {scope_variable_do_not_translate}\n").format(
scope_variable_do_not_translate=scope_name
),
)
)
plist += f"`{i}.` {playlist_info}"
embed = await self.cog.pylav.construct_embed(
messageable=menu.ctx,
title=_("Playlists you can access in this server:"),
description=plist,
)
total_number_of_entries = len(self.entries)
current_page = humanize_number(page_num + 1)
total_number_of_pages = humanize_number(self.get_max_pages())
match total_number_of_entries:
case 1:
message = _("Page 1 / 1 | 1 playlist")
case 0:
message = _("Page 1 / 1 | 0 playlists")
case __:
message = _(
"Page {current_page_variable_do_not_translate} / {total_number_of_pages_variable_do_not_translate} | {total_number_of_entries_variable_do_not_translate} playlists"
).format(
current_page_variable_do_not_translate=current_page,
total_number_of_pages_variable_do_not_translate=total_number_of_pages,
total_number_of_entries_variable_do_not_translate=humanize_number(total_number_of_entries),
)
embed.set_footer(text=message)
return embed
[docs]
def get_max_pages(self):
""":class:`int`: The maximum number of pages required to paginate this sequence"""
return self._max_pages or 1