main.py 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570
  1. import os
  2. from dotenv import load_dotenv
  3. import discord
  4. from discord.ext import commands
  5. from discord.commands import Option
  6. from discord.commands import slash_command
  7. from datetime import datetime
  8. import configparser
  9. import mysql.connector
  10. import asyncio
  11. intents = discord.Intents.default()
  12. intents.message_content = True
  13. intents.members = True
  14. intents.guilds = True
  15. intents.reactions = True
  16. client = discord.Client(intents=intents)
  17. #---------------------------------#
  18. #Load .env file
  19. load_dotenv()
  20. token = os.getenv("TOKEN")
  21. if token is None:
  22. raise ValueError("TOKEN not found in .env file")
  23. debug_guilds_up = []
  24. server_token = os.getenv("SERVER").split(",")
  25. for i in range(len(server_token)):
  26. debug_guilds_up.append(int(server_token[i]))
  27. dbhost = os.getenv("HOST")
  28. if dbhost is None:
  29. raise ValueError("HOST not found in .env file")
  30. dbname = os.getenv("NAME")
  31. if dbname is None:
  32. raise ValueError("NAME not found in .env file")
  33. dbpsswd = os.getenv("PASSWORD")
  34. if dbpsswd is None:
  35. raise ValueError("PASSWORD not found in .env file")
  36. dbdb = os.getenv("DATABASE")
  37. if dbdb is None:
  38. raise ValueError("DATABASE not found in .env file")
  39. #---------------------------------#
  40. #ConfigParser
  41. config = configparser.RawConfigParser()
  42. configFilePath = r'config.cfg'
  43. config.read_file(open(configFilePath))
  44. label_rules = config.get('Reactionroles Rules', 'label_rules')
  45. role_rules = config.get('Reactionroles Rules', 'rules_role')
  46. channel_log = config.get('Logs', 'channel_log')
  47. channel_banlog = config.get('Logs', 'ban_log')
  48. #---------------------------------#
  49. #Database initialization
  50. conn = mysql.connector.connect(
  51. host=dbhost,
  52. user=dbname,
  53. password=dbpsswd,
  54. charset='utf8mb4',
  55. collation='utf8mb4_unicode_ci'
  56. )
  57. cursor = conn.cursor()
  58. conn.database = dbdb
  59. cursor.execute("""
  60. CREATE TABLE IF NOT EXISTS User (
  61. id INT AUTO_INCREMENT PRIMARY KEY,
  62. userid BIGINT UNIQUE,
  63. discordname VARCHAR(100),
  64. rolesnumber INT,
  65. Roles TEXT
  66. )
  67. """)
  68. cursor.execute("""
  69. CREATE TABLE IF NOT EXISTS Warns (
  70. id INT AUTO_INCREMENT PRIMARY KEY,
  71. userid BIGINT,
  72. username VARCHAR(100),
  73. moderatorname VARCHAR(100),
  74. reason VARCHAR(250),
  75. date TIMESTAMP DEFAULT CURRENT_TIMESTAMP
  76. )
  77. """)
  78. cursor.execute("""
  79. CREATE TABLE IF NOT EXISTS Bans (
  80. id INT AUTO_INCREMENT PRIMARY KEY,
  81. userid BIGINT,
  82. username VARCHAR(100),
  83. moderatorname VARCHAR(100),
  84. reason VARCHAR(250),
  85. date TIMESTAMP DEFAULT CURRENT_TIMESTAMP
  86. )
  87. """)
  88. cursor.execute("""
  89. CREATE TABLE IF NOT EXISTS Unbans (
  90. id INT AUTO_INCREMENT PRIMARY KEY,
  91. userid BIGINT,
  92. username VARCHAR(100),
  93. moderatorname VARCHAR(100),
  94. reason VARCHAR(250),
  95. date TIMESTAMP DEFAULT CURRENT_TIMESTAMP
  96. )
  97. """
  98. )
  99. cursor.execute("""
  100. CREATE TABLE IF NOT EXISTS Kick (
  101. id INT AUTO_INCREMENT PRIMARY KEY,
  102. userid BIGINT,
  103. username VARCHAR(100),
  104. moderatorname VARCHAR(100),
  105. reason VARCHAR(250),
  106. date TIMESTAMP DEFAULT CURRENT_TIMESTAMP
  107. )
  108. """)
  109. #---------------------------------#
  110. #Initialize Bot
  111. bot = commands.Bot(
  112. command_prefix=commands.when_mentioned_or("!"),
  113. description="VicePD Bot",
  114. intents=intents,
  115. debug_guilds=debug_guilds_up if debug_guilds_up else None
  116. )
  117. #Loading Cogs
  118. def load_extensions():
  119. cogs_dir = "./cogs"
  120. if not os.path.exists(cogs_dir):
  121. print(f"Cogs directory '{cogs_dir}' not found!")
  122. return
  123. for filename in os.listdir(cogs_dir):
  124. if filename.endswith(".py"):
  125. cog_list = os.path.splitext(filename)[0]
  126. try:
  127. bot.load_extension(f"cogs.{cog_list}")
  128. print(f"Loaded cog: {cog_list}")
  129. except Exception as e:
  130. print(f"Failed to load cog {cog_list}: {e}")
  131. class Admin(commands.Cog):
  132. def __init__(self, bot):
  133. self.bot = bot
  134. #---------------------------------#
  135. #Print in Log if error occurs
  136. @bot.event
  137. async def on_application_command_error(ctx, error):
  138. channel = discord.utils.get(ctx.guild.channels, id=int(channel_log))
  139. if channel:
  140. await channel.send(f"Error occurred: {str(error)}")
  141. #---------------------------------#
  142. #Bot Online Console
  143. @bot.event
  144. async def on_ready():
  145. print(f"{bot.user} is online")
  146. if bot.guilds:
  147. channel = discord.utils.get(bot.guilds[0].channels, id=int(channel_log))
  148. if channel:
  149. await channel.send(f"{bot.user} is online")
  150. bot.add_view(PersistentRoleView()) #loading reactionrole memory
  151. print("Registrierte Slash-Commands:")
  152. await channel.send("Registered Slash-Commands:")
  153. for command in bot.pending_application_commands:
  154. print(f" - {command.name}")
  155. await channel.send(f"- /{command.name}")
  156. bot.loop.create_task(update_users_periodically())
  157. #---------------------------------------------------------------------------------------#
  158. #DONT Touch anything above this line, unless you know what you are doing!#
  159. #---------------------------------------------------------------------------------------#
  160. #_________________________________#
  161. #BAN SYSTEM
  162. #---------------------------------#
  163. ##Ban
  164. @bot.slash_command(name="ban", description="Ban a user from this Server")
  165. async def ban(
  166. ctx,
  167. user: Option(discord.User, description = "Select User", required=True), # type: ignore
  168. reason: Option(str, description = "Reason for the ban", default="No reason provided") # type: ignore
  169. ):
  170. if not ctx.author.guild_permissions.ban_members:
  171. await ctx.respond("Error: You don't have the permission to ban Members!", ephemeral=True)
  172. return
  173. if user == bot.user:
  174. await ctx.respond("Error: I can't ban myself!", ephemeral=True)
  175. return
  176. if user == ctx.author:
  177. await ctx.respond("Error: You can't ban yourself!", ephemeral=True)
  178. return
  179. channel= discord.utils.get(ctx.guild.channels, id = int(channel_banlog))
  180. embed = discord.Embed(
  181. title=f"Ban of **{user.name}**",
  182. description=f"User {user.mention} has been banned from the Server",
  183. color=discord.Color.red()
  184. )
  185. time = discord.utils.format_dt(datetime.now(), "f")
  186. embed.add_field(name="Ban Date", value=time, inline=False)
  187. embed.add_field(name="Moderator", value=f"{ctx.author}", inline=False)
  188. embed.add_field(name="Reason", value=reason, inline=False)
  189. embed.add_field(name="User ID", value=user.id)
  190. embed.set_thumbnail(url=user.display_avatar.url)
  191. embed.set_author(name="VicePD", icon_url="https://i.imgur.com/6QteFrg.png")
  192. embed.set_footer(text="VicePD - Bot | Made by BaumSplitter41")
  193. try:
  194. await ctx.guild.ban(user, reason=reason)
  195. await ctx.respond(f"User {user.mention} has been banned from this Server!", ephemeral=True)
  196. await channel.send(embed=embed)
  197. cursor.execute(
  198. "INSERT INTO Bans (userid, username, moderatorname, reason) VALUES (%s, %s, %s, %s)",
  199. (user.id, str(user), str(ctx.author), reason)
  200. )
  201. conn.commit()
  202. except discord.Forbidden:
  203. await ctx.respond("Error: I don't have permission to ban this user.", ephemeral=True)
  204. except discord.HTTPException as e:
  205. await ctx.respond(f"Error: Could not ban User {user.mention}. Reason: {e}", ephemeral=True)
  206. except Exception as e:
  207. await ctx.respond(f"Unexpected error: {e}", ephemeral=True)
  208. #---------------------------------#
  209. #Unban
  210. @bot.slash_command(name="unban", description="Unban a user from this Server")
  211. async def unban(
  212. ctx,
  213. user: Option(discord.User, description = "Insert User ID", required=True), # type: ignore
  214. reason: Option(str, description = "Reason for the unbanning", default="No reason provided") # type: ignore
  215. ):
  216. if not ctx.author.guild_permissions.ban_members:
  217. await ctx.respond("Error: You don't have the permission to unban Members!", ephemeral=True)
  218. return
  219. if user == bot.user:
  220. await ctx.respond("Error: I can't unban myself!", ephemeral=True)
  221. return
  222. if user == ctx.author:
  223. await ctx.respond("Error: You can't unban yourself!", ephemeral=True)
  224. return
  225. if user in ctx.guild.members:
  226. await ctx.respond("Error: This user is not banned!", ephemeral=True)
  227. return
  228. channel= discord.utils.get(ctx.guild.channels, id = int(channel_banlog))
  229. embed = discord.Embed(
  230. title=f"Unban of **{user.name}**",
  231. description=f"User {user.mention} was unbanned from this server.",
  232. color=discord.Color.green()
  233. )
  234. time = discord.utils.format_dt(datetime.now(), "f")
  235. embed.add_field(name="Unban Date", value=time, inline=False)
  236. embed.add_field(name="Moderator", value=f"{ctx.author}", inline=False)
  237. embed.add_field(name="Reason", value=reason, inline=False)
  238. embed.add_field(name="User ID", value=user.id)
  239. embed.set_thumbnail(url=user.display_avatar.url)
  240. embed.set_author(name="VicePD", icon_url="https://i.imgur.com/6QteFrg.png")
  241. embed.set_footer(text="VicePD - Bot | Made by BaumSplitter41")
  242. try:
  243. await ctx.guild.unban(user, reason=reason)
  244. await ctx.respond(f"User {user.mention} is now unbanned!", ephemeral=True)
  245. await channel.send(embed=embed)
  246. cursor.execute(
  247. "INSERT INTO Unbans (userid, username, moderatorname, reason) VALUES (%s, %s, %s, %s)",
  248. (user.id, str(user), str(ctx.author), reason)
  249. )
  250. conn.commit()
  251. except discord.Forbidden:
  252. await ctx.respond("Error: I don't have permission to unban this user.", ephemeral=True)
  253. except discord.HTTPException as e:
  254. await ctx.respond(f"Error: Could not unban User {user.mention}. Reason: {e}", ephemeral=True)
  255. except Exception as e:
  256. await ctx.respond(f"Unexpected error: {e}", ephemeral=True)
  257. #---------------------------------#
  258. #_________________________________#
  259. #---------------------------------#
  260. #Kick
  261. @bot.slash_command(name="kick", description="Kick a user from this Server")
  262. async def kick(
  263. ctx,
  264. user: Option(discord.User, description = "Select User", required=True), # type: ignore
  265. reason: Option(str, description = "Reason for the ban", default="No reason provided") # type: ignore
  266. ):
  267. if not ctx.author.guild_permissions.kick_members:
  268. await ctx.respond("Error: You don't have the permission to kick Members!", ephemeral=True)
  269. return
  270. if user == bot.user:
  271. await ctx.respond("Error: I can't kick myself!", ephemeral=True)
  272. return
  273. if user == ctx.author:
  274. await ctx.respond("Error: You can't kick yourself!", ephemeral=True)
  275. return
  276. channel= discord.utils.get(ctx.guild.channels, id = int(channel_banlog))
  277. embed = discord.Embed(
  278. title=f"Kick of **{user.name}**",
  279. description=f"User {user.mention} has been kicked from the Server",
  280. color=discord.Color.red()
  281. )
  282. time = discord.utils.format_dt(datetime.now(), "f")
  283. embed.add_field(name="Kick Date", value=time, inline=False)
  284. embed.add_field(name="Moderator", value=f"{ctx.author}", inline=False)
  285. embed.add_field(name="Reason", value=reason, inline=False)
  286. embed.add_field(name="User ID", value=user.id)
  287. embed.set_thumbnail(url=user.display_avatar.url)
  288. embed.set_author(name="VicePD", icon_url="https://i.imgur.com/6QteFrg.png")
  289. embed.set_footer(text="VicePD - Bot | Made by BaumSplitter41")
  290. try:
  291. await ctx.guild.kick(user, reason=reason)
  292. await ctx.respond(f"User {user.mention} has been kicked from this Server!", ephemeral=True)
  293. cursor.execute(
  294. "INSERT INTO Kick (userid, username, moderatorname, reason) VALUES (%s, %s, %s, %s)",
  295. (int(user.id), str(user), str(ctx.author), reason)
  296. )
  297. conn.commit()
  298. await channel.send(embed=embed)
  299. except discord.Forbidden:
  300. await ctx.respond("Error: I don't have permission to kick this user.", ephemeral=True)
  301. except discord.HTTPException as e:
  302. await ctx.respond(f"Error: Could not kick User {user.mention}. Reason: {e}", ephemeral=True)
  303. except Exception as e:
  304. await ctx.respond(f"Unexpected error: {e}", ephemeral=True)
  305. #---------------------------------#
  306. #---------------------------------#
  307. #Warn
  308. @bot.slash_command(name="warn", description="Warn a user from this Server")
  309. async def warn(
  310. ctx,
  311. user: Option(discord.User, required=True), # type: ignore
  312. reason: Option(str, default="No reason provided") # type: ignore
  313. ):
  314. await ctx.defer(ephemeral=True)
  315. if not ctx.author.guild_permissions.kick_members:
  316. await ctx.followup.send("No permission.", ephemeral=True)
  317. return
  318. if user in (bot.user, ctx.author):
  319. await ctx.followup.send("Invalid target.", ephemeral=True)
  320. return
  321. cursor.execute(
  322. "INSERT INTO Warns (userid, username, moderatorname, reason) VALUES (%s, %s, %s, %s)",
  323. (user.id, str(user), str(ctx.author), reason)
  324. )
  325. conn.commit()
  326. await ctx.followup.send(
  327. f"User {user.mention} has been warned for: {reason}",
  328. ephemeral=True
  329. )
  330. #---------------------------------#
  331. #Modinfo
  332. @bot.slash_command(name="modinfo", description="Shows the moderative history of a user from this Server")
  333. async def modinfo(
  334. ctx,
  335. user: Option(discord.User, required=True) # type: ignore
  336. ):
  337. await ctx.defer(ephemeral=False)
  338. if not ctx.author.guild_permissions.kick_members:
  339. await ctx.followup.send("No permission.", ephemeral=True)
  340. return
  341. embed = discord.Embed(
  342. title=f"__Moderation History for {user.name}__",
  343. color=discord.Color.orange()
  344. )
  345. cursor.execute(
  346. "SELECT moderatorname, reason, date FROM Warns WHERE userid = %s",
  347. (user.id,)
  348. )
  349. warns = cursor.fetchall()
  350. if warns:
  351. for moderatorname, reason, date in warns:
  352. embed.add_field(
  353. name=f"Warned by {moderatorname} on {date.strftime('%Y-%m-%d %H:%M:%S')}",
  354. value=f"Reason: {reason}",
  355. inline=False
  356. )
  357. cursor.execute(
  358. "SELECT moderatorname, reason, date FROM Kick WHERE userid = %s",
  359. (user.id,)
  360. )
  361. kicks = cursor.fetchall()
  362. if kicks:
  363. for moderatorname, reason, date in kicks:
  364. embed.add_field(
  365. name=f"Kicked by {moderatorname} on {date.strftime('%Y-%m-%d %H:%M:%S')}",
  366. value=f"Reason: {reason}",
  367. inline=False
  368. )
  369. cursor.execute(
  370. "SELECT moderatorname, reason, date FROM Bans WHERE userid = %s",
  371. (user.id,)
  372. )
  373. bans = cursor.fetchall()
  374. if bans:
  375. for moderatorname, reason, date in bans:
  376. embed.add_field(
  377. name=f"Banned by {moderatorname} on {date.strftime('%Y-%m-%d %H:%M:%S')}",
  378. value=f"Reason: {reason}",
  379. inline=False
  380. )
  381. cursor.execute(
  382. "SELECT moderatorname, reason, date FROM Unbans WHERE userid = %s",
  383. (user.id,)
  384. )
  385. unbans = cursor.fetchall()
  386. if unbans:
  387. for moderatorname, reason, date in unbans:
  388. embed.add_field(
  389. name=f"Unbanned by {moderatorname} on {date.strftime('%Y-%m-%d %H:%M:%S')}",
  390. value=f"Reason: {reason}",
  391. inline=False
  392. )
  393. if not warns and not kicks and not bans and not unbans:
  394. await ctx.followup.send(f"User `{user.name}` has no moderation history.", ephemeral=True)
  395. return
  396. embed.set_thumbnail(url=user.display_avatar.url)
  397. embed.set_author(name="VicePD", icon_url="https://i.imgur.com/6QteFrg.png")
  398. embed.set_footer(text="VicePD - Bot | Made by BaumSplitter41")
  399. await ctx.followup.send(embed=embed, ephemeral=False)
  400. #_________________________________#
  401. ## Reaction role system
  402. #---------------------------------#
  403. #reaction role verfiy
  404. class PersistentRoleView(discord.ui.View):
  405. def __init__(self):
  406. super().__init__(timeout=None)
  407. @discord.ui.button(
  408. label=label_rules,
  409. style=discord.ButtonStyle.success,
  410. emoji="✅",
  411. custom_id="persistent_view:role_verify"
  412. )
  413. async def verify_callback(self, button: discord.ui.Button, interaction: discord.Interaction):
  414. role = interaction.guild.get_role(int(role_rules))
  415. if role is None:
  416. await interaction.response.send_message("Error: The konfigured role was not found", ephemeral=True)
  417. return
  418. if role in interaction.user.roles:
  419. await interaction.user.remove_roles(role)
  420. await interaction.response.send_message(f"Rolle **{role.name}** wurde entfernt.", ephemeral=True)
  421. else:
  422. await interaction.user.add_roles(role)
  423. await interaction.response.send_message(f"Du hast die Rolle **{role.name}** erhalten!", ephemeral=True)
  424. @bot.slash_command(name="verify_message", description="Send the reactionrole message")
  425. async def setup_rr(
  426. ctx: discord.ApplicationContext,
  427. channel: discord.TextChannel,
  428. title: str,
  429. description: str
  430. ):
  431. if not ctx.author.guild_permissions.administrator:
  432. await ctx.respond("You dont have the permissions to do that..", ephemeral=True)
  433. return
  434. embed = discord.Embed(
  435. title=title,
  436. description=f"{description}\n\nViel Spass auf dem Server!",
  437. color=discord.Color.red()
  438. )
  439. embed.set_image(url="https://i.imgur.com/FoF791J.png")
  440. try:
  441. await channel.send(embed=embed, view=PersistentRoleView())
  442. await ctx.respond(f"Message was succesfully sent in {channel.mention}!", ephemeral=True)
  443. except discord.Forbidden:
  444. await ctx.respond("I dont have permissions to write in this channel", ephemeral=True)
  445. #---------------------------------#
  446. #_________________________________#
  447. #--------------------------------#
  448. #Get all Users in Database periodically
  449. async def update_users_periodically():
  450. await bot.wait_until_ready()
  451. while not bot.is_closed():
  452. try:
  453. for guild in bot.guilds:
  454. batch_count = 0
  455. async for member in guild.fetch_members(limit=None):
  456. cursor.execute(
  457. "INSERT INTO User (userid, discordname, rolesnumber, roles) VALUES (%s, %s, %s, %s) ON DUPLICATE KEY UPDATE discordname=%s, rolesnumber=%s, roles=%s",
  458. (member.id, str(member), len(member.roles), str(member.roles), str(member), len(member.roles), str(member.roles))
  459. )
  460. batch_count += 1
  461. if batch_count >= 100:
  462. conn.commit()
  463. batch_count = 0
  464. if batch_count > 0:
  465. conn.commit()
  466. except Exception as e:
  467. print(f"Error updating users: {e}")
  468. await asyncio.sleep(600) # Update every 10 minutes
  469. #_________________________________#
  470. ## TXADMIN ROLE PERMISSIONS
  471. #---------------------------------#
  472. #Run function
  473. load_extensions()
  474. bot.run(token)
  475. #---------------------------------#