aboutsummaryrefslogtreecommitdiffstats
path: root/arthur/exts/fun/numbers.py
blob: 90c055104ea48e35c3f60f3d86f47be7625a67ae (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
import discord
from discord.ext import commands

from arthur.apis.netcup.ssh import rce_as_a_service
from arthur.config import CONFIG


class Numbers(commands.GroupCog):
    """Commands for working with and controlling the numbers forwarder."""

    def __init__(self, bot: commands.Bot):
        self.bot = bot
        self.devops_vc: discord.VoiceChannel | discord.StageChannel | None = None

    async def cog_load(self) -> None:
        """Join devops channel on cog load."""
        devops_vc = self.bot.get_channel(CONFIG.devops_vc_id)
        if not isinstance(devops_vc, (discord.VoiceChannel, discord.StageChannel)):
            return

        self.devops_vc = devops_vc
        vc = await devops_vc.connect(self_deaf=True, self_mute=False)
        vc.play(discord.FFmpegOpusAudio(CONFIG.numbers_url))

    async def cog_unload(self) -> None:
        """Disconnect from devops channel on cog unload."""
        if self.devops_vc and (vc := self.devops_vc.guild.voice_client):
            await vc.disconnect(force=True)

    @commands.group(invoke_without_command=True)
    async def numbers(
        self,
        ctx: commands.Context,
        channel: discord.VoiceChannel | discord.StageChannel | None = None,
    ) -> None:
        """
        Join a voice channel and forward numbers.

        If channel is not provided, the bot will join the voice channel of the user who invoked the command.
        """
        if not ctx.guild:
            return

        if not channel and isinstance(ctx.author, discord.Member) and (vs := ctx.author.voice):
            channel = vs.channel

        if not channel:
            await ctx.send(":x: Join a voice channel first!")
            return

        if vc := ctx.guild.voice_client:
            # Should always be the case, but I wanted type hinting
            if isinstance(vc, discord.VoiceClient):
                await vc.move_to(channel)
                await ctx.message.add_reaction("🔊")
            return

        vc = await channel.connect(self_deaf=True, self_mute=False)
        vc.play(discord.FFmpegOpusAudio(CONFIG.numbers_url))
        await ctx.message.add_reaction("🔊")

    @numbers.command()
    async def tts(self, ctx: commands.Context, *, text: str) -> None:
        """Have KA read out a message in the current VC."""
        if not ctx.guild or not ctx.guild.voice_client:
            return

        if not ctx.bot.is_owner(ctx.author):
            await ctx.message.add_reaction("❌")
            return

        await rce_as_a_service(
            f"echo '{text}' > /opt/messages/tts_message.txt && "
            "touch -a -m -t 197501010001 /opt/messages/tts_message.txt && "
            "sudo /opt/numbers-code-generator.sh"
        )

    @numbers.command()
    async def stop(self, ctx: commands.Context) -> None:
        """Stop relaying numbers in the voice channel."""
        if ctx.guild and (vc := ctx.guild.voice_client):
            await vc.disconnect(force=True)
            await ctx.message.add_reaction("🔇")
        else:
            await ctx.send(":x: Not in a voice channel!")


async def setup(bot: commands.Bot) -> None:
    """Set up the numbers cog."""
    await bot.add_cog(Numbers(bot))