main.py 20 KB

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