ban commands added
This commit is contained in:
1 parent
a578c143e7
commit
1283569622
4 files changed
+96
-97
No files matched your search
Binary file not shown.
+29
-89
@@ -3,22 +3,21 @@ import time
|
|||||||
import json
|
import json
|
||||||
import aiohttp
|
import aiohttp
|
||||||
import discord
|
import discord
|
||||||
from discord import app_commands
|
|
||||||
from discord.ext import commands
|
from discord.ext import commands
|
||||||
|
|
||||||
# load aliases from json
|
# load aliases from json
|
||||||
with open(os.path.join(os.path.dirname(__file__), 'currency_aliases.json'), 'r', encoding='utf-8') as f:
|
with open(os.path.join(os.path.dirname(__file__), 'currency_aliases.json'), 'r', encoding='utf-8') as f:
|
||||||
_ALIAS_MAP = json.load(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)}
|
# in-memory cache for rates: {base: (timestamp, rates_dict)}
|
||||||
_RATE_CACHE = {}
|
_RATE_CACHE = {}
|
||||||
_CACHE_TTL = 3600 # seconds
|
_CACHE_TTL = 3600 # seconds
|
||||||
|
|
||||||
|
class CurrencyConverter(commands.Cog):
|
||||||
|
def __init__(self, bot):
|
||||||
|
self.bot = bot
|
||||||
|
|
||||||
def _norm(cur: str) -> str:
|
def _norm(self, cur: str) -> str:
|
||||||
"""Normalize a currency string to its ISO code.
|
"""Normalize a currency string to its ISO code.
|
||||||
Returns upper-case ISO code or raises ValueError if unknown.
|
Returns upper-case ISO code or raises ValueError if unknown.
|
||||||
"""
|
"""
|
||||||
@@ -30,9 +29,10 @@ def _norm(cur: str) -> str:
|
|||||||
return cur
|
return cur
|
||||||
raise ValueError(f"unknown currency '{cur}'")
|
raise ValueError(f"unknown currency '{cur}'")
|
||||||
|
|
||||||
|
async def _get_rates(self, base: str) -> dict:
|
||||||
async def _get_rates(base: str) -> dict:
|
"""Fetch rates for *base* from cache or the Frankfurter API.
|
||||||
"""Fetch rates for *base* from cache or the Frankfurter API."""
|
Returns a dict mapping target ISO codes to float rates.
|
||||||
|
"""
|
||||||
now = time.time()
|
now = time.time()
|
||||||
cached = _RATE_CACHE.get(base)
|
cached = _RATE_CACHE.get(base)
|
||||||
if cached and now - cached[0] < _CACHE_TTL:
|
if cached and now - cached[0] < _CACHE_TTL:
|
||||||
@@ -47,97 +47,37 @@ async def _get_rates(base: str) -> dict:
|
|||||||
_RATE_CACHE[base] = (now, rates)
|
_RATE_CACHE[base] = (now, rates)
|
||||||
return 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):
|
|
||||||
self.bot = bot
|
|
||||||
|
|
||||||
# ── slash command ────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
@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) ───────────────────────────
|
|
||||||
|
|
||||||
@commands.command(name='currency')
|
@commands.command(name='currency')
|
||||||
async def currency_prefix(self, ctx, amount: float = 1, from_cur: str = None, to_cur: str = None):
|
async def currency(self, ctx, amount: float = 1, from_cur: str = None, to_cur: str = None):
|
||||||
"""Convert amount from from_cur to to_cur. Usage: !currency [amount] <from> <to>"""
|
"""Convert *amount* from *from_cur* to *to_cur*.
|
||||||
|
If *amount* is omitted the command is parsed as ``!currency <from> <to>``.
|
||||||
|
"""
|
||||||
|
# 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:
|
if from_cur is None and to_cur is None:
|
||||||
|
# only two arguments were given, treat them as from/to and default amount=1
|
||||||
to_cur = amount # type: ignore
|
to_cur = amount # type: ignore
|
||||||
from_cur = str(amount) # type: ignore
|
from_cur = str(amount) # type: ignore
|
||||||
amount = 1.0
|
amount = 1.0
|
||||||
try:
|
try:
|
||||||
from_code = _norm(str(from_cur))
|
from_code = self._norm(str(from_cur))
|
||||||
to_code = _norm(str(to_cur))
|
to_code = self._norm(str(to_cur))
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
return await ctx.reply(f"Unknown currency: {e}")
|
return await ctx.reply(f"❌ {e}")
|
||||||
try:
|
try:
|
||||||
rates = await _get_rates(from_code)
|
rates = await self._get_rates(from_code)
|
||||||
except Exception:
|
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)
|
rate = rates.get(to_code)
|
||||||
if rate is None:
|
if rate is None:
|
||||||
return await ctx.reply(f"Unsupported conversion: {from_code} to {to_code}")
|
return await ctx.reply(f"❌ unsupported conversion: {from_code} → {to_code}")
|
||||||
await ctx.reply(embed=_make_embed(amount, from_code, to_code, rate))
|
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):
|
||||||
async def setup(bot: commands.Bot):
|
|
||||||
await bot.add_cog(CurrencyConverter(bot))
|
await bot.add_cog(CurrencyConverter(bot))
|
||||||
+66
-2
@@ -1,3 +1,4 @@
|
|||||||
|
import discord
|
||||||
from aiohttp import client_exceptions
|
from aiohttp import client_exceptions
|
||||||
from discord.ext import commands
|
from discord.ext import commands
|
||||||
import os
|
import os
|
||||||
@@ -65,11 +66,74 @@ class General(commands.Cog):
|
|||||||
return
|
return
|
||||||
await ctx.channel.purge(limit=amount + 1)
|
await ctx.channel.purge(limit=amount + 1)
|
||||||
|
|
||||||
async def setup(bot):
|
@commands.command()
|
||||||
await bot.add_cog(General(bot))
|
async def ban(self, ctx, *, reason=None):
|
||||||
|
if ctx.author.id != int(os.getenv('OWNER_ID')):
|
||||||
|
return
|
||||||
|
|
||||||
|
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()
|
@commands.Cog.listener()
|
||||||
async def on_command_error(self, ctx, error):
|
async def on_command_error(self, ctx, error):
|
||||||
if isinstance(error, commands.CommandNotFound):
|
if isinstance(error, commands.CommandNotFound):
|
||||||
return # ignore typos silently
|
return # ignore typos silently
|
||||||
print(f'Error: {error}')
|
print(f'Error: {error}')
|
||||||
|
|
||||||
|
async def setup(bot):
|
||||||
|
await bot.add_cog(General(bot))␍
|
||||||
@@ -14,6 +14,7 @@ load_dotenv('credentials.env')
|
|||||||
|
|
||||||
intents = discord.Intents.default()
|
intents = discord.Intents.default()
|
||||||
intents.message_content = True
|
intents.message_content = True
|
||||||
|
intents.members = True
|
||||||
|
|
||||||
bot = commands.Bot(command_prefix='!', intents=intents, help_command=None)
|
bot = commands.Bot(command_prefix='!', intents=intents, help_command=None)
|
||||||
|
|
||||||
@@ -40,12 +41,6 @@ async def on_ready():
|
|||||||
print(f'✅ {bot.user} is online!')
|
print(f'✅ {bot.user} is online!')
|
||||||
asyncio.create_task(forwarder.start())
|
asyncio.create_task(forwarder.start())
|
||||||
asyncio.create_task(twitter.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 def main():
|
||||||
async with bot:
|
async with bot:
|
||||||
|
|||||||
Reference in new issue
Block a user