Compare commits
186 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
9bffdebe25 | |
|
|
720ba187ab | |
|
|
855bf4e0d7 | |
|
|
cd579becad | |
|
|
3a1ecf9eec | |
|
|
5227962228 | |
|
|
be7106616b | |
|
|
ba9534bc27 | |
|
|
2e0f5b365a | |
|
|
851f00aa97 | |
|
|
817295d321 | |
|
|
8ab3ae9ccd | |
|
|
094f2be181 | |
|
|
b60a6aec18 | |
|
|
80f7b77cd3 | |
|
|
8679d6d125 | |
|
|
ad01407fff | |
|
|
f1609f7049 | |
|
|
5fcfc73901 | |
|
|
86b35106b2 | |
|
|
519a14fbde | |
|
|
9a42093f64 | |
|
|
347a6e0b96 | |
|
|
83d5add134 | |
|
|
bb12e33584 | |
|
|
6817cd8e07 | |
|
|
179472bd6e | |
|
|
ba761743b9 | |
|
|
b3795102b8 | |
|
|
2a492c793f | |
|
|
705ac9feab | |
|
|
a926616028 | |
|
|
4507b50b8b | |
|
|
bd78f47585 | |
|
|
9b18759864 | |
|
|
001b801a15 | |
|
|
db1c66dd40 | |
|
|
341164a0d2 | |
|
|
7829086ae3 | |
|
|
f9cb48c48f | |
|
|
3401b669e8 | |
|
|
d7a7efb051 | |
|
|
0904196979 | |
|
|
7617ecf2d1 | |
|
|
1acc594467 | |
|
|
e48c31b7a9 | |
|
|
f3c5461854 | |
|
|
aa826c7da2 | |
|
|
bc71088092 | |
|
|
7eca4724da | |
|
|
b098b681be | |
|
|
c52a379b87 | |
|
|
50b5eab860 | |
|
|
18fed3a089 | |
|
|
ab432cc8e6 | |
|
|
223be29384 | |
|
|
c5f8ded0b1 | |
|
|
0b1d36cf64 | |
|
|
1f20ebf6c6 | |
|
|
af5418c958 | |
|
|
6670da76e8 | |
|
|
69d3bc9ce1 | |
|
|
e3fe1b52b2 | |
|
|
02d22f20b5 | |
|
|
cbb676e004 | |
|
|
2d8acf7800 | |
|
|
481b2079ed | |
|
|
952a3eff14 | |
|
|
4fc9bd8810 | |
|
|
28db38a00e | |
|
|
00ac166371 | |
|
|
dd3d43e702 | |
|
|
334d74095e | |
|
|
b461b91587 | |
|
|
56843c459c | |
|
|
d5cf16ac63 | |
|
|
394e3a3907 | |
|
|
380266f2c3 | |
|
|
4e720e3dc9 | |
|
|
b91f6ec04e | |
|
|
248cce6656 | |
|
|
d23fe6b8a4 | |
|
|
665d6c13a3 | |
|
|
f823786029 | |
|
|
e69349bca8 | |
|
|
5445661f42 | |
|
|
b75d2f580c | |
|
|
bb835fb173 | |
|
|
b0ef03d2d1 | |
|
|
f9bf268c89 | |
|
|
dfc516f8bd | |
|
|
00370cfbc7 | |
|
|
6ba2ea1d6d | |
|
|
2cab4cb7d0 | |
|
|
77a7246b6a | |
|
|
2461cbb831 | |
|
|
42d886554e | |
|
|
e7c627dcd2 | |
|
|
5c71e9a562 | |
|
|
ab374d4ba8 | |
|
|
02b62d493f | |
|
|
4caaff8b04 | |
|
|
74256dc5ac | |
|
|
bf144a783c | |
|
|
0b0b50f259 | |
|
|
cf3834d5c2 | |
|
|
3a8e622f89 | |
|
|
2ddbb5d91a | |
|
|
b73af37bbf | |
|
|
6ed2fd961b | |
|
|
c88f020280 | |
|
|
206adbd70b | |
|
|
14ba273d35 | |
|
|
6d96a9e53d | |
|
|
45d3e611a5 | |
|
|
9c262c7455 | |
|
|
a8a586bfb1 | |
|
|
b0e0bba27b | |
|
|
0d78b00342 | |
|
|
367a215b05 | |
|
|
dde6e3711c | |
|
|
b561f272a5 | |
|
|
b1ec08026d | |
|
|
5676d35681 | |
|
|
3ea426b44b | |
|
|
a7da475c0c | |
|
|
52a45afd70 | |
|
|
c2b952438a | |
|
|
c35fd650f7 | |
|
|
7b82d6c81a | |
|
|
8ee1a39cb5 | |
|
|
5b036d843a | |
|
|
cc571f17f2 | |
|
|
6c7f06ec25 | |
|
|
5aea4bcaf2 | |
|
|
e8d29f9263 | |
|
|
9f102ae3b8 | |
|
|
4f86e44fec | |
|
|
8860df99de | |
|
|
3949c5b1a6 | |
|
|
4ce4c6205f | |
|
|
39bde67d06 | |
|
|
e30ec92c34 | |
|
|
987de07fc5 | |
|
|
481e616414 | |
|
|
a4a49c249e | |
|
|
834e5bde62 | |
|
|
19c06301e7 | |
|
|
2ded9d6205 | |
|
|
9e0a5e0ad0 | |
|
|
9dc8a9098e | |
|
|
145634ce79 | |
|
|
4564e89b4e | |
|
|
c5ca63b014 | |
|
|
458b686769 | |
|
|
c58786ed3f | |
|
|
d9137f6b29 | |
|
|
7b91e717ac | |
|
|
9f30e90da2 | |
|
|
4c3cd7e81a | |
|
|
f0726cddde | |
|
|
fa8a444bf6 | |
|
|
5c6ae99e80 | |
|
|
739e926d09 | |
|
|
c9a331b278 | |
|
|
31d4e1aca2 | |
|
|
de7385d8ff | |
|
|
d5db276b90 | |
|
|
70eb449d44 | |
|
|
9d831d3ecd | |
|
|
0d3a96e82c | |
|
|
952ffa2e63 | |
|
|
0ccbe42c06 | |
|
|
38afb3a501 | |
|
|
7fbf201d30 | |
|
|
e9c2d053d3 | |
|
|
f05df85dae | |
|
|
e5af15a237 | |
|
|
8071a85533 | |
|
|
5da841cba8 | |
|
|
a1b3d5bb1b | |
|
|
317e84b234 | |
|
|
90f9242ddb | |
|
|
c7da354d68 | |
|
|
e0ff5f13a4 | |
|
|
b3799214e6 |
|
|
@ -3,5 +3,15 @@
|
||||||
__pycache/
|
__pycache/
|
||||||
dist/
|
dist/
|
||||||
pomice.egg-info/
|
pomice.egg-info/
|
||||||
|
|
||||||
docs/_build/
|
docs/_build/
|
||||||
|
build/
|
||||||
|
.gitpod.yml
|
||||||
|
.python-version
|
||||||
|
Pipfile.lock
|
||||||
|
.mypy_cache/
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
.venv/
|
||||||
|
*.code-workspace
|
||||||
|
*.ini
|
||||||
|
.pypirc
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
# See https://pre-commit.com for more information
|
||||||
|
# See https://pre-commit.com/hooks.html for more hooks
|
||||||
|
repos:
|
||||||
|
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||||
|
rev: v4.5.0
|
||||||
|
hooks:
|
||||||
|
- id: check-ast
|
||||||
|
- id: check-builtin-literals
|
||||||
|
- id: debug-statements
|
||||||
|
- id: end-of-file-fixer
|
||||||
|
- id: requirements-txt-fixer
|
||||||
|
- id: trailing-whitespace
|
||||||
|
- repo: https://github.com/psf/black
|
||||||
|
rev: 23.10.1
|
||||||
|
hooks:
|
||||||
|
- id: black
|
||||||
|
language_version: python3.13
|
||||||
|
- repo: https://github.com/asottile/pyupgrade
|
||||||
|
rev: v3.15.0
|
||||||
|
hooks:
|
||||||
|
- id: pyupgrade
|
||||||
|
args: [--py37-plus, --keep-runtime-typing]
|
||||||
|
- repo: https://github.com/asottile/reorder-python-imports
|
||||||
|
rev: v3.12.0
|
||||||
|
hooks:
|
||||||
|
- id: reorder-python-imports
|
||||||
|
- repo: https://github.com/asottile/add-trailing-comma
|
||||||
|
rev: v3.1.0
|
||||||
|
hooks:
|
||||||
|
- id: add-trailing-comma
|
||||||
|
|
||||||
|
default_language_version:
|
||||||
|
python: python3.13
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
prepare:
|
||||||
|
pipenv install --dev
|
||||||
|
pipenv run pre-commit install
|
||||||
|
|
||||||
|
shell:
|
||||||
|
pipenv shell
|
||||||
|
|
||||||
|
lint:
|
||||||
|
pipenv run pre-commit run --all-files
|
||||||
|
|
||||||
|
test:
|
||||||
|
pipenv run mypy
|
||||||
|
|
||||||
|
serve-docs:
|
||||||
|
@cd docs;\
|
||||||
|
make html;\
|
||||||
|
cd _build/html;\
|
||||||
|
python -m http.server;\
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
[[source]]
|
||||||
|
url = "https://pypi.org/simple"
|
||||||
|
verify_ssl = true
|
||||||
|
name = "pypi"
|
||||||
|
|
||||||
|
[packages]
|
||||||
|
orjson = "*"
|
||||||
|
"discord.py" = {extras = ["voice"], version = "*"}
|
||||||
|
websockets = "*"
|
||||||
|
|
||||||
|
[dev-packages]
|
||||||
|
mypy = "*"
|
||||||
|
pre-commit = "*"
|
||||||
|
furo = "*"
|
||||||
|
sphinx = "*"
|
||||||
|
myst-parser = "*"
|
||||||
|
black = "*"
|
||||||
|
typing-extensions = "*"
|
||||||
|
|
||||||
|
[requires]
|
||||||
|
python_version = "3.8"
|
||||||
17
README.md
17
README.md
|
|
@ -3,11 +3,16 @@
|
||||||

|

|
||||||
|
|
||||||
|
|
||||||
[](https://github.com/cloudwithax/pomice/blob/main/LICENSE) 
|
[](https://github.com/cloudwithax/pomice/blob/main/LICENSE)  [](https://github.com/psf/black)
|
||||||
[](https://discord.gg/r64qjTSHG8) [](https://pomice.readthedocs.io/en/latest/)
|
[](https://discord.gg/r64qjTSHG8) [](https://pomice.readthedocs.io/en/latest/)
|
||||||
|
|
||||||
|
|
||||||
Pomice is a fully asynchronous Python library designed for communicating with [Lavalink](https://github.com/freyacodes/Lavalink) seamlessly within the [discord.py](https://github.com/Rapptz/discord.py) library. It features 100% API coverage of the entire [Lavalink](https://github.com/freyacodes/Lavalink) spec that can be accessed with easy-to-understand functions. We also include Spotify and Apple Music querying capabilites using built-in custom clients, making it easier to develop your next big music bot.
|
Pomice is a fully asynchronous Python library designed for communicating with [Lavalink](https://github.com/freyacodes/Lavalink) seamlessly within the [discord.py](https://github.com/Rapptz/discord.py) library. It features 100% coverage of the [Lavalink](https://github.com/freyacodes/Lavalink) spec that can be accessed with easy-to-understand functions along with Spotify and Apple Music querying capabilities using built-in custom clients, making it easier to develop your next big music bot.
|
||||||
|
|
||||||
|
## Quick Links
|
||||||
|
- [Discord Server](https://discord.gg/r64qjTSHG8)
|
||||||
|
- [Read the Docs](https://pomice.readthedocs.io/en/latest/)
|
||||||
|
- [PyPI Homepage](https://pypi.org/project/pomice/)
|
||||||
|
|
||||||
|
|
||||||
# Install
|
# Install
|
||||||
|
|
@ -23,7 +28,7 @@ pip install pomice
|
||||||
pip install git+https://github.com/cloudwithax/pomice
|
pip install git+https://github.com/cloudwithax/pomice
|
||||||
```
|
```
|
||||||
|
|
||||||
# Support
|
# Support And Documentation
|
||||||
|
|
||||||
The official documentation is [here](https://pomice.readthedocs.io/en/latest/)
|
The official documentation is [here](https://pomice.readthedocs.io/en/latest/)
|
||||||
|
|
||||||
|
|
@ -31,7 +36,7 @@ You can join our support server [here](https://discord.gg/r64qjTSHG8)
|
||||||
|
|
||||||
|
|
||||||
# Examples
|
# Examples
|
||||||
In-depth examples are located in the examples folder
|
In-depth examples are located in the [examples folder](https://github.com/cloudwithax/pomice/tree/main/examples)
|
||||||
|
|
||||||
Here's a quick example:
|
Here's a quick example:
|
||||||
|
|
||||||
|
|
@ -109,7 +114,7 @@ bot.run("token here")
|
||||||
# FAQ
|
# FAQ
|
||||||
Why is it saying "Cannot connect to host"?
|
Why is it saying "Cannot connect to host"?
|
||||||
|
|
||||||
- You need to have a Lavalink node setup before you can use this library. Download it [here](https://github.com/freyacodes/Lavalink/releases/tag/3.3.2.5)
|
- You need to have a Lavalink node setup before you can use this library. Download it [here](https://github.com/freyacodes/Lavalink/releases/latest)
|
||||||
|
|
||||||
What experience do I need?
|
What experience do I need?
|
||||||
|
|
||||||
|
|
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -1,4 +0,0 @@
|
||||||
# Sphinx build info version 1
|
|
||||||
# This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done.
|
|
||||||
config: 0ed884b921767abe79957ecf5d5887be
|
|
||||||
tags: 645f666f9bcd5a90fca523b33c5a78b7
|
|
||||||
|
|
@ -1,112 +0,0 @@
|
||||||
.. pomice documentation master file, created by
|
|
||||||
sphinx-quickstart on Tue Nov 2 19:31:07 2021.
|
|
||||||
You can adapt this file completely to your liking, but it should at least
|
|
||||||
contain the root `toctree` directive.
|
|
||||||
|
|
||||||
Welcome to Pomice!
|
|
||||||
==================
|
|
||||||
|
|
||||||
.. image:: https://raw.githubusercontent.com/cloudwithax/pomice/main/banner.jpg
|
|
||||||
|
|
||||||
The modern `Lavalink <https://github.com/freyacodes/Lavalink>`_ wrapper designed for `discord.py <https://github.com/Rapptz/discord.py>`_
|
|
||||||
|
|
||||||
|
|
||||||
Contents
|
|
||||||
---------
|
|
||||||
|
|
||||||
.. toctree::
|
|
||||||
modules
|
|
||||||
|
|
||||||
|
|
||||||
Before You Start
|
|
||||||
----------------
|
|
||||||
|
|
||||||
This library is designed to work with the Lavalink audio delivery system,
|
|
||||||
which directly interfaces with Discord to provide buttery smooth audio without
|
|
||||||
wasting your precious system resources.
|
|
||||||
|
|
||||||
Pomice is made with convenience to the user, in that everything is easy to use
|
|
||||||
and is out of your way, while also being customizable.
|
|
||||||
|
|
||||||
In order to start using this library, please download a Lavalink node to start,
|
|
||||||
you can get it `here <https://github.com/freyacodes/Lavalink/releases/latest>`_
|
|
||||||
|
|
||||||
Quick Jumpstart
|
|
||||||
----------------
|
|
||||||
|
|
||||||
If you want a quick example as to how to start with Pomice, look below:
|
|
||||||
|
|
||||||
.. code-block:: python3
|
|
||||||
|
|
||||||
import pomice
|
|
||||||
import discord
|
|
||||||
import re
|
|
||||||
|
|
||||||
from discord.ext import commands
|
|
||||||
|
|
||||||
URL_REG = re.compile(r'https?://(?:www\.)?.+')
|
|
||||||
|
|
||||||
class MyBot(commands.Bot):
|
|
||||||
|
|
||||||
def __init__(self) -> None:
|
|
||||||
super().__init__(command_prefix='!', activity=discord.Activity(type=discord.ActivityType.listening, name='to music!'))
|
|
||||||
|
|
||||||
self.add_cog(Music(self))
|
|
||||||
|
|
||||||
async def on_ready(self) -> None:
|
|
||||||
print("I'm online!")
|
|
||||||
await self.cogs["Music"].start_nodes()
|
|
||||||
|
|
||||||
|
|
||||||
class Music(commands.Cog):
|
|
||||||
|
|
||||||
def __init__(self, bot) -> None:
|
|
||||||
self.bot = bot
|
|
||||||
|
|
||||||
self.pomice = pomice.NodePool()
|
|
||||||
|
|
||||||
async def start_nodes(self):
|
|
||||||
await self.pomice.create_node(bot=self.bot, host='127.0.0.1', port='3030',
|
|
||||||
password='youshallnotpass', identifier='MAIN')
|
|
||||||
print(f"Node is ready!")
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@commands.command(name='join', aliases=['connect'])
|
|
||||||
async def join(self, ctx: commands.Context, *, channel: discord.TextChannel = None) -> None:
|
|
||||||
|
|
||||||
if not channel:
|
|
||||||
channel = getattr(ctx.author.voice, 'channel', None)
|
|
||||||
if not channel:
|
|
||||||
raise commands.CheckFailure('You must be in a voice channel to use this command'
|
|
||||||
'without specifying the channel argument.')
|
|
||||||
|
|
||||||
|
|
||||||
await ctx.author.voice.channel.connect(cls=pomice.Player)
|
|
||||||
await ctx.send(f'Joined the voice channel `{channel}`')
|
|
||||||
|
|
||||||
@commands.command(name='play')
|
|
||||||
async def play(self, ctx, *, search: str) -> None:
|
|
||||||
|
|
||||||
if not ctx.voice_client:
|
|
||||||
await ctx.invoke(self.join)
|
|
||||||
|
|
||||||
player = ctx.voice_client
|
|
||||||
|
|
||||||
results = await player.get_tracks(query=f'{search}')
|
|
||||||
|
|
||||||
if not results:
|
|
||||||
raise commands.CommandError('No results were found for that search term.')
|
|
||||||
|
|
||||||
if isinstance(results, pomice.Playlist):
|
|
||||||
await player.play(track=results.tracks[0])
|
|
||||||
else:
|
|
||||||
await player.play(track=results[0])
|
|
||||||
|
|
||||||
|
|
||||||
bot = MyBot()
|
|
||||||
bot.run("token here")
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
.. toctree::
|
|
||||||
:maxdepth: 4
|
|
||||||
|
|
||||||
pomice
|
|
||||||
|
|
@ -1,74 +0,0 @@
|
||||||
Pomice
|
|
||||||
==============
|
|
||||||
|
|
||||||
|
|
||||||
.. toctree::
|
|
||||||
:maxdepth: 4
|
|
||||||
|
|
||||||
pomice.spotify
|
|
||||||
|
|
||||||
|
|
||||||
Enums
|
|
||||||
-------------------
|
|
||||||
|
|
||||||
.. automodule:: pomice.enums
|
|
||||||
:members:
|
|
||||||
:undoc-members:
|
|
||||||
:show-inheritance:
|
|
||||||
|
|
||||||
Events
|
|
||||||
--------------------
|
|
||||||
|
|
||||||
.. automodule:: pomice.events
|
|
||||||
:members:
|
|
||||||
:undoc-members:
|
|
||||||
:show-inheritance:
|
|
||||||
|
|
||||||
Exceptions
|
|
||||||
------------------------
|
|
||||||
|
|
||||||
.. automodule:: pomice.exceptions
|
|
||||||
:members:
|
|
||||||
:undoc-members:
|
|
||||||
:show-inheritance:
|
|
||||||
|
|
||||||
Filters
|
|
||||||
---------------------
|
|
||||||
|
|
||||||
.. automodule:: pomice.filters
|
|
||||||
:members:
|
|
||||||
:undoc-members:
|
|
||||||
:show-inheritance:
|
|
||||||
|
|
||||||
Objects
|
|
||||||
---------------------
|
|
||||||
|
|
||||||
.. automodule:: pomice.objects
|
|
||||||
:members:
|
|
||||||
:undoc-members:
|
|
||||||
:show-inheritance:
|
|
||||||
|
|
||||||
Player
|
|
||||||
--------------------
|
|
||||||
|
|
||||||
.. automodule:: pomice.player
|
|
||||||
:members:
|
|
||||||
:undoc-members:
|
|
||||||
:show-inheritance:
|
|
||||||
|
|
||||||
Pool
|
|
||||||
------------------
|
|
||||||
|
|
||||||
.. automodule:: pomice.pool
|
|
||||||
:members:
|
|
||||||
:undoc-members:
|
|
||||||
:show-inheritance:
|
|
||||||
|
|
||||||
Utils
|
|
||||||
-------------------
|
|
||||||
|
|
||||||
.. automodule:: pomice.utils
|
|
||||||
:members:
|
|
||||||
:undoc-members:
|
|
||||||
:show-inheritance:
|
|
||||||
|
|
||||||
|
|
@ -1,43 +0,0 @@
|
||||||
Spotify
|
|
||||||
======================
|
|
||||||
|
|
||||||
spotify.Album
|
|
||||||
---------------------------
|
|
||||||
|
|
||||||
.. automodule:: pomice.spotify.album
|
|
||||||
:members:
|
|
||||||
:undoc-members:
|
|
||||||
:show-inheritance:
|
|
||||||
|
|
||||||
spotify.Client
|
|
||||||
----------------------------
|
|
||||||
|
|
||||||
.. automodule:: pomice.spotify.client
|
|
||||||
:members:
|
|
||||||
:undoc-members:
|
|
||||||
:show-inheritance:
|
|
||||||
|
|
||||||
spotify.Exceptions
|
|
||||||
--------------------------------
|
|
||||||
|
|
||||||
.. automodule:: pomice.spotify.exceptions
|
|
||||||
:members:
|
|
||||||
:undoc-members:
|
|
||||||
:show-inheritance:
|
|
||||||
|
|
||||||
spotify.Playlist
|
|
||||||
------------------------------
|
|
||||||
|
|
||||||
.. automodule:: pomice.spotify.playlist
|
|
||||||
:members:
|
|
||||||
:undoc-members:
|
|
||||||
:show-inheritance:
|
|
||||||
|
|
||||||
spotify.Track
|
|
||||||
---------------------------
|
|
||||||
|
|
||||||
.. automodule:: pomice.spotify.track
|
|
||||||
:members:
|
|
||||||
:undoc-members:
|
|
||||||
:show-inheritance:
|
|
||||||
|
|
||||||
|
|
@ -1,701 +0,0 @@
|
||||||
@import url("basic.css");
|
|
||||||
|
|
||||||
/* -- page layout ----------------------------------------------------------- */
|
|
||||||
|
|
||||||
body {
|
|
||||||
font-family: Georgia, serif;
|
|
||||||
font-size: 17px;
|
|
||||||
background-color: #fff;
|
|
||||||
color: #000;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
div.document {
|
|
||||||
width: 940px;
|
|
||||||
margin: 30px auto 0 auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.documentwrapper {
|
|
||||||
float: left;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.bodywrapper {
|
|
||||||
margin: 0 0 0 220px;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.sphinxsidebar {
|
|
||||||
width: 220px;
|
|
||||||
font-size: 14px;
|
|
||||||
line-height: 1.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
hr {
|
|
||||||
border: 1px solid #B1B4B6;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.body {
|
|
||||||
background-color: #fff;
|
|
||||||
color: #3E4349;
|
|
||||||
padding: 0 30px 0 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.body > .section {
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.footer {
|
|
||||||
width: 940px;
|
|
||||||
margin: 20px auto 30px auto;
|
|
||||||
font-size: 14px;
|
|
||||||
color: #888;
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.footer a {
|
|
||||||
color: #888;
|
|
||||||
}
|
|
||||||
|
|
||||||
p.caption {
|
|
||||||
font-family: inherit;
|
|
||||||
font-size: inherit;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
div.relations {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
div.sphinxsidebar a {
|
|
||||||
color: #444;
|
|
||||||
text-decoration: none;
|
|
||||||
border-bottom: 1px dotted #999;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.sphinxsidebar a:hover {
|
|
||||||
border-bottom: 1px solid #999;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.sphinxsidebarwrapper {
|
|
||||||
padding: 18px 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.sphinxsidebarwrapper p.logo {
|
|
||||||
padding: 0;
|
|
||||||
margin: -10px 0 0 0px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.sphinxsidebarwrapper h1.logo {
|
|
||||||
margin-top: -10px;
|
|
||||||
text-align: center;
|
|
||||||
margin-bottom: 5px;
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.sphinxsidebarwrapper h1.logo-name {
|
|
||||||
margin-top: 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.sphinxsidebarwrapper p.blurb {
|
|
||||||
margin-top: 0;
|
|
||||||
font-style: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.sphinxsidebar h3,
|
|
||||||
div.sphinxsidebar h4 {
|
|
||||||
font-family: Georgia, serif;
|
|
||||||
color: #444;
|
|
||||||
font-size: 24px;
|
|
||||||
font-weight: normal;
|
|
||||||
margin: 0 0 5px 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.sphinxsidebar h4 {
|
|
||||||
font-size: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.sphinxsidebar h3 a {
|
|
||||||
color: #444;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.sphinxsidebar p.logo a,
|
|
||||||
div.sphinxsidebar h3 a,
|
|
||||||
div.sphinxsidebar p.logo a:hover,
|
|
||||||
div.sphinxsidebar h3 a:hover {
|
|
||||||
border: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.sphinxsidebar p {
|
|
||||||
color: #555;
|
|
||||||
margin: 10px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.sphinxsidebar ul {
|
|
||||||
margin: 10px 0;
|
|
||||||
padding: 0;
|
|
||||||
color: #000;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.sphinxsidebar ul li.toctree-l1 > a {
|
|
||||||
font-size: 120%;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.sphinxsidebar ul li.toctree-l2 > a {
|
|
||||||
font-size: 110%;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.sphinxsidebar input {
|
|
||||||
border: 1px solid #CCC;
|
|
||||||
font-family: Georgia, serif;
|
|
||||||
font-size: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.sphinxsidebar hr {
|
|
||||||
border: none;
|
|
||||||
height: 1px;
|
|
||||||
color: #AAA;
|
|
||||||
background: #AAA;
|
|
||||||
|
|
||||||
text-align: left;
|
|
||||||
margin-left: 0;
|
|
||||||
width: 50%;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.sphinxsidebar .badge {
|
|
||||||
border-bottom: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.sphinxsidebar .badge:hover {
|
|
||||||
border-bottom: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* To address an issue with donation coming after search */
|
|
||||||
div.sphinxsidebar h3.donation {
|
|
||||||
margin-top: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -- body styles ----------------------------------------------------------- */
|
|
||||||
|
|
||||||
a {
|
|
||||||
color: #004B6B;
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
|
|
||||||
a:hover {
|
|
||||||
color: #6D4100;
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.body h1,
|
|
||||||
div.body h2,
|
|
||||||
div.body h3,
|
|
||||||
div.body h4,
|
|
||||||
div.body h5,
|
|
||||||
div.body h6 {
|
|
||||||
font-family: Georgia, serif;
|
|
||||||
font-weight: normal;
|
|
||||||
margin: 30px 0px 10px 0px;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.body h1 { margin-top: 0; padding-top: 0; font-size: 240%; }
|
|
||||||
div.body h2 { font-size: 180%; }
|
|
||||||
div.body h3 { font-size: 150%; }
|
|
||||||
div.body h4 { font-size: 130%; }
|
|
||||||
div.body h5 { font-size: 100%; }
|
|
||||||
div.body h6 { font-size: 100%; }
|
|
||||||
|
|
||||||
a.headerlink {
|
|
||||||
color: #DDD;
|
|
||||||
padding: 0 4px;
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
a.headerlink:hover {
|
|
||||||
color: #444;
|
|
||||||
background: #EAEAEA;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.body p, div.body dd, div.body li {
|
|
||||||
line-height: 1.4em;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.admonition {
|
|
||||||
margin: 20px 0px;
|
|
||||||
padding: 10px 30px;
|
|
||||||
background-color: #EEE;
|
|
||||||
border: 1px solid #CCC;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.admonition tt.xref, div.admonition code.xref, div.admonition a tt {
|
|
||||||
background-color: #FBFBFB;
|
|
||||||
border-bottom: 1px solid #fafafa;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.admonition p.admonition-title {
|
|
||||||
font-family: Georgia, serif;
|
|
||||||
font-weight: normal;
|
|
||||||
font-size: 24px;
|
|
||||||
margin: 0 0 10px 0;
|
|
||||||
padding: 0;
|
|
||||||
line-height: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.admonition p.last {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.highlight {
|
|
||||||
background-color: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
dt:target, .highlight {
|
|
||||||
background: #FAF3E8;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.warning {
|
|
||||||
background-color: #FCC;
|
|
||||||
border: 1px solid #FAA;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.danger {
|
|
||||||
background-color: #FCC;
|
|
||||||
border: 1px solid #FAA;
|
|
||||||
-moz-box-shadow: 2px 2px 4px #D52C2C;
|
|
||||||
-webkit-box-shadow: 2px 2px 4px #D52C2C;
|
|
||||||
box-shadow: 2px 2px 4px #D52C2C;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.error {
|
|
||||||
background-color: #FCC;
|
|
||||||
border: 1px solid #FAA;
|
|
||||||
-moz-box-shadow: 2px 2px 4px #D52C2C;
|
|
||||||
-webkit-box-shadow: 2px 2px 4px #D52C2C;
|
|
||||||
box-shadow: 2px 2px 4px #D52C2C;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.caution {
|
|
||||||
background-color: #FCC;
|
|
||||||
border: 1px solid #FAA;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.attention {
|
|
||||||
background-color: #FCC;
|
|
||||||
border: 1px solid #FAA;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.important {
|
|
||||||
background-color: #EEE;
|
|
||||||
border: 1px solid #CCC;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.note {
|
|
||||||
background-color: #EEE;
|
|
||||||
border: 1px solid #CCC;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.tip {
|
|
||||||
background-color: #EEE;
|
|
||||||
border: 1px solid #CCC;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.hint {
|
|
||||||
background-color: #EEE;
|
|
||||||
border: 1px solid #CCC;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.seealso {
|
|
||||||
background-color: #EEE;
|
|
||||||
border: 1px solid #CCC;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.topic {
|
|
||||||
background-color: #EEE;
|
|
||||||
}
|
|
||||||
|
|
||||||
p.admonition-title {
|
|
||||||
display: inline;
|
|
||||||
}
|
|
||||||
|
|
||||||
p.admonition-title:after {
|
|
||||||
content: ":";
|
|
||||||
}
|
|
||||||
|
|
||||||
pre, tt, code {
|
|
||||||
font-family: 'Consolas', 'Menlo', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', monospace;
|
|
||||||
font-size: 0.9em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hll {
|
|
||||||
background-color: #FFC;
|
|
||||||
margin: 0 -12px;
|
|
||||||
padding: 0 12px;
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
img.screenshot {
|
|
||||||
}
|
|
||||||
|
|
||||||
tt.descname, tt.descclassname, code.descname, code.descclassname {
|
|
||||||
font-size: 0.95em;
|
|
||||||
}
|
|
||||||
|
|
||||||
tt.descname, code.descname {
|
|
||||||
padding-right: 0.08em;
|
|
||||||
}
|
|
||||||
|
|
||||||
img.screenshot {
|
|
||||||
-moz-box-shadow: 2px 2px 4px #EEE;
|
|
||||||
-webkit-box-shadow: 2px 2px 4px #EEE;
|
|
||||||
box-shadow: 2px 2px 4px #EEE;
|
|
||||||
}
|
|
||||||
|
|
||||||
table.docutils {
|
|
||||||
border: 1px solid #888;
|
|
||||||
-moz-box-shadow: 2px 2px 4px #EEE;
|
|
||||||
-webkit-box-shadow: 2px 2px 4px #EEE;
|
|
||||||
box-shadow: 2px 2px 4px #EEE;
|
|
||||||
}
|
|
||||||
|
|
||||||
table.docutils td, table.docutils th {
|
|
||||||
border: 1px solid #888;
|
|
||||||
padding: 0.25em 0.7em;
|
|
||||||
}
|
|
||||||
|
|
||||||
table.field-list, table.footnote {
|
|
||||||
border: none;
|
|
||||||
-moz-box-shadow: none;
|
|
||||||
-webkit-box-shadow: none;
|
|
||||||
box-shadow: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
table.footnote {
|
|
||||||
margin: 15px 0;
|
|
||||||
width: 100%;
|
|
||||||
border: 1px solid #EEE;
|
|
||||||
background: #FDFDFD;
|
|
||||||
font-size: 0.9em;
|
|
||||||
}
|
|
||||||
|
|
||||||
table.footnote + table.footnote {
|
|
||||||
margin-top: -15px;
|
|
||||||
border-top: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
table.field-list th {
|
|
||||||
padding: 0 0.8em 0 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
table.field-list td {
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
table.field-list p {
|
|
||||||
margin-bottom: 0.8em;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Cloned from
|
|
||||||
* https://github.com/sphinx-doc/sphinx/commit/ef60dbfce09286b20b7385333d63a60321784e68
|
|
||||||
*/
|
|
||||||
.field-name {
|
|
||||||
-moz-hyphens: manual;
|
|
||||||
-ms-hyphens: manual;
|
|
||||||
-webkit-hyphens: manual;
|
|
||||||
hyphens: manual;
|
|
||||||
}
|
|
||||||
|
|
||||||
table.footnote td.label {
|
|
||||||
width: .1px;
|
|
||||||
padding: 0.3em 0 0.3em 0.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
table.footnote td {
|
|
||||||
padding: 0.3em 0.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
dl {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
dl dd {
|
|
||||||
margin-left: 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
blockquote {
|
|
||||||
margin: 0 0 0 30px;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
ul, ol {
|
|
||||||
/* Matches the 30px from the narrow-screen "li > ul" selector below */
|
|
||||||
margin: 10px 0 10px 30px;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
pre {
|
|
||||||
background: #EEE;
|
|
||||||
padding: 7px 30px;
|
|
||||||
margin: 15px 0px;
|
|
||||||
line-height: 1.3em;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.viewcode-block:target {
|
|
||||||
background: #ffd;
|
|
||||||
}
|
|
||||||
|
|
||||||
dl pre, blockquote pre, li pre {
|
|
||||||
margin-left: 0;
|
|
||||||
padding-left: 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
tt, code {
|
|
||||||
background-color: #ecf0f3;
|
|
||||||
color: #222;
|
|
||||||
/* padding: 1px 2px; */
|
|
||||||
}
|
|
||||||
|
|
||||||
tt.xref, code.xref, a tt {
|
|
||||||
background-color: #FBFBFB;
|
|
||||||
border-bottom: 1px solid #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
a.reference {
|
|
||||||
text-decoration: none;
|
|
||||||
border-bottom: 1px dotted #004B6B;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Don't put an underline on images */
|
|
||||||
a.image-reference, a.image-reference:hover {
|
|
||||||
border-bottom: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
a.reference:hover {
|
|
||||||
border-bottom: 1px solid #6D4100;
|
|
||||||
}
|
|
||||||
|
|
||||||
a.footnote-reference {
|
|
||||||
text-decoration: none;
|
|
||||||
font-size: 0.7em;
|
|
||||||
vertical-align: top;
|
|
||||||
border-bottom: 1px dotted #004B6B;
|
|
||||||
}
|
|
||||||
|
|
||||||
a.footnote-reference:hover {
|
|
||||||
border-bottom: 1px solid #6D4100;
|
|
||||||
}
|
|
||||||
|
|
||||||
a:hover tt, a:hover code {
|
|
||||||
background: #EEE;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@media screen and (max-width: 870px) {
|
|
||||||
|
|
||||||
div.sphinxsidebar {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.document {
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
div.documentwrapper {
|
|
||||||
margin-left: 0;
|
|
||||||
margin-top: 0;
|
|
||||||
margin-right: 0;
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.bodywrapper {
|
|
||||||
margin-top: 0;
|
|
||||||
margin-right: 0;
|
|
||||||
margin-bottom: 0;
|
|
||||||
margin-left: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
ul {
|
|
||||||
margin-left: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
li > ul {
|
|
||||||
/* Matches the 30px from the "ul, ol" selector above */
|
|
||||||
margin-left: 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.document {
|
|
||||||
width: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer {
|
|
||||||
width: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bodywrapper {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer {
|
|
||||||
width: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.github {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@media screen and (max-width: 875px) {
|
|
||||||
|
|
||||||
body {
|
|
||||||
margin: 0;
|
|
||||||
padding: 20px 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.documentwrapper {
|
|
||||||
float: none;
|
|
||||||
background: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.sphinxsidebar {
|
|
||||||
display: block;
|
|
||||||
float: none;
|
|
||||||
width: 102.5%;
|
|
||||||
margin: 50px -30px -20px -30px;
|
|
||||||
padding: 10px 20px;
|
|
||||||
background: #333;
|
|
||||||
color: #FFF;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.sphinxsidebar h3, div.sphinxsidebar h4, div.sphinxsidebar p,
|
|
||||||
div.sphinxsidebar h3 a {
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.sphinxsidebar a {
|
|
||||||
color: #AAA;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.sphinxsidebar p.logo {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.document {
|
|
||||||
width: 100%;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.footer {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.bodywrapper {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.body {
|
|
||||||
min-height: 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rtd_doc_footer {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.document {
|
|
||||||
width: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer {
|
|
||||||
width: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer {
|
|
||||||
width: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.github {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* misc. */
|
|
||||||
|
|
||||||
.revsys-inline {
|
|
||||||
display: none!important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Make nested-list/multi-paragraph items look better in Releases changelog
|
|
||||||
* pages. Without this, docutils' magical list fuckery causes inconsistent
|
|
||||||
* formatting between different release sub-lists.
|
|
||||||
*/
|
|
||||||
div#changelog > div.section > ul > li > p:only-child {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Hide fugly table cell borders in ..bibliography:: directive output */
|
|
||||||
table.docutils.citation, table.docutils.citation td, table.docutils.citation th {
|
|
||||||
border: none;
|
|
||||||
/* Below needed in some edge cases; if not applied, bottom shadows appear */
|
|
||||||
-moz-box-shadow: none;
|
|
||||||
-webkit-box-shadow: none;
|
|
||||||
box-shadow: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* relbar */
|
|
||||||
|
|
||||||
.related {
|
|
||||||
line-height: 30px;
|
|
||||||
width: 100%;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.related.top {
|
|
||||||
border-bottom: 1px solid #EEE;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.related.bottom {
|
|
||||||
border-top: 1px solid #EEE;
|
|
||||||
}
|
|
||||||
|
|
||||||
.related ul {
|
|
||||||
padding: 0;
|
|
||||||
margin: 0;
|
|
||||||
list-style: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.related li {
|
|
||||||
display: inline;
|
|
||||||
}
|
|
||||||
|
|
||||||
nav#rellinks {
|
|
||||||
float: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
nav#rellinks li+li:before {
|
|
||||||
content: "|";
|
|
||||||
}
|
|
||||||
|
|
||||||
nav#breadcrumbs li+li:before {
|
|
||||||
content: "\00BB";
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Hide certain items when printing */
|
|
||||||
@media print {
|
|
||||||
div.related {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,904 +0,0 @@
|
||||||
/*
|
|
||||||
* basic.css
|
|
||||||
* ~~~~~~~~~
|
|
||||||
*
|
|
||||||
* Sphinx stylesheet -- basic theme.
|
|
||||||
*
|
|
||||||
* :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS.
|
|
||||||
* :license: BSD, see LICENSE for details.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* -- main layout ----------------------------------------------------------- */
|
|
||||||
|
|
||||||
div.clearer {
|
|
||||||
clear: both;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.section::after {
|
|
||||||
display: block;
|
|
||||||
content: '';
|
|
||||||
clear: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -- relbar ---------------------------------------------------------------- */
|
|
||||||
|
|
||||||
div.related {
|
|
||||||
width: 100%;
|
|
||||||
font-size: 90%;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.related h3 {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.related ul {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0 0 0 10px;
|
|
||||||
list-style: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.related li {
|
|
||||||
display: inline;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.related li.right {
|
|
||||||
float: right;
|
|
||||||
margin-right: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -- sidebar --------------------------------------------------------------- */
|
|
||||||
|
|
||||||
div.sphinxsidebarwrapper {
|
|
||||||
padding: 10px 5px 0 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.sphinxsidebar {
|
|
||||||
float: left;
|
|
||||||
width: 230px;
|
|
||||||
margin-left: -100%;
|
|
||||||
font-size: 90%;
|
|
||||||
word-wrap: break-word;
|
|
||||||
overflow-wrap : break-word;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.sphinxsidebar ul {
|
|
||||||
list-style: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.sphinxsidebar ul ul,
|
|
||||||
div.sphinxsidebar ul.want-points {
|
|
||||||
margin-left: 20px;
|
|
||||||
list-style: square;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.sphinxsidebar ul ul {
|
|
||||||
margin-top: 0;
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.sphinxsidebar form {
|
|
||||||
margin-top: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.sphinxsidebar input {
|
|
||||||
border: 1px solid #98dbcc;
|
|
||||||
font-family: sans-serif;
|
|
||||||
font-size: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.sphinxsidebar #searchbox form.search {
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.sphinxsidebar #searchbox input[type="text"] {
|
|
||||||
float: left;
|
|
||||||
width: 80%;
|
|
||||||
padding: 0.25em;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.sphinxsidebar #searchbox input[type="submit"] {
|
|
||||||
float: left;
|
|
||||||
width: 20%;
|
|
||||||
border-left: none;
|
|
||||||
padding: 0.25em;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
img {
|
|
||||||
border: 0;
|
|
||||||
max-width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -- search page ----------------------------------------------------------- */
|
|
||||||
|
|
||||||
ul.search {
|
|
||||||
margin: 10px 0 0 20px;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
ul.search li {
|
|
||||||
padding: 5px 0 5px 20px;
|
|
||||||
background-image: url(file.png);
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
background-position: 0 7px;
|
|
||||||
}
|
|
||||||
|
|
||||||
ul.search li a {
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
ul.search li p.context {
|
|
||||||
color: #888;
|
|
||||||
margin: 2px 0 0 30px;
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
ul.keywordmatches li.goodmatch a {
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -- index page ------------------------------------------------------------ */
|
|
||||||
|
|
||||||
table.contentstable {
|
|
||||||
width: 90%;
|
|
||||||
margin-left: auto;
|
|
||||||
margin-right: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
table.contentstable p.biglink {
|
|
||||||
line-height: 150%;
|
|
||||||
}
|
|
||||||
|
|
||||||
a.biglink {
|
|
||||||
font-size: 1.3em;
|
|
||||||
}
|
|
||||||
|
|
||||||
span.linkdescr {
|
|
||||||
font-style: italic;
|
|
||||||
padding-top: 5px;
|
|
||||||
font-size: 90%;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -- general index --------------------------------------------------------- */
|
|
||||||
|
|
||||||
table.indextable {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
table.indextable td {
|
|
||||||
text-align: left;
|
|
||||||
vertical-align: top;
|
|
||||||
}
|
|
||||||
|
|
||||||
table.indextable ul {
|
|
||||||
margin-top: 0;
|
|
||||||
margin-bottom: 0;
|
|
||||||
list-style-type: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
table.indextable > tbody > tr > td > ul {
|
|
||||||
padding-left: 0em;
|
|
||||||
}
|
|
||||||
|
|
||||||
table.indextable tr.pcap {
|
|
||||||
height: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
table.indextable tr.cap {
|
|
||||||
margin-top: 10px;
|
|
||||||
background-color: #f2f2f2;
|
|
||||||
}
|
|
||||||
|
|
||||||
img.toggler {
|
|
||||||
margin-right: 3px;
|
|
||||||
margin-top: 3px;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.modindex-jumpbox {
|
|
||||||
border-top: 1px solid #ddd;
|
|
||||||
border-bottom: 1px solid #ddd;
|
|
||||||
margin: 1em 0 1em 0;
|
|
||||||
padding: 0.4em;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.genindex-jumpbox {
|
|
||||||
border-top: 1px solid #ddd;
|
|
||||||
border-bottom: 1px solid #ddd;
|
|
||||||
margin: 1em 0 1em 0;
|
|
||||||
padding: 0.4em;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -- domain module index --------------------------------------------------- */
|
|
||||||
|
|
||||||
table.modindextable td {
|
|
||||||
padding: 2px;
|
|
||||||
border-collapse: collapse;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -- general body styles --------------------------------------------------- */
|
|
||||||
|
|
||||||
div.body {
|
|
||||||
min-width: 450px;
|
|
||||||
max-width: 800px;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.body p, div.body dd, div.body li, div.body blockquote {
|
|
||||||
-moz-hyphens: auto;
|
|
||||||
-ms-hyphens: auto;
|
|
||||||
-webkit-hyphens: auto;
|
|
||||||
hyphens: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
a.headerlink {
|
|
||||||
visibility: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
a.brackets:before,
|
|
||||||
span.brackets > a:before{
|
|
||||||
content: "[";
|
|
||||||
}
|
|
||||||
|
|
||||||
a.brackets:after,
|
|
||||||
span.brackets > a:after {
|
|
||||||
content: "]";
|
|
||||||
}
|
|
||||||
|
|
||||||
h1:hover > a.headerlink,
|
|
||||||
h2:hover > a.headerlink,
|
|
||||||
h3:hover > a.headerlink,
|
|
||||||
h4:hover > a.headerlink,
|
|
||||||
h5:hover > a.headerlink,
|
|
||||||
h6:hover > a.headerlink,
|
|
||||||
dt:hover > a.headerlink,
|
|
||||||
caption:hover > a.headerlink,
|
|
||||||
p.caption:hover > a.headerlink,
|
|
||||||
div.code-block-caption:hover > a.headerlink {
|
|
||||||
visibility: visible;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.body p.caption {
|
|
||||||
text-align: inherit;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.body td {
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
.first {
|
|
||||||
margin-top: 0 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
p.rubric {
|
|
||||||
margin-top: 30px;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
img.align-left, figure.align-left, .figure.align-left, object.align-left {
|
|
||||||
clear: left;
|
|
||||||
float: left;
|
|
||||||
margin-right: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
img.align-right, figure.align-right, .figure.align-right, object.align-right {
|
|
||||||
clear: right;
|
|
||||||
float: right;
|
|
||||||
margin-left: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
img.align-center, figure.align-center, .figure.align-center, object.align-center {
|
|
||||||
display: block;
|
|
||||||
margin-left: auto;
|
|
||||||
margin-right: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
img.align-default, figure.align-default, .figure.align-default {
|
|
||||||
display: block;
|
|
||||||
margin-left: auto;
|
|
||||||
margin-right: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.align-left {
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
.align-center {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.align-default {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.align-right {
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -- sidebars -------------------------------------------------------------- */
|
|
||||||
|
|
||||||
div.sidebar,
|
|
||||||
aside.sidebar {
|
|
||||||
margin: 0 0 0.5em 1em;
|
|
||||||
border: 1px solid #ddb;
|
|
||||||
padding: 7px;
|
|
||||||
background-color: #ffe;
|
|
||||||
width: 40%;
|
|
||||||
float: right;
|
|
||||||
clear: right;
|
|
||||||
overflow-x: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
p.sidebar-title {
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.admonition, div.topic, blockquote {
|
|
||||||
clear: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -- topics ---------------------------------------------------------------- */
|
|
||||||
|
|
||||||
div.topic {
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
padding: 7px;
|
|
||||||
margin: 10px 0 10px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
p.topic-title {
|
|
||||||
font-size: 1.1em;
|
|
||||||
font-weight: bold;
|
|
||||||
margin-top: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -- admonitions ----------------------------------------------------------- */
|
|
||||||
|
|
||||||
div.admonition {
|
|
||||||
margin-top: 10px;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
padding: 7px;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.admonition dt {
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
p.admonition-title {
|
|
||||||
margin: 0px 10px 5px 0px;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.body p.centered {
|
|
||||||
text-align: center;
|
|
||||||
margin-top: 25px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -- content of sidebars/topics/admonitions -------------------------------- */
|
|
||||||
|
|
||||||
div.sidebar > :last-child,
|
|
||||||
aside.sidebar > :last-child,
|
|
||||||
div.topic > :last-child,
|
|
||||||
div.admonition > :last-child {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.sidebar::after,
|
|
||||||
aside.sidebar::after,
|
|
||||||
div.topic::after,
|
|
||||||
div.admonition::after,
|
|
||||||
blockquote::after {
|
|
||||||
display: block;
|
|
||||||
content: '';
|
|
||||||
clear: both;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -- tables ---------------------------------------------------------------- */
|
|
||||||
|
|
||||||
table.docutils {
|
|
||||||
margin-top: 10px;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
border: 0;
|
|
||||||
border-collapse: collapse;
|
|
||||||
}
|
|
||||||
|
|
||||||
table.align-center {
|
|
||||||
margin-left: auto;
|
|
||||||
margin-right: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
table.align-default {
|
|
||||||
margin-left: auto;
|
|
||||||
margin-right: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
table caption span.caption-number {
|
|
||||||
font-style: italic;
|
|
||||||
}
|
|
||||||
|
|
||||||
table caption span.caption-text {
|
|
||||||
}
|
|
||||||
|
|
||||||
table.docutils td, table.docutils th {
|
|
||||||
padding: 1px 8px 1px 5px;
|
|
||||||
border-top: 0;
|
|
||||||
border-left: 0;
|
|
||||||
border-right: 0;
|
|
||||||
border-bottom: 1px solid #aaa;
|
|
||||||
}
|
|
||||||
|
|
||||||
table.footnote td, table.footnote th {
|
|
||||||
border: 0 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
th {
|
|
||||||
text-align: left;
|
|
||||||
padding-right: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
table.citation {
|
|
||||||
border-left: solid 1px gray;
|
|
||||||
margin-left: 1px;
|
|
||||||
}
|
|
||||||
|
|
||||||
table.citation td {
|
|
||||||
border-bottom: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
th > :first-child,
|
|
||||||
td > :first-child {
|
|
||||||
margin-top: 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
th > :last-child,
|
|
||||||
td > :last-child {
|
|
||||||
margin-bottom: 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -- figures --------------------------------------------------------------- */
|
|
||||||
|
|
||||||
div.figure, figure {
|
|
||||||
margin: 0.5em;
|
|
||||||
padding: 0.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.figure p.caption, figcaption {
|
|
||||||
padding: 0.3em;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.figure p.caption span.caption-number,
|
|
||||||
figcaption span.caption-number {
|
|
||||||
font-style: italic;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.figure p.caption span.caption-text,
|
|
||||||
figcaption span.caption-text {
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -- field list styles ----------------------------------------------------- */
|
|
||||||
|
|
||||||
table.field-list td, table.field-list th {
|
|
||||||
border: 0 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.field-list ul {
|
|
||||||
margin: 0;
|
|
||||||
padding-left: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.field-list p {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.field-name {
|
|
||||||
-moz-hyphens: manual;
|
|
||||||
-ms-hyphens: manual;
|
|
||||||
-webkit-hyphens: manual;
|
|
||||||
hyphens: manual;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -- hlist styles ---------------------------------------------------------- */
|
|
||||||
|
|
||||||
table.hlist {
|
|
||||||
margin: 1em 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
table.hlist td {
|
|
||||||
vertical-align: top;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -- object description styles --------------------------------------------- */
|
|
||||||
|
|
||||||
.sig {
|
|
||||||
font-family: 'Consolas', 'Menlo', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', monospace;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sig-name, code.descname {
|
|
||||||
background-color: transparent;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sig-name {
|
|
||||||
font-size: 1.1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
code.descname {
|
|
||||||
font-size: 1.2em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sig-prename, code.descclassname {
|
|
||||||
background-color: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
.optional {
|
|
||||||
font-size: 1.3em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sig-paren {
|
|
||||||
font-size: larger;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sig-param.n {
|
|
||||||
font-style: italic;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* C++ specific styling */
|
|
||||||
|
|
||||||
.sig-inline.c-texpr,
|
|
||||||
.sig-inline.cpp-texpr {
|
|
||||||
font-family: unset;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sig.c .k, .sig.c .kt,
|
|
||||||
.sig.cpp .k, .sig.cpp .kt {
|
|
||||||
color: #0033B3;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sig.c .m,
|
|
||||||
.sig.cpp .m {
|
|
||||||
color: #1750EB;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sig.c .s, .sig.c .sc,
|
|
||||||
.sig.cpp .s, .sig.cpp .sc {
|
|
||||||
color: #067D17;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* -- other body styles ----------------------------------------------------- */
|
|
||||||
|
|
||||||
ol.arabic {
|
|
||||||
list-style: decimal;
|
|
||||||
}
|
|
||||||
|
|
||||||
ol.loweralpha {
|
|
||||||
list-style: lower-alpha;
|
|
||||||
}
|
|
||||||
|
|
||||||
ol.upperalpha {
|
|
||||||
list-style: upper-alpha;
|
|
||||||
}
|
|
||||||
|
|
||||||
ol.lowerroman {
|
|
||||||
list-style: lower-roman;
|
|
||||||
}
|
|
||||||
|
|
||||||
ol.upperroman {
|
|
||||||
list-style: upper-roman;
|
|
||||||
}
|
|
||||||
|
|
||||||
:not(li) > ol > li:first-child > :first-child,
|
|
||||||
:not(li) > ul > li:first-child > :first-child {
|
|
||||||
margin-top: 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
:not(li) > ol > li:last-child > :last-child,
|
|
||||||
:not(li) > ul > li:last-child > :last-child {
|
|
||||||
margin-bottom: 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
ol.simple ol p,
|
|
||||||
ol.simple ul p,
|
|
||||||
ul.simple ol p,
|
|
||||||
ul.simple ul p {
|
|
||||||
margin-top: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
ol.simple > li:not(:first-child) > p,
|
|
||||||
ul.simple > li:not(:first-child) > p {
|
|
||||||
margin-top: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
ol.simple p,
|
|
||||||
ul.simple p {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
dl.footnote > dt,
|
|
||||||
dl.citation > dt {
|
|
||||||
float: left;
|
|
||||||
margin-right: 0.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
dl.footnote > dd,
|
|
||||||
dl.citation > dd {
|
|
||||||
margin-bottom: 0em;
|
|
||||||
}
|
|
||||||
|
|
||||||
dl.footnote > dd:after,
|
|
||||||
dl.citation > dd:after {
|
|
||||||
content: "";
|
|
||||||
clear: both;
|
|
||||||
}
|
|
||||||
|
|
||||||
dl.field-list {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: fit-content(30%) auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
dl.field-list > dt {
|
|
||||||
font-weight: bold;
|
|
||||||
word-break: break-word;
|
|
||||||
padding-left: 0.5em;
|
|
||||||
padding-right: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
dl.field-list > dt:after {
|
|
||||||
content: ":";
|
|
||||||
}
|
|
||||||
|
|
||||||
dl.field-list > dd {
|
|
||||||
padding-left: 0.5em;
|
|
||||||
margin-top: 0em;
|
|
||||||
margin-left: 0em;
|
|
||||||
margin-bottom: 0em;
|
|
||||||
}
|
|
||||||
|
|
||||||
dl {
|
|
||||||
margin-bottom: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
dd > :first-child {
|
|
||||||
margin-top: 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
dd ul, dd table {
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
dd {
|
|
||||||
margin-top: 3px;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
margin-left: 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
dl > dd:last-child,
|
|
||||||
dl > dd:last-child > :last-child {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
dt:target, span.highlighted {
|
|
||||||
background-color: #fbe54e;
|
|
||||||
}
|
|
||||||
|
|
||||||
rect.highlighted {
|
|
||||||
fill: #fbe54e;
|
|
||||||
}
|
|
||||||
|
|
||||||
dl.glossary dt {
|
|
||||||
font-weight: bold;
|
|
||||||
font-size: 1.1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.versionmodified {
|
|
||||||
font-style: italic;
|
|
||||||
}
|
|
||||||
|
|
||||||
.system-message {
|
|
||||||
background-color: #fda;
|
|
||||||
padding: 5px;
|
|
||||||
border: 3px solid red;
|
|
||||||
}
|
|
||||||
|
|
||||||
.footnote:target {
|
|
||||||
background-color: #ffa;
|
|
||||||
}
|
|
||||||
|
|
||||||
.line-block {
|
|
||||||
display: block;
|
|
||||||
margin-top: 1em;
|
|
||||||
margin-bottom: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.line-block .line-block {
|
|
||||||
margin-top: 0;
|
|
||||||
margin-bottom: 0;
|
|
||||||
margin-left: 1.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.guilabel, .menuselection {
|
|
||||||
font-family: sans-serif;
|
|
||||||
}
|
|
||||||
|
|
||||||
.accelerator {
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
|
|
||||||
.classifier {
|
|
||||||
font-style: oblique;
|
|
||||||
}
|
|
||||||
|
|
||||||
.classifier:before {
|
|
||||||
font-style: normal;
|
|
||||||
margin: 0.5em;
|
|
||||||
content: ":";
|
|
||||||
}
|
|
||||||
|
|
||||||
abbr, acronym {
|
|
||||||
border-bottom: dotted 1px;
|
|
||||||
cursor: help;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -- code displays --------------------------------------------------------- */
|
|
||||||
|
|
||||||
pre {
|
|
||||||
overflow: auto;
|
|
||||||
overflow-y: hidden; /* fixes display issues on Chrome browsers */
|
|
||||||
}
|
|
||||||
|
|
||||||
pre, div[class*="highlight-"] {
|
|
||||||
clear: both;
|
|
||||||
}
|
|
||||||
|
|
||||||
span.pre {
|
|
||||||
-moz-hyphens: none;
|
|
||||||
-ms-hyphens: none;
|
|
||||||
-webkit-hyphens: none;
|
|
||||||
hyphens: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
div[class*="highlight-"] {
|
|
||||||
margin: 1em 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
td.linenos pre {
|
|
||||||
border: 0;
|
|
||||||
background-color: transparent;
|
|
||||||
color: #aaa;
|
|
||||||
}
|
|
||||||
|
|
||||||
table.highlighttable {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
table.highlighttable tbody {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
table.highlighttable tr {
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
|
|
||||||
table.highlighttable td {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
table.highlighttable td.linenos {
|
|
||||||
padding-right: 0.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
table.highlighttable td.code {
|
|
||||||
flex: 1;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.highlight .hll {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.highlight pre,
|
|
||||||
table.highlighttable pre {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.code-block-caption + div {
|
|
||||||
margin-top: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.code-block-caption {
|
|
||||||
margin-top: 1em;
|
|
||||||
padding: 2px 5px;
|
|
||||||
font-size: small;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.code-block-caption code {
|
|
||||||
background-color: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
table.highlighttable td.linenos,
|
|
||||||
span.linenos,
|
|
||||||
div.highlight span.gp { /* gp: Generic.Prompt */
|
|
||||||
user-select: none;
|
|
||||||
-webkit-user-select: text; /* Safari fallback only */
|
|
||||||
-webkit-user-select: none; /* Chrome/Safari */
|
|
||||||
-moz-user-select: none; /* Firefox */
|
|
||||||
-ms-user-select: none; /* IE10+ */
|
|
||||||
}
|
|
||||||
|
|
||||||
div.code-block-caption span.caption-number {
|
|
||||||
padding: 0.1em 0.3em;
|
|
||||||
font-style: italic;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.code-block-caption span.caption-text {
|
|
||||||
}
|
|
||||||
|
|
||||||
div.literal-block-wrapper {
|
|
||||||
margin: 1em 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
code.xref, a code {
|
|
||||||
background-color: transparent;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1 code, h2 code, h3 code, h4 code, h5 code, h6 code {
|
|
||||||
background-color: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
.viewcode-link {
|
|
||||||
float: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
.viewcode-back {
|
|
||||||
float: right;
|
|
||||||
font-family: sans-serif;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.viewcode-block:target {
|
|
||||||
margin: -1px -10px;
|
|
||||||
padding: 0 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -- math display ---------------------------------------------------------- */
|
|
||||||
|
|
||||||
img.math {
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.body div.math p {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
span.eqno {
|
|
||||||
float: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
span.eqno a.headerlink {
|
|
||||||
position: absolute;
|
|
||||||
z-index: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.math:hover a.headerlink {
|
|
||||||
visibility: visible;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -- printout stylesheet --------------------------------------------------- */
|
|
||||||
|
|
||||||
@media print {
|
|
||||||
div.document,
|
|
||||||
div.documentwrapper,
|
|
||||||
div.bodywrapper {
|
|
||||||
margin: 0 !important;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.sphinxsidebar,
|
|
||||||
div.related,
|
|
||||||
div.footer,
|
|
||||||
#top-link {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
/* This file intentionally left blank. */
|
|
||||||
|
|
@ -1,323 +0,0 @@
|
||||||
/*
|
|
||||||
* doctools.js
|
|
||||||
* ~~~~~~~~~~~
|
|
||||||
*
|
|
||||||
* Sphinx JavaScript utilities for all documentation.
|
|
||||||
*
|
|
||||||
* :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS.
|
|
||||||
* :license: BSD, see LICENSE for details.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* select a different prefix for underscore
|
|
||||||
*/
|
|
||||||
$u = _.noConflict();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* make the code below compatible with browsers without
|
|
||||||
* an installed firebug like debugger
|
|
||||||
if (!window.console || !console.firebug) {
|
|
||||||
var names = ["log", "debug", "info", "warn", "error", "assert", "dir",
|
|
||||||
"dirxml", "group", "groupEnd", "time", "timeEnd", "count", "trace",
|
|
||||||
"profile", "profileEnd"];
|
|
||||||
window.console = {};
|
|
||||||
for (var i = 0; i < names.length; ++i)
|
|
||||||
window.console[names[i]] = function() {};
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* small helper function to urldecode strings
|
|
||||||
*
|
|
||||||
* See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/decodeURIComponent#Decoding_query_parameters_from_a_URL
|
|
||||||
*/
|
|
||||||
jQuery.urldecode = function(x) {
|
|
||||||
if (!x) {
|
|
||||||
return x
|
|
||||||
}
|
|
||||||
return decodeURIComponent(x.replace(/\+/g, ' '));
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* small helper function to urlencode strings
|
|
||||||
*/
|
|
||||||
jQuery.urlencode = encodeURIComponent;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This function returns the parsed url parameters of the
|
|
||||||
* current request. Multiple values per key are supported,
|
|
||||||
* it will always return arrays of strings for the value parts.
|
|
||||||
*/
|
|
||||||
jQuery.getQueryParameters = function(s) {
|
|
||||||
if (typeof s === 'undefined')
|
|
||||||
s = document.location.search;
|
|
||||||
var parts = s.substr(s.indexOf('?') + 1).split('&');
|
|
||||||
var result = {};
|
|
||||||
for (var i = 0; i < parts.length; i++) {
|
|
||||||
var tmp = parts[i].split('=', 2);
|
|
||||||
var key = jQuery.urldecode(tmp[0]);
|
|
||||||
var value = jQuery.urldecode(tmp[1]);
|
|
||||||
if (key in result)
|
|
||||||
result[key].push(value);
|
|
||||||
else
|
|
||||||
result[key] = [value];
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* highlight a given string on a jquery object by wrapping it in
|
|
||||||
* span elements with the given class name.
|
|
||||||
*/
|
|
||||||
jQuery.fn.highlightText = function(text, className) {
|
|
||||||
function highlight(node, addItems) {
|
|
||||||
if (node.nodeType === 3) {
|
|
||||||
var val = node.nodeValue;
|
|
||||||
var pos = val.toLowerCase().indexOf(text);
|
|
||||||
if (pos >= 0 &&
|
|
||||||
!jQuery(node.parentNode).hasClass(className) &&
|
|
||||||
!jQuery(node.parentNode).hasClass("nohighlight")) {
|
|
||||||
var span;
|
|
||||||
var isInSVG = jQuery(node).closest("body, svg, foreignObject").is("svg");
|
|
||||||
if (isInSVG) {
|
|
||||||
span = document.createElementNS("http://www.w3.org/2000/svg", "tspan");
|
|
||||||
} else {
|
|
||||||
span = document.createElement("span");
|
|
||||||
span.className = className;
|
|
||||||
}
|
|
||||||
span.appendChild(document.createTextNode(val.substr(pos, text.length)));
|
|
||||||
node.parentNode.insertBefore(span, node.parentNode.insertBefore(
|
|
||||||
document.createTextNode(val.substr(pos + text.length)),
|
|
||||||
node.nextSibling));
|
|
||||||
node.nodeValue = val.substr(0, pos);
|
|
||||||
if (isInSVG) {
|
|
||||||
var rect = document.createElementNS("http://www.w3.org/2000/svg", "rect");
|
|
||||||
var bbox = node.parentElement.getBBox();
|
|
||||||
rect.x.baseVal.value = bbox.x;
|
|
||||||
rect.y.baseVal.value = bbox.y;
|
|
||||||
rect.width.baseVal.value = bbox.width;
|
|
||||||
rect.height.baseVal.value = bbox.height;
|
|
||||||
rect.setAttribute('class', className);
|
|
||||||
addItems.push({
|
|
||||||
"parent": node.parentNode,
|
|
||||||
"target": rect});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (!jQuery(node).is("button, select, textarea")) {
|
|
||||||
jQuery.each(node.childNodes, function() {
|
|
||||||
highlight(this, addItems);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var addItems = [];
|
|
||||||
var result = this.each(function() {
|
|
||||||
highlight(this, addItems);
|
|
||||||
});
|
|
||||||
for (var i = 0; i < addItems.length; ++i) {
|
|
||||||
jQuery(addItems[i].parent).before(addItems[i].target);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
};
|
|
||||||
|
|
||||||
/*
|
|
||||||
* backward compatibility for jQuery.browser
|
|
||||||
* This will be supported until firefox bug is fixed.
|
|
||||||
*/
|
|
||||||
if (!jQuery.browser) {
|
|
||||||
jQuery.uaMatch = function(ua) {
|
|
||||||
ua = ua.toLowerCase();
|
|
||||||
|
|
||||||
var match = /(chrome)[ \/]([\w.]+)/.exec(ua) ||
|
|
||||||
/(webkit)[ \/]([\w.]+)/.exec(ua) ||
|
|
||||||
/(opera)(?:.*version|)[ \/]([\w.]+)/.exec(ua) ||
|
|
||||||
/(msie) ([\w.]+)/.exec(ua) ||
|
|
||||||
ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec(ua) ||
|
|
||||||
[];
|
|
||||||
|
|
||||||
return {
|
|
||||||
browser: match[ 1 ] || "",
|
|
||||||
version: match[ 2 ] || "0"
|
|
||||||
};
|
|
||||||
};
|
|
||||||
jQuery.browser = {};
|
|
||||||
jQuery.browser[jQuery.uaMatch(navigator.userAgent).browser] = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Small JavaScript module for the documentation.
|
|
||||||
*/
|
|
||||||
var Documentation = {
|
|
||||||
|
|
||||||
init : function() {
|
|
||||||
this.fixFirefoxAnchorBug();
|
|
||||||
this.highlightSearchWords();
|
|
||||||
this.initIndexTable();
|
|
||||||
if (DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) {
|
|
||||||
this.initOnKeyListeners();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* i18n support
|
|
||||||
*/
|
|
||||||
TRANSLATIONS : {},
|
|
||||||
PLURAL_EXPR : function(n) { return n === 1 ? 0 : 1; },
|
|
||||||
LOCALE : 'unknown',
|
|
||||||
|
|
||||||
// gettext and ngettext don't access this so that the functions
|
|
||||||
// can safely bound to a different name (_ = Documentation.gettext)
|
|
||||||
gettext : function(string) {
|
|
||||||
var translated = Documentation.TRANSLATIONS[string];
|
|
||||||
if (typeof translated === 'undefined')
|
|
||||||
return string;
|
|
||||||
return (typeof translated === 'string') ? translated : translated[0];
|
|
||||||
},
|
|
||||||
|
|
||||||
ngettext : function(singular, plural, n) {
|
|
||||||
var translated = Documentation.TRANSLATIONS[singular];
|
|
||||||
if (typeof translated === 'undefined')
|
|
||||||
return (n == 1) ? singular : plural;
|
|
||||||
return translated[Documentation.PLURALEXPR(n)];
|
|
||||||
},
|
|
||||||
|
|
||||||
addTranslations : function(catalog) {
|
|
||||||
for (var key in catalog.messages)
|
|
||||||
this.TRANSLATIONS[key] = catalog.messages[key];
|
|
||||||
this.PLURAL_EXPR = new Function('n', 'return +(' + catalog.plural_expr + ')');
|
|
||||||
this.LOCALE = catalog.locale;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* add context elements like header anchor links
|
|
||||||
*/
|
|
||||||
addContextElements : function() {
|
|
||||||
$('div[id] > :header:first').each(function() {
|
|
||||||
$('<a class="headerlink">\u00B6</a>').
|
|
||||||
attr('href', '#' + this.id).
|
|
||||||
attr('title', _('Permalink to this headline')).
|
|
||||||
appendTo(this);
|
|
||||||
});
|
|
||||||
$('dt[id]').each(function() {
|
|
||||||
$('<a class="headerlink">\u00B6</a>').
|
|
||||||
attr('href', '#' + this.id).
|
|
||||||
attr('title', _('Permalink to this definition')).
|
|
||||||
appendTo(this);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* workaround a firefox stupidity
|
|
||||||
* see: https://bugzilla.mozilla.org/show_bug.cgi?id=645075
|
|
||||||
*/
|
|
||||||
fixFirefoxAnchorBug : function() {
|
|
||||||
if (document.location.hash && $.browser.mozilla)
|
|
||||||
window.setTimeout(function() {
|
|
||||||
document.location.href += '';
|
|
||||||
}, 10);
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* highlight the search words provided in the url in the text
|
|
||||||
*/
|
|
||||||
highlightSearchWords : function() {
|
|
||||||
var params = $.getQueryParameters();
|
|
||||||
var terms = (params.highlight) ? params.highlight[0].split(/\s+/) : [];
|
|
||||||
if (terms.length) {
|
|
||||||
var body = $('div.body');
|
|
||||||
if (!body.length) {
|
|
||||||
body = $('body');
|
|
||||||
}
|
|
||||||
window.setTimeout(function() {
|
|
||||||
$.each(terms, function() {
|
|
||||||
body.highlightText(this.toLowerCase(), 'highlighted');
|
|
||||||
});
|
|
||||||
}, 10);
|
|
||||||
$('<p class="highlight-link"><a href="javascript:Documentation.' +
|
|
||||||
'hideSearchWords()">' + _('Hide Search Matches') + '</a></p>')
|
|
||||||
.appendTo($('#searchbox'));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* init the domain index toggle buttons
|
|
||||||
*/
|
|
||||||
initIndexTable : function() {
|
|
||||||
var togglers = $('img.toggler').click(function() {
|
|
||||||
var src = $(this).attr('src');
|
|
||||||
var idnum = $(this).attr('id').substr(7);
|
|
||||||
$('tr.cg-' + idnum).toggle();
|
|
||||||
if (src.substr(-9) === 'minus.png')
|
|
||||||
$(this).attr('src', src.substr(0, src.length-9) + 'plus.png');
|
|
||||||
else
|
|
||||||
$(this).attr('src', src.substr(0, src.length-8) + 'minus.png');
|
|
||||||
}).css('display', '');
|
|
||||||
if (DOCUMENTATION_OPTIONS.COLLAPSE_INDEX) {
|
|
||||||
togglers.click();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* helper function to hide the search marks again
|
|
||||||
*/
|
|
||||||
hideSearchWords : function() {
|
|
||||||
$('#searchbox .highlight-link').fadeOut(300);
|
|
||||||
$('span.highlighted').removeClass('highlighted');
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* make the url absolute
|
|
||||||
*/
|
|
||||||
makeURL : function(relativeURL) {
|
|
||||||
return DOCUMENTATION_OPTIONS.URL_ROOT + '/' + relativeURL;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* get the current relative url
|
|
||||||
*/
|
|
||||||
getCurrentURL : function() {
|
|
||||||
var path = document.location.pathname;
|
|
||||||
var parts = path.split(/\//);
|
|
||||||
$.each(DOCUMENTATION_OPTIONS.URL_ROOT.split(/\//), function() {
|
|
||||||
if (this === '..')
|
|
||||||
parts.pop();
|
|
||||||
});
|
|
||||||
var url = parts.join('/');
|
|
||||||
return path.substring(url.lastIndexOf('/') + 1, path.length - 1);
|
|
||||||
},
|
|
||||||
|
|
||||||
initOnKeyListeners: function() {
|
|
||||||
$(document).keydown(function(event) {
|
|
||||||
var activeElementType = document.activeElement.tagName;
|
|
||||||
// don't navigate when in search box, textarea, dropdown or button
|
|
||||||
if (activeElementType !== 'TEXTAREA' && activeElementType !== 'INPUT' && activeElementType !== 'SELECT'
|
|
||||||
&& activeElementType !== 'BUTTON' && !event.altKey && !event.ctrlKey && !event.metaKey
|
|
||||||
&& !event.shiftKey) {
|
|
||||||
switch (event.keyCode) {
|
|
||||||
case 37: // left
|
|
||||||
var prevHref = $('link[rel="prev"]').prop('href');
|
|
||||||
if (prevHref) {
|
|
||||||
window.location.href = prevHref;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 39: // right
|
|
||||||
var nextHref = $('link[rel="next"]').prop('href');
|
|
||||||
if (nextHref) {
|
|
||||||
window.location.href = nextHref;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// quick alias for translations
|
|
||||||
_ = Documentation.gettext;
|
|
||||||
|
|
||||||
$(document).ready(function() {
|
|
||||||
Documentation.init();
|
|
||||||
});
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
var DOCUMENTATION_OPTIONS = {
|
|
||||||
URL_ROOT: document.getElementById("documentation_options").getAttribute('data-url_root'),
|
|
||||||
VERSION: '1.1.1',
|
|
||||||
LANGUAGE: 'None',
|
|
||||||
COLLAPSE_INDEX: false,
|
|
||||||
BUILDER: 'html',
|
|
||||||
FILE_SUFFIX: '.html',
|
|
||||||
LINK_SUFFIX: '.html',
|
|
||||||
HAS_SOURCE: true,
|
|
||||||
SOURCELINK_SUFFIX: '.txt',
|
|
||||||
NAVIGATION_WITH_KEYS: false
|
|
||||||
};
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 286 B |
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
|
|
@ -1,297 +0,0 @@
|
||||||
/*
|
|
||||||
* language_data.js
|
|
||||||
* ~~~~~~~~~~~~~~~~
|
|
||||||
*
|
|
||||||
* This script contains the language-specific data used by searchtools.js,
|
|
||||||
* namely the list of stopwords, stemmer, scorer and splitter.
|
|
||||||
*
|
|
||||||
* :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS.
|
|
||||||
* :license: BSD, see LICENSE for details.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
var stopwords = ["a","and","are","as","at","be","but","by","for","if","in","into","is","it","near","no","not","of","on","or","such","that","the","their","then","there","these","they","this","to","was","will","with"];
|
|
||||||
|
|
||||||
|
|
||||||
/* Non-minified version is copied as a separate JS file, is available */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Porter Stemmer
|
|
||||||
*/
|
|
||||||
var Stemmer = function() {
|
|
||||||
|
|
||||||
var step2list = {
|
|
||||||
ational: 'ate',
|
|
||||||
tional: 'tion',
|
|
||||||
enci: 'ence',
|
|
||||||
anci: 'ance',
|
|
||||||
izer: 'ize',
|
|
||||||
bli: 'ble',
|
|
||||||
alli: 'al',
|
|
||||||
entli: 'ent',
|
|
||||||
eli: 'e',
|
|
||||||
ousli: 'ous',
|
|
||||||
ization: 'ize',
|
|
||||||
ation: 'ate',
|
|
||||||
ator: 'ate',
|
|
||||||
alism: 'al',
|
|
||||||
iveness: 'ive',
|
|
||||||
fulness: 'ful',
|
|
||||||
ousness: 'ous',
|
|
||||||
aliti: 'al',
|
|
||||||
iviti: 'ive',
|
|
||||||
biliti: 'ble',
|
|
||||||
logi: 'log'
|
|
||||||
};
|
|
||||||
|
|
||||||
var step3list = {
|
|
||||||
icate: 'ic',
|
|
||||||
ative: '',
|
|
||||||
alize: 'al',
|
|
||||||
iciti: 'ic',
|
|
||||||
ical: 'ic',
|
|
||||||
ful: '',
|
|
||||||
ness: ''
|
|
||||||
};
|
|
||||||
|
|
||||||
var c = "[^aeiou]"; // consonant
|
|
||||||
var v = "[aeiouy]"; // vowel
|
|
||||||
var C = c + "[^aeiouy]*"; // consonant sequence
|
|
||||||
var V = v + "[aeiou]*"; // vowel sequence
|
|
||||||
|
|
||||||
var mgr0 = "^(" + C + ")?" + V + C; // [C]VC... is m>0
|
|
||||||
var meq1 = "^(" + C + ")?" + V + C + "(" + V + ")?$"; // [C]VC[V] is m=1
|
|
||||||
var mgr1 = "^(" + C + ")?" + V + C + V + C; // [C]VCVC... is m>1
|
|
||||||
var s_v = "^(" + C + ")?" + v; // vowel in stem
|
|
||||||
|
|
||||||
this.stemWord = function (w) {
|
|
||||||
var stem;
|
|
||||||
var suffix;
|
|
||||||
var firstch;
|
|
||||||
var origword = w;
|
|
||||||
|
|
||||||
if (w.length < 3)
|
|
||||||
return w;
|
|
||||||
|
|
||||||
var re;
|
|
||||||
var re2;
|
|
||||||
var re3;
|
|
||||||
var re4;
|
|
||||||
|
|
||||||
firstch = w.substr(0,1);
|
|
||||||
if (firstch == "y")
|
|
||||||
w = firstch.toUpperCase() + w.substr(1);
|
|
||||||
|
|
||||||
// Step 1a
|
|
||||||
re = /^(.+?)(ss|i)es$/;
|
|
||||||
re2 = /^(.+?)([^s])s$/;
|
|
||||||
|
|
||||||
if (re.test(w))
|
|
||||||
w = w.replace(re,"$1$2");
|
|
||||||
else if (re2.test(w))
|
|
||||||
w = w.replace(re2,"$1$2");
|
|
||||||
|
|
||||||
// Step 1b
|
|
||||||
re = /^(.+?)eed$/;
|
|
||||||
re2 = /^(.+?)(ed|ing)$/;
|
|
||||||
if (re.test(w)) {
|
|
||||||
var fp = re.exec(w);
|
|
||||||
re = new RegExp(mgr0);
|
|
||||||
if (re.test(fp[1])) {
|
|
||||||
re = /.$/;
|
|
||||||
w = w.replace(re,"");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (re2.test(w)) {
|
|
||||||
var fp = re2.exec(w);
|
|
||||||
stem = fp[1];
|
|
||||||
re2 = new RegExp(s_v);
|
|
||||||
if (re2.test(stem)) {
|
|
||||||
w = stem;
|
|
||||||
re2 = /(at|bl|iz)$/;
|
|
||||||
re3 = new RegExp("([^aeiouylsz])\\1$");
|
|
||||||
re4 = new RegExp("^" + C + v + "[^aeiouwxy]$");
|
|
||||||
if (re2.test(w))
|
|
||||||
w = w + "e";
|
|
||||||
else if (re3.test(w)) {
|
|
||||||
re = /.$/;
|
|
||||||
w = w.replace(re,"");
|
|
||||||
}
|
|
||||||
else if (re4.test(w))
|
|
||||||
w = w + "e";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Step 1c
|
|
||||||
re = /^(.+?)y$/;
|
|
||||||
if (re.test(w)) {
|
|
||||||
var fp = re.exec(w);
|
|
||||||
stem = fp[1];
|
|
||||||
re = new RegExp(s_v);
|
|
||||||
if (re.test(stem))
|
|
||||||
w = stem + "i";
|
|
||||||
}
|
|
||||||
|
|
||||||
// Step 2
|
|
||||||
re = /^(.+?)(ational|tional|enci|anci|izer|bli|alli|entli|eli|ousli|ization|ation|ator|alism|iveness|fulness|ousness|aliti|iviti|biliti|logi)$/;
|
|
||||||
if (re.test(w)) {
|
|
||||||
var fp = re.exec(w);
|
|
||||||
stem = fp[1];
|
|
||||||
suffix = fp[2];
|
|
||||||
re = new RegExp(mgr0);
|
|
||||||
if (re.test(stem))
|
|
||||||
w = stem + step2list[suffix];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Step 3
|
|
||||||
re = /^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$/;
|
|
||||||
if (re.test(w)) {
|
|
||||||
var fp = re.exec(w);
|
|
||||||
stem = fp[1];
|
|
||||||
suffix = fp[2];
|
|
||||||
re = new RegExp(mgr0);
|
|
||||||
if (re.test(stem))
|
|
||||||
w = stem + step3list[suffix];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Step 4
|
|
||||||
re = /^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|iti|ous|ive|ize)$/;
|
|
||||||
re2 = /^(.+?)(s|t)(ion)$/;
|
|
||||||
if (re.test(w)) {
|
|
||||||
var fp = re.exec(w);
|
|
||||||
stem = fp[1];
|
|
||||||
re = new RegExp(mgr1);
|
|
||||||
if (re.test(stem))
|
|
||||||
w = stem;
|
|
||||||
}
|
|
||||||
else if (re2.test(w)) {
|
|
||||||
var fp = re2.exec(w);
|
|
||||||
stem = fp[1] + fp[2];
|
|
||||||
re2 = new RegExp(mgr1);
|
|
||||||
if (re2.test(stem))
|
|
||||||
w = stem;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Step 5
|
|
||||||
re = /^(.+?)e$/;
|
|
||||||
if (re.test(w)) {
|
|
||||||
var fp = re.exec(w);
|
|
||||||
stem = fp[1];
|
|
||||||
re = new RegExp(mgr1);
|
|
||||||
re2 = new RegExp(meq1);
|
|
||||||
re3 = new RegExp("^" + C + v + "[^aeiouwxy]$");
|
|
||||||
if (re.test(stem) || (re2.test(stem) && !(re3.test(stem))))
|
|
||||||
w = stem;
|
|
||||||
}
|
|
||||||
re = /ll$/;
|
|
||||||
re2 = new RegExp(mgr1);
|
|
||||||
if (re.test(w) && re2.test(w)) {
|
|
||||||
re = /.$/;
|
|
||||||
w = w.replace(re,"");
|
|
||||||
}
|
|
||||||
|
|
||||||
// and turn initial Y back to y
|
|
||||||
if (firstch == "y")
|
|
||||||
w = firstch.toLowerCase() + w.substr(1);
|
|
||||||
return w;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
var splitChars = (function() {
|
|
||||||
var result = {};
|
|
||||||
var singles = [96, 180, 187, 191, 215, 247, 749, 885, 903, 907, 909, 930, 1014, 1648,
|
|
||||||
1748, 1809, 2416, 2473, 2481, 2526, 2601, 2609, 2612, 2615, 2653, 2702,
|
|
||||||
2706, 2729, 2737, 2740, 2857, 2865, 2868, 2910, 2928, 2948, 2961, 2971,
|
|
||||||
2973, 3085, 3089, 3113, 3124, 3213, 3217, 3241, 3252, 3295, 3341, 3345,
|
|
||||||
3369, 3506, 3516, 3633, 3715, 3721, 3736, 3744, 3748, 3750, 3756, 3761,
|
|
||||||
3781, 3912, 4239, 4347, 4681, 4695, 4697, 4745, 4785, 4799, 4801, 4823,
|
|
||||||
4881, 5760, 5901, 5997, 6313, 7405, 8024, 8026, 8028, 8030, 8117, 8125,
|
|
||||||
8133, 8181, 8468, 8485, 8487, 8489, 8494, 8527, 11311, 11359, 11687, 11695,
|
|
||||||
11703, 11711, 11719, 11727, 11735, 12448, 12539, 43010, 43014, 43019, 43587,
|
|
||||||
43696, 43713, 64286, 64297, 64311, 64317, 64319, 64322, 64325, 65141];
|
|
||||||
var i, j, start, end;
|
|
||||||
for (i = 0; i < singles.length; i++) {
|
|
||||||
result[singles[i]] = true;
|
|
||||||
}
|
|
||||||
var ranges = [[0, 47], [58, 64], [91, 94], [123, 169], [171, 177], [182, 184], [706, 709],
|
|
||||||
[722, 735], [741, 747], [751, 879], [888, 889], [894, 901], [1154, 1161],
|
|
||||||
[1318, 1328], [1367, 1368], [1370, 1376], [1416, 1487], [1515, 1519], [1523, 1568],
|
|
||||||
[1611, 1631], [1642, 1645], [1750, 1764], [1767, 1773], [1789, 1790], [1792, 1807],
|
|
||||||
[1840, 1868], [1958, 1968], [1970, 1983], [2027, 2035], [2038, 2041], [2043, 2047],
|
|
||||||
[2070, 2073], [2075, 2083], [2085, 2087], [2089, 2307], [2362, 2364], [2366, 2383],
|
|
||||||
[2385, 2391], [2402, 2405], [2419, 2424], [2432, 2436], [2445, 2446], [2449, 2450],
|
|
||||||
[2483, 2485], [2490, 2492], [2494, 2509], [2511, 2523], [2530, 2533], [2546, 2547],
|
|
||||||
[2554, 2564], [2571, 2574], [2577, 2578], [2618, 2648], [2655, 2661], [2672, 2673],
|
|
||||||
[2677, 2692], [2746, 2748], [2750, 2767], [2769, 2783], [2786, 2789], [2800, 2820],
|
|
||||||
[2829, 2830], [2833, 2834], [2874, 2876], [2878, 2907], [2914, 2917], [2930, 2946],
|
|
||||||
[2955, 2957], [2966, 2968], [2976, 2978], [2981, 2983], [2987, 2989], [3002, 3023],
|
|
||||||
[3025, 3045], [3059, 3076], [3130, 3132], [3134, 3159], [3162, 3167], [3170, 3173],
|
|
||||||
[3184, 3191], [3199, 3204], [3258, 3260], [3262, 3293], [3298, 3301], [3312, 3332],
|
|
||||||
[3386, 3388], [3390, 3423], [3426, 3429], [3446, 3449], [3456, 3460], [3479, 3481],
|
|
||||||
[3518, 3519], [3527, 3584], [3636, 3647], [3655, 3663], [3674, 3712], [3717, 3718],
|
|
||||||
[3723, 3724], [3726, 3731], [3752, 3753], [3764, 3772], [3774, 3775], [3783, 3791],
|
|
||||||
[3802, 3803], [3806, 3839], [3841, 3871], [3892, 3903], [3949, 3975], [3980, 4095],
|
|
||||||
[4139, 4158], [4170, 4175], [4182, 4185], [4190, 4192], [4194, 4196], [4199, 4205],
|
|
||||||
[4209, 4212], [4226, 4237], [4250, 4255], [4294, 4303], [4349, 4351], [4686, 4687],
|
|
||||||
[4702, 4703], [4750, 4751], [4790, 4791], [4806, 4807], [4886, 4887], [4955, 4968],
|
|
||||||
[4989, 4991], [5008, 5023], [5109, 5120], [5741, 5742], [5787, 5791], [5867, 5869],
|
|
||||||
[5873, 5887], [5906, 5919], [5938, 5951], [5970, 5983], [6001, 6015], [6068, 6102],
|
|
||||||
[6104, 6107], [6109, 6111], [6122, 6127], [6138, 6159], [6170, 6175], [6264, 6271],
|
|
||||||
[6315, 6319], [6390, 6399], [6429, 6469], [6510, 6511], [6517, 6527], [6572, 6592],
|
|
||||||
[6600, 6607], [6619, 6655], [6679, 6687], [6741, 6783], [6794, 6799], [6810, 6822],
|
|
||||||
[6824, 6916], [6964, 6980], [6988, 6991], [7002, 7042], [7073, 7085], [7098, 7167],
|
|
||||||
[7204, 7231], [7242, 7244], [7294, 7400], [7410, 7423], [7616, 7679], [7958, 7959],
|
|
||||||
[7966, 7967], [8006, 8007], [8014, 8015], [8062, 8063], [8127, 8129], [8141, 8143],
|
|
||||||
[8148, 8149], [8156, 8159], [8173, 8177], [8189, 8303], [8306, 8307], [8314, 8318],
|
|
||||||
[8330, 8335], [8341, 8449], [8451, 8454], [8456, 8457], [8470, 8472], [8478, 8483],
|
|
||||||
[8506, 8507], [8512, 8516], [8522, 8525], [8586, 9311], [9372, 9449], [9472, 10101],
|
|
||||||
[10132, 11263], [11493, 11498], [11503, 11516], [11518, 11519], [11558, 11567],
|
|
||||||
[11622, 11630], [11632, 11647], [11671, 11679], [11743, 11822], [11824, 12292],
|
|
||||||
[12296, 12320], [12330, 12336], [12342, 12343], [12349, 12352], [12439, 12444],
|
|
||||||
[12544, 12548], [12590, 12592], [12687, 12689], [12694, 12703], [12728, 12783],
|
|
||||||
[12800, 12831], [12842, 12880], [12896, 12927], [12938, 12976], [12992, 13311],
|
|
||||||
[19894, 19967], [40908, 40959], [42125, 42191], [42238, 42239], [42509, 42511],
|
|
||||||
[42540, 42559], [42592, 42593], [42607, 42622], [42648, 42655], [42736, 42774],
|
|
||||||
[42784, 42785], [42889, 42890], [42893, 43002], [43043, 43055], [43062, 43071],
|
|
||||||
[43124, 43137], [43188, 43215], [43226, 43249], [43256, 43258], [43260, 43263],
|
|
||||||
[43302, 43311], [43335, 43359], [43389, 43395], [43443, 43470], [43482, 43519],
|
|
||||||
[43561, 43583], [43596, 43599], [43610, 43615], [43639, 43641], [43643, 43647],
|
|
||||||
[43698, 43700], [43703, 43704], [43710, 43711], [43715, 43738], [43742, 43967],
|
|
||||||
[44003, 44015], [44026, 44031], [55204, 55215], [55239, 55242], [55292, 55295],
|
|
||||||
[57344, 63743], [64046, 64047], [64110, 64111], [64218, 64255], [64263, 64274],
|
|
||||||
[64280, 64284], [64434, 64466], [64830, 64847], [64912, 64913], [64968, 65007],
|
|
||||||
[65020, 65135], [65277, 65295], [65306, 65312], [65339, 65344], [65371, 65381],
|
|
||||||
[65471, 65473], [65480, 65481], [65488, 65489], [65496, 65497]];
|
|
||||||
for (i = 0; i < ranges.length; i++) {
|
|
||||||
start = ranges[i][0];
|
|
||||||
end = ranges[i][1];
|
|
||||||
for (j = start; j <= end; j++) {
|
|
||||||
result[j] = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
})();
|
|
||||||
|
|
||||||
function splitQuery(query) {
|
|
||||||
var result = [];
|
|
||||||
var start = -1;
|
|
||||||
for (var i = 0; i < query.length; i++) {
|
|
||||||
if (splitChars[query.charCodeAt(i)]) {
|
|
||||||
if (start !== -1) {
|
|
||||||
result.push(query.slice(start, i));
|
|
||||||
start = -1;
|
|
||||||
}
|
|
||||||
} else if (start === -1) {
|
|
||||||
start = i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (start !== -1) {
|
|
||||||
result.push(query.slice(start));
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 90 B |
Binary file not shown.
|
Before Width: | Height: | Size: 90 B |
|
|
@ -1,82 +0,0 @@
|
||||||
pre { line-height: 125%; }
|
|
||||||
td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
|
|
||||||
span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
|
|
||||||
td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
|
|
||||||
span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
|
|
||||||
.highlight .hll { background-color: #ffffcc }
|
|
||||||
.highlight { background: #f8f8f8; }
|
|
||||||
.highlight .c { color: #8f5902; font-style: italic } /* Comment */
|
|
||||||
.highlight .err { color: #a40000; border: 1px solid #ef2929 } /* Error */
|
|
||||||
.highlight .g { color: #000000 } /* Generic */
|
|
||||||
.highlight .k { color: #004461; font-weight: bold } /* Keyword */
|
|
||||||
.highlight .l { color: #000000 } /* Literal */
|
|
||||||
.highlight .n { color: #000000 } /* Name */
|
|
||||||
.highlight .o { color: #582800 } /* Operator */
|
|
||||||
.highlight .x { color: #000000 } /* Other */
|
|
||||||
.highlight .p { color: #000000; font-weight: bold } /* Punctuation */
|
|
||||||
.highlight .ch { color: #8f5902; font-style: italic } /* Comment.Hashbang */
|
|
||||||
.highlight .cm { color: #8f5902; font-style: italic } /* Comment.Multiline */
|
|
||||||
.highlight .cp { color: #8f5902 } /* Comment.Preproc */
|
|
||||||
.highlight .cpf { color: #8f5902; font-style: italic } /* Comment.PreprocFile */
|
|
||||||
.highlight .c1 { color: #8f5902; font-style: italic } /* Comment.Single */
|
|
||||||
.highlight .cs { color: #8f5902; font-style: italic } /* Comment.Special */
|
|
||||||
.highlight .gd { color: #a40000 } /* Generic.Deleted */
|
|
||||||
.highlight .ge { color: #000000; font-style: italic } /* Generic.Emph */
|
|
||||||
.highlight .gr { color: #ef2929 } /* Generic.Error */
|
|
||||||
.highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */
|
|
||||||
.highlight .gi { color: #00A000 } /* Generic.Inserted */
|
|
||||||
.highlight .go { color: #888888 } /* Generic.Output */
|
|
||||||
.highlight .gp { color: #745334 } /* Generic.Prompt */
|
|
||||||
.highlight .gs { color: #000000; font-weight: bold } /* Generic.Strong */
|
|
||||||
.highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */
|
|
||||||
.highlight .gt { color: #a40000; font-weight: bold } /* Generic.Traceback */
|
|
||||||
.highlight .kc { color: #004461; font-weight: bold } /* Keyword.Constant */
|
|
||||||
.highlight .kd { color: #004461; font-weight: bold } /* Keyword.Declaration */
|
|
||||||
.highlight .kn { color: #004461; font-weight: bold } /* Keyword.Namespace */
|
|
||||||
.highlight .kp { color: #004461; font-weight: bold } /* Keyword.Pseudo */
|
|
||||||
.highlight .kr { color: #004461; font-weight: bold } /* Keyword.Reserved */
|
|
||||||
.highlight .kt { color: #004461; font-weight: bold } /* Keyword.Type */
|
|
||||||
.highlight .ld { color: #000000 } /* Literal.Date */
|
|
||||||
.highlight .m { color: #990000 } /* Literal.Number */
|
|
||||||
.highlight .s { color: #4e9a06 } /* Literal.String */
|
|
||||||
.highlight .na { color: #c4a000 } /* Name.Attribute */
|
|
||||||
.highlight .nb { color: #004461 } /* Name.Builtin */
|
|
||||||
.highlight .nc { color: #000000 } /* Name.Class */
|
|
||||||
.highlight .no { color: #000000 } /* Name.Constant */
|
|
||||||
.highlight .nd { color: #888888 } /* Name.Decorator */
|
|
||||||
.highlight .ni { color: #ce5c00 } /* Name.Entity */
|
|
||||||
.highlight .ne { color: #cc0000; font-weight: bold } /* Name.Exception */
|
|
||||||
.highlight .nf { color: #000000 } /* Name.Function */
|
|
||||||
.highlight .nl { color: #f57900 } /* Name.Label */
|
|
||||||
.highlight .nn { color: #000000 } /* Name.Namespace */
|
|
||||||
.highlight .nx { color: #000000 } /* Name.Other */
|
|
||||||
.highlight .py { color: #000000 } /* Name.Property */
|
|
||||||
.highlight .nt { color: #004461; font-weight: bold } /* Name.Tag */
|
|
||||||
.highlight .nv { color: #000000 } /* Name.Variable */
|
|
||||||
.highlight .ow { color: #004461; font-weight: bold } /* Operator.Word */
|
|
||||||
.highlight .w { color: #f8f8f8; text-decoration: underline } /* Text.Whitespace */
|
|
||||||
.highlight .mb { color: #990000 } /* Literal.Number.Bin */
|
|
||||||
.highlight .mf { color: #990000 } /* Literal.Number.Float */
|
|
||||||
.highlight .mh { color: #990000 } /* Literal.Number.Hex */
|
|
||||||
.highlight .mi { color: #990000 } /* Literal.Number.Integer */
|
|
||||||
.highlight .mo { color: #990000 } /* Literal.Number.Oct */
|
|
||||||
.highlight .sa { color: #4e9a06 } /* Literal.String.Affix */
|
|
||||||
.highlight .sb { color: #4e9a06 } /* Literal.String.Backtick */
|
|
||||||
.highlight .sc { color: #4e9a06 } /* Literal.String.Char */
|
|
||||||
.highlight .dl { color: #4e9a06 } /* Literal.String.Delimiter */
|
|
||||||
.highlight .sd { color: #8f5902; font-style: italic } /* Literal.String.Doc */
|
|
||||||
.highlight .s2 { color: #4e9a06 } /* Literal.String.Double */
|
|
||||||
.highlight .se { color: #4e9a06 } /* Literal.String.Escape */
|
|
||||||
.highlight .sh { color: #4e9a06 } /* Literal.String.Heredoc */
|
|
||||||
.highlight .si { color: #4e9a06 } /* Literal.String.Interpol */
|
|
||||||
.highlight .sx { color: #4e9a06 } /* Literal.String.Other */
|
|
||||||
.highlight .sr { color: #4e9a06 } /* Literal.String.Regex */
|
|
||||||
.highlight .s1 { color: #4e9a06 } /* Literal.String.Single */
|
|
||||||
.highlight .ss { color: #4e9a06 } /* Literal.String.Symbol */
|
|
||||||
.highlight .bp { color: #3465a4 } /* Name.Builtin.Pseudo */
|
|
||||||
.highlight .fm { color: #000000 } /* Name.Function.Magic */
|
|
||||||
.highlight .vc { color: #000000 } /* Name.Variable.Class */
|
|
||||||
.highlight .vg { color: #000000 } /* Name.Variable.Global */
|
|
||||||
.highlight .vi { color: #000000 } /* Name.Variable.Instance */
|
|
||||||
.highlight .vm { color: #000000 } /* Name.Variable.Magic */
|
|
||||||
.highlight .il { color: #990000 } /* Literal.Number.Integer.Long */
|
|
||||||
|
|
@ -1,528 +0,0 @@
|
||||||
/*
|
|
||||||
* searchtools.js
|
|
||||||
* ~~~~~~~~~~~~~~~~
|
|
||||||
*
|
|
||||||
* Sphinx JavaScript utilities for the full-text search.
|
|
||||||
*
|
|
||||||
* :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS.
|
|
||||||
* :license: BSD, see LICENSE for details.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
if (!Scorer) {
|
|
||||||
/**
|
|
||||||
* Simple result scoring code.
|
|
||||||
*/
|
|
||||||
var Scorer = {
|
|
||||||
// Implement the following function to further tweak the score for each result
|
|
||||||
// The function takes a result array [filename, title, anchor, descr, score]
|
|
||||||
// and returns the new score.
|
|
||||||
/*
|
|
||||||
score: function(result) {
|
|
||||||
return result[4];
|
|
||||||
},
|
|
||||||
*/
|
|
||||||
|
|
||||||
// query matches the full name of an object
|
|
||||||
objNameMatch: 11,
|
|
||||||
// or matches in the last dotted part of the object name
|
|
||||||
objPartialMatch: 6,
|
|
||||||
// Additive scores depending on the priority of the object
|
|
||||||
objPrio: {0: 15, // used to be importantResults
|
|
||||||
1: 5, // used to be objectResults
|
|
||||||
2: -5}, // used to be unimportantResults
|
|
||||||
// Used when the priority is not in the mapping.
|
|
||||||
objPrioDefault: 0,
|
|
||||||
|
|
||||||
// query found in title
|
|
||||||
title: 15,
|
|
||||||
partialTitle: 7,
|
|
||||||
// query found in terms
|
|
||||||
term: 5,
|
|
||||||
partialTerm: 2
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!splitQuery) {
|
|
||||||
function splitQuery(query) {
|
|
||||||
return query.split(/\s+/);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Search Module
|
|
||||||
*/
|
|
||||||
var Search = {
|
|
||||||
|
|
||||||
_index : null,
|
|
||||||
_queued_query : null,
|
|
||||||
_pulse_status : -1,
|
|
||||||
|
|
||||||
htmlToText : function(htmlString) {
|
|
||||||
var virtualDocument = document.implementation.createHTMLDocument('virtual');
|
|
||||||
var htmlElement = $(htmlString, virtualDocument);
|
|
||||||
htmlElement.find('.headerlink').remove();
|
|
||||||
docContent = htmlElement.find('[role=main]')[0];
|
|
||||||
if(docContent === undefined) {
|
|
||||||
console.warn("Content block not found. Sphinx search tries to obtain it " +
|
|
||||||
"via '[role=main]'. Could you check your theme or template.");
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
return docContent.textContent || docContent.innerText;
|
|
||||||
},
|
|
||||||
|
|
||||||
init : function() {
|
|
||||||
var params = $.getQueryParameters();
|
|
||||||
if (params.q) {
|
|
||||||
var query = params.q[0];
|
|
||||||
$('input[name="q"]')[0].value = query;
|
|
||||||
this.performSearch(query);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
loadIndex : function(url) {
|
|
||||||
$.ajax({type: "GET", url: url, data: null,
|
|
||||||
dataType: "script", cache: true,
|
|
||||||
complete: function(jqxhr, textstatus) {
|
|
||||||
if (textstatus != "success") {
|
|
||||||
document.getElementById("searchindexloader").src = url;
|
|
||||||
}
|
|
||||||
}});
|
|
||||||
},
|
|
||||||
|
|
||||||
setIndex : function(index) {
|
|
||||||
var q;
|
|
||||||
this._index = index;
|
|
||||||
if ((q = this._queued_query) !== null) {
|
|
||||||
this._queued_query = null;
|
|
||||||
Search.query(q);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
hasIndex : function() {
|
|
||||||
return this._index !== null;
|
|
||||||
},
|
|
||||||
|
|
||||||
deferQuery : function(query) {
|
|
||||||
this._queued_query = query;
|
|
||||||
},
|
|
||||||
|
|
||||||
stopPulse : function() {
|
|
||||||
this._pulse_status = 0;
|
|
||||||
},
|
|
||||||
|
|
||||||
startPulse : function() {
|
|
||||||
if (this._pulse_status >= 0)
|
|
||||||
return;
|
|
||||||
function pulse() {
|
|
||||||
var i;
|
|
||||||
Search._pulse_status = (Search._pulse_status + 1) % 4;
|
|
||||||
var dotString = '';
|
|
||||||
for (i = 0; i < Search._pulse_status; i++)
|
|
||||||
dotString += '.';
|
|
||||||
Search.dots.text(dotString);
|
|
||||||
if (Search._pulse_status > -1)
|
|
||||||
window.setTimeout(pulse, 500);
|
|
||||||
}
|
|
||||||
pulse();
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* perform a search for something (or wait until index is loaded)
|
|
||||||
*/
|
|
||||||
performSearch : function(query) {
|
|
||||||
// create the required interface elements
|
|
||||||
this.out = $('#search-results');
|
|
||||||
this.title = $('<h2>' + _('Searching') + '</h2>').appendTo(this.out);
|
|
||||||
this.dots = $('<span></span>').appendTo(this.title);
|
|
||||||
this.status = $('<p class="search-summary"> </p>').appendTo(this.out);
|
|
||||||
this.output = $('<ul class="search"/>').appendTo(this.out);
|
|
||||||
|
|
||||||
$('#search-progress').text(_('Preparing search...'));
|
|
||||||
this.startPulse();
|
|
||||||
|
|
||||||
// index already loaded, the browser was quick!
|
|
||||||
if (this.hasIndex())
|
|
||||||
this.query(query);
|
|
||||||
else
|
|
||||||
this.deferQuery(query);
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* execute search (requires search index to be loaded)
|
|
||||||
*/
|
|
||||||
query : function(query) {
|
|
||||||
var i;
|
|
||||||
|
|
||||||
// stem the searchterms and add them to the correct list
|
|
||||||
var stemmer = new Stemmer();
|
|
||||||
var searchterms = [];
|
|
||||||
var excluded = [];
|
|
||||||
var hlterms = [];
|
|
||||||
var tmp = splitQuery(query);
|
|
||||||
var objectterms = [];
|
|
||||||
for (i = 0; i < tmp.length; i++) {
|
|
||||||
if (tmp[i] !== "") {
|
|
||||||
objectterms.push(tmp[i].toLowerCase());
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($u.indexOf(stopwords, tmp[i].toLowerCase()) != -1 || tmp[i] === "") {
|
|
||||||
// skip this "word"
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
// stem the word
|
|
||||||
var word = stemmer.stemWord(tmp[i].toLowerCase());
|
|
||||||
// prevent stemmer from cutting word smaller than two chars
|
|
||||||
if(word.length < 3 && tmp[i].length >= 3) {
|
|
||||||
word = tmp[i];
|
|
||||||
}
|
|
||||||
var toAppend;
|
|
||||||
// select the correct list
|
|
||||||
if (word[0] == '-') {
|
|
||||||
toAppend = excluded;
|
|
||||||
word = word.substr(1);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
toAppend = searchterms;
|
|
||||||
hlterms.push(tmp[i].toLowerCase());
|
|
||||||
}
|
|
||||||
// only add if not already in the list
|
|
||||||
if (!$u.contains(toAppend, word))
|
|
||||||
toAppend.push(word);
|
|
||||||
}
|
|
||||||
var highlightstring = '?highlight=' + $.urlencode(hlterms.join(" "));
|
|
||||||
|
|
||||||
// console.debug('SEARCH: searching for:');
|
|
||||||
// console.info('required: ', searchterms);
|
|
||||||
// console.info('excluded: ', excluded);
|
|
||||||
|
|
||||||
// prepare search
|
|
||||||
var terms = this._index.terms;
|
|
||||||
var titleterms = this._index.titleterms;
|
|
||||||
|
|
||||||
// array of [filename, title, anchor, descr, score]
|
|
||||||
var results = [];
|
|
||||||
$('#search-progress').empty();
|
|
||||||
|
|
||||||
// lookup as object
|
|
||||||
for (i = 0; i < objectterms.length; i++) {
|
|
||||||
var others = [].concat(objectterms.slice(0, i),
|
|
||||||
objectterms.slice(i+1, objectterms.length));
|
|
||||||
results = results.concat(this.performObjectSearch(objectterms[i], others));
|
|
||||||
}
|
|
||||||
|
|
||||||
// lookup as search terms in fulltext
|
|
||||||
results = results.concat(this.performTermsSearch(searchterms, excluded, terms, titleterms));
|
|
||||||
|
|
||||||
// let the scorer override scores with a custom scoring function
|
|
||||||
if (Scorer.score) {
|
|
||||||
for (i = 0; i < results.length; i++)
|
|
||||||
results[i][4] = Scorer.score(results[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// now sort the results by score (in opposite order of appearance, since the
|
|
||||||
// display function below uses pop() to retrieve items) and then
|
|
||||||
// alphabetically
|
|
||||||
results.sort(function(a, b) {
|
|
||||||
var left = a[4];
|
|
||||||
var right = b[4];
|
|
||||||
if (left > right) {
|
|
||||||
return 1;
|
|
||||||
} else if (left < right) {
|
|
||||||
return -1;
|
|
||||||
} else {
|
|
||||||
// same score: sort alphabetically
|
|
||||||
left = a[1].toLowerCase();
|
|
||||||
right = b[1].toLowerCase();
|
|
||||||
return (left > right) ? -1 : ((left < right) ? 1 : 0);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// for debugging
|
|
||||||
//Search.lastresults = results.slice(); // a copy
|
|
||||||
//console.info('search results:', Search.lastresults);
|
|
||||||
|
|
||||||
// print the results
|
|
||||||
var resultCount = results.length;
|
|
||||||
function displayNextItem() {
|
|
||||||
// results left, load the summary and display it
|
|
||||||
if (results.length) {
|
|
||||||
var item = results.pop();
|
|
||||||
var listItem = $('<li></li>');
|
|
||||||
var requestUrl = "";
|
|
||||||
var linkUrl = "";
|
|
||||||
if (DOCUMENTATION_OPTIONS.BUILDER === 'dirhtml') {
|
|
||||||
// dirhtml builder
|
|
||||||
var dirname = item[0] + '/';
|
|
||||||
if (dirname.match(/\/index\/$/)) {
|
|
||||||
dirname = dirname.substring(0, dirname.length-6);
|
|
||||||
} else if (dirname == 'index/') {
|
|
||||||
dirname = '';
|
|
||||||
}
|
|
||||||
requestUrl = DOCUMENTATION_OPTIONS.URL_ROOT + dirname;
|
|
||||||
linkUrl = requestUrl;
|
|
||||||
|
|
||||||
} else {
|
|
||||||
// normal html builders
|
|
||||||
requestUrl = DOCUMENTATION_OPTIONS.URL_ROOT + item[0] + DOCUMENTATION_OPTIONS.FILE_SUFFIX;
|
|
||||||
linkUrl = item[0] + DOCUMENTATION_OPTIONS.LINK_SUFFIX;
|
|
||||||
}
|
|
||||||
listItem.append($('<a/>').attr('href',
|
|
||||||
linkUrl +
|
|
||||||
highlightstring + item[2]).html(item[1]));
|
|
||||||
if (item[3]) {
|
|
||||||
listItem.append($('<span> (' + item[3] + ')</span>'));
|
|
||||||
Search.output.append(listItem);
|
|
||||||
setTimeout(function() {
|
|
||||||
displayNextItem();
|
|
||||||
}, 5);
|
|
||||||
} else if (DOCUMENTATION_OPTIONS.HAS_SOURCE) {
|
|
||||||
$.ajax({url: requestUrl,
|
|
||||||
dataType: "text",
|
|
||||||
complete: function(jqxhr, textstatus) {
|
|
||||||
var data = jqxhr.responseText;
|
|
||||||
if (data !== '' && data !== undefined) {
|
|
||||||
var summary = Search.makeSearchSummary(data, searchterms, hlterms);
|
|
||||||
if (summary) {
|
|
||||||
listItem.append(summary);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Search.output.append(listItem);
|
|
||||||
setTimeout(function() {
|
|
||||||
displayNextItem();
|
|
||||||
}, 5);
|
|
||||||
}});
|
|
||||||
} else {
|
|
||||||
// no source available, just display title
|
|
||||||
Search.output.append(listItem);
|
|
||||||
setTimeout(function() {
|
|
||||||
displayNextItem();
|
|
||||||
}, 5);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// search finished, update title and status message
|
|
||||||
else {
|
|
||||||
Search.stopPulse();
|
|
||||||
Search.title.text(_('Search Results'));
|
|
||||||
if (!resultCount)
|
|
||||||
Search.status.text(_('Your search did not match any documents. Please make sure that all words are spelled correctly and that you\'ve selected enough categories.'));
|
|
||||||
else
|
|
||||||
Search.status.text(_('Search finished, found %s page(s) matching the search query.').replace('%s', resultCount));
|
|
||||||
Search.status.fadeIn(500);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
displayNextItem();
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* search for object names
|
|
||||||
*/
|
|
||||||
performObjectSearch : function(object, otherterms) {
|
|
||||||
var filenames = this._index.filenames;
|
|
||||||
var docnames = this._index.docnames;
|
|
||||||
var objects = this._index.objects;
|
|
||||||
var objnames = this._index.objnames;
|
|
||||||
var titles = this._index.titles;
|
|
||||||
|
|
||||||
var i;
|
|
||||||
var results = [];
|
|
||||||
|
|
||||||
for (var prefix in objects) {
|
|
||||||
for (var name in objects[prefix]) {
|
|
||||||
var fullname = (prefix ? prefix + '.' : '') + name;
|
|
||||||
var fullnameLower = fullname.toLowerCase()
|
|
||||||
if (fullnameLower.indexOf(object) > -1) {
|
|
||||||
var score = 0;
|
|
||||||
var parts = fullnameLower.split('.');
|
|
||||||
// check for different match types: exact matches of full name or
|
|
||||||
// "last name" (i.e. last dotted part)
|
|
||||||
if (fullnameLower == object || parts[parts.length - 1] == object) {
|
|
||||||
score += Scorer.objNameMatch;
|
|
||||||
// matches in last name
|
|
||||||
} else if (parts[parts.length - 1].indexOf(object) > -1) {
|
|
||||||
score += Scorer.objPartialMatch;
|
|
||||||
}
|
|
||||||
var match = objects[prefix][name];
|
|
||||||
var objname = objnames[match[1]][2];
|
|
||||||
var title = titles[match[0]];
|
|
||||||
// If more than one term searched for, we require other words to be
|
|
||||||
// found in the name/title/description
|
|
||||||
if (otherterms.length > 0) {
|
|
||||||
var haystack = (prefix + ' ' + name + ' ' +
|
|
||||||
objname + ' ' + title).toLowerCase();
|
|
||||||
var allfound = true;
|
|
||||||
for (i = 0; i < otherterms.length; i++) {
|
|
||||||
if (haystack.indexOf(otherterms[i]) == -1) {
|
|
||||||
allfound = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!allfound) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var descr = objname + _(', in ') + title;
|
|
||||||
|
|
||||||
var anchor = match[3];
|
|
||||||
if (anchor === '')
|
|
||||||
anchor = fullname;
|
|
||||||
else if (anchor == '-')
|
|
||||||
anchor = objnames[match[1]][1] + '-' + fullname;
|
|
||||||
// add custom score for some objects according to scorer
|
|
||||||
if (Scorer.objPrio.hasOwnProperty(match[2])) {
|
|
||||||
score += Scorer.objPrio[match[2]];
|
|
||||||
} else {
|
|
||||||
score += Scorer.objPrioDefault;
|
|
||||||
}
|
|
||||||
results.push([docnames[match[0]], fullname, '#'+anchor, descr, score, filenames[match[0]]]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return results;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions
|
|
||||||
*/
|
|
||||||
escapeRegExp : function(string) {
|
|
||||||
return string.replace(/[.*+\-?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* search for full-text terms in the index
|
|
||||||
*/
|
|
||||||
performTermsSearch : function(searchterms, excluded, terms, titleterms) {
|
|
||||||
var docnames = this._index.docnames;
|
|
||||||
var filenames = this._index.filenames;
|
|
||||||
var titles = this._index.titles;
|
|
||||||
|
|
||||||
var i, j, file;
|
|
||||||
var fileMap = {};
|
|
||||||
var scoreMap = {};
|
|
||||||
var results = [];
|
|
||||||
|
|
||||||
// perform the search on the required terms
|
|
||||||
for (i = 0; i < searchterms.length; i++) {
|
|
||||||
var word = searchterms[i];
|
|
||||||
var files = [];
|
|
||||||
var _o = [
|
|
||||||
{files: terms[word], score: Scorer.term},
|
|
||||||
{files: titleterms[word], score: Scorer.title}
|
|
||||||
];
|
|
||||||
// add support for partial matches
|
|
||||||
if (word.length > 2) {
|
|
||||||
var word_regex = this.escapeRegExp(word);
|
|
||||||
for (var w in terms) {
|
|
||||||
if (w.match(word_regex) && !terms[word]) {
|
|
||||||
_o.push({files: terms[w], score: Scorer.partialTerm})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (var w in titleterms) {
|
|
||||||
if (w.match(word_regex) && !titleterms[word]) {
|
|
||||||
_o.push({files: titleterms[w], score: Scorer.partialTitle})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// no match but word was a required one
|
|
||||||
if ($u.every(_o, function(o){return o.files === undefined;})) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
// found search word in contents
|
|
||||||
$u.each(_o, function(o) {
|
|
||||||
var _files = o.files;
|
|
||||||
if (_files === undefined)
|
|
||||||
return
|
|
||||||
|
|
||||||
if (_files.length === undefined)
|
|
||||||
_files = [_files];
|
|
||||||
files = files.concat(_files);
|
|
||||||
|
|
||||||
// set score for the word in each file to Scorer.term
|
|
||||||
for (j = 0; j < _files.length; j++) {
|
|
||||||
file = _files[j];
|
|
||||||
if (!(file in scoreMap))
|
|
||||||
scoreMap[file] = {};
|
|
||||||
scoreMap[file][word] = o.score;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// create the mapping
|
|
||||||
for (j = 0; j < files.length; j++) {
|
|
||||||
file = files[j];
|
|
||||||
if (file in fileMap && fileMap[file].indexOf(word) === -1)
|
|
||||||
fileMap[file].push(word);
|
|
||||||
else
|
|
||||||
fileMap[file] = [word];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// now check if the files don't contain excluded terms
|
|
||||||
for (file in fileMap) {
|
|
||||||
var valid = true;
|
|
||||||
|
|
||||||
// check if all requirements are matched
|
|
||||||
var filteredTermCount = // as search terms with length < 3 are discarded: ignore
|
|
||||||
searchterms.filter(function(term){return term.length > 2}).length
|
|
||||||
if (
|
|
||||||
fileMap[file].length != searchterms.length &&
|
|
||||||
fileMap[file].length != filteredTermCount
|
|
||||||
) continue;
|
|
||||||
|
|
||||||
// ensure that none of the excluded terms is in the search result
|
|
||||||
for (i = 0; i < excluded.length; i++) {
|
|
||||||
if (terms[excluded[i]] == file ||
|
|
||||||
titleterms[excluded[i]] == file ||
|
|
||||||
$u.contains(terms[excluded[i]] || [], file) ||
|
|
||||||
$u.contains(titleterms[excluded[i]] || [], file)) {
|
|
||||||
valid = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// if we have still a valid result we can add it to the result list
|
|
||||||
if (valid) {
|
|
||||||
// select one (max) score for the file.
|
|
||||||
// for better ranking, we should calculate ranking by using words statistics like basic tf-idf...
|
|
||||||
var score = $u.max($u.map(fileMap[file], function(w){return scoreMap[file][w]}));
|
|
||||||
results.push([docnames[file], titles[file], '', null, score, filenames[file]]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return results;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* helper function to return a node containing the
|
|
||||||
* search summary for a given text. keywords is a list
|
|
||||||
* of stemmed words, hlwords is the list of normal, unstemmed
|
|
||||||
* words. the first one is used to find the occurrence, the
|
|
||||||
* latter for highlighting it.
|
|
||||||
*/
|
|
||||||
makeSearchSummary : function(htmlText, keywords, hlwords) {
|
|
||||||
var text = Search.htmlToText(htmlText);
|
|
||||||
if (text == "") {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
var textLower = text.toLowerCase();
|
|
||||||
var start = 0;
|
|
||||||
$.each(keywords, function() {
|
|
||||||
var i = textLower.indexOf(this.toLowerCase());
|
|
||||||
if (i > -1)
|
|
||||||
start = i;
|
|
||||||
});
|
|
||||||
start = Math.max(start - 120, 0);
|
|
||||||
var excerpt = ((start > 0) ? '...' : '') +
|
|
||||||
$.trim(text.substr(start, 240)) +
|
|
||||||
((start + 240 - text.length) ? '...' : '');
|
|
||||||
var rv = $('<p class="context"></p>').text(excerpt);
|
|
||||||
$.each(hlwords, function() {
|
|
||||||
rv = rv.highlightText(this, 'highlighted');
|
|
||||||
});
|
|
||||||
return rv;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
$(document).ready(function() {
|
|
||||||
Search.init();
|
|
||||||
});
|
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
|
|
@ -1,631 +0,0 @@
|
||||||
|
|
||||||
<!DOCTYPE html>
|
|
||||||
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
||||||
<title>Index — Pomice 1.1.1 documentation</title>
|
|
||||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
|
|
||||||
<link rel="stylesheet" type="text/css" href="_static/alabaster.css" />
|
|
||||||
<script data-url_root="./" id="documentation_options" src="_static/documentation_options.js"></script>
|
|
||||||
<script src="_static/jquery.js"></script>
|
|
||||||
<script src="_static/underscore.js"></script>
|
|
||||||
<script src="_static/doctools.js"></script>
|
|
||||||
<link rel="index" title="Index" href="#" />
|
|
||||||
<link rel="search" title="Search" href="search.html" />
|
|
||||||
|
|
||||||
<link rel="stylesheet" href="_static/custom.css" type="text/css" />
|
|
||||||
|
|
||||||
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=0.9, maximum-scale=0.9" />
|
|
||||||
|
|
||||||
</head><body>
|
|
||||||
|
|
||||||
|
|
||||||
<div class="document">
|
|
||||||
<div class="documentwrapper">
|
|
||||||
<div class="bodywrapper">
|
|
||||||
|
|
||||||
|
|
||||||
<div class="body" role="main">
|
|
||||||
|
|
||||||
|
|
||||||
<h1 id="index">Index</h1>
|
|
||||||
|
|
||||||
<div class="genindex-jumpbox">
|
|
||||||
<a href="#A"><strong>A</strong></a>
|
|
||||||
| <a href="#B"><strong>B</strong></a>
|
|
||||||
| <a href="#C"><strong>C</strong></a>
|
|
||||||
| <a href="#D"><strong>D</strong></a>
|
|
||||||
| <a href="#E"><strong>E</strong></a>
|
|
||||||
| <a href="#F"><strong>F</strong></a>
|
|
||||||
| <a href="#G"><strong>G</strong></a>
|
|
||||||
| <a href="#H"><strong>H</strong></a>
|
|
||||||
| <a href="#I"><strong>I</strong></a>
|
|
||||||
| <a href="#K"><strong>K</strong></a>
|
|
||||||
| <a href="#L"><strong>L</strong></a>
|
|
||||||
| <a href="#M"><strong>M</strong></a>
|
|
||||||
| <a href="#N"><strong>N</strong></a>
|
|
||||||
| <a href="#O"><strong>O</strong></a>
|
|
||||||
| <a href="#P"><strong>P</strong></a>
|
|
||||||
| <a href="#R"><strong>R</strong></a>
|
|
||||||
| <a href="#S"><strong>S</strong></a>
|
|
||||||
| <a href="#T"><strong>T</strong></a>
|
|
||||||
| <a href="#U"><strong>U</strong></a>
|
|
||||||
| <a href="#V"><strong>V</strong></a>
|
|
||||||
| <a href="#W"><strong>W</strong></a>
|
|
||||||
| <a href="#Y"><strong>Y</strong></a>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<h2 id="A">A</h2>
|
|
||||||
<table style="width: 100%" class="indextable genindextable"><tr>
|
|
||||||
<td style="width: 33%; vertical-align: top;"><ul>
|
|
||||||
<li><a href="pomice.spotify.html#pomice.spotify.album.Album">Album (class in pomice.spotify.album)</a>
|
|
||||||
</li>
|
|
||||||
</ul></td>
|
|
||||||
</tr></table>
|
|
||||||
|
|
||||||
<h2 id="B">B</h2>
|
|
||||||
<table style="width: 100%" class="indextable genindextable"><tr>
|
|
||||||
<td style="width: 33%; vertical-align: top;"><ul>
|
|
||||||
<li><a href="pomice.html#pomice.player.Player.bot">bot (pomice.player.Player property)</a>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li><a href="pomice.html#pomice.pool.Node.bot">(pomice.pool.Node property)</a>
|
|
||||||
</li>
|
|
||||||
</ul></li>
|
|
||||||
</ul></td>
|
|
||||||
<td style="width: 33%; vertical-align: top;"><ul>
|
|
||||||
<li><a href="pomice.html#pomice.pool.Node.build_track">build_track() (pomice.pool.Node method)</a>
|
|
||||||
</li>
|
|
||||||
</ul></td>
|
|
||||||
</tr></table>
|
|
||||||
|
|
||||||
<h2 id="C">C</h2>
|
|
||||||
<table style="width: 100%" class="indextable genindextable"><tr>
|
|
||||||
<td style="width: 33%; vertical-align: top;"><ul>
|
|
||||||
<li><a href="pomice.html#pomice.filters.ChannelMix">ChannelMix (class in pomice.filters)</a>
|
|
||||||
</li>
|
|
||||||
<li><a href="pomice.spotify.html#pomice.spotify.client.Client">Client (class in pomice.spotify.client)</a>
|
|
||||||
</li>
|
|
||||||
<li><a href="pomice.html#pomice.player.Player.connect">connect() (pomice.player.Player method)</a>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li><a href="pomice.html#pomice.pool.Node.connect">(pomice.pool.Node method)</a>
|
|
||||||
</li>
|
|
||||||
</ul></li>
|
|
||||||
</ul></td>
|
|
||||||
<td style="width: 33%; vertical-align: top;"><ul>
|
|
||||||
<li><a href="pomice.html#pomice.pool.NodePool.create_node">create_node() (pomice.pool.NodePool class method)</a>
|
|
||||||
</li>
|
|
||||||
<li><a href="pomice.html#pomice.player.Player.current">current (pomice.player.Player property)</a>
|
|
||||||
</li>
|
|
||||||
</ul></td>
|
|
||||||
</tr></table>
|
|
||||||
|
|
||||||
<h2 id="D">D</h2>
|
|
||||||
<table style="width: 100%" class="indextable genindextable"><tr>
|
|
||||||
<td style="width: 33%; vertical-align: top;"><ul>
|
|
||||||
<li><a href="pomice.html#pomice.utils.ExponentialBackoff.delay">delay() (pomice.utils.ExponentialBackoff method)</a>
|
|
||||||
</li>
|
|
||||||
<li><a href="pomice.html#pomice.player.Player.destroy">destroy() (pomice.player.Player method)</a>
|
|
||||||
</li>
|
|
||||||
<li><a href="pomice.html#pomice.player.Player.disconnect">disconnect() (pomice.player.Player method)</a>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li><a href="pomice.html#pomice.pool.Node.disconnect">(pomice.pool.Node method)</a>
|
|
||||||
</li>
|
|
||||||
</ul></li>
|
|
||||||
</ul></td>
|
|
||||||
<td style="width: 33%; vertical-align: top;"><ul>
|
|
||||||
<li><a href="pomice.html#pomice.events.PomiceEvent.dispatch">dispatch() (pomice.events.PomiceEvent method)</a>
|
|
||||||
</li>
|
|
||||||
<li><a href="pomice.html#pomice.filters.Distortion">Distortion (class in pomice.filters)</a>
|
|
||||||
</li>
|
|
||||||
</ul></td>
|
|
||||||
</tr></table>
|
|
||||||
|
|
||||||
<h2 id="E">E</h2>
|
|
||||||
<table style="width: 100%" class="indextable genindextable"><tr>
|
|
||||||
<td style="width: 33%; vertical-align: top;"><ul>
|
|
||||||
<li><a href="pomice.html#pomice.filters.Equalizer">Equalizer (class in pomice.filters)</a>
|
|
||||||
</li>
|
|
||||||
</ul></td>
|
|
||||||
<td style="width: 33%; vertical-align: top;"><ul>
|
|
||||||
<li><a href="pomice.html#pomice.utils.ExponentialBackoff">ExponentialBackoff (class in pomice.utils)</a>
|
|
||||||
</li>
|
|
||||||
</ul></td>
|
|
||||||
</tr></table>
|
|
||||||
|
|
||||||
<h2 id="F">F</h2>
|
|
||||||
<table style="width: 100%" class="indextable genindextable"><tr>
|
|
||||||
<td style="width: 33%; vertical-align: top;"><ul>
|
|
||||||
<li><a href="pomice.html#pomice.filters.Filter">Filter (class in pomice.filters)</a>
|
|
||||||
</li>
|
|
||||||
</ul></td>
|
|
||||||
<td style="width: 33%; vertical-align: top;"><ul>
|
|
||||||
<li><a href="pomice.html#pomice.player.Player.filter">filter (pomice.player.Player property)</a>
|
|
||||||
</li>
|
|
||||||
<li><a href="pomice.html#pomice.exceptions.FilterInvalidArgument">FilterInvalidArgument</a>
|
|
||||||
</li>
|
|
||||||
</ul></td>
|
|
||||||
</tr></table>
|
|
||||||
|
|
||||||
<h2 id="G">G</h2>
|
|
||||||
<table style="width: 100%" class="indextable genindextable"><tr>
|
|
||||||
<td style="width: 33%; vertical-align: top;"><ul>
|
|
||||||
<li><a href="pomice.html#pomice.pool.NodePool.get_node">get_node() (pomice.pool.NodePool class method)</a>
|
|
||||||
</li>
|
|
||||||
<li><a href="pomice.html#pomice.pool.Node.get_player">get_player() (pomice.pool.Node method)</a>
|
|
||||||
</li>
|
|
||||||
</ul></td>
|
|
||||||
<td style="width: 33%; vertical-align: top;"><ul>
|
|
||||||
<li><a href="pomice.html#pomice.player.Player.get_tracks">get_tracks() (pomice.player.Player method)</a>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li><a href="pomice.html#pomice.pool.Node.get_tracks">(pomice.pool.Node method)</a>
|
|
||||||
</li>
|
|
||||||
</ul></li>
|
|
||||||
<li><a href="pomice.html#pomice.player.Player.guild">guild (pomice.player.Player property)</a>
|
|
||||||
</li>
|
|
||||||
</ul></td>
|
|
||||||
</tr></table>
|
|
||||||
|
|
||||||
<h2 id="H">H</h2>
|
|
||||||
<table style="width: 100%" class="indextable genindextable"><tr>
|
|
||||||
<td style="width: 33%; vertical-align: top;"><ul>
|
|
||||||
<li><a href="pomice.html#pomice.events.PomiceEvent.handler_args">handler_args (pomice.events.PomiceEvent attribute)</a>
|
|
||||||
</li>
|
|
||||||
</ul></td>
|
|
||||||
</tr></table>
|
|
||||||
|
|
||||||
<h2 id="I">I</h2>
|
|
||||||
<table style="width: 100%" class="indextable genindextable"><tr>
|
|
||||||
<td style="width: 33%; vertical-align: top;"><ul>
|
|
||||||
<li><a href="pomice.html#pomice.exceptions.InvalidSpotifyClientAuthorization">InvalidSpotifyClientAuthorization</a>
|
|
||||||
</li>
|
|
||||||
<li><a href="pomice.spotify.html#pomice.spotify.exceptions.InvalidSpotifyURL">InvalidSpotifyURL</a>
|
|
||||||
</li>
|
|
||||||
<li><a href="pomice.html#pomice.player.Player.is_connected">is_connected (pomice.player.Player property)</a>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li><a href="pomice.html#pomice.pool.Node.is_connected">(pomice.pool.Node property)</a>
|
|
||||||
</li>
|
|
||||||
</ul></li>
|
|
||||||
</ul></td>
|
|
||||||
<td style="width: 33%; vertical-align: top;"><ul>
|
|
||||||
<li><a href="pomice.html#pomice.player.Player.is_dead">is_dead (pomice.player.Player property)</a>
|
|
||||||
</li>
|
|
||||||
<li><a href="pomice.html#pomice.player.Player.is_paused">is_paused (pomice.player.Player property)</a>
|
|
||||||
</li>
|
|
||||||
<li><a href="pomice.html#pomice.player.Player.is_playing">is_playing (pomice.player.Player property)</a>
|
|
||||||
</li>
|
|
||||||
</ul></td>
|
|
||||||
</tr></table>
|
|
||||||
|
|
||||||
<h2 id="K">K</h2>
|
|
||||||
<table style="width: 100%" class="indextable genindextable"><tr>
|
|
||||||
<td style="width: 33%; vertical-align: top;"><ul>
|
|
||||||
<li><a href="pomice.html#pomice.filters.Karaoke">Karaoke (class in pomice.filters)</a>
|
|
||||||
</li>
|
|
||||||
</ul></td>
|
|
||||||
</tr></table>
|
|
||||||
|
|
||||||
<h2 id="L">L</h2>
|
|
||||||
<table style="width: 100%" class="indextable genindextable"><tr>
|
|
||||||
<td style="width: 33%; vertical-align: top;"><ul>
|
|
||||||
<li><a href="pomice.html#pomice.pool.Node.latency">latency (pomice.pool.Node property)</a>
|
|
||||||
</li>
|
|
||||||
</ul></td>
|
|
||||||
<td style="width: 33%; vertical-align: top;"><ul>
|
|
||||||
<li><a href="pomice.html#pomice.filters.LowPass">LowPass (class in pomice.filters)</a>
|
|
||||||
</li>
|
|
||||||
</ul></td>
|
|
||||||
</tr></table>
|
|
||||||
|
|
||||||
<h2 id="M">M</h2>
|
|
||||||
<table style="width: 100%" class="indextable genindextable"><tr>
|
|
||||||
<td style="width: 33%; vertical-align: top;"><ul>
|
|
||||||
<li>
|
|
||||||
module
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li><a href="pomice.html#module-pomice.enums">pomice.enums</a>
|
|
||||||
</li>
|
|
||||||
<li><a href="pomice.html#module-pomice.events">pomice.events</a>
|
|
||||||
</li>
|
|
||||||
<li><a href="pomice.html#module-pomice.exceptions">pomice.exceptions</a>
|
|
||||||
</li>
|
|
||||||
<li><a href="pomice.html#module-pomice.filters">pomice.filters</a>
|
|
||||||
</li>
|
|
||||||
<li><a href="pomice.html#module-pomice.objects">pomice.objects</a>
|
|
||||||
</li>
|
|
||||||
<li><a href="pomice.html#module-pomice.player">pomice.player</a>
|
|
||||||
</li>
|
|
||||||
<li><a href="pomice.html#module-pomice.pool">pomice.pool</a>
|
|
||||||
</li>
|
|
||||||
<li><a href="pomice.spotify.html#module-pomice.spotify.album">pomice.spotify.album</a>
|
|
||||||
</li>
|
|
||||||
<li><a href="pomice.spotify.html#module-pomice.spotify.client">pomice.spotify.client</a>
|
|
||||||
</li>
|
|
||||||
<li><a href="pomice.spotify.html#module-pomice.spotify.exceptions">pomice.spotify.exceptions</a>
|
|
||||||
</li>
|
|
||||||
<li><a href="pomice.spotify.html#module-pomice.spotify.playlist">pomice.spotify.playlist</a>
|
|
||||||
</li>
|
|
||||||
<li><a href="pomice.spotify.html#module-pomice.spotify.track">pomice.spotify.track</a>
|
|
||||||
</li>
|
|
||||||
<li><a href="pomice.html#module-pomice.utils">pomice.utils</a>
|
|
||||||
</li>
|
|
||||||
</ul></li>
|
|
||||||
</ul></td>
|
|
||||||
</tr></table>
|
|
||||||
|
|
||||||
<h2 id="N">N</h2>
|
|
||||||
<table style="width: 100%" class="indextable genindextable"><tr>
|
|
||||||
<td style="width: 33%; vertical-align: top;"><ul>
|
|
||||||
<li><a href="pomice.html#pomice.events.PomiceEvent.name">name (pomice.events.PomiceEvent attribute)</a>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li><a href="pomice.html#pomice.events.TrackEndEvent.name">(pomice.events.TrackEndEvent attribute)</a>
|
|
||||||
</li>
|
|
||||||
<li><a href="pomice.html#pomice.events.TrackExceptionEvent.name">(pomice.events.TrackExceptionEvent attribute)</a>
|
|
||||||
</li>
|
|
||||||
<li><a href="pomice.html#pomice.events.TrackStartEvent.name">(pomice.events.TrackStartEvent attribute)</a>
|
|
||||||
</li>
|
|
||||||
<li><a href="pomice.html#pomice.events.TrackStuckEvent.name">(pomice.events.TrackStuckEvent attribute)</a>
|
|
||||||
</li>
|
|
||||||
<li><a href="pomice.html#pomice.events.WebSocketClosedEvent.name">(pomice.events.WebSocketClosedEvent attribute)</a>
|
|
||||||
</li>
|
|
||||||
<li><a href="pomice.html#pomice.events.WebSocketOpenEvent.name">(pomice.events.WebSocketOpenEvent attribute)</a>
|
|
||||||
</li>
|
|
||||||
</ul></li>
|
|
||||||
<li><a href="pomice.html#pomice.pool.Node">Node (class in pomice.pool)</a>
|
|
||||||
</li>
|
|
||||||
<li><a href="pomice.html#pomice.player.Player.node">node (pomice.player.Player property)</a>
|
|
||||||
</li>
|
|
||||||
</ul></td>
|
|
||||||
<td style="width: 33%; vertical-align: top;"><ul>
|
|
||||||
<li><a href="pomice.html#pomice.pool.NodePool.node_count">node_count (pomice.pool.NodePool property)</a>
|
|
||||||
</li>
|
|
||||||
<li><a href="pomice.html#pomice.exceptions.NodeConnectionClosed">NodeConnectionClosed</a>
|
|
||||||
</li>
|
|
||||||
<li><a href="pomice.html#pomice.exceptions.NodeConnectionFailure">NodeConnectionFailure</a>
|
|
||||||
</li>
|
|
||||||
<li><a href="pomice.html#pomice.exceptions.NodeCreationError">NodeCreationError</a>
|
|
||||||
</li>
|
|
||||||
<li><a href="pomice.html#pomice.exceptions.NodeException">NodeException</a>
|
|
||||||
</li>
|
|
||||||
<li><a href="pomice.html#pomice.exceptions.NodeNotAvailable">NodeNotAvailable</a>
|
|
||||||
</li>
|
|
||||||
<li><a href="pomice.html#pomice.pool.NodePool">NodePool (class in pomice.pool)</a>
|
|
||||||
</li>
|
|
||||||
<li><a href="pomice.html#pomice.pool.NodePool.nodes">nodes (pomice.pool.NodePool property)</a>
|
|
||||||
</li>
|
|
||||||
<li><a href="pomice.html#pomice.utils.NodeStats">NodeStats (class in pomice.utils)</a>
|
|
||||||
</li>
|
|
||||||
<li><a href="pomice.html#pomice.exceptions.NoNodesAvailable">NoNodesAvailable</a>
|
|
||||||
</li>
|
|
||||||
</ul></td>
|
|
||||||
</tr></table>
|
|
||||||
|
|
||||||
<h2 id="O">O</h2>
|
|
||||||
<table style="width: 100%" class="indextable genindextable"><tr>
|
|
||||||
<td style="width: 33%; vertical-align: top;"><ul>
|
|
||||||
<li><a href="pomice.html#pomice.player.Player.on_voice_server_update">on_voice_server_update() (pomice.player.Player method)</a>
|
|
||||||
</li>
|
|
||||||
</ul></td>
|
|
||||||
<td style="width: 33%; vertical-align: top;"><ul>
|
|
||||||
<li><a href="pomice.html#pomice.player.Player.on_voice_state_update">on_voice_state_update() (pomice.player.Player method)</a>
|
|
||||||
</li>
|
|
||||||
</ul></td>
|
|
||||||
</tr></table>
|
|
||||||
|
|
||||||
<h2 id="P">P</h2>
|
|
||||||
<table style="width: 100%" class="indextable genindextable"><tr>
|
|
||||||
<td style="width: 33%; vertical-align: top;"><ul>
|
|
||||||
<li><a href="pomice.html#pomice.player.Player.play">play() (pomice.player.Player method)</a>
|
|
||||||
</li>
|
|
||||||
<li><a href="pomice.html#pomice.player.Player">Player (class in pomice.player)</a>
|
|
||||||
</li>
|
|
||||||
<li><a href="pomice.html#pomice.pool.Node.player_count">player_count (pomice.pool.Node property)</a>
|
|
||||||
</li>
|
|
||||||
<li><a href="pomice.html#pomice.pool.Node.players">players (pomice.pool.Node property)</a>
|
|
||||||
</li>
|
|
||||||
<li><a href="pomice.html#pomice.objects.Playlist">Playlist (class in pomice.objects)</a>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li><a href="pomice.spotify.html#pomice.spotify.playlist.Playlist">(class in pomice.spotify.playlist)</a>
|
|
||||||
</li>
|
|
||||||
</ul></li>
|
|
||||||
<li>
|
|
||||||
pomice.enums
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li><a href="pomice.html#module-pomice.enums">module</a>
|
|
||||||
</li>
|
|
||||||
</ul></li>
|
|
||||||
<li>
|
|
||||||
pomice.events
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li><a href="pomice.html#module-pomice.events">module</a>
|
|
||||||
</li>
|
|
||||||
</ul></li>
|
|
||||||
<li>
|
|
||||||
pomice.exceptions
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li><a href="pomice.html#module-pomice.exceptions">module</a>
|
|
||||||
</li>
|
|
||||||
</ul></li>
|
|
||||||
<li>
|
|
||||||
pomice.filters
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li><a href="pomice.html#module-pomice.filters">module</a>
|
|
||||||
</li>
|
|
||||||
</ul></li>
|
|
||||||
<li>
|
|
||||||
pomice.objects
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li><a href="pomice.html#module-pomice.objects">module</a>
|
|
||||||
</li>
|
|
||||||
</ul></li>
|
|
||||||
<li>
|
|
||||||
pomice.player
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li><a href="pomice.html#module-pomice.player">module</a>
|
|
||||||
</li>
|
|
||||||
</ul></li>
|
|
||||||
</ul></td>
|
|
||||||
<td style="width: 33%; vertical-align: top;"><ul>
|
|
||||||
<li>
|
|
||||||
pomice.pool
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li><a href="pomice.html#module-pomice.pool">module</a>
|
|
||||||
</li>
|
|
||||||
</ul></li>
|
|
||||||
<li>
|
|
||||||
pomice.spotify.album
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li><a href="pomice.spotify.html#module-pomice.spotify.album">module</a>
|
|
||||||
</li>
|
|
||||||
</ul></li>
|
|
||||||
<li>
|
|
||||||
pomice.spotify.client
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li><a href="pomice.spotify.html#module-pomice.spotify.client">module</a>
|
|
||||||
</li>
|
|
||||||
</ul></li>
|
|
||||||
<li>
|
|
||||||
pomice.spotify.exceptions
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li><a href="pomice.spotify.html#module-pomice.spotify.exceptions">module</a>
|
|
||||||
</li>
|
|
||||||
</ul></li>
|
|
||||||
<li>
|
|
||||||
pomice.spotify.playlist
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li><a href="pomice.spotify.html#module-pomice.spotify.playlist">module</a>
|
|
||||||
</li>
|
|
||||||
</ul></li>
|
|
||||||
<li>
|
|
||||||
pomice.spotify.track
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li><a href="pomice.spotify.html#module-pomice.spotify.track">module</a>
|
|
||||||
</li>
|
|
||||||
</ul></li>
|
|
||||||
<li>
|
|
||||||
pomice.utils
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li><a href="pomice.html#module-pomice.utils">module</a>
|
|
||||||
</li>
|
|
||||||
</ul></li>
|
|
||||||
<li><a href="pomice.html#pomice.events.PomiceEvent">PomiceEvent (class in pomice.events)</a>
|
|
||||||
</li>
|
|
||||||
<li><a href="pomice.html#pomice.exceptions.PomiceException">PomiceException</a>
|
|
||||||
</li>
|
|
||||||
<li><a href="pomice.html#pomice.pool.Node.pool">pool (pomice.pool.Node property)</a>
|
|
||||||
</li>
|
|
||||||
<li><a href="pomice.html#pomice.player.Player.position">position (pomice.player.Player property)</a>
|
|
||||||
</li>
|
|
||||||
</ul></td>
|
|
||||||
</tr></table>
|
|
||||||
|
|
||||||
<h2 id="R">R</h2>
|
|
||||||
<table style="width: 100%" class="indextable genindextable"><tr>
|
|
||||||
<td style="width: 33%; vertical-align: top;"><ul>
|
|
||||||
<li><a href="pomice.html#pomice.filters.Rotation">Rotation (class in pomice.filters)</a>
|
|
||||||
</li>
|
|
||||||
</ul></td>
|
|
||||||
</tr></table>
|
|
||||||
|
|
||||||
<h2 id="S">S</h2>
|
|
||||||
<table style="width: 100%" class="indextable genindextable"><tr>
|
|
||||||
<td style="width: 33%; vertical-align: top;"><ul>
|
|
||||||
<li><a href="pomice.html#pomice.enums.SearchType.scsearch">scsearch (pomice.enums.SearchType attribute)</a>
|
|
||||||
</li>
|
|
||||||
<li><a href="pomice.spotify.html#pomice.spotify.client.Client.search">search() (pomice.spotify.client.Client method)</a>
|
|
||||||
</li>
|
|
||||||
<li><a href="pomice.html#pomice.enums.SearchType">SearchType (class in pomice.enums)</a>
|
|
||||||
</li>
|
|
||||||
<li><a href="pomice.html#pomice.player.Player.seek">seek() (pomice.player.Player method)</a>
|
|
||||||
</li>
|
|
||||||
<li><a href="pomice.html#pomice.pool.Node.send">send() (pomice.pool.Node method)</a>
|
|
||||||
</li>
|
|
||||||
<li><a href="pomice.html#pomice.player.Player.set_filter">set_filter() (pomice.player.Player method)</a>
|
|
||||||
</li>
|
|
||||||
<li><a href="pomice.html#pomice.player.Player.set_pause">set_pause() (pomice.player.Player method)</a>
|
|
||||||
</li>
|
|
||||||
</ul></td>
|
|
||||||
<td style="width: 33%; vertical-align: top;"><ul>
|
|
||||||
<li><a href="pomice.html#pomice.player.Player.set_volume">set_volume() (pomice.player.Player method)</a>
|
|
||||||
</li>
|
|
||||||
<li><a href="pomice.html#pomice.exceptions.SpotifyAlbumLoadFailed">SpotifyAlbumLoadFailed</a>
|
|
||||||
</li>
|
|
||||||
<li><a href="pomice.html#pomice.exceptions.SpotifyPlaylistLoadFailed">SpotifyPlaylistLoadFailed</a>
|
|
||||||
</li>
|
|
||||||
<li><a href="pomice.spotify.html#pomice.spotify.exceptions.SpotifyRequestException">SpotifyRequestException</a>
|
|
||||||
</li>
|
|
||||||
<li><a href="pomice.html#pomice.exceptions.SpotifyTrackLoadFailed">SpotifyTrackLoadFailed</a>
|
|
||||||
</li>
|
|
||||||
<li><a href="pomice.html#pomice.pool.Node.stats">stats (pomice.pool.Node property)</a>
|
|
||||||
</li>
|
|
||||||
<li><a href="pomice.html#pomice.player.Player.stop">stop() (pomice.player.Player method)</a>
|
|
||||||
</li>
|
|
||||||
</ul></td>
|
|
||||||
</tr></table>
|
|
||||||
|
|
||||||
<h2 id="T">T</h2>
|
|
||||||
<table style="width: 100%" class="indextable genindextable"><tr>
|
|
||||||
<td style="width: 33%; vertical-align: top;"><ul>
|
|
||||||
<li><a href="pomice.html#pomice.objects.Playlist.thumbnail">thumbnail (pomice.objects.Playlist property)</a>
|
|
||||||
</li>
|
|
||||||
<li><a href="pomice.html#pomice.filters.Timescale">Timescale (class in pomice.filters)</a>
|
|
||||||
</li>
|
|
||||||
<li><a href="pomice.html#pomice.objects.Track">Track (class in pomice.objects)</a>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li><a href="pomice.spotify.html#pomice.spotify.track.Track">(class in pomice.spotify.track)</a>
|
|
||||||
</li>
|
|
||||||
</ul></li>
|
|
||||||
<li><a href="pomice.html#pomice.events.TrackEndEvent">TrackEndEvent (class in pomice.events)</a>
|
|
||||||
</li>
|
|
||||||
</ul></td>
|
|
||||||
<td style="width: 33%; vertical-align: top;"><ul>
|
|
||||||
<li><a href="pomice.html#pomice.events.TrackExceptionEvent">TrackExceptionEvent (class in pomice.events)</a>
|
|
||||||
</li>
|
|
||||||
<li><a href="pomice.html#pomice.exceptions.TrackInvalidPosition">TrackInvalidPosition</a>
|
|
||||||
</li>
|
|
||||||
<li><a href="pomice.html#pomice.exceptions.TrackLoadError">TrackLoadError</a>
|
|
||||||
</li>
|
|
||||||
<li><a href="pomice.html#pomice.events.TrackStartEvent">TrackStartEvent (class in pomice.events)</a>
|
|
||||||
</li>
|
|
||||||
<li><a href="pomice.html#pomice.events.TrackStuckEvent">TrackStuckEvent (class in pomice.events)</a>
|
|
||||||
</li>
|
|
||||||
<li><a href="pomice.html#pomice.filters.Tremolo">Tremolo (class in pomice.filters)</a>
|
|
||||||
</li>
|
|
||||||
</ul></td>
|
|
||||||
</tr></table>
|
|
||||||
|
|
||||||
<h2 id="U">U</h2>
|
|
||||||
<table style="width: 100%" class="indextable genindextable"><tr>
|
|
||||||
<td style="width: 33%; vertical-align: top;"><ul>
|
|
||||||
<li><a href="pomice.html#pomice.objects.Playlist.uri">uri (pomice.objects.Playlist property)</a>
|
|
||||||
</li>
|
|
||||||
</ul></td>
|
|
||||||
</tr></table>
|
|
||||||
|
|
||||||
<h2 id="V">V</h2>
|
|
||||||
<table style="width: 100%" class="indextable genindextable"><tr>
|
|
||||||
<td style="width: 33%; vertical-align: top;"><ul>
|
|
||||||
<li><a href="pomice.html#pomice.filters.Vibrato">Vibrato (class in pomice.filters)</a>
|
|
||||||
</li>
|
|
||||||
</ul></td>
|
|
||||||
<td style="width: 33%; vertical-align: top;"><ul>
|
|
||||||
<li><a href="pomice.html#pomice.player.Player.volume">volume (pomice.player.Player property)</a>
|
|
||||||
</li>
|
|
||||||
</ul></td>
|
|
||||||
</tr></table>
|
|
||||||
|
|
||||||
<h2 id="W">W</h2>
|
|
||||||
<table style="width: 100%" class="indextable genindextable"><tr>
|
|
||||||
<td style="width: 33%; vertical-align: top;"><ul>
|
|
||||||
<li><a href="pomice.html#pomice.events.WebSocketClosedEvent">WebSocketClosedEvent (class in pomice.events)</a>
|
|
||||||
</li>
|
|
||||||
</ul></td>
|
|
||||||
<td style="width: 33%; vertical-align: top;"><ul>
|
|
||||||
<li><a href="pomice.html#pomice.events.WebSocketClosedPayload">WebSocketClosedPayload (class in pomice.events)</a>
|
|
||||||
</li>
|
|
||||||
<li><a href="pomice.html#pomice.events.WebSocketOpenEvent">WebSocketOpenEvent (class in pomice.events)</a>
|
|
||||||
</li>
|
|
||||||
</ul></td>
|
|
||||||
</tr></table>
|
|
||||||
|
|
||||||
<h2 id="Y">Y</h2>
|
|
||||||
<table style="width: 100%" class="indextable genindextable"><tr>
|
|
||||||
<td style="width: 33%; vertical-align: top;"><ul>
|
|
||||||
<li><a href="pomice.html#pomice.enums.SearchType.ytmsearch">ytmsearch (pomice.enums.SearchType attribute)</a>
|
|
||||||
</li>
|
|
||||||
</ul></td>
|
|
||||||
<td style="width: 33%; vertical-align: top;"><ul>
|
|
||||||
<li><a href="pomice.html#pomice.enums.SearchType.ytsearch">ytsearch (pomice.enums.SearchType attribute)</a>
|
|
||||||
</li>
|
|
||||||
</ul></td>
|
|
||||||
</tr></table>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="sphinxsidebar" role="navigation" aria-label="main navigation">
|
|
||||||
<div class="sphinxsidebarwrapper">
|
|
||||||
<h1 class="logo"><a href="index.html">Pomice</a></h1>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<h3>Navigation</h3>
|
|
||||||
<ul>
|
|
||||||
<li class="toctree-l1"><a class="reference internal" href="pomice.html">Pomice</a></li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<div class="relations">
|
|
||||||
<h3>Related Topics</h3>
|
|
||||||
<ul>
|
|
||||||
<li><a href="index.html">Documentation overview</a><ul>
|
|
||||||
</ul></li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<div id="searchbox" style="display: none" role="search">
|
|
||||||
<h3 id="searchlabel">Quick search</h3>
|
|
||||||
<div class="searchformwrapper">
|
|
||||||
<form class="search" action="search.html" method="get">
|
|
||||||
<input type="text" name="q" aria-labelledby="searchlabel" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false"/>
|
|
||||||
<input type="submit" value="Go" />
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<script>$('#searchbox').show(0);</script>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="clearer"></div>
|
|
||||||
</div>
|
|
||||||
<div class="footer">
|
|
||||||
©2021, cloudwithax.
|
|
||||||
|
|
||||||
|
|
|
||||||
Powered by <a href="http://sphinx-doc.org/">Sphinx 4.2.0</a>
|
|
||||||
& <a href="https://github.com/bitprophet/alabaster">Alabaster 0.7.12</a>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
@ -1,218 +0,0 @@
|
||||||
|
|
||||||
<!DOCTYPE html>
|
|
||||||
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" /><meta name="generator" content="Docutils 0.17.1: http://docutils.sourceforge.net/" />
|
|
||||||
|
|
||||||
<title>Welcome to Pomice! — Pomice 1.1.1 documentation</title>
|
|
||||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
|
|
||||||
<link rel="stylesheet" type="text/css" href="_static/alabaster.css" />
|
|
||||||
<script data-url_root="./" id="documentation_options" src="_static/documentation_options.js"></script>
|
|
||||||
<script src="_static/jquery.js"></script>
|
|
||||||
<script src="_static/underscore.js"></script>
|
|
||||||
<script src="_static/doctools.js"></script>
|
|
||||||
<link rel="index" title="Index" href="genindex.html" />
|
|
||||||
<link rel="search" title="Search" href="search.html" />
|
|
||||||
<link rel="next" title="<no title>" href="modules.html" />
|
|
||||||
|
|
||||||
<link rel="stylesheet" href="_static/custom.css" type="text/css" />
|
|
||||||
|
|
||||||
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=0.9, maximum-scale=0.9" />
|
|
||||||
|
|
||||||
</head><body>
|
|
||||||
|
|
||||||
|
|
||||||
<div class="document">
|
|
||||||
<div class="documentwrapper">
|
|
||||||
<div class="bodywrapper">
|
|
||||||
|
|
||||||
|
|
||||||
<div class="body" role="main">
|
|
||||||
|
|
||||||
<section id="welcome-to-pomice">
|
|
||||||
<h1>Welcome to Pomice!<a class="headerlink" href="#welcome-to-pomice" title="Permalink to this headline">¶</a></h1>
|
|
||||||
<img alt="https://raw.githubusercontent.com/cloudwithax/pomice/main/banner.jpg" src="https://raw.githubusercontent.com/cloudwithax/pomice/main/banner.jpg" />
|
|
||||||
<p>The modern <a class="reference external" href="https://github.com/freyacodes/Lavalink">Lavalink</a> wrapper designed for <a class="reference external" href="https://github.com/Rapptz/discord.py">discord.py</a></p>
|
|
||||||
<section id="contents">
|
|
||||||
<h2>Contents<a class="headerlink" href="#contents" title="Permalink to this headline">¶</a></h2>
|
|
||||||
<div class="toctree-wrapper compound">
|
|
||||||
<ul>
|
|
||||||
<li class="toctree-l1"><a class="reference internal" href="pomice.html">Pomice</a><ul>
|
|
||||||
<li class="toctree-l2"><a class="reference internal" href="pomice.spotify.html">Spotify</a><ul>
|
|
||||||
<li class="toctree-l3"><a class="reference internal" href="pomice.spotify.html#module-pomice.spotify.album">spotify.Album</a></li>
|
|
||||||
<li class="toctree-l3"><a class="reference internal" href="pomice.spotify.html#module-pomice.spotify.client">spotify.Client</a></li>
|
|
||||||
<li class="toctree-l3"><a class="reference internal" href="pomice.spotify.html#module-pomice.spotify.exceptions">spotify.Exceptions</a></li>
|
|
||||||
<li class="toctree-l3"><a class="reference internal" href="pomice.spotify.html#module-pomice.spotify.playlist">spotify.Playlist</a></li>
|
|
||||||
<li class="toctree-l3"><a class="reference internal" href="pomice.spotify.html#module-pomice.spotify.track">spotify.Track</a></li>
|
|
||||||
</ul>
|
|
||||||
</li>
|
|
||||||
<li class="toctree-l2"><a class="reference internal" href="pomice.html#module-pomice.enums">Enums</a></li>
|
|
||||||
<li class="toctree-l2"><a class="reference internal" href="pomice.html#module-pomice.events">Events</a></li>
|
|
||||||
<li class="toctree-l2"><a class="reference internal" href="pomice.html#module-pomice.exceptions">Exceptions</a></li>
|
|
||||||
<li class="toctree-l2"><a class="reference internal" href="pomice.html#module-pomice.filters">Filters</a></li>
|
|
||||||
<li class="toctree-l2"><a class="reference internal" href="pomice.html#module-pomice.objects">Objects</a></li>
|
|
||||||
<li class="toctree-l2"><a class="reference internal" href="pomice.html#module-pomice.player">Player</a></li>
|
|
||||||
<li class="toctree-l2"><a class="reference internal" href="pomice.html#module-pomice.pool">Pool</a></li>
|
|
||||||
<li class="toctree-l2"><a class="reference internal" href="pomice.html#module-pomice.utils">Utils</a></li>
|
|
||||||
</ul>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
<section id="before-you-start">
|
|
||||||
<h2>Before You Start<a class="headerlink" href="#before-you-start" title="Permalink to this headline">¶</a></h2>
|
|
||||||
<p>This library is designed to work with the Lavalink audio delivery system,
|
|
||||||
which directly interfaces with Discord to provide buttery smooth audio without
|
|
||||||
wasting your precious system resources.</p>
|
|
||||||
<p>Pomice is made with convenience to the user, in that everything is easy to use
|
|
||||||
and is out of your way, while also being customizable.</p>
|
|
||||||
<p>In order to start using this library, please download a Lavalink node to start,
|
|
||||||
you can get it <a class="reference external" href="https://github.com/freyacodes/Lavalink/releases/latest">here</a></p>
|
|
||||||
</section>
|
|
||||||
<section id="quick-jumpstart">
|
|
||||||
<h2>Quick Jumpstart<a class="headerlink" href="#quick-jumpstart" title="Permalink to this headline">¶</a></h2>
|
|
||||||
<p>If you want a quick example as to how to start with Pomice, look below:</p>
|
|
||||||
<div class="highlight-python3 notranslate"><div class="highlight"><pre><span></span><span class="kn">import</span> <span class="nn">pomice</span>
|
|
||||||
<span class="kn">import</span> <span class="nn">discord</span>
|
|
||||||
<span class="kn">import</span> <span class="nn">re</span>
|
|
||||||
|
|
||||||
<span class="kn">from</span> <span class="nn">discord.ext</span> <span class="kn">import</span> <span class="n">commands</span>
|
|
||||||
|
|
||||||
<span class="n">URL_REG</span> <span class="o">=</span> <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="sa">r</span><span class="s1">'https?://(?:www\.)?.+'</span><span class="p">)</span>
|
|
||||||
|
|
||||||
<span class="k">class</span> <span class="nc">MyBot</span><span class="p">(</span><span class="n">commands</span><span class="o">.</span><span class="n">Bot</span><span class="p">):</span>
|
|
||||||
|
|
||||||
<span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-></span> <span class="kc">None</span><span class="p">:</span>
|
|
||||||
<span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__init__</span><span class="p">(</span><span class="n">command_prefix</span><span class="o">=</span><span class="s1">'!'</span><span class="p">,</span> <span class="n">activity</span><span class="o">=</span><span class="n">discord</span><span class="o">.</span><span class="n">Activity</span><span class="p">(</span><span class="nb">type</span><span class="o">=</span><span class="n">discord</span><span class="o">.</span><span class="n">ActivityType</span><span class="o">.</span><span class="n">listening</span><span class="p">,</span> <span class="n">name</span><span class="o">=</span><span class="s1">'to music!'</span><span class="p">))</span>
|
|
||||||
|
|
||||||
<span class="bp">self</span><span class="o">.</span><span class="n">add_cog</span><span class="p">(</span><span class="n">Music</span><span class="p">(</span><span class="bp">self</span><span class="p">))</span>
|
|
||||||
|
|
||||||
<span class="k">async</span> <span class="k">def</span> <span class="nf">on_ready</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-></span> <span class="kc">None</span><span class="p">:</span>
|
|
||||||
<span class="nb">print</span><span class="p">(</span><span class="s2">"I'm online!"</span><span class="p">)</span>
|
|
||||||
<span class="k">await</span> <span class="bp">self</span><span class="o">.</span><span class="n">cogs</span><span class="p">[</span><span class="s2">"Music"</span><span class="p">]</span><span class="o">.</span><span class="n">start_nodes</span><span class="p">()</span>
|
|
||||||
|
|
||||||
|
|
||||||
<span class="k">class</span> <span class="nc">Music</span><span class="p">(</span><span class="n">commands</span><span class="o">.</span><span class="n">Cog</span><span class="p">):</span>
|
|
||||||
|
|
||||||
<span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">bot</span><span class="p">)</span> <span class="o">-></span> <span class="kc">None</span><span class="p">:</span>
|
|
||||||
<span class="bp">self</span><span class="o">.</span><span class="n">bot</span> <span class="o">=</span> <span class="n">bot</span>
|
|
||||||
|
|
||||||
<span class="bp">self</span><span class="o">.</span><span class="n">pomice</span> <span class="o">=</span> <span class="n">pomice</span><span class="o">.</span><span class="n">NodePool</span><span class="p">()</span>
|
|
||||||
|
|
||||||
<span class="k">async</span> <span class="k">def</span> <span class="nf">start_nodes</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
|
||||||
<span class="k">await</span> <span class="bp">self</span><span class="o">.</span><span class="n">pomice</span><span class="o">.</span><span class="n">create_node</span><span class="p">(</span><span class="n">bot</span><span class="o">=</span><span class="bp">self</span><span class="o">.</span><span class="n">bot</span><span class="p">,</span> <span class="n">host</span><span class="o">=</span><span class="s1">'127.0.0.1'</span><span class="p">,</span> <span class="n">port</span><span class="o">=</span><span class="s1">'3030'</span><span class="p">,</span>
|
|
||||||
<span class="n">password</span><span class="o">=</span><span class="s1">'youshallnotpass'</span><span class="p">,</span> <span class="n">identifier</span><span class="o">=</span><span class="s1">'MAIN'</span><span class="p">)</span>
|
|
||||||
<span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">"Node is ready!"</span><span class="p">)</span>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<span class="nd">@commands</span><span class="o">.</span><span class="n">command</span><span class="p">(</span><span class="n">name</span><span class="o">=</span><span class="s1">'join'</span><span class="p">,</span> <span class="n">aliases</span><span class="o">=</span><span class="p">[</span><span class="s1">'connect'</span><span class="p">])</span>
|
|
||||||
<span class="k">async</span> <span class="k">def</span> <span class="nf">join</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">ctx</span><span class="p">:</span> <span class="n">commands</span><span class="o">.</span><span class="n">Context</span><span class="p">,</span> <span class="o">*</span><span class="p">,</span> <span class="n">channel</span><span class="p">:</span> <span class="n">discord</span><span class="o">.</span><span class="n">TextChannel</span> <span class="o">=</span> <span class="kc">None</span><span class="p">)</span> <span class="o">-></span> <span class="kc">None</span><span class="p">:</span>
|
|
||||||
|
|
||||||
<span class="k">if</span> <span class="ow">not</span> <span class="n">channel</span><span class="p">:</span>
|
|
||||||
<span class="n">channel</span> <span class="o">=</span> <span class="nb">getattr</span><span class="p">(</span><span class="n">ctx</span><span class="o">.</span><span class="n">author</span><span class="o">.</span><span class="n">voice</span><span class="p">,</span> <span class="s1">'channel'</span><span class="p">,</span> <span class="kc">None</span><span class="p">)</span>
|
|
||||||
<span class="k">if</span> <span class="ow">not</span> <span class="n">channel</span><span class="p">:</span>
|
|
||||||
<span class="k">raise</span> <span class="n">commands</span><span class="o">.</span><span class="n">CheckFailure</span><span class="p">(</span><span class="s1">'You must be in a voice channel to use this command'</span>
|
|
||||||
<span class="s1">'without specifying the channel argument.'</span><span class="p">)</span>
|
|
||||||
|
|
||||||
|
|
||||||
<span class="k">await</span> <span class="n">ctx</span><span class="o">.</span><span class="n">author</span><span class="o">.</span><span class="n">voice</span><span class="o">.</span><span class="n">channel</span><span class="o">.</span><span class="n">connect</span><span class="p">(</span><span class="bp">cls</span><span class="o">=</span><span class="n">pomice</span><span class="o">.</span><span class="n">Player</span><span class="p">)</span>
|
|
||||||
<span class="k">await</span> <span class="n">ctx</span><span class="o">.</span><span class="n">send</span><span class="p">(</span><span class="sa">f</span><span class="s1">'Joined the voice channel `</span><span class="si">{</span><span class="n">channel</span><span class="si">}</span><span class="s1">`'</span><span class="p">)</span>
|
|
||||||
|
|
||||||
<span class="nd">@commands</span><span class="o">.</span><span class="n">command</span><span class="p">(</span><span class="n">name</span><span class="o">=</span><span class="s1">'play'</span><span class="p">)</span>
|
|
||||||
<span class="k">async</span> <span class="k">def</span> <span class="nf">play</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">ctx</span><span class="p">,</span> <span class="o">*</span><span class="p">,</span> <span class="n">search</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-></span> <span class="kc">None</span><span class="p">:</span>
|
|
||||||
|
|
||||||
<span class="k">if</span> <span class="ow">not</span> <span class="n">ctx</span><span class="o">.</span><span class="n">voice_client</span><span class="p">:</span>
|
|
||||||
<span class="k">await</span> <span class="n">ctx</span><span class="o">.</span><span class="n">invoke</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">join</span><span class="p">)</span>
|
|
||||||
|
|
||||||
<span class="n">player</span> <span class="o">=</span> <span class="n">ctx</span><span class="o">.</span><span class="n">voice_client</span>
|
|
||||||
|
|
||||||
<span class="n">results</span> <span class="o">=</span> <span class="k">await</span> <span class="n">player</span><span class="o">.</span><span class="n">get_tracks</span><span class="p">(</span><span class="n">query</span><span class="o">=</span><span class="sa">f</span><span class="s1">'</span><span class="si">{</span><span class="n">search</span><span class="si">}</span><span class="s1">'</span><span class="p">)</span>
|
|
||||||
|
|
||||||
<span class="k">if</span> <span class="ow">not</span> <span class="n">results</span><span class="p">:</span>
|
|
||||||
<span class="k">raise</span> <span class="n">commands</span><span class="o">.</span><span class="n">CommandError</span><span class="p">(</span><span class="s1">'No results were found for that search term.'</span><span class="p">)</span>
|
|
||||||
|
|
||||||
<span class="k">if</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">results</span><span class="p">,</span> <span class="n">pomice</span><span class="o">.</span><span class="n">Playlist</span><span class="p">):</span>
|
|
||||||
<span class="k">await</span> <span class="n">player</span><span class="o">.</span><span class="n">play</span><span class="p">(</span><span class="n">track</span><span class="o">=</span><span class="n">results</span><span class="o">.</span><span class="n">tracks</span><span class="p">[</span><span class="mi">0</span><span class="p">])</span>
|
|
||||||
<span class="k">else</span><span class="p">:</span>
|
|
||||||
<span class="k">await</span> <span class="n">player</span><span class="o">.</span><span class="n">play</span><span class="p">(</span><span class="n">track</span><span class="o">=</span><span class="n">results</span><span class="p">[</span><span class="mi">0</span><span class="p">])</span>
|
|
||||||
|
|
||||||
|
|
||||||
<span class="n">bot</span> <span class="o">=</span> <span class="n">MyBot</span><span class="p">()</span>
|
|
||||||
<span class="n">bot</span><span class="o">.</span><span class="n">run</span><span class="p">(</span><span class="s2">"token here"</span><span class="p">)</span>
|
|
||||||
</pre></div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="sphinxsidebar" role="navigation" aria-label="main navigation">
|
|
||||||
<div class="sphinxsidebarwrapper">
|
|
||||||
<h1 class="logo"><a href="#">Pomice</a></h1>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<h3>Navigation</h3>
|
|
||||||
<ul>
|
|
||||||
<li class="toctree-l1"><a class="reference internal" href="pomice.html">Pomice</a></li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<div class="relations">
|
|
||||||
<h3>Related Topics</h3>
|
|
||||||
<ul>
|
|
||||||
<li><a href="#">Documentation overview</a><ul>
|
|
||||||
<li>Next: <a href="modules.html" title="next chapter"><no title></a></li>
|
|
||||||
</ul></li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<div id="searchbox" style="display: none" role="search">
|
|
||||||
<h3 id="searchlabel">Quick search</h3>
|
|
||||||
<div class="searchformwrapper">
|
|
||||||
<form class="search" action="search.html" method="get">
|
|
||||||
<input type="text" name="q" aria-labelledby="searchlabel" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false"/>
|
|
||||||
<input type="submit" value="Go" />
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<script>$('#searchbox').show(0);</script>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="clearer"></div>
|
|
||||||
</div>
|
|
||||||
<div class="footer">
|
|
||||||
©2021, cloudwithax.
|
|
||||||
|
|
||||||
|
|
|
||||||
Powered by <a href="http://sphinx-doc.org/">Sphinx 4.2.0</a>
|
|
||||||
& <a href="https://github.com/bitprophet/alabaster">Alabaster 0.7.12</a>
|
|
||||||
|
|
||||||
|
|
|
||||||
<a href="_sources/index.rst.txt"
|
|
||||||
rel="nofollow">Page source</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
@ -1,128 +0,0 @@
|
||||||
|
|
||||||
<!DOCTYPE html>
|
|
||||||
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" /><meta name="generator" content="Docutils 0.17.1: http://docutils.sourceforge.net/" />
|
|
||||||
|
|
||||||
<title><no title> — Pomice 1.1.1 documentation</title>
|
|
||||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
|
|
||||||
<link rel="stylesheet" type="text/css" href="_static/alabaster.css" />
|
|
||||||
<script data-url_root="./" id="documentation_options" src="_static/documentation_options.js"></script>
|
|
||||||
<script src="_static/jquery.js"></script>
|
|
||||||
<script src="_static/underscore.js"></script>
|
|
||||||
<script src="_static/doctools.js"></script>
|
|
||||||
<link rel="index" title="Index" href="genindex.html" />
|
|
||||||
<link rel="search" title="Search" href="search.html" />
|
|
||||||
<link rel="next" title="Pomice" href="pomice.html" />
|
|
||||||
<link rel="prev" title="Welcome to Pomice!" href="index.html" />
|
|
||||||
|
|
||||||
<link rel="stylesheet" href="_static/custom.css" type="text/css" />
|
|
||||||
|
|
||||||
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=0.9, maximum-scale=0.9" />
|
|
||||||
|
|
||||||
</head><body>
|
|
||||||
|
|
||||||
|
|
||||||
<div class="document">
|
|
||||||
<div class="documentwrapper">
|
|
||||||
<div class="bodywrapper">
|
|
||||||
|
|
||||||
|
|
||||||
<div class="body" role="main">
|
|
||||||
|
|
||||||
<div class="toctree-wrapper compound">
|
|
||||||
<ul>
|
|
||||||
<li class="toctree-l1"><a class="reference internal" href="pomice.html">Pomice</a><ul>
|
|
||||||
<li class="toctree-l2"><a class="reference internal" href="pomice.spotify.html">pomice.spotify</a><ul>
|
|
||||||
<li class="toctree-l3"><a class="reference internal" href="pomice.spotify.html#module-pomice.spotify.album">spotify.Album</a></li>
|
|
||||||
<li class="toctree-l3"><a class="reference internal" href="pomice.spotify.html#module-pomice.spotify.client">spotify.Client</a></li>
|
|
||||||
<li class="toctree-l3"><a class="reference internal" href="pomice.spotify.html#module-pomice.spotify.exceptions">spotify.Exceptions</a></li>
|
|
||||||
<li class="toctree-l3"><a class="reference internal" href="pomice.spotify.html#module-pomice.spotify.playlist">spotify.Playlist</a></li>
|
|
||||||
<li class="toctree-l3"><a class="reference internal" href="pomice.spotify.html#module-pomice.spotify.track">spotify.Track</a></li>
|
|
||||||
</ul>
|
|
||||||
</li>
|
|
||||||
<li class="toctree-l2"><a class="reference internal" href="pomice.html#module-pomice.enums">Enums</a></li>
|
|
||||||
<li class="toctree-l2"><a class="reference internal" href="pomice.html#module-pomice.events">Events</a></li>
|
|
||||||
<li class="toctree-l2"><a class="reference internal" href="pomice.html#module-pomice.exceptions">Exceptions</a></li>
|
|
||||||
<li class="toctree-l2"><a class="reference internal" href="pomice.html#module-pomice.filters">Filters</a></li>
|
|
||||||
<li class="toctree-l2"><a class="reference internal" href="pomice.html#module-pomice.objects">Objects</a></li>
|
|
||||||
<li class="toctree-l2"><a class="reference internal" href="pomice.html#module-pomice.player">Player</a></li>
|
|
||||||
<li class="toctree-l2"><a class="reference internal" href="pomice.html#module-pomice.pool">Pool</a></li>
|
|
||||||
<li class="toctree-l2"><a class="reference internal" href="pomice.html#module-pomice.utils">Utils</a></li>
|
|
||||||
</ul>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="sphinxsidebar" role="navigation" aria-label="main navigation">
|
|
||||||
<div class="sphinxsidebarwrapper">
|
|
||||||
<h1 class="logo"><a href="index.html">Pomice</a></h1>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<h3>Navigation</h3>
|
|
||||||
<ul>
|
|
||||||
<li class="toctree-l1"><a class="reference internal" href="pomice.html">Pomice</a></li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<div class="relations">
|
|
||||||
<h3>Related Topics</h3>
|
|
||||||
<ul>
|
|
||||||
<li><a href="index.html">Documentation overview</a><ul>
|
|
||||||
<li>Previous: <a href="index.html" title="previous chapter">Welcome to Pomice!</a></li>
|
|
||||||
<li>Next: <a href="pomice.html" title="next chapter">Pomice</a></li>
|
|
||||||
</ul></li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<div id="searchbox" style="display: none" role="search">
|
|
||||||
<h3 id="searchlabel">Quick search</h3>
|
|
||||||
<div class="searchformwrapper">
|
|
||||||
<form class="search" action="search.html" method="get">
|
|
||||||
<input type="text" name="q" aria-labelledby="searchlabel" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false"/>
|
|
||||||
<input type="submit" value="Go" />
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<script>$('#searchbox').show(0);</script>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="clearer"></div>
|
|
||||||
</div>
|
|
||||||
<div class="footer">
|
|
||||||
©2021, cloudwithax.
|
|
||||||
|
|
||||||
|
|
|
||||||
Powered by <a href="http://sphinx-doc.org/">Sphinx 4.2.0</a>
|
|
||||||
& <a href="https://github.com/bitprophet/alabaster">Alabaster 0.7.12</a>
|
|
||||||
|
|
||||||
|
|
|
||||||
<a href="_sources/modules.rst.txt"
|
|
||||||
rel="nofollow">Page source</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
Binary file not shown.
|
|
@ -1,854 +0,0 @@
|
||||||
|
|
||||||
<!DOCTYPE html>
|
|
||||||
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" /><meta name="generator" content="Docutils 0.17.1: http://docutils.sourceforge.net/" />
|
|
||||||
|
|
||||||
<title>Pomice — Pomice 1.1.1 documentation</title>
|
|
||||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
|
|
||||||
<link rel="stylesheet" type="text/css" href="_static/alabaster.css" />
|
|
||||||
<script data-url_root="./" id="documentation_options" src="_static/documentation_options.js"></script>
|
|
||||||
<script src="_static/jquery.js"></script>
|
|
||||||
<script src="_static/underscore.js"></script>
|
|
||||||
<script src="_static/doctools.js"></script>
|
|
||||||
<link rel="index" title="Index" href="genindex.html" />
|
|
||||||
<link rel="search" title="Search" href="search.html" />
|
|
||||||
<link rel="next" title="Spotify" href="pomice.spotify.html" />
|
|
||||||
<link rel="prev" title="<no title>" href="modules.html" />
|
|
||||||
|
|
||||||
<link rel="stylesheet" href="_static/custom.css" type="text/css" />
|
|
||||||
|
|
||||||
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=0.9, maximum-scale=0.9" />
|
|
||||||
|
|
||||||
</head><body>
|
|
||||||
|
|
||||||
|
|
||||||
<div class="document">
|
|
||||||
<div class="documentwrapper">
|
|
||||||
<div class="bodywrapper">
|
|
||||||
|
|
||||||
|
|
||||||
<div class="body" role="main">
|
|
||||||
|
|
||||||
<section id="pomice">
|
|
||||||
<h1>Pomice<a class="headerlink" href="#pomice" title="Permalink to this headline">¶</a></h1>
|
|
||||||
<div class="toctree-wrapper compound">
|
|
||||||
<ul>
|
|
||||||
<li class="toctree-l1"><a class="reference internal" href="pomice.spotify.html">Spotify</a><ul>
|
|
||||||
<li class="toctree-l2"><a class="reference internal" href="pomice.spotify.html#module-pomice.spotify.album">spotify.Album</a></li>
|
|
||||||
<li class="toctree-l2"><a class="reference internal" href="pomice.spotify.html#module-pomice.spotify.client">spotify.Client</a></li>
|
|
||||||
<li class="toctree-l2"><a class="reference internal" href="pomice.spotify.html#module-pomice.spotify.exceptions">spotify.Exceptions</a></li>
|
|
||||||
<li class="toctree-l2"><a class="reference internal" href="pomice.spotify.html#module-pomice.spotify.playlist">spotify.Playlist</a></li>
|
|
||||||
<li class="toctree-l2"><a class="reference internal" href="pomice.spotify.html#module-pomice.spotify.track">spotify.Track</a></li>
|
|
||||||
</ul>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<section id="module-pomice.enums">
|
|
||||||
<span id="enums"></span><h2>Enums<a class="headerlink" href="#module-pomice.enums" title="Permalink to this headline">¶</a></h2>
|
|
||||||
<dl class="py class">
|
|
||||||
<dt class="sig sig-object py" id="pomice.enums.SearchType">
|
|
||||||
<em class="property"><span class="pre">class</span> </em><span class="sig-prename descclassname"><span class="pre">pomice.enums.</span></span><span class="sig-name descname"><span class="pre">SearchType</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">value</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#pomice.enums.SearchType" title="Permalink to this definition">¶</a></dt>
|
|
||||||
<dd><p>Bases: <code class="xref py py-class docutils literal notranslate"><span class="pre">enum.Enum</span></code></p>
|
|
||||||
<p>The enum for the different search types for Pomice.
|
|
||||||
This feature is exclusively for the Spotify search feature of Pomice.
|
|
||||||
If you are not using this feature, this class is not necessary.</p>
|
|
||||||
<p>SearchType.ytsearch searches using regular Youtube,
|
|
||||||
which is best for all scenarios.</p>
|
|
||||||
<p>SearchType.ytmsearch searches using YouTube Music,
|
|
||||||
which is best for getting audio-only results.</p>
|
|
||||||
<p>SearchType.scsearch searches using SoundCloud,
|
|
||||||
which is an alternative to YouTube or YouTube Music.</p>
|
|
||||||
<dl class="py attribute">
|
|
||||||
<dt class="sig sig-object py" id="pomice.enums.SearchType.scsearch">
|
|
||||||
<span class="sig-name descname"><span class="pre">scsearch</span></span><em class="property"> <span class="pre">=</span> <span class="pre">'scsearch'</span></em><a class="headerlink" href="#pomice.enums.SearchType.scsearch" title="Permalink to this definition">¶</a></dt>
|
|
||||||
<dd></dd></dl>
|
|
||||||
|
|
||||||
<dl class="py attribute">
|
|
||||||
<dt class="sig sig-object py" id="pomice.enums.SearchType.ytmsearch">
|
|
||||||
<span class="sig-name descname"><span class="pre">ytmsearch</span></span><em class="property"> <span class="pre">=</span> <span class="pre">'ytmsearch'</span></em><a class="headerlink" href="#pomice.enums.SearchType.ytmsearch" title="Permalink to this definition">¶</a></dt>
|
|
||||||
<dd></dd></dl>
|
|
||||||
|
|
||||||
<dl class="py attribute">
|
|
||||||
<dt class="sig sig-object py" id="pomice.enums.SearchType.ytsearch">
|
|
||||||
<span class="sig-name descname"><span class="pre">ytsearch</span></span><em class="property"> <span class="pre">=</span> <span class="pre">'ytsearch'</span></em><a class="headerlink" href="#pomice.enums.SearchType.ytsearch" title="Permalink to this definition">¶</a></dt>
|
|
||||||
<dd></dd></dl>
|
|
||||||
|
|
||||||
</dd></dl>
|
|
||||||
|
|
||||||
</section>
|
|
||||||
<section id="module-pomice.events">
|
|
||||||
<span id="events"></span><h2>Events<a class="headerlink" href="#module-pomice.events" title="Permalink to this headline">¶</a></h2>
|
|
||||||
<dl class="py class">
|
|
||||||
<dt class="sig sig-object py" id="pomice.events.PomiceEvent">
|
|
||||||
<em class="property"><span class="pre">class</span> </em><span class="sig-prename descclassname"><span class="pre">pomice.events.</span></span><span class="sig-name descname"><span class="pre">PomiceEvent</span></span><a class="headerlink" href="#pomice.events.PomiceEvent" title="Permalink to this definition">¶</a></dt>
|
|
||||||
<dd><p>Bases: <code class="xref py py-class docutils literal notranslate"><span class="pre">object</span></code></p>
|
|
||||||
<p>The base class for all events dispatched by a node.
|
|
||||||
Every event must be formatted within your bot’s code as a listener.
|
|
||||||
i.e: If you want to listen for when a track starts, the event would be:
|
|
||||||
<code class="docutils literal notranslate"><span class="pre">`py</span>
|
|
||||||
<span class="pre">@bot.listen</span>
|
|
||||||
<span class="pre">async</span> <span class="pre">def</span> <span class="pre">on_pomice_track_start(self,</span> <span class="pre">event):</span>
|
|
||||||
<span class="pre">`</span></code></p>
|
|
||||||
<dl class="py method">
|
|
||||||
<dt class="sig sig-object py" id="pomice.events.PomiceEvent.dispatch">
|
|
||||||
<span class="sig-name descname"><span class="pre">dispatch</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">bot</span></span><span class="p"><span class="pre">:</span></span> <span class="n"><span class="pre">Union</span><span class="p"><span class="pre">[</span></span><span class="pre">discord.ext.commands.bot.AutoShardedBot</span><span class="p"><span class="pre">,</span> </span><span class="pre">discord.shard.AutoShardedClient</span><span class="p"><span class="pre">,</span> </span><span class="pre">discord.ext.commands.bot.Bot</span><span class="p"><span class="pre">,</span> </span><span class="pre">discord.client.Client</span><span class="p"><span class="pre">]</span></span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#pomice.events.PomiceEvent.dispatch" title="Permalink to this definition">¶</a></dt>
|
|
||||||
<dd></dd></dl>
|
|
||||||
|
|
||||||
<dl class="py attribute">
|
|
||||||
<dt class="sig sig-object py" id="pomice.events.PomiceEvent.handler_args">
|
|
||||||
<span class="sig-name descname"><span class="pre">handler_args</span></span><em class="property"> <span class="pre">=</span> <span class="pre">()</span></em><a class="headerlink" href="#pomice.events.PomiceEvent.handler_args" title="Permalink to this definition">¶</a></dt>
|
|
||||||
<dd></dd></dl>
|
|
||||||
|
|
||||||
<dl class="py attribute">
|
|
||||||
<dt class="sig sig-object py" id="pomice.events.PomiceEvent.name">
|
|
||||||
<span class="sig-name descname"><span class="pre">name</span></span><em class="property"> <span class="pre">=</span> <span class="pre">'event'</span></em><a class="headerlink" href="#pomice.events.PomiceEvent.name" title="Permalink to this definition">¶</a></dt>
|
|
||||||
<dd></dd></dl>
|
|
||||||
|
|
||||||
</dd></dl>
|
|
||||||
|
|
||||||
<dl class="py class">
|
|
||||||
<dt class="sig sig-object py" id="pomice.events.TrackEndEvent">
|
|
||||||
<em class="property"><span class="pre">class</span> </em><span class="sig-prename descclassname"><span class="pre">pomice.events.</span></span><span class="sig-name descname"><span class="pre">TrackEndEvent</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">data</span></span><span class="p"><span class="pre">:</span></span> <span class="n"><span class="pre">dict</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#pomice.events.TrackEndEvent" title="Permalink to this definition">¶</a></dt>
|
|
||||||
<dd><p>Bases: <a class="reference internal" href="#pomice.events.PomiceEvent" title="pomice.events.PomiceEvent"><code class="xref py py-class docutils literal notranslate"><span class="pre">pomice.events.PomiceEvent</span></code></a></p>
|
|
||||||
<p>Fired when a track has successfully ended.
|
|
||||||
Returns the player associated with the event along with the pomice.Track object and reason.</p>
|
|
||||||
<dl class="py attribute">
|
|
||||||
<dt class="sig sig-object py" id="pomice.events.TrackEndEvent.name">
|
|
||||||
<span class="sig-name descname"><span class="pre">name</span></span><em class="property"> <span class="pre">=</span> <span class="pre">'track_end'</span></em><a class="headerlink" href="#pomice.events.TrackEndEvent.name" title="Permalink to this definition">¶</a></dt>
|
|
||||||
<dd></dd></dl>
|
|
||||||
|
|
||||||
</dd></dl>
|
|
||||||
|
|
||||||
<dl class="py class">
|
|
||||||
<dt class="sig sig-object py" id="pomice.events.TrackExceptionEvent">
|
|
||||||
<em class="property"><span class="pre">class</span> </em><span class="sig-prename descclassname"><span class="pre">pomice.events.</span></span><span class="sig-name descname"><span class="pre">TrackExceptionEvent</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">data</span></span><span class="p"><span class="pre">:</span></span> <span class="n"><span class="pre">dict</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#pomice.events.TrackExceptionEvent" title="Permalink to this definition">¶</a></dt>
|
|
||||||
<dd><p>Bases: <a class="reference internal" href="#pomice.events.PomiceEvent" title="pomice.events.PomiceEvent"><code class="xref py py-class docutils literal notranslate"><span class="pre">pomice.events.PomiceEvent</span></code></a></p>
|
|
||||||
<p>Fired when a track error has occured.
|
|
||||||
Returns the player associated with the event along with the error code and exception.</p>
|
|
||||||
<dl class="py attribute">
|
|
||||||
<dt class="sig sig-object py" id="pomice.events.TrackExceptionEvent.name">
|
|
||||||
<span class="sig-name descname"><span class="pre">name</span></span><em class="property"> <span class="pre">=</span> <span class="pre">'track_exception'</span></em><a class="headerlink" href="#pomice.events.TrackExceptionEvent.name" title="Permalink to this definition">¶</a></dt>
|
|
||||||
<dd></dd></dl>
|
|
||||||
|
|
||||||
</dd></dl>
|
|
||||||
|
|
||||||
<dl class="py class">
|
|
||||||
<dt class="sig sig-object py" id="pomice.events.TrackStartEvent">
|
|
||||||
<em class="property"><span class="pre">class</span> </em><span class="sig-prename descclassname"><span class="pre">pomice.events.</span></span><span class="sig-name descname"><span class="pre">TrackStartEvent</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">data</span></span><span class="p"><span class="pre">:</span></span> <span class="n"><span class="pre">dict</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#pomice.events.TrackStartEvent" title="Permalink to this definition">¶</a></dt>
|
|
||||||
<dd><p>Bases: <a class="reference internal" href="#pomice.events.PomiceEvent" title="pomice.events.PomiceEvent"><code class="xref py py-class docutils literal notranslate"><span class="pre">pomice.events.PomiceEvent</span></code></a></p>
|
|
||||||
<p>Fired when a track has successfully started.
|
|
||||||
Returns the player associated with the event and the pomice.Track object.</p>
|
|
||||||
<dl class="py attribute">
|
|
||||||
<dt class="sig sig-object py" id="pomice.events.TrackStartEvent.name">
|
|
||||||
<span class="sig-name descname"><span class="pre">name</span></span><em class="property"> <span class="pre">=</span> <span class="pre">'track_start'</span></em><a class="headerlink" href="#pomice.events.TrackStartEvent.name" title="Permalink to this definition">¶</a></dt>
|
|
||||||
<dd></dd></dl>
|
|
||||||
|
|
||||||
</dd></dl>
|
|
||||||
|
|
||||||
<dl class="py class">
|
|
||||||
<dt class="sig sig-object py" id="pomice.events.TrackStuckEvent">
|
|
||||||
<em class="property"><span class="pre">class</span> </em><span class="sig-prename descclassname"><span class="pre">pomice.events.</span></span><span class="sig-name descname"><span class="pre">TrackStuckEvent</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">data</span></span><span class="p"><span class="pre">:</span></span> <span class="n"><span class="pre">dict</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#pomice.events.TrackStuckEvent" title="Permalink to this definition">¶</a></dt>
|
|
||||||
<dd><p>Bases: <a class="reference internal" href="#pomice.events.PomiceEvent" title="pomice.events.PomiceEvent"><code class="xref py py-class docutils literal notranslate"><span class="pre">pomice.events.PomiceEvent</span></code></a></p>
|
|
||||||
<p>Fired when a track is stuck and cannot be played. Returns the player
|
|
||||||
associated with the event along with the pomice.Track object
|
|
||||||
to be further parsed by the end user.</p>
|
|
||||||
<dl class="py attribute">
|
|
||||||
<dt class="sig sig-object py" id="pomice.events.TrackStuckEvent.name">
|
|
||||||
<span class="sig-name descname"><span class="pre">name</span></span><em class="property"> <span class="pre">=</span> <span class="pre">'track_stuck'</span></em><a class="headerlink" href="#pomice.events.TrackStuckEvent.name" title="Permalink to this definition">¶</a></dt>
|
|
||||||
<dd></dd></dl>
|
|
||||||
|
|
||||||
</dd></dl>
|
|
||||||
|
|
||||||
<dl class="py class">
|
|
||||||
<dt class="sig sig-object py" id="pomice.events.WebSocketClosedEvent">
|
|
||||||
<em class="property"><span class="pre">class</span> </em><span class="sig-prename descclassname"><span class="pre">pomice.events.</span></span><span class="sig-name descname"><span class="pre">WebSocketClosedEvent</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">data</span></span><span class="p"><span class="pre">:</span></span> <span class="n"><span class="pre">dict</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#pomice.events.WebSocketClosedEvent" title="Permalink to this definition">¶</a></dt>
|
|
||||||
<dd><p>Bases: <a class="reference internal" href="#pomice.events.PomiceEvent" title="pomice.events.PomiceEvent"><code class="xref py py-class docutils literal notranslate"><span class="pre">pomice.events.PomiceEvent</span></code></a></p>
|
|
||||||
<p>Fired when a websocket connection to a node has been closed.
|
|
||||||
Returns the reason and the error code.</p>
|
|
||||||
<dl class="py attribute">
|
|
||||||
<dt class="sig sig-object py" id="pomice.events.WebSocketClosedEvent.name">
|
|
||||||
<span class="sig-name descname"><span class="pre">name</span></span><em class="property"> <span class="pre">=</span> <span class="pre">'websocket_closed'</span></em><a class="headerlink" href="#pomice.events.WebSocketClosedEvent.name" title="Permalink to this definition">¶</a></dt>
|
|
||||||
<dd></dd></dl>
|
|
||||||
|
|
||||||
</dd></dl>
|
|
||||||
|
|
||||||
<dl class="py class">
|
|
||||||
<dt class="sig sig-object py" id="pomice.events.WebSocketClosedPayload">
|
|
||||||
<em class="property"><span class="pre">class</span> </em><span class="sig-prename descclassname"><span class="pre">pomice.events.</span></span><span class="sig-name descname"><span class="pre">WebSocketClosedPayload</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">data</span></span><span class="p"><span class="pre">:</span></span> <span class="n"><span class="pre">dict</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#pomice.events.WebSocketClosedPayload" title="Permalink to this definition">¶</a></dt>
|
|
||||||
<dd><p>Bases: <code class="xref py py-class docutils literal notranslate"><span class="pre">object</span></code></p>
|
|
||||||
</dd></dl>
|
|
||||||
|
|
||||||
<dl class="py class">
|
|
||||||
<dt class="sig sig-object py" id="pomice.events.WebSocketOpenEvent">
|
|
||||||
<em class="property"><span class="pre">class</span> </em><span class="sig-prename descclassname"><span class="pre">pomice.events.</span></span><span class="sig-name descname"><span class="pre">WebSocketOpenEvent</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">data</span></span><span class="p"><span class="pre">:</span></span> <span class="n"><span class="pre">dict</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#pomice.events.WebSocketOpenEvent" title="Permalink to this definition">¶</a></dt>
|
|
||||||
<dd><p>Bases: <a class="reference internal" href="#pomice.events.PomiceEvent" title="pomice.events.PomiceEvent"><code class="xref py py-class docutils literal notranslate"><span class="pre">pomice.events.PomiceEvent</span></code></a></p>
|
|
||||||
<p>Fired when a websocket connection to a node has been initiated.
|
|
||||||
Returns the target and the session SSRC.</p>
|
|
||||||
<dl class="py attribute">
|
|
||||||
<dt class="sig sig-object py" id="pomice.events.WebSocketOpenEvent.name">
|
|
||||||
<span class="sig-name descname"><span class="pre">name</span></span><em class="property"> <span class="pre">=</span> <span class="pre">'websocket_open'</span></em><a class="headerlink" href="#pomice.events.WebSocketOpenEvent.name" title="Permalink to this definition">¶</a></dt>
|
|
||||||
<dd></dd></dl>
|
|
||||||
|
|
||||||
</dd></dl>
|
|
||||||
|
|
||||||
</section>
|
|
||||||
<section id="module-pomice.exceptions">
|
|
||||||
<span id="exceptions"></span><h2>Exceptions<a class="headerlink" href="#module-pomice.exceptions" title="Permalink to this headline">¶</a></h2>
|
|
||||||
<dl class="py exception">
|
|
||||||
<dt class="sig sig-object py" id="pomice.exceptions.FilterInvalidArgument">
|
|
||||||
<em class="property"><span class="pre">exception</span> </em><span class="sig-prename descclassname"><span class="pre">pomice.exceptions.</span></span><span class="sig-name descname"><span class="pre">FilterInvalidArgument</span></span><a class="headerlink" href="#pomice.exceptions.FilterInvalidArgument" title="Permalink to this definition">¶</a></dt>
|
|
||||||
<dd><p>Bases: <a class="reference internal" href="#pomice.exceptions.PomiceException" title="pomice.exceptions.PomiceException"><code class="xref py py-class docutils literal notranslate"><span class="pre">pomice.exceptions.PomiceException</span></code></a></p>
|
|
||||||
<p>An invalid argument was passed to a filter.</p>
|
|
||||||
</dd></dl>
|
|
||||||
|
|
||||||
<dl class="py exception">
|
|
||||||
<dt class="sig sig-object py" id="pomice.exceptions.InvalidSpotifyClientAuthorization">
|
|
||||||
<em class="property"><span class="pre">exception</span> </em><span class="sig-prename descclassname"><span class="pre">pomice.exceptions.</span></span><span class="sig-name descname"><span class="pre">InvalidSpotifyClientAuthorization</span></span><a class="headerlink" href="#pomice.exceptions.InvalidSpotifyClientAuthorization" title="Permalink to this definition">¶</a></dt>
|
|
||||||
<dd><p>Bases: <a class="reference internal" href="#pomice.exceptions.PomiceException" title="pomice.exceptions.PomiceException"><code class="xref py py-class docutils literal notranslate"><span class="pre">pomice.exceptions.PomiceException</span></code></a></p>
|
|
||||||
<p>No Spotify client authorization was provided for track searching.</p>
|
|
||||||
</dd></dl>
|
|
||||||
|
|
||||||
<dl class="py exception">
|
|
||||||
<dt class="sig sig-object py" id="pomice.exceptions.NoNodesAvailable">
|
|
||||||
<em class="property"><span class="pre">exception</span> </em><span class="sig-prename descclassname"><span class="pre">pomice.exceptions.</span></span><span class="sig-name descname"><span class="pre">NoNodesAvailable</span></span><a class="headerlink" href="#pomice.exceptions.NoNodesAvailable" title="Permalink to this definition">¶</a></dt>
|
|
||||||
<dd><p>Bases: <a class="reference internal" href="#pomice.exceptions.PomiceException" title="pomice.exceptions.PomiceException"><code class="xref py py-class docutils literal notranslate"><span class="pre">pomice.exceptions.PomiceException</span></code></a></p>
|
|
||||||
<p>There are no nodes currently available.</p>
|
|
||||||
</dd></dl>
|
|
||||||
|
|
||||||
<dl class="py exception">
|
|
||||||
<dt class="sig sig-object py" id="pomice.exceptions.NodeConnectionClosed">
|
|
||||||
<em class="property"><span class="pre">exception</span> </em><span class="sig-prename descclassname"><span class="pre">pomice.exceptions.</span></span><span class="sig-name descname"><span class="pre">NodeConnectionClosed</span></span><a class="headerlink" href="#pomice.exceptions.NodeConnectionClosed" title="Permalink to this definition">¶</a></dt>
|
|
||||||
<dd><p>Bases: <a class="reference internal" href="#pomice.exceptions.NodeException" title="pomice.exceptions.NodeException"><code class="xref py py-class docutils literal notranslate"><span class="pre">pomice.exceptions.NodeException</span></code></a></p>
|
|
||||||
<p>The node’s connection is closed.</p>
|
|
||||||
</dd></dl>
|
|
||||||
|
|
||||||
<dl class="py exception">
|
|
||||||
<dt class="sig sig-object py" id="pomice.exceptions.NodeConnectionFailure">
|
|
||||||
<em class="property"><span class="pre">exception</span> </em><span class="sig-prename descclassname"><span class="pre">pomice.exceptions.</span></span><span class="sig-name descname"><span class="pre">NodeConnectionFailure</span></span><a class="headerlink" href="#pomice.exceptions.NodeConnectionFailure" title="Permalink to this definition">¶</a></dt>
|
|
||||||
<dd><p>Bases: <a class="reference internal" href="#pomice.exceptions.NodeException" title="pomice.exceptions.NodeException"><code class="xref py py-class docutils literal notranslate"><span class="pre">pomice.exceptions.NodeException</span></code></a></p>
|
|
||||||
<p>There was a problem while connecting to the node.</p>
|
|
||||||
</dd></dl>
|
|
||||||
|
|
||||||
<dl class="py exception">
|
|
||||||
<dt class="sig sig-object py" id="pomice.exceptions.NodeCreationError">
|
|
||||||
<em class="property"><span class="pre">exception</span> </em><span class="sig-prename descclassname"><span class="pre">pomice.exceptions.</span></span><span class="sig-name descname"><span class="pre">NodeCreationError</span></span><a class="headerlink" href="#pomice.exceptions.NodeCreationError" title="Permalink to this definition">¶</a></dt>
|
|
||||||
<dd><p>Bases: <a class="reference internal" href="#pomice.exceptions.NodeException" title="pomice.exceptions.NodeException"><code class="xref py py-class docutils literal notranslate"><span class="pre">pomice.exceptions.NodeException</span></code></a></p>
|
|
||||||
<p>There was a problem while creating the node.</p>
|
|
||||||
</dd></dl>
|
|
||||||
|
|
||||||
<dl class="py exception">
|
|
||||||
<dt class="sig sig-object py" id="pomice.exceptions.NodeException">
|
|
||||||
<em class="property"><span class="pre">exception</span> </em><span class="sig-prename descclassname"><span class="pre">pomice.exceptions.</span></span><span class="sig-name descname"><span class="pre">NodeException</span></span><a class="headerlink" href="#pomice.exceptions.NodeException" title="Permalink to this definition">¶</a></dt>
|
|
||||||
<dd><p>Bases: <code class="xref py py-class docutils literal notranslate"><span class="pre">Exception</span></code></p>
|
|
||||||
<p>Base exception for nodes.</p>
|
|
||||||
</dd></dl>
|
|
||||||
|
|
||||||
<dl class="py exception">
|
|
||||||
<dt class="sig sig-object py" id="pomice.exceptions.NodeNotAvailable">
|
|
||||||
<em class="property"><span class="pre">exception</span> </em><span class="sig-prename descclassname"><span class="pre">pomice.exceptions.</span></span><span class="sig-name descname"><span class="pre">NodeNotAvailable</span></span><a class="headerlink" href="#pomice.exceptions.NodeNotAvailable" title="Permalink to this definition">¶</a></dt>
|
|
||||||
<dd><p>Bases: <a class="reference internal" href="#pomice.exceptions.PomiceException" title="pomice.exceptions.PomiceException"><code class="xref py py-class docutils literal notranslate"><span class="pre">pomice.exceptions.PomiceException</span></code></a></p>
|
|
||||||
<p>The node is currently unavailable.</p>
|
|
||||||
</dd></dl>
|
|
||||||
|
|
||||||
<dl class="py exception">
|
|
||||||
<dt class="sig sig-object py" id="pomice.exceptions.PomiceException">
|
|
||||||
<em class="property"><span class="pre">exception</span> </em><span class="sig-prename descclassname"><span class="pre">pomice.exceptions.</span></span><span class="sig-name descname"><span class="pre">PomiceException</span></span><a class="headerlink" href="#pomice.exceptions.PomiceException" title="Permalink to this definition">¶</a></dt>
|
|
||||||
<dd><p>Bases: <code class="xref py py-class docutils literal notranslate"><span class="pre">Exception</span></code></p>
|
|
||||||
<p>Base of all Pomice exceptions.</p>
|
|
||||||
</dd></dl>
|
|
||||||
|
|
||||||
<dl class="py exception">
|
|
||||||
<dt class="sig sig-object py" id="pomice.exceptions.SpotifyAlbumLoadFailed">
|
|
||||||
<em class="property"><span class="pre">exception</span> </em><span class="sig-prename descclassname"><span class="pre">pomice.exceptions.</span></span><span class="sig-name descname"><span class="pre">SpotifyAlbumLoadFailed</span></span><a class="headerlink" href="#pomice.exceptions.SpotifyAlbumLoadFailed" title="Permalink to this definition">¶</a></dt>
|
|
||||||
<dd><p>Bases: <a class="reference internal" href="#pomice.exceptions.PomiceException" title="pomice.exceptions.PomiceException"><code class="xref py py-class docutils literal notranslate"><span class="pre">pomice.exceptions.PomiceException</span></code></a></p>
|
|
||||||
<p>The pomice Spotify client was unable to load an album.</p>
|
|
||||||
</dd></dl>
|
|
||||||
|
|
||||||
<dl class="py exception">
|
|
||||||
<dt class="sig sig-object py" id="pomice.exceptions.SpotifyPlaylistLoadFailed">
|
|
||||||
<em class="property"><span class="pre">exception</span> </em><span class="sig-prename descclassname"><span class="pre">pomice.exceptions.</span></span><span class="sig-name descname"><span class="pre">SpotifyPlaylistLoadFailed</span></span><a class="headerlink" href="#pomice.exceptions.SpotifyPlaylistLoadFailed" title="Permalink to this definition">¶</a></dt>
|
|
||||||
<dd><p>Bases: <a class="reference internal" href="#pomice.exceptions.PomiceException" title="pomice.exceptions.PomiceException"><code class="xref py py-class docutils literal notranslate"><span class="pre">pomice.exceptions.PomiceException</span></code></a></p>
|
|
||||||
<p>The pomice Spotify client was unable to load a playlist.</p>
|
|
||||||
</dd></dl>
|
|
||||||
|
|
||||||
<dl class="py exception">
|
|
||||||
<dt class="sig sig-object py" id="pomice.exceptions.SpotifyTrackLoadFailed">
|
|
||||||
<em class="property"><span class="pre">exception</span> </em><span class="sig-prename descclassname"><span class="pre">pomice.exceptions.</span></span><span class="sig-name descname"><span class="pre">SpotifyTrackLoadFailed</span></span><a class="headerlink" href="#pomice.exceptions.SpotifyTrackLoadFailed" title="Permalink to this definition">¶</a></dt>
|
|
||||||
<dd><p>Bases: <a class="reference internal" href="#pomice.exceptions.PomiceException" title="pomice.exceptions.PomiceException"><code class="xref py py-class docutils literal notranslate"><span class="pre">pomice.exceptions.PomiceException</span></code></a></p>
|
|
||||||
<p>The pomice Spotify client was unable to load a track.</p>
|
|
||||||
</dd></dl>
|
|
||||||
|
|
||||||
<dl class="py exception">
|
|
||||||
<dt class="sig sig-object py" id="pomice.exceptions.TrackInvalidPosition">
|
|
||||||
<em class="property"><span class="pre">exception</span> </em><span class="sig-prename descclassname"><span class="pre">pomice.exceptions.</span></span><span class="sig-name descname"><span class="pre">TrackInvalidPosition</span></span><a class="headerlink" href="#pomice.exceptions.TrackInvalidPosition" title="Permalink to this definition">¶</a></dt>
|
|
||||||
<dd><p>Bases: <a class="reference internal" href="#pomice.exceptions.PomiceException" title="pomice.exceptions.PomiceException"><code class="xref py py-class docutils literal notranslate"><span class="pre">pomice.exceptions.PomiceException</span></code></a></p>
|
|
||||||
<p>An invalid position was chosen for a track.</p>
|
|
||||||
</dd></dl>
|
|
||||||
|
|
||||||
<dl class="py exception">
|
|
||||||
<dt class="sig sig-object py" id="pomice.exceptions.TrackLoadError">
|
|
||||||
<em class="property"><span class="pre">exception</span> </em><span class="sig-prename descclassname"><span class="pre">pomice.exceptions.</span></span><span class="sig-name descname"><span class="pre">TrackLoadError</span></span><a class="headerlink" href="#pomice.exceptions.TrackLoadError" title="Permalink to this definition">¶</a></dt>
|
|
||||||
<dd><p>Bases: <a class="reference internal" href="#pomice.exceptions.PomiceException" title="pomice.exceptions.PomiceException"><code class="xref py py-class docutils literal notranslate"><span class="pre">pomice.exceptions.PomiceException</span></code></a></p>
|
|
||||||
<p>There was an error while loading a track.</p>
|
|
||||||
</dd></dl>
|
|
||||||
|
|
||||||
</section>
|
|
||||||
<section id="module-pomice.filters">
|
|
||||||
<span id="filters"></span><h2>Filters<a class="headerlink" href="#module-pomice.filters" title="Permalink to this headline">¶</a></h2>
|
|
||||||
<dl class="py class">
|
|
||||||
<dt class="sig sig-object py" id="pomice.filters.ChannelMix">
|
|
||||||
<em class="property"><span class="pre">class</span> </em><span class="sig-prename descclassname"><span class="pre">pomice.filters.</span></span><span class="sig-name descname"><span class="pre">ChannelMix</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="o"><span class="pre">*</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">left_to_left</span></span><span class="p"><span class="pre">:</span></span> <span class="n"><span class="pre">float</span></span> <span class="o"><span class="pre">=</span></span> <span class="default_value"><span class="pre">1</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">right_to_right</span></span><span class="p"><span class="pre">:</span></span> <span class="n"><span class="pre">float</span></span> <span class="o"><span class="pre">=</span></span> <span class="default_value"><span class="pre">1</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">left_to_right</span></span><span class="p"><span class="pre">:</span></span> <span class="n"><span class="pre">float</span></span> <span class="o"><span class="pre">=</span></span> <span class="default_value"><span class="pre">0</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">right_to_left</span></span><span class="p"><span class="pre">:</span></span> <span class="n"><span class="pre">float</span></span> <span class="o"><span class="pre">=</span></span> <span class="default_value"><span class="pre">0</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#pomice.filters.ChannelMix" title="Permalink to this definition">¶</a></dt>
|
|
||||||
<dd><p>Bases: <a class="reference internal" href="#pomice.filters.Filter" title="pomice.filters.Filter"><code class="xref py py-class docutils literal notranslate"><span class="pre">pomice.filters.Filter</span></code></a></p>
|
|
||||||
<p>Filter which manually adjusts the panning of the audio, which can make
|
|
||||||
for some cool effects when done correctly.</p>
|
|
||||||
</dd></dl>
|
|
||||||
|
|
||||||
<dl class="py class">
|
|
||||||
<dt class="sig sig-object py" id="pomice.filters.Distortion">
|
|
||||||
<em class="property"><span class="pre">class</span> </em><span class="sig-prename descclassname"><span class="pre">pomice.filters.</span></span><span class="sig-name descname"><span class="pre">Distortion</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="o"><span class="pre">*</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">sin_offset</span></span><span class="p"><span class="pre">:</span></span> <span class="n"><span class="pre">float</span></span> <span class="o"><span class="pre">=</span></span> <span class="default_value"><span class="pre">0</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">sin_scale</span></span><span class="p"><span class="pre">:</span></span> <span class="n"><span class="pre">float</span></span> <span class="o"><span class="pre">=</span></span> <span class="default_value"><span class="pre">1</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">cos_offset</span></span><span class="p"><span class="pre">:</span></span> <span class="n"><span class="pre">float</span></span> <span class="o"><span class="pre">=</span></span> <span class="default_value"><span class="pre">0</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">cos_scale</span></span><span class="p"><span class="pre">:</span></span> <span class="n"><span class="pre">float</span></span> <span class="o"><span class="pre">=</span></span> <span class="default_value"><span class="pre">1</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">tan_offset</span></span><span class="p"><span class="pre">:</span></span> <span class="n"><span class="pre">float</span></span> <span class="o"><span class="pre">=</span></span> <span class="default_value"><span class="pre">0</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">tan_scale</span></span><span class="p"><span class="pre">:</span></span> <span class="n"><span class="pre">float</span></span> <span class="o"><span class="pre">=</span></span> <span class="default_value"><span class="pre">1</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">offset</span></span><span class="p"><span class="pre">:</span></span> <span class="n"><span class="pre">float</span></span> <span class="o"><span class="pre">=</span></span> <span class="default_value"><span class="pre">0</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">scale</span></span><span class="p"><span class="pre">:</span></span> <span class="n"><span class="pre">float</span></span> <span class="o"><span class="pre">=</span></span> <span class="default_value"><span class="pre">1</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#pomice.filters.Distortion" title="Permalink to this definition">¶</a></dt>
|
|
||||||
<dd><p>Bases: <a class="reference internal" href="#pomice.filters.Filter" title="pomice.filters.Filter"><code class="xref py py-class docutils literal notranslate"><span class="pre">pomice.filters.Filter</span></code></a></p>
|
|
||||||
<p>Filter which generates a distortion effect. Useful for certain filter implementations where
|
|
||||||
distortion is needed.</p>
|
|
||||||
</dd></dl>
|
|
||||||
|
|
||||||
<dl class="py class">
|
|
||||||
<dt class="sig sig-object py" id="pomice.filters.Equalizer">
|
|
||||||
<em class="property"><span class="pre">class</span> </em><span class="sig-prename descclassname"><span class="pre">pomice.filters.</span></span><span class="sig-name descname"><span class="pre">Equalizer</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="o"><span class="pre">*</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">levels</span></span><span class="p"><span class="pre">:</span></span> <span class="n"><span class="pre">list</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#pomice.filters.Equalizer" title="Permalink to this definition">¶</a></dt>
|
|
||||||
<dd><p>Bases: <code class="xref py py-class docutils literal notranslate"><span class="pre">object</span></code></p>
|
|
||||||
<p>Filter which represents a 15 band equalizer.
|
|
||||||
You can adjust the dynamic of the sound using this filter.
|
|
||||||
i.e: Applying a bass boost filter to emphasize the bass in a song.
|
|
||||||
The format for the levels is: List[Tuple[int, float]]</p>
|
|
||||||
</dd></dl>
|
|
||||||
|
|
||||||
<dl class="py class">
|
|
||||||
<dt class="sig sig-object py" id="pomice.filters.Filter">
|
|
||||||
<em class="property"><span class="pre">class</span> </em><span class="sig-prename descclassname"><span class="pre">pomice.filters.</span></span><span class="sig-name descname"><span class="pre">Filter</span></span><a class="headerlink" href="#pomice.filters.Filter" title="Permalink to this definition">¶</a></dt>
|
|
||||||
<dd><p>Bases: <code class="xref py py-class docutils literal notranslate"><span class="pre">object</span></code></p>
|
|
||||||
</dd></dl>
|
|
||||||
|
|
||||||
<dl class="py class">
|
|
||||||
<dt class="sig sig-object py" id="pomice.filters.Karaoke">
|
|
||||||
<em class="property"><span class="pre">class</span> </em><span class="sig-prename descclassname"><span class="pre">pomice.filters.</span></span><span class="sig-name descname"><span class="pre">Karaoke</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="o"><span class="pre">*</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">level</span></span><span class="p"><span class="pre">:</span></span> <span class="n"><span class="pre">float</span></span> <span class="o"><span class="pre">=</span></span> <span class="default_value"><span class="pre">1.0</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">mono_level</span></span><span class="p"><span class="pre">:</span></span> <span class="n"><span class="pre">float</span></span> <span class="o"><span class="pre">=</span></span> <span class="default_value"><span class="pre">1.0</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">filter_band</span></span><span class="p"><span class="pre">:</span></span> <span class="n"><span class="pre">float</span></span> <span class="o"><span class="pre">=</span></span> <span class="default_value"><span class="pre">220.0</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">filter_width</span></span><span class="p"><span class="pre">:</span></span> <span class="n"><span class="pre">float</span></span> <span class="o"><span class="pre">=</span></span> <span class="default_value"><span class="pre">100.0</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#pomice.filters.Karaoke" title="Permalink to this definition">¶</a></dt>
|
|
||||||
<dd><p>Bases: <a class="reference internal" href="#pomice.filters.Filter" title="pomice.filters.Filter"><code class="xref py py-class docutils literal notranslate"><span class="pre">pomice.filters.Filter</span></code></a></p>
|
|
||||||
<p>Filter which filters the vocal track from any song and leaves the instrumental.
|
|
||||||
Best for karaoke as the filter implies.</p>
|
|
||||||
</dd></dl>
|
|
||||||
|
|
||||||
<dl class="py class">
|
|
||||||
<dt class="sig sig-object py" id="pomice.filters.LowPass">
|
|
||||||
<em class="property"><span class="pre">class</span> </em><span class="sig-prename descclassname"><span class="pre">pomice.filters.</span></span><span class="sig-name descname"><span class="pre">LowPass</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="o"><span class="pre">*</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">smoothing</span></span><span class="p"><span class="pre">:</span></span> <span class="n"><span class="pre">float</span></span> <span class="o"><span class="pre">=</span></span> <span class="default_value"><span class="pre">20</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#pomice.filters.LowPass" title="Permalink to this definition">¶</a></dt>
|
|
||||||
<dd><p>Bases: <a class="reference internal" href="#pomice.filters.Filter" title="pomice.filters.Filter"><code class="xref py py-class docutils literal notranslate"><span class="pre">pomice.filters.Filter</span></code></a></p>
|
|
||||||
<p>Filter which supresses higher frequencies and allows lower frequencies to pass.
|
|
||||||
You can also do this with the Equalizer filter, but this is an easier way to do it.</p>
|
|
||||||
</dd></dl>
|
|
||||||
|
|
||||||
<dl class="py class">
|
|
||||||
<dt class="sig sig-object py" id="pomice.filters.Rotation">
|
|
||||||
<em class="property"><span class="pre">class</span> </em><span class="sig-prename descclassname"><span class="pre">pomice.filters.</span></span><span class="sig-name descname"><span class="pre">Rotation</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="o"><span class="pre">*</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">rotation_hertz</span></span><span class="p"><span class="pre">:</span></span> <span class="n"><span class="pre">float</span></span> <span class="o"><span class="pre">=</span></span> <span class="default_value"><span class="pre">5</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#pomice.filters.Rotation" title="Permalink to this definition">¶</a></dt>
|
|
||||||
<dd><p>Bases: <a class="reference internal" href="#pomice.filters.Filter" title="pomice.filters.Filter"><code class="xref py py-class docutils literal notranslate"><span class="pre">pomice.filters.Filter</span></code></a></p>
|
|
||||||
<p>Filter which produces a stereo-like panning effect, which sounds like
|
|
||||||
the audio is being rotated around the listener’s head</p>
|
|
||||||
</dd></dl>
|
|
||||||
|
|
||||||
<dl class="py class">
|
|
||||||
<dt class="sig sig-object py" id="pomice.filters.Timescale">
|
|
||||||
<em class="property"><span class="pre">class</span> </em><span class="sig-prename descclassname"><span class="pre">pomice.filters.</span></span><span class="sig-name descname"><span class="pre">Timescale</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="o"><span class="pre">*</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">speed</span></span><span class="p"><span class="pre">:</span></span> <span class="n"><span class="pre">float</span></span> <span class="o"><span class="pre">=</span></span> <span class="default_value"><span class="pre">1.0</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">pitch</span></span><span class="p"><span class="pre">:</span></span> <span class="n"><span class="pre">float</span></span> <span class="o"><span class="pre">=</span></span> <span class="default_value"><span class="pre">1.0</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">rate</span></span><span class="p"><span class="pre">:</span></span> <span class="n"><span class="pre">float</span></span> <span class="o"><span class="pre">=</span></span> <span class="default_value"><span class="pre">1.0</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#pomice.filters.Timescale" title="Permalink to this definition">¶</a></dt>
|
|
||||||
<dd><p>Bases: <a class="reference internal" href="#pomice.filters.Filter" title="pomice.filters.Filter"><code class="xref py py-class docutils literal notranslate"><span class="pre">pomice.filters.Filter</span></code></a></p>
|
|
||||||
<p>Filter which changes the speed and pitch of a track.
|
|
||||||
Do be warned that this filter is bugged as of the lastest Lavalink dev version
|
|
||||||
due to the filter patch not corresponding with the track time.</p>
|
|
||||||
<p>In short this means that your track will either end prematurely or end later due to this.
|
|
||||||
This is not the library’s fault.</p>
|
|
||||||
</dd></dl>
|
|
||||||
|
|
||||||
<dl class="py class">
|
|
||||||
<dt class="sig sig-object py" id="pomice.filters.Tremolo">
|
|
||||||
<em class="property"><span class="pre">class</span> </em><span class="sig-prename descclassname"><span class="pre">pomice.filters.</span></span><span class="sig-name descname"><span class="pre">Tremolo</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="o"><span class="pre">*</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">frequency</span></span><span class="p"><span class="pre">:</span></span> <span class="n"><span class="pre">float</span></span> <span class="o"><span class="pre">=</span></span> <span class="default_value"><span class="pre">2.0</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">depth</span></span><span class="p"><span class="pre">:</span></span> <span class="n"><span class="pre">float</span></span> <span class="o"><span class="pre">=</span></span> <span class="default_value"><span class="pre">0.5</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#pomice.filters.Tremolo" title="Permalink to this definition">¶</a></dt>
|
|
||||||
<dd><p>Bases: <a class="reference internal" href="#pomice.filters.Filter" title="pomice.filters.Filter"><code class="xref py py-class docutils literal notranslate"><span class="pre">pomice.filters.Filter</span></code></a></p>
|
|
||||||
<p>Filter which produces a wavering tone in the music,
|
|
||||||
causing it to sound like the music is changing in volume rapidly.</p>
|
|
||||||
</dd></dl>
|
|
||||||
|
|
||||||
<dl class="py class">
|
|
||||||
<dt class="sig sig-object py" id="pomice.filters.Vibrato">
|
|
||||||
<em class="property"><span class="pre">class</span> </em><span class="sig-prename descclassname"><span class="pre">pomice.filters.</span></span><span class="sig-name descname"><span class="pre">Vibrato</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="o"><span class="pre">*</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">frequency</span></span><span class="p"><span class="pre">:</span></span> <span class="n"><span class="pre">float</span></span> <span class="o"><span class="pre">=</span></span> <span class="default_value"><span class="pre">2.0</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">depth</span></span><span class="p"><span class="pre">:</span></span> <span class="n"><span class="pre">float</span></span> <span class="o"><span class="pre">=</span></span> <span class="default_value"><span class="pre">0.5</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#pomice.filters.Vibrato" title="Permalink to this definition">¶</a></dt>
|
|
||||||
<dd><p>Bases: <a class="reference internal" href="#pomice.filters.Filter" title="pomice.filters.Filter"><code class="xref py py-class docutils literal notranslate"><span class="pre">pomice.filters.Filter</span></code></a></p>
|
|
||||||
<p>Filter which produces a wavering tone in the music, similar to the Tremolo filter,
|
|
||||||
but changes in pitch rather than volume.</p>
|
|
||||||
</dd></dl>
|
|
||||||
|
|
||||||
</section>
|
|
||||||
<section id="module-pomice.objects">
|
|
||||||
<span id="objects"></span><h2>Objects<a class="headerlink" href="#module-pomice.objects" title="Permalink to this headline">¶</a></h2>
|
|
||||||
<dl class="py class">
|
|
||||||
<dt class="sig sig-object py" id="pomice.objects.Playlist">
|
|
||||||
<em class="property"><span class="pre">class</span> </em><span class="sig-prename descclassname"><span class="pre">pomice.objects.</span></span><span class="sig-name descname"><span class="pre">Playlist</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="o"><span class="pre">*</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">playlist_info</span></span><span class="p"><span class="pre">:</span></span> <span class="n"><span class="pre">dict</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">tracks</span></span><span class="p"><span class="pre">:</span></span> <span class="n"><span class="pre">list</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">ctx</span></span><span class="p"><span class="pre">:</span></span> <span class="n"><span class="pre">Optional</span><span class="p"><span class="pre">[</span></span><span class="pre">discord.ext.commands.context.Context</span><span class="p"><span class="pre">]</span></span></span> <span class="o"><span class="pre">=</span></span> <span class="default_value"><span class="pre">None</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">spotify</span></span><span class="p"><span class="pre">:</span></span> <span class="n"><span class="pre">bool</span></span> <span class="o"><span class="pre">=</span></span> <span class="default_value"><span class="pre">False</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">thumbnail</span></span><span class="p"><span class="pre">:</span></span> <span class="n"><span class="pre">Optional</span><span class="p"><span class="pre">[</span></span><span class="pre">str</span><span class="p"><span class="pre">]</span></span></span> <span class="o"><span class="pre">=</span></span> <span class="default_value"><span class="pre">None</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">uri</span></span><span class="p"><span class="pre">:</span></span> <span class="n"><span class="pre">Optional</span><span class="p"><span class="pre">[</span></span><span class="pre">str</span><span class="p"><span class="pre">]</span></span></span> <span class="o"><span class="pre">=</span></span> <span class="default_value"><span class="pre">None</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#pomice.objects.Playlist" title="Permalink to this definition">¶</a></dt>
|
|
||||||
<dd><p>Bases: <code class="xref py py-class docutils literal notranslate"><span class="pre">object</span></code></p>
|
|
||||||
<p>The base playlist object.
|
|
||||||
Returns critical playlist information needed for parsing by Lavalink.
|
|
||||||
You can also pass in commands.Context to get a discord.py Context object in your tracks.</p>
|
|
||||||
<dl class="py property">
|
|
||||||
<dt class="sig sig-object py" id="pomice.objects.Playlist.thumbnail">
|
|
||||||
<em class="property"><span class="pre">property</span> </em><span class="sig-name descname"><span class="pre">thumbnail</span></span><em class="property"><span class="pre">:</span> <span class="pre">Optional</span><span class="p"><span class="pre">[</span></span><span class="pre">str</span><span class="p"><span class="pre">]</span></span></em><a class="headerlink" href="#pomice.objects.Playlist.thumbnail" title="Permalink to this definition">¶</a></dt>
|
|
||||||
<dd><p>Spotify album/playlist thumbnail, or None if not a Spotify object.</p>
|
|
||||||
</dd></dl>
|
|
||||||
|
|
||||||
<dl class="py property">
|
|
||||||
<dt class="sig sig-object py" id="pomice.objects.Playlist.uri">
|
|
||||||
<em class="property"><span class="pre">property</span> </em><span class="sig-name descname"><span class="pre">uri</span></span><em class="property"><span class="pre">:</span> <span class="pre">Optional</span><span class="p"><span class="pre">[</span></span><span class="pre">str</span><span class="p"><span class="pre">]</span></span></em><a class="headerlink" href="#pomice.objects.Playlist.uri" title="Permalink to this definition">¶</a></dt>
|
|
||||||
<dd><p>Spotify album/playlist URI, or None if not a Spotify object.</p>
|
|
||||||
</dd></dl>
|
|
||||||
|
|
||||||
</dd></dl>
|
|
||||||
|
|
||||||
<dl class="py class">
|
|
||||||
<dt class="sig sig-object py" id="pomice.objects.Track">
|
|
||||||
<em class="property"><span class="pre">class</span> </em><span class="sig-prename descclassname"><span class="pre">pomice.objects.</span></span><span class="sig-name descname"><span class="pre">Track</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="o"><span class="pre">*</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">track_id</span></span><span class="p"><span class="pre">:</span></span> <span class="n"><span class="pre">str</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">info</span></span><span class="p"><span class="pre">:</span></span> <span class="n"><span class="pre">dict</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">ctx</span></span><span class="p"><span class="pre">:</span></span> <span class="n"><span class="pre">Optional</span><span class="p"><span class="pre">[</span></span><span class="pre">discord.ext.commands.context.Context</span><span class="p"><span class="pre">]</span></span></span> <span class="o"><span class="pre">=</span></span> <span class="default_value"><span class="pre">None</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">spotify</span></span><span class="p"><span class="pre">:</span></span> <span class="n"><span class="pre">bool</span></span> <span class="o"><span class="pre">=</span></span> <span class="default_value"><span class="pre">False</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">search_type</span></span><span class="p"><span class="pre">:</span></span> <span class="n"><a class="reference internal" href="#pomice.enums.SearchType" title="pomice.enums.SearchType"><span class="pre">pomice.enums.SearchType</span></a></span> <span class="o"><span class="pre">=</span></span> <span class="default_value"><span class="pre">SearchType.ytsearch</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#pomice.objects.Track" title="Permalink to this definition">¶</a></dt>
|
|
||||||
<dd><p>Bases: <code class="xref py py-class docutils literal notranslate"><span class="pre">object</span></code></p>
|
|
||||||
<p>The base track object. Returns critical track information needed for parsing by Lavalink.
|
|
||||||
You can also pass in commands.Context to get a discord.py Context object in your track.</p>
|
|
||||||
</dd></dl>
|
|
||||||
|
|
||||||
</section>
|
|
||||||
<section id="module-pomice.player">
|
|
||||||
<span id="player"></span><h2>Player<a class="headerlink" href="#module-pomice.player" title="Permalink to this headline">¶</a></h2>
|
|
||||||
<dl class="py class">
|
|
||||||
<dt class="sig sig-object py" id="pomice.player.Player">
|
|
||||||
<em class="property"><span class="pre">class</span> </em><span class="sig-prename descclassname"><span class="pre">pomice.player.</span></span><span class="sig-name descname"><span class="pre">Player</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">client</span></span><span class="p"><span class="pre">:</span></span> <span class="n"><span class="pre">Optional</span><span class="p"><span class="pre">[</span></span><span class="pre">Union</span><span class="p"><span class="pre">[</span></span><span class="pre">discord.ext.commands.bot.AutoShardedBot</span><span class="p"><span class="pre">,</span> </span><span class="pre">discord.shard.AutoShardedClient</span><span class="p"><span class="pre">,</span> </span><span class="pre">discord.ext.commands.bot.Bot</span><span class="p"><span class="pre">,</span> </span><span class="pre">discord.client.Client</span><span class="p"><span class="pre">]</span></span><span class="p"><span class="pre">]</span></span></span> <span class="o"><span class="pre">=</span></span> <span class="default_value"><span class="pre">None</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">channel</span></span><span class="p"><span class="pre">:</span></span> <span class="n"><span class="pre">Optional</span><span class="p"><span class="pre">[</span></span><span class="pre">discord.channel.VoiceChannel</span><span class="p"><span class="pre">]</span></span></span> <span class="o"><span class="pre">=</span></span> <span class="default_value"><span class="pre">None</span></span></em>, <em class="sig-param"><span class="o"><span class="pre">**</span></span><span class="n"><span class="pre">kwargs</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#pomice.player.Player" title="Permalink to this definition">¶</a></dt>
|
|
||||||
<dd><p>Bases: <code class="xref py py-class docutils literal notranslate"><span class="pre">discord.voice_client.VoiceProtocol</span></code></p>
|
|
||||||
<p>The base player class for Pomice.
|
|
||||||
In order to initiate a player, you must pass it in as a cls when you connect to a channel.
|
|
||||||
i.e: <code class="docutils literal notranslate"><span class="pre">`py</span>
|
|
||||||
<span class="pre">await</span> <span class="pre">ctx.author.voice.channel.connect(cls=pomice.Player)</span>
|
|
||||||
<span class="pre">`</span></code></p>
|
|
||||||
<dl class="py property">
|
|
||||||
<dt class="sig sig-object py" id="pomice.player.Player.bot">
|
|
||||||
<em class="property"><span class="pre">property</span> </em><span class="sig-name descname"><span class="pre">bot</span></span><em class="property"><span class="pre">:</span> <span class="pre">Union</span><span class="p"><span class="pre">[</span></span><span class="pre">discord.ext.commands.bot.AutoShardedBot</span><span class="p"><span class="pre">,</span> </span><span class="pre">discord.shard.AutoShardedClient</span><span class="p"><span class="pre">,</span> </span><span class="pre">discord.ext.commands.bot.Bot</span><span class="p"><span class="pre">,</span> </span><span class="pre">discord.client.Client</span><span class="p"><span class="pre">]</span></span></em><a class="headerlink" href="#pomice.player.Player.bot" title="Permalink to this definition">¶</a></dt>
|
|
||||||
<dd><p>Property which returns the bot associated with this player instance</p>
|
|
||||||
</dd></dl>
|
|
||||||
|
|
||||||
<dl class="py method">
|
|
||||||
<dt class="sig sig-object py" id="pomice.player.Player.connect">
|
|
||||||
<em class="property"><span class="pre">async</span> </em><span class="sig-name descname"><span class="pre">connect</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="o"><span class="pre">*</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">timeout</span></span><span class="p"><span class="pre">:</span></span> <span class="n"><span class="pre">float</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">reconnect</span></span><span class="p"><span class="pre">:</span></span> <span class="n"><span class="pre">bool</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#pomice.player.Player.connect" title="Permalink to this definition">¶</a></dt>
|
|
||||||
<dd><p><a href="#id3"><span class="problematic" id="id4">|coro|</span></a></p>
|
|
||||||
<p>An abstract method called when the client initiates the connection request.</p>
|
|
||||||
<p>When a connection is requested initially, the library calls the constructor
|
|
||||||
under <code class="docutils literal notranslate"><span class="pre">__init__</span></code> and then calls <a class="reference internal" href="#pomice.player.Player.connect" title="pomice.player.Player.connect"><code class="xref py py-meth docutils literal notranslate"><span class="pre">connect()</span></code></a>. If <a class="reference internal" href="#pomice.player.Player.connect" title="pomice.player.Player.connect"><code class="xref py py-meth docutils literal notranslate"><span class="pre">connect()</span></code></a> fails at
|
|
||||||
some point then <a class="reference internal" href="#pomice.player.Player.disconnect" title="pomice.player.Player.disconnect"><code class="xref py py-meth docutils literal notranslate"><span class="pre">disconnect()</span></code></a> is called.</p>
|
|
||||||
<p>Within this method, to start the voice connection flow it is recommended to
|
|
||||||
use <code class="xref py py-meth docutils literal notranslate"><span class="pre">Guild.change_voice_state()</span></code> to start the flow. After which,
|
|
||||||
<a class="reference internal" href="#pomice.player.Player.on_voice_server_update" title="pomice.player.Player.on_voice_server_update"><code class="xref py py-meth docutils literal notranslate"><span class="pre">on_voice_server_update()</span></code></a> and <a class="reference internal" href="#pomice.player.Player.on_voice_state_update" title="pomice.player.Player.on_voice_state_update"><code class="xref py py-meth docutils literal notranslate"><span class="pre">on_voice_state_update()</span></code></a> will be called.
|
|
||||||
The order that these two are called is unspecified.</p>
|
|
||||||
<dl class="simple">
|
|
||||||
<dt>timeout: <code class="xref py py-class docutils literal notranslate"><span class="pre">float</span></code></dt><dd><p>The timeout for the connection.</p>
|
|
||||||
</dd>
|
|
||||||
<dt>reconnect: <code class="xref py py-class docutils literal notranslate"><span class="pre">bool</span></code></dt><dd><p>Whether reconnection is expected.</p>
|
|
||||||
</dd>
|
|
||||||
</dl>
|
|
||||||
</dd></dl>
|
|
||||||
|
|
||||||
<dl class="py property">
|
|
||||||
<dt class="sig sig-object py" id="pomice.player.Player.current">
|
|
||||||
<em class="property"><span class="pre">property</span> </em><span class="sig-name descname"><span class="pre">current</span></span><em class="property"><span class="pre">:</span> <a class="reference internal" href="#pomice.objects.Track" title="pomice.objects.Track"><span class="pre">pomice.objects.Track</span></a></em><a class="headerlink" href="#pomice.player.Player.current" title="Permalink to this definition">¶</a></dt>
|
|
||||||
<dd><p>Property which returns the currently playing track</p>
|
|
||||||
</dd></dl>
|
|
||||||
|
|
||||||
<dl class="py method">
|
|
||||||
<dt class="sig sig-object py" id="pomice.player.Player.destroy">
|
|
||||||
<em class="property"><span class="pre">async</span> </em><span class="sig-name descname"><span class="pre">destroy</span></span><span class="sig-paren">(</span><span class="sig-paren">)</span><a class="headerlink" href="#pomice.player.Player.destroy" title="Permalink to this definition">¶</a></dt>
|
|
||||||
<dd><p>Disconnects and destroys the player, and runs internal cleanup.</p>
|
|
||||||
</dd></dl>
|
|
||||||
|
|
||||||
<dl class="py method">
|
|
||||||
<dt class="sig sig-object py" id="pomice.player.Player.disconnect">
|
|
||||||
<em class="property"><span class="pre">async</span> </em><span class="sig-name descname"><span class="pre">disconnect</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="o"><span class="pre">*</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">force</span></span><span class="p"><span class="pre">:</span></span> <span class="n"><span class="pre">bool</span></span> <span class="o"><span class="pre">=</span></span> <span class="default_value"><span class="pre">False</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#pomice.player.Player.disconnect" title="Permalink to this definition">¶</a></dt>
|
|
||||||
<dd><p>Disconnects the player from voice.</p>
|
|
||||||
</dd></dl>
|
|
||||||
|
|
||||||
<dl class="py property">
|
|
||||||
<dt class="sig sig-object py" id="pomice.player.Player.filter">
|
|
||||||
<em class="property"><span class="pre">property</span> </em><span class="sig-name descname"><span class="pre">filter</span></span><em class="property"><span class="pre">:</span> <a class="reference internal" href="#pomice.filters.Filter" title="pomice.filters.Filter"><span class="pre">pomice.filters.Filter</span></a></em><a class="headerlink" href="#pomice.player.Player.filter" title="Permalink to this definition">¶</a></dt>
|
|
||||||
<dd><p>Property which returns the currently applied filter, if one is applied</p>
|
|
||||||
</dd></dl>
|
|
||||||
|
|
||||||
<dl class="py method">
|
|
||||||
<dt class="sig sig-object py" id="pomice.player.Player.get_tracks">
|
|
||||||
<em class="property"><span class="pre">async</span> </em><span class="sig-name descname"><span class="pre">get_tracks</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">query</span></span><span class="p"><span class="pre">:</span></span> <span class="n"><span class="pre">str</span></span></em>, <em class="sig-param"><span class="o"><span class="pre">*</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">ctx</span></span><span class="p"><span class="pre">:</span></span> <span class="n"><span class="pre">Optional</span><span class="p"><span class="pre">[</span></span><span class="pre">discord.ext.commands.context.Context</span><span class="p"><span class="pre">]</span></span></span> <span class="o"><span class="pre">=</span></span> <span class="default_value"><span class="pre">None</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">search_type</span></span><span class="p"><span class="pre">:</span></span> <span class="n"><a class="reference internal" href="#pomice.enums.SearchType" title="pomice.enums.SearchType"><span class="pre">pomice.enums.SearchType</span></a></span> <span class="o"><span class="pre">=</span></span> <span class="default_value"><span class="pre">SearchType.ytsearch</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#pomice.player.Player.get_tracks" title="Permalink to this definition">¶</a></dt>
|
|
||||||
<dd><p>Fetches tracks from the node’s REST api to parse into Lavalink.</p>
|
|
||||||
<p>If you passed in Spotify API credentials when you created the node,
|
|
||||||
you can also pass in a Spotify URL of a playlist, album or track and it will be parsed
|
|
||||||
accordingly.</p>
|
|
||||||
<p>You can also pass in a discord.py Context object to get a
|
|
||||||
Context object on any track you search.</p>
|
|
||||||
</dd></dl>
|
|
||||||
|
|
||||||
<dl class="py property">
|
|
||||||
<dt class="sig sig-object py" id="pomice.player.Player.guild">
|
|
||||||
<em class="property"><span class="pre">property</span> </em><span class="sig-name descname"><span class="pre">guild</span></span><em class="property"><span class="pre">:</span> <span class="pre">discord.guild.Guild</span></em><a class="headerlink" href="#pomice.player.Player.guild" title="Permalink to this definition">¶</a></dt>
|
|
||||||
<dd><p>Property which returns the guild associated with the player</p>
|
|
||||||
</dd></dl>
|
|
||||||
|
|
||||||
<dl class="py property">
|
|
||||||
<dt class="sig sig-object py" id="pomice.player.Player.is_connected">
|
|
||||||
<em class="property"><span class="pre">property</span> </em><span class="sig-name descname"><span class="pre">is_connected</span></span><em class="property"><span class="pre">:</span> <span class="pre">bool</span></em><a class="headerlink" href="#pomice.player.Player.is_connected" title="Permalink to this definition">¶</a></dt>
|
|
||||||
<dd><p>Property which returns whether or not the player is connected</p>
|
|
||||||
</dd></dl>
|
|
||||||
|
|
||||||
<dl class="py property">
|
|
||||||
<dt class="sig sig-object py" id="pomice.player.Player.is_dead">
|
|
||||||
<em class="property"><span class="pre">property</span> </em><span class="sig-name descname"><span class="pre">is_dead</span></span><em class="property"><span class="pre">:</span> <span class="pre">bool</span></em><a class="headerlink" href="#pomice.player.Player.is_dead" title="Permalink to this definition">¶</a></dt>
|
|
||||||
<dd><p>Returns a bool representing whether the player is dead or not.
|
|
||||||
A player is considered dead if it has been destroyed and removed from stored players.</p>
|
|
||||||
</dd></dl>
|
|
||||||
|
|
||||||
<dl class="py property">
|
|
||||||
<dt class="sig sig-object py" id="pomice.player.Player.is_paused">
|
|
||||||
<em class="property"><span class="pre">property</span> </em><span class="sig-name descname"><span class="pre">is_paused</span></span><em class="property"><span class="pre">:</span> <span class="pre">bool</span></em><a class="headerlink" href="#pomice.player.Player.is_paused" title="Permalink to this definition">¶</a></dt>
|
|
||||||
<dd><p>Property which returns whether or not the player has a track which is paused or not.</p>
|
|
||||||
</dd></dl>
|
|
||||||
|
|
||||||
<dl class="py property">
|
|
||||||
<dt class="sig sig-object py" id="pomice.player.Player.is_playing">
|
|
||||||
<em class="property"><span class="pre">property</span> </em><span class="sig-name descname"><span class="pre">is_playing</span></span><em class="property"><span class="pre">:</span> <span class="pre">bool</span></em><a class="headerlink" href="#pomice.player.Player.is_playing" title="Permalink to this definition">¶</a></dt>
|
|
||||||
<dd><p>Property which returns whether or not the player is actively playing a track.</p>
|
|
||||||
</dd></dl>
|
|
||||||
|
|
||||||
<dl class="py property">
|
|
||||||
<dt class="sig sig-object py" id="pomice.player.Player.node">
|
|
||||||
<em class="property"><span class="pre">property</span> </em><span class="sig-name descname"><span class="pre">node</span></span><em class="property"><span class="pre">:</span> <a class="reference internal" href="#pomice.pool.Node" title="pomice.pool.Node"><span class="pre">pomice.pool.Node</span></a></em><a class="headerlink" href="#pomice.player.Player.node" title="Permalink to this definition">¶</a></dt>
|
|
||||||
<dd><p>Property which returns the node the player is connected to</p>
|
|
||||||
</dd></dl>
|
|
||||||
|
|
||||||
<dl class="py method">
|
|
||||||
<dt class="sig sig-object py" id="pomice.player.Player.on_voice_server_update">
|
|
||||||
<em class="property"><span class="pre">async</span> </em><span class="sig-name descname"><span class="pre">on_voice_server_update</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">data</span></span><span class="p"><span class="pre">:</span></span> <span class="n"><span class="pre">dict</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#pomice.player.Player.on_voice_server_update" title="Permalink to this definition">¶</a></dt>
|
|
||||||
<dd><p><a href="#id5"><span class="problematic" id="id6">|coro|</span></a></p>
|
|
||||||
<p>An abstract method that is called when initially connecting to voice.
|
|
||||||
This corresponds to <code class="docutils literal notranslate"><span class="pre">VOICE_SERVER_UPDATE</span></code>.</p>
|
|
||||||
<dl class="simple">
|
|
||||||
<dt>data: <code class="xref py py-class docutils literal notranslate"><span class="pre">dict</span></code></dt><dd><p>The raw <a class="reference external" href="https://discord.com/developers/docs/topics/gateway#voice-server-update-voice-server-update-event-fields">voice server update payload</a>.</p>
|
|
||||||
</dd>
|
|
||||||
</dl>
|
|
||||||
</dd></dl>
|
|
||||||
|
|
||||||
<dl class="py method">
|
|
||||||
<dt class="sig sig-object py" id="pomice.player.Player.on_voice_state_update">
|
|
||||||
<em class="property"><span class="pre">async</span> </em><span class="sig-name descname"><span class="pre">on_voice_state_update</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">data</span></span><span class="p"><span class="pre">:</span></span> <span class="n"><span class="pre">dict</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#pomice.player.Player.on_voice_state_update" title="Permalink to this definition">¶</a></dt>
|
|
||||||
<dd><p><a href="#id7"><span class="problematic" id="id8">|coro|</span></a></p>
|
|
||||||
<p>An abstract method that is called when the client’s voice state
|
|
||||||
has changed. This corresponds to <code class="docutils literal notranslate"><span class="pre">VOICE_STATE_UPDATE</span></code>.</p>
|
|
||||||
<dl class="simple">
|
|
||||||
<dt>data: <code class="xref py py-class docutils literal notranslate"><span class="pre">dict</span></code></dt><dd><p>The raw <a class="reference external" href="https://discord.com/developers/docs/resources/voice#voice-state-object">voice state payload</a>.</p>
|
|
||||||
</dd>
|
|
||||||
</dl>
|
|
||||||
</dd></dl>
|
|
||||||
|
|
||||||
<dl class="py method">
|
|
||||||
<dt class="sig sig-object py" id="pomice.player.Player.play">
|
|
||||||
<em class="property"><span class="pre">async</span> </em><span class="sig-name descname"><span class="pre">play</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">track</span></span><span class="p"><span class="pre">:</span></span> <span class="n"><a class="reference internal" href="#pomice.objects.Track" title="pomice.objects.Track"><span class="pre">pomice.objects.Track</span></a></span></em>, <em class="sig-param"><span class="o"><span class="pre">*</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">start</span></span><span class="p"><span class="pre">:</span></span> <span class="n"><span class="pre">int</span></span> <span class="o"><span class="pre">=</span></span> <span class="default_value"><span class="pre">0</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">end</span></span><span class="p"><span class="pre">:</span></span> <span class="n"><span class="pre">int</span></span> <span class="o"><span class="pre">=</span></span> <span class="default_value"><span class="pre">0</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">ignore_if_playing</span></span><span class="p"><span class="pre">:</span></span> <span class="n"><span class="pre">bool</span></span> <span class="o"><span class="pre">=</span></span> <span class="default_value"><span class="pre">False</span></span></em><span class="sig-paren">)</span> <span class="sig-return"><span class="sig-return-icon">→</span> <span class="sig-return-typehint"><a class="reference internal" href="#pomice.objects.Track" title="pomice.objects.Track"><span class="pre">pomice.objects.Track</span></a></span></span><a class="headerlink" href="#pomice.player.Player.play" title="Permalink to this definition">¶</a></dt>
|
|
||||||
<dd><p>Plays a track. If a Spotify track is passed in, it will be handled accordingly.</p>
|
|
||||||
</dd></dl>
|
|
||||||
|
|
||||||
<dl class="py property">
|
|
||||||
<dt class="sig sig-object py" id="pomice.player.Player.position">
|
|
||||||
<em class="property"><span class="pre">property</span> </em><span class="sig-name descname"><span class="pre">position</span></span><em class="property"><span class="pre">:</span> <span class="pre">float</span></em><a class="headerlink" href="#pomice.player.Player.position" title="Permalink to this definition">¶</a></dt>
|
|
||||||
<dd><p>Property which returns the player’s position in a track in milliseconds</p>
|
|
||||||
</dd></dl>
|
|
||||||
|
|
||||||
<dl class="py method">
|
|
||||||
<dt class="sig sig-object py" id="pomice.player.Player.seek">
|
|
||||||
<em class="property"><span class="pre">async</span> </em><span class="sig-name descname"><span class="pre">seek</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">position</span></span><span class="p"><span class="pre">:</span></span> <span class="n"><span class="pre">float</span></span></em><span class="sig-paren">)</span> <span class="sig-return"><span class="sig-return-icon">→</span> <span class="sig-return-typehint"><span class="pre">float</span></span></span><a class="headerlink" href="#pomice.player.Player.seek" title="Permalink to this definition">¶</a></dt>
|
|
||||||
<dd><p>Seeks to a position in the currently playing track milliseconds</p>
|
|
||||||
</dd></dl>
|
|
||||||
|
|
||||||
<dl class="py method">
|
|
||||||
<dt class="sig sig-object py" id="pomice.player.Player.set_filter">
|
|
||||||
<em class="property"><span class="pre">async</span> </em><span class="sig-name descname"><span class="pre">set_filter</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">filter</span></span><span class="p"><span class="pre">:</span></span> <span class="n"><a class="reference internal" href="#pomice.filters.Filter" title="pomice.filters.Filter"><span class="pre">pomice.filters.Filter</span></a></span></em><span class="sig-paren">)</span> <span class="sig-return"><span class="sig-return-icon">→</span> <span class="sig-return-typehint"><a class="reference internal" href="#pomice.filters.Filter" title="pomice.filters.Filter"><span class="pre">pomice.filters.Filter</span></a></span></span><a class="headerlink" href="#pomice.player.Player.set_filter" title="Permalink to this definition">¶</a></dt>
|
|
||||||
<dd><p>Sets a filter of the player. Takes a pomice.Filter object.
|
|
||||||
This will only work if you are using the development version of Lavalink.</p>
|
|
||||||
</dd></dl>
|
|
||||||
|
|
||||||
<dl class="py method">
|
|
||||||
<dt class="sig sig-object py" id="pomice.player.Player.set_pause">
|
|
||||||
<em class="property"><span class="pre">async</span> </em><span class="sig-name descname"><span class="pre">set_pause</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">pause</span></span><span class="p"><span class="pre">:</span></span> <span class="n"><span class="pre">bool</span></span></em><span class="sig-paren">)</span> <span class="sig-return"><span class="sig-return-icon">→</span> <span class="sig-return-typehint"><span class="pre">bool</span></span></span><a class="headerlink" href="#pomice.player.Player.set_pause" title="Permalink to this definition">¶</a></dt>
|
|
||||||
<dd><p>Sets the pause state of the currently playing track.</p>
|
|
||||||
</dd></dl>
|
|
||||||
|
|
||||||
<dl class="py method">
|
|
||||||
<dt class="sig sig-object py" id="pomice.player.Player.set_volume">
|
|
||||||
<em class="property"><span class="pre">async</span> </em><span class="sig-name descname"><span class="pre">set_volume</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">volume</span></span><span class="p"><span class="pre">:</span></span> <span class="n"><span class="pre">int</span></span></em><span class="sig-paren">)</span> <span class="sig-return"><span class="sig-return-icon">→</span> <span class="sig-return-typehint"><span class="pre">int</span></span></span><a class="headerlink" href="#pomice.player.Player.set_volume" title="Permalink to this definition">¶</a></dt>
|
|
||||||
<dd><p>Sets the volume of the player as an integer. Lavalink accepts values from 0 to 500.</p>
|
|
||||||
</dd></dl>
|
|
||||||
|
|
||||||
<dl class="py method">
|
|
||||||
<dt class="sig sig-object py" id="pomice.player.Player.stop">
|
|
||||||
<em class="property"><span class="pre">async</span> </em><span class="sig-name descname"><span class="pre">stop</span></span><span class="sig-paren">(</span><span class="sig-paren">)</span><a class="headerlink" href="#pomice.player.Player.stop" title="Permalink to this definition">¶</a></dt>
|
|
||||||
<dd><p>Stops the currently playing track.</p>
|
|
||||||
</dd></dl>
|
|
||||||
|
|
||||||
<dl class="py property">
|
|
||||||
<dt class="sig sig-object py" id="pomice.player.Player.volume">
|
|
||||||
<em class="property"><span class="pre">property</span> </em><span class="sig-name descname"><span class="pre">volume</span></span><em class="property"><span class="pre">:</span> <span class="pre">int</span></em><a class="headerlink" href="#pomice.player.Player.volume" title="Permalink to this definition">¶</a></dt>
|
|
||||||
<dd><p>Property which returns the players current volume</p>
|
|
||||||
</dd></dl>
|
|
||||||
|
|
||||||
</dd></dl>
|
|
||||||
|
|
||||||
</section>
|
|
||||||
<section id="module-pomice.pool">
|
|
||||||
<span id="pool"></span><h2>Pool<a class="headerlink" href="#module-pomice.pool" title="Permalink to this headline">¶</a></h2>
|
|
||||||
<dl class="py class">
|
|
||||||
<dt class="sig sig-object py" id="pomice.pool.Node">
|
|
||||||
<em class="property"><span class="pre">class</span> </em><span class="sig-prename descclassname"><span class="pre">pomice.pool.</span></span><span class="sig-name descname"><span class="pre">Node</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="o"><span class="pre">*</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">pool</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">bot</span></span><span class="p"><span class="pre">:</span></span> <span class="n"><span class="pre">Union</span><span class="p"><span class="pre">[</span></span><span class="pre">discord.ext.commands.bot.AutoShardedBot</span><span class="p"><span class="pre">,</span> </span><span class="pre">discord.shard.AutoShardedClient</span><span class="p"><span class="pre">,</span> </span><span class="pre">discord.ext.commands.bot.Bot</span><span class="p"><span class="pre">,</span> </span><span class="pre">discord.client.Client</span><span class="p"><span class="pre">]</span></span></span></em>, <em class="sig-param"><span class="n"><span class="pre">host</span></span><span class="p"><span class="pre">:</span></span> <span class="n"><span class="pre">str</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">port</span></span><span class="p"><span class="pre">:</span></span> <span class="n"><span class="pre">int</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">password</span></span><span class="p"><span class="pre">:</span></span> <span class="n"><span class="pre">str</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">identifier</span></span><span class="p"><span class="pre">:</span></span> <span class="n"><span class="pre">str</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">session</span></span><span class="p"><span class="pre">:</span></span> <span class="n"><span class="pre">Optional</span><span class="p"><span class="pre">[</span></span><span class="pre">aiohttp.client.ClientSession</span><span class="p"><span class="pre">]</span></span></span></em>, <em class="sig-param"><span class="n"><span class="pre">spotify_client_id</span></span><span class="p"><span class="pre">:</span></span> <span class="n"><span class="pre">Optional</span><span class="p"><span class="pre">[</span></span><span class="pre">str</span><span class="p"><span class="pre">]</span></span></span></em>, <em class="sig-param"><span class="n"><span class="pre">spotify_client_secret</span></span><span class="p"><span class="pre">:</span></span> <span class="n"><span class="pre">Optional</span><span class="p"><span class="pre">[</span></span><span class="pre">str</span><span class="p"><span class="pre">]</span></span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#pomice.pool.Node" title="Permalink to this definition">¶</a></dt>
|
|
||||||
<dd><p>Bases: <code class="xref py py-class docutils literal notranslate"><span class="pre">object</span></code></p>
|
|
||||||
<p>The base class for a node.
|
|
||||||
This node object represents a Lavalink node.
|
|
||||||
To enable Spotify searching, pass in a proper Spotify Client ID and Spotify Client Secret</p>
|
|
||||||
<dl class="py property">
|
|
||||||
<dt class="sig sig-object py" id="pomice.pool.Node.bot">
|
|
||||||
<em class="property"><span class="pre">property</span> </em><span class="sig-name descname"><span class="pre">bot</span></span><em class="property"><span class="pre">:</span> <span class="pre">Union</span><span class="p"><span class="pre">[</span></span><span class="pre">discord.ext.commands.bot.AutoShardedBot</span><span class="p"><span class="pre">,</span> </span><span class="pre">discord.shard.AutoShardedClient</span><span class="p"><span class="pre">,</span> </span><span class="pre">discord.ext.commands.bot.Bot</span><span class="p"><span class="pre">,</span> </span><span class="pre">discord.client.Client</span><span class="p"><span class="pre">]</span></span></em><a class="headerlink" href="#pomice.pool.Node.bot" title="Permalink to this definition">¶</a></dt>
|
|
||||||
<dd><p>Property which returns the discord.py client linked to this node</p>
|
|
||||||
</dd></dl>
|
|
||||||
|
|
||||||
<dl class="py method">
|
|
||||||
<dt class="sig sig-object py" id="pomice.pool.Node.build_track">
|
|
||||||
<em class="property"><span class="pre">async</span> </em><span class="sig-name descname"><span class="pre">build_track</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">identifier</span></span><span class="p"><span class="pre">:</span></span> <span class="n"><span class="pre">str</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">ctx</span></span><span class="p"><span class="pre">:</span></span> <span class="n"><span class="pre">Optional</span><span class="p"><span class="pre">[</span></span><span class="pre">discord.ext.commands.context.Context</span><span class="p"><span class="pre">]</span></span></span> <span class="o"><span class="pre">=</span></span> <span class="default_value"><span class="pre">None</span></span></em><span class="sig-paren">)</span> <span class="sig-return"><span class="sig-return-icon">→</span> <span class="sig-return-typehint"><a class="reference internal" href="#pomice.objects.Track" title="pomice.objects.Track"><span class="pre">pomice.objects.Track</span></a></span></span><a class="headerlink" href="#pomice.pool.Node.build_track" title="Permalink to this definition">¶</a></dt>
|
|
||||||
<dd><p>Builds a track using a valid track identifier</p>
|
|
||||||
<p>You can also pass in a discord.py Context object to get a
|
|
||||||
Context object on the track it builds.</p>
|
|
||||||
</dd></dl>
|
|
||||||
|
|
||||||
<dl class="py method">
|
|
||||||
<dt class="sig sig-object py" id="pomice.pool.Node.connect">
|
|
||||||
<em class="property"><span class="pre">async</span> </em><span class="sig-name descname"><span class="pre">connect</span></span><span class="sig-paren">(</span><span class="sig-paren">)</span><a class="headerlink" href="#pomice.pool.Node.connect" title="Permalink to this definition">¶</a></dt>
|
|
||||||
<dd><p>Initiates a connection with a Lavalink node and adds it to the node pool.</p>
|
|
||||||
</dd></dl>
|
|
||||||
|
|
||||||
<dl class="py method">
|
|
||||||
<dt class="sig sig-object py" id="pomice.pool.Node.disconnect">
|
|
||||||
<em class="property"><span class="pre">async</span> </em><span class="sig-name descname"><span class="pre">disconnect</span></span><span class="sig-paren">(</span><span class="sig-paren">)</span><a class="headerlink" href="#pomice.pool.Node.disconnect" title="Permalink to this definition">¶</a></dt>
|
|
||||||
<dd><p>Disconnects a connected Lavalink node and removes it from the node pool.
|
|
||||||
This also destroys any players connected to the node.</p>
|
|
||||||
</dd></dl>
|
|
||||||
|
|
||||||
<dl class="py method">
|
|
||||||
<dt class="sig sig-object py" id="pomice.pool.Node.get_player">
|
|
||||||
<span class="sig-name descname"><span class="pre">get_player</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">guild_id</span></span><span class="p"><span class="pre">:</span></span> <span class="n"><span class="pre">int</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#pomice.pool.Node.get_player" title="Permalink to this definition">¶</a></dt>
|
|
||||||
<dd><p>Takes a guild ID as a parameter. Returns a pomice Player object.</p>
|
|
||||||
</dd></dl>
|
|
||||||
|
|
||||||
<dl class="py method">
|
|
||||||
<dt class="sig sig-object py" id="pomice.pool.Node.get_tracks">
|
|
||||||
<em class="property"><span class="pre">async</span> </em><span class="sig-name descname"><span class="pre">get_tracks</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">query</span></span><span class="p"><span class="pre">:</span></span> <span class="n"><span class="pre">str</span></span></em>, <em class="sig-param"><span class="o"><span class="pre">*</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">ctx</span></span><span class="p"><span class="pre">:</span></span> <span class="n"><span class="pre">Optional</span><span class="p"><span class="pre">[</span></span><span class="pre">discord.ext.commands.context.Context</span><span class="p"><span class="pre">]</span></span></span> <span class="o"><span class="pre">=</span></span> <span class="default_value"><span class="pre">None</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">search_type</span></span><span class="p"><span class="pre">:</span></span> <span class="n"><a class="reference internal" href="#pomice.enums.SearchType" title="pomice.enums.SearchType"><span class="pre">pomice.enums.SearchType</span></a></span> <span class="o"><span class="pre">=</span></span> <span class="default_value"><span class="pre">SearchType.ytsearch</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#pomice.pool.Node.get_tracks" title="Permalink to this definition">¶</a></dt>
|
|
||||||
<dd><p>Fetches tracks from the node’s REST api to parse into Lavalink.</p>
|
|
||||||
<p>If you passed in Spotify API credentials, you can also pass in a
|
|
||||||
Spotify URL of a playlist, album or track and it will be parsed accordingly.</p>
|
|
||||||
<p>You can also pass in a discord.py Context object to get a
|
|
||||||
Context object on any track you search.</p>
|
|
||||||
</dd></dl>
|
|
||||||
|
|
||||||
<dl class="py property">
|
|
||||||
<dt class="sig sig-object py" id="pomice.pool.Node.is_connected">
|
|
||||||
<em class="property"><span class="pre">property</span> </em><span class="sig-name descname"><span class="pre">is_connected</span></span><em class="property"><span class="pre">:</span> <span class="pre">bool</span></em><a class="headerlink" href="#pomice.pool.Node.is_connected" title="Permalink to this definition">¶</a></dt>
|
|
||||||
<dd><p>“Property which returns whether this node is connected or not</p>
|
|
||||||
</dd></dl>
|
|
||||||
|
|
||||||
<dl class="py property">
|
|
||||||
<dt class="sig sig-object py" id="pomice.pool.Node.latency">
|
|
||||||
<em class="property"><span class="pre">property</span> </em><span class="sig-name descname"><span class="pre">latency</span></span><em class="property"><span class="pre">:</span> <span class="pre">int</span></em><a class="headerlink" href="#pomice.pool.Node.latency" title="Permalink to this definition">¶</a></dt>
|
|
||||||
<dd><p>Property which returns the latency of the node in milliseconds</p>
|
|
||||||
</dd></dl>
|
|
||||||
|
|
||||||
<dl class="py property">
|
|
||||||
<dt class="sig sig-object py" id="pomice.pool.Node.player_count">
|
|
||||||
<em class="property"><span class="pre">property</span> </em><span class="sig-name descname"><span class="pre">player_count</span></span><em class="property"><span class="pre">:</span> <span class="pre">int</span></em><a class="headerlink" href="#pomice.pool.Node.player_count" title="Permalink to this definition">¶</a></dt>
|
|
||||||
<dd><p>Property which returns how many players are connected to this node</p>
|
|
||||||
</dd></dl>
|
|
||||||
|
|
||||||
<dl class="py property">
|
|
||||||
<dt class="sig sig-object py" id="pomice.pool.Node.players">
|
|
||||||
<em class="property"><span class="pre">property</span> </em><span class="sig-name descname"><span class="pre">players</span></span><em class="property"><span class="pre">:</span> <span class="pre">Dict</span><span class="p"><span class="pre">[</span></span><span class="pre">int</span><span class="p"><span class="pre">,</span> </span><span class="pre">Player</span><span class="p"><span class="pre">]</span></span></em><a class="headerlink" href="#pomice.pool.Node.players" title="Permalink to this definition">¶</a></dt>
|
|
||||||
<dd><p>Property which returns a dict containing the guild ID and the player object.</p>
|
|
||||||
</dd></dl>
|
|
||||||
|
|
||||||
<dl class="py property">
|
|
||||||
<dt class="sig sig-object py" id="pomice.pool.Node.pool">
|
|
||||||
<em class="property"><span class="pre">property</span> </em><span class="sig-name descname"><span class="pre">pool</span></span><a class="headerlink" href="#pomice.pool.Node.pool" title="Permalink to this definition">¶</a></dt>
|
|
||||||
<dd><p>Property which returns the pool this node is apart of</p>
|
|
||||||
</dd></dl>
|
|
||||||
|
|
||||||
<dl class="py method">
|
|
||||||
<dt class="sig sig-object py" id="pomice.pool.Node.send">
|
|
||||||
<em class="property"><span class="pre">async</span> </em><span class="sig-name descname"><span class="pre">send</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="o"><span class="pre">**</span></span><span class="n"><span class="pre">data</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#pomice.pool.Node.send" title="Permalink to this definition">¶</a></dt>
|
|
||||||
<dd></dd></dl>
|
|
||||||
|
|
||||||
<dl class="py property">
|
|
||||||
<dt class="sig sig-object py" id="pomice.pool.Node.stats">
|
|
||||||
<em class="property"><span class="pre">property</span> </em><span class="sig-name descname"><span class="pre">stats</span></span><em class="property"><span class="pre">:</span> <a class="reference internal" href="#pomice.utils.NodeStats" title="pomice.utils.NodeStats"><span class="pre">pomice.utils.NodeStats</span></a></em><a class="headerlink" href="#pomice.pool.Node.stats" title="Permalink to this definition">¶</a></dt>
|
|
||||||
<dd><p>Property which returns the node stats.</p>
|
|
||||||
</dd></dl>
|
|
||||||
|
|
||||||
</dd></dl>
|
|
||||||
|
|
||||||
<dl class="py class">
|
|
||||||
<dt class="sig sig-object py" id="pomice.pool.NodePool">
|
|
||||||
<em class="property"><span class="pre">class</span> </em><span class="sig-prename descclassname"><span class="pre">pomice.pool.</span></span><span class="sig-name descname"><span class="pre">NodePool</span></span><a class="headerlink" href="#pomice.pool.NodePool" title="Permalink to this definition">¶</a></dt>
|
|
||||||
<dd><p>Bases: <code class="xref py py-class docutils literal notranslate"><span class="pre">object</span></code></p>
|
|
||||||
<p>The base class for the node pool.
|
|
||||||
This holds all the nodes that are to be used by the bot.</p>
|
|
||||||
<dl class="py method">
|
|
||||||
<dt class="sig sig-object py" id="pomice.pool.NodePool.create_node">
|
|
||||||
<em class="property"><span class="pre">async</span> <span class="pre">classmethod</span> </em><span class="sig-name descname"><span class="pre">create_node</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="o"><span class="pre">*</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">bot</span></span><span class="p"><span class="pre">:</span></span> <span class="n"><span class="pre">Union</span><span class="p"><span class="pre">[</span></span><span class="pre">discord.ext.commands.bot.AutoShardedBot</span><span class="p"><span class="pre">,</span> </span><span class="pre">discord.shard.AutoShardedClient</span><span class="p"><span class="pre">,</span> </span><span class="pre">discord.ext.commands.bot.Bot</span><span class="p"><span class="pre">,</span> </span><span class="pre">discord.client.Client</span><span class="p"><span class="pre">]</span></span></span></em>, <em class="sig-param"><span class="n"><span class="pre">host</span></span><span class="p"><span class="pre">:</span></span> <span class="n"><span class="pre">str</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">port</span></span><span class="p"><span class="pre">:</span></span> <span class="n"><span class="pre">str</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">password</span></span><span class="p"><span class="pre">:</span></span> <span class="n"><span class="pre">str</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">identifier</span></span><span class="p"><span class="pre">:</span></span> <span class="n"><span class="pre">str</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">spotify_client_id</span></span><span class="p"><span class="pre">:</span></span> <span class="n"><span class="pre">Optional</span><span class="p"><span class="pre">[</span></span><span class="pre">str</span><span class="p"><span class="pre">]</span></span></span></em>, <em class="sig-param"><span class="n"><span class="pre">spotify_client_secret</span></span><span class="p"><span class="pre">:</span></span> <span class="n"><span class="pre">Optional</span><span class="p"><span class="pre">[</span></span><span class="pre">str</span><span class="p"><span class="pre">]</span></span></span></em>, <em class="sig-param"><span class="n"><span class="pre">session</span></span><span class="p"><span class="pre">:</span></span> <span class="n"><span class="pre">Optional</span><span class="p"><span class="pre">[</span></span><span class="pre">aiohttp.client.ClientSession</span><span class="p"><span class="pre">]</span></span></span> <span class="o"><span class="pre">=</span></span> <span class="default_value"><span class="pre">None</span></span></em><span class="sig-paren">)</span> <span class="sig-return"><span class="sig-return-icon">→</span> <span class="sig-return-typehint"><a class="reference internal" href="#pomice.pool.Node" title="pomice.pool.Node"><span class="pre">pomice.pool.Node</span></a></span></span><a class="headerlink" href="#pomice.pool.NodePool.create_node" title="Permalink to this definition">¶</a></dt>
|
|
||||||
<dd><p>Creates a Node object to be then added into the node pool.
|
|
||||||
For Spotify searching capabilites, pass in valid Spotify API credentials.</p>
|
|
||||||
</dd></dl>
|
|
||||||
|
|
||||||
<dl class="py method">
|
|
||||||
<dt class="sig sig-object py" id="pomice.pool.NodePool.get_node">
|
|
||||||
<em class="property"><span class="pre">classmethod</span> </em><span class="sig-name descname"><span class="pre">get_node</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="o"><span class="pre">*</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">identifier</span></span><span class="p"><span class="pre">:</span></span> <span class="n"><span class="pre">Optional</span><span class="p"><span class="pre">[</span></span><span class="pre">str</span><span class="p"><span class="pre">]</span></span></span> <span class="o"><span class="pre">=</span></span> <span class="default_value"><span class="pre">None</span></span></em><span class="sig-paren">)</span> <span class="sig-return"><span class="sig-return-icon">→</span> <span class="sig-return-typehint"><a class="reference internal" href="#pomice.pool.Node" title="pomice.pool.Node"><span class="pre">pomice.pool.Node</span></a></span></span><a class="headerlink" href="#pomice.pool.NodePool.get_node" title="Permalink to this definition">¶</a></dt>
|
|
||||||
<dd><p>Fetches a node from the node pool using it’s identifier.
|
|
||||||
If no identifier is provided, it will choose a node at random.</p>
|
|
||||||
</dd></dl>
|
|
||||||
|
|
||||||
<dl class="py property">
|
|
||||||
<dt class="sig sig-object py" id="pomice.pool.NodePool.node_count">
|
|
||||||
<em class="property"><span class="pre">property</span> </em><span class="sig-name descname"><span class="pre">node_count</span></span><a class="headerlink" href="#pomice.pool.NodePool.node_count" title="Permalink to this definition">¶</a></dt>
|
|
||||||
<dd></dd></dl>
|
|
||||||
|
|
||||||
<dl class="py property">
|
|
||||||
<dt class="sig sig-object py" id="pomice.pool.NodePool.nodes">
|
|
||||||
<em class="property"><span class="pre">property</span> </em><span class="sig-name descname"><span class="pre">nodes</span></span><em class="property"><span class="pre">:</span> <span class="pre">Dict</span><span class="p"><span class="pre">[</span></span><span class="pre">str</span><span class="p"><span class="pre">,</span> </span><a class="reference internal" href="#pomice.pool.Node" title="pomice.pool.Node"><span class="pre">pomice.pool.Node</span></a><span class="p"><span class="pre">]</span></span></em><a class="headerlink" href="#pomice.pool.NodePool.nodes" title="Permalink to this definition">¶</a></dt>
|
|
||||||
<dd><p>Property which returns a dict with the node identifier and the Node object.</p>
|
|
||||||
</dd></dl>
|
|
||||||
|
|
||||||
</dd></dl>
|
|
||||||
|
|
||||||
</section>
|
|
||||||
<section id="module-pomice.utils">
|
|
||||||
<span id="utils"></span><h2>Utils<a class="headerlink" href="#module-pomice.utils" title="Permalink to this headline">¶</a></h2>
|
|
||||||
<p>The MIT License (MIT)
|
|
||||||
Copyright (c) 2015-present Rapptz
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a
|
|
||||||
copy of this software and associated documentation files (the “Software”),
|
|
||||||
to deal in the Software without restriction, including without limitation
|
|
||||||
the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
||||||
and/or sell copies of the Software, and to permit persons to whom the
|
|
||||||
Software is furnished to do so, subject to the following conditions:
|
|
||||||
The above copyright notice and this permission notice shall be included in
|
|
||||||
all copies or substantial portions of the Software.
|
|
||||||
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
|
||||||
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
||||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
||||||
DEALINGS IN THE SOFTWARE.</p>
|
|
||||||
<dl class="py class">
|
|
||||||
<dt class="sig sig-object py" id="pomice.utils.ExponentialBackoff">
|
|
||||||
<em class="property"><span class="pre">class</span> </em><span class="sig-prename descclassname"><span class="pre">pomice.utils.</span></span><span class="sig-name descname"><span class="pre">ExponentialBackoff</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">base</span></span><span class="p"><span class="pre">:</span></span> <span class="n"><span class="pre">int</span></span> <span class="o"><span class="pre">=</span></span> <span class="default_value"><span class="pre">1</span></span></em>, <em class="sig-param"><span class="o"><span class="pre">*</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">integral</span></span><span class="p"><span class="pre">:</span></span> <span class="n"><span class="pre">bool</span></span> <span class="o"><span class="pre">=</span></span> <span class="default_value"><span class="pre">False</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#pomice.utils.ExponentialBackoff" title="Permalink to this definition">¶</a></dt>
|
|
||||||
<dd><p>Bases: <code class="xref py py-class docutils literal notranslate"><span class="pre">object</span></code></p>
|
|
||||||
<dl class="py method">
|
|
||||||
<dt class="sig sig-object py" id="pomice.utils.ExponentialBackoff.delay">
|
|
||||||
<span class="sig-name descname"><span class="pre">delay</span></span><span class="sig-paren">(</span><span class="sig-paren">)</span> <span class="sig-return"><span class="sig-return-icon">→</span> <span class="sig-return-typehint"><span class="pre">float</span></span></span><a class="headerlink" href="#pomice.utils.ExponentialBackoff.delay" title="Permalink to this definition">¶</a></dt>
|
|
||||||
<dd></dd></dl>
|
|
||||||
|
|
||||||
</dd></dl>
|
|
||||||
|
|
||||||
<dl class="py class">
|
|
||||||
<dt class="sig sig-object py" id="pomice.utils.NodeStats">
|
|
||||||
<em class="property"><span class="pre">class</span> </em><span class="sig-prename descclassname"><span class="pre">pomice.utils.</span></span><span class="sig-name descname"><span class="pre">NodeStats</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">data</span></span><span class="p"><span class="pre">:</span></span> <span class="n"><span class="pre">dict</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#pomice.utils.NodeStats" title="Permalink to this definition">¶</a></dt>
|
|
||||||
<dd><p>Bases: <code class="xref py py-class docutils literal notranslate"><span class="pre">object</span></code></p>
|
|
||||||
<p>The base class for the node stats object.
|
|
||||||
Gives critical information on the node, which is updated every minute.</p>
|
|
||||||
</dd></dl>
|
|
||||||
|
|
||||||
</section>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="sphinxsidebar" role="navigation" aria-label="main navigation">
|
|
||||||
<div class="sphinxsidebarwrapper">
|
|
||||||
<h1 class="logo"><a href="index.html">Pomice</a></h1>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<h3>Navigation</h3>
|
|
||||||
<ul class="current">
|
|
||||||
<li class="toctree-l1 current"><a class="current reference internal" href="#">Pomice</a><ul>
|
|
||||||
<li class="toctree-l2"><a class="reference internal" href="pomice.spotify.html">Spotify</a></li>
|
|
||||||
<li class="toctree-l2"><a class="reference internal" href="#module-pomice.enums">Enums</a></li>
|
|
||||||
<li class="toctree-l2"><a class="reference internal" href="#module-pomice.events">Events</a></li>
|
|
||||||
<li class="toctree-l2"><a class="reference internal" href="#module-pomice.exceptions">Exceptions</a></li>
|
|
||||||
<li class="toctree-l2"><a class="reference internal" href="#module-pomice.filters">Filters</a></li>
|
|
||||||
<li class="toctree-l2"><a class="reference internal" href="#module-pomice.objects">Objects</a></li>
|
|
||||||
<li class="toctree-l2"><a class="reference internal" href="#module-pomice.player">Player</a></li>
|
|
||||||
<li class="toctree-l2"><a class="reference internal" href="#module-pomice.pool">Pool</a></li>
|
|
||||||
<li class="toctree-l2"><a class="reference internal" href="#module-pomice.utils">Utils</a></li>
|
|
||||||
</ul>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<div class="relations">
|
|
||||||
<h3>Related Topics</h3>
|
|
||||||
<ul>
|
|
||||||
<li><a href="index.html">Documentation overview</a><ul>
|
|
||||||
<li><a href="modules.html"><no title></a><ul>
|
|
||||||
<li>Previous: <a href="modules.html" title="previous chapter"><no title></a></li>
|
|
||||||
<li>Next: <a href="pomice.spotify.html" title="next chapter">Spotify</a></li>
|
|
||||||
</ul></li>
|
|
||||||
</ul></li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<div id="searchbox" style="display: none" role="search">
|
|
||||||
<h3 id="searchlabel">Quick search</h3>
|
|
||||||
<div class="searchformwrapper">
|
|
||||||
<form class="search" action="search.html" method="get">
|
|
||||||
<input type="text" name="q" aria-labelledby="searchlabel" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false"/>
|
|
||||||
<input type="submit" value="Go" />
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<script>$('#searchbox').show(0);</script>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="clearer"></div>
|
|
||||||
</div>
|
|
||||||
<div class="footer">
|
|
||||||
©2021, cloudwithax.
|
|
||||||
|
|
||||||
|
|
|
||||||
Powered by <a href="http://sphinx-doc.org/">Sphinx 4.2.0</a>
|
|
||||||
& <a href="https://github.com/bitprophet/alabaster">Alabaster 0.7.12</a>
|
|
||||||
|
|
||||||
|
|
|
||||||
<a href="_sources/pomice.rst.txt"
|
|
||||||
rel="nofollow">Page source</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
@ -1,192 +0,0 @@
|
||||||
|
|
||||||
<!DOCTYPE html>
|
|
||||||
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" /><meta name="generator" content="Docutils 0.17.1: http://docutils.sourceforge.net/" />
|
|
||||||
|
|
||||||
<title>Spotify — Pomice 1.1.1 documentation</title>
|
|
||||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
|
|
||||||
<link rel="stylesheet" type="text/css" href="_static/alabaster.css" />
|
|
||||||
<script data-url_root="./" id="documentation_options" src="_static/documentation_options.js"></script>
|
|
||||||
<script src="_static/jquery.js"></script>
|
|
||||||
<script src="_static/underscore.js"></script>
|
|
||||||
<script src="_static/doctools.js"></script>
|
|
||||||
<link rel="index" title="Index" href="genindex.html" />
|
|
||||||
<link rel="search" title="Search" href="search.html" />
|
|
||||||
<link rel="prev" title="Pomice" href="pomice.html" />
|
|
||||||
|
|
||||||
<link rel="stylesheet" href="_static/custom.css" type="text/css" />
|
|
||||||
|
|
||||||
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=0.9, maximum-scale=0.9" />
|
|
||||||
|
|
||||||
</head><body>
|
|
||||||
|
|
||||||
|
|
||||||
<div class="document">
|
|
||||||
<div class="documentwrapper">
|
|
||||||
<div class="bodywrapper">
|
|
||||||
|
|
||||||
|
|
||||||
<div class="body" role="main">
|
|
||||||
|
|
||||||
<section id="spotify">
|
|
||||||
<h1>Spotify<a class="headerlink" href="#spotify" title="Permalink to this headline">¶</a></h1>
|
|
||||||
<section id="module-pomice.spotify.album">
|
|
||||||
<span id="spotify-album"></span><h2>spotify.Album<a class="headerlink" href="#module-pomice.spotify.album" title="Permalink to this headline">¶</a></h2>
|
|
||||||
<dl class="py class">
|
|
||||||
<dt class="sig sig-object py" id="pomice.spotify.album.Album">
|
|
||||||
<em class="property"><span class="pre">class</span> </em><span class="sig-prename descclassname"><span class="pre">pomice.spotify.album.</span></span><span class="sig-name descname"><span class="pre">Album</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">data</span></span><span class="p"><span class="pre">:</span></span> <span class="n"><span class="pre">dict</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#pomice.spotify.album.Album" title="Permalink to this definition">¶</a></dt>
|
|
||||||
<dd><p>Bases: <code class="xref py py-class docutils literal notranslate"><span class="pre">object</span></code></p>
|
|
||||||
<p>The base class for a Spotify album</p>
|
|
||||||
</dd></dl>
|
|
||||||
|
|
||||||
</section>
|
|
||||||
<section id="module-pomice.spotify.client">
|
|
||||||
<span id="spotify-client"></span><h2>spotify.Client<a class="headerlink" href="#module-pomice.spotify.client" title="Permalink to this headline">¶</a></h2>
|
|
||||||
<dl class="py class">
|
|
||||||
<dt class="sig sig-object py" id="pomice.spotify.client.Client">
|
|
||||||
<em class="property"><span class="pre">class</span> </em><span class="sig-prename descclassname"><span class="pre">pomice.spotify.client.</span></span><span class="sig-name descname"><span class="pre">Client</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">client_id</span></span><span class="p"><span class="pre">:</span></span> <span class="n"><span class="pre">str</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">client_secret</span></span><span class="p"><span class="pre">:</span></span> <span class="n"><span class="pre">str</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#pomice.spotify.client.Client" title="Permalink to this definition">¶</a></dt>
|
|
||||||
<dd><p>Bases: <code class="xref py py-class docutils literal notranslate"><span class="pre">object</span></code></p>
|
|
||||||
<p>The base client for the Spotify module of Pomice.
|
|
||||||
This class will do all the heavy lifting of getting all the metadata
|
|
||||||
for any Spotify URL you throw at it.</p>
|
|
||||||
<dl class="py method">
|
|
||||||
<dt class="sig sig-object py" id="pomice.spotify.client.Client.search">
|
|
||||||
<em class="property"><span class="pre">async</span> </em><span class="sig-name descname"><span class="pre">search</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="o"><span class="pre">*</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">query</span></span><span class="p"><span class="pre">:</span></span> <span class="n"><span class="pre">str</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#pomice.spotify.client.Client.search" title="Permalink to this definition">¶</a></dt>
|
|
||||||
<dd></dd></dl>
|
|
||||||
|
|
||||||
</dd></dl>
|
|
||||||
|
|
||||||
</section>
|
|
||||||
<section id="module-pomice.spotify.exceptions">
|
|
||||||
<span id="spotify-exceptions"></span><h2>spotify.Exceptions<a class="headerlink" href="#module-pomice.spotify.exceptions" title="Permalink to this headline">¶</a></h2>
|
|
||||||
<dl class="py exception">
|
|
||||||
<dt class="sig sig-object py" id="pomice.spotify.exceptions.InvalidSpotifyURL">
|
|
||||||
<em class="property"><span class="pre">exception</span> </em><span class="sig-prename descclassname"><span class="pre">pomice.spotify.exceptions.</span></span><span class="sig-name descname"><span class="pre">InvalidSpotifyURL</span></span><a class="headerlink" href="#pomice.spotify.exceptions.InvalidSpotifyURL" title="Permalink to this definition">¶</a></dt>
|
|
||||||
<dd><p>Bases: <code class="xref py py-class docutils literal notranslate"><span class="pre">Exception</span></code></p>
|
|
||||||
<p>An invalid Spotify URL was passed</p>
|
|
||||||
</dd></dl>
|
|
||||||
|
|
||||||
<dl class="py exception">
|
|
||||||
<dt class="sig sig-object py" id="pomice.spotify.exceptions.SpotifyRequestException">
|
|
||||||
<em class="property"><span class="pre">exception</span> </em><span class="sig-prename descclassname"><span class="pre">pomice.spotify.exceptions.</span></span><span class="sig-name descname"><span class="pre">SpotifyRequestException</span></span><a class="headerlink" href="#pomice.spotify.exceptions.SpotifyRequestException" title="Permalink to this definition">¶</a></dt>
|
|
||||||
<dd><p>Bases: <code class="xref py py-class docutils literal notranslate"><span class="pre">Exception</span></code></p>
|
|
||||||
<p>An error occurred when making a request to the Spotify API</p>
|
|
||||||
</dd></dl>
|
|
||||||
|
|
||||||
</section>
|
|
||||||
<section id="module-pomice.spotify.playlist">
|
|
||||||
<span id="spotify-playlist"></span><h2>spotify.Playlist<a class="headerlink" href="#module-pomice.spotify.playlist" title="Permalink to this headline">¶</a></h2>
|
|
||||||
<dl class="py class">
|
|
||||||
<dt class="sig sig-object py" id="pomice.spotify.playlist.Playlist">
|
|
||||||
<em class="property"><span class="pre">class</span> </em><span class="sig-prename descclassname"><span class="pre">pomice.spotify.playlist.</span></span><span class="sig-name descname"><span class="pre">Playlist</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">data</span></span><span class="p"><span class="pre">:</span></span> <span class="n"><span class="pre">dict</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">tracks</span></span><span class="p"><span class="pre">:</span></span> <span class="n"><span class="pre">List</span><span class="p"><span class="pre">[</span></span><a class="reference internal" href="#pomice.spotify.track.Track" title="pomice.spotify.track.Track"><span class="pre">pomice.spotify.track.Track</span></a><span class="p"><span class="pre">]</span></span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#pomice.spotify.playlist.Playlist" title="Permalink to this definition">¶</a></dt>
|
|
||||||
<dd><p>Bases: <code class="xref py py-class docutils literal notranslate"><span class="pre">object</span></code></p>
|
|
||||||
<p>The base class for a Spotify playlist</p>
|
|
||||||
</dd></dl>
|
|
||||||
|
|
||||||
</section>
|
|
||||||
<section id="module-pomice.spotify.track">
|
|
||||||
<span id="spotify-track"></span><h2>spotify.Track<a class="headerlink" href="#module-pomice.spotify.track" title="Permalink to this headline">¶</a></h2>
|
|
||||||
<dl class="py class">
|
|
||||||
<dt class="sig sig-object py" id="pomice.spotify.track.Track">
|
|
||||||
<em class="property"><span class="pre">class</span> </em><span class="sig-prename descclassname"><span class="pre">pomice.spotify.track.</span></span><span class="sig-name descname"><span class="pre">Track</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">data</span></span><span class="p"><span class="pre">:</span></span> <span class="n"><span class="pre">dict</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#pomice.spotify.track.Track" title="Permalink to this definition">¶</a></dt>
|
|
||||||
<dd><p>Bases: <code class="xref py py-class docutils literal notranslate"><span class="pre">object</span></code></p>
|
|
||||||
<p>The base class for a Spotify Track</p>
|
|
||||||
</dd></dl>
|
|
||||||
|
|
||||||
</section>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="sphinxsidebar" role="navigation" aria-label="main navigation">
|
|
||||||
<div class="sphinxsidebarwrapper">
|
|
||||||
<h1 class="logo"><a href="index.html">Pomice</a></h1>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<h3>Navigation</h3>
|
|
||||||
<ul class="current">
|
|
||||||
<li class="toctree-l1 current"><a class="reference internal" href="pomice.html">Pomice</a><ul class="current">
|
|
||||||
<li class="toctree-l2 current"><a class="current reference internal" href="#">Spotify</a><ul>
|
|
||||||
<li class="toctree-l3"><a class="reference internal" href="#module-pomice.spotify.album">spotify.Album</a></li>
|
|
||||||
<li class="toctree-l3"><a class="reference internal" href="#module-pomice.spotify.client">spotify.Client</a></li>
|
|
||||||
<li class="toctree-l3"><a class="reference internal" href="#module-pomice.spotify.exceptions">spotify.Exceptions</a></li>
|
|
||||||
<li class="toctree-l3"><a class="reference internal" href="#module-pomice.spotify.playlist">spotify.Playlist</a></li>
|
|
||||||
<li class="toctree-l3"><a class="reference internal" href="#module-pomice.spotify.track">spotify.Track</a></li>
|
|
||||||
</ul>
|
|
||||||
</li>
|
|
||||||
<li class="toctree-l2"><a class="reference internal" href="pomice.html#module-pomice.enums">Enums</a></li>
|
|
||||||
<li class="toctree-l2"><a class="reference internal" href="pomice.html#module-pomice.events">Events</a></li>
|
|
||||||
<li class="toctree-l2"><a class="reference internal" href="pomice.html#module-pomice.exceptions">Exceptions</a></li>
|
|
||||||
<li class="toctree-l2"><a class="reference internal" href="pomice.html#module-pomice.filters">Filters</a></li>
|
|
||||||
<li class="toctree-l2"><a class="reference internal" href="pomice.html#module-pomice.objects">Objects</a></li>
|
|
||||||
<li class="toctree-l2"><a class="reference internal" href="pomice.html#module-pomice.player">Player</a></li>
|
|
||||||
<li class="toctree-l2"><a class="reference internal" href="pomice.html#module-pomice.pool">Pool</a></li>
|
|
||||||
<li class="toctree-l2"><a class="reference internal" href="pomice.html#module-pomice.utils">Utils</a></li>
|
|
||||||
</ul>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<div class="relations">
|
|
||||||
<h3>Related Topics</h3>
|
|
||||||
<ul>
|
|
||||||
<li><a href="index.html">Documentation overview</a><ul>
|
|
||||||
<li><a href="modules.html"><no title></a><ul>
|
|
||||||
<li><a href="pomice.html">Pomice</a><ul>
|
|
||||||
<li>Previous: <a href="pomice.html" title="previous chapter">Pomice</a></li>
|
|
||||||
</ul></li>
|
|
||||||
</ul></li>
|
|
||||||
</ul></li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<div id="searchbox" style="display: none" role="search">
|
|
||||||
<h3 id="searchlabel">Quick search</h3>
|
|
||||||
<div class="searchformwrapper">
|
|
||||||
<form class="search" action="search.html" method="get">
|
|
||||||
<input type="text" name="q" aria-labelledby="searchlabel" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false"/>
|
|
||||||
<input type="submit" value="Go" />
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<script>$('#searchbox').show(0);</script>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="clearer"></div>
|
|
||||||
</div>
|
|
||||||
<div class="footer">
|
|
||||||
©2021, cloudwithax.
|
|
||||||
|
|
||||||
|
|
|
||||||
Powered by <a href="http://sphinx-doc.org/">Sphinx 4.2.0</a>
|
|
||||||
& <a href="https://github.com/bitprophet/alabaster">Alabaster 0.7.12</a>
|
|
||||||
|
|
||||||
|
|
|
||||||
<a href="_sources/pomice.spotify.rst.txt"
|
|
||||||
rel="nofollow">Page source</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
@ -1,183 +0,0 @@
|
||||||
|
|
||||||
<!DOCTYPE html>
|
|
||||||
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
||||||
<title>Python Module Index — Pomice 1.1.1 documentation</title>
|
|
||||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
|
|
||||||
<link rel="stylesheet" type="text/css" href="_static/alabaster.css" />
|
|
||||||
<script data-url_root="./" id="documentation_options" src="_static/documentation_options.js"></script>
|
|
||||||
<script src="_static/jquery.js"></script>
|
|
||||||
<script src="_static/underscore.js"></script>
|
|
||||||
<script src="_static/doctools.js"></script>
|
|
||||||
<link rel="index" title="Index" href="genindex.html" />
|
|
||||||
<link rel="search" title="Search" href="search.html" />
|
|
||||||
|
|
||||||
|
|
||||||
<link rel="stylesheet" href="_static/custom.css" type="text/css" />
|
|
||||||
|
|
||||||
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=0.9, maximum-scale=0.9" />
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</head><body>
|
|
||||||
|
|
||||||
|
|
||||||
<div class="document">
|
|
||||||
<div class="documentwrapper">
|
|
||||||
<div class="bodywrapper">
|
|
||||||
|
|
||||||
|
|
||||||
<div class="body" role="main">
|
|
||||||
|
|
||||||
|
|
||||||
<h1>Python Module Index</h1>
|
|
||||||
|
|
||||||
<div class="modindex-jumpbox">
|
|
||||||
<a href="#cap-p"><strong>p</strong></a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<table class="indextable modindextable">
|
|
||||||
<tr class="pcap"><td></td><td> </td><td></td></tr>
|
|
||||||
<tr class="cap" id="cap-p"><td></td><td>
|
|
||||||
<strong>p</strong></td><td></td></tr>
|
|
||||||
<tr>
|
|
||||||
<td><img src="_static/minus.png" class="toggler"
|
|
||||||
id="toggle-1" style="display: none" alt="-" /></td>
|
|
||||||
<td>
|
|
||||||
<code class="xref">pomice</code></td><td>
|
|
||||||
<em></em></td></tr>
|
|
||||||
<tr class="cg-1">
|
|
||||||
<td></td>
|
|
||||||
<td>   
|
|
||||||
<a href="pomice.html#module-pomice.enums"><code class="xref">pomice.enums</code></a></td><td>
|
|
||||||
<em></em></td></tr>
|
|
||||||
<tr class="cg-1">
|
|
||||||
<td></td>
|
|
||||||
<td>   
|
|
||||||
<a href="pomice.html#module-pomice.events"><code class="xref">pomice.events</code></a></td><td>
|
|
||||||
<em></em></td></tr>
|
|
||||||
<tr class="cg-1">
|
|
||||||
<td></td>
|
|
||||||
<td>   
|
|
||||||
<a href="pomice.html#module-pomice.exceptions"><code class="xref">pomice.exceptions</code></a></td><td>
|
|
||||||
<em></em></td></tr>
|
|
||||||
<tr class="cg-1">
|
|
||||||
<td></td>
|
|
||||||
<td>   
|
|
||||||
<a href="pomice.html#module-pomice.filters"><code class="xref">pomice.filters</code></a></td><td>
|
|
||||||
<em></em></td></tr>
|
|
||||||
<tr class="cg-1">
|
|
||||||
<td></td>
|
|
||||||
<td>   
|
|
||||||
<a href="pomice.html#module-pomice.objects"><code class="xref">pomice.objects</code></a></td><td>
|
|
||||||
<em></em></td></tr>
|
|
||||||
<tr class="cg-1">
|
|
||||||
<td></td>
|
|
||||||
<td>   
|
|
||||||
<a href="pomice.html#module-pomice.player"><code class="xref">pomice.player</code></a></td><td>
|
|
||||||
<em></em></td></tr>
|
|
||||||
<tr class="cg-1">
|
|
||||||
<td></td>
|
|
||||||
<td>   
|
|
||||||
<a href="pomice.html#module-pomice.pool"><code class="xref">pomice.pool</code></a></td><td>
|
|
||||||
<em></em></td></tr>
|
|
||||||
<tr class="cg-1">
|
|
||||||
<td></td>
|
|
||||||
<td>   
|
|
||||||
<a href="pomice.spotify.html#module-pomice.spotify.album"><code class="xref">pomice.spotify.album</code></a></td><td>
|
|
||||||
<em></em></td></tr>
|
|
||||||
<tr class="cg-1">
|
|
||||||
<td></td>
|
|
||||||
<td>   
|
|
||||||
<a href="pomice.spotify.html#module-pomice.spotify.client"><code class="xref">pomice.spotify.client</code></a></td><td>
|
|
||||||
<em></em></td></tr>
|
|
||||||
<tr class="cg-1">
|
|
||||||
<td></td>
|
|
||||||
<td>   
|
|
||||||
<a href="pomice.spotify.html#module-pomice.spotify.exceptions"><code class="xref">pomice.spotify.exceptions</code></a></td><td>
|
|
||||||
<em></em></td></tr>
|
|
||||||
<tr class="cg-1">
|
|
||||||
<td></td>
|
|
||||||
<td>   
|
|
||||||
<a href="pomice.spotify.html#module-pomice.spotify.playlist"><code class="xref">pomice.spotify.playlist</code></a></td><td>
|
|
||||||
<em></em></td></tr>
|
|
||||||
<tr class="cg-1">
|
|
||||||
<td></td>
|
|
||||||
<td>   
|
|
||||||
<a href="pomice.spotify.html#module-pomice.spotify.track"><code class="xref">pomice.spotify.track</code></a></td><td>
|
|
||||||
<em></em></td></tr>
|
|
||||||
<tr class="cg-1">
|
|
||||||
<td></td>
|
|
||||||
<td>   
|
|
||||||
<a href="pomice.html#module-pomice.utils"><code class="xref">pomice.utils</code></a></td><td>
|
|
||||||
<em></em></td></tr>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="sphinxsidebar" role="navigation" aria-label="main navigation">
|
|
||||||
<div class="sphinxsidebarwrapper">
|
|
||||||
<h1 class="logo"><a href="index.html">Pomice</a></h1>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<h3>Navigation</h3>
|
|
||||||
<ul>
|
|
||||||
<li class="toctree-l1"><a class="reference internal" href="pomice.html">Pomice</a></li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<div class="relations">
|
|
||||||
<h3>Related Topics</h3>
|
|
||||||
<ul>
|
|
||||||
<li><a href="index.html">Documentation overview</a><ul>
|
|
||||||
</ul></li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<div id="searchbox" style="display: none" role="search">
|
|
||||||
<h3 id="searchlabel">Quick search</h3>
|
|
||||||
<div class="searchformwrapper">
|
|
||||||
<form class="search" action="search.html" method="get">
|
|
||||||
<input type="text" name="q" aria-labelledby="searchlabel" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false"/>
|
|
||||||
<input type="submit" value="Go" />
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<script>$('#searchbox').show(0);</script>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="clearer"></div>
|
|
||||||
</div>
|
|
||||||
<div class="footer">
|
|
||||||
©2021, cloudwithax.
|
|
||||||
|
|
||||||
|
|
|
||||||
Powered by <a href="http://sphinx-doc.org/">Sphinx 4.2.0</a>
|
|
||||||
& <a href="https://github.com/bitprophet/alabaster">Alabaster 0.7.12</a>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
@ -1,122 +0,0 @@
|
||||||
|
|
||||||
<!DOCTYPE html>
|
|
||||||
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
||||||
<title>Search — Pomice 1.1.1 documentation</title>
|
|
||||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
|
|
||||||
<link rel="stylesheet" type="text/css" href="_static/alabaster.css" />
|
|
||||||
|
|
||||||
<script data-url_root="./" id="documentation_options" src="_static/documentation_options.js"></script>
|
|
||||||
<script src="_static/jquery.js"></script>
|
|
||||||
<script src="_static/underscore.js"></script>
|
|
||||||
<script src="_static/doctools.js"></script>
|
|
||||||
<script src="_static/searchtools.js"></script>
|
|
||||||
<script src="_static/language_data.js"></script>
|
|
||||||
<link rel="index" title="Index" href="genindex.html" />
|
|
||||||
<link rel="search" title="Search" href="#" />
|
|
||||||
<script src="searchindex.js" defer></script>
|
|
||||||
|
|
||||||
|
|
||||||
<link rel="stylesheet" href="_static/custom.css" type="text/css" />
|
|
||||||
|
|
||||||
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=0.9, maximum-scale=0.9" />
|
|
||||||
|
|
||||||
|
|
||||||
</head><body>
|
|
||||||
|
|
||||||
|
|
||||||
<div class="document">
|
|
||||||
<div class="documentwrapper">
|
|
||||||
<div class="bodywrapper">
|
|
||||||
|
|
||||||
|
|
||||||
<div class="body" role="main">
|
|
||||||
|
|
||||||
<h1 id="search-documentation">Search</h1>
|
|
||||||
|
|
||||||
<noscript>
|
|
||||||
<div class="admonition warning">
|
|
||||||
<p>
|
|
||||||
Please activate JavaScript to enable the search
|
|
||||||
functionality.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</noscript>
|
|
||||||
|
|
||||||
|
|
||||||
<p>
|
|
||||||
Searching for multiple words only shows matches that contain
|
|
||||||
all words.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
|
|
||||||
<form action="" method="get">
|
|
||||||
<input type="text" name="q" aria-labelledby="search-documentation" value="" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false"/>
|
|
||||||
<input type="submit" value="search" />
|
|
||||||
<span id="search-progress" style="padding-left: 10px"></span>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<div id="search-results">
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="sphinxsidebar" role="navigation" aria-label="main navigation">
|
|
||||||
<div class="sphinxsidebarwrapper">
|
|
||||||
<h1 class="logo"><a href="index.html">Pomice</a></h1>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<h3>Navigation</h3>
|
|
||||||
<ul>
|
|
||||||
<li class="toctree-l1"><a class="reference internal" href="pomice.html">Pomice</a></li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<div class="relations">
|
|
||||||
<h3>Related Topics</h3>
|
|
||||||
<ul>
|
|
||||||
<li><a href="index.html">Documentation overview</a><ul>
|
|
||||||
</ul></li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="clearer"></div>
|
|
||||||
</div>
|
|
||||||
<div class="footer">
|
|
||||||
©2021, cloudwithax.
|
|
||||||
|
|
||||||
|
|
|
||||||
Powered by <a href="http://sphinx-doc.org/">Sphinx 4.2.0</a>
|
|
||||||
& <a href="https://github.com/bitprophet/alabaster">Alabaster 0.7.12</a>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1,9 @@
|
||||||
|
```{eval-rst}
|
||||||
|
Enums
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
.. automodule:: pomice.enums
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
```
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
```{eval-rst}
|
||||||
|
Events
|
||||||
|
--------------------
|
||||||
|
|
||||||
|
.. automodule:: pomice.events
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
```
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
```{eval-rst}
|
||||||
|
Exceptions
|
||||||
|
------------------------
|
||||||
|
|
||||||
|
.. automodule:: pomice.exceptions
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
```
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
```{eval-rst}
|
||||||
|
Filters
|
||||||
|
---------------------
|
||||||
|
|
||||||
|
.. automodule:: pomice.filters
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
```
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
# API Reference
|
||||||
|
|
||||||
|
|
||||||
|
Here, you will find the different classes and methods used within Pomice.
|
||||||
|
|
||||||
|
|
||||||
|
```{toctree}
|
||||||
|
:maxdepth: 1
|
||||||
|
enums.md
|
||||||
|
events.md
|
||||||
|
exceptions.md
|
||||||
|
filters.md
|
||||||
|
objects.md
|
||||||
|
player.md
|
||||||
|
pool.md
|
||||||
|
queue.md
|
||||||
|
utils.md
|
||||||
|
```
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
```{eval-rst}
|
||||||
|
Objects
|
||||||
|
---------------------
|
||||||
|
|
||||||
|
.. automodule:: pomice.objects
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
```
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
```{eval-rst}
|
||||||
|
Player
|
||||||
|
--------------------
|
||||||
|
|
||||||
|
.. automodule:: pomice.player
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
```
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
```{eval-rst}
|
||||||
|
Pool
|
||||||
|
------------------
|
||||||
|
|
||||||
|
.. automodule:: pomice.pool
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
```
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
```{eval-rst}
|
||||||
|
Queue
|
||||||
|
------------------
|
||||||
|
|
||||||
|
.. automodule:: pomice.queue
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
```
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
```{eval-rst}
|
||||||
|
Utils
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
.. automodule:: pomice.utils
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
```
|
||||||
150
docs/conf.py
150
docs/conf.py
|
|
@ -1,57 +1,121 @@
|
||||||
# Configuration file for the Sphinx documentation builder.
|
# type: ignore
|
||||||
#
|
import importlib
|
||||||
# This file only contains a selection of the most common options. For a full
|
import inspect
|
||||||
# list see the documentation:
|
|
||||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html
|
|
||||||
|
|
||||||
# -- Path setup --------------------------------------------------------------
|
|
||||||
|
|
||||||
# If extensions (or modules to document with autodoc) are in another directory,
|
|
||||||
# add these directories to sys.path here. If the directory is relative to the
|
|
||||||
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
|
||||||
#
|
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
sys.path.insert(0, os.path.abspath('../'))
|
from typing import Any
|
||||||
|
from typing import Dict
|
||||||
|
|
||||||
|
sys.path.insert(0, os.path.abspath("."))
|
||||||
|
sys.path.insert(0, os.path.abspath(".."))
|
||||||
|
|
||||||
|
|
||||||
# -- Project information -----------------------------------------------------
|
project = "Pomice"
|
||||||
|
copyright = "2023, cloudwithax"
|
||||||
|
author = "cloudwithax"
|
||||||
|
|
||||||
project = 'Pomice'
|
release = "2.2"
|
||||||
copyright = '2023, cloudwithax'
|
|
||||||
author = 'cloudwithax'
|
|
||||||
|
|
||||||
# The full version, including alpha/beta/rc tags
|
|
||||||
release = '2.0'
|
|
||||||
|
|
||||||
|
|
||||||
# -- General configuration ---------------------------------------------------
|
|
||||||
|
|
||||||
# Add any Sphinx extension module names here, as strings. They can be
|
|
||||||
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
|
|
||||||
# ones.
|
|
||||||
extensions = [
|
extensions = [
|
||||||
'sphinx.ext.autodoc',
|
"sphinx.ext.autodoc",
|
||||||
'sphinx.ext.autosummary'
|
"sphinx.ext.autosummary",
|
||||||
]
|
"sphinx.ext.linkcode",
|
||||||
|
"myst_parser",
|
||||||
|
]
|
||||||
|
|
||||||
# Add any paths that contain templates here, relative to this directory.
|
myst_enable_extensions = [
|
||||||
templates_path = ['_templates']
|
"amsmath",
|
||||||
|
"colon_fence",
|
||||||
|
"deflist",
|
||||||
|
"dollarmath",
|
||||||
|
"fieldlist",
|
||||||
|
"html_admonition",
|
||||||
|
"html_image",
|
||||||
|
"replacements",
|
||||||
|
"smartquotes",
|
||||||
|
"strikethrough",
|
||||||
|
"substitution",
|
||||||
|
"tasklist",
|
||||||
|
]
|
||||||
|
|
||||||
# List of patterns, relative to source directory, that match files and
|
myst_heading_anchors = 3
|
||||||
# directories to ignore when looking for source files.
|
|
||||||
# This pattern also affects html_static_path and html_extra_path.
|
|
||||||
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
|
|
||||||
|
|
||||||
|
|
||||||
# -- Options for HTML output -------------------------------------------------
|
templates_path = ["_templates"]
|
||||||
|
|
||||||
# The theme to use for HTML and HTML Help pages. See the documentation for
|
exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"]
|
||||||
# a list of builtin themes.
|
|
||||||
#
|
|
||||||
html_theme = 'alabaster'
|
|
||||||
|
|
||||||
# Add any paths that contain custom static files (such as style sheets) here,
|
# We need to include this because discord.py has special tags
|
||||||
# relative to this directory. They are copied after the builtin static files,
|
# they inlcude within their docstrings that dont parse
|
||||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
# right within our docs
|
||||||
html_static_path = ['_static']
|
|
||||||
|
rst_prolog = """
|
||||||
|
.. |coro| replace:: This function is a |coroutine_link|_.
|
||||||
|
.. |maybecoro| replace:: This function *could be a* |coroutine_link|_.
|
||||||
|
.. |coroutine_link| replace:: *coroutine*
|
||||||
|
.. _coroutine_link: https://docs.python.org/3/library/asyncio-task.html#coroutine
|
||||||
|
"""
|
||||||
|
|
||||||
|
html_theme = "furo"
|
||||||
|
|
||||||
|
html_static_path = ["_static"]
|
||||||
|
|
||||||
|
html_title = "Pomice"
|
||||||
|
|
||||||
|
language = "en"
|
||||||
|
|
||||||
|
html_theme_options: Dict[str, Any] = {
|
||||||
|
"footer_icons": [
|
||||||
|
{
|
||||||
|
"name": "GitHub",
|
||||||
|
"url": "https://github.com/cloudwithax/pomice",
|
||||||
|
"html": """
|
||||||
|
<svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 16 16">
|
||||||
|
<path fill-rule="evenodd" d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0 0 16 8c0-4.42-3.58-8-8-8z"></path>
|
||||||
|
</svg>
|
||||||
|
""",
|
||||||
|
"class": "",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"source_repository": "https://github.com/cloudwithax/pomice",
|
||||||
|
"source_branch": "main",
|
||||||
|
"source_directory": "docs/",
|
||||||
|
}
|
||||||
|
|
||||||
|
# Grab lines from source files and embed into the docs
|
||||||
|
# so theres a point of reference
|
||||||
|
|
||||||
|
|
||||||
|
def linkcode_resolve(domain, info):
|
||||||
|
# i absolutely MUST add this here or else
|
||||||
|
# the docs will not build. fuck sphinx
|
||||||
|
try:
|
||||||
|
if domain != "py":
|
||||||
|
return None
|
||||||
|
if not info["module"]:
|
||||||
|
return None
|
||||||
|
|
||||||
|
mod = importlib.import_module(info["module"])
|
||||||
|
if "." in info["fullname"]:
|
||||||
|
objname, attrname = info["fullname"].split(".")
|
||||||
|
obj = getattr(mod, objname)
|
||||||
|
try:
|
||||||
|
obj = getattr(obj, attrname)
|
||||||
|
except AttributeError:
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
obj = getattr(mod, info["fullname"])
|
||||||
|
|
||||||
|
try:
|
||||||
|
file = inspect.getsourcefile(obj)
|
||||||
|
lines = inspect.getsourcelines(obj)
|
||||||
|
except TypeError:
|
||||||
|
# e.g. object is a typing.Union
|
||||||
|
return None
|
||||||
|
file = os.path.relpath(file, os.path.abspath(".."))
|
||||||
|
start, end = lines[1], lines[1] + len(lines[0]) - 1
|
||||||
|
|
||||||
|
return f"https://github.com/cloudwithax/pomice/blob/main/{file}#L{start}-L{end}"
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
# Frequently Asked Questions
|
||||||
|
> Why is it saying "Cannot connect to host"?
|
||||||
|
|
||||||
|
Here are some common issues:
|
||||||
|
- You don't have a Lavalink node installed
|
||||||
|
- You have a Lavalink node, but it's not configured properly
|
||||||
|
- You have a Lavalink node and it's configured properly, but is unreachable due to firewall rules or a malformed network configuration.
|
||||||
|
|
||||||
|
If you are experiencing the first issue, you can download Lavalink [here.](https://github.com/freyacodes/Lavalink/releases/latest)
|
||||||
|
|
||||||
|
As for the other listed issues, either consult the Lavalink docs or go through the proper support channels for your specfic issue at hand.
|
||||||
|
|
||||||
|
For any other issues not listed here, please consult your preferred resource for more information.
|
||||||
|
|
||||||
|
> What experience do I need?
|
||||||
|
|
||||||
|
This library assumes that you have some experience with Python, asynchronous programming and the discord.py library.
|
||||||
|
|
||||||
|
> How do I install Pomice?
|
||||||
|
|
||||||
|
Refer to the [Installation](installation.md) section.
|
||||||
|
|
||||||
|
> How do I use Pomice?
|
||||||
|
|
||||||
|
If you are interested in learning how Pomice works, refer to the [API Reference](api/index.md) section.
|
||||||
|
|
||||||
|
If you want a quick example, refer to the [Quickstart](quickstart.md) section.
|
||||||
|
|
@ -0,0 +1,47 @@
|
||||||
|
# Use the Events class
|
||||||
|
|
||||||
|
Pomice has different events that are triggered depending on events that Lavalink emits:
|
||||||
|
- `Event.TrackEndEvent()`
|
||||||
|
- `Event.TrackExceptionEvent()`
|
||||||
|
- `Event.TrackStartEvent()`
|
||||||
|
- `Event.TrackStuckEvent()`
|
||||||
|
- `Event.WebsoocketClosedEvent()`
|
||||||
|
- `Event.WebsocketOpenEvent()`
|
||||||
|
|
||||||
|
|
||||||
|
The classes listed here are as they appear in Pomice. When you use them within your application,
|
||||||
|
the way you use them will be different. Here's an example on how you would use the `TrackStartEvent` within an event listener in a cog:
|
||||||
|
|
||||||
|
```py
|
||||||
|
@commands.Cog.listener
|
||||||
|
async def on_pomice_track_start(self, player: Player, track: Track):
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
## Event definitions
|
||||||
|
|
||||||
|
Each event within Pomice has an event definition you can use to listen for said event within
|
||||||
|
your application. Here are all the definitions:
|
||||||
|
|
||||||
|
- `Event.TrackEndEvent()` -> `on_pomice_track_end`
|
||||||
|
- `Event.TrackExceptionEvent()` -> `on_pomice_track_exception`
|
||||||
|
- `Event.TrackStartEvent()` -> `on_pomice_track_start`
|
||||||
|
- `Event.TrackStuckEvent()` -> `on_pomice_track_stuck`
|
||||||
|
- `Event.WebsocketClosedEvent()` -> `on_pomice_websocket_closed`
|
||||||
|
- `Event.WebsocketOpenEvent()` -> `on_pomice_websocket_open`
|
||||||
|
|
||||||
|
|
||||||
|
All events related to tracks carry a `Player` object so you can access player-specific functions
|
||||||
|
and properties for further evaluation. They also carry a `Track` object so you can access track-specific functions and properties for further evaluation as well.
|
||||||
|
|
||||||
|
`Event.TrackEndEvent()` carries the reason for the track ending. If the track ends suddenly, you can use the reason provided to determine a solution.
|
||||||
|
|
||||||
|
`Event.TrackExceptionEvent()` carries the exception, or reason why the track failed to play. The format for the exception is `REASON: [SEVERITY]`.
|
||||||
|
|
||||||
|
`Event.TrackStuckEvent()` carries the threshold, or amount of time Lavalink will wait before it discards the stuck track and stops it from playing.
|
||||||
|
|
||||||
|
`Event.WebsocketClosedEvent()` carries a payload object that contains a `Guild` object, the code number, the reason for disconnect and whether or not it was by the
|
||||||
|
remote, or the node.
|
||||||
|
|
||||||
|
`Event.WebsocketOpenEvent()` carries a target, which is usually the node IP, and the SSRC, a 32-bit integer uniquely identifying the source of the RTP packets sent from
|
||||||
|
Lavalink.
|
||||||
|
|
@ -0,0 +1,186 @@
|
||||||
|
# Use the Filter class
|
||||||
|
|
||||||
|
Pomice takes full advantage of the Lavalink filter system by using a unique system to apply filters on top of one another. We call this system "filter stacking". With this system, we can stack any filter on top of one another to produce one-of-a-kind audio effects on playback while still being able to easily manage each filters.
|
||||||
|
|
||||||
|
|
||||||
|
## Types of filters
|
||||||
|
|
||||||
|
Lavalink, and by extension, Pomice, has different types of filters you can use.
|
||||||
|
|
||||||
|
Here are the different types and what they do:
|
||||||
|
|
||||||
|
:::{list-table}
|
||||||
|
:header-rows: 1
|
||||||
|
|
||||||
|
* - Type
|
||||||
|
- Class
|
||||||
|
- Description
|
||||||
|
|
||||||
|
* - Channel Mix
|
||||||
|
- `pomice.ChannelMix()`
|
||||||
|
- Adjusts stereo panning of a track.
|
||||||
|
|
||||||
|
* - Distortion
|
||||||
|
- `pomice.Distortion()`
|
||||||
|
- Generates a distortion effect on a track.
|
||||||
|
|
||||||
|
* - Equalizer
|
||||||
|
- `pomice.Equalizer()`
|
||||||
|
- Represents a 15 band equalizer. You can adjust the dynamic of the sound using this filter.
|
||||||
|
|
||||||
|
* - Karaoke
|
||||||
|
- `pomice.Karaoke()`
|
||||||
|
- Filters the vocals from the track.
|
||||||
|
|
||||||
|
* - Low Pass
|
||||||
|
- `pomice.LowPass()`
|
||||||
|
- Filters out high frequencies and only lets low frequencies pass through.
|
||||||
|
|
||||||
|
* - Rotation
|
||||||
|
- `pomice.Rotation()`
|
||||||
|
- Produces a stereo-like panning effect, which sounds like the audio is being rotated around the listener’s head
|
||||||
|
|
||||||
|
* - Timescale
|
||||||
|
- `pomice.Timescale()`
|
||||||
|
- Adjusts the speed and pitch of a track.
|
||||||
|
|
||||||
|
* - Tremolo
|
||||||
|
- `pomice.Tremolo()`
|
||||||
|
- Rapidly changes the volume of the track, producing a wavering tone.
|
||||||
|
|
||||||
|
* - Vibrato
|
||||||
|
- `pomice.Vibrato()`
|
||||||
|
- Rapidly changes the pitch of the track.
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
|
|
||||||
|
Each filter has individual values you can adjust to fine-tune the sound of the filter. If you want to see what values each filter has, refer to [](../api/filters.md).
|
||||||
|
|
||||||
|
If you are stuck on what values adjust what, some filters include presets that you can apply to get a certain sound, i.e: `pomice.Timescale` has the `vaporwave()` and `nightcore()` and so on. You can also play around with the values and generate your own unique sound if you'd like.
|
||||||
|
|
||||||
|
## Adding a filter
|
||||||
|
|
||||||
|
:::{important}
|
||||||
|
|
||||||
|
You must have the `Player` class initialized first before using this. Refer to [](player.md)
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
|
To add a filter, we need to use `Player.add_filter()`
|
||||||
|
|
||||||
|
|
||||||
|
```py
|
||||||
|
|
||||||
|
await Player.add_filter(...)
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
After you have initialized your function, we need to fill in the proper parameters:
|
||||||
|
|
||||||
|
:::{list-table}
|
||||||
|
:header-rows: 1
|
||||||
|
|
||||||
|
* - Name
|
||||||
|
- Type
|
||||||
|
- Description
|
||||||
|
|
||||||
|
* - `filter`
|
||||||
|
- `Filter`
|
||||||
|
- The filter to apply
|
||||||
|
|
||||||
|
* - `fast_apply`
|
||||||
|
- `bool`
|
||||||
|
- If set to `True`, the specified filter will apply (almost) instantly if a song is playing. Default value is `False`.
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
|
After you set those parameters, your function should look something like this:
|
||||||
|
|
||||||
|
```py
|
||||||
|
|
||||||
|
await Player.add_filter(
|
||||||
|
filter=<your filter object here>,
|
||||||
|
fast_apply=<True/False>
|
||||||
|
)
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
After running this function, you should see your currently playing track sound different depending on the filter you chose.
|
||||||
|
|
||||||
|
## Removing a filter
|
||||||
|
|
||||||
|
:::{important}
|
||||||
|
|
||||||
|
You must have the `Player` class initialized first before using this. Refer to [](player.md)
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
|
|
||||||
|
To remove a filter, we need to use `Player.remove_filter()`
|
||||||
|
|
||||||
|
```py
|
||||||
|
|
||||||
|
await Player.remove_filter(...)
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
After you have initialized your function, we need to fill in the proper parameters:
|
||||||
|
|
||||||
|
:::{list-table}
|
||||||
|
:header-rows: 1
|
||||||
|
|
||||||
|
* - Name
|
||||||
|
- Type
|
||||||
|
- Description
|
||||||
|
|
||||||
|
* - `filter`
|
||||||
|
- `Filter`
|
||||||
|
- The filter to remove
|
||||||
|
|
||||||
|
* - `fast_apply`
|
||||||
|
- `bool`
|
||||||
|
- If set to `True`, the specified filter will be removed (almost) instantly if a song is playing. Default value is `False`.
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
|
After you set those parameters, your function should look something like this:
|
||||||
|
|
||||||
|
```py
|
||||||
|
|
||||||
|
await Player.remove_filter(
|
||||||
|
filter=<your filter object here>,
|
||||||
|
fast_apply=<True/False>
|
||||||
|
)
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
After running this function, you should see your currently playing track sound different depending on the filter you chose to remove.
|
||||||
|
|
||||||
|
|
||||||
|
## Resetting all filters
|
||||||
|
|
||||||
|
:::{important}
|
||||||
|
|
||||||
|
You must have the `Player` class initialized first before using this. Refer to [](player.md)
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
|
To reset all filters, we need to use `Player.reset_filters()`
|
||||||
|
|
||||||
|
```py
|
||||||
|
|
||||||
|
await Player.reset_filters()
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
After you have initialized your function, you can optionally include the `fast_apply` parameter, which is a boolean. If this is set to `True`, it'll remove all filters (almost) instantly if theres a track playing.
|
||||||
|
|
||||||
|
```py
|
||||||
|
|
||||||
|
await Player.reset_filters(fast_apply=<True/False>)
|
||||||
|
|
||||||
|
```
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
# How Do I?
|
||||||
|
|
||||||
|
This section covers all the basic functions of Pomice and how to use them.
|
||||||
|
|
||||||
|
If you find the [API Reference](../api/index.md) section too confusing or would
|
||||||
|
rather have a straightforward explanation as to how to use a certain function,
|
||||||
|
this is for you.
|
||||||
|
|
||||||
|
```{toctree}
|
||||||
|
pool.md
|
||||||
|
node.md
|
||||||
|
player.md
|
||||||
|
filters.md
|
||||||
|
queue.md
|
||||||
|
events.md
|
||||||
|
```
|
||||||
|
|
@ -0,0 +1,182 @@
|
||||||
|
# Use the Node class
|
||||||
|
|
||||||
|
The `Node` class is one of the main classes you will be interacting with when using Pomice.
|
||||||
|
|
||||||
|
The `Node` class has a couple functions you will be using frequently:
|
||||||
|
|
||||||
|
- `Node.get_player()`
|
||||||
|
- `Node.get_tracks()`
|
||||||
|
- `Node.get_recommendations()`
|
||||||
|
|
||||||
|
|
||||||
|
There are also properties the `Node` class has to access certain values:
|
||||||
|
|
||||||
|
:::{list-table}
|
||||||
|
:header-rows: 1
|
||||||
|
|
||||||
|
* - Property
|
||||||
|
- Type
|
||||||
|
- Description
|
||||||
|
|
||||||
|
* - `Node.bot`
|
||||||
|
- `Client`
|
||||||
|
- Returns the discord.py client linked to this node.
|
||||||
|
|
||||||
|
* - `Node.is_connected`
|
||||||
|
- `bool`
|
||||||
|
- Returns whether this node is connected or not.
|
||||||
|
|
||||||
|
* - `Node.latency` `Node.ping`
|
||||||
|
- `float`
|
||||||
|
- Returns the latency of the node.
|
||||||
|
|
||||||
|
* - `Node.player_count`
|
||||||
|
- `int`
|
||||||
|
- Returns how many players are connected to this node.
|
||||||
|
|
||||||
|
* - `Node.players`
|
||||||
|
- `Dict[int, Player]`
|
||||||
|
- Returns a dict containing the guild ID and the player object.
|
||||||
|
|
||||||
|
* - `Node.pool`
|
||||||
|
- `NodePool`
|
||||||
|
- Returns the pool this node is apart of.
|
||||||
|
|
||||||
|
* - `Node.stats`
|
||||||
|
- `NodeStats`
|
||||||
|
- Returns the nodes stats.
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
|
## Getting a player
|
||||||
|
|
||||||
|
To get a player from the nodes list of players, we need to use `Node.get_player()`
|
||||||
|
|
||||||
|
```py
|
||||||
|
|
||||||
|
await Node.get_player(...)
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
After you have initialized your function, you need to specify the `guild_id` of the player.
|
||||||
|
|
||||||
|
```py
|
||||||
|
|
||||||
|
await Node.get_player(guild_id=<your guild ID here>)
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
If the node finds a player with the guild ID you provided, it'll return the [](../api/player.md) object associated with the guild ID.
|
||||||
|
|
||||||
|
|
||||||
|
## Getting tracks
|
||||||
|
|
||||||
|
To get tracks using Lavalink, we need to use `Node.get_tracks()`
|
||||||
|
|
||||||
|
You can also use `Player.get_tracks()` to do the same thing, but this can be used to fetch tracks regardless if a player exists.
|
||||||
|
|
||||||
|
```py
|
||||||
|
|
||||||
|
await Node.get_tracks(...)
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
After you have initialized your function, we need to fill in the proper parameters:
|
||||||
|
|
||||||
|
:::{list-table}
|
||||||
|
:header-rows: 1
|
||||||
|
|
||||||
|
* - Name
|
||||||
|
- Type
|
||||||
|
- Description
|
||||||
|
|
||||||
|
* - `query`
|
||||||
|
- `str`
|
||||||
|
- The string you want to search up
|
||||||
|
|
||||||
|
* - `ctx`
|
||||||
|
- `Optional[commands.Context]`
|
||||||
|
- Optional value which sets a `Context` object on the tracks you search.
|
||||||
|
|
||||||
|
* - `search_type`
|
||||||
|
- `SearchType`
|
||||||
|
- Enum which sets the provider to search from. Default value is `SearchType.ytsearch`
|
||||||
|
|
||||||
|
* - `filters`
|
||||||
|
- `Optional[List[Filter]]`
|
||||||
|
- Optional value which sets the filters that should apply when the track is played on the tracks you search.
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
|
After you set those parameters, your function should look something like this:
|
||||||
|
|
||||||
|
```py
|
||||||
|
|
||||||
|
await Node.get_tracks(
|
||||||
|
query="<your query here>",
|
||||||
|
ctx=<optional ctx object here>,
|
||||||
|
search_type=<optional search type here>,
|
||||||
|
filters=[<optional filters here>]
|
||||||
|
)
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
:::{important}
|
||||||
|
|
||||||
|
All querying of Spotify and Apple Music tracks or playlists is handled in this function if you enabled that functionality when creating your node.
|
||||||
|
If you want to enable it, refer to [](pool.md#adding-a-node)
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
You should get a list of `Track` in return after running this function for you to then do whatever you want with it.
|
||||||
|
Ideally, you should be putting all tracks into some sort of a queue. If you would like to learn about how to use
|
||||||
|
our queue implementation, you can refer to [](queue.md)
|
||||||
|
|
||||||
|
|
||||||
|
## Getting recommendations
|
||||||
|
|
||||||
|
To get recommadations using Lavalink, we need to use `Node.get_recommendations()`
|
||||||
|
|
||||||
|
You can also use `Player.get_recommendations()` to do the same thing, but this can be used to fetch recommendations regardless if a player exists.
|
||||||
|
|
||||||
|
```py
|
||||||
|
|
||||||
|
await Node.get_recommendations(...)
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
After you have initialized your function, we need to fill in the proper parameters:
|
||||||
|
|
||||||
|
:::{list-table}
|
||||||
|
:header-rows: 1
|
||||||
|
|
||||||
|
* - Name
|
||||||
|
- Type
|
||||||
|
- Description
|
||||||
|
|
||||||
|
* - `track`
|
||||||
|
- `Track`
|
||||||
|
- The track to fetch recommendations for
|
||||||
|
|
||||||
|
* - `ctx`
|
||||||
|
- `Optional[commands.Context]`
|
||||||
|
- Optional value which sets a `Context` object on the recommendations you fetch.
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
|
After you set those parameters, your function should look something like this:
|
||||||
|
|
||||||
|
```py
|
||||||
|
|
||||||
|
await Node.get_recommendations(
|
||||||
|
track=<your track object here>,
|
||||||
|
ctx=<optional ctx object here>,
|
||||||
|
)
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
You should get a list of `Track` in return after running this function for you to then do whatever you want with it.
|
||||||
|
Ideally, you should be putting all tracks into some sort of a queue. If you would like to learn about how to use
|
||||||
|
our queue implementation, you can refer to [](queue.md)
|
||||||
|
|
@ -0,0 +1,501 @@
|
||||||
|
# Use the Player class
|
||||||
|
|
||||||
|
The `Player` class is the class you will be interacting with the most within Pomice.
|
||||||
|
|
||||||
|
The `Player` class has a couple functions you will be using frequently:
|
||||||
|
|
||||||
|
- `Player.add_filter()`
|
||||||
|
- `Player.destroy()`
|
||||||
|
- `Player.get_recommendations()`
|
||||||
|
- `Player.get_tracks()`
|
||||||
|
- `Player.play()`
|
||||||
|
- `Player.remove_filter()`
|
||||||
|
- `Player.reset_filters()`
|
||||||
|
- `Player.seek()`
|
||||||
|
- `Player.set_pause()`
|
||||||
|
- `Player.set_volume()`
|
||||||
|
- `Player.stop()`
|
||||||
|
|
||||||
|
|
||||||
|
There are also properties the `Player` class has to access certain values:
|
||||||
|
|
||||||
|
|
||||||
|
:::{list-table}
|
||||||
|
:header-rows: 1
|
||||||
|
|
||||||
|
* - Property
|
||||||
|
- Type
|
||||||
|
- Description
|
||||||
|
|
||||||
|
* - `Player.bot`
|
||||||
|
- `Client`
|
||||||
|
- Returns the bot associated with this player instance.
|
||||||
|
|
||||||
|
* - `Player.current`
|
||||||
|
- `Track`
|
||||||
|
- Returns the currently playing track.
|
||||||
|
|
||||||
|
* - `Player.filters`
|
||||||
|
- `Filters`
|
||||||
|
- Returns the helper class for interacting with filters.
|
||||||
|
|
||||||
|
* - `Player.guild`
|
||||||
|
- `Guild`
|
||||||
|
- Returns the guild associated with the player.
|
||||||
|
|
||||||
|
* - `Player.is_connected`
|
||||||
|
- `bool`
|
||||||
|
- Returns whether or not the player is connected.
|
||||||
|
|
||||||
|
* - `Player.is_dead`
|
||||||
|
- `bool`
|
||||||
|
- Returns whether the player is dead or not. A player is considered dead if it has been destroyed and removed from stored players.
|
||||||
|
|
||||||
|
* - `Player.is_paused`
|
||||||
|
- `bool`
|
||||||
|
- Returns whether or not the player has a track which is paused or not.
|
||||||
|
|
||||||
|
* - `Player.is_playing`
|
||||||
|
- `bool`
|
||||||
|
- Returns whether or not the player is actively playing a track.
|
||||||
|
|
||||||
|
* - `Player.node`
|
||||||
|
- `Node`
|
||||||
|
- Returns the node the player is connected to.
|
||||||
|
|
||||||
|
* - `Player.position`
|
||||||
|
- `float`
|
||||||
|
- Returns the player’s position in a track in milliseconds.
|
||||||
|
|
||||||
|
* - `Player.adjusted_position`
|
||||||
|
- `float`
|
||||||
|
- Returns the player’s position in a track in milliseconds, adjusted for rate if affected.
|
||||||
|
|
||||||
|
* - `Player.adjusted_length`
|
||||||
|
- `float`
|
||||||
|
- Returns the current track length in milliseconds, adjusted for rate if affected.
|
||||||
|
|
||||||
|
* - `Player.rate`
|
||||||
|
- `float`
|
||||||
|
- Returns the players current rate, which represents the speed of the currently playing track. This rate is affected by the `Timescale` filter.
|
||||||
|
|
||||||
|
* - `Player.volume`
|
||||||
|
- `int`
|
||||||
|
- Returns the players current volume.
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
|
## Getting tracks
|
||||||
|
|
||||||
|
To get tracks using Lavalink, we need to use `Player.get_tracks()`
|
||||||
|
|
||||||
|
You can also use `Node.get_tracks()` to do the same thing but without having a player.
|
||||||
|
|
||||||
|
```py
|
||||||
|
|
||||||
|
await Player.get_tracks(...)
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
After you have initialized your function, we need to fill in the proper parameters:
|
||||||
|
|
||||||
|
:::{list-table}
|
||||||
|
:header-rows: 1
|
||||||
|
|
||||||
|
* - Name
|
||||||
|
- Type
|
||||||
|
- Description
|
||||||
|
|
||||||
|
* - `query`
|
||||||
|
- `str`
|
||||||
|
- The string you want to search up
|
||||||
|
|
||||||
|
* - `ctx`
|
||||||
|
- `Optional[commands.Context]`
|
||||||
|
- Optional value which sets a `Context` object on the tracks you search.
|
||||||
|
|
||||||
|
* - `search_type`
|
||||||
|
- `SearchType`
|
||||||
|
- Enum which sets the provider to search from. Default value is `SearchType.ytsearch`
|
||||||
|
|
||||||
|
* - `filters`
|
||||||
|
- `Optional[List[Filter]]`
|
||||||
|
- Optional value which sets the filters that should apply when the track is played on the tracks you search.
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
|
After you set those parameters, your function should look something like this:
|
||||||
|
|
||||||
|
```py
|
||||||
|
|
||||||
|
await Player.get_tracks(
|
||||||
|
query="<your query here>",
|
||||||
|
ctx=<optional ctx object here>,
|
||||||
|
search_type=<optional search type here>,
|
||||||
|
filters=[<optional filters here>]
|
||||||
|
)
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
:::{important}
|
||||||
|
|
||||||
|
All querying of Spotify and Apple Music tracks or playlists is handled in this function if you enabled that functionality when creating your node.
|
||||||
|
If you want to enable it, refer to [](pool.md#adding-a-node)
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
You should get a list of `Track` in return after running this function for you to then do whatever you want with it.
|
||||||
|
Ideally, you should be putting all tracks into some sort of a queue. If you would like to learn about how to use
|
||||||
|
our queue implementation, you can refer to [](queue.md)
|
||||||
|
|
||||||
|
|
||||||
|
## Getting recommendations
|
||||||
|
|
||||||
|
To get recommendations using Lavalink, we need to use `Player.get_recommendations()`
|
||||||
|
|
||||||
|
You can also use `Node.get_recommendations()` to do the same thing without having a player.
|
||||||
|
|
||||||
|
```py
|
||||||
|
|
||||||
|
await Player.get_recommendations(...)
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
After you have initialized your function, we need to fill in the proper parameters:
|
||||||
|
|
||||||
|
:::{list-table}
|
||||||
|
:header-rows: 1
|
||||||
|
|
||||||
|
* - Name
|
||||||
|
- Type
|
||||||
|
- Description
|
||||||
|
|
||||||
|
* - `track`
|
||||||
|
- `Track`
|
||||||
|
- The track to fetch recommendations for
|
||||||
|
|
||||||
|
* - `ctx`
|
||||||
|
- `Optional[commands.Context]`
|
||||||
|
- Optional value which sets a `Context` object on the recommendations you fetch.
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
|
After you set those parameters, your function should look something like this:
|
||||||
|
|
||||||
|
```py
|
||||||
|
|
||||||
|
await Player.get_recommendations(
|
||||||
|
track=<your track object here>,
|
||||||
|
ctx=<optional ctx object here>,
|
||||||
|
)
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
You should get a list of `Track` in return after running this function for you to then do whatever you want with it.
|
||||||
|
Ideally, you should be putting all tracks into some sort of a queue. If you would like to learn about how to use
|
||||||
|
our queue implementation, you can refer to [](queue.md)
|
||||||
|
|
||||||
|
## Connecting a player
|
||||||
|
|
||||||
|
To connect a player to a channel you need to pass the `Player` class into your `channel.connect()` function:
|
||||||
|
|
||||||
|
```py
|
||||||
|
|
||||||
|
await voice_channel.connect(cls=Player)
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
This will instance the player and make it available to your guild. If you want to access your player after instancing it,
|
||||||
|
you must use either `Guild.voice_client` or `Context.voice_client`.
|
||||||
|
|
||||||
|
## Controlling the player
|
||||||
|
|
||||||
|
There are a few functions to control the player:
|
||||||
|
|
||||||
|
- `Player.destroy()`
|
||||||
|
- `Player.play()`
|
||||||
|
- `Player.seek()`
|
||||||
|
- `Player.set_pause()`
|
||||||
|
- `Player.set_volume()`
|
||||||
|
- `Player.stop()`
|
||||||
|
|
||||||
|
### Destroying a player
|
||||||
|
|
||||||
|
To destroy a player, we need to use `Player.destroy()`
|
||||||
|
|
||||||
|
```py
|
||||||
|
|
||||||
|
await Player.destroy()
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
### Playing a track
|
||||||
|
|
||||||
|
To play a track, we need to use `Player.play()`
|
||||||
|
|
||||||
|
```py
|
||||||
|
|
||||||
|
await Player.play(...)
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
After you have initialized your function, we need to fill in the proper parameters:
|
||||||
|
|
||||||
|
:::{list-table}
|
||||||
|
:header-rows: 1
|
||||||
|
|
||||||
|
* - Name
|
||||||
|
- Type
|
||||||
|
- Description
|
||||||
|
|
||||||
|
* - `track`
|
||||||
|
- `Track`
|
||||||
|
- The track to play
|
||||||
|
|
||||||
|
* - `start`
|
||||||
|
- `int`
|
||||||
|
- The time (in milliseconds) to start the track at. Default value is `0`
|
||||||
|
|
||||||
|
* - `end`
|
||||||
|
- `int`
|
||||||
|
- The time (in milliseconds) to end the track at. Default value is `0`
|
||||||
|
|
||||||
|
* - `ignore_if_playing`
|
||||||
|
- `bool`
|
||||||
|
- If set, ignores the current track playing and replaces it with this track. Default value is `False`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
|
After you set those parameters, your function should look something like this:
|
||||||
|
|
||||||
|
```py
|
||||||
|
|
||||||
|
await Player.play(
|
||||||
|
track=<your track object here>,
|
||||||
|
start=<your optional start time here>,
|
||||||
|
end=<your optional end time here>,
|
||||||
|
ignore_if_playing=<your optional boolean here>
|
||||||
|
)
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
After running this function, it should return the `Track` you specified when running the function. This means the track is now playing.
|
||||||
|
|
||||||
|
|
||||||
|
### Seeking to a position
|
||||||
|
|
||||||
|
To seek to a position, we need to use `Player.seek()`
|
||||||
|
|
||||||
|
```py
|
||||||
|
|
||||||
|
await Player.seek(...)
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
After you have initialized your function, we need to include the `position` parameter, which is an amount in milliseconds:
|
||||||
|
|
||||||
|
```py
|
||||||
|
|
||||||
|
await Player.seek(position=<your pos here>)
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
After running this function, your currently playing track should seek to your specified position
|
||||||
|
|
||||||
|
|
||||||
|
### Pausing/unpausing the player
|
||||||
|
|
||||||
|
|
||||||
|
To pause/unpause the player, we need to use `Player.set_pause()`
|
||||||
|
|
||||||
|
```py
|
||||||
|
|
||||||
|
await Player.set_pause(...)
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
After you have initialized your function, we need to include the `pause` parameter, which is a boolean:
|
||||||
|
|
||||||
|
```py
|
||||||
|
|
||||||
|
await Player.set_pause(pause=<True/False>)
|
||||||
|
|
||||||
|
```
|
||||||
|
After running this function, your currently playing track should either pause or unpause depending on what you set.
|
||||||
|
|
||||||
|
### Setting the player volume
|
||||||
|
|
||||||
|
To set the volume the player, we need to use `Player.set_volume()`
|
||||||
|
|
||||||
|
```py
|
||||||
|
|
||||||
|
await Player.set_volume(...)
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
:::{important}
|
||||||
|
Lavalink accept ranges from 0 to 500 for this parameter. Inputting a value either higher or lower
|
||||||
|
than this amount will **not work.**
|
||||||
|
:::
|
||||||
|
|
||||||
|
After you have initialized your function, we need to include the `amount` parameter, which is an integer:
|
||||||
|
|
||||||
|
```py
|
||||||
|
|
||||||
|
await Player.set_volume(amount=<int>)
|
||||||
|
|
||||||
|
```
|
||||||
|
After running this function, your currently playing track should adjust in volume depending on the amount you set.
|
||||||
|
|
||||||
|
### Stopping the player
|
||||||
|
|
||||||
|
To stop the player, we need to use `Player.stop()`
|
||||||
|
|
||||||
|
```py
|
||||||
|
|
||||||
|
await Player.stop()
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
### Moving the player to another channel
|
||||||
|
|
||||||
|
To move the player to another channel, we need to use `Player.move_to()`
|
||||||
|
|
||||||
|
```py
|
||||||
|
|
||||||
|
await Player.move_to(...)
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
After you have initialized your function, we need to include the `channel` parameter, which is a `VoiceChannel`:
|
||||||
|
|
||||||
|
```py
|
||||||
|
|
||||||
|
await Player.move_to(channel)
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
After running this function, your player should be in the new voice channel. All voice state updates should also be handled.
|
||||||
|
|
||||||
|
|
||||||
|
## Controlling filters
|
||||||
|
|
||||||
|
Pomice has an extensive suite of filter management tools to help you make the most of Lavalink and it's filters.
|
||||||
|
|
||||||
|
Here are some of the functions you will be using to control filters:
|
||||||
|
|
||||||
|
- `Player.add_filter()`
|
||||||
|
- `Player.remove_filter()`
|
||||||
|
- `Player.reset_filters()`
|
||||||
|
|
||||||
|
|
||||||
|
### Adding a filter
|
||||||
|
|
||||||
|
|
||||||
|
To add a filter, we need to use `Player.add_filter()`
|
||||||
|
|
||||||
|
```py
|
||||||
|
|
||||||
|
await Player.add_filter(...)
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
After you have initialized your function, we need to fill in the proper parameters:
|
||||||
|
|
||||||
|
:::{list-table}
|
||||||
|
:header-rows: 1
|
||||||
|
|
||||||
|
* - Name
|
||||||
|
- Type
|
||||||
|
- Description
|
||||||
|
|
||||||
|
* - `filter`
|
||||||
|
- `Filter`
|
||||||
|
- The filter to apply
|
||||||
|
|
||||||
|
* - `fast_apply`
|
||||||
|
- `bool`
|
||||||
|
- If set to `True`, the specified filter will apply (almost) instantly if a song is playing. Default value is `False`.
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
|
After you set those parameters, your function should look something like this:
|
||||||
|
|
||||||
|
```py
|
||||||
|
|
||||||
|
await Player.add_filter(
|
||||||
|
filter=<your filter object here>,
|
||||||
|
fast_apply=<True/False>
|
||||||
|
)
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
After running this function, you should see your currently playing track sound different depending on the filter you chose.
|
||||||
|
|
||||||
|
### Removing a filter
|
||||||
|
|
||||||
|
|
||||||
|
To remove a filter, we need to use `Player.remove_filter()`
|
||||||
|
|
||||||
|
```py
|
||||||
|
|
||||||
|
await Player.remove_filter(...)
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
After you have initialized your function, we need to fill in the proper parameters:
|
||||||
|
|
||||||
|
:::{list-table}
|
||||||
|
:header-rows: 1
|
||||||
|
|
||||||
|
* - Name
|
||||||
|
- Type
|
||||||
|
- Description
|
||||||
|
|
||||||
|
* - `filter`
|
||||||
|
- `Filter`
|
||||||
|
- The filter to remove
|
||||||
|
|
||||||
|
* - `fast_apply`
|
||||||
|
- `bool`
|
||||||
|
- If set to `True`, the specified filter will be removed (almost) instantly if a song is playing. Default value is `False`.
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
|
After you set those parameters, your function should look something like this:
|
||||||
|
|
||||||
|
```py
|
||||||
|
|
||||||
|
await Player.remove_filter(
|
||||||
|
filter=<your filter object here>,
|
||||||
|
fast_apply=<True/False>
|
||||||
|
)
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
After running this function, you should see your currently playing track sound different depending on the filter you chose to remove.
|
||||||
|
|
||||||
|
|
||||||
|
### Resetting all filters
|
||||||
|
|
||||||
|
To reset all filters, we need to use `Player.reset_filters()`
|
||||||
|
|
||||||
|
```py
|
||||||
|
|
||||||
|
await Player.reset_filters()
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
After you have initialized your function, you can optionally include the `fast_apply` parameter, which is a boolean. If this is set to `True`, it'll remove all filters (almost) instantly if theres a track playing.
|
||||||
|
|
||||||
|
```py
|
||||||
|
|
||||||
|
await Player.reset_filters(fast_apply=<True/False>)
|
||||||
|
|
||||||
|
```
|
||||||
|
|
@ -0,0 +1,156 @@
|
||||||
|
# Use the NodePool class
|
||||||
|
|
||||||
|
The `NodePool` class is the first class you will use when using Pomice.
|
||||||
|
|
||||||
|
The `NodePool` Class has three main functions you can use:
|
||||||
|
|
||||||
|
- `NodePool.create_node()`
|
||||||
|
- `NodePool.get_node()`
|
||||||
|
- `NodePool.get_best_node()`
|
||||||
|
|
||||||
|
|
||||||
|
## Adding a node
|
||||||
|
|
||||||
|
To add a node to our `NodePool`, we need to run `NodePool.create_node()`.
|
||||||
|
|
||||||
|
```py
|
||||||
|
|
||||||
|
await NodePool.create_node(...)
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
After you have initialized your function, we need to fill in the proper parameters:
|
||||||
|
|
||||||
|
|
||||||
|
:::{list-table}
|
||||||
|
:header-rows: 1
|
||||||
|
|
||||||
|
* - Name
|
||||||
|
- Type
|
||||||
|
- Description
|
||||||
|
|
||||||
|
* - `bot`
|
||||||
|
- `Client`
|
||||||
|
- A discord.py `Client` object (can be either a `Client` or a `commands.Bot`)
|
||||||
|
|
||||||
|
* - `host`
|
||||||
|
- `str`
|
||||||
|
- The IP/URL of your Lavalink node. Remember not to include the port in this field
|
||||||
|
|
||||||
|
* - `port`
|
||||||
|
- `int`
|
||||||
|
- The port your Lavalink node uses. By default, Lavalink uses `2333`.
|
||||||
|
|
||||||
|
* - `identifier`
|
||||||
|
- `str`
|
||||||
|
- The identifier your `Node` object uses to distinguish itself.
|
||||||
|
|
||||||
|
* - `password`
|
||||||
|
- `str`
|
||||||
|
- The password used to connect to your node.
|
||||||
|
|
||||||
|
* - `spotify_client_id`
|
||||||
|
- `Optional[str]`
|
||||||
|
- Your Spotify client ID goes here. You need this along with the client secret if you want to use Spotify functionality within Pomice.
|
||||||
|
|
||||||
|
* - `spotify_client_secret`
|
||||||
|
- `Optional[str]`
|
||||||
|
- Your Spotify client secret goes here. You need this along with the client ID if you want to use Spotify functionality within Pomice.
|
||||||
|
|
||||||
|
* - `apple_music`
|
||||||
|
- `bool`
|
||||||
|
- Set this value to `True` if you want to use Apple Music functionality within Pomice. Apple Music will **not work** if you don't enable this.
|
||||||
|
|
||||||
|
* - `fallback`
|
||||||
|
- `bool`
|
||||||
|
- Set this value to `True` if you want Pomice to automatically switch all players to another available node if one disconnects.
|
||||||
|
You must have two or more nodes to be able to do this.
|
||||||
|
|
||||||
|
* - `logger`
|
||||||
|
- `Optional[logging.Logger]`
|
||||||
|
- If you would like to receive logging information from Pomice, set this to your logger class
|
||||||
|
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
|
|
||||||
|
All the other parameters not listed here have default values that are either set within the function or set later in the initialization of the node. If you would like to set these parameters to a different value, you are free to do so.
|
||||||
|
|
||||||
|
After you set those parameters, your function should look something like this:
|
||||||
|
|
||||||
|
```py
|
||||||
|
|
||||||
|
await NodePool.create_node(
|
||||||
|
bot=bot,
|
||||||
|
host="<your ip here>",
|
||||||
|
port=<your port here>,
|
||||||
|
identifier="<your id here>",
|
||||||
|
password="<your password here>",
|
||||||
|
spotify_client_id="<your spotify client id here>",
|
||||||
|
spotify_client_secret="<your spotify client secret here>"
|
||||||
|
apple_music=<True/False>,
|
||||||
|
fallback=<True/False>,
|
||||||
|
logger=<your logger here>
|
||||||
|
)
|
||||||
|
|
||||||
|
```
|
||||||
|
:::{important}
|
||||||
|
|
||||||
|
For features like Spotify and Apple Music, you are **not required** to fill in anything for them if you do not want to use them. If you do end up queuing a Spotify or Apple Music track, it is **up to you** on how you decide to handle it, whether it be through your own methods or a Lavalink plugin.
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
|
Now that you have your Node object created, move on to [Using a node](node.md) to see what you can do with your `Node` object.
|
||||||
|
|
||||||
|
## Getting a node
|
||||||
|
|
||||||
|
To get a node from the node pool, we need to use `NodePool.get_node()`
|
||||||
|
|
||||||
|
```py
|
||||||
|
|
||||||
|
await NodePool.get_node(...)
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
After you have initialized your function, you can specify a identifier if you want to grab a specified node:
|
||||||
|
|
||||||
|
```py
|
||||||
|
|
||||||
|
await NodePool.get_node(identifier="<your id here>")
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
If you do not set a identifier, it'll return a random node from the pool.
|
||||||
|
|
||||||
|
|
||||||
|
## Getting the best node
|
||||||
|
|
||||||
|
To get a node from the node pool based on certain requirements, we need to use `NodePool.get_best_node()`
|
||||||
|
|
||||||
|
```py
|
||||||
|
|
||||||
|
await NodePool.get_best_node(...)
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
After you have initialized your function, you need to specify a `NodeAlgorithm` to use to grab your node from the pool.
|
||||||
|
The available algorithms are `by_ping` and `by_players`.
|
||||||
|
If you want to view what they do, refer to the `NodeAlgorithm` enum in the [](../api/enums.md) section.
|
||||||
|
|
||||||
|
```py
|
||||||
|
|
||||||
|
await NodePool.get_best_node(algorithm=NodeAlgorithm.xyz)
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
## Disconnecting all nodes from the pool
|
||||||
|
|
||||||
|
To disconnect all nodes from the pool, we need to use `NodePool.disconnect()`
|
||||||
|
|
||||||
|
```py
|
||||||
|
|
||||||
|
await NodePool.disconnect()
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
After running this function, all nodes in the pool should disconnect and no longer be available to use.
|
||||||
|
|
@ -0,0 +1,224 @@
|
||||||
|
# Use the Queue class
|
||||||
|
|
||||||
|
Pomice has an optional queue system that works seamlessly with the library. This queue system introduce quality-of-life features that every music application should ideally have like queue shuffling, queue jumping, and looping.
|
||||||
|
|
||||||
|
|
||||||
|
To use the queue system with Pomice, you must first subclass the `Player` class within your application like so:
|
||||||
|
|
||||||
|
```py
|
||||||
|
|
||||||
|
from pomice import Player
|
||||||
|
|
||||||
|
class CustomPlayer(Player):
|
||||||
|
...
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
After you have initialized your subclass, you can add a `queue` variable to your class so you can access your queue when you initialize your player:
|
||||||
|
|
||||||
|
```py
|
||||||
|
|
||||||
|
from pomice import Player, Queue
|
||||||
|
|
||||||
|
class CustomPlayer(Player):
|
||||||
|
...
|
||||||
|
self.queue = Queue()
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
## Adding a song to the queue
|
||||||
|
|
||||||
|
To add a song to the queue, we must use `Queue.put()`
|
||||||
|
|
||||||
|
```py
|
||||||
|
|
||||||
|
Queue.put()
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
After you have initialized your function, we need to include the `item` parameter, which is a `Track`:
|
||||||
|
|
||||||
|
```py
|
||||||
|
|
||||||
|
Queue.put(item=<your Track here>)
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
After running the function, your track should be in the queue.
|
||||||
|
|
||||||
|
## Getting a track from the queue
|
||||||
|
|
||||||
|
To get a track from the queue, we need to do a few things.
|
||||||
|
|
||||||
|
To get a track using its position within the queue, you first need to get the position as a number, also known as its index. If you dont have the index and instead want to search for its index using keywords, you have to implement a fuzzy searching algorithm to find said track using a search query as an input and have it compare that query against the titles of the tracks in the queue. After that, you can get the `Track` object by [getting it with its index](queue.md#getting-track-with-its-index)
|
||||||
|
|
||||||
|
### Getting index of track
|
||||||
|
|
||||||
|
If you have the `Track` object and want to get its index within the queue, we can use `Queue.find_position()`
|
||||||
|
|
||||||
|
```py
|
||||||
|
|
||||||
|
Queue.find_position()
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
After you have initialized your function, we need to include the `item` parameter, which is a `Track`:
|
||||||
|
|
||||||
|
```py
|
||||||
|
|
||||||
|
Queue.find_position(item=<your Track here>)
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
After running the function, it should return the position of the track as an integer.
|
||||||
|
|
||||||
|
|
||||||
|
### Getting track with its index
|
||||||
|
|
||||||
|
If you have the index of the track and want to get the `Track` object, you first need to get the raw queue list:
|
||||||
|
|
||||||
|
```py
|
||||||
|
|
||||||
|
queue = Queue.get_queue()
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
After you have your queue, you can use basic list splicing to get the track object:
|
||||||
|
|
||||||
|
```py
|
||||||
|
|
||||||
|
track = queue[<index>]
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
## Getting the next track in the queue
|
||||||
|
|
||||||
|
To get the next track in the queue, we need to use `Queue.get()`
|
||||||
|
|
||||||
|
```py
|
||||||
|
|
||||||
|
Queue.get()
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
After running this function, it'll return the first track from the queue and remove it.
|
||||||
|
|
||||||
|
:::{note}
|
||||||
|
|
||||||
|
If you have a queue loop mode set, this behavior will be overridden since the queue is not allowed to remove tracks from the queue if its looping.
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
|
## Removing a track from the queue
|
||||||
|
|
||||||
|
|
||||||
|
To remove a track from the queue, we must use `Queue.remove()`
|
||||||
|
|
||||||
|
```py
|
||||||
|
|
||||||
|
Queue.remove()
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
After you have initialized your function, we need to include the `item` parameter, which is a `Track`:
|
||||||
|
|
||||||
|
```py
|
||||||
|
|
||||||
|
Queue.remove(item=<your Track here>)
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
:::{important}
|
||||||
|
|
||||||
|
Your `Track` object must be in the queue if you want to remove it. Make sure you follow [](queue.md#getting-a-track-from-the-queue) before running this function.
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
|
After running this function, your track should be removed from the queue.
|
||||||
|
|
||||||
|
|
||||||
|
## Shuffling the queue
|
||||||
|
|
||||||
|
To shuffle the queue, we must use `Queue.shuffle()`
|
||||||
|
|
||||||
|
```py
|
||||||
|
|
||||||
|
Queue.shuffle()
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
After running this function, your queue should be in a different order than it was originally.
|
||||||
|
|
||||||
|
:::{tip}
|
||||||
|
|
||||||
|
This function works best if theres atleast **3** tracks in the queue. The more tracks, the more variation the shuffle has.
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
|
|
||||||
|
## Looping the queue
|
||||||
|
|
||||||
|
To loop the queue, we must use `Queue.set_loop_mode()`
|
||||||
|
|
||||||
|
```py
|
||||||
|
|
||||||
|
Queue.set_loop_mode(...)
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
After you have initialized your function, we need to include the `mode` parameter, which is a `LoopMode` enum:
|
||||||
|
|
||||||
|
```py
|
||||||
|
|
||||||
|
Queue.set_loop_mode(mode=LoopMode.<mode>)
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
The two types of `LoopMode` enums are `LoopMode.QUEUE` and `LoopMode.TRACK`. `QUEUE` loops the entire queue and `TRACK` loops the current track.
|
||||||
|
|
||||||
|
After running the function, your queue will now loop using the mode you specify.
|
||||||
|
|
||||||
|
### Resetting the loop mode
|
||||||
|
|
||||||
|
To reset the loop mode, we must use `Queue.disable_loop()`
|
||||||
|
|
||||||
|
```py
|
||||||
|
|
||||||
|
Queue.disable_loop()
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
:::{important}
|
||||||
|
|
||||||
|
You must have a loop mode set before using this function. It will **not work** if you do not a loop mode set
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
|
After running the function, your queue should return to its normal functionality.
|
||||||
|
|
||||||
|
## Jumping to a track in the queue
|
||||||
|
|
||||||
|
To jump to a track in the queue, we must use `Queue.jump()`
|
||||||
|
|
||||||
|
|
||||||
|
```py
|
||||||
|
|
||||||
|
Queue.jump(...)
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
After you have initialized your function, we need to include the `item` parameter, which is a `Track`:
|
||||||
|
|
||||||
|
```py
|
||||||
|
|
||||||
|
Queue.jump(item=<your Track here>)
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
:::{important}
|
||||||
|
|
||||||
|
Your `Track` object must be in the queue if you want to jump to it. Make sure you follow [](queue.md#getting-a-track-from-the-queue) before running this function.
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
|
After running this function, any items before the specified item will be removed, effectively "jumping" to the specified item in the queue. The next item obtained using `Queue.get()` will be your specified track.
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
---
|
||||||
|
hide-toc: true
|
||||||
|
---
|
||||||
|
|
||||||
|
# Pomice
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Pomice is a fully asynchronous Python library designed for communicating with [Lavalink](https://github.com/freyacodes/Lavalink) seamlessly within the [discord.py](https://github.com/Rapptz/discord.py) library. It features 100% API coverage of the entire [Lavalink](https://github.com/freyacodes/Lavalink) spec that can be accessed with easy-to-understand functions. We also include Spotify and Apple Music querying capabilites using built-in custom clients, making it easier to develop your next big music bot.
|
||||||
|
|
||||||
|
|
||||||
|
## Quick Links:
|
||||||
|
- [Installation](installation.md)
|
||||||
|
- [Quickstart](quickstart.md)
|
||||||
|
- [Frequently Asked Questions](faq.md)
|
||||||
|
- [How Do I?](hdi/index.md)
|
||||||
|
- [API Reference](api/index.md)
|
||||||
|
|
||||||
|
|
||||||
|
```{toctree}
|
||||||
|
:caption: Before You Start
|
||||||
|
:hidden:
|
||||||
|
installation
|
||||||
|
quickstart
|
||||||
|
faq
|
||||||
|
```
|
||||||
|
|
||||||
|
```{toctree}
|
||||||
|
:caption: How Do I?
|
||||||
|
:hidden:
|
||||||
|
hdi/index.md
|
||||||
|
```
|
||||||
|
|
||||||
|
```{toctree}
|
||||||
|
:caption: API Reference
|
||||||
|
:hidden:
|
||||||
|
api/index.md
|
||||||
|
```
|
||||||
112
docs/index.rst
112
docs/index.rst
|
|
@ -1,112 +0,0 @@
|
||||||
.. pomice documentation master file, created by
|
|
||||||
sphinx-quickstart on Tue Nov 2 19:31:07 2021.
|
|
||||||
You can adapt this file completely to your liking, but it should at least
|
|
||||||
contain the root `toctree` directive.
|
|
||||||
|
|
||||||
Welcome to Pomice!
|
|
||||||
==================
|
|
||||||
|
|
||||||
.. image:: https://raw.githubusercontent.com/cloudwithax/pomice/main/banner.jpg
|
|
||||||
|
|
||||||
The modern `Lavalink <https://github.com/freyacodes/Lavalink>`_ wrapper designed for `discord.py <https://github.com/Rapptz/discord.py>`_
|
|
||||||
|
|
||||||
|
|
||||||
Contents
|
|
||||||
---------
|
|
||||||
|
|
||||||
.. toctree::
|
|
||||||
modules
|
|
||||||
|
|
||||||
|
|
||||||
Before You Start
|
|
||||||
----------------
|
|
||||||
|
|
||||||
This library is designed to work with the Lavalink audio delivery system,
|
|
||||||
which directly interfaces with Discord to provide buttery smooth audio without
|
|
||||||
wasting your precious system resources.
|
|
||||||
|
|
||||||
Pomice is made with convenience to the user, in that everything is easy to use
|
|
||||||
and is out of your way, while also being customizable.
|
|
||||||
|
|
||||||
In order to start using this library, please download a Lavalink node to start,
|
|
||||||
you can get it `here <https://github.com/freyacodes/Lavalink/releases/latest>`_
|
|
||||||
|
|
||||||
Quick Jumpstart
|
|
||||||
----------------
|
|
||||||
|
|
||||||
If you want a quick example as to how to start with Pomice, look below:
|
|
||||||
|
|
||||||
.. code-block:: python3
|
|
||||||
|
|
||||||
import pomice
|
|
||||||
import discord
|
|
||||||
import re
|
|
||||||
|
|
||||||
from discord.ext import commands
|
|
||||||
|
|
||||||
URL_REG = re.compile(r'https?://(?:www\.)?.+')
|
|
||||||
|
|
||||||
class MyBot(commands.Bot):
|
|
||||||
|
|
||||||
def __init__(self) -> None:
|
|
||||||
super().__init__(command_prefix='!', activity=discord.Activity(type=discord.ActivityType.listening, name='to music!'))
|
|
||||||
|
|
||||||
self.add_cog(Music(self))
|
|
||||||
|
|
||||||
async def on_ready(self) -> None:
|
|
||||||
print("I'm online!")
|
|
||||||
await self.cogs["Music"].start_nodes()
|
|
||||||
|
|
||||||
|
|
||||||
class Music(commands.Cog):
|
|
||||||
|
|
||||||
def __init__(self, bot) -> None:
|
|
||||||
self.bot = bot
|
|
||||||
|
|
||||||
self.pomice = pomice.NodePool()
|
|
||||||
|
|
||||||
async def start_nodes(self):
|
|
||||||
await self.pomice.create_node(bot=self.bot, host='127.0.0.1', port='3030',
|
|
||||||
password='youshallnotpass', identifier='MAIN')
|
|
||||||
print(f"Node is ready!")
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@commands.command(name='join', aliases=['connect'])
|
|
||||||
async def join(self, ctx: commands.Context, *, channel: discord.TextChannel = None) -> None:
|
|
||||||
|
|
||||||
if not channel:
|
|
||||||
channel = getattr(ctx.author.voice, 'channel', None)
|
|
||||||
if not channel:
|
|
||||||
raise commands.CheckFailure('You must be in a voice channel to use this command'
|
|
||||||
'without specifying the channel argument.')
|
|
||||||
|
|
||||||
|
|
||||||
await ctx.author.voice.channel.connect(cls=pomice.Player)
|
|
||||||
await ctx.send(f'Joined the voice channel `{channel}`')
|
|
||||||
|
|
||||||
@commands.command(name='play')
|
|
||||||
async def play(self, ctx, *, search: str) -> None:
|
|
||||||
|
|
||||||
if not ctx.voice_client:
|
|
||||||
await ctx.invoke(self.join)
|
|
||||||
|
|
||||||
player = ctx.voice_client
|
|
||||||
|
|
||||||
results = await player.get_tracks(query=f'{search}')
|
|
||||||
|
|
||||||
if not results:
|
|
||||||
raise commands.CommandError('No results were found for that search term.')
|
|
||||||
|
|
||||||
if isinstance(results, pomice.Playlist):
|
|
||||||
await player.play(track=results.tracks[0])
|
|
||||||
else:
|
|
||||||
await player.play(track=results[0])
|
|
||||||
|
|
||||||
|
|
||||||
bot = MyBot()
|
|
||||||
bot.run("token here")
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
# Installation
|
||||||
|
|
||||||
|
This library is designed to work with the Lavalink audio delivery system,
|
||||||
|
which directly interfaces with Discord to provide buttery smooth audio without
|
||||||
|
wasting your precious system resources.
|
||||||
|
|
||||||
|
Pomice is made with convenience to the user, in that everything is easy to use
|
||||||
|
and is out of your way, while also being customizable.
|
||||||
|
|
||||||
|
In order to start using this library, please download a Lavalink node to start,
|
||||||
|
you can get it [here](https://github.com/freyacodes/Lavalink/releases/latest)
|
||||||
|
|
||||||
|
After you have your Lavalink node set up, you can install Pomice:
|
||||||
|
|
||||||
|
```
|
||||||
|
pip install pomice
|
||||||
|
```
|
||||||
|
|
||||||
|
Pomice will handle installing all required dependencies for you so you can
|
||||||
|
start coding with it without a hitch.
|
||||||
|
|
||||||
|
After you installed Pomice, get familiar with how it works by starting out with [an example.](quickstart.md)
|
||||||
|
|
||||||
|
If you need more than just a quick example, get our drop-in [advanced cog](https://github.com/cloudwithax/pomice/blob/main/examples/advanced.py)
|
||||||
|
to take advantage of all of Pomice's features.
|
||||||
|
You are free to use this as a base to add on to for any music features you want to implement within your application.
|
||||||
|
|
||||||
|
If you want to jump into the library and learn how to do everything you need, refer to the [How Do I?](hdi/index.md) section.
|
||||||
|
|
||||||
|
If you want a deeper look into how the library works beyond the [How Do I?](hdi/index.md) guide, refer to the [API Reference](api/index.md) section.
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
.. toctree::
|
|
||||||
:maxdepth: 4
|
|
||||||
|
|
||||||
pomice
|
|
||||||
|
|
@ -1,77 +0,0 @@
|
||||||
Pomice
|
|
||||||
==============
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Enums
|
|
||||||
-------------------
|
|
||||||
|
|
||||||
.. automodule:: pomice.enums
|
|
||||||
:members:
|
|
||||||
:undoc-members:
|
|
||||||
:show-inheritance:
|
|
||||||
|
|
||||||
Events
|
|
||||||
--------------------
|
|
||||||
|
|
||||||
.. automodule:: pomice.events
|
|
||||||
:members:
|
|
||||||
:undoc-members:
|
|
||||||
:show-inheritance:
|
|
||||||
|
|
||||||
Exceptions
|
|
||||||
------------------------
|
|
||||||
|
|
||||||
.. automodule:: pomice.exceptions
|
|
||||||
:members:
|
|
||||||
:undoc-members:
|
|
||||||
:show-inheritance:
|
|
||||||
|
|
||||||
Filters
|
|
||||||
---------------------
|
|
||||||
|
|
||||||
.. automodule:: pomice.filters
|
|
||||||
:members:
|
|
||||||
:undoc-members:
|
|
||||||
:show-inheritance:
|
|
||||||
|
|
||||||
Objects
|
|
||||||
---------------------
|
|
||||||
|
|
||||||
.. automodule:: pomice.objects
|
|
||||||
:members:
|
|
||||||
:undoc-members:
|
|
||||||
:show-inheritance:
|
|
||||||
|
|
||||||
Player
|
|
||||||
--------------------
|
|
||||||
|
|
||||||
.. automodule:: pomice.player
|
|
||||||
:members:
|
|
||||||
:undoc-members:
|
|
||||||
:show-inheritance:
|
|
||||||
|
|
||||||
Pool
|
|
||||||
------------------
|
|
||||||
|
|
||||||
.. automodule:: pomice.pool
|
|
||||||
:members:
|
|
||||||
:undoc-members:
|
|
||||||
:show-inheritance:
|
|
||||||
|
|
||||||
Queue
|
|
||||||
------------------
|
|
||||||
|
|
||||||
.. automodule:: pomice.queue
|
|
||||||
:members:
|
|
||||||
:undoc-members:
|
|
||||||
:show-inheritance:
|
|
||||||
|
|
||||||
Utils
|
|
||||||
-------------------
|
|
||||||
|
|
||||||
.. automodule:: pomice.utils
|
|
||||||
:members:
|
|
||||||
:undoc-members:
|
|
||||||
:show-inheritance:
|
|
||||||
|
|
||||||
|
|
@ -0,0 +1,83 @@
|
||||||
|
# Quick Jumpstart
|
||||||
|
|
||||||
|
|
||||||
|
If you want a quick example as to how to start with Pomice, look below:
|
||||||
|
|
||||||
|
```py
|
||||||
|
import pomice
|
||||||
|
import discord
|
||||||
|
import re
|
||||||
|
|
||||||
|
from discord.ext import commands
|
||||||
|
|
||||||
|
URL_REG = re.compile(r"https?://(?:www\.)?.+")
|
||||||
|
|
||||||
|
|
||||||
|
class MyBot(commands.Bot):
|
||||||
|
def __init__(self) -> None:
|
||||||
|
super().__init__(
|
||||||
|
command_prefix="!",
|
||||||
|
activity=discord.Activity(
|
||||||
|
type=discord.ActivityType.listening, name="to music!"
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
self.add_cog(Music(self))
|
||||||
|
|
||||||
|
async def on_ready(self) -> None:
|
||||||
|
print("I'm online!")
|
||||||
|
await self.cogs["Music"].start_nodes()
|
||||||
|
|
||||||
|
|
||||||
|
class Music(commands.Cog):
|
||||||
|
def __init__(self, bot) -> None:
|
||||||
|
self.bot = bot
|
||||||
|
|
||||||
|
self.pomice = pomice.NodePool()
|
||||||
|
|
||||||
|
async def start_nodes(self):
|
||||||
|
await self.pomice.create_node(
|
||||||
|
bot=self.bot,
|
||||||
|
host="127.0.0.1",
|
||||||
|
port="3030",
|
||||||
|
password="youshallnotpass",
|
||||||
|
identifier="MAIN",
|
||||||
|
)
|
||||||
|
print(f"Node is ready!")
|
||||||
|
|
||||||
|
@commands.command(name="join", aliases=["connect"])
|
||||||
|
async def join(
|
||||||
|
self, ctx: commands.Context, *, channel: discord.TextChannel = None
|
||||||
|
) -> None:
|
||||||
|
if not channel:
|
||||||
|
channel = getattr(ctx.author.voice, "channel", None)
|
||||||
|
if not channel:
|
||||||
|
raise commands.CheckFailure(
|
||||||
|
"You must be in a voice channel to use this command"
|
||||||
|
"without specifying the channel argument."
|
||||||
|
)
|
||||||
|
|
||||||
|
await ctx.author.voice.channel.connect(cls=pomice.Player)
|
||||||
|
await ctx.send(f"Joined the voice channel `{channel}`")
|
||||||
|
|
||||||
|
@commands.command(name="play")
|
||||||
|
async def play(self, ctx, *, search: str) -> None:
|
||||||
|
if not ctx.voice_client:
|
||||||
|
await ctx.invoke(self.join)
|
||||||
|
|
||||||
|
player = ctx.voice_client
|
||||||
|
|
||||||
|
results = await player.get_tracks(query=f"{search}")
|
||||||
|
|
||||||
|
if not results:
|
||||||
|
raise commands.CommandError("No results were found for that search term.")
|
||||||
|
|
||||||
|
if isinstance(results, pomice.Playlist):
|
||||||
|
await player.play(track=results.tracks[0])
|
||||||
|
else:
|
||||||
|
await player.play(track=results[0])
|
||||||
|
|
||||||
|
|
||||||
|
bot = MyBot()
|
||||||
|
bot.run("token here")
|
||||||
|
```
|
||||||
|
|
@ -1,3 +1,6 @@
|
||||||
discord.py[voice]
|
|
||||||
aiohttp
|
aiohttp
|
||||||
|
discord.py[voice]
|
||||||
|
furo
|
||||||
|
myst_parser
|
||||||
orjson
|
orjson
|
||||||
|
websockets
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,17 @@
|
||||||
|
# type: ignore
|
||||||
"""
|
"""
|
||||||
This example aims to show the full capabilities of the library.
|
This example aims to show the full capabilities of the library.
|
||||||
This is in the form of a drop-in cog you can use and modify to your liking.
|
This is in the form of a drop-in cog you can use and modify to your liking.
|
||||||
This example aims to include everything you would need to make a fully functioning music bot,
|
This example aims to include everything you would need to make a fully functioning music bot,
|
||||||
from a queue system, advanced queue control and more.
|
from a queue system, advanced queue control and more.
|
||||||
"""
|
"""
|
||||||
|
import math
|
||||||
|
from contextlib import suppress
|
||||||
|
|
||||||
import discord
|
import discord
|
||||||
import pomice
|
|
||||||
import math
|
|
||||||
|
|
||||||
from discord.ext import commands
|
from discord.ext import commands
|
||||||
from contextlib import suppress
|
|
||||||
|
import pomice
|
||||||
|
|
||||||
|
|
||||||
class Player(pomice.Player):
|
class Player(pomice.Player):
|
||||||
|
|
@ -44,8 +45,7 @@ class Player(pomice.Player):
|
||||||
with suppress(discord.HTTPException):
|
with suppress(discord.HTTPException):
|
||||||
await self.controller.delete()
|
await self.controller.delete()
|
||||||
|
|
||||||
|
# Queue up the next track, else teardown the player
|
||||||
# Queue up the next track, else teardown the player
|
|
||||||
try:
|
try:
|
||||||
track: pomice.Track = self.queue.get()
|
track: pomice.Track = self.queue.get()
|
||||||
except pomice.QueueEmpty:
|
except pomice.QueueEmpty:
|
||||||
|
|
@ -56,13 +56,18 @@ class Player(pomice.Player):
|
||||||
# Call the controller (a.k.a: The "Now Playing" embed) and check if one exists
|
# Call the controller (a.k.a: The "Now Playing" embed) and check if one exists
|
||||||
|
|
||||||
if track.is_stream:
|
if track.is_stream:
|
||||||
embed = discord.Embed(title="Now playing", description=f":red_circle: **LIVE** [{track.title}]({track.uri}) [{track.requester.mention}]")
|
embed = discord.Embed(
|
||||||
|
title="Now playing",
|
||||||
|
description=f":red_circle: **LIVE** [{track.title}]({track.uri}) [{track.requester.mention}]",
|
||||||
|
)
|
||||||
self.controller = await self.context.send(embed=embed)
|
self.controller = await self.context.send(embed=embed)
|
||||||
else:
|
else:
|
||||||
embed = discord.Embed(title=f"Now playing", description=f"[{track.title}]({track.uri}) [{track.requester.mention}]")
|
embed = discord.Embed(
|
||||||
|
title=f"Now playing",
|
||||||
|
description=f"[{track.title}]({track.uri}) [{track.requester.mention}]",
|
||||||
|
)
|
||||||
self.controller = await self.context.send(embed=embed)
|
self.controller = await self.context.send(embed=embed)
|
||||||
|
|
||||||
|
|
||||||
async def teardown(self):
|
async def teardown(self):
|
||||||
"""Clear internal states, remove player controller and disconnect."""
|
"""Clear internal states, remove player controller and disconnect."""
|
||||||
with suppress((discord.HTTPException), (KeyError)):
|
with suppress((discord.HTTPException), (KeyError)):
|
||||||
|
|
@ -76,8 +81,6 @@ class Player(pomice.Player):
|
||||||
self.dj = ctx.author
|
self.dj = ctx.author
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class Music(commands.Cog):
|
class Music(commands.Cog):
|
||||||
def __init__(self, bot: commands.Bot) -> None:
|
def __init__(self, bot: commands.Bot) -> None:
|
||||||
self.bot = bot
|
self.bot = bot
|
||||||
|
|
@ -98,31 +101,30 @@ class Music(commands.Cog):
|
||||||
await self.pomice.create_node(
|
await self.pomice.create_node(
|
||||||
bot=self.bot,
|
bot=self.bot,
|
||||||
host="127.0.0.1",
|
host="127.0.0.1",
|
||||||
port="3030",
|
port=3030,
|
||||||
password="youshallnotpass",
|
password="youshallnotpass",
|
||||||
identifier="MAIN"
|
identifier="MAIN",
|
||||||
)
|
)
|
||||||
print(f"Node is ready!")
|
print(f"Node is ready!")
|
||||||
|
|
||||||
async def required(self, ctx: commands.Context):
|
def required(self, ctx: commands.Context):
|
||||||
"""Method which returns required votes based on amount of members in a channel."""
|
"""Method which returns required votes based on amount of members in a channel."""
|
||||||
player: Player = ctx.voice_client
|
player: Player = ctx.voice_client
|
||||||
channel = self.bot.get_channel(int(player.channel.id))
|
channel = self.bot.get_channel(int(player.channel.id))
|
||||||
required = math.ceil((len(channel.members) - 1) / 2.5)
|
required = math.ceil((len(channel.members) - 1) / 2.5)
|
||||||
|
|
||||||
if ctx.command.name == 'stop':
|
if ctx.command.name == "stop":
|
||||||
if len(channel.members) == 3:
|
if len(channel.members) == 3:
|
||||||
required = 2
|
required = 2
|
||||||
|
|
||||||
return required
|
return required
|
||||||
|
|
||||||
async def is_privileged(self, ctx: commands.Context):
|
def is_privileged(self, ctx: commands.Context):
|
||||||
"""Check whether the user is an Admin or DJ."""
|
"""Check whether the user is an Admin or DJ."""
|
||||||
player: Player = ctx.voice_client
|
player: Player = ctx.voice_client
|
||||||
|
|
||||||
return player.dj == ctx.author or ctx.author.guild_permissions.kick_members
|
return player.dj == ctx.author or ctx.author.guild_permissions.kick_members
|
||||||
|
|
||||||
|
|
||||||
# The following are events from pomice.events
|
# The following are events from pomice.events
|
||||||
# We are using these so that if the track either stops or errors,
|
# We are using these so that if the track either stops or errors,
|
||||||
# we can just skip to the next track
|
# we can just skip to the next track
|
||||||
|
|
@ -141,12 +143,14 @@ class Music(commands.Cog):
|
||||||
async def on_pomice_track_exception(self, player: Player, track, _):
|
async def on_pomice_track_exception(self, player: Player, track, _):
|
||||||
await player.do_next()
|
await player.do_next()
|
||||||
|
|
||||||
@commands.command(aliases=['joi', 'j', 'summon', 'su', 'con', 'connect'])
|
@commands.command(aliases=["joi", "j", "summon", "su", "con", "connect"])
|
||||||
async def join(self, ctx: commands.Context, *, channel: discord.VoiceChannel = None) -> None:
|
async def join(self, ctx: commands.Context, *, channel: discord.VoiceChannel = None) -> None:
|
||||||
if not channel:
|
if not channel:
|
||||||
channel = getattr(ctx.author.voice, "channel", None)
|
channel = getattr(ctx.author.voice, "channel", None)
|
||||||
if not channel:
|
if not channel:
|
||||||
return await ctx.send("You must be in a voice channel in order to use this command!")
|
return await ctx.send(
|
||||||
|
"You must be in a voice channel in order to use this command!",
|
||||||
|
)
|
||||||
|
|
||||||
# With the release of discord.py 1.7, you can now add a compatible
|
# With the release of discord.py 1.7, you can now add a compatible
|
||||||
# VoiceProtocol class as an argument in VoiceChannel.connect().
|
# VoiceProtocol class as an argument in VoiceChannel.connect().
|
||||||
|
|
@ -158,15 +162,18 @@ class Music(commands.Cog):
|
||||||
await player.set_context(ctx=ctx)
|
await player.set_context(ctx=ctx)
|
||||||
await ctx.send(f"Joined the voice channel `{channel.name}`")
|
await ctx.send(f"Joined the voice channel `{channel.name}`")
|
||||||
|
|
||||||
@commands.command(aliases=['disconnect', 'dc', 'disc', 'lv', 'fuckoff'])
|
@commands.command(aliases=["disconnect", "dc", "disc", "lv", "fuckoff"])
|
||||||
async def leave(self, ctx: commands.Context):
|
async def leave(self, ctx: commands.Context):
|
||||||
if not (player := ctx.voice_client):
|
if not (player := ctx.voice_client):
|
||||||
return await ctx.send("You must have the bot in a channel in order to use this command", delete_after=7)
|
return await ctx.send(
|
||||||
|
"You must have the bot in a channel in order to use this command",
|
||||||
|
delete_after=7,
|
||||||
|
)
|
||||||
|
|
||||||
await player.destroy()
|
await player.destroy()
|
||||||
await ctx.send("Player has left the channel.")
|
await ctx.send("Player has left the channel.")
|
||||||
|
|
||||||
@commands.command(aliases=['pla', 'p'])
|
@commands.command(aliases=["pla", "p"])
|
||||||
async def play(self, ctx: commands.Context, *, search: str) -> None:
|
async def play(self, ctx: commands.Context, *, search: str) -> None:
|
||||||
# Checks if the player is in the channel before we play anything
|
# Checks if the player is in the channel before we play anything
|
||||||
if not (player := ctx.voice_client):
|
if not (player := ctx.voice_client):
|
||||||
|
|
@ -195,19 +202,20 @@ class Music(commands.Cog):
|
||||||
if not player.is_playing:
|
if not player.is_playing:
|
||||||
await player.do_next()
|
await player.do_next()
|
||||||
|
|
||||||
|
@commands.command(aliases=["pau", "pa"])
|
||||||
|
|
||||||
@commands.command(aliases=['pau', 'pa'])
|
|
||||||
async def pause(self, ctx: commands.Context):
|
async def pause(self, ctx: commands.Context):
|
||||||
"""Pause the currently playing song."""
|
"""Pause the currently playing song."""
|
||||||
if not (player := ctx.voice_client):
|
if not (player := ctx.voice_client):
|
||||||
return await ctx.send("You must have the bot in a channel in order to use this command", delete_after=7)
|
return await ctx.send(
|
||||||
|
"You must have the bot in a channel in order to use this command",
|
||||||
|
delete_after=7,
|
||||||
|
)
|
||||||
|
|
||||||
if player.is_paused or not player.is_connected:
|
if player.is_paused or not player.is_connected:
|
||||||
return
|
return
|
||||||
|
|
||||||
if self.is_privileged(ctx):
|
if self.is_privileged(ctx):
|
||||||
await ctx.send('An admin or DJ has paused the player.', delete_after=10)
|
await ctx.send("An admin or DJ has paused the player.", delete_after=10)
|
||||||
player.pause_votes.clear()
|
player.pause_votes.clear()
|
||||||
|
|
||||||
return await player.set_pause(True)
|
return await player.set_pause(True)
|
||||||
|
|
@ -216,23 +224,29 @@ class Music(commands.Cog):
|
||||||
player.pause_votes.add(ctx.author)
|
player.pause_votes.add(ctx.author)
|
||||||
|
|
||||||
if len(player.pause_votes) >= required:
|
if len(player.pause_votes) >= required:
|
||||||
await ctx.send('Vote to pause passed. Pausing player.', delete_after=10)
|
await ctx.send("Vote to pause passed. Pausing player.", delete_after=10)
|
||||||
player.pause_votes.clear()
|
player.pause_votes.clear()
|
||||||
await player.set_pause(True)
|
await player.set_pause(True)
|
||||||
else:
|
else:
|
||||||
await ctx.send(f'{ctx.author.mention} has voted to pause the player. Votes: {len(player.pause_votes)}/{required}', delete_after=15)
|
await ctx.send(
|
||||||
|
f"{ctx.author.mention} has voted to pause the player. Votes: {len(player.pause_votes)}/{required}",
|
||||||
|
delete_after=15,
|
||||||
|
)
|
||||||
|
|
||||||
@commands.command(aliases=['res', 'r'])
|
@commands.command(aliases=["res", "r"])
|
||||||
async def resume(self, ctx: commands.Context):
|
async def resume(self, ctx: commands.Context):
|
||||||
"""Resume a currently paused player."""
|
"""Resume a currently paused player."""
|
||||||
if not (player := ctx.voice_client):
|
if not (player := ctx.voice_client):
|
||||||
return await ctx.send("You must have the bot in a channel in order to use this command", delete_after=7)
|
return await ctx.send(
|
||||||
|
"You must have the bot in a channel in order to use this command",
|
||||||
|
delete_after=7,
|
||||||
|
)
|
||||||
|
|
||||||
if not player.is_paused or not player.is_connected:
|
if not player.is_paused or not player.is_connected:
|
||||||
return
|
return
|
||||||
|
|
||||||
if self.is_privileged(ctx):
|
if self.is_privileged(ctx):
|
||||||
await ctx.send('An admin or DJ has resumed the player.', delete_after=10)
|
await ctx.send("An admin or DJ has resumed the player.", delete_after=10)
|
||||||
player.resume_votes.clear()
|
player.resume_votes.clear()
|
||||||
|
|
||||||
return await player.set_pause(False)
|
return await player.set_pause(False)
|
||||||
|
|
@ -241,29 +255,35 @@ class Music(commands.Cog):
|
||||||
player.resume_votes.add(ctx.author)
|
player.resume_votes.add(ctx.author)
|
||||||
|
|
||||||
if len(player.resume_votes) >= required:
|
if len(player.resume_votes) >= required:
|
||||||
await ctx.send('Vote to resume passed. Resuming player.', delete_after=10)
|
await ctx.send("Vote to resume passed. Resuming player.", delete_after=10)
|
||||||
player.resume_votes.clear()
|
player.resume_votes.clear()
|
||||||
await player.set_pause(False)
|
await player.set_pause(False)
|
||||||
else:
|
else:
|
||||||
await ctx.send(f'{ctx.author.mention} has voted to resume the player. Votes: {len(player.resume_votes)}/{required}', delete_after=15)
|
await ctx.send(
|
||||||
|
f"{ctx.author.mention} has voted to resume the player. Votes: {len(player.resume_votes)}/{required}",
|
||||||
|
delete_after=15,
|
||||||
|
)
|
||||||
|
|
||||||
@commands.command(aliases=['n', 'nex', 'next', 'sk'])
|
@commands.command(aliases=["n", "nex", "next", "sk"])
|
||||||
async def skip(self, ctx: commands.Context):
|
async def skip(self, ctx: commands.Context):
|
||||||
"""Skip the currently playing song."""
|
"""Skip the currently playing song."""
|
||||||
if not (player := ctx.voice_client):
|
if not (player := ctx.voice_client):
|
||||||
return await ctx.send("You must have the bot in a channel in order to use this command", delete_after=7)
|
return await ctx.send(
|
||||||
|
"You must have the bot in a channel in order to use this command",
|
||||||
|
delete_after=7,
|
||||||
|
)
|
||||||
|
|
||||||
if not player.is_connected:
|
if not player.is_connected:
|
||||||
return
|
return
|
||||||
|
|
||||||
if self.is_privileged(ctx):
|
if self.is_privileged(ctx):
|
||||||
await ctx.send('An admin or DJ has skipped the song.', delete_after=10)
|
await ctx.send("An admin or DJ has skipped the song.", delete_after=10)
|
||||||
player.skip_votes.clear()
|
player.skip_votes.clear()
|
||||||
|
|
||||||
return await player.stop()
|
return await player.stop()
|
||||||
|
|
||||||
if ctx.author == player.current.requester:
|
if ctx.author == player.current.requester:
|
||||||
await ctx.send('The song requester has skipped the song.', delete_after=10)
|
await ctx.send("The song requester has skipped the song.", delete_after=10)
|
||||||
player.skip_votes.clear()
|
player.skip_votes.clear()
|
||||||
|
|
||||||
return await player.stop()
|
return await player.stop()
|
||||||
|
|
@ -272,48 +292,63 @@ class Music(commands.Cog):
|
||||||
player.skip_votes.add(ctx.author)
|
player.skip_votes.add(ctx.author)
|
||||||
|
|
||||||
if len(player.skip_votes) >= required:
|
if len(player.skip_votes) >= required:
|
||||||
await ctx.send('Vote to skip passed. Skipping song.', delete_after=10)
|
await ctx.send("Vote to skip passed. Skipping song.", delete_after=10)
|
||||||
player.skip_votes.clear()
|
player.skip_votes.clear()
|
||||||
await player.stop()
|
await player.stop()
|
||||||
else:
|
else:
|
||||||
await ctx.send(f'{ctx.author.mention} has voted to skip the song. Votes: {len(player.skip_votes)}/{required} ', delete_after=15)
|
await ctx.send(
|
||||||
|
f"{ctx.author.mention} has voted to skip the song. Votes: {len(player.skip_votes)}/{required} ",
|
||||||
|
delete_after=15,
|
||||||
|
)
|
||||||
|
|
||||||
@commands.command()
|
@commands.command()
|
||||||
async def stop(self, ctx: commands.Context):
|
async def stop(self, ctx: commands.Context):
|
||||||
"""Stop the player and clear all internal states."""
|
"""Stop the player and clear all internal states."""
|
||||||
if not (player := ctx.voice_client):
|
if not (player := ctx.voice_client):
|
||||||
return await ctx.send("You must have the bot in a channel in order to use this command", delete_after=7)
|
return await ctx.send(
|
||||||
|
"You must have the bot in a channel in order to use this command",
|
||||||
|
delete_after=7,
|
||||||
|
)
|
||||||
|
|
||||||
if not player.is_connected:
|
if not player.is_connected:
|
||||||
return
|
return
|
||||||
|
|
||||||
if self.is_privileged(ctx):
|
if self.is_privileged(ctx):
|
||||||
await ctx.send('An admin or DJ has stopped the player.', delete_after=10)
|
await ctx.send("An admin or DJ has stopped the player.", delete_after=10)
|
||||||
return await player.teardown()
|
return await player.teardown()
|
||||||
|
|
||||||
required = self.required(ctx)
|
required = self.required(ctx)
|
||||||
player.stop_votes.add(ctx.author)
|
player.stop_votes.add(ctx.author)
|
||||||
|
|
||||||
if len(player.stop_votes) >= required:
|
if len(player.stop_votes) >= required:
|
||||||
await ctx.send('Vote to stop passed. Stopping the player.', delete_after=10)
|
await ctx.send("Vote to stop passed. Stopping the player.", delete_after=10)
|
||||||
await player.teardown()
|
await player.teardown()
|
||||||
else:
|
else:
|
||||||
await ctx.send(f'{ctx.author.mention} has voted to stop the player. Votes: {len(player.stop_votes)}/{required}', delete_after=15)
|
await ctx.send(
|
||||||
|
f"{ctx.author.mention} has voted to stop the player. Votes: {len(player.stop_votes)}/{required}",
|
||||||
|
delete_after=15,
|
||||||
|
)
|
||||||
|
|
||||||
@commands.command(aliases=['mix', 'shuf'])
|
@commands.command(aliases=["mix", "shuf"])
|
||||||
async def shuffle(self, ctx: commands.Context):
|
async def shuffle(self, ctx: commands.Context):
|
||||||
"""Shuffle the players queue."""
|
"""Shuffle the players queue."""
|
||||||
if not (player := ctx.voice_client):
|
if not (player := ctx.voice_client):
|
||||||
return await ctx.send("You must have the bot in a channel in order to use this command", delete_after=7)
|
return await ctx.send(
|
||||||
|
"You must have the bot in a channel in order to use this command",
|
||||||
|
delete_after=7,
|
||||||
|
)
|
||||||
|
|
||||||
if not player.is_connected:
|
if not player.is_connected:
|
||||||
return
|
return
|
||||||
|
|
||||||
if player.queue.qsize() < 3:
|
if player.queue.qsize() < 3:
|
||||||
return await ctx.send('The queue is empty. Add some songs to shuffle the queue.', delete_after=15)
|
return await ctx.send(
|
||||||
|
"The queue is empty. Add some songs to shuffle the queue.",
|
||||||
|
delete_after=15,
|
||||||
|
)
|
||||||
|
|
||||||
if self.is_privileged(ctx):
|
if self.is_privileged(ctx):
|
||||||
await ctx.send('An admin or DJ has shuffled the queue.', delete_after=10)
|
await ctx.send("An admin or DJ has shuffled the queue.", delete_after=10)
|
||||||
player.shuffle_votes.clear()
|
player.shuffle_votes.clear()
|
||||||
return player.queue.shuffle()
|
return player.queue.shuffle()
|
||||||
|
|
||||||
|
|
@ -321,30 +356,36 @@ class Music(commands.Cog):
|
||||||
player.shuffle_votes.add(ctx.author)
|
player.shuffle_votes.add(ctx.author)
|
||||||
|
|
||||||
if len(player.shuffle_votes) >= required:
|
if len(player.shuffle_votes) >= required:
|
||||||
await ctx.send('Vote to shuffle passed. Shuffling the queue.', delete_after=10)
|
await ctx.send("Vote to shuffle passed. Shuffling the queue.", delete_after=10)
|
||||||
player.shuffle_votes.clear()
|
player.shuffle_votes.clear()
|
||||||
player.queue.shuffle()
|
player.queue.shuffle()
|
||||||
else:
|
else:
|
||||||
await ctx.send(f'{ctx.author.mention} has voted to shuffle the queue. Votes: {len(player.shuffle_votes)}/{required}', delete_after=15)
|
await ctx.send(
|
||||||
|
f"{ctx.author.mention} has voted to shuffle the queue. Votes: {len(player.shuffle_votes)}/{required}",
|
||||||
|
delete_after=15,
|
||||||
|
)
|
||||||
|
|
||||||
@commands.command(aliases=['v', 'vol'])
|
@commands.command(aliases=["v", "vol"])
|
||||||
async def volume(self, ctx: commands.Context, *, vol: int):
|
async def volume(self, ctx: commands.Context, *, vol: int):
|
||||||
"""Change the players volume, between 1 and 100."""
|
"""Change the players volume, between 1 and 100."""
|
||||||
if not (player := ctx.voice_client):
|
if not (player := ctx.voice_client):
|
||||||
return await ctx.send("You must have the bot in a channel in order to use this command", delete_after=7)
|
return await ctx.send(
|
||||||
|
"You must have the bot in a channel in order to use this command",
|
||||||
|
delete_after=7,
|
||||||
|
)
|
||||||
|
|
||||||
if not player.is_connected:
|
if not player.is_connected:
|
||||||
return
|
return
|
||||||
|
|
||||||
if not self.is_privileged(ctx):
|
if not self.is_privileged(ctx):
|
||||||
return await ctx.send('Only the DJ or admins may change the volume.')
|
return await ctx.send("Only the DJ or admins may change the volume.")
|
||||||
|
|
||||||
if not 0 < vol < 101:
|
if not 0 < vol < 101:
|
||||||
return await ctx.send('Please enter a value between 1 and 100.')
|
return await ctx.send("Please enter a value between 1 and 100.")
|
||||||
|
|
||||||
await player.set_volume(vol)
|
await player.set_volume(vol)
|
||||||
await ctx.send(f'Set the volume to **{vol}**%', delete_after=7)
|
await ctx.send(f"Set the volume to **{vol}**%", delete_after=7)
|
||||||
|
|
||||||
|
|
||||||
async def setup(bot: commands.Bot):
|
async def setup(bot: commands.Bot):
|
||||||
await bot.add_cog(Music(bot))
|
await bot.add_cog(Music(bot))
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,18 @@
|
||||||
|
# type: ignore
|
||||||
import discord
|
import discord
|
||||||
import pomice
|
|
||||||
from discord.ext import commands
|
from discord.ext import commands
|
||||||
|
|
||||||
|
import pomice
|
||||||
|
|
||||||
|
|
||||||
class MyBot(commands.Bot):
|
class MyBot(commands.Bot):
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
super().__init__(
|
super().__init__(
|
||||||
command_prefix="!",
|
command_prefix="!",
|
||||||
activity=discord.Activity(type=discord.ActivityType.listening, name="to music!")
|
activity=discord.Activity(
|
||||||
|
type=discord.ActivityType.listening,
|
||||||
|
name="to music!",
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
self.add_cog(Music(self))
|
self.add_cog(Music(self))
|
||||||
|
|
@ -31,9 +36,9 @@ class Music(commands.Cog):
|
||||||
await self.pomice.create_node(
|
await self.pomice.create_node(
|
||||||
bot=self.bot,
|
bot=self.bot,
|
||||||
host="127.0.0.1",
|
host="127.0.0.1",
|
||||||
port="3030",
|
port=3030,
|
||||||
password="youshallnotpass",
|
password="youshallnotpass",
|
||||||
identifier="MAIN"
|
identifier="MAIN",
|
||||||
)
|
)
|
||||||
print(f"Node is ready!")
|
print(f"Node is ready!")
|
||||||
|
|
||||||
|
|
@ -44,7 +49,7 @@ class Music(commands.Cog):
|
||||||
if not channel:
|
if not channel:
|
||||||
raise commands.CheckFailure(
|
raise commands.CheckFailure(
|
||||||
"You must be in a voice channel to use this command "
|
"You must be in a voice channel to use this command "
|
||||||
"without specifying the channel argument."
|
"without specifying the channel argument.",
|
||||||
)
|
)
|
||||||
|
|
||||||
# With the release of discord.py 1.7, you can now add a compatible
|
# With the release of discord.py 1.7, you can now add a compatible
|
||||||
|
|
@ -78,7 +83,9 @@ class Music(commands.Cog):
|
||||||
results = await player.get_tracks(search)
|
results = await player.get_tracks(search)
|
||||||
|
|
||||||
if not results:
|
if not results:
|
||||||
raise commands.CommandError("No results were found for that search term.")
|
raise commands.CommandError(
|
||||||
|
"No results were found for that search term.",
|
||||||
|
)
|
||||||
|
|
||||||
if isinstance(results, pomice.Playlist):
|
if isinstance(results, pomice.Playlist):
|
||||||
await player.play(track=results.tracks[0])
|
await player.play(track=results.tracks[0])
|
||||||
|
|
|
||||||
|
|
@ -3,24 +3,28 @@ Pomice
|
||||||
~~~~~~
|
~~~~~~
|
||||||
The modern Lavalink wrapper designed for discord.py.
|
The modern Lavalink wrapper designed for discord.py.
|
||||||
|
|
||||||
:copyright: 2023, cloudwithax
|
Copyright (c) 2024, cloudwithax
|
||||||
:license: GPL-3.0
|
|
||||||
|
Licensed under GPL-3.0
|
||||||
"""
|
"""
|
||||||
import discord
|
import discord
|
||||||
|
|
||||||
if not discord.version_info.major >= 2:
|
if not discord.version_info.major >= 2:
|
||||||
|
|
||||||
class DiscordPyOutdated(Exception):
|
class DiscordPyOutdated(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
raise DiscordPyOutdated(
|
raise DiscordPyOutdated(
|
||||||
"You must have discord.py (v2.0 or greater) to use this library. "
|
"You must have discord.py (v2.0 or greater) to use this library. "
|
||||||
"Uninstall your current version and install discord.py 2.0 "
|
"Uninstall your current version and install discord.py 2.0 "
|
||||||
"using 'pip install discord.py'"
|
"using 'pip install discord.py'",
|
||||||
)
|
)
|
||||||
|
|
||||||
__version__ = "2.1"
|
__version__ = "2.10.0"
|
||||||
__title__ = "pomice"
|
__title__ = "pomice"
|
||||||
__author__ = "cloudwithax"
|
__author__ = "cloudwithax"
|
||||||
|
__license__ = "GPL-3.0"
|
||||||
|
__copyright__ = "Copyright (c) 2023, cloudwithax"
|
||||||
|
|
||||||
from .enums import *
|
from .enums import *
|
||||||
from .events import *
|
from .events import *
|
||||||
|
|
@ -31,4 +35,3 @@ from .queue import *
|
||||||
from .player import *
|
from .player import *
|
||||||
from .pool import *
|
from .pool import *
|
||||||
from .routeplanner import *
|
from .routeplanner import *
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
"""Apple Music module for Pomice, made possible by cloudwithax 2023"""
|
"""Apple Music module for Pomice, made possible by cloudwithax 2023"""
|
||||||
|
from .client import *
|
||||||
from .exceptions import *
|
from .exceptions import *
|
||||||
from .objects import *
|
from .objects import *
|
||||||
from .client import Client
|
|
||||||
|
|
@ -1,54 +1,118 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
import base64
|
||||||
|
import logging
|
||||||
import re
|
import re
|
||||||
|
from datetime import datetime
|
||||||
|
from typing import AsyncGenerator
|
||||||
|
from typing import Dict
|
||||||
|
from typing import List
|
||||||
|
from typing import Optional
|
||||||
|
from typing import Union
|
||||||
|
|
||||||
import aiohttp
|
import aiohttp
|
||||||
import orjson as json
|
import orjson as json
|
||||||
import base64
|
|
||||||
|
|
||||||
from datetime import datetime
|
|
||||||
from .objects import *
|
|
||||||
from .exceptions import *
|
from .exceptions import *
|
||||||
|
from .objects import *
|
||||||
|
|
||||||
|
__all__ = ("Client",)
|
||||||
|
|
||||||
|
AM_URL_REGEX = re.compile(
|
||||||
|
r"https?://music\.apple\.com/(?P<country>[a-zA-Z]{2})/(?P<type>album|playlist|song|artist)/(?P<name>.+?)/(?P<id>[^/?]+?)(?:/)?(?:\?.*)?$",
|
||||||
|
)
|
||||||
|
AM_SINGLE_IN_ALBUM_REGEX = re.compile(
|
||||||
|
r"https?://music\.apple\.com/(?P<country>[a-zA-Z]{2})/(?P<type>album|playlist|song|artist)/(?P<name>.+)/(?P<id>[^/?]+)(\?i=)(?P<id2>[^&]+)(?:&.*)?$",
|
||||||
|
)
|
||||||
|
|
||||||
|
AM_SCRIPT_REGEX = re.compile(r'<script.*?src="(/assets/index-.*?)"')
|
||||||
|
|
||||||
AM_URL_REGEX = re.compile(r"https?://music.apple.com/(?P<country>[a-zA-Z]{2})/(?P<type>album|playlist|song|artist)/(?P<name>.+)/(?P<id>[^?]+)")
|
|
||||||
AM_SINGLE_IN_ALBUM_REGEX = re.compile(r"https?://music.apple.com/(?P<country>[a-zA-Z]{2})/(?P<type>album|playlist|song|artist)/(?P<name>.+)/(?P<id>.+)(\?i=)(?P<id2>.+)")
|
|
||||||
AM_REQ_URL = "https://api.music.apple.com/v1/catalog/{country}/{type}s/{id}"
|
AM_REQ_URL = "https://api.music.apple.com/v1/catalog/{country}/{type}s/{id}"
|
||||||
AM_BASE_URL = "https://api.music.apple.com"
|
AM_BASE_URL = "https://api.music.apple.com"
|
||||||
|
|
||||||
|
|
||||||
class Client:
|
class Client:
|
||||||
"""The base Apple Music client for Pomice.
|
"""The base Apple Music client for Pomice.
|
||||||
This will do all the heavy lifting of getting tracks from Apple Music
|
This will do all the heavy lifting of getting tracks from Apple Music
|
||||||
and translating it to a valid Lavalink track. No client auth is required here.
|
and translating it to a valid Lavalink track. No client auth is required here.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self, *, playlist_concurrency: int = 6) -> None:
|
||||||
self.token: str = None
|
self.expiry: datetime = datetime(1970, 1, 1)
|
||||||
self.expiry: datetime = None
|
self.token: str = ""
|
||||||
self.session: aiohttp.ClientSession = aiohttp.ClientSession()
|
self.headers: Dict[str, str] = {}
|
||||||
self.headers = None
|
self.session: aiohttp.ClientSession = None # type: ignore
|
||||||
|
self._log = logging.getLogger(__name__)
|
||||||
|
# Concurrency knob for parallel playlist page retrieval
|
||||||
|
self._playlist_concurrency = max(1, playlist_concurrency)
|
||||||
|
|
||||||
|
async def _set_session(self, session: aiohttp.ClientSession) -> None:
|
||||||
|
self.session = session
|
||||||
|
|
||||||
async def request_token(self):
|
async def request_token(self) -> None:
|
||||||
async with self.session.get("https://music.apple.com/assets/index.919fe17f.js") as resp:
|
# First lets get the raw response from the main page
|
||||||
if resp.status != 200:
|
|
||||||
raise AppleMusicRequestException(
|
|
||||||
f"Error while fetching results: {resp.status} {resp.reason}"
|
|
||||||
)
|
|
||||||
text = await resp.text()
|
|
||||||
result = re.search("\"(eyJ.+?)\"", text).group(1)
|
|
||||||
self.token = result
|
|
||||||
self.headers = {
|
|
||||||
'Authorization': f"Bearer {result}",
|
|
||||||
'Origin': 'https://apple.com',
|
|
||||||
}
|
|
||||||
token_split = self.token.split(".")[1]
|
|
||||||
token_json = base64.b64decode(token_split + '=' * (-len(token_split) % 4)).decode()
|
|
||||||
token_data = json.loads(token_json)
|
|
||||||
self.expiry = datetime.fromtimestamp(token_data["exp"])
|
|
||||||
|
|
||||||
|
resp = await self.session.get("https://music.apple.com")
|
||||||
|
|
||||||
async def search(self, query: str):
|
if resp.status != 200:
|
||||||
|
raise AppleMusicRequestException(
|
||||||
|
f"Error while fetching results: {resp.status} {resp.reason}",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Looking for script tag that fits criteria
|
||||||
|
|
||||||
|
text = await resp.text()
|
||||||
|
match = re.search(AM_SCRIPT_REGEX, text)
|
||||||
|
|
||||||
|
if not match:
|
||||||
|
raise AppleMusicRequestException(
|
||||||
|
"Could not find valid script URL in response.",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Found the script file, lets grab our token
|
||||||
|
|
||||||
|
result = match.group(1)
|
||||||
|
asset_url = result
|
||||||
|
|
||||||
|
resp = await self.session.get("https://music.apple.com" + asset_url)
|
||||||
|
|
||||||
|
if resp.status != 200:
|
||||||
|
raise AppleMusicRequestException(
|
||||||
|
f"Error while fetching results: {resp.status} {resp.reason}",
|
||||||
|
)
|
||||||
|
|
||||||
|
text = await resp.text()
|
||||||
|
match = re.search('"(eyJ.+?)"', text)
|
||||||
|
if not match:
|
||||||
|
raise AppleMusicRequestException(
|
||||||
|
"Could not find token in response.",
|
||||||
|
)
|
||||||
|
result = match.group(1)
|
||||||
|
|
||||||
|
self.token = result
|
||||||
|
self.headers = {
|
||||||
|
"Authorization": f"Bearer {result}",
|
||||||
|
"Origin": "https://apple.com",
|
||||||
|
}
|
||||||
|
token_split = self.token.split(".")[1]
|
||||||
|
token_json = base64.b64decode(
|
||||||
|
token_split + "=" * (-len(token_split) % 4),
|
||||||
|
).decode()
|
||||||
|
token_data = json.loads(token_json)
|
||||||
|
self.expiry = datetime.fromtimestamp(token_data["exp"])
|
||||||
|
if self._log:
|
||||||
|
self._log.debug(f"Fetched Apple Music bearer token successfully")
|
||||||
|
|
||||||
|
async def search(self, query: str) -> Union[Album, Playlist, Song, Artist]:
|
||||||
if not self.token or datetime.utcnow() > self.expiry:
|
if not self.token or datetime.utcnow() > self.expiry:
|
||||||
await self.request_token()
|
await self.request_token()
|
||||||
|
|
||||||
result = AM_URL_REGEX.match(query)
|
result = AM_URL_REGEX.match(query)
|
||||||
|
if not result:
|
||||||
|
raise InvalidAppleMusicURL(
|
||||||
|
"The Apple Music link provided is not valid.",
|
||||||
|
)
|
||||||
|
|
||||||
country = result.group("country")
|
country = result.group("country")
|
||||||
type = result.group("type")
|
type = result.group("type")
|
||||||
|
|
@ -64,17 +128,21 @@ class Client:
|
||||||
else:
|
else:
|
||||||
request_url = AM_REQ_URL.format(country=country, type=type, id=id)
|
request_url = AM_REQ_URL.format(country=country, type=type, id=id)
|
||||||
|
|
||||||
|
resp = await self.session.get(request_url, headers=self.headers)
|
||||||
|
|
||||||
async with self.session.get(request_url, headers=self.headers) as resp:
|
if resp.status != 200:
|
||||||
if resp.status != 200:
|
raise AppleMusicRequestException(
|
||||||
raise AppleMusicRequestException(
|
f"Error while fetching results: {resp.status} {resp.reason}",
|
||||||
f"Error while fetching results: {resp.status} {resp.reason}"
|
)
|
||||||
)
|
|
||||||
data: dict = await resp.json(loads=json.loads)
|
data: dict = await resp.json(loads=json.loads)
|
||||||
|
if self._log:
|
||||||
|
self._log.debug(
|
||||||
|
f"Made request to Apple Music API with status {resp.status} and response {data}",
|
||||||
|
)
|
||||||
|
|
||||||
data = data["data"][0]
|
data = data["data"][0]
|
||||||
|
|
||||||
|
|
||||||
if type == "song":
|
if type == "song":
|
||||||
return Song(data)
|
return Song(data)
|
||||||
|
|
||||||
|
|
@ -82,43 +150,149 @@ class Client:
|
||||||
return Album(data)
|
return Album(data)
|
||||||
|
|
||||||
elif type == "artist":
|
elif type == "artist":
|
||||||
async with self.session.get(f"{request_url}/view/top-songs", headers=self.headers) as resp:
|
resp = await self.session.get(
|
||||||
if resp.status != 200:
|
f"{request_url}/view/top-songs",
|
||||||
raise AppleMusicRequestException(
|
headers=self.headers,
|
||||||
f"Error while fetching results: {resp.status} {resp.reason}"
|
)
|
||||||
)
|
if resp.status != 200:
|
||||||
top_tracks: dict = await resp.json(loads=json.loads)
|
raise AppleMusicRequestException(
|
||||||
tracks: dict = top_tracks["data"]
|
f"Error while fetching results: {resp.status} {resp.reason}",
|
||||||
|
)
|
||||||
|
|
||||||
return Artist(data, tracks=tracks)
|
top_tracks: dict = await resp.json(loads=json.loads)
|
||||||
|
artist_tracks: dict = top_tracks["data"]
|
||||||
|
|
||||||
|
return Artist(data, tracks=artist_tracks)
|
||||||
else:
|
else:
|
||||||
|
|
||||||
track_data: dict = data["relationships"]["tracks"]
|
track_data: dict = data["relationships"]["tracks"]
|
||||||
|
album_tracks: List[Song] = [Song(track) for track in track_data["data"]]
|
||||||
|
|
||||||
tracks = [Song(track) for track in track_data.get("data")]
|
if not len(album_tracks):
|
||||||
|
raise AppleMusicRequestException(
|
||||||
|
"This playlist is empty and therefore cannot be queued.",
|
||||||
|
)
|
||||||
|
|
||||||
if not len(tracks):
|
# Apple Music uses cursor pagination with 'next'. We'll fetch subsequent pages
|
||||||
raise AppleMusicRequestException("This playlist is empty and therefore cannot be queued.")
|
# concurrently by first collecting cursors in rolling waves.
|
||||||
|
next_cursor = track_data.get("next")
|
||||||
|
semaphore = asyncio.Semaphore(self._playlist_concurrency)
|
||||||
|
|
||||||
if track_data.get("next"):
|
async def fetch_page(url: str) -> List[Song]:
|
||||||
next_page_url = AM_BASE_URL + track_data.get("next")
|
async with semaphore:
|
||||||
|
resp = await self.session.get(url, headers=self.headers)
|
||||||
while next_page_url is not None:
|
if resp.status != 200:
|
||||||
async with self.session.get(next_page_url, headers=self.headers) as resp:
|
if self._log:
|
||||||
if resp.status != 200:
|
self._log.warning(
|
||||||
raise AppleMusicRequestException(
|
f"Apple Music page fetch failed {resp.status} {resp.reason} for {url}",
|
||||||
f"Error while fetching results: {resp.status} {resp.reason}"
|
|
||||||
)
|
)
|
||||||
|
return []
|
||||||
|
pj: dict = await resp.json(loads=json.loads)
|
||||||
|
songs = [Song(track) for track in pj.get("data", [])]
|
||||||
|
# Return songs; we will look for pj.get('next') in streaming iterator variant
|
||||||
|
return songs, pj.get("next") # type: ignore
|
||||||
|
|
||||||
next_data: dict = await resp.json(loads=json.loads)
|
# We'll implement a wave-based approach similar to Spotify but need to follow cursors.
|
||||||
|
# Because we cannot know all cursors upfront, we'll iteratively fetch waves.
|
||||||
|
waves: List[List[Song]] = []
|
||||||
|
cursors: List[str] = []
|
||||||
|
if next_cursor:
|
||||||
|
cursors.append(next_cursor)
|
||||||
|
|
||||||
tracks += [Song(track) for track in next_data["data"]]
|
# Limit total waves to avoid infinite loops in malformed responses
|
||||||
if next_data.get("next"):
|
max_waves = 50
|
||||||
next_page_url = AM_BASE_URL + next_data.get("next")
|
wave_size = self._playlist_concurrency * 2
|
||||||
else:
|
wave_counter = 0
|
||||||
next_page_url = None
|
while cursors and wave_counter < max_waves:
|
||||||
|
current = cursors[:wave_size]
|
||||||
|
cursors = cursors[wave_size:]
|
||||||
|
tasks = [
|
||||||
|
fetch_page(AM_BASE_URL + cursor) for cursor in current # type: ignore[arg-type]
|
||||||
|
]
|
||||||
|
results = await asyncio.gather(*tasks, return_exceptions=True)
|
||||||
|
for res in results:
|
||||||
|
if isinstance(res, tuple): # (songs, next)
|
||||||
|
songs, nxt = res
|
||||||
|
if songs:
|
||||||
|
waves.append(songs)
|
||||||
|
if nxt:
|
||||||
|
cursors.append(nxt)
|
||||||
|
wave_counter += 1
|
||||||
|
|
||||||
|
for w in waves:
|
||||||
|
album_tracks.extend(w)
|
||||||
|
|
||||||
|
return Playlist(data, album_tracks)
|
||||||
|
|
||||||
return Playlist(data, tracks)
|
async def iter_playlist_tracks(
|
||||||
|
self,
|
||||||
|
*,
|
||||||
|
query: str,
|
||||||
|
batch_size: int = 100,
|
||||||
|
) -> AsyncGenerator[List[Song], None]:
|
||||||
|
"""Stream Apple Music playlist tracks in batches.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
query: str
|
||||||
|
Apple Music playlist URL.
|
||||||
|
batch_size: int
|
||||||
|
Logical grouping size for yielded batches.
|
||||||
|
"""
|
||||||
|
if not self.token or datetime.utcnow() > self.expiry:
|
||||||
|
await self.request_token()
|
||||||
|
|
||||||
|
result = AM_URL_REGEX.match(query)
|
||||||
|
if not result or result.group("type") != "playlist":
|
||||||
|
raise InvalidAppleMusicURL("Provided query is not a valid Apple Music playlist URL.")
|
||||||
|
|
||||||
|
country = result.group("country")
|
||||||
|
playlist_id = result.group("id")
|
||||||
|
request_url = AM_REQ_URL.format(country=country, type="playlist", id=playlist_id)
|
||||||
|
resp = await self.session.get(request_url, headers=self.headers)
|
||||||
|
if resp.status != 200:
|
||||||
|
raise AppleMusicRequestException(
|
||||||
|
f"Error while fetching results: {resp.status} {resp.reason}",
|
||||||
|
)
|
||||||
|
data: dict = await resp.json(loads=json.loads)
|
||||||
|
playlist_data = data["data"][0]
|
||||||
|
track_data: dict = playlist_data["relationships"]["tracks"]
|
||||||
|
|
||||||
|
first_page_tracks = [Song(track) for track in track_data["data"]]
|
||||||
|
for i in range(0, len(first_page_tracks), batch_size):
|
||||||
|
yield first_page_tracks[i : i + batch_size]
|
||||||
|
|
||||||
|
next_cursor = track_data.get("next")
|
||||||
|
semaphore = asyncio.Semaphore(self._playlist_concurrency)
|
||||||
|
|
||||||
|
async def fetch(cursor: str) -> tuple[List[Song], Optional[str]]:
|
||||||
|
url = AM_BASE_URL + cursor
|
||||||
|
async with semaphore:
|
||||||
|
r = await self.session.get(url, headers=self.headers)
|
||||||
|
if r.status != 200:
|
||||||
|
if self._log:
|
||||||
|
self._log.warning(
|
||||||
|
f"Skipping Apple Music page due to {r.status} {r.reason}",
|
||||||
|
)
|
||||||
|
return [], None
|
||||||
|
pj: dict = await r.json(loads=json.loads)
|
||||||
|
songs = [Song(track) for track in pj.get("data", [])]
|
||||||
|
return songs, pj.get("next")
|
||||||
|
|
||||||
|
# Rolling waves of fetches following cursor chain
|
||||||
|
max_waves = 50
|
||||||
|
wave_size = self._playlist_concurrency * 2
|
||||||
|
waves = 0
|
||||||
|
cursors: List[str] = []
|
||||||
|
if next_cursor:
|
||||||
|
cursors.append(next_cursor)
|
||||||
|
while cursors and waves < max_waves:
|
||||||
|
current = cursors[:wave_size]
|
||||||
|
cursors = cursors[wave_size:]
|
||||||
|
results = await asyncio.gather(*[fetch(c) for c in current])
|
||||||
|
for songs, nxt in results:
|
||||||
|
if songs:
|
||||||
|
for j in range(0, len(songs), batch_size):
|
||||||
|
yield songs[j : j + batch_size]
|
||||||
|
if nxt:
|
||||||
|
cursors.append(nxt)
|
||||||
|
waves += 1
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,16 @@
|
||||||
|
__all__ = (
|
||||||
|
"AppleMusicRequestException",
|
||||||
|
"InvalidAppleMusicURL",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class AppleMusicRequestException(Exception):
|
class AppleMusicRequestException(Exception):
|
||||||
"""An error occurred when making a request to the Apple Music API"""
|
"""An error occurred when making a request to the Apple Music API"""
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class InvalidAppleMusicURL(Exception):
|
class InvalidAppleMusicURL(Exception):
|
||||||
"""An invalid Apple Music URL was passed"""
|
"""An invalid Apple Music URL was passed"""
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,18 @@
|
||||||
"""Module for managing Apple Music objects"""
|
"""Module for managing Apple Music objects"""
|
||||||
|
|
||||||
from typing import List
|
from typing import List
|
||||||
|
|
||||||
|
__all__ = (
|
||||||
|
"Song",
|
||||||
|
"Playlist",
|
||||||
|
"Album",
|
||||||
|
"Artist",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class Song:
|
class Song:
|
||||||
"""The base class for an Apple Music song"""
|
"""The base class for an Apple Music song"""
|
||||||
def __init__(self, data: dict) -> None:
|
|
||||||
|
|
||||||
|
def __init__(self, data: dict) -> None:
|
||||||
self.name: str = data["attributes"]["name"]
|
self.name: str = data["attributes"]["name"]
|
||||||
self.url: str = data["attributes"]["url"]
|
self.url: str = data["attributes"]["url"]
|
||||||
self.isrc: str = data["attributes"]["isrc"]
|
self.isrc: str = data["attributes"]["isrc"]
|
||||||
|
|
@ -15,7 +21,7 @@ class Song:
|
||||||
self.artists: str = data["attributes"]["artistName"]
|
self.artists: str = data["attributes"]["artistName"]
|
||||||
self.image: str = data["attributes"]["artwork"]["url"].replace(
|
self.image: str = data["attributes"]["artwork"]["url"].replace(
|
||||||
"{w}x{h}",
|
"{w}x{h}",
|
||||||
f'{data["attributes"]["artwork"]["width"]}x{data["attributes"]["artwork"]["height"]}'
|
f'{data["attributes"]["artwork"]["width"]}x{data["attributes"]["artwork"]["height"]}',
|
||||||
)
|
)
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
|
|
@ -27,6 +33,7 @@ class Song:
|
||||||
|
|
||||||
class Playlist:
|
class Playlist:
|
||||||
"""The base class for an Apple Music playlist"""
|
"""The base class for an Apple Music playlist"""
|
||||||
|
|
||||||
def __init__(self, data: dict, tracks: List[Song]) -> None:
|
def __init__(self, data: dict, tracks: List[Song]) -> None:
|
||||||
self.name: str = data["attributes"]["name"]
|
self.name: str = data["attributes"]["name"]
|
||||||
self.owner: str = data["attributes"]["curatorName"]
|
self.owner: str = data["attributes"]["curatorName"]
|
||||||
|
|
@ -37,7 +44,6 @@ class Playlist:
|
||||||
# we'll use the first song's image as the image for the playlist
|
# we'll use the first song's image as the image for the playlist
|
||||||
# because apple dynamically generates playlist covers client-side
|
# because apple dynamically generates playlist covers client-side
|
||||||
self.image = self.tracks[0].image
|
self.image = self.tracks[0].image
|
||||||
print("worked")
|
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return (
|
return (
|
||||||
|
|
@ -48,6 +54,7 @@ class Playlist:
|
||||||
|
|
||||||
class Album:
|
class Album:
|
||||||
"""The base class for an Apple Music album"""
|
"""The base class for an Apple Music album"""
|
||||||
|
|
||||||
def __init__(self, data: dict) -> None:
|
def __init__(self, data: dict) -> None:
|
||||||
self.name: str = data["attributes"]["name"]
|
self.name: str = data["attributes"]["name"]
|
||||||
self.url: str = data["attributes"]["url"]
|
self.url: str = data["attributes"]["url"]
|
||||||
|
|
@ -57,7 +64,7 @@ class Album:
|
||||||
self.tracks: List[Song] = [Song(track) for track in data["relationships"]["tracks"]["data"]]
|
self.tracks: List[Song] = [Song(track) for track in data["relationships"]["tracks"]["data"]]
|
||||||
self.image: str = data["attributes"]["artwork"]["url"].replace(
|
self.image: str = data["attributes"]["artwork"]["url"].replace(
|
||||||
"{w}x{h}",
|
"{w}x{h}",
|
||||||
f'{data["attributes"]["artwork"]["width"]}x{data["attributes"]["artwork"]["height"]}'
|
f'{data["attributes"]["artwork"]["width"]}x{data["attributes"]["artwork"]["height"]}',
|
||||||
)
|
)
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
|
|
@ -67,9 +74,9 @@ class Album:
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class Artist:
|
class Artist:
|
||||||
"""The base class for an Apple Music artist"""
|
"""The base class for an Apple Music artist"""
|
||||||
|
|
||||||
def __init__(self, data: dict, tracks: dict) -> None:
|
def __init__(self, data: dict, tracks: dict) -> None:
|
||||||
self.name: str = f'Top tracks for {data["attributes"]["name"]}'
|
self.name: str = f'Top tracks for {data["attributes"]["name"]}'
|
||||||
self.url: str = data["attributes"]["url"]
|
self.url: str = data["attributes"]["url"]
|
||||||
|
|
@ -78,11 +85,8 @@ class Artist:
|
||||||
self.tracks: List[Song] = [Song(track) for track in tracks]
|
self.tracks: List[Song] = [Song(track) for track in tracks]
|
||||||
self.image: str = data["attributes"]["artwork"]["url"].replace(
|
self.image: str = data["attributes"]["artwork"]["url"].replace(
|
||||||
"{w}x{h}",
|
"{w}x{h}",
|
||||||
f'{data["attributes"]["artwork"]["width"]}x{data["attributes"]["artwork"]["height"]}'
|
f'{data["attributes"]["artwork"]["width"]}x{data["attributes"]["artwork"]["height"]}',
|
||||||
)
|
)
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return (
|
return f"<Pomice.applemusic.Artist name={self.name} id={self.id} " f"tracks={self.tracks}>"
|
||||||
f"<Pomice.applemusic.Artist name={self.name} id={self.id} "
|
|
||||||
f"tracks={self.tracks}>"
|
|
||||||
)
|
|
||||||
|
|
|
||||||
162
pomice/enums.py
162
pomice/enums.py
|
|
@ -1,6 +1,18 @@
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
|
from enum import IntEnum
|
||||||
|
|
||||||
|
__all__ = (
|
||||||
|
"SearchType",
|
||||||
|
"TrackType",
|
||||||
|
"PlaylistType",
|
||||||
|
"NodeAlgorithm",
|
||||||
|
"LoopMode",
|
||||||
|
"RouteStrategy",
|
||||||
|
"RouteIPType",
|
||||||
|
"URLRegex",
|
||||||
|
"LogLevel",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class SearchType(Enum):
|
class SearchType(Enum):
|
||||||
|
|
@ -18,9 +30,15 @@ class SearchType(Enum):
|
||||||
SearchType.scsearch searches using SoundCloud,
|
SearchType.scsearch searches using SoundCloud,
|
||||||
which is an alternative to YouTube or YouTube Music.
|
which is an alternative to YouTube or YouTube Music.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
ytsearch = "ytsearch"
|
ytsearch = "ytsearch"
|
||||||
ytmsearch = "ytmsearch"
|
ytmsearch = "ytmsearch"
|
||||||
scsearch = "scsearch"
|
scsearch = "scsearch"
|
||||||
|
other = "other"
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _missing_(cls, value: object) -> "SearchType": # type: ignore[override]
|
||||||
|
return cls.other
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
return self.value
|
return self.value
|
||||||
|
|
@ -39,18 +57,29 @@ class TrackType(Enum):
|
||||||
TrackType.APPLE_MUSIC defines that the track is from Apple Music.
|
TrackType.APPLE_MUSIC defines that the track is from Apple Music.
|
||||||
|
|
||||||
TrackType.HTTP defines that the track is from an HTTP source.
|
TrackType.HTTP defines that the track is from an HTTP source.
|
||||||
|
|
||||||
|
TrackType.LOCAL defines that the track is from a local source.
|
||||||
|
|
||||||
|
TrackType.OTHER defines that the track is from an unknown source (possible from 3rd-party plugins).
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# We don't have to define anything special for these, since these just serve as flags
|
# We don't have to define anything special for these, since these just serve as flags
|
||||||
YOUTUBE = "youtube_track"
|
YOUTUBE = "youtube"
|
||||||
SOUNDCLOUD = "soundcloud_track"
|
SOUNDCLOUD = "soundcloud"
|
||||||
SPOTIFY = "spotify_track"
|
SPOTIFY = "spotify"
|
||||||
APPLE_MUSIC = "apple_music_track"
|
APPLE_MUSIC = "apple_music"
|
||||||
HTTP = "http_source"
|
HTTP = "http"
|
||||||
|
LOCAL = "local"
|
||||||
|
OTHER = "other"
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _missing_(cls, value: object) -> "TrackType": # type: ignore[override]
|
||||||
|
return cls.OTHER
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
return self.value
|
return self.value
|
||||||
|
|
||||||
|
|
||||||
class PlaylistType(Enum):
|
class PlaylistType(Enum):
|
||||||
"""
|
"""
|
||||||
The enum for the different playlist types for Pomice.
|
The enum for the different playlist types for Pomice.
|
||||||
|
|
@ -62,19 +91,25 @@ class PlaylistType(Enum):
|
||||||
PlaylistType.SPOTIFY defines that the playlist is from Spotify
|
PlaylistType.SPOTIFY defines that the playlist is from Spotify
|
||||||
|
|
||||||
PlaylistType.APPLE_MUSIC defines that the playlist is from Apple Music.
|
PlaylistType.APPLE_MUSIC defines that the playlist is from Apple Music.
|
||||||
|
|
||||||
|
PlaylistType.OTHER defines that the playlist is from an unknown source (possible from 3rd-party plugins).
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# We don't have to define anything special for these, since these just serve as flags
|
# We don't have to define anything special for these, since these just serve as flags
|
||||||
YOUTUBE = "youtube_playlist"
|
YOUTUBE = "youtube"
|
||||||
SOUNDCLOUD = "soundcloud_playlist"
|
SOUNDCLOUD = "soundcloud"
|
||||||
SPOTIFY = "spotify_playlist"
|
SPOTIFY = "spotify"
|
||||||
APPLE_MUSIC = "apple_music_list"
|
APPLE_MUSIC = "apple_music"
|
||||||
|
OTHER = "other"
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _missing_(cls, value: object) -> "PlaylistType": # type: ignore[override]
|
||||||
|
return cls.OTHER
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
return self.value
|
return self.value
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class NodeAlgorithm(Enum):
|
class NodeAlgorithm(Enum):
|
||||||
"""
|
"""
|
||||||
The enum for the different node algorithms in Pomice.
|
The enum for the different node algorithms in Pomice.
|
||||||
|
|
@ -98,6 +133,7 @@ class NodeAlgorithm(Enum):
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
return self.value
|
return self.value
|
||||||
|
|
||||||
|
|
||||||
class LoopMode(Enum):
|
class LoopMode(Enum):
|
||||||
"""
|
"""
|
||||||
The enum for the different loop modes.
|
The enum for the different loop modes.
|
||||||
|
|
@ -109,32 +145,11 @@ class LoopMode(Enum):
|
||||||
LoopMode.QUEUE sets the queue loop to the whole queue.
|
LoopMode.QUEUE sets the queue loop to the whole queue.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# We don't have to define anything special for these, since these just serve as flags
|
# We don't have to define anything special for these, since these just serve as flags
|
||||||
TRACK = "track"
|
TRACK = "track"
|
||||||
QUEUE = "queue"
|
QUEUE = "queue"
|
||||||
|
|
||||||
|
|
||||||
def __str__(self) -> str:
|
|
||||||
return self.value
|
|
||||||
|
|
||||||
class PlatformRecommendation(Enum):
|
|
||||||
|
|
||||||
"""
|
|
||||||
The enum for choosing what platform you want for recommendations.
|
|
||||||
This feature is exclusively for the recommendations function.
|
|
||||||
If you are not using this feature, this class is not necessary.
|
|
||||||
|
|
||||||
PlatformRecommendation.SPOTIFY sets the recommendations to come from Spotify
|
|
||||||
|
|
||||||
PlatformRecommendation.YOUTUBE sets the recommendations to come from YouTube
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
# We don't have to define anything special for these, since these just serve as flags
|
|
||||||
SPOTIFY = "spotify"
|
|
||||||
YOUTUBE = "youtube"
|
|
||||||
|
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
return self.value
|
return self.value
|
||||||
|
|
||||||
|
|
@ -181,9 +196,9 @@ class RouteIPType(Enum):
|
||||||
IPV6 = "Inet6Address"
|
IPV6 = "Inet6Address"
|
||||||
|
|
||||||
|
|
||||||
class URLRegex():
|
class URLRegex:
|
||||||
"""
|
"""
|
||||||
The enums for all the URL Regexes in use by Pomice.
|
The enum for all the URL Regexes in use by Pomice.
|
||||||
|
|
||||||
URLRegex.SPOTIFY_URL returns the Spotify URL Regex.
|
URLRegex.SPOTIFY_URL returns the Spotify URL Regex.
|
||||||
|
|
||||||
|
|
@ -202,59 +217,92 @@ class URLRegex():
|
||||||
URLRegex.BASE_URL returns the standard URL Regex.
|
URLRegex.BASE_URL returns the standard URL Regex.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
# Spotify share links can include query parameters like ?si=XXXX, a trailing slash,
|
||||||
|
# or an intl locale segment (e.g. /intl-en/). Broaden the regex so we still capture
|
||||||
|
# the type and id while ignoring extra parameters. This prevents the URL from being
|
||||||
|
# treated as a generic Lavalink identifier and ensures internal Spotify handling runs.
|
||||||
SPOTIFY_URL = re.compile(
|
SPOTIFY_URL = re.compile(
|
||||||
r"https?://open.spotify.com/(?P<type>album|playlist|track|artist)/(?P<id>[a-zA-Z0-9]+)"
|
r"https?://open\.spotify\.com/(?:intl-[a-zA-Z-]+/)?(?P<type>album|playlist|track|artist)/(?P<id>[a-zA-Z0-9]+)(?:/)?(?:\?.*)?$",
|
||||||
)
|
)
|
||||||
|
|
||||||
DISCORD_MP3_URL = re.compile(
|
DISCORD_MP3_URL = re.compile(
|
||||||
r"https?://cdn.discordapp.com/attachments/(?P<channel_id>[0-9]+)/"
|
r"https?://cdn.discordapp.com/attachments/(?P<channel_id>[0-9]+)/"
|
||||||
r"(?P<message_id>[0-9]+)/(?P<file>[a-zA-Z0-9_.]+)+"
|
r"(?P<message_id>[0-9]+)/(?P<file>[a-zA-Z0-9_.]+)+",
|
||||||
)
|
)
|
||||||
|
|
||||||
YOUTUBE_URL = re.compile(
|
YOUTUBE_URL = re.compile(
|
||||||
r"^((?:https?:)?\/\/)?((?:www|m)\.)?((?:youtube\.com|youtu.be))"
|
r"^((?:https?:)?\/\/)?((?:www|m)\.)?((?:youtube\.com|youtu.be))"
|
||||||
r"(\/(?:[\w\-]+\?v=|embed\/|v\/)?)([\w\-]+)(\S+)?$"
|
r"(\/(?:[\w\-]+\?v=|embed\/|v\/)?)([\w\-]+)(\S+)?$",
|
||||||
)
|
)
|
||||||
|
|
||||||
YOUTUBE_PLAYLIST_URL = re.compile(
|
YOUTUBE_PLAYLIST_URL = re.compile(
|
||||||
r"^((?:https?:)?\/\/)?((?:www|m)\.)?((?:youtube\.com|youtu.be))/playlist\?list=.*"
|
r"^((?:https?:)?\/\/)?((?:www|m)\.)?((?:youtube\.com|youtu.be))/playlist\?list=.*",
|
||||||
)
|
|
||||||
|
|
||||||
YOUTUBE_VID_IN_PLAYLIST = re.compile(
|
|
||||||
r"(?P<video>^.*?v.*?)(?P<list>&list.*)"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
YOUTUBE_TIMESTAMP = re.compile(
|
YOUTUBE_TIMESTAMP = re.compile(
|
||||||
r"(?P<video>^.*?)(\?t|&start)=(?P<time>\d+)?.*"
|
r"(?P<video>^.*?)(\?t|&start)=(?P<time>\d+)?.*",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Apple Music links sometimes append additional query parameters (e.g. &l=en, &uo=4).
|
||||||
|
# Allow arbitrary query parameters so valid links are captured and parsed.
|
||||||
AM_URL = re.compile(
|
AM_URL = re.compile(
|
||||||
r"https?://music.apple.com/(?P<country>[a-zA-Z]{2})/"
|
r"https?://music\.apple\.com/(?P<country>[a-zA-Z]{2})/"
|
||||||
r"(?P<type>album|playlist|song|artist)/(?P<name>.+)/(?P<id>[^?]+)"
|
r"(?P<type>album|playlist|song|artist)/(?P<name>.+?)/(?P<id>[^/?]+?)(?:/)?(?:\?.*)?$",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Single-in-album links may also carry extra query params beyond the ?i=<trackid> token.
|
||||||
AM_SINGLE_IN_ALBUM_REGEX = re.compile(
|
AM_SINGLE_IN_ALBUM_REGEX = re.compile(
|
||||||
r"https?://music.apple.com/(?P<country>[a-zA-Z]{2})/(?P<type>album|playlist|song|artist)/"
|
r"https?://music\.apple\.com/(?P<country>[a-zA-Z]{2})/(?P<type>album|playlist|song|artist)/"
|
||||||
r"(?P<name>.+)/(?P<id>.+)(\?i=)(?P<id2>.+)"
|
r"(?P<name>.+)/(?P<id>[^/?]+)(\?i=)(?P<id2>[^&]+)(?:&.*)?$",
|
||||||
)
|
)
|
||||||
|
|
||||||
SOUNDCLOUD_URL = re.compile(
|
SOUNDCLOUD_URL = re.compile(
|
||||||
r"((?:https?:)?\/\/)?((?:www|m)\.)?soundcloud.com\/.*/.*"
|
r"((?:https?:)?\/\/)?((?:www|m)\.)?soundcloud.com\/.*/.*",
|
||||||
)
|
)
|
||||||
|
|
||||||
SOUNDCLOUD_PLAYLIST_URL = re.compile(
|
SOUNDCLOUD_PLAYLIST_URL = re.compile(
|
||||||
r"^(https?:\/\/)?(www.)?(m\.)?soundcloud\.com\/.*/sets/.*"
|
r"^(https?:\/\/)?(www.)?(m\.)?soundcloud\.com\/.*/sets/.*",
|
||||||
)
|
)
|
||||||
|
|
||||||
SOUNDCLOUD_TRACK_IN_SET_URL = re.compile(
|
SOUNDCLOUD_TRACK_IN_SET_URL = re.compile(
|
||||||
r"^(https?:\/\/)?(www.)?(m\.)?soundcloud\.com/[a-zA-Z0-9-._]+/[a-zA-Z0-9-._]+(\?in)"
|
r"^(https?:\/\/)?(www.)?(m\.)?soundcloud\.com/[a-zA-Z0-9-._]+/[a-zA-Z0-9-._]+(\?in)",
|
||||||
)
|
)
|
||||||
|
|
||||||
LAVALINK_SEARCH = re.compile(
|
LAVALINK_SEARCH = re.compile(r"(?P<type>ytm?|sc)search:")
|
||||||
r"(?P<type>ytm?|sc)search:"
|
|
||||||
)
|
|
||||||
|
|
||||||
BASE_URL = re.compile(
|
BASE_URL = re.compile(r"https?://(?:www\.)?.+")
|
||||||
r"https?://(?:www\.)?.+"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
|
class LogLevel(IntEnum):
|
||||||
|
"""
|
||||||
|
The enum for specifying the logging level within Pomice.
|
||||||
|
This class serves as shorthand for logging.<level>
|
||||||
|
This enum is exclusively for the logging feature in Pomice.
|
||||||
|
If you are not using this feature, this class is not necessary.
|
||||||
|
|
||||||
|
|
||||||
|
LogLevel.DEBUG sets the logging level to "debug".
|
||||||
|
|
||||||
|
LogLevel.INFO sets the logging level to "info".
|
||||||
|
|
||||||
|
LogLevel.WARN sets the logging level to "warn".
|
||||||
|
|
||||||
|
LogLevel.ERROR sets the logging level to "error".
|
||||||
|
|
||||||
|
LogLevel.CRITICAL sets the logging level to "CRITICAL".
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
DEBUG = 10
|
||||||
|
INFO = 20
|
||||||
|
WARN = 30
|
||||||
|
ERROR = 40
|
||||||
|
CRITICAL = 50
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_str(cls, level_str):
|
||||||
|
try:
|
||||||
|
return cls[level_str.upper()]
|
||||||
|
except KeyError:
|
||||||
|
raise ValueError(f"No such log level: {level_str}")
|
||||||
|
|
|
||||||
135
pomice/events.py
135
pomice/events.py
|
|
@ -1,58 +1,85 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from abc import ABC
|
||||||
|
from typing import Any
|
||||||
|
from typing import Optional
|
||||||
|
from typing import Tuple
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from discord import Client
|
from discord import Client
|
||||||
|
from discord import Guild
|
||||||
from discord.ext import commands
|
from discord.ext import commands
|
||||||
|
|
||||||
|
from .objects import Track
|
||||||
from .pool import NodePool
|
from .pool import NodePool
|
||||||
|
|
||||||
|
|
||||||
from typing import TYPE_CHECKING, Union
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from .player import Player
|
from .player import Player
|
||||||
|
|
||||||
|
__all__ = (
|
||||||
|
"PomiceEvent",
|
||||||
|
"TrackStartEvent",
|
||||||
|
"TrackEndEvent",
|
||||||
|
"TrackStuckEvent",
|
||||||
|
"TrackExceptionEvent",
|
||||||
|
"WebSocketClosedPayload",
|
||||||
|
"WebSocketClosedEvent",
|
||||||
|
"WebSocketOpenEvent",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class PomiceEvent:
|
class PomiceEvent(ABC):
|
||||||
"""The base class for all events dispatched by a node.
|
"""The base class for all events dispatched by a node.
|
||||||
Every event must be formatted within your bot's code as a listener.
|
Every event must be formatted within your bot's code as a listener.
|
||||||
i.e: If you want to listen for when a track starts, the event would be:
|
i.e: If you want to listen for when a track starts, the event would be:
|
||||||
```py
|
```py
|
||||||
@bot.listen
|
@bot.listen
|
||||||
async def on_pomice_track_start(self, event):
|
async def on_pomice_track_start(self, event):
|
||||||
```
|
```
|
||||||
"""
|
"""
|
||||||
name = "event"
|
|
||||||
handler_args = ()
|
|
||||||
|
|
||||||
def dispatch(self, bot: Union[Client, commands.Bot]):
|
name = "event"
|
||||||
|
handler_args: Tuple
|
||||||
|
|
||||||
|
def dispatch(self, bot: Client) -> None:
|
||||||
bot.dispatch(f"pomice_{self.name}", *self.handler_args)
|
bot.dispatch(f"pomice_{self.name}", *self.handler_args)
|
||||||
|
|
||||||
|
|
||||||
class TrackStartEvent(PomiceEvent):
|
class TrackStartEvent(PomiceEvent):
|
||||||
"""Fired when a track has successfully started.
|
"""Fired when a track has successfully started.
|
||||||
Returns the player associated with the event and the pomice.Track object.
|
Returns the player associated with the event and the pomice.Track object.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
name = "track_start"
|
name = "track_start"
|
||||||
|
|
||||||
|
__slots__ = (
|
||||||
|
"player",
|
||||||
|
"track",
|
||||||
|
)
|
||||||
|
|
||||||
def __init__(self, data: dict, player: Player):
|
def __init__(self, data: dict, player: Player):
|
||||||
self.player = player
|
self.player: Player = player
|
||||||
self.track = self.player._current
|
self.track: Optional[Track] = self.player._current
|
||||||
|
|
||||||
# on_pomice_track_start(player, track)
|
# on_pomice_track_start(player, track)
|
||||||
self.handler_args = self.player, self.track
|
self.handler_args = self.player, self.track
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return f"<Pomice.TrackStartEvent player={self.player} track_id={self.track.track_id}>"
|
return f"<Pomice.TrackStartEvent player={self.player!r} track={self.track!r}>"
|
||||||
|
|
||||||
|
|
||||||
class TrackEndEvent(PomiceEvent):
|
class TrackEndEvent(PomiceEvent):
|
||||||
"""Fired when a track has successfully ended.
|
"""Fired when a track has successfully ended.
|
||||||
Returns the player associated with the event along with the pomice.Track object and reason.
|
Returns the player associated with the event along with the pomice.Track object and reason.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
name = "track_end"
|
name = "track_end"
|
||||||
|
|
||||||
|
__slots__ = ("player", "track", "reason")
|
||||||
|
|
||||||
def __init__(self, data: dict, player: Player):
|
def __init__(self, data: dict, player: Player):
|
||||||
self.player = player
|
self.player: Player = player
|
||||||
self.track = self.player._ending_track
|
self.track: Optional[Track] = self.player._ending_track
|
||||||
self.reason: str = data["reason"]
|
self.reason: str = data["reason"]
|
||||||
|
|
||||||
# on_pomice_track_end(player, track, reason)
|
# on_pomice_track_end(player, track, reason)
|
||||||
|
|
@ -60,46 +87,53 @@ class TrackEndEvent(PomiceEvent):
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return (
|
return (
|
||||||
f"<Pomice.TrackEndEvent player={self.player} track_id={self.track.track_id} "
|
f"<Pomice.TrackEndEvent player={self.player!r} track_id={self.track!r} "
|
||||||
f"reason={self.reason}>"
|
f"reason={self.reason!r}>"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class TrackStuckEvent(PomiceEvent):
|
class TrackStuckEvent(PomiceEvent):
|
||||||
"""Fired when a track is stuck and cannot be played. Returns the player
|
"""Fired when a track is stuck and cannot be played. Returns the player
|
||||||
associated with the event along with the pomice.Track object
|
associated with the event along with the pomice.Track object
|
||||||
to be further parsed by the end user.
|
to be further parsed by the end user.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
name = "track_stuck"
|
name = "track_stuck"
|
||||||
|
|
||||||
|
__slots__ = ("player", "track", "threshold")
|
||||||
|
|
||||||
def __init__(self, data: dict, player: Player):
|
def __init__(self, data: dict, player: Player):
|
||||||
self.player = player
|
self.player: Player = player
|
||||||
self.track = self.player._ending_track
|
self.track: Optional[Track] = self.player._ending_track
|
||||||
self.threshold: float = data["thresholdMs"]
|
self.threshold: float = data["thresholdMs"]
|
||||||
|
|
||||||
# on_pomice_track_stuck(player, track, threshold)
|
# on_pomice_track_stuck(player, track, threshold)
|
||||||
self.handler_args = self.player, self.track, self.threshold
|
self.handler_args = self.player, self.track, self.threshold
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return f"<Pomice.TrackStuckEvent player={self.player!r} track={self.track!r} " \
|
return (
|
||||||
f"threshold={self.threshold!r}>"
|
f"<Pomice.TrackStuckEvent player={self.player!r} track={self.track!r} "
|
||||||
|
f"threshold={self.threshold!r}>"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class TrackExceptionEvent(PomiceEvent):
|
class TrackExceptionEvent(PomiceEvent):
|
||||||
"""Fired when a track error has occured.
|
"""Fired when a track error has occured.
|
||||||
Returns the player associated with the event along with the error code and exception.
|
Returns the player associated with the event along with the error code and exception.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
name = "track_exception"
|
name = "track_exception"
|
||||||
|
|
||||||
|
__slots__ = ("player", "track", "exception")
|
||||||
|
|
||||||
def __init__(self, data: dict, player: Player):
|
def __init__(self, data: dict, player: Player):
|
||||||
self.player = player
|
self.player: Player = player
|
||||||
self.track = self.player._ending_track
|
self.track: Optional[Track] = self.player._ending_track
|
||||||
if data.get('error'):
|
# Error is for Lavalink <= 3.3
|
||||||
# User is running Lavalink <= 3.3
|
self.exception: str = data.get(
|
||||||
self.exception: str = data["error"]
|
"error",
|
||||||
else:
|
"",
|
||||||
# User is running Lavalink >=3.4
|
) or data.get("exception", "")
|
||||||
self.exception: str = data["exception"]
|
|
||||||
|
|
||||||
# on_pomice_track_exception(player, track, error)
|
# on_pomice_track_exception(player, track, error)
|
||||||
self.handler_args = self.player, self.track, self.exception
|
self.handler_args = self.player, self.track, self.exception
|
||||||
|
|
@ -109,28 +143,35 @@ class TrackExceptionEvent(PomiceEvent):
|
||||||
|
|
||||||
|
|
||||||
class WebSocketClosedPayload:
|
class WebSocketClosedPayload:
|
||||||
|
__slots__ = ("guild", "code", "reason", "by_remote")
|
||||||
|
|
||||||
def __init__(self, data: dict):
|
def __init__(self, data: dict):
|
||||||
self.guild = NodePool.get_node().bot.get_guild(int(data["guildId"]))
|
self.guild: Optional[Guild] = NodePool.get_node().bot.get_guild(int(data["guildId"]))
|
||||||
self.code: int = data["code"]
|
self.code: int = data["code"]
|
||||||
self.reason: str = data["code"]
|
self.reason: str = data["code"]
|
||||||
self.by_remote: bool = data["byRemote"]
|
self.by_remote: bool = data["byRemote"]
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return f"<Pomice.WebSocketClosedPayload guild={self.guild!r} code={self.code!r} " \
|
return (
|
||||||
f"reason={self.reason!r} by_remote={self.by_remote!r}>"
|
f"<Pomice.WebSocketClosedPayload guild={self.guild!r} code={self.code!r} "
|
||||||
|
f"reason={self.reason!r} by_remote={self.by_remote!r}>"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class WebSocketClosedEvent(PomiceEvent):
|
class WebSocketClosedEvent(PomiceEvent):
|
||||||
"""Fired when a websocket connection to a node has been closed.
|
"""Fired when a websocket connection to a node has been closed.
|
||||||
Returns the reason and the error code.
|
Returns the reason and the error code.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
name = "websocket_closed"
|
name = "websocket_closed"
|
||||||
|
|
||||||
def __init__(self, data: dict, _):
|
__slots__ = ("payload",)
|
||||||
self.payload = WebSocketClosedPayload(data)
|
|
||||||
|
def __init__(self, data: dict, _: Any) -> None:
|
||||||
|
self.payload: WebSocketClosedPayload = WebSocketClosedPayload(data)
|
||||||
|
|
||||||
# on_pomice_websocket_closed(payload)
|
# on_pomice_websocket_closed(payload)
|
||||||
self.handler_args = self.payload,
|
self.handler_args = (self.payload,)
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return f"<Pomice.WebsocketClosedEvent payload={self.payload!r}>"
|
return f"<Pomice.WebsocketClosedEvent payload={self.payload!r}>"
|
||||||
|
|
@ -138,11 +179,14 @@ class WebSocketClosedEvent(PomiceEvent):
|
||||||
|
|
||||||
class WebSocketOpenEvent(PomiceEvent):
|
class WebSocketOpenEvent(PomiceEvent):
|
||||||
"""Fired when a websocket connection to a node has been initiated.
|
"""Fired when a websocket connection to a node has been initiated.
|
||||||
Returns the target and the session SSRC.
|
Returns the target and the session SSRC.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
name = "websocket_open"
|
name = "websocket_open"
|
||||||
|
|
||||||
def __init__(self, data: dict, _):
|
__slots__ = ("target", "ssrc")
|
||||||
|
|
||||||
|
def __init__(self, data: dict, _: Any) -> None:
|
||||||
self.target: str = data["target"]
|
self.target: str = data["target"]
|
||||||
self.ssrc: int = data["ssrc"]
|
self.ssrc: int = data["ssrc"]
|
||||||
|
|
||||||
|
|
@ -151,4 +195,3 @@ class WebSocketOpenEvent(PomiceEvent):
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return f"<Pomice.WebsocketOpenEvent target={self.target!r} ssrc={self.ssrc!r}>"
|
return f"<Pomice.WebsocketOpenEvent target={self.target!r} ssrc={self.ssrc!r}>"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,26 @@
|
||||||
|
__all__ = (
|
||||||
|
"PomiceException",
|
||||||
|
"NodeException",
|
||||||
|
"NodeCreationError",
|
||||||
|
"NodeConnectionFailure",
|
||||||
|
"NodeConnectionClosed",
|
||||||
|
"NodeRestException",
|
||||||
|
"NodeNotAvailable",
|
||||||
|
"NoNodesAvailable",
|
||||||
|
"TrackInvalidPosition",
|
||||||
|
"TrackLoadError",
|
||||||
|
"FilterInvalidArgument",
|
||||||
|
"FilterTagInvalid",
|
||||||
|
"FilterTagAlreadyInUse",
|
||||||
|
"InvalidSpotifyClientAuthorization",
|
||||||
|
"AppleMusicNotEnabled",
|
||||||
|
"QueueException",
|
||||||
|
"QueueFull",
|
||||||
|
"QueueEmpty",
|
||||||
|
"LavalinkVersionIncompatible",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class PomiceException(Exception):
|
class PomiceException(Exception):
|
||||||
"""Base of all Pomice exceptions."""
|
"""Base of all Pomice exceptions."""
|
||||||
|
|
||||||
|
|
@ -16,83 +39,89 @@ class NodeConnectionFailure(NodeException):
|
||||||
|
|
||||||
class NodeConnectionClosed(NodeException):
|
class NodeConnectionClosed(NodeException):
|
||||||
"""The node's connection is closed."""
|
"""The node's connection is closed."""
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class NodeRestException(NodeException):
|
class NodeRestException(NodeException):
|
||||||
"""A request made using the node's REST uri failed"""
|
"""A request made using the node's REST uri failed"""
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class NodeNotAvailable(PomiceException):
|
class NodeNotAvailable(PomiceException):
|
||||||
"""The node is currently unavailable."""
|
"""The node is currently unavailable."""
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class NoNodesAvailable(PomiceException):
|
class NoNodesAvailable(PomiceException):
|
||||||
"""There are no nodes currently available."""
|
"""There are no nodes currently available."""
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class TrackInvalidPosition(PomiceException):
|
class TrackInvalidPosition(PomiceException):
|
||||||
"""An invalid position was chosen for a track."""
|
"""An invalid position was chosen for a track."""
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class TrackLoadError(PomiceException):
|
class TrackLoadError(PomiceException):
|
||||||
"""There was an error while loading a track."""
|
"""There was an error while loading a track."""
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class FilterInvalidArgument(PomiceException):
|
class FilterInvalidArgument(PomiceException):
|
||||||
"""An invalid argument was passed to a filter."""
|
"""An invalid argument was passed to a filter."""
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class FilterTagInvalid(PomiceException):
|
class FilterTagInvalid(PomiceException):
|
||||||
"""An invalid tag was passed or Pomice was unable to find a filter tag"""
|
"""An invalid tag was passed or Pomice was unable to find a filter tag"""
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class FilterTagAlreadyInUse(PomiceException):
|
class FilterTagAlreadyInUse(PomiceException):
|
||||||
"""A filter with a tag is already in use by another filter"""
|
"""A filter with a tag is already in use by another filter"""
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class SpotifyAlbumLoadFailed(PomiceException):
|
|
||||||
"""The pomice Spotify client was unable to load an album."""
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class SpotifyTrackLoadFailed(PomiceException):
|
|
||||||
"""The pomice Spotify client was unable to load a track."""
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class SpotifyPlaylistLoadFailed(PomiceException):
|
|
||||||
"""The pomice Spotify client was unable to load a playlist."""
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class InvalidSpotifyClientAuthorization(PomiceException):
|
class InvalidSpotifyClientAuthorization(PomiceException):
|
||||||
"""No Spotify client authorization was provided for track searching."""
|
"""No Spotify client authorization was provided for track searching."""
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class AppleMusicNotEnabled(PomiceException):
|
class AppleMusicNotEnabled(PomiceException):
|
||||||
"""An Apple Music Link was passed in when Apple Music functionality was not enabled."""
|
"""An Apple Music Link was passed in when Apple Music functionality was not enabled."""
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class QueueException(Exception):
|
class QueueException(Exception):
|
||||||
"""Base Pomice queue exception."""
|
"""Base Pomice queue exception."""
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class QueueFull(QueueException):
|
class QueueFull(QueueException):
|
||||||
"""Exception raised when attempting to add to a full Queue."""
|
"""Exception raised when attempting to add to a full Queue."""
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class QueueEmpty(QueueException):
|
class QueueEmpty(QueueException):
|
||||||
"""Exception raised when attempting to retrieve from an empty Queue."""
|
"""Exception raised when attempting to retrieve from an empty Queue."""
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class LavalinkVersionIncompatible(PomiceException):
|
class LavalinkVersionIncompatible(PomiceException):
|
||||||
"""Lavalink version is incompatible. Must be using Lavalink > 3.7.0 to avoid this error."""
|
"""Lavalink version is incompatible. Must be using Lavalink > 3.7.0 to avoid this error."""
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,25 @@
|
||||||
import collections
|
import collections
|
||||||
|
from typing import Any
|
||||||
|
from typing import Dict
|
||||||
|
from typing import List
|
||||||
|
from typing import Optional
|
||||||
|
from typing import Tuple
|
||||||
|
|
||||||
from .exceptions import FilterInvalidArgument
|
from .exceptions import FilterInvalidArgument
|
||||||
|
|
||||||
|
__all__ = (
|
||||||
|
"Filter",
|
||||||
|
"Equalizer",
|
||||||
|
"Timescale",
|
||||||
|
"Karaoke",
|
||||||
|
"Tremolo",
|
||||||
|
"Vibrato",
|
||||||
|
"Rotation",
|
||||||
|
"Distortion",
|
||||||
|
"ChannelMix",
|
||||||
|
"LowPass",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class Filter:
|
class Filter:
|
||||||
"""
|
"""
|
||||||
|
|
@ -12,9 +31,12 @@ class Filter:
|
||||||
You must specify a tag for each filter you put on.
|
You must specify a tag for each filter you put on.
|
||||||
This is necessary for the removal of filters.
|
This is necessary for the removal of filters.
|
||||||
"""
|
"""
|
||||||
def __init__(self):
|
|
||||||
self.payload = None
|
__slots__ = ("payload", "tag", "preload")
|
||||||
self.tag: str = None
|
|
||||||
|
def __init__(self, *, tag: str):
|
||||||
|
self.payload: Optional[Dict] = None
|
||||||
|
self.tag: str = tag
|
||||||
self.preload: bool = False
|
self.preload: bool = False
|
||||||
|
|
||||||
def set_preload(self) -> bool:
|
def set_preload(self) -> bool:
|
||||||
|
|
@ -31,99 +53,151 @@ class Equalizer(Filter):
|
||||||
The format for the levels is: List[Tuple[int, float]]
|
The format for the levels is: List[Tuple[int, float]]
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
__slots__ = (
|
||||||
|
"eq",
|
||||||
|
"raw",
|
||||||
|
)
|
||||||
|
|
||||||
def __init__(self, *, tag: str, levels: list):
|
def __init__(self, *, tag: str, levels: list):
|
||||||
super().__init__()
|
super().__init__(tag=tag)
|
||||||
|
|
||||||
self.eq = self._factory(levels)
|
self.eq = self._factory(levels)
|
||||||
self.raw = levels
|
self.raw = levels
|
||||||
|
|
||||||
self.payload = {"equalizer": self.eq}
|
self.payload = {"equalizer": self.eq}
|
||||||
self.tag = tag
|
|
||||||
|
|
||||||
def _factory(self, levels: list):
|
def _factory(self, levels: List[Tuple[Any, Any]]) -> List[Dict]:
|
||||||
_dict = collections.defaultdict(int)
|
_dict: Dict = collections.defaultdict(int)
|
||||||
|
|
||||||
_dict.update(levels)
|
_dict.update(levels)
|
||||||
_dict = [{"band": i, "gain": _dict[i]} for i in range(15)]
|
data = [{"band": i, "gain": _dict[i]} for i in range(15)]
|
||||||
|
|
||||||
return _dict
|
return data
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return f"<Pomice.EqualizerFilter tag={self.tag} eq={self.eq} raw={self.raw}>"
|
return f"<Pomice.EqualizerFilter tag={self.tag} eq={self.eq} raw={self.raw}>"
|
||||||
|
|
||||||
|
def __eq__(self, __value: object) -> bool:
|
||||||
|
if not isinstance(__value, Equalizer):
|
||||||
|
return False
|
||||||
|
|
||||||
|
return self.raw == __value.raw
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def flat(cls):
|
def flat(cls) -> "Equalizer":
|
||||||
"""Equalizer preset which represents a flat EQ board,
|
"""Equalizer preset which represents a flat EQ board,
|
||||||
with all levels set to their default values.
|
with all levels set to their default values.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
levels = [
|
levels = [
|
||||||
(0, 0.0), (1, 0.0), (2, 0.0), (3, 0.0), (4, 0.0),
|
(0, 0.0),
|
||||||
(5, 0.0), (6, 0.0), (7, 0.0), (8, 0.0), (9, 0.0),
|
(1, 0.0),
|
||||||
(10, 0.0), (11, 0.0), (12, 0.0), (13, 0.0), (14, 0.0)
|
(2, 0.0),
|
||||||
|
(3, 0.0),
|
||||||
|
(4, 0.0),
|
||||||
|
(5, 0.0),
|
||||||
|
(6, 0.0),
|
||||||
|
(7, 0.0),
|
||||||
|
(8, 0.0),
|
||||||
|
(9, 0.0),
|
||||||
|
(10, 0.0),
|
||||||
|
(11, 0.0),
|
||||||
|
(12, 0.0),
|
||||||
|
(13, 0.0),
|
||||||
|
(14, 0.0),
|
||||||
]
|
]
|
||||||
return cls(tag="flat", levels=levels)
|
return cls(tag="flat", levels=levels)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def boost(cls):
|
def boost(cls) -> "Equalizer":
|
||||||
"""Equalizer preset which boosts the sound of a track,
|
"""Equalizer preset which boosts the sound of a track,
|
||||||
making it sound fun and energetic by increasing the bass
|
making it sound fun and energetic by increasing the bass
|
||||||
and the highs.
|
and the highs.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
levels = [
|
levels = [
|
||||||
(0, -0.075), (1, 0.125), (2, 0.125), (3, 0.1), (4, 0.1),
|
(0, -0.075),
|
||||||
(5, .05), (6, 0.075), (7, 0.0), (8, 0.0), (9, 0.0),
|
(1, 0.125),
|
||||||
(10, 0.0), (11, 0.0), (12, 0.125), (13, 0.15), (14, 0.05)
|
(2, 0.125),
|
||||||
|
(3, 0.1),
|
||||||
|
(4, 0.1),
|
||||||
|
(5, 0.05),
|
||||||
|
(6, 0.075),
|
||||||
|
(7, 0.0),
|
||||||
|
(8, 0.0),
|
||||||
|
(9, 0.0),
|
||||||
|
(10, 0.0),
|
||||||
|
(11, 0.0),
|
||||||
|
(12, 0.125),
|
||||||
|
(13, 0.15),
|
||||||
|
(14, 0.05),
|
||||||
]
|
]
|
||||||
return cls(tag="boost", levels=levels)
|
return cls(tag="boost", levels=levels)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def metal(cls):
|
def metal(cls) -> "Equalizer":
|
||||||
"""Equalizer preset which increases the mids of a track,
|
"""Equalizer preset which increases the mids of a track,
|
||||||
preferably one of the metal genre, to make it sound
|
preferably one of the metal genre, to make it sound
|
||||||
more full and concert-like.
|
more full and concert-like.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
levels = [
|
levels = [
|
||||||
(0, 0.0), (1, 0.1), (2, 0.1), (3, 0.15), (4, 0.13),
|
(0, 0.0),
|
||||||
(5, 0.1), (6, 0.0), (7, 0.125), (8, 0.175), (9, 0.175),
|
(1, 0.1),
|
||||||
(10, 0.125), (11, 0.125), (12, 0.1), (13, 0.075), (14, 0.0)
|
(2, 0.1),
|
||||||
|
(3, 0.15),
|
||||||
|
(4, 0.13),
|
||||||
|
(5, 0.1),
|
||||||
|
(6, 0.0),
|
||||||
|
(7, 0.125),
|
||||||
|
(8, 0.175),
|
||||||
|
(9, 0.175),
|
||||||
|
(10, 0.125),
|
||||||
|
(11, 0.125),
|
||||||
|
(12, 0.1),
|
||||||
|
(13, 0.075),
|
||||||
|
(14, 0.0),
|
||||||
]
|
]
|
||||||
|
|
||||||
return cls(tag="metal", levels=levels)
|
return cls(tag="metal", levels=levels)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def piano(cls):
|
def piano(cls) -> "Equalizer":
|
||||||
"""Equalizer preset which increases the mids and highs
|
"""Equalizer preset which increases the mids and highs
|
||||||
of a track, preferably a piano based one, to make it
|
of a track, preferably a piano based one, to make it
|
||||||
stand out.
|
stand out.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
levels = [
|
levels = [
|
||||||
(0, -0.25), (1, -0.25), (2, -0.125), (3, 0.0),
|
(0, -0.25),
|
||||||
(4, 0.25), (5, 0.25), (6, 0.0), (7, -0.25), (8, -0.25),
|
(1, -0.25),
|
||||||
(9, 0.0), (10, 0.0), (11, 0.5), (12, 0.25), (13, -0.025)
|
(2, -0.125),
|
||||||
|
(3, 0.0),
|
||||||
|
(4, 0.25),
|
||||||
|
(5, 0.25),
|
||||||
|
(6, 0.0),
|
||||||
|
(7, -0.25),
|
||||||
|
(8, -0.25),
|
||||||
|
(9, 0.0),
|
||||||
|
(10, 0.0),
|
||||||
|
(11, 0.5),
|
||||||
|
(12, 0.25),
|
||||||
|
(13, -0.025),
|
||||||
]
|
]
|
||||||
return cls(tag="piano", levels=levels)
|
return cls(tag="piano", levels=levels)
|
||||||
|
|
||||||
|
|
||||||
class Timescale(Filter):
|
class Timescale(Filter):
|
||||||
"""Filter which changes the speed and pitch of a track.
|
"""Filter which changes the speed and pitch of a track.
|
||||||
You can make some very nice effects with this filter,
|
You can make some very nice effects with this filter,
|
||||||
i.e: a vaporwave-esque filter which slows the track down
|
i.e: a vaporwave-esque filter which slows the track down
|
||||||
a certain amount to produce said effect.
|
a certain amount to produce said effect.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(
|
__slots__ = ("speed", "pitch", "rate")
|
||||||
self,
|
|
||||||
*,
|
def __init__(self, *, tag: str, speed: float = 1.0, pitch: float = 1.0, rate: float = 1.0):
|
||||||
tag: str,
|
super().__init__(tag=tag)
|
||||||
speed: float = 1.0,
|
|
||||||
pitch: float = 1.0,
|
|
||||||
rate: float = 1.0
|
|
||||||
):
|
|
||||||
super().__init__()
|
|
||||||
|
|
||||||
if speed < 0:
|
if speed < 0:
|
||||||
raise FilterInvalidArgument("Timescale speed must be more than 0.")
|
raise FilterInvalidArgument("Timescale speed must be more than 0.")
|
||||||
|
|
@ -132,17 +206,16 @@ class Timescale(Filter):
|
||||||
if rate < 0:
|
if rate < 0:
|
||||||
raise FilterInvalidArgument("Timescale rate must be more than 0.")
|
raise FilterInvalidArgument("Timescale rate must be more than 0.")
|
||||||
|
|
||||||
self.speed = speed
|
self.speed: float = speed
|
||||||
self.pitch = pitch
|
self.pitch: float = pitch
|
||||||
self.rate = rate
|
self.rate: float = rate
|
||||||
self.tag = tag
|
|
||||||
|
|
||||||
self.payload = {"timescale": {"speed": self.speed,
|
self.payload: dict = {
|
||||||
"pitch": self.pitch,
|
"timescale": {"speed": self.speed, "pitch": self.pitch, "rate": self.rate},
|
||||||
"rate": self.rate}}
|
}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def vaporwave(cls):
|
def vaporwave(cls) -> "Timescale":
|
||||||
"""Timescale preset which slows down the currently playing track,
|
"""Timescale preset which slows down the currently playing track,
|
||||||
giving it the effect of a half-speed record/casette playing.
|
giving it the effect of a half-speed record/casette playing.
|
||||||
|
|
||||||
|
|
@ -152,7 +225,7 @@ class Timescale(Filter):
|
||||||
return cls(tag="vaporwave", speed=0.8, pitch=0.8)
|
return cls(tag="vaporwave", speed=0.8, pitch=0.8)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def nightcore(cls):
|
def nightcore(cls) -> "Timescale":
|
||||||
"""Timescale preset which speeds up the currently playing track,
|
"""Timescale preset which speeds up the currently playing track,
|
||||||
which matches up to nightcore, a genre of sped-up music
|
which matches up to nightcore, a genre of sped-up music
|
||||||
|
|
||||||
|
|
@ -161,15 +234,27 @@ class Timescale(Filter):
|
||||||
|
|
||||||
return cls(tag="nightcore", speed=1.25, pitch=1.3)
|
return cls(tag="nightcore", speed=1.25, pitch=1.3)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self) -> str:
|
||||||
return f"<Pomice.TimescaleFilter tag={self.tag} speed={self.speed} pitch={self.pitch} rate={self.rate}>"
|
return f"<Pomice.TimescaleFilter tag={self.tag} speed={self.speed} pitch={self.pitch} rate={self.rate}>"
|
||||||
|
|
||||||
|
def __eq__(self, __value: object) -> bool:
|
||||||
|
if not isinstance(__value, Timescale):
|
||||||
|
return False
|
||||||
|
|
||||||
|
return (
|
||||||
|
self.speed == __value.speed
|
||||||
|
and self.pitch == __value.pitch
|
||||||
|
and self.rate == __value.rate
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class Karaoke(Filter):
|
class Karaoke(Filter):
|
||||||
"""Filter which filters the vocal track from any song and leaves the instrumental.
|
"""Filter which filters the vocal track from any song and leaves the instrumental.
|
||||||
Best for karaoke as the filter implies.
|
Best for karaoke as the filter implies.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
__slots__ = ("level", "mono_level", "filter_band", "filter_width")
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
*,
|
*,
|
||||||
|
|
@ -177,90 +262,122 @@ class Karaoke(Filter):
|
||||||
level: float = 1.0,
|
level: float = 1.0,
|
||||||
mono_level: float = 1.0,
|
mono_level: float = 1.0,
|
||||||
filter_band: float = 220.0,
|
filter_band: float = 220.0,
|
||||||
filter_width: float = 100.0
|
filter_width: float = 100.0,
|
||||||
):
|
):
|
||||||
super().__init__()
|
super().__init__(tag=tag)
|
||||||
|
|
||||||
self.level = level
|
self.level: float = level
|
||||||
self.mono_level = mono_level
|
self.mono_level: float = mono_level
|
||||||
self.filter_band = filter_band
|
self.filter_band: float = filter_band
|
||||||
self.filter_width = filter_width
|
self.filter_width: float = filter_width
|
||||||
self.tag = tag
|
|
||||||
|
|
||||||
self.payload = {"karaoke": {"level": self.level,
|
self.payload: dict = {
|
||||||
"monoLevel": self.mono_level,
|
"karaoke": {
|
||||||
"filterBand": self.filter_band,
|
"level": self.level,
|
||||||
"filterWidth": self.filter_width}}
|
"monoLevel": self.mono_level,
|
||||||
|
"filterBand": self.filter_band,
|
||||||
|
"filterWidth": self.filter_width,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self) -> str:
|
||||||
return (
|
return (
|
||||||
f"<Pomice.KaraokeFilter tag={self.tag} level={self.level} mono_level={self.mono_level} "
|
f"<Pomice.KaraokeFilter tag={self.tag} level={self.level} mono_level={self.mono_level} "
|
||||||
f"filter_band={self.filter_band} filter_width={self.filter_width}>"
|
f"filter_band={self.filter_band} filter_width={self.filter_width}>"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def __eq__(self, __value: object) -> bool:
|
||||||
|
if not isinstance(__value, Karaoke):
|
||||||
|
return False
|
||||||
|
|
||||||
|
return (
|
||||||
|
self.level == __value.level
|
||||||
|
and self.mono_level == __value.mono_level
|
||||||
|
and self.filter_band == __value.filter_band
|
||||||
|
and self.filter_width == __value.filter_width
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class Tremolo(Filter):
|
class Tremolo(Filter):
|
||||||
"""Filter which produces a wavering tone in the music,
|
"""Filter which produces a wavering tone in the music,
|
||||||
causing it to sound like the music is changing in volume rapidly.
|
causing it to sound like the music is changing in volume rapidly.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(
|
__slots__ = ("frequency", "depth")
|
||||||
self,
|
|
||||||
*,
|
def __init__(self, *, tag: str, frequency: float = 2.0, depth: float = 0.5):
|
||||||
tag: str,
|
super().__init__(tag=tag)
|
||||||
frequency: float = 2.0,
|
|
||||||
depth: float = 0.5
|
|
||||||
):
|
|
||||||
super().__init__()
|
|
||||||
|
|
||||||
if frequency < 0:
|
if frequency < 0:
|
||||||
raise FilterInvalidArgument(
|
raise FilterInvalidArgument(
|
||||||
"Tremolo frequency must be more than 0.")
|
"Tremolo frequency must be more than 0.",
|
||||||
|
)
|
||||||
if depth < 0 or depth > 1:
|
if depth < 0 or depth > 1:
|
||||||
raise FilterInvalidArgument(
|
raise FilterInvalidArgument(
|
||||||
"Tremolo depth must be between 0 and 1.")
|
"Tremolo depth must be between 0 and 1.",
|
||||||
|
)
|
||||||
|
|
||||||
self.frequency = frequency
|
self.frequency: float = frequency
|
||||||
self.depth = depth
|
self.depth: float = depth
|
||||||
self.tag = tag
|
|
||||||
|
|
||||||
self.payload = {"tremolo": {"frequency": self.frequency,
|
self.payload: dict = {
|
||||||
"depth": self.depth}}
|
"tremolo": {
|
||||||
|
"frequency": self.frequency,
|
||||||
|
"depth": self.depth,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self) -> str:
|
||||||
return f"<Pomice.TremoloFilter tag={self.tag} frequency={self.frequency} depth={self.depth}>"
|
return (
|
||||||
|
f"<Pomice.TremoloFilter tag={self.tag} frequency={self.frequency} depth={self.depth}>"
|
||||||
|
)
|
||||||
|
|
||||||
|
def __eq__(self, __value: object) -> bool:
|
||||||
|
if not isinstance(__value, Tremolo):
|
||||||
|
return False
|
||||||
|
|
||||||
|
return self.frequency == __value.frequency and self.depth == __value.depth
|
||||||
|
|
||||||
|
|
||||||
class Vibrato(Filter):
|
class Vibrato(Filter):
|
||||||
"""Filter which produces a wavering tone in the music, similar to the Tremolo filter,
|
"""Filter which produces a wavering tone in the music, similar to the Tremolo filter,
|
||||||
but changes in pitch rather than volume.
|
but changes in pitch rather than volume.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(
|
__slots__ = ("frequency", "depth")
|
||||||
self,
|
|
||||||
*,
|
def __init__(self, *, tag: str, frequency: float = 2.0, depth: float = 0.5):
|
||||||
tag: str,
|
super().__init__(tag=tag)
|
||||||
frequency: float = 2.0,
|
|
||||||
depth: float = 0.5
|
|
||||||
):
|
|
||||||
|
|
||||||
super().__init__()
|
|
||||||
if frequency < 0 or frequency > 14:
|
if frequency < 0 or frequency > 14:
|
||||||
raise FilterInvalidArgument(
|
raise FilterInvalidArgument(
|
||||||
"Vibrato frequency must be between 0 and 14.")
|
"Vibrato frequency must be between 0 and 14.",
|
||||||
|
)
|
||||||
if depth < 0 or depth > 1:
|
if depth < 0 or depth > 1:
|
||||||
raise FilterInvalidArgument(
|
raise FilterInvalidArgument(
|
||||||
"Vibrato depth must be between 0 and 1.")
|
"Vibrato depth must be between 0 and 1.",
|
||||||
|
)
|
||||||
|
|
||||||
self.frequency = frequency
|
self.frequency: float = frequency
|
||||||
self.depth = depth
|
self.depth: float = depth
|
||||||
self.tag = tag
|
|
||||||
|
|
||||||
self.payload = {"vibrato": {"frequency": self.frequency,
|
self.payload: dict = {
|
||||||
"depth": self.depth}}
|
"vibrato": {
|
||||||
|
"frequency": self.frequency,
|
||||||
|
"depth": self.depth,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self) -> str:
|
||||||
return f"<Pomice.VibratoFilter tag={self.tag} frequency={self.frequency} depth={self.depth}>"
|
return (
|
||||||
|
f"<Pomice.VibratoFilter tag={self.tag} frequency={self.frequency} depth={self.depth}>"
|
||||||
|
)
|
||||||
|
|
||||||
|
def __eq__(self, __value: object) -> bool:
|
||||||
|
if not isinstance(__value, Vibrato):
|
||||||
|
return False
|
||||||
|
|
||||||
|
return self.frequency == __value.frequency and self.depth == __value.depth
|
||||||
|
|
||||||
|
|
||||||
class Rotation(Filter):
|
class Rotation(Filter):
|
||||||
|
|
@ -268,22 +385,36 @@ class Rotation(Filter):
|
||||||
the audio is being rotated around the listener's head
|
the audio is being rotated around the listener's head
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, *, tag: str, rotation_hertz: float = 5):
|
__slots__ = ("rotation_hertz",)
|
||||||
super().__init__()
|
|
||||||
|
|
||||||
self.rotation_hertz = rotation_hertz
|
def __init__(self, *, tag: str, rotation_hertz: float = 5):
|
||||||
self.tag = tag
|
super().__init__(tag=tag)
|
||||||
self.payload = {"rotation": {"rotationHz": self.rotation_hertz}}
|
|
||||||
|
self.rotation_hertz: float = rotation_hertz
|
||||||
|
self.payload: dict = {"rotation": {"rotationHz": self.rotation_hertz}}
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return f"<Pomice.RotationFilter tag={self.tag} rotation_hertz={self.rotation_hertz}>"
|
return f"<Pomice.RotationFilter tag={self.tag} rotation_hertz={self.rotation_hertz}>"
|
||||||
|
|
||||||
|
def __eq__(self, __value: object) -> bool:
|
||||||
|
if not isinstance(__value, Rotation):
|
||||||
|
return False
|
||||||
|
|
||||||
|
return self.rotation_hertz == __value.rotation_hertz
|
||||||
|
|
||||||
|
|
||||||
class ChannelMix(Filter):
|
class ChannelMix(Filter):
|
||||||
"""Filter which manually adjusts the panning of the audio, which can make
|
"""Filter which manually adjusts the panning of the audio, which can make
|
||||||
for some cool effects when done correctly.
|
for some cool effects when done correctly.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
__slots__ = (
|
||||||
|
"left_to_left",
|
||||||
|
"right_to_right",
|
||||||
|
"left_to_right",
|
||||||
|
"right_to_left",
|
||||||
|
)
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
*,
|
*,
|
||||||
|
|
@ -291,88 +422,132 @@ class ChannelMix(Filter):
|
||||||
left_to_left: float = 1,
|
left_to_left: float = 1,
|
||||||
right_to_right: float = 1,
|
right_to_right: float = 1,
|
||||||
left_to_right: float = 0,
|
left_to_right: float = 0,
|
||||||
right_to_left: float = 0
|
right_to_left: float = 0,
|
||||||
):
|
):
|
||||||
super().__init__()
|
super().__init__(tag=tag)
|
||||||
|
|
||||||
if 0 > left_to_left > 1:
|
if 0 > left_to_left > 1:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
"'left_to_left' value must be more than or equal to 0 or less than or equal to 1.")
|
"'left_to_left' value must be more than or equal to 0 or less than or equal to 1.",
|
||||||
|
)
|
||||||
if 0 > right_to_right > 1:
|
if 0 > right_to_right > 1:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
"'right_to_right' value must be more than or equal to 0 or less than or equal to 1.")
|
"'right_to_right' value must be more than or equal to 0 or less than or equal to 1.",
|
||||||
|
)
|
||||||
if 0 > left_to_right > 1:
|
if 0 > left_to_right > 1:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
"'left_to_right' value must be more than or equal to 0 or less than or equal to 1.")
|
"'left_to_right' value must be more than or equal to 0 or less than or equal to 1.",
|
||||||
|
)
|
||||||
if 0 > right_to_left > 1:
|
if 0 > right_to_left > 1:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
"'right_to_left' value must be more than or equal to 0 or less than or equal to 1.")
|
"'right_to_left' value must be more than or equal to 0 or less than or equal to 1.",
|
||||||
|
)
|
||||||
|
|
||||||
self.left_to_left = left_to_left
|
self.left_to_left: float = left_to_left
|
||||||
self.left_to_right = left_to_right
|
self.left_to_right: float = left_to_right
|
||||||
self.right_to_left = right_to_left
|
self.right_to_left: float = right_to_left
|
||||||
self.right_to_right = right_to_right
|
self.right_to_right: float = right_to_right
|
||||||
self.tag = tag
|
|
||||||
|
|
||||||
self.payload = {"channelMix": {"leftToLeft": self.left_to_left,
|
|
||||||
"leftToRight": self.left_to_right,
|
|
||||||
"rightToLeft": self.right_to_left,
|
|
||||||
"rightToRight": self.right_to_right}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
self.payload: dict = {
|
||||||
|
"channelMix": {
|
||||||
|
"leftToLeft": self.left_to_left,
|
||||||
|
"leftToRight": self.left_to_right,
|
||||||
|
"rightToLeft": self.right_to_left,
|
||||||
|
"rightToRight": self.right_to_right,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return (
|
return (
|
||||||
f"<Pomice.ChannelMix tag={self.tag} left_to_left={self.left_to_left} left_to_right={self.left_to_right} "
|
f"<Pomice.ChannelMix tag={self.tag} left_to_left={self.left_to_left} left_to_right={self.left_to_right} "
|
||||||
f"right_to_left={self.right_to_left} right_to_right={self.right_to_right}>"
|
f"right_to_left={self.right_to_left} right_to_right={self.right_to_right}>"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def __eq__(self, __value: object) -> bool:
|
||||||
|
if not isinstance(__value, ChannelMix):
|
||||||
|
return False
|
||||||
|
|
||||||
|
return (
|
||||||
|
self.left_to_left == __value.left_to_left
|
||||||
|
and self.left_to_right == __value.left_to_right
|
||||||
|
and self.right_to_left == __value.right_to_left
|
||||||
|
and self.right_to_right == __value.right_to_right
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class Distortion(Filter):
|
class Distortion(Filter):
|
||||||
"""Filter which generates a distortion effect. Useful for certain filter implementations where
|
"""Filter which generates a distortion effect. Useful for certain filter implementations where
|
||||||
distortion is needed.
|
distortion is needed.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
__slots__ = (
|
||||||
|
"sin_offset",
|
||||||
|
"sin_scale",
|
||||||
|
"cos_offset",
|
||||||
|
"cos_scale",
|
||||||
|
"tan_offset",
|
||||||
|
"tan_scale",
|
||||||
|
"offset",
|
||||||
|
"scale",
|
||||||
|
)
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
*,
|
*,
|
||||||
tag: str,
|
tag: str,
|
||||||
sin_offset: float = 0,
|
sin_offset: float = 0,
|
||||||
sin_scale: float = 1,
|
sin_scale: float = 1,
|
||||||
cos_offset: float = 0,
|
cos_offset: float = 0,
|
||||||
cos_scale: float = 1,
|
cos_scale: float = 1,
|
||||||
tan_offset: float = 0,
|
tan_offset: float = 0,
|
||||||
tan_scale: float = 1,
|
tan_scale: float = 1,
|
||||||
offset: float = 0,
|
offset: float = 0,
|
||||||
scale: float = 1
|
scale: float = 1,
|
||||||
):
|
):
|
||||||
super().__init__()
|
super().__init__(tag=tag)
|
||||||
|
|
||||||
self.sin_offset = sin_offset
|
self.sin_offset: float = sin_offset
|
||||||
self.sin_scale = sin_scale
|
self.sin_scale: float = sin_scale
|
||||||
self.cos_offset = cos_offset
|
self.cos_offset: float = cos_offset
|
||||||
self.cos_scale = cos_scale
|
self.cos_scale: float = cos_scale
|
||||||
self.tan_offset = tan_offset
|
self.tan_offset: float = tan_offset
|
||||||
self.tan_scale = tan_scale
|
self.tan_scale: float = tan_scale
|
||||||
self.offset = offset
|
self.offset: float = offset
|
||||||
self.scale = scale
|
self.scale: float = scale
|
||||||
self.tag = tag
|
|
||||||
|
|
||||||
self.payload = {"distortion": {
|
self.payload: dict = {
|
||||||
"sinOffset": self.sin_offset,
|
"distortion": {
|
||||||
"sinScale": self.sin_scale,
|
"sinOffset": self.sin_offset,
|
||||||
"cosOffset": self.cos_offset,
|
"sinScale": self.sin_scale,
|
||||||
"cosScale": self.cos_scale,
|
"cosOffset": self.cos_offset,
|
||||||
"tanOffset": self.tan_offset,
|
"cosScale": self.cos_scale,
|
||||||
"tanScale": self.tan_scale,
|
"tanOffset": self.tan_offset,
|
||||||
"offset": self.offset,
|
"tanScale": self.tan_scale,
|
||||||
"scale": self.scale
|
"offset": self.offset,
|
||||||
}}
|
"scale": self.scale,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return (
|
return (
|
||||||
f"<Pomice.Distortion tag={self.tag} sin_offset={self.sin_offset} sin_scale={self.sin_scale}> "
|
f"<Pomice.Distortion tag={self.tag} sin_offset={self.sin_offset} sin_scale={self.sin_scale}> "
|
||||||
f"cos_offset={self.cos_offset} cos_scale={self.cos_scale} tan_offset={self.tan_offset} "
|
f"cos_offset={self.cos_offset} cos_scale={self.cos_scale} tan_offset={self.tan_offset} "
|
||||||
f"tan_scale={self.tan_scale} offset={self.offset} scale={self.scale}"
|
f"tan_scale={self.tan_scale} offset={self.offset} scale={self.scale}"
|
||||||
|
)
|
||||||
|
|
||||||
|
def __eq__(self, __value: object) -> bool:
|
||||||
|
if not isinstance(__value, Distortion):
|
||||||
|
return False
|
||||||
|
|
||||||
|
return (
|
||||||
|
self.sin_offset == __value.sin_offset
|
||||||
|
and self.sin_scale == __value.sin_scale
|
||||||
|
and self.cos_offset == __value.cos_offset
|
||||||
|
and self.cos_scale == __value.cos_scale
|
||||||
|
and self.tan_offset == __value.tan_offset
|
||||||
|
and self.tan_scale == __value.tan_scale
|
||||||
|
and self.offset == __value.offset
|
||||||
|
and self.scale == __value.scale
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -380,14 +555,20 @@ class LowPass(Filter):
|
||||||
"""Filter which supresses higher frequencies and allows lower frequencies to pass.
|
"""Filter which supresses higher frequencies and allows lower frequencies to pass.
|
||||||
You can also do this with the Equalizer filter, but this is an easier way to do it.
|
You can also do this with the Equalizer filter, but this is an easier way to do it.
|
||||||
"""
|
"""
|
||||||
def __init__(self, *, tag: str, smoothing: float = 20):
|
|
||||||
super().__init__()
|
|
||||||
|
|
||||||
self.smoothing = smoothing
|
__slots__ = ("smoothing", "payload")
|
||||||
self.tag = tag
|
|
||||||
self.payload = {"lowPass": {"smoothing": self.smoothing}}
|
def __init__(self, *, tag: str, smoothing: float = 20):
|
||||||
|
super().__init__(tag=tag)
|
||||||
|
|
||||||
|
self.smoothing: float = smoothing
|
||||||
|
self.payload: dict = {"lowPass": {"smoothing": self.smoothing}}
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return f"<Pomice.LowPass tag={self.tag} smoothing={self.smoothing}>"
|
return f"<Pomice.LowPass tag={self.tag} smoothing={self.smoothing}>"
|
||||||
|
|
||||||
|
def __eq__(self, __value: object) -> bool:
|
||||||
|
if not isinstance(__value, LowPass):
|
||||||
|
return False
|
||||||
|
|
||||||
|
return self.smoothing == __value.smoothing
|
||||||
|
|
|
||||||
|
|
@ -1,23 +1,53 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
from typing import List, Optional, Union
|
|
||||||
from discord import Member, User
|
|
||||||
|
|
||||||
|
from typing import List
|
||||||
|
from typing import Optional
|
||||||
|
from typing import Union
|
||||||
|
|
||||||
|
from discord import ClientUser
|
||||||
|
from discord import Member
|
||||||
|
from discord import User
|
||||||
from discord.ext import commands
|
from discord.ext import commands
|
||||||
|
|
||||||
from .enums import SearchType, TrackType, PlaylistType
|
from .enums import PlaylistType
|
||||||
|
from .enums import SearchType
|
||||||
|
from .enums import TrackType
|
||||||
from .filters import Filter
|
from .filters import Filter
|
||||||
|
|
||||||
from . import (
|
__all__ = (
|
||||||
spotify,
|
"Track",
|
||||||
applemusic
|
"Playlist",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class Track:
|
class Track:
|
||||||
"""The base track object. Returns critical track information needed for parsing by Lavalink.
|
"""The base track object. Returns critical track information needed for parsing by Lavalink.
|
||||||
You can also pass in commands.Context to get a discord.py Context object in your track.
|
You can also pass in commands.Context to get a discord.py Context object in your track.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
__slots__ = (
|
||||||
|
"track_id",
|
||||||
|
"info",
|
||||||
|
"track_type",
|
||||||
|
"filters",
|
||||||
|
"timestamp",
|
||||||
|
"original",
|
||||||
|
"_search_type",
|
||||||
|
"playlist",
|
||||||
|
"title",
|
||||||
|
"author",
|
||||||
|
"uri",
|
||||||
|
"identifier",
|
||||||
|
"isrc",
|
||||||
|
"thumbnail",
|
||||||
|
"length",
|
||||||
|
"ctx",
|
||||||
|
"requester",
|
||||||
|
"is_stream",
|
||||||
|
"is_seekable",
|
||||||
|
"position",
|
||||||
|
)
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
*,
|
*,
|
||||||
|
|
@ -28,10 +58,10 @@ class Track:
|
||||||
search_type: SearchType = SearchType.ytsearch,
|
search_type: SearchType = SearchType.ytsearch,
|
||||||
filters: Optional[List[Filter]] = None,
|
filters: Optional[List[Filter]] = None,
|
||||||
timestamp: Optional[float] = None,
|
timestamp: Optional[float] = None,
|
||||||
requester: Optional[Union[Member, User]] = None,
|
requester: Optional[Union[Member, User, ClientUser]] = None,
|
||||||
):
|
):
|
||||||
self.track_id = track_id
|
self.track_id: str = track_id
|
||||||
self.info = info
|
self.info: dict = info
|
||||||
self.track_type: TrackType = track_type
|
self.track_type: TrackType = track_type
|
||||||
self.filters: Optional[List[Filter]] = filters
|
self.filters: Optional[List[Filter]] = filters
|
||||||
self.timestamp: Optional[float] = timestamp
|
self.timestamp: Optional[float] = timestamp
|
||||||
|
|
@ -40,58 +70,60 @@ class Track:
|
||||||
self.original: Optional[Track] = None
|
self.original: Optional[Track] = None
|
||||||
else:
|
else:
|
||||||
self.original = self
|
self.original = self
|
||||||
self._search_type = search_type
|
self._search_type: SearchType = search_type
|
||||||
|
|
||||||
self.playlist: Playlist = None
|
self.playlist: Optional[Playlist] = None
|
||||||
|
|
||||||
self.title = info.get("title")
|
self.title: str = info.get("title", "Unknown Title")
|
||||||
self.author = info.get("author")
|
self.author: str = info.get("author", "Unknown Author")
|
||||||
self.uri = info.get("uri")
|
self.uri: str = info.get("uri", "")
|
||||||
self.identifier = info.get("identifier")
|
self.identifier: str = info.get("identifier", "")
|
||||||
self.isrc = info.get("isrc")
|
self.isrc: Optional[str] = info.get("isrc", None)
|
||||||
|
self.thumbnail: Optional[str] = info.get("thumbnail")
|
||||||
|
|
||||||
if self.uri:
|
if self.uri and self.track_type is TrackType.YOUTUBE:
|
||||||
if info.get("thumbnail"):
|
self.thumbnail = f"https://img.youtube.com/vi/{self.identifier}/mqdefault.jpg"
|
||||||
self.thumbnail = info.get("thumbnail")
|
|
||||||
elif self.track_type == TrackType.SOUNDCLOUD:
|
|
||||||
# ok so theres no feasible way of getting a Soundcloud image URL
|
|
||||||
# so we're just gonna leave it blank for brevity
|
|
||||||
self.thumbnail = None
|
|
||||||
else:
|
|
||||||
self.thumbnail = f"https://img.youtube.com/vi/{self.identifier}/mqdefault.jpg"
|
|
||||||
|
|
||||||
self.length = info.get("length")
|
self.length: int = info.get("length", 0)
|
||||||
self.ctx = ctx
|
self.is_stream: bool = info.get("isStream", False)
|
||||||
if requester:
|
self.is_seekable: bool = info.get("isSeekable", False)
|
||||||
self.requester = requester
|
self.position: int = info.get("position", 0)
|
||||||
else:
|
|
||||||
self.requester = self.ctx.author if ctx else None
|
|
||||||
self.is_stream = info.get("isStream")
|
|
||||||
self.is_seekable = info.get("isSeekable")
|
|
||||||
self.position = info.get("position")
|
|
||||||
|
|
||||||
def __eq__(self, other):
|
self.ctx: Optional[commands.Context] = ctx
|
||||||
|
self.requester: Optional[Union[Member, User, ClientUser]] = requester
|
||||||
|
if not self.requester and self.ctx:
|
||||||
|
self.requester = self.ctx.author
|
||||||
|
|
||||||
|
def __eq__(self, other: object) -> bool:
|
||||||
if not isinstance(other, Track):
|
if not isinstance(other, Track):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if self.ctx and other.ctx:
|
|
||||||
return other.track_id == self.track_id and other.ctx.message.id == self.ctx.message.id
|
|
||||||
|
|
||||||
return other.track_id == self.track_id
|
return other.track_id == self.track_id
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self) -> str:
|
||||||
return self.title
|
return self.title
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self) -> str:
|
||||||
return f"<Pomice.track title={self.title!r} uri=<{self.uri!r}> length={self.length}>"
|
return f"<Pomice.track title={self.title!r} uri=<{self.uri!r}> length={self.length}>"
|
||||||
|
|
||||||
|
|
||||||
class Playlist:
|
class Playlist:
|
||||||
"""The base playlist object.
|
"""The base playlist object.
|
||||||
Returns critical playlist information needed for parsing by Lavalink.
|
Returns critical playlist information needed for parsing by Lavalink.
|
||||||
You can also pass in commands.Context to get a discord.py Context object in your tracks.
|
You can also pass in commands.Context to get a discord.py Context object in your tracks.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
__slots__ = (
|
||||||
|
"playlist_info",
|
||||||
|
"tracks",
|
||||||
|
"name",
|
||||||
|
"playlist_type",
|
||||||
|
"_thumbnail",
|
||||||
|
"_uri",
|
||||||
|
"selected_track",
|
||||||
|
"track_count",
|
||||||
|
)
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
*,
|
*,
|
||||||
|
|
@ -99,30 +131,29 @@ class Playlist:
|
||||||
tracks: list,
|
tracks: list,
|
||||||
playlist_type: PlaylistType,
|
playlist_type: PlaylistType,
|
||||||
thumbnail: Optional[str] = None,
|
thumbnail: Optional[str] = None,
|
||||||
uri: Optional[str] = None
|
uri: Optional[str] = None,
|
||||||
):
|
):
|
||||||
self.playlist_info = playlist_info
|
self.playlist_info: dict = playlist_info
|
||||||
self.tracks: List[Track] = tracks
|
self.tracks: List[Track] = tracks
|
||||||
self.name = playlist_info.get("name")
|
self.name: str = playlist_info.get("name", "Unknown Playlist")
|
||||||
self.playlist_type = playlist_type
|
self.playlist_type: PlaylistType = playlist_type
|
||||||
|
|
||||||
self._thumbnail = thumbnail
|
self._thumbnail: Optional[str] = thumbnail
|
||||||
self._uri = uri
|
self._uri: Optional[str] = uri
|
||||||
|
|
||||||
for track in self.tracks:
|
for track in self.tracks:
|
||||||
track.playlist = self
|
track.playlist = self
|
||||||
|
|
||||||
if (index := playlist_info.get("selectedTrack")) == -1:
|
self.selected_track: Optional[Track] = None
|
||||||
self.selected_track = None
|
if (index := playlist_info.get("selectedTrack", -1)) != -1:
|
||||||
else:
|
|
||||||
self.selected_track = self.tracks[index]
|
self.selected_track = self.tracks[index]
|
||||||
|
|
||||||
self.track_count = len(self.tracks)
|
self.track_count: int = len(self.tracks)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self) -> str:
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self) -> str:
|
||||||
return f"<Pomice.playlist name={self.name!r} track_count={len(self.tracks)}>"
|
return f"<Pomice.playlist name={self.name!r} track_count={len(self.tracks)}>"
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|
|
||||||
535
pomice/player.py
535
pomice/player.py
|
|
@ -1,138 +1,197 @@
|
||||||
import time
|
from __future__ import annotations
|
||||||
from typing import (
|
|
||||||
Any,
|
|
||||||
Dict,
|
|
||||||
List,
|
|
||||||
Optional,
|
|
||||||
Union
|
|
||||||
)
|
|
||||||
|
|
||||||
from discord import (
|
import time
|
||||||
Client,
|
from typing import Any
|
||||||
Guild,
|
from typing import Dict
|
||||||
VoiceChannel,
|
from typing import List
|
||||||
VoiceProtocol
|
from typing import Optional
|
||||||
)
|
from typing import TYPE_CHECKING
|
||||||
|
from typing import Union
|
||||||
|
|
||||||
|
from discord import Client
|
||||||
|
from discord import Guild
|
||||||
|
from discord import VoiceChannel
|
||||||
|
from discord import VoiceProtocol
|
||||||
from discord.ext import commands
|
from discord.ext import commands
|
||||||
|
|
||||||
from . import events
|
from . import events
|
||||||
from .enums import SearchType, PlatformRecommendation
|
from .enums import SearchType
|
||||||
from .events import PomiceEvent, TrackEndEvent, TrackStartEvent
|
from .events import PomiceEvent
|
||||||
from .exceptions import FilterInvalidArgument, FilterTagAlreadyInUse, FilterTagInvalid, TrackInvalidPosition, TrackLoadError
|
from .events import TrackEndEvent
|
||||||
|
from .events import TrackStartEvent
|
||||||
|
from .exceptions import FilterInvalidArgument
|
||||||
|
from .exceptions import FilterTagAlreadyInUse
|
||||||
|
from .exceptions import FilterTagInvalid
|
||||||
|
from .exceptions import TrackInvalidPosition
|
||||||
|
from .exceptions import TrackLoadError
|
||||||
from .filters import Filter
|
from .filters import Filter
|
||||||
from .objects import Track, Playlist
|
from .filters import Timescale
|
||||||
from .pool import Node, NodePool
|
from .objects import Playlist
|
||||||
|
from .objects import Track
|
||||||
|
from .pool import Node
|
||||||
|
from .pool import NodePool
|
||||||
|
from pomice.utils import LavalinkVersion
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from discord.types.voice import VoiceServerUpdate
|
||||||
|
from discord.types.voice import GuildVoiceState
|
||||||
|
|
||||||
|
__all__ = ("Filters", "Player")
|
||||||
|
|
||||||
|
|
||||||
class Filters:
|
class Filters:
|
||||||
"""Helper class for filters"""
|
"""Helper class for filters"""
|
||||||
def __init__(self):
|
|
||||||
|
__slots__ = ("_filters",)
|
||||||
|
|
||||||
|
def __init__(self) -> None:
|
||||||
self._filters: List[Filter] = []
|
self._filters: List[Filter] = []
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def has_preload(self):
|
def has_preload(self) -> bool:
|
||||||
"""Property which checks if any applied filters were preloaded"""
|
"""Property which checks if any applied filters were preloaded"""
|
||||||
return any(f for f in self._filters if f.preload == True)
|
return any(f for f in self._filters if f.preload == True)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def has_global(self):
|
def has_global(self) -> bool:
|
||||||
"""Property which checks if any applied filters are global"""
|
"""Property which checks if any applied filters are global"""
|
||||||
return any(f for f in self._filters if f.preload == False)
|
return any(f for f in self._filters if f.preload == False)
|
||||||
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def empty(self):
|
def empty(self) -> bool:
|
||||||
"""Property which checks if the filter list is empty"""
|
"""Property which checks if the filter list is empty"""
|
||||||
return len(self._filters) == 0
|
return len(self._filters) == 0
|
||||||
|
|
||||||
|
def add_filter(self, *, filter: Filter) -> None:
|
||||||
def add_filter(self, *, filter: Filter):
|
|
||||||
"""Adds a filter to the list of filters applied"""
|
"""Adds a filter to the list of filters applied"""
|
||||||
if any(f for f in self._filters if f.tag == filter.tag):
|
if any(f for f in self._filters if f.tag == filter.tag):
|
||||||
raise FilterTagAlreadyInUse(
|
raise FilterTagAlreadyInUse(
|
||||||
"A filter with that tag is already in use."
|
"A filter with that tag is already in use.",
|
||||||
)
|
)
|
||||||
self._filters.append(filter)
|
self._filters.append(filter)
|
||||||
|
|
||||||
def remove_filter(self, *, filter_tag: str):
|
def remove_filter(self, *, filter_tag: str) -> None:
|
||||||
"""Removes a filter from the list of filters applied using its filter tag"""
|
"""Removes a filter from the list of filters applied using its filter tag"""
|
||||||
if not any(f for f in self._filters if f.tag == filter_tag):
|
if not any(f for f in self._filters if f.tag == filter_tag):
|
||||||
raise FilterTagInvalid(
|
raise FilterTagInvalid("A filter with that tag was not found.")
|
||||||
"A filter with that tag was not found."
|
|
||||||
)
|
|
||||||
|
|
||||||
for index, filter in enumerate(self._filters):
|
for index, filter in enumerate(self._filters):
|
||||||
if filter.tag == filter_tag:
|
if filter.tag == filter_tag:
|
||||||
del self._filters[index]
|
del self._filters[index]
|
||||||
|
|
||||||
def has_filter(self, *, filter_tag: str):
|
def edit_filter(self, *, filter_tag: str, to_apply: Filter) -> None:
|
||||||
|
"""Edits a filter in the list of filters applied using its filter tag and replaces it with the new filter."""
|
||||||
|
if not any(f for f in self._filters if f.tag == filter_tag):
|
||||||
|
raise FilterTagInvalid("A filter with that tag was not found.")
|
||||||
|
|
||||||
|
for index, filter in enumerate(self._filters):
|
||||||
|
if filter.tag == filter_tag:
|
||||||
|
if not type(filter) == type(to_apply):
|
||||||
|
raise FilterInvalidArgument(
|
||||||
|
"Edited filter is not the same type as the current filter.",
|
||||||
|
)
|
||||||
|
if self._filters[index] == to_apply:
|
||||||
|
raise FilterInvalidArgument("Edited filter is the same as the current filter.")
|
||||||
|
|
||||||
|
if to_apply.tag != filter_tag:
|
||||||
|
raise FilterInvalidArgument(
|
||||||
|
"Edited filter tag is not the same as the current filter tag.",
|
||||||
|
)
|
||||||
|
|
||||||
|
self._filters[index] = to_apply
|
||||||
|
|
||||||
|
def has_filter(self, *, filter_tag: str) -> bool:
|
||||||
"""Checks if a filter exists in the list of filters using its filter tag"""
|
"""Checks if a filter exists in the list of filters using its filter tag"""
|
||||||
return any(f for f in self._filters if f.tag == filter_tag)
|
return any(f for f in self._filters if f.tag == filter_tag)
|
||||||
|
|
||||||
def reset_filters(self):
|
def has_filter_type(self, *, filter_type: Filter) -> bool:
|
||||||
|
"""Checks if any filters applied match the specified filter type."""
|
||||||
|
return any(f for f in self._filters if type(f) == type(filter_type))
|
||||||
|
|
||||||
|
def reset_filters(self) -> None:
|
||||||
"""Removes all filters from the list"""
|
"""Removes all filters from the list"""
|
||||||
self._filters = []
|
self._filters = []
|
||||||
|
|
||||||
def get_preload_filters(self):
|
def get_preload_filters(self) -> List[Filter]:
|
||||||
"""Get all preloaded filters"""
|
"""Get all preloaded filters"""
|
||||||
return [f for f in self._filters if f.preload == True]
|
return [f for f in self._filters if f.preload == True]
|
||||||
|
|
||||||
def get_all_payloads(self):
|
def get_all_payloads(self) -> Dict[str, Any]:
|
||||||
"""Returns a formatted dict of all the filter payloads"""
|
"""Returns a formatted dict of all the filter payloads"""
|
||||||
payload = {}
|
payload: Dict[str, Any] = {}
|
||||||
for filter in self._filters:
|
for _filter in self._filters:
|
||||||
payload.update(filter.payload)
|
if _filter.payload:
|
||||||
|
payload.update(_filter.payload)
|
||||||
return payload
|
return payload
|
||||||
|
|
||||||
def get_filters(self):
|
def get_filters(self) -> List[Filter]:
|
||||||
"""Returns the current list of applied filters"""
|
"""Returns the current list of applied filters"""
|
||||||
return self._filters
|
return self._filters
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class Player(VoiceProtocol):
|
class Player(VoiceProtocol):
|
||||||
"""The base player class for Pomice.
|
"""The base player class for Pomice.
|
||||||
In order to initiate a player, you must pass it in as a cls when you connect to a channel.
|
In order to initiate a player, you must pass it in as a cls when you connect to a channel.
|
||||||
i.e: ```py
|
i.e: ```py
|
||||||
await ctx.author.voice.channel.connect(cls=pomice.Player)
|
await ctx.author.voice.channel.connect(cls=pomice.Player)
|
||||||
```
|
```
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __call__(self, client: Client, channel: VoiceChannel):
|
__slots__ = (
|
||||||
self.client: Client = client
|
"client",
|
||||||
self.channel: VoiceChannel = channel
|
"channel",
|
||||||
self._guild: Guild = channel.guild
|
"_bot",
|
||||||
|
"_guild",
|
||||||
|
"_node",
|
||||||
|
"_current",
|
||||||
|
"_filters",
|
||||||
|
"_volume",
|
||||||
|
"_paused",
|
||||||
|
"_is_connected",
|
||||||
|
"_last_position",
|
||||||
|
"_last_update",
|
||||||
|
"_ending_track",
|
||||||
|
"_log",
|
||||||
|
"_voice_state",
|
||||||
|
"_player_endpoint_uri",
|
||||||
|
)
|
||||||
|
|
||||||
|
def __call__(self, client: Client, channel: VoiceChannel) -> Player:
|
||||||
|
self.client = client
|
||||||
|
self.channel = channel
|
||||||
|
self._guild = channel.guild
|
||||||
|
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
client: Optional[Client] = None,
|
client: Client,
|
||||||
channel: Optional[VoiceChannel] = None,
|
channel: VoiceChannel,
|
||||||
*,
|
*,
|
||||||
node: Node = None
|
node: Optional[Node] = None,
|
||||||
):
|
) -> None:
|
||||||
self.client = client
|
self.client: Client = client
|
||||||
self._bot: Union[Client, commands.Bot] = client
|
self.channel: VoiceChannel = channel
|
||||||
self.channel = channel
|
self._guild = channel.guild
|
||||||
self._guild = channel.guild if channel else None
|
|
||||||
|
|
||||||
self._node = node if node else NodePool.get_node()
|
self._bot: Client = client
|
||||||
self._current: Track = None
|
self._node: Node = node if node else NodePool.get_node()
|
||||||
|
self._current: Optional[Track] = None
|
||||||
self._filters: Filters = Filters()
|
self._filters: Filters = Filters()
|
||||||
self._volume = 100
|
self._volume: int = 100
|
||||||
self._paused = False
|
self._paused: bool = False
|
||||||
self._is_connected = False
|
self._is_connected: bool = False
|
||||||
|
|
||||||
self._position = 0
|
self._last_position: int = 0
|
||||||
self._last_position = 0
|
self._last_update: float = 0
|
||||||
self._last_update = 0
|
|
||||||
self._ending_track: Optional[Track] = None
|
self._ending_track: Optional[Track] = None
|
||||||
|
self._log = self._node._log
|
||||||
|
|
||||||
self._voice_state = {}
|
self._voice_state: dict = {}
|
||||||
|
|
||||||
self._player_endpoint_uri = f'sessions/{self._node._session_id}/players'
|
self._player_endpoint_uri: str = f"sessions/{self._node._session_id}/players"
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self) -> str:
|
||||||
return (
|
return (
|
||||||
f"<Pomice.player bot={self.bot} guildId={self.guild.id} "
|
f"<Pomice.player bot={self.bot} guildId={self.guild.id} "
|
||||||
f"is_connected={self.is_connected} is_playing={self.is_playing}>"
|
f"is_connected={self.is_connected} is_playing={self.is_playing}>"
|
||||||
|
|
@ -141,21 +200,40 @@ class Player(VoiceProtocol):
|
||||||
@property
|
@property
|
||||||
def position(self) -> float:
|
def position(self) -> float:
|
||||||
"""Property which returns the player's position in a track in milliseconds"""
|
"""Property which returns the player's position in a track in milliseconds"""
|
||||||
current = self._current.original
|
if not self.is_playing:
|
||||||
|
|
||||||
if not self.is_playing or not self._current:
|
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
current: Track = self._current # type: ignore
|
||||||
|
if current.original:
|
||||||
|
current = current.original
|
||||||
|
|
||||||
if self.is_paused:
|
if self.is_paused:
|
||||||
return min(self._last_position, current.length)
|
return min(self._last_position, current.length)
|
||||||
|
|
||||||
difference = (time.time() * 1000) - self._last_update
|
difference = (time.time() * 1000) - self._last_update
|
||||||
position = self._last_position + difference
|
position = self._last_position + difference
|
||||||
|
|
||||||
if position > current.length:
|
return round(min(position, current.length))
|
||||||
|
|
||||||
|
@property
|
||||||
|
def rate(self) -> float:
|
||||||
|
"""Property which returns the player's current rate"""
|
||||||
|
if _filter := next((f for f in self._filters._filters if isinstance(f, Timescale)), None):
|
||||||
|
return _filter.speed or _filter.rate
|
||||||
|
return 1.0
|
||||||
|
|
||||||
|
@property
|
||||||
|
def adjusted_position(self) -> float:
|
||||||
|
"""Property which returns the player's position in a track in milliseconds adjusted for rate"""
|
||||||
|
return self.position / self.rate
|
||||||
|
|
||||||
|
@property
|
||||||
|
def adjusted_length(self) -> float:
|
||||||
|
"""Property which returns the player's track length in milliseconds adjusted for rate"""
|
||||||
|
if not self.is_playing:
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
return min(position, current.length)
|
return self.current.length / self.rate # type: ignore
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_playing(self) -> bool:
|
def is_playing(self) -> bool:
|
||||||
|
|
@ -173,7 +251,7 @@ class Player(VoiceProtocol):
|
||||||
return self._is_connected and self._paused
|
return self._is_connected and self._paused
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def current(self) -> Track:
|
def current(self) -> Optional[Track]:
|
||||||
"""Property which returns the currently playing track"""
|
"""Property which returns the currently playing track"""
|
||||||
return self._current
|
return self._current
|
||||||
|
|
||||||
|
|
@ -198,64 +276,88 @@ class Player(VoiceProtocol):
|
||||||
return self._filters
|
return self._filters
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def bot(self) -> Union[Client, commands.Bot]:
|
def bot(self) -> Client:
|
||||||
"""Property which returns the bot associated with this player instance"""
|
"""Property which returns the bot associated with this player instance"""
|
||||||
return self._bot
|
return self._bot
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_dead(self) -> bool:
|
def is_dead(self) -> bool:
|
||||||
"""Returns a bool representing whether the player is dead or not.
|
"""Returns a bool representing whether the player is dead or not.
|
||||||
A player is considered dead if it has been destroyed and removed from stored players.
|
A player is considered dead if it has been destroyed and removed from stored players.
|
||||||
"""
|
"""
|
||||||
return self.guild.id not in self._node._players
|
return self.guild.id not in self._node._players
|
||||||
|
|
||||||
async def _update_state(self, data: dict):
|
def _adjust_end_time(self) -> Optional[str]:
|
||||||
state: dict = data.get("state")
|
if self._node._version >= LavalinkVersion(3, 7, 5):
|
||||||
self._last_update = time.time() * 1000
|
return None
|
||||||
self._is_connected = state.get("connected")
|
|
||||||
self._last_position = state.get("position")
|
|
||||||
|
|
||||||
async def _dispatch_voice_update(self, voice_data: Dict[str, Any]):
|
return "0"
|
||||||
|
|
||||||
|
async def _update_state(self, data: dict) -> None:
|
||||||
|
state: dict = data.get("state", {})
|
||||||
|
self._last_update = int(state.get("time", 0))
|
||||||
|
self._is_connected = bool(state.get("connected"))
|
||||||
|
self._last_position = int(state.get("position", 0))
|
||||||
|
if self._log:
|
||||||
|
self._log.debug(f"Got player update state with data {state}")
|
||||||
|
|
||||||
|
async def _dispatch_voice_update(self, voice_data: Optional[Dict[str, Any]] = None) -> None:
|
||||||
if {"sessionId", "event"} != self._voice_state.keys():
|
if {"sessionId", "event"} != self._voice_state.keys():
|
||||||
return
|
return
|
||||||
|
|
||||||
|
state = voice_data or self._voice_state
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
"token": voice_data['event']['token'],
|
"token": state["event"]["token"],
|
||||||
"endpoint": voice_data['event']['endpoint'],
|
"endpoint": state["event"]["endpoint"],
|
||||||
"sessionId": voice_data['sessionId'],
|
"sessionId": state["sessionId"],
|
||||||
}
|
}
|
||||||
|
|
||||||
await self._node.send(
|
await self._node.send(
|
||||||
method="PATCH",
|
method="PATCH",
|
||||||
path=self._player_endpoint_uri,
|
path=self._player_endpoint_uri,
|
||||||
guild_id=self._guild.id,
|
guild_id=self._guild.id,
|
||||||
data={"voice": data}
|
data={"voice": data},
|
||||||
)
|
)
|
||||||
|
|
||||||
async def on_voice_server_update(self, data: dict):
|
if self._log:
|
||||||
|
self._log.debug(
|
||||||
|
f"Dispatched voice update to {state['event']['endpoint']} with data {data}",
|
||||||
|
)
|
||||||
|
|
||||||
|
async def on_voice_server_update(self, data: VoiceServerUpdate) -> None:
|
||||||
self._voice_state.update({"event": data})
|
self._voice_state.update({"event": data})
|
||||||
await self._dispatch_voice_update(self._voice_state)
|
await self._dispatch_voice_update(self._voice_state)
|
||||||
|
|
||||||
async def on_voice_state_update(self, data: dict):
|
async def on_voice_state_update(self, data: GuildVoiceState) -> None:
|
||||||
self._voice_state.update({"sessionId": data.get("session_id")})
|
self._voice_state.update({"sessionId": data.get("session_id")})
|
||||||
|
|
||||||
if not (channel_id := data.get("channel_id")):
|
channel_id = data.get("channel_id")
|
||||||
|
if not channel_id:
|
||||||
await self.disconnect()
|
await self.disconnect()
|
||||||
self._voice_state.clear()
|
self._voice_state.clear()
|
||||||
return
|
return
|
||||||
|
|
||||||
self.channel = self.guild.get_channel(int(channel_id))
|
channel = self.guild.get_channel(int(channel_id))
|
||||||
|
|
||||||
|
if self.channel != channel:
|
||||||
|
self.channel = channel
|
||||||
|
|
||||||
|
if not channel:
|
||||||
|
await self.disconnect()
|
||||||
|
self._voice_state.clear()
|
||||||
|
return
|
||||||
|
|
||||||
if not data.get("token"):
|
if not data.get("token"):
|
||||||
return
|
return
|
||||||
|
|
||||||
await self._dispatch_voice_update({**self._voice_state, "event": data})
|
await self._dispatch_voice_update({**self._voice_state, "event": data})
|
||||||
|
|
||||||
async def _dispatch_event(self, data: dict):
|
async def _dispatch_event(self, data: dict) -> None:
|
||||||
event_type = data.get("type")
|
event_type: str = data["type"]
|
||||||
event: PomiceEvent = getattr(events, event_type)(data, self)
|
event: PomiceEvent = getattr(events, event_type)(data, self)
|
||||||
|
|
||||||
if isinstance(event, TrackEndEvent) and event.reason != "REPLACED":
|
if isinstance(event, TrackEndEvent) and event.reason not in ("REPLACED", "replaced"):
|
||||||
self._current = None
|
self._current = None
|
||||||
|
|
||||||
event.dispatch(self._bot)
|
event.dispatch(self._bot)
|
||||||
|
|
@ -263,14 +365,40 @@ class Player(VoiceProtocol):
|
||||||
if isinstance(event, TrackStartEvent):
|
if isinstance(event, TrackStartEvent):
|
||||||
self._ending_track = self._current
|
self._ending_track = self._current
|
||||||
|
|
||||||
|
if self._log:
|
||||||
|
self._log.debug(f"Dispatched event {data['type']} to player.")
|
||||||
|
|
||||||
|
async def _refresh_endpoint_uri(self, session_id: Optional[str]) -> None:
|
||||||
|
self._player_endpoint_uri = f"sessions/{session_id}/players"
|
||||||
|
|
||||||
|
async def _swap_node(self, *, new_node: Node) -> None:
|
||||||
|
if self.current:
|
||||||
|
data: dict = {"position": self.position, "encodedTrack": self.current.track_id}
|
||||||
|
|
||||||
|
del self._node._players[self._guild.id]
|
||||||
|
self._node = new_node
|
||||||
|
self._node._players[self._guild.id] = self
|
||||||
|
# reassign uri to update session id
|
||||||
|
await self._refresh_endpoint_uri(new_node._session_id)
|
||||||
|
await self._dispatch_voice_update()
|
||||||
|
await self._node.send(
|
||||||
|
method="PATCH",
|
||||||
|
path=self._player_endpoint_uri,
|
||||||
|
guild_id=self._guild.id,
|
||||||
|
data=data or None,
|
||||||
|
)
|
||||||
|
|
||||||
|
if self._log:
|
||||||
|
self._log.debug(f"Swapped all players to new node {new_node._identifier}.")
|
||||||
|
|
||||||
async def get_tracks(
|
async def get_tracks(
|
||||||
self,
|
self,
|
||||||
query: str,
|
query: str,
|
||||||
*,
|
*,
|
||||||
ctx: Optional[commands.Context] = None,
|
ctx: Optional[commands.Context] = None,
|
||||||
search_type: SearchType = SearchType.ytsearch,
|
search_type: SearchType | None = SearchType.ytsearch,
|
||||||
filters: Optional[List[Filter]] = None
|
filters: Optional[List[Filter]] = None,
|
||||||
):
|
) -> Optional[Union[List[Track], Playlist]]:
|
||||||
"""Fetches tracks from the node's REST api to parse into Lavalink.
|
"""Fetches tracks from the node's REST api to parse into Lavalink.
|
||||||
|
|
||||||
If you passed in Spotify API credentials when you created the node,
|
If you passed in Spotify API credentials when you created the node,
|
||||||
|
|
@ -285,12 +413,22 @@ class Player(VoiceProtocol):
|
||||||
"""
|
"""
|
||||||
return await self._node.get_tracks(query, ctx=ctx, search_type=search_type, filters=filters)
|
return await self._node.get_tracks(query, ctx=ctx, search_type=search_type, filters=filters)
|
||||||
|
|
||||||
|
async def build_track(self, identifier: str, ctx: Optional[commands.Context] = None) -> Track:
|
||||||
|
"""
|
||||||
|
Builds a track using a valid track identifier
|
||||||
|
|
||||||
|
You can also pass in a discord.py Context object to get a
|
||||||
|
Context object on the track it builds.
|
||||||
|
"""
|
||||||
|
|
||||||
|
return await self._node.build_track(identifier, ctx=ctx)
|
||||||
|
|
||||||
async def get_recommendations(
|
async def get_recommendations(
|
||||||
self,
|
self,
|
||||||
*,
|
*,
|
||||||
track: Track,
|
track: Track,
|
||||||
ctx: Optional[commands.Context] = None
|
ctx: Optional[commands.Context] = None,
|
||||||
) -> Union[List[Track], None]:
|
) -> Optional[Union[List[Track], Playlist]]:
|
||||||
"""
|
"""
|
||||||
Gets recommendations from either YouTube or Spotify.
|
Gets recommendations from either YouTube or Spotify.
|
||||||
You can pass in a discord.py Context object to get a
|
You can pass in a discord.py Context object to get a
|
||||||
|
|
@ -298,31 +436,45 @@ class Player(VoiceProtocol):
|
||||||
"""
|
"""
|
||||||
return await self._node.get_recommendations(track=track, ctx=ctx)
|
return await self._node.get_recommendations(track=track, ctx=ctx)
|
||||||
|
|
||||||
async def connect(self, *, timeout: float, reconnect: bool, self_deaf: bool = False, self_mute: bool = False):
|
async def connect(
|
||||||
await self.guild.change_voice_state(channel=self.channel, self_deaf=self_deaf, self_mute=self_mute)
|
self,
|
||||||
|
*,
|
||||||
|
timeout: float,
|
||||||
|
reconnect: bool,
|
||||||
|
self_deaf: bool = False,
|
||||||
|
self_mute: bool = False,
|
||||||
|
) -> None:
|
||||||
|
await self.guild.change_voice_state(
|
||||||
|
channel=self.channel,
|
||||||
|
self_deaf=self_deaf,
|
||||||
|
self_mute=self_mute,
|
||||||
|
)
|
||||||
self._node._players[self.guild.id] = self
|
self._node._players[self.guild.id] = self
|
||||||
self._is_connected = True
|
self._is_connected = True
|
||||||
|
|
||||||
async def stop(self):
|
async def stop(self) -> None:
|
||||||
"""Stops the currently playing track."""
|
"""Stops the currently playing track."""
|
||||||
self._current = None
|
self._current = None
|
||||||
await self._node.send(
|
await self._node.send(
|
||||||
method="PATCH",
|
method="PATCH",
|
||||||
path=self._player_endpoint_uri,
|
path=self._player_endpoint_uri,
|
||||||
guild_id=self._guild.id,
|
guild_id=self._guild.id,
|
||||||
data={'encodedTrack': None}
|
data={"encodedTrack": None},
|
||||||
)
|
)
|
||||||
|
|
||||||
async def disconnect(self, *, force: bool = False):
|
if self._log:
|
||||||
|
self._log.debug(f"Player has been stopped.")
|
||||||
|
|
||||||
|
async def disconnect(self, *, force: bool = False) -> None:
|
||||||
"""Disconnects the player from voice."""
|
"""Disconnects the player from voice."""
|
||||||
try:
|
try:
|
||||||
await self.guild.change_voice_state(channel=None)
|
await self.guild.change_voice_state(channel=None)
|
||||||
finally:
|
finally:
|
||||||
self.cleanup()
|
self.cleanup()
|
||||||
self._is_connected = False
|
self._is_connected = False
|
||||||
self.channel = None
|
self.channel = None # type: ignore
|
||||||
|
|
||||||
async def destroy(self):
|
async def destroy(self) -> None:
|
||||||
"""Disconnects and destroys the player, and runs internal cleanup."""
|
"""Disconnects and destroys the player, and runs internal cleanup."""
|
||||||
try:
|
try:
|
||||||
await self.disconnect()
|
await self.disconnect()
|
||||||
|
|
@ -332,7 +484,15 @@ class Player(VoiceProtocol):
|
||||||
assert self.channel is None and not self.is_connected
|
assert self.channel is None and not self.is_connected
|
||||||
|
|
||||||
self._node._players.pop(self.guild.id)
|
self._node._players.pop(self.guild.id)
|
||||||
await self._node.send(method="DELETE", path=self._player_endpoint_uri, guild_id=self._guild.id)
|
if self.node.is_connected:
|
||||||
|
await self._node.send(
|
||||||
|
method="DELETE",
|
||||||
|
path=self._player_endpoint_uri,
|
||||||
|
guild_id=self._guild.id,
|
||||||
|
)
|
||||||
|
|
||||||
|
if self._log:
|
||||||
|
self._log.debug("Player has been destroyed.")
|
||||||
|
|
||||||
async def play(
|
async def play(
|
||||||
self,
|
self,
|
||||||
|
|
@ -340,33 +500,45 @@ class Player(VoiceProtocol):
|
||||||
*,
|
*,
|
||||||
start: int = 0,
|
start: int = 0,
|
||||||
end: int = 0,
|
end: int = 0,
|
||||||
ignore_if_playing: bool = False
|
ignore_if_playing: bool = False,
|
||||||
) -> Track:
|
) -> Track:
|
||||||
"""Plays a track. If a Spotify track is passed in, it will be handled accordingly."""
|
"""Plays a track. If a Spotify track is passed in, it will be handled accordingly."""
|
||||||
|
|
||||||
|
if not track._search_type:
|
||||||
|
track.original = track
|
||||||
|
|
||||||
# Make sure we've never searched the track before
|
# Make sure we've never searched the track before
|
||||||
if track.original is None:
|
if track._search_type and track.original is None:
|
||||||
# First lets try using the tracks ISRC, every track has one (hopefully)
|
# First lets try using the tracks ISRC, every track has one (hopefully)
|
||||||
try:
|
try:
|
||||||
if not track.isrc:
|
if not track.isrc:
|
||||||
# We have to bare raise here because theres no other way to skip this block feasibly
|
# We have to bare raise here because theres no other way to skip this block feasibly
|
||||||
raise
|
raise
|
||||||
search: Track = (await self._node.get_tracks(
|
search = (
|
||||||
f"{track._search_type}:{track.isrc}", ctx=track.ctx))[0]
|
await self._node.get_tracks(f"{track._search_type}:{track.isrc}", ctx=track.ctx)
|
||||||
|
)[
|
||||||
|
0
|
||||||
|
] # type: ignore
|
||||||
except Exception:
|
except Exception:
|
||||||
# First method didn't work, lets try just searching it up
|
# First method didn't work, lets try just searching it up
|
||||||
try:
|
try:
|
||||||
search: Track = (await self._node.get_tracks(
|
search = (
|
||||||
f"{track._search_type}:{track.title} - {track.author}", ctx=track.ctx))[0]
|
await self._node.get_tracks(
|
||||||
|
f"{track._search_type}:{track.title} - {track.author}",
|
||||||
|
ctx=track.ctx,
|
||||||
|
)
|
||||||
|
)[
|
||||||
|
0
|
||||||
|
] # type: ignore
|
||||||
except:
|
except:
|
||||||
# The song wasn't able to be found, raise error
|
# The song wasn't able to be found, raise error
|
||||||
raise TrackLoadError (
|
raise TrackLoadError(
|
||||||
"No equivalent track was able to be found."
|
"No equivalent track was able to be found.",
|
||||||
)
|
)
|
||||||
data = {
|
data = {
|
||||||
"encodedTrack": search.track_id,
|
"encodedTrack": search.track_id,
|
||||||
"position": str(start),
|
"position": str(start),
|
||||||
"endTime": str(end)
|
"endTime": self._adjust_end_time(),
|
||||||
}
|
}
|
||||||
track.original = search
|
track.original = search
|
||||||
track.track_id = search.track_id
|
track.track_id = search.track_id
|
||||||
|
|
@ -375,10 +547,9 @@ class Player(VoiceProtocol):
|
||||||
data = {
|
data = {
|
||||||
"encodedTrack": track.track_id,
|
"encodedTrack": track.track_id,
|
||||||
"position": str(start),
|
"position": str(start),
|
||||||
"endTime": str(end)
|
"endTime": self._adjust_end_time(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
# Lets set the current track before we play it so any
|
# Lets set the current track before we play it so any
|
||||||
# corresponding events can capture it correctly
|
# corresponding events can capture it correctly
|
||||||
|
|
||||||
|
|
@ -399,7 +570,12 @@ class Player(VoiceProtocol):
|
||||||
if track.filters and not self.filters.has_global:
|
if track.filters and not self.filters.has_global:
|
||||||
# Now apply all filters
|
# Now apply all filters
|
||||||
for filter in track.filters:
|
for filter in track.filters:
|
||||||
await self.add_filter(filter=filter)
|
await self.add_filter(_filter=filter)
|
||||||
|
|
||||||
|
# Lavalink v3.7.5 changed the way the end time parameter works
|
||||||
|
# so now the end time cannot be zero.
|
||||||
|
# If it isnt zero, it'll be set to None.
|
||||||
|
# Otherwise, it'll be set here:
|
||||||
|
|
||||||
if end > 0:
|
if end > 0:
|
||||||
data["endTime"] = str(end)
|
data["endTime"] = str(end)
|
||||||
|
|
@ -409,25 +585,36 @@ class Player(VoiceProtocol):
|
||||||
path=self._player_endpoint_uri,
|
path=self._player_endpoint_uri,
|
||||||
guild_id=self._guild.id,
|
guild_id=self._guild.id,
|
||||||
data=data,
|
data=data,
|
||||||
query=f"noReplace={ignore_if_playing}"
|
query=f"noReplace={ignore_if_playing}",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if self._log:
|
||||||
|
self._log.debug(
|
||||||
|
f"Playing {track.title} from uri {track.uri} with a length of {track.length}",
|
||||||
|
)
|
||||||
|
|
||||||
return self._current
|
return self._current
|
||||||
|
|
||||||
async def seek(self, position: float) -> float:
|
async def seek(self, position: float) -> float:
|
||||||
"""Seeks to a position in the currently playing track milliseconds"""
|
"""Seeks to a position in the currently playing track milliseconds"""
|
||||||
|
if not self._current or not self._current.original:
|
||||||
|
return 0.0
|
||||||
|
|
||||||
if position < 0 or position > self._current.original.length:
|
if position < 0 or position > self._current.original.length:
|
||||||
raise TrackInvalidPosition(
|
raise TrackInvalidPosition(
|
||||||
"Seek position must be between 0 and the track length"
|
"Seek position must be between 0 and the track length",
|
||||||
)
|
)
|
||||||
|
|
||||||
await self._node.send(
|
await self._node.send(
|
||||||
method="PATCH",
|
method="PATCH",
|
||||||
path=self._player_endpoint_uri,
|
path=self._player_endpoint_uri,
|
||||||
guild_id=self._guild.id,
|
guild_id=self._guild.id,
|
||||||
data={"position": position}
|
data={"position": position},
|
||||||
)
|
)
|
||||||
return self._position
|
|
||||||
|
if self._log:
|
||||||
|
self._log.debug(f"Seeking to {position}.")
|
||||||
|
return self.position
|
||||||
|
|
||||||
async def set_pause(self, pause: bool) -> bool:
|
async def set_pause(self, pause: bool) -> bool:
|
||||||
"""Sets the pause state of the currently playing track."""
|
"""Sets the pause state of the currently playing track."""
|
||||||
|
|
@ -435,9 +622,12 @@ class Player(VoiceProtocol):
|
||||||
method="PATCH",
|
method="PATCH",
|
||||||
path=self._player_endpoint_uri,
|
path=self._player_endpoint_uri,
|
||||||
guild_id=self._guild.id,
|
guild_id=self._guild.id,
|
||||||
data={"paused": pause}
|
data={"paused": pause},
|
||||||
)
|
)
|
||||||
self._paused = pause
|
self._paused = pause
|
||||||
|
|
||||||
|
if self._log:
|
||||||
|
self._log.debug(f"Player has been {'paused' if pause else 'resumed'}.")
|
||||||
return self._paused
|
return self._paused
|
||||||
|
|
||||||
async def set_volume(self, volume: int) -> int:
|
async def set_volume(self, volume: int) -> int:
|
||||||
|
|
@ -446,38 +636,55 @@ class Player(VoiceProtocol):
|
||||||
method="PATCH",
|
method="PATCH",
|
||||||
path=self._player_endpoint_uri,
|
path=self._player_endpoint_uri,
|
||||||
guild_id=self._guild.id,
|
guild_id=self._guild.id,
|
||||||
data={"volume": volume}
|
data={"volume": volume},
|
||||||
)
|
)
|
||||||
self._volume = volume
|
self._volume = volume
|
||||||
|
|
||||||
|
if self._log:
|
||||||
|
self._log.debug(f"Player volume has been adjusted to {volume}")
|
||||||
return self._volume
|
return self._volume
|
||||||
|
|
||||||
async def add_filter(self, filter: Filter, fast_apply: bool = False) -> Filter:
|
async def move_to(self, channel: VoiceChannel) -> None:
|
||||||
"""Adds a filter to the player. Takes a pomice.Filter object.
|
"""Moves the player to a new voice channel."""
|
||||||
This will only work if you are using a version of Lavalink that supports filters.
|
|
||||||
If you would like for the filter to apply instantly, set the `fast_apply` arg to `True`.
|
|
||||||
|
|
||||||
(You must have a song playing in order for `fast_apply` to work.)
|
await self.guild.change_voice_state(channel=channel)
|
||||||
|
|
||||||
|
self.channel = channel
|
||||||
|
|
||||||
|
await self._dispatch_voice_update()
|
||||||
|
|
||||||
|
async def add_filter(self, _filter: Filter, fast_apply: bool = False) -> Filters:
|
||||||
|
"""Adds a filter to the player. Takes a pomice.Filter object.
|
||||||
|
This will only work if you are using a version of Lavalink that supports filters.
|
||||||
|
If you would like for the filter to apply instantly, set the `fast_apply` arg to `True`.
|
||||||
|
|
||||||
|
(You must have a song playing in order for `fast_apply` to work.)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self._filters.add_filter(filter=filter)
|
self._filters.add_filter(filter=_filter)
|
||||||
payload = self._filters.get_all_payloads()
|
payload = self._filters.get_all_payloads()
|
||||||
await self._node.send(
|
await self._node.send(
|
||||||
method="PATCH",
|
method="PATCH",
|
||||||
path=self._player_endpoint_uri,
|
path=self._player_endpoint_uri,
|
||||||
guild_id=self._guild.id,
|
guild_id=self._guild.id,
|
||||||
data={"filters": payload}
|
data={"filters": payload},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if self._log:
|
||||||
|
self._log.debug(f"Filter has been applied to player with tag {_filter.tag}")
|
||||||
if fast_apply:
|
if fast_apply:
|
||||||
|
if self._log:
|
||||||
|
self._log.debug(f"Fast apply passed, now applying filter instantly.")
|
||||||
await self.seek(self.position)
|
await self.seek(self.position)
|
||||||
|
|
||||||
return self._filters
|
return self._filters
|
||||||
|
|
||||||
async def remove_filter(self, filter_tag: str, fast_apply: bool = False) -> Filter:
|
async def remove_filter(self, filter_tag: str, fast_apply: bool = False) -> Filters:
|
||||||
"""Removes a filter from the player. Takes a filter tag.
|
"""Removes a filter from the player. Takes a filter tag.
|
||||||
This will only work if you are using a version of Lavalink that supports filters.
|
This will only work if you are using a version of Lavalink that supports filters.
|
||||||
If you would like for the filter to apply instantly, set the `fast_apply` arg to `True`.
|
If you would like for the filter to apply instantly, set the `fast_apply` arg to `True`.
|
||||||
|
|
||||||
(You must have a song playing in order for `fast_apply` to work.)
|
(You must have a song playing in order for `fast_apply` to work.)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self._filters.remove_filter(filter_tag=filter_tag)
|
self._filters.remove_filter(filter_tag=filter_tag)
|
||||||
|
|
@ -486,35 +693,73 @@ class Player(VoiceProtocol):
|
||||||
method="PATCH",
|
method="PATCH",
|
||||||
path=self._player_endpoint_uri,
|
path=self._player_endpoint_uri,
|
||||||
guild_id=self._guild.id,
|
guild_id=self._guild.id,
|
||||||
data={"filters": payload}
|
data={"filters": payload},
|
||||||
)
|
)
|
||||||
|
if self._log:
|
||||||
|
self._log.debug(f"Filter has been removed from player with tag {filter_tag}")
|
||||||
if fast_apply:
|
if fast_apply:
|
||||||
|
if self._log:
|
||||||
|
self._log.debug(f"Fast apply passed, now removing filter instantly.")
|
||||||
await self.seek(self.position)
|
await self.seek(self.position)
|
||||||
|
|
||||||
return self._filters
|
return self._filters
|
||||||
|
|
||||||
async def reset_filters(self, *, fast_apply: bool = False):
|
async def edit_filter(
|
||||||
"""Resets all currently applied filters to their default parameters.
|
self,
|
||||||
You must have filters applied in order for this to work.
|
*,
|
||||||
If you would like the filters to be removed instantly, set the `fast_apply` arg to `True`.
|
filter_tag: str,
|
||||||
|
edited_filter: Filter,
|
||||||
|
fast_apply: bool = False,
|
||||||
|
) -> Filters:
|
||||||
|
"""Edits a filter from the player using its filter tag and a new filter of the same type.
|
||||||
|
The filter to be replaced must have the same tag as the one you are replacing it with.
|
||||||
|
This will only work if you are using a version of Lavalink that supports filters.
|
||||||
|
|
||||||
(You must have a song playing in order for `fast_apply` to work.)
|
If you would like for the filter to apply instantly, set the `fast_apply` arg to `True`.
|
||||||
|
|
||||||
|
(You must have a song playing in order for `fast_apply` to work.)
|
||||||
|
"""
|
||||||
|
|
||||||
|
self._filters.edit_filter(filter_tag=filter_tag, to_apply=edited_filter)
|
||||||
|
payload = self._filters.get_all_payloads()
|
||||||
|
await self._node.send(
|
||||||
|
method="PATCH",
|
||||||
|
path=self._player_endpoint_uri,
|
||||||
|
guild_id=self._guild.id,
|
||||||
|
data={"filters": payload},
|
||||||
|
)
|
||||||
|
if self._log:
|
||||||
|
self._log.debug(f"Filter with tag {filter_tag} has been edited to {edited_filter!r}")
|
||||||
|
if fast_apply:
|
||||||
|
if self._log:
|
||||||
|
self._log.debug(f"Fast apply passed, now editing filter instantly.")
|
||||||
|
await self.seek(self.position)
|
||||||
|
|
||||||
|
return self._filters
|
||||||
|
|
||||||
|
async def reset_filters(self, *, fast_apply: bool = False) -> None:
|
||||||
|
"""Resets all currently applied filters to their default parameters.
|
||||||
|
You must have filters applied in order for this to work.
|
||||||
|
If you would like the filters to be removed instantly, set the `fast_apply` arg to `True`.
|
||||||
|
|
||||||
|
(You must have a song playing in order for `fast_apply` to work.)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if not self._filters:
|
if not self._filters:
|
||||||
raise FilterInvalidArgument(
|
raise FilterInvalidArgument(
|
||||||
"You must have filters applied first in order to use this method."
|
"You must have filters applied first in order to use this method.",
|
||||||
)
|
)
|
||||||
self._filters.reset_filters()
|
self._filters.reset_filters()
|
||||||
await self._node.send(
|
await self._node.send(
|
||||||
method="PATCH",
|
method="PATCH",
|
||||||
path=self._player_endpoint_uri,
|
path=self._player_endpoint_uri,
|
||||||
guild_id=self._guild.id,
|
guild_id=self._guild.id,
|
||||||
data={"filters": {}}
|
data={"filters": {}},
|
||||||
)
|
)
|
||||||
|
if self._log:
|
||||||
|
self._log.debug(f"All filters have been removed from player.")
|
||||||
|
|
||||||
if fast_apply:
|
if fast_apply:
|
||||||
|
if self._log:
|
||||||
|
self._log.debug(f"Fast apply passed, now removing all filters instantly.")
|
||||||
await self.seek(self.position)
|
await self.seek(self.position)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
1015
pomice/pool.py
1015
pomice/pool.py
File diff suppressed because it is too large
Load Diff
112
pomice/queue.py
112
pomice/queue.py
|
|
@ -1,21 +1,33 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import random
|
import random
|
||||||
from copy import copy
|
from copy import copy
|
||||||
from typing import (
|
from typing import Iterable
|
||||||
Iterable,
|
from typing import Iterator
|
||||||
Iterator,
|
from typing import List
|
||||||
List,
|
from typing import Optional
|
||||||
Optional,
|
from typing import Union
|
||||||
Union,
|
|
||||||
)
|
|
||||||
|
|
||||||
from .objects import Track
|
|
||||||
from .enums import LoopMode
|
from .enums import LoopMode
|
||||||
from .exceptions import QueueEmpty, QueueException, QueueFull
|
from .exceptions import QueueEmpty
|
||||||
|
from .exceptions import QueueException
|
||||||
|
from .exceptions import QueueFull
|
||||||
|
from .objects import Track
|
||||||
|
|
||||||
|
__all__ = ("Queue",)
|
||||||
|
|
||||||
|
|
||||||
class Queue(Iterable[Track]):
|
class Queue(Iterable[Track]):
|
||||||
"""Queue for Pomice. This queue takes pomice.Track as an input and includes looping and shuffling."""
|
"""Queue for Pomice. This queue takes pomice.Track as an input and includes looping and shuffling."""
|
||||||
|
|
||||||
|
__slots__ = (
|
||||||
|
"max_size",
|
||||||
|
"_queue",
|
||||||
|
"_overflow",
|
||||||
|
"_loop_mode",
|
||||||
|
"_current_item",
|
||||||
|
)
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
max_size: Optional[int] = None,
|
max_size: Optional[int] = None,
|
||||||
|
|
@ -23,10 +35,10 @@ class Queue(Iterable[Track]):
|
||||||
overflow: bool = True,
|
overflow: bool = True,
|
||||||
):
|
):
|
||||||
self.max_size: Optional[int] = max_size
|
self.max_size: Optional[int] = max_size
|
||||||
self._queue: List[Track] = [] # type: ignore
|
self._current_item: Track
|
||||||
|
self._queue: List[Track] = []
|
||||||
self._overflow: bool = overflow
|
self._overflow: bool = overflow
|
||||||
self._loop_mode: Optional[LoopMode] = None
|
self._loop_mode: Optional[LoopMode] = None
|
||||||
self._current_item: Optional[Track] = None
|
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
"""String showing all Track objects appearing as a list."""
|
"""String showing all Track objects appearing as a list."""
|
||||||
|
|
@ -34,9 +46,7 @@ class Queue(Iterable[Track]):
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
"""Official representation with max_size and member count."""
|
"""Official representation with max_size and member count."""
|
||||||
return (
|
return f"<{self.__class__.__name__} max_size={self.max_size} members={self.count}>"
|
||||||
f"<{self.__class__.__name__} max_size={self.max_size} members={self.count}>"
|
|
||||||
)
|
|
||||||
|
|
||||||
def __bool__(self) -> bool:
|
def __bool__(self) -> bool:
|
||||||
"""Treats the queue as a bool, with it evaluating True when it contains members."""
|
"""Treats the queue as a bool, with it evaluating True when it contains members."""
|
||||||
|
|
@ -59,7 +69,7 @@ class Queue(Iterable[Track]):
|
||||||
|
|
||||||
return self._queue[index]
|
return self._queue[index]
|
||||||
|
|
||||||
def __setitem__(self, index: int, item: Track):
|
def __setitem__(self, index: int, item: Track) -> None:
|
||||||
"""Inserts an item at given position."""
|
"""Inserts an item at given position."""
|
||||||
if not isinstance(index, int):
|
if not isinstance(index, int):
|
||||||
raise ValueError("'int' type required.'")
|
raise ValueError("'int' type required.'")
|
||||||
|
|
@ -89,7 +99,9 @@ class Queue(Iterable[Track]):
|
||||||
The new queue will have the same max_size as the original.
|
The new queue will have the same max_size as the original.
|
||||||
"""
|
"""
|
||||||
if not isinstance(other, Iterable):
|
if not isinstance(other, Iterable):
|
||||||
raise TypeError(f"Adding with the '{type(other)}' type is not supported.")
|
raise TypeError(
|
||||||
|
f"Adding with the '{type(other)}' type is not supported.",
|
||||||
|
)
|
||||||
|
|
||||||
new_queue = self.copy()
|
new_queue = self.copy()
|
||||||
new_queue.extend(other)
|
new_queue.extend(other)
|
||||||
|
|
@ -105,7 +117,9 @@ class Queue(Iterable[Track]):
|
||||||
self.extend(other)
|
self.extend(other)
|
||||||
return self
|
return self
|
||||||
|
|
||||||
raise TypeError(f"Adding '{type(other)}' type to the queue is not supported.")
|
raise TypeError(
|
||||||
|
f"Adding '{type(other)}' type to the queue is not supported.",
|
||||||
|
)
|
||||||
|
|
||||||
def _get(self) -> Track:
|
def _get(self) -> Track:
|
||||||
return self._queue.pop(0)
|
return self._queue.pop(0)
|
||||||
|
|
@ -116,7 +130,6 @@ class Queue(Iterable[Track]):
|
||||||
def _index(self, item: Track) -> int:
|
def _index(self, item: Track) -> int:
|
||||||
return self._queue.index(item)
|
return self._queue.index(item)
|
||||||
|
|
||||||
|
|
||||||
def _put(self, item: Track) -> None:
|
def _put(self, item: Track) -> None:
|
||||||
self._queue.append(item)
|
self._queue.append(item)
|
||||||
|
|
||||||
|
|
@ -165,7 +178,7 @@ class Queue(Iterable[Track]):
|
||||||
return bool(self._loop_mode)
|
return bool(self._loop_mode)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def loop_mode(self) -> LoopMode:
|
def loop_mode(self) -> Optional[LoopMode]:
|
||||||
"""Returns the LoopMode enum set in the queue object"""
|
"""Returns the LoopMode enum set in the queue object"""
|
||||||
return self._loop_mode
|
return self._loop_mode
|
||||||
|
|
||||||
|
|
@ -174,14 +187,11 @@ class Queue(Iterable[Track]):
|
||||||
"""Returns the amount of items in the queue"""
|
"""Returns the amount of items in the queue"""
|
||||||
return len(self._queue)
|
return len(self._queue)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def get_queue(self) -> List:
|
def get_queue(self) -> List:
|
||||||
"""Returns the queue as a List"""
|
"""Returns the queue as a List"""
|
||||||
return self._queue
|
return self._queue
|
||||||
|
|
||||||
|
def get(self) -> Track:
|
||||||
def get(self):
|
|
||||||
"""Return next immediately available item in queue if any.
|
"""Return next immediately available item in queue if any.
|
||||||
Raises QueueEmpty if no items in queue.
|
Raises QueueEmpty if no items in queue.
|
||||||
"""
|
"""
|
||||||
|
|
@ -193,10 +203,13 @@ class Queue(Iterable[Track]):
|
||||||
raise QueueEmpty("No items in the queue.")
|
raise QueueEmpty("No items in the queue.")
|
||||||
|
|
||||||
if self._loop_mode == LoopMode.QUEUE:
|
if self._loop_mode == LoopMode.QUEUE:
|
||||||
|
# set current item to first track in queue if not set already
|
||||||
# recurse if the item isnt in the queue
|
# otherwise exception will be raised
|
||||||
if self._current_item not in self._queue:
|
if not self._current_item or self._current_item not in self._queue:
|
||||||
self.get()
|
if self._queue:
|
||||||
|
item = self._queue[0]
|
||||||
|
else:
|
||||||
|
raise QueueEmpty("No items in the queue.")
|
||||||
|
|
||||||
# set current item to first track in queue if not set already
|
# set current item to first track in queue if not set already
|
||||||
if not self._current_item:
|
if not self._current_item:
|
||||||
|
|
@ -233,7 +246,6 @@ class Queue(Iterable[Track]):
|
||||||
"""
|
"""
|
||||||
return self._remove(self._check_track(item))
|
return self._remove(self._check_track(item))
|
||||||
|
|
||||||
|
|
||||||
def find_position(self, item: Track) -> int:
|
def find_position(self, item: Track) -> int:
|
||||||
"""Find the position a given item within the queue.
|
"""Find the position a given item within the queue.
|
||||||
Raises ValueError if item is not in queue.
|
Raises ValueError if item is not in queue.
|
||||||
|
|
@ -244,7 +256,9 @@ class Queue(Iterable[Track]):
|
||||||
"""Put the given item into the back of the queue."""
|
"""Put the given item into the back of the queue."""
|
||||||
if self.is_full:
|
if self.is_full:
|
||||||
if not self._overflow:
|
if not self._overflow:
|
||||||
raise QueueFull(f"Queue max_size of {self.max_size} has been reached.")
|
raise QueueFull(
|
||||||
|
f"Queue max_size of {self.max_size} has been reached.",
|
||||||
|
)
|
||||||
|
|
||||||
self._drop()
|
self._drop()
|
||||||
|
|
||||||
|
|
@ -254,7 +268,9 @@ class Queue(Iterable[Track]):
|
||||||
"""Put the given item into the queue at the specified index."""
|
"""Put the given item into the queue at the specified index."""
|
||||||
if self.is_full:
|
if self.is_full:
|
||||||
if not self._overflow:
|
if not self._overflow:
|
||||||
raise QueueFull(f"Queue max_size of {self.max_size} has been reached.")
|
raise QueueFull(
|
||||||
|
f"Queue max_size of {self.max_size} has been reached.",
|
||||||
|
)
|
||||||
|
|
||||||
self._drop()
|
self._drop()
|
||||||
|
|
||||||
|
|
@ -280,7 +296,7 @@ class Queue(Iterable[Track]):
|
||||||
if (new_len + self.count) > self.max_size:
|
if (new_len + self.count) > self.max_size:
|
||||||
raise QueueFull(
|
raise QueueFull(
|
||||||
f"Queue has {self.count}/{self.max_size} items, "
|
f"Queue has {self.count}/{self.max_size} items, "
|
||||||
f"cannot add {new_len} more."
|
f"cannot add {new_len} more.",
|
||||||
)
|
)
|
||||||
|
|
||||||
for item in iterable:
|
for item in iterable:
|
||||||
|
|
@ -297,7 +313,7 @@ class Queue(Iterable[Track]):
|
||||||
"""Remove all items from the queue."""
|
"""Remove all items from the queue."""
|
||||||
self._queue.clear()
|
self._queue.clear()
|
||||||
|
|
||||||
def set_loop_mode(self, mode: LoopMode):
|
def set_loop_mode(self, mode: LoopMode) -> None:
|
||||||
"""
|
"""
|
||||||
Sets the loop mode of the queue.
|
Sets the loop mode of the queue.
|
||||||
Takes the LoopMode enum as an argument.
|
Takes the LoopMode enum as an argument.
|
||||||
|
|
@ -312,8 +328,7 @@ class Queue(Iterable[Track]):
|
||||||
self._queue.insert(index, self._current_item)
|
self._queue.insert(index, self._current_item)
|
||||||
self._current_item = self._queue[index]
|
self._current_item = self._queue[index]
|
||||||
|
|
||||||
|
def disable_loop(self) -> None:
|
||||||
def disable_loop(self):
|
|
||||||
"""
|
"""
|
||||||
Disables loop mode if set.
|
Disables loop mode if set.
|
||||||
Raises QueueException if loop mode is already None.
|
Raises QueueException if loop mode is already None.
|
||||||
|
|
@ -327,18 +342,33 @@ class Queue(Iterable[Track]):
|
||||||
|
|
||||||
self._loop_mode = None
|
self._loop_mode = None
|
||||||
|
|
||||||
|
def shuffle(self) -> None:
|
||||||
def shuffle(self):
|
|
||||||
"""Shuffles the queue."""
|
"""Shuffles the queue."""
|
||||||
return random.shuffle(self._queue)
|
return random.shuffle(self._queue)
|
||||||
|
|
||||||
def clear_track_filters(self):
|
def clear_track_filters(self) -> None:
|
||||||
"""Clears all filters applied to tracks"""
|
"""Clears all filters applied to tracks"""
|
||||||
for track in self._queue:
|
for track in self._queue:
|
||||||
track.filters = None
|
track.filters = None
|
||||||
|
|
||||||
def jump(self, item: Track):
|
def jump(self, item: Track) -> None:
|
||||||
"""Returns a new queue with the specified track at the beginning."""
|
"""
|
||||||
|
Jumps to the item specified in the queue.
|
||||||
|
|
||||||
|
If the queue is not looping, the queue will be mutated.
|
||||||
|
Otherwise, the current item will be adjusted to the item
|
||||||
|
before the specified track.
|
||||||
|
|
||||||
|
The queue is adjusted so that the next item that is retrieved
|
||||||
|
is the track that is specified, effectively 'jumping' the queue.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if self._loop_mode == LoopMode.TRACK:
|
||||||
|
raise QueueException("Jumping the queue whilst looping a track is not allowed.")
|
||||||
|
|
||||||
index = self.find_position(item)
|
index = self.find_position(item)
|
||||||
new_queue = self._queue[index:self.size]
|
if self._loop_mode == LoopMode.QUEUE:
|
||||||
self._queue = new_queue
|
self._current_item = self._queue[index - 1]
|
||||||
|
else:
|
||||||
|
new_queue = self._queue[index : self.size]
|
||||||
|
self._queue = new_queue
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,15 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from .pool import Node
|
from .pool import Node
|
||||||
|
|
||||||
from .utils import RouteStats
|
from .utils import RouteStats
|
||||||
|
|
||||||
|
__all__ = ("RoutePlanner",)
|
||||||
|
|
||||||
|
|
||||||
class RoutePlanner:
|
class RoutePlanner:
|
||||||
"""
|
"""
|
||||||
The base route planner class for Pomice.
|
The base route planner class for Pomice.
|
||||||
|
|
@ -12,19 +17,17 @@ class RoutePlanner:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, node: Node) -> None:
|
def __init__(self, node: Node) -> None:
|
||||||
self.node = node
|
self.node: Node = node
|
||||||
self.session = node._session
|
|
||||||
|
|
||||||
async def get_status(self):
|
async def get_status(self) -> RouteStats:
|
||||||
"""Gets the status of the route planner API."""
|
"""Gets the status of the route planner API."""
|
||||||
data: dict = await self.node.send(method="GET", path="routeplanner/status")
|
data: dict = await self.node.send(method="GET", path="routeplanner/status")
|
||||||
return RouteStats(data)
|
return RouteStats(data)
|
||||||
|
|
||||||
|
async def free_address(self, ip: str) -> None:
|
||||||
async def free_address(self, ip: str):
|
|
||||||
"""Frees an address using the route planner API"""
|
"""Frees an address using the route planner API"""
|
||||||
await self.node.send(method="POST", path="routeplanner/free/address", data={"address": ip})
|
await self.node.send(method="POST", path="routeplanner/free/address", data={"address": ip})
|
||||||
|
|
||||||
async def free_all_addresses(self):
|
async def free_all_addresses(self) -> None:
|
||||||
"""Frees all available addresses using the route planner api"""
|
"""Frees all available addresses using the route planner api"""
|
||||||
await self.node.send(method="POST", path="routeplanner/free/address/all")
|
await self.node.send(method="POST", path="routeplanner/free/address/all")
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
"""Spotify module for Pomice, made possible by cloudwithax 2023"""
|
"""Spotify module for Pomice, made possible by cloudwithax 2023"""
|
||||||
|
from .client import Client
|
||||||
from .exceptions import *
|
from .exceptions import *
|
||||||
from .objects import *
|
from .objects import *
|
||||||
from .client import Client
|
|
||||||
|
|
|
||||||
|
|
@ -1,141 +1,360 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
import logging
|
||||||
import re
|
import re
|
||||||
import time
|
import time
|
||||||
from base64 import b64encode
|
from base64 import b64encode
|
||||||
|
from typing import AsyncGenerator
|
||||||
|
from typing import Dict
|
||||||
|
from typing import List
|
||||||
|
from typing import Optional
|
||||||
|
from typing import Union
|
||||||
|
from urllib.parse import quote
|
||||||
|
|
||||||
import aiohttp
|
import aiohttp
|
||||||
import orjson as json
|
import orjson as json
|
||||||
|
|
||||||
|
from .exceptions import InvalidSpotifyURL
|
||||||
from .exceptions import InvalidSpotifyURL, SpotifyRequestException
|
from .exceptions import SpotifyRequestException
|
||||||
from .objects import *
|
from .objects import *
|
||||||
|
|
||||||
|
__all__ = ("Client",)
|
||||||
|
|
||||||
|
|
||||||
GRANT_URL = "https://accounts.spotify.com/api/token"
|
GRANT_URL = "https://accounts.spotify.com/api/token"
|
||||||
REQUEST_URL = "https://api.spotify.com/v1/{type}s/{id}"
|
REQUEST_URL = "https://api.spotify.com/v1/{type}s/{id}"
|
||||||
|
# Keep this in sync with URLRegex.SPOTIFY_URL (enums.py). Accept intl locale segment,
|
||||||
|
# optional trailing slash, and query parameters.
|
||||||
SPOTIFY_URL_REGEX = re.compile(
|
SPOTIFY_URL_REGEX = re.compile(
|
||||||
r"https?://open.spotify.com/(?P<type>album|playlist|track|artist)/(?P<id>[a-zA-Z0-9]+)"
|
r"https?://open\.spotify\.com/(?:intl-[a-zA-Z-]+/)?(?P<type>album|playlist|track|artist)/(?P<id>[a-zA-Z0-9]+)(?:/)?(?:\?.*)?$",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class Client:
|
class Client:
|
||||||
"""The base client for the Spotify module of Pomice.
|
"""The base client for the Spotify module of Pomice.
|
||||||
This class will do all the heavy lifting of getting all the metadata
|
This class will do all the heavy lifting of getting all the metadata
|
||||||
for any Spotify URL you throw at it.
|
for any Spotify URL you throw at it.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, client_id: str, client_secret: str) -> None:
|
def __init__(
|
||||||
|
self,
|
||||||
|
client_id: str,
|
||||||
|
client_secret: str,
|
||||||
|
*,
|
||||||
|
playlist_concurrency: int = 10,
|
||||||
|
playlist_page_limit: Optional[int] = None,
|
||||||
|
) -> None:
|
||||||
self._client_id = client_id
|
self._client_id = client_id
|
||||||
self._client_secret = client_secret
|
self._client_secret = client_secret
|
||||||
|
|
||||||
self.session = aiohttp.ClientSession()
|
# HTTP session will be injected by Node
|
||||||
|
self.session: Optional[aiohttp.ClientSession] = None
|
||||||
|
|
||||||
self._bearer_token: str = None
|
self._bearer_token: Optional[str] = None
|
||||||
self._expiry = 0
|
self._expiry: float = 0.0
|
||||||
self._auth_token = b64encode(f"{self._client_id}:{self._client_secret}".encode())
|
self._auth_token = b64encode(f"{self._client_id}:{self._client_secret}".encode())
|
||||||
self._grant_headers = {"Authorization": f"Basic {self._auth_token.decode()}"}
|
self._grant_headers = {"Authorization": f"Basic {self._auth_token.decode()}"}
|
||||||
self._bearer_headers = None
|
self._bearer_headers: Optional[Dict] = None
|
||||||
|
self._log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
# Performance tuning knobs
|
||||||
|
self._playlist_concurrency = max(1, playlist_concurrency)
|
||||||
|
self._playlist_page_limit = playlist_page_limit
|
||||||
|
|
||||||
|
async def _set_session(self, session: aiohttp.ClientSession) -> None:
|
||||||
|
self.session = session
|
||||||
|
|
||||||
async def _fetch_bearer_token(self) -> None:
|
async def _fetch_bearer_token(self) -> None:
|
||||||
_data = {"grant_type": "client_credentials"}
|
_data = {"grant_type": "client_credentials"}
|
||||||
|
|
||||||
async with self.session.post(GRANT_URL, data=_data, headers=self._grant_headers) as resp:
|
if not self.session:
|
||||||
if resp.status != 200:
|
raise SpotifyRequestException("HTTP session not initialized for Spotify client.")
|
||||||
raise SpotifyRequestException(
|
resp = await self.session.post(GRANT_URL, data=_data, headers=self._grant_headers)
|
||||||
f"Error fetching bearer token: {resp.status} {resp.reason}"
|
|
||||||
)
|
|
||||||
|
|
||||||
data: dict = await resp.json(loads=json.loads)
|
if resp.status != 200:
|
||||||
|
raise SpotifyRequestException(
|
||||||
|
f"Error fetching bearer token: {resp.status} {resp.reason}",
|
||||||
|
)
|
||||||
|
|
||||||
|
data: dict = await resp.json(loads=json.loads)
|
||||||
|
if self._log:
|
||||||
|
self._log.debug(f"Fetched Spotify bearer token successfully")
|
||||||
|
|
||||||
self._bearer_token = data["access_token"]
|
self._bearer_token = data["access_token"]
|
||||||
self._expiry = time.time() + (int(data["expires_in"]) - 10)
|
self._expiry = time.time() + (int(data["expires_in"]) - 10)
|
||||||
self._bearer_headers = {"Authorization": f"Bearer {self._bearer_token}"}
|
self._bearer_headers = {
|
||||||
|
"Authorization": f"Bearer {self._bearer_token}",
|
||||||
|
}
|
||||||
|
|
||||||
async def search(self, *, query: str):
|
async def search(self, *, query: str) -> Union[Track, Album, Artist, Playlist]:
|
||||||
if not self._bearer_token or time.time() >= self._expiry:
|
if not self._bearer_token or time.time() >= self._expiry:
|
||||||
await self._fetch_bearer_token()
|
await self._fetch_bearer_token()
|
||||||
|
|
||||||
result = SPOTIFY_URL_REGEX.match(query)
|
result = SPOTIFY_URL_REGEX.match(query)
|
||||||
spotify_type = result.group("type")
|
|
||||||
spotify_id = result.group("id")
|
|
||||||
|
|
||||||
if not result:
|
if not result:
|
||||||
raise InvalidSpotifyURL("The Spotify link provided is not valid.")
|
raise InvalidSpotifyURL("The Spotify link provided is not valid.")
|
||||||
|
|
||||||
|
spotify_type = result.group("type")
|
||||||
|
spotify_id = result.group("id")
|
||||||
|
|
||||||
request_url = REQUEST_URL.format(type=spotify_type, id=spotify_id)
|
request_url = REQUEST_URL.format(type=spotify_type, id=spotify_id)
|
||||||
|
|
||||||
async with self.session.get(request_url, headers=self._bearer_headers) as resp:
|
if not self.session:
|
||||||
if resp.status != 200:
|
raise SpotifyRequestException("HTTP session not initialized for Spotify client.")
|
||||||
raise SpotifyRequestException(
|
resp = await self.session.get(request_url, headers=self._bearer_headers)
|
||||||
f"Error while fetching results: {resp.status} {resp.reason}"
|
if resp.status != 200:
|
||||||
)
|
raise SpotifyRequestException(
|
||||||
|
f"Error while fetching results: {resp.status} {resp.reason}",
|
||||||
|
)
|
||||||
|
|
||||||
data: dict = await resp.json(loads=json.loads)
|
data: dict = await resp.json(loads=json.loads)
|
||||||
|
if self._log:
|
||||||
|
self._log.debug(
|
||||||
|
f"Made request to Spotify API with status {resp.status} and response {data}",
|
||||||
|
)
|
||||||
|
|
||||||
if spotify_type == "track":
|
if spotify_type == "track":
|
||||||
return Track(data)
|
return Track(data)
|
||||||
elif spotify_type == "album":
|
elif spotify_type == "album":
|
||||||
return Album(data)
|
return Album(data)
|
||||||
elif spotify_type == "artist":
|
elif spotify_type == "artist":
|
||||||
async with self.session.get(f"{request_url}/top-tracks?market=US", headers=self._bearer_headers) as resp:
|
if not self.session:
|
||||||
if resp.status != 200:
|
raise SpotifyRequestException("HTTP session not initialized for Spotify client.")
|
||||||
raise SpotifyRequestException(
|
resp = await self.session.get(
|
||||||
f"Error while fetching results: {resp.status} {resp.reason}"
|
f"{request_url}/top-tracks?market=US",
|
||||||
)
|
headers=self._bearer_headers,
|
||||||
|
)
|
||||||
|
if resp.status != 200:
|
||||||
|
raise SpotifyRequestException(
|
||||||
|
f"Error while fetching results: {resp.status} {resp.reason}",
|
||||||
|
)
|
||||||
|
|
||||||
track_data: dict = await resp.json(loads=json.loads)
|
track_data: dict = await resp.json(loads=json.loads)
|
||||||
tracks = track_data['tracks']
|
tracks = track_data["tracks"]
|
||||||
return Artist(data, tracks)
|
return Artist(data, tracks)
|
||||||
else:
|
else:
|
||||||
|
# For playlists we optionally use a reduced fields payload to shrink response sizes.
|
||||||
|
# NB: We cannot apply fields filter to initial request because original metadata is needed.
|
||||||
tracks = [
|
tracks = [
|
||||||
Track(track["track"])
|
Track(track["track"])
|
||||||
for track in data["tracks"]["items"] if track["track"] is not None
|
for track in data["tracks"]["items"]
|
||||||
|
if track["track"] is not None
|
||||||
]
|
]
|
||||||
|
|
||||||
if not len(tracks):
|
if not tracks:
|
||||||
raise SpotifyRequestException("This playlist is empty and therefore cannot be queued.")
|
raise SpotifyRequestException(
|
||||||
|
"This playlist is empty and therefore cannot be queued.",
|
||||||
|
)
|
||||||
|
|
||||||
next_page_url = data["tracks"]["next"]
|
total_tracks = data["tracks"]["total"]
|
||||||
|
limit = data["tracks"]["limit"]
|
||||||
|
|
||||||
while next_page_url is not None:
|
# Short‑circuit small playlists (single page)
|
||||||
async with self.session.get(next_page_url, headers=self._bearer_headers) as resp:
|
if total_tracks <= limit:
|
||||||
if resp.status != 200:
|
return Playlist(data, tracks)
|
||||||
raise SpotifyRequestException(
|
|
||||||
f"Error while fetching results: {resp.status} {resp.reason}"
|
|
||||||
)
|
|
||||||
|
|
||||||
next_data: dict = await resp.json(loads=json.loads)
|
# Build remaining page URLs; Spotify supports offset-based pagination.
|
||||||
|
remaining_offsets = range(limit, total_tracks, limit)
|
||||||
|
page_urls: List[str] = []
|
||||||
|
fields_filter = (
|
||||||
|
"items(track(name,duration_ms,id,is_local,external_urls,external_ids,artists(name),album(images)))"
|
||||||
|
",next"
|
||||||
|
)
|
||||||
|
for idx, offset in enumerate(remaining_offsets):
|
||||||
|
if self._playlist_page_limit is not None and idx >= self._playlist_page_limit:
|
||||||
|
break
|
||||||
|
page_urls.append(
|
||||||
|
f"{request_url}/tracks?offset={offset}&limit={limit}&fields={quote(fields_filter)}",
|
||||||
|
)
|
||||||
|
|
||||||
tracks += [
|
if page_urls:
|
||||||
Track(track["track"])
|
semaphore = asyncio.Semaphore(self._playlist_concurrency)
|
||||||
for track in next_data["items"] if track["track"] is not None
|
|
||||||
]
|
async def fetch_page(url: str) -> Optional[List[Track]]:
|
||||||
next_page_url = next_data["next"]
|
async with semaphore:
|
||||||
|
if not self.session:
|
||||||
|
raise SpotifyRequestException(
|
||||||
|
"HTTP session not initialized for Spotify client.",
|
||||||
|
)
|
||||||
|
resp = await self.session.get(url, headers=self._bearer_headers)
|
||||||
|
if resp.status != 200:
|
||||||
|
if self._log:
|
||||||
|
self._log.warning(
|
||||||
|
f"Page fetch failed {resp.status} {resp.reason} for {url}",
|
||||||
|
)
|
||||||
|
return None
|
||||||
|
page_json: dict = await resp.json(loads=json.loads)
|
||||||
|
return [
|
||||||
|
Track(item["track"])
|
||||||
|
for item in page_json.get("items", [])
|
||||||
|
if item.get("track") is not None
|
||||||
|
]
|
||||||
|
|
||||||
|
# Chunk gather in waves to avoid creating thousands of tasks at once
|
||||||
|
aggregated: List[Track] = []
|
||||||
|
wave_size = self._playlist_concurrency * 2
|
||||||
|
for i in range(0, len(page_urls), wave_size):
|
||||||
|
wave = page_urls[i : i + wave_size]
|
||||||
|
results = await asyncio.gather(
|
||||||
|
*[fetch_page(url) for url in wave],
|
||||||
|
return_exceptions=False,
|
||||||
|
)
|
||||||
|
for result in results:
|
||||||
|
if result:
|
||||||
|
aggregated.extend(result)
|
||||||
|
|
||||||
|
tracks.extend(aggregated)
|
||||||
|
|
||||||
return Playlist(data, tracks)
|
return Playlist(data, tracks)
|
||||||
|
|
||||||
async def get_recommendations(self, *, query: str):
|
async def iter_playlist_tracks(
|
||||||
|
self,
|
||||||
|
*,
|
||||||
|
query: str,
|
||||||
|
batch_size: int = 100,
|
||||||
|
) -> AsyncGenerator[List[Track], None]:
|
||||||
|
"""Stream playlist tracks in batches without waiting for full materialization.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
query: str
|
||||||
|
Spotify playlist URL.
|
||||||
|
batch_size: int
|
||||||
|
Number of tracks yielded per batch (logical grouping after fetch). Does not alter API page size.
|
||||||
|
"""
|
||||||
|
if not self._bearer_token or time.time() >= self._expiry:
|
||||||
|
await self._fetch_bearer_token()
|
||||||
|
|
||||||
|
match = SPOTIFY_URL_REGEX.match(query)
|
||||||
|
if not match or match.group("type") != "playlist":
|
||||||
|
raise InvalidSpotifyURL("Provided query is not a valid Spotify playlist URL.")
|
||||||
|
|
||||||
|
playlist_id = match.group("id")
|
||||||
|
request_url = REQUEST_URL.format(type="playlist", id=playlist_id)
|
||||||
|
if not self.session:
|
||||||
|
raise SpotifyRequestException("HTTP session not initialized for Spotify client.")
|
||||||
|
resp = await self.session.get(request_url, headers=self._bearer_headers)
|
||||||
|
if resp.status != 200:
|
||||||
|
raise SpotifyRequestException(
|
||||||
|
f"Error while fetching results: {resp.status} {resp.reason}",
|
||||||
|
)
|
||||||
|
data: dict = await resp.json(loads=json.loads)
|
||||||
|
|
||||||
|
# Yield first page immediately
|
||||||
|
first_page_tracks = [
|
||||||
|
Track(item["track"])
|
||||||
|
for item in data["tracks"]["items"]
|
||||||
|
if item.get("track") is not None
|
||||||
|
]
|
||||||
|
# Batch yield
|
||||||
|
for i in range(0, len(first_page_tracks), batch_size):
|
||||||
|
yield first_page_tracks[i : i + batch_size]
|
||||||
|
|
||||||
|
total = data["tracks"]["total"]
|
||||||
|
limit = data["tracks"]["limit"]
|
||||||
|
remaining_offsets = range(limit, total, limit)
|
||||||
|
fields_filter = (
|
||||||
|
"items(track(name,duration_ms,id,is_local,external_urls,external_ids,artists(name),album(images)))"
|
||||||
|
",next"
|
||||||
|
)
|
||||||
|
|
||||||
|
semaphore = asyncio.Semaphore(self._playlist_concurrency)
|
||||||
|
|
||||||
|
async def fetch(offset: int) -> List[Track]:
|
||||||
|
url = (
|
||||||
|
f"{request_url}/tracks?offset={offset}&limit={limit}&fields={quote(fields_filter)}"
|
||||||
|
)
|
||||||
|
async with semaphore:
|
||||||
|
if not self.session:
|
||||||
|
raise SpotifyRequestException(
|
||||||
|
"HTTP session not initialized for Spotify client.",
|
||||||
|
)
|
||||||
|
r = await self.session.get(url, headers=self._bearer_headers)
|
||||||
|
if r.status != 200:
|
||||||
|
if self._log:
|
||||||
|
self._log.warning(
|
||||||
|
f"Skipping page offset={offset} due to {r.status} {r.reason}",
|
||||||
|
)
|
||||||
|
return []
|
||||||
|
pj: dict = await r.json(loads=json.loads)
|
||||||
|
return [
|
||||||
|
Track(item["track"])
|
||||||
|
for item in pj.get("items", [])
|
||||||
|
if item.get("track") is not None
|
||||||
|
]
|
||||||
|
|
||||||
|
# Fetch pages in rolling waves; yield promptly as soon as a wave completes.
|
||||||
|
wave_size = self._playlist_concurrency * 2
|
||||||
|
for i, offset in enumerate(remaining_offsets):
|
||||||
|
# Build wave
|
||||||
|
if i % wave_size == 0:
|
||||||
|
wave_offsets = list(
|
||||||
|
o for o in remaining_offsets if o >= offset and o < offset + wave_size
|
||||||
|
)
|
||||||
|
results = await asyncio.gather(*[fetch(o) for o in wave_offsets])
|
||||||
|
for page_tracks in results:
|
||||||
|
if not page_tracks:
|
||||||
|
continue
|
||||||
|
for j in range(0, len(page_tracks), batch_size):
|
||||||
|
yield page_tracks[j : j + batch_size]
|
||||||
|
# Skip ahead in iterator by adjusting enumerate drive (consume extras)
|
||||||
|
# Fast-forward the generator manually
|
||||||
|
for _ in range(len(wave_offsets) - 1):
|
||||||
|
try:
|
||||||
|
next(remaining_offsets) # type: ignore
|
||||||
|
except StopIteration:
|
||||||
|
break
|
||||||
|
|
||||||
|
async def get_recommendations(self, *, query: str) -> List[Track]:
|
||||||
if not self._bearer_token or time.time() >= self._expiry:
|
if not self._bearer_token or time.time() >= self._expiry:
|
||||||
await self._fetch_bearer_token()
|
await self._fetch_bearer_token()
|
||||||
|
|
||||||
result = SPOTIFY_URL_REGEX.match(query)
|
result = SPOTIFY_URL_REGEX.match(query)
|
||||||
spotify_type = result.group("type")
|
|
||||||
spotify_id = result.group("id")
|
|
||||||
|
|
||||||
if not result:
|
if not result:
|
||||||
raise InvalidSpotifyURL("The Spotify link provided is not valid.")
|
raise InvalidSpotifyURL("The Spotify link provided is not valid.")
|
||||||
|
|
||||||
|
spotify_type = result.group("type")
|
||||||
|
spotify_id = result.group("id")
|
||||||
|
|
||||||
if not spotify_type == "track":
|
if not spotify_type == "track":
|
||||||
raise InvalidSpotifyURL("The provided query is not a Spotify track.")
|
raise InvalidSpotifyURL(
|
||||||
|
"The provided query is not a Spotify track.",
|
||||||
|
)
|
||||||
|
|
||||||
request_url = REQUEST_URL.format(type="recommendation", id=f"?seed_tracks={spotify_id}")
|
request_url = REQUEST_URL.format(
|
||||||
|
type="recommendation",
|
||||||
|
id=f"?seed_tracks={spotify_id}",
|
||||||
|
)
|
||||||
|
|
||||||
async with self.session.get(request_url, headers=self._bearer_headers) as resp:
|
if not self.session:
|
||||||
if resp.status != 200:
|
raise SpotifyRequestException("HTTP session not initialized for Spotify client.")
|
||||||
raise SpotifyRequestException(
|
resp = await self.session.get(request_url, headers=self._bearer_headers)
|
||||||
f"Error while fetching results: {resp.status} {resp.reason}"
|
if resp.status != 200:
|
||||||
)
|
raise SpotifyRequestException(
|
||||||
|
f"Error while fetching results: {resp.status} {resp.reason}",
|
||||||
data: dict = await resp.json(loads=json.loads)
|
)
|
||||||
|
|
||||||
|
data: dict = await resp.json(loads=json.loads)
|
||||||
tracks = [Track(track) for track in data["tracks"]]
|
tracks = [Track(track) for track in data["tracks"]]
|
||||||
|
|
||||||
return tracks
|
return tracks
|
||||||
|
|
||||||
|
async def track_search(self, *, query: str) -> List[Track]:
|
||||||
|
if not self._bearer_token or time.time() >= self._expiry:
|
||||||
|
await self._fetch_bearer_token()
|
||||||
|
|
||||||
|
request_url = f"https://api.spotify.com/v1/search?q={quote(query)}&type=track"
|
||||||
|
|
||||||
|
if not self.session:
|
||||||
|
raise SpotifyRequestException("HTTP session not initialized for Spotify client.")
|
||||||
|
resp = await self.session.get(request_url, headers=self._bearer_headers)
|
||||||
|
if resp.status != 200:
|
||||||
|
raise SpotifyRequestException(
|
||||||
|
f"Error while fetching results: {resp.status} {resp.reason}",
|
||||||
|
)
|
||||||
|
|
||||||
|
data: dict = await resp.json(loads=json.loads)
|
||||||
|
tracks = [Track(track) for track in data["tracks"]["items"]]
|
||||||
|
|
||||||
|
return tracks
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,16 @@
|
||||||
|
__all__ = (
|
||||||
|
"SpotifyRequestException",
|
||||||
|
"InvalidSpotifyURL",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class SpotifyRequestException(Exception):
|
class SpotifyRequestException(Exception):
|
||||||
"""An error occurred when making a request to the Spotify API"""
|
"""An error occurred when making a request to the Spotify API"""
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class InvalidSpotifyURL(Exception):
|
class InvalidSpotifyURL(Exception):
|
||||||
"""An invalid Spotify URL was passed"""
|
"""An invalid Spotify URL was passed"""
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
|
||||||
|
|
@ -1,29 +1,34 @@
|
||||||
from typing import List
|
from typing import List
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
__all__ = (
|
||||||
|
"Track",
|
||||||
|
"Playlist",
|
||||||
|
"Album",
|
||||||
|
"Artist",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class Track:
|
class Track:
|
||||||
"""The base class for a Spotify Track"""
|
"""The base class for a Spotify Track"""
|
||||||
|
|
||||||
def __init__(self, data: dict, image = None) -> None:
|
def __init__(self, data: dict, image: Optional[str] = None) -> None:
|
||||||
self.name: str = data["name"]
|
self.name: str = data["name"]
|
||||||
self.artists: str = ", ".join(artist["name"] for artist in data["artists"])
|
self.artists: str = ", ".join(artist["name"] for artist in data["artists"])
|
||||||
self.length: float = data["duration_ms"]
|
self.length: float = data["duration_ms"]
|
||||||
self.id: str = data["id"]
|
self.id: str = data["id"]
|
||||||
|
|
||||||
|
self.isrc: Optional[str] = None
|
||||||
if data.get("external_ids"):
|
if data.get("external_ids"):
|
||||||
self.isrc: str = data["external_ids"]["isrc"]
|
self.isrc = data["external_ids"]["isrc"]
|
||||||
else:
|
|
||||||
self.isrc = None
|
|
||||||
|
|
||||||
|
self.image: Optional[str] = image
|
||||||
if data.get("album") and data["album"].get("images"):
|
if data.get("album") and data["album"].get("images"):
|
||||||
self.image: str = data["album"]["images"][0]["url"]
|
self.image = data["album"]["images"][0]["url"]
|
||||||
else:
|
|
||||||
self.image: str = image
|
|
||||||
|
|
||||||
if data["is_local"]:
|
self.uri: Optional[str] = None
|
||||||
self.uri = None
|
if not data["is_local"]:
|
||||||
else:
|
self.uri = data["external_urls"]["spotify"]
|
||||||
self.uri: str = data["external_urls"]["spotify"]
|
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return (
|
return (
|
||||||
|
|
@ -31,6 +36,7 @@ class Track:
|
||||||
f"length={self.length} id={self.id} isrc={self.isrc}>"
|
f"length={self.length} id={self.id} isrc={self.isrc}>"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class Playlist:
|
class Playlist:
|
||||||
"""The base class for a Spotify playlist"""
|
"""The base class for a Spotify playlist"""
|
||||||
|
|
||||||
|
|
@ -41,7 +47,7 @@ class Playlist:
|
||||||
self.total_tracks: int = data["tracks"]["total"]
|
self.total_tracks: int = data["tracks"]["total"]
|
||||||
self.id: str = data["id"]
|
self.id: str = data["id"]
|
||||||
if data.get("images") and len(data["images"]):
|
if data.get("images") and len(data["images"]):
|
||||||
self.image: str = data["images"][0]["url"]
|
self.image = data["images"][0]["url"]
|
||||||
else:
|
else:
|
||||||
self.image = self.tracks[0].image
|
self.image = self.tracks[0].image
|
||||||
self.uri = data["external_urls"]["spotify"]
|
self.uri = data["external_urls"]["spotify"]
|
||||||
|
|
@ -52,6 +58,7 @@ class Playlist:
|
||||||
f"total_tracks={self.total_tracks} tracks={self.tracks}>"
|
f"total_tracks={self.total_tracks} tracks={self.tracks}>"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class Album:
|
class Album:
|
||||||
"""The base class for a Spotify album"""
|
"""The base class for a Spotify album"""
|
||||||
|
|
||||||
|
|
@ -70,11 +77,15 @@ class Album:
|
||||||
f"total_tracks={self.total_tracks} tracks={self.tracks}>"
|
f"total_tracks={self.total_tracks} tracks={self.tracks}>"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class Artist:
|
class Artist:
|
||||||
"""The base class for a Spotify artist"""
|
"""The base class for a Spotify artist"""
|
||||||
|
|
||||||
def __init__(self, data: dict, tracks: dict) -> None:
|
def __init__(self, data: dict, tracks: dict) -> None:
|
||||||
self.name: str = f"Top tracks for {data['name']}" # Setting that because its only playing top tracks
|
self.name: str = (
|
||||||
|
# Setting that because its only playing top tracks
|
||||||
|
f"Top tracks for {data['name']}"
|
||||||
|
)
|
||||||
self.genres: str = ", ".join(genre for genre in data["genres"])
|
self.genres: str = ", ".join(genre for genre in data["genres"])
|
||||||
self.followers: int = data["followers"]["total"]
|
self.followers: int = data["followers"]["total"]
|
||||||
self.image: str = data["images"][0]["url"]
|
self.image: str = data["images"][0]["url"]
|
||||||
|
|
@ -83,7 +94,4 @@ class Artist:
|
||||||
self.uri: str = data["external_urls"]["spotify"]
|
self.uri: str = data["external_urls"]["spotify"]
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return (
|
return f"<Pomice.spotify.Artist name={self.name} id={self.id} " f"tracks={self.tracks}>"
|
||||||
f"<Pomice.spotify.Artist name={self.name} id={self.id} "
|
|
||||||
f"tracks={self.tracks}>"
|
|
||||||
)
|
|
||||||
|
|
|
||||||
169
pomice/utils.py
169
pomice/utils.py
|
|
@ -1,17 +1,27 @@
|
||||||
import random
|
import random
|
||||||
import time
|
|
||||||
import socket
|
import socket
|
||||||
|
import time
|
||||||
from .enums import RouteStrategy, RouteIPType
|
|
||||||
from timeit import default_timer as timer
|
|
||||||
from itertools import zip_longest
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
from itertools import zip_longest
|
||||||
|
from timeit import default_timer as timer
|
||||||
|
from typing import Any
|
||||||
|
from typing import Callable
|
||||||
|
from typing import Dict
|
||||||
|
from typing import Iterable
|
||||||
|
from typing import NamedTuple
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from .enums import RouteIPType
|
||||||
|
from .enums import RouteStrategy
|
||||||
|
|
||||||
__all__ = [
|
__all__ = (
|
||||||
"ExponentialBackoff",
|
"ExponentialBackoff",
|
||||||
"NodeStats"
|
"NodeStats",
|
||||||
]
|
"FailingIPBlock",
|
||||||
|
"RouteStats",
|
||||||
|
"Ping",
|
||||||
|
"LavalinkVersion",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class ExponentialBackoff:
|
class ExponentialBackoff:
|
||||||
|
|
@ -36,12 +46,11 @@ class ExponentialBackoff:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, base: int = 1, *, integral: bool = False) -> None:
|
def __init__(self, base: int = 1, *, integral: bool = False) -> None:
|
||||||
|
|
||||||
self._base = base
|
self._base = base
|
||||||
|
|
||||||
self._exp = 0
|
self._exp = 0
|
||||||
self._max = 10
|
self._max = 10
|
||||||
self._reset_time = base * 2 ** 11
|
self._reset_time = base * 2**11
|
||||||
self._last_invocation = time.monotonic()
|
self._last_invocation = time.monotonic()
|
||||||
|
|
||||||
rand = random.Random()
|
rand = random.Random()
|
||||||
|
|
@ -50,7 +59,6 @@ class ExponentialBackoff:
|
||||||
self._randfunc = rand.randrange if integral else rand.uniform
|
self._randfunc = rand.randrange if integral else rand.uniform
|
||||||
|
|
||||||
def delay(self) -> float:
|
def delay(self) -> float:
|
||||||
|
|
||||||
invocation = time.monotonic()
|
invocation = time.monotonic()
|
||||||
interval = invocation - self._last_invocation
|
interval = invocation - self._last_invocation
|
||||||
self._last_invocation = invocation
|
self._last_invocation = invocation
|
||||||
|
|
@ -59,23 +67,35 @@ class ExponentialBackoff:
|
||||||
self._exp = 0
|
self._exp = 0
|
||||||
|
|
||||||
self._exp = min(self._exp + 1, self._max)
|
self._exp = min(self._exp + 1, self._max)
|
||||||
return self._randfunc(0, self._base * 2 ** self._exp)
|
return self._randfunc(0, self._base * 2**self._exp) # type: ignore
|
||||||
|
|
||||||
|
|
||||||
class NodeStats:
|
class NodeStats:
|
||||||
"""The base class for the node stats object.
|
"""The base class for the node stats object.
|
||||||
Gives critical information on the node, which is updated every minute.
|
Gives critical information on the node, which is updated every minute.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, data: dict) -> None:
|
__slots__ = (
|
||||||
|
"used",
|
||||||
|
"free",
|
||||||
|
"reservable",
|
||||||
|
"allocated",
|
||||||
|
"cpu_cores",
|
||||||
|
"cpu_system_load",
|
||||||
|
"cpu_process_load",
|
||||||
|
"players_active",
|
||||||
|
"players_total",
|
||||||
|
"uptime",
|
||||||
|
)
|
||||||
|
|
||||||
memory: dict = data.get("memory")
|
def __init__(self, data: Dict[str, Any]) -> None:
|
||||||
|
memory: dict = data.get("memory", {})
|
||||||
self.used = memory.get("used")
|
self.used = memory.get("used")
|
||||||
self.free = memory.get("free")
|
self.free = memory.get("free")
|
||||||
self.reservable = memory.get("reservable")
|
self.reservable = memory.get("reservable")
|
||||||
self.allocated = memory.get("allocated")
|
self.allocated = memory.get("allocated")
|
||||||
|
|
||||||
cpu: dict = data.get("cpu")
|
cpu: dict = data.get("cpu", {})
|
||||||
self.cpu_cores = cpu.get("cores")
|
self.cpu_cores = cpu.get("cores")
|
||||||
self.cpu_system_load = cpu.get("systemLoad")
|
self.cpu_system_load = cpu.get("systemLoad")
|
||||||
self.cpu_process_load = cpu.get("lavalinkLoad")
|
self.cpu_process_load = cpu.get("lavalinkLoad")
|
||||||
|
|
@ -87,15 +107,21 @@ class NodeStats:
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return f"<Pomice.NodeStats total_players={self.players_total!r} playing_active={self.players_active!r}>"
|
return f"<Pomice.NodeStats total_players={self.players_total!r} playing_active={self.players_active!r}>"
|
||||||
|
|
||||||
|
|
||||||
class FailingIPBlock:
|
class FailingIPBlock:
|
||||||
"""
|
"""
|
||||||
The base class for the failing IP block object from the route planner stats.
|
The base class for the failing IP block object from the route planner stats.
|
||||||
Gives critical information about any failing addresses on the block
|
Gives critical information about any failing addresses on the block
|
||||||
and the time they failed.
|
and the time they failed.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
__slots__ = ("address", "failing_time")
|
||||||
|
|
||||||
def __init__(self, data: dict) -> None:
|
def __init__(self, data: dict) -> None:
|
||||||
self.address = data.get("address")
|
self.address = data.get("address")
|
||||||
self.failing_time = datetime.fromtimestamp(float(data.get("failingTimestamp")))
|
self.failing_time = datetime.fromtimestamp(
|
||||||
|
float(data.get("failingTimestamp", 0)),
|
||||||
|
)
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return f"<Pomice.FailingIPBlock address={self.address} failing_time={self.failing_time}>"
|
return f"<Pomice.FailingIPBlock address={self.address} failing_time={self.failing_time}>"
|
||||||
|
|
@ -107,28 +133,40 @@ class RouteStats:
|
||||||
Gives critical information about the route planner strategy on the node.
|
Gives critical information about the route planner strategy on the node.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, data: dict) -> None:
|
__slots__ = (
|
||||||
|
"strategy",
|
||||||
|
"ip_block_type",
|
||||||
|
"ip_block_size",
|
||||||
|
"failing_addresses",
|
||||||
|
"block_index",
|
||||||
|
"address_index",
|
||||||
|
)
|
||||||
|
|
||||||
|
def __init__(self, data: Dict[str, Any]) -> None:
|
||||||
self.strategy = RouteStrategy(data.get("class"))
|
self.strategy = RouteStrategy(data.get("class"))
|
||||||
|
|
||||||
details: dict = data.get("details")
|
details: dict = data.get("details", {})
|
||||||
|
|
||||||
ip_block: dict = details.get("ipBlock")
|
ip_block: dict = details.get("ipBlock", {})
|
||||||
self.ip_block_type = RouteIPType(ip_block.get("type"))
|
self.ip_block_type = RouteIPType(ip_block.get("type"))
|
||||||
self.ip_block_size = ip_block.get("size")
|
self.ip_block_size = ip_block.get("size")
|
||||||
self.failing_addresses = [FailingIPBlock(data) for data in details.get("failingAddresses")]
|
self.failing_addresses = [
|
||||||
|
FailingIPBlock(
|
||||||
|
data,
|
||||||
|
)
|
||||||
|
for data in details.get("failingAddresses", [])
|
||||||
|
]
|
||||||
|
|
||||||
self.block_index = details.get("blockIndex")
|
self.block_index = details.get("blockIndex")
|
||||||
self.address_index = details.get("currentAddressIndex")
|
self.address_index = details.get("currentAddressIndex")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return f"<Pomice.RouteStats route_strategy={self.strategy!r} failing_addresses={len(self.failing_addresses)}>"
|
return f"<Pomice.RouteStats route_strategy={self.strategy!r} failing_addresses={len(self.failing_addresses)}>"
|
||||||
|
|
||||||
|
|
||||||
class Ping:
|
class Ping:
|
||||||
# Thanks to https://github.com/zhengxiaowai/tcping for the nice ping impl
|
# Thanks to https://github.com/zhengxiaowai/tcping for the nice ping impl
|
||||||
def __init__(self, host, port, timeout=5):
|
def __init__(self, host: str, port: int, timeout: int = 5) -> None:
|
||||||
self.timer = self.Timer()
|
self.timer = self.Timer()
|
||||||
|
|
||||||
self._successed = 0
|
self._successed = 0
|
||||||
|
|
@ -138,34 +176,33 @@ class Ping:
|
||||||
self._port = port
|
self._port = port
|
||||||
self._timeout = timeout
|
self._timeout = timeout
|
||||||
|
|
||||||
class Socket(object):
|
class Socket:
|
||||||
def __init__(self, family, type_, timeout):
|
def __init__(self, family: int, type_: int, timeout: Optional[float]) -> None:
|
||||||
s = socket.socket(family, type_)
|
s = socket.socket(family, type_)
|
||||||
s.settimeout(timeout)
|
s.settimeout(timeout)
|
||||||
self._s = s
|
self._s = s
|
||||||
|
|
||||||
def connect(self, host, port):
|
def connect(self, host: str, port: int) -> None:
|
||||||
self._s.connect((host, int(port)))
|
self._s.connect((host, port))
|
||||||
|
|
||||||
def shutdown(self):
|
def shutdown(self) -> None:
|
||||||
self._s.shutdown(socket.SHUT_RD)
|
self._s.shutdown(socket.SHUT_RD)
|
||||||
|
|
||||||
def close(self):
|
def close(self) -> None:
|
||||||
self._s.close()
|
self._s.close()
|
||||||
|
|
||||||
|
class Timer:
|
||||||
|
def __init__(self) -> None:
|
||||||
|
self._start: float = 0.0
|
||||||
|
self._stop: float = 0.0
|
||||||
|
|
||||||
class Timer(object):
|
def start(self) -> None:
|
||||||
def __init__(self):
|
|
||||||
self._start = 0
|
|
||||||
self._stop = 0
|
|
||||||
|
|
||||||
def start(self):
|
|
||||||
self._start = timer()
|
self._start = timer()
|
||||||
|
|
||||||
def stop(self):
|
def stop(self) -> None:
|
||||||
self._stop = timer()
|
self._stop = timer()
|
||||||
|
|
||||||
def cost(self, funcs, args):
|
def cost(self, funcs: Iterable[Callable], args: Any) -> float:
|
||||||
self.start()
|
self.start()
|
||||||
for func, arg in zip_longest(funcs, args):
|
for func, arg in zip_longest(funcs, args):
|
||||||
if arg:
|
if arg:
|
||||||
|
|
@ -176,16 +213,66 @@ class Ping:
|
||||||
self.stop()
|
self.stop()
|
||||||
return self._stop - self._start
|
return self._stop - self._start
|
||||||
|
|
||||||
def _create_socket(self, family, type_):
|
def _create_socket(self, family: int, type_: int) -> Socket:
|
||||||
return self.Socket(family, type_, self._timeout)
|
return self.Socket(family, type_, self._timeout)
|
||||||
|
|
||||||
def get_ping(self):
|
def get_ping(self) -> float:
|
||||||
s = self._create_socket(socket.AF_INET, socket.SOCK_STREAM)
|
s = self._create_socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
|
||||||
cost_time = self.timer.cost(
|
cost_time = self.timer.cost(
|
||||||
(s.connect, s.shutdown),
|
(s.connect, s.shutdown),
|
||||||
((self._host, self._port), None))
|
((self._host, self._port), None),
|
||||||
|
)
|
||||||
s_runtime = 1000 * (cost_time)
|
s_runtime = 1000 * (cost_time)
|
||||||
|
|
||||||
return s_runtime
|
return s_runtime
|
||||||
|
|
||||||
|
|
||||||
|
class LavalinkVersion(NamedTuple):
|
||||||
|
major: int
|
||||||
|
minor: int
|
||||||
|
fix: int
|
||||||
|
|
||||||
|
def __eq__(self, other: object) -> bool:
|
||||||
|
if not isinstance(other, LavalinkVersion):
|
||||||
|
return False
|
||||||
|
|
||||||
|
return (
|
||||||
|
(self.major == other.major) and (self.minor == other.minor) and (self.fix == other.fix)
|
||||||
|
)
|
||||||
|
|
||||||
|
def __ne__(self, other: object) -> bool:
|
||||||
|
if not isinstance(other, LavalinkVersion):
|
||||||
|
return False
|
||||||
|
|
||||||
|
return not (self == other)
|
||||||
|
|
||||||
|
def __lt__(self, other: object) -> bool:
|
||||||
|
if not isinstance(other, LavalinkVersion):
|
||||||
|
return False
|
||||||
|
|
||||||
|
if self.major > other.major:
|
||||||
|
return False
|
||||||
|
if self.minor > other.minor:
|
||||||
|
return False
|
||||||
|
if self.fix > other.fix:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
def __gt__(self, other: object) -> bool:
|
||||||
|
if not isinstance(other, LavalinkVersion):
|
||||||
|
return False
|
||||||
|
|
||||||
|
return not (self < other)
|
||||||
|
|
||||||
|
def __le__(self, other: object) -> bool:
|
||||||
|
if not isinstance(other, LavalinkVersion):
|
||||||
|
return False
|
||||||
|
|
||||||
|
return (self < other) or (self == other)
|
||||||
|
|
||||||
|
def __ge__(self, other: object) -> bool:
|
||||||
|
if not isinstance(other, LavalinkVersion):
|
||||||
|
return False
|
||||||
|
|
||||||
|
return (self > other) or (self == other)
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,15 @@ requires = [
|
||||||
]
|
]
|
||||||
build-backend = "setuptools.build_meta"
|
build-backend = "setuptools.build_meta"
|
||||||
|
|
||||||
[tool.autopep8]
|
[tool.black]
|
||||||
max_line_length = 100
|
line-length = 100
|
||||||
in-place = true
|
|
||||||
recursive = true
|
[tool.mypy]
|
||||||
aggressive = 3
|
mypy_path = "./"
|
||||||
|
files = ["pomice"]
|
||||||
|
disallow_untyped_defs = true
|
||||||
|
disallow_any_unimported = true
|
||||||
|
no_implicit_optional = true
|
||||||
|
check_untyped_defs = true
|
||||||
|
warn_unused_ignores = true
|
||||||
|
show_error_codes = true
|
||||||
|
|
|
||||||
57
setup.py
57
setup.py
|
|
@ -1,33 +1,45 @@
|
||||||
import setuptools
|
# type: ignore
|
||||||
import re
|
import re
|
||||||
|
|
||||||
version = ''
|
import setuptools
|
||||||
requirements = ['discord.py>=2.0.0', 'aiohttp>=3.7.4,<4', 'orjson']
|
|
||||||
with open('pomice/__init__.py') as f:
|
version = ""
|
||||||
version = re.search(r'^__version__\s*=\s*[\'"]([^\'"]*)[\'"]', f.read(), re.MULTILINE).group(1)
|
requirements = ["aiohttp>=3.7.4,<4", "orjson", "websockets"]
|
||||||
|
with open("pomice/__init__.py") as f:
|
||||||
|
version = re.search(
|
||||||
|
r'^__version__\s*=\s*[\'"]([^\'"]*)[\'"]',
|
||||||
|
f.read(),
|
||||||
|
re.MULTILINE,
|
||||||
|
).group(1)
|
||||||
|
|
||||||
if not version:
|
if not version:
|
||||||
raise RuntimeError('version is not set')
|
raise RuntimeError("version is not set")
|
||||||
|
|
||||||
if version.endswith(('a', 'b', 'rc')):
|
if version.endswith(("a", "b", "rc")):
|
||||||
# append version identifier based on commit count
|
# append version identifier based on commit count
|
||||||
try:
|
try:
|
||||||
import subprocess
|
import subprocess
|
||||||
p = subprocess.Popen(['git', 'rev-list', '--count', 'HEAD'],
|
|
||||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
p = subprocess.Popen(
|
||||||
|
["git", "rev-list", "--count", "HEAD"],
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.PIPE,
|
||||||
|
)
|
||||||
out, err = p.communicate()
|
out, err = p.communicate()
|
||||||
if out:
|
if out:
|
||||||
version += out.decode('utf-8').strip()
|
version += out.decode("utf-8").strip()
|
||||||
p = subprocess.Popen(['git', 'rev-parse', '--short', 'HEAD'],
|
p = subprocess.Popen(
|
||||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
["git", "rev-parse", "--short", "HEAD"],
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.PIPE,
|
||||||
|
)
|
||||||
out, err = p.communicate()
|
out, err = p.communicate()
|
||||||
if out:
|
if out:
|
||||||
version += '+g' + out.decode('utf-8').strip()
|
version += "+g" + out.decode("utf-8").strip()
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
with open("README.md") as f:
|
with open("README.md") as f:
|
||||||
readme = f.read()
|
readme = f.read()
|
||||||
|
|
||||||
|
|
@ -41,20 +53,21 @@ setuptools.setup(
|
||||||
description="The modern Lavalink wrapper designed for Discord.py",
|
description="The modern Lavalink wrapper designed for Discord.py",
|
||||||
long_description=readme,
|
long_description=readme,
|
||||||
long_description_content_type="text/markdown",
|
long_description_content_type="text/markdown",
|
||||||
|
package_data={"pomice": ["py.typed"]},
|
||||||
include_package_data=True,
|
include_package_data=True,
|
||||||
install_requires=requirements,
|
install_requires=requirements,
|
||||||
extra_require=None,
|
extra_require=None,
|
||||||
classifiers=[
|
classifiers=[
|
||||||
"Framework :: AsyncIO",
|
"Framework :: AsyncIO",
|
||||||
'Operating System :: OS Independent',
|
"Operating System :: OS Independent",
|
||||||
'Natural Language :: English',
|
"Natural Language :: English",
|
||||||
'Intended Audience :: Developers',
|
"Intended Audience :: Developers",
|
||||||
"Programming Language :: Python :: 3 :: Only",
|
"Programming Language :: Python :: 3 :: Only",
|
||||||
"Programming Language :: Python :: 3.8",
|
"Programming Language :: Python :: 3.8",
|
||||||
'Topic :: Software Development :: Libraries :: Python Modules',
|
"Topic :: Software Development :: Libraries :: Python Modules",
|
||||||
'Topic :: Software Development :: Libraries',
|
"Topic :: Software Development :: Libraries",
|
||||||
"Topic :: Internet"
|
"Topic :: Internet",
|
||||||
],
|
],
|
||||||
python_requires='>=3.8',
|
python_requires=">=3.8",
|
||||||
keywords=['pomice', 'lavalink', "discord.py"],
|
keywords=["pomice", "lavalink", "discord.py"],
|
||||||
)
|
)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue