Spaces:
Runtime error
Runtime error
Create main.py
Browse files
main.py
ADDED
@@ -0,0 +1,277 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import gradio as gr
|
2 |
+
import discord
|
3 |
+
from discord.ext import commands, tasks
|
4 |
+
from collections import defaultdict
|
5 |
+
import json
|
6 |
+
import os
|
7 |
+
from dotenv import load_dotenv
|
8 |
+
import os
|
9 |
+
load_dotenv()
|
10 |
+
|
11 |
+
intents = discord.Intents.default()
|
12 |
+
intents.messages = True
|
13 |
+
intents.message_content = True
|
14 |
+
intents.voice_states = True
|
15 |
+
intents.reactions = True
|
16 |
+
|
17 |
+
bot = commands.Bot(command_prefix='!', intents=intents)
|
18 |
+
|
19 |
+
# Place your bot's token here
|
20 |
+
TOKEN = os.getenv('TOKEN')
|
21 |
+
YOUR_CHANNEL_ID= os.getenv('YOUR_CHANNEL_ID')
|
22 |
+
|
23 |
+
# Initialize the sessions and pending_messages dictionaries
|
24 |
+
sessions = {}
|
25 |
+
pending_messages = {}
|
26 |
+
|
27 |
+
class Session:
|
28 |
+
def __init__(self):
|
29 |
+
self.camera_required = False
|
30 |
+
self.time_without_video = defaultdict(int)
|
31 |
+
self.warnings_given = defaultdict(int)
|
32 |
+
self.exemptions = set()
|
33 |
+
self.instructions_sent = False # Flag to track if instructions have been sent
|
34 |
+
|
35 |
+
def to_dict(self):
|
36 |
+
# Convert sets of Member IDs to lists for JSON serialization
|
37 |
+
return {
|
38 |
+
"camera_required": self.camera_required,
|
39 |
+
"time_without_video": {str(member_id): time for member_id, time in self.time_without_video.items()},
|
40 |
+
"warnings_given": {str(member_id): warnings for member_id, warnings in self.warnings_given.items()},
|
41 |
+
"exemptions": list(self.exemptions) # Assuming this already stores member IDs
|
42 |
+
}
|
43 |
+
|
44 |
+
@classmethod
|
45 |
+
def from_dict(cls, data):
|
46 |
+
session = cls()
|
47 |
+
session.camera_required = data.get("camera_required", False)
|
48 |
+
session.time_without_video = defaultdict(int, data.get("time_without_video", {}))
|
49 |
+
session.warnings_given = defaultdict(int, data.get("warnings_given", {}))
|
50 |
+
session.exemptions = set(data.get("exemptions", []))
|
51 |
+
session.instructions_sent = data.get("instructions_sent", False)
|
52 |
+
return session
|
53 |
+
|
54 |
+
|
55 |
+
def save_sessions():
|
56 |
+
with open('sessions.json', 'w') as f:
|
57 |
+
json.dump({str(vc_id): session.to_dict() for vc_id, session in sessions.items()}, f, ensure_ascii=False, indent=4)
|
58 |
+
def correct_and_load_json(path):
|
59 |
+
try:
|
60 |
+
with open(path, 'r') as file:
|
61 |
+
return json.load(file)
|
62 |
+
except json.JSONDecodeError:
|
63 |
+
print("JSON format error detected. Attempting to correct.")
|
64 |
+
with open(path, 'r') as file:
|
65 |
+
file_content = file.read()
|
66 |
+
corrected_content = file_content.replace("'", '"') # Replace single quotes with double quotes
|
67 |
+
# Implement other corrections here as needed
|
68 |
+
with open(path, 'w') as file:
|
69 |
+
file.write(corrected_content)
|
70 |
+
with open(path, 'r') as file:
|
71 |
+
return json.load(file) # Attempt to load corrected content
|
72 |
+
|
73 |
+
|
74 |
+
def load_sessions():
|
75 |
+
if os.path.exists('sessions.json') and os.path.getsize('sessions.json') > 0:
|
76 |
+
try:
|
77 |
+
session_data = correct_and_load_json('sessions.json')
|
78 |
+
for vc_id, data in session_data.items():
|
79 |
+
sessions[int(vc_id)] = Session.from_dict(data)
|
80 |
+
except json.JSONDecodeError:
|
81 |
+
print("Failed to correct JSON file. Please check the file format manually.")
|
82 |
+
else:
|
83 |
+
print("Session file not found or is empty, starting fresh.")
|
84 |
+
@bot.event
|
85 |
+
async def on_ready():
|
86 |
+
load_sessions()
|
87 |
+
print(f'Logged in as {bot.user.name}')
|
88 |
+
check_camera_status.start()
|
89 |
+
# Ensure save_sessions_task starts properly within the on_ready event
|
90 |
+
if not save_sessions_task.is_running():
|
91 |
+
save_sessions_task.start()
|
92 |
+
|
93 |
+
|
94 |
+
@bot.command()
|
95 |
+
async def test(ctx):
|
96 |
+
await ctx.send("Test successful!")
|
97 |
+
|
98 |
+
|
99 |
+
@tasks.loop(seconds=30)
|
100 |
+
async def check_camera_status():
|
101 |
+
for vc_id, session in sessions.items():
|
102 |
+
if not session.camera_required:
|
103 |
+
continue
|
104 |
+
vc = bot.get_channel(vc_id)
|
105 |
+
if vc is None:
|
106 |
+
continue
|
107 |
+
for member in vc.members:
|
108 |
+
if member.id in session.exemptions:
|
109 |
+
continue
|
110 |
+
if not member.voice.self_video:
|
111 |
+
session.time_without_video[member] += 30
|
112 |
+
if session.time_without_video[member] == 30:
|
113 |
+
session.warnings_given[member] = 1
|
114 |
+
await member.send("Warning 1: Please turn on your camera within the next 30 seconds.")
|
115 |
+
elif session.time_without_video[member] >= 60:
|
116 |
+
session.warnings_given[member] = 2
|
117 |
+
await member.send("Final Warning: You are being removed for not turning on your camera.")
|
118 |
+
await member.move_to(None)
|
119 |
+
# Reset the member's session data
|
120 |
+
session.time_without_video.pop(member, None)
|
121 |
+
session.warnings_given.pop(member, None)
|
122 |
+
else:
|
123 |
+
# Reset the member's session data if their camera is on
|
124 |
+
session.time_without_video.pop(member, None)
|
125 |
+
session.warnings_given.pop(member, None)
|
126 |
+
|
127 |
+
@tasks.loop(minutes=5) # Save sessions every 5 minutes
|
128 |
+
async def save_sessions_task():
|
129 |
+
save_sessions()
|
130 |
+
print("Sessions saved.")
|
131 |
+
|
132 |
+
@save_sessions_task.before_loop
|
133 |
+
async def before_save_sessions():
|
134 |
+
await bot.wait_until_ready()
|
135 |
+
|
136 |
+
|
137 |
+
@bot.event
|
138 |
+
async def on_voice_state_update(member, before, after):
|
139 |
+
if member.bot:
|
140 |
+
return
|
141 |
+
|
142 |
+
if after.channel and (not before.channel or before.channel.id != after.channel.id):
|
143 |
+
session = sessions.get(after.channel.id)
|
144 |
+
if session is None:
|
145 |
+
session = Session()
|
146 |
+
sessions[after.channel.id] = session
|
147 |
+
|
148 |
+
if any(role.name == "Admin" for role in member.roles):
|
149 |
+
other_admins_in_channel = [m for m in after.channel.members if m != member and any(role.name == "Admin" for role in m.roles)]
|
150 |
+
|
151 |
+
if not other_admins_in_channel:
|
152 |
+
# Replace "YOUR_CHANNEL_ID" with your actual general channel ID
|
153 |
+
general_channel_id = YOUR_CHANNEL_ID
|
154 |
+
general_channel = member.guild.get_channel(int(general_channel_id))
|
155 |
+
if not general_channel:
|
156 |
+
print(f"Couldn't find the channel with ID: {general_channel_id}.")
|
157 |
+
return
|
158 |
+
|
159 |
+
message_content = f"Admin {member.mention}, do you require cameras to be ON for this session in {after.channel.name}? React with π· for YES or β for NO."
|
160 |
+
|
161 |
+
try:
|
162 |
+
message = await general_channel.send(message_content)
|
163 |
+
pending_messages[message.id] = after.channel.id
|
164 |
+
|
165 |
+
await message.add_reaction("π·")
|
166 |
+
await message.add_reaction("β")
|
167 |
+
except discord.DiscordException as e:
|
168 |
+
print(f"Failed to send message or add reactions due to: {e}")
|
169 |
+
|
170 |
+
|
171 |
+
@bot.command()
|
172 |
+
@commands.has_permissions(manage_channels=True)
|
173 |
+
async def setperms(ctx, channel: discord.TextChannel = None):
|
174 |
+
channel = channel or ctx.channel
|
175 |
+
if not channel.permissions_for(ctx.guild.me).manage_channels:
|
176 |
+
await ctx.send("I don't have permission to manage channels here.")
|
177 |
+
return
|
178 |
+
bot_member = ctx.guild.get_member(bot.user.id)
|
179 |
+
overwrites = {
|
180 |
+
ctx.guild.default_role: discord.PermissionOverwrite(read_messages=False),
|
181 |
+
bot_member: discord.PermissionOverwrite(read_messages=True, send_messages=True, manage_messages=True, add_reactions=True)
|
182 |
+
}
|
183 |
+
try:
|
184 |
+
await channel.set_permissions(ctx.guild.default_role, read_messages=False)
|
185 |
+
await channel.set_permissions(bot_member, **overwrites)
|
186 |
+
await ctx.send(f"Permissions set for {channel.mention} successfully!")
|
187 |
+
except Exception as e:
|
188 |
+
await ctx.send(f"Failed to set permissions for {channel.mention} due to: {e}")
|
189 |
+
|
190 |
+
@bot.event
|
191 |
+
async def on_reaction_add(reaction, user):
|
192 |
+
await process_reaction(reaction, user)
|
193 |
+
|
194 |
+
@bot.event
|
195 |
+
async def on_reaction_remove(reaction, user):
|
196 |
+
await process_reaction(reaction, user)
|
197 |
+
|
198 |
+
async def process_reaction(reaction, user):
|
199 |
+
if reaction.message.id in pending_messages and not user.bot:
|
200 |
+
channel_id = pending_messages[reaction.message.id]
|
201 |
+
session = sessions.get(channel_id)
|
202 |
+
|
203 |
+
if session and any(role.name == "Admin" for role in user.roles):
|
204 |
+
if str(reaction.emoji) == "π·":
|
205 |
+
session.camera_required = True
|
206 |
+
session.exemptions.clear()
|
207 |
+
elif str(reaction.emoji) == "β":
|
208 |
+
session.camera_required = False
|
209 |
+
session.exemptions.clear()
|
210 |
+
|
211 |
+
# Edit the message to reflect the updated session status
|
212 |
+
await reaction.message.edit(content=f"Camera requirement for {reaction.message.channel.guild.get_channel(channel_id).name} has been {'UPDATED to ON' if session.camera_required else 'UPDATED to OFF'}.")
|
213 |
+
|
214 |
+
# Send instructions only if they haven't been sent before
|
215 |
+
if not session.instructions_sent:
|
216 |
+
follow_up_message = (
|
217 |
+
"**Next Steps for Admins:**\n"
|
218 |
+
"- Use `#camoff @user` to exempt any specific user from the camera requirement.\n"
|
219 |
+
"- Use `#camoff` to exempt yourself if actively participating in the session.\n"
|
220 |
+
"- Use `#end` to conclude the session and reset all settings.\n"
|
221 |
+
"**Note:** These commands work within the context of the current voice session."
|
222 |
+
)
|
223 |
+
await reaction.message.channel.send(follow_up_message)
|
224 |
+
session.instructions_sent = True # Update the flag to prevent re-sending
|
225 |
+
|
226 |
+
# Cleanup
|
227 |
+
del pending_messages[reaction.message.id]
|
228 |
+
|
229 |
+
def dummy_function(input_text):
|
230 |
+
return "This is a dummy function for the Gradio UI."
|
231 |
+
|
232 |
+
iface = gr.Interface(fn=dummy_function,
|
233 |
+
inputs=gr.inputs.Textbox(label="Input"),
|
234 |
+
outputs=gr.outputs.Textbox(label="Output"),
|
235 |
+
title="Discord Bot",
|
236 |
+
description="This Space runs a Discord bot in the background.")
|
237 |
+
|
238 |
+
|
239 |
+
@bot.event
|
240 |
+
async def on_message(message):
|
241 |
+
# Skip bot messages and non-admin users
|
242 |
+
if message.author.bot or not any(role.name == "Admin" for role in message.author.roles):
|
243 |
+
await bot.process_commands(message) # Ensure other commands are still processed
|
244 |
+
return
|
245 |
+
|
246 |
+
# Process commands only if the author is in a voice channel
|
247 |
+
if message.author.voice:
|
248 |
+
vc_id = message.author.voice.channel.id
|
249 |
+
session = sessions.get(vc_id)
|
250 |
+
|
251 |
+
if not session:
|
252 |
+
await message.channel.send("No active session found for this voice channel.")
|
253 |
+
await bot.process_commands(message)
|
254 |
+
return
|
255 |
+
|
256 |
+
# Admin commands
|
257 |
+
if "#camoff" in message.content:
|
258 |
+
# If no users are mentioned, exempt the author; otherwise, exempt mentioned users
|
259 |
+
users_to_exempt = message.mentions if message.mentions else [message.author]
|
260 |
+
for user in users_to_exempt:
|
261 |
+
session.exemptions.add(user.id)
|
262 |
+
await message.channel.send(f"{user.display_name} is now exempt from the camera requirement.")
|
263 |
+
elif "#end" in message.content:
|
264 |
+
# Clear exemptions and reset camera requirement
|
265 |
+
session.exemptions.clear()
|
266 |
+
session.camera_required = False
|
267 |
+
await message.channel.send("Session ended: Camera requirements and exemptions have been reset.")
|
268 |
+
|
269 |
+
await bot.process_commands(message) # Ensure this is at the end to process other commands
|
270 |
+
|
271 |
+
# Process other commands
|
272 |
+
await bot.process_commands(message)
|
273 |
+
@check_camera_status.before_loop
|
274 |
+
async def before_camera_check():
|
275 |
+
await bot.wait_until_ready()
|
276 |
+
iface.launch(inline=False, share=True)
|
277 |
+
bot.run(TOKEN)
|