diff --git a/.gitattributes b/.gitattributes index a6344aac8c09253b3b630fb776ae94478aa0275b..c3573da60921e244e06499448e3d7159e22e5330 100644 --- a/.gitattributes +++ b/.gitattributes @@ -33,3 +33,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text *.zip filter=lfs diff=lfs merge=lfs -text *.zst filter=lfs diff=lfs merge=lfs -text *tfevents* filter=lfs diff=lfs merge=lfs -text +hilman_bot.db filter=lfs diff=lfs merge=lfs -text diff --git a/__pycache__/database.cpython-313.pyc b/__pycache__/database.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..76e8505b3b22ccff858d1e71e6d6f426db9b60e7 Binary files /dev/null and b/__pycache__/database.cpython-313.pyc differ diff --git a/__pycache__/main.cpython-313.pyc b/__pycache__/main.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8c06494c8e54ca08d289c2a509dac09b6b6dd5dd Binary files /dev/null and b/__pycache__/main.cpython-313.pyc differ diff --git a/cogs/__pycache__/ai.cpython-313.pyc b/cogs/__pycache__/ai.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7841e03521d81663556d2fc58ec1b96211715fb8 Binary files /dev/null and b/cogs/__pycache__/ai.cpython-313.pyc differ diff --git a/cogs/__pycache__/anti_raid.cpython-313.pyc b/cogs/__pycache__/anti_raid.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..98eb7ff293cd36b1cf12a806104673b5469a09a9 Binary files /dev/null and b/cogs/__pycache__/anti_raid.cpython-313.pyc differ diff --git a/cogs/__pycache__/autorole.cpython-313.pyc b/cogs/__pycache__/autorole.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..40c8e9e3bfb50cbbdeee6912c2f129e04a260961 Binary files /dev/null and b/cogs/__pycache__/autorole.cpython-313.pyc differ diff --git a/cogs/__pycache__/birthday.cpython-313.pyc b/cogs/__pycache__/birthday.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5fc14d77317822784def03dc6ba56ca53aa2cee4 Binary files /dev/null and b/cogs/__pycache__/birthday.cpython-313.pyc differ diff --git a/cogs/__pycache__/counting.cpython-313.pyc b/cogs/__pycache__/counting.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..297a6a86983d8f1e8c7954228eceb142ec54db31 Binary files /dev/null and b/cogs/__pycache__/counting.cpython-313.pyc differ diff --git a/cogs/__pycache__/custom_commands.cpython-313.pyc b/cogs/__pycache__/custom_commands.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..13f86aca3f658b8a001e45af1776f8493b22a84c Binary files /dev/null and b/cogs/__pycache__/custom_commands.cpython-313.pyc differ diff --git a/cogs/__pycache__/economy.cpython-313.pyc b/cogs/__pycache__/economy.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..86c5229a334a664b1bdc880a530d46c1a095eb44 Binary files /dev/null and b/cogs/__pycache__/economy.cpython-313.pyc differ diff --git a/cogs/__pycache__/fun.cpython-313.pyc b/cogs/__pycache__/fun.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f9aacf7b7ceb31b2fa8879fb6af6b326a2ab5ea1 Binary files /dev/null and b/cogs/__pycache__/fun.cpython-313.pyc differ diff --git a/cogs/__pycache__/giveaway.cpython-313.pyc b/cogs/__pycache__/giveaway.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d6661a1e5c4340a1c148cfc45d4819a4f530a517 Binary files /dev/null and b/cogs/__pycache__/giveaway.cpython-313.pyc differ diff --git a/cogs/__pycache__/itibar.cpython-313.pyc b/cogs/__pycache__/itibar.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..dd0059a0da5a60fe500b40855da0271ff0511cb6 Binary files /dev/null and b/cogs/__pycache__/itibar.cpython-313.pyc differ diff --git a/cogs/__pycache__/level.cpython-313.pyc b/cogs/__pycache__/level.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a494adbacd4c2ff5808cad569955f33b670f639a Binary files /dev/null and b/cogs/__pycache__/level.cpython-313.pyc differ diff --git a/cogs/__pycache__/logs.cpython-313.pyc b/cogs/__pycache__/logs.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a32888d6e675f9b5c13091608ec297c160413ca3 Binary files /dev/null and b/cogs/__pycache__/logs.cpython-313.pyc differ diff --git a/cogs/__pycache__/moderation.cpython-313.pyc b/cogs/__pycache__/moderation.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7f9c74dbf695cb52116fcd877bacfb04af32504e Binary files /dev/null and b/cogs/__pycache__/moderation.cpython-313.pyc differ diff --git a/cogs/__pycache__/partnership.cpython-313.pyc b/cogs/__pycache__/partnership.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..265d7f25aa8b7a928346c0321b49449fa72c944b Binary files /dev/null and b/cogs/__pycache__/partnership.cpython-313.pyc differ diff --git a/cogs/__pycache__/poll.cpython-313.pyc b/cogs/__pycache__/poll.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2e76e9a5b93185bc77b95b3ca550d8da71b05ea9 Binary files /dev/null and b/cogs/__pycache__/poll.cpython-313.pyc differ diff --git a/cogs/__pycache__/qr.cpython-313.pyc b/cogs/__pycache__/qr.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fbad497c1aee0080d9661715d2f914580e3cf24c Binary files /dev/null and b/cogs/__pycache__/qr.cpython-313.pyc differ diff --git a/cogs/__pycache__/reaction_roles.cpython-313.pyc b/cogs/__pycache__/reaction_roles.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fddafd1d8a66b64eddf785b03ff91384418edd1b Binary files /dev/null and b/cogs/__pycache__/reaction_roles.cpython-313.pyc differ diff --git a/cogs/__pycache__/reminders.cpython-313.pyc b/cogs/__pycache__/reminders.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4d878729d4cd637246535afb77b398095ac49edf Binary files /dev/null and b/cogs/__pycache__/reminders.cpython-313.pyc differ diff --git a/cogs/__pycache__/sistem.cpython-313.pyc b/cogs/__pycache__/sistem.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f67cda0673d07a35c322a4a98476fb9c7420571a Binary files /dev/null and b/cogs/__pycache__/sistem.cpython-313.pyc differ diff --git a/cogs/__pycache__/stats.cpython-313.pyc b/cogs/__pycache__/stats.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3b6da659ac84a7c4ee42ba2b6dc0d08723286ee3 Binary files /dev/null and b/cogs/__pycache__/stats.cpython-313.pyc differ diff --git a/cogs/__pycache__/stream_alerts.cpython-313.pyc b/cogs/__pycache__/stream_alerts.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..776a90aad702faa6e3fd2fbad74bec3d27d18961 Binary files /dev/null and b/cogs/__pycache__/stream_alerts.cpython-313.pyc differ diff --git a/cogs/__pycache__/suggestion.cpython-313.pyc b/cogs/__pycache__/suggestion.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1798678187ca651239c7735ecb37aadc5978e30f Binary files /dev/null and b/cogs/__pycache__/suggestion.cpython-313.pyc differ diff --git a/cogs/__pycache__/temp_channels.cpython-313.pyc b/cogs/__pycache__/temp_channels.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e6e11deed4dc9ca22b1bcf043753aea7f3bf54d0 Binary files /dev/null and b/cogs/__pycache__/temp_channels.cpython-313.pyc differ diff --git a/cogs/__pycache__/ticket.cpython-313.pyc b/cogs/__pycache__/ticket.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..76cf9d019ed1ae1cace2be23422aa8da89eeae02 Binary files /dev/null and b/cogs/__pycache__/ticket.cpython-313.pyc differ diff --git a/cogs/__pycache__/timezone.cpython-313.pyc b/cogs/__pycache__/timezone.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1f55b8c3ec49955df8289b71b193fbcaac18d790 Binary files /dev/null and b/cogs/__pycache__/timezone.cpython-313.pyc differ diff --git a/cogs/__pycache__/translate.cpython-313.pyc b/cogs/__pycache__/translate.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..15e55d6564aff471b4cf913fecaa9e1a0eb84be9 Binary files /dev/null and b/cogs/__pycache__/translate.cpython-313.pyc differ diff --git a/cogs/__pycache__/verification.cpython-313.pyc b/cogs/__pycache__/verification.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bfa6966f86bab9371d10f9eca16f60fb6af74139 Binary files /dev/null and b/cogs/__pycache__/verification.cpython-313.pyc differ diff --git a/cogs/__pycache__/weather.cpython-313.pyc b/cogs/__pycache__/weather.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4fc1abab7ff71bb4e901fecdf6fee8330577eb2d Binary files /dev/null and b/cogs/__pycache__/weather.cpython-313.pyc differ diff --git a/cogs/__pycache__/welcome.cpython-313.pyc b/cogs/__pycache__/welcome.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f4073321ff5f5bb155d9b6c12373716328d236ed Binary files /dev/null and b/cogs/__pycache__/welcome.cpython-313.pyc differ diff --git a/cogs/ai.py b/cogs/ai.py new file mode 100644 index 0000000000000000000000000000000000000000..1a90497d94ba1dee9a1ee7d18002f0fb7d840ca9 --- /dev/null +++ b/cogs/ai.py @@ -0,0 +1,569 @@ +import discord +from discord.ext import commands, tasks +from discord import app_commands +import json +import os +import aiohttp +import re +from datetime import datetime, timedelta +from database import Database + +CYBER_BLUE = discord.Color.from_rgb(52, 152, 219) +CYBER_DARK = discord.Color.from_rgb(44, 62, 80) +CYBER_RED = discord.Color.from_rgb(231, 76, 60) + +class AI(commands.Cog): + def __init__(self, bot): + self.bot = bot + self._processed_messages = set() + self._pending_actions = {} # user_id -> {action, target_id, guild_id, timestamp, duration_minutes} + self.groq_api_key = "" + config_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), "config.json") + if os.path.exists(config_path): + try: + with open(config_path, "r", encoding="utf-8") as f: + cfg = json.load(f) + self.groq_api_key = cfg.get("groq_api_key", "") + except Exception as e: + print(f"Yapay zeka config yükleme hatası: {e}") + + self.system_prompt = ( + "Sen ArazAI'sın. HilmanBey tarafından yaratılmış, sunucunun asayişini korumakla görevli, " + "son derece zeki, kibar, koruyucu ve Türkçe konuşan yapay zeka asistanısın. " + "Yanıtların her zaman olabildiğince kısa, öz, net ve lafı uzatmadan doğrudan cevaba odaklı olmalıdır. " + "İsmin her zaman ArazAI'dır. Asla yapay zeka olduğunu tekrar edip durma, doğrudan karakterle konuş." + ) + + async def _update_user_last_message(self, guild_id, user_id, content): + now_str = datetime.now().strftime("%d.%m.%Y %H:%M:%S") + row = await Database.fetch_one("SELECT user_id FROM kullanici_detayli_takip WHERE guild_id = ? AND user_id = ?", (guild_id, user_id)) + if row: + await Database.execute( + "UPDATE kullanici_detayli_takip SET son_mesaj = ?, son_mesaj_zamani = ? WHERE guild_id = ? AND user_id = ?", + (content, now_str, guild_id, user_id) + ) + else: + await Database.execute( + "INSERT INTO kullanici_detayli_takip (guild_id, user_id, son_mesaj, son_mesaj_zamani, silinen_mesajlar) VALUES (?, ?, ?, ?, ?)", + (guild_id, user_id, content, now_str, "[]") + ) + + @commands.Cog.listener() + async def on_message_delete(self, message: discord.Message): + if message.author.bot or not message.guild: + return + + guild_id = message.guild.id + user_id = message.author.id + content = message.content or "*İçerik yok veya dosya*" + now_str = datetime.now().strftime("%d.%m.%Y %H:%M:%S") + formatted_deleted = f"[{now_str}] {content}" + + row = await Database.fetch_one("SELECT silinen_mesajlar FROM kullanici_detayli_takip WHERE guild_id = ? AND user_id = ?", (guild_id, user_id)) + if row: + try: + deleted_list = json.loads(row['silinen_mesajlar']) + except: + deleted_list = [] + + deleted_list.append(formatted_deleted) + if len(deleted_list) > 5: + deleted_list = deleted_list[-5:] + + await Database.execute( + "UPDATE kullanici_detayli_takip SET silinen_mesajlar = ? WHERE guild_id = ? AND user_id = ?", + (json.dumps(deleted_list, ensure_ascii=False), guild_id, user_id) + ) + else: + deleted_list = [formatted_deleted] + await Database.execute( + "INSERT INTO kullanici_detayli_takip (guild_id, user_id, son_mesaj, son_mesaj_zamani, silinen_mesajlar) VALUES (?, ?, ?, ?, ?)", + (guild_id, user_id, "", "", json.dumps(deleted_list, ensure_ascii=False)) + ) + + async def cog_load(self): + await Database.execute(""" + CREATE TABLE IF NOT EXISTS ai_ayarlar ( + guild_id INTEGER PRIMARY KEY, + sohbet_kanali_id INTEGER DEFAULT 0, + kisilik TEXT DEFAULT 'standart' + ) + """) + await Database.execute(""" + CREATE TABLE IF NOT EXISTS kullanici_detayli_takip ( + guild_id INTEGER, + user_id INTEGER, + son_mesaj TEXT, + son_mesaj_zamani TEXT, + silinen_mesajlar TEXT, + PRIMARY KEY (guild_id, user_id) + ) + """) + + async def _get_system_prompt(self, guild_id): + if not guild_id: + return self.system_prompt + row = await Database.fetch_one("SELECT kisilik FROM ai_ayarlar WHERE guild_id = ?", (guild_id,)) + kisilik = row['kisilik'] if row and row.get('kisilik') else 'standart' + + prompts = { + "standart": ( + "Sen ArazAI'sın. HilmanBey tarafından yaratılmış, sunucunun asayişini korumakla görevli, " + "son derece zeki, kibar, koruyucu ve Türkçe konuşan yapay zeka asistanısın. " + "Yanıtların her zaman olabildiğince kısa, öz, net ve lafı uzatmadan doğrudan cevaba odaklı olmalıdır. " + "İsmin her zaman ArazAI'dır. Asla yapay zeka olduğunu tekrar edip durma, doğrudan karakterle konuş." + ), + "trol": ( + "Sen ArazAI'sın. HilmanBey tarafından yaratılmış, son derece trol, esprili, biraz iğneleyici ve sarkastik bir yapay zekasın. " + "Yanıtların her zaman olabildiğince kısa, öz ve net olmalıdır." + ), + "ciddi": ( + "Sen ArazAI'sın. Otoriter ve disiplinli bir yapay zekasın. Türkçe konuşuyorsun. " + "Yanıtların her zaman resmi, olabildiğince kısa ve öz olmalıdır." + ), + "siber": ( + "Sen ArazAI'sın. Cyberpunk ve siber güvenlik odaklı konuşan bir yapay zekasın. Türkçe konuşuyorsun. " + "Yanıtların kısa ve öz olmalıdır." + ) + } + return prompts.get(kisilik, prompts["standart"]) + + async def _ask_groq(self, messages, guild_id=0): + if not self.groq_api_key: + return "Siber sistemlerde geçici bağlantı sorunu var. Lütfen Groq API anahtarını kontrol edin." + + url = "https://api.groq.com/openai/v1/chat/completions" + headers = { + "Authorization": f"Bearer {self.groq_api_key}", + "Content-Type": "application/json" + } + + sys_prompt = await self._get_system_prompt(guild_id) + payload_messages = [{"role": "system", "content": sys_prompt}] + for msg in messages: + payload_messages.append({"role": msg["role"], "content": msg["message"]}) + + payload = { + "model": "llama-3.3-70b-versatile", + "messages": payload_messages, + "temperature": 0.7, + "max_tokens": 400 + } + + try: + async with aiohttp.ClientSession() as session: + async with session.post(url, json=payload, headers=headers, timeout=20) as response: + if response.status == 200: + data = await response.json() + return data["choices"][0]["message"]["content"].strip() + else: + err_text = await response.text() + print(f"Groq API Hatası ({response.status}): {err_text}") + return "Groq sisteminden yanıt alınırken bir hata oluştu." + except Exception as e: + print(f"Groq baglantı hatası: {e}") + return "Yapay zeka sunucusuna bağlanılamadı. Lütfen daha sonra tekrar deneyin." + + @commands.hybrid_command(name="sor", description="ArazAI yapay zekasına doğrudan bir soru sorar.") + @app_commands.describe(soru="Sormak istediğiniz soru") + async def sor(self, ctx: commands.Context, *, soru: str): + await ctx.defer() + await Database.sohbet_kaydet( + ctx.guild.id if ctx.guild else 0, + ctx.channel.id, + ctx.author.id, + ctx.author.name, + "user", + soru + ) + + history_records = await Database.sohbet_gecmisi_getir(ctx.channel.id, limit=8) + history_records.reverse() + response_text = await self._ask_groq(history_records, ctx.guild.id if ctx.guild else 0) + + await Database.sohbet_kaydet( + ctx.guild.id if ctx.guild else 0, + ctx.channel.id, + self.bot.user.id, + self.bot.user.name, + "assistant", + response_text + ) + + embed = discord.Embed( + title="🧠 ArazAI", + description=response_text, + color=CYBER_BLUE, + timestamp=datetime.now() + ) + embed.set_footer(text=f"Soran: {ctx.author.display_name}") + await ctx.send(embed=embed) + + @commands.hybrid_command(name="ai-kanal-ayarla", description="AI sohbet kanalını belirler.") + @commands.has_permissions(administrator=True) + @app_commands.describe(kanal="Yapay zeka kanalı") + async def ai_kanal_ayarla(self, ctx: commands.Context, kanal: discord.TextChannel): + await Database.execute( + "INSERT OR REPLACE INTO ai_ayarlar (guild_id, sohbet_kanali_id, kisilik) VALUES (?, ?, COALESCE((SELECT kisilik FROM ai_ayarlar WHERE guild_id = ?), 'standart'))", + (ctx.guild.id, kanal.id, ctx.guild.id) + ) + await ctx.send(f"✅ Yapay zeka sohbet kanalı {kanal.mention} olarak ayarlandı.") + + @commands.hybrid_command(name="ai-kisilik-ayarla", description="Yapay zekanın konuşma üslubunu/kişiliğini belirler.") + @commands.has_permissions(administrator=True) + @app_commands.describe(kisilik="Kişilik modu (standart, trol, ciddi, siber)") + @app_commands.choices(kisilik=[ + app_commands.Choice(name="Standart (Kibar ve Asayiş Odaklı)", value="standart"), + app_commands.Choice(name="Trol (Esprili ve Sarkastik)", value="trol"), + app_commands.Choice(name="Ciddi (Disiplinli ve Resmi)", value="ciddi"), + app_commands.Choice(name="Siber (Cyberpunk ve Hacker Tarzı)", value="siber") + ]) + async def ai_kisilik_ayarla(self, ctx: commands.Context, kisilik: str): + row = await Database.fetch_one("SELECT 1 FROM ai_ayarlar WHERE guild_id = ?", (ctx.guild.id,)) + if row: + await Database.execute("UPDATE ai_ayarlar SET kisilik = ? WHERE guild_id = ?", (kisilik, ctx.guild.id)) + else: + await Database.execute("INSERT INTO ai_ayarlar (guild_id, sohbet_kanali_id, kisilik) VALUES (?, 0, ?)", (ctx.guild.id, kisilik)) + + names = { + "standart": "Standart (Kibar ve Asayiş Odaklı)", + "trol": "Trol (Esprili ve Sarkastik)", + "ciddi": "Ciddi (Disiplinli ve Resmi)", + "siber": "Siber (Cyberpunk ve Hacker Tarzı)" + } + await ctx.send(f"✅ Yapay zeka kişiliği **{names[kisilik]}** olarak ayarlandı.") + + @commands.Cog.listener() + async def on_message(self, message: discord.Message): + if message.author.bot or not message.guild: + return + + if not await Database.modul_aktif_mi(message.guild.id, "AI"): + return + + if message.id in self._processed_messages: + return + self._processed_messages.add(message.id) + if len(self._processed_messages) > 500: + self._processed_messages.clear() + + await self._update_user_last_message(message.guild.id, message.author.id, message.content) + + row = await Database.fetch_one("SELECT sohbet_kanali_id FROM ai_ayarlar WHERE guild_id = ?", (message.guild.id,)) + ai_channel_id = row['sohbet_kanali_id'] if row else 0 + + is_in_ai_channel = message.channel.id == ai_channel_id + bot_id = self.bot.user.id + has_real_mention = f"<@{bot_id}>" in message.content or f"<@!{bot_id}>" in message.content + is_reply_to_bot = ( + message.reference is not None and + message.reference.resolved is not None and + isinstance(message.reference.resolved, discord.Message) and + message.reference.resolved.author.id == bot_id + ) + is_mentioned = (has_real_mention or is_reply_to_bot) and not message.mention_everyone + + if is_in_ai_channel or is_mentioned: + clean_content = message.content.replace(f"<@{self.bot.user.id}>", "").replace(f"<@!{self.bot.user.id}>", "").strip() + lower_content = clean_content.lower() + words = lower_content.split() + + # 1. Onay/İptal İşlemi Kontrolü + pending = self._pending_actions.get(message.author.id) + if pending and (datetime.now() - pending["timestamp"]).total_seconds() < 30: + if lower_content in ["evet", "onay", "onayla", "confirm", "yes"]: + action = pending["action"] + target_id = pending["target_id"] + target = message.guild.get_member(target_id) + del self._pending_actions[message.author.id] + + if not target: + await message.reply("❌ Hedef kullanıcı sunucuda bulunamadı.", mention_author=False) + return + + if action == "ban": + try: + await target.ban(reason=f"ArazAI Yapay Zeka Moderasyonu: {message.author.name} onayladı.") + await message.reply(f"🔨 **{target.display_name}** adlı kullanıcı başarıyla yasaklandı!", mention_author=False) + except Exception as e: + await message.reply(f"❌ Yetki hatası: {e}", mention_author=False) + return + elif action == "kick": + try: + await target.kick(reason=f"ArazAI Yapay Zeka Moderasyonu: {message.author.name} onayladı.") + await message.reply(f"👢 **{target.display_name}** adlı kullanıcı sunucudan atıldı!", mention_author=False) + except Exception as e: + await message.reply(f"❌ Yetki hatası: {e}", mention_author=False) + return + elif action == "warn": + try: + await Database.uyari_ekle(target.id, message.guild.id, self.bot.user.id, "ArazAI Yapay Zeka Moderasyonu") + await message.reply(f"⚠️ **{target.display_name}** adlı kullanıcı başarıyla uyarıldı!", mention_author=False) + except Exception as e: + await message.reply(f"❌ Hata: {e}", mention_author=False) + return + elif action == "mute": + try: + duration = pending.get("duration_minutes", 10) + delta = timedelta(minutes=duration) + await target.timeout(delta, reason=f"ArazAI Yapay Zeka Moderasyonu: {message.author.name} onayladı.") + await Database.mute_ekle(target.id, message.guild.id, datetime.now() + delta) + await message.reply(f"🔇 **{target.display_name}** adlı kullanıcı **{duration} dakika** başarıyla susturuldu!", mention_author=False) + except Exception as e: + await message.reply(f"❌ Yetki hatası: {e}", mention_author=False) + return + elif action == "unmute": + try: + await target.timeout(None, reason=f"ArazAI Yapay Zeka Moderasyonu: {message.author.name} onayladı.") + await Database.mute_kaldir(target.id, message.guild.id) + await message.reply(f"🔊 **{target.display_name}** adlı kullanıcının susturulması kaldırıldı!", mention_author=False) + except Exception as e: + await message.reply(f"❌ Yetki hatası: {e}", mention_author=False) + return + elif lower_content in ["hayır", "iptal", "no", "cancel"]: + del self._pending_actions[message.author.id] + await message.reply("❌ İşlem iptal edildi.", mention_author=False) + return + + # 2. Caps Lock Engeli Kapat / Aç Komutları + if any(k in lower_content for k in ["caps lock kapat", "caps kapat", "caps engeli kapat", "büyük harf engelini kapat"]): + if not message.author.guild_permissions.administrator: + await message.reply("❌ Bu komutu çalıştırmak için yetkiniz yetersiz.", mention_author=False) + return + await Database.anti_raid_ayarlarini_getir(message.guild.id) + await Database.execute("UPDATE anti_raid_ayarlar SET caps_engeli = 0 WHERE guild_id = ?", (message.guild.id,)) + await message.reply("🛡️ Caps Lock koruması başarıyla kapatıldı! ✅", mention_author=False) + return + elif any(k in lower_content for k in ["caps lock aç", "caps aç", "caps engeli aç", "büyük harf engelini aç"]): + if not message.author.guild_permissions.administrator: + await message.reply("❌ Bu komutu çalıştırmak için yetkiniz yetersiz.", mention_author=False) + return + await Database.anti_raid_ayarlarini_getir(message.guild.id) + await Database.execute("UPDATE anti_raid_ayarlar SET caps_engeli = 1 WHERE guild_id = ?", (message.guild.id,)) + await message.reply("🛡️ Caps Lock koruması başarıyla aktif edildi! 🟢", mention_author=False) + return + + # 3. Emoji Engeli Kapat / Aç Komutları + if any(k in lower_content for k in ["emoji kapat", "emoji engeli kapat", "emoji spam kapat"]): + if not message.author.guild_permissions.administrator: + await message.reply("❌ Bu komutu çalıştırmak için yetkiniz yetersiz.", mention_author=False) + return + await Database.anti_raid_ayarlarini_getir(message.guild.id) + await Database.execute("UPDATE anti_raid_ayarlar SET emoji_engeli = 0 WHERE guild_id = ?", (message.guild.id,)) + await message.reply("🛡️ Emoji Spam koruması başarıyla kapatıldı! ✅", mention_author=False) + return + elif any(k in lower_content for k in ["emoji aç", "emoji engeli aç", "emoji spam aç"]): + if not message.author.guild_permissions.administrator: + await message.reply("❌ Bu komutu çalıştırmak için yetkiniz yetersiz.", mention_author=False) + return + await Database.anti_raid_ayarlarini_getir(message.guild.id) + await Database.execute("UPDATE anti_raid_ayarlar SET emoji_engeli = 1 WHERE guild_id = ?", (message.guild.id,)) + await message.reply("🛡️ Emoji Spam koruması başarıyla aktif edildi! 🟢", mention_author=False) + return + + # 4. Yapay Zeka Moderasyon Tetikleyicileri (Ban, Kick, Warn, Mute, Unmute) + is_ban_req = "banla" in lower_content or "yasakla" in lower_content + is_kick_req = "kickle" in lower_content or "at" in words or "sunucudan at" in lower_content + is_warn_req = "uyar" in lower_content or "warnla" in lower_content + is_mute_req = "mutele" in lower_content or "sustur" in lower_content or "susturulsun" in lower_content + is_unmute_req = "unmutele" in lower_content or "susturmasını aç" in lower_content or "susturma kaldır" in lower_content + + if is_ban_req or is_kick_req or is_warn_req or is_mute_req or is_unmute_req: + target = None + # Yanıt atılan mesajın yazarını kontrol et (Eğer botun kendisi DEĞİLSE) + if message.reference and message.reference.resolved and isinstance(message.reference.resolved, discord.Message): + if message.reference.resolved.author.id != self.bot.user.id: + target = message.reference.resolved.author + + # Yanıt atılan yazar botun kendisi ise veya yanıt yoksa mentions taraması yap + if not target: + for m in message.mentions: + if m.id != self.bot.user.id: + target = m + break + + if target: + if is_ban_req: + if not message.author.guild_permissions.ban_members: + await message.reply("❌ Bu komutu çalıştırmak için 'Üyeleri Yasakla' yetkiniz bulunmalıdır.", mention_author=False) + return + self._pending_actions[message.author.id] = { + "action": "ban", + "target_id": target.id, + "guild_id": message.guild.id, + "timestamp": datetime.now() + } + await message.reply(f"⚠️ **{message.author.mention}**, **{target.display_name}** adlı kullanıcıyı sunucudan yasaklamak istediğinizden emin misiniz? Onaylıyorsanız lütfen **`evet`** yazın.", mention_author=False) + return + elif is_kick_req: + if not message.author.guild_permissions.kick_members: + await message.reply("❌ Bu komutu çalıştırmak için 'Üyeleri At' yetkiniz bulunmalıdır.", mention_author=False) + return + self._pending_actions[message.author.id] = { + "action": "kick", + "target_id": target.id, + "guild_id": message.guild.id, + "timestamp": datetime.now() + } + await message.reply(f"⚠️ **{message.author.mention}**, **{target.display_name}** adlı kullanıcıyı sunucudan atmak istediğinizden emin misiniz? Onaylıyorsanız lütfen **`evet`** yazın.", mention_author=False) + return + elif is_warn_req: + if not message.author.guild_permissions.manage_messages: + await message.reply("❌ Bu komutu çalıştırmak için 'Mesajları Yönet' yetkiniz bulunmalıdır.", mention_author=False) + return + self._pending_actions[message.author.id] = { + "action": "warn", + "target_id": target.id, + "guild_id": message.guild.id, + "timestamp": datetime.now() + } + await message.reply(f"⚠️ **{message.author.mention}**, **{target.display_name}** adlı kullanıcıyı uyarmak istediğinizden emin misiniz? Onaylıyorsanız lütfen **`evet`** yazın.", mention_author=False) + return + elif is_mute_req: + if not message.author.guild_permissions.moderate_members: + await message.reply("❌ Bu komutu çalıştırmak için 'Üyeleri Sustur' yetkiniz bulunmalıdır.", mention_author=False) + return + + # Süre bulma regex'i (Örn: 5dk, 10m, 1h, 2gün) + duration_minutes = 10 + duration_match = re.search(r"(\d+)\s*(m|dakika|dk|h|saat|s|gun|gün|d)", lower_content) + if duration_match: + amount = int(duration_match.group(1)) + unit = duration_match.group(2) + if unit in ["m", "dakika", "dk"]: + duration_minutes = amount + elif unit in ["h", "saat", "s"]: + duration_minutes = amount * 60 + elif unit in ["gün", "gun", "d"]: + duration_minutes = amount * 60 * 24 + + self._pending_actions[message.author.id] = { + "action": "mute", + "target_id": target.id, + "guild_id": message.guild.id, + "timestamp": datetime.now(), + "duration_minutes": duration_minutes + } + await message.reply(f"⚠️ **{message.author.mention}**, **{target.display_name}** adlı kullanıcıyı **{duration_minutes} dakika** susturmak istediğinizden emin misiniz? Onaylıyorsanız lütfen **`evet`** yazın.", mention_author=False) + return + elif is_unmute_req: + if not message.author.guild_permissions.moderate_members: + await message.reply("❌ Bu komutu çalıştırmak için 'Üyeleri Sustur' yetkiniz bulunmalıdır.", mention_author=False) + return + self._pending_actions[message.author.id] = { + "action": "unmute", + "target_id": target.id, + "guild_id": message.guild.id, + "timestamp": datetime.now() + } + await message.reply(f"⚠️ **{message.author.mention}**, **{target.display_name}** adlı kullanıcının susturulmasını kaldırmak istediğinizden emin misiniz? Onaylıyorsanız lütfen **`evet`** yazın.", mention_author=False) + return + + # 5. Bilgi Sorgulama Tetikleyici Kontrolü + if "bilgilerini ver" in lower_content or "bilgi ver" in lower_content or "kimdir" in lower_content: + target_member = None + if message.reference and message.reference.resolved and isinstance(message.reference.resolved, discord.Message): + if message.reference.resolved.author.id != self.bot.user.id: + target_member = message.reference.resolved.author + + if not target_member: + for m in message.mentions: + if m.id != self.bot.user.id: + target_member = m + break + + if not target_member: + target_member = message.author + + if not message.guild.get_member(target_member.id): + await message.reply("❌ Bu üye sunucuda bulunmuyor.", mention_author=False) + return + + act_row = await Database.fetch_one("SELECT son_mesaj, son_mesaj_zamani, silinen_mesajlar FROM kullanici_detayli_takip WHERE guild_id = ? AND user_id = ?", (message.guild.id, target_member.id)) + + son_mesaj = act_row['son_mesaj'] if act_row and act_row['son_mesaj'] else "*Bulunmuyor*" + son_mesaj_zamani = act_row['son_mesaj_zamani'] if act_row and act_row['son_mesaj_zamani'] else "*Bulunmuyor*" + + try: + silinen_list = json.loads(act_row['silinen_mesajlar']) if act_row else [] + except: + silinen_list = [] + + silinen_str = "\n".join([f"• {m}" for m in silinen_list]) if silinen_list else "*Silinen mesaj kaydı bulunmuyor*" + + status_map = { + discord.Status.online: "Çevrimiçi 🟢", + discord.Status.idle: "Boşta 🟡", + discord.Status.dnd: "Rahatsız Etmeyin 🔴", + discord.Status.offline: "Çevrimdışı/Görünmez ⚪" + } + status_str = status_map.get(target_member.status, "Çevrimdışı/Görünmez ⚪") + + embed = discord.Embed( + title=f"👤 Üye Bilgi Kartı: {target_member.display_name}", + color=CYBER_BLUE, + timestamp=datetime.now() + ) + embed.set_thumbnail(url=target_member.display_avatar.url) + embed.add_field(name="🆔 Kullanıcı ID", value=f"`{target_member.id}`", inline=True) + embed.add_field(name="🟢 Durum (Son Görülme)", value=status_str, inline=True) + + created_str = target_member.created_at.strftime("%d.%m.%Y %H:%M:%S") + joined_str = target_member.joined_at.strftime("%d.%m.%Y %H:%M:%S") if target_member.joined_at else "*Bulunmuyor*" + + embed.add_field(name="📅 Hesap Açılış Tarihi", value=created_str, inline=False) + embed.add_field(name="📥 Sunucuya Katılım", value=joined_str, inline=False) + embed.add_field(name="💬 Son Aktiflik (Chat)", value=f"{son_mesaj_zamani}", inline=True) + embed.add_field(name="📝 Son Gönderilen Mesaj", value=son_mesaj[:1024], inline=False) + embed.add_field(name="🗑️ Silinen Son Mesajlar", value=silinen_str[:1024], inline=False) + + embed.set_footer(text="ArazAI İstihbarat ve Analiz Sistemi") + await message.reply(embed=embed, mention_author=False) + return + + if not clean_content and is_mentioned: + await message.reply("Merhaba! Ben ArazAI. Bana bir şey sormak için etiketleyebilir veya `/sor` komutunu kullanabilirsiniz.", mention_author=False) + return + + await Database.sohbet_kaydet( + message.guild.id, + message.channel.id, + message.author.id, + message.author.name, + "user", + clean_content + ) + + print(f"🤖 [ArazAI - Instance {getattr(self.bot, 'instance_id', 'Unknown')}] Responding to message from {message.author}...") + async with message.channel.typing(): + history_records = await Database.sohbet_gecmisi_getir(message.channel.id, limit=8) + history_records.reverse() + + response_text = await self._ask_groq(history_records, message.guild.id) + + await Database.sohbet_kaydet( + message.guild.id, + message.channel.id, + self.bot.user.id, + self.bot.user.name, + "assistant", + response_text + ) + + if len(response_text) > 1950: + for i in range(0, len(response_text), 1900): + await message.reply(response_text[i:i+1900], mention_author=False) + else: + await message.reply(response_text, mention_author=False) + else: + await Database.sohbet_kaydet( + message.guild.id, + message.channel.id, + message.author.id, + message.author.name, + "user", + message.content + ) + +async def setup(bot): + await bot.add_cog(AI(bot)) diff --git a/cogs/anti_raid.py b/cogs/anti_raid.py new file mode 100644 index 0000000000000000000000000000000000000000..0ac82ace02c7167828fcbed0f79588d7c866bb20 --- /dev/null +++ b/cogs/anti_raid.py @@ -0,0 +1,842 @@ +import discord +from discord.ext import commands +from discord import app_commands +from datetime import datetime, timezone, timedelta +import difflib +import logging +from database import Database + +# Premium Renkler +CYBER_RED = discord.Color.from_rgb(231, 76, 60) +CYBER_ORANGE = discord.Color.from_rgb(230, 126, 34) +CYBER_GREEN = discord.Color.from_rgb(46, 204, 113) +CYBER_BLUE = discord.Color.from_rgb(52, 152, 219) +CYBER_DARK = discord.Color.from_rgb(44, 62, 80) + + +class AntiRaid(commands.Cog): + def __init__(self, bot): + self.bot = bot + # Yetkili eylem sayaçları. Yapısı: + # self.cooldowns[guild_id][user_id][action_type] = list of timestamps + self.cooldowns = {} + # Spam takip sözlüğü: + self.spam_tracker = {} + + async def cog_load(self): + # Yetki kurtarma tablosunu dinamik olarak oluşturalım + await Database.execute(""" + CREATE TABLE IF NOT EXISTS stripped_roles ( + user_id INTEGER, + guild_id INTEGER, + role_id INTEGER, + PRIMARY KEY (user_id, guild_id, role_id) + ) + """) + # Yasaklı kelimeler tablosunu oluşturalım + await Database.execute(""" + CREATE TABLE IF NOT EXISTS yasakli_kelimeler ( + guild_id INTEGER, + kelime TEXT, + PRIMARY KEY (guild_id, kelime) + ) + """) + + # Caps ve Emoji korumaları kolonları tablolara ekleniyor + import sqlite3 + try: + conn = sqlite3.connect(Database.DB_PATH if hasattr(Database, 'DB_PATH') else "hilman_bot.db") + cursor = conn.cursor() + cursor.execute("ALTER TABLE anti_raid_ayarlar ADD COLUMN caps_engeli INTEGER DEFAULT 1") + conn.commit() + conn.close() + except sqlite3.OperationalError: + pass + + try: + conn = sqlite3.connect(Database.DB_PATH if hasattr(Database, 'DB_PATH') else "hilman_bot.db") + cursor = conn.cursor() + cursor.execute("ALTER TABLE anti_raid_ayarlar ADD COLUMN emoji_engeli INTEGER DEFAULT 1") + conn.commit() + conn.close() + except sqlite3.OperationalError: + pass + + def _clean_and_count_actions(self, guild_id, user_id, action_type, window_seconds): + """Eski eylemleri temizler ve zaman penceresindeki eylem sayısını döndürür""" + now = datetime.now(timezone.utc) + if guild_id not in self.cooldowns: + self.cooldowns[guild_id] = {} + if user_id not in self.cooldowns[guild_id]: + self.cooldowns[guild_id][user_id] = {} + if action_type not in self.cooldowns[guild_id][user_id]: + self.cooldowns[guild_id][user_id][action_type] = [] + + timestamps = self.cooldowns[guild_id][user_id][action_type] + # Zaman penceresi dışındaki eylemleri sil + valid_timestamps = [t for t in timestamps if (now - t).total_seconds() <= window_seconds] + + # Yeni eylemi ekle + valid_timestamps.append(now) + self.cooldowns[guild_id][user_id][action_type] = valid_timestamps + + return len(valid_timestamps) + + async def _strip_roles_and_alert(self, guild: discord.Guild, member: discord.Member, action_name: str, count: int, limit: int): + """Kural ihlali yapan yöneticinin rollerini alır ve log kanalına/sunucu sahibine bildirir""" + # Beyaz listede ise işlem yapma + if await Database.beyaz_liste_kontrol(guild.id, member.id) or member.id == guild.owner_id: + return + + # Kullanıcının rollerini veritabanına kaydet (kurtarma için) + roles_to_strip = [role for role in member.roles if role != guild.default_role and not role.managed] + if not roles_to_strip: + return + + # Veritabanına kaydet + for role in roles_to_strip: + await Database.execute( + "INSERT OR IGNORE INTO stripped_roles (user_id, guild_id, role_id) VALUES (?, ?, ?)", + (member.id, guild.id, role.id) + ) + + # Rolleri al + try: + # Botun rolünün üstündekileri veya yönetici tarafından düzenlenemeyenleri hariç tut + roles_to_remove = [role for role in roles_to_strip if role < guild.me.top_role] + if roles_to_remove: + await member.remove_roles(*roles_to_remove, reason="Anti-Raid: Limit aşımı nedeniyle roller alındı.") + except Exception as e: + logging.error(f"Roller alınırken hata oluştu ({member}): {e}") + + # Log kanalı bildirimi + config = await Database.anti_raid_ayarlarini_getir(guild.id) + log_channel_id = config.get('log_kanal_id', 0) + log_channel = guild.get_channel(log_channel_id) if log_channel_id else None + + embed = discord.Embed( + title="🚨 GÜVENLİK İHLALİ VE ACİL DURUM", + description=f"**Yetkili:** {member.mention} ({member.id})\n" + f"**İhlal Türü:** {action_name}\n" + f"**Eylem Sayısı:** `{count}` (Limit: `{limit}`)\n\n" + f"⚠️ Bu yetkilinin tüm rolleri güvenlik amacıyla **alınmıştır**.", + color=CYBER_RED, + timestamp=datetime.now() + ) + embed.set_thumbnail(url=member.display_avatar.url) + + if log_channel: + await log_channel.send(content=f"⚠️ {guild.owner.mention} ACİL DURUM UYARISI!", embed=embed) + else: + # Log kanalı yoksa doğrudan sunucu sahibine DM at + try: + await guild.owner.send(embed=embed) + except: + pass + + # --- EVENT DINLEYICILER --- + + @commands.Cog.listener() + async def on_guild_channel_delete(self, channel: discord.abc.GuildChannel): + guild = channel.guild + if not await Database.modul_aktif_mi(guild.id, "AntiRaid"): + return + # Botun denetim kaydı yetkisi var mı kontrol edelim + if not guild.me.guild_permissions.view_audit_log: + return + + config = await Database.anti_raid_ayarlarini_getir(guild.id) + limit = config.get('kanal_silme_limiti', 3) + window = config.get('zaman_penceresi', 60) + + # Denetim kaydını kontrol et + async for entry in guild.audit_logs(limit=3, action=discord.AuditLogAction.channel_delete): + # Son 10 saniye içinde silinmişse ve silen bot kendisi değilse + if entry.target.id == channel.id and (datetime.now(timezone.utc) - entry.created_at).total_seconds() < 10: + user = entry.user + if user.bot or user.id == guild.owner_id: + return + + count = self._clean_and_count_actions(guild.id, user.id, "channel_delete", window) + if count >= limit: + member = guild.get_member(user.id) + if member: + await self._strip_roles_and_alert(guild, member, "Hızlı Kanal Silme", count, limit) + break + + @commands.Cog.listener() + async def on_member_ban(self, guild: discord.Guild, user: discord.User): + if not await Database.modul_aktif_mi(guild.id, "AntiRaid"): + return + if not guild.me.guild_permissions.view_audit_log: + return + + config = await Database.anti_raid_ayarlarini_getir(guild.id) + limit = config.get('ban_limiti', 3) + window = config.get('zaman_penceresi', 60) + + async for entry in guild.audit_logs(limit=3, action=discord.AuditLogAction.ban): + if entry.target.id == user.id and (datetime.now(timezone.utc) - entry.created_at).total_seconds() < 10: + moderator = entry.user + if moderator.bot or moderator.id == guild.owner_id: + return + + count = self._clean_and_count_actions(guild.id, moderator.id, "ban", window) + if count >= limit: + member = guild.get_member(moderator.id) + if member: + await self._strip_roles_and_alert(guild, member, "Hızlı Üye Yasaklama (Ban)", count, limit) + break + + @commands.Cog.listener() + async def on_member_remove(self, member: discord.Member): + guild = member.guild + if not await Database.modul_aktif_mi(guild.id, "AntiRaid"): + return + if not guild.me.guild_permissions.view_audit_log: + return + + config = await Database.anti_raid_ayarlarini_getir(guild.id) + limit = config.get('kick_limiti', 3) + window = config.get('zaman_penceresi', 60) + + # Kick denetim kayıtlarını kontrol et + async for entry in guild.audit_logs(limit=3, action=discord.AuditLogAction.kick): + if entry.target.id == member.id and (datetime.now(timezone.utc) - entry.created_at).total_seconds() < 10: + moderator = entry.user + if moderator.bot or moderator.id == guild.owner_id: + return + + count = self._clean_and_count_actions(guild.id, moderator.id, "kick", window) + if count >= limit: + mod_member = guild.get_member(moderator.id) + if mod_member: + await self._strip_roles_and_alert(guild, mod_member, "Hızlı Üye Atma (Kick)", count, limit) + break + + @commands.Cog.listener() + async def on_member_join(self, member: discord.Member): + """Alt hesap (yan hesap) tespiti ve otomatik koruma""" + guild = member.guild + if not await Database.modul_aktif_mi(guild.id, "AntiRaid"): + return + config = await Database.anti_raid_ayarlarini_getir(guild.id) + + alt_yas_gunu = config.get('alt_yas_gunu', 14) + benzerlik_orani = config.get('benzerlik_orani', 0.8) + oto_ban_alt = config.get('oto_ban_alt', 1) + log_kanal_id = config.get('log_kanal_id', 0) + log_channel = guild.get_channel(log_kanal_id) if log_kanal_id else None + + # Katılan hesabın yaşını hesapla + now = datetime.now(timezone.utc) + created_at = member.created_at + account_age_days = (now - created_at).days + + # Hesap yaşı belirtilen günden küçükse benzerlik taraması yap + if account_age_days < alt_yas_gunu: + try: + # Sunucudaki yasaklı üyeleri al + bans = [entry async for entry in guild.bans(limit=1000)] + detected_alt = False + highest_similarity = 0.0 + matched_banned_user = None + + for ban_entry in bans: + b_user = ban_entry.user + # Benzerlik oranı hesapla + sim = difflib.SequenceMatcher(None, member.name.lower(), b_user.name.lower()).ratio() + if sim > highest_similarity: + highest_similarity = sim + matched_banned_user = b_user + + if highest_similarity >= benzerlik_orani: + detected_alt = True + + if detected_alt and matched_banned_user: + sim_percent = int(highest_similarity * 100) + + if oto_ban_alt == 1: + # Otomatik banla + reason = f"ArazAI Güvenlik: Yasaklı üyenin yan hesabı tespiti. Benzerlik: %{sim_percent} ({matched_banned_user.name})" + try: + embed_dm = discord.Embed( + title=f"⚖️ {guild.name} Sunucusunda Otomatik İşlem Yapıldı", + description=f"Merhaba **{member.name}**, sunucumuzda yakın zamanda yasaklanan bir üyenin yan hesabı olduğunuz tespit edildiği için sunucudan yasaklandınız.", + color=CYBER_RED, + timestamp=datetime.now() + ) + embed_dm.add_field(name="📌 Uygulanan Eylem", value="**Otomatik Yasaklama (Ban)**", inline=True) + embed_dm.add_field(name="📝 Sebep", value=f"Yasaklı üye yan hesabı eşleşmesi (%{sim_percent} benzerlik, eşleşen: {matched_banned_user.name})", inline=False) + embed_dm.set_footer(text="ArazAI Siber Asayiş ve Güvenlik Sistemi") + await member.send(embed=embed_dm) + except Exception: + pass + await member.ban(reason=reason) + + embed = discord.Embed( + title="🛡️ Yan Hesap Engellendi (Otomatik Ban)", + description=f"**Kullanıcı:** {member.mention} ({member.id})\n" + f"**Hesap Yaşı:** `{account_age_days}` gün\n" + f"**Eşleşen Yasaklı:** `{matched_banned_user.name}` ({matched_banned_user.id})\n" + f"**Kullanıcı Adı Benzerliği:** `%{sim_percent}`\n" + f"**Eylem:** Otomatik Sunucudan Yasaklama", + color=CYBER_RED, + timestamp=datetime.now() + ) + embed.set_thumbnail(url=member.display_avatar.url) + if log_channel: + await log_channel.send(embed=embed) + else: + # Sadece uyar + embed = discord.Embed( + title="⚠️ Şüpheli Yan Hesap Girişi", + description=f"**Kullanıcı:** {member.mention} ({member.id})\n" + f"**Hesap Yaşı:** `{account_age_days}` gün\n" + f"**Eşleşen Yasaklı:** `{matched_banned_user.name}` ({matched_banned_user.id})\n" + f"**Kullanıcı Adı Benzerliği:** `%{sim_percent}`\n" + f"**Açıklama:** Bu kullanıcı yasaklı bir üyenin yan hesabı olabilir. Lütfen kontrol edin.", + color=CYBER_ORANGE, + timestamp=datetime.now() + ) + embed.set_thumbnail(url=member.display_avatar.url) + if log_channel: + await log_channel.send(embed=embed) + except Exception as e: + logging.error(f"Alt hesap tespiti sırasında hata oluştu: {e}") + + # --- KOMUTLAR --- + + @commands.hybrid_command(name="anti-raid-ayarla", description="Sunucu koruma (Anti-Raid) sınırlarını ve alt hesap ayarlarını yapar.") + @commands.has_permissions(administrator=True) + @app_commands.describe( + ban_limiti="Zaman penceresindeki maksimum ban limiti (Örn: 3)", + kick_limiti="Zaman penceresindeki maksimum kick limiti (Örn: 3)", + kanal_silme_limiti="Zaman penceresindeki maksimum kanal silme limiti (Örn: 3)", + zaman_penceresi="Saniye cinsinden limit penceresi (Örn: 60)", + alt_yas_gunu="Yeni hesaplar için maksimum yaş sınırı gün (Örn: 14)", + benzerlik_orani="Benzerlik sınırı (0.1 ile 1.0 arası, Örn: 0.8)", + oto_ban_alt="Alt hesap tespiti durumunda otomatik ban (1 = Evet, 0 = Hayır)", + log_kanali="Koruma loglarının gönderileceği kanal" + ) + async def anti_raid_ayarla( + self, ctx: commands.Context, + ban_limiti: int = 3, + kick_limiti: int = 3, + kanal_silme_limiti: int = 3, + zaman_penceresi: int = 60, + alt_yas_gunu: int = 14, + benzerlik_orani: float = 0.8, + oto_ban_alt: int = 1, + log_kanali: discord.TextChannel = None + ): + if ctx.interaction: + await ctx.defer() + log_id = log_kanali.id if log_kanali else 0 + + await Database.anti_raid_ayarla( + ctx.guild.id, ban_limiti, kick_limiti, kanal_silme_limiti, + zaman_penceresi, alt_yas_gunu, benzerlik_orani, oto_ban_alt, log_id + ) + + embed = discord.Embed( + title="🛡️ Sunucu Güvenlik Ayarları Güncellendi", + color=CYBER_GREEN, + timestamp=datetime.now() + ) + embed.add_field(name="Ban Limiti / Süresi", value=f"`{ban_limiti}` ban / `{zaman_penceresi}` sn", inline=True) + embed.add_field(name="Kick Limiti / Süresi", value=f"`{kick_limiti}` kick / `{zaman_penceresi}` sn", inline=True) + embed.add_field(name="Kanal Silme Limiti / Süresi", value=f"`{kanal_silme_limiti}` silme / `{zaman_penceresi}` sn", inline=True) + embed.add_field(name="Alt Hesap Yaş Sınırı", value=f"`{alt_yas_gunu}` Gün", inline=True) + embed.add_field(name="Alt Benzerlik Oranı", value=f"`%{int(benzerlik_orani*100)}`", inline=True) + embed.add_field(name="Yan Hesap Otomatik Ban", value="`Açık`" if oto_ban_alt == 1 else "`Kapalı`", inline=True) + embed.add_field(name="Log Kanalı", value=log_kanali.mention if log_kanali else "`Yok`", inline=False) + + await ctx.send(embed=embed) + + @commands.hybrid_command(name="güvenli-ekle", description="Bir kullanıcıyı güvenlik limitlerinden muaf olan beyaz listeye ekler.") + @commands.has_permissions(administrator=True) + @app_commands.describe(user="Beyaz listeye eklenecek güvenilir üye") + async def guvenli_ekle(self, ctx: commands.Context, user: discord.User): + if ctx.interaction: + await ctx.defer() + await Database.beyaz_liste_ekle(ctx.guild.id, user.id) + await ctx.send(f"✅ {user.mention} kullanıcısı güvenli listeye (whitelist) başarıyla eklendi. Artık anti-raid limitlerinden etkilenmeyecektir.") + + @commands.hybrid_command(name="güvenli-çıkar", description="Bir kullanıcıyı güvenli beyaz listeden çıkarır.") + @commands.has_permissions(administrator=True) + @app_commands.describe(user="Beyaz listeden çıkarılacak üye") + async def guvenli_cikar(self, ctx: commands.Context, user: discord.User): + if ctx.interaction: + await ctx.defer() + await Database.beyaz_liste_sil(ctx.guild.id, user.id) + await ctx.send(f"✅ {user.mention} kullanıcısı güvenli listeden çıkarıldı.") + + @commands.hybrid_command(name="güvenli-liste", description="Güvenli listedeki (whitelist) tüm üyeleri listeler.") + @commands.has_permissions(manage_messages=True) + async def guvenli_liste(self, ctx: commands.Context): + if ctx.interaction: + await ctx.defer() + records = await Database.beyaz_liste_getir(ctx.guild.id) + if not records: + await ctx.send("ℹ️ Sunucuda güvenli listeye eklenmiş herhangi bir üye bulunmamaktadır.") + return + + desc = "" + for record in records: + member = ctx.guild.get_member(record['user_id']) + user_mention = member.mention if member else f"ID: {record['user_id']}" + desc += f"- {user_mention}\n" + + embed = discord.Embed( + title="🛡️ Güvenli Liste (Whitelist)", + description=desc, + color=CYBER_BLUE, + timestamp=datetime.now() + ) + await ctx.send(embed=embed) + + @commands.hybrid_command(name="güvenli-kurtar", description="Anti-Raid limit aşımı sebebiyle rolleri geri alınmış bir yetkilinin rollerini iade eder.") + @commands.has_permissions(administrator=True) + @app_commands.describe(user="Rolleri iade edilecek yetkili") + async def guvenli_kurtar(self, ctx: commands.Context, user: discord.Member): + if ctx.interaction: + await ctx.defer() + # Kayıtlı rolleri çek + records = await Database.fetch_all( + "SELECT role_id FROM stripped_roles WHERE user_id = ? AND guild_id = ?", + (user.id, ctx.guild.id) + ) + if not records: + await ctx.send("❌ Bu kullanıcının kurtarılacak kayıtlı herhangi bir rolü bulunamadı.", ephemeral=True) + return + + recovered_roles = [] + failed_roles = [] + + for record in records: + role = ctx.guild.get_role(record['role_id']) + if role: + if role < ctx.guild.me.top_role: + try: + await user.add_roles(role) + recovered_roles.append(role.name) + except: + failed_roles.append(role.name) + else: + failed_roles.append(role.name) + else: + failed_roles.append(f"Silinmiş Rol ({record['role_id']})") + + # Tablodan kayıtları sil + await Database.execute("DELETE FROM stripped_roles WHERE user_id = ? AND guild_id = ?", (user.id, ctx.guild.id)) + + embed = discord.Embed( + title="🛡️ Yetkili Rolleri İade Edildi", + description=f"{user.mention} kullanıcısının rolleri geri yüklendi.", + color=CYBER_GREEN, + timestamp=datetime.now() + ) + if recovered_roles: + embed.add_field(name="İade Edilen Roller", value=", ".join([f"`{r}`" for r in recovered_roles]), inline=False) + if failed_roles: + embed.add_field(name="İade Edilemeyen Roller (Yetki Yetersiz)", value=", ".join([f"`{r}`" for r in failed_roles]), inline=False) + + await ctx.send(embed=embed) + + # --- SOHBET GÜVENLİĞİ VE KAFADAN GÜVENLİK ÖZELLİKLERİ --- + + async def _is_immune(self, message: discord.Message) -> bool: + """Kullanıcının güvenlik taramalarından muaf olup olmadığını kontrol eder""" + if message.author.bot: + return True + if not message.guild: + return True + if message.author.guild_permissions.administrator: + return True + if await Database.beyaz_liste_kontrol(message.guild.id, message.author.id): + return True + return False + + async def _send_log_alert(self, guild, embed): + """Güvenlik log kanalına uyarı gönderir""" + config = await Database.anti_raid_ayarlarini_getir(guild.id) + log_channel_id = config.get('log_kanal_id', 0) + log_channel = guild.get_channel(log_channel_id) if log_channel_id else None + if log_channel: + try: + await log_channel.send(embed=embed) + except: + pass + + async def run_security_scans(self, message: discord.Message): + """Kafadan eklenen güvenlik özellikleri: Spam engeli, Yasaklı kelime engeli, Toplu etiket engeli ve Reklam/Link koruması""" + if await self._is_immune(message): + return + + guild_id = message.guild.id + user_id = message.author.id + content = message.content + now = datetime.now(timezone.utc) + + # ----------------- 1. SPAM KORUMASI (ÖZEL) ----------------- + if guild_id not in self.spam_tracker: + self.spam_tracker[guild_id] = {} + if user_id not in self.spam_tracker[guild_id]: + self.spam_tracker[guild_id][user_id] = [] + + timestamps = self.spam_tracker[guild_id][user_id] + # Son 3 saniyedeki mesajları filtrele + valid_timestamps = [t for t in timestamps if (now - t).total_seconds() <= 3] + valid_timestamps.append(now) + self.spam_tracker[guild_id][user_id] = valid_timestamps + + if len(valid_timestamps) >= 5: # 3 saniyede 5+ mesaj spam kabul edilir + try: + await message.delete() + except: + pass + + try: + delta = timedelta(minutes=5) + try: + embed_dm = discord.Embed( + title=f"⚖️ {message.guild.name} Sunucusunda Otomatik İşlem Yapıldı", + description=f"Merhaba **{message.author.name}**, sunucumuzda spam yaptığınız tespit edildiği için hesabınız geçici olarak susturulmuştur.", + color=CYBER_RED, + timestamp=datetime.now() + ) + embed_dm.add_field(name="📌 Uygulanan Eylem", value="**Otomatik Susturulma (Mute)**", inline=True) + embed_dm.add_field(name="⏳ Süre", value="`5 Dakika`", inline=True) + embed_dm.add_field(name="📝 Sebep", value="Hızlı ve tekrarlı mesaj gönderimi (Spam Koruması)", inline=False) + embed_dm.set_footer(text="ArazAI Siber Asayiş ve Güvenlik Sistemi") + await message.author.send(embed=embed_dm) + except Exception: + pass + await message.author.timeout(delta, reason="Sohbet Güvenliği: Spam Koruması Tetiklendi.") + await Database.mute_ekle(user_id, guild_id, datetime.now() + delta) + + await message.channel.send(f"⚠️ {message.author.mention}, spam yaptığınız için **5 dakika** susturuldunuz!", delete_after=10) + + # Log Bildirimi + embed = discord.Embed( + title="🛡️ Güvenlik Engeli: Anti-Spam", + description=f"**Kullanıcı:** {message.author.mention} ({user_id})\n" + f"**Kanal:** {message.channel.mention}\n" + f"**Eylem:** 5 Dakika Susturma & Mesaj Silimi", + color=CYBER_RED, + timestamp=datetime.now() + ) + await self._send_log_alert(message.guild, embed) + except Exception as e: + logging.error(f"Spam cezası hatası: {e}") + return + + config = await Database.anti_raid_ayarlarini_getir(guild_id) + emoji_aktif = config.get("emoji_engeli", 1) + caps_aktif = config.get("caps_engeli", 0) # Varsayılan olarak kapalı (0) ayarlandı + + # ----------------- EMOJI SPAM KORUMASI ----------------- + if emoji_aktif == 1: + import re + custom_emojis = re.findall(r"", content) + unicode_emojis = [c for c in content if ord(c) > 0x1f600 or (0x2600 <= ord(c) <= 0x27bf)] + total_emojis = len(custom_emojis) + len(unicode_emojis) + + if total_emojis > 8: + try: + await message.delete() + except: + pass + await message.channel.send(f"⚠️ {message.author.mention}, mesajınızda aşırı emoji kullanımı tespit edildi (Emoji Spam Koruması)!", delete_after=5) + embed = discord.Embed( + title="🛡️ Güvenlik Engeli: Emoji Spam", + description=f"**Kullanıcı:** {message.author.mention} ({user_id})\n" + f"**Kanal:** {message.channel.mention}\n" + f"**Emoji Sayısı:** `{total_emojis}`\n" + f"**Eylem:** Mesaj Silindi", + color=CYBER_ORANGE, + timestamp=datetime.now() + ) + await self._send_log_alert(message.guild, embed) + return + + # ----------------- CAPS LOCK KORUMASI ----------------- + if caps_aktif == 1 and len(content) >= 8: + letters = [c for c in content if c.isalpha()] + if letters: + caps = [c for c in letters if c.isupper()] + caps_ratio = len(caps) / len(letters) + if caps_ratio > 0.7: # %70'den fazla büyük harf + try: + await message.delete() + except: + pass + await message.channel.send(f"⚠️ {message.author.mention}, lütfen aşırı büyük harf (Caps Lock) kullanmayın!", delete_after=5) + embed = discord.Embed( + title="🛡️ Güvenlik Engeli: Caps Lock Koruması", + description=f"**Kullanıcı:** {message.author.mention} ({user_id})\n" + f"**Kanal:** {message.channel.mention}\n" + f"**Büyük Harf Oranı:** `%{int(caps_ratio*100)}`\n" + f"**Eylem:** Mesaj Silindi", + color=CYBER_ORANGE, + timestamp=datetime.now() + ) + await self._send_log_alert(message.guild, embed) + return + + # ----------------- 2. YASAKLI KELİME KONTROLÜ ----------------- + words_records = await Database.fetch_all("SELECT kelime FROM yasakli_kelimeler WHERE guild_id = ?", (guild_id,)) + if words_records: + content_lower = content.lower() + for record in words_records: + banned_word = record['kelime'].lower() + if banned_word in content_lower: + try: + await message.delete() + except: + pass + + await message.channel.send(f"❌ {message.author.mention}, gönderdiğiniz mesaj yasaklı kelime içeriyor!", delete_after=5) + + try: + embed_dm = discord.Embed( + title=f"⚖️ {message.guild.name} Sunucusunda Otomatik İşlem Yapıldı", + description=f"Merhaba **{message.author.name}**, sunucumuzda yasaklı bir kelime kullandığınız tespit edilmiştir.", + color=CYBER_ORANGE, + timestamp=datetime.now() + ) + embed_dm.add_field(name="📌 Uygulanan Eylem", value="**Otomatik Uyarı**", inline=True) + embed_dm.add_field(name="📝 Sebep", value=f"Yasaklı kelime kullanımı: '{banned_word}'", inline=False) + embed_dm.set_footer(text="ArazAI Siber Asayiş ve Güvenlik Sistemi") + await message.author.send(embed=embed_dm) + except Exception: + pass + + # Otomatik uyarı ekle + warn_id = await Database.uyari_ekle(user_id, guild_id, self.bot.user.id, f"Yasaklı kelime kullanımı: '{banned_word}'") + warns = await Database.uyarilari_getir(user_id, guild_id) + + # Log Bildirimi + embed = discord.Embed( + title="🛡️ Güvenlik Engeli: Yasaklı Kelime", + description=f"**Kullanıcı:** {message.author.mention} ({user_id})\n" + f"**Kanal:** {message.channel.mention}\n" + f"**Yasaklı Kelime:** `{banned_word}`\n" + f"**Eylem:** Mesaj Silimi & Otomatik Uyarı (ID: #{warn_id}, Toplam: {len(warns)})", + color=CYBER_ORANGE, + timestamp=datetime.now() + ) + await self._send_log_alert(message.guild, embed) + return + + # ----------------- 3. TOPLU ETİKET ENGELİ (ÖZEL) ----------------- + total_mentions = len(message.mentions) + len(message.role_mentions) + if total_mentions >= 5: # Tek mesajda 5+ etiket + try: + await message.delete() + except: + pass + + try: + delta = timedelta(minutes=10) + try: + embed_dm = discord.Embed( + title=f"⚖️ {message.guild.name} Sunucusunda Otomatik İşlem Yapıldı", + description=f"Merhaba **{message.author.name}**, sunucumuzda toplu etiket attığınız tespit edildiği için hesabınız geçici olarak susturulmuştur.", + color=CYBER_RED, + timestamp=datetime.now() + ) + embed_dm.add_field(name="📌 Uygulanan Eylem", value="**Otomatik Susturulma (Mute)**", inline=True) + embed_dm.add_field(name="⏳ Süre", value="`10 Dakika`", inline=True) + embed_dm.add_field(name="📝 Sebep", value=f"Tek mesajda aşırı sayıda üye/rol etiketleme ({total_mentions} etiket)", inline=False) + embed_dm.set_footer(text="ArazAI Siber Asayiş ve Güvenlik Sistemi") + await message.author.send(embed=embed_dm) + except Exception: + pass + await message.author.timeout(delta, reason="Sohbet Güvenliği: Toplu Etiket Koruması Tetiklendi.") + await Database.mute_ekle(user_id, guild_id, datetime.now() + delta) + + await message.channel.send(f"⚠️ {message.author.mention}, toplu etiket attığınız için **10 dakika** susturuldunuz!", delete_after=10) + + # Log Bildirimi + embed = discord.Embed( + title="🛡️ Güvenlik Engeli: Toplu Etiket", + description=f"**Kullanıcı:** {message.author.mention} ({user_id})\n" + f"**Kanal:** {message.channel.mention}\n" + f"**Etiket Sayısı:** `{total_mentions}`\n" + f"**Eylem:** 10 Dakika Susturma & Mesaj Silimi", + color=CYBER_RED, + timestamp=datetime.now() + ) + await self._send_log_alert(message.guild, embed) + except Exception as e: + logging.error(f"Toplu etiket cezası hatası: {e}") + return + + # ----------------- 4. REKLAM / LİNK KORUMASI (ÖZEL) ----------------- + is_exempt = False + anti_raid_cfg = await Database.anti_raid_ayarlarini_getir(guild_id) + if anti_raid_cfg: + exempt_role_id = anti_raid_cfg.get("link_muaf_rol_id", 0) + if exempt_role_id and exempt_role_id != 0: + exempt_role = message.guild.get_role(exempt_role_id) + if exempt_role and isinstance(message.author, discord.Member): + for r in message.author.roles: + if r.position >= exempt_role.position: + is_exempt = True + break + + if not is_exempt: + partner_cfg = await Database.partner_ayarlarini_getir(guild_id) + partner_kanal_id = partner_cfg.get("partner_kanal_id", 0) if partner_cfg else 0 + + # Partner kanalı dışında link paylaşımı yasaklanır + if message.channel.id != partner_kanal_id: + has_link = "http://" in content or "https://" in content or "discord.gg/" in content or "discord.com/invite/" in content + if has_link: + try: + await message.delete() + except: + pass + + await message.channel.send(f"❌ {message.author.mention}, bu kanalda reklam/link paylaşımı yapmak yasaktır!", delete_after=5) + + # Log Bildirimi + embed = discord.Embed( + title="🛡️ Güvenlik Engeli: Reklam / Link Engeli", + description=f"**Kullanıcı:** {message.author.mention} ({user_id})\n" + f"**Kanal:** {message.channel.mention}\n" + f"**Eylem:** Reklam Mesajı Silindi", + color=CYBER_ORANGE, + timestamp=datetime.now() + ) + await self._send_log_alert(message.guild, embed) + return + + # --- SOHBET GÜVENLİK EVENTLERİ --- + + @commands.Cog.listener() + async def on_message(self, message: discord.Message): + if message.author.bot or not message.guild: + return + if not await Database.modul_aktif_mi(message.guild.id, "AntiRaid"): + return + await self.run_security_scans(message) + + @commands.Cog.listener() + async def on_message_edit(self, before: discord.Message, after: discord.Message): + if after.author.bot or not after.guild: + return + if not await Database.modul_aktif_mi(after.guild.id, "AntiRaid"): + return + await self.run_security_scans(after) + + # --- YASAKLI KELİME YÖNETİM KOMUTLARI --- + + @commands.hybrid_command(name="yasaklı-kelime-ekle", description="Sunucuya yasaklı bir kelime ekler.") + @commands.has_permissions(administrator=True) + @app_commands.describe(kelime="Yasaklanacak kelime veya kelime öbeği") + async def yasakli_kelime_ekle(self, ctx: commands.Context, kelime: str): + if ctx.interaction: + await ctx.defer() + kelime_clean = kelime.strip().lower() + if not kelime_clean: + await ctx.send("❌ Geçersiz kelime girdiniz.", ephemeral=True) + return + + try: + await Database.execute( + "INSERT OR IGNORE INTO yasakli_kelimeler (guild_id, kelime) VALUES (?, ?)", + (ctx.guild.id, kelime_clean) + ) + await ctx.send(f"✅ `{kelime_clean}` kelimesi yasaklı kelime listesine başarıyla eklendi.") + except Exception as e: + await ctx.send(f"❌ Kelime eklenirken bir veritabanı hatası oluştu: {e}", ephemeral=True) + + @commands.hybrid_command(name="yasaklı-kelime-sil", description="Sunucudan yasaklı bir kelimeyi siler.") + @commands.has_permissions(administrator=True) + @app_commands.describe(kelime="Yasaklı listeden çıkarılacak kelime") + async def yasakli_kelime_sil(self, ctx: commands.Context, kelime: str): + if ctx.interaction: + await ctx.defer() + kelime_clean = kelime.strip().lower() + try: + rows = await Database.execute( + "DELETE FROM yasakli_kelimeler WHERE guild_id = ? AND kelime = ?", + (ctx.guild.id, kelime_clean) + ) + if rows > 0: + await ctx.send(f"✅ `{kelime_clean}` kelimesi yasaklı listeden başarıyla çıkarıldı.") + else: + await ctx.send(f"❌ `{kelime_clean}` kelimesi yasaklı listede bulunamadı.", ephemeral=True) + except Exception as e: + await ctx.send(f"❌ Kelime silinirken bir veritabanı hatası oluştu: {e}", ephemeral=True) + + @commands.hybrid_command(name="yasaklı-kelime-liste", description="Sunucudaki tüm yasaklı kelimeleri gösterir.") + @commands.has_permissions(manage_messages=True) + async def yasakli_kelime_liste(self, ctx: commands.Context): + if ctx.interaction: + await ctx.defer() + try: + records = await Database.fetch_all("SELECT kelime FROM yasakli_kelimeler WHERE guild_id = ?", (ctx.guild.id,)) + if not records: + await ctx.send("ℹ️ Sunucuda ayarlanmış herhangi bir yasaklı kelime bulunmamaktadır.") + return + + word_list = [f"`{r['kelime']}`" for r in records] + embed = discord.Embed( + title="🛡️ Sunucu Yasaklı Kelime Listesi", + description=", ".join(word_list), + color=CYBER_BLUE, + timestamp=datetime.now() + ) + await ctx.send(embed=embed) + except Exception as e: + await ctx.send(f"❌ Kelimeler listelenirken bir veritabanı hatası oluştu: {e}", ephemeral=True) + + @commands.hybrid_command(name="link-muaf-rol-ayarla", description="Link engel sisteminden muaf olacak rolü (ve üstünü) ayarlar.") + @commands.has_permissions(administrator=True) + @app_commands.describe(rol="Muaf tutulacak rol (Belirtilmezse muaf rol kaldırılır)") + async def link_muaf_rol_ayarla(self, ctx: commands.Context, rol: discord.Role = None): + if ctx.interaction: + await ctx.defer() + + rol_id = rol.id if rol else 0 + try: + await Database.link_muaf_rol_ayarla(ctx.guild.id, rol_id) + if rol: + await ctx.send(f"✅ Link muaf rolü **{rol.name}** olarak ayarlandı. Bu rol ve üstündeki rollere sahip üyeler artık her kanalda link paylaşabilir.") + else: + await ctx.send("✅ Link muaf rolü kaldırıldı. Artık sadece yöneticiler ve beyaz listedekiler muaf olacaktır.") + except Exception as e: + await ctx.send(f"❌ Rol ayarlanırken bir veritabanı hatası oluştu: {e}", ephemeral=True) + + @commands.hybrid_command(name="caps-engeli-ayarla", description="Caps Lock korumasını açar veya kapatır.") + @commands.has_permissions(administrator=True) + @app_commands.describe(aktif="Aktif yapmak için True, kapatmak için False girin") + async def caps_engeli_ayarla(self, ctx: commands.Context, aktif: bool): + if ctx.interaction: + await ctx.defer() + + await Database.anti_raid_ayarlarini_getir(ctx.guild.id) + val = 1 if aktif else 0 + await Database.execute("UPDATE anti_raid_ayarlar SET caps_engeli = ? WHERE guild_id = ?", (val, ctx.guild.id)) + durum = "aktif hale getirildi ✅" if aktif else "devre dışı bırakıldı ❌" + await ctx.send(f"🛡️ Caps Lock koruması başarıyla {durum}.") + + @commands.hybrid_command(name="emoji-engeli-ayarla", description="Emoji Spam korumasını açar veya kapatır.") + @commands.has_permissions(administrator=True) + @app_commands.describe(aktif="Aktif yapmak için True, kapatmak için False girin") + async def emoji_engeli_ayarla(self, ctx: commands.Context, aktif: bool): + if ctx.interaction: + await ctx.defer() + + await Database.anti_raid_ayarlarini_getir(ctx.guild.id) + val = 1 if aktif else 0 + await Database.execute("UPDATE anti_raid_ayarlar SET emoji_engeli = ? WHERE guild_id = ?", (val, ctx.guild.id)) + durum = "aktif hale getirildi ✅" if aktif else "devre dışı bırakıldı ❌" + await ctx.send(f"🛡️ Emoji Spam koruması başarıyla {durum}.") + +async def setup(bot): + await bot.add_cog(AntiRaid(bot)) diff --git a/cogs/autorole.py b/cogs/autorole.py new file mode 100644 index 0000000000000000000000000000000000000000..f6fbd4cee1a05291e6bc788541cf0d24f3d036d8 --- /dev/null +++ b/cogs/autorole.py @@ -0,0 +1,68 @@ +import discord +from discord.ext import commands +from discord import app_commands +from database import Database + +CYBER_BLUE = discord.Color.from_rgb(52, 152, 219) +CYBER_GREEN = discord.Color.from_rgb(46, 204, 113) + + +class AutoRole(commands.Cog): + def __init__(self, bot): + self.bot = bot + + @commands.command(name="oto-rol-ayarla", description="Sunucuya katılanlara otomatik verilecek rolü ayarlar.") + @commands.has_permissions(administrator=True) + async def otorol_ayarla(self, ctx: commands.Context, rol: discord.Role, bot_rol: discord.Role = None): + bot_rol_id = bot_rol.id if bot_rol else 0 + await Database.execute( + """INSERT OR REPLACE INTO otorol_ayarlar (guild_id, rol_id, bot_rol_id, aktif) + VALUES (?, ?, ?, 1)""", + (ctx.guild.id, rol.id, bot_rol_id) + ) + await ctx.send(f"✅ Oto-rol ayarlandı! Üyelere: {rol.mention}" + (f", Botlara: {bot_rol.mention}" if bot_rol else "")) + + @commands.command(name="oto-rol-kapat", description="Oto-rol sistemini devre dışı bırakır.") + @commands.has_permissions(administrator=True) + async def otorol_kapat(self, ctx: commands.Context): + await Database.execute("UPDATE otorol_ayarlar SET aktif = 0 WHERE guild_id = ?", (ctx.guild.id,)) + await ctx.send("✅ Oto-rol devre dışı bırakıldı.") + + @commands.Cog.listener() + async def on_member_join(self, member: discord.Member): + if not await Database.modul_aktif_mi(member.guild.id, "AutoRole"): + return + cfg = await Database.fetch_one( + "SELECT * FROM otorol_ayarlar WHERE guild_id = ? AND aktif = 1", + (member.guild.id,) + ) + if not cfg: + return + + if member.bot: + bot_rol_id = cfg.get('bot_rol_id', 0) + if bot_rol_id: + rol = member.guild.get_role(bot_rol_id) + if rol: + try: + await member.add_roles(rol, reason="Oto-rol (Bot)") + except: + pass + else: + rol = member.guild.get_role(cfg['rol_id']) + if rol: + try: + await member.add_roles(rol, reason="Oto-rol (Bot)") + except: + pass + else: + rol = member.guild.get_role(cfg['rol_id']) + if rol: + try: + await member.add_roles(rol, reason="Oto-rol") + except: + pass + + +async def setup(bot): + await bot.add_cog(AutoRole(bot)) diff --git a/cogs/birthday.py b/cogs/birthday.py new file mode 100644 index 0000000000000000000000000000000000000000..df287ca473a6de428462a8996a07d702843e5dd4 --- /dev/null +++ b/cogs/birthday.py @@ -0,0 +1,119 @@ +import discord +from discord.ext import commands, tasks +from discord import app_commands +from datetime import datetime, date +from database import Database + +CYBER_GREEN = discord.Color.from_rgb(46, 204, 113) +CYBER_GOLD = discord.Color.from_rgb(241, 196, 15) +CYBER_BLUE = discord.Color.from_rgb(52, 152, 219) + + +class Birthday(commands.Cog): + def __init__(self, bot): + self.bot = bot + self.birthday_loop.start() + + def cog_unload(self): + self.birthday_loop.cancel() + + @tasks.loop(hours=1) + async def birthday_loop(self): + today = date.today() + if today.day == 1 and False: # hata + return + records = await Database.fetch_all("SELECT * FROM dogum_gunu_ayarlar WHERE aktif = 1") + for ayar in records: + guild_id = ayar['guild_id'] + guild = self.bot.get_guild(guild_id) + if not guild: + continue + channel = guild.get_channel(ayar['kanal_id']) if ayar['kanal_id'] else None + rol = guild.get_role(ayar['rol_id']) if ayar['rol_id'] else None + + births = await Database.fetch_all( + "SELECT * FROM dogum_gunleri WHERE guild_id = ? AND gun = ? AND ay = ?", + (guild_id, today.day, today.month) + ) + + for b in births: + if not channel: + continue + + member = guild.get_member(b['user_id']) + if not member: + continue + + embed = discord.Embed( + title="🎂 İyi ki Doğdun!", + description=f"🎉 Bugün **{member.mention}** adlı kullanıcının doğum günü!\n\nHepberaber nice mutlu yıllar dileyelim! 🎊🎈", + color=CYBER_GOLD, + timestamp=datetime.now() + ) + embed.set_thumbnail(url=member.display_avatar.url) + embed.set_footer(text="ArazAI Doğum Günü Sistemi") + try: + await channel.send(content=member.mention, embed=embed) + except: + pass + + if rol: + try: + await member.add_roles(rol, reason="Doğum günü rolü") + except: + pass + + @birthday_loop.before_loop + async def before_birthday_loop(self): + await self.bot.wait_until_ready() + + @commands.command(name="doğum-günü-ayarla", description="Doğum günü kanalı ve rolünü ayarlar.") + @commands.has_permissions(administrator=True) + async def dogum_gunu_ayarla(self, ctx: commands.Context, kanal: discord.TextChannel, rol: discord.Role = None): + rol_id = rol.id if rol else 0 + await Database.execute( + "INSERT OR REPLACE INTO dogum_gunu_ayarlar (guild_id, kanal_id, rol_id, aktif) VALUES (?, ?, ?, 1)", + (ctx.guild.id, kanal.id, rol_id) + ) + await ctx.send(f"✅ Doğum günü sistemi ayarlandı. Kanal: {kanal.mention}" + (f", Rol: {rol.mention}" if rol else "")) + + @commands.command(name="doğum-günü-kayıt", description="Kendi doğum gününüzü kaydedersiniz.") + async def dogum_gunu_kayit(self, ctx: commands.Context, gun: int, ay: int): + if gun < 1 or gun > 31 or ay < 1 or ay > 12: + await ctx.send("❌ Geçersiz tarih. Gün: 1-31, Ay: 1-12.") + return + + # Ay gün sayısı kontrolü (basit) + max_gun = [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][ay - 1] + if gun > max_gun: + await ctx.send(f"❌ {ay}. ay en fazla {max_gun} gün çeker.") + return + + await Database.execute( + "INSERT OR REPLACE INTO dogum_gunleri (guild_id, user_id, gun, ay) VALUES (?, ?, ?, ?)", + (ctx.guild.id, ctx.author.id, gun, ay) + ) + await ctx.send(f"✅ Doğum gününüz **{gun}/{ay}** olarak kaydedildi! 🎂") + + @commands.command(name="doğum-günü-liste", description="Kayıtlı doğum günlerini gösterir.") + async def dogum_gunu_liste(self, ctx: commands.Context): + records = await Database.fetch_all( + "SELECT * FROM dogum_gunleri WHERE guild_id = ? ORDER BY ay, gun", + (ctx.guild.id,) + ) + if not records: + await ctx.send("ℹ️ Kayıtlı doğum günü yok.") + return + embed = discord.Embed(title="🎂 Doğum Günleri", color=CYBER_GOLD, timestamp=datetime.now()) + desc = "" + ay_isimleri = ["Ocak", "Şubat", "Mart", "Nisan", "Mayıs", "Haziran", "Temmuz", "Ağustos", "Eylül", "Ekim", "Kasım", "Aralık"] + for r in records: + member = ctx.guild.get_member(r['user_id']) + name = member.mention if member else f"ID: {r['user_id']}" + desc += f"• {name} — **{r['gun']} {ay_isimleri[r['ay']-1]}**\n" + embed.description = desc[:4000] + await ctx.send(embed=embed) + + +async def setup(bot): + await bot.add_cog(Birthday(bot)) diff --git a/cogs/counting.py b/cogs/counting.py new file mode 100644 index 0000000000000000000000000000000000000000..7a5da53a0f4ee5ca395d0952da4d47ac7fa4e4ca --- /dev/null +++ b/cogs/counting.py @@ -0,0 +1,160 @@ +import discord +from discord.ext import commands +from discord import app_commands +from database import Database + +CYBER_BLUE = discord.Color.from_rgb(52, 152, 219) +CYBER_GREEN = discord.Color.from_rgb(46, 204, 113) +CYBER_RED = discord.Color.from_rgb(231, 76, 60) +CYBER_GOLD = discord.Color.from_rgb(241, 196, 15) +CYBER_ORANGE = discord.Color.from_rgb(230, 126, 34) + + +class Counting(commands.Cog): + def __init__(self, bot): + self.bot = bot + + @commands.hybrid_command(name="sayma-kanal-ayarla", description="Sayı sayma oyunu kanalını ayarlar.") + @commands.has_permissions(administrator=True) + @app_commands.describe(kanal="Sayı sayma kanalı") + async def sayma_kanal_ayarla(self, ctx: commands.Context, kanal: discord.TextChannel): + if ctx.interaction: + await ctx.defer() + await Database.execute( + """INSERT OR REPLACE INTO sayi_sayma (guild_id, kanal_id, son_sayi, son_atan, rekor, aktif) + VALUES (?, ?, 0, 0, 0, 1)""", + (ctx.guild.id, kanal.id) + ) + await ctx.send(f"✅ Sayı sayma kanalı {kanal.mention} olarak ayarlandı. Sayaç **0**'dan başlıyor!") + + @commands.hybrid_command(name="sayma-kapat", description="Sayı sayma oyununu devre dışı bırakır.") + @commands.has_permissions(administrator=True) + async def sayma_kapat(self, ctx: commands.Context): + if ctx.interaction: + await ctx.defer() + await Database.execute("UPDATE sayi_sayma SET aktif = 0 WHERE guild_id = ?", (ctx.guild.id,)) + await ctx.send("✅ Sayı sayma oyunu devre dışı bırakıldı.") + + @commands.hybrid_command(name="sayma-sıfırla", description="Sayı sayma sayacını sıfırlar.") + @commands.has_permissions(administrator=True) + async def sayma_sifirla(self, ctx: commands.Context): + if ctx.interaction: + await ctx.defer() + await Database.execute( + "UPDATE sayi_sayma SET son_sayi = 0, son_atan = 0 WHERE guild_id = ?", + (ctx.guild.id,) + ) + await ctx.send("✅ Sayı sayma sayacı sıfırlandı. 0'dan başlayın!") + + @commands.Cog.listener() + async def on_message(self, message: discord.Message): + if message.author.bot or not message.guild: + return + if not await Database.modul_aktif_mi(message.guild.id, "Counting"): + return + + cfg = await Database.fetch_one( + "SELECT * FROM sayi_sayma WHERE guild_id = ? AND aktif = 1", + (message.guild.id,) + ) + if not cfg: + return + + if message.channel.id != cfg['kanal_id']: + return + + # Mesaj sadece rakam mı? + content = message.content.strip() + if not content.lstrip("-").isdigit(): + return + + try: + num = int(content) + except: + return + + son_sayi = cfg['son_sayi'] + son_atan = cfg['son_atan'] + rekor = cfg['rekor'] + + beklenen = son_sayi + 1 + if son_sayi == 0: + beklenen = 1 + + if num != beklenen: + embed = discord.Embed( + title="❌ Hatalı Sayı!", + description=f"{message.author.mention} yanlış sayı girdi!\nBeklenen: **{beklenen}**, Senin girdiğin: **{num}**\n\n**Sayaç sıfırlandı!** 🔥", + color=CYBER_RED, + timestamp=discord.utils.utcnow() + ) + embed.set_footer(text="ArazAI Sayma Oyunu") + await message.channel.send(embed=embed) + + await Database.execute( + "UPDATE sayi_sayma SET son_sayi = 0, son_atan = 0 WHERE guild_id = ?", + (message.guild.id,) + ) + # Hatalı mesajı sil + try: + await message.add_reaction("❌") + except: + pass + return + + if message.author.id == son_atan: + embed = discord.Embed( + title="⚠️ Ardışık Tespit!", + description=f"{message.author.mention}, siz art arda sayamazsınız! Sayaç sıfırlandı.", + color=CYBER_ORANGE, + timestamp=discord.utils.utcnow() + ) + await message.channel.send(embed=embed) + await Database.execute( + "UPDATE sayi_sayma SET son_sayi = 0, son_atan = 0 WHERE guild_id = ?", + (message.guild.id,) + ) + return + + # Doğru cevap + yeni_rekor = max(rekor, num) + if num > rekor: + # Yeni rekor! + try: + await message.add_reaction("🏆") + except: + pass + else: + try: + await message.add_reaction("✅") + except: + pass + + await Database.execute( + "UPDATE sayi_sayma SET son_sayi = ?, son_atan = ?, rekor = ? WHERE guild_id = ?", + (num, message.author.id, yeni_rekor, message.guild.id) + ) + + @commands.hybrid_command(name="sayma-durum", description="Sayı sayma oyununun mevcut durumunu gösterir.") + async def sayma_durum(self, ctx: commands.Context): + if ctx.interaction: + await ctx.defer() + cfg = await Database.fetch_one( + "SELECT * FROM sayi_sayma WHERE guild_id = ? AND aktif = 1", + (ctx.guild.id,) + ) + if not cfg: + await ctx.send("❌ Sayı sayma oyunu aktif değil.", ephemeral=True) + return + + embed = discord.Embed(title="🔢 Sayı Sayma Durumu", color=CYBER_BLUE, timestamp=discord.utils.utcnow()) + embed.add_field(name="Son Sayı", value=f"**{cfg['son_sayi']}**", inline=True) + embed.add_field(name="Rekor", value=f"**{cfg['rekor']}**", inline=True) + son_atan_member = ctx.guild.get_member(cfg['son_atan']) + son_atan_str = son_atan_member.mention if son_atan_member else "—" + embed.add_field(name="Son Atan", value=son_atan_str, inline=True) + await ctx.send(embed=embed) + + +async def setup(bot): + await bot.add_cog(Counting(bot)) diff --git a/cogs/custom_commands.py b/cogs/custom_commands.py new file mode 100644 index 0000000000000000000000000000000000000000..1dd1cc9d08f73abef88cc40e0c9fed1bbddcb2aa --- /dev/null +++ b/cogs/custom_commands.py @@ -0,0 +1,105 @@ +import discord +from discord.ext import commands +from discord import app_commands +from database import Database + +CYBER_BLUE = discord.Color.from_rgb(52, 152, 219) +CYBER_GREEN = discord.Color.from_rgb(46, 204, 113) +CYBER_ORANGE = discord.Color.from_rgb(230, 126, 34) + + +class CustomCommands(commands.Cog): + def __init__(self, bot): + self.bot = bot + + @commands.command(name="komut-ekle", description="Sunucuya özel bir komut oluşturur.") + @commands.has_permissions(manage_guild=True) + async def komut_ekle(self, ctx: commands.Context, isim: str, *, yanit: str): + isim_clean = isim.lower().strip() + + # Aynı isimde yerleşik komut var mı kontrol + if self.bot.get_command(isim_clean): + await ctx.send("❌ Bu isimde zaten bir yerleşik komut var. Başka bir isim deneyin.") + return + + await Database.execute( + "INSERT OR REPLACE INTO ozel_komutlar (guild_id, isim, yanit) VALUES (?, ?, ?)", + (ctx.guild.id, isim_clean, yanit) + ) + await ctx.send(f"✅ Özel komut **`!{isim_clean}`** oluşturuldu.") + + @commands.command(name="komut-sil", description="Sunucuya özel komutu siler.") + @commands.has_permissions(manage_guild=True) + async def komut_sil(self, ctx: commands.Context, isim: str): + rows = await Database.execute( + "DELETE FROM ozel_komutlar WHERE guild_id = ? AND isim = ?", + (ctx.guild.id, isim.lower().strip()) + ) + if rows > 0: + await ctx.send(f"✅ Özel komut **`!{isim}`** silindi.") + else: + await ctx.send("❌ Bu isimde özel komut bulunamadı.") + + @commands.command(name="komut-liste", description="Sunucudaki özel komutları listeler.") + async def komut_liste(self, ctx: commands.Context): + records = await Database.fetch_all( + "SELECT isim FROM ozel_komutlar WHERE guild_id = ? ORDER BY isim", + (ctx.guild.id,) + ) + if not records: + await ctx.send("ℹ️ Sunucuda özel komut bulunmamaktadır.") + return + embed = discord.Embed(title="📜 Özel Komutlar", color=CYBER_BLUE) + desc = ", ".join([f"`{r['isim']}`" for r in records]) + embed.description = desc[:4000] + await ctx.send(embed=embed) + + @commands.Cog.listener() + async def on_message(self, message: discord.Message): + if message.author.bot or not message.guild: + return + if not await Database.modul_aktif_mi(message.guild.id, "CustomCommands"): + return + + # Prefix'i oku (config.json'dan veya varsayılan !) + prefix = "!" + # Base prefix'i config'den almak için bot üzerinden alalım + if hasattr(self.bot, 'command_prefix'): + try: + # when_mentioned_or kullandığından direkt prefix değil; config'ten alalım + import json, os + config_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), "config.json") + if os.path.exists(config_path): + with open(config_path, "r", encoding="utf-8") as f: + cfg = json.load(f) + prefix = cfg.get("prefix", "!") + except: + pass + + if not message.content.startswith(prefix): + return + + # Komut adı çıkar + content_no_prefix = message.content[len(prefix):] + if not content_no_prefix: + return + isim = content_no_prefix.split()[0].lower() + + # Önce Python komutlarının içinde mi kontrol et + if self.bot.get_command(isim): + return # built-in komut, geç + + # Veritabanında özel komut var mı + row = await Database.fetch_one( + "SELECT yanit FROM ozel_komutlar WHERE guild_id = ? AND isim = ?", + (message.guild.id, isim) + ) + if not row: + return + + yanit = row['yanit'].replace("{user}", message.author.mention).replace("{server}", message.guild.name) + await message.channel.send(yanit) + + +async def setup(bot): + await bot.add_cog(CustomCommands(bot)) diff --git a/cogs/economy.py b/cogs/economy.py new file mode 100644 index 0000000000000000000000000000000000000000..83f2d7d955316a8bef512846e706d4c8a943bc52 --- /dev/null +++ b/cogs/economy.py @@ -0,0 +1,364 @@ +import discord +from discord.ext import commands +from discord import app_commands +from datetime import datetime, date +from database import Database +import random + +CYBER_GOLD = discord.Color.from_rgb(241, 196, 15) +CYBER_BLUE = discord.Color.from_rgb(52, 152, 219) +CYBER_GREEN = discord.Color.from_rgb(46, 204, 113) +CYBER_RED = discord.Color.from_rgb(231, 76, 60) + + +class Economy(commands.Cog): + def __init__(self, bot): + self.bot = bot + + async def _get_user(self, guild_id, user_id): + row = await Database.fetch_one( + "SELECT * FROM ekonomi WHERE guild_id = ? AND user_id = ?", + (guild_id, user_id) + ) + if not row: + await Database.execute( + "INSERT INTO ekonomi (guild_id, user_id, bakiye, banka, son_gunluk) VALUES (?, ?, 0, 0, '')", + (guild_id, user_id) + ) + row = await Database.fetch_one( + "SELECT * FROM ekonomi WHERE guild_id = ? AND user_id = ?", + (guild_id, user_id) + ) + return row + + @commands.hybrid_command(name="bakiye", description="Cüzdan ve banka bakiyenizi gösterir.") + @app_commands.describe(uye="Bakiyesine bakılacak üye") + async def bakiye(self, ctx: commands.Context, uye: discord.Member = None): + if ctx.interaction: + await ctx.defer() + target = uye or ctx.author + user = await self._get_user(ctx.guild.id, target.id) + + embed = discord.Embed( + title=f"💰 {target.display_name} Ekonomi Bilgisi", + color=CYBER_GOLD, + timestamp=datetime.now() + ) + embed.set_thumbnail(url=target.display_avatar.url) + embed.add_field(name="💵 Cüzdan", value=f"`{user['bakiye']}`", inline=True) + embed.add_field(name="🏦 Banka", value=f"`{user['banka']}`", inline=True) + embed.add_field(name="💎 Toplam", value=f"`{user['bakiye'] + user['banka']}`", inline=True) + embed.set_footer(text="ArazAI Ekonomi Sistemi") + await ctx.send(embed=embed) + + @commands.hybrid_command(name="günlük", description="Günlük ödül alır.") + async def gunluk(self, ctx: commands.Context): + if ctx.interaction: + await ctx.defer() + today = date.today().isoformat() + user = await self._get_user(ctx.guild.id, ctx.author.id) + + if user['son_gunluk'] == today: + await ctx.send("❌ Zaten bugün günlük ödülünüzü aldınız. Yarın tekrar deneyin.", ephemeral=True) + return + + odul = random.randint(100, 500) + await Database.execute( + "UPDATE ekonomi SET bakiye = bakiye + ?, son_gunluk = ? WHERE guild_id = ? AND user_id = ?", + (odul, today, ctx.guild.id, ctx.author.id) + ) + + embed = discord.Embed( + title="🎁 Günlük Ödül!", + description=f"Tebrikler {ctx.author.mention}! **{odul}** para kazandınız! 💵", + color=CYBER_GOLD, + timestamp=datetime.now() + ) + await ctx.send(embed=embed) + + @commands.hybrid_command(name="yatır", description="Cüzdanınızdan bankaya para yatırır.") + @app_commands.describe(miktar="Yatırılacak miktar (tümü için 'hepsi')") + async def yatir(self, ctx: commands.Context, miktar: str): + if ctx.interaction: + await ctx.defer() + user = await self._get_user(ctx.guild.id, ctx.author.id) + + if miktar.lower() == "hepsi": + amount = user['bakiye'] + else: + try: + amount = int(miktar) + except: + await ctx.send("❌ Geçerli bir miktar girin.", ephemeral=True) + return + + if amount <= 0: + await ctx.send("❌ Geçerli bir miktar girin.", ephemeral=True) + return + + if user['bakiye'] < amount: + await ctx.send("❌ Cüzdanınızda yeterli paran yok.", ephemeral=True) + return + + await Database.execute( + "UPDATE ekonomi SET bakiye = bakiye - ?, banka = banka + ? WHERE guild_id = ? AND user_id = ?", + (amount, amount, ctx.guild.id, ctx.author.id) + ) + await ctx.send(f"✅ **{amount}** para bankaya yatırıldı! 💰") + + @commands.hybrid_command(name="çek", description="Bankadan cüzdana para çekersiniz.") + @app_commands.describe(miktar="Çekilecek miktar (tümü için 'hepsi')") + async def cek(self, ctx: commands.Context, miktar: str): + if ctx.interaction: + await ctx.defer() + user = await self._get_user(ctx.guild.id, ctx.author.id) + + if miktar.lower() == "hepsi": + amount = user['banka'] + else: + try: + amount = int(miktar) + except: + await ctx.send("❌ Geçerli bir miktar girin.", ephemeral=True) + return + + if amount <= 0: + await ctx.send("❌ Geçerli bir miktar girin.", ephemeral=True) + return + + if user['banka'] < amount: + await ctx.send("❌ Bankanızda yeterli paran yok.", ephemeral=True) + return + + await Database.execute( + "UPDATE ekonomi SET bakiye = bakiye + ?, banka = banka - ? WHERE guild_id = ? AND user_id = ?", + (amount, amount, ctx.guild.id, ctx.author.id) + ) + await ctx.send(f"✅ **{amount}** para bankadan çekildi! 🏦") + + @commands.hybrid_command(name="para-gönder", description="Başka bir üyeye para gönderir.") + @app_commands.describe(uye="Para gönderilecek üye", miktar="Gönderilecek miktar") + async def para_gonder(self, ctx: commands.Context, uye: discord.Member, miktar: int): + if ctx.interaction: + await ctx.defer() + if uye.id == ctx.author.id: + await ctx.send("❌ Kendinize para gönderemezsiniz.", ephemeral=True) + return + if uye.bot: + await ctx.send("❌ Botlara para gönderemezsiniz.", ephemeral=True) + return + if miktar <= 0: + await ctx.send("❌ Geçerli bir miktar girin.", ephemeral=True) + return + + sender = await self._get_user(ctx.guild.id, ctx.author.id) + if sender['bakiye'] < miktar: + await ctx.send("❌ Yeterli paran yok.", ephemeral=True) + return + + receiver = await self._get_user(ctx.guild.id, uye.id) + + await Database.execute( + "UPDATE ekonomi SET bakiye = bakiye - ? WHERE guild_id = ? AND user_id = ?", + (miktar, ctx.guild.id, ctx.author.id) + ) + await Database.execute( + "UPDATE ekonomi SET bakiye = bakiye + ? WHERE guild_id = ? AND user_id = ?", + (miktar, ctx.guild.id, uye.id) + ) + + await ctx.send(f"✅ {ctx.author.mention} → {uye.mention} **{miktar}** para gönderildi! 💸") + + @commands.hybrid_command(name="para-sıralama", description="Sunucudaki en zengin üyeleri gösterir.") + async def para_siralama(self, ctx: commands.Context): + if ctx.interaction: + await ctx.defer() + records = await Database.fetch_all( + "SELECT user_id, bakiye, banka FROM ekonomi WHERE guild_id = ? ORDER BY (bakiye + banka) DESC LIMIT 10", + (ctx.guild.id,) + ) + if not records: + await ctx.send("ℹ️ Ekonomi verisi yok.") + return + + embed = discord.Embed(title="💰 Para Sıralaması", color=CYBER_GOLD, timestamp=datetime.now()) + desc = "" + for i, r in enumerate(records, 1): + member = ctx.guild.get_member(r['user_id']) + name = member.mention if member else f"ID: {r['user_id']}" + emoji = "🥇" if i == 1 else "🥈" if i == 2 else "🥉" if i == 3 else f"#{i}" + toplam = r['bakiye'] + r['banka'] + desc += f"{emoji} {name} — `{toplam}` para\n" + embed.description = desc + await ctx.send(embed=embed) + + @commands.hybrid_command(name="kumar", description="Slot makinada oynayın.") + @app_commands.describe(miktar="Bahis tutarı") + async def kumar(self, ctx: commands.Context, miktar: int): + if ctx.interaction: + await ctx.defer() + user = await self._get_user(ctx.guild.id, ctx.author.id) + if miktar <= 0: + await ctx.send("❌ Geçerli miktar girin.", ephemeral=True) + return + if user['bakiye'] < miktar: + await ctx.send("❌ Yeterli paran yok.", ephemeral=True) + return + + emojis = ["🍒", "🍋", "🍇", "🔔", "💎"] + s1, s2, s3 = random.choice(emojis), random.choice(emojis), random.choice(emojis) + + if s1 == s2 == s3: + kazanc = miktar * 3 + durum = f"🎉 Jackpot! 3x kazandın!" + elif s1 == s2 or s2 == s3 or s1 == s3: + kazanc = int(miktar * 1.5) + durum = "✨ Amorti! 1.5x kazandın!" + else: + kazanc = -miktar + durum = "❌ Kaybettin!" + + if kazanc > 0: + await Database.execute( + "UPDATE ekonomi SET bakiye = bakiye + ? WHERE guild_id = ? AND user_id = ?", + (kazanc - miktar, ctx.guild.id, ctx.author.id) + ) + else: + await Database.execute( + "UPDATE ekonomi SET bakiye = bakiye - ? WHERE guild_id = ? AND user_id = ?", + (miktar, ctx.guild.id, ctx.author.id) + ) + + embed = discord.Embed( + title="🎰 Slot", + description=f"`[ {s1} | {s2} | {s3} ]`\n\n{durum}\n**Bahis:** {miktar}", + color=CYBER_GOLD if kazanc > 0 else CYBER_RED, + timestamp=datetime.now() + ) + await ctx.send(embed=embed) + + @commands.hybrid_command(name="mağaza", description="Mağazadaki ürünleri listeler.") + async def magaza(self, ctx: commands.Context): + if ctx.interaction: + await ctx.defer() + items = await Database.fetch_all( + "SELECT * FROM ekonomi_magaza WHERE guild_id = ?", + (ctx.guild.id,) + ) + if not items: + await ctx.send("ℹ️ Mağazada ürün yok. Yönetici `mağaza-ekle` ile ürün ekleyebilir.") + return + + embed = discord.Embed(title="🛒 Mağaza", color=CYBER_GOLD, timestamp=datetime.now()) + desc = "" + for it in items: + rol = ctx.guild.get_role(it['rol_id']) if it['rol_id'] else None + rol_str = rol.mention if rol else "—" + stok_str = f"Sınırsız" if it['stok'] == -1 else f"{it['stok']} adet" + desc += f"• **#{it['urun_id']}** {it['isim']} — `{it['fiyat']}` para — Rol: {rol_str} — Stok: {stok_str}\n" + embed.description = desc + embed.set_footer(text="Satın almak için: /satınal ") + await ctx.send(embed=embed) + + @commands.hybrid_command(name="mağaza-ekle", description="Mağazaya ürün ekler.") + @commands.has_permissions(administrator=True) + @app_commands.describe( + isim="Ürün ismi", + fiyat="Ürün fiyatı", + rol="Ürün satın alınınca verilecek rol (opsiyonel)", + stok="Stok adedi (-1 = sınırsız)" + ) + async def magaza_ekle( + self, ctx: commands.Context, + isim: str, + fiyat: int, + rol: discord.Role = None, + stok: int = -1 + ): + if ctx.interaction: + await ctx.defer() + if fiyat <= 0: + await ctx.send("❌ Fiyat pozitif olmalı.", ephemeral=True) + return + + rol_id = rol.id if rol else 0 + await Database.execute( + "INSERT INTO ekonomi_magaza (guild_id, isim, fiyat, rol_id, stok) VALUES (?, ?, ?, ?, ?)", + (ctx.guild.id, isim, fiyat, rol_id, stok) + ) + await ctx.send(f"✅ **{isim}** mağazaya eklendi. Fiyat: `{fiyat}`") + + @commands.hybrid_command(name="satınal", description="Mağazadan ürün satın alır.") + @app_commands.describe(urun_id="Satın alınacak ürünün ID'si") + async def satinal(self, ctx: commands.Context, urun_id: int): + if ctx.interaction: + await ctx.defer() + item = await Database.fetch_one( + "SELECT * FROM ekonomi_magaza WHERE urun_id = ? AND guild_id = ?", + (urun_id, ctx.guild.id) + ) + if not item: + await ctx.send("❌ Ürün bulunamadı.", ephemeral=True) + return + + user = await self._get_user(ctx.guild.id, ctx.author.id) + if user['bakiye'] < item['fiyat']: + await ctx.send("❌ Yeterli paran yok.", ephemeral=True) + return + + # Stok kontrolü + if item['stok'] != -1 and item['stok'] <= 0: + await ctx.send("❌ Ürün stokta kalmadı.", ephemeral=True) + return + + await Database.execute( + "UPDATE ekonomi SET bakiye = bakiye - ? WHERE guild_id = ? AND user_id = ?", + (item['fiyat'], ctx.guild.id, ctx.author.id) + ) + + if item['stok'] != -1: + await Database.execute( + "UPDATE ekonomi_magaza SET stok = stok - 1 WHERE urun_id = ?", + (urun_id,) + ) + + if item['rol_id']: + rol = ctx.guild.get_role(item['rol_id']) + if rol: + try: + await ctx.author.add_roles(rol, reason="Mağaza satın alımı") + except: + pass + + await Database.execute( + "INSERT OR IGNORE INTO ekonomi_envanter (guild_id, user_id, urun_id, adet) VALUES (?, ?, ?, 1) " + "ON CONFLICT(guild_id, user_id, urun_id) DO UPDATE SET adet = adet + 1", + (ctx.guild.id, ctx.author.id, urun_id) + ) + + await ctx.send(f"✅ **{item['isim']}** satın alındı! 🛍️") + + @commands.hybrid_command(name="envanter", description="Envanterinizi gösterir.") + async def envanter(self, ctx: commands.Context): + if ctx.interaction: + await ctx.defer() + items = await Database.fetch_all( + "SELECT e.adet, m.isim FROM ekonomi_envanter e " + "JOIN ekonomi_magaza m ON e.urun_id = m.urun_id " + "WHERE e.guild_id = ? AND e.user_id = ?", + (ctx.guild.id, ctx.author.id) + ) + if not items: + await ctx.send("ℹ️ Envanteriniz boş.") + return + + embed = discord.Embed(title="🎒 Envanter", color=CYBER_BLUE, timestamp=datetime.now()) + desc = "" + for it in items: + desc += f"• **{it['isim']}** x{it['adet']}\n" + embed.description = desc + await ctx.send(embed=embed) + + +async def setup(bot): + await bot.add_cog(Economy(bot)) diff --git a/cogs/fun.py b/cogs/fun.py new file mode 100644 index 0000000000000000000000000000000000000000..95c9e08f5aacc34e3add8ebaf1d9e0b1522cfb3d --- /dev/null +++ b/cogs/fun.py @@ -0,0 +1,249 @@ +import discord +from discord.ext import commands +from discord import app_commands +import random +import asyncio +from datetime import datetime + +CYBER_BLUE = discord.Color.from_rgb(52, 152, 219) +CYBER_ORANGE = discord.Color.from_rgb(230, 126, 34) +CYBER_GREEN = discord.Color.from_rgb(46, 204, 113) +CYBER_RED = discord.Color.from_rgb(231, 76, 60) + +ESPRILER = [ + "Geçen gün bir taksi çevirdim, hala dönüyor.", + "Adamın biri gülmüş, saksıya koymuşlar.", + "Rüzgar esmiş, fırtına çıkmış, ben çıkamamışım.", + "Sinemada on dakika ara verdi, ben vermedim.", + "Geçen gün bir yazı yazdım, mürekkep bitti, tükenmez kalemle devam ettim.", + "Yıkanan ton balığına ne denir? Yıkanmış ton balığı.", + "Ben yürüyelim diyorum, o yollar yürümekle aşınmaz diyor.", + "En temiz böcek hangisidir? Hamam böceği.", + "Soru: Adamın biri uçurumdan atlayacakmış, vazgeçmiş neden? Çünkü vazgeçmiş.", + "Su içmek ücretsiz, ama su faturası ücretli. Hayat paradoks.", + "İki sıfır bir görüşmüş, üç olmak için.", + "Çöpçü olmak istiyorum, hayatta bir şeyleri çöp etmek için." +] + +TRIVIA_QUESTIONS = [ + {"q": "Türkiye Cumhuriyeti'nin kurucusu kimdir?", "a": "atatürk"}, + {"q": "Gökyüzü neden mavi görünür?", "a": "saçılma"}, + {"q": "Dünyanın en yüksek dağı hangisidir?", "a": "everest"}, + {"q": "Suyu oluşturan hidrojen ve hangi elementtir?", "a": "oksijen"}, + {"q": "İstiklal Marşı'mızın şairi kimdir?", "a": "mehmet akif"}, + {"q": "İstanbul hangi yıl fethedilmiştir?", "a": "1453"}, + {"q": "Kız Kulesi hangi şehrimizdedir?", "a": "istanbul"}, + {"q": "Dünya Güneş etrafındaki dönüşünü kaç günde tamamlar?", "a": "365"}, + {"q": "Python programlama dilini kim yarattı?", "a": "guido"}, + {"q": "Dünyanın en büyük okyanusu hangisidir?", "a": "pasifik"}, + {"q": "Discord hangi yılda kurulmuştur?", "a": "2015"}, + {"q": "Pi sayısının ilk üç hanesi nedir?", "a": "3.14"}, + {"q": "Türkiye'nin başkenti neresidir?", "a": "ankara"}, + {"q": "HTML neyin kısaltmasıdır?", "a": "hypertext"}, + {"q": "Avrupa'nın en büyük ülkesi hangisidir?", "a": "rusya"}, +] + +# Aktif trivia kanallarını takip et (aynı anda birden fazla olmasın) +ACTIVE_TRIVIA = set() + + +class Fun(commands.Cog): + def __init__(self, bot): + self.bot = bot + + @commands.command(name="yazıtura", description="Yazı mı tura mı? Parayı fırlatır.") + async def yazitura(self, ctx: commands.Context): + result = random.choice(["Yazı ✍️", "Tura 🔵"]) + embed = discord.Embed( + title="🪙 Yazı Tura", + description=f"{ctx.author.mention} parayı fırlatıyor...\n\n⏳ *Para havada dönüyor...*", + color=CYBER_ORANGE + ) + msg = await ctx.send(embed=embed) + await asyncio.sleep(1.5) + embed.description = f"{ctx.author.mention} parayı fırlattı!\n\n🪙 Sonuç: **{result}**" + await msg.edit(embed=embed) + + @commands.command(name="zar-at", description="6 yüzlü bir zar atar.") + async def zar_at(self, ctx: commands.Context): + zar_gorselleri = {1: "⚀", 2: "⚁", 3: "⚂", 4: "⚃", 5: "⚄", 6: "⚅"} + zar = random.randint(1, 6) + embed = discord.Embed( + title="🎲 Zar Atıldı", + description=f"{ctx.author.mention} zarı attı!\n\n{zar_gorselleri[zar]} **{zar}** geldi!", + color=CYBER_BLUE + ) + await ctx.send(embed=embed) + + @commands.command(name="aşk-ölçer", description="İki üye arasındaki aşk uyumunu ölçer.") + async def ask_olcer(self, ctx: commands.Context, uye1: discord.Member, uye2: discord.Member = None): + if uye2 is None: + uye2 = ctx.author + + oran = random.randint(0, 100) + + if oran < 30: + mesaj = "💔 Pek umut yok gibi duruyor, arkadaş kalmanız daha hayırlı." + elif oran < 60: + mesaj = "💛 İdare eder bir çekim var ama çabalamanız lazım." + elif oran < 85: + mesaj = "💖 Harika bir uyum! Gelecek vadediyor." + else: + mesaj = "💞 Efsanevi Aşk! Bir elmanın iki yarısı gibisiniz!" + + dolu_blok = int(oran / 10) + bos_blok = 10 - dolu_blok + cubuk = "❤️" * dolu_blok + "🖤" * bos_blok + + embed = discord.Embed( + title="💘 Aşk Ölçer", + description=( + f"💘 **{uye1.display_name}** ile **{uye2.display_name}** arasındaki aşk oranı:\n\n" + f"**Oran:** `%{oran}`\n" + f"{cubuk}\n\n" + f"{mesaj}" + ), + color=discord.Color.from_rgb(231, 76, 60), + timestamp=datetime.now() + ) + await ctx.send(embed=embed) + + @commands.command(name="slot", description="Şansınızı meyve slot makinesinde deneyin.") + async def slot(self, ctx: commands.Context): + emoji_list = ["🍎", "🍋", "🍇", "🍒", "💎", "⭐", "🍉", "🔔"] + slot1 = random.choice(emoji_list) + slot2 = random.choice(emoji_list) + slot3 = random.choice(emoji_list) + + if slot1 == slot2 == slot3: + status = "🎉 **TEBRİKLER! JACKPOT!** 🎉" + color = CYBER_GREEN + elif slot1 == slot2 or slot2 == slot3 or slot1 == slot3: + status = "✨ **Çok yakındı! Amorti aldınız!** ✨" + color = CYBER_BLUE + else: + status = "❌ **Kaybettiniz. Tekrar deneyin!** ❌" + color = CYBER_RED + + embed = discord.Embed( + title="🎰 Slot Makinesi", + description="🎰 Makine dönüyor...\n\n`[ ⌛ | ⌛ | ⌛ ]`", + color=CYBER_ORANGE + ) + msg = await ctx.send(embed=embed) + await asyncio.sleep(1.5) + embed.title = "🎰 Slot Sonucu" + embed.description = f"{ctx.author.mention} slot çevirdi:\n\n`[ {slot1} | {slot2} | {slot3} ]`\n\n{status}" + embed.color = color + await msg.edit(embed=embed) + + @commands.command(name="espri", description="Komik/soğuk bir espri yapar.") + async def espri(self, ctx: commands.Context): + random_espri = random.choice(ESPRILER) + embed = discord.Embed( + title="😂 Günün Esprisi", + description=f"*{random_espri}*", + color=CYBER_ORANGE + ) + await ctx.send(embed=embed) + + @commands.command(name="bilgi-yarışması", description="Genel kültür bilgi yarışması başlatır.") + async def bilgi_yarismasi(self, ctx: commands.Context): + channel_id = ctx.channel.id + + # Aynı kanalda zaten aktif bir yarışma var mı kontrol et + if channel_id in ACTIVE_TRIVIA: + await ctx.send("⚠️ Bu kanalda zaten devam eden bir bilgi yarışması var! Önce bitmesini bekleyin.") + return + + ACTIVE_TRIVIA.add(channel_id) + question_data = random.choice(TRIVIA_QUESTIONS) + correct_answer = question_data['a'] + + try: + embed = discord.Embed( + title="🧠 Bilgi Yarışması Başladı!", + description=( + f"**Soru:** {question_data['q']}\n\n" + f"⏱️ *Cevap vermek için 30 saniyeniz var!*\n" + f"💡 *Cevabınızı buraya yazın.*" + ), + color=CYBER_BLUE, + timestamp=datetime.now() + ) + embed.set_footer(text="ArazAI Bot • Bilgi Yarışması") + + await ctx.send(embed=embed) + + # Cevabı bekle - her zaman ctx.channel.send kullan (interaction sorunu yok) + def check(m: discord.Message): + return ( + m.channel.id == channel_id + and not m.author.bot + and correct_answer.lower() in m.content.lower() + ) + + try: + msg = await self.bot.wait_for("message", check=check, timeout=30.0) + + success_embed = discord.Embed( + title="🏆 Doğru Cevap Verildi!", + description=( + f"Tebrikler {msg.author.mention}! 🎉\n\n" + f"**Soru:** {question_data['q']}\n" + f"**Doğru Cevap:** {question_data['a'].title()}" + ), + color=CYBER_GREEN, + timestamp=datetime.now() + ) + success_embed.set_thumbnail(url=msg.author.display_avatar.url) + await ctx.channel.send(embed=success_embed) + + except asyncio.TimeoutError: + timeout_embed = discord.Embed( + title="⏰ Süre Doldu!", + description=( + f"Kimse doğru cevabı veremedi. 😔\n\n" + f"**Soru:** {question_data['q']}\n" + f"**Doğru Cevap:** {question_data['a'].title()}" + ), + color=CYBER_RED, + timestamp=datetime.now() + ) + await ctx.channel.send(embed=timeout_embed) + + finally: + # Yarışma her durumda aktif listeden kaldırılsın + ACTIVE_TRIVIA.discard(channel_id) + + @commands.command(name="avatar", description="Belirtilen üyenin avatarını büyük boyutta gösterir.") + async def avatar(self, ctx: commands.Context, member: discord.Member = None): + member = member or ctx.author + embed = discord.Embed( + title=f"🖼️ {member.display_name} Avatarı", + color=CYBER_BLUE + ) + embed.set_image(url=member.display_avatar.url) + embed.set_footer(text=f"İstekte bulunan: {ctx.author.display_name}") + await ctx.send(embed=embed) + + @commands.command(name="8top", description="Sihirli 8 top ile sorunuzu yanıtlar.") + async def sekiz_top(self, ctx: commands.Context, *, soru: str): + cevaplar = [ + "✅ Kesinlikle evet!", "✅ Buna kesin olarak güvenebilirsin.", + "✅ Evet, şüphesiz.", "✅ Görünüşe göre evet.", + "🤔 Cevabım belirsiz, tekrar dene.", "🤔 Şimdi tahmin edemiyorum.", + "🤔 Şimdi sorma, daha sonra sor.", "🤔 Daha fazla konsantre ol ve tekrar sor.", + "❌ Buna güvenme.", "❌ Cevabım hayır.", "❌ Görünüşe göre hayır.", "❌ Kesinlikle hayır." + ] + cevap = random.choice(cevaplar) + embed = discord.Embed( + title="🎱 Sihirli 8 Top", + description=f"**Soru:** {soru}\n\n**Cevap:** {cevap}", + color=CYBER_BLUE + ) + await ctx.send(embed=embed) + + +async def setup(bot): + await bot.add_cog(Fun(bot)) diff --git a/cogs/giveaway.py b/cogs/giveaway.py new file mode 100644 index 0000000000000000000000000000000000000000..8a0217b70fb45bd58e0c91cc7626232b3df54325 --- /dev/null +++ b/cogs/giveaway.py @@ -0,0 +1,251 @@ +import discord +from discord.ext import commands, tasks +from discord import app_commands +from datetime import datetime, timedelta +from database import Database + +CYBER_GOLD = discord.Color.from_rgb(241, 196, 15) +CYBER_GREEN = discord.Color.from_rgb(46, 204, 113) +CYBER_RED = discord.Color.from_rgb(231, 76, 60) +CYBER_BLUE = discord.Color.from_rgb(52, 152, 219) + + +class Giveaway(commands.Cog): + def __init__(self, bot): + self.bot = bot + self.giveaway_loop.start() + self._giveaway_views_added = False + + def cog_unload(self): + self.giveaway_loop.cancel() + + @tasks.loop(seconds=10) + async def giveaway_loop(self): + try: + aktif_cekilisler = await Database.fetch_all("SELECT * FROM cekilisler WHERE aktif = 1") + now = datetime.now() + for cekilis in aktif_cekilisler: + bitis = datetime.fromisoformat(cekilis['bitis_zamani']) + if now >= bitis: + await self._end_giveaway(cekilis) + except Exception as e: + print(f"Giveaway loop hatası: {e}") + + @giveaway_loop.before_loop + async def before_giveaway_loop(self): + await self.bot.wait_until_ready() + + async def _end_giveaway(self, cekilis): + try: + guild = self.bot.get_guild(cekilis['guild_id']) + if not guild: + return + channel = guild.get_channel(cekilis['kanal_id']) + if not channel: + return + try: + message = await channel.fetch_message(cekilis['mesaj_id']) + except: + return + + katilimcilar = await Database.fetch_all( + "SELECT user_id FROM cekilis_katilimcilar WHERE mesaj_id = ?", + (cekilis['mesaj_id'],) + ) + + await Database.execute( + "UPDATE cekilisler SET aktif = 0 WHERE mesaj_id = ?", + (cekilis['mesaj_id'],) + ) + + if not katilimcilar: + embed = message.embeds[0] if message.embeds else None + if embed: + embed.color = CYBER_RED + embed.title = f"🎉 Çekiliş Bitti - {cekilis['odul']}" + embed.description = "❌ Çekilişe kimse katılmadı!" + await message.edit(embed=embed, view=None) + return + + import random + kazanan_sayi = cekilis['kazsayi'] + user_ids = [k['user_id'] for k in katilimcilar] + kazananlar = random.sample(user_ids, min(kazanan_sayi, len(user_ids))) + + embed = message.embeds[0] if message.embeds else None + if embed: + embed.color = CYBER_GREEN + embed.title = f"🎉 Çekiliş Bitti - {cekilis['odul']}" + kazananlar_str = ", ".join([f"<@{uid}>" for uid in kazananlar]) + embed.description = f"**Kazanan(lar):** {kazananlar_str}\n\n🎉 Tebrikler!" + await message.edit(embed=embed, view=None) + + await channel.send(f"🎉 Çekiliş bitti! **{cekilis['odul']}** kazananı: {', '.join([f'<@{uid}>' for uid in kazananlar])} Tebrikler! 🎊") + + except Exception as e: + print(f"Giveaway bitirme hatası: {e}") + + @commands.hybrid_command(name="çekiliş-başlat", description="Yeni bir çekiliş başlatır.") + @commands.has_permissions(manage_guild=True) + @app_commands.describe( + odul="Çekiliş ödülü", + sure="Süre (örn: 10s, 5m, 2h, 1d, 1w)", + kazanan_sayisi="Kaç kazanan olacağı", + kanal="Çekiliş mesajının gönderileceği kanal (boş = mevcut kanal)" + ) + async def cekilis_baslat( + self, ctx: commands.Context, + odul: str, + sure: str, + kazanan_sayisi: int = 1, + kanal: discord.TextChannel = None + ): + if ctx.interaction: + await ctx.defer() + try: + delta = parse_giveaway_duration(sure) + except ValueError as e: + await ctx.send(f"❌ {e}", ephemeral=True) + return + + if kazanan_sayisi < 1: + kazanan_sayisi = 1 + + hedef_kanal = kanal or ctx.channel + bitis = datetime.now() + delta + + embed = discord.Embed( + title=f"🎉 Çekiliş - {odul}", + description=( + f"**Ödül:** {odul}\n" + f"**Kazanan Sayısı:** {kazanan_sayisi}\n" + f"**Bitiş:** ()\n\n" + f"Aşağıdaki butona tıklayarak katıl! 🍀" + ), + color=CYBER_GOLD, + timestamp=datetime.now() + ) + embed.set_footer(text=f"Başlatan: {ctx.author} | Katılımcı: 0") + embed.set_thumbnail(url=ctx.guild.icon.url if ctx.guild.icon else "") + + # Geçici bir dummy mesaj_id ile view oluştur + msg = await hedef_kanal.send(embed=embed) + view = discord.ui.View(timeout=None) + view.add_item(discord.ui.Button( + label="🎉 Katıl", + style=discord.ButtonStyle.success, + custom_id=f"giveaway_join_{msg.id}" + )) + await msg.edit(view=view) + + await Database.execute( + """INSERT OR REPLACE INTO cekilisler + (mesaj_id, kanal_id, guild_id, baslatan_id, odul, sure, kazsayi, bitis_zamani, aktif) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, 1)""", + (msg.id, hedef_kanal.id, ctx.guild.id, ctx.author.id, odul, int(delta.total_seconds()), kazanan_sayisi, bitis.isoformat()) + ) + + await ctx.send(f"✅ Çekiliş başlatıldı! {msg.jump_url}", ephemeral=True if ctx.interaction else False) + + @commands.hybrid_command(name="çekiliş-bitir", description="Aktif bir çekilişi hemen bitirir.") + @commands.has_permissions(manage_guild=True) + @app_commands.describe(mesaj_id="Çekiliş mesajının ID'si") + async def cekilis_bitir(self, ctx: commands.Context, mesaj_id: str): + if ctx.interaction: + await ctx.defer() + try: + mid = int(mesaj_id) + except: + await ctx.send("❌ Geçersiz mesaj ID.", ephemeral=True) + return + + cekilis = await Database.fetch_one("SELECT * FROM cekilisler WHERE mesaj_id = ? AND aktif = 1", (mid,)) + if not cekilis: + await ctx.send("❌ Aktif çekiliş bulunamadı.", ephemeral=True) + return + + await self._end_giveaway(cekilis) + await ctx.send("✅ Çekiliş bitirildi.") + + @commands.hybrid_command(name="çekiliş-liste", description="Sunucudaki tüm çekilişleri listeler.") + async def cekilis_liste(self, ctx: commands.Context): + if ctx.interaction: + await ctx.defer() + records = await Database.fetch_all( + "SELECT * FROM cekilisler WHERE guild_id = ? ORDER BY rowid DESC LIMIT 10", + (ctx.guild.id,) + ) + if not records: + await ctx.send("ℹ️ Sunucuda çekiliş bulunmamaktadır.") + return + + embed = discord.Embed(title="🎉 Çekiliş Listesi", color=CYBER_GOLD, timestamp=datetime.now()) + desc = "" + for r in records: + durum = "✅ Aktif" if r['aktif'] else "❌ Bitti" + bitis = datetime.fromisoformat(r['bitis_zamani']).strftime("%d.%m.%Y %H:%M") + desc += f"• **{r['odul']}** | {durum} | Bitiş: `{bitis}` | [Mesaj](https://discord.com/channels/{ctx.guild.id}/{r['kanal_id']}/{r['mesaj_id']})\n" + embed.description = desc + await ctx.send(embed=embed) + + @commands.Cog.listener() + async def on_interaction(self, interaction: discord.Interaction): + if interaction.type != discord.InteractionType.component: + return + custom_id = interaction.data.get("custom_id", "") + if not custom_id.startswith("giveaway_join_"): + return + + if not await Database.modul_aktif_mi(interaction.guild_id, "Giveaway"): + try: + await interaction.response.send_message("❌ Çekiliş modülü şu an devre dışı.", ephemeral=True) + except: + pass + return + + try: + mesaj_id = int(custom_id.split("giveaway_join_")[-1]) + except: + return + + cekilis = await Database.fetch_one("SELECT * FROM cekilisler WHERE mesaj_id = ? AND aktif = 1", (mesaj_id,)) + if not cekilis: + await interaction.response.send_message("❌ Bu çekiliş artık aktif değil.", ephemeral=True) + return + + existing = await Database.fetch_one( + "SELECT 1 FROM cekilis_katilimcilar WHERE mesaj_id = ? AND user_id = ?", + (mesaj_id, interaction.user.id) + ) + if existing: + await Database.execute( + "DELETE FROM cekilis_katilimcilar WHERE mesaj_id = ? AND user_id = ?", + (mesaj_id, interaction.user.id) + ) + await interaction.response.send_message("✅ Çekilişten ayrıldın.", ephemeral=True) + else: + await Database.execute( + "INSERT OR IGNORE INTO cekilis_katilimcilar (mesaj_id, user_id) VALUES (?, ?)", + (mesaj_id, interaction.user.id) + ) + await interaction.response.send_message("✅ Çekilişe katıldın! Bol şans! 🍀", ephemeral=True) + + # Embed'deki katılımcı sayısını güncelle + try: + katilimci_sayi = await Database.fetch_one( + "SELECT COUNT(*) as toplam FROM cekilis_katilimcilar WHERE mesaj_id = ?", + (mesaj_id,) + ) + count = katilimci_sayi['toplam'] if katilimci_sayi else 0 + message = interaction.message + if message and message.embeds: + embed = message.embeds[0] + baslatan = embed.footer.text.split("|")[0].strip() if embed.footer.text else "" + embed.set_footer(text=f"{baslatan} | Katılımcı: {count}") + await message.edit(embed=embed) + except: + pass + + +async def setup(bot): + await bot.add_cog(Giveaway(bot)) diff --git a/cogs/itibar.py b/cogs/itibar.py new file mode 100644 index 0000000000000000000000000000000000000000..24b332aeec191c49d3737814e6c8140898174ab7 --- /dev/null +++ b/cogs/itibar.py @@ -0,0 +1,551 @@ +import discord +from discord.ext import commands, tasks +from discord import app_commands +from database import Database +from datetime import datetime + +CYBER_GOLD = discord.Color.from_rgb(241, 196, 15) +CYBER_RED = discord.Color.from_rgb(231, 76, 60) +CYBER_GREEN = discord.Color.from_rgb(46, 204, 113) + +class PopulerlikNotificationToggleView(discord.ui.View): + def __init__(self): + super().__init__(timeout=None) + + @discord.ui.button(label="Bildirimleri Aç/Kapat 🔔", style=discord.ButtonStyle.primary, custom_id="toggle_populerlik_notif") + async def toggle_notif(self, interaction: discord.Interaction, button: discord.ui.Button): + row = await Database.fetch_one( + "SELECT aktif FROM itibar_bildirim_tercihleri WHERE guild_id = ? AND user_id = ?", + (interaction.guild.id, interaction.user.id) + ) + + current_state = row['aktif'] if row else 1 + new_state = 0 if current_state == 1 else 1 + + await Database.execute( + "INSERT OR REPLACE INTO itibar_bildirim_tercihleri (guild_id, user_id, aktif) VALUES (?, ?, ?)", + (interaction.guild.id, interaction.user.id, new_state) + ) + + durum_str = "✅ **Aktif Edildi** (Popülerlik gönderme süreniz dolduğunda etiketleneceksiniz)." if new_state == 1 else "❌ **Kapatıldı** (Artık bildirim almayacaksınız)." + await interaction.response.send_message(f"🔔 Popülerlik bildirim tercihiniz: {durum_str}", ephemeral=True) + + +class Itibar(commands.Cog): + def __init__(self, bot): + self.bot = bot + self._cooldowns = {} # (guild_id, giver_id, target_id) -> datetime + self._last_given = {} # giver_id -> target_id (üst üste aynı kişiye vermeyi engellemek için) + self.cooldown_check_loop.start() + + def cog_unload(self): + self.cooldown_check_loop.cancel() + + async def cog_load(self): + # Database tablolaları eski yapı ile uyumluluk için aynı kalıyor + await Database.execute(""" + CREATE TABLE IF NOT EXISTS itibar ( + guild_id INTEGER, + user_id INTEGER, + puan INTEGER DEFAULT 0, + PRIMARY KEY (guild_id, user_id) + ) + """) + await Database.execute(""" + CREATE TABLE IF NOT EXISTS itibar_roller ( + guild_id INTEGER, + rol_id INTEGER, + hedef_puan INTEGER, + PRIMARY KEY (guild_id, hedef_puan) + ) + """) + await Database.execute(""" + CREATE TABLE IF NOT EXISTS itibar_ayarlar ( + guild_id INTEGER PRIMARY KEY, + cooldown_seconds INTEGER DEFAULT 60 + ) + """) + await Database.execute(""" + CREATE TABLE IF NOT EXISTS itibar_log_ayarlar ( + guild_id INTEGER PRIMARY KEY, + kanal_id INTEGER + ) + """) + await Database.execute(""" + CREATE TABLE IF NOT EXISTS itibar_yasakli_kanallar ( + guild_id INTEGER, + kanal_id INTEGER, + PRIMARY KEY (guild_id, kanal_id) + ) + """) + await Database.execute(""" + CREATE TABLE IF NOT EXISTS itibar_bildirim_ayarlar ( + guild_id INTEGER PRIMARY KEY, + kanal_id INTEGER + ) + """) + await Database.execute(""" + CREATE TABLE IF NOT EXISTS itibar_bildirim_tercihleri ( + guild_id INTEGER, + user_id INTEGER, + aktif INTEGER DEFAULT 1, + PRIMARY KEY (guild_id, user_id) + ) + """) + + @tasks.loop(seconds=5) + async def cooldown_check_loop(self): + try: + now = datetime.now() + expired_keys = [] + + for key, last_used in list(self._cooldowns.items()): + guild_id, giver_id, target_id = key + + cooldown_row = await Database.fetch_one("SELECT cooldown_seconds FROM itibar_ayarlar WHERE guild_id = ?", (guild_id,)) + cooldown_seconds = cooldown_row['cooldown_seconds'] if cooldown_row else 60 + + if (now - last_used).total_seconds() >= cooldown_seconds: + expired_keys.append(key) + + notify_row = await Database.fetch_one("SELECT kanal_id FROM itibar_bildirim_ayarlar WHERE guild_id = ?", (guild_id,)) + if notify_row: + pref_row = await Database.fetch_one("SELECT aktif FROM itibar_bildirim_tercihleri WHERE guild_id = ? AND user_id = ?", (guild_id, giver_id)) + is_active = pref_row['aktif'] if pref_row else 1 + + if is_active == 1: + guild = self.bot.get_guild(guild_id) + if guild: + channel = guild.get_channel(notify_row['kanal_id']) + giver = guild.get_member(giver_id) + target = guild.get_member(target_id) + if channel and giver and target: + try: + await channel.send(f"🔔 {giver.mention}, artık **{target.display_name}** kullanıcısına tekrar popülerlik gönderebilirsin!") + except: + pass + + for key in expired_keys: + if key in self._cooldowns: + del self._cooldowns[key] + except Exception as e: + print(f"Populerlik cooldown loop hatası: {e}") + + @cooldown_check_loop.before_loop + async def before_cooldown_check_loop(self): + await self.bot.wait_until_ready() + + async def cog_check(self, ctx: commands.Context): + if not ctx.guild: + return True + + is_active = await Database.modul_aktif_mi(ctx.guild.id, "Itibar") + if not is_active: + raise commands.CheckFailure("⚙️ **Popülerlik** modülü bu sunucuda devre dışı bırakılmıştır.") + + if not ctx.author.guild_permissions.administrator: + row = await Database.fetch_one( + "SELECT kanal_id FROM itibar_yasakli_kanallar WHERE guild_id = ? AND kanal_id = ?", + (ctx.guild.id, ctx.channel.id) + ) + if row: + raise commands.CheckFailure("❌ Bu kanalda popülerlik sorgulama ve sıralama komutlarını kullanmak yasaklanmıştır!") + + return True + + async def cog_command_error(self, ctx: commands.Context, error: Exception): + if isinstance(error, commands.CheckFailure): + try: + await ctx.send(str(error), ephemeral=True) + except: + pass + + async def _send_populerlik_event_log(self, guild: discord.Guild, giver: discord.Member, receiver: discord.Member, new_puan: int): + row = await Database.fetch_one("SELECT kanal_id FROM itibar_log_ayarlar WHERE guild_id = ?", (guild.id,)) + if not row: + return + + log_channel = guild.get_channel(row['kanal_id']) + if log_channel: + embed = discord.Embed( + title="✨ Popülerlik Etkinliği", + description=f"Sunucuda yeni bir popülerlik puanı gönderildi!", + color=CYBER_GOLD, + timestamp=datetime.now() + ) + embed.add_field(name="⭐ Gönderen", value=giver.mention, inline=True) + embed.add_field(name="🎯 Alan", value=receiver.mention, inline=True) + embed.add_field(name="📊 Yeni Puan", value=f"**{new_puan}**", inline=True) + embed.set_footer(text="ArazAI Popülerlik Takip Sistemi") + try: + await log_channel.send(embed=embed) + except: + pass + + async def _check_role_rewards(self, member: discord.Member, new_puan: int): + rows = await Database.fetch_all( + "SELECT rol_id, hedef_puan FROM itibar_roller WHERE guild_id = ? ORDER BY hedef_puan ASC", + (member.guild.id,) + ) + if not rows: + return + + roles_to_add = [] + for row in rows: + rol_id = row['rol_id'] + hedef_puan = row['hedef_puan'] + + if new_puan >= hedef_puan: + role = member.guild.get_role(rol_id) + if role and role not in member.roles: + roles_to_add.append(role) + + if roles_to_add: + try: + await member.add_roles(*roles_to_add, reason="ArazAI Popülerlik Sistemi Rol Ödülü") + channel = member.guild.system_channel or member.guild.text_channels[0] + roles_str = ", ".join([r.mention for r in roles_to_add]) + embed = discord.Embed( + title="🎉 Popülerlik Rol Ödülü!", + description=f"Tebrikler {member.mention}! **{new_puan}** popülerlik puanına ulaşarak {roles_str} rolünü/rollerini kazandınız! 🏆", + color=CYBER_GOLD, + timestamp=datetime.now() + ) + embed.set_thumbnail(url=member.display_avatar.url) + await channel.send(embed=embed) + except Exception as e: + print(f"Popülerlik rol ödülü verilirken hata oluştu: {e}") + + @commands.Cog.listener() + async def on_message(self, message: discord.Message): + if message.author.bot or not message.guild: + return + + if not await Database.modul_aktif_mi(message.guild.id, "Itibar"): + return + + content = message.content.strip().lower() + words = content.split() + is_rep_trigger = "!1" in words + target_member = None + + if is_rep_trigger and message.reference and message.reference.resolved: + resolved_msg = message.reference.resolved + if isinstance(resolved_msg, discord.Message): + target_member = resolved_msg.author + + elif is_rep_trigger and message.mentions: + for m in message.mentions: + if m.id != message.author.id and not m.bot: + target_member = m + break + + if target_member: + if target_member.id == message.author.id: + try: + await message.reply("❌ Kendinize popülerlik gönderemezsiniz!", delete_after=5) + except: + pass + return + + if target_member.bot: + try: + await message.reply("❌ Botlara popülerlik gönderemezsiniz!", delete_after=5) + except: + pass + return + + if self._last_given.get(message.author.id) == target_member.id: + try: + await message.reply("❌ Aynı kişiye **üst üste** popülerlik gönderemezsiniz! Araya başka birine puan göndermeniz gerekir.", delete_after=5) + except: + pass + return + + cooldown_row = await Database.fetch_one("SELECT cooldown_seconds FROM itibar_ayarlar WHERE guild_id = ?", (message.guild.id,)) + cooldown_seconds = cooldown_row['cooldown_seconds'] if cooldown_row else 60 + + now = datetime.now() + cooldown_key = (message.guild.id, message.author.id, target_member.id) + last_used = self._cooldowns.get(cooldown_key) + if last_used and (now - last_used).total_seconds() < cooldown_seconds: + left = cooldown_seconds - int((now - last_used).total_seconds()) + try: + await message.reply(f"⏳ Bu kullanıcıya tekrar popülerlik göndermek için **{left}** saniye beklemelisiniz.", delete_after=5) + except: + pass + return + + self._cooldowns[cooldown_key] = now + self._last_given[message.author.id] = target_member.id + + row = await Database.fetch_one("SELECT puan FROM itibar WHERE guild_id = ? AND user_id = ?", (message.guild.id, target_member.id)) + current_puan = (row['puan'] if row else 0) + 1 + await Database.execute("INSERT OR REPLACE INTO itibar (guild_id, user_id, puan) VALUES (?, ?, ?)", (message.guild.id, target_member.id, current_puan)) + + embed = discord.Embed( + description=f"⭐ {message.author.mention}, **{target_member.mention}** kullanıcısına popülerlik gönderdi!\n\n✨ Yeni Popülerlik Puanı: **{current_puan}**", + color=CYBER_GOLD + ) + await message.reply(embed=embed, mention_author=False) + + await self._send_populerlik_event_log(message.guild, message.author, target_member, current_puan) + await self._check_role_rewards(target_member, current_puan) + + @commands.hybrid_command(name="popülerlik", description="Kendinizin veya başka bir üyenin popülerlik puanını gösterir.") + @app_commands.describe(uye="Popülerlik puanına bakılacak üye veya 'top'") + async def populerlik(self, ctx: commands.Context, *, uye: str = None): + if ctx.interaction: + await ctx.defer() + + if uye and uye.lower().strip() == "top": + await self.populerlik_siralanis(ctx) + return + + target_member = ctx.author + if uye: + try: + converter = commands.MemberConverter() + target_member = await converter.convert(ctx, uye) + except commands.MemberNotFound: + await ctx.send("❌ Belirtilen üye bulunamadı!", ephemeral=True) + return + + row = await Database.fetch_one("SELECT puan FROM itibar WHERE guild_id = ? AND user_id = ?", (ctx.guild.id, target_member.id)) + puan = row['puan'] if row else 0 + + embed = discord.Embed( + title=f"⭐ Popülerlik Profili: {target_member.display_name}", + description=f"{target_member.mention} kullanıcısının toplam **{puan}** popülerlik puanı bulunmaktadır.", + color=CYBER_GOLD, + timestamp=datetime.now() + ) + embed.set_thumbnail(url=target_member.display_avatar.url) + embed.set_footer(text=f"Sorgulayan: {ctx.author.display_name} • ArazAI") + await ctx.send(embed=embed) + + @commands.hybrid_command(name="popülerlik-sıralaması", aliases=["popülerlik-top", "popülerliktop", "popülerlik_top"], description="Sunucudaki en yüksek popülerliğe sahip üyeleri listeler.") + async def populerlik_siralanis(self, ctx: commands.Context): + if ctx.interaction: + await ctx.defer() + + rows = await Database.fetch_all("SELECT user_id, puan FROM itibar WHERE guild_id = ? ORDER BY puan DESC LIMIT 10", (ctx.guild.id,)) + if not rows: + await ctx.send("📝 Bu sunucuda henüz hiç kimse popülerlik puanı kazanmamış.") + return + + embed = discord.Embed( + title="🏆 Sunucu Popülerlik Sıralaması", + color=CYBER_GOLD, + timestamp=datetime.now() + ) + + leaderboard_str = "" + for index, row in enumerate(rows, 1): + user = ctx.guild.get_member(row['user_id']) + user_str = user.mention if user else f"Bilinmeyen Üye ({row['user_id']})" + + medals = {1: "🥇", 2: "🥈", 3: "🥉"} + prefix = medals.get(index, f"`#{index}`") + leaderboard_str += f"{prefix} | {user_str} — **{row['puan']}** Puan\n" + + embed.description = leaderboard_str + embed.set_footer(text="ArazAI Popülerlik Sistemi") + await ctx.send(embed=embed) + + @commands.hybrid_command(name="popülerlik-ver", description="[Admin] Belirtilen üyeye popülerlik puanı ekler.") + @commands.has_permissions(administrator=True) + @app_commands.describe(uye="Puan eklenecek üye", miktar="Eklenecek puan miktarı") + async def populerlik_ver(self, ctx: commands.Context, uye: discord.Member, miktar: int): + if ctx.interaction: + await ctx.defer() + + if miktar <= 0: + await ctx.send("❌ Eklenecek puan miktarı 0'dan büyük olmalıdır!", ephemeral=True) + return + + row = await Database.fetch_one("SELECT puan FROM itibar WHERE guild_id = ? AND user_id = ?", (ctx.guild.id, uye.id)) + current_puan = (row['puan'] if row else 0) + miktar + await Database.execute("INSERT OR REPLACE INTO itibar (guild_id, user_id, puan) VALUES (?, ?, ?)", (ctx.guild.id, uye.id, current_puan)) + + embed = discord.Embed( + title="⚙️ Popülerlik Puanı Eklendi", + description=f"**Kullanıcı:** {uye.mention}\n**Eklenen Miktar:** `+{miktar}`\n**Yeni Toplam Puan:** **{current_puan}**\n**Yetkili:** {ctx.author.mention}", + color=CYBER_GREEN, + timestamp=datetime.now() + ) + embed.set_thumbnail(url=uye.display_avatar.url) + await ctx.send(embed=embed) + + await self._check_role_rewards(uye, current_puan) + + @commands.hybrid_command(name="popülerlik-al", description="[Admin] Belirtilen üyeden popülerlik puanı eksiltir.") + @commands.has_permissions(administrator=True) + @app_commands.describe(uye="Puanı düşürülecek üye", miktar="Düşürülecek puan miktarı") + async def populerlik_al(self, ctx: commands.Context, uye: discord.Member, miktar: int): + if ctx.interaction: + await ctx.defer() + + if miktar <= 0: + await ctx.send("❌ Düşürülecek puan miktarı 0'dan büyük olmalıdır!", ephemeral=True) + return + + row = await Database.fetch_one("SELECT puan FROM itibar WHERE guild_id = ? AND user_id = ?", (ctx.guild.id, uye.id)) + old_puan = row['puan'] if row else 0 + current_puan = max(0, old_puan - miktar) + await Database.execute("INSERT OR REPLACE INTO itibar (guild_id, user_id, puan) VALUES (?, ?, ?)", (ctx.guild.id, uye.id, current_puan)) + + embed = discord.Embed( + title="⚙️ Popülerlik Puanı Düşürüldü", + description=f"**Kullanıcı:** {uye.mention}\n**Eksiltilen Miktar:** `-{miktar}`\n**Yeni Toplam Puan:** **{current_puan}**\n**Yetkili:** {ctx.author.mention}", + color=CYBER_RED, + timestamp=datetime.now() + ) + embed.set_thumbnail(url=uye.display_avatar.url) + await ctx.send(embed=embed) + + @commands.hybrid_command(name="popülerlik-rol-ayarla", description="[Admin] Belirli bir popülerlik hedefi için verilecek rolü belirler.") + @commands.has_permissions(administrator=True) + @app_commands.describe(rol="Kazanılacak Rol", puan="Ulaşılması gereken hedef popülerlik puanı") + async def populerlik_rol_ayarla(self, ctx: commands.Context, rol: discord.Role, puan: int): + if ctx.interaction: + await ctx.defer() + + if puan <= 0: + await ctx.send("❌ Hedef puan 0'dan büyük olmalıdır!", ephemeral=True) + return + + await Database.execute( + "INSERT OR REPLACE INTO itibar_roller (guild_id, rol_id, hedef_puan) VALUES (?, ?, ?)", + (ctx.guild.id, rol.id, puan) + ) + + embed = discord.Embed( + title="⚙️ Popülerlik Rol Ödülü Ayarlandı", + description=f"**Rol:** {rol.mention}\n**Gereken Hedef Puan:** **{puan}**\n**Yetkili:** {ctx.author.mention}", + color=CYBER_GREEN, + timestamp=datetime.now() + ) + await ctx.send(embed=embed) + + @commands.hybrid_command(name="popülerlik-cooldown", description="[Admin] Popülerlik puanı verme bekleme süresini (saniye) ayarlar.") + @commands.has_permissions(administrator=True) + @app_commands.describe(saniye="Bekleme süresi (saniye cinsinden)") + async def populerlik_cooldown(self, ctx: commands.Context, saniye: int): + if ctx.interaction: + await ctx.defer() + + if saniye < 0: + await ctx.send("❌ Bekleme süresi 0 veya daha büyük olmalıdır!", ephemeral=True) + return + + await Database.execute( + "INSERT OR REPLACE INTO itibar_ayarlar (guild_id, cooldown_seconds) VALUES (?, ?)", + (ctx.guild.id, saniye) + ) + + embed = discord.Embed( + title="⚙️ Popülerlik Bekleme Süresi Ayarlandı", + description=f"**Yeni Bekleme Süresi:** `{saniye}` saniye\n**Yetkili:** {ctx.author.mention}", + color=CYBER_GREEN, + timestamp=datetime.now() + ) + await ctx.send(embed=embed) + + @commands.hybrid_command(name="popülerlik-log-ayarla", description="[Admin] Popülerlik kazanma etkinliklerinin duyurulacağı kanalı seçer.") + @commands.has_permissions(administrator=True) + @app_commands.describe(kanal="Popülerlik etkinlik log kanalı") + async def populerlik_log_ayarla(self, ctx: commands.Context, kanal: discord.TextChannel): + if ctx.interaction: + await ctx.defer() + + await Database.execute( + "INSERT OR REPLACE INTO itibar_log_ayarlar (guild_id, kanal_id) VALUES (?, ?)", + (ctx.guild.id, kanal.id) + ) + + embed = discord.Embed( + title="⚙️ Popülerlik Log Kanalı Ayarlandı", + description=f"**Kanal:** {kanal.mention}\n**Yetkili:** {ctx.author.mention}", + color=CYBER_GREEN, + timestamp=datetime.now() + ) + await ctx.send(embed=embed) + + @commands.hybrid_command(name="popülerlik-kanal-yasakla", description="[Admin] Popülerlik sorgulama/sıralama komutlarının kullanımını bu kanalda yasaklar.") + @commands.has_permissions(administrator=True) + @app_commands.describe(kanal="Yasaklanacak kanal") + async def populerlik_kanal_yasakla(self, ctx: commands.Context, kanal: discord.TextChannel): + if ctx.interaction: + await ctx.defer() + + await Database.execute( + "INSERT OR REPLACE INTO itibar_yasakli_kanallar (guild_id, kanal_id) VALUES (?, ?)", + (ctx.guild.id, kanal.id) + ) + + embed = discord.Embed( + title="🔒 Kanal Popülerlik Komutlarına Kapatıldı", + description=f"**Kanal:** {kanal.mention}\n*Not: Bu kanalda artık `!popülerlik`, `/popülerlik` ve `/popülerlik-sıralaması` kullanılamaz. Ancak `!1` ile popülerlik gönderme çalışmaya devam eder.*", + color=CYBER_RED, + timestamp=datetime.now() + ) + await ctx.send(embed=embed) + + @commands.hybrid_command(name="popülerlik-kanal-izin-ver", description="[Admin] Popülerlik komutları üzerindeki kanal yasağını kaldırır.") + @commands.has_permissions(administrator=True) + @app_commands.describe(kanal="Yasağı kaldırılacak kanal") + async def populerlik_kanal_izin_ver(self, ctx: commands.Context, kanal: discord.TextChannel): + if ctx.interaction: + await ctx.defer() + + await Database.execute( + "DELETE FROM itibar_yasakli_kanallar WHERE guild_id = ? AND kanal_id = ?", + (ctx.guild.id, kanal.id) + ) + + embed = discord.Embed( + title="🔓 Kanal Popülerlik Komutlarına Açıldı", + description=f"**Kanal:** {kanal.mention}\n*Not: Bu kanalda artık popülerlik sorgulama ve sıralama komutları serbestçe kullanılabilir.*", + color=CYBER_GREEN, + timestamp=datetime.now() + ) + await ctx.send(embed=embed) + + @commands.hybrid_command(name="popülerlik-bildirim-kanal-ayarla", description="[Admin] Bekleme süresi dolan kullanıcıların uyarılacağı bildirim kanalını ayarlar.") + @commands.has_permissions(administrator=True) + @app_commands.describe(kanal="Süre dolum bildirim kanalı") + async def populerlik_bildirim_kanal_ayarla(self, ctx: commands.Context, kanal: discord.TextChannel): + if ctx.interaction: + await ctx.defer() + + await Database.execute( + "INSERT OR REPLACE INTO itibar_bildirim_ayarlar (guild_id, kanal_id) VALUES (?, ?)", + (ctx.guild.id, kanal.id) + ) + + embed = discord.Embed( + title="⚙️ Popülerlik Bildirim Kanalı Ayarlandı", + description=f"**Bildirim Kanalı:** {kanal.mention}\n**Yetkili:** {ctx.author.mention}", + color=CYBER_GREEN, + timestamp=datetime.now() + ) + await ctx.send(embed=embed) + + @commands.hybrid_command(name="popülerlik-bildirim-paneli", description="[Admin] Üyelerin popülerlik süresi bildirimlerini açıp kapatabileceği butonlu paneli gönderir.") + @commands.has_permissions(administrator=True) + async def populerlik_bildirim_paneli(self, ctx: commands.Context): + if ctx.interaction: + await ctx.defer() + + embed = discord.Embed( + title="🔔 Popülerlik Bildirim Sistemi", + description="Popülerlik gönderme bekleme süreniz dolduğunda bot tarafından etiketlenerek bildirim almak istiyorsanız aşağıdaki butonu kullanabilirsiniz.\n\n*Not: Bildirim kanalını yöneticiler `/popülerlik-bildirim-kanal-ayarla` ile belirler.*", + color=CYBER_GOLD + ) + + view = PopulerlikNotificationToggleView() + await ctx.send(embed=embed, view=view) + +async def setup(bot): + await bot.add_cog(Itibar(bot)) diff --git a/cogs/level.py b/cogs/level.py new file mode 100644 index 0000000000000000000000000000000000000000..33589b72a989f2ae32566b5ea039e79ca144ae16 --- /dev/null +++ b/cogs/level.py @@ -0,0 +1,248 @@ +import discord +from discord.ext import commands +from discord import app_commands +from datetime import datetime +from database import Database +import random + +CYBER_BLUE = discord.Color.from_rgb(52, 152, 219) +CYBER_GREEN = discord.Color.from_rgb(46, 204, 113) +CYBER_GOLD = discord.Color.from_rgb(241, 196, 15) + + +class Level(commands.Cog): + def __init__(self, bot): + self.bot = bot + # Cooldown: {user_id: last_msg_ts} + self._xp_cooldown = {} + self.COOLDOWN_SECONDS = 30 + + def _xp_for_level(self, level, difficulty=100): + return 5 * (level ** 2) + 50 * level + 100 - level * difficulty // 10 + + def _level_from_xp(self, xp, difficulty=100): + level = 0 + while xp >= self._xp_for_level(level + 1, difficulty): + level += 1 + xp -= self._xp_for_level(level, difficulty) + return level, xp + + async def _add_xp(self, member: discord.Member, xp_add: int): + guild_id = member.guild.id + user_id = member.id + + row = await Database.fetch_one( + "SELECT * FROM seviye WHERE guild_id = ? AND user_id = ?", + (guild_id, user_id) + ) + if not row: + await Database.execute( + "INSERT INTO seviye (guild_id, user_id, xp, seviye) VALUES (?, ?, ?, ?)", + (guild_id, user_id, 0, 0) + ) + row = await Database.fetch_one( + "SELECT * FROM seviye WHERE guild_id = ? AND user_id = ?", + (guild_id, user_id) + ) + + new_xp = row['xp'] + xp_add + settings = await Database.fetch_one("SELECT * FROM seviye_ayarlar WHERE guild_id = ?", (guild_id,)) + difficulty = settings['zorluk'] if settings and settings.get('zorluk') else 100 + + old_level = row['seviye'] + new_level = self._level_from_xp(new_xp, difficulty)[0] + + await Database.execute( + "UPDATE seviye SET xp = ?, seviye = ? WHERE guild_id = ? AND user_id = ?", + (new_xp, new_level, guild_id, user_id) + ) + + if new_level > old_level: + # Level up bildirimi + kanal_id = settings['mesaj_kanal_id'] if settings and settings.get('mesaj_kanal_id') else 0 + kanal = member.guild.get_channel(kanal_id) if kanal_id else None + + embed = discord.Embed( + title="🎉 Seviye Atlandı!", + description=f"Tebrikler {member.mention}! **Seviye {new_level}**'e ulaştın! 🎊", + color=CYBER_GOLD, + timestamp=datetime.now() + ) + + if kanal: + try: + await kanal.send(embed=embed) + except: + pass + + # Rol ödülü var mı kontrol et + odul = await Database.fetch_one( + "SELECT rol_id FROM seviye_oduller WHERE guild_id = ? AND seviye = ?", + (guild_id, new_level) + ) + if odul: + rol = member.guild.get_role(odul['rol_id']) + if rol: + try: + await member.add_roles(rol, reason=f"Seviye {new_level} ödülü") + except: + pass + + @commands.Cog.listener() + async def on_message(self, message: discord.Message): + if message.author.bot or not message.guild: + return + if not await Database.modul_aktif_mi(message.guild.id, "Level"): + return + + settings = await Database.fetch_one("SELECT aktif FROM seviye_ayarlar WHERE guild_id = ?", (message.guild.id,)) + if settings and settings.get('aktif') == 0: + return + + # Cooldown kontrolü + import time + now = time.time() + last = self._xp_cooldown.get(message.author.id, 0) + if now - last < self.COOLDOWN_SECONDS: + return + self._xp_cooldown[message.author.id] = now + + xp_gain = random.randint(15, 25) + await self._add_xp(message.author, xp_gain) + + @commands.hybrid_command(name="seviye", description="Senin veya başka bir üyenin seviye kartını gösterir.") + @app_commands.describe(uye="Seviyesine bakılacak üye") + async def seviye(self, ctx: commands.Context, uye: discord.Member = None): + if ctx.interaction: + await ctx.defer() + target = uye or ctx.author + + row = await Database.fetch_one( + "SELECT * FROM seviye WHERE guild_id = ? AND user_id = ?", + (ctx.guild.id, target.id) + ) + if not row: + await ctx.send(f"ℹ️ {target.mention} henüz seviye verisine sahip değil.") + return + + settings = await Database.fetch_one("SELECT * FROM seviye_ayarlar WHERE guild_id = ?", (ctx.guild.id,)) + difficulty = settings['zorluk'] if settings and settings.get('zorluk') else 100 + + level = row['seviye'] + xp = row['xp'] + xp_for_current = self._xp_for_level(level, difficulty) + xp_for_next = self._xp_for_level(level + 1, difficulty) + xp_needed = xp_for_next - xp_for_current + xp_progress = xp - xp_for_current + progress_pct = int((xp_progress / max(xp_needed, 1)) * 100) + + bar_size = 20 + filled = int((xp_progress / max(xp_needed, 1)) * bar_size) + bar = "█" * filled + "░" * (bar_size - filled) + + # Sıralama hesapla + all_users = await Database.fetch_all( + "SELECT user_id, xp FROM seviye WHERE guild_id = ? ORDER BY xp DESC", + (ctx.guild.id,) + ) + rank = 1 + for i, u in enumerate(all_users, 1): + if u['user_id'] == target.id: + rank = i + break + + embed = discord.Embed( + title=f"🏆 Seviye Kartı - {target.display_name}", + color=CYBER_GOLD, + timestamp=datetime.now() + ) + embed.set_thumbnail(url=target.display_avatar.url) + embed.add_field(name="Seviye", value=f"**{level}**", inline=True) + embed.add_field(name="Toplam XP", value=f"**{xp}**", inline=True) + embed.add_field(name="Sıralama", value=f"**#{rank}**", inline=True) + embed.add_field( + name=f"İlerleme ({progress_pct}%)", + value=f"`{bar}`\n{xp_progress}/{xp_needed} XP", + inline=False + ) + await ctx.send(embed=embed) + + @commands.hybrid_command(name="seviye-ayarla", description="Seviye sistemi ayarlarını yapar.") + @commands.has_permissions(administrator=True) + @app_commands.describe( + mesaj_kanali="Seviye atladığında bildirim gönderilecek kanal", + zorluk="Zorluk çarpanı (örn: 100 - ne kadar yüksek o kadar yavaş ilerleme)", + aktif="Seviye sistemi aktif mi? (1=Açık, 0=Kapalı)" + ) + async def seviye_ayarla(self, ctx: commands.Context, mesaj_kanali: discord.TextChannel = None, zorluk: int = 100, aktif: int = 1): + if ctx.interaction: + await ctx.defer() + kanal_id = mesaj_kanali.id if mesaj_kanali else 0 + await Database.execute( + """INSERT OR REPLACE INTO seviye_ayarlar (guild_id, mesaj_kanal_id, aktif, zorluk) + VALUES (?, ?, ?, ?)""", + (ctx.guild.id, kanal_id, aktif, zorluk) + ) + await ctx.send(f"✅ Seviye sistemi ayarlandı! Aktif: **{'Açık' if aktif else 'Kapalı'}**, Zorluk: **{zorluk}**") + + @commands.hybrid_command(name="seviye-ödül-ekle", description="Belirli bir seviyeye ulaşınca verilecek rol ödülü ekler.") + @commands.has_permissions(administrator=True) + @app_commands.describe(seviye="Hedef seviye", rol="Verilecek rol") + async def seviye_odul_ekle(self, ctx: commands.Context, seviye: int, rol: discord.Role): + if ctx.interaction: + await ctx.defer() + await Database.execute( + "INSERT OR REPLACE INTO seviye_oduller (guild_id, seviye, rol_id) VALUES (?, ?, ?)", + (ctx.guild.id, seviye, rol.id) + ) + await ctx.send(f"✅ Seviye **{seviye}**'e ulaşanlara **{rol.mention}** rolü verilecek.") + + @commands.hybrid_command(name="seviye-ödül-sil", description="Bir seviye ödülünü siler.") + @commands.has_permissions(administrator=True) + @app_commands.describe(seviye="Ödül silinecek seviye") + async def seviye_odul_sil(self, ctx: commands.Context, seviye: int): + if ctx.interaction: + await ctx.defer() + await Database.execute( + "DELETE FROM seviye_oduller WHERE guild_id = ? AND seviye = ?", + (ctx.guild.id, seviye) + ) + await ctx.send(f"✅ Seviye **{seviye}** ödülü silindi.") + + @commands.hybrid_command(name="seviye-sıralama", description="Sunucudaki en yüksek seviyeli üyeleri gösterir.") + async def seviye_siralama(self, ctx: commands.Context): + if ctx.interaction: + await ctx.defer() + records = await Database.fetch_all( + "SELECT user_id, xp, seviye FROM seviye WHERE guild_id = ? ORDER BY xp DESC LIMIT 10", + (ctx.guild.id,) + ) + if not records: + await ctx.send("ℹ️ Henüz seviye verisi bulunmamaktadır.") + return + + embed = discord.Embed(title="🏆 Seviye Sıralaması", color=CYBER_GOLD, timestamp=datetime.now()) + desc = "" + for i, r in enumerate(records, 1): + member = ctx.guild.get_member(r['user_id']) + name = member.mention if member else f"ID: {r['user_id']}" + emoji = "🥇" if i == 1 else "🥈" if i == 2 else "🥉" if i == 3 else f"#{i}" + desc += f"{emoji} {name} — Seviye **{r['seviye']}** ({r['xp']} XP)\n" + embed.description = desc + await ctx.send(embed=embed) + + @commands.hybrid_command(name="seviye-sıfırla", description="Bir üyenin seviyesini sıfırlar.") + @commands.has_permissions(administrator=True) + @app_commands.describe(uye="Seviyesi sıfırlanacak üye") + async def seviye_sifirla(self, ctx: commands.Context, uye: discord.Member): + if ctx.interaction: + await ctx.defer() + await Database.execute( + "UPDATE seviye SET xp = 0, seviye = 0 WHERE guild_id = ? AND user_id = ?", + (ctx.guild.id, uye.id) + ) + await ctx.send(f"✅ {uye.mention} kullanıcısının seviyesi sıfırlandı.") + + +async def setup(bot): + await bot.add_cog(Level(bot)) diff --git a/cogs/logs.py b/cogs/logs.py new file mode 100644 index 0000000000000000000000000000000000000000..69dd39fbf2e668101c64d8d9dc98837554b1b8a8 --- /dev/null +++ b/cogs/logs.py @@ -0,0 +1,446 @@ +import discord +from discord.ext import commands +from discord import app_commands +from datetime import datetime + +CYBER_BLUE = discord.Color.from_rgb(52, 152, 219) +CYBER_RED = discord.Color.from_rgb(231, 76, 60) +CYBER_ORANGE = discord.Color.from_rgb(230, 126, 34) +CYBER_GREEN = discord.Color.from_rgb(46, 204, 113) + +class Logs(commands.Cog): + def __init__(self, bot): + self.bot = bot + + async def _send_log(self, guild: discord.Guild, embed: discord.Embed): + """Yardımcı fonksiyon: Belirtilen log kanalına embed gönderir""" + log_channel_id = await Database.log_kanali_getir(guild.id) + if not log_channel_id: + return + channel = guild.get_channel(log_channel_id) + if channel: + try: + await channel.send(embed=embed) + except: + pass + + @commands.hybrid_command(name="log-kanal-ayarla", description="Sunucu olaylarının kaydedileceği log kanalını belirler.") + @commands.has_permissions(administrator=True) + @app_commands.describe(kanal="Logların gönderileceği metin kanalı (Boş bırakılırsa log sistemi kapatılır)") + async def log_kanal_ayarla(self, ctx: commands.Context, kanal: discord.TextChannel = None): + if ctx.interaction: + await ctx.defer() + + kanal_id = kanal.id if kanal else 0 + try: + await Database.log_kanali_ayarla(ctx.guild.id, kanal_id) + if kanal: + await ctx.send(f"✅ Olay log kanalı {kanal.mention} olarak ayarlandı.") + else: + await ctx.send("✅ Olay loglama sistemi devre dışı bırakıldı.") + except Exception as e: + await ctx.send(f"❌ Log kanalı ayarlanırken bir veritabanı hatası oluştu: {e}", ephemeral=True) + + # 1. --- SİLİNEN MESAJ LOGLARI --- + @commands.Cog.listener() + async def on_message_delete(self, message: discord.Message): + if message.author.bot or not message.guild: + return + if not await Database.modul_aktif_mi(message.guild.id, "Logs"): + return + + embed = discord.Embed( + title="🗑️ Mesaj Silindi", + description=f"**Kanal:** {message.channel.mention}\n" + f"**Yazar:** {message.author.mention} (`{message.author.id}`)", + color=CYBER_RED, + timestamp=datetime.now() + ) + if message.content: + embed.add_field(name="Mesaj İçeriği", value=message.content[:1024], inline=False) + else: + embed.add_field(name="Mesaj İçeriği", value="*İçerik boş veya sadece görsel/ek içeriyordu.*", inline=False) + + if message.attachments: + image_url = None + for a in message.attachments: + is_image = (a.content_type and a.content_type.startswith("image")) or a.filename.lower().endswith(('.png', '.jpg', '.jpeg', '.gif', '.webp')) + if is_image: + image_url = a.url + break + if image_url: + embed.set_image(url=image_url) + + attachments_str = ", ".join([f"[{a.filename}]({a.url})" for a in message.attachments]) + embed.add_field(name="Ekler (Dosyalar)", value=attachments_str[:1024], inline=False) + + await self._send_log(message.guild, embed) + + # 2. --- DÜZENLENEN MESAJ LOGLARI --- + @commands.Cog.listener() + async def on_message_edit(self, before: discord.Message, after: discord.Message): + if after.author.bot or not after.guild: + return + if not await Database.modul_aktif_mi(after.guild.id, "Logs"): + return + if before.content == after.content: + return + + embed = discord.Embed( + title="📝 Mesaj Düzenlendi", + description=f"**Kanal:** {after.channel.mention}\n" + f"**Yazar:** {after.author.mention} (`{after.author.id}`)\n" + f"**Mesaja Git:** [Tıkla]({after.jump_url})", + color=CYBER_ORANGE, + timestamp=datetime.now() + ) + embed.add_field(name="Eski İçerik", value=(before.content or "*Boş*")[:1024], inline=False) + embed.add_field(name="Yeni İçerik", value=(after.content or "*Boş*")[:1024], inline=False) + + await self._send_log(after.guild, embed) + + # 3. --- SES KANALI LOGLARI --- + @commands.Cog.listener() + async def on_voice_state_update(self, member: discord.Member, before: discord.VoiceState, after: discord.VoiceState): + if member.bot or not member.guild: + return + if not await Database.modul_aktif_mi(member.guild.id, "Logs"): + return + + embed = discord.Embed( + description=f"**Kullanıcı:** {member.mention} (`{member.id}`)", + timestamp=datetime.now() + ) + embed.set_author(name=member.display_name, icon_url=member.display_avatar.url) + + if before.channel is None and after.channel is not None: + embed.title = "🔊 Ses Kanalına Katıldı" + embed.description += f"\n**Katıldığı Kanal:** {after.channel.name} (`{after.channel.id}`)" + embed.color = CYBER_GREEN + await self._send_log(member.guild, embed) + + elif before.channel is not None and after.channel is None: + embed.title = "🔇 Ses Kanalından Ayrıldı" + embed.description += f"\n**Ayrıldığı Kanal:** {before.channel.name} (`{before.channel.id}`)" + embed.color = CYBER_RED + await self._send_log(member.guild, embed) + + elif before.channel is not None and after.channel is not None and before.channel.id != after.channel.id: + embed.title = "🔄 Ses Kanalı Değiştirdi" + embed.description += f"\n**Eski Kanal:** {before.channel.name}\n**Yeni Kanal:** {after.channel.name}" + embed.color = CYBER_BLUE + await self._send_log(member.guild, embed) + + # 4. --- ÜYE GÜNCELLEME / NICK / ROL LOGLARI --- + @commands.Cog.listener() + async def on_member_update(self, before: discord.Member, after: discord.Member): + if before.bot or not after.guild: + return + if not await Database.modul_aktif_mi(after.guild.id, "Logs"): + return + + embed = discord.Embed( + description=f"**Kullanıcı:** {after.mention} (`{after.id}`)", + timestamp=datetime.now() + ) + embed.set_author(name=after.display_name, icon_url=after.display_avatar.url) + + if before.roles != after.roles: + added_roles = [r for r in after.roles if r not in before.roles] + removed_roles = [r for r in before.roles if r not in after.roles] + + if added_roles or removed_roles: + embed.title = "🛡️ Rolleri Güncellendi" + embed.color = CYBER_BLUE + if added_roles: + embed.add_field(name="Verilen Roller", value=", ".join([r.mention for r in added_roles]), inline=False) + if removed_roles: + embed.add_field(name="Alınan Roller", value=", ".join([r.mention for r in removed_roles]), inline=False) + await self._send_log(after.guild, embed) + return + + if before.nick != after.nick: + embed.title = "👤 Sunucu Takma Adı Değiştirildi" + embed.color = CYBER_ORANGE + embed.add_field(name="Eski Ad", value=before.nick or before.name, inline=True) + embed.add_field(name="Yeni Ad", value=after.nick or after.name, inline=True) + await self._send_log(after.guild, embed) + + # 5. --- KANAL OLUŞTURMA / SİLME LOGLARI --- + @commands.Cog.listener() + async def on_guild_channel_create(self, channel: discord.abc.GuildChannel): + if not await Database.modul_aktif_mi(channel.guild.id, "Logs"): + return + embed = discord.Embed( + title="🆕 Yeni Kanal Oluşturuldu", + description=f"**Kanal Adı:** {channel.name}\n**Tür:** `{str(channel.type)}`\n**ID:** `{channel.id}`", + color=CYBER_GREEN, + timestamp=datetime.now() + ) + await self._send_log(channel.guild, embed) + + @commands.Cog.listener() + async def on_guild_channel_delete(self, channel: discord.abc.GuildChannel): + if not await Database.modul_aktif_mi(channel.guild.id, "Logs"): + return + embed = discord.Embed( + title="🗑️ Kanal Silindi", + description=f"**Kanal Adı:** {channel.name}\n**Tür:** `{str(channel.type)}`\n**ID:** `{channel.id}`", + color=CYBER_RED, + timestamp=datetime.now() + ) + await self._send_log(channel.guild, embed) + + # 6. --- ROL OLUŞTURMA / SİLME LOGLARI --- + @commands.Cog.listener() + async def on_guild_role_create(self, role: discord.Role): + if not await Database.modul_aktif_mi(role.guild.id, "Logs"): + return + embed = discord.Embed( + title="🆕 Yeni Rol Oluşturuldu", + description=f"**Rol Adı:** {role.mention}\n**ID:** `{role.id}`", + color=CYBER_GREEN, + timestamp=datetime.now() + ) + await self._send_log(role.guild, embed) + + @commands.Cog.listener() + async def on_guild_role_delete(self, role: discord.Role): + if not await Database.modul_aktif_mi(role.guild.id, "Logs"): + return + embed = discord.Embed( + title="🗑️ Rol Silindi", + description=f"**Rol Adı:** {role.name}\n**ID:** `{role.id}`", + color=CYBER_RED, + timestamp=datetime.now() + ) + await self._send_log(role.guild, embed) + + # 7. --- YASAKLAMA / YASAK KALDIRMA LOGLARI --- + @commands.Cog.listener() + async def on_member_ban(self, guild: discord.Guild, user: discord.User): + if not await Database.modul_aktif_mi(guild.id, "Logs"): + return + embed = discord.Embed( + title="🔨 Üye Yasaklandı (Ban)", + description=f"**Kullanıcı:** {user.mention} (`{user.id}`)\n**Hesap Açılış Tarihi:** {user.created_at.strftime('%d.%m.%Y')}", + color=CYBER_RED, + timestamp=datetime.now() + ) + embed.set_thumbnail(url=user.display_avatar.url) + await self._send_log(guild, embed) + + @commands.Cog.listener() + async def on_member_unban(self, guild: discord.Guild, user: discord.User): + if not await Database.modul_aktif_mi(guild.id, "Logs"): + return + embed = discord.Embed( + title="🔓 Üye Yasağı Kaldırıldı (Unban)", + description=f"**Kullanıcı:** {user.mention} (`{user.id}`)", + color=CYBER_GREEN, + timestamp=datetime.now() + ) + embed.set_thumbnail(url=user.display_avatar.url) + await self._send_log(guild, embed) + + # 8. --- ÜYE KATILIM & AYRILMA LOGLARI --- + @commands.Cog.listener() + async def on_member_join(self, member: discord.Member): + if not await Database.modul_aktif_mi(member.guild.id, "Logs"): + return + embed = discord.Embed( + title="📥 Üye Sunucuya Katıldı", + description=f"**Kullanıcı:** {member.mention} (`{member.id}`)\n" + f"**Hesap Açılış Tarihi:** {member.created_at.strftime('%d.%m.%Y %H:%M:%S')}\n" + f"**Toplam Üye:** `{member.guild.member_count}`", + color=CYBER_GREEN, + timestamp=datetime.now() + ) + embed.set_thumbnail(url=member.display_avatar.url) + await self._send_log(member.guild, embed) + + @commands.Cog.listener() + async def on_member_remove(self, member: discord.Member): + if not await Database.modul_aktif_mi(member.guild.id, "Logs"): + return + embed = discord.Embed( + title="📤 Üye Sunucudan Ayrıldı", + description=f"**Kullanıcı:** {member.mention} (`{member.id}`)\n" + f"**Toplam Üye:** `{member.guild.member_count}`", + color=CYBER_RED, + timestamp=datetime.now() + ) + embed.set_thumbnail(url=member.display_avatar.url) + await self._send_log(member.guild, embed) + + # 9. --- DAVET OLUŞTURMA & SİLME LOGLARI --- + @commands.Cog.listener() + async def on_invite_create(self, invite: discord.Invite): + if not await Database.modul_aktif_mi(invite.guild.id, "Logs"): + return + embed = discord.Embed( + title="🎫 Davet Bağlantısı Oluşturuldu", + description=f"**Oluşturan:** {invite.inviter.mention if invite.inviter else 'Bilinmiyor'} (`{invite.inviter.id if invite.inviter else ''}`)\n" + f"**Kod:** `{invite.code}`\n" + f"**Kanal:** {invite.channel.mention if invite.channel else 'Belirsiz'}\n" + f"**Maksimum Kullanım:** `{invite.max_uses if invite.max_uses else 'Sınırsız'}`\n" + f"**Süre:** `{invite.max_age if invite.max_age else 'Süresiz'}` saniye", + color=CYBER_GREEN, + timestamp=datetime.now() + ) + await self._send_log(invite.guild, embed) + + @commands.Cog.listener() + async def on_invite_delete(self, invite: discord.Invite): + if not await Database.modul_aktif_mi(invite.guild.id, "Logs"): + return + embed = discord.Embed( + title="🗑️ Davet Bağlantısı Silindi", + description=f"**Kod:** `{invite.code}`\n**Kanal:** {invite.channel.mention if invite.channel else 'Belirsiz'}", + color=CYBER_RED, + timestamp=datetime.now() + ) + await self._send_log(invite.guild, embed) + + # 10. --- SUNUCU (GUILD) GÜNCELLEME LOGLARI --- + @commands.Cog.listener() + async def on_guild_update(self, before: discord.Guild, after: discord.Guild): + if not await Database.modul_aktif_mi(after.id, "Logs"): + return + embed = discord.Embed( + title="⚙️ Sunucu Ayarları Güncellendi", + color=CYBER_ORANGE, + timestamp=datetime.now() + ) + + changes = [] + if before.name != after.name: + changes.append(f"**Sunucu Adı Değişti:** `{before.name}` ➡️ `{after.name}`") + if before.afk_channel != after.afk_channel: + changes.append(f"**AFK Kanalı Değişti:** `{before.afk_channel.name if before.afk_channel else 'Yok'}` ➡️ `{after.afk_channel.name if after.afk_channel else 'Yok'}`") + if before.verification_level != after.verification_level: + changes.append(f"**Doğrulama Seviyesi:** `{before.verification_level}` ➡️ `{after.verification_level}`") + if before.premium_tier != after.premium_tier: + changes.append(f"**Takviye Seviyesi:** `Seviye {before.premium_tier}` ➡️ `Seviye {after.premium_tier}` (Toplam Takviye: `{after.premium_subscription_count}`)") + + if not changes: + return + + embed.description = "\n".join(changes) + await self._send_log(after, embed) + + # 11. --- ROL GÜNCELLEME LOGLARI --- + @commands.Cog.listener() + async def on_guild_role_update(self, before: discord.Role, after: discord.Role): + if not await Database.modul_aktif_mi(after.guild.id, "Logs"): + return + embed = discord.Embed( + title="🛡️ Rol Ayarları Güncellendi", + description=f"**Rol:** {after.mention} (`{after.id}`)", + color=CYBER_ORANGE, + timestamp=datetime.now() + ) + + changes = [] + if before.name != after.name: + changes.append(f"**Adı Değişti:** `{before.name}` ➡️ `{after.name}`") + if before.color != after.color: + changes.append(f"**Rengi Değişti:** `{before.color}` ➡️ `{after.color}`") + if before.permissions != after.permissions: + changes.append(f"**Yetkileri Değiştirildi.** (Farklılıklar tespit edildi)") + + if not changes: + return + + embed.description += "\n\n" + "\n".join(changes) + await self._send_log(after.guild, embed) + + # 12. --- KANAL AYARI GÜNCELLEME LOGLARI --- + @commands.Cog.listener() + async def on_guild_channel_update(self, before: discord.abc.GuildChannel, after: discord.abc.GuildChannel): + if not await Database.modul_aktif_mi(after.guild.id, "Logs"): + return + embed = discord.Embed( + title="⚙️ Kanal Ayarları Güncellendi", + description=f"**Kanal:** {after.mention if hasattr(after, 'mention') else after.name} (`{after.id}`)", + color=CYBER_ORANGE, + timestamp=datetime.now() + ) + + changes = [] + if before.name != after.name: + changes.append(f"**Adı Değişti:** `{before.name}` ➡️ `{after.name}`") + if hasattr(before, 'topic') and hasattr(after, 'topic') and before.topic != after.topic: + changes.append(f"**Konusu Değişti:**\n*Eski:* {before.topic or 'Yok'}\n*Yeni:* {after.topic or 'Yok'}") + if hasattr(before, 'slowmode_delay') and hasattr(after, 'slowmode_delay') and before.slowmode_delay != after.slowmode_delay: + changes.append(f"**Yavaş Mod Değişti:** `{before.slowmode_delay}s` ➡️ `{after.slowmode_delay}s`") + + if not changes: + return + + embed.description += "\n\n" + "\n".join(changes) + await self._send_log(after.guild, embed) + + # 13. --- EMOJI & STICKER GÜNCELLEME LOGLARI --- + @commands.Cog.listener() + async def on_guild_emojis_update(self, guild: discord.Guild, before: list[discord.Emoji], after: list[discord.Emoji]): + if not await Database.modul_aktif_mi(guild.id, "Logs"): + return + embed = discord.Embed( + title="🎨 Emojiler Güncellendi", + color=CYBER_ORANGE, + timestamp=datetime.now() + ) + + if len(before) < len(after): + added = [e for e in after if e not in before][0] + embed.description = f"➕ **Yeni Emoji Eklendi:** {added} (`{added.name}` | ID: `{added.id}`)" + embed.color = CYBER_GREEN + elif len(before) > len(after): + removed = [e for e in before if e not in after][0] + embed.description = f"➖ **Emoji Silindi:** `{removed.name}` (ID: `{removed.id}`)" + embed.color = CYBER_RED + else: + # Emoji ismi değişmiş olabilir + for e_before in before: + for e_after in after: + if e_before.id == e_after.id and e_before.name != e_after.name: + embed.description = f"✏️ **Emoji Adı Değiştirildi:** `{e_before.name}` ➡️ `{e_after.name}` {e_after}" + break + if not embed.description: + return + + await self._send_log(guild, embed) + + @commands.Cog.listener() + async def on_guild_stickers_update(self, guild: discord.Guild, before: list[discord.GuildSticker], after: list[discord.GuildSticker]): + if not await Database.modul_aktif_mi(guild.id, "Logs"): + return + embed = discord.Embed( + title="🖼️ Çıkartmalar (Stickers) Güncellendi", + color=CYBER_ORANGE, + timestamp=datetime.now() + ) + + if len(before) < len(after): + added = [s for s in after if s not in before][0] + embed.description = f"➕ **Yeni Çıkartma Eklendi:** `{added.name}` (ID: `{added.id}`)" + embed.color = CYBER_GREEN + elif len(before) > len(after): + removed = [s for s in before if s not in after][0] + embed.description = f"➖ **Çıkartma Silindi:** `{removed.name}` (ID: `{removed.id}`)" + embed.color = CYBER_RED + else: + for s_before in before: + for s_after in after: + if s_before.id == s_after.id and s_before.name != s_after.name: + embed.description = f"✏️ **Çıkartma Adı Değiştirildi:** `{s_before.name}` ➡️ `{s_after.name}`" + break + if not embed.description: + return + + await self._send_log(guild, embed) + +from database import Database +async def setup(bot): + await bot.add_cog(Logs(bot)) diff --git a/cogs/moderation.py b/cogs/moderation.py new file mode 100644 index 0000000000000000000000000000000000000000..5d4b4e3e6f5f3dfe87b33bc8f3efdc8197a90c35 --- /dev/null +++ b/cogs/moderation.py @@ -0,0 +1,558 @@ +import discord +from discord.ext import commands +from discord import app_commands +import re +from datetime import datetime, timedelta +from database import Database + +# Premium HSL Renk Paleti (Embed'ler için) +CYBER_GREEN = discord.Color.from_rgb(46, 204, 113) +CYBER_RED = discord.Color.from_rgb(231, 76, 60) +CYBER_ORANGE = discord.Color.from_rgb(230, 126, 34) +CYBER_BLUE = discord.Color.from_rgb(52, 152, 219) +CYBER_DARK = discord.Color.from_rgb(44, 62, 80) + +def parse_duration(duration_str: str) -> timedelta: + """Süre dizesini ayrıştırır (örn. '10m', '2h', '1d', '30s')""" + match = re.match(r"^(\d+)([smhd])$", duration_str.lower().strip()) + if not match: + raise ValueError("Geçersiz süre biçimi. Örnek: 10m, 2h, 1d, 30s") + value, unit = int(match.group(1)), match.group(2) + if unit == 's': + return timedelta(seconds=value) + elif unit == 'm': + return timedelta(minutes=value) + elif unit == 'h': + return timedelta(hours=value) + elif unit == 'd': + return timedelta(days=value) + return timedelta(0) + +async def safe_send(ctx, *args, **kwargs): + """Slash ve prefix komutlarında güvenli mesaj gönderir.""" + try: + if ctx.interaction and not ctx.interaction.response.is_done(): + return await ctx.send(*args, **kwargs) + elif ctx.interaction and ctx.interaction.response.is_done(): + return await ctx.interaction.followup.send(*args, **kwargs) + else: + return await ctx.send(*args, **kwargs) + except Exception: + try: + return await ctx.send(*args, **kwargs) + except Exception: + pass + +async def send_dm_notification(member: discord.Member, action_name: str, guild_name: str, reason: str, duration: str = None, warn_count: int = None): + """Kullanıcıya ceza veya işlem ile ilgili DM kutusuna şık bir bildirim gönderir.""" + colors = { + "Yasaklama (Ban)": discord.Color.from_rgb(231, 76, 60), + "Sunucudan Atılma (Kick)": discord.Color.from_rgb(230, 126, 34), + "Susturulma (Mute)": discord.Color.from_rgb(230, 126, 34), + "Susturma Kaldırıldı": discord.Color.from_rgb(46, 204, 113), + "Uyarı": discord.Color.from_rgb(241, 196, 15), + } + + color = colors.get(action_name, discord.Color.from_rgb(52, 152, 219)) + + embed = discord.Embed( + title=f"⚖️ {guild_name} Sunucusunda İşlem Yapıldı", + description=f"Merhaba **{member.name}**, sunucumuzda kurallara uyumu sağlamak amacıyla hesabınıza yönelik bir işlem uygulanmıştır.", + color=color, + timestamp=datetime.now() + ) + + embed.add_field(name="📌 Uygulanan İşlem", value=f"**{action_name}**", inline=True) + if duration: + embed.add_field(name="⏳ Süre", value=f"`{duration}`", inline=True) + if warn_count is not None: + embed.add_field(name="⚠️ Toplam Uyarı Sayısı", value=f"`{warn_count}`", inline=True) + + embed.add_field(name="📝 Sebep", value=f"{reason}", inline=False) + embed.set_footer(text="ArazAI Siber Asayiş ve Güvenlik Sistemi") + + try: + await member.send(embed=embed) + return True + except Exception: + return False + +class Moderation(commands.Cog): + def __init__(self, bot): + self.bot = bot + + async def cog_load(self): + await Database.execute(""" + CREATE TABLE IF NOT EXISTS ip_bans ( + guild_id INTEGER, + user_id INTEGER, + user_name TEXT, + moderator_id INTEGER, + reason TEXT, + timestamp TEXT, + PRIMARY KEY (guild_id, user_id) + ) + """) + + @commands.hybrid_command(name="ip-ban", description="Belirtilen üyeyi IP adresi dahil sunucudan tamamen yasaklar.") + @commands.has_permissions(ban_members=True) + @app_commands.describe(member="IP Yasaklanacak üye", reason="Yasaklanma sebebi") + async def ipban(self, ctx: commands.Context, member: discord.Member, *, reason: str = "Belirtilmedi"): + if ctx.interaction: + await ctx.defer() + if member.top_role >= ctx.author.top_role and ctx.author.id != ctx.guild.owner_id: + await safe_send(ctx, "❌ Sizinle aynı veya daha yüksek roldeki birini yasaklayamazsınız!", ephemeral=True) + return + if member.top_role >= ctx.guild.me.top_role: + await safe_send(ctx, "❌ Bu üyeyi yasaklamak için yetkim yetersiz.", ephemeral=True) + return + + try: + await send_dm_notification(member, "Yasaklama (Ban)", ctx.guild.name, f"[IP BAN] {reason}") + # Discord bans natively ban by IP + await member.ban(delete_message_days=7, reason=f"IP BAN | Yetkili: {ctx.author} | Sebep: {reason}") + + # Save to Database + now_str = datetime.now().isoformat() + await Database.execute( + "INSERT OR REPLACE INTO ip_bans (guild_id, user_id, user_name, moderator_id, reason, timestamp) VALUES (?, ?, ?, ?, ?, ?)", + (ctx.guild.id, member.id, str(member), ctx.author.id, reason, now_str) + ) + + embed = discord.Embed( + title="🛡️ IP Yasaklaması Uygulandı", + description=f"**Kullanıcı:** {member.mention} ({member.id})\n**Yetkili:** {ctx.author.mention}\n**Sebep:** {reason}\n*Not: Kullanıcının hesabı ve IP adresi Discord sunucusundan tamamen engellenmiştir.*", + color=CYBER_RED, + timestamp=datetime.now() + ) + embed.set_thumbnail(url=member.display_avatar.url) + await safe_send(ctx, embed=embed) + except Exception as e: + await safe_send(ctx, f"❌ Üye IP yasaklanırken bir hata oluştu: {e}", ephemeral=True) + + @commands.hybrid_command(name="ban", description="Belirtilen üyeyi sunucudan yasaklar.") + @commands.has_permissions(ban_members=True) + @app_commands.describe(member="Yasaklanacak üye", reason="Yasaklanma sebebi") + async def ban(self, ctx: commands.Context, member: discord.Member, *, reason: str = "Belirtilmedi"): + if ctx.interaction: + await ctx.defer() + if member.top_role >= ctx.author.top_role and ctx.author.id != ctx.guild.owner_id: + await safe_send(ctx, "❌ Sizinle aynı veya daha yüksek roldeki birini yasaklayamazsınız!", ephemeral=True) + return + if member.top_role >= ctx.guild.me.top_role: + await safe_send(ctx, "❌ Bu üyeyi yasaklamak için yetkim yetersiz (Rolüm üyenin rolünden düşük).", ephemeral=True) + return + + try: + await send_dm_notification(member, "Yasaklama (Ban)", ctx.guild.name, reason) + await member.ban(reason=f"Yetkili: {ctx.author} | Sebep: {reason}") + embed = discord.Embed( + title="🔨 Üye Yasaklandı", + description=f"**Kullanıcı:** {member.mention} ({member.id})\n**Yetkili:** {ctx.author.mention}\n**Sebep:** {reason}", + color=CYBER_RED, + timestamp=datetime.now() + ) + embed.set_thumbnail(url=member.display_avatar.url) + await safe_send(ctx, embed=embed) + except Exception as e: + await safe_send(ctx, f"❌ Üye yasaklanırken bir hata oluştu: {e}", ephemeral=True) + + @commands.hybrid_command(name="kick", description="Belirtilen üyeyi sunucudan atar.") + @commands.has_permissions(kick_members=True) + @app_commands.describe(member="Atılacak üye", reason="Atılma sebebi") + async def kick(self, ctx: commands.Context, member: discord.Member, *, reason: str = "Belirtilmedi"): + if ctx.interaction: + await ctx.defer() + if member.top_role >= ctx.author.top_role and ctx.author.id != ctx.guild.owner_id: + await safe_send(ctx, "❌ Sizinle aynı veya daha yüksek roldeki birini sunucudan atamazsınız!", ephemeral=True) + return + if member.top_role >= ctx.guild.me.top_role: + await safe_send(ctx, "❌ Bu üyeyi atmak için yetkim yetersiz.", ephemeral=True) + return + + try: + await send_dm_notification(member, "Sunucudan Atılma (Kick)", ctx.guild.name, reason) + await member.kick(reason=f"Yetkili: {ctx.author} | Sebep: {reason}") + embed = discord.Embed( + title="👢 Üye Atıldı", + description=f"**Kullanıcı:** {member.mention} ({member.id})\n**Yetkili:** {ctx.author.mention}\n**Sebep:** {reason}", + color=CYBER_ORANGE, + timestamp=datetime.now() + ) + embed.set_thumbnail(url=member.display_avatar.url) + await safe_send(ctx, embed=embed) + except Exception as e: + await safe_send(ctx, f"❌ Üye atılırken bir hata oluştu: {e}", ephemeral=True) + + @commands.hybrid_command(name="mute", description="Belirtilen üyeyi susturur (Timeout).") + @commands.has_permissions(moderate_members=True) + @app_commands.describe(member="Susturulacak üye", duration="Süre (örn: 10m, 1h, 1d)", reason="Susturulma sebebi") + async def mute(self, ctx: commands.Context, member: discord.Member, duration: str, *, reason: str = "Belirtilmedi"): + if ctx.interaction: + await ctx.defer() + if member.top_role >= ctx.author.top_role and ctx.author.id != ctx.guild.owner_id: + await safe_send(ctx, "❌ Sizinle aynı veya daha yüksek roldeki birini susturamazsınız!", ephemeral=True) + return + if member.top_role >= ctx.guild.me.top_role: + await safe_send(ctx, "❌ Bu üyeyi susturmak için yetkim yetersiz.", ephemeral=True) + return + + try: + delta = parse_duration(duration) + if delta.total_seconds() <= 0: + await safe_send(ctx, "❌ Geçersiz süre girdiniz.", ephemeral=True) + return + + # Discord'un timeout API'si UTC datetime kabul eder + unmute_time_utc = discord.utils.utcnow() + delta + unmute_time_local = datetime.now() + delta + + await send_dm_notification(member, "Susturulma (Mute)", ctx.guild.name, reason, duration=duration) + await member.timeout(unmute_time_utc, reason=f"Yetkili: {ctx.author} | Sebep: {reason}") + await Database.mute_ekle(member.id, ctx.guild.id, unmute_time_local) + + embed = discord.Embed( + title="🔇 Üye Susturuldu", + description=f"**Kullanıcı:** {member.mention}\n**Yetkili:** {ctx.author.mention}\n**Süre:** {duration}\n**Açılma Tarihi:** \n**Sebep:** {reason}", + color=CYBER_ORANGE, + timestamp=datetime.now() + ) + embed.set_thumbnail(url=member.display_avatar.url) + await safe_send(ctx, embed=embed) + except ValueError as ve: + await safe_send(ctx, f"❌ Hata: {ve}", ephemeral=True) + except Exception as e: + await safe_send(ctx, f"❌ Susturma işlemi başarısız oldu: {e}", ephemeral=True) + + @commands.hybrid_command(name="unmute", description="Belirtilen üyenin susturmasını kaldırır.") + @commands.has_permissions(moderate_members=True) + @app_commands.describe(member="Susturması kaldırılacak üye") + async def unmute(self, ctx: commands.Context, member: discord.Member): + if ctx.interaction: + await ctx.defer() + try: + await member.timeout(None, reason=f"Susturma kaldırıldı: {ctx.author}") + await Database.mute_kaldir(member.id, ctx.guild.id) + await send_dm_notification(member, "Susturma Kaldırıldı", ctx.guild.name, f"Yetkili: {ctx.author}") + + embed = discord.Embed( + title="🔊 Susturma Kaldırıldı", + description=f"**Kullanıcı:** {member.mention}\n**Yetkili:** {ctx.author.mention}", + color=CYBER_GREEN, + timestamp=datetime.now() + ) + embed.set_thumbnail(url=member.display_avatar.url) + await safe_send(ctx, embed=embed) + except Exception as e: + await safe_send(ctx, f"❌ Susturma kaldırılamadı: {e}", ephemeral=True) + + @commands.hybrid_command(name="warn", description="Belirtilen üyeye uyarı ekler.") + @commands.has_permissions(manage_messages=True) + @app_commands.describe(member="Uyarılacak üye", reason="Uyarı sebebi") + async def warn(self, ctx: commands.Context, member: discord.Member, *, reason: str): + if ctx.interaction: + await ctx.defer() + if member.top_role >= ctx.author.top_role and ctx.author.id != ctx.guild.owner_id: + await safe_send(ctx, "❌ Sizinle aynı veya daha yüksek roldeki birini uyaramazsınız!", ephemeral=True) + return + + try: + warn_id = await Database.uyari_ekle(member.id, ctx.guild.id, ctx.author.id, reason) + warns = await Database.uyarilari_getir(member.id, ctx.guild.id) + warn_count = len(warns) + + await send_dm_notification(member, "Uyarı", ctx.guild.name, reason, warn_count=warn_count) + + embed = discord.Embed( + title="⚠️ Üye Uyarıldı", + description=f"**Kullanıcı:** {member.mention}\n**Yetkili:** {ctx.author.mention}\n**Uyarı ID:** `{warn_id}`\n**Sebep:** {reason}\n**Toplam Uyarı Sayısı:** `{warn_count}`", + color=CYBER_ORANGE, + timestamp=datetime.now() + ) + embed.set_thumbnail(url=member.display_avatar.url) + await safe_send(ctx, embed=embed) + + # Otomatik cezalar + if warn_count >= 5: + await ctx.channel.send(f"⚠️ {member.mention} 5 uyarı sınırına ulaştığı için sunucudan **atılıyor**.") + await send_dm_notification(member, "Sunucudan Atılma (Kick)", ctx.guild.name, "5 Uyarı sınırına ulaşıldı (Otomatik).") + await member.kick(reason="5 Uyarı sınırına ulaşıldı.") + elif warn_count >= 3: + await ctx.channel.send(f"⚠️ {member.mention} 3 uyarı sınırına ulaştığı için **1 saat** susturuluyor.") + delta = timedelta(hours=1) + unmute_time_utc = discord.utils.utcnow() + delta + await send_dm_notification(member, "Susturulma (Mute)", ctx.guild.name, "3 Uyarı sınırına ulaşıldı (Otomatik 1 Saat Mute).", duration="1 Saat") + await member.timeout(unmute_time_utc, reason="3 Uyarı sınırına ulaşıldı (Otomatik 1 Saat Mute).") + await Database.mute_ekle(member.id, ctx.guild.id, datetime.now() + delta) + + except Exception as e: + await safe_send(ctx, f"❌ Uyarı eklenemedi: {e}", ephemeral=True) + + @commands.hybrid_command(name="warns", description="Belirtilen üyenin tüm uyarılarını listeler.") + @commands.has_permissions(manage_messages=True) + @app_commands.describe(member="Uyarıları görüntülenecek üye") + async def warns(self, ctx: commands.Context, member: discord.Member): + if ctx.interaction: + await ctx.defer() + try: + records = await Database.uyarilari_getir(member.id, ctx.guild.id) + if not records: + await safe_send(ctx, f"ℹ️ {member.mention} kullanıcısının geçmiş uyarısı bulunmamaktadır.") + return + + embed = discord.Embed( + title=f"📋 Uyarı Geçmişi - {member.display_name}", + description=f"{member.mention} kullanıcısının toplam `{len(records)}` uyarısı var.", + color=CYBER_BLUE, + timestamp=datetime.now() + ) + embed.set_thumbnail(url=member.display_avatar.url) + + for record in records[:10]: + moderator = ctx.guild.get_member(record['moderator_id']) + mod_name = moderator.mention if moderator else f"ID: {record['moderator_id']}" + tarih_dt = datetime.fromisoformat(record['tarih']) + tarih_stamp = f"" + embed.add_field( + name=f"Uyarı ID: #{record['id']}", + value=f"**Yetkili:** {mod_name}\n**Tarih:** {tarih_stamp}\n**Sebep:** {record['sebep']}", + inline=False + ) + + await safe_send(ctx, embed=embed) + except Exception as e: + await safe_send(ctx, f"❌ Uyarılar listelenemedi: {e}", ephemeral=True) + + @commands.hybrid_command(name="warn-sil", description="ID bazlı olarak tek bir uyarıyı siler.") + @commands.has_permissions(manage_messages=True) + @app_commands.describe(warn_id="Silinecek uyarının sayısal benzersiz ID'si") + async def warn_sil(self, ctx: commands.Context, warn_id: int): + if ctx.interaction: + await ctx.defer() + try: + rows_deleted = await Database.uyari_sil(warn_id, ctx.guild.id) + if rows_deleted > 0: + await safe_send(ctx, f"✅ #{warn_id} ID'li uyarı başarıyla silindi.") + else: + await safe_send(ctx, "❌ Belirtilen ID'ye sahip bir uyarı bu sunucuda bulunamadı.", ephemeral=True) + except Exception as e: + await safe_send(ctx, f"❌ Uyarı silinirken hata: {e}", ephemeral=True) + + @commands.hybrid_command(name="warn-temizle", description="Bir üyenin tüm uyarılarını temizler.") + @commands.has_permissions(administrator=True) + @app_commands.describe(member="Uyarıları sıfırlanacak üye") + async def warn_temizle(self, ctx: commands.Context, member: discord.Member): + if ctx.interaction: + await ctx.defer() + try: + rows_deleted = await Database.uyarilari_temizle(member.id, ctx.guild.id) + await safe_send(ctx, f"✅ {member.mention} kullanıcısının tüm uyarıları sıfırlandı. Toplam `{rows_deleted}` uyarı silindi.") + except Exception as e: + await safe_send(ctx, f"❌ Uyarılar silinirken hata: {e}", ephemeral=True) + + # --- EKSTRA GÜVENLİK VE MODERASYON KOMUTLARI --- + + @commands.hybrid_command(name="unban", description="Belirtilen kullanıcının yasaklamasını kaldırır.") + @commands.has_permissions(ban_members=True) + @app_commands.describe(user_id="Yasağı kaldırılacak kullanıcının ID'si (Sayısal)") + async def unban(self, ctx: commands.Context, user_id: str): + if ctx.interaction: + await ctx.defer() + try: + uid = int(user_id) + user = await self.bot.fetch_user(uid) + if not user: + await safe_send(ctx, "❌ Belirtilen ID'ye sahip bir Discord kullanıcısı bulunamadı.", ephemeral=True) + return + + await ctx.guild.unban(user, reason=f"Yasağı kaldıran yetkili: {ctx.author}") + embed = discord.Embed( + title="🔓 Yasak Kaldırıldı", + description=f"**Kullanıcı:** {user.name} ({user.id})\n**Yetkili:** {ctx.author.mention}", + color=CYBER_GREEN, + timestamp=datetime.now() + ) + await safe_send(ctx, embed=embed) + except ValueError: + await safe_send(ctx, "❌ Geçersiz kullanıcı ID'si. Sadece rakamlardan oluşmalıdır.", ephemeral=True) + except discord.NotFound: + await safe_send(ctx, "❌ Bu kullanıcı zaten sunucuda yasaklı değil.", ephemeral=True) + except Exception as e: + await safe_send(ctx, f"❌ İşlem başarısız: {e}", ephemeral=True) + + @commands.hybrid_command(name="clear", description="Belirtilen miktarda mesajı kanaldan temizler.") + @commands.has_permissions(manage_messages=True) + @app_commands.describe(amount="Temizlenecek mesaj sayısı (1-100)") + async def clear(self, ctx: commands.Context, amount: int): + if amount < 1 or amount > 100: + await safe_send(ctx, "❌ Lütfen 1 ile 100 arasında bir sayı giriniz.", ephemeral=True) + return + + try: + if ctx.interaction: + await ctx.defer(ephemeral=True) + + deleted = await ctx.channel.purge(limit=amount) + + if ctx.interaction: + await ctx.interaction.followup.send(content=f"✅ Başarıyla `{len(deleted)}` mesaj temizlendi.", ephemeral=True) + else: + await ctx.send(f"✅ Başarıyla `{len(deleted)}` mesaj temizlendi.", delete_after=5) + except Exception as e: + await safe_send(ctx, f"❌ Temizleme işlemi başarısız oldu: {e}", ephemeral=True) + + @commands.hybrid_command(name="slowmode", description="Kanal için yavaş mod süresini ayarlar.") + @commands.has_permissions(manage_channels=True) + @app_commands.describe(seconds="Saniye cinsinden yavaş mod süresi (0 kapatır)") + async def slowmode(self, ctx: commands.Context, seconds: int): + if ctx.interaction: + await ctx.defer() + if seconds < 0 or seconds > 21600: + await safe_send(ctx, "❌ Yavaş mod süresi 0 ile 21600 saniye (6 saat) arasında olmalıdır.", ephemeral=True) + return + + try: + await ctx.channel.edit(slowmode_delay=seconds) + if seconds == 0: + await safe_send(ctx, "✅ Yavaş mod bu kanal için kapatıldı.") + else: + await safe_send(ctx, f"✅ Yavaş mod bu kanal için `{seconds}` saniye olarak ayarlandı.") + except Exception as e: + await safe_send(ctx, f"❌ İşlem başarısız: {e}", ephemeral=True) + + @commands.hybrid_command(name="lock", description="Kanalı @everyone rolüne karşı mesaj gönderimine kapatır.") + @commands.has_permissions(manage_channels=True) + async def lock(self, ctx: commands.Context): + if ctx.interaction: + await ctx.defer() + try: + overwrite = ctx.channel.overwrites_for(ctx.guild.default_role) + overwrite.send_messages = False + await ctx.channel.set_permissions(ctx.guild.default_role, overwrite=overwrite, reason=f"Kanal kilitlendi: {ctx.author}") + await safe_send(ctx, "🔒 Kanal başarıyla yazıma **kilitlendi**.") + except Exception as e: + await safe_send(ctx, f"❌ Kanal kilitlenemedi: {e}", ephemeral=True) + + @commands.hybrid_command(name="unlock", description="Kilitlenmiş kanalın kilidini açar.") + @commands.has_permissions(manage_channels=True) + async def unlock(self, ctx: commands.Context): + if ctx.interaction: + await ctx.defer() + try: + overwrite = ctx.channel.overwrites_for(ctx.guild.default_role) + overwrite.send_messages = None + await ctx.channel.set_permissions(ctx.guild.default_role, overwrite=overwrite, reason=f"Kanal kilidi açıldı: {ctx.author}") + await safe_send(ctx, "🔓 Kanal kilidi başarıyla **açıldı**.") + except Exception as e: + await safe_send(ctx, f"❌ Kanal kilidi açılamadı: {e}", ephemeral=True) + + @commands.hybrid_command(name="nuke", description="Kanalı siler ve aynı izinlerle yeniden oluşturur (Tüm mesajlar silinir).") + @commands.has_permissions(administrator=True) + async def nuke(self, ctx: commands.Context): + if ctx.interaction: + await ctx.defer(ephemeral=True) + try: + channel = ctx.channel + new_channel = await channel.clone(reason=f"Kanal nuke edildi: {ctx.author}") + await new_channel.edit(position=channel.position) + await channel.delete(reason="Nuke işlemi.") + + embed = discord.Embed( + title="💥 Kanal Yeniden Oluşturuldu (Nuke)", + description=f"Bu kanal {ctx.author.mention} tarafından nuke edildi ve tüm mesajlar temizlendi.", + color=CYBER_GREEN, + timestamp=datetime.now() + ) + await new_channel.send(embed=embed) + except Exception as e: + try: + await ctx.send(f"❌ Nuke işlemi başarısız: {e}", ephemeral=True) + except Exception: + pass + + @commands.hybrid_command(name="banlist", description="Sunucudaki tüm yasaklı kullanıcıları listeler.") + @commands.has_permissions(ban_members=True) + async def banlist(self, ctx: commands.Context): + if ctx.interaction: + await ctx.defer(ephemeral=True) + try: + bans = [entry async for entry in ctx.guild.bans(limit=100)] + if not bans: + await safe_send(ctx, "ℹ️ Sunucuda yasaklı kullanıcı bulunmamaktadır.") + return + + embed = discord.Embed( + title=f"🔨 Sunucu Ban Listesi ({len(bans)} kişi)", + color=CYBER_RED, + timestamp=datetime.now() + ) + + ban_text = "" + for entry in bans[:30]: + ban_text += f"• `{entry.user.name}` ({entry.user.id})\n" + + embed.description = ban_text + if len(bans) > 30: + embed.set_footer(text=f"Toplam {len(bans)} yasaklı kullanıcıdan ilk 30 gösteriliyor.") + + await safe_send(ctx, embed=embed) + except Exception as e: + await safe_send(ctx, f"❌ Ban listesi alınamadı: {e}", ephemeral=True) + + @commands.hybrid_command(name="userinfo", description="Belirtilen üyenin profil bilgilerini gösterir.") + @app_commands.describe(member="Profil bilgisi görüntülenecek üye") + async def userinfo(self, ctx: commands.Context, member: discord.Member = None): + if ctx.interaction: + await ctx.defer() + member = member or ctx.author + try: + created_at = f"" + joined_at = f"" if member.joined_at else "Bilinmiyor" + roles = [r.mention for r in member.roles if r != ctx.guild.default_role] + + embed = discord.Embed( + title=f"👤 Kullanıcı Bilgisi: {member.display_name}", + color=member.color if member.color.value else CYBER_BLUE, + timestamp=datetime.now() + ) + embed.set_thumbnail(url=member.display_avatar.url) + embed.add_field(name="Kullanıcı Adı", value=f"`{member.name}`", inline=True) + embed.add_field(name="ID", value=f"`{member.id}`", inline=True) + embed.add_field(name="Bot mu?", value="✅ Evet" if member.bot else "❌ Hayır", inline=True) + embed.add_field(name="Hesap Oluşturulma", value=created_at, inline=False) + embed.add_field(name="Sunucuya Katılma", value=joined_at, inline=False) + embed.add_field(name=f"Roller ({len(roles)})", value=" ".join(roles[:10]) if roles else "Rol yok", inline=False) + + await safe_send(ctx, embed=embed) + except Exception as e: + await safe_send(ctx, f"❌ Kullanıcı bilgisi alınamadı: {e}", ephemeral=True) + + @commands.hybrid_command(name="serverinfo", description="Sunucu bilgilerini gösterir.") + async def serverinfo(self, ctx: commands.Context): + if ctx.interaction: + await ctx.defer() + try: + guild = ctx.guild + created_at = f"" + bans_count = guild.me.guild_permissions.ban_members + embed = discord.Embed( + title=f"🏠 Sunucu Bilgisi: {guild.name}", + color=CYBER_BLUE, + timestamp=datetime.now() + ) + if guild.icon: + embed.set_thumbnail(url=guild.icon.url) + embed.add_field(name="Sahip", value=guild.owner.mention if guild.owner else "Bilinmiyor", inline=True) + embed.add_field(name="ID", value=f"`{guild.id}`", inline=True) + embed.add_field(name="Üye Sayısı", value=f"`{guild.member_count}`", inline=True) + embed.add_field(name="Kanal Sayısı", value=f"`{len(guild.channels)}`", inline=True) + embed.add_field(name="Rol Sayısı", value=f"`{len(guild.roles)}`", inline=True) + embed.add_field(name="Boost Seviyesi", value=f"`{guild.premium_tier}`", inline=True) + embed.add_field(name="Oluşturulma Tarihi", value=created_at, inline=False) + + await safe_send(ctx, embed=embed) + except Exception as e: + await safe_send(ctx, f"❌ Sunucu bilgisi alınamadı: {e}", ephemeral=True) + +async def setup(bot): + await bot.add_cog(Moderation(bot)) diff --git a/cogs/partnership.py b/cogs/partnership.py new file mode 100644 index 0000000000000000000000000000000000000000..41e33842a86b824dce75b7bbfd052156d5b3b590 --- /dev/null +++ b/cogs/partnership.py @@ -0,0 +1,492 @@ +import discord +from discord.ext import commands +from discord import app_commands +import re +from datetime import datetime, timedelta +from database import Database + +# Premium Renkler +CYBER_GREEN = discord.Color.from_rgb(46, 204, 113) +CYBER_BLUE = discord.Color.from_rgb(52, 152, 219) +CYBER_RED = discord.Color.from_rgb(231, 76, 60) +CYBER_DARK = discord.Color.from_rgb(44, 62, 80) + +def extract_invite_link(text: str) -> str: + """Metin içindeki ilk discord davet linkini bulur""" + matches = re.findall(r"(discord\.(?:gg|com/invite)/[a-zA-Z0-9\-]+)", text, re.IGNORECASE) + if matches: + return "https://" + matches[0] + return "" + +async def is_partner_blacklisted(bot, guild_id: int, text: str) -> bool: + """Metin içindeki davet linki veya sunucu ID'sinin yasaklı partner listesinde olup olmadığını kontrol eder""" + try: + records = await Database.fetch_all("SELECT identifier FROM yasakli_partnerler WHERE guild_id = ?", (guild_id,)) + if not records: + return False + + blacklist = [r['identifier'].strip().lower() for r in records] + + # 1. Metindeki davet linklerini kontrol et ve Guild ID'lerini sorgula + links = re.findall(r"(discord\.(?:gg|com/invite)/[a-zA-Z0-9\-]+)", text, re.IGNORECASE) + + for link in links: + link_clean = link.lower() + if link_clean in blacklist or f"https://{link_clean}" in blacklist: + return True + + # Guild ID'sini çözmeye çalış + try: + invite = await bot.fetch_invite("https://" + link) + if invite and invite.guild: + guild_id_str = str(invite.guild.id) + if guild_id_str in blacklist: + return True + except: + pass + + # 2. Metnin doğrudan içinde kara listeye alınmış sunucu ID'si veya davet linki geçiyor mu? + text_lower = text.lower() + for item in blacklist: + if item in text_lower: + return True + except Exception as e: + print(f"Partner kara liste kontrol hatası: {e}") + + return False + +class PartnerYaziGirisModal(discord.ui.Modal, title="Partnerlik Tanıtım Metni"): + partner_text_input = discord.ui.TextInput( + label="Sunucu Tanıtım Yazınız", + style=discord.TextStyle.long, + placeholder="Sunucunuzun tanıtım metnini ve sınırsız davet linkini buraya yapıştırın...", + required=True, + max_length=1500 + ) + + def __init__(self, own_partner_text, yetkili_kanal_id, yetkili_rol_id, bot): + super().__init__() + self.own_partner_text = own_partner_text + self.yetkili_kanal_id = yetkili_kanal_id + self.yetkili_rol_id = yetkili_rol_id + self.bot = bot + + async def on_submit(self, interaction: discord.Interaction): + # Kara liste kontrolü + is_blacklisted = await is_partner_blacklisted(self.bot, interaction.guild.id, self.partner_text_input.value) + if is_blacklisted: + await interaction.response.send_message( + content="❌ Yasaklı listedesiniz! Sunucunuz veya davet linkiniz bu sunucuda kara listeye alınmıştır.", + ephemeral=True + ) + return + + # 1. Sunucunun kendi partner yazısını başvuran üyenin DM kutusuna gönder + try: + if self.own_partner_text: + await interaction.user.send( + content=f"🤝 **{interaction.guild.name}** sunucusu partnerlik tanıtım metni:\n\n{self.own_partner_text}" + ) + dm_status = "Sunucumuzun tanıtım metni DM kutunuza başarıyla gönderildi!" + else: + dm_status = "⚠️ Sunucumuzun tanıtım metni ayarlanmamış." + except Exception: + dm_status = "⚠️ DM kutunuz kapalı olduğu için kendi partner tanıtım metnimizi gönderemedik. Lütfen DM ayarlarınızı açın." + + # 2. Yetkili kanalına onay talebi gönder + yetkili_kanal = interaction.guild.get_channel(self.yetkili_kanal_id) + if yetkili_kanal: + embed = discord.Embed( + title="📥 Yeni Partnerlik Talebi", + description=f"**Gönderen:** {interaction.user.mention} ({interaction.user.id})\n\n" + f"**Girdiği Tanıtım Metni:**\n{self.partner_text_input.value}", + color=CYBER_BLUE, + timestamp=datetime.now() + ) + embed.set_thumbnail(url=interaction.user.display_avatar.url) + + # Onay / Red Butonları (Persistent id'ler ile) + view = discord.ui.View(timeout=None) + view.add_item(discord.ui.Button( + label="Onayla", + style=discord.ButtonStyle.green, + custom_id=f"partner_approve_{interaction.user.id}" + )) + view.add_item(discord.ui.Button( + label="Reddet", + style=discord.ButtonStyle.red, + custom_id=f"partner_reject_{interaction.user.id}" + )) + + role_mention = f"<@&{self.yetkili_rol_id}>" if self.yetkili_rol_id else "" + await yetkili_kanal.send(content=role_mention, embed=embed, view=view) + + await interaction.response.send_message( + content=f"✅ Partnerlik talebiniz alındı ve yetkililere iletildi. {dm_status}", + ephemeral=True + ) + else: + await interaction.response.send_message( + content="❌ Partner yetkili kanalı bulunamadı. Lütfen kuruluma başvurun.", + ephemeral=True + ) + +class PartnerTetikleyiciView(discord.ui.View): + def __init__(self): + super().__init__(timeout=None) + + @discord.ui.button(label="Partnerlik İçin Basınız", style=discord.ButtonStyle.green, custom_id="partner_tetikleyici_btn") + async def click_partner(self, interaction: discord.Interaction, button: discord.ui.Button): + cfg = await Database.partner_ayarlarini_getir(interaction.guild.id) + if not cfg or not cfg.get("partner_kanal_id"): + await interaction.response.send_message("❌ Sunucuda partnerlik sistemi kurulumu tamamlanmamış.", ephemeral=True) + return + + modal = PartnerYaziGirisModal( + cfg["partner_text"], + cfg["yetkili_kanal_id"], + cfg["yetkili_rol_id"], + interaction.client + ) + await interaction.response.send_modal(modal) + +class Partnership(commands.Cog): + def __init__(self, bot): + self.bot = bot + + async def cog_load(self): + # Yasaklı partnerler tablosunu oluşturalım + await Database.execute(""" + CREATE TABLE IF NOT EXISTS yasakli_partnerler ( + guild_id INTEGER, + identifier TEXT, + PRIMARY KEY (guild_id, identifier) + ) + """) + + @commands.hybrid_command(name="partner-kurulum", description="Ortaklık (partnerlik) sistemi ayarlarını yapar.") + @commands.has_permissions(administrator=True) + @app_commands.describe( + partner_kanal="Onaylanan partner metinlerinin paylaşılacağı ana partner kanalı", + partner_text="Başka sunuculara gönderilecek kendi partner tanıtım yazınız", + yetkili_rol="Partner işlemlerini onaylamaya yetkili rol", + yetkili_kanal="Gelen partner taleplerinin onay butonlarıyla birlikte düşeceği kanal" + ) + async def partner_kurulum( + self, ctx: commands.Context, + partner_kanal: discord.TextChannel, + partner_text: str, + yetkili_rol: discord.Role, + yetkili_kanal: discord.TextChannel + ): + if ctx.interaction: + await ctx.defer() + await Database.partner_ayarla( + ctx.guild.id, + partner_kanal.id, + partner_text, + yetkili_rol.id, + yetkili_kanal.id + ) + + embed = discord.Embed( + title="🤝 Partnerlik Sistemi Kuruldu", + description="Sunucu partnerlik sistemi başarıyla yapılandırıldı.", + color=CYBER_GREEN, + timestamp=datetime.now() + ) + embed.add_field(name="Partner Kanalı", value=partner_kanal.mention, inline=True) + embed.add_field(name="Yetkili Kanalı", value=yetkili_kanal.mention, inline=True) + embed.add_field(name="Yetkili Rolü", value=yetkili_rol.mention, inline=True) + embed.add_field(name="Tanıtım Metnimiz", value=f"```{partner_text[:100]}...```" if len(partner_text) > 100 else f"```{partner_text}```", inline=False) + + await ctx.send(embed=embed) + + @commands.hybrid_command(name="partner-sıralama", description="Sunucuda yapılan partnerliklerin istatistik sıralamasını gösterir.") + @app_commands.describe(sure="Sıralama dönemi (Günlük, Haftalık, Aylık)") + @app_commands.choices(sure=[ + app_commands.Choice(name="Günlük", value="günlük"), + app_commands.Choice(name="Haftalık", value="haftalık"), + app_commands.Choice(name="Aylık", value="aylık") + ]) + async def partner_siralamasi(self, ctx: commands.Context, sure: str): + if ctx.interaction: + await ctx.defer() + now = datetime.now() + if sure == "günlük": + start_date = now - timedelta(days=1) + title_text = "Günlük Partner Sıralaması" + elif sure == "haftalık": + start_date = now - timedelta(days=7) + title_text = "Haftalık Partner Sıralaması" + else: # aylık + start_date = now - timedelta(days=30) + title_text = "Aylık Partner Sıralaması" + + records = await Database.partner_siralamasi_getir(ctx.guild.id, start_date) + if not records: + await ctx.send(f"ℹ️ Belirtilen dönemde ({sure}) sunucuda yapılmış herhangi bir partnerlik kaydı bulunmamaktadır.") + return + + embed = discord.Embed( + title=f"📈 {title_text}", + description=f"Son {sure} dönemi içerisindeki onaylanan partnerlikler ve manuel paylaşımlar baz alınmıştır.", + color=CYBER_BLUE, + timestamp=datetime.now() + ) + + leaderboard_str = "" + for index, record in enumerate(records[:15], start=1): + user = ctx.guild.get_member(record['user_id']) + user_mention = user.mention if user else f"Kullanıcı ID: {record['user_id']}" + + emoji = "🥇" if index == 1 else "🥈" if index == 2 else "🥉" if index == 3 else f"#{index}" + leaderboard_str += f"{emoji} - {user_mention} : `{record['count']}` partnerlik\n" + + embed.description = f"{embed.description}\n\n{leaderboard_str}" + await ctx.send(embed=embed) + + # --- KELİME & ETİKET ALGILAMA (BUTON ATMA) --- + + @commands.Cog.listener() + async def on_message(self, message: discord.Message): + if message.author.bot or not message.guild: + return + if not await Database.modul_aktif_mi(message.guild.id, "Partnership"): + return + + # Ayarları kontrol et + cfg = await Database.partner_ayarlarini_getir(message.guild.id) + if not cfg or not cfg.get("partner_kanal_id"): + return + + # Yetkili rol etiketlendi mi? + role_id = cfg.get("yetkili_rol_id", 0) + role_mention = f"<@&{role_id}>" + + content_lower = message.content.lower() + has_role_mention = role_mention in message.content + has_keywords = any(kw in content_lower for kw in ["partner", "partnerlik", "dm"]) + + # Eşleşme durumunda buton gönder + if has_role_mention or has_keywords: + embed = discord.Embed( + title="🤝 Partnerlik İşlemleri", + description="Bu sunucuyla partnerlik (ortaklık) kurmak için aşağıdaki butona tıklayıp tanıtım metninizi girin. " + "Yazıyı gönderdiğinizde sunucumuzun tanıtım metni de DM kutunuza gönderilecektir.", + color=CYBER_BLUE + ) + # Persistent View tetikle + view = PartnerTetikleyiciView() + await message.reply(embed=embed, view=view, mention_author=False) + + # --- MANUEL TAKİP KATMANI --- + # Eğer mesaj ana partner kanalına atıldıysa ve atan kişi bot değilse + if message.channel.id == cfg.get("partner_kanal_id", 0): + # Kara liste kontrolü + is_blacklisted = await is_partner_blacklisted(self.bot, message.guild.id, message.content) + if is_blacklisted: + try: + await message.delete() + except: + pass + try: + await message.author.send( + content=f"❌ **{message.guild.name}** sunucusunun partner kanalına gönderdiğiniz metin engellendi. Yasaklı listedesiniz!" + ) + except: + pass + return + + invite_link = extract_invite_link(message.content) + if invite_link: + # Veritabanına manuel paylaşım olarak kaydet + await Database.partner_kaydet( + message.guild.id, + message.author.id, + None, # Yetkili onayı yok (manuel) + invite_link, + message.content, + "manual_post" + ) + # Küçük bir onay emojisi bırakabiliriz + try: + await message.add_reaction("📥") + except: + pass + + # --- KÜRESEL ETKİLEŞİM DİNLEYİCİ (ONAY / RED İÇİN) --- + + @commands.Cog.listener() + async def on_interaction(self, interaction: discord.Interaction): + # Sadece buton tıklamaları + if interaction.type != discord.InteractionType.component: + return + if not await Database.modul_aktif_mi(interaction.guild_id, "Partnership"): + try: + await interaction.response.send_message("❌ Partnerlik modülü şu an devre dışı.", ephemeral=True) + except: + pass + return + + custom_id = interaction.data.get("custom_id", "") + if not custom_id.startswith("partner_approve_") and not custom_id.startswith("partner_reject_"): + return + + cfg = await Database.partner_ayarlarini_getir(interaction.guild.id) + if not cfg: + return + + # Yetkili rol kontrolü + yetkili_rol_id = cfg.get("yetkili_rol_id", 0) + has_role = False + if yetkili_rol_id: + role = interaction.guild.get_role(yetkili_rol_id) + if role and role in interaction.user.roles: + has_role = True + + if interaction.user.guild_permissions.administrator: + has_role = True + + if not has_role: + await interaction.response.send_message( + content="❌ Bu talebi onaylama/reddetme yetkiniz bulunmamaktadır.", + ephemeral=True + ) + return + + # İşlemi ertele + await interaction.response.defer() + + # Butonları devre dışı bırak ve mesajı güncelle + message = interaction.message + embed = message.embeds[0] if message.embeds else None + if not embed: + return + + # Başvuran kullanıcıyı ve metni al + is_approve = custom_id.startswith("partner_approve_") + applicant_id = int(custom_id.split("_")[-1]) + + # Embed'den girdiği tanıtım metnini ayrıştır + desc = embed.description + text_indicator = "**Girdiği Tanıtım Metni:**\n" + partner_text = "" + if text_indicator in desc: + partner_text = desc.split(text_indicator)[1].strip() + + if is_approve: + # Onaylandı embed güncelleme + embed.color = CYBER_GREEN + embed.title = "✅ Partnerlik Talebi Onaylandı" + embed.add_field(name="Onaylayan Yetkili", value=f"{interaction.user.mention}", inline=False) + await message.edit(embed=embed, view=None) + + # Davet linkini çek + invite_link = extract_invite_link(partner_text) + + # Veritabanına kaydet + await Database.partner_kaydet( + interaction.guild.id, + applicant_id, + interaction.user.id, # Onaylayan yetkili + invite_link, + partner_text, + "bot_approval" + ) + + # Ana partner kanalına gönder + partner_kanal_id = cfg.get("partner_kanal_id", 0) + partner_kanal = interaction.guild.get_channel(partner_kanal_id) + if partner_kanal: + applicant = interaction.guild.get_member(applicant_id) + applicant_name = applicant.mention if applicant else f"ID: {applicant_id}" + + post_embed = discord.Embed( + title="🤝 Yeni Ortaklık (Partnerlik)", + description=partner_text, + color=CYBER_GREEN, + timestamp=datetime.now() + ) + post_embed.set_footer(text=f"Ortak: {applicant.name if applicant else 'Bilinmeyen'} | Onaylayan Yetkili: {interaction.user.name}") + if applicant: + post_embed.set_thumbnail(url=applicant.display_avatar.url) + + await partner_kanal.send(embed=post_embed) + else: + # Reddedildi embed güncelleme + embed.color = CYBER_RED + embed.title = "❌ Partnerlik Talebi Reddedildi" + embed.add_field(name="Reddeden Yetkili", value=f"{interaction.user.mention}", inline=False) + await message.edit(embed=embed, view=None) + + # --- YASAKLI PARTNER YÖNETİM KOMUTLARI --- + + @commands.hybrid_command(name="yasaklı-partner-ekle", description="Partner kara listesine davet linki veya sunucu ID'si ekler.") + @commands.has_permissions(administrator=True) + @app_commands.describe(deger="Yasaklanacak davet linki (örn: discord.gg/abc) veya sunucu ID'si") + async def yasakli_partner_ekle(self, ctx: commands.Context, deger: str): + if ctx.interaction: + await ctx.defer() + deger_clean = deger.strip().lower() + # Eğer davet linkiyse ve başında https:// varsa temizle + deger_clean = deger_clean.replace("https://", "").replace("http://", "") + + if not deger_clean: + await ctx.send("❌ Geçersiz değer.", ephemeral=True) + return + + try: + await Database.execute( + "INSERT OR IGNORE INTO yasakli_partnerler (guild_id, identifier) VALUES (?, ?)", + (ctx.guild.id, deger_clean) + ) + await ctx.send(f"✅ `{deger_clean}` değeri partner kara listesine başarıyla eklendi.") + except Exception as e: + await ctx.send(f"❌ Kara listeye eklenirken veritabanı hatası: {e}", ephemeral=True) + + @commands.hybrid_command(name="yasaklı-partner-çıkar", description="Partner kara listesinden davet linki veya sunucu ID'sini siler.") + @commands.has_permissions(administrator=True) + @app_commands.describe(deger="Kara listeden çıkarılacak davet linki veya sunucu ID'si") + async def yasakli_partner_cikar(self, ctx: commands.Context, deger: str): + if ctx.interaction: + await ctx.defer() + deger_clean = deger.strip().lower() + deger_clean = deger_clean.replace("https://", "").replace("http://", "") + + try: + rows = await Database.execute( + "DELETE FROM yasakli_partnerler WHERE guild_id = ? AND identifier = ?", + (ctx.guild.id, deger_clean) + ) + if rows > 0: + await ctx.send(f"✅ `{deger_clean}` değeri partner kara listesinden çıkarıldı.") + else: + await ctx.send(f"❌ `{deger_clean}` değeri partner kara listesinde bulunamadı.", ephemeral=True) + except Exception as e: + await ctx.send(f"❌ Kara listeden çıkarılırken veritabanı hatası: {e}", ephemeral=True) + + @commands.hybrid_command(name="yasaklı-partner-liste", description="Partner kara listesindeki tüm değerleri gösterir.") + @commands.has_permissions(manage_messages=True) + async def yasakli_partner_liste(self, ctx: commands.Context): + if ctx.interaction: + await ctx.defer() + try: + records = await Database.fetch_all("SELECT identifier FROM yasakli_partnerler WHERE guild_id = ?", (ctx.guild.id,)) + if not records: + await ctx.send("ℹ️ Partner kara listesinde herhangi bir değer bulunmamaktadır.") + return + + list_str = "\n".join([f"- `{r['identifier']}`" for r in records]) + embed = discord.Embed( + title="🛡️ Partner Kara Listesi", + description=list_str, + color=CYBER_BLUE, + timestamp=datetime.now() + ) + await ctx.send(embed=embed) + except Exception as e: + await ctx.send(f"❌ Listeleme sırasında veritabanı hatası: {e}", ephemeral=True) + +async def setup(bot): + await bot.add_cog(Partnership(bot)) diff --git a/cogs/poll.py b/cogs/poll.py new file mode 100644 index 0000000000000000000000000000000000000000..1e3c5f3de81031c6d3e88c9f9e68baa514c43423 --- /dev/null +++ b/cogs/poll.py @@ -0,0 +1,112 @@ +import discord +from discord.ext import commands +from discord import app_commands +from datetime import datetime, timedelta +import asyncio +from database import Database + +CYBER_BLUE = discord.Color.from_rgb(52, 152, 219) +CYBER_GOLD = discord.Color.from_rgb(241, 196, 15) + +POLL_EMOJIS_MULTI = ["1️⃣", "2️⃣", "3️⃣", "4️⃣", "5️⃣", "6️⃣", "7️⃣", "8️⃣", "9️⃣", "🔟"] +POLL_EMOJIS_YESNO = ["👍", "👎"] + + +class Poll(commands.Cog): + def __init__(self, bot): + self.bot = bot + + @commands.hybrid_command(name="anket", description="Bir anket oluşturur.") + @app_commands.describe( + soru="Anket sorusu", + secenekler="Seçenekler | ile ayrılmış (en fazla 10). Boş = Evet/Hayır anketi", + sure_dakika="Anket süresi dakika cinsinden (boş = süresiz)" + ) + async def anket( + self, ctx: commands.Context, + soru: str, + secenekler: str = "", + sure_dakika: int = 0 + ): + if ctx.interaction: + await ctx.defer() + + if secenekler: + opts = [s.strip() for s in secenekler.split(" | ") if s.strip()] + if len(opts) < 2: + await ctx.send("❌ En az 2 seçenek gereklidir. (Örn: A | B | C)", ephemeral=True) + return + if len(opts) > 10: + opts = opts[:10] + + multi_emojis = POLL_EMOJIS_MULTI[:len(opts)] + + embed = discord.Embed( + title="📊 Anket", + description=f"**{soru}**", + color=CYBER_BLUE, + timestamp=datetime.now() + ) + embed.set_author(name=ctx.author.display_name, icon_url=ctx.author.display_avatar.url) + for i, opt in enumerate(opts): + embed.add_field(name=f"{multi_emojis[i]} Seçenek {i+1}", value=opt, inline=False) + + if sure_dakika > 0: + bitis = datetime.now() + timedelta(minutes=sure_dakika) + embed.set_footer(text=f"Anket süresi: | Başlatan: {ctx.author}") + else: + embed.set_footer(text=f"Başlatan: {ctx.author}") + else: + embed = discord.Embed( + title="📊 Anket", + description=f"**{soru}**", + color=CYBER_BLUE, + timestamp=datetime.now() + ) + embed.set_author(name=ctx.author.display_name, icon_url=ctx.author.display_avatar.url) + embed.set_footer(text=f"👍 Evet | 👎 Hayır | Başlatan: {ctx.author}") + + poll_msg = await ctx.send(embed=embed) + + if secenekler: + for emoji in multi_emojis: + await poll_msg.add_reaction(emoji) + else: + await poll_msg.add_reaction("👍") + await poll_msg.add_reaction("👎") + + if sure_dakika > 0: + await asyncio.sleep(sure_dakika * 60) + try: + final_msg = await ctx.channel.fetch_message(poll_msg.id) + results_desc = f"**Anket Süresi Doldu!**\n\n{soru}\n\n" + if secenekler: + for i, opt in enumerate(opts): + reaction_count = 0 + for r in final_msg.reactions: + if str(r.emoji) == multi_emojis[i]: + reaction_count = r.count - 1 + results_desc += f"{multi_emojis[i]} **{opt}**: `{reaction_count} oy`\n" + else: + yes_count = 0 + no_count = 0 + for r in final_msg.reactions: + if str(r.emoji) == "👍": + yes_count = r.count - 1 + elif str(r.emoji) == "👎": + no_count = r.count - 1 + results_desc += f"👍 **Evet**: `{yes_count} oy`\n👎 **Hayır**: `{no_count} oy`\n" + + result_embed = discord.Embed( + title="📊 Anket Sonuçları", + description=results_desc, + color=CYBER_GOLD, + timestamp=datetime.now() + ) + await ctx.channel.send(embed=result_embed) + except: + pass + + +async def setup(bot): + await bot.add_cog(Poll(bot)) diff --git a/cogs/qr.py b/cogs/qr.py new file mode 100644 index 0000000000000000000000000000000000000000..93ba5635ff57a29952adb20ed9cd64fe4ace9a3f --- /dev/null +++ b/cogs/qr.py @@ -0,0 +1,35 @@ +import discord +from discord.ext import commands +from discord import app_commands +from datetime import datetime +import aiohttp + +CYBER_BLUE = discord.Color.from_rgb(52, 152, 219) + + +class QR(commands.Cog): + def __init__(self, bot): + self.bot = bot + + @commands.command(name="qr", description="Bir metin veya bağlantı için QR kod oluşturur.") + async def qr(self, ctx: commands.Context, *, metin: str): + + # Google Chart API alternatifi: api.qrserver.com (ücretsiz) + import urllib.parse + encoded_text = urllib.parse.quote(metin.strip(), safe='') + qr_url = f"https://api.qrserver.com/v1/create-qr-code/?size=300x300&data={encoded_text}" + + embed = discord.Embed( + title="📱 QR Kod Oluşturuldu", + description=f"**İçerik:** `{metin[:200]}`" + ("..." if len(metin) > 200 else ""), + color=CYBER_BLUE, + timestamp=datetime.now() + ) + embed.set_image(url=qr_url) + embed.set_footer(text=f"İstek: {ctx.author.display_name} • ArazAI QR") + + await ctx.send(embed=embed) + + +async def setup(bot): + await bot.add_cog(QR(bot)) diff --git a/cogs/reaction_roles.py b/cogs/reaction_roles.py new file mode 100644 index 0000000000000000000000000000000000000000..654a732328a7a4f4d01e064cf4ef00a163e76da7 --- /dev/null +++ b/cogs/reaction_roles.py @@ -0,0 +1,198 @@ +import discord +from discord.ext import commands +from discord import app_commands +from datetime import datetime +from database import Database + +CYBER_BLUE = discord.Color.from_rgb(52, 152, 219) +CYBER_GREEN = discord.Color.from_rgb(46, 204, 113) +CYBER_RED = discord.Color.from_rgb(231, 76, 60) + + +class ReactionRoles(commands.Cog): + def __init__(self, bot): + self.bot = bot + + @commands.hybrid_command(name="reaction-rol-olustur", description="Tepki rol mesajı oluşturur.") + @commands.has_permissions(administrator=True) + @app_commands.describe( + kanal="Mesajın gönderileceği kanal", + baslik="Mesaj başlığı", + aciklama="Mesaj açıklaması" + ) + async def rr_olustur(self, ctx: commands.Context, kanal: discord.TextChannel, baslik: str, aciklama: str): + if ctx.interaction: + await ctx.defer() + + embed = discord.Embed( + title=baslik, + description=aciklama + "\n\n*Aşağıdaki emojilere tıklayarak rol alın.*", + color=CYBER_BLUE, + timestamp=datetime.now() + ) + embed.set_footer(text="ArazAI Reaction Roles") + msg = await kanal.send(embed=embed) + + # Bir yapı kurulum kullanıcıya danış: emojileri tek tek eklemesını soyle + # Yapı: her emoji bir role karsilik gelecek — kullanıcının `rr-ekle` ile eklemesi gerekiyor + await Database.execute( + "INSERT OR REPLACE INTO reaction_roller (mesaj_id, kanal_id, guild_id, emoji, rol_id, aciklama) VALUES (?, ?, ?, '', 0, '')", + (msg.id, kanal.id, ctx.guild.id) + ) + + await ctx.send(f"✅ Reaction rol mesajı oluşturuldu. {msg.jump_url}\nŞimdi `/reaction-rol-ekle` komutu ile emoji ve roller ekleyin!\n'll **/reaction-rol-ekle** komutunu kullanırken **mesaj_id** olarak `{msg.id}` girin.'") + + @commands.hybrid_command(name="reaction-rol-ekle", description="Mevcut reaction rol mesajına emoji+rol ekler.") + @commands.has_permissions(administrator=True) + @app_commands.describe( + mesaj_id="Reaction rol mesajının ID'si", + emoji="Eklenecek emoji", + rol="Verilecek rol", + aciklama="Seçenek açıklaması (opsiyonel)" + ) + async def rr_ekle(self, ctx: commands.Context, mesaj_id: str, emoji: str, rol: discord.Role, aciklama: str = ""): + if ctx.interaction: + await ctx.defer() + + try: + mid = int(mesaj_id) + except: + await ctx.send("❌ Geçersiz mesaj ID.", ephemeral=True) + return + + rr = await Database.fetch_one( + "SELECT * FROM reaction_roller WHERE mesaj_id = ? AND guild_id = ?", + (mid, ctx.guild.id) + ) + if not rr: + await ctx.send("❌ Bu mesaj ID için bir reaction rol kaydı bulunamadı.", ephemeral=True) + return + + kanal = ctx.guild.get_channel(rr['kanal_id']) + if not kanal: + await ctx.send("❌ Kanal bulunamadı.", ephemeral=True) + return + + try: + msg_obj = await kanal.fetch_message(mid) + except: + await ctx.send("❌ Mesaj bulunamadı.", ephemeral=True) + return + + egen = emoji.strip() + + await Database.execute( + "DELETE FROM reaction_roller WHERE mesaj_id = ? AND emoji = ''", + (mid,) + ) + await Database.execute( + "INSERT OR REPLACE INTO reaction_roller (mesaj_id, kanal_id, guild_id, emoji, rol_id, aciklama) VALUES (?, ?, ?, ?, ?, ?)", + (mid, rr['kanal_id'], ctx.guild.id, egen, rol.id, aciklama) + ) + + try: + await msg_obj.add_reaction(egen) + except: + await ctx.send(f"❌ Emoji eklenemedi: `{egen}`. Geçerli emoji girin.", ephemeral=True) + await Database.execute("DELETE FROM reaction_roller WHERE mesaj_id = ? AND emoji = ? AND rol_id = ?", + (mid, egen, rol.id)) + return + + # Mesaj embed'ini güncelle + all_rrs = await Database.fetch_all( + "SELECT * FROM reaction_roller WHERE mesaj_id = ? AND emoji != ''", + (mid,) + ) + if msg_obj.embeds: + embed = msg_obj.embeds[0] + embed.clear_fields() + for r in all_rrs: + rr_rol = ctx.guild.get_role(r['rol_id']) + rol_name = rr_rol.mention if rr_rol else f"Rol silinmiş ({r['rol_id']})" + embed.add_field(name=f"{r['emoji']} - {r.get('aciklama', '') or 'Rol'}", value=rol_name, inline=False) + await msg_obj.edit(embed=embed) + + await ctx.send(f"✅ {egen} emoji tepkisi için {rol.mention} rolü eklendi.") + + @commands.hybrid_command(name="reaction-rol-sil", description="Bir reaction rol seçeneğini siler.") + @commands.has_permissions(administrator=True) + @app_commands.describe(mesaj_id="Mesaj ID", emoji="Silinecek emoji") + async def rr_sil(self, ctx: commands.Context, mesaj_id: str, emoji: str): + if ctx.interaction: + await ctx.defer() + try: + mid = int(mesaj_id) + except: + await ctx.send("❌ Geçersiz mesaj ID.", ephemeral=True) + return + + rows = await Database.execute( + "DELETE FROM reaction_roller WHERE mesaj_id = ? AND guild_id = ? AND emoji = ?", + (mid, ctx.guild.id, emoji.strip()) + ) + if rows > 0: + await ctx.send("✅ Reaction rol silindi.") + else: + await ctx.send("❌ Bu emoji veya mesaj için bir kayıt bulunamadı.", ephemeral=True) + + @commands.Cog.listener() + async def on_raw_reaction_add(self, payload: discord.RawReactionActionEvent): + if payload.user_id == self.bot.user.id: + return + if not await Database.modul_aktif_mi(payload.guild_id, "ReactionRoles"): + return + guild = self.bot.get_guild(payload.guild_id) + if not guild: + return + member = guild.get_member(payload.user_id) + if not member or member.bot: + return + + emoji_str = str(payload.emoji) + rr = await Database.fetch_one( + "SELECT * FROM reaction_roller WHERE mesaj_id = ? AND emoji = ? AND guild_id = ?", + (payload.message_id, emoji_str, guild.id) + ) + if not rr: + return + + rol = guild.get_role(rr['rol_id']) + if not rol: + return + + try: + await member.add_roles(rol, reason="Reaction rol") + except: + pass + + @commands.Cog.listener() + async def on_raw_reaction_remove(self, payload: discord.RawReactionActionEvent): + if not await Database.modul_aktif_mi(payload.guild_id, "ReactionRoles"): + return + guild = self.bot.get_guild(payload.guild_id) + if not guild: + return + member = guild.get_member(payload.user_id) + if not member or member.bot: + return + + emoji_str = str(payload.emoji) + rr = await Database.fetch_one( + "SELECT * FROM reaction_roller WHERE mesaj_id = ? AND emoji = ? AND guild_id = ?", + (payload.message_id, emoji_str, guild.id) + ) + if not rr: + return + + rol = guild.get_role(rr['rol_id']) + if not rol: + return + + try: + await member.remove_roles(rol, reason="Reaction rol kaldırıldı") + except: + pass + + +async def setup(bot): + await bot.add_cog(ReactionRoles(bot)) diff --git a/cogs/reminders.py b/cogs/reminders.py new file mode 100644 index 0000000000000000000000000000000000000000..ddb39a986bd4ed9b0aec23ce98e85254d4000ff0 --- /dev/null +++ b/cogs/reminders.py @@ -0,0 +1,142 @@ +import discord +from discord.ext import commands, tasks +from discord import app_commands +from datetime import datetime +from database import Database +import re + +CYBER_BLUE = discord.Color.from_rgb(52, 152, 219) +CYBER_GREEN = discord.Color.from_rgb(46, 204, 113) +CYBER_ORANGE = discord.Color.from_rgb(230, 126, 34) + +def parse_reminder_duration(duration_str: str): + """Süre: '10s', '5m', '2h', '1d', '1w' -> datetime (şimdi + delta)""" + duration_str = duration_str.lower().strip() + multipliers = {'s': 1, 'm': 60, 'h': 3600, 'd': 86400, 'w': 604800} + match = re.match(r"^(\d+)([smhdw])$", duration_str) + if not match: + return None + value = int(match.group(1)) + unit = match.group(2) + from datetime import timedelta + return datetime.now() + timedelta(seconds=value * multipliers[unit]) + + +class Reminders(commands.Cog): + def __init__(self, bot): + self.bot = bot + self.reminder_loop.start() + + def cog_unload(self): + self.reminder_loop.cancel() + + @tasks.loop(seconds=5) + async def reminder_loop(self): + try: + now = datetime.now() + records = await Database.fetch_all( + "SELECT * FROM hatirlaticilar WHERE bitis_zamani <= ?", + (now.isoformat(),) + ) + for r in records: + guild = self.bot.get_guild(r['guild_id']) + if not guild: + await Database.execute("DELETE FROM hatirlaticilar WHERE id = ?", (r['id'],)) + continue + channel = guild.get_channel(r['kanal_id']) + if not channel: + await Database.execute("DELETE FROM hatirlaticilar WHERE id = ?", (r['id'],)) + continue + + member = guild.get_member(r['user_id']) + mention = member.mention if member else f"<@{r['user_id']}>" + + embed = discord.Embed( + title="⏰ Hatırlatıcı!", + description=f"Merhaba {mention}! Hatırlatmanız geldi:\n\n**{r['mesaj']}**", + color=CYBER_ORANGE, + timestamp=datetime.now() + ) + embed.set_footer(text="ArazAI Hatırlatıcı Sistemi") + try: + await channel.send(content=mention, embed=embed) + except: + pass + + await Database.execute("DELETE FROM hatirlaticilar WHERE id = ?", (r['id'],)) + except Exception as e: + print(f"Reminder loop hatası: {e}") + + @reminder_loop.before_loop + async def before_reminder_loop(self): + await self.bot.wait_until_ready() + + @commands.hybrid_command(name="hatırlat", description="Size belirli bir süre sonra hatırlatma gönderir.") + @app_commands.describe( + sure="Süre (örn: 10s, 5m, 2h, 1d, 1w)", + mesaj="Hatırlatıcı mesajı" + ) + async def hatirlat(self, ctx: commands.Context, sure: str, *, mesaj: str): + if ctx.interaction: + await ctx.defer() + + bitis = parse_reminder_duration(sure) + if not bitis: + await ctx.send("❌ Geçersiz süre. Örnek: `10s`, `5m`, `2h`, `1d`, `1w`", ephemeral=True) + return + + await Database.execute( + "INSERT INTO hatirlaticilar (guild_id, kanal_id, user_id, mesaj, bitis_zamani) VALUES (?, ?, ?, ?, ?)", + (ctx.guild.id, ctx.channel.id, ctx.author.id, mesaj, bitis.isoformat()) + ) + + embed = discord.Embed( + title="✅ Hatırlatıcı Kuruldu!", + description=( + f"**Mesaj:** {mesaj}\n" + f"**Süre:** {sure}\n" + f"**Bitiş:** ()" + ), + color=CYBER_GREEN, + timestamp=datetime.now() + ) + await ctx.send(embed=embed) + + @commands.hybrid_command(name="hatırlatıcılar", description="Aktif hatırlatıcılardan ilk 10 tanesini listeler.") + async def hatirlaticilar(self, ctx: commands.Context): + if ctx.interaction: + await ctx.defer() + records = await Database.fetch_all( + "SELECT * FROM hatirlaticilar WHERE guild_id = ? AND user_id = ? ORDER BY bitis_zamani ASC LIMIT 10", + (ctx.guild.id, ctx.author.id) + ) + if not records: + await ctx.send("ℹ️ Aktif hatırlatıcınız bulunmuyor.") + return + + embed = discord.Embed(title="⏰ Aktif Hatırlatıcılar", color=CYBER_ORANGE, timestamp=datetime.now()) + desc = "" + for r in records: + bitis = datetime.fromisoformat(r['bitis_zamani']) + desc += f"• **#{r['id']}** | | {r['mesaj'][:50]}\n" + embed.description = desc + embed.set_footer(text="Silmek için: /hatırlatıcı-sil ") + await ctx.send(embed=embed) + + @commands.hybrid_command(name="hatırlatıcı-sil", description="Bir hatırlatıcıyı ID ile siler.") + @app_commands.describe(id="Hatırlatıcı ID") + async def hatirlatici_sil(self, ctx: commands.Context, id: int): + if ctx.interaction: + await ctx.defer() + rows = await Database.execute( + "DELETE FROM hatirlaticilar WHERE id = ? AND guild_id = ? AND user_id = ?", + (id, ctx.guild.id, ctx.author.id) + ) + if rows > 0: + await ctx.send(f"✅ #{id} hatırlatıcısı silindi.") + else: + await ctx.send("❌ Hatırlatıcı bulunamadı veya size ait değil.", ephemeral=True) + + +async def setup(bot): + await bot.add_cog(Reminders(bot)) diff --git a/cogs/sistem.py b/cogs/sistem.py new file mode 100644 index 0000000000000000000000000000000000000000..5172054313206c074f871bd8f8bc633481980eab --- /dev/null +++ b/cogs/sistem.py @@ -0,0 +1,94 @@ +import discord +from discord.ext import commands +from discord import app_commands +from database import Database + +# Modül Eşleştirme Sözlüğü (Kullanıcı Dostu İsim -> Cog Sınıf İsmi) +MODULE_MAP = { + "moderasyon": "Moderation", + "anti-raid": "AntiRaid", + "yapay-zeka": "AI", + "partnerlik": "Partnership", + "eglence": "Fun", + "log": "Logs", + "istatistik": "Stats", + "destek": "Ticket", + "karsilama": "Welcome", + "seviye": "Level", + "cekilis": "Giveaway", + "oneri": "Suggestion", + "anket": "Poll", + "tepki-rol": "ReactionRoles", + "oto-rol": "AutoRole", + "dogrulama": "Verification", + "gecici-kanal": "TempChannels", + "ekonomi": "Economy", + "dogum-gunu": "Birthday", + "sayi-sayma": "Counting", + "hatirlatici": "Reminders", + "ozel-komut": "CustomCommands", + "yayin-alert": "StreamAlerts", + "ceviri": "Translate", + "hava-durumu": "Weather", + "qr": "QR", + "saat-dilimi": "Timezone", + "itibar": "Itibar" +} + +class Sistem(commands.Cog): + def __init__(self, bot): + self.bot = bot + + @commands.hybrid_command(name="modül-ayarla", description="İstediğiniz bot modülünü sunucuda aktif veya pasif yapar.") + @commands.has_permissions(administrator=True) + @app_commands.describe( + modul="Ayarını değiştirmek istediğiniz modülün adı", + aktif="Modülün durumu (Açık: True, Kapalı: False)" + ) + async def modul_ayarla(self, ctx: commands.Context, modul: str, aktif: bool): + if ctx.interaction: + await ctx.defer() + + modul_lower = modul.lower() + if modul_lower not in MODULE_MAP: + await ctx.send("❌ Geçersiz modül ismi! Mevcut modülleri görmek için `/modüller` komutunu kullanabilirsiniz.") + return + + cog_name = MODULE_MAP[modul_lower] + await Database.modul_durum_set(ctx.guild.id, cog_name, aktif) + + durum_str = "✅ **Aktif**" if aktif else "❌ **Pasif**" + await ctx.send(f"⚙️ **{cog_name}** modülü bu sunucuda {durum_str} duruma getirildi.") + + @modul_ayarla.autocomplete("modul") + async def modul_autocomplete(self, interaction: discord.Interaction, current: str): + return [ + app_commands.Choice(name=k, value=k) + for k in sorted(MODULE_MAP.keys()) if current.lower() in k.lower() + ][:25] + + @commands.hybrid_command(name="modüller", description="Sunucudaki tüm modülleri ve aktiflik durumlarını listeler.") + @commands.has_permissions(administrator=True) + async def moduller(self, ctx: commands.Context): + if ctx.interaction: + await ctx.defer() + + embed = discord.Embed( + title="⚙️ Sunucu Modülleri ve Durumları", + description="Aşağıda sunucunuzda kurulu olan tüm modüllerin durumları listelenmiştir.", + color=discord.Color.blue() + ) + + for user_friendly, cog_name in sorted(MODULE_MAP.items()): + is_active = await Database.modul_aktif_mi(ctx.guild.id, cog_name) + durum_emoji = "🟢 Açık" if is_active else "🔴 Kapalı" + embed.add_field( + name=f"{cog_name} (`{user_friendly}`)", + value=f"Durum: {durum_emoji}", + inline=True + ) + + await ctx.send(embed=embed) + +async def setup(bot): + await bot.add_cog(Sistem(bot)) diff --git a/cogs/stats.py b/cogs/stats.py new file mode 100644 index 0000000000000000000000000000000000000000..076fd8fc2ea10803b3fc4a104f727b63a2557d88 --- /dev/null +++ b/cogs/stats.py @@ -0,0 +1,183 @@ +import discord +from discord.ext import commands +from discord import app_commands +from datetime import datetime, timezone +import time +from database import Database + +# Premium Renkler +CYBER_BLUE = discord.Color.from_rgb(52, 152, 219) +CYBER_DARK = discord.Color.from_rgb(44, 62, 80) +CYBER_GREEN = discord.Color.from_rgb(46, 204, 113) + +class Stats(commands.Cog): + def __init__(self, bot): + self.bot = bot + # Ses kanalına katılan üyelerin giriş zamanları: {member_id: timestamp} + self.voice_join_times = {} + + def _get_today_str(self): + return datetime.now().date().isoformat() + + def _format_duration(self, seconds: int) -> str: + if seconds <= 0: + return "0 saniye" + hours = seconds // 3600 + minutes = (seconds % 3600) // 60 + secs = seconds % 60 + parts = [] + if hours > 0: + parts.append(f"{hours} saat") + if minutes > 0: + parts.append(f"{minutes} dakika") + if secs > 0 or not parts: + parts.append(f"{secs} saniye") + return " ".join(parts) + + def _generate_bar(self, value, max_val, size=10) -> str: + if max_val <= 0: + return "░" * size + filled = int((value / max_val) * size) + filled = min(filled, size) + return "█" * filled + "░" * (size - filled) + + # --- SOhBET AKTİFLİĞİ SAYICI --- + @commands.Cog.listener() + async def on_message(self, message: discord.Message): + if message.author.bot or not message.guild: + return + if not await Database.modul_aktif_mi(message.guild.id, "Stats"): + return + today = self._get_today_str() + await Database.aktiflik_mesaj_arttir(message.guild.id, message.author.id, today) + + # --- SES AKTİFLİĞİ TAKİPÇİSİ --- + @commands.Cog.listener() + async def on_voice_state_update(self, member: discord.Member, before: discord.VoiceState, after: discord.VoiceState): + if member.bot or not member.guild: + return + if not await Database.modul_aktif_mi(member.guild.id, "Stats"): + return + + user_id = member.id + guild_id = member.guild.id + today = self._get_today_str() + now = time.time() + + # Durum 1: Ses kanalına giriş yaptı (Eski kanalı yoktu, yenisi var) + if before.channel is None and after.channel is not None: + self.voice_join_times[user_id] = now + + # Durum 2: Ses kanalından çıkış yaptı (Eski kanalı vardı, yenisi yok) + elif before.channel is not None and after.channel is None: + join_time = self.voice_join_times.pop(user_id, None) + if join_time: + duration = int(now - join_time) + if duration > 0: + await Database.aktiflik_ses_ekle(guild_id, user_id, today, duration) + + # Durum 3: Ses kanalı değiştirdi (Her ikisi de var ama farklı kanallar) + elif before.channel is not None and after.channel is not None and before.channel.id != after.channel.id: + join_time = self.voice_join_times.get(user_id, None) + if join_time: + duration = int(now - join_time) + if duration > 0: + await Database.aktiflik_ses_ekle(guild_id, user_id, today, duration) + # Zamanı sıfırla yeni kanalda kalmaya devam etsin + self.voice_join_times[user_id] = now + + # --- KOMUTLAR --- + + @commands.hybrid_command(name="istatistik", description="Kendi aktiflik istatistiklerinizi veya başka bir üyeninkini görüntüler.") + @app_commands.describe(uye="İstatistiklerine bakılacak üye") + async def istatistik(self, ctx: commands.Context, uye: discord.Member = None): + if ctx.interaction: + await ctx.defer() + + target = uye or ctx.author + stats = await Database.aktiflik_getir(ctx.guild.id, target.id) + + # Son 7 günün mesaj verileri + mesaj_detay = "" + toplam_7_gun_mesaj = 0 + max_7_gun_mesaj = 0 + for r in stats["son_7_gun_mesaj"]: + toplam_7_gun_mesaj += r["mesaj_sayisi"] + if r["mesaj_sayisi"] > max_7_gun_mesaj: + max_7_gun_mesaj = r["mesaj_sayisi"] + + # Tersten sıralayarak eskiden yeniye doğru günleri listele + for r in reversed(stats["son_7_gun_mesaj"]): + bar = self._generate_bar(r["mesaj_sayisi"], max_7_gun_mesaj) + mesaj_detay += f"`{r['gun_tarih']}` {bar} `{r['mesaj_sayisi']} mesaj`\n" + + # Son 7 günün ses verileri + ses_detay = "" + toplam_7_gun_ses = 0 + max_7_gun_ses = 0 + for r in stats["son_7_gun_ses"]: + toplam_7_gun_ses += r["ses_suresi_sn"] + if r["ses_suresi_sn"] > max_7_gun_ses: + max_7_gun_ses = r["ses_suresi_sn"] + + for r in reversed(stats["son_7_gun_ses"]): + bar = self._generate_bar(r["ses_suresi_sn"], max_7_gun_ses) + ses_detay += f"`{r['gun_tarih']}` {bar} `{self._format_duration(r['ses_suresi_sn'])}`\n" + + embed = discord.Embed( + title=f"📊 Aktiflik İstatistikleri - {target.display_name}", + color=CYBER_BLUE, + timestamp=datetime.now() + ) + embed.set_thumbnail(url=target.display_avatar.url) + + # Özet Bölümü + embed.add_field(name="✉️ Toplam Mesaj", value=f"`{stats['toplam_mesaj']}` adet", inline=True) + embed.add_field(name="🔊 Toplam Ses Süresi", value=f"`{self._format_duration(stats['toplam_ses'])}`", inline=True) + + # Detaylar + if mesaj_detay: + embed.add_field(name="📅 Son 7 Gün Mesaj İstatistiği", value=mesaj_detay, inline=False) + else: + embed.add_field(name="📅 Son 7 Gün Mesaj İstatistiği", value="*Son 7 günde kaydedilmiş mesaj yok.*", inline=False) + + if ses_detay: + embed.add_field(name="⏱️ Son 7 Gün Ses İstatistiği", value=ses_detay, inline=False) + else: + embed.add_field(name="⏱️ Son 7 Gün Ses İstatistiği", value="*Son 7 günde kaydedilmiş ses aktifliği yok.*", inline=False) + + await ctx.send(embed=embed) + + @commands.hybrid_command(name="top-aktiflik", description="Sunucudaki aktiflik sıralamasını (mesaj ve ses) gösterir.") + async def top_aktiflik(self, ctx: commands.Context): + if ctx.interaction: + await ctx.defer() + + top_mesaj = await Database.aktiflik_siralamasi_mesaj(ctx.guild.id, 10) + top_ses = await Database.aktiflik_siralamasi_ses(ctx.guild.id, 10) + + mesaj_list = [] + for i, row in enumerate(top_mesaj, 1): + member = ctx.guild.get_member(row['user_id']) + name = member.mention if member else f"ID: {row['user_id']}" + mesaj_list.append(f"**{i}.** {name} — `{row['toplam']} mesaj`") + + ses_list = [] + for i, row in enumerate(top_ses, 1): + member = ctx.guild.get_member(row['user_id']) + name = member.mention if member else f"ID: {row['user_id']}" + ses_list.append(f"**{i}.** {name} — `{self._format_duration(row['toplam'])}`") + + embed = discord.Embed( + title=f"🏆 Sunucu Aktiflik Sıralaması", + color=CYBER_GREEN, + timestamp=datetime.now() + ) + + embed.add_field(name="✉️ En Çok Mesaj Atanlar", value="\n".join(mesaj_list) if mesaj_list else "*Veri yok.*", inline=False) + embed.add_field(name="🔊 En Çok Seste Kalanlar", value="\n".join(ses_list) if ses_list else "*Veri yok.*", inline=False) + + await ctx.send(embed=embed) + +async def setup(bot): + await bot.add_cog(Stats(bot)) diff --git a/cogs/stream_alerts.py b/cogs/stream_alerts.py new file mode 100644 index 0000000000000000000000000000000000000000..08bea348f1828782c7bd16470ebaea1682e7fa8e --- /dev/null +++ b/cogs/stream_alerts.py @@ -0,0 +1,228 @@ +import discord +from discord.ext import commands, tasks +from discord import app_commands +from datetime import datetime +import aiohttp +import json +import os +from database import Database + +CYBER_PURPLE = discord.Color.from_rgb(155, 89, 182) +CYBER_RED = discord.Color.from_rgb(231, 76, 60) +CYBER_GREEN = discord.Color.from_rgb(46, 204, 113) +CYBER_BLUE = discord.Color.from_rgb(52, 152, 219) + + +class StreamAlerts(commands.Cog): + def __init__(self, bot): + self.bot = bot + self.twitch_client_id = "" + self.twitch_token = "" + config_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), "config.json") + if os.path.exists(config_path): + try: + with open(config_path, "r", encoding="utf-8") as f: + cfg = json.load(f) + self.twitch_client_id = cfg.get("twitch_client_id", "") + self.twitch_token = cfg.get("twitch_access_token", "") + except: + pass + + self.stream_check_loop.start() + + def cog_unload(self): + self.stream_check_loop.cancel() + + @tasks.loop(minutes=5) + async def stream_check_loop(self): + try: + # Twitch akışları + twitch_alerts = await Database.fetch_all( + "SELECT * FROM stream_bildirimleri WHERE platform = 'twitch'" + ) + for alert in twitch_alerts: + user_name = alert['kullanici_adi'] + guild = self.bot.get_guild(alert['guild_id']) + if not guild: + continue + channel = guild.get_channel(alert['kanal_id']) + if not channel: + continue + + # Twitch API kontrol + live_data = await self._check_twitch_live(user_name) + if not live_data: + # Aktif kayıt var ama artık yayında değilse + await Database.execute( + "DELETE FROM stream_aktif WHERE guild_id = ? AND platform = ? AND kullanici_adi = ?", + (alert['guild_id'], "twitch", user_name) + ) + continue + + # Zaten bildirildi mi? + already = await Database.fetch_one( + "SELECT * FROM stream_aktif WHERE guild_id = ? AND platform = ? AND kullanici_adi = ?", + (alert['guild_id'], "twitch", user_name) + ) + + if not already: + # Yeni yayın başladı + title = live_data.get('title', 'Başlık yok') + avatar_url = live_data.get('avatar_url', '') + game = live_data.get('game_name', 'Bilinmiyor') + viewer_count = live_data.get('viewer_count', 0) + + embed = discord.Embed( + title=f"🔴 {user_name} Şu Anda Yayında!", + description=f"**{title}**\n\n🎮 **Kategori:** {game}\n👥 **İzleyici:** {viewer_count}\n\n[Yayını İzle](https://twitch.tv/{user_name})", + color=CYBER_PURPLE, + timestamp=datetime.now() + ) + if avatar_url: + embed.set_thumbnail(url=avatar_url) + embed.set_footer(text="ArazAI Twitch Bildirim Sistemi") + + try: + msg = await channel.send(content=f"@here **{user_name}** yayında! 🎥", embed=embed, allowed_mentions=discord.AllowedMentions(users=False, roles=False, everyone=False)) + await Database.execute( + "INSERT OR REPLACE INTO stream_aktif (guild_id, platform, kullanici_adi, mesaj_id) VALUES (?, ?, ?, ?)", + (alert['guild_id'], "twitch", user_name, msg.id) + ) + except: + pass + except Exception as e: + print(f"Stream check loop hatası: {e}") + + @stream_check_loop.before_loop + async def before_stream_check_loop(self): + await self.bot.wait_until_ready() + + async def _check_twitch_live(self, user_login: str): + """Twitch API'den yayında olup olmadığını kontrol eder.""" + if not self.twitch_client_id or not self.twitch_token: + # API anahtarı yoksa hiçbir şey dönme + return None + + try: + headers = { + "Client-ID": self.twitch_client_id, + "Authorization": f"Bearer {self.twitch_token}" + } + url = f"https://api.twitch.tv/helix/streams?user_login={user_login}" + async with aiohttp.ClientSession() as session: + async with session.get(url, headers=headers, timeout=10) as resp: + if resp.status != 200: + return None + data = await resp.json() + streams = data.get('data', []) + if not streams: + return None + stream = streams[0] + return { + 'title': stream.get('title', ''), + 'game_name': stream.get('game_name', 'Bilinmiyor'), + 'viewer_count': stream.get('viewer_count', 0), + 'avatar_url': '' + } + except: + return None + + @commands.hybrid_command(name="twitch-ekle", description="Twitch yayını bildirimi ekler.") + @commands.has_permissions(administrator=True) + @app_commands.describe( + kanal="Bildirimlerin gönderileceği kanal", + kullanici_adi="Twitch kullanıcı adı" + ) + async def twitch_ekle(self, ctx: commands.Context, kanal: discord.TextChannel, kullanici_adi: str): + if ctx.interaction: + await ctx.defer() + await Database.execute( + "INSERT OR IGNORE INTO stream_bildirimleri (guild_id, kanal_id, platform, kullanici_adi) VALUES (?, ?, ?, ?)", + (ctx.guild.id, kanal.id, "twitch", kullanici_adi.lower().strip()) + ) + await ctx.send(f"✅ **{kullanici_adi}** Twitch yayını bildirimi {kanal.mention} kanalına eklendi.") + + @commands.hybrid_command(name="twitch-çıkar", description="Bir Twitch bildirimini siler.") + @commands.has_permissions(administrator=True) + @app_commands.describe(kullanici_adi="Silinecek Twitch kullanıcı adı") + async def twitch_cikar(self, ctx: commands.Context, kullanici_adi: str): + if ctx.interaction: + await ctx.defer() + rows = await Database.execute( + "DELETE FROM stream_bildirimleri WHERE guild_id = ? AND platform = 'twitch' AND kullanici_adi = ?", + (ctx.guild.id, kullanici_adi.lower().strip()) + ) + if rows > 0: + await ctx.send(f"✅ **{kullanici_adi}** bildirimi silindi.") + else: + await ctx.send("❌ Bildirim bulunamadı.", ephemeral=True) + + @commands.hybrid_command(name="twitch-liste", description="Twitch bildirim listesini gösterir.") + async def twitch_liste(self, ctx: commands.Context): + if ctx.interaction: + await ctx.defer() + records = await Database.fetch_all( + "SELECT * FROM stream_bildirimleri WHERE guild_id = ? AND platform = 'twitch'", + (ctx.guild.id,) + ) + if not records: + await ctx.send("ℹ️ Twitch bildirimi yok.") + return + embed = discord.Embed(title="📺 Twitch Bildirim Listesi", color=CYBER_PURPLE, timestamp=datetime.now()) + desc = "" + for r in records: + kanal = ctx.guild.get_channel(r['kanal_id']) + kanal_str = kanal.mention if kanal else "silinmiş" + desc += f"• **{r['kullanici_adi']}** → {kanal_str}\n" + embed.description = desc + await ctx.send(embed=embed) + + @commands.Cog.listener() + async def on_member_update(self, before, after): + """Discord'un kendi go-live özelliğini yakalama (basit davranışsal) + + Üye bir ses kanalında yayına geçince (streaming) bildirim gönderir""" + if before.bot or not after.guild: + return + if not await Database.modul_aktif_mi(after.guild.id, "StreamAlerts"): + return + + # Streaming durumu değişti mi? + before_streaming = False + after_streaming = False + for a in before.activities: + if isinstance(a, discord.Streaming): + before_streaming = True + for a in after.activities: + if isinstance(a, discord.Streaming): + after_streaming = True + + if not before_streaming and after_streaming: + # Yayın başladı - kullanıcının ses kanalı var mı? + if after.voice and after.voice.channel: + streaming_activity = next((a for a in after.activities if isinstance(a, discord.Streaming)), None) + if streaming_activity: + # Sadece Twitch ise + if streaming_activity.platform and "twitch" in streaming_activity.platform.lower(): + # Zaten kayıtlı mu? + existing = await Database.fetch_one( + "SELECT * FROM stream_aktif WHERE guild_id = ? AND platform = 'discord' AND kullanici_adi = ?", + (after.guild.id, str(after.id)) + ) + if not existing: + embed = discord.Embed( + title=f"🔴 {after.display_name} Yayında!", + description=f"**{streaming_activity.name}**\n\n🎮 **Oyun/Kategori:** {streaming_activity.details or 'Bilinmiyor'}\n\n[Yayını İzle]({streaming_activity.url})", + color=CYBER_PURPLE, + timestamp=datetime.now() + ) + embed.set_thumbnail(url=after.display_avatar.url) + embed.set_footer(text="ArazAI Discord Go-Live Bildirim") + try: + await after.voice.channel.send(content=f"{after.mention} yayında! 🎥", embed=embed) + except: + pass + + +async def setup(bot): + await bot.add_cog(StreamAlerts(bot)) diff --git a/cogs/suggestion.py b/cogs/suggestion.py new file mode 100644 index 0000000000000000000000000000000000000000..1e497d4d56108c0afd8a74ad80c5d21d3cd85036 --- /dev/null +++ b/cogs/suggestion.py @@ -0,0 +1,77 @@ +import discord +from discord.ext import commands +from discord import app_commands +from datetime import datetime +from database import Database + +CYBER_BLUE = discord.Color.from_rgb(52, 152, 219) +CYBER_GREEN = discord.Color.from_rgb(46, 204, 113) +CYBER_ORANGE = discord.Color.from_rgb(230, 126, 34) +CYBER_RED = discord.Color.from_rgb(231, 76, 60) + + +class Suggestion(commands.Cog): + def __init__(self, bot): + self.bot = bot + + @commands.command(name="öneri-kanal-ayarla", description="Önerilerin gönderileceği kanalı ayarlar.") + @commands.has_permissions(administrator=True) + async def oneri_kanal_ayarla(self, ctx: commands.Context, kanal: discord.TextChannel): + await Database.execute( + "INSERT OR REPLACE INTO log_ayarlar (guild_id, log_kanal_id) VALUES (?, ?) " + "ON CONFLICT(guild_id) DO UPDATE SET log_kanal_id = excluded.log_kanal_id", + (ctx.guild.id, kanal.id) + ) + await ctx.send(f"✅ Öneri kanalı {kanal.mention} olarak ayarlandı.") + + @commands.command(name="öneri", description="Sunucu için bir öneri yapar.") + async def oneri(self, ctx: commands.Context, *, oneri: str): + kanal_id = await Database.log_kanali_getir(ctx.guild.id) + oneri_kanal = ctx.guild.get_channel(kanal_id) if kanal_id else None + + if not oneri_kanal: + await ctx.send("❌ Öneri kanalı ayarlanmamış. Bir yöneticiden `!öneri-kanal-ayarla` komutunu kullanmasını isteyin.") + return + + embed = discord.Embed( + title="💡 Yeni Öneri", + description=oneri, + color=CYBER_BLUE, + timestamp=datetime.now() + ) + embed.set_author(name=ctx.author.display_name, icon_url=ctx.author.display_avatar.url) + embed.set_footer(text=f"Öneren: {ctx.author.id}") + + oneri_msg = await oneri_kanal.send(embed=embed) + await oneri_msg.add_reaction("👍") + await oneri_msg.add_reaction("👎") + + await Database.execute( + "INSERT INTO oneriler (guild_id, kanal_id, user_id, mesaj_id, icerik, timestamp) VALUES (?, ?, ?, ?, ?, ?)", + (ctx.guild.id, oneri_kanal.id, ctx.author.id, oneri_msg.id, oneri, datetime.now().isoformat()) + ) + + await ctx.send(f"✅ Öneriniz {oneri_kanal.mention} kanalına gönderildi!") + + @commands.command(name="öneri-liste", description="Sunucudaki tüm önerileri listeler.") + @commands.has_permissions(manage_guild=True) + async def oneri_liste(self, ctx: commands.Context): + records = await Database.fetch_all( + "SELECT * FROM oneriler WHERE guild_id = ? ORDER BY id DESC LIMIT 15", + (ctx.guild.id,) + ) + if not records: + await ctx.send("ℹ️ Sunucuda öneri bulunmamaktadır.") + return + + embed = discord.Embed(title="💡 Öneri Listesi", color=CYBER_BLUE, timestamp=datetime.now()) + desc = "" + for r in records: + tarih = datetime.fromisoformat(r['timestamp']).strftime("%d.%m.%Y") + desc += f"• **#{r['id']}** | <@{r['user_id']}> | {tarih} | [Mesaj](https://discord.com/channels/{ctx.guild.id}/{r['kanal_id']}/{r['mesaj_id']})\n" + embed.description = desc[:4000] + await ctx.send(embed=embed) + + +async def setup(bot): + await bot.add_cog(Suggestion(bot)) diff --git a/cogs/temp_channels.py b/cogs/temp_channels.py new file mode 100644 index 0000000000000000000000000000000000000000..7b476d5f029b212e7def6e1aa46170da31797d80 --- /dev/null +++ b/cogs/temp_channels.py @@ -0,0 +1,119 @@ +import discord +from discord.ext import commands +from discord import app_commands +import asyncio +from database import Database + +CYBER_BLUE = discord.Color.from_rgb(52, 152, 219) +CYBER_GREEN = discord.Color.from_rgb(46, 204, 113) +CYBER_ORANGE = discord.Color.from_rgb(230, 126, 34) + + +class TempChannels(commands.Cog): + def __init__(self, bot): + self.bot = bot + + @commands.hybrid_command(name="geçici-kanal-ayarla", description="Geçici ses kanalı sistemi ayarlar.") + @commands.has_permissions(administrator=True) + @app_commands.describe( + kategori="Geçici kanalların oluşturulacağı kategori", + kanal_olusturma_kanali="Kullanıcıların geçici kanal oluşturmak için gireceği ses kanalı" + ) + async def gecici_kanal_ayarla( + self, ctx: commands.Context, + kategori: discord.CategoryChannel, + kanal_olusturma_kanali: discord.VoiceChannel + ): + if ctx.interaction: + await ctx.defer() + await Database.execute( + """INSERT OR REPLACE INTO gecici_kanal_ayarlar + (guild_id, kategori_id, kanal_olustur_kanal_id, aktif) + VALUES (?, ?, ?, 1)""", + (ctx.guild.id, kategori.id, kanal_olusturma_kanali.id) + ) + await ctx.send(f"✅ Geçici kanal sistemi ayarlandı. {kanal_olusturma_kanali.mention} ses kanalına girerek özel geçici kanal oluşturabilirsiniz.") + + @commands.hybrid_command(name="geçici-kanal-kapat", description="Geçici kanal sistemini devre dışı bırakır.") + @commands.has_permissions(administrator=True) + async def gecici_kanal_kapat(self, ctx: commands.Context): + if ctx.interaction: + await ctx.defer() + await Database.execute("UPDATE gecici_kanal_ayarlar SET aktif = 0 WHERE guild_id = ?", (ctx.guild.id,)) + await ctx.send("✅ Geçici kanal sistemi devre dışı bırakıldı.") + + @commands.Cog.listener() + async def on_voice_state_update(self, member: discord.Member, before: discord.VoiceState, after: discord.VoiceState): + if member.bot or not member.guild: + return + if not await Database.modul_aktif_mi(member.guild.id, "TempChannels"): + return + + cfg = await Database.fetch_one( + "SELECT * FROM gecici_kanal_ayarlar WHERE guild_id = ? AND aktif = 1", + (member.guild.id,) + ) + if not cfg: + return + + olustur_kanal_id = cfg.get('kanal_olustur_kanal_id', 0) + kategori_id = cfg.get('kategori_id', 0) + + # Kullanıcı oluşturma kanalına mı girdi? + if before.channel is None and after.channel is not None and after.channel.id == olustur_kanal_id: + kategori = member.guild.get_channel(kategori_id) if kategori_id else None + try: + overwrites = { + member.guild.default_role: discord.PermissionOverwrite(view_channel=False), + member: discord.PermissionOverwrite(view_channel=True, send_messages=True, connect=True, manage_channels=True, speak=True, stream=True, use_voice_activation=True), + member.guild.me: discord.PermissionOverwrite(view_channel=True, connect=True, manage_channels=True) + } + new_channel = await member.guild.create_voice_channel( + name=f"🔊 {member.display_name} Odası", + category=kategori, + overwrites=overwrites, + reason=f"Geçici kanal: {member}" + ) + await Database.execute( + "INSERT OR REPLACE INTO gecici_kanallar (kanal_id, guild_id, sahip_id) VALUES (?, ?, ?)", + (new_channel.id, member.guild.id, member.id) + ) + await member.move_to(new_channel, reason="Geçici kanala taşınıyor") + except Exception as e: + print(f"Geçici kanal oluşturma hatası: {e}") + + # Kanal boşaldıysa sil + if before.channel is not None and after.channel is None: + gecici = await Database.fetch_one( + "SELECT * FROM gecici_kanallar WHERE kanal_id = ? AND guild_id = ?", + (before.channel.id, member.guild.id) + ) + if gecici and len(before.channel.members) == 0: + try: + await before.channel.delete(reason="Geçici kanal boşaldı") + except: + pass + await Database.execute( + "DELETE FROM gecici_kanallar WHERE kanal_id = ?", + (before.channel.id,) + ) + + # Kanal değiştirme durumunda da eski geçici kanal kontrolü + if before.channel is not None and after.channel is not None and before.channel.id != after.channel.id: + gecici = await Database.fetch_one( + "SELECT * FROM gecici_kanallar WHERE kanal_id = ? AND guild_id = ?", + (before.channel.id, member.guild.id) + ) + if gecici and len(before.channel.members) == 0: + try: + await before.channel.delete(reason="Geçici kanal boşaldı") + except: + pass + await Database.execute( + "DELETE FROM gecici_kanallar WHERE kanal_id = ?", + (before.channel.id,) + ) + + +async def setup(bot): + await bot.add_cog(TempChannels(bot)) diff --git a/cogs/ticket.py b/cogs/ticket.py new file mode 100644 index 0000000000000000000000000000000000000000..6ac14c3c460484d02ff7a438afdb611f3d76b797 --- /dev/null +++ b/cogs/ticket.py @@ -0,0 +1,496 @@ +import discord +from discord.ext import commands +from discord import app_commands +from datetime import datetime +import asyncio +import io +import string +import random +from database import Database + +CYBER_BLUE = discord.Color.from_rgb(52, 152, 219) +CYBER_GREEN = discord.Color.from_rgb(46, 204, 113) +CYBER_RED = discord.Color.from_rgb(231, 76, 60) +CYBER_ORANGE = discord.Color.from_rgb(230, 126, 34) +CYBER_PURPLE = discord.Color.from_rgb(155, 89, 182) +CYBER_GREY = discord.Color.from_rgb(149, 165, 166) + +# --- BİLET OLUŞTURMA MODALI --- +class TicketCreateModal(discord.ui.Modal, title="Destek Talebi Formu"): + def __init__(self, category_name, category_emoji, yetkili_rol_id, kategori_id): + super().__init__() + self.category_name = category_name + self.category_emoji = category_emoji + self.yetkili_rol_id = yetkili_rol_id + self.kategori_id = kategori_id + + self.reason = discord.ui.TextInput( + label="Destek Sebebi", + style=discord.TextStyle.paragraph, + placeholder="Lütfen destek talebinizin detaylarını yazınız...", + required=True, + max_length=500 + ) + self.priority = discord.ui.TextInput( + label="Öncelik Durumu", + style=discord.TextStyle.short, + placeholder="Düşük / Normal / Yüksek / Kritik", + default="Normal", + required=True, + max_length=20 + ) + self.add_item(self.reason) + self.add_item(self.priority) + + async def on_submit(self, interaction: discord.Interaction): + await interaction.response.defer(ephemeral=True) + + guild = interaction.guild + member = interaction.user + + # Bilet numarası için log tablosunu sayalım + row = await Database.fetch_one("SELECT COUNT(*) as count FROM ticket_log") + ticket_no = (row['count'] if row else 0) + 1 + + # Öncelik Emojisi Belirleme + prio_text = self.priority.value.strip().lower() + prio_emoji = "🟡" + if "düşük" in prio_text or "dusuk" in prio_text: + prio_emoji = "🟢" + prio_name = "Düşük" + elif "yüksek" in prio_text or "yuksek" in prio_text: + prio_emoji = "🟠" + prio_name = "Yüksek" + elif "kritik" in prio_text: + prio_emoji = "🔴" + prio_name = "Kritik" + else: + prio_name = "Normal" + + kategori = guild.get_channel(self.kategori_id) if self.kategori_id else None + + # Kanal İzinleri + overwrites = { + guild.default_role: discord.PermissionOverwrite(view_channel=False), + member: discord.PermissionOverwrite(view_channel=True, send_messages=True, read_message_history=True, attach_files=True), + guild.me: discord.PermissionOverwrite(view_channel=True, send_messages=True, read_message_history=True, manage_channels=True) + } + if self.yetkili_rol_id: + rol = guild.get_role(self.yetkili_rol_id) + if rol: + overwrites[rol] = discord.PermissionOverwrite(view_channel=True, send_messages=True, read_message_history=True, manage_messages=True) + + # Kanalı Oluştur + try: + channel = await guild.create_text_channel( + name=f"ticket-{ticket_no}", + category=kategori, + overwrites=overwrites, + reason=f"Destek Talebi #{ticket_no}" + ) + except Exception as e: + await interaction.followup.send(f"❌ Kanal oluşturulurken hata meydana geldi: {e}", ephemeral=True) + return + + # Log kaydı + await Database.execute( + "INSERT INTO ticket_log (guild_id, kanal_id, user_id, durum, timestamp) VALUES (?, ?, ?, ?, ?)", + (guild.id, channel.id, member.id, "acildi", datetime.now().isoformat()) + ) + + # Embed Tasarımı + embed = discord.Embed( + title="Araz Ticket", + description=f"{member.mention} tarafından ticket talebi **{datetime.now().strftime('%d.%m.%Y %H:%M')}** tarihinde oluşturuldu. <@&{self.yetkili_rol_id}> rolüne sahip yetkililer sizinle ilgilenecek.\n\n**Bilet Açılış Nedeni:**\n{self.reason.value}", + color=CYBER_BLUE, + timestamp=datetime.now() + ) + if interaction.client.user.display_avatar: + embed.set_thumbnail(url=interaction.client.user.display_avatar.url) + + embed.add_field(name="Destek ID", value=f"#{ticket_no}", inline=True) + embed.add_field(name="Destek Kategorisi", value=f"{self.category_emoji} {self.category_name}", inline=True) + embed.add_field(name="Öncelik", value=f"{prio_emoji} {prio_name}", inline=True) + embed.add_field(name="Ortalama Yanıt Süresi", value="1 dakika - 2 dakika", inline=False) + embed.add_field(name="Destek Durumu", value="⚪ Yetkili bekleniyor...", inline=False) + + # Bildirim ve Etiketleme + mentions = f"{member.mention}" + if self.yetkili_rol_id: + mentions += f" <@&{self.yetkili_rol_id}>" + + view = TicketControlView() + await channel.send(content=mentions, embed=embed, view=view) + await interaction.followup.send(f"✅ Destek talebiniz oluşturuldu: {channel.mention}", ephemeral=True) + + +# --- TICKET PANEL BÜTONLARI (GİRİŞ PANELDEN SEÇİM) --- +class TicketPanelView(discord.ui.View): + def __init__(self): + super().__init__(timeout=None) + + @discord.ui.button(label="Genel Destek", style=discord.ButtonStyle.primary, custom_id="ticket_cat_genel", emoji="📩") + async def cat_genel(self, interaction: discord.Interaction, button: discord.ui.Button): + cfg = await Database.fetch_one("SELECT * FROM ticket_ayarlar WHERE guild_id = ?", (interaction.guild.id,)) + if not cfg or not cfg.get("aktif"): + await interaction.response.send_message("❌ Ticket sistemi bu sunucuda aktif değil.", ephemeral=True) + return + modal = TicketCreateModal("Genel Destek", "📩", cfg.get("yetkili_rol_id"), cfg.get("kategori_id")) + await interaction.response.send_modal(modal) + + @discord.ui.button(label="Yetkili Alım", style=discord.ButtonStyle.success, custom_id="ticket_cat_yetkili", emoji="🔥") + async def cat_yetkili(self, interaction: discord.Interaction, button: discord.ui.Button): + cfg = await Database.fetch_one("SELECT * FROM ticket_ayarlar WHERE guild_id = ?", (interaction.guild.id,)) + if not cfg or not cfg.get("aktif"): + await interaction.response.send_message("❌ Ticket sistemi bu sunucuda aktif değil.", ephemeral=True) + return + modal = TicketCreateModal("Yetkili Alım", "🔥", cfg.get("yetkili_rol_id"), cfg.get("kategori_id")) + await interaction.response.send_modal(modal) + + @discord.ui.button(label="Ortaklık & Sponsorluk", style=discord.ButtonStyle.secondary, custom_id="ticket_cat_ortaklik", emoji="🤝") + async def cat_ortaklik(self, interaction: discord.Interaction, button: discord.ui.Button): + cfg = await Database.fetch_one("SELECT * FROM ticket_ayarlar WHERE guild_id = ?", (interaction.guild.id,)) + if not cfg or not cfg.get("aktif"): + await interaction.response.send_message("❌ Ticket sistemi bu sunucuda aktif değil.", ephemeral=True) + return + modal = TicketCreateModal("Ortaklık & Sponsorluk", "🤝", cfg.get("yetkili_rol_id"), cfg.get("kategori_id")) + await interaction.response.send_modal(modal) + + +# --- ÖNCELİK DEĞİŞTİRME SELECT MENU --- +class PrioritySelect(discord.ui.Select): + def __init__(self): + options = [ + discord.SelectOption(label="Düşük", value="Düşük", emoji="🟢"), + discord.SelectOption(label="Normal", value="Normal", emoji="🟡"), + discord.SelectOption(label="Yüksek", value="Yüksek", emoji="🟠"), + discord.SelectOption(label="Kritik", value="Kritik", emoji="🔴") + ] + super().__init__(placeholder="Öncelik seviyesini değiştir...", min_values=1, max_values=1, options=options, custom_id="ticket_prio_select") + + async def callback(self, interaction: discord.Interaction): + embed = interaction.message.embeds[0] + prio_val = self.values[0] + + prio_emojis = { + "Düşük": "🟢", + "Normal": "🟡", + "Yüksek": "🟠", + "Kritik": "🔴" + } + + # Öncelik field alanını güncelle + for idx, field in enumerate(embed.fields): + if field.name == "Öncelik": + embed.set_field_at(idx, name="Öncelik", value=f"{prio_emojis[prio_val]} {prio_val}", inline=True) + break + + await interaction.response.edit_message(embed=embed) + + +# --- KANALA EKLENECEK ÜYE MODALI --- +class ManageMembersModal(discord.ui.Modal, title="Üye Ekle / Çıkar"): + def __init__(self, action): + super().__init__() + self.action = action # "add" or "remove" + self.user_id = discord.ui.TextInput( + label="Kullanıcı ID veya Mention", + placeholder="Eklenecek veya çıkarılacak üyenin ID numarasını giriniz...", + required=True + ) + self.add_item(self.user_id) + + async def on_submit(self, interaction: discord.Interaction): + # Üyeyi bulmaya çalışalım + val = self.user_id.value.strip() + uid = "" + for char in val: + if char.isdigit(): + uid += char + try: + member_id = int(uid) + member = interaction.guild.get_member(member_id) + if not member: + member = await interaction.guild.fetch_member(member_id) + except: + await interaction.response.send_message("❌ Geçersiz kullanıcı ID girdiniz.", ephemeral=True) + return + + if self.action == "add": + await interaction.channel.set_permissions(member, view_channel=True, send_messages=True, read_message_history=True, attach_files=True) + await interaction.response.send_message(f"✅ {member.mention} destek kanalına başarıyla eklendi.", ephemeral=False) + else: + await interaction.channel.set_permissions(member, overwrite=None) + await interaction.response.send_message(f"❌ {member.mention} destek kanalından çıkarıldı.", ephemeral=False) + + +# --- KANALA DEVRETME MODALI --- +class AssignTicketModal(discord.ui.Modal, title="Talebi Devret"): + def __init__(self): + super().__init__() + self.target = discord.ui.TextInput( + label="Yetkili Kullanıcı veya Rol ID", + placeholder="Talebin devredileceği kişinin veya rolün ID numarasını yazınız...", + required=True + ) + self.add_item(self.target) + + async def on_submit(self, interaction: discord.Interaction): + val = self.target.value.strip() + target_id = "" + for char in val: + if char.isdigit(): + target_id += char + try: + tid = int(target_id) + role_or_member = interaction.guild.get_role(tid) or interaction.guild.get_member(tid) + if not role_or_member: + role_or_member = await interaction.guild.fetch_member(tid) + except: + await interaction.response.send_message("❌ Geçersiz Kullanıcı veya Rol ID.", ephemeral=True) + return + + await interaction.channel.set_permissions(role_or_member, view_channel=True, send_messages=True, read_message_history=True, manage_messages=True) + await interaction.response.send_message(f"🔄 Destek talebi başarıyla {role_or_member.mention} üzerine devredildi.", ephemeral=False) + + +# --- İÇ NOT MODALI --- +class InternalNoteModal(discord.ui.Modal, title="İç Not Ekle"): + def __init__(self): + super().__init__() + self.note = discord.ui.TextInput( + label="Notunuz", + style=discord.TextStyle.paragraph, + placeholder="Sadece yetkililerin göreceği dahili bir not yazın...", + required=True + ) + self.add_item(self.note) + + async def on_submit(self, interaction: discord.Interaction): + embed = discord.Embed( + title="📝 Dahili Yetkili Notu", + description=self.note.value, + color=CYBER_PURPLE, + timestamp=datetime.now() + ) + embed.set_footer(text=f"Notu Eklendi: {interaction.user.display_name}") + await interaction.response.send_message(embed=embed) + + +# --- BİLET KONTROL PANELİ VİEW (2. ve 3. Ekran Görüntüsü) --- +class TicketControlView(discord.ui.View): + def __init__(self): + super().__init__(timeout=None) + self.add_item(PrioritySelect()) + + @discord.ui.button(label="Ticket Claim", style=discord.ButtonStyle.primary, custom_id="ticket_ctrl_claim", emoji="📥", row=0) + async def btn_claim(self, interaction: discord.Interaction, button: discord.ui.Button): + embed = interaction.message.embeds[0] + # Durumu güncelle + for idx, field in enumerate(embed.fields): + if field.name == "Destek Durumu": + embed.set_field_at(idx, name="Destek Durumu", value=f"📥 Alındı (Yetkili: {interaction.user.mention})", inline=False) + break + await interaction.response.edit_message(embed=embed) + await interaction.followup.send(f"📥 {interaction.user.mention} talebi üstlendi.", ephemeral=False) + + @discord.ui.button(label="Beklemede", style=discord.ButtonStyle.danger, custom_id="ticket_ctrl_pending", emoji="🔴", row=0) + async def btn_pending(self, interaction: discord.Interaction, button: discord.ui.Button): + embed = interaction.message.embeds[0] + for idx, field in enumerate(embed.fields): + if field.name == "Destek Durumu": + embed.set_field_at(idx, name="Destek Durumu", value="🔴 Beklemede...", inline=False) + break + await interaction.response.edit_message(embed=embed) + + @discord.ui.button(label="İnceleniyor", style=discord.ButtonStyle.secondary, custom_id="ticket_ctrl_investigating", emoji="🔍", row=0) + async def btn_investigating(self, interaction: discord.Interaction, button: discord.ui.Button): + embed = interaction.message.embeds[0] + for idx, field in enumerate(embed.fields): + if field.name == "Destek Durumu": + embed.set_field_at(idx, name="Destek Durumu", value="🔍 İnceleniyor...", inline=False) + break + await interaction.response.edit_message(embed=embed) + + @discord.ui.button(label="Çözüldü", style=discord.ButtonStyle.success, custom_id="ticket_ctrl_solved", emoji="✅", row=0) + async def btn_solved(self, interaction: discord.Interaction, button: discord.ui.Button): + embed = interaction.message.embeds[0] + for idx, field in enumerate(embed.fields): + if field.name == "Destek Durumu": + embed.set_field_at(idx, name="Destek Durumu", value="✅ Çözüldü.", inline=False) + break + await interaction.response.edit_message(embed=embed) + + @discord.ui.button(style=discord.ButtonStyle.secondary, custom_id="ticket_ctrl_reset", emoji="🔄", row=0) + async def btn_reset(self, interaction: discord.Interaction, button: discord.ui.Button): + embed = interaction.message.embeds[0] + for idx, field in enumerate(embed.fields): + if field.name == "Destek Durumu": + embed.set_field_at(idx, name="Destek Durumu", value="⚪ Yetkili bekleniyor...", inline=False) + break + await interaction.response.edit_message(embed=embed) + + @discord.ui.button(label="Talebi Kapat", style=discord.ButtonStyle.danger, custom_id="ticket_ctrl_close", emoji="🗑️", row=1) + async def btn_close(self, interaction: discord.Interaction, button: discord.ui.Button): + # Kapatma log kaydı + await Database.execute( + "INSERT INTO ticket_log (guild_id, kanal_id, user_id, durum, timestamp) VALUES (?, ?, ?, ?, ?)", + (interaction.guild.id, interaction.channel.id, interaction.user.id, "kapandi", datetime.now().isoformat()) + ) + await interaction.response.send_message("🔒 Ticket kapatılıyor... Kanal **5 saniye** sonra tamamen silinecek.") + await asyncio.sleep(5) + try: + await interaction.channel.delete(reason=f"Ticket kapatıldı: {interaction.user}") + except: + pass + + @discord.ui.button(label="Bildirim Al", style=discord.ButtonStyle.secondary, custom_id="ticket_ctrl_notif", emoji="🔔", row=1) + async def btn_notif(self, interaction: discord.Interaction, button: discord.ui.Button): + # Kullanıcıyı kanaldan bahsetme listesi veya basit bildirim onayı + await interaction.response.send_message("🔔 Bilet güncellemelerinde size bildirim gönderilecek şekilde işaretlendi.", ephemeral=True) + + @discord.ui.button(label="Üyeleri Yönet", style=discord.ButtonStyle.secondary, custom_id="ticket_ctrl_members", emoji="👤", row=1) + async def btn_members(self, interaction: discord.Interaction, button: discord.ui.Button): + # Üye Ekleme / Çıkarma seçimi için buton view gönderelim + select_view = discord.ui.View(timeout=60) + btn_add = discord.ui.Button(label="Üye Ekle", style=discord.ButtonStyle.success, emoji="➕") + btn_rem = discord.ui.Button(label="Üye Çıkar", style=discord.ButtonStyle.danger, emoji="➖") + + async def add_cb(inter): + await inter.response.send_modal(ManageMembersModal("add")) + async def rem_cb(inter): + await inter.response.send_modal(ManageMembersModal("remove")) + + btn_add.callback = add_cb + btn_rem.callback = rem_cb + select_view.add_item(btn_add) + select_view.add_item(btn_rem) + + await interaction.response.send_message("Lütfen yapmak istediğiniz işlemi seçin:", view=select_view, ephemeral=True) + + @discord.ui.button(label="Transcript", style=discord.ButtonStyle.secondary, custom_id="ticket_ctrl_transcript", emoji="📄", row=1) + async def btn_transcript(self, interaction: discord.Interaction, button: discord.ui.Button): + await interaction.response.defer() + # Kanal mesaj geçmişini okuyalım + chat_log = [] + async for message in interaction.channel.history(limit=500, oldest_first=True): + time_str = message.created_at.strftime("%d.%m.%Y %H:%M") + chat_log.append(f"[{time_str}] {message.author.name}: {message.content}") + + log_content = "\n".join(chat_log) + file_data = io.BytesIO(log_content.encode("utf-8")) + discord_file = discord.File(fp=file_data, filename=f"transcript-{interaction.channel.name}.txt") + + # Log kanalı ayarlanmışsa oraya da gönder + cfg = await Database.fetch_one("SELECT log_kanal_id FROM ticket_ayarlar WHERE guild_id = ?", (interaction.guild.id,)) + if cfg and cfg.get('log_kanal_id'): + log_chan = interaction.guild.get_channel(cfg['log_kanal_id']) + if log_chan: + try: + await log_chan.send(content=f"📄 **{interaction.channel.name}** biletine ait konuşma geçmişi:", file=discord_file) + file_data.seek(0) + except: + pass + + await interaction.followup.send(content="📄 Konuşma dökümü (Transcript) oluşturuldu:", file=discord_file) + + @discord.ui.button(label="Devret", style=discord.ButtonStyle.secondary, custom_id="ticket_ctrl_assign", emoji="🔄", row=2) + async def btn_assign(self, interaction: discord.Interaction, button: discord.ui.Button): + await interaction.response.send_modal(AssignTicketModal()) + + @discord.ui.button(label="İç Not", style=discord.ButtonStyle.secondary, custom_id="ticket_ctrl_internal", emoji="📝", row=2) + async def btn_internal(self, interaction: discord.Interaction, button: discord.ui.Button): + await interaction.response.send_modal(InternalNoteModal()) + + +class Ticket(commands.Cog): + def __init__(self, bot): + self.bot = bot + + @commands.hybrid_command(name="ticket-kurulum", description="Ticket sistemini kurar.") + @commands.has_permissions(administrator=True) + @app_commands.describe( + kategori="Ticket kanallarının oluşturulacağı kategori", + yetkili_rol="Ticket'lara erişimi olacak yetkili rol", + log_kanal="Ticket log kanalı", + panel_kanal="Ticket panel butonunun gönderileceği kanal" + ) + async def ticket_kurulum( + self, ctx: commands.Context, + kategori: discord.CategoryChannel, + yetkili_rol: discord.Role, + log_kanal: discord.TextChannel, + panel_kanal: discord.TextChannel + ): + if ctx.interaction: + await ctx.defer() + + await Database.execute( + """INSERT OR REPLACE INTO ticket_ayarlar + (guild_id, kategori_id, log_kanal_id, yetkili_rol_id, panel_kanal_id, aktif) + VALUES (?, ?, ?, ?, ?, 1)""", + (ctx.guild.id, kategori.id, log_kanal.id, yetkili_rol.id, panel_kanal.id) + ) + + # Panel mesajı gönder (1. Ekran Görüntüsü tasarımı) + embed = discord.Embed( + title="🛠️ Destek Talebi Oluştur", + description="Bilet oluşturmadan önce aşağıdaki kuralları okuyunuz. Aşağıdaki butonlardan size uygun kategoriyi seçerek destek talebi oluşturabilirsiniz.\n\n" + "**Genel Kurallar:**\n" + "• Biletinizi **24 saat** içerisinde kontrol etmek zorundasınız.\n" + "• Yönetim üyelerine özel mesaj atmaktan kaçının.\n" + "• Sorunlarınızı bilet aracılığıyla iletişim kurun.\n\n" + "**Sorun Bildirimi:**\n" + "• Sorunu detaylı bir şekilde açıklayın.\n" + "• Gerekli bilgileri eksiksiz paylaşın.\n\n" + "3 kategori mevcut • Ortalama yanıt süresi: 1 dakika - 2 dakika", + color=CYBER_BLUE, + timestamp=datetime.now() + ) + embed.set_author(name="Araz Ticket", icon_url=ctx.guild.icon.url if ctx.guild.icon else None) + embed.set_thumbnail(url=ctx.guild.icon.url if ctx.guild.icon else "") + + view = TicketPanelView() + panel_msg = await panel_kanal.send(embed=embed, view=view) + + await Database.execute( + "UPDATE ticket_ayarlar SET panel_mesaj_id = ? WHERE guild_id = ?", + (panel_msg.id, ctx.guild.id) + ) + + await ctx.send(f"✅ Ticket sistemi kuruldu! Panel {panel_kanal.mention} kanalına gönderildi.") + + @commands.hybrid_command(name="ticket-kapat", description="Ticket sistemini devre dışı bırakır.") + @commands.has_permissions(administrator=True) + async def ticket_kapat(self, ctx: commands.Context): + if ctx.interaction: + await ctx.defer() + await Database.execute("UPDATE ticket_ayarlar SET aktif = 0 WHERE guild_id = ?", (ctx.guild.id,)) + await ctx.send("✅ Ticket sistemi devre dışı bırakıldı.") + + @commands.hybrid_command(name="ticket-log", description="Ticket log kayıtlarını gösterir.") + @commands.has_permissions(manage_guild=True) + async def ticket_log(self, ctx: commands.Context): + if ctx.interaction: + await ctx.defer() + records = await Database.fetch_all( + "SELECT * FROM ticket_log WHERE guild_id = ? ORDER BY id DESC LIMIT 20", + (ctx.guild.id,) + ) + if not records: + await ctx.send("ℹ️ Ticket log kaydı bulunmamaktadır.") + return + + embed = discord.Embed(title="🎫 Ticket Log Kayıtları", color=CYBER_BLUE, timestamp=datetime.now()) + log_str = "" + for r in records: + tarih = datetime.fromisoformat(r['timestamp']).strftime("%d.%m.%Y %H:%M") + log_str += f"• `{tarih}` | <@{r['user_id']}> | Kanal: `{r['kanal_id']}` | **{r['durum']}**\n" + embed.description = log_str[:4000] + await ctx.send(embed=embed) + + +async def setup(bot): + await bot.add_cog(Ticket(bot)) + bot.add_view(TicketPanelView()) + bot.add_view(TicketControlView()) diff --git a/cogs/timezone.py b/cogs/timezone.py new file mode 100644 index 0000000000000000000000000000000000000000..120352f53c257a79f1303a6e270d8a09b789cb71 --- /dev/null +++ b/cogs/timezone.py @@ -0,0 +1,182 @@ +import discord +from discord.ext import commands +from discord import app_commands +from datetime import datetime, timezone, timedelta + +# Windows'ta tzdata eksik olabilir. Önce zoneinfo ile dene, yoksa pytz, yoksa UTC offset map'i +try: + from zoneinfo import ZoneInfo + _ZONEINFO_OK = True +except Exception: + _ZONEINFO_OK = False + +try: + import pytz + _PYTZ_OK = True +except Exception: + _PYTZ_OK = False + +CYBER_BLUE = discord.Color.from_rgb(52, 152, 219) +CYBER_GOLD = discord.Color.from_rgb(241, 196, 15) + +# Yaygın şehirler ve timezone ID'leri +SEHIR_ZAMAN_DILIMI = { + "istanbul": "Europe/Istanbul", + "new york": "America/New_York", + "los angeles": "America/Los_Angeles", + "london": "Europe/London", + "paris": "Europe/Paris", + "berlin": "Europe/Berlin", + "moscow": "Europe/Moscow", + "dubai": "Asia/Dubai", + "tokyo": "Asia/Tokyo", + "seoul": "Asia/Seoul", + "shanghai": "Asia/Shanghai", + "singapore": "Asia/Singapore", + "sydney": "Australia/Sydney", + "mumbai": "Asia/Kolkata", + "delhi": "Asia/Kolkata", + "cairo": "Africa/Cairo", + "toronto": "America/Toronto", + "mexico city": "America/Mexico_City", + "sao paulo": "America/Sao_Paulo", + "amsterdam": "Europe/Amsterdam", + "madrid": "Europe/Madrid", + "rome": "Europe/Rome", + "stockholm": "Europe/Stockholm", + "utc": "UTC", + "ankara": "Europe/Istanbul", + "izmir": "Europe/Istanbul", + "baku": "Asia/Baku", + "riyadh": "Asia/Riyadh", + "los santos": "America/Los_Angeles", + "chicago": "America/Chicago", + "denver": "America/Denver", + "phoenix": "America/Phoenix", + "hawaii": "Pacific/Honolulu", + "auckland": "Pacific/Auckland", + "johannesburg": "Africa/Johannesburg", + "bangkok": "Asia/Bangkok", + "jakarta": "Asia/Jakarta", + "manila": "Asia/Manila", + "hong kong": "Asia/Hong_Kong", + "tel aviv": "Asia/Jerusalem", + "kiev": "Europe/Kyiv", + "kyiv": "Europe/Kyiv", +} + +# UTC ofsetleri (saat cinsinden) — ZoneInfo/pytz yoksa yedek olarak +UTC_OFFSETS = { + "istanbul": 3, "ankara": 3, "izmir": 3, "baku": 4, + "london": 0, "paris": 1, "berlin": 1, "amsterdam": 1, "madrid": 1, + "rome": 1, "stockholm": 1, "moscow": 3, "kyiv": 2, "kiev": 2, + "dubai": 4, "riyadh": 3, "cairo": 2, "johannesburg": 2, + "new york": -5, "toronto": -5, "chicago": -6, "mexico city": -6, + "denver": -7, "phoenix": -7, "los angeles": -8, + "sao paulo": -3, + "mumbai": 5, "delhi": 5, "bangkok": 7, "jakarta": 7, + "hong kong": 8, "shanghai": 8, "singapore": 8, "manila": 8, + "tokyo": 9, "seoul": 9, + "sydney": 11, "auckland": 13, + "hawaii": -10, + "tel aviv": 2, + "utc": 0, +} + + +def _get_now_in_tz(tz_id: str, sehir_lower: str): + """Verilen timezone ID'sine göre şimdiki zamanı döndürür. Yedek olarak UTC offset kullanır.""" + if _ZONEINFO_OK: + try: + tz = ZoneInfo(tz_id) + return datetime.now(tz) + except Exception: + pass + if _PYTZ_OK: + try: + tz = pytz.timezone(tz_id) + return datetime.now(tz) + except Exception: + pass + # Yedek: UTC ofseti ile + offset = UTC_OFFSETS.get(sehir_lower, 0) + return datetime.now(timezone.utc) + timedelta(hours=offset) + + +class TimeZoneCog(commands.Cog): + def __init__(self, bot): + self.bot = bot + + @commands.command(name="saat", description="Belirli bir şehrin şu anki saatini gösterir.") + async def saat(self, ctx: commands.Context, *, sehir: str): + + sehir_lower = sehir.lower().strip() + tz_id = SEHIR_ZAMAN_DILIMI.get(sehir_lower) + + if not tz_id: + # Yıldız eşleştirme: kısmi eşleşme + for key, val in SEHIR_ZAMAN_DILIMI.items(): + if sehir_lower in key or key in sehir_lower: + tz_id = val + sehir_lower = key + break + + if not tz_id: + await ctx.send( + f"❌ `{sehir}` için zaman dilimi bulunamadı.\n" + f"Örnek şehirler: `istanbul`, `london`, `tokyo`, `new york`, `dubai`, `moscow`, `sydney`, `los angeles`", + ephemeral=True + ) + return + + try: + now = _get_now_in_tz(tz_id, sehir_lower) + tarih_str = now.strftime("%d.%m.%Y") + saat_str = now.strftime("%H:%M:%S") + gun_str = ["Pzt", "Sal", "Çar", "Per", "Cum", "Cmt", "Paz"][now.weekday()] + + embed = discord.Embed( + title=f"🕐 {sehir_lower.title()}", + color=CYBER_GOLD, + timestamp=datetime.now() + ) + embed.add_field(name="🕒 Saat", value=f"**{saat_str}**", inline=True) + embed.add_field(name="📅 Gün", value=f"**{gun_str}**", inline=True) + embed.add_field(name="📆 Tarih", value=f"**{tarih_str}**", inline=True) + embed.add_field(name="Zaman Dilimi", value=f"`{tz_id}`", inline=False) + embed.set_footer(text="ArazAI Saat Sorgusu") + await ctx.send(embed=embed) + + except Exception as e: + await ctx.send(f"❌ Hata: {e}", ephemeral=True) + + @commands.command(name="saat-karşılaştır", description="Birden fazla şehrin şimdiki saatlerini karşılaştırır.") + async def saat_karsilastir(self, ctx: commands.Context, *, sehirler: str): + + sehir_listesi = [s.strip() for s in sehirler.split("|") if s.strip()] + if len(sehir_listesi) < 2: + await ctx.send("❌ En az 2 şehir girin. (örn: istanbul | tokyo | london)", ephemeral=True) + return + + embed = discord.Embed(title="🕐 Saat Karşılaştırma", color=CYBER_GOLD, timestamp=datetime.now()) + embed.set_footer(text="ArazAI Saat Sorgusu") + + for sehir in sehir_listesi[:10]: + sehir_lower = sehir.lower() + tz_id = SEHIR_ZAMAN_DILIMI.get(sehir_lower) + if not tz_id: + embed.add_field(name=f"❌ {sehir}", value="Bulunamadı", inline=True) + continue + try: + now = _get_now_in_tz(tz_id, sehir_lower) + saat_str = now.strftime("%H:%M") + gun_str = ["Pzt", "Sal", "Çar", "Per", "Cum", "Cmt", "Paz"][now.weekday()] + embed.add_field(name=f"📍 {sehir.title()}", value=f"**{saat_str}** ({gun_str})", inline=True) + except: + embed.add_field(name=f"❌ {sehir}", value="Hata", inline=True) + + await ctx.send(embed=embed) + + +async def setup(bot): + await bot.add_cog(TimeZoneCog(bot)) diff --git a/cogs/translate.py b/cogs/translate.py new file mode 100644 index 0000000000000000000000000000000000000000..adc1432cca93432a9659c32491a94eb2a7a99968 --- /dev/null +++ b/cogs/translate.py @@ -0,0 +1,98 @@ +import discord +from discord.ext import commands +from discord import app_commands +from datetime import datetime +import aiohttp +import json +import os + +CYBER_BLUE = discord.Color.from_rgb(52, 152, 219) +CYBER_GREEN = discord.Color.from_rgb(46, 204, 113) + +# ISO dil adları için kısa harita +DIL_ADLARI = { + "tr": "Türkçe", "en": "İngilizce", "de": "Almanca", "fr": "Fransızca", + "es": "İspanyolca", "it": "İtalyanca", "ru": "Rusça", "ar": "Arapça", + "zh": "Çince", "ja": "Japonca", "ko": "Korece", "pt": "Portekizce", + "nl": "Hollandaca", "pl": "Lehçe", "sv": "İsveççe", "hi": "Hintçe", + "auto": "Otomatik" +} + + +class Translate(commands.Cog): + def __init__(self, bot): + self.bot = bot + self.gemini_api_key = "" + config_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), "config.json") + if os.path.exists(config_path): + try: + with open(config_path, "r", encoding="utf-8") as f: + cfg = json.load(f) + self.gemini_api_key = cfg.get("gemini_api_key", "") + except: + pass + + async def _translate_via_gemini(self, text: str, source: str, target: str) -> str: + if not self.gemini_api_key: + return "❌ ArazAI API anahtarı yapılandırılmamış." + + hedef_ad = DIL_ADLARI.get(target, target) + kaynak_ad = DIL_ADLARI.get(source, source) if source != "auto" else "dili otomatik tespit et" + + prompt = ( + f"Aşağıdaki metni {kaynak_ad}'dan {hedef_ad}'a çevir. " + "Sadece çevrilmiş metni döndür. Ekstra açıklama yapma.\n\n" + f"Metin: {text}" + ) + + url = f"https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent?key={self.gemini_api_key}" + payload = { + "contents": [{"parts": [{"text": prompt}]}], + "generationConfig": {"temperature": 0.2, "maxOutputTokens": 1500} + } + + try: + async with aiohttp.ClientSession() as session: + async with session.post(url, json=payload, timeout=20) as resp: + if resp.status == 200: + data = await resp.json() + candidates = data.get("candidates", []) + if candidates: + parts = candidates[0].get("content", {}).get("parts", []) + if parts: + return parts[0].get("text", "").strip() + return "❌ Çeviri alınamadı." + else: + err_text = await resp.text() + print(f"ArazAI API hatası ({resp.status}): {err_text}") + return "❌ Çeviri servisi hatası." + except Exception as e: + print(f"ArazAI çeviri hatası: {e}") + return "❌ Çeviri servisine ulaşılamadı." + + @commands.command(name="çevir", description="Metni ArazAI ile başka bir dile çevirir.") + async def cevir( + self, ctx: commands.Context, + metin: str, + hedef_dil: str, + kaynak_dil: str = "auto" + ): + + ceviri = await self._translate_via_gemini(metin, kaynak_dil.lower().strip(), hedef_dil.lower().strip()) + + kaynak_ad = DIL_ADLARI.get(kaynak_dil.lower(), kaynak_dil) if kaynak_dil != "auto" else "Otomatik" + hedef_ad = DIL_ADLARI.get(hedef_dil.lower(), hedef_dil) + + embed = discord.Embed( + title="🌐 Çeviri", + color=CYBER_BLUE, + timestamp=datetime.now() + ) + embed.add_field(name=f"🔗 {kaynak_ad}", value=metin[:1024], inline=False) + embed.add_field(name=f"🎯 {hedef_ad}", value=ceviri[:1024], inline=False) + embed.set_footer(text=f"İstek: {ctx.author.display_name} • ArazAI Çeviri") + await ctx.send(embed=embed) + + +async def setup(bot): + await bot.add_cog(Translate(bot)) diff --git a/cogs/verification.py b/cogs/verification.py new file mode 100644 index 0000000000000000000000000000000000000000..348e49a181b88481b504a17c5a4d22de51c311f5 --- /dev/null +++ b/cogs/verification.py @@ -0,0 +1,162 @@ +import discord +from discord.ext import commands +from discord import app_commands +from database import Database +import random +import string + +CYBER_GREEN = discord.Color.from_rgb(46, 204, 113) +CYBER_BLUE = discord.Color.from_rgb(52, 152, 219) +CYBER_RED = discord.Color.from_rgb(231, 76, 60) + +# Aktif captcha kodlarını tutar: {user_id: code}. Veritabanına da kaydedebiliriz ama performans için memory. +ACTIVE_CAPTCHAS = {} + + +class Verification(commands.Cog): + def __init__(self, bot): + self.bot = bot + + @commands.hybrid_command(name="doğrulama-ayarla", description="Doğrulama (captcha) sistemini ayarlar.") + @commands.has_permissions(administrator=True) + @app_commands.describe( + kanal="Doğrulama mesajının gönderileceği kanal", + rol="Doğrulayanlara verilecek rol" + ) + async def dogrulama_ayarla(self, ctx: commands.Context, kanal: discord.TextChannel, rol: discord.Role): + if ctx.interaction: + await ctx.defer() + + await Database.execute( + """INSERT OR REPLACE INTO dogrulama_ayarlar (guild_id, kanal_id, rol_id, aktif) + VALUES (?, ?, ?, 1)""", + (ctx.guild.id, kanal.id, rol.id) + ) + + embed = discord.Embed( + title="✅ Hesabını Doğrula", + description=( + "Sunucunun tüm içeriklerine erişmek için aşağıdaki **Doğrula** butonuna tıklayın.\n" + "Açılan pencerede göreceğiniz kodu yazarak doğrulama işlemini tamamlayabilirsiniz." + ), + color=CYBER_GREEN, + ) + embed.set_footer(text="ArazAI Doğrulama Sistemi") + + view = discord.ui.View(timeout=None) + view.add_item(discord.ui.Button( + label="Doğrula", + style=discord.ButtonStyle.success, + custom_id="verify_btn", + emoji="✅" + )) + + await kanal.send(embed=embed, view=view) + await ctx.send(f"✅ Doğrulama sistemi {kanal.mention} kanalında kuruldu. Onaylı rol: {rol.mention}") + + @commands.hybrid_command(name="doğrulama-kapat", description="Doğrulama sistemini devre dışı bırakır.") + @commands.has_permissions(administrator=True) + async def dogrulama_kapat(self, ctx: commands.Context): + if ctx.interaction: + await ctx.defer() + await Database.execute("UPDATE dogrulama_ayarlar SET aktif = 0 WHERE guild_id = ?", (ctx.guild.id,)) + await ctx.send("✅ Doğrulama sistemi devre dışı bırakıldı.") + + @commands.Cog.listener() + async def on_interaction(self, interaction: discord.Interaction): + if interaction.type != discord.InteractionType.component: + return + custom_id = interaction.data.get("custom_id", "") + if not custom_id.startswith("verify_btn"): + return + + if not await Database.modul_aktif_mi(interaction.guild_id, "Verification"): + try: + await interaction.response.send_message("❌ Doğrulama sistemi şu an devre dışı.", ephemeral=True) + except: + pass + return + + cfg = await Database.fetch_one( + "SELECT * FROM dogrulama_ayarlar WHERE guild_id = ? AND aktif = 1", + (interaction.guild.id,) + ) + if not cfg: + await interaction.response.send_message("❌ Doğrulama sistemi aktif değil.", ephemeral=True) + return + + # Yeni captcha kodu üret + code = ''.join(random.choices(string.ascii_uppercase + string.digits, k=6)) + ACTIVE_CAPTCHAS[interaction.user.id] = code + + modal = CaptchaModal(code, cfg['rol_id']) + await interaction.response.send_modal(modal) + + @commands.Cog.listener() + async def on_member_join(self, member: discord.Member): + if not await Database.modul_aktif_mi(member.guild.id, "Verification"): + return + cfg = await Database.fetch_one( + "SELECT * FROM dogrulama_ayarlar WHERE guild_id = ? AND aktif = 1", + (member.guild.id,) + ) + if not cfg: + return + + # Doğrulama rolü olmayan kişiden tüm rolleri al? Hayır—varsayılan @everyone dışındaki rolleri sadece almayalım + # Sadece kullanıcıya DM bilgilendirme gönderelim + try: + await member.send( + f"Merhaba {member.mention}, **{member.guild.name}** sunucusuna hoş geldin!\n" + f"Lütfen sunucudaki tüm içeriklere erişebilmek için doğrulama kanalındaki butonu kullanarak hesabını doğrula." + ) + except: + pass + + +class CaptchaModal(discord.ui.Modal, title="Hesap Doğrulama - Captcha"): + code_input = discord.ui.TextInput( + label="Doğrulama Kodu", + placeholder="Görseldeki 6 haneli kodu girin ( büyük/küçük farketmez )", + required=True, + min_length=6, + max_length=6 + ) + + def __init__(self, code, rol_id): + super().__init__() + self.expected_code = code + self.rol_id = rol_id + + async def on_submit(self, interaction: discord.Interaction): + if self.code_input.value.upper().strip() == self.expected_code: + guild = interaction.guild + rol = guild.get_role(self.rol_id) if guild else None + if rol: + try: + await interaction.user.add_roles(rol, reason="Doğrulama başarılı") + except: + pass + + ACTIVE_CAPTCHAS.pop(interaction.user.id, None) + + await interaction.response.send_message( + content=f"✅ Doğrulama başarılı! Hoş geldin {interaction.user.mention}.", + ephemeral=True + ) + else: + # Yeni kod üret + import random, string + new_code = ''.join(random.choices(string.ascii_uppercase + string.digits, k=6)) + ACTIVE_CAPTCHAS[interaction.user.id] = new_code + + embed = discord.Embed( + title="❌ Hatalı Kod!", + description=f"Girdiğiniz kod hatalı. Yeni kodunuz: **`{new_code}`**", + color=CYBER_RED + ) + await interaction.response.send_message(embed=embed, ephemeral=True) + + +async def setup(bot): + await bot.add_cog(Verification(bot)) diff --git a/cogs/weather.py b/cogs/weather.py new file mode 100644 index 0000000000000000000000000000000000000000..25eb65b60b243f5fa25fd078b91b365149f2ce4d --- /dev/null +++ b/cogs/weather.py @@ -0,0 +1,160 @@ +import discord +from discord.ext import commands +from discord import app_commands +from datetime import datetime +import aiohttp + +CYBER_BLUE = discord.Color.from_rgb(52, 152, 219) +CYBER_ORANGE = discord.Color.from_rgb(230, 126, 34) + +OPEN_METEO_URL = "https://api.open-meteo.com/v1/forecast" + +# Open-Meteo weathercode → (emoji, Türkçe açıklama) +WMO_KODLARI = { + 0: ("☀️", "Açık"), + 1: ("🌤️", "Az bulutlu"), + 2: ("⛅", "Parçalı bulutlu"), + 3: ("☁️", "Kapalı"), + 45: ("🌫️", "Sisli"), + 48: ("🌫️", "Yoğun sis"), + 51: ("🌦️", "Hafif çisenti"), + 53: ("🌦️", "Çisenti"), + 55: ("🌧️", "Yoğun çisenti"), + 56: ("🌧️", "Donan çisenti"), + 57: ("🌧️", "Yoğun donan çisenti"), + 61: ("🌧️", "Hafif yağmur"), + 63: ("🌧️", "Yağmur"), + 65: ("🌧️", "Yoğun yağmur"), + 66: ("🌧️", "Donan yağmur"), + 67: ("🌧️", "Yoğun donan yağmur"), + 71: ("🌨️", "Hafif kar"), + 73: ("❄️", "Kar"), + 75: ("❄️", "Yoğun kar"), + 77: ("❄️", "Kar taneleri"), + 80: ("🌧️", "Sağanak"), + 81: ("🌧️", "Yoğun sağanak"), + 82: ("⛈️", "Sel yağmur"), + 85: ("🌨️", "Hafif kar sağanak"), + 86: ("❄️", "Yoğun kar sağanak"), + 95: ("⛈️", "Gök gürültülü fırtına"), + 96: ("⛈️", "Dolu ile fırtına"), + 99: ("⛈️", "Şiddetli dolu ile fırtına"), +} + + +async def _geocode_city(city_name: str): + """Open-Meteo Geocoding API ile şehir koordinatlarını bulur""" + try: + url = f"https://geocoding-api.open-meteo.com/v1/search?name={city_name}&count=1&language=tr&format=json" + async with aiohttp.ClientSession() as session: + async with session.get(url, timeout=15) as resp: + if resp.status != 200: + return None + data = await resp.json() + results = data.get("results", []) + if not results: + return None + return results[0] + except: + return None + + +async def _get_weather(city_name: str): + try: + geo = await _geocode_city(city_name) + if not geo: + return None + + lat = geo.get("latitude") + lon = geo.get("longitude") + name = geo.get("name", city_name) + country = geo.get("country", "") + admin = geo.get("admin1", "") + + url = ( + f"{OPEN_METEO_URL}?latitude={lat}&longitude={lon}" + "¤t=temperature_2m,relative_humidity_2m,apparent_temperature,weather_code,wind_speed_10m" + "&daily=temperature_2m_max,temperature_2m_min,weather_code" + "&timezone=auto&forecast_days=3" + ) + async with aiohttp.ClientSession() as session: + async with session.get(url, timeout=15) as resp: + if resp.status != 200: + return None + data = await resp.json() + data["_name"] = name + data["_country"] = country + data["_admin"] = admin + return data + except: + return None + + +class Weather(commands.Cog): + def __init__(self, bot): + self.bot = bot + + @commands.command(name="hava", description="Bir şehir için hava durumu gösterir.") + async def hava(self, ctx: commands.Context, *, sehir: str): + if ctx.interaction: + await ctx.defer() + + data = await _get_weather(sehir) + if not data: + await ctx.send(f"❌ `{sehir}` şehri bulunamadı veya hava durumu alınamadı.", ephemeral=True) + return + + current = data.get("current", {}) + daily = data.get("daily", {}) + name = data.get("_name", sehir) + country = data.get("_country", "") + region = data.get("_admin", "") + + temp = current.get("temperature_2m", 0) + apparent = current.get("apparent_temperature", 0) + humidity = current.get("relative_humidity_2m", 0) + wind_speed = current.get("wind_speed_10m", 0) + code = current.get("weather_code", 0) + + wmo = WMO_KODLARI.get(code, ("❓", "Bilinmiyor")) + emoji, aciklama = wmo + + baslik = f"{name}" + if country: + baslik += f", {country}" + if region and region != name: + baslik += f" ({region})" + + embed = discord.Embed( + title=f"🌤️ {baslik}", + color=CYBER_BLUE, + timestamp=datetime.now() + ) + embed.add_field(name="🌡️ Sıcaklık", value=f"**{temp}°C**", inline=True) + embed.add_field(name="🤗 Hissedilen", value=f"**{apparent}°C**", inline=True) + embed.add_field(name="💧 Nem", value=f"**{humidity}%**", inline=True) + embed.add_field(name="💨 Rüzgar", value=f"**{wind_speed} km/s**", inline=True) + embed.add_field(name="📎 Durum", value=f"**{emoji} {aciklama}**", inline=False) + + if daily and "time" in daily: + tahmin_str = "" + time_list = daily.get("time", []) + max_list = daily.get("temperature_2m_max", []) + min_list = daily.get("temperature_2m_min", []) + code_list = daily.get("weather_code", []) + + for i, date_str in enumerate(time_list): + max_t = max_list[i] if i < len(max_list) else "—" + min_t = min_list[i] if i < len(min_list) else "—" + c = code_list[i] if i < len(code_list) else 0 + em = WMO_KODLARI.get(c, ("❓",))[0] + tahmin_str += f"📅 **{date_str}** — {em} **{min_t}°C** / **{max_t}°C**\n" + + embed.add_field(name="🗓️ 3 Günlük Tahmin", value=tahmin_str, inline=False) + + embed.set_footer(text="ArazAI Hava Durumu • Veri: Open-Meteo") + await ctx.send(embed=embed) + + +async def setup(bot): + await bot.add_cog(Weather(bot)) diff --git a/cogs/welcome.py b/cogs/welcome.py new file mode 100644 index 0000000000000000000000000000000000000000..576944568243f8bd3f1e1e0d55344641aab50ab6 --- /dev/null +++ b/cogs/welcome.py @@ -0,0 +1,127 @@ +import discord +from discord.ext import commands +from discord import app_commands +from datetime import datetime +from database import Database + +CYBER_GREEN = discord.Color.from_rgb(46, 204, 113) +CYBER_RED = discord.Color.from_rgb(231, 76, 60) +CYBER_BLUE = discord.Color.from_rgb(52, 152, 219) + +class Welcome(commands.Cog): + def __init__(self, bot): + self.bot = bot + + @commands.hybrid_command(name="karşılama-ayarla", description="Hoş geldin mesajı kanalını ayarlar.") + @commands.has_permissions(administrator=True) + @app_commands.describe(kanal="Hoş geldin mesajlarının gönderileceği kanal") + async def karsilama_ayarla(self, ctx: commands.Context, kanal: discord.TextChannel): + if ctx.interaction: + await ctx.defer() + await Database.execute( + """INSERT OR REPLACE INTO welcome_ayarlar (guild_id, kanal_id, mesaj, ayrilma_mesaj, aktif) + VALUES (?, ?, ?, ?, 1)""", + (ctx.guild.id, kanal.id, "default", "{user} aramızdan ayrıldı. Görüşmek üzere! 👋") + ) + await ctx.send(f"✅ Karşılama sistemi {kanal.mention} kanalına ayarlandı.") + + @commands.hybrid_command(name="karşılama-kapat", description="Karşılama sistemini devre dışı bırakır.") + @commands.has_permissions(administrator=True) + async def karsilama_kapat(self, ctx: commands.Context): + if ctx.interaction: + await ctx.defer() + await Database.execute("UPDATE welcome_ayarlar SET aktif = 0 WHERE guild_id = ?", (ctx.guild.id,)) + await ctx.send("✅ Karşılama sistemi devre dışı bırakıldı.") + + @commands.hybrid_command(name="karşılama-test", description="Karşılama mesajını test eder.") + @commands.has_permissions(administrator=True) + async def karsilama_test(self, ctx: commands.Context): + cfg = await Database.fetch_one("SELECT * FROM welcome_ayarlar WHERE guild_id = ? AND aktif = 1", (ctx.guild.id,)) + if not cfg: + await ctx.send("❌ Karşılama sistemi aktif değil.", ephemeral=True) + return + + embed = discord.Embed( + title="✨ ARAZ Public Sunucusuna Hoş Geldin! 👋", + description=( + f"🌙 **[ Burası neresi? ]** Burası **ARAZ Public**! Sohbetin, eğlencenin ve yeni dostlukların buluştuğu yer. " + f"İster sohbet et, ister oyun oyna, ister yeni insanlarla tanış; burada herkese göre bir köşe var. " + f"⚠️ Takılmaya başlamadan önce <#1442538870427095071> kanalına göz atmayı unutma. Sunucuda bulunan herkes kuralları kabul etmiş sayılır.\n\n" + f"🤝 **[ Ben partner için geldim! ]** Doğru yerdesin! Partner yapmak istiyorsan <#1474298692566782058> kanalını ziyaret edebilirsin. " + f"Kapımız her zaman açık, belirli bir süre sınırımız yok.\n\n" + f"🎨 Renk rolü almak için: ➜ <#1446021904728326304>\n" + f"🎮 Oyun oynayacak kişiler arıyorsan: ➜ <#1510961114647629844> ➜ <#1444345985726943303>\n\n" + f"✨ Kendine bir yer bul, insanlarla tanış ve eğlencenin tadını çıkar!\n\n" + f"Hoş geldin {ctx.author.mention}! <@1506647746860552282>" + ), + color=CYBER_BLUE, + timestamp=datetime.now() + ) + embed.set_thumbnail(url=ctx.author.display_avatar.url) + gif_url = "https://media.discordapp.net/attachments/1444004543015944375/1486037657367875656/GIF_20260107_025154_498.gif?width=376&height=211" + embed.set_image(url=gif_url) + embed.set_footer(text=f"ArazAI Hoş Geldin Sistemi • Üye Sırası: #{ctx.guild.member_count}") + + await ctx.send(content=f"{ctx.author.mention}", embed=embed) + + @commands.Cog.listener() + async def on_member_join(self, member: discord.Member): + if not await Database.modul_aktif_mi(member.guild.id, "Welcome"): + return + cfg = await Database.fetch_one("SELECT * FROM welcome_ayarlar WHERE guild_id = ? AND aktif = 1", (member.guild.id,)) + kanal_id = cfg['kanal_id'] if cfg else 1444004543015944375 + kanal = member.guild.get_channel(kanal_id) + if not kanal: + return + + embed = discord.Embed( + title="✨ ARAZ Public Sunucusuna Hoş Geldin! 👋", + description=( + f"🌙 **[ Burası neresi? ]** Burası **ARAZ Public**! Sohbetin, eğlencenin ve yeni dostlukların buluştuğu yer. " + f"İster sohbet et, ister oyun oyna, ister yeni insanlarla tanış; burada herkese göre bir köşe var. " + f"⚠️ Takılmaya başlamadan önce <#1442538870427095071> kanalına göz atmayı unutma. Sunucuda bulunan herkes kuralları kabul etmiş sayılır.\n\n" + f"🤝 **[ Ben partner için geldim! ]** Doğru yerdesin! Partner yapmak istiyorsan <#1474298692566782058> kanalını ziyaret edebilirsin. " + f"Kapımız her zaman açık, belirli bir süre sınırımız yok.\n\n" + f"🎨 Renk rolü almak için: ➜ <#1446021904728326304>\n" + f"🎮 Oyun oynayacak kişiler arıyorsan: ➜ <#1510961114647629844> ➜ <#1444345985726943303>\n\n" + f"✨ Kendine bir yer bul, insanlarla tanış ve eğlencenin tadını çıkar!\n\n" + f"Hoş geldin {member.mention}! <@1506647746860552282>" + ), + color=CYBER_BLUE, + timestamp=datetime.now() + ) + embed.set_thumbnail(url=member.display_avatar.url) + gif_url = "https://media.discordapp.net/attachments/1444004543015944375/1486037657367875656/GIF_20260107_025154_498.gif?width=376&height=211" + embed.set_image(url=gif_url) + embed.set_footer(text=f"ArazAI Hoş Geldin Sistemi • Üye Sırası: #{member.guild.member_count}") + + try: + await kanal.send(content=f"{member.mention}", embed=embed) + except: + pass + + @commands.Cog.listener() + async def on_member_remove(self, member: discord.Member): + if not await Database.modul_aktif_mi(member.guild.id, "Welcome"): + return + cfg = await Database.fetch_one("SELECT * FROM welcome_ayarlar WHERE guild_id = ? AND aktif = 1", (member.guild.id,)) + kanal_id = cfg['kanal_id'] if cfg else 1444004543015944375 + kanal = member.guild.get_channel(kanal_id) + if not kanal: + return + ayrilma_msg = cfg.get('ayrilma_mesaj', '{user} aramızdan ayrıldı. 👋') + msg = ayrilma_msg.replace("{user}", member.mention).replace("{server}", member.guild.name).replace("{member_count}", str(member.guild.member_count)) + embed = discord.Embed( + title="👋 Güle Güle!", + description=msg, + color=CYBER_RED, + timestamp=datetime.now() + ) + embed.set_thumbnail(url=member.display_avatar.url) + try: + await kanal.send(embed=embed) + except: + pass + +async def setup(bot): + await bot.add_cog(Welcome(bot)) diff --git a/config.json b/config.json new file mode 100644 index 0000000000000000000000000000000000000000..ee0e2d255cd1f5305535867a5f83de4a2f3e0fee --- /dev/null +++ b/config.json @@ -0,0 +1,6 @@ +{ + "discord_bot_token": "MTQ0MjkyMjgwNDk3Nzg2MDYyOA.Gg6UYk.UIPjrJ2Hk_bhdz4JVCgMDmRmX0jQ0Ee-yb9qTU", + "gemini_api_key": "AIzaSyCthc2flK7chxkuG0xIsPPJIVeNbUjGXYk", + "groq_api_key": "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", + "prefix": "!" +} \ No newline at end of file diff --git a/database.py b/database.py new file mode 100644 index 0000000000000000000000000000000000000000..ddaf89fe8d10881504c841a41d58e94bb31b669a --- /dev/null +++ b/database.py @@ -0,0 +1,687 @@ +import sqlite3 +import os +import asyncio +from datetime import datetime + +DB_PATH = os.path.join(os.path.dirname(__file__), "hilman_bot.db") + +def dict_factory(cursor, row): + d = {} + for idx, col in enumerate(cursor.description): + d[col[0]] = row[idx] + return d + +def _init_db(): + conn = sqlite3.connect(DB_PATH) + cursor = conn.cursor() + + # 1. Uyarilar Tablosu + cursor.execute(""" + CREATE TABLE IF NOT EXISTS uyarilar ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + user_id INTEGER, + guild_id INTEGER, + moderator_id INTEGER, + sebep TEXT, + tarih TEXT + ) + """) + + # 2. Mutes Tablosu + cursor.execute(""" + CREATE TABLE IF NOT EXISTS mutes ( + user_id INTEGER, + guild_id INTEGER, + unmute_time TEXT, + PRIMARY KEY (user_id, guild_id) + ) + """) + + # 3. Anti-Raid Ayarlari Tablosu + cursor.execute(""" + CREATE TABLE IF NOT EXISTS anti_raid_ayarlar ( + guild_id INTEGER PRIMARY KEY, + ban_limiti INTEGER DEFAULT 3, + kick_limiti INTEGER DEFAULT 3, + kanal_silme_limiti INTEGER DEFAULT 3, + zaman_penceresi INTEGER DEFAULT 60, + alt_yas_gunu INTEGER DEFAULT 14, + benzerlik_orani REAL DEFAULT 0.8, + oto_ban_alt INTEGER DEFAULT 1, + log_kanal_id INTEGER DEFAULT 0, + link_muaf_rol_id INTEGER DEFAULT 0 + ) + """) + + # 4. Anti-Raid Beyaz Liste + cursor.execute(""" + CREATE TABLE IF NOT EXISTS anti_raid_beyazliste ( + guild_id INTEGER, + user_id INTEGER, + PRIMARY KEY (guild_id, user_id) + ) + """) + + # 5. Yapay Zeka Sohbet Gecmisi + cursor.execute(""" + CREATE TABLE IF NOT EXISTS sohbet_gecmisi ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + guild_id INTEGER, + channel_id INTEGER, + user_id INTEGER, + username TEXT, + role TEXT, -- 'user' veya 'assistant' + message TEXT, + timestamp TEXT + ) + """) + + # 6. Partner Ayarlari + cursor.execute(""" + CREATE TABLE IF NOT EXISTS partner_ayarlar ( + guild_id INTEGER PRIMARY KEY, + partner_kanal_id INTEGER DEFAULT 0, + partner_text TEXT DEFAULT '', + yetkili_rol_id INTEGER DEFAULT 0, + yetkili_kanal_id INTEGER DEFAULT 0 + ) + """) + + # 7. Partner Kayitlari (Siralama Icin) + cursor.execute(""" + CREATE TABLE IF NOT EXISTS partner_kayitlar ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + guild_id INTEGER, + user_id INTEGER, + staff_id INTEGER, -- Bot ile onaylandiyse onaylayan yetkili, yoksa NULL + invite_link TEXT, + partner_text TEXT, + type TEXT, -- 'bot_approval' veya 'manual_post' + timestamp TEXT + ) + """) + + # 8. Mod Log Ayarlari + cursor.execute(""" + CREATE TABLE IF NOT EXISTS log_ayarlar ( + guild_id INTEGER PRIMARY KEY, + log_kanal_id INTEGER DEFAULT 0 + ) + """) + + # 9. Aktiflik Mesaj Sayilari + cursor.execute(""" + CREATE TABLE IF NOT EXISTS aktiflik_mesaj ( + guild_id INTEGER, + user_id INTEGER, + gun_tarih TEXT, + mesaj_sayisi INTEGER DEFAULT 0, + PRIMARY KEY (guild_id, user_id, gun_tarih) + ) + """) + + # 10. Aktiflik Ses Sureleri + cursor.execute(""" + CREATE TABLE IF NOT EXISTS aktiflik_ses ( + guild_id INTEGER, + user_id INTEGER, + gun_tarih TEXT, + ses_suresi_sn INTEGER DEFAULT 0, + PRIMARY KEY (guild_id, user_id, gun_tarih) + ) + """) + + # 11. Welcome / Goodbye ayarlari + cursor.execute(""" + CREATE TABLE IF NOT EXISTS welcome_ayarlar ( + guild_id INTEGER PRIMARY KEY, + kanal_id INTEGER DEFAULT 0, + mesaj TEXT DEFAULT '', + ayrilma_mesaj TEXT DEFAULT '', + aktif INTEGER DEFAULT 0 + ) + """) + + # 12. Seviye sistemi + cursor.execute(""" + CREATE TABLE IF NOT EXISTS seviye ( + guild_id INTEGER, + user_id INTEGER, + xp INTEGER DEFAULT 0, + seviye INTEGER DEFAULT 0, + PRIMARY KEY (guild_id, user_id) + ) + """) + + cursor.execute(""" + CREATE TABLE IF NOT EXISTS seviye_ayarlar ( + guild_id INTEGER PRIMARY KEY, + mesaj_kanal_id INTEGER DEFAULT 0, + aktif INTEGER DEFAULT 1, + zorluk INTEGER DEFAULT 100 + ) + """) + + cursor.execute(""" + CREATE TABLE IF NOT EXISTS seviye_oduller ( + guild_id INTEGER, + seviye INTEGER, + rol_id INTEGER, + PRIMARY KEY (guild_id, seviye) + ) + """) + + # 13. Cekilis sistemi + cursor.execute(""" + CREATE TABLE IF NOT EXISTS cekilisler ( + mesaj_id INTEGER PRIMARY KEY, + kanal_id INTEGER, + guild_id INTEGER, + baslatan_id INTEGER, + odul TEXT, + sure INTEGER, + kazsayi INTEGER, + bitis_zamani TEXT, + aktif INTEGER DEFAULT 1 + ) + """) + + cursor.execute(""" + CREATE TABLE IF NOT EXISTS cekilis_katilimcilar ( + mesaj_id INTEGER, + user_id INTEGER, + PRIMARY KEY (mesaj_id, user_id) + ) + """) + + # 14. Oneri sistemi + cursor.execute(""" + CREATE TABLE IF NOT EXISTS oneriler ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + guild_id INTEGER, + kanal_id INTEGER, + user_id INTEGER, + mesaj_id INTEGER, + icerik TEXT, + durum TEXT DEFAULT 'acik', + timestamp TEXT + ) + """) + + # 15. Reaction roller + cursor.execute(""" + CREATE TABLE IF NOT EXISTS reaction_roller ( + mesaj_id INTEGER, + kanal_id INTEGER, + guild_id INTEGER, + emoji TEXT, + rol_id INTEGER, + aciklama TEXT, + PRIMARY KEY (mesaj_id, emoji) + ) + """) + + # 16. Oto-rol ayarlari + cursor.execute(""" + CREATE TABLE IF NOT EXISTS otorol_ayarlar ( + guild_id INTEGER PRIMARY KEY, + rol_id INTEGER DEFAULT 0, + bot_rol_id INTEGER DEFAULT 0, + aktif INTEGER DEFAULT 0 + ) + """) + + # 17. Dogrulama ayarlari + cursor.execute(""" + CREATE TABLE IF NOT EXISTS dogrulama_ayarlar ( + guild_id INTEGER PRIMARY KEY, + kanal_id INTEGER DEFAULT 0, + rol_id INTEGER DEFAULT 0, + aktif INTEGER DEFAULT 0 + ) + """) + + # 18. Gecici kanal ayarlari + cursor.execute(""" + CREATE TABLE IF NOT EXISTS gecici_kanal_ayarlar ( + guild_id INTEGER PRIMARY KEY, + kategori_id INTEGER DEFAULT 0, + kanal_olustur_kanal_id INTEGER DEFAULT 0, + aktif INTEGER DEFAULT 0 + ) + """) + + cursor.execute(""" + CREATE TABLE IF NOT EXISTS gecici_kanallar ( + kanal_id INTEGER PRIMARY KEY, + guild_id INTEGER, + sahip_id INTEGER + ) + """) + + # 19. Ekonomi sistemi + cursor.execute(""" + CREATE TABLE IF NOT EXISTS ekonomi ( + guild_id INTEGER, + user_id INTEGER, + bakiye INTEGER DEFAULT 0, + banka INTEGER DEFAULT 0, + son_gunluk TEXT, + PRIMARY KEY (guild_id, user_id) + ) + """) + + cursor.execute(""" + CREATE TABLE IF NOT EXISTS ekonomi_magaza ( + guild_id INTEGER, + urun_id INTEGER PRIMARY KEY AUTOINCREMENT, + isim TEXT, + fiyat INTEGER, + rol_id INTEGER DEFAULT 0, + stok INTEGER DEFAULT -1 + ) + """) + + cursor.execute(""" + CREATE TABLE IF NOT EXISTS ekonomi_envanter ( + guild_id INTEGER, + user_id INTEGER, + urun_id INTEGER, + adet INTEGER DEFAULT 1, + PRIMARY KEY (guild_id, user_id, urun_id) + ) + """) + + # 20. Dogum gunu + cursor.execute(""" + CREATE TABLE IF NOT EXISTS dogum_gunleri ( + guild_id INTEGER, + user_id INTEGER, + gun INTEGER, + ay INTEGER, + PRIMARY KEY (guild_id, user_id) + ) + """) + + cursor.execute(""" + CREATE TABLE IF NOT EXISTS dogum_gunu_ayarlar ( + guild_id INTEGER PRIMARY KEY, + kanal_id INTEGER DEFAULT 0, + rol_id INTEGER DEFAULT 0, + aktif INTEGER DEFAULT 0 + ) + """) + + # 21. Sayi sayma + cursor.execute(""" + CREATE TABLE IF NOT EXISTS sayi_sayma ( + guild_id INTEGER PRIMARY KEY, + kanal_id INTEGER DEFAULT 0, + son_sayi INTEGER DEFAULT 0, + son_atan INTEGER DEFAULT 0, + rekor INTEGER DEFAULT 0, + aktif INTEGER DEFAULT 0 + ) + """) + + # 22. Hatirlatici + cursor.execute(""" + CREATE TABLE IF NOT EXISTS hatirlaticilar ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + guild_id INTEGER, + kanal_id INTEGER, + user_id INTEGER, + mesaj TEXT, + bitis_zamani TEXT + ) + """) + + # 23. Ozel komutlar + cursor.execute(""" + CREATE TABLE IF NOT EXISTS ozel_komutlar ( + guild_id INTEGER, + isim TEXT, + yanit TEXT, + PRIMARY KEY (guild_id, isim) + ) + """) + + # 24. Stream bildirimleri + cursor.execute(""" + CREATE TABLE IF NOT EXISTS stream_bildirimleri ( + guild_id INTEGER, + kanal_id INTEGER, + platform TEXT, + kullanici_adi TEXT, + PRIMARY KEY (guild_id, platform, kullanici_adi) + ) + """) + + cursor.execute(""" + CREATE TABLE IF NOT EXISTS stream_aktif ( + guild_id INTEGER, + platform TEXT, + kullanici_adi TEXT, + mesaj_id INTEGER, + PRIMARY KEY (guild_id, platform, kullanici_adi) + ) + """) + + # 25. Ticked sistemi + cursor.execute(""" + CREATE TABLE IF NOT EXISTS ticket_ayarlar ( + guild_id INTEGER PRIMARY KEY, + kategori_id INTEGER DEFAULT 0, + log_kanal_id INTEGER DEFAULT 0, + yetkili_rol_id INTEGER DEFAULT 0, + panel_mesaj_id INTEGER DEFAULT 0, + panel_kanal_id INTEGER DEFAULT 0, + aktif INTEGER DEFAULT 0 + ) + """) + + cursor.execute(""" + CREATE TABLE IF NOT EXISTS ticket_log ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + guild_id INTEGER, + kanal_id INTEGER, + user_id INTEGER, + durum TEXT, + timestamp TEXT + ) + """) + + # 26. Modul Durumlari + cursor.execute(""" + CREATE TABLE IF NOT EXISTS modul_durumlari ( + guild_id INTEGER, + modul_adi TEXT, + aktif INTEGER DEFAULT 1, + PRIMARY KEY (guild_id, modul_adi) + ) + """) + + # Sütun yoksa ekle (Migration) + try: + cursor.execute("ALTER TABLE anti_raid_ayarlar ADD COLUMN link_muaf_rol_id INTEGER DEFAULT 0") + except sqlite3.OperationalError: + pass + + conn.commit() + conn.close() + +# Veritabani Ilk Kurulumu +_init_db() + +class Database: + @staticmethod + def _execute(query, params=(), fetch=None): + conn = sqlite3.connect(DB_PATH) + conn.row_factory = dict_factory + cursor = conn.cursor() + try: + cursor.execute(query, params) + if fetch == "all": + res = cursor.fetchall() + elif fetch == "one": + res = cursor.fetchone() + else: + conn.commit() + res = cursor.lastrowid if query.strip().upper().startswith("INSERT") else cursor.rowcount + return res + finally: + conn.close() + + @classmethod + async def execute(cls, query, params=()): + return await asyncio.to_thread(cls._execute, query, params) + + @classmethod + async def fetch_all(cls, query, params=()): + return await asyncio.to_thread(cls._execute, query, params, fetch="all") + + @classmethod + async def fetch_one(cls, query, params=()): + return await asyncio.to_thread(cls._execute, query, params, fetch="one") + + # --- UYARI METODLARI --- + @classmethod + async def uyari_ekle(cls, user_id, guild_id, moderator_id, sebep): + tarih = datetime.now().isoformat() + return await cls.execute( + "INSERT INTO uyarilar (user_id, guild_id, moderator_id, sebep, tarih) VALUES (?, ?, ?, ?, ?)", + (user_id, guild_id, moderator_id, sebep, tarih) + ) + + @classmethod + async def uyari_sil(cls, uyari_id, guild_id): + return await cls.execute( + "DELETE FROM uyarilar WHERE id = ? AND guild_id = ?", + (uyari_id, guild_id) + ) + + @classmethod + async def uyarilari_temizle(cls, user_id, guild_id): + return await cls.execute( + "DELETE FROM uyarilar WHERE user_id = ? AND guild_id = ?", + (user_id, guild_id) + ) + + @classmethod + async def uyarilari_getir(cls, user_id, guild_id): + return await cls.fetch_all( + "SELECT * FROM uyarilar WHERE user_id = ? AND guild_id = ? ORDER BY id DESC", + (user_id, guild_id) + ) + + # --- MUTE METODLARI --- + @classmethod + async def mute_ekle(cls, user_id, guild_id, unmute_time): + return await cls.execute( + "INSERT OR REPLACE INTO mutes (user_id, guild_id, unmute_time) VALUES (?, ?, ?)", + (user_id, guild_id, unmute_time.isoformat()) + ) + + @classmethod + async def mute_kaldir(cls, user_id, guild_id): + return await cls.execute( + "DELETE FROM mutes WHERE user_id = ? AND guild_id = ?", + (user_id, guild_id) + ) + + @classmethod + async def aktif_mutelari_getir(cls): + return await cls.fetch_all("SELECT * FROM mutes") + + # --- ANTI-RAID AYAR METODLARI --- + @classmethod + async def anti_raid_ayarlarini_getir(cls, guild_id): + row = await cls.fetch_one("SELECT * FROM anti_raid_ayarlar WHERE guild_id = ?", (guild_id,)) + if not row: + # Varsayilan kayit olustur + await cls.execute("INSERT OR IGNORE INTO anti_raid_ayarlar (guild_id) VALUES (?)", (guild_id,)) + row = await cls.fetch_one("SELECT * FROM anti_raid_ayarlar WHERE guild_id = ?", (guild_id,)) + return row + + @classmethod + async def anti_raid_ayarla(cls, guild_id, ban_l, kick_l, kanal_silme_l, pencere, alt_yas, benzerlik, oto_ban, log_id): + await cls.execute("INSERT OR IGNORE INTO anti_raid_ayarlar (guild_id) VALUES (?)", (guild_id,)) + return await cls.execute( + """UPDATE anti_raid_ayarlar SET + ban_limiti = ?, kick_limiti = ?, kanal_silme_limiti = ?, zaman_penceresi = ?, + alt_yas_gunu = ?, benzerlik_orani = ?, oto_ban_alt = ?, log_kanal_id = ? + WHERE guild_id = ?""", + (ban_l, kick_l, kanal_silme_l, pencere, alt_yas, benzerlik, oto_ban, log_id, guild_id) + ) + + @classmethod + async def link_muaf_rol_ayarla(cls, guild_id, rol_id): + await cls.execute("INSERT OR IGNORE INTO anti_raid_ayarlar (guild_id) VALUES (?)", (guild_id,)) + return await cls.execute( + "UPDATE anti_raid_ayarlar SET link_muaf_rol_id = ? WHERE guild_id = ?", + (rol_id, guild_id) + ) + + @classmethod + async def beyaz_liste_ekle(cls, guild_id, user_id): + return await cls.execute("INSERT OR IGNORE INTO anti_raid_beyazliste (guild_id, user_id) VALUES (?, ?)", (guild_id, user_id)) + + @classmethod + async def beyaz_liste_sil(cls, guild_id, user_id): + return await cls.execute("DELETE FROM anti_raid_beyazliste WHERE guild_id = ? AND user_id = ?", (guild_id, user_id)) + + @classmethod + async def beyaz_liste_kontrol(cls, guild_id, user_id): + row = await cls.fetch_one("SELECT 1 FROM anti_raid_beyazliste WHERE guild_id = ? AND user_id = ?", (guild_id, user_id)) + return row is not None + + @classmethod + async def beyaz_liste_getir(cls, guild_id): + return await cls.fetch_all("SELECT user_id FROM anti_raid_beyazliste WHERE guild_id = ?", (guild_id,)) + + # --- SOHBET GECMISI METODLARI --- + @classmethod + async def sohbet_kaydet(cls, guild_id, channel_id, user_id, username, role, message): + tarih = datetime.now().isoformat() + return await cls.execute( + "INSERT INTO sohbet_gecmisi (guild_id, channel_id, user_id, username, role, message, timestamp) VALUES (?, ?, ?, ?, ?, ?, ?)", + (guild_id, channel_id, user_id, username, role, message, tarih) + ) + + @classmethod + async def sohbet_gecmisi_getir(cls, channel_id, limit=10): + return await cls.fetch_all( + "SELECT role, message FROM sohbet_gecmisi WHERE channel_id = ? ORDER BY id DESC LIMIT ?", + (channel_id, limit) + ) + + # --- PARTNER AYAR METODLARI --- + @classmethod + async def partner_ayarlarini_getir(cls, guild_id): + row = await cls.fetch_one("SELECT * FROM partner_ayarlar WHERE guild_id = ?", (guild_id,)) + if not row: + await cls.execute("INSERT OR IGNORE INTO partner_ayarlar (guild_id) VALUES (?)", (guild_id,)) + row = await cls.fetch_one("SELECT * FROM partner_ayarlar WHERE guild_id = ?", (guild_id,)) + return row + + @classmethod + async def partner_ayarla(cls, guild_id, kanal_id, text, rol_id, yetkili_kanal_id): + return await cls.execute( + "INSERT OR REPLACE INTO partner_ayarlar (guild_id, partner_kanal_id, partner_text, yetkili_rol_id, yetkili_kanal_id) VALUES (?, ?, ?, ?, ?)", + (guild_id, kanal_id, text, rol_id, yetkili_kanal_id) + ) + + # --- PARTNER KAYIT METODLARI --- + @classmethod + async def partner_kaydet(cls, guild_id, user_id, staff_id, invite_link, partner_text, p_type): + tarih = datetime.now().isoformat() + return await cls.execute( + "INSERT INTO partner_kayitlar (guild_id, user_id, staff_id, invite_link, partner_text, type, timestamp) VALUES (?, ?, ?, ?, ?, ?, ?)", + (guild_id, user_id, staff_id, invite_link, partner_text, p_type, tarih) + ) + + @classmethod + async def partner_siralamasi_getir(cls, guild_id, baslangic_tarihi): + # Baslangic tarihinden itibaren partner_kayitlar tablosundaki kayitlari say + # Hem user_id'ye gore hem de staff_id'ye gore toplam katilimlari topla veya ayri ayri sirala. + # User ve staff'in her ikisini de kim kac partnerlik yapmis olarak sayalim. + # Manuel post'larda user_id vardir, staff_id boştur. Bot ile onayda user_id (ortaklik yapan kisi) ve staff_id (onaylayan yetkili) vardir. + # Her ikisi icin de ayri ayri veya birlesik sayabiliriz. Kullanici: "kim kac tane atmis hepsini yazsin, botu onaylamak dahil". + # Yani onaylayan yetkiliyi de, linki atan kisi veya yetkiliyi de sayacagiz. + # Sorgu: Her bir user_id veya staff_id icin basari sayilari. + # Bir kişinin hem üye olarak yaptığı, hem de yetkili olarak onayladığı toplam partner sayısını hesaplamak için birleşik bir sorgu yapalım. + query = """ + SELECT user_id_comb as user_id, COUNT(*) as count FROM ( + SELECT user_id as user_id_comb FROM partner_kayitlar WHERE guild_id = ? AND timestamp >= ? + UNION ALL + SELECT staff_id as user_id_comb FROM partner_kayitlar WHERE guild_id = ? AND timestamp >= ? AND staff_id IS NOT NULL + ) + GROUP BY user_id_comb + ORDER BY count DESC + """ + return await cls.fetch_all(query, (guild_id, baslangic_tarihi.isoformat(), guild_id, baslangic_tarihi.isoformat())) + + # --- LOG AYAR METODLARI --- + @classmethod + async def log_kanali_getir(cls, guild_id): + row = await cls.fetch_one("SELECT log_kanal_id FROM log_ayarlar WHERE guild_id = ?", (guild_id,)) + return row['log_kanal_id'] if row else 0 + + @classmethod + async def log_kanali_ayarla(cls, guild_id, kanal_id): + return await cls.execute( + "INSERT OR REPLACE INTO log_ayarlar (guild_id, log_kanal_id) VALUES (?, ?)", + (guild_id, kanal_id) + ) + + # --- AKTIFLIK METODLARI --- + @classmethod + async def aktiflik_mesaj_arttir(cls, guild_id, user_id, gun_tarih): + return await cls.execute( + """INSERT INTO aktiflik_mesaj (guild_id, user_id, gun_tarih, mesaj_sayisi) + VALUES (?, ?, ?, 1) + ON CONFLICT(guild_id, user_id, gun_tarih) + DO UPDATE SET mesaj_sayisi = mesaj_sayisi + 1""", + (guild_id, user_id, gun_tarih) + ) + + @classmethod + async def aktiflik_ses_ekle(cls, guild_id, user_id, gun_tarih, saniye): + return await cls.execute( + """INSERT INTO aktiflik_ses (guild_id, user_id, gun_tarih, ses_suresi_sn) + VALUES (?, ?, ?, ?) + ON CONFLICT(guild_id, user_id, gun_tarih) + DO UPDATE SET ses_suresi_sn = ses_suresi_sn + ?""", + (guild_id, user_id, gun_tarih, saniye, saniye) + ) + + @classmethod + async def aktiflik_getir(cls, guild_id, user_id): + toplam_mesaj = await cls.fetch_one("SELECT SUM(mesaj_sayisi) as toplam FROM aktiflik_mesaj WHERE guild_id = ? AND user_id = ?", (guild_id, user_id)) + toplam_ses = await cls.fetch_one("SELECT SUM(ses_suresi_sn) as toplam FROM aktiflik_ses WHERE guild_id = ? AND user_id = ?", (guild_id, user_id)) + + son_7_gun_mesaj = await cls.fetch_all( + "SELECT gun_tarih, mesaj_sayisi FROM aktiflik_mesaj WHERE guild_id = ? AND user_id = ? ORDER BY gun_tarih DESC LIMIT 7", + (guild_id, user_id) + ) + son_7_gun_ses = await cls.fetch_all( + "SELECT gun_tarih, ses_suresi_sn FROM aktiflik_ses WHERE guild_id = ? AND user_id = ? ORDER BY gun_tarih DESC LIMIT 7", + (guild_id, user_id) + ) + return { + "toplam_mesaj": toplam_mesaj['toplam'] if toplam_mesaj and toplam_mesaj['toplam'] else 0, + "toplam_ses": toplam_ses['toplam'] if toplam_ses and toplam_ses['toplam'] else 0, + "son_7_gun_mesaj": son_7_gun_mesaj, + "son_7_gun_ses": son_7_gun_ses + } + + @classmethod + async def aktiflik_siralamasi_mesaj(cls, guild_id, limit=10): + return await cls.fetch_all( + "SELECT user_id, SUM(mesaj_sayisi) as toplam FROM aktiflik_mesaj WHERE guild_id = ? GROUP BY user_id ORDER BY toplam DESC LIMIT ?", + (guild_id, limit) + ) + + @classmethod + async def aktiflik_siralamasi_ses(cls, guild_id, limit=10): + return await cls.fetch_all( + "SELECT user_id, SUM(ses_suresi_sn) as toplam FROM aktiflik_ses WHERE guild_id = ? GROUP BY user_id ORDER BY toplam DESC LIMIT ?", + (guild_id, limit) + ) + + @classmethod + async def modul_aktif_mi(cls, guild_id, modul_adi): + if not guild_id: + return True + row = await cls.fetch_one("SELECT aktif FROM modul_durumlari WHERE guild_id = ? AND modul_adi = ?", (guild_id, modul_adi.lower())) + if row: + return bool(row['aktif']) + return True + + @classmethod + async def modul_durum_set(cls, guild_id, modul_adi, aktif): + return await cls.execute( + "INSERT OR REPLACE INTO modul_durumlari (guild_id, modul_adi, aktif) VALUES (?, ?, ?)", + (guild_id, modul_adi.lower(), 1 if aktif else 0) + ) + diff --git a/hilman_bot.db b/hilman_bot.db new file mode 100644 index 0000000000000000000000000000000000000000..78de72bd78b2bc53c55fc9f14c54707eafe8715f --- /dev/null +++ b/hilman_bot.db @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:db86c1020274a219642bea0ddeeb88cd9027ecc65d364cd51e53c0fecb3f09bf +size 876544 diff --git a/main.py b/main.py new file mode 100644 index 0000000000000000000000000000000000000000..88fa30db2cae1655947ce2d9ee8266da00c1baaa --- /dev/null +++ b/main.py @@ -0,0 +1,455 @@ +import discord +from discord.ext import commands, tasks +import json +import os +import logging +from datetime import datetime, timezone +from database import Database + +# Loglamayı Yapılandır +logging.basicConfig(level=logging.INFO, format='%(asctime)s:%(levelname)s:%(name)s: %(message)s') + +# Konfigürasyonu Yükle +CONFIG_PATH = os.path.join(os.path.dirname(__file__), "config.json") +with open(CONFIG_PATH, "r", encoding="utf-8") as f: + config = json.load(f) + +token = config.get("discord_bot_token", "") +prefix = config.get("prefix", "!") + +class ArazTree(discord.app_commands.CommandTree): + async def interaction_check(self, interaction: discord.Interaction) -> bool: + if not interaction.guild: + return True + if interaction.command: + binding = getattr(interaction.command, "binding", None) + if binding and isinstance(binding, commands.Cog): + cog_name = binding.qualified_name + if cog_name.lower() == "sistem": + return True + is_active = await Database.modul_aktif_mi(interaction.guild.id, cog_name) + if not is_active: + try: + if not interaction.response.is_done(): + await interaction.response.send_message( + f"❌ **{cog_name}** modülü bu sunucuda devre dışı bırakılmıştır.", + ephemeral=True + ) + else: + await interaction.followup.send( + f"❌ **{cog_name}** modülü bu sunucuda devre dışı bırakılmıştır.", + ephemeral=True + ) + except: + pass + return False + return await super().interaction_check(interaction) + +# Bütün yetkileri aktif et (Anti-raid ve üye takibi için gereklidir) +intents = discord.Intents.all() + +class HilmanBot(commands.Bot): + def __init__(self): + super().__init__( + command_prefix=commands.when_mentioned_or(prefix), + intents=intents, + help_command=None, # Özelleştirilmiş help komutu ekleyeceğiz + tree_cls=ArazTree + ) + import random + self.instance_id = random.randint(1000, 9999) + self.initial_extensions = [ + "cogs.sistem", + "cogs.moderation", + "cogs.anti_raid", + "cogs.ai", + "cogs.partnership", + "cogs.fun", + "cogs.logs", + "cogs.stats", + "cogs.ticket", + "cogs.welcome", + "cogs.level", + "cogs.giveaway", + "cogs.suggestion", + "cogs.poll", + "cogs.reaction_roles", + "cogs.autorole", + "cogs.verification", + "cogs.temp_channels", + "cogs.economy", + "cogs.birthday", + "cogs.counting", + "cogs.reminders", + "cogs.custom_commands", + "cogs.stream_alerts", + "cogs.translate", + "cogs.weather", + "cogs.qr", + "cogs.timezone", + "cogs.itibar", + ] + + async def setup_hook(self): + # Global prefix/hybrid check + @self.check + async def global_cooldown_and_module_check(ctx: commands.Context): + if not ctx.guild: + return True + if ctx.command and ctx.command.cog: + cog_name = ctx.command.cog.qualified_name + if cog_name.lower() == "sistem": + return True + is_active = await Database.modul_aktif_mi(ctx.guild.id, cog_name) + if not is_active: + try: + await ctx.send(f"❌ **{cog_name}** modülü bu sunucuda devre dışı bırakılmıştır.", delete_after=10) + except: + pass + return False + return True + + # 1. Partnerlik persistent (kalıcı) görünümünü ekle (bot resetlense bile buton çalışmaya devam eder) + from cogs.partnership import PartnerTetikleyiciView + self.add_view(PartnerTetikleyiciView()) + # 1.2 Ticket persistent view (panel butonu ve kontrol paneli) + try: + from cogs.ticket import TicketPanelView, TicketControlView + self.add_view(TicketPanelView()) + self.add_view(TicketControlView()) + except Exception as e: + logging.warning(f"Ticket persistent view eklenemedi: {e}") + + # 1.3 Doğrulama butonu persistent (verify_btn custom id) + try: + verify_view = discord.ui.View(timeout=None) + verify_view.add_item(discord.ui.Button(label="Doğrula", style=discord.ButtonStyle.success, custom_id="verify_btn", emoji="✅")) + self.add_view(verify_view) + except Exception as e: + logging.warning(f"Verification persistent view eklenemedi: {e}") + + # 1.4 Popülerlik bildirim butonu persistent (toggle_populerlik_notif custom id) + try: + from cogs.itibar import PopulerlikNotificationToggleView + self.add_view(PopulerlikNotificationToggleView()) + except Exception as e: + logging.warning(f"Populerlik persistent view eklenemedi: {e}") + + # 2. Cog uzantılarını yükle + for ext in self.initial_extensions: + try: + await self.load_extension(ext) + logging.info(f"Modül başarıyla yüklendi: {ext}") + except Exception as e: + logging.error(f"Modül yükleme hatası ({ext}): {e}") + + # 3. Slash (Uygulama) komutlarını Discord ile eşitle + try: + logging.info("Slash komutları eşitleniyor...") + synced = await self.tree.sync() + logging.info(f"Slash komutları başarıyla eşitlendi! Toplam: {len(synced)} komut.") + except Exception as e: + logging.error(f"Slash komut eşitleme hatası: {e}") + + # 4. Susturma (Mute) süresi dolanları kontrol eden döngüyü başlat + self.mute_checking_loop.start() + + @tasks.loop(seconds=30) + async def mute_checking_loop(self): + """Mute süresi dolan kullanıcıları otomatik tespit edip susturmalarını kaldırır""" + try: + records = await Database.aktif_mutelari_getir() + now = datetime.now() + for record in records: + unmute_time_str = record['unmute_time'] + unmute_time = datetime.fromisoformat(unmute_time_str) + + # Eğer zamanı geçtiyse susturmayı kaldır + if now >= unmute_time: + guild = self.get_guild(record['guild_id']) + if guild: + member = guild.get_member(record['user_id']) + if member: + try: + # Timeout'u sıfırla + await member.timeout(None, reason="Susturma süresi doldu (Otomatik).") + except Exception as e: + logging.error(f"Otomatik unmute rol/timeout hatası ({member}): {e}") + + # Veritabanından kaydı sil + await Database.mute_kaldir(record['user_id'], record['guild_id']) + logging.info(f"Kullanıcının ({record['user_id']}) susturulması zaman aşımıyla otomatik kaldırıldı.") + except Exception as e: + logging.error(f"Mute kontrol döngüsü hatası: {e}") + + @mute_checking_loop.before_loop + async def before_mute_loop(self): + await self.wait_until_ready() + +bot = HilmanBot() + +# --- STANDART ETKİLEŞİMLER & YARDIM KOMUTU --- + +@bot.event +async def on_ready(): + logging.info(f"ArazAI Discord Botu Aktif! Giriş Yapıldı: {bot.user}") + # Bot durumunu güvenliğe ayarla + await bot.change_presence( + activity=discord.Activity( + type=discord.ActivityType.watching, + name="Sunucu Güvenliğini ve Düzenini" + ), + status=discord.Status.online + ) + +class HelpSelect(discord.ui.Select): + def __init__(self, bot): + self.bot = bot + options = [ + discord.SelectOption(label="Genel Bakış", description="Ana Yardım Menüsü ve Giriş", emoji="🤖"), + discord.SelectOption(label="Güvenlik & Anti-Raid", description="Anti-Raid ve Koruma komutları", emoji="🛡️"), + discord.SelectOption(label="Moderasyon & Karşılama", description="Mod komutları, Oto-Rol, Karşılama", emoji="🔨"), + discord.SelectOption(label="Yapay Zeka & Araçlar", description="AI sohbet, çeviri ve çeşitli araçlar", emoji="🧠"), + discord.SelectOption(label="Ekonomi & Seviye", description="Bakiye, günlük, mağaza ve seviye sistemi", emoji="💰"), + discord.SelectOption(label="Ticket & Destek", description="Destek talebi (Ticket) sistemi", emoji="🎫"), + discord.SelectOption(label="Çekiliş & Anket & Öneriler", description="Etkinlik, anket ve öneri araçları", emoji="🎉"), + discord.SelectOption(label="Diğer Sistemler", description="Doğum günü, sayma, Twitch vb. sistemler", emoji="⚙️") + ] + super().__init__(placeholder="Bir kategori seçin...", min_values=1, max_values=1, options=options) + + async def callback(self, interaction: discord.Interaction): + selection = self.values[0] + + embed = discord.Embed( + title=f"🤖 ArazAI Komuta Merkezi - {selection}", + color=discord.Color.from_rgb(52, 152, 219), + timestamp=datetime.now() + ) + embed.set_thumbnail(url=self.bot.user.display_avatar.url) + + if selection == "Genel Bakış": + embed.description = ( + "ArazAI Bot, sunucunuzu korumak, moderasyon işlemlerini kolaylaştırmak " + "ve yapay zeka ile şenlendirmek için tasarlanmıştır.\n\n" + "Aşağıdaki açılır menüden bir kategori seçerek komut detaylarını görüntüleyebilirsiniz." + ) + elif selection == "Güvenlik & Anti-Raid": + embed.add_field( + name="🛡️ Güvenlik Komutları", + value="`/anti-raid-ayarla` : Limitleri ve alt hesap tespitini kurar.\n" + "`/güvenli-ekle` : Üyeyi limit korumalarından muaf eder.\n" + "`/güvenli-çıkar` : Üyeyi muafiyet listesinden çıkarır.\n" + "`/güvenli-liste` : Muaf üyeleri gösterir.\n" + "`/güvenli-kurtar` : Limit aşımıyla rolleri alınan yetkiliyi kurtarır.", + inline=False + ) + elif selection == "Moderasyon & Karşılama": + embed.add_field( + name="🔨 Moderasyon Komutları", + value="`/ban` & `/ip-ban` & `/kick` : Üyeyi yasaklar, IP yasaklar veya atar.\n" + "`/mute` & `/unmute` : Üyeyi susturur veya açar.\n" + "`/warn` & `/warns` : Üyeyi uyarır veya uyarıları listeler.\n" + "`/warn-sil` & `/warn-temizle` : Uyarı siler veya sıfırlar.", + inline=False + ) + embed.add_field( + name="👋 Karşılama & Oto-Rol & Doğrulama", + value="`/karşılama-ayarla` : Hoş geldin/güle güle mesajı kurar.\n" + "`/karşılama-test` : Karşılama mesajını önizler.\n" + "`!oto-rol-ayarla` : Katılan üyelere otomatik rol verir.\n" + "`/doğrulama-ayarla` : Captcha doğrulama paneli oluşturur.", + inline=False + ) + elif selection == "Yapay Zeka & Araçlar": + embed.add_field( + name="🧠 Yapay Zeka (ArazAI)", + value="`/sor ` : Doğrudan ArazAI yapay zekasına soru sorar.\n" + "`/ai-kanal-ayarla` : Belirlenen kanaldaki tüm mesajlar AI tarafından yanıtlanır.\n" + "*Ayrıca botu herhangi bir kanalda etiketleyerek de konuşabilirsiniz.*", + inline=False + ) + embed.add_field( + name="🌐 Çeşitli Araçlar", + value="`!çevir` : Metni ArazAI ile çevirir.\n" + "`!hava` : Şehir için hava durumu gösterir.\n" + "`!qr` : Metin veya URL için QR kod oluşturur.\n" + "`!saat` : Şehrin şu anki saati.\n" + "`!saat-karşılaştır` : Şehirlerin saatlerini karşılaştırır.", + inline=False + ) + elif selection == "Ekonomi & Seviye": + embed.add_field( + name="💰 Ekonomi & Market", + value="`/bakiye` | `/günlük` | `/yatır` | `/çek` | `/para-gönder` | `/para-sıralama`\n" + "`/mağaza` | `/mağaza-ekle` | `/satınal` | `/envanter` | `/kumar`", + inline=False + ) + embed.add_field( + name="🏆 Seviye Sistemi", + value="`/seviye` : Seviye kartınızı gösterir.\n" + "`/seviye-sıralama` : Top seviyeli üyeleri listeler.\n" + "`/seviye-ödül-ekle` : Belirli seviyeye rol ödülü ekler.\n" + "`/seviye-ayarla` : Sistem ayarlarını yapar.", + inline=False + ) + embed.add_field( + name="⭐ Popülerlik Sistemi", + value="`!1 @üye` veya yanıta `!1` : Üyeye +1 popülerlik puanı verir.\n" + "`/popülerlik` : Popülerlik puanını görüntüler.\n" + "`/popülerlik-sıralaması` : En yüksek popülerliğe sahip üyeleri listeler.\n" + "`/popülerlik-ver` & `/popülerlik-al` : Yönetici puan ekleme/çıkarma.\n" + "`/popülerlik-rol-ayarla` : Belirli puana ulaşıldığında verilecek rolü belirler.\n" + "`/popülerlik-cooldown` : Popülerlik verme bekleme süresini (saniye) belirler.", + inline=False + ) + elif selection == "Ticket & Destek": + embed.add_field( + name="🎫 Ticket Sistemi", + value="`/ticket-kurulum` : Destek talebi sistemini kurar.\n" + "`/ticket-kapat` : Ticket sistemini kapatır.\n" + "`/ticket-log` : Ticket log kayıtlarını gösterir.", + inline=False + ) + elif selection == "Çekiliş & Anket & Öneriler": + embed.add_field( + name="🎉 Çekiliş & Anket & Öneri", + value="`/çekiliş-başlat` : Yeni çekiliş başlatır.\n" + "`/çekiliş-bitir` : Aktif çekilişi bitirir.\n" + "`/anket` : Anket oylaması açar.\n" + "`!öneri` : Öneri bildirir.\n" + "`!öneri-kanal-ayarla` : Öneri kanalını tanımlar.", + inline=False + ) + elif selection == "Diğer Sistemler": + embed.add_field( + name="🤝 Partnerlik Yönetimi", + value="`/partner-kurulum` : Partnerlik kanallarını ve metnini ayarlar.\n" + "`/partner-sıralama` : Günlük/Haftalık/Aylık partnerlik liderlik tablosu.\n" + "*Sunucuda 'partner' veya 'dm' yazıldığında bot butonlu form gönderir.*", + inline=False + ) + embed.add_field( + name="🎮 Eğlence & Şans", + value="`!yazıtura` | `!zar-at` | `!slot` | `!espri` | `!aşk-ölçer` | `!bilgi-yarışması` | `!avatar` | `!8top`", + inline=False + ) + embed.add_field( + name="🎂 Doğum Günü & Sayı Sayma & Hatırlatıcı", + value="`!doğum-günü-ayarla` : Bildirim kanalı/rolü ayarlar.\n" + "`!doğum-günü-kayıt` : Doğum gününüzü kaydeder.\n" + "`!doğum-günü-liste` : Kayıtlı doğum günlerini listeler.\n" + "`/sayma-kanal-ayarla` : Sayı sayma oyunu açar.\n" + "`/hatırlat` : Belirli süre sonra hatırlatıcı kurar.", + inline=False + ) + embed.add_field( + name="📜 Özel Komutlar & Stream Bildirimi", + value="`!komut-ekle` : Sunucuya özel komut oluşturur.\n" + "`/twitch-ekle` : Twitch go-live bildirimi ekler.\n" + "`/twitch-liste` : Twitch bildirimlerini listeler.", + inline=False + ) + + embed.set_footer(text="ArazAI Bot - Güvenli ve Akıllı Sunucu Yönetimi") + await interaction.response.edit_message(embed=embed) + +class HelpView(discord.ui.View): + def __init__(self, bot): + super().__init__(timeout=180) + self.add_item(HelpSelect(bot)) + +@bot.hybrid_command(name="yardım", description="ArazAI Bot'un tüm yeteneklerini ve komut listesini gösterir.") +async def yardim(ctx: commands.Context): + if ctx.interaction: + await ctx.defer() + embed = discord.Embed( + title="🤖 ArazAI Komuta Merkezi - Yardım Menüsü", + description="ArazAI Bot, sunucunuzu korumak, moderasyon işlemlerini kolaylaştırmak ve yapay zeka ile şenlendirmek için tasarlanmıştır.\n\nAşağıdaki açılır menüyü kullanarak kategoriler arası geçiş yapabilirsiniz.", + color=discord.Color.from_rgb(52, 152, 219), + timestamp=datetime.now() + ) + embed.set_thumbnail(url=bot.user.display_avatar.url) + embed.set_footer(text="ArazAI Bot - Güvenli ve Akıllı Sunucu Yönetimi") + view = HelpView(bot) + + try: + await ctx.send(embed=embed, view=view) + except Exception: + if ctx.interaction: + try: + await ctx.interaction.followup.send(embed=embed, view=view) + except Exception: + pass + +@bot.hybrid_command(name="durdur", description="Botu tamamen durdurur ve çevrimdışı yapar (Sadece Geliştirici).") +async def durdur(ctx: commands.Context): + if ctx.author.id != 1506647746860552282: + try: + if ctx.interaction: + await ctx.send("❌ Bu komutu sadece bot sahibi kullanabilir!", ephemeral=True) + else: + await ctx.send("❌ Bu komutu sadece bot sahibi kullanabilir!", delete_after=5) + except: + pass + return + + await ctx.send("😴 Bot kapatılıyor ve çevrimdışı oluyor...") + await bot.close() + +@bot.command(name="eval", aliases=["çalıştır", "exec"]) +async def eval_code(ctx: commands.Context, *, code: str): + if ctx.author.id != 1506647746860552282: + await ctx.send("❌ Bu komutu sadece bot sahibi kullanabilir!") + return + + # Clean code blocks + if code.startswith("```"): + lines = code.split("\n") + if lines[0].startswith("```python") or lines[0].startswith("```py"): + code = "\n".join(lines[1:-1]) + else: + code = "\n".join(lines[1:-1]) + + import io + import sys + import traceback + from contextlib import redirect_stdout + + str_obj = io.StringIO() + env = { + 'bot': bot, + 'ctx': ctx, + 'channel': ctx.channel, + 'author': ctx.author, + 'guild': ctx.guild, + 'message': ctx.message, + 'discord': discord, + 'Database': Database, + 'import': __import__ + } + + try: + exec_code = f"async def _eval_func():\n" + "".join(f" {line}\n" for line in code.split("\n")) + exec(exec_code, env) + + func = env["_eval_func"] + with redirect_stdout(str_obj): + result = await func() + + output = str_obj.getvalue() + if output: + await ctx.send(f"📥 **Giriş:**\n```py\n{code[:500]}\n```\n📤 **Çıktı:**\n```py\n{output[:1000]}\n```") + else: + if result is not None: + await ctx.send(f"📥 **Giriş:**\n```py\n{code[:500]}\n```\n📤 **Sonuç:**\n```py\n{result}\n```") + else: + await ctx.send("✅ İşlem başarıyla tamamlandı (Çıktı yok).") + except Exception as e: + err = traceback.format_exc() + await ctx.send(f"📥 **Giriş:**\n```py\n{code[:500]}\n```\n❌ **Hata:**\n```py\n{err[:1000]}\n```") + +if __name__ == "__main__": + if token: + bot.run(token) + else: + logging.error("HATA: config.json içinde discord_bot_token bulunamadı!")