From 128356962240c5a6b08b9085cce56bb20d9ba714 Mon Sep 17 00:00:00 2001 From: esefxdz Date: Mon, 29 Jun 2026 16:20:57 +0300 Subject: [PATCH] ban commands added --- cogs/__pycache__/general.cpython-312.pyc | Bin 1487 -> 9690 bytes cogs/currency_converter.py | 162 +++++++---------------- cogs/general.py | 78 ++++++++++- main.py | 7 +- 4 files changed, 123 insertions(+), 124 deletions(-) diff --git a/cogs/__pycache__/general.cpython-312.pyc b/cogs/__pycache__/general.cpython-312.pyc index 6cf1b57c06ec874ab38fe2db4e708faa769d275d..44e69f9d8cd6abeb70eaa15da599b95ac34bf4f3 100644 GIT binary patch literal 9690 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 a_H|0@=O@kw&IZ0D2tRm=rF4tN@P7fcQ$nTy delta 662 zcmYLF&ubGw6rMLTn@#g$NeP6cMqQgk4Anvu@t~GMl?X!55|9#h7ERq`WhQ|}4{ao9 z^&L3*PmQV|X9XJx*L(olbSI4z23d%gLK{PxV-5thR_T~q()Y&W-ytrTu7-ru7{UqGzp(o z=r!VLt>|aCaTIi(I2}AJl~WT8MroR31h>n)W-6_UrP*X`&GGL{i{iQfRIsCggxl@i z=LN+iUsNszyQdyoGSL#Pj_5W!j>}1~I)7qQZ^JVxNWiR1KP~MLroOn_k6vvR;gWLzmPcVVw2PBPfA-skS{sD)j Bnd$%l diff --git a/cogs/currency_converter.py b/cogs/currency_converter.py index 21df741..bc0d18f 100644 --- a/cogs/currency_converter.py +++ b/cogs/currency_converter.py @@ -3,141 +3,81 @@ import time import json import aiohttp import discord -from discord import app_commands from discord.ext import commands # load aliases from json with open(os.path.join(os.path.dirname(__file__), 'currency_aliases.json'), 'r', encoding='utf-8') as f: _ALIAS_MAP = json.load(f) -# unique ISO codes available for autocomplete (preserves order, deduplicates) -_ISO_CODES = sorted(set(_ALIAS_MAP.values())) - # in-memory cache for rates: {base: (timestamp, rates_dict)} _RATE_CACHE = {} _CACHE_TTL = 3600 # seconds - -def _norm(cur: str) -> str: - """Normalize a currency string to its ISO code. - Returns upper-case ISO code or raises ValueError if unknown. - """ - cur = cur.strip().lower() - if cur in _ALIAS_MAP: - return _ALIAS_MAP[cur] - cur = cur.upper() - if len(cur) == 3 and cur.isalpha(): - return cur - raise ValueError(f"unknown currency '{cur}'") - - -async def _get_rates(base: str) -> dict: - """Fetch rates for *base* from cache or the Frankfurter API.""" - now = time.time() - cached = _RATE_CACHE.get(base) - if cached and now - cached[0] < _CACHE_TTL: - return cached[1] - url = f"https://api.frankfurter.app/latest?base={base}" - async with aiohttp.ClientSession() as session: - async with session.get(url) as resp: - if resp.status != 200: - raise RuntimeError('currency API error') - data = await resp.json() - rates = data.get('rates', {}) - _RATE_CACHE[base] = (now, rates) - return rates - - -def _make_embed(amount: float, from_code: str, to_code: str, rate: float) -> discord.Embed: - result = amount * rate - embed = discord.Embed(title='Currency conversion', color=0x5865F2) - embed.add_field(name='From', value=f"{amount:g} {from_code}", inline=True) - embed.add_field(name='To', value=f"{result:g} {to_code}", inline=True) - embed.add_field(name='Rate', value=f"1 {from_code} = {rate:g} {to_code}", inline=False) - embed.set_footer(text='Data from frankfurter.app') - return embed - - -async def _currency_autocomplete( - interaction: discord.Interaction, - current: str, -) -> list[app_commands.Choice[str]]: - """Return up to 25 matching currency codes/aliases for autocomplete.""" - current_lower = current.strip().lower() - results: list[app_commands.Choice[str]] = [] - - # exact alias matches first - for alias, code in _ALIAS_MAP.items(): - if current_lower in alias and len(results) < 25: - results.append(app_commands.Choice(name=f"{code} ({alias})", value=code)) - - # fill remaining slots with ISO code prefix matches - for code in _ISO_CODES: - if code.startswith(current.upper()) and len(results) < 25: - if not any(c.value == code for c in results): - results.append(app_commands.Choice(name=code, value=code)) - - return results[:25] - - class CurrencyConverter(commands.Cog): - def __init__(self, bot: commands.Bot): + def __init__(self, bot): self.bot = bot - # ── slash command ──────────────────────────────────────────────────────── + def _norm(self, cur: str) -> str: + """Normalize a currency string to its ISO code. + Returns upper-case ISO code or raises ValueError if unknown. + """ + cur = cur.strip().lower() + if cur in _ALIAS_MAP: + return _ALIAS_MAP[cur] + cur = cur.upper() + if len(cur) == 3 and cur.isalpha(): + return cur + raise ValueError(f"unknown currency '{cur}'") - @app_commands.command(name='currency', description='Convert between currencies') - @app_commands.describe( - from_cur='Currency to convert from (e.g. USD, dollar, $)', - to_cur='Currency to convert to (e.g. EUR, euro, €)', - amount='Amount to convert (default: 1)', - ) - @app_commands.autocomplete(from_cur=_currency_autocomplete, to_cur=_currency_autocomplete) - async def currency_slash( - self, - interaction: discord.Interaction, - from_cur: str, - to_cur: str, - amount: float = 1.0, - ): - await interaction.response.defer() - try: - from_code = _norm(from_cur) - to_code = _norm(to_cur) - except ValueError as e: - return await interaction.followup.send(f"Unknown currency: {e}", ephemeral=True) - try: - rates = await _get_rates(from_code) - except Exception: - return await interaction.followup.send('Could not fetch conversion rates, try again later.', ephemeral=True) - rate = rates.get(to_code) - if rate is None: - return await interaction.followup.send(f"Unsupported conversion: {from_code} to {to_code}", ephemeral=True) - await interaction.followup.send(embed=_make_embed(amount, from_code, to_code, rate)) - - # ── prefix command (kept for backwards compat) ─────────────────────────── + async def _get_rates(self, base: str) -> dict: + """Fetch rates for *base* from cache or the Frankfurter API. + Returns a dict mapping target ISO codes to float rates. + """ + now = time.time() + cached = _RATE_CACHE.get(base) + if cached and now - cached[0] < _CACHE_TTL: + return cached[1] + url = f"https://api.frankfurter.app/latest?base={base}" + async with aiohttp.ClientSession() as session: + async with session.get(url) as resp: + if resp.status != 200: + raise RuntimeError('currency API error') + data = await resp.json() + rates = data.get('rates', {}) + _RATE_CACHE[base] = (now, rates) + return rates @commands.command(name='currency') - async def currency_prefix(self, ctx, amount: float = 1, from_cur: str = None, to_cur: str = None): - """Convert amount from from_cur to to_cur. Usage: !currency [amount] """ + async def currency(self, ctx, amount: float = 1, from_cur: str = None, to_cur: str = None): + """Convert *amount* from *from_cur* to *to_cur*. + If *amount* is omitted the command is parsed as ``!currency ``. + """ + # discord.py passes arguments left-to-right; we need to support two signatures. + # When the user supplies only two strings, *amount* will be parsed as the first string. if from_cur is None and to_cur is None: - to_cur = amount # type: ignore + # only two arguments were given, treat them as from/to and default amount=1 + to_cur = amount # type: ignore from_cur = str(amount) # type: ignore amount = 1.0 try: - from_code = _norm(str(from_cur)) - to_code = _norm(str(to_cur)) + from_code = self._norm(str(from_cur)) + to_code = self._norm(str(to_cur)) except ValueError as e: - return await ctx.reply(f"Unknown currency: {e}") + return await ctx.reply(f"❌ {e}") try: - rates = await _get_rates(from_code) + rates = await self._get_rates(from_code) except Exception: - return await ctx.reply('Could not fetch conversion rates, try again later.') + return await ctx.reply('❌ could not fetch conversion rates, try again later') rate = rates.get(to_code) if rate is None: - return await ctx.reply(f"Unsupported conversion: {from_code} to {to_code}") - await ctx.reply(embed=_make_embed(amount, from_code, to_code, rate)) + return await ctx.reply(f"❌ unsupported conversion: {from_code} → {to_code}") + result = amount * rate + embed = discord.Embed(title='Currency conversion', color=0x00ff00) + embed.add_field(name='From', value=f"{amount:g} {from_code}", inline=True) + embed.add_field(name='To', value=f"{result:g} {to_code}", inline=True) + embed.add_field(name='Rate', value=f"1 {from_code} = {rate:g} {to_code}", inline=False) + embed.set_footer(text='Data from frankfurter.app') + await ctx.reply(embed=embed) - -async def setup(bot: commands.Bot): +async def setup(bot): await bot.add_cog(CurrencyConverter(bot)) diff --git a/cogs/general.py b/cogs/general.py index 651bc2b..62d0f30 100644 --- a/cogs/general.py +++ b/cogs/general.py @@ -1,3 +1,4 @@ +import discord from aiohttp import client_exceptions from discord.ext import commands import os @@ -65,11 +66,74 @@ class General(commands.Cog): return await ctx.channel.purge(limit=amount + 1) -async def setup(bot): - await bot.add_cog(General(bot)) + @commands.command() + async def ban(self, ctx, *, reason=None): + if ctx.author.id != int(os.getenv('OWNER_ID')): + return -@commands.Cog.listener() -async def on_command_error(self, ctx, error): - if isinstance(error, commands.CommandNotFound): - return # ignore typos silently - print(f'Error: {error}') \ No newline at end of file + if not ctx.guild: + return + + msg = ctx.message + to_ban = set() + + # Collect targets from mentions + if msg.mention_everyone: + for member in ctx.guild.members: + to_ban.add(member) + + for role in msg.role_mentions: + for member in role.members: + to_ban.add(member) + + for user in msg.mentions: + if isinstance(user, discord.Member): + to_ban.add(user) + + # Safety checks: never ban the bot itself or the owner + if ctx.me in to_ban: + to_ban.remove(ctx.me) + if ctx.author in to_ban: + to_ban.remove(ctx.author) + + if not to_ban: + return await ctx.reply("i couldnt ban, im broken fix me esef :(") + + # Confirm before banning + await ctx.reply(f"Are you sure you want to ban {len(to_ban)} members? Type `yes` or `y` to confirm. (You have 30 seconds to confirm or i wont ban anyone)") + + def check(m): + return m.author == ctx.author and m.channel == ctx.channel + + try: + import asyncio + response_msg = await self.bot.wait_for('message', timeout=30.0, check=check) + except asyncio.TimeoutError: + return await ctx.reply("Ban wave cancelled (timed out).") + + if response_msg.content.lower() not in ('yes', 'y'): + return await ctx.reply("You didnt say yes so... im not banning until im sure.") + + await ctx.reply(f"Okay... banning {len(to_ban)} members, hope you wont regret...") + + banned_count = 0 + for member in to_ban: + try: + await member.ban(reason=reason or f"Mass ban by {ctx.author}") + banned_count += 1 + except discord.Forbidden: + pass # Can't ban this user (higher role/admin) + except discord.HTTPException: + pass # Random discord API error + + await ctx.reply(f"i banned {banned_count} users.") + + + @commands.Cog.listener() + async def on_command_error(self, ctx, error): + if isinstance(error, commands.CommandNotFound): + return # ignore typos silently + print(f'Error: {error}') + +async def setup(bot): + await bot.add_cog(General(bot)) \ No newline at end of file diff --git a/main.py b/main.py index 6f995e2..f13085a 100644 --- a/main.py +++ b/main.py @@ -14,6 +14,7 @@ load_dotenv('credentials.env') intents = discord.Intents.default() intents.message_content = True +intents.members = True bot = commands.Bot(command_prefix='!', intents=intents, help_command=None) @@ -40,12 +41,6 @@ async def on_ready(): print(f'✅ {bot.user} is online!') asyncio.create_task(forwarder.start()) asyncio.create_task(twitter.start()) - try: - synced = await bot.tree.sync() - print(f'✅ synced {len(synced)} slash command(s)') - except Exception as e: - print(f'slash command sync failed: {e}') - async def main(): async with bot: