froscord 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,6 @@
1
+ Metadata-Version: 2.4
2
+ Name: froscord
3
+ Version: 0.1.0
4
+ Summary: Simple Discord helpers for voice channels
5
+ Author: Frosea
6
+ Requires-Dist: discord.py>=2.0
File without changes
@@ -0,0 +1,3 @@
1
+ from .core import VoiceSystem
2
+
3
+ __all__ = ["VoiceSystem"]
@@ -0,0 +1,98 @@
1
+ # froscord/voice/core.py
2
+ import discord
3
+ from discord.ext import commands
4
+
5
+ from .panel import VoicePanel
6
+
7
+
8
+ class VoiceSystem:
9
+ def __init__(
10
+ self,
11
+ bot: commands.Bot,
12
+ join_channel: str,
13
+ category: str,
14
+ user_limit: int = 0,
15
+ panel: bool = True,
16
+ ):
17
+ self.bot = bot
18
+ self.join_channel = join_channel
19
+ self.category_name = category
20
+ self.user_limit = user_limit
21
+ self.panel_enabled = panel
22
+
23
+ # replaces TEMP_VC (no globals anymore)
24
+ # vc_id -> {"owner": user_id, "message": message_id}
25
+ self._vcs = {}
26
+
27
+ # register listener internally (no Cog needed)
28
+ bot.add_listener(self._on_voice_state_update)
29
+
30
+ # ───────────────── EVENT ─────────────────
31
+
32
+ async def _on_voice_state_update(self, member, before, after):
33
+ # JOIN → CREATE
34
+ if after.channel and after.channel.name == self.join_channel:
35
+ await self._create_vc(member)
36
+
37
+ # LEAVE → CLEANUP
38
+ if before.channel and before.channel.id in self._vcs:
39
+ await self._handle_leave(before.channel, member)
40
+
41
+ # ───────────────── CREATE ─────────────────
42
+
43
+ async def _create_vc(self, member):
44
+ guild = member.guild
45
+
46
+ category = discord.utils.get(
47
+ guild.categories,
48
+ name=self.category_name
49
+ )
50
+ if not category:
51
+ category = await guild.create_category(self.category_name)
52
+
53
+ vc = await guild.create_voice_channel(
54
+ name=f"🔊 {member.display_name}'s VC",
55
+ category=category,
56
+ user_limit=self.user_limit
57
+ )
58
+
59
+ await member.move_to(vc)
60
+
61
+ self._vcs[vc.id] = {"owner": member.id, "message": None}
62
+
63
+ if self.panel_enabled:
64
+ panel = VoicePanel(self, vc.id)
65
+ msg = await vc.send(embed=panel.build_embed(), view=panel)
66
+ self._vcs[vc.id]["message"] = msg.id
67
+
68
+ # ───────────────── LEAVE / CLEANUP ─────────────────
69
+
70
+ async def _handle_leave(self, channel, member):
71
+ data = self._vcs[channel.id]
72
+
73
+ if member.id == data["owner"]:
74
+ if channel.members:
75
+ data["owner"] = channel.members[0].id
76
+ else:
77
+ await channel.delete()
78
+ self._vcs.pop(channel.id, None)
79
+
80
+ # ───────────────── HELPERS (USED BY PANEL) ─────────────────
81
+
82
+ def get_vc_data(self, vc_id: int):
83
+ return self._vcs.get(vc_id)
84
+
85
+ async def refresh_panel(self, interaction, vc_id: int):
86
+ data = self._vcs.get(vc_id)
87
+ if not data:
88
+ return
89
+
90
+ vc = interaction.guild.get_channel(vc_id)
91
+ if not vc:
92
+ return
93
+
94
+ owner = interaction.guild.get_member(data["owner"])
95
+ msg = await interaction.channel.fetch_message(data["message"])
96
+
97
+ panel = VoicePanel(self, vc_id)
98
+ await msg.edit(embed=panel.build_embed(), view=panel)
@@ -0,0 +1,155 @@
1
+ # froscord/voice/panel.py
2
+ import discord
3
+
4
+
5
+ # ───────────────── EMBED ─────────────────
6
+
7
+ def voice_embed(vc: discord.VoiceChannel, owner: discord.Member):
8
+ locked = not vc.permissions_for(vc.guild.default_role).connect
9
+
10
+ embed = discord.Embed(
11
+ title="🎧 Voice Channel Control",
12
+ description="Manage **your personal voice channel**",
13
+ color=discord.Color.blurple()
14
+ )
15
+
16
+ embed.add_field(name="👑 Owner", value=owner.mention, inline=True)
17
+ embed.add_field(
18
+ name="👥 Members",
19
+ value=f"{len(vc.members)} / {vc.user_limit or '∞'}",
20
+ inline=True
21
+ )
22
+ embed.add_field(
23
+ name="🔒 Status",
24
+ value="Locked" if locked else "Unlocked",
25
+ inline=True
26
+ )
27
+
28
+ embed.set_footer(text="Only the VC owner can use this panel")
29
+ return embed
30
+
31
+
32
+ # ───────────────── MODALS ─────────────────
33
+
34
+ class RenameVCModal(discord.ui.Modal, title="Rename Voice Channel"):
35
+ new_name = discord.ui.TextInput(label="New channel name", max_length=100)
36
+
37
+ def __init__(self, manager, vc_id: int):
38
+ super().__init__()
39
+ self.manager = manager
40
+ self.vc_id = vc_id
41
+
42
+ async def on_submit(self, interaction: discord.Interaction):
43
+ vc = interaction.guild.get_channel(self.vc_id)
44
+ if not vc:
45
+ return await interaction.response.send_message(
46
+ "❌ Voice channel not found", ephemeral=True
47
+ )
48
+
49
+ await vc.edit(name=self.new_name.value)
50
+ await interaction.response.send_message(
51
+ f"✏️ VC renamed to **{self.new_name.value}**", ephemeral=True
52
+ )
53
+ await self.manager.refresh_panel(interaction, self.vc_id)
54
+
55
+
56
+ class LimitVCModal(discord.ui.Modal, title="Set User Limit"):
57
+ limit = discord.ui.TextInput(label="User limit (0 = unlimited)", max_length=2)
58
+
59
+ def __init__(self, manager, vc_id: int):
60
+ super().__init__()
61
+ self.manager = manager
62
+ self.vc_id = vc_id
63
+
64
+ async def on_submit(self, interaction: discord.Interaction):
65
+ vc = interaction.guild.get_channel(self.vc_id)
66
+ if not vc:
67
+ return await interaction.response.send_message(
68
+ "❌ Voice channel not found", ephemeral=True
69
+ )
70
+
71
+ try:
72
+ value = int(self.limit.value)
73
+ if value < 0:
74
+ raise ValueError
75
+ except ValueError:
76
+ return await interaction.response.send_message(
77
+ "❌ Enter a valid number", ephemeral=True
78
+ )
79
+
80
+ await vc.edit(user_limit=value)
81
+ await interaction.response.send_message(
82
+ f"👥 User limit set to **{value or '∞'}**", ephemeral=True
83
+ )
84
+ await self.manager.refresh_panel(interaction, self.vc_id)
85
+
86
+
87
+ # ───────────────── VIEW ─────────────────
88
+
89
+ class VoicePanel(discord.ui.View):
90
+ def __init__(self, manager, vc_id: int):
91
+ super().__init__(timeout=None)
92
+ self.manager = manager
93
+ self.vc_id = vc_id
94
+
95
+ # owner check
96
+ async def interaction_check(self, interaction: discord.Interaction):
97
+ data = self.manager.get_vc_data(self.vc_id)
98
+ if not data or interaction.user.id != data["owner"]:
99
+ await interaction.response.send_message(
100
+ "❌ This control panel is not yours.", ephemeral=True
101
+ )
102
+ return False
103
+ return True
104
+
105
+ def build_embed(self):
106
+ data = self.manager.get_vc_data(self.vc_id)
107
+ if not data:
108
+ return None
109
+
110
+ vc = self.manager.bot.get_channel(self.vc_id)
111
+ owner = vc.guild.get_member(data["owner"])
112
+ return voice_embed(vc, owner)
113
+
114
+ # ───── BUTTONS ─────
115
+
116
+ @discord.ui.button(label="Lock", emoji="🔒", style=discord.ButtonStyle.secondary)
117
+ async def lock(self, interaction, button):
118
+ vc = interaction.guild.get_channel(self.vc_id)
119
+ await vc.set_permissions(interaction.guild.default_role, connect=False)
120
+ await interaction.response.send_message("🔒 VC locked", ephemeral=True)
121
+ await self.manager.refresh_panel(interaction, self.vc_id)
122
+
123
+ @discord.ui.button(label="Unlock", emoji="🔓", style=discord.ButtonStyle.success)
124
+ async def unlock(self, interaction, button):
125
+ vc = interaction.guild.get_channel(self.vc_id)
126
+ await vc.set_permissions(interaction.guild.default_role, connect=True)
127
+ await interaction.response.send_message("🔓 VC unlocked", ephemeral=True)
128
+ await self.manager.refresh_panel(interaction, self.vc_id)
129
+
130
+ @discord.ui.button(label="Rename", emoji="✏️", style=discord.ButtonStyle.primary)
131
+ async def rename(self, interaction, button):
132
+ await interaction.response.send_modal(
133
+ RenameVCModal(self.manager, self.vc_id)
134
+ )
135
+
136
+ @discord.ui.button(label="Set Limit", emoji="👥", style=discord.ButtonStyle.primary)
137
+ async def set_limit(self, interaction, button):
138
+ await interaction.response.send_modal(
139
+ LimitVCModal(self.manager, self.vc_id)
140
+ )
141
+
142
+ @discord.ui.button(label="Claim", emoji="👑", style=discord.ButtonStyle.secondary)
143
+ async def claim(self, interaction, button):
144
+ data = self.manager.get_vc_data(self.vc_id)
145
+ data["owner"] = interaction.user.id
146
+ await interaction.response.send_message(
147
+ "👑 You are now the owner", ephemeral=True
148
+ )
149
+ await self.manager.refresh_panel(interaction, self.vc_id)
150
+
151
+ @discord.ui.button(label="Delete", emoji="🗑️", style=discord.ButtonStyle.danger)
152
+ async def delete(self, interaction, button):
153
+ vc = interaction.guild.get_channel(self.vc_id)
154
+ await vc.delete()
155
+ self.manager._vcs.pop(self.vc_id, None)
@@ -0,0 +1,6 @@
1
+ Metadata-Version: 2.4
2
+ Name: froscord
3
+ Version: 0.1.0
4
+ Summary: Simple Discord helpers for voice channels
5
+ Author: Frosea
6
+ Requires-Dist: discord.py>=2.0
@@ -0,0 +1,10 @@
1
+ pyproject.toml
2
+ froscord/__init__.py
3
+ froscord.egg-info/PKG-INFO
4
+ froscord.egg-info/SOURCES.txt
5
+ froscord.egg-info/dependency_links.txt
6
+ froscord.egg-info/requires.txt
7
+ froscord.egg-info/top_level.txt
8
+ froscord/voice/__init__.py
9
+ froscord/voice/core.py
10
+ froscord/voice/panel.py
@@ -0,0 +1 @@
1
+ discord.py>=2.0
@@ -0,0 +1 @@
1
+ froscord
@@ -0,0 +1,10 @@
1
+ [build-system]
2
+ requires = ["setuptools"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "froscord"
7
+ version = "0.1.0"
8
+ description = "Simple Discord helpers for voice channels"
9
+ authors = [{name="Frosea"}]
10
+ dependencies = ["discord.py>=2.0"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+