Upload 2 Cogs
Some checks are pending
Run pre-commit / Run pre-commit (push) Waiting to run

This commit is contained in:
Valerie 2025-04-27 08:06:54 -04:00
parent e6d3d13e5d
commit e700af61cb
12 changed files with 437 additions and 5 deletions

View file

@ -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
View 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
View 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
View 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
View 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
View 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 ""

View file

@ -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",

View file

@ -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
View 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
View 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."
}

View 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"

View 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"