Skip to content

Commit 7d254e1

Browse files
committed
v.2
1 parent 7b9530f commit 7d254e1

File tree

12 files changed

+518
-399
lines changed

12 files changed

+518
-399
lines changed

HyperCodePyBot/__init__.py

Lines changed: 389 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,389 @@
1+
# __init__.py
2+
import discord
3+
from discord.ext import commands
4+
from discord import app_commands
5+
6+
from packaging import version
7+
8+
from typing import *
9+
10+
# Import utility modules
11+
if __name__ != "__main__":
12+
from .utils.token_utils import *
13+
from .utils.embed_utils import *
14+
from .utils.webhook_utils import *
15+
else:
16+
from utils.token_utils import *
17+
from utils.embed_utils import *
18+
from utils.webhook_utils import *
19+
20+
token = None # Placeholder for the token, to be set later
21+
22+
# Ensure discord.py version is compatible
23+
if version.parse(discord.__version__) < version.parse("2.0.0"):
24+
raise RuntimeError("HyperCodePyBot requires discord.py version 2.0.0 or higher.")
25+
26+
class HyperCodePyBot(commands.Bot):
27+
def __init__(self, command_prefix='!'):
28+
if command_prefix == "/": warn("Command prefix cannot be '/' as it is reserved for slash commands. Use a different prefix or no prefix at all. it is buggy it not show autocompletion in chat and not recommended.")
29+
intents = discord.Intents.default()
30+
intents.message_content = True
31+
super().__init__(command_prefix=command_prefix, intents=intents)
32+
self.bot = self # For compatibility with older code
33+
34+
if not self.tree:
35+
# discord.py 2.0+ requires a CommandTree for slash commands
36+
self.tree = app_commands.CommandTree(self)
37+
self._token = None
38+
39+
# === Lifecycle Events ===
40+
async def setup_hook(self) -> None:
41+
"""Initial setup after the bot is ready."""
42+
await self.tree.sync()
43+
print("Commands synced with Discord!")
44+
if hasattr(self, 'hook') and self.hook.on_setup_hook:
45+
await self.hook.on_setup_hook()
46+
47+
async def on_ready(self):
48+
print(f'Logged in as {self.user} (ID: {self.user.id})')
49+
print('------')
50+
if hasattr(self, 'hook') and self.hook.on_ready:
51+
await self.hook.on_ready()
52+
53+
async def on_command_error(self, ctx, error):
54+
await ctx.send(embed=embed_utils.create_embed("Error: Command failed", str(error), color=0xff0000))
55+
if hasattr(self, 'hook') and self.hook.on_command_error:
56+
await self.hook.on_command_error(ctx, error)
57+
58+
async def on_interaction(self, interaction: discord.Interaction):
59+
"""Handle interactions, such as slash commands."""
60+
if interaction.type == discord.InteractionType.application_command:
61+
await self.tree.process_interaction(interaction)
62+
if hasattr(self, 'hook') and self.hook.on_interaction:
63+
await self.hook.on_interaction(interaction)
64+
65+
async def on_message(self, message: discord.Message):
66+
"""Handle messages sent in channels."""
67+
if message.author == self.user:
68+
return
69+
await self.process_commands(message)
70+
if hasattr(self, 'hook') and self.hook.on_message:
71+
await self.hook.on_message(message)
72+
73+
async def on_disconnect(self):
74+
"""Handle bot disconnection."""
75+
print("Bot has disconnected.")
76+
if hasattr(self, 'hook') and self.hook.on_disconnect:
77+
await self.hook.on_disconnect()
78+
79+
async def on_connect(self):
80+
"""Handle bot connection."""
81+
print("Bot has connected.")
82+
if hasattr(self, 'hook') and self.hook.on_connect:
83+
await self.hook.on_connect()
84+
85+
def run(self, new_token: Optional[str] = None):
86+
"""Run the bot with the provided token."""
87+
global token
88+
89+
# Priority: passed token > instance token > global token
90+
running_token = new_token or self._token or token
91+
92+
if running_token is None:
93+
raise ValueError("Token is not set. Please load a token before running the bot.")
94+
95+
# Update all token references
96+
self._token = running_token
97+
token = running_token
98+
99+
super().run(running_token)
100+
101+
def reboot_bot(self, callback: Optional[Callable[[], Any]] = None):
102+
"""Reboot the bot."""
103+
print("Rebooting bot...")
104+
try:
105+
self.loop.run_until_complete(self.close())
106+
self.loop.run_until_complete(self.start(self._token))
107+
callback(True, "Reboot successful") if callback else None
108+
except Exception as e:
109+
print(f"Error rebooting bot: {e}")
110+
callback(False, str(e)) if callback else None
111+
112+
def add_custom_command(self, name, function):
113+
"""Add a custom prefix command to the bot."""
114+
self.add_command(commands.Command(function, name=name, help=function.__doc__))
115+
116+
# === Token Methods (delegated to utils) ===
117+
@staticmethod
118+
def load_token_from_string(token_string):
119+
global token
120+
token = token_utils.load_token_from_string(token_string)
121+
return token
122+
123+
@staticmethod
124+
def load_token_from_file(file_path):
125+
global token
126+
token = token_utils.load_token_from_file(file_path)
127+
return token
128+
129+
@staticmethod
130+
def load_token_from_environment(env_var):
131+
global token
132+
token = token_utils.load_token_from_environment(env_var)
133+
return token
134+
135+
@staticmethod
136+
def load_token_from_config(config_file, token_key):
137+
global token
138+
token = token_utils.load_token_from_config(config_file, token_key)
139+
return token
140+
141+
@staticmethod
142+
def load_token_from_secret(secret_name):
143+
global token
144+
token = token_utils.load_token_from_secret(secret_name)
145+
return token
146+
147+
class Member:
148+
@staticmethod
149+
def get_member(guild: discord.Guild, member_id: int):
150+
"""Get a member by ID in a guild."""
151+
return guild.get_member(member_id)
152+
153+
@staticmethod
154+
def get_members(guild: discord.Guild):
155+
"""Get all members in a guild."""
156+
return guild.members
157+
158+
@staticmethod
159+
def get_member_roles(member: discord.Member):
160+
"""Get roles of a member."""
161+
return member.roles
162+
163+
@staticmethod
164+
def add_role(member: discord.Member, role: discord.Role):
165+
"""Add a role to a member."""
166+
return member.add_roles(role)
167+
168+
@staticmethod
169+
def remove_role(member: discord.Member, role: discord.Role):
170+
"""Remove a role from a member."""
171+
return member.remove_roles(role)
172+
173+
class Channel:
174+
@staticmethod
175+
def get_channel(guild: discord.Guild, channel_id: int):
176+
"""Get a channel by ID in a guild."""
177+
return guild.get_channel(channel_id)
178+
179+
@staticmethod
180+
def get_channels(guild: discord.Guild):
181+
"""Get all channels in a guild."""
182+
return guild.channels
183+
184+
@staticmethod
185+
def create_text_channel(guild: discord.Guild, name: str, category: Optional[discord.CategoryChannel] = None):
186+
"""Create a text channel in a guild."""
187+
return guild.create_text_channel(name, category=category)
188+
189+
@staticmethod
190+
def create_voice_channel(guild: discord.Guild, name: str, category: Optional[discord.CategoryChannel] = None):
191+
"""Create a voice channel in a guild."""
192+
return guild.create_voice_channel(name, category=category)
193+
194+
@staticmethod
195+
def delete_channel(channel: discord.abc.GuildChannel):
196+
"""Delete a channel."""
197+
return channel.delete()
198+
199+
@staticmethod
200+
def edit_channel(channel: discord.abc.GuildChannel, **kwargs):
201+
"""Edit a channel's properties."""
202+
return channel.edit(**kwargs)
203+
204+
def move_channel(channel: discord.abc.GuildChannel, position: int):
205+
"""Move a channel to a specific position."""
206+
return channel.edit(position=position)
207+
208+
class Role:
209+
@staticmethod
210+
def get_role(guild: discord.Guild, role_id: int):
211+
"""Get a role by ID in a guild."""
212+
return guild.get_role(role_id)
213+
214+
@staticmethod
215+
def get_roles(guild: discord.Guild):
216+
"""Get all roles in a guild."""
217+
return guild.roles
218+
219+
@staticmethod
220+
def create_role(guild: discord.Guild, name: str, **kwargs):
221+
"""Create a role in a guild."""
222+
return guild.create_role(name=name, **kwargs)
223+
224+
@staticmethod
225+
def delete_role(role: discord.Role):
226+
"""Delete a role."""
227+
return role.delete()
228+
229+
@staticmethod
230+
def edit_role(role: discord.Role, **kwargs):
231+
"""Edit a role's properties."""
232+
return role.edit(**kwargs)
233+
234+
class Guild:
235+
@staticmethod
236+
def get_guild(guild_id: int):
237+
"""Get a guild by ID."""
238+
return discord.utils.get(discord.Client.guilds, id=guild_id)
239+
240+
@staticmethod
241+
def get_guilds(client: discord.Client):
242+
"""Get all guilds the bot is in."""
243+
return client.guilds
244+
245+
@staticmethod
246+
def create_guild(name: str):
247+
"""Create a new guild."""
248+
return discord.Guild.create(name=name)
249+
250+
class VoiceChannel:
251+
@staticmethod
252+
def join_vc(channel: discord.VoiceChannel):
253+
"""Join a voice channel."""
254+
return channel.connect()
255+
def leave_vc(channel: discord.VoiceChannel):
256+
"""Leave a voice channel."""
257+
return channel.disconnect()
258+
def play_audio(channel: discord.VoiceChannel, source: str):
259+
"""Play audio in a voice channel."""
260+
if not channel.guild.voice_client:
261+
return channel.connect()
262+
return channel.guild.voice_client.play(discord.FFmpegPCMAudio(source))
263+
def stop_audio(channel: discord.VoiceChannel):
264+
"""Stop audio playback in a voice channel."""
265+
if channel.guild.voice_client and channel.guild.voice_client.is_playing():
266+
return channel.guild.voice_client.stop()
267+
return None
268+
def pause_audio(channel: discord.VoiceChannel):
269+
"""Pause audio playback in a voice channel."""
270+
if channel.guild.voice_client and channel.guild.voice_client.is_playing():
271+
return channel.guild.voice_client.pause()
272+
return None
273+
def resume_audio(channel: discord.VoiceChannel):
274+
"""Resume audio playback in a voice channel."""
275+
if channel.guild.voice_client and channel.guild.voice_client.is_paused():
276+
return channel.guild.voice_client.resume()
277+
return None
278+
def get_vc_status(channel: discord.VoiceChannel):
279+
"""Get the status of the voice channel."""
280+
if channel.guild.voice_client:
281+
return {
282+
"is_connected": channel.guild.voice_client.is_connected(),
283+
"is_playing": channel.guild.voice_client.is_playing(),
284+
"is_paused": channel.guild.voice_client.is_paused(),
285+
"channel": channel.guild.voice_client.channel.name if channel.guild.voice_client.channel else None
286+
}
287+
return {"is_connected": False, "is_playing": False, "is_paused": False, "channel": None}
288+
def get_vc_channel(channel: discord.VoiceChannel):
289+
"""Get the voice channel the bot is connected to."""
290+
if channel.guild.voice_client:
291+
return channel.guild.voice_client.channel
292+
return None
293+
def get_vc_members(channel: discord.VoiceChannel):
294+
"""Get the members in the voice channel."""
295+
if channel.guild.voice_client:
296+
return [member.name for member in channel.guild.voice_client.channel.members]
297+
return []
298+
def kick_member(channel: discord.VoiceChannel, member: discord.Member):
299+
"""Kick a member from the voice channel."""
300+
if channel.guild.voice_client and member in channel.guild.voice_client.channel.members:
301+
return member.move_to(None)
302+
return None
303+
def move_member(channel: discord.VoiceChannel, member: discord.Member, target_channel: discord.VoiceChannel):
304+
"""Move a member to another voice channel."""
305+
if channel.guild.voice_client and member in channel.guild.voice_client.channel.members:
306+
return member.move_to(target_channel)
307+
return None
308+
309+
class WebhookUtils:
310+
@staticmethod
311+
def add_webhook(url):
312+
return webhook_utils.add_webhook(url)
313+
314+
@staticmethod
315+
def send_webhook(webhook, content):
316+
return webhook_utils.send_webhook(webhook, content)
317+
318+
@staticmethod
319+
def send_embed(webhook, embed):
320+
return webhook_utils.send_embed(webhook, embed)
321+
322+
# === Embed Utility Methods ===
323+
@staticmethod
324+
def create_embed(title, description=None, color=0x000000):
325+
return embed_utils.create_embed(title, description, color)
326+
327+
@staticmethod
328+
def set_embed_title(embed, title):
329+
return embed_utils.set_embed_title(embed, title)
330+
331+
@staticmethod
332+
def set_embed_description(embed, description):
333+
return embed_utils.set_embed_description(embed, description)
334+
335+
@staticmethod
336+
def set_embed_color(embed, color):
337+
return embed_utils.set_embed_color(embed, color)
338+
339+
@staticmethod
340+
def set_embed_url(embed, url):
341+
return embed_utils.set_embed_url(embed, url)
342+
343+
@staticmethod
344+
def set_embed_timestamp(embed, timestamp=None):
345+
return embed_utils.set_embed_timestamp(embed, timestamp)
346+
347+
@staticmethod
348+
def set_embed_fields(embed, fields):
349+
return embed_utils.set_embed_fields(embed, fields)
350+
351+
@staticmethod
352+
def add_embed_field(embed, name, value, inline=True):
353+
return embed_utils.add_embed_field(embed, name, value, inline)
354+
355+
@staticmethod
356+
def set_embed_footer(embed, text, icon_url=None):
357+
return embed_utils.set_embed_footer(embed, text, icon_url)
358+
359+
@staticmethod
360+
def set_embed_footer_text(embed, text):
361+
return embed_utils.set_embed_footer_text(embed, text)
362+
363+
@staticmethod
364+
def set_embed_footer_icon(embed, icon_url):
365+
return embed_utils.set_embed_footer_icon(embed, icon_url)
366+
367+
@staticmethod
368+
def set_embed_thumbnail(embed, url):
369+
return embed_utils.set_embed_thumbnail(embed, url)
370+
371+
@staticmethod
372+
def set_embed_image(embed, url):
373+
return embed_utils.set_embed_image(embed, url)
374+
375+
@staticmethod
376+
def set_embed_author(embed, name, url=None, icon_url=None):
377+
return embed_utils.set_embed_author(embed, name, url, icon_url)
378+
379+
@staticmethod
380+
def set_embed_author_name(embed, name):
381+
return embed_utils.set_embed_author_name(embed, name)
382+
383+
@staticmethod
384+
def set_embed_author_url(embed, url):
385+
return embed_utils.set_embed_author_url(embed, url)
386+
387+
@staticmethod
388+
def set_embed_author_icon(embed, icon_url):
389+
return embed_utils.set_embed_author_icon(embed, icon_url)

0 commit comments

Comments
 (0)