This commit is contained in:
parent
e6d3d13e5d
commit
e700af61cb
12 changed files with 437 additions and 5 deletions
|
@ -27,4 +27,5 @@ Credits:
|
|||
[Vert Cogs](https://github.com/vertyco/vrt-cogs)
|
||||
[Vex Cogs](https://github.com/Vexed01/Vex-Cogs)
|
||||
[x26 Cogs](https://github.com/Twentysix26/x26-Cogs)
|
||||
[Toxic Cogs](https://github.com/NeuroAssassin/Toxic-Cogs)
|
||||
[Toxic Cogs](https://github.com/NeuroAssassin/Toxic-Cogs)
|
||||
[Karlo Cogs](https://github.com/karlsbjorn/karlo-cogs)
|
13
autoplay/__init__.py
Normal file
13
autoplay/__init__.py
Normal file
|
@ -0,0 +1,13 @@
|
|||
import json
|
||||
from pathlib import Path
|
||||
|
||||
from pylav.extension.red.utils.required_methods import pylav_auto_setup
|
||||
|
||||
from .autoplay import AutoPlay
|
||||
|
||||
with open(Path(__file__).parent / "info.json") as fp:
|
||||
__red_end_user_data_statement__ = json.load(fp)["end_user_data_statement"]
|
||||
|
||||
|
||||
async def setup(bot):
|
||||
await pylav_auto_setup(bot, AutoPlay)
|
240
autoplay/autoplay.py
Normal file
240
autoplay/autoplay.py
Normal file
|
@ -0,0 +1,240 @@
|
|||
from typing import Optional
|
||||
|
||||
import discord
|
||||
from discord import AppCommandType
|
||||
from redbot.core import Config, commands
|
||||
from redbot.core.i18n import Translator, cog_i18n
|
||||
|
||||
from pylav.events.player import PlayerDisconnectedEvent
|
||||
from pylav.logging import getLogger
|
||||
from pylav.players.player import Player
|
||||
from pylav.players.query.obj import Query
|
||||
from pylav.type_hints.bot import DISCORD_BOT_TYPE, DISCORD_INTERACTION_TYPE
|
||||
|
||||
log = getLogger("PyLav.3rdpt.karlo-cogs.autoplay")
|
||||
_ = Translator("AutoPlay", __file__)
|
||||
|
||||
|
||||
@cog_i18n(_)
|
||||
class AutoPlay(commands.Cog):
|
||||
"""Automatically play music that a guild member is listening to on Spotify."""
|
||||
|
||||
def __init__(self, bot: DISCORD_BOT_TYPE):
|
||||
self.bot: DISCORD_BOT_TYPE = bot
|
||||
self.config = Config.get_conf(self, identifier=87446677010550784, force_registration=True)
|
||||
default_guild = {"tracked_member": None, "autoplaying": False, "paused_track": None}
|
||||
self.config.register_guild(**default_guild)
|
||||
self.context_user_autoplay = discord.app_commands.ContextMenu(
|
||||
name=_("Start AutoPlay"),
|
||||
callback=self._context_user_autoplay,
|
||||
type=AppCommandType.user,
|
||||
)
|
||||
self.bot.tree.add_command(self.context_user_autoplay)
|
||||
|
||||
@commands.hybrid_command()
|
||||
@commands.guild_only()
|
||||
async def autoplay(self, ctx, member: discord.Member = None):
|
||||
"""Toggle autoplay for a member.
|
||||
|
||||
This will cause the bot to automatically play music that
|
||||
the member is listening to on Spotify.
|
||||
|
||||
To stop it, use `[p]autoplay` without a member, or
|
||||
use a player command like `[p]stop` or `[p]play`.
|
||||
"""
|
||||
if ctx.interaction:
|
||||
await ctx.defer(ephemeral=True)
|
||||
|
||||
if member is None:
|
||||
await self.config.guild(ctx.guild).tracked_member.set(None)
|
||||
return
|
||||
else:
|
||||
await self.config.guild(ctx.guild).tracked_member.set(member.id)
|
||||
msg = await self._prepare_autoplay(ctx.guild, ctx.author)
|
||||
await ctx.send(embed=await self.pylav.construct_embed(description=msg, messageable=ctx))
|
||||
|
||||
async def _context_user_autoplay(
|
||||
self, interaction: DISCORD_INTERACTION_TYPE, member: discord.Member
|
||||
):
|
||||
await interaction.response.defer(ephemeral=True)
|
||||
|
||||
if not interaction.guild:
|
||||
await interaction.followup.send(
|
||||
embed=await self.pylav.construct_embed(
|
||||
description=_("This can only be used in a guild."), messageable=interaction
|
||||
)
|
||||
)
|
||||
return
|
||||
|
||||
await self.config.guild(interaction.guild).tracked_member.set(member.id)
|
||||
|
||||
msg = await self._prepare_autoplay(interaction.guild, interaction.user)
|
||||
await interaction.followup.send(
|
||||
embed=await self.pylav.construct_embed(
|
||||
description=msg.format(member=member.mention),
|
||||
messageable=interaction,
|
||||
)
|
||||
)
|
||||
|
||||
async def _prepare_autoplay(self, guild, author) -> str:
|
||||
player: Player = self.bot.pylav.get_player(guild.id)
|
||||
|
||||
if not player and author.voice:
|
||||
await self.bot.pylav.connect_player(author, author.voice.channel)
|
||||
player: Player = self.bot.pylav.get_player(guild.id)
|
||||
elif not player:
|
||||
return _(
|
||||
"I am not in a voice channel. Please connect to a voice channel and then use this command."
|
||||
)
|
||||
elif player and player.channel.id != author.voice.channel.id:
|
||||
await player.move_to(author, author.voice.channel)
|
||||
|
||||
if player.is_playing or player.paused:
|
||||
await player.stop(author)
|
||||
if player.queue.size:
|
||||
player.queue.clear()
|
||||
return _(
|
||||
"I'll now play whatever {member} is listening to.\n"
|
||||
"To stop autoplay, use a player command like `stop`"
|
||||
)
|
||||
|
||||
@commands.Cog.listener()
|
||||
async def on_presence_update(
|
||||
self, member_before: discord.Member, member_after: discord.Member
|
||||
):
|
||||
if await self._member_checks(member_after):
|
||||
return
|
||||
|
||||
player: Player = self.bot.lavalink.get_player(member_after.guild.id)
|
||||
if player is None:
|
||||
log.verbose("No player found.")
|
||||
return
|
||||
|
||||
current_activity = self._get_spotify_activity(member_after)
|
||||
past_activity = self._get_spotify_activity(member_before)
|
||||
if not current_activity:
|
||||
# Member is no longer listening to Spotify.
|
||||
autoplaying = await self.config.guild(member_after.guild).autoplaying()
|
||||
if autoplaying and player.is_playing:
|
||||
await player.set_pause(True, member_after)
|
||||
await self.config.guild(member_after.guild).paused_track.set(
|
||||
past_activity.track_id
|
||||
)
|
||||
return
|
||||
log.verbose(f"Presence update detected. {current_activity.track_url}")
|
||||
if past_activity and past_activity.track_id == current_activity.track_id:
|
||||
# Same track, no need to do anything.
|
||||
return
|
||||
if current_activity.track_id == await self.config.guild(member_after.guild).paused_track():
|
||||
# If the track is the same as when the activity stopped, it was probably paused,
|
||||
# so we'll resume it.
|
||||
log.verbose("Resuming track.")
|
||||
await player.set_pause(False, member_after)
|
||||
return
|
||||
|
||||
log.verbose(f"Querying {current_activity.track_url}")
|
||||
query = await Query.from_string(current_activity.track_url)
|
||||
response = await self.bot.lavalink.search_query(query=query)
|
||||
if response.loadType == "error":
|
||||
log.verbose(f"No tracks found. Response: {response}")
|
||||
return
|
||||
|
||||
log.verbose(f"Query successful: {response.data}")
|
||||
|
||||
if player.paused:
|
||||
# To prevent overlapping tracks, we'll stop the player first to clear the paused track.
|
||||
await player.stop(member_after)
|
||||
if player.queue.size():
|
||||
log.verbose("Queue is not empty, clearing.")
|
||||
player.queue.clear()
|
||||
log.verbose(f"Playing {response.data.info.title}.")
|
||||
await player.play(
|
||||
query=query,
|
||||
track=response.data,
|
||||
requester=member_after,
|
||||
)
|
||||
await self.config.guild(member_after.guild).paused_track.set(None)
|
||||
await self.config.guild(member_after.guild).autoplaying.set(True)
|
||||
|
||||
async def _member_checks(self, member: discord.Member) -> bool:
|
||||
"""Check if the member is valid for autoplay."""
|
||||
return (
|
||||
member.id != tracked_member
|
||||
if (tracked_member := await self.config.guild(member.guild).tracked_member())
|
||||
else True
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _get_spotify_activity(member: discord.Member) -> Optional[discord.Spotify]:
|
||||
"""Get the Spotify activity of a member."""
|
||||
activity = next(
|
||||
(
|
||||
activity
|
||||
for activity in member.activities
|
||||
if activity.type == discord.ActivityType.listening
|
||||
and hasattr(activity, "track_id")
|
||||
and hasattr(activity, "track_url")
|
||||
),
|
||||
None,
|
||||
)
|
||||
return activity if activity and isinstance(activity, discord.Spotify) else None
|
||||
|
||||
@commands.Cog.listener()
|
||||
async def on_command(self, ctx: commands.Context):
|
||||
"""Stop autoplay when a player command is used."""
|
||||
log.verbose(f"Command {ctx.command.name}, {ctx.command.qualified_name} used.")
|
||||
player_commands = [
|
||||
"play",
|
||||
"skip",
|
||||
"stop",
|
||||
"playlist play",
|
||||
"dc",
|
||||
"prev",
|
||||
"repeat",
|
||||
"shuffle",
|
||||
"pause",
|
||||
"resume",
|
||||
]
|
||||
if ctx.command.name in player_commands and ctx.guild:
|
||||
await self._stop_autoplay(ctx.guild)
|
||||
|
||||
@commands.Cog.listener()
|
||||
async def on_interaction(self, interaction: discord.Interaction):
|
||||
"""Stop autoplay when a player interaction is used."""
|
||||
if interaction.type != discord.InteractionType.application_command:
|
||||
return
|
||||
log.verbose(
|
||||
f"Interaction {interaction.type}, "
|
||||
f"{interaction.command.name if interaction.command else None} used."
|
||||
)
|
||||
player_commands = [
|
||||
"play",
|
||||
"skip",
|
||||
"stop",
|
||||
"playlist play",
|
||||
"dc",
|
||||
"prev",
|
||||
"repeat",
|
||||
"shuffle",
|
||||
"pause",
|
||||
"resume",
|
||||
]
|
||||
if interaction.command.name in player_commands and interaction.guild:
|
||||
await self._stop_autoplay(interaction.guild)
|
||||
|
||||
@commands.Cog.listener()
|
||||
async def on_pylav_player_disconnected_event(self, event: PlayerDisconnectedEvent):
|
||||
"""Stop autoplay when the player is disconnected."""
|
||||
guild = event.player.channel.guild
|
||||
log.verbose(f"Player in {guild} disconnected.")
|
||||
if await self.config.guild(guild).autoplaying():
|
||||
await self.pylav.player_state_db_manager.delete_player(guild.id)
|
||||
await self._stop_autoplay(guild)
|
||||
|
||||
async def _stop_autoplay(self, guild: discord.Guild):
|
||||
if not await self.config.guild(guild).autoplaying():
|
||||
return
|
||||
log.verbose("Stopping autoplay.")
|
||||
await self.config.guild(guild).autoplaying.set(False)
|
||||
await self.config.guild(guild).tracked_member.set(None)
|
||||
await self.config.guild(guild).paused_track.set(None)
|
12
autoplay/info.json
Normal file
12
autoplay/info.json
Normal file
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"author": ["Karlo"],
|
||||
"description": "Automatically play music that a guild member is listening to on Spotify.\nThis cog REQUIRES a fully set up PyLav and will not work without it.",
|
||||
"short": "Automatically play music that a guild member is listening to on Spotify.",
|
||||
"tags": ["music", "spotify"],
|
||||
"min_python_version": [3, 11, 0],
|
||||
"requirements": ["Py-Lav>=1.10.10"],
|
||||
"min_bot_version": "3.5.0.dev306",
|
||||
"max_bot_version": "3.5.99",
|
||||
"type": "COG",
|
||||
"end_user_data_statement": "This cog does not persistently store data or metadata about users."
|
||||
}
|
42
autoplay/locales/hr-HR.po
Normal file
42
autoplay/locales/hr-HR.po
Normal file
|
@ -0,0 +1,42 @@
|
|||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: karlo-cogs\n"
|
||||
"POT-Creation-Date: 2025-01-06 18:00+0000\n"
|
||||
"PO-Revision-Date: 2025-02-12 11:32\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Croatian\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Generated-By: redgettext 3.4.2\n"
|
||||
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
|
||||
"X-Crowdin-Project: karlo-cogs\n"
|
||||
"X-Crowdin-Project-ID: 523580\n"
|
||||
"X-Crowdin-Language: hr\n"
|
||||
"X-Crowdin-File: /autoplay/locales/messages.pot\n"
|
||||
"X-Crowdin-File-ID: 37\n"
|
||||
"Language: hr_HR\n"
|
||||
|
||||
#: autoplay/autoplay.py:20
|
||||
#, docstring
|
||||
msgid "Automatically play music that a guild member is listening to on Spotify."
|
||||
msgstr "Automatski reproduciraj glazbu koju neki član guilde sluša na Spotifyu."
|
||||
|
||||
#: autoplay/autoplay.py:28
|
||||
msgid "Start AutoPlay"
|
||||
msgstr "Pokreni AutoPlay"
|
||||
|
||||
#: autoplay/autoplay.py:64
|
||||
msgid "This can only be used in a guild."
|
||||
msgstr "Ovo se može koristiti samo u guildu."
|
||||
|
||||
#: autoplay/autoplay.py:86
|
||||
msgid "I am not in a voice channel. Please connect to a voice channel and then use this command."
|
||||
msgstr ""
|
||||
|
||||
#: autoplay/autoplay.py:96
|
||||
msgid "I'll now play whatever {member} is listening to.\n"
|
||||
"To stop autoplay, use a player command like `stop`"
|
||||
msgstr "Sada ću pustiti što god {member} sluša.\n"
|
||||
"Za zaustavljanje automatske reprodukcije upotrijebite naredbu playera poput `stop`"
|
||||
|
41
autoplay/locales/sr-SP.po
Normal file
41
autoplay/locales/sr-SP.po
Normal file
|
@ -0,0 +1,41 @@
|
|||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: karlo-cogs\n"
|
||||
"POT-Creation-Date: 2025-01-06 18:00+0000\n"
|
||||
"PO-Revision-Date: 2025-01-06 18:00\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Serbian (Cyrillic)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Generated-By: redgettext 3.4.2\n"
|
||||
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
|
||||
"X-Crowdin-Project: karlo-cogs\n"
|
||||
"X-Crowdin-Project-ID: 523580\n"
|
||||
"X-Crowdin-Language: sr\n"
|
||||
"X-Crowdin-File: /autoplay/locales/messages.pot\n"
|
||||
"X-Crowdin-File-ID: 37\n"
|
||||
"Language: sr_SP\n"
|
||||
|
||||
#: autoplay/autoplay.py:20
|
||||
#, docstring
|
||||
msgid "Automatically play music that a guild member is listening to on Spotify."
|
||||
msgstr ""
|
||||
|
||||
#: autoplay/autoplay.py:28
|
||||
msgid "Start AutoPlay"
|
||||
msgstr ""
|
||||
|
||||
#: autoplay/autoplay.py:64
|
||||
msgid "This can only be used in a guild."
|
||||
msgstr ""
|
||||
|
||||
#: autoplay/autoplay.py:86
|
||||
msgid "I am not in a voice channel. Please connect to a voice channel and then use this command."
|
||||
msgstr ""
|
||||
|
||||
#: autoplay/autoplay.py:96
|
||||
msgid "I'll now play whatever {member} is listening to.\n"
|
||||
"To stop autoplay, use a player command like `stop`"
|
||||
msgstr ""
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
{
|
||||
"author": [
|
||||
"Valerie (1050531216589332581)"
|
||||
"Valerie",
|
||||
"Rose Haven Network"
|
||||
],
|
||||
"install_msg": "Thanks for adding Ruby Cogs! Join our Discord @ https://discord.gg/5CA8sewarU",
|
||||
"name": "Ruby Cogs",
|
||||
|
|
|
@ -3,10 +3,10 @@
|
|||
"Valerie"
|
||||
],
|
||||
"install_msg": "Thank you for installing Modrinth Tracker",
|
||||
"name": "Modrinth tracker",
|
||||
"name": "Modrinth Tracker",
|
||||
"disabled": false,
|
||||
"short": "Track Modrinth Projects",
|
||||
"description": "Track Modrinth Projects..",
|
||||
"short": "Modrinth Projects Tracker",
|
||||
"description": "A cog for tracking Modrinth Projects via their project ID, using Modrinth's API",
|
||||
"tags": [
|
||||
"modrinth"
|
||||
],
|
||||
|
|
33
silentreplier/__init__.py
Normal file
33
silentreplier/__init__.py
Normal file
|
@ -0,0 +1,33 @@
|
|||
"""
|
||||
A large majority of the code here is from Zephyrkul's cmdreplier
|
||||
https://github.com/Zephyrkul/FluffyCogs/blob/master/cmdreplier/__init__.py
|
||||
Thanks Zeph
|
||||
"""
|
||||
|
||||
import contextlib
|
||||
from functools import partial
|
||||
|
||||
from redbot.core import commands
|
||||
from redbot.core.bot import Red
|
||||
|
||||
|
||||
async def silent_send(__sender, /, *args, **kwargs):
|
||||
ctx: commands.Context = __sender.__self__
|
||||
if not ctx.command_failed and "silent" not in kwargs:
|
||||
kwargs["silent"] = True
|
||||
return await __sender(*args, **kwargs)
|
||||
|
||||
|
||||
async def before_hook(ctx: commands.Context):
|
||||
if not ctx.message.edited_at and ctx.message.flags.silent:
|
||||
with contextlib.suppress(AttributeError):
|
||||
del ctx.send
|
||||
ctx.send = partial(silent_send, ctx.send)
|
||||
|
||||
|
||||
async def setup(bot: Red):
|
||||
bot.before_invoke(before_hook)
|
||||
|
||||
|
||||
async def teardown(bot: Red):
|
||||
bot.remove_before_invoke_hook(before_hook)
|
11
silentreplier/info.json
Normal file
11
silentreplier/info.json
Normal file
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"author": ["Karlo"],
|
||||
"description": "Have the bot silently respond to commands which have been prefixed with @silent",
|
||||
"short": "Have the bot silently respond to commands which have been prefixed with @silent",
|
||||
"install_msg": "This cog has no commands. It will silently respond to commands which have been prefixed with @silent",
|
||||
"tags": ["utility"],
|
||||
"min_bot_version": "3.5.0.dev329",
|
||||
"max_bot_version": "3.6.0.dev0",
|
||||
"type": "COG",
|
||||
"end_user_data_statement": "This cog does not persistently store any data or metadata about users."
|
||||
}
|
19
silentreplier/locales/hr-HR.po
Normal file
19
silentreplier/locales/hr-HR.po
Normal file
|
@ -0,0 +1,19 @@
|
|||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: karlo-cogs\n"
|
||||
"POT-Creation-Date: 2023-03-29 12:09+0000\n"
|
||||
"PO-Revision-Date: 2025-02-12 11:32\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Croatian\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Generated-By: redgettext 3.4.2\n"
|
||||
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
|
||||
"X-Crowdin-Project: karlo-cogs\n"
|
||||
"X-Crowdin-Project-ID: 523580\n"
|
||||
"X-Crowdin-Language: hr\n"
|
||||
"X-Crowdin-File: /silentreplier/locales/messages.pot\n"
|
||||
"X-Crowdin-File-ID: 43\n"
|
||||
"Language: hr_HR\n"
|
||||
|
19
silentreplier/locales/sr-SP.po
Normal file
19
silentreplier/locales/sr-SP.po
Normal file
|
@ -0,0 +1,19 @@
|
|||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: karlo-cogs\n"
|
||||
"POT-Creation-Date: 2023-03-29 12:09+0000\n"
|
||||
"PO-Revision-Date: 2023-03-29 12:10\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Serbian (Cyrillic)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Generated-By: redgettext 3.4.2\n"
|
||||
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
|
||||
"X-Crowdin-Project: karlo-cogs\n"
|
||||
"X-Crowdin-Project-ID: 523580\n"
|
||||
"X-Crowdin-Language: sr\n"
|
||||
"X-Crowdin-File: /silentreplier/locales/messages.pot\n"
|
||||
"X-Crowdin-File-ID: 43\n"
|
||||
"Language: sr_SP\n"
|
||||
|
Loading…
Add table
Reference in a new issue