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.
- froscord-0.1.0/PKG-INFO +6 -0
- froscord-0.1.0/froscord/__init__.py +0 -0
- froscord-0.1.0/froscord/voice/__init__.py +3 -0
- froscord-0.1.0/froscord/voice/core.py +98 -0
- froscord-0.1.0/froscord/voice/panel.py +155 -0
- froscord-0.1.0/froscord.egg-info/PKG-INFO +6 -0
- froscord-0.1.0/froscord.egg-info/SOURCES.txt +10 -0
- froscord-0.1.0/froscord.egg-info/dependency_links.txt +1 -0
- froscord-0.1.0/froscord.egg-info/requires.txt +1 -0
- froscord-0.1.0/froscord.egg-info/top_level.txt +1 -0
- froscord-0.1.0/pyproject.toml +10 -0
- froscord-0.1.0/setup.cfg +4 -0
froscord-0.1.0/PKG-INFO
ADDED
|
File without changes
|
|
@@ -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,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
|
+
|
|
@@ -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"]
|
froscord-0.1.0/setup.cfg
ADDED