mirror of
https://github.com/Cog-Creators/Red-DiscordBot.git
synced 2025-11-06 03:08:55 -05:00
* More changes
Signed-off-by: Guy <guyreis96@gmail.com>
* Fixed auto play defaulting to playlist
Signed-off-by: Guy <guyreis96@gmail.com>
* Localtrack fix
Signed-off-by: Guy <guyreis96@gmail.com>
* Updated deps .. since for some reason aiosqlite is not being auto installed for everyone
Signed-off-by: Guy <guyreis96@gmail.com>
* Yupo
Signed-off-by: Guy <guyreis96@gmail.com>
* Fixed a crash in [p]now
Signed-off-by: Guy <guyreis96@gmail.com>
* Fixed crash on playlist save
Signed-off-by: Guy <guyreis96@gmail.com>
* Debugging Commit
Signed-off-by: Guy <guyreis96@gmail.com>
* Yet more prints
Signed-off-by: Guy <guyreis96@gmail.com>
* Even more spammy debug
Signed-off-by: Guy <guyreis96@gmail.com>
* Debugging commit + NEw Dispatches
Signed-off-by: Guy <guyreis96@gmail.com>
* Debugging commit + NEw Dispatches
Signed-off-by: Guy <guyreis96@gmail.com>
* Fixed localpath checks
Signed-off-by: Guy <guyreis96@gmail.com>
* more fixes for Localpaths
Signed-off-by: Guy <guyreis96@gmail.com>
* Spelling mistake on method
Signed-off-by: Guy <guyreis96@gmail.com>
* Fixed Crash on event handler
Signed-off-by: Guy <guyreis96@gmail.com>
* Fixed Crash on local search
Signed-off-by: Guy <guyreis96@gmail.com>
* Reduced fuzzy match percentage threshold for local tracks to account for nested folders
Signed-off-by: Guy <guyreis96@gmail.com>
* Fixed a crash on queue end
Signed-off-by: Guy <guyreis96@gmail.com>
* Sigh ... Removed a duplicate dispatch
Signed-off-by: Guy <guyreis96@gmail.com>
* Sigh i removed this before ...
Signed-off-by: Guy <guyreis96@gmail.com>
* Reorder dispatch signatures so all 3 new dispatch have matching signature
Signed-off-by: Guy <guyreis96@gmail.com>
* Formatting
Signed-off-by: Guy <guyreis96@gmail.com>
* Edited Error Event to support localtracks
Signed-off-by: Guy <guyreis96@gmail.com>
* Fix a Crash on track crash :awesome:
Signed-off-by: Guy <guyreis96@gmail.com>
* Yikes soo much spam
Signed-off-by: Guy <guyreis96@gmail.com>
* Remove spam and improve existance check
Signed-off-by: Guy <guyreis96@gmail.com>
* Repeat and Auto-play are mutually exclusive now
Signed-off-by: Guy <guyreis96@gmail.com>
* DEBUGS for Preda
Signed-off-by: Guy <guyreis96@gmail.com>
* Vimeo tracks can be from both these domains "vimeo.com", "beam.pro"
Signed-off-by: Guy <guyreis96@gmail.com>
* I mean Mixer can be from those 2 domains ....
Signed-off-by: Guy <guyreis96@gmail.com>
* Fixed `search sc` command
Signed-off-by: Guy <guyreis96@gmail.com>
* Run everything though lints.
rename localtracks module to dataclasses
Clear lock on errors
Signed-off-by: Draper <guyreis96@gmail.com>
* Try to speed up long playlist loading
Signed-off-by: Draper <guyreis96@gmail.com>
* Im an idiot
Signed-off-by: Draper <guyreis96@gmail.com>
* Im an idiot
Signed-off-by: Draper <guyreis96@gmail.com>
* Added logging for writes
Signed-off-by: Draper <guyreis96@gmail.com>
* Fix crash on cog reload
Signed-off-by: Draper <guyreis96@gmail.com>
* Fix for runtimewarning ?
Signed-off-by: Draper <guyreis96@gmail.com>
* Fix for Local Track cache
Signed-off-by: Draper <guyreis96@gmail.com>
* Remove broken tracks from queue on exception
Theoretically do not auto play if track stop reason is Stopped or cleanup
Signed-off-by: Draper <guyreis96@gmail.com>
* Previous commit was a fluke ... ignore it
Signed-off-by: Draper <guyreis96@gmail.com>
* Change from cleanup to Replaced
Signed-off-by: Draper <guyreis96@gmail.com>
* Fixed AttributeError: 'Track' object has no attribute 'info'.
[p]skip will only work for autoplay is there a track being played.
Fixed Console spam if query saving failed in the background while reloading bot.
Autoplay now respect [p]stop command
Signed-off-by: Guy <guyreis96@gmail.com>
* Black formatting
Fix Issue with auto play working when there is songs in the queue
Stop notifying queue ended if autoplay is on
Signed-off-by: Guy <guyreis96@gmail.com>
* Fixed a crash on track load timeout
Signed-off-by: Guy <guyreis96@gmail.com>
* [p]playlist start will now show the playlist name in embed body
Improved Logic for handling broken tracks when repeat is on.
Signed-off-by: Draper <guyreis96@gmail.com>
* Enqueue tracks as soon as we have the youtube URL ....
This basically changes how spotify urls are handled
Need to test saving spotify playlist
Need to test loading a spotify playlist from file
Need to test enqueuing a spotify playlist
Signed-off-by: Draper <guyreis96@gmail.com>
* Updated a track whrn enqueuing spotify playlist
Signed-off-by: Draper <guyreis96@gmail.com>
* Debug
Signed-off-by: Draper <guyreis96@gmail.com>
* Debug
Signed-off-by: Draper <guyreis96@gmail.com>
* Debug
Signed-off-by: Draper <guyreis96@gmail.com>
* Debug
Signed-off-by: Draper <guyreis96@gmail.com>
* Debug
Signed-off-by: Draper <guyreis96@gmail.com>
* Debug
Signed-off-by: Draper <guyreis96@gmail.com>
* Debug
Signed-off-by: Draper <guyreis96@gmail.com>
* Debug
Signed-off-by: Draper <guyreis96@gmail.com>
* Debug
Signed-off-by: Draper <guyreis96@gmail.com>
* Debug
Signed-off-by: Draper <guyreis96@gmail.com>
* Revert spotify_enqueue changes
Signed-off-by: Draper <guyreis96@gmail.com>
* Revert spotify_enqueue changes
Signed-off-by: Draper <guyreis96@gmail.com>
* Allow to set Lavalink jar version from Environment vars
Signed-off-by: Draper <guyreis96@gmail.com>
* Allow to set Lavalink jar version from Environment vars
Signed-off-by: Draper <guyreis96@gmail.com>
* Fix for a crash on Equalizer, Merge Spotify_enqueue changes and revert manager changes
Signed-off-by: Draper <guyreis96@gmail.com>
* Break playlist enqueue after 10 consecutive failures
Signed-off-by: Draper <guyreis96@gmail.com>
* Auto DC, is not compatible with Auto Play
Signed-off-by: Draper <guyreis96@gmail.com>
* Make notifier aware of guild its being called for
Signed-off-by: Draper <guyreis96@gmail.com>
* Type checking
Signed-off-by: Draper <guyreis96@gmail.com>
* Remove lock from 2 exits that i didn't before
Signed-off-by: Draper <guyreis96@gmail.com>
* Fixed TypeError: spotify_enqueue() got an unexpected keyword argument 'notify'
Signed-off-by: Guy <guyreis96@gmail.com>
* Reorder toggles to alphabetical order
Signed-off-by: Guy <guyreis96@gmail.com>
* Update Query to handle spotify URIs
Signed-off-by: Guy <guyreis96@gmail.com>
* update database
Signed-off-by: Guy <guyreis96@gmail.com>
* Dont say tracks enqued on invalid link
Make autop lay a mod only setting
Signed-off-by: Draper <guyreis96@gmail.com>
* Dont say tracks enqued on invalid spotify link
Signed-off-by: Draper <guyreis96@gmail.com>
* Set default age to 365 days
Signed-off-by: Draper <guyreis96@gmail.com>
* Allow Audio mods to set auto play playlists.
Save playlists songs to cache when migrating
Signed-off-by: Guy <guyreis96@gmail.com>
* Black formatting
Signed-off-by: Guy <guyreis96@gmail.com>
* [p]eq cooldown is not triggered is player check fails (i.e if nothing is currently playing)
Adding and removing reaction is no longer a blocking action
Signed-off-by: Guy <guyreis96@gmail.com>
* changelog for non blocking reaction handles
Signed-off-by: Guy <guyreis96@gmail.com>
* Show auto dc and auto play settings by default
Signed-off-by: Guy <guyreis96@gmail.com>
* lint is being a bitch
Signed-off-by: Guy <guyreis96@gmail.com>
* lint changes
Signed-off-by: Draper <guyreis96@gmail.com>
* stop caching local tracks
Signed-off-by: Draper <guyreis96@gmail.com>
* List of Lavalink.Tracks natively added to Playlist Objects
Signed-off-by: Draper <guyreis96@gmail.com>
* Fix UX changes and should fix autoplay
Signed-off-by: Draper <guyreis96@gmail.com>
* Fixed Skip x number of tracks
Signed-off-by: Draper <guyreis96@gmail.com>
* Lint changes
Signed-off-by: Draper <guyreis96@gmail.com>
* Remvoe dead code
Signed-off-by: Draper <guyreis96@gmail.com>
* Update playlist embed formatting to reflect Preda's suggestions
Signed-off-by: Draper <guyreis96@gmail.com>
* Update change logs
Signed-off-by: Draper <guyreis96@gmail.com>
* Add `async with ctx.typing():` to queue and to local folder
Signed-off-by: Draper <guyreis96@gmail.com>
* Stop queuing now when queue is empty with [p]queue
Signed-off-by: Draper <guyreis96@gmail.com>
* fix ctx.typing()
Signed-off-by: Draper <guyreis96@gmail.com>
* fix ctx.typing()
Signed-off-by: Draper <guyreis96@gmail.com>
* Part 1
Signed-off-by: Draper <guyreis96@gmail.com>
* Dont check local track author and name if title is Unknown
Signed-off-by: Guy <guyreis96@gmail.com>
* Makes auto play more random
Signed-off-by: Guy <guyreis96@gmail.com>
* Fixes local play
Fixed missing format
Signed-off-by: Guy <guyreis96@gmail.com>
* Query.process_input accept lavalink.Track objects
Signed-off-by: Draper <guyreis96@gmail.com>
* docstrings
Signed-off-by: Draper <guyreis96@gmail.com>
* Add TODO for timestamp support
Signed-off-by: Draper <guyreis96@gmail.com>
* Improve autoplay from cache logic (possibly slightly slower but more efficient overall)
Signed-off-by: Draper <guyreis96@gmail.com>
* Add My Lavalink PR as a dependency
Remember to remove this .... The PR will bump it to 0.3.2
Signed-off-by: Draper <guyreis96@gmail.com>
* Add My Lavalink PR as a dependency
Remember to remove this .... The PR will bump it to 0.3.2
Signed-off-by: Draper <guyreis96@gmail.com>
* Add My Lavalink PR as a dependency
Remember to remove this .... The PR will bump it to 0.3.2
Signed-off-by: Draper <guyreis96@gmail.com>
* Compile all regex at runtime
Signed-off-by: Draper <guyreis96@gmail.com>
* Fixes local play
Fixed missing format
Signed-off-by: Guy <guyreis96@gmail.com>
* Revert Dep error
Signed-off-by: Guy <guyreis96@gmail.com>
* black
Signed-off-by: Guy <guyreis96@gmail.com>
* Fixed attribute error
Signed-off-by: Guy <guyreis96@gmail.com>
* add `self.bot.dispatch("audio_disconnect", ctx.guild)` dispatch when the player is disconnected
Signed-off-by: Guy <guyreis96@gmail.com>
* Removed shuffle lock on skip
Signed-off-by: Guy <guyreis96@gmail.com>
* Better logic for auto seek (timestamps)
Signed-off-by: Guy <guyreis96@gmail.com>
* Better logic for auto seek (timestamps)
Signed-off-by: Guy <guyreis96@gmail.com>
* Fixes timestamps on spotify tracks
Signed-off-by: Guy <guyreis96@gmail.com>
* Add ctx typing to playlist enqueue
Signed-off-by: Guy <guyreis96@gmail.com>
* Fix Deps
Signed-off-by: Guy <guyreis96@gmail.com>
* Black formatting + Using new lavalink methods for shuffling
Signed-off-by: Guy <guyreis96@gmail.com>
* remove ctx.typing from playlist start
Signed-off-by: Guy <guyreis96@gmail.com>
* Fixes typerror when enqueuing spotify playlists
Signed-off-by: Guy <guyreis96@gmail.com>
* Fix keyerror
Signed-off-by: Guy <guyreis96@gmail.com>
* black formatting, + embed for [p]audioset cache as I forgot it before
Signed-off-by: Guy <guyreis96@gmail.com>
* Fix Error on playlist upload
Signed-off-by: Guy <guyreis96@gmail.com>
* Fix Text help for bump
Signed-off-by: Guy <guyreis96@gmail.com>
* Allow track bumping while shuffle is on
Signed-off-by: Guy <guyreis96@gmail.com>
* Edit bump embed to be consistent with other embed
Hyperlink tracks and removed dynamic title
Signed-off-by: Guy <guyreis96@gmail.com>
* Black
Signed-off-by: Guy <guyreis96@gmail.com>
* Errors not printing fix?
Signed-off-by: Guy <guyreis96@gmail.com>
* Errors not printing fix?
Signed-off-by: Guy <guyreis96@gmail.com>
* Track enqueued footer now shows correct track position when shuffle is on
Signed-off-by: Guy <guyreis96@gmail.com>
* Update changelogs
Signed-off-by: Guy <guyreis96@gmail.com>
* Fix is_owner check in audioset settings
Signed-off-by: Guy <guyreis96@gmail.com>
* Changelogs
Signed-off-by: Guy <guyreis96@gmail.com>
* Dont store searches with no results in cache, fix malformated playlist to cache upon settings migration
Signed-off-by: Guy <guyreis96@gmail.com>
* _clear_lock_on_error > Needs to be reviewed to see if it has been done correctly
Signed-off-by: Guy <guyreis96@gmail.com>
* _clear_lock_on_error > Needs to be reviewed to see if it has been done correctly
Signed-off-by: Guy <guyreis96@gmail.com>
* Fix Query search so that it works with absolute paths for localtracks
Signed-off-by: Guy <guyreis96@gmail.com>
* Extra error if lavalink is set to external and the query is a localtrack and nothing is found
Signed-off-by: Guy <guyreis96@gmail.com>
* Black
Signed-off-by: Guy <guyreis96@gmail.com>
* More detailed error message
Signed-off-by: Guy <guyreis96@gmail.com>
* [p]seek and [p]skip can be used by user if they are the song requester while DJ mode is enabled, if votes are disabled. , [p]queue shuffle can be used to shuffle the queue manually. and [p]queue clean self can be used to remove all songs you requested from the queue.
Signed-off-by: Guy <guyreis96@gmail.com>
* black
Signed-off-by: Guy <guyreis96@gmail.com>
* All the fixes + a `should_auto_play` dispatch for the tech savy peeps
Signed-off-by: Guy <guyreis96@gmail.com>
* Spellchecker + Pythonic changes
Signed-off-by: Guy <guyreis96@gmail.com>
* NO spam for logs
Signed-off-by: Guy <guyreis96@gmail.com>
* Pass Current voice channel to `red_audio_should_auto_play` dispatch
Signed-off-by: Guy <guyreis96@gmail.com>
* Black
Signed-off-by: Guy <guyreis96@gmail.com>
* playlist upload also updates cache in the background
Signed-off-by: Guy <guyreis96@gmail.com>
* playlist upload also updates cache in the background
Signed-off-by: Guy <guyreis96@gmail.com>
* Add scope to playlist picker
Signed-off-by: Guy <guyreis96@gmail.com>
* Delete Playlist picker message once something is selected
Signed-off-by: Guy <guyreis96@gmail.com>
* OCD Fix
Signed-off-by: Guy <guyreis96@gmail.com>
* Facepalm
Signed-off-by: Guy <guyreis96@gmail.com>
* Fix a Potential crash
Signed-off-by: Guy <guyreis96@gmail.com>
* Update my stupidity
Signed-off-by: Guy <guyreis96@gmail.com>
* Auto Pause + Skip tracks already in playlist upon playlist append + a command to remove duplicated tracks from playlist
Signed-off-by: Guy <guyreis96@gmail.com>
* Fix DJ mode when Role is deleted - Credits go to Neuro Assassin#4779
Fix an issue where auto play MAY not trigger
Signed-off-by: Guy <guyreis96@gmail.com>
* Change log to Neuro Assassin#4779 fix
Signed-off-by: Guy <guyreis96@gmail.com>
* Black
Signed-off-by: Guy <guyreis96@gmail.com>
* Dont auto pause manual pauses
Signed-off-by: Guy <guyreis96@gmail.com>
* Adds `[p]autoplay` that can be run by mods or higher
Signed-off-by: Guy <guyreis96@gmail.com>
* 🤦
Signed-off-by: Guy <guyreis96@gmail.com>
* 2x 🤦
Signed-off-by: Guy <guyreis96@gmail.com>
* Fixed wrong import
Signed-off-by: Guy <guyreis96@gmail.com>
* Added Autoplay notify
Signed-off-by: Guy <guyreis96@gmail.com>
* Added Autoplay notify
Signed-off-by: Guy <guyreis96@gmail.com>
* Black
Signed-off-by: Guy <guyreis96@gmail.com>
* Store Track object as prev song instead of URI
Signed-off-by: Guy <guyreis96@gmail.com>
* Black why do u hate me
Signed-off-by: Guy <guyreis96@gmail.com>
* Fix command name
Signed-off-by: Guy <guyreis96@gmail.com>
* Fix Autoplay notify
Signed-off-by: Guy <guyreis96@gmail.com>
* Fix missing await and TypeError, Thanks Flame
Signed-off-by: Guy <guyreis96@gmail.com>
* Add a list of tracks to show as a menu
Signed-off-by: Guy <guyreis96@gmail.com>
* adds the `[p]genre` command which uses the Spotify and Youtube API
Signed-off-by: Guy <guyreis96@gmail.com>
* Enqueue Playlists from genre command
Signed-off-by: Guy <guyreis96@gmail.com>
* Pretify `[p]genre`
Signed-off-by: Guy <guyreis96@gmail.com>
* Fix a Typo and correct jukebox charge order
Signed-off-by: Guy <guyreis96@gmail.com>
* Add genre command to error handling
Signed-off-by: Guy <guyreis96@gmail.com>
* Type checking
Signed-off-by: Guy <guyreis96@gmail.com>
* Update naming scheme for `[p]genre`
Signed-off-by: Guy <guyreis96@gmail.com>
* Black why do you hate me
Signed-off-by: Guy <guyreis96@gmail.com>
* Fixed `[p]local start`
Playlist picker auto selects if theres just 1 playlist found
`[p]queue cleanself` added
Signed-off-by: Guy <guyreis96@gmail.com>
* *sigh* back compatibility with old localtrack paths
Signed-off-by: Guy <guyreis96@gmail.com>
* *sigh* back compatibility with old localtrack paths, even more
Signed-off-by: Guy <guyreis96@gmail.com>
* *sigh* back compatibility with old localtrack paths Even more
Signed-off-by: Guy <guyreis96@gmail.com>
* Fixes localtracks in playlist info command
Signed-off-by: Guy <guyreis96@gmail.com>
* Debug Local Strings
Signed-off-by: Guy <guyreis96@gmail.com>
* Debug Local Strings
Signed-off-by: Guy <guyreis96@gmail.com>
* Fixes `[p]playlist info` for local tracks + fixed error in `[p]remove`
Signed-off-by: Guy <guyreis96@gmail.com>
* Black
Signed-off-by: Guy <guyreis96@gmail.com>
* Fixes formatting in `[p]playlist info`
Signed-off-by: Guy <guyreis96@gmail.com>
* Fix an issue with User Scope playlists were not being deleted
Signed-off-by: Guy <guyreis96@gmail.com>
* Typechecking
Signed-off-by: Guy <guyreis96@gmail.com>
* Black
Signed-off-by: Guy <guyreis96@gmail.com>
* Fix the logic of `delegate_autoplay`
Signed-off-by: Guy <guyreis96@gmail.com>
* Fix a Crash on Load due to type hinting
Signed-off-by: Guy <guyreis96@gmail.com>
* Fix a Crash on Load due to type hintingBlack + fix order of `red_audio_should_auto_play`
Signed-off-by: Guy <guyreis96@gmail.com>
* Add `red_audio_initialized` dispatch so that ownership of auto play can be maintained after a reload
Signed-off-by: Guy <guyreis96@gmail.com>
* Check if the current owner is loaded before raising an error
Signed-off-by: Guy <guyreis96@gmail.com>
* Fixes the Existence Check in `delegate_autoplay`
Signed-off-by: Guy <guyreis96@gmail.com>
* Turns `own_autoplay` in a property of Audio and improves `delegate_autoplay` Thanks Sinbad!
Signed-off-by: Guy <guyreis96@gmail.com>
* Fix for Localtracks playlists
Signed-off-by: Guy <guyreis96@gmail.com>
* When disconnecting send `Disconnecting...`
Fix Stop after a skip
Fix UX discrepancy on Playlist IDs
Fixed Exception when theres a track error
Signed-off-by: Guy <guyreis96@gmail.com>
* add `on_red_audio_unload` dispatch
Signed-off-by: guyre <27962761+drapersniper@users.noreply.github.com>
* Fix a crash on track start where `player.current` can be none?
Signed-off-by: guyre <27962761+drapersniper@users.noreply.github.com>
* Missing new line
Signed-off-by: guyre <27962761+drapersniper@users.noreply.github.com>
* Allow `--author` for playlist to be used to filter playlist for an specific author.
Plus a few bugfixes for UX
Signed-off-by: guyre <27962761+drapersniper@users.noreply.github.com>
* Rename `remdupe` to `dedupe`
Make global scope always be referenced as Global
add missing backwards quotes around the Playlist ID for 1 string
Signed-off-by: guyre <27962761+drapersniper@users.noreply.github.com>
* Towncrier entries for dep changes
Signed-off-by: guyre <27962761+drapersniper@users.noreply.github.com>
* Remove track index when shuffle is on
Fix Progress bar for livestreams
Signed-off-by: guyre <27962761+drapersniper@users.noreply.github.com>
* Trigger autoplay on `QUEUE_END` event instead of `TRACK_END`
Signed-off-by: guyre <27962761+drapersniper@users.noreply.github.com>
* Can't reproduce Ians bug but here a safeguard agaisnt it just in case
Signed-off-by: guyre <27962761+drapersniper@users.noreply.github.com>
* Fixes 2 Messages that had the wrong formatting
Signed-off-by: guyre <27962761+drapersniper@users.noreply.github.com>
* standerdize playlist naming scheme
Signed-off-by: guyre <27962761+drapersniper@users.noreply.github.com>
* Fix `[p]autoplay` message when Notify is enabled
Signed-off-by: guyre <27962761+drapersniper@users.noreply.github.com>
* y u h8 me black
Signed-off-by: guyre <27962761+drapersniper@users.noreply.github.com>
* Fix an issue with `[p]audioset localpath` where the localtracks folder was incorrect
Signed-off-by: guyre <27962761+drapersniper@users.noreply.github.com>
* Pythonic formatting
Signed-off-by: guyre <27962761+drapersniper@users.noreply.github.com>
* Ugh
Signed-off-by: guyre <27962761+drapersniper@users.noreply.github.com>
* Fix a typo
Signed-off-by: guyre <27962761+drapersniper@users.noreply.github.com>
* Silently try to delete messages + fixes error Ian found with `[p]genre`
Signed-off-by: guyre <27962761+drapersniper@users.noreply.github.com>
* sigh black
Signed-off-by: guyre <27962761+drapersniper@users.noreply.github.com>
* Add humanize_number usage correctly
Signed-off-by: guyre <27962761+drapersniper@users.noreply.github.com>
* Bump RLL to 0.4.0
Signed-off-by: guyre <27962761+drapersniper@users.noreply.github.com>
* Update changelog entries
Signed-off-by: guyre <27962761+drapersniper@users.noreply.github.com>
* Change `bot.db` to new API's added by #2967
Signed-off-by: guyre <27962761+drapersniper@users.noreply.github.com>
* Additional reformatting
Signed-off-by: guyre <27962761+drapersniper@users.noreply.github.com>
* Remove PyCharm noise + Fixes a few Pycharm warnings
Signed-off-by: guyre <27962761+drapersniper@users.noreply.github.com>
* Rework `index` parsing for youtube urls
Signed-off-by: guyre <27962761+drapersniper@users.noreply.github.com>
* Addess Aika's review
Signed-off-by: guyre <27962761+drapersniper@users.noreply.github.com>
* Fix a potential crash, saves guild ID to playlists to avoid an scheme change in the future
Signed-off-by: guyre <27962761+drapersniper@users.noreply.github.com>
* Add handling for Python installs without sqlite3.
Signed-off-by: guyre <27962761+drapersniper@users.noreply.github.com>
* Address Flame's review
Signed-off-by: guyre <27962761+drapersniper@users.noreply.github.com>
* Fix ma stupidity
Signed-off-by: guyre <27962761+drapersniper@users.noreply.github.com>
* Address Aika's latest review.
1. Update docstring for `[p]playlist rename`.
2. Fix punctuation for playlist matching.
3. `[p]playlist update` now respect playlist management perms
4. Playlist management errors now shows playlist name, id and scope where possible
5. Remove duplicated code and dead code.
Signed-off-by: guyre <27962761+drapersniper@users.noreply.github.com>
* Pluralize string
Signed-off-by: guyre <27962761+drapersniper@users.noreply.github.com>
487 lines
18 KiB
Python
487 lines
18 KiB
Python
import os
|
|
import re
|
|
from pathlib import Path, PosixPath, WindowsPath
|
|
from typing import List, Optional, Union
|
|
from urllib.parse import urlparse
|
|
|
|
import lavalink
|
|
|
|
from redbot.core import Config
|
|
from redbot.core.bot import Red
|
|
from redbot.core.i18n import Translator
|
|
|
|
_config: Optional[Config] = None
|
|
_bot: Optional[Red] = None
|
|
_localtrack_folder: Optional[str] = None
|
|
_ = Translator("Audio", __file__)
|
|
_remove_start = re.compile(r"^(sc|list) ")
|
|
_re_youtube_timestamp = re.compile(r"&t=(\d+)s?")
|
|
_re_youtube_index = re.compile(r"&index=(\d+)")
|
|
_re_spotify_url = re.compile(r"(http[s]?://)?(open.spotify.com)/")
|
|
_re_spotify_timestamp = re.compile(r"#(\d+):(\d+)")
|
|
_re_soundcloud_timestamp = re.compile(r"#t=(\d+):(\d+)s?")
|
|
_re_twitch_timestamp = re.compile(r"\?t=(\d+)h(\d+)m(\d+)s")
|
|
|
|
|
|
def _pass_config_to_dataclasses(config: Config, bot: Red, folder: str):
|
|
global _config, _bot, _localtrack_folder
|
|
if _config is None:
|
|
_config = config
|
|
if _bot is None:
|
|
_bot = bot
|
|
_localtrack_folder = folder
|
|
|
|
|
|
class ChdirClean(object):
|
|
def __init__(self, directory):
|
|
self.old_dir = os.getcwd()
|
|
self.new_dir = directory
|
|
self.cwd = None
|
|
|
|
def __enter__(self):
|
|
return self
|
|
|
|
def __exit__(self, _type, value, traceback):
|
|
self.chdir_out()
|
|
return isinstance(value, OSError)
|
|
|
|
def chdir_in(self):
|
|
self.cwd = Path(self.new_dir)
|
|
os.chdir(self.new_dir)
|
|
|
|
def chdir_out(self):
|
|
self.cwd = Path(self.old_dir)
|
|
os.chdir(self.old_dir)
|
|
|
|
|
|
class LocalPath(ChdirClean):
|
|
"""
|
|
Local tracks class.
|
|
Used to handle system dir trees in a cross system manner.
|
|
The only use of this class is for `localtracks`.
|
|
"""
|
|
|
|
_supported_music_ext = (".mp3", ".flac", ".ogg")
|
|
|
|
def __init__(self, path, **kwargs):
|
|
self._path = path
|
|
if isinstance(path, (Path, WindowsPath, PosixPath, LocalPath)):
|
|
path = str(path.absolute())
|
|
elif path is not None:
|
|
path = str(path)
|
|
|
|
self.cwd = Path.cwd()
|
|
_lt_folder = Path(_localtrack_folder) if _localtrack_folder else self.cwd
|
|
_path = Path(path) if path else self.cwd
|
|
|
|
if _lt_folder.parts[-1].lower() == "localtracks" and not kwargs.get("forced"):
|
|
self.localtrack_folder = _lt_folder
|
|
elif kwargs.get("forced"):
|
|
if _path.parts[-1].lower() == "localtracks":
|
|
self.localtrack_folder = _path
|
|
else:
|
|
self.localtrack_folder = _path / "localtracks"
|
|
else:
|
|
self.localtrack_folder = _lt_folder / "localtracks"
|
|
|
|
try:
|
|
_path = Path(path)
|
|
_path.relative_to(self.localtrack_folder)
|
|
self.path = _path
|
|
except (ValueError, TypeError):
|
|
if path and path.startswith("localtracks//"):
|
|
path = path.replace("localtracks//", "", 1)
|
|
elif path and path.startswith("localtracks/"):
|
|
path = path.replace("localtracks/", "", 1)
|
|
self.path = self.localtrack_folder.joinpath(path) if path else self.localtrack_folder
|
|
|
|
try:
|
|
if self.path.is_file():
|
|
parent = self.path.parent
|
|
else:
|
|
parent = self.path
|
|
super().__init__(str(parent.absolute()))
|
|
|
|
self.parent = Path(parent)
|
|
except OSError:
|
|
self.parent = None
|
|
|
|
self.cwd = Path.cwd()
|
|
|
|
@property
|
|
def name(self):
|
|
return str(self.path.name)
|
|
|
|
def is_dir(self):
|
|
try:
|
|
return self.path.is_dir()
|
|
except OSError:
|
|
return False
|
|
|
|
def exists(self):
|
|
try:
|
|
return self.path.exists()
|
|
except OSError:
|
|
return False
|
|
|
|
def is_file(self):
|
|
try:
|
|
return self.path.is_file()
|
|
except OSError:
|
|
return False
|
|
|
|
def absolute(self):
|
|
try:
|
|
return self.path.absolute()
|
|
except OSError:
|
|
return self._path
|
|
|
|
@classmethod
|
|
def joinpath(cls, *args):
|
|
modified = cls(None)
|
|
modified.path = modified.path.joinpath(*args)
|
|
return modified
|
|
|
|
def multiglob(self, *patterns):
|
|
paths = []
|
|
for p in patterns:
|
|
paths.extend(list(self.path.glob(p)))
|
|
for p in self._filtered(paths):
|
|
yield p
|
|
|
|
def multirglob(self, *patterns):
|
|
paths = []
|
|
for p in patterns:
|
|
paths.extend(list(self.path.rglob(p)))
|
|
|
|
for p in self._filtered(paths):
|
|
yield p
|
|
|
|
def _filtered(self, paths: List[Path]):
|
|
for p in paths:
|
|
if p.suffix in self._supported_music_ext:
|
|
yield p
|
|
|
|
def __str__(self):
|
|
return str(self.path.absolute())
|
|
|
|
def to_string(self):
|
|
try:
|
|
return str(self.path.absolute())
|
|
except OSError:
|
|
return str(self._path)
|
|
|
|
def to_string_hidden(self, arg: str = None):
|
|
string = str(self.absolute()).replace(
|
|
(str(self.localtrack_folder.absolute()) + os.sep) if arg is None else arg, ""
|
|
)
|
|
chunked = False
|
|
while len(string) > 145 and os.sep in string:
|
|
string = string.split(os.sep, 1)[-1]
|
|
chunked = True
|
|
|
|
if chunked:
|
|
string = f"...{os.sep}{string}"
|
|
return string
|
|
|
|
def tracks_in_tree(self):
|
|
tracks = []
|
|
for track in self.multirglob(*[f"*{ext}" for ext in self._supported_music_ext]):
|
|
if track.exists() and track.is_file() and track.parent != self.localtrack_folder:
|
|
tracks.append(Query.process_input(LocalPath(str(track.absolute()))))
|
|
return tracks
|
|
|
|
def subfolders_in_tree(self):
|
|
files = list(self.multirglob(*[f"*{ext}" for ext in self._supported_music_ext]))
|
|
folders = []
|
|
for f in files:
|
|
if f.exists() and f.parent not in folders and f.parent != self.localtrack_folder:
|
|
folders.append(f.parent)
|
|
return_folders = []
|
|
for folder in folders:
|
|
if folder.exists() and folder.is_dir():
|
|
return_folders.append(LocalPath(str(folder.absolute())))
|
|
return return_folders
|
|
|
|
def tracks_in_folder(self):
|
|
tracks = []
|
|
for track in self.multiglob(*[f"*{ext}" for ext in self._supported_music_ext]):
|
|
if track.exists() and track.is_file() and track.parent != self.localtrack_folder:
|
|
tracks.append(Query.process_input(LocalPath(str(track.absolute()))))
|
|
return tracks
|
|
|
|
def subfolders(self):
|
|
files = list(self.multiglob(*[f"*{ext}" for ext in self._supported_music_ext]))
|
|
folders = []
|
|
for f in files:
|
|
if f.exists() and f.parent not in folders and f.parent != self.localtrack_folder:
|
|
folders.append(f.parent)
|
|
return_folders = []
|
|
for folder in folders:
|
|
if folder.exists() and folder.is_dir():
|
|
return_folders.append(LocalPath(str(folder.absolute())))
|
|
return return_folders
|
|
|
|
|
|
class Query:
|
|
"""
|
|
Query data class.
|
|
Use: Query.process_input(query) to generate the Query object.
|
|
"""
|
|
|
|
def __init__(self, query: Union[LocalPath, str], **kwargs):
|
|
query = kwargs.get("queryforced", query)
|
|
self._raw: Union[LocalPath, str] = query
|
|
|
|
_localtrack: LocalPath = LocalPath(query)
|
|
|
|
self.track: Union[LocalPath, str] = _localtrack if (
|
|
(_localtrack.is_file() or _localtrack.is_dir()) and _localtrack.exists()
|
|
) else query
|
|
|
|
self.valid: bool = query != "InvalidQueryPlaceHolderName"
|
|
self.is_local: bool = kwargs.get("local", False)
|
|
self.is_spotify: bool = kwargs.get("spotify", False)
|
|
self.is_youtube: bool = kwargs.get("youtube", False)
|
|
self.is_soundcloud: bool = kwargs.get("soundcloud", False)
|
|
self.is_bandcamp: bool = kwargs.get("bandcamp", False)
|
|
self.is_vimeo: bool = kwargs.get("vimeo", False)
|
|
self.is_mixer: bool = kwargs.get("mixer", False)
|
|
self.is_twitch: bool = kwargs.get("twitch", False)
|
|
self.is_other: bool = kwargs.get("other", False)
|
|
self.is_playlist: bool = kwargs.get("playlist", False)
|
|
self.is_album: bool = kwargs.get("album", False)
|
|
self.is_search: bool = kwargs.get("search", False)
|
|
self.is_stream: bool = kwargs.get("stream", False)
|
|
self.single_track: bool = kwargs.get("single", False)
|
|
self.id: Optional[str] = kwargs.get("id", None)
|
|
self.invoked_from: Optional[str] = kwargs.get("invoked_from", None)
|
|
self.local_name: Optional[str] = kwargs.get("name", None)
|
|
self.search_subfolders: bool = kwargs.get("search_subfolders", False)
|
|
self.spotify_uri: Optional[str] = kwargs.get("uri", None)
|
|
|
|
self.start_time: int = kwargs.get("start_time", 0)
|
|
self.track_index: Optional[int] = kwargs.get("track_index", None)
|
|
|
|
if self.invoked_from == "sc search":
|
|
self.is_youtube = False
|
|
self.is_soundcloud = True
|
|
|
|
self.lavalink_query: str = self._get_query()
|
|
|
|
if self.is_playlist or self.is_album:
|
|
self.single_track = False
|
|
|
|
def __str__(self):
|
|
return str(self.lavalink_query)
|
|
|
|
@classmethod
|
|
def process_input(cls, query: Union[LocalPath, lavalink.Track, "Query", str], **kwargs):
|
|
"""
|
|
A replacement for :code:`lavalink.Player.load_tracks`.
|
|
This will try to get a valid cached entry first if not found or if in valid
|
|
it will then call the lavalink API.
|
|
|
|
Parameters
|
|
----------
|
|
query : Union[Query, LocalPath, lavalink.Track, str]
|
|
The query string or LocalPath object.
|
|
Returns
|
|
-------
|
|
Query
|
|
Returns a parsed Query object.
|
|
"""
|
|
if not query:
|
|
query = "InvalidQueryPlaceHolderName"
|
|
possible_values = dict()
|
|
|
|
if isinstance(query, str):
|
|
query = query.strip("<>")
|
|
|
|
elif isinstance(query, Query):
|
|
for key, val in kwargs.items():
|
|
setattr(query, key, val)
|
|
return query
|
|
elif isinstance(query, lavalink.Track):
|
|
possible_values["stream"] = query.is_stream
|
|
query = query.uri
|
|
|
|
possible_values.update(dict(**kwargs))
|
|
possible_values.update(cls._parse(query, **kwargs))
|
|
return cls(query, **possible_values)
|
|
|
|
@staticmethod
|
|
def _parse(track, **kwargs):
|
|
returning = {}
|
|
if (
|
|
type(track) == type(LocalPath)
|
|
and (track.is_file() or track.is_dir())
|
|
and track.exists()
|
|
):
|
|
returning["local"] = True
|
|
returning["name"] = track.name
|
|
if track.is_file():
|
|
returning["single"] = True
|
|
elif track.is_dir():
|
|
returning["album"] = True
|
|
else:
|
|
track = str(track)
|
|
if track.startswith("spotify:"):
|
|
returning["spotify"] = True
|
|
if ":playlist:" in track:
|
|
returning["playlist"] = True
|
|
elif ":album:" in track:
|
|
returning["album"] = True
|
|
elif ":track:" in track:
|
|
returning["single"] = True
|
|
_id = track.split(":", 2)[-1]
|
|
_id = _id.split("?")[0]
|
|
returning["id"] = _id
|
|
if "#" in _id:
|
|
match = re.search(_re_spotify_timestamp, track)
|
|
if match:
|
|
returning["start_time"] = (int(match.group(1)) * 60) + int(match.group(2))
|
|
returning["uri"] = track
|
|
return returning
|
|
if track.startswith("sc ") or track.startswith("list "):
|
|
if track.startswith("sc "):
|
|
returning["invoked_from"] = "sc search"
|
|
returning["soundcloud"] = True
|
|
elif track.startswith("list "):
|
|
returning["invoked_from"] = "search list"
|
|
track = _remove_start.sub("", track, 1)
|
|
returning["queryforced"] = track
|
|
|
|
_localtrack = LocalPath(track)
|
|
if _localtrack.exists():
|
|
if _localtrack.is_file():
|
|
returning["local"] = True
|
|
returning["single"] = True
|
|
returning["name"] = _localtrack.name
|
|
return returning
|
|
elif _localtrack.is_dir():
|
|
returning["album"] = True
|
|
returning["local"] = True
|
|
returning["name"] = _localtrack.name
|
|
return returning
|
|
try:
|
|
query_url = urlparse(track)
|
|
if all([query_url.scheme, query_url.netloc, query_url.path]):
|
|
url_domain = ".".join(query_url.netloc.split(".")[-2:])
|
|
if not query_url.netloc:
|
|
url_domain = ".".join(query_url.path.split("/")[0].split(".")[-2:])
|
|
if url_domain in ["youtube.com", "youtu.be"]:
|
|
returning["youtube"] = True
|
|
_has_index = "&index=" in track
|
|
if "&t=" in track:
|
|
match = re.search(_re_youtube_timestamp, track)
|
|
if match:
|
|
returning["start_time"] = int(match.group(1))
|
|
if _has_index:
|
|
match = re.search(_re_youtube_index, track)
|
|
if match:
|
|
returning["track_index"] = int(match.group(1)) - 1
|
|
|
|
if all(k in track for k in ["&list=", "watch?"]):
|
|
returning["track_index"] = 0
|
|
returning["playlist"] = True
|
|
returning["single"] = False
|
|
elif all(x in track for x in ["playlist?"]):
|
|
returning["playlist"] = True if not _has_index else False
|
|
returning["single"] = True if _has_index else False
|
|
else:
|
|
returning["single"] = True
|
|
elif url_domain == "spotify.com":
|
|
returning["spotify"] = True
|
|
if "/playlist/" in track:
|
|
returning["playlist"] = True
|
|
elif "/album/" in track:
|
|
returning["album"] = True
|
|
elif "/track/" in track:
|
|
returning["single"] = True
|
|
val = re.sub(_re_spotify_url, "", track).replace("/", ":")
|
|
if "user:" in val:
|
|
val = val.split(":", 2)[-1]
|
|
_id = val.split(":", 1)[-1]
|
|
_id = _id.split("?")[0]
|
|
|
|
if "#" in _id:
|
|
_id = _id.split("#")[0]
|
|
match = re.search(_re_spotify_timestamp, track)
|
|
if match:
|
|
returning["start_time"] = (int(match.group(1)) * 60) + int(
|
|
match.group(2)
|
|
)
|
|
|
|
returning["id"] = _id
|
|
returning["uri"] = f"spotify:{val}"
|
|
elif url_domain == "soundcloud.com":
|
|
returning["soundcloud"] = True
|
|
if "#t=" in track:
|
|
match = re.search(_re_soundcloud_timestamp, track)
|
|
if match:
|
|
returning["start_time"] = (int(match.group(1)) * 60) + int(
|
|
match.group(2)
|
|
)
|
|
if "/sets/" in track:
|
|
if "?in=" in track:
|
|
returning["single"] = True
|
|
else:
|
|
returning["playlist"] = True
|
|
else:
|
|
returning["single"] = True
|
|
elif url_domain == "bandcamp.com":
|
|
returning["bandcamp"] = True
|
|
if "/album/" in track:
|
|
returning["album"] = True
|
|
else:
|
|
returning["single"] = True
|
|
elif url_domain == "vimeo.com":
|
|
returning["vimeo"] = True
|
|
elif url_domain in ["mixer.com", "beam.pro"]:
|
|
returning["mixer"] = True
|
|
elif url_domain == "twitch.tv":
|
|
returning["twitch"] = True
|
|
if "?t=" in track:
|
|
match = re.search(_re_twitch_timestamp, track)
|
|
if match:
|
|
returning["start_time"] = (
|
|
(int(match.group(1)) * 60 * 60)
|
|
+ (int(match.group(2)) * 60)
|
|
+ int(match.group(3))
|
|
)
|
|
|
|
if not any(x in track for x in ["/clip/", "/videos/"]):
|
|
returning["stream"] = True
|
|
else:
|
|
returning["other"] = True
|
|
returning["single"] = True
|
|
else:
|
|
if kwargs.get("soundcloud", False):
|
|
returning["soundcloud"] = True
|
|
else:
|
|
returning["youtube"] = True
|
|
returning["search"] = True
|
|
returning["single"] = True
|
|
except Exception:
|
|
returning["search"] = True
|
|
returning["youtube"] = True
|
|
returning["single"] = True
|
|
return returning
|
|
|
|
def _get_query(self):
|
|
if self.is_local:
|
|
return self.track.to_string()
|
|
elif self.is_spotify:
|
|
return self.spotify_uri
|
|
elif self.is_search and self.is_youtube:
|
|
return f"ytsearch:{self.track}"
|
|
elif self.is_search and self.is_soundcloud:
|
|
return f"scsearch:{self.track}"
|
|
return self.track
|
|
|
|
def to_string_user(self):
|
|
if self.is_local:
|
|
return str(self.track.to_string_hidden())
|
|
return str(self._raw)
|