├── None ├── errors.xlsx ├── commands.xlsx ├── .vscode ├── settings.json └── launch.json ├── assets ├── generators │ └── assets │ │ ├── qp.png │ │ ├── 1st.png │ │ ├── 2nd.png │ │ ├── 3rd.png │ │ ├── beta.png │ │ ├── card.png │ │ ├── font.ttf │ │ ├── font2.ttf │ │ ├── top30.png │ │ ├── voted.png │ │ ├── creator.png │ │ ├── partner.png │ │ ├── richest.png │ │ ├── top100.png │ │ └── country │ │ ├── uk.png │ │ ├── usa.png │ │ ├── brazil.png │ │ ├── canada.png │ │ ├── china.png │ │ ├── denmark.png │ │ ├── egypt.png │ │ ├── europe.png │ │ ├── france.png │ │ ├── germany.png │ │ ├── iceland.png │ │ ├── japan.png │ │ ├── mexico.png │ │ ├── poland.png │ │ ├── russia.png │ │ ├── spain.png │ │ ├── sweden.png │ │ ├── turkey.png │ │ ├── australia.png │ │ ├── greenland.png │ │ └── southafrica.png ├── dictionary.py ├── images.py └── blackjack_game.py ├── requirements.py ├── languages ├── en │ ├── error.py │ ├── levelling.py │ ├── help.py │ ├── fun.py │ ├── reactions.py │ ├── inventory.py │ └── hentai.py └── fr │ ├── error.py │ ├── levelling.py │ ├── help.py │ ├── fun.py │ ├── reactions.py │ ├── inventory.py │ └── hentai.py ├── LICENSE ├── cogs ├── cmd_logger.py ├── error.py ├── help.py ├── inventory.py ├── levelling.py ├── image.py ├── hentai.py ├── fun.py ├── owner.py └── info.py ├── README.md ├── config.py ├── jeanne.py ├── events ├── dbl.py ├── tasks.py ├── listeners.py └── welcomer.py ├── .gitignore └── Privacy_policy.md /None: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /errors.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Stacer-Varien/Jeanne-Bot/HEAD/errors.xlsx -------------------------------------------------------------------------------- /commands.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Stacer-Varien/Jeanne-Bot/HEAD/commands.xlsx -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "python.formatting.provider": "yapf", 3 | "discord.enabled": true 4 | } -------------------------------------------------------------------------------- /assets/generators/assets/qp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Stacer-Varien/Jeanne-Bot/HEAD/assets/generators/assets/qp.png -------------------------------------------------------------------------------- /assets/generators/assets/1st.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Stacer-Varien/Jeanne-Bot/HEAD/assets/generators/assets/1st.png -------------------------------------------------------------------------------- /assets/generators/assets/2nd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Stacer-Varien/Jeanne-Bot/HEAD/assets/generators/assets/2nd.png -------------------------------------------------------------------------------- /assets/generators/assets/3rd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Stacer-Varien/Jeanne-Bot/HEAD/assets/generators/assets/3rd.png -------------------------------------------------------------------------------- /assets/generators/assets/beta.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Stacer-Varien/Jeanne-Bot/HEAD/assets/generators/assets/beta.png -------------------------------------------------------------------------------- /assets/generators/assets/card.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Stacer-Varien/Jeanne-Bot/HEAD/assets/generators/assets/card.png -------------------------------------------------------------------------------- /assets/generators/assets/font.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Stacer-Varien/Jeanne-Bot/HEAD/assets/generators/assets/font.ttf -------------------------------------------------------------------------------- /assets/generators/assets/font2.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Stacer-Varien/Jeanne-Bot/HEAD/assets/generators/assets/font2.ttf -------------------------------------------------------------------------------- /assets/generators/assets/top30.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Stacer-Varien/Jeanne-Bot/HEAD/assets/generators/assets/top30.png -------------------------------------------------------------------------------- /assets/generators/assets/voted.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Stacer-Varien/Jeanne-Bot/HEAD/assets/generators/assets/voted.png -------------------------------------------------------------------------------- /assets/generators/assets/creator.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Stacer-Varien/Jeanne-Bot/HEAD/assets/generators/assets/creator.png -------------------------------------------------------------------------------- /assets/generators/assets/partner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Stacer-Varien/Jeanne-Bot/HEAD/assets/generators/assets/partner.png -------------------------------------------------------------------------------- /assets/generators/assets/richest.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Stacer-Varien/Jeanne-Bot/HEAD/assets/generators/assets/richest.png -------------------------------------------------------------------------------- /assets/generators/assets/top100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Stacer-Varien/Jeanne-Bot/HEAD/assets/generators/assets/top100.png -------------------------------------------------------------------------------- /assets/generators/assets/country/uk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Stacer-Varien/Jeanne-Bot/HEAD/assets/generators/assets/country/uk.png -------------------------------------------------------------------------------- /assets/generators/assets/country/usa.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Stacer-Varien/Jeanne-Bot/HEAD/assets/generators/assets/country/usa.png -------------------------------------------------------------------------------- /assets/generators/assets/country/brazil.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Stacer-Varien/Jeanne-Bot/HEAD/assets/generators/assets/country/brazil.png -------------------------------------------------------------------------------- /assets/generators/assets/country/canada.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Stacer-Varien/Jeanne-Bot/HEAD/assets/generators/assets/country/canada.png -------------------------------------------------------------------------------- /assets/generators/assets/country/china.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Stacer-Varien/Jeanne-Bot/HEAD/assets/generators/assets/country/china.png -------------------------------------------------------------------------------- /assets/generators/assets/country/denmark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Stacer-Varien/Jeanne-Bot/HEAD/assets/generators/assets/country/denmark.png -------------------------------------------------------------------------------- /assets/generators/assets/country/egypt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Stacer-Varien/Jeanne-Bot/HEAD/assets/generators/assets/country/egypt.png -------------------------------------------------------------------------------- /assets/generators/assets/country/europe.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Stacer-Varien/Jeanne-Bot/HEAD/assets/generators/assets/country/europe.png -------------------------------------------------------------------------------- /assets/generators/assets/country/france.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Stacer-Varien/Jeanne-Bot/HEAD/assets/generators/assets/country/france.png -------------------------------------------------------------------------------- /assets/generators/assets/country/germany.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Stacer-Varien/Jeanne-Bot/HEAD/assets/generators/assets/country/germany.png -------------------------------------------------------------------------------- /assets/generators/assets/country/iceland.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Stacer-Varien/Jeanne-Bot/HEAD/assets/generators/assets/country/iceland.png -------------------------------------------------------------------------------- /assets/generators/assets/country/japan.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Stacer-Varien/Jeanne-Bot/HEAD/assets/generators/assets/country/japan.png -------------------------------------------------------------------------------- /assets/generators/assets/country/mexico.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Stacer-Varien/Jeanne-Bot/HEAD/assets/generators/assets/country/mexico.png -------------------------------------------------------------------------------- /assets/generators/assets/country/poland.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Stacer-Varien/Jeanne-Bot/HEAD/assets/generators/assets/country/poland.png -------------------------------------------------------------------------------- /assets/generators/assets/country/russia.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Stacer-Varien/Jeanne-Bot/HEAD/assets/generators/assets/country/russia.png -------------------------------------------------------------------------------- /assets/generators/assets/country/spain.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Stacer-Varien/Jeanne-Bot/HEAD/assets/generators/assets/country/spain.png -------------------------------------------------------------------------------- /assets/generators/assets/country/sweden.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Stacer-Varien/Jeanne-Bot/HEAD/assets/generators/assets/country/sweden.png -------------------------------------------------------------------------------- /assets/generators/assets/country/turkey.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Stacer-Varien/Jeanne-Bot/HEAD/assets/generators/assets/country/turkey.png -------------------------------------------------------------------------------- /assets/generators/assets/country/australia.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Stacer-Varien/Jeanne-Bot/HEAD/assets/generators/assets/country/australia.png -------------------------------------------------------------------------------- /assets/generators/assets/country/greenland.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Stacer-Varien/Jeanne-Bot/HEAD/assets/generators/assets/country/greenland.png -------------------------------------------------------------------------------- /assets/generators/assets/country/southafrica.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Stacer-Varien/Jeanne-Bot/HEAD/assets/generators/assets/country/southafrica.png -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "Python: Current File", 9 | "type": "python", 10 | "request": "launch", 11 | "program": "jeanne.py", 12 | "console": "integratedTerminal", 13 | "justMyCode": true 14 | } 15 | ] 16 | } -------------------------------------------------------------------------------- /requirements.py: -------------------------------------------------------------------------------- 1 | # To run this bot, the packages are set and ready to install. 2 | import os 3 | import platform 4 | 5 | if platform.system() == "Windows": # If you are using a Windows Operating System 6 | # Upgrades the pip package for fluent installation 7 | os.system("python.exe -m pip install -U pip") 8 | elif platform.system() == "Linux": # If you are using a Linux Operating System 9 | # Upgrades the pip package for fluent installation #You need Python3.9 and over with an activated venv 10 | os.system("python3 -m pip install -U pip") 11 | # Required packages for the bot to use. Also upgrades it if a new release is found 12 | os.system( 13 | "pip install -U discord.py aiohttp websockets requests asyncio python-dotenv humanfriendly datetime Pillow py-expression-eval topggpy lxml reactionmenu jishaku pandas openpyxl" 14 | ) 15 | -------------------------------------------------------------------------------- /languages/en/error.py: -------------------------------------------------------------------------------- 1 | from discord import Color, Embed, Interaction 2 | from discord import app_commands as Jeanne 3 | from discord.ext.commands import Bot 4 | 5 | 6 | class Errors(): 7 | def __init__(self, bot: Bot): 8 | self.bot = bot 9 | 10 | async def handle_missing_permissions(self, ctx: Interaction, error: Jeanne.MissingPermissions): 11 | embed = Embed( 12 | description=f"You are missing {''.join(error.missing_permissions)} for this command", 13 | color=Color.red(), 14 | ) 15 | await ctx.response.send_message(embed=embed) 16 | 17 | async def handle_bot_missing_permissions(self, ctx: Interaction, error: Jeanne.BotMissingPermissions): 18 | embed = Embed( 19 | description=f"I am missing {''.join(error.missing_permissions)} for this command", 20 | color=Color.red(), 21 | ) 22 | await ctx.response.send_message(embed=embed) 23 | 24 | -------------------------------------------------------------------------------- /languages/fr/error.py: -------------------------------------------------------------------------------- 1 | from discord import Color, Embed, Interaction 2 | from discord import app_commands as Jeanne 3 | from discord.ext.commands import Bot 4 | 5 | 6 | class Errors(): 7 | def __init__(self, bot: Bot): 8 | self.bot = bot 9 | 10 | async def handle_missing_permissions(self, ctx: Interaction, error: Jeanne.MissingPermissions): 11 | embed = Embed( 12 | description=f"Il vous manque {''.join(error.missing_permissions)} pour cette commande", 13 | color=Color.red(), 14 | ) 15 | await ctx.response.send_message(embed=embed) 16 | 17 | async def handle_bot_missing_permissions(self, ctx: Interaction, error: Jeanne.BotMissingPermissions): 18 | embed = Embed( 19 | description=f"Je manque {''.join(error.missing_permissions)} pour cette commande", 20 | color=Color.red(), 21 | ) 22 | await ctx.response.send_message(embed=embed) 23 | 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Stacer-Varien 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /cogs/cmd_logger.py: -------------------------------------------------------------------------------- 1 | from typing import Union 2 | from discord import Interaction, app_commands as Jeanne 3 | from discord.ext.commands import Bot, Cog 4 | from datetime import datetime 5 | import pandas as pd 6 | 7 | 8 | class CommandLog(Cog, name="CommandLogSlash"): 9 | def __init__(self, bot: Bot) -> None: 10 | self.bot = bot 11 | 12 | @Cog.listener() 13 | async def on_app_command_completion( 14 | self, ctx: Interaction, command: Union[Jeanne.Command, Jeanne.ContextMenu] 15 | ): 16 | 17 | existing_file = "commands.xlsx" 18 | new_data = { 19 | "Date and Time": [str(datetime.now())], 20 | "Username": [ctx.user], 21 | "User ID": [str(ctx.user.id)], 22 | "Command Used": [command.qualified_name], 23 | "Command Usage": [str(ctx.data)], 24 | } 25 | df_new = pd.DataFrame(new_data) 26 | df_existing = pd.read_excel(existing_file) 27 | df_combined = df_existing._append(df_new, ignore_index=True) 28 | df_combined.to_excel(existing_file, index=False) 29 | 30 | 31 | async def setup(bot: Bot): 32 | await bot.add_cog(CommandLog(bot)) 33 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Jeanne Discord Bot 2 | 3 | Jeanne is a multipurpose Discord bot with miscellaneous, moderation, management, and fun commands. It offers a range of features to enhance your server's functionality and user experience. 4 | 5 | ## Features 6 | 7 | ✨ **Slash Commands:** Jeanne supports slash commands for easy and intuitive interaction. 8 | 9 | 📈 **Levelling System:** Engage your community with a built-in levelling system to reward active members. 10 | 11 | ⚖️ **Moderation:** Ensure a safe and harmonious server environment with moderation commands such as purge, warn, timeout, kick, ban, and unban. 12 | 13 | 🛠️ **Server Management:** Take control of your server with commands to create, delete, and rename categories, roles, and text and voice channels. 14 | 15 | 🎮 **Fun Commands:** Keep your members entertained with a variety of fun commands to play with. 16 | 17 | ℹ️ **Information Retrieval:** Get detailed information about users and servers at your fingertips. 18 | 19 | 🙌 **Reaction System:** Express your emotions with reaction commands like slap, hug, and pat. 20 | 21 | 👋 **Welcome and Goodbye Messages:** Greet new members and bid farewell to those who leave with customizable welcome and goodbye messages. 22 | 23 | 📊 **Advanced Embed Generator:** Create visually appealing embeds with [Discohook](discohook.org.) 24 | 25 | > Note: This bot is primarily intended for educational purposes. We do not recommend self-hosting the bot as updates may introduce new private APIs required for certain features. 26 | 27 | ## Getting Started 28 | 29 | To invite Jeanne to your server, click the link below: 30 | 31 | 🔗 [Invite Jeanne](https://discord.com/oauth2/authorize?client_id=831993597166747679) 32 | 33 | 34 | 35 | ## Support and Feedback 36 | 37 | If you have any questions or need assistance, feel free to join our support server or contact us via email: 38 | 39 | 🌐 Support Server: [Join Now](https://discord.gg/Vfa796yvNq) 40 | 41 | 📧 Email: jeannebot.discord@gmail.com 42 | -------------------------------------------------------------------------------- /config.py: -------------------------------------------------------------------------------- 1 | """Everything such as APIs and tokens for the bot, commands and functions to run on""" 2 | 3 | from os import getenv 4 | from dotenv import load_dotenv 5 | from sqlite3 import connect 6 | 7 | load_dotenv() 8 | TOKEN = getenv("token") 9 | WEATHER = getenv("weather_api") 10 | TOPGG = getenv("topgg") 11 | TOPGG_AUTH = getenv("topgg_auth") 12 | DB_AUTH = getenv("db_auth") 13 | WEBHOOK = getenv("report_webhook") 14 | BB_WEBHOOK = getenv("botban_webhook") 15 | TENOR = getenv("tenor") 16 | CLIENTKEY = getenv("client_key") 17 | JEANNE = str(getenv("jeanne_album")) 18 | SABER = str(getenv("saber_album")) 19 | WALLPAPER = str(getenv("wallpaper_album")) 20 | MEDUSA = str(getenv("medusa_album")) 21 | ANIMEME = str(getenv("animeme_album")) 22 | NEKO = str(getenv("neko_album")) 23 | MORGAN = str(getenv("morgan_album")) 24 | KITSUNE = str(getenv("kitsune_album")) 25 | CATBOX_HASH = str(getenv("catbox_hash")) 26 | BADGES = str(getenv("badges_album")) 27 | STATUS_WEBHOOK=str(getenv("status")) 28 | 29 | db = connect("database.db") 30 | 31 | hug = f"https://tenor.googleapis.com/v2/search?q=hug%20anime&key={TENOR}&client_key={CLIENTKEY}&limit=35" 32 | slap = f"https://tenor.googleapis.com/v2/search?q=slap%20anime&key={TENOR}&client_key={CLIENTKEY}&limit=35" 33 | smug = f"https://tenor.googleapis.com/v2/search?q=smug%20anime&key={TENOR}&client_key={CLIENTKEY}&limit=35" 34 | poke = f"https://tenor.googleapis.com/v2/search?q=poke%20anime&key={TENOR}&client_key={CLIENTKEY}&limit=35" 35 | pat = f"https://tenor.googleapis.com/v2/search?q=headpat%20anime&key={TENOR}&client_key={CLIENTKEY}&limit=35" 36 | kiss = f"https://tenor.googleapis.com/v2/search?q=kiss%20anime&key={TENOR}&client_key={CLIENTKEY}&limit=35" 37 | tickle = f"https://tenor.googleapis.com/v2/search?q=tickle%20anime&key={TENOR}&client_key={CLIENTKEY}&limit=35" 38 | baka = f"https://tenor.googleapis.com/v2/search?q=baka%20anime&key={TENOR}&client_key={CLIENTKEY}&limit=35" 39 | feed = f"https://tenor.googleapis.com/v2/search?q=feed%20anime&key={TENOR}&client_key={CLIENTKEY}&limit=35" 40 | cry = f"https://tenor.googleapis.com/v2/search?q=cry%20anime&key={TENOR}&client_key={CLIENTKEY}&limit=35" 41 | bite = f"https://tenor.googleapis.com/v2/search?q=bite%20anime&key={TENOR}&client_key={CLIENTKEY}&limit=35" 42 | blush = f"https://tenor.googleapis.com/v2/search?q=blush%20anime&key={TENOR}&client_key={CLIENTKEY}&limit=35" 43 | cuddle = f"https://tenor.googleapis.com/v2/search?q=cuddle%20anime&key={TENOR}&client_key={CLIENTKEY}&limit=35" 44 | dance = f"https://tenor.googleapis.com/v2/search?q=dance%20anime&key={TENOR}&client_key={CLIENTKEY}&limit=35" 45 | -------------------------------------------------------------------------------- /jeanne.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | from discord.ext.commands import AutoShardedBot, when_mentioned_or 3 | from discord import Intents, AllowedMentions 4 | from os import listdir 5 | from languages.Translator import MyTranslator 6 | from config import TOKEN 7 | 8 | 9 | 10 | class Jeanne(AutoShardedBot): 11 | async def setup_hook(self): 12 | dirs = ["./events", "./cogs"] 13 | for i in dirs: 14 | for filename in listdir(i): 15 | if filename.endswith(".py"): 16 | await self.load_extension(f"{i[2:]}.{filename[:-3]}") 17 | print(f"{i}.{filename} loaded") 18 | else: 19 | print(f"Unable to load {i}.{filename[:-3]}") 20 | self.translator = MyTranslator() 21 | await self.tree.set_translator(self.translator) 22 | await self.load_extension("jishaku") 23 | await self.tree.sync() 24 | 25 | 26 | intents = Intents() 27 | intents.messages = True 28 | intents.message_content = True 29 | intents.guilds = True 30 | intents.members = True 31 | intents.reactions = True 32 | intents.expressions = True 33 | intents.typing = True 34 | intents.presences = False 35 | intents.voice_states = False 36 | intents.auto_moderation = False 37 | intents.invites = False 38 | intents.integrations = False 39 | intents.webhooks = False 40 | intents.guild_scheduled_events = False 41 | 42 | 43 | bot = Jeanne( 44 | command_prefix=when_mentioned_or("J!", "j!", "Jeanne", "jeanne"), 45 | intents=intents, 46 | allowed_mentions=AllowedMentions.all(), 47 | case_insensitive=True, 48 | strip_after_prefix=True, 49 | chunk_guilds_at_startup=False, 50 | ) 51 | bot.remove_command("help") 52 | 53 | 54 | @bot.event 55 | async def on_ready(): 56 | print("Connected to bot: {}".format(bot.user.name)) 57 | print("Bot ID: {}".format(bot.user.id)) 58 | print("Connected to {} servers".format(len(bot.guilds))) 59 | print("Listening to {} shards".format(bot.shard_count)) 60 | 61 | for guild in bot.guilds: 62 | try: 63 | print(f"Chunking guild: {guild.name} ({guild.id})...") 64 | await asyncio.wait_for(guild.chunk(), timeout=60.0) 65 | print(f"Successfully chunked {guild.name}.") 66 | except asyncio.TimeoutError: 67 | print(f"Chunking timed out for {guild.name}.") 68 | except Exception as e: 69 | print(f"An error occurred while chunking {guild.name}: {e}") 70 | print("Listening to {} users".format(len(bot.users))) 71 | 72 | 73 | bot.run(TOKEN) 74 | -------------------------------------------------------------------------------- /assets/dictionary.py: -------------------------------------------------------------------------------- 1 | from discord import Color, Embed, Interaction 2 | import requests 3 | from reactionmenu import ViewMenu, ViewButton 4 | 5 | 6 | async def dictionary(ctx: Interaction, word: str): 7 | 8 | embed = Embed() 9 | response = requests.get(f"https://api.dictionaryapi.dev/api/v2/entries/en/{word}") 10 | data = response.json() 11 | 12 | if response.status_code == 404: 13 | embed.color = Color.red() 14 | embed.title = data["title"] 15 | embed.description = ( 16 | "Sorry, definitions for the word you were looking for could not be found." 17 | ) 18 | await ctx.response.send_message(embed=embed) 19 | return 20 | 21 | if response.status_code == 429: 22 | embed.color = Color.red() 23 | embed.title = data["title"] 24 | embed.description = "Unfortunately, the dictionary API is being rate limited.\nPlease wait for a few minutes." 25 | await ctx.response.send_message(embed=embed) 26 | return 27 | 28 | if response.status_code != 200: 29 | embed.color = Color.red() 30 | embed.title = data["title"] 31 | embed.description = "It seems the dictionary API is facing problems.\nPlease wait for a few minutes." 32 | await ctx.response.send_message(embed=embed) 33 | return 34 | 35 | menu = ViewMenu( 36 | ctx, 37 | menu_type=ViewMenu.TypeEmbed, 38 | disable_items_on_timeout=True, 39 | style="Page $/& | Fetched from dictionaryapi.dev", 40 | ) 41 | embed.color = Color.random() 42 | embed.title = f"Word: {data[0]['word']}" 43 | 44 | for i in data[0]["meanings"]: 45 | partOfSpeech = i["partOfSpeech"] 46 | for j in i["definitions"]: 47 | definition = j["definition"] 48 | try: 49 | example = j["example"] 50 | except: 51 | pass 52 | 53 | page_embed = Embed(color=embed.color, title=embed.title) 54 | page_embed.add_field( 55 | name="Part of Speech", value=partOfSpeech, inline=False 56 | ) 57 | page_embed.add_field(name="Definition", value=definition, inline=False) 58 | try: 59 | page_embed.add_field(name="Example", value=example, inline=False) 60 | except: 61 | pass 62 | 63 | menu.add_page(embed=page_embed) 64 | 65 | menu.add_button(ViewButton.go_to_first_page()) 66 | menu.add_button(ViewButton.back()) 67 | menu.add_button(ViewButton.next()) 68 | menu.add_button(ViewButton.go_to_last_page()) 69 | 70 | await menu.start() 71 | -------------------------------------------------------------------------------- /events/dbl.py: -------------------------------------------------------------------------------- 1 | import aiohttp 2 | from functions import BetaTest, Currency, Levelling, DevPunishment 3 | from config import DB_AUTH, TOPGG, TOPGG_AUTH 4 | from topgg import DBLClient, WebhookManager 5 | from discord.ext import tasks 6 | from discord.ext.commands import Cog, Bot 7 | from datetime import datetime 8 | 9 | 10 | class DBL(Cog, name="DBL"): 11 | def __init__(self, bot: Bot): 12 | self.bot = bot 13 | self.topggpy = DBLClient( 14 | bot=self.bot, token=TOPGG, autopost=True, post_shard_count=True 15 | ) 16 | self.topgg_webhook = WebhookManager(self.bot).dbl_webhook( 17 | route="/dblwebhook", auth_key=TOPGG_AUTH 18 | ) 19 | self.topgg_webhook.run(5000) 20 | self.update_stats.start() 21 | 22 | @tasks.loop(minutes=30, reconnect=True) 23 | async def update_stats(self): 24 | servers = len(self.bot.guilds) 25 | dbheaders = { 26 | "Content-Type": "application/json", 27 | "Authorization": DB_AUTH, 28 | } 29 | try: 30 | async with aiohttp.ClientSession(headers=dbheaders) as session: 31 | await session.post( 32 | "https://discord.bots.gg/api/v1/bots/831993597166747679/stats", 33 | json={"guildCount": servers, "shardCount": self.bot.shard_count}, 34 | ) 35 | await self.topggpy.post_guild_count( 36 | guild_count=servers, shard_count=self.bot.shard_count 37 | ) 38 | print( 39 | f"Posted server and shard count ({servers}, {self.bot.shard_count}) at {datetime.now().strftime('%H:%M')}" 40 | ) 41 | except Exception as e: 42 | print(f"Failed to post stats\n{e.__class__.__name__}: {e}") 43 | 44 | @update_stats.before_loop 45 | async def before_update_stats(self): 46 | await self.bot.wait_until_ready() 47 | 48 | @Cog.listener() 49 | async def on_dbl_vote(self, data: dict): 50 | if data["type"] != "upvote": 51 | return 52 | 53 | voter_id = int(data["user"]) 54 | voter = await self.bot.fetch_user(voter_id) 55 | if DevPunishment(voter).check_botbanned_user: 56 | return 57 | 58 | weekend_bonus = await self.topggpy.get_weekend_status() 59 | credits = 100 if weekend_bonus else 50 60 | xp_multiplier = 10 if weekend_bonus else 5 61 | xp = xp_multiplier * Levelling(voter).get_user_level 62 | 63 | if await BetaTest(self.bot).check(voter): 64 | credits = round(credits * 1.25) 65 | xp = 5 * round(xp / 5) 66 | 67 | await Currency(voter).add_qp(credits) 68 | await Levelling(voter).add_xp(xp) 69 | 70 | 71 | async def setup(bot: Bot): 72 | await bot.add_cog(DBL(bot)) 73 | -------------------------------------------------------------------------------- /cogs/error.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | import traceback 3 | from discord import Color, Embed, Interaction 4 | from discord import app_commands as Jeanne 5 | from discord.ext.commands import Bot, Cog 6 | import pandas as pd 7 | import languages.en.error as en 8 | import languages.fr.error as fr 9 | 10 | 11 | class ErrorsCog(Cog, name="ErrorsSlash"): 12 | def __init__(self, bot: Bot): 13 | self.bot = bot 14 | 15 | def cog_load(self): 16 | tree = self.bot.tree 17 | self._old_tree_error = tree.on_error 18 | tree.on_error = self.on_app_command_error 19 | 20 | def cog_unload(self): 21 | tree = self.bot.tree 22 | tree.on_error = self._old_tree_error 23 | 24 | @Cog.listener() 25 | async def on_app_command_error( 26 | self, ctx: Interaction, error: Jeanne.AppCommandError 27 | ): 28 | existing_file = "errors.xlsx" 29 | error_traceback = "".join( 30 | traceback.format_exception(type(error), error, error.__traceback__) 31 | ) 32 | new_data = { 33 | "Date": [f"{datetime.now()}"], 34 | "Command": [f"{ctx.command.qualified_name}"], 35 | "Error": [{f"{error_traceback}"}], 36 | } 37 | df_new = pd.DataFrame(new_data) 38 | df_existing = pd.read_excel(existing_file) 39 | df_combined = df_existing._append(df_new, ignore_index=True) 40 | df_combined.to_excel(existing_file, index=False) 41 | 42 | if isinstance(error, Jeanne.MissingPermissions): 43 | if ctx.locale.value == "en-GB" or ctx.locale.value == "en-US": 44 | await en.Errors.handle_missing_permissions(self, ctx, error) 45 | elif ctx.locale.value == "fr": 46 | await fr.Errors.handle_missing_permissions(self, ctx, error) 47 | elif isinstance(error, Jeanne.BotMissingPermissions): 48 | if ctx.locale.value == "en-GB" or ctx.locale.value == "en-US": 49 | await en.Errors.handle_bot_missing_permissions(self, ctx, error) 50 | elif ctx.locale.value == "fr": 51 | await fr.Errors.handle_bot_missing_permissions(self, ctx, error) 52 | elif isinstance(error, Jeanne.NoPrivateMessage): 53 | embed = Embed(description=str(error), color=Color.red()) 54 | await ctx.response.send_message(embed=embed) 55 | elif isinstance(error, Jeanne.CommandInvokeError) and isinstance( 56 | error.original, RuntimeError 57 | ): 58 | if ctx.command.qualified_name == "help command": 59 | return 60 | embed = Embed(description=str(error), color=Color.red()) 61 | await ctx.response.send_message(embed=embed) 62 | elif isinstance(error, Jeanne.CommandOnCooldown): 63 | pass 64 | 65 | 66 | async def setup(bot: Bot): 67 | await bot.add_cog(ErrorsCog(bot)) 68 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | 131 | #databases 132 | database.db 133 | logging.db 134 | 135 | #files 136 | /assets/media.py 137 | *.db 138 | cmd-invoke-errors.txt 139 | voter_data.json 140 | assets/Images/ 141 | commandlog.txt 142 | errors.txt 143 | commandlog.csv 144 | errors.csv 145 | -------------------------------------------------------------------------------- /Privacy_policy.md: -------------------------------------------------------------------------------- 1 | # Privacy Policy 2 | 3 | This document outlines the privacy policy and agreement that you accept when adding Jeanne (Bot ID 831993597166747679) to a server or as a member of such a server. This document does not supersede the following: 4 | 5 | * [Developer Terms of Service](https://discordapp.com/developers/docs/legal) 6 | * [Discord's Terms of Service](https://discord.com/terms) 7 | * [Community Guidelines](https://discord.com/guidelines) 8 | 9 | ## Data Collection 10 | 11 | The main data is stored in an SQLite Database. Errors and usage of commands are logged into two separate CSV files. Level cooldowns are stored in a JSON file. The data is backed up in case of changes or failure to prevent data loss and damage. It can only be accessed by the bot's developer, [Stacer Varien](https://github.com/Stacer-Varien). In the event of unintentional data loss or alteration, the developer will try to compensate affected users. 12 | 13 | User IDs, server IDs, and channel IDs are the main data stored and kept privately in the database. However, they are not always 100% secure. The data is not sold or given to unauthorized third parties unless required by law or the Terms of Service. The CSV files are cleared with each update. 14 | 15 | The user's or server's data is **not registered** in the database if: 16 | 17 | 1. The user has not sent a message in the server or is sending messages in ignored channels and direct messages between them and the bot, or in a channel where the bot cannot view. 18 | 2. A certain command required to add/enter data in the database has not been used at least once. 19 | 3. The user has been banned from using the bot. 20 | 21 | ## Confession Command Policy 22 | 23 | The `/confess` command stores anonymous confessions in the database for moderation and user safety purposes. All confessions are retained for **30 days** before being automatically deleted, unless flagged or escalated. 24 | 25 | If a confession is **reported** to the developer or moderators by a server member, it will be **reviewed and escalated** based on the severity of the situation. This may involve logging the confession for a longer duration, notifying server administrators, or involving Discord Trust & Safety if necessary. 26 | 27 | While all data is kept private and stored securely, users are reminded that misuse of this system—including threats, abuse, or illegal content—may result in permanent blacklisting from the bot and action taken as per Discord's Community Guidelines. 28 | 29 | ## Data Access 30 | 31 | Data can be accessed by everyone but is limited except the bot developer, who has full access to all kinds of data. Level and rank data can be accessed freely, but data such as inventory can only be accessed by the main user. 32 | 33 | ## Removal of Data 34 | 35 | Users are free to request the removal of data by joining the [support server](https://discord.gg/Vfa796yvNq) or by emailing [jeannebot.discord@gmail.com](mailto:jeannebot.discord@gmail.com) to contact the bot developer with valid reasons. 36 | 37 | ## Questions 38 | 39 | If you have any questions concerning this policy, please feel free to join the server and ask or email [jeannebot.discord@gmail.com](mailto:jeannebot.discord@gmail.com). 40 | -------------------------------------------------------------------------------- /cogs/help.py: -------------------------------------------------------------------------------- 1 | from discord import ( 2 | Interaction, 3 | app_commands as Jeanne, 4 | ) 5 | from discord.ext.commands import GroupCog, Bot 6 | from functions import AutoCompleteChoices, check_botbanned_app_command, is_suspended 7 | import languages.en.help as en 8 | import languages.fr.help as fr 9 | from discord.app_commands import locale_str as T 10 | 11 | 12 | class HelpGroup(GroupCog, name=T("help_group_name")): 13 | def __init__(self, bot: Bot): 14 | self.bot = bot 15 | 16 | @Jeanne.command( 17 | name=T("command_name"), 18 | description=T("command_desc"), 19 | extras={ 20 | "en": { 21 | "name": "help command", 22 | "description": "Get help with a command", 23 | "parameters": [ 24 | { 25 | "name": "command", 26 | "description": "Get help on a certain command", 27 | "required": True, 28 | } 29 | ], 30 | }, 31 | "fr": { 32 | "name": "aide commande", 33 | "description": "Obtenez de l'aide sur une certaine commande", 34 | "parameters": [ 35 | { 36 | "name": "commande", 37 | "description": "Obtenez de l'aide sur une certaine commande", 38 | "required": True, 39 | } 40 | ], 41 | }, 42 | }, 43 | ) 44 | @Jeanne.autocomplete(command=AutoCompleteChoices.command_choices) 45 | @Jeanne.rename(command=T("command_parm_name")) 46 | @Jeanne.describe(command=T("command_parm_desc")) 47 | @Jeanne.check(check_botbanned_app_command) 48 | @Jeanne.check(is_suspended) 49 | async def command(self, ctx: Interaction, command: Jeanne.Range[str, 3]): 50 | if ctx.locale.value == "fr": 51 | await fr.HelpGroup(self.bot).command(ctx, command) 52 | else: 53 | await en.HelpGroup(self.bot).command(ctx, command) 54 | 55 | 56 | @command.error 57 | async def command_error(self, ctx: Interaction, error: Jeanne.AppCommandError): 58 | if isinstance(error, Jeanne.CommandInvokeError) and isinstance( 59 | error.original, IndexError 60 | ): 61 | if ctx.locale.value == "fr": 62 | await fr.HelpGroup(self.bot).command_error(ctx) 63 | else: 64 | await en.HelpGroup(self.bot).command_error(ctx) 65 | 66 | 67 | @Jeanne.command( 68 | name=T("support_name"), 69 | description=T("support_desc"), 70 | extras={ 71 | "en": { 72 | "name": "support", 73 | "description": "Need help? Visit the website or join the server for further assistance.", 74 | }, 75 | "fr": { 76 | "name": "aide", 77 | "description": "Besoin d'aide? Visitez le site web ou rejoignez le serveur pour plus d'assistance.", 78 | }, 79 | }, 80 | ) 81 | @Jeanne.check(check_botbanned_app_command) 82 | @Jeanne.check(is_suspended) 83 | async def support(self, ctx: Interaction): 84 | if ctx.locale.value == "fr": 85 | await fr.HelpGroup(self.bot).support(ctx) 86 | else: 87 | await en.HelpGroup(self.bot).support(ctx) 88 | 89 | 90 | 91 | async def setup(bot: Bot): 92 | await bot.add_cog(HelpGroup(bot)) 93 | -------------------------------------------------------------------------------- /events/tasks.py: -------------------------------------------------------------------------------- 1 | from discord import Color, Embed 2 | from functions import DevPunishment, Moderation, Reminder 3 | from discord.ext import tasks 4 | from discord.ext.commands import Cog, Bot 5 | from datetime import datetime 6 | 7 | 8 | class tasksCog(Cog): 9 | def __init__(self, bot: Bot): 10 | self.bot = bot 11 | self.check_softbanned_members.start() 12 | self.check_reminders.start() 13 | self.check_suspended_users.start() 14 | 15 | @tasks.loop(seconds=60, reconnect=True) 16 | async def check_softbanned_members(self): 17 | for bans in Moderation().get_softban_data(): 18 | if int(round(datetime.now().timestamp())) > int(bans[2]): 19 | guild = await self.bot.fetch_guild(bans[1]) 20 | member = await self.bot.fetch_user(bans[0]) 21 | await guild.unban(member, reason="Softban expired") 22 | await Moderation(guild, member).remove_softban() 23 | modlog = Moderation(guild).get_modlog_channel 24 | if modlog != None: 25 | unmute = Embed(title="Member unbanned", color=0xFF0000) 26 | unmute.add_field(name="Member", value=member, inline=True) 27 | unmute.add_field(name="ID", value=member.id, inline=True) 28 | unmute.add_field( 29 | name="Reason", value="Softban expired", inline=True 30 | ) 31 | unmute.set_thumbnail(url=member.display_avatar) 32 | 33 | await modlog.send(embed=unmute) 34 | else: 35 | continue 36 | 37 | @tasks.loop(seconds=60, reconnect=True) 38 | async def check_reminders(self): 39 | data = Reminder().get_all_reminders 40 | if data == None: 41 | return 42 | for reminder in data: 43 | if int(round(datetime.now().timestamp())) > int(reminder[2]): 44 | member = await self.bot.fetch_user(reminder[0]) 45 | id = reminder[1] 46 | reason = reminder[3] 47 | try: 48 | embed = Embed(title="Reminder ended", color=Color.random()) 49 | embed.add_field(name="Reminder", value=reason, inline=False) 50 | await member.send(embed=embed) 51 | except: 52 | pass 53 | await Reminder(member).remove(id) 54 | else: 55 | continue 56 | 57 | @tasks.loop(seconds=60, reconnect=True) 58 | async def check_suspended_users(self): 59 | data=DevPunishment().get_suspended_users() 60 | if data ==None: 61 | return 62 | for i in data: 63 | current_time = int(round(datetime.now().timestamp())) 64 | suspended_time = int(i[2]) 65 | user=await self.bot.fetch_user(int(i[0])) 66 | if current_time > suspended_time: 67 | await DevPunishment(user).remove_suspended_user() 68 | else: 69 | continue 70 | 71 | @check_softbanned_members.before_loop 72 | async def before_softbans(self): 73 | await self.bot.wait_until_ready() 74 | 75 | @check_reminders.before_loop 76 | async def before_reminders(self): 77 | await self.bot.wait_until_ready() 78 | 79 | @check_suspended_users.before_loop 80 | async def before_check_suspended_users(self): 81 | await self.bot.wait_until_ready() 82 | 83 | 84 | async def setup(bot: Bot): 85 | await bot.add_cog(tasksCog(bot)) 86 | -------------------------------------------------------------------------------- /languages/en/levelling.py: -------------------------------------------------------------------------------- 1 | from discord.ext.commands import Cog, Bot, GroupCog 2 | from discord import ( 3 | Color, 4 | Embed, 5 | File, 6 | Interaction, 7 | Member, 8 | app_commands as Jeanne, 9 | ) 10 | from config import TOPGG 11 | from functions import ( 12 | Inventory, 13 | Levelling, 14 | ) 15 | from typing import Optional 16 | from assets.generators.profile_card import Profile 17 | from topgg import DBLClient 18 | 19 | 20 | class Rank_Group(GroupCog, name="rank"): 21 | def __init__(self, bot: Bot) -> None: 22 | self.bot = bot 23 | 24 | async def send_leaderboard( 25 | self, ctx: Interaction, title: str, leaderboard: list, exp_index: int 26 | ): 27 | await ctx.response.defer() 28 | embed = Embed(color=Color.random()) 29 | embed.set_author(name=title) 30 | if not leaderboard: 31 | embed.description = f"No {title.lower()} provided" 32 | await ctx.followup.send(embed=embed) 33 | return 34 | for rank, entry in enumerate(leaderboard, start=1): 35 | user = await self.bot.fetch_user(entry[0]) 36 | exp = entry[exp_index] 37 | embed.add_field(name=f"`{rank}.` {user}", value=f"`{exp}XP`", inline=True) 38 | await ctx.followup.send(embed=embed) 39 | 40 | 41 | async def _global(self, ctx: Interaction): 42 | leaderboard = Levelling().get_global_rank 43 | await self.send_leaderboard(ctx, "Global XP Leaderboard", leaderboard, 2) 44 | 45 | 46 | async def server(self, ctx: Interaction): 47 | leaderboard = Levelling(server=ctx.guild).get_server_rank 48 | await self.send_leaderboard(ctx, "Server XP Leaderboard", leaderboard, 3) 49 | 50 | 51 | class levelling(Cog): 52 | def __init__(self, bot: Bot): 53 | self.bot = bot 54 | self.topggpy = DBLClient(bot=self.bot, token=TOPGG) 55 | 56 | 57 | 58 | async def generate_profile_card(self, ctx: Interaction, member: Member): 59 | try: 60 | #voted = await self.topggpy.get_user_vote(member.id) 61 | inventory = Inventory(member) 62 | image = await Profile(self.bot).generate_profile(ctx, 63 | member, 64 | bg_image=inventory.selected_wallpaper, 65 | voted=False, 66 | country=inventory.selected_country, 67 | ) 68 | file = File(fp=image, filename=f"{member.name}_profile_card.png") 69 | await ctx.followup.send(file=file) 70 | except Exception as e: 71 | embed = Embed(description=f"Failed to generate profile card: {e}", color=Color.red()) 72 | await ctx.followup.send(embed=embed) 73 | 74 | async def profile_generate(self, ctx: Interaction, member: Member): 75 | await ctx.response.defer() 76 | await self.generate_profile_card(ctx, member) 77 | 78 | async def profile_generate_error(self, ctx: Interaction, error: Exception) -> None: 79 | embed = Embed( 80 | description=f"You have already used the profile command!\nTry again after `{round(error.retry_after, 2)} seconds`", 81 | color=Color.red(), 82 | ) 83 | await ctx.response.send_message(embed=embed) 84 | 85 | 86 | async def profile(self, ctx: Interaction, member: Optional[Member] = None) -> None: 87 | await ctx.response.defer() 88 | await self.generate_profile_card(ctx, member or ctx.user) 89 | 90 | async def profile_error(self, ctx: Interaction, error: Jeanne.AppCommandError): 91 | embed = Embed( 92 | description=f"You have already used the profile command!\nTry again after `{round(error.retry_after, 2)} seconds`", 93 | color=Color.red(), 94 | ) 95 | await ctx.response.send_message(embed=embed) 96 | -------------------------------------------------------------------------------- /languages/fr/levelling.py: -------------------------------------------------------------------------------- 1 | from discord.ext.commands import Cog, Bot, GroupCog 2 | from discord import ( 3 | Color, 4 | Embed, 5 | File, 6 | Interaction, 7 | Member, 8 | app_commands as Jeanne, 9 | ) 10 | from config import TOPGG 11 | from functions import ( 12 | Inventory, 13 | Levelling, 14 | ) 15 | from typing import Optional 16 | from assets.generators.profile_card import Profile 17 | from topgg import DBLClient 18 | 19 | 20 | class Rank_Group(GroupCog, name="rank"): 21 | def __init__(self, bot: Bot) -> None: 22 | self.bot = bot 23 | 24 | async def send_leaderboard( 25 | self, ctx: Interaction, title: str, leaderboard: list, exp_index: int 26 | ): 27 | await ctx.response.defer() 28 | embed = Embed(color=Color.random()) 29 | embed.set_author(name=title) 30 | if not leaderboard: 31 | embed.description = f"Aucun {title.lower()} fourni" 32 | await ctx.followup.send(embed=embed) 33 | return 34 | for rank, entry in enumerate(leaderboard, start=1): 35 | user = await self.bot.fetch_user(entry[0]) 36 | exp = entry[exp_index] 37 | embed.add_field(name=f"`{rank}.` {user}", value=f"`{exp}XP`", inline=True) 38 | await ctx.followup.send(embed=embed) 39 | 40 | 41 | async def _global(self, ctx: Interaction): 42 | leaderboard = Levelling().get_global_rank 43 | await self.send_leaderboard(ctx, "Global XP Leaderboard", leaderboard, 2) 44 | 45 | 46 | async def server(self, ctx: Interaction): 47 | leaderboard = Levelling(server=ctx.guild).get_server_rank 48 | await self.send_leaderboard(ctx, "Server XP Leaderboard", leaderboard, 3) 49 | 50 | 51 | class levelling(Cog): 52 | def __init__(self, bot: Bot): 53 | self.bot = bot 54 | self.topggpy = DBLClient(bot=self.bot, token=TOPGG) 55 | 56 | 57 | 58 | async def generate_profile_card(self, ctx: Interaction, member: Member): 59 | try: 60 | #voted = await self.topggpy.get_user_vote(member.id) 61 | inventory = Inventory(member) 62 | image = await Profile(self.bot).generate_profile(ctx, 63 | member, 64 | bg_image=inventory.selected_wallpaper, 65 | voted=False, 66 | country=inventory.selected_country, 67 | ) 68 | file = File(fp=image, filename=f"{member.name}_profile_card.png") 69 | await ctx.followup.send(file=file) 70 | except Exception as e: 71 | embed = Embed(description=f"Échec de la génération de la carte de profil : {e}", color=Color.red()) 72 | await ctx.followup.send(embed=embed) 73 | 74 | async def profile_generate(self, ctx: Interaction, member: Member): 75 | await ctx.response.defer() 76 | await self.generate_profile_card(ctx, member) 77 | 78 | async def profile_generate_error(self, ctx: Interaction, error: Exception) -> None: 79 | embed = Embed( 80 | description=f"Vous avez déjà utilisé la commande de profil !\nRéessayez après `{round(error.retry_after, 2)} secondes`", 81 | color=Color.red(), 82 | ) 83 | await ctx.response.send_message(embed=embed) 84 | 85 | 86 | async def profile(self, ctx: Interaction, member: Optional[Member] = None) -> None: 87 | await ctx.response.defer() 88 | await self.generate_profile_card(ctx, member or ctx.user) 89 | 90 | async def profile_error(self, ctx: Interaction, error: Jeanne.AppCommandError): 91 | embed = Embed( 92 | description=f"Vous avez déjà utilisé la commande de profil !\nRéessayez après `{round(error.retry_after, 2)} secondes`", 93 | color=Color.red(), 94 | ) 95 | await ctx.response.send_message(embed=embed) 96 | -------------------------------------------------------------------------------- /languages/en/help.py: -------------------------------------------------------------------------------- 1 | from discord import ( 2 | ButtonStyle, 3 | Color, 4 | Embed, 5 | Interaction, 6 | ui, 7 | app_commands as Jeanne, 8 | ) 9 | from discord.ext.commands import Bot 10 | 11 | 12 | class help_button(ui.View): 13 | def __init__(self): 14 | super().__init__() 15 | wiki_url = "https://jeannebot.gitbook.io/jeannebot/help" 16 | orleans_url = "https://discord.gg/jh7jkuk2pp" 17 | tos_and_policy_url = "https://jeannebot.gitbook.io/jeannebot/tos-and-privacy" 18 | self.add_item( 19 | ui.Button(style=ButtonStyle.link, label="Jeanne Webiste", url=wiki_url) 20 | ) 21 | self.add_item( 22 | ui.Button(style=ButtonStyle.link, label="Support Server", url=orleans_url) 23 | ) 24 | self.add_item( 25 | ui.Button( 26 | style=ButtonStyle.link, 27 | label="ToS and Privacy Policy", 28 | url=tos_and_policy_url, 29 | ) 30 | ) 31 | 32 | 33 | class HelpGroup: 34 | def __init__(self, bot: Bot): 35 | self.bot = bot 36 | 37 | async def command(self, ctx: Interaction, command: Jeanne.Range[str, 3]): 38 | await ctx.response.defer() 39 | cmd = next( 40 | ( 41 | cmd.extras 42 | for cmd in self.bot.tree.walk_commands() 43 | if not isinstance(cmd, Jeanne.Group) and cmd.qualified_name == command 44 | ), 45 | ) 46 | command = cmd["en"] 47 | try: 48 | bot_perms = command["bot_perms"] 49 | except: 50 | bot_perms = None 51 | try: 52 | member_perms = command["member_perms"] 53 | except: 54 | member_perms = None 55 | try: 56 | nsfw = cmd["nsfw"] 57 | except: 58 | nsfw = None 59 | name = command["name"] 60 | description = command["description"] 61 | embed = Embed(title=f"{name.title()} Help", color=Color.random()) 62 | embed.description = description 63 | try: 64 | parms = [ 65 | f"[{i["name"]}]" if bool(i["required"])==True else f"<{i["name"]}>" 66 | for i in command["parameters"] 67 | ] 68 | descs = [ 69 | f"`{parm}` - {i["description"]}" 70 | for i, parm in zip(command["parameters"], parms) 71 | ] 72 | embed.add_field(name="Parameters", value="\n".join(descs), inline=False) 73 | except: 74 | parms = [] 75 | if bot_perms: 76 | embed.add_field(name="Jeanne Permissions", value=bot_perms, inline=True) 77 | if member_perms: 78 | embed.add_field(name="Member Permissions", value=member_perms, inline=True) 79 | if nsfw: 80 | embed.add_field(name="Requires NSFW Channel", value=nsfw, inline=True) 81 | 82 | cmd_usage = "/" + name + " " + " ".join(parms) 83 | embed.add_field(name="Command Usage", value=f"`{cmd_usage}`", inline=False) 84 | embed.set_footer( 85 | text="Legend:\n[] - Required\n<> - Optional\n\nIt is best to go to the websites for detailed explanations and usages" 86 | ) 87 | await ctx.followup.send(embed=embed) 88 | 89 | async def command_error(self, ctx: Interaction): 90 | embed = Embed(description="I don't have this command", color=Color.red()) 91 | await ctx.followup.send(embed=embed) 92 | 93 | async def support(self, ctx: Interaction): 94 | view = help_button() 95 | help = Embed( 96 | description="Click on one of the buttons to open the documentation or get help in the support server", 97 | color=Color.random(), 98 | ) 99 | await ctx.response.send_message(embed=help, view=view) 100 | -------------------------------------------------------------------------------- /languages/fr/help.py: -------------------------------------------------------------------------------- 1 | from discord import ( 2 | ButtonStyle, 3 | Color, 4 | Embed, 5 | Interaction, 6 | ui, 7 | app_commands as Jeanne, 8 | ) 9 | from discord.ext.commands import Bot 10 | 11 | 12 | class help_button(ui.View): 13 | def __init__(self): 14 | super().__init__() 15 | wiki_url = "https://jeannebot.gitbook.io/jeannebot/help" 16 | orleans_url = "https://discord.gg/jh7jkuk2pp" 17 | tos_and_policy_url = "https://jeannebot.gitbook.io/jeannebot/tos-and-privacy" 18 | self.add_item( 19 | ui.Button( 20 | style=ButtonStyle.link, label=("Site Web de Jeanne"), url=wiki_url 21 | ) 22 | ) 23 | self.add_item( 24 | ui.Button( 25 | style=ButtonStyle.link, label=("Serveur de Support"), url=orleans_url 26 | ) 27 | ) 28 | self.add_item( 29 | ui.Button( 30 | style=ButtonStyle.link, 31 | label=("Conditions d'utilisation et Politique de Confidentialité"), 32 | url=tos_and_policy_url, 33 | ) 34 | ) 35 | 36 | 37 | class HelpGroup: 38 | def __init__(self, bot: Bot): 39 | self.bot = bot 40 | 41 | async def command(self, ctx: Interaction, command: Jeanne.Range[str, 3]): 42 | await ctx.response.defer() 43 | cmd = next( 44 | ( 45 | cmd.extras 46 | for cmd in self.bot.tree.walk_commands() 47 | if not isinstance(cmd, Jeanne.Group) and cmd.qualified_name == command 48 | ), 49 | ) 50 | command = cmd["fr"] 51 | try: 52 | bot_perms = command["bot_perms"] 53 | except: 54 | bot_perms = None 55 | try: 56 | member_perms = command["member_perms"] 57 | except: 58 | member_perms = None 59 | try: 60 | nsfw = cmd["nsfw"] 61 | except: 62 | nsfw = None 63 | name = command["name"] 64 | description = command["description"] 65 | 66 | embed = Embed( 67 | title=(f"Aide pour {name}"), 68 | description=description, 69 | color=Color.random(), 70 | ) 71 | 72 | try: 73 | 74 | parms = [ 75 | f"[{i["name"]}]" if bool(i["required"])==True else f"<{i["name"]}>" 76 | for i in command["parameters"] 77 | ] 78 | descs = [ 79 | f"`{parm}` - {i["description"]}" 80 | for i, parm in zip(command["parameters"], parms) 81 | ] 82 | embed.add_field(name="Paramètres", value="\n".join(descs), inline=False) 83 | except: 84 | parms = [] 85 | 86 | if bot_perms: 87 | embed.add_field(name="Permissions de Jeanne", value=bot_perms, inline=True) 88 | if member_perms: 89 | embed.add_field( 90 | name="Permissions du Membre", value=member_perms, inline=True 91 | ) 92 | if nsfw: 93 | embed.add_field(name="Nécessite un Canal NSFW", value=nsfw, inline=True) 94 | 95 | cmd_usage = "/" + name + " " + " ".join(parms) 96 | embed.add_field( 97 | name="Utilisation de la Commande", value=f"`{cmd_usage}`", inline=False 98 | ) 99 | embed.set_footer( 100 | text="Légende:\n[] - Requis\n<> - Optionnel\n\nIl est préférable de visiter les sites web pour des explications et utilisations détaillées" 101 | ) 102 | 103 | await ctx.followup.send(embed=embed) 104 | 105 | async def command_error(self, ctx: Interaction): 106 | embed = Embed(description=("Je n'ai pas cette commande"), color=Color.red()) 107 | await ctx.followup.send(embed=embed) 108 | 109 | async def support(self, ctx: Interaction): 110 | view = help_button() 111 | help = Embed( 112 | description=( 113 | "Cliquez sur l'un des boutons pour ouvrir la documentation ou obtenir de l'aide sur le serveur de support" 114 | ), 115 | color=Color.random(), 116 | ) 117 | await ctx.response.send_message(embed=help, view=view) 118 | -------------------------------------------------------------------------------- /languages/en/fun.py: -------------------------------------------------------------------------------- 1 | from random import choice, randint 2 | from discord import Color, Embed, Interaction, Member 3 | from discord.ext.commands import Bot 4 | from functions import ( 5 | DevPunishment, 6 | ) 7 | from typing import Optional 8 | 9 | 10 | class fun(): 11 | def __init__(self, bot: Bot): 12 | self.bot = bot 13 | 14 | async def _8ball(self, ctx: Interaction, question: str): 15 | await ctx.response.defer() 16 | answers = [ 17 | "It is certain.", 18 | "It is decidedly so.", 19 | "Without a doubt.", 20 | "Yes – definitely.", 21 | "You may rely on it.", 22 | "As I see it, yes.", 23 | "Most likely.", 24 | "Outlook good.", 25 | "Yes.", 26 | "Signs point to yes.", 27 | "Reply hazy, try again.", 28 | "Ask again later.", 29 | "Better not tell you now.", 30 | "Cannot predict now.", 31 | "Concentrate and ask again.", 32 | "Don't count on it.", 33 | "My reply is no.", 34 | "My sources say no.", 35 | "Outlook not so good.", 36 | "Very doubtful.", 37 | "Why ask me? Just do it!", 38 | "Why ask me? Just don't do it!", 39 | "Yeah... no", 40 | "Yeah... whatever", 41 | "Yeah... I don't know", 42 | "Yes? No? I don't know!", 43 | "Absolutely not, and I'm offended you asked.", 44 | "Sure, if the stars align and pigs fly.", 45 | "Only on Tuesdays.", 46 | "The answer lies within... your fridge.", 47 | "Ask your cat.", 48 | "Try again after coffee.", 49 | "404 answer not found.", 50 | "You're not ready for that truth.", 51 | "Do you really want to know?", 52 | "Hmm... my magic circuits are glitching.", 53 | "I'm just a ball, not a therapist.", 54 | "Let me think... nope.", 55 | "Yes. But also no.", 56 | "If I told you, I'd have to vanish in a puff of smoke.", 57 | ] 58 | 59 | embed = Embed(color=Color.random()) 60 | embed.add_field(name="Question:", value=question, inline=False) 61 | embed.add_field(name="Answer:", value=choice(answers), inline=False) 62 | await ctx.followup.send(embed=embed) 63 | 64 | async def reverse(self, ctx: Interaction, text: str): 65 | await ctx.response.defer() 66 | if any(word in text for word in ["riffak", "reggin", "aggin"]): 67 | await DevPunishment(ctx.user).add_botbanned_user( 68 | "Using the reversed version of a common racial slur" 69 | ) 70 | return 71 | embed = Embed(description=text[::-1], color=Color.random()).set_footer( 72 | text=f"Author: {ctx.user} | {ctx.user.id}" 73 | ) 74 | await ctx.followup.send(embed=embed) 75 | 76 | async def combine(self, ctx: Interaction, first_word: str, second_word: str): 77 | await ctx.response.defer() 78 | combine1 = ( 79 | first_word[: len(first_word) // 2] + second_word[len(second_word) // 2 :] 80 | ) 81 | combine2 = ( 82 | first_word[len(first_word) // 2 :] + second_word[: len(second_word) // 2] 83 | ) 84 | embed = Embed( 85 | description=f"**1st combined word**: {combine1}\n**2nd combined word**: {combine2}", 86 | color=Color.random(), 87 | ).set_author(name=f"{first_word} + {second_word}") 88 | await ctx.followup.send(embed=embed) 89 | 90 | async def choose(self, ctx: Interaction, choices: str): 91 | await ctx.response.defer() 92 | embed = Embed( 93 | description=f"I chose **{choice(choices.split(','))}**", 94 | color=Color.random(), 95 | ) 96 | await ctx.followup.send(embed=embed) 97 | 98 | async def simprate(self, ctx: Interaction, member: Optional[Member] = None): 99 | await ctx.response.defer() 100 | perc = randint(0, 100) 101 | member = member or ctx.user 102 | embed = Embed( 103 | description=f"{member}'s simp rate is {perc}%", color=Color.random() 104 | ) 105 | if perc >= 75: 106 | embed.set_image(url="https://i.imgur.com/W4u4Igk.jpg") 107 | elif perc >= 50: 108 | embed.set_image(url="https://i.imgur.com/Rs1IP2I.jpg") 109 | await ctx.followup.send(embed=embed) 110 | 111 | async def gayrate(self, ctx: Interaction, member: Optional[Member] = None): 112 | await ctx.response.defer() 113 | perc = randint(0, 100) 114 | member = member or ctx.user 115 | embed = Embed( 116 | description=f"{member}'s gay rate is {perc}%", color=Color.random() 117 | ) 118 | if perc >= 75: 119 | embed.set_image(url="https://i.imgur.com/itOD0Da.png?1") 120 | elif perc >= 50: 121 | embed.set_image(url="https://i.imgur.com/tYAbWCl.jpg") 122 | await ctx.followup.send(embed=embed) 123 | 124 | -------------------------------------------------------------------------------- /languages/fr/fun.py: -------------------------------------------------------------------------------- 1 | from random import choice, randint 2 | from discord import Color, Embed, Interaction, Member 3 | from discord.ext.commands import Bot 4 | from functions import ( 5 | DevPunishment, 6 | ) 7 | from typing import Optional 8 | 9 | 10 | class fun(): 11 | def __init__(self, bot: Bot): 12 | self.bot = bot 13 | 14 | async def _8ball(self, ctx: Interaction, question: str): 15 | await ctx.response.defer() 16 | answers = [ 17 | "C'est certain.", 18 | "C'est décidément ainsi.", 19 | "Sans aucun doute.", 20 | "Oui – définitivement.", 21 | "Vous pouvez compter dessus.", 22 | "D'après moi, oui.", 23 | "Très probable.", 24 | "Les perspectives sont bonnes.", 25 | "Oui.", 26 | "Les signes indiquent oui.", 27 | "Réponse floue, réessayez.", 28 | "Demandez à nouveau plus tard.", 29 | "Mieux vaut ne pas vous le dire maintenant.", 30 | "Impossible de prédire maintenant.", 31 | "Concentrez-vous et demandez à nouveau.", 32 | "Ne comptez pas dessus.", 33 | "Ma réponse est non.", 34 | "Mes sources disent non.", 35 | "Les perspectives ne sont pas si bonnes.", 36 | "Très douteux.", 37 | "Pourquoi me demander ? Faites-le simplement !", 38 | "Pourquoi me demander ? Ne le faites pas !", 39 | "Ouais... non", 40 | "Ouais... peu importe", 41 | "Ouais... je ne sais pas", 42 | "Oui ? Non ? Je ne sais pas !", 43 | "Absolument pas, et je suis offensé que vous ayez demandé.", 44 | "Bien sûr, si les étoiles s'alignent et que les cochons volent.", 45 | "Seulement les mardis.", 46 | "La réponse se trouve à l'intérieur... de votre frigo.", 47 | "Demandez à votre chat.", 48 | "Réessayez après un café.", 49 | "404 réponse introuvable.", 50 | "Vous n'êtes pas prêt pour cette vérité.", 51 | "Voulez-vous vraiment savoir ?", 52 | "Hmm... mes circuits magiques buguent.", 53 | "Je ne suis qu'une boule, pas un thérapeute.", 54 | "Laissez-moi réfléchir... non.", 55 | "Oui. Mais aussi non.", 56 | "Si je vous le disais, je devrais disparaître dans un nuage de fumée.", 57 | ] 58 | 59 | embed = Embed(color=Color.random()) 60 | embed.add_field(name="Question :", value=question, inline=False) 61 | embed.add_field(name="Réponse :", value=choice(answers), inline=False) 62 | await ctx.followup.send(embed=embed) 63 | 64 | async def reverse(self, ctx: Interaction, text: str): 65 | await ctx.response.defer() 66 | if any(word in text for word in ["riffak", "reggin", "aggin"]): 67 | await DevPunishment(ctx.user).add_botbanned_user( 68 | "Using the reversed version of a common racial slur" 69 | ) 70 | return 71 | embed = Embed(description=text[::-1], color=Color.random()).set_footer( 72 | text=f"Auteur : {ctx.user} | {ctx.user.id}" 73 | ) 74 | await ctx.followup.send(embed=embed) 75 | 76 | async def combine(self, ctx: Interaction, first_word: str, second_word: str): 77 | await ctx.response.defer() 78 | combine1 = ( 79 | first_word[: len(first_word) // 2] + second_word[len(second_word) // 2 :] 80 | ) 81 | combine2 = ( 82 | first_word[len(first_word) // 2 :] + second_word[: len(second_word) // 2] 83 | ) 84 | embed = Embed( 85 | description=f"**1er mot combiné** : {combine1}\n**2ème mot combiné** : {combine2}", 86 | color=Color.random(), 87 | ).set_author(name=f"{first_word} + {second_word}") 88 | await ctx.followup.send(embed=embed) 89 | 90 | async def choose(self, ctx: Interaction, choices: str): 91 | await ctx.response.defer() 92 | embed = Embed( 93 | description=f"J'ai choisi **{choice(choices.split(','))}**", 94 | color=Color.random(), 95 | ) 96 | await ctx.followup.send(embed=embed) 97 | 98 | async def simprate(self, ctx: Interaction, member: Optional[Member] = None): 99 | await ctx.response.defer() 100 | perc = randint(0, 100) 101 | member = member or ctx.user 102 | embed = Embed( 103 | description=f"Le taux de simp de {member} est de {perc}%", color=Color.random() 104 | ) 105 | if perc >= 75: 106 | embed.set_image(url="https://i.imgur.com/W4u4Igk.jpg") 107 | elif perc >= 50: 108 | embed.set_image(url="https://i.imgur.com/Rs1IP2I.jpg") 109 | await ctx.followup.send(embed=embed) 110 | 111 | async def gayrate(self, ctx: Interaction, member: Optional[Member] = None): 112 | await ctx.response.defer() 113 | perc = randint(0, 100) 114 | member = member or ctx.user 115 | embed = Embed( 116 | description=f"Le taux de gay de {member} est de {perc}%", color=Color.random() 117 | ) 118 | if perc >= 75: 119 | embed.set_image(url="https://i.imgur.com/itOD0Da.png?1") 120 | elif perc >= 50: 121 | embed.set_image(url="https://i.imgur.com/tYAbWCl.jpg") 122 | await ctx.followup.send(embed=embed) 123 | 124 | -------------------------------------------------------------------------------- /languages/en/reactions.py: -------------------------------------------------------------------------------- 1 | import json 2 | import random 3 | from discord import Color, Embed, Interaction, Member 4 | from discord.ext.commands import Bot 5 | from requests import get 6 | from config import ( 7 | hug, 8 | slap, 9 | smug, 10 | poke, 11 | cry, 12 | pat, 13 | kiss, 14 | tickle, 15 | baka, 16 | feed, 17 | bite, 18 | blush, 19 | cuddle, 20 | dance, 21 | ) 22 | from typing import Optional 23 | 24 | 25 | class Reactions: 26 | def __init__(self, bot: Bot): 27 | self.bot = bot 28 | 29 | async def _send_reaction( 30 | self, 31 | ctx: Interaction, 32 | action: str, 33 | member: Optional[Member] = None, 34 | api_url: str = None, 35 | ) -> None: 36 | reaction_api = get(api_url) 37 | reaction_embed = Embed(color=Color.random()) 38 | reaction_embed.set_footer(text="Fetched from Tenor") 39 | random_gif = random.choice(json.loads(reaction_api.content)["results"]) 40 | reaction_url = random_gif["media_formats"]["gif"]["url"] 41 | reaction_embed.set_image(url=reaction_url) 42 | 43 | messages = { 44 | "baka": ( 45 | f"*{ctx.user}*, you are a baka!" 46 | if member is None 47 | else f"*{member.mention}*, *{ctx.user} called you a baka!*" 48 | ), 49 | "smug": f"*{ctx.user}* is smirking", 50 | "hug": ( 51 | f"*Hugging {ctx.user}*" 52 | if member is None 53 | else f"*{ctx.user} hugged {member.mention}*" 54 | ), 55 | "poke": ( 56 | f"*Poking {ctx.user}*" 57 | if member is None 58 | else f"*{ctx.user} is poking {member.mention}*" 59 | ), 60 | "cuddle": ( 61 | f"*Cuddling {ctx.user}*" 62 | if member is None 63 | else f"*{ctx.user} is cuddling with {member.mention}*" 64 | ), 65 | "dance": ( 66 | f"*{ctx.user} is dancing*" 67 | if member is None 68 | else f"*{ctx.user} is dancing with {member.mention}*" 69 | ), 70 | "pat": ( 71 | f"*Patting {ctx.user}*" 72 | if member is None 73 | else f"*{ctx.user} patted {member.mention}*" 74 | ), 75 | "blush": f"*{ctx.user} is blushing*", 76 | "bite": ( 77 | f"*Biting {ctx.user}*" 78 | if member is None 79 | else f"*{ctx.user} bit {member.mention}*" 80 | ), 81 | "feed": ( 82 | f"*Feeding {ctx.user}*" 83 | if member is None 84 | else f"*{ctx.user} is feeding {member.mention}. Eat up*" 85 | ), 86 | "cry": f"*{ctx.user} is crying*", 87 | "slap": ( 88 | f"*Slapping {ctx.user}*" 89 | if member is None 90 | else f"*{ctx.user} slapped {member.mention}*" 91 | ), 92 | "kiss": ( 93 | f"*Kissing {ctx.user}*" 94 | if member is None 95 | else f"*{ctx.user} kissed {member.mention}*" 96 | ), 97 | "tickle": ( 98 | f"*Tickling {ctx.user}*" 99 | if member is None 100 | else f"*{ctx.user} tickled {member.mention}*" 101 | ), 102 | } 103 | 104 | msg = messages.get(action, f"*{ctx.user} is performing an action*") 105 | await ctx.response.send_message(msg, embed=reaction_embed) 106 | 107 | async def hug(self, ctx: Interaction, member: Optional[Member] = None) -> None: 108 | await self._send_reaction(ctx, "hug", member, hug) 109 | 110 | async def slap(self, ctx: Interaction, member: Optional[Member] = None) -> None: 111 | await self._send_reaction(ctx, "slap", member, slap) 112 | 113 | async def smug(self, ctx: Interaction): 114 | await self._send_reaction(ctx, "smug", api_url=smug) 115 | 116 | async def poke(self, ctx: Interaction, member: Optional[Member] = None) -> None: 117 | await self._send_reaction(ctx, "poke", member, poke) 118 | 119 | async def pat(self, ctx: Interaction, member: Optional[Member] = None) -> None: 120 | await self._send_reaction(ctx, "pat", member, pat) 121 | 122 | async def kiss(self, ctx: Interaction, member: Optional[Member] = None) -> None: 123 | await self._send_reaction(ctx, "kiss", member, kiss) 124 | 125 | async def tickle(self, ctx: Interaction, member: Optional[Member] = None) -> None: 126 | await self._send_reaction(ctx, "tickle", member, tickle) 127 | 128 | async def baka(self, ctx: Interaction, member: Optional[Member] = None) -> None: 129 | await self._send_reaction(ctx, "baka", member, baka) 130 | 131 | async def feed(self, ctx: Interaction, member: Optional[Member] = None) -> None: 132 | await self._send_reaction(ctx, "feed", member, feed) 133 | 134 | async def cry(self, ctx: Interaction): 135 | await self._send_reaction(ctx, "cry", api_url=cry) 136 | 137 | async def bite(self, ctx: Interaction, member: Optional[Member] = None) -> None: 138 | await self._send_reaction(ctx, "bite", member, bite) 139 | 140 | async def blush(self, ctx: Interaction): 141 | await self._send_reaction(ctx, "blush", api_url=blush) 142 | 143 | async def cuddle(self, ctx: Interaction, member: Optional[Member] = None) -> None: 144 | await self._send_reaction(ctx, "cuddle", member, cuddle) 145 | 146 | async def dance(self, ctx: Interaction, member: Optional[Member] = None) -> None: 147 | await self._send_reaction(ctx, "dance", member, dance) 148 | -------------------------------------------------------------------------------- /languages/fr/reactions.py: -------------------------------------------------------------------------------- 1 | import json 2 | import random 3 | from discord import Color, Embed, Interaction, Member 4 | from discord.ext.commands import Bot 5 | from requests import get 6 | from config import ( 7 | hug, 8 | slap, 9 | smug, 10 | poke, 11 | cry, 12 | pat, 13 | kiss, 14 | tickle, 15 | baka, 16 | feed, 17 | bite, 18 | blush, 19 | cuddle, 20 | dance, 21 | ) 22 | from typing import Optional 23 | 24 | 25 | class Reactions(): 26 | def __init__(self, bot: Bot): 27 | self.bot = bot 28 | 29 | async def _send_reaction( 30 | self, 31 | ctx: Interaction, 32 | action: str, 33 | member: Optional[Member] = None, 34 | api_url: str = None, 35 | ) -> None: 36 | reaction_api = get(api_url) 37 | reaction_embed = Embed(color=Color.random()) 38 | reaction_embed.set_footer(text="Tiré de Ténor") 39 | random_gif = random.choice(json.loads(reaction_api.content)["results"]) 40 | reaction_url = random_gif["media_formats"]["gif"]["url"] 41 | reaction_embed.set_image(url=reaction_url) 42 | 43 | messages = { 44 | "baka": ( 45 | f"*{ctx.user}*, tu es un baka !" 46 | if member is None 47 | else f"*{member.mention}*, *{ctx.user} t'a traité de baka !*" 48 | ), 49 | "smug": f"*{ctx.user}* a un sourire narquois", 50 | "hug": ( 51 | f"*{ctx.user} se fait un câlin*" 52 | if member is None 53 | else f"*{ctx.user} a fait un câlin à {member.mention}*" 54 | ), 55 | "poke": ( 56 | f"*{ctx.user} se fait un poke*" 57 | if member is None 58 | else f"*{ctx.user} poke {member.mention}*" 59 | ), 60 | "cuddle": ( 61 | f"*{ctx.user} se fait un câlin*" 62 | if member is None 63 | else f"*{ctx.user} fait un câlin à {member.mention}*" 64 | ), 65 | "dance": ( 66 | f"*{ctx.user} danse*" 67 | if member is None 68 | else f"*{ctx.user} danse avec {member.mention}*" 69 | ), 70 | "pat": ( 71 | f"*{ctx.user} se fait tapoter la tête*" 72 | if member is None 73 | else f"*{ctx.user} tapote la tête de {member.mention}*" 74 | ), 75 | "blush": f"*{ctx.user} rougit*", 76 | "bite": ( 77 | f"*{ctx.user} se mord*" 78 | if member is None 79 | else f"*{ctx.user} mord {member.mention}*" 80 | ), 81 | "feed": ( 82 | f"*{ctx.user} se nourrit*" 83 | if member is None 84 | else f"*{ctx.user} nourrit {member.mention}. Bon appétit*" 85 | ), 86 | "cry": f"*{ctx.user} pleure*", 87 | "slap": ( 88 | f"*{ctx.user} se gifle*" 89 | if member is None 90 | else f"*{ctx.user} a giflé {member.mention}*" 91 | ), 92 | "kiss": ( 93 | f"*{ctx.user} s'embrasse*" 94 | if member is None 95 | else f"*{ctx.user} a embrassé {member.mention}*" 96 | ), 97 | "tickle": ( 98 | f"*{ctx.user} se chatouille*" 99 | if member is None 100 | else f"*{ctx.user} a chatouillé {member.mention}*" 101 | ), 102 | } 103 | 104 | msg = messages.get(action, f"*{ctx.user} effectue une action*") 105 | await ctx.response.send_message(msg, embed=reaction_embed) 106 | 107 | async def hug(self, ctx: Interaction, member: Optional[Member] = None) -> None: 108 | await self._send_reaction(ctx, "hug", member, hug) 109 | 110 | async def slap(self, ctx: Interaction, member: Optional[Member] = None) -> None: 111 | await self._send_reaction(ctx, "slap", member, slap) 112 | 113 | async def smug(self, ctx: Interaction): 114 | await self._send_reaction(ctx, "smug", api_url=smug) 115 | 116 | async def poke(self, ctx: Interaction, member: Optional[Member] = None) -> None: 117 | await self._send_reaction(ctx, "poke", member, poke) 118 | 119 | async def pat(self, ctx: Interaction, member: Optional[Member] = None) -> None: 120 | await self._send_reaction(ctx, "pat", member, pat) 121 | 122 | async def kiss(self, ctx: Interaction, member: Optional[Member] = None) -> None: 123 | await self._send_reaction(ctx, "kiss", member, kiss) 124 | 125 | async def tickle(self, ctx: Interaction, member: Optional[Member] = None) -> None: 126 | await self._send_reaction(ctx, "tickle", member, tickle) 127 | 128 | async def baka(self, ctx: Interaction, member: Optional[Member] = None) -> None: 129 | await self._send_reaction(ctx, "baka", member, baka) 130 | 131 | async def feed(self, ctx: Interaction, member: Optional[Member] = None) -> None: 132 | await self._send_reaction(ctx, "feed", member, feed) 133 | 134 | async def cry(self, ctx: Interaction): 135 | await self._send_reaction(ctx, "cry", api_url=cry) 136 | 137 | async def bite(self, ctx: Interaction, member: Optional[Member] = None) -> None: 138 | await self._send_reaction(ctx, "bite", member, bite) 139 | 140 | async def blush(self, ctx: Interaction): 141 | await self._send_reaction(ctx, "blush", api_url=blush) 142 | 143 | async def cuddle(self, ctx: Interaction, member: Optional[Member] = None) -> None: 144 | await self._send_reaction(ctx, "cuddle", member, cuddle) 145 | 146 | async def dance(self, ctx: Interaction, member: Optional[Member] = None) -> None: 147 | await self._send_reaction(ctx, "dance", member, dance) 148 | -------------------------------------------------------------------------------- /cogs/inventory.py: -------------------------------------------------------------------------------- 1 | from functions import ( 2 | check_botbanned_app_command, 3 | check_disabled_app_command, 4 | is_suspended, 5 | ) 6 | from discord import Interaction, app_commands as Jeanne 7 | from discord.ext.commands import Bot, GroupCog 8 | from discord.app_commands import locale_str as T 9 | import languages.en.inventory as en 10 | import languages.fr.inventory as fr 11 | 12 | 13 | class Shop_Group(GroupCog, name="shop"): 14 | def __init__(self, bot: Bot) -> None: 15 | self.bot = bot 16 | super().__init__() 17 | 18 | @Jeanne.command( 19 | description=T("country_desc"), 20 | extras={ 21 | "en": {"name": "country", "description": "Buy a country badge"}, 22 | "fr": {"name": "pays", "description": "Acheter un badge de pays"}, 23 | }, 24 | ) 25 | @Jeanne.checks.cooldown(1, 60, key=lambda i: (i.user.id)) 26 | @Jeanne.check(check_botbanned_app_command) 27 | @Jeanne.check(check_disabled_app_command) 28 | @Jeanne.check(is_suspended) 29 | async def country(self, ctx: Interaction): 30 | if ctx.locale.value == "en-GB" or ctx.locale.value == "en-US": 31 | await en.Shop_Group(self.bot).country(ctx) 32 | elif ctx.locale.value == "fr": 33 | await fr.Shop_Group(self.bot).country(ctx) 34 | 35 | @Jeanne.command( 36 | description=T("backgrounds_desc"), 37 | extras={ 38 | "en": { 39 | "name": "backgrounds", 40 | "description": "Check all the wallpapers available", 41 | }, 42 | "fr": { 43 | "name": "fonds d'écran", 44 | "description": "Vérifiez tous les fonds d'écran disponibles", 45 | }, 46 | }, 47 | ) 48 | @Jeanne.checks.cooldown(1, 60, key=lambda i: (i.user.id)) 49 | @Jeanne.check(check_botbanned_app_command) 50 | @Jeanne.check(check_disabled_app_command) 51 | @Jeanne.check(is_suspended) 52 | async def backgrounds(self, ctx: Interaction): 53 | if ctx.locale.value == "en-GB" or ctx.locale.value == "en-US": 54 | await en.Shop_Group(self.bot).backgrounds(ctx) 55 | elif ctx.locale.value == "fr": 56 | await fr.Shop_Group(self.bot).backgrounds(ctx) 57 | 58 | @backgrounds.error 59 | async def backgrounds_error(self, ctx: Interaction, error: Jeanne.AppCommandError): 60 | if ctx.locale.value == "en-GB" or ctx.locale.value == "en-US": 61 | await en.Shop_Group(self.bot).backgrounds_error(ctx, error) 62 | elif ctx.locale.value == "fr": 63 | await fr.Shop_Group(self.bot).backgrounds_error(ctx, error) 64 | 65 | 66 | class Background_Group(GroupCog, name="background"): 67 | def __init__(self, bot: Bot) -> None: 68 | self.bot = bot 69 | super().__init__() 70 | 71 | @Jeanne.command( 72 | name="buy-custom", 73 | description=T("buycustom_desc"), 74 | extras={ 75 | "en": { 76 | "name": "buycustom", 77 | "description": "Buy a custom background pic for your level card", 78 | "parameters": [ 79 | { 80 | "name": "name", 81 | "description": "What will you name it?", 82 | "required": True, 83 | }, 84 | { 85 | "name": "link", 86 | "description": "Add an image link", 87 | "required": True, 88 | }, 89 | ], 90 | }, 91 | "fr": { 92 | "name": "buy-custom", 93 | "description": "Acheter une image de fond personnalisée pour votre carte de niveau", 94 | "parameters": [ 95 | { 96 | "name": "nom", 97 | "description": "Comment voulez-vous l'appeler?", 98 | "required": True, 99 | }, 100 | { 101 | "name": "lien", 102 | "description": "Ajoutez un lien d'image", 103 | "required": True, 104 | }, 105 | ], 106 | }, 107 | }, 108 | ) 109 | @Jeanne.checks.cooldown(1, 60, key=lambda i: (i.user.id)) 110 | @Jeanne.describe(name=T("name_parm_desc"), link=T("link_parm_desc")) 111 | @Jeanne.rename(name="name_parm_name", link="link_parm_name") 112 | @Jeanne.check(check_botbanned_app_command) 113 | @Jeanne.check(check_disabled_app_command) 114 | @Jeanne.check(is_suspended) 115 | async def buycustom(self, ctx: Interaction, name: str, link: str): 116 | if ctx.locale.value == "en-GB" or ctx.locale.value == "en-US": 117 | await en.Background_Group(self.bot).buycustom(ctx, name, link) 118 | elif ctx.locale.value == "fr": 119 | await fr.Background_Group(self.bot).buycustom(ctx, name, link) 120 | 121 | @buycustom.error 122 | async def buycustom_error(self, ctx: Interaction, error: Jeanne.AppCommandError): 123 | if ctx.locale.value == "en-GB" or ctx.locale.value == "en-US": 124 | await en.Background_Group(self.bot).buycustom_error( 125 | ctx, 126 | error, 127 | ( 128 | "cooldown" 129 | if isinstance(error, Jeanne.CommandOnCooldown) 130 | else "invalid" 131 | ), 132 | ) 133 | elif ctx.locale.value == "fr": 134 | await fr.Background_Group(self.bot).buycustom_error( 135 | ctx, 136 | error, 137 | ( 138 | "cooldown" 139 | if isinstance(error, Jeanne.CommandOnCooldown) 140 | else "invalid" 141 | ), 142 | ) 143 | 144 | @Jeanne.command( 145 | name=T("list_name"), 146 | description=T("list_desc"), 147 | extras={ 148 | "en": {"name": "list", "description": "Check which backgrounds you have"}, 149 | "fr": { 150 | "name": "liste", 151 | "description": "Vérifiez quels fonds d'écran vous avez", 152 | }, 153 | }, 154 | ) 155 | @Jeanne.check(check_botbanned_app_command) 156 | @Jeanne.check(check_disabled_app_command) 157 | @Jeanne.check(is_suspended) 158 | async def _list(self, ctx: Interaction): 159 | if ctx.locale.value == "en-GB" or ctx.locale.value == "en-US": 160 | await en.Background_Group(self.bot).list(ctx) 161 | elif ctx.locale.value == "fr": 162 | await fr.Background_Group(self.bot).list(ctx) 163 | 164 | 165 | async def setup(bot: Bot): 166 | await bot.add_cog(Shop_Group(bot)) 167 | await bot.add_cog(Background_Group(bot)) 168 | -------------------------------------------------------------------------------- /cogs/levelling.py: -------------------------------------------------------------------------------- 1 | from discord.ext.commands import Cog, Bot, GroupCog 2 | from discord import ( 3 | Interaction, 4 | Member, 5 | app_commands as Jeanne, 6 | ) 7 | from config import TOPGG 8 | from functions import ( 9 | Levelling, 10 | check_botbanned_app_command, 11 | check_disabled_app_command, 12 | is_suspended, 13 | ) 14 | from typing import Optional 15 | from topgg import DBLClient 16 | import languages.en.levelling as en 17 | import languages.fr.levelling as fr 18 | from discord.app_commands import locale_str as T 19 | 20 | 21 | class Rank_Group(GroupCog, name="rank"): 22 | def __init__(self, bot: Bot) -> None: 23 | self.bot = bot 24 | super().__init__() 25 | 26 | async def send_leaderboard( 27 | self, ctx: Interaction, title: str, leaderboard: list, exp_index: int 28 | ): 29 | if ctx.locale.value == "en-GB" or ctx.locale.value == "en-US": 30 | await en.Rank_Group(self.bot).send_leaderboard( 31 | ctx, title, leaderboard, exp_index 32 | ) 33 | elif ctx.locale.value == "fr": 34 | await fr.Rank_Group(self.bot).send_leaderboard( 35 | ctx, title, leaderboard, exp_index 36 | ) 37 | 38 | @Jeanne.command( 39 | name="global", 40 | description=T("global_desc"), 41 | extras={ 42 | "en": { 43 | "name": "global", 44 | "description": "Check the users with the most XP globally", 45 | }, 46 | "fr": { 47 | "name": "global", 48 | "description": "Vérifiez les utilisateurs avec le plus d'XP globalement", 49 | }, 50 | }, 51 | ) 52 | @Jeanne.checks.cooldown(1, 60, key=lambda i: (i.user.id)) 53 | @Jeanne.check(check_botbanned_app_command) 54 | @Jeanne.check(check_disabled_app_command) 55 | @Jeanne.check(is_suspended) 56 | async def _global(self, ctx: Interaction): 57 | leaderboard = Levelling().get_global_rank 58 | await self.send_leaderboard(ctx, "Global XP Leaderboard", leaderboard, 2) 59 | 60 | @Jeanne.command( 61 | description=T("server_desc"), 62 | extras={ 63 | "en": { 64 | "name": "server", 65 | "description": "Check the users with the most XP in this server", 66 | }, 67 | "fr": { 68 | "name": "serveur", 69 | "description": "Vérifiez les utilisateurs avec le plus d'XP dans ce serveur", 70 | }, 71 | }, 72 | ) 73 | @Jeanne.checks.cooldown(1, 60, key=lambda i: (i.user.id)) 74 | @Jeanne.check(check_botbanned_app_command) 75 | @Jeanne.check(check_disabled_app_command) 76 | @Jeanne.check(is_suspended) 77 | async def server(self, ctx: Interaction): 78 | leaderboard = Levelling(server=ctx.guild).get_server_rank 79 | await self.send_leaderboard(ctx, "Server XP Leaderboard", leaderboard, 3) 80 | 81 | 82 | class levelling(Cog): 83 | def __init__(self, bot: Bot): 84 | self.bot = bot 85 | self.topggpy = DBLClient(bot=self.bot, token=TOPGG) 86 | self.profile_context = Jeanne.ContextMenu( 87 | name="Profile", callback=self.profile_generate 88 | ) 89 | self.bot.tree.add_command(self.profile_context) 90 | self.profile_context.error(self.profile_generate_error) 91 | 92 | async def cog_unload(self) -> None: 93 | self.bot.tree.remove_command( 94 | self.profile_context.name, type=self.profile_context.type 95 | ) 96 | 97 | async def generate_profile_card(self, ctx: Interaction, member: Member): 98 | if ctx.locale.value == "en-GB" or ctx.locale.value == "en-US": 99 | await en.levelling(self.bot).generate_profile_card(ctx, member) 100 | elif ctx.locale.value == "fr": 101 | await fr.levelling(self.bot).generate_profile_card(ctx, member) 102 | 103 | @Jeanne.checks.cooldown(1, 120, key=lambda i: (i.user.id)) 104 | @Jeanne.check(check_botbanned_app_command) 105 | @Jeanne.check(check_disabled_app_command) 106 | @Jeanne.check(is_suspended) 107 | async def profile_generate(self, ctx: Interaction, member: Member): 108 | await ctx.response.defer() 109 | await self.generate_profile_card(ctx, member) 110 | 111 | async def profile_generate_error(self, ctx: Interaction, error: Exception) -> None: 112 | if isinstance(error, Jeanne.CommandOnCooldown): 113 | if ctx.locale.value == "en-GB" or ctx.locale.value == "en-US": 114 | await en.levelling(self.bot).profile_error(ctx, error) 115 | elif ctx.locale.value == "fr": 116 | await fr.levelling(self.bot).profile_error(ctx, error) 117 | 118 | @Jeanne.command( 119 | name=T("profile_name"), 120 | description=T("profile_desc"), 121 | extras={ 122 | "en": { 123 | "name": "profile", 124 | "description": "See your profile or someone else's profile", 125 | "parameters": [ 126 | { 127 | "name": "member", 128 | "description": "Which member?", 129 | "required": False, 130 | } 131 | ], 132 | }, 133 | "fr": { 134 | "name": "profil", 135 | "description": "Voir votre profil ou celui d'un autre membre", 136 | "parameters": [ 137 | {"name": "membre", "description": "Quel membre?", "required": False} 138 | ], 139 | }, 140 | }, 141 | ) 142 | @Jeanne.describe(member=T("member_parm_desc")) 143 | @Jeanne.rename(member=T("member_parm_name")) 144 | @Jeanne.checks.cooldown(1, 120, key=lambda i: (i.user.id)) 145 | @Jeanne.check(check_botbanned_app_command) 146 | @Jeanne.check(check_disabled_app_command) 147 | @Jeanne.check(is_suspended) 148 | async def profile(self, ctx: Interaction, member: Optional[Member] = None) -> None: 149 | if ctx.locale.value == "en-GB" or ctx.locale.value == "en-US": 150 | await en.levelling(self.bot).profile(ctx, member) 151 | elif ctx.locale.value == "fr": 152 | await fr.levelling(self.bot).profile(ctx, member) 153 | 154 | @profile.error 155 | async def profile_error(self, ctx: Interaction, error: Jeanne.AppCommandError): 156 | if isinstance(error, Jeanne.CommandOnCooldown): 157 | if ctx.locale.value == "en-GB" or ctx.locale.value == "en-US": 158 | await en.levelling(self.bot).profile_error(ctx, error) 159 | elif ctx.locale.value == "fr": 160 | await fr.levelling(self.bot).profile_error(ctx, error) 161 | 162 | 163 | async def setup(bot: Bot): 164 | await bot.add_cog(Rank_Group(bot)) 165 | await bot.add_cog(levelling(bot)) 166 | -------------------------------------------------------------------------------- /assets/images.py: -------------------------------------------------------------------------------- 1 | from random import choice 2 | from discord import Color, Embed, File, Interaction 3 | import requests 4 | from config import ANIMEME, JEANNE, KITSUNE, MEDUSA, MORGAN, SABER, WALLPAPER, NEKO 5 | import lxml.etree as ET 6 | from os import listdir, path 7 | 8 | 9 | def get_saber_pic(ctx: Interaction) -> tuple[Embed, File]: 10 | folder_path = SABER 11 | files = listdir(folder_path) 12 | 13 | image_files = [ 14 | file for file in files if file.endswith((".jpg", ".jpeg", ".png", ".gif")) 15 | ] 16 | random_image = choice(image_files) 17 | file = File(path.join(folder_path, random_image), random_image) 18 | embed = Embed(color=Color.random()) 19 | embed.set_image(url=f"attachment://{random_image}") 20 | 21 | if ctx.locale.value == "fr": 22 | embed.set_footer(text="Récupéré de Saber_1936 • Les crédits doivent aller à l'artiste") 23 | else: 24 | embed.set_footer(text="Fetched from Saber_1936 • Credits must go to the artist") 25 | 26 | 27 | return embed, file 28 | 29 | 30 | def get_jeanne_pic(ctx: Interaction) -> tuple[Embed, File]: 31 | folder_path = JEANNE 32 | files = listdir(folder_path) 33 | 34 | image_files = [ 35 | file for file in files if file.endswith((".jpg", ".jpeg", ".png", ".gif")) 36 | ] 37 | 38 | random_image = choice(image_files) 39 | file = File(path.join(folder_path, random_image), random_image) 40 | embed = Embed(color=Color.random()) 41 | embed.set_image(url=f"attachment://{random_image}") 42 | 43 | if ctx.locale.value == "fr": 44 | embed.set_footer(text="Récupéré de Jeanne_1936 • Les crédits doivent aller à l'artiste") 45 | else: 46 | embed.set_footer(text="Fetched from Jeanne_1936 • Credits must go to the artist") 47 | 48 | 49 | return embed, file 50 | 51 | 52 | def get_wallpaper_pic(ctx: Interaction) -> tuple[Embed, File]: 53 | folder_path = WALLPAPER 54 | files = listdir(folder_path) 55 | 56 | image_files = [ 57 | file for file in files if file.endswith((".jpg", ".jpeg", ".png", ".gif")) 58 | ] 59 | random_image = choice(image_files) 60 | file = File(path.join(folder_path, random_image), random_image) 61 | embed = Embed(color=Color.random()) 62 | embed.set_image(url=f"attachment://{random_image}") 63 | 64 | if ctx.locale.value == "fr": 65 | embed.set_footer(text="Récupéré de Wallpaper_1936 • Les crédits doivent aller à l'artiste") 66 | else: 67 | embed.set_footer(text="Fetched from Wallpaper_1936 • Credits must go to the artist") 68 | 69 | 70 | return embed, file 71 | 72 | 73 | def get_medusa_pic(ctx: Interaction) -> tuple[Embed, File]: 74 | folder_path = MEDUSA 75 | files = listdir(folder_path) 76 | 77 | image_files = [ 78 | file for file in files if file.endswith((".jpg", ".jpeg", ".png", ".gif")) 79 | ] 80 | random_image = choice(image_files) 81 | embed = Embed(color=Color.random()) 82 | embed.set_image(url=f"attachment://{random_image}") 83 | 84 | if ctx.locale.value == "fr": 85 | embed.set_footer(text="Récupéré de Medusa_1936 • Les crédits doivent aller à l'artiste") 86 | else: 87 | embed.set_footer(text="Fetched from Medusa_1936 • Credits must go to the artist") 88 | 89 | 90 | file = File(path.join(folder_path, random_image), random_image) 91 | return embed, file 92 | 93 | 94 | def get_animeme_pic(ctx:Interaction) -> tuple[Embed, File]: 95 | folder_path = ANIMEME 96 | files = listdir(folder_path) 97 | 98 | image_files = [ 99 | file for file in files if file.endswith((".jpg", ".jpeg", ".png", "..gif")) 100 | ] 101 | random_image = choice(image_files) 102 | embed = Embed(color=Color.random()) 103 | embed.set_image(url=f"attachment://{random_image}") 104 | 105 | if ctx.locale.value == "fr": 106 | embed.set_footer(text="Récupéré de Animeme_1936 • Les crédits doivent aller à l'artiste") 107 | else: 108 | embed.set_footer(text="Fetched from Animeme_1936 • Credits must go to the artist") 109 | 110 | 111 | file = File(path.join(folder_path, random_image), random_image) 112 | return embed, file 113 | 114 | 115 | def get_neko_pic(ctx: Interaction) -> tuple[Embed, File]: 116 | folder_path = NEKO 117 | files = listdir(folder_path) 118 | 119 | image_files = [ 120 | file for file in files if file.endswith((".jpg", ".jpeg", ".png", ".gif")) 121 | ] 122 | 123 | random_image = choice(image_files) 124 | file = File(path.join(folder_path, random_image), random_image) 125 | embed = Embed(color=Color.random()) 126 | embed.set_image(url=f"attachment://{random_image}") 127 | 128 | if ctx.locale.value == "fr": 129 | embed.set_footer(text="Récupéré de Neko_1936 • Les crédits doivent aller à l'artiste") 130 | else: 131 | embed.set_footer(text="Fetched from Neko_1936 • Credits must go to the artist") 132 | 133 | return embed, file 134 | 135 | 136 | def get_morgan_pic(ctx: Interaction) -> tuple[Embed, File]: 137 | folder_path = MORGAN 138 | files = listdir(folder_path) 139 | 140 | image_files = [ 141 | file for file in files if file.endswith((".jpg", ".jpeg", ".png", ".gif")) 142 | ] 143 | 144 | random_image = choice(image_files) 145 | file = File(path.join(folder_path, random_image), random_image) 146 | embed = Embed(color=Color.random()) 147 | embed.set_image(url=f"attachment://{random_image}") 148 | 149 | if ctx.locale.value == "fr": 150 | embed.set_footer(text="Récupéré de Morgan_le_Fay_1936 • Les crédits doivent aller à l'artiste") 151 | else: 152 | embed.set_footer(text="Fetched from Morgan_le_Fay_1936 • Credits must go to the artist") 153 | 154 | 155 | return embed, file 156 | 157 | 158 | def get_kistune_pic(ctx: Interaction) -> tuple[Embed, File]: 159 | folder_path = KITSUNE 160 | files = listdir(folder_path) 161 | 162 | image_files = [ 163 | file for file in files if file.endswith((".jpg", ".jpeg", ".png", ".gif")) 164 | ] 165 | 166 | random_image = choice(image_files) 167 | file = File(path.join(folder_path, random_image), random_image) 168 | embed = Embed(color=Color.random()) 169 | embed.set_image(url=f"attachment://{random_image}") 170 | 171 | if ctx.locale.value == "fr": 172 | embed.set_footer(text="Récupéré de Kitsune_1936 • Les crédits doivent aller à l'artiste") 173 | else: 174 | embed.set_footer(text="Fetched from Kitsune_1936 • Credits must go to the artist") 175 | 176 | return embed, file 177 | 178 | 179 | def safebooru_pic() -> str: 180 | response = requests.get( 181 | "https://safebooru.org/index.php?page=dapi&s=post&q=index&limit=100&tags=-rating:questionable+-animated+score:>=10" 182 | ).text.encode("utf-8") 183 | parser = ET.XMLParser(recover=True) 184 | tree = ET.ElementTree(ET.fromstring(response, parser=parser)) 185 | root = tree.getroot() 186 | image = choice(root).attrib["file_url"] 187 | return str(image) 188 | -------------------------------------------------------------------------------- /cogs/image.py: -------------------------------------------------------------------------------- 1 | from functions import ( 2 | check_botbanned_app_command, 3 | check_disabled_app_command, 4 | is_suspended, 5 | ) 6 | from discord import Color, Embed, Interaction, app_commands as Jeanne 7 | from discord.ext.commands import GroupCog, Bot 8 | from assets.images import ( 9 | get_jeanne_pic, 10 | get_kistune_pic, 11 | get_medusa_pic, 12 | get_morgan_pic, 13 | get_neko_pic, 14 | get_saber_pic, 15 | get_wallpaper_pic, 16 | safebooru_pic, 17 | ) 18 | from discord.app_commands import locale_str as T 19 | 20 | 21 | class images(GroupCog, name="image"): 22 | def __init__(self, bot): 23 | self.bot = bot 24 | super().__init__() 25 | 26 | @Jeanne.command( 27 | description=T("kitsune_desc"), 28 | extras={ 29 | "en": {"name": "kitsune", "description": "Get a random kitsune image"}, 30 | "fr": { 31 | "name": "kitsune", 32 | "description": "Obtenez une image de kitsune aléatoire", 33 | }, 34 | }, 35 | ) 36 | @Jeanne.check(check_botbanned_app_command) 37 | @Jeanne.check(check_disabled_app_command) 38 | @Jeanne.check(is_suspended) 39 | async def kitsune(self, ctx: Interaction): 40 | await ctx.response.defer() 41 | embed, file = get_kistune_pic(ctx) 42 | await ctx.followup.send(embed=embed, file=file) 43 | 44 | @Jeanne.command( 45 | description=T("wallpaper_desc"), 46 | extras={ 47 | "en": { 48 | "name": "wallpaper", 49 | "description": "Get a random wallpaper for your PC or phone", 50 | }, 51 | "fr": { 52 | "name": "wallpaper", 53 | "description": "Obtenez un fond d'écran aléatoire pour votre PC ou téléphone", 54 | }, 55 | }, 56 | ) 57 | @Jeanne.check(check_botbanned_app_command) 58 | @Jeanne.check(check_disabled_app_command) 59 | @Jeanne.check(is_suspended) 60 | async def wallpaper(self, ctx: Interaction): 61 | await ctx.response.defer() 62 | embed, file = get_wallpaper_pic(ctx) 63 | await ctx.followup.send(embed=embed, file=file) 64 | 65 | @Jeanne.command( 66 | description=T("jeanne_desc"), 67 | extras={ 68 | "en": { 69 | "name": "jeanne", 70 | "description": "Get a random image of Jeanne d'Arc", 71 | }, 72 | "fr": { 73 | "name": "jeanne", 74 | "description": "Obtenez une image aléatoire de Jeanne d'Arc", 75 | }, 76 | }, 77 | ) 78 | @Jeanne.check(check_botbanned_app_command) 79 | @Jeanne.check(check_disabled_app_command) 80 | @Jeanne.check(is_suspended) 81 | async def jeanne(self, ctx: Interaction): 82 | await ctx.response.defer() 83 | embed, file = get_jeanne_pic(ctx) 84 | await ctx.followup.send(embed=embed, file=file) 85 | 86 | @Jeanne.command( 87 | description=T("saber_desc"), 88 | extras={ 89 | "en": {"name": "saber", "description": "Get a random Saber image"}, 90 | "fr": { 91 | "name": "saber", 92 | "description": "Obtenez une image aléatoire de Saber", 93 | }, 94 | }, 95 | ) 96 | @Jeanne.check(check_botbanned_app_command) 97 | @Jeanne.check(check_disabled_app_command) 98 | @Jeanne.check(is_suspended) 99 | async def saber(self, ctx: Interaction): 100 | await ctx.response.defer() 101 | embed, file = get_saber_pic(ctx) 102 | await ctx.followup.send(embed=embed, file=file) 103 | 104 | @Jeanne.command( 105 | description=T("neko_desc"), 106 | extras={ 107 | "en": {"name": "neko", "description": "Get a random Neko image"}, 108 | "fr": { 109 | "name": "neko", 110 | "description": "Obtenez une image aléatoire de Neko", 111 | }, 112 | }, 113 | ) 114 | @Jeanne.check(check_botbanned_app_command) 115 | @Jeanne.check(check_disabled_app_command) 116 | @Jeanne.check(is_suspended) 117 | async def neko(self, ctx: Interaction): 118 | await ctx.response.defer() 119 | embed, file = get_neko_pic(ctx) 120 | await ctx.followup.send(embed=embed, file=file) 121 | 122 | @Jeanne.command( 123 | description=T("morgan_desc"), 124 | extras={ 125 | "en": { 126 | "name": "morgan", 127 | "description": "Get a random image of Morgan Le Fay", 128 | }, 129 | "fr": { 130 | "name": "morgan", 131 | "description": "Obtenez une image aléatoire de Morgan Le Fay", 132 | }, 133 | }, 134 | ) 135 | @Jeanne.check(check_botbanned_app_command) 136 | @Jeanne.check(check_disabled_app_command) 137 | @Jeanne.check(is_suspended) 138 | async def morgan(self, ctx: Interaction): 139 | await ctx.response.defer() 140 | embed, file = get_morgan_pic(ctx) 141 | await ctx.followup.send(embed=embed, file=file) 142 | 143 | @Jeanne.command( 144 | description=T("medusa_desc"), 145 | extras={ 146 | "en": {"name": "medusa", "description": "Get a random Medusa image"}, 147 | "fr": { 148 | "name": "medusa", 149 | "description": "Obtenez une image aléatoire de Medusa", 150 | }, 151 | }, 152 | ) 153 | @Jeanne.check(check_botbanned_app_command) 154 | @Jeanne.check(check_disabled_app_command) 155 | @Jeanne.check(is_suspended) 156 | async def medusa(self, ctx: Interaction): 157 | await ctx.response.defer() 158 | embed, file = get_medusa_pic(ctx) 159 | await ctx.followup.send(embed=embed, file=file) 160 | 161 | @Jeanne.command( 162 | description=T("safebooru_desc"), 163 | extras={ 164 | "en": { 165 | "name": "safebooru", 166 | "description": "Get a random image from Safebooru", 167 | }, 168 | "fr": { 169 | "name": "safebooru", 170 | "description": "Obtenez une image aléatoire de Safebooru", 171 | }, 172 | }, 173 | ) 174 | @Jeanne.check(check_botbanned_app_command) 175 | @Jeanne.check(check_disabled_app_command) 176 | @Jeanne.check(is_suspended) 177 | async def safebooru(self, ctx: Interaction): 178 | await ctx.response.defer() 179 | embed = Embed(color=Color.random()) 180 | embed.set_image(url=safebooru_pic()) 181 | if ctx.locale.value == "en-GB" or ctx.locale.value == "en-US": 182 | embed.set_footer( 183 | text="Fetched from Safebooru • Credits must go to the artist" 184 | ) 185 | elif ctx.locale.value == "fr": 186 | embed.set_footer( 187 | text="Récupéré depuis Safebooru • Les crédits doivent aller à l'artiste" 188 | ) 189 | await ctx.followup.send(embed=embed) 190 | 191 | 192 | async def setup(bot: Bot): 193 | await bot.add_cog(images(bot)) 194 | -------------------------------------------------------------------------------- /events/listeners.py: -------------------------------------------------------------------------------- 1 | from collections import OrderedDict 2 | from datetime import datetime 3 | from json import loads 4 | from discord import AllowedMentions, DMChannel, Embed, Message 5 | from discord.ext.commands import Bot, Cog 6 | from functions import BetaTest, DevPunishment, Levelling 7 | # from topgg import DBLClient 8 | # from config import TOPGG 9 | 10 | 11 | class listenersCog(Cog): 12 | def __init__(self, bot: Bot) -> None: 13 | self.bot = bot 14 | # self.topggpy = DBLClient(bot=self.bot, token=TOPGG) 15 | 16 | @staticmethod 17 | def replace_all(text: str, dic: dict): 18 | for i, j in dic.items(): 19 | text = text.replace(i, j) 20 | return text 21 | 22 | @Cog.listener() 23 | async def on_message(self, message: Message): 24 | if DevPunishment(message.author).check_botbanned_user: 25 | return 26 | 27 | try: 28 | if not message.author.bot and not isinstance(message.channel, DMChannel): 29 | level_instance = Levelling(message.author, message.guild) 30 | if level_instance.check_xpblacklist_channel(message.channel) is None: 31 | try: 32 | now_time = round(datetime.now().timestamp()) 33 | if now_time > level_instance.get_next_time_global: 34 | #temporary paused voting rewards 35 | # get_vote = await self.topggpy.get_user_vote( 36 | # int(message.author.id) 37 | # ) 38 | 39 | check = await BetaTest(self.bot).check(message.author) 40 | weekend_check = datetime.now().isoweekday() >= 6 41 | xp = 15 if weekend_check else 10 42 | # if not get_vote: 43 | # xp = 10 if weekend_check else 5 44 | if check: 45 | xp += 5 46 | 47 | lvl = await level_instance.add_xp(xp) 48 | if lvl is None: 49 | return 50 | channel, update, levelup = lvl 51 | role_reward = message.guild.get_role( 52 | level_instance.get_role_reward 53 | ) 54 | parameters = OrderedDict( 55 | [ 56 | ("%member%", str(message.author)), 57 | ("%pfp%", str(message.author.display_avatar)), 58 | ("%server%", str(message.guild.name)), 59 | ("%mention%", str(message.author.mention)), 60 | ("%name%", str(message.author.name)), 61 | ( 62 | "%newlevel%", 63 | str( 64 | Levelling( 65 | message.author, message.guild 66 | ).get_member_level 67 | ), 68 | ), 69 | ( 70 | "%role%", 71 | str( 72 | (role_reward.name if role_reward else None) 73 | ), 74 | ), 75 | ( 76 | "%rolemention%", 77 | str( 78 | ( 79 | role_reward.mention 80 | if role_reward 81 | else None 82 | ) 83 | ), 84 | ), 85 | ] 86 | ) 87 | 88 | if update == "0" or update is None: 89 | # Separate English and French messages 90 | if message.guild.preferred_locale.value in ["en-GB", "en-US"]: 91 | msg = "{} has leveled up to `{}`".format( 92 | message.author, 93 | Levelling( 94 | message.author, message.guild 95 | ).get_member_level, 96 | ) 97 | else: 98 | msg = "{} a atteint le niveau `{}`".format( 99 | message.author, 100 | Levelling( 101 | message.author, message.guild 102 | ).get_member_level, 103 | ) 104 | 105 | await channel.send( 106 | msg, 107 | allowed_mentions=AllowedMentions( 108 | roles=False, everyone=False, users=True 109 | ), 110 | ) 111 | else: 112 | json = loads(self.replace_all(update, parameters)) 113 | msg = json["content"] 114 | embed = Embed.from_dict(json["embeds"][0]) 115 | 116 | await channel.send(content=msg, embed=embed) 117 | if role_reward: 118 | await message.author.add_roles(role_reward) 119 | if levelup == "0" or levelup is None: 120 | # Separate English and French messages 121 | if message.guild.preferred_locale.value in ["en-GB", "en-US"]: 122 | msg = "CONGRATS {}! You were role awarded {}".format( 123 | message.author, 124 | role_reward.name, 125 | ) 126 | else: 127 | msg = "FÉLICITATIONS {}! Tu as reçu le rôle {}".format( 128 | message.author, 129 | role_reward.name, 130 | ) 131 | 132 | await channel.send( 133 | msg, 134 | allowed_mentions=AllowedMentions( 135 | roles=False, everyone=False, users=True 136 | ), 137 | ) 138 | else: 139 | json = loads(self.replace_all(levelup, parameters)) 140 | msg = json["content"] 141 | embed = Embed.from_dict(json["embeds"][0]) 142 | 143 | await channel.send(content=msg, embed=embed) 144 | return 145 | except AttributeError: 146 | 147 | return 148 | except Exception: 149 | return 150 | 151 | 152 | async def setup(bot: Bot): 153 | await bot.add_cog(listenersCog(bot)) 154 | 155 | -------------------------------------------------------------------------------- /events/welcomer.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | from discord import Color, Embed, Guild, Member, AllowedMentions, RawMemberRemoveEvent 3 | from discord.ext.commands import Cog, Bot 4 | from functions import Welcomer 5 | from collections import OrderedDict 6 | from json import loads, JSONDecodeError 7 | 8 | 9 | class WelcomerCog(Cog): 10 | def __init__(self, bot: Bot): 11 | self.bot = bot 12 | 13 | @staticmethod 14 | def replace_all(text: str, dic: dict): 15 | for i, j in dic.items(): 16 | text = text.replace(i, j) 17 | return text 18 | 19 | @Cog.listener() 20 | async def on_member_join(self, member: Member): 21 | try: 22 | welcomer_instance = Welcomer(member.guild) 23 | welcomer = welcomer_instance.get_welcomer 24 | if welcomer is None: 25 | return 26 | 27 | server = welcomer_instance.server 28 | if member.guild.id == server.id: 29 | welcomemsg = welcomer_instance.get_welcoming_msg 30 | if welcomemsg is None: 31 | if ( 32 | server.preferred_locale.value == "en-GB" 33 | or server.preferred_locale.value == "en-US" 34 | ): 35 | welcome = Embed( 36 | description=f"Hi {member} and welcome to {member.guild.name}!", 37 | color=Color.random(), 38 | ).set_thumbnail(url=member.display_avatar.url if member.display_avatar else "") 39 | await welcomer.send(embed=welcome) 40 | return 41 | elif server.preferred_locale.value=="fr": 42 | welcome = Embed( 43 | description=f"Salut {member} et bienvenue sur {member.guild.name} !", 44 | color=Color.random(), 45 | ).set_thumbnail( 46 | url=( 47 | member.display_avatar.url 48 | if member.display_avatar 49 | else "" 50 | ) 51 | ) 52 | await welcomer.send(embed=welcome) 53 | return 54 | 55 | humans = sum(not m.bot for m in member.guild.members) 56 | parameters = OrderedDict( 57 | [ 58 | ("%member%", str(member)), 59 | ("%pfp%", str(member.display_avatar.url if member.display_avatar else "")), 60 | ("%server%", str(member.guild.name)), 61 | ("%mention%", str(member.mention)), 62 | ("%name%", str(member.global_name or member.name)), 63 | ("%members%", str(member.guild.member_count)), 64 | ("%humans%", str(humans)), 65 | ("%icon%", str(member.guild.icon.url if member.guild.icon else "")), 66 | ] 67 | ) 68 | try: 69 | json_data: dict = loads(self.replace_all(welcomemsg, parameters)) 70 | content: str = json_data.get("content") 71 | embed_data = json_data.get("embeds") 72 | if embed_data: 73 | embed = Embed.from_dict(embed_data[0]) 74 | await welcomer.send( 75 | content=content, 76 | embed=embed, 77 | allowed_mentions=AllowedMentions(everyone=False, users=True), 78 | ) 79 | return 80 | await welcomer.send(content=content) 81 | except JSONDecodeError: 82 | print("Error: Invalid JSON in welcoming message.") 83 | except Exception as e: 84 | print(f"Error in on_member_join: {e}") 85 | 86 | @Cog.listener() 87 | async def on_raw_member_remove(self, payload: RawMemberRemoveEvent): 88 | try: 89 | member = payload.user 90 | server = await self.bot.fetch_guild(payload.guild_id) 91 | welcomer_instance = Welcomer(server) 92 | leaver = welcomer_instance.get_leaver 93 | if leaver is None: 94 | return 95 | 96 | server_data = welcomer_instance.server 97 | if payload.guild_id == server_data.id: 98 | leavingmsg = welcomer_instance.get_leaving_msg 99 | if leavingmsg is None: 100 | if ( 101 | server.preferred_locale.value == "en-GB" 102 | or server.preferred_locale.value == "en-US" 103 | ): 104 | leave = Embed( 105 | description=f"{member} left the server", 106 | color=Color.random(), 107 | ).set_thumbnail(url=member.display_avatar.url if member.display_avatar else "") 108 | await leaver.send(embed=leave) 109 | return 110 | if server.preferred_locale.value == "fr": 111 | leave = Embed( 112 | description=f"{member} a quitté le serveur", 113 | color=Color.random(), 114 | ).set_thumbnail( 115 | url=( 116 | member.display_avatar.url 117 | if member.display_avatar 118 | else "" 119 | ) 120 | ) 121 | await leaver.send(embed=leave) 122 | return 123 | 124 | humans = len([m for m in server.members if not m.bot]) 125 | parameters = OrderedDict( 126 | [ 127 | ("%member%", str(member)), 128 | ("%pfp%", str(member.display_avatar.url if member.display_avatar else "")), 129 | ("%server%", str(server.name)), 130 | ("%mention%", str(member.mention)), 131 | ("%name%", str(member.global_name or member.name)), 132 | ("%members%", str(server.member_count)), 133 | ("%humans%", str(humans)), 134 | ("%icon%", str(server.icon.url if server.icon else "")), 135 | ] 136 | ) 137 | try: 138 | json_data: dict = loads(self.replace_all(leavingmsg, parameters)) 139 | content: str = json_data.get("content") 140 | embed_data = json_data.get("embeds") 141 | if embed_data: 142 | embed = Embed.from_dict(embed_data[0]) 143 | await leaver.send( 144 | content=content, 145 | embed=embed, 146 | allowed_mentions=AllowedMentions(everyone=False, users=True), 147 | ) 148 | else: 149 | await leaver.send(content=content) 150 | except JSONDecodeError: 151 | print("Error: Invalid JSON in leaving message.") 152 | except Exception as e: 153 | print(f"Error in on_raw_member_remove: {e}") 154 | 155 | @Cog.listener() 156 | async def on_guild_join(self, server: Guild): 157 | try: 158 | print(f"Chunking guild: {server.name} ({server.id})...") 159 | await asyncio.wait_for(server.chunk(), timeout=60.0) 160 | print(f"Successfully chunked {server.name}.") 161 | except asyncio.TimeoutError: 162 | print(f"Chunking timed out for {server.name}.") 163 | except Exception as e: 164 | print(f"An error occurred while chunking {server.name}: {e}") 165 | 166 | 167 | async def setup(bot: Bot): 168 | await bot.add_cog(WelcomerCog(bot)) 169 | -------------------------------------------------------------------------------- /languages/en/inventory.py: -------------------------------------------------------------------------------- 1 | from assets.components import ( 2 | Confirmation, 3 | Country_Badge_Buttons, 4 | buy_function_app, 5 | use_function_app, 6 | ) 7 | from functions import ( 8 | Currency, 9 | Inventory, 10 | ) 11 | from discord import ButtonStyle, Color, Embed, File, Interaction, app_commands as Jeanne 12 | from discord.ext.commands import Bot 13 | from assets.generators.profile_card import Profile 14 | from reactionmenu import ViewButton, ViewMenu 15 | 16 | 17 | class Shop_Group(): 18 | def __init__(self, bot: Bot) -> None: 19 | self.bot = bot 20 | 21 | async def country(self, ctx: Interaction): 22 | await ctx.response.defer() 23 | balance = Currency(ctx.user).get_balance 24 | if balance is None or balance < 500: 25 | nomoney = Embed(description="You do not have enough QP.") 26 | await ctx.followup.send(embed=nomoney) 27 | return 28 | view = Country_Badge_Buttons(self.bot, ctx.user) 29 | embed = Embed( 30 | description="Here are the available country badges:", color=Color.random() 31 | ) 32 | embed.set_footer(text="Click on one of the buttons to buy the badge") 33 | await ctx.followup.send(embed=embed, view=view) 34 | await view.wait() 35 | 36 | if view.value: 37 | country = view.value 38 | await Inventory(ctx.user).add_country(country) 39 | embed1 = Embed( 40 | description="Country badge bought and added to profile", 41 | color=Color.random(), 42 | ) 43 | await ctx.edit_original_response(embed=embed1, view=None) 44 | return 45 | await ctx.delete_original_response() 46 | 47 | async def backgrounds(self, ctx: Interaction): 48 | await ctx.response.defer() 49 | disabled = False 50 | balance = Currency(ctx.user).get_balance 51 | if balance < 1000: 52 | disabled = True 53 | wallpapers = Inventory().fetch_wallpapers() 54 | embed = Embed() 55 | menu = ViewMenu( 56 | ctx, 57 | menu_type=ViewMenu.TypeEmbed, 58 | disable_items_on_timeout=True, 59 | style="Page $/&", 60 | ) 61 | embed.color = Color.random() 62 | for wallpaper in wallpapers: 63 | name = str(wallpaper[1]) 64 | page_embed = Embed(title=name, color=embed.color) 65 | 66 | page_embed.add_field( 67 | name="Price", value="1000 <:quantumpiece:1161010445205905418>" 68 | ) 69 | page_embed.set_image(url=str(wallpaper[2])) 70 | menu.add_page(embed=page_embed) 71 | 72 | async def buy_callback(): 73 | await buy_function_app(self.bot, ctx, menu.last_viewed.embed.title) 74 | menu.remove_all_buttons() 75 | 76 | call_followup = ViewButton.Followup( 77 | details=ViewButton.Followup.set_caller_details(buy_callback) 78 | ) 79 | 80 | menu.add_button(ViewButton.go_to_first_page()) 81 | menu.add_button(ViewButton.back()) 82 | menu.add_button( 83 | ViewButton( 84 | label="Buy", 85 | style=ButtonStyle.green, 86 | custom_id=ViewButton.ID_CALLER, 87 | followup=call_followup, 88 | disabled=disabled, 89 | ) 90 | ) 91 | menu.add_button(ViewButton.next()) 92 | menu.add_button(ViewButton.go_to_last_page()) 93 | await menu.start() 94 | 95 | async def backgrounds_error(self, ctx: Interaction, error: Jeanne.AppCommandError): 96 | cooldown = Embed( 97 | description=f"You have already tried to preview a background!\nTry again after `{round(error.retry_after, 2)} seconds`", 98 | color=Color.random(), 99 | ) 100 | await ctx.response.send_message(embed=cooldown) 101 | 102 | 103 | class Background_Group(): 104 | def __init__(self, bot: Bot) -> None: 105 | self.bot = bot 106 | 107 | 108 | async def buycustom(self, ctx: Interaction, name: str, link: str): 109 | await ctx.response.defer() 110 | balance = Currency(ctx.user).get_balance 111 | if balance is None or balance < 1500: 112 | nomoney = Embed(description="You do not have enough QP.") 113 | await ctx.followup.send(embed=nomoney) 114 | return 115 | await ctx.followup.send( 116 | embed=Embed( 117 | description="Creating preview... This will take some time " 118 | ) 119 | ) 120 | image = await Profile(self.bot).generate_profile(ctx, 121 | ctx.user, link, True, True, "southafrica" 122 | ) 123 | if image == False: 124 | size_error = Embed( 125 | description="The image is below the 900x500 size.\nPlease enlarge the image and try again" 126 | ) 127 | await ctx.edit_original_response(embed=size_error) 128 | return 129 | file = File(fp=image, filename=f"preview_profile_card.png") 130 | preview = ( 131 | Embed( 132 | description="This is the preview of the profile card.", 133 | color=Color.blue(), 134 | ) 135 | .add_field(name="Cost", value="1500 <:quantumpiece:1161010445205905418>") 136 | .set_footer(text="Is this the background you wanted?") 137 | .set_footer( 138 | text="Please note that if the custom background violates ToS or is NSFW, it will be removed with NO REFUNDS!" 139 | ) 140 | ) 141 | view = Confirmation(ctx.user) 142 | await ctx.edit_original_response(embed=preview, attachments=[file], view=view) 143 | await view.wait() 144 | if view.value: 145 | url=await Inventory(ctx.user).upload_to_catbox(link) 146 | await Inventory(ctx.user).add_user_custom_wallpaper(name, url) 147 | embed1 = Embed( 148 | description="Background wallpaper bought and selected", 149 | color=Color.random(), 150 | ) 151 | await ctx.edit_original_response(embed=embed1, view=None, attachments=[]) 152 | else: 153 | await ctx.edit_original_response( 154 | embed=Embed(description="Cancelled"), view=None, attachments=[] 155 | ) 156 | 157 | async def buycustom_error(self, ctx: Interaction, error: Jeanne.AppCommandError, type:str): 158 | if type == "cooldown": 159 | cooldown = Embed( 160 | description=f"You have already tried to preview a background!\nTry again after `{round(error.retry_after, 2)} seconds`", 161 | color=Color.random(), 162 | ) 163 | await ctx.response.send_message(embed=cooldown) 164 | return 165 | if type == "invalid": 166 | embed = Embed(description="Invalid image URL", color=Color.red()) 167 | await ctx.edit_original_response(content=None, embed=embed) 168 | 169 | async def list(self, ctx: Interaction): 170 | await ctx.response.defer() 171 | if Inventory(ctx.user).get_user_inventory == None: 172 | embed = Embed(description="Your inventory is empty", color=Color.red()) 173 | await ctx.followup.send(embed=embed) 174 | return 175 | a = Inventory(ctx.user).get_user_inventory 176 | embed = Embed() 177 | menu = ViewMenu( 178 | ctx, 179 | menu_type=ViewMenu.TypeEmbed, 180 | disable_items_on_timeout=True, 181 | style="Page $/&", 182 | ) 183 | embed.color = Color.random() 184 | for wallpaper in a: 185 | page_embed = Embed(title=str(wallpaper[1]), color=embed.color) 186 | page_embed.set_image(url=str(wallpaper[2])) 187 | menu.add_page(embed=page_embed) 188 | 189 | async def use_callback(): 190 | await use_function_app(ctx, menu.last_viewed.embed.title) 191 | menu.remove_all_buttons() 192 | 193 | call_followup = ViewButton.Followup( 194 | details=ViewButton.Followup.set_caller_details(use_callback) 195 | ) 196 | menu.add_button(ViewButton.go_to_first_page()) 197 | menu.add_button(ViewButton.back()) 198 | menu.add_button( 199 | ViewButton( 200 | label="Use", 201 | style=ButtonStyle.green, 202 | custom_id=ViewButton.ID_CALLER, 203 | followup=call_followup, 204 | ) 205 | ) 206 | menu.add_button(ViewButton.next()) 207 | menu.add_button(ViewButton.go_to_last_page()) 208 | await menu.start() 209 | 210 | -------------------------------------------------------------------------------- /cogs/hentai.py: -------------------------------------------------------------------------------- 1 | from discord import ( 2 | Interaction, 3 | app_commands as Jeanne, 4 | ) 5 | from discord.ext.commands import Cog, Bot 6 | from functions import ( 7 | check_botbanned_app_command, 8 | check_disabled_app_command, 9 | is_suspended, 10 | ) 11 | from typing import Optional 12 | import languages.en.hentai as en 13 | import languages.fr.hentai as fr 14 | from discord.app_commands import locale_str as T 15 | 16 | 17 | class nsfw(Cog): 18 | def __init__(self, bot: Bot): 19 | self.bot = bot 20 | 21 | @Jeanne.command( 22 | description=T("hentai_desc"), 23 | nsfw=True, 24 | extras={ 25 | "nsfw": True, 26 | "name": "hentai", 27 | "en": {"description": "Get a random hentai from Jeanne"}, 28 | "fr": { 29 | "name": "hentai", 30 | "description": "Obtenez un hentai aléatoire de Jeanne", 31 | }, 32 | }, 33 | ) 34 | @Jeanne.checks.cooldown(1, 5, key=lambda i: (i.user.id)) 35 | @Jeanne.check(check_botbanned_app_command) 36 | @Jeanne.check(check_disabled_app_command) 37 | @Jeanne.check(is_suspended) 38 | async def hentai( 39 | self, 40 | ctx: Interaction, 41 | ) -> None: 42 | if ctx.locale.value == "fr": 43 | await fr.nsfw(self.bot).hentai(ctx) 44 | else: 45 | await en.nsfw(self.bot).hentai(ctx) 46 | 47 | 48 | 49 | @Jeanne.command( 50 | description=T("yandere_desc"), 51 | nsfw=True, 52 | extras={ 53 | "nsfw": True, 54 | "en": { 55 | "name": "yandere", 56 | "description": "Get a random media content from Yandere", 57 | "parameters": [ 58 | {"name": "tag", "description": "Add your tags", "required": False}, 59 | { 60 | "name": "plus", 61 | "description": "Need more content? (up to 4)", 62 | "required": False, 63 | }, 64 | ], 65 | }, 66 | "fr": { 67 | "name": "yandere", 68 | "description": "Obtenez un contenu multimédia aléatoire de Yandere", 69 | "parameters": [ 70 | { 71 | "name": "tag", 72 | "description": "Ajoutez vos tags", 73 | "required": False, 74 | }, 75 | { 76 | "name": "plus", 77 | "description": "Besoin de plus de contenu? (jusqu'à 4)", 78 | "required": False, 79 | }, 80 | ], 81 | }, 82 | }, 83 | ) 84 | @Jeanne.checks.cooldown(1, 5, key=lambda i: (i.user.id)) 85 | @Jeanne.describe(tag=T("tag_parm_desc"), plus=T("plus_parm_desc")) 86 | @Jeanne.rename(tag=T("tag_parm_name"), plus=T("plus_parm_name")) 87 | @Jeanne.check(check_botbanned_app_command) 88 | @Jeanne.check(check_disabled_app_command) 89 | @Jeanne.check(is_suspended) 90 | async def yandere( 91 | self, 92 | ctx: Interaction, 93 | tag: Optional[str] = None, 94 | plus: Optional[bool] = None, 95 | ) -> None: 96 | if ctx.locale.value == "fr": 97 | await fr.nsfw(self.bot).yandere(ctx, tag, plus) 98 | else: 99 | await en.nsfw(self.bot).yandere(ctx, tag, plus) 100 | 101 | 102 | @Jeanne.command( 103 | description=T("konachan_desc"), 104 | nsfw=True, 105 | extras={ 106 | "nsfw": True, 107 | "en": { 108 | "name": "konachan", 109 | "description": "Get a random media content from Konachan", 110 | "parameters": [ 111 | {"name": "tag", "description": "Add your tags", "required": False}, 112 | { 113 | "name": "plus", 114 | "description": "Need more content? (up to 4)", 115 | "required": False, 116 | }, 117 | ], 118 | }, 119 | "fr": { 120 | "name": "konachan", 121 | "description": "Obtenez un contenu multimédia aléatoire de Konachan", 122 | "parameters": [ 123 | { 124 | "name": "tag", 125 | "description": "Ajoutez vos tags", 126 | "required": False, 127 | }, 128 | { 129 | "name": "plus", 130 | "description": "Besoin de plus de contenu? (jusqu'à 4)", 131 | "required": False, 132 | }, 133 | ], 134 | }, 135 | }, 136 | ) 137 | @Jeanne.checks.cooldown(1, 5, key=lambda i: (i.user.id)) 138 | @Jeanne.describe( 139 | tag=T("tag_parm_desc"), 140 | plus=T("plus_parm_desc"), 141 | ) 142 | @Jeanne.rename( 143 | tag=T("tag_parm_name"), 144 | plus=T("plus_parm_name"), 145 | ) 146 | @Jeanne.check(check_botbanned_app_command) 147 | @Jeanne.check(check_disabled_app_command) 148 | @Jeanne.check(is_suspended) 149 | async def konachan( 150 | self, 151 | ctx: Interaction, 152 | tag: Optional[str] = None, 153 | plus: Optional[bool] = None, 154 | ) -> None: 155 | if ctx.locale.value == "fr": 156 | await fr.nsfw(self.bot).konachan(ctx, tag, plus) 157 | else: 158 | await en.nsfw(self.bot).konachan(ctx, tag, plus) 159 | 160 | 161 | @Jeanne.command( 162 | description=T("danbooru_desc"), 163 | nsfw=True, 164 | extras={ 165 | "nsfw": True, 166 | "en": { 167 | "name": "danbooru", 168 | "description": "Get a random media content from Danbooru", 169 | "parameters": [ 170 | {"name": "tag", "description": "Add your tags", "required": False}, 171 | { 172 | "name": "plus", 173 | "description": "Need more content? (up to 4)", 174 | "required": False, 175 | }, 176 | ], 177 | }, 178 | "fr": { 179 | "name": "danbooru", 180 | "description": "Obtenez un contenu multimédia aléatoire de Danbooru", 181 | "parameters": [ 182 | { 183 | "name": "tag", 184 | "description": "Ajoutez vos tags", 185 | "required": False, 186 | }, 187 | { 188 | "name": "plus", 189 | "description": "Besoin de plus de contenu? (jusqu'à 4)", 190 | "required": False, 191 | }, 192 | ], 193 | }, 194 | }, 195 | ) 196 | @Jeanne.checks.cooldown(1, 5, key=lambda i: (i.user.id)) 197 | @Jeanne.describe( 198 | tag=T("tag_parm_desc"), 199 | plus=T("plus_parm_desc"), 200 | ) 201 | @Jeanne.rename( 202 | tag=T("tag_parm_name"), 203 | plus=T("plus_parm_name"), 204 | ) 205 | @Jeanne.check(check_botbanned_app_command) 206 | @Jeanne.check(check_disabled_app_command) 207 | @Jeanne.check(is_suspended) 208 | async def danbooru( 209 | self, 210 | ctx: Interaction, 211 | tag: Optional[str] = None, 212 | plus: Optional[bool] = None, 213 | ) -> None: 214 | if ctx.locale.value == "fr": 215 | await fr.nsfw(self.bot).danbooru(ctx, tag, plus) 216 | else: 217 | await en.nsfw(self.bot).danbooru(ctx, tag, plus) 218 | 219 | 220 | @hentai.error 221 | @konachan.error 222 | @yandere.error 223 | @danbooru.error 224 | async def Hentai_error(self, ctx: Interaction, error: Jeanne.AppCommandError): 225 | if isinstance(error, Jeanne.CommandInvokeError) and isinstance( 226 | error.original, (IndexError, KeyError, TypeError) 227 | ): 228 | if ctx.locale.value == "fr": 229 | await fr.nsfw(self.bot).Hentai_error(ctx, error, "NotFound") 230 | else: 231 | await en.nsfw(self.bot).Hentai_error(ctx, error, "NotFound") 232 | 233 | if isinstance(error, Jeanne.errors.CommandOnCooldown): 234 | if ctx.locale.value == "fr": 235 | await fr.nsfw(self.bot).Hentai_error(ctx, error, "Cooldown") 236 | else: 237 | await en.nsfw(self.bot).Hentai_error(ctx, error, "Cooldown") 238 | 239 | 240 | 241 | async def setup(bot: Bot): 242 | await bot.add_cog(nsfw(bot)) 243 | -------------------------------------------------------------------------------- /languages/fr/inventory.py: -------------------------------------------------------------------------------- 1 | from assets.components import ( 2 | Confirmation, 3 | Country_Badge_Buttons, 4 | buy_function_app, 5 | use_function_app, 6 | ) 7 | from functions import ( 8 | Currency, 9 | Inventory, 10 | ) 11 | from discord import ButtonStyle, Color, Embed, File, Interaction, app_commands as Jeanne 12 | from discord.ext.commands import Bot 13 | from assets.generators.profile_card import Profile 14 | from reactionmenu import ViewButton, ViewMenu 15 | 16 | 17 | class Shop_Group(): 18 | def __init__(self, bot: Bot) -> None: 19 | self.bot = bot 20 | 21 | async def country(self, ctx: Interaction): 22 | await ctx.response.defer() 23 | balance = Currency(ctx.user).get_balance 24 | if balance is None or balance < 500: 25 | nomoney = Embed(description="Vous n'avez pas assez de QP.") 26 | await ctx.followup.send(embed=nomoney) 27 | return 28 | view = Country_Badge_Buttons(self.bot, ctx.user) 29 | embed = Embed( 30 | description="Voici les badges de pays disponibles :", color=Color.random() 31 | ) 32 | embed.set_footer(text="Cliquez sur l'un des boutons pour acheter le badge") 33 | await ctx.followup.send(embed=embed, view=view) 34 | await view.wait() 35 | 36 | if view.value: 37 | country = view.value 38 | await Inventory(ctx.user).add_country(country) 39 | embed1 = Embed( 40 | description="Badge de pays acheté et ajouté au profil", 41 | color=Color.random(), 42 | ) 43 | await ctx.edit_original_response(embed=embed1, view=None) 44 | return 45 | await ctx.delete_original_response() 46 | 47 | async def backgrounds(self, ctx: Interaction): 48 | await ctx.response.defer() 49 | disabled = False 50 | balance = Currency(ctx.user).get_balance 51 | if balance < 1000: 52 | disabled = True 53 | wallpapers = Inventory().fetch_wallpapers() 54 | embed = Embed() 55 | menu = ViewMenu( 56 | ctx, 57 | menu_type=ViewMenu.TypeEmbed, 58 | disable_items_on_timeout=True, 59 | style="Page $/&", 60 | ) 61 | embed.color = Color.random() 62 | for wallpaper in wallpapers: 63 | name = str(wallpaper[1]) 64 | page_embed = Embed(title=name, color=embed.color) 65 | 66 | page_embed.add_field( 67 | name="Prix", value="1000 <:quantumpiece:1161010445205905418>" 68 | ) 69 | page_embed.set_image(url=str(wallpaper[2])) 70 | menu.add_page(embed=page_embed) 71 | 72 | async def buy_callback(): 73 | await buy_function_app(self.bot, ctx, menu.last_viewed.embed.title) 74 | menu.remove_all_buttons() 75 | 76 | call_followup = ViewButton.Followup( 77 | details=ViewButton.Followup.set_caller_details(buy_callback) 78 | ) 79 | 80 | menu.add_button(ViewButton.go_to_first_page()) 81 | menu.add_button(ViewButton.back()) 82 | menu.add_button( 83 | ViewButton( 84 | label="Acheter", 85 | style=ButtonStyle.green, 86 | custom_id=ViewButton.ID_CALLER, 87 | followup=call_followup, 88 | disabled=disabled, 89 | ) 90 | ) 91 | menu.add_button(ViewButton.next()) 92 | menu.add_button(ViewButton.go_to_last_page()) 93 | await menu.start() 94 | 95 | async def backgrounds_error(self, ctx: Interaction, error: Jeanne.AppCommandError): 96 | cooldown = Embed( 97 | description=f"Vous avez déjà essayé de prévisualiser un fond d'écran !\nRéessayez après `{round(error.retry_after, 2)} secondes`", 98 | color=Color.random(), 99 | ) 100 | await ctx.response.send_message(embed=cooldown) 101 | 102 | 103 | class Background_Group(): 104 | def __init__(self, bot: Bot) -> None: 105 | self.bot = bot 106 | 107 | 108 | async def buycustom(self, ctx: Interaction, name: str, link: str): 109 | await ctx.response.defer() 110 | balance = Currency(ctx.user).get_balance 111 | if balance is None or balance < 1500: 112 | nomoney = Embed(description="Vous n'avez pas assez de QP.") 113 | await ctx.followup.send(embed=nomoney) 114 | return 115 | await ctx.followup.send( 116 | embed=Embed( 117 | description="Création de la prévisualisation... Cela prendra un peu de temps " 118 | ) 119 | ) 120 | image = await Profile(self.bot).generate_profile(ctx, 121 | ctx.user, link, True, True, "southafrica" 122 | ) 123 | if image == False: 124 | size_error = Embed( 125 | description="L'image est inférieure à la taille 900x500.\nVeuillez agrandir l'image et réessayer" 126 | ) 127 | await ctx.edit_original_response(embed=size_error) 128 | return 129 | file = File(fp=image, filename="preview_profile_card.png") 130 | preview = ( 131 | Embed( 132 | description="Voici la prévisualisation de la carte de profil.", 133 | color=Color.blue(), 134 | ) 135 | .add_field(name="Coût", value="1500 <:quantumpiece:1161010445205905418>") 136 | .set_footer(text="Est-ce le fond d'écran que vous vouliez ?") 137 | .set_footer( 138 | text="Veuillez noter que si le fond d'écran personnalisé enfreint les CGU ou est NSFW, il sera supprimé SANS REMBOURSEMENT !" 139 | ) 140 | ) 141 | view = Confirmation(ctx.user) 142 | await ctx.edit_original_response(embed=preview, attachments=[file], view=view) 143 | await view.wait() 144 | if view.value: 145 | url=await Inventory(ctx.user).upload_to_catbox(link) 146 | await Inventory(ctx.user).add_user_custom_wallpaper(name, url) 147 | embed1 = Embed( 148 | description="Fond d'écran acheté et sélectionné", 149 | color=Color.random(), 150 | ) 151 | await ctx.edit_original_response(embed=embed1, view=None, attachments=[]) 152 | else: 153 | await ctx.edit_original_response( 154 | embed=Embed(description="Annulé"), view=None, attachments=[] 155 | ) 156 | 157 | async def buycustom_error(self, ctx: Interaction, error: Jeanne.AppCommandError, type:str): 158 | if type == "cooldown": 159 | cooldown = Embed( 160 | description=f"Vous avez déjà essayé de prévisualiser un fond d'écran !\nRéessayez après `{round(error.retry_after, 2)} secondes`", 161 | color=Color.random(), 162 | ) 163 | await ctx.response.send_message(embed=cooldown) 164 | return 165 | if type == "invalid": 166 | embed = Embed(description="URL de l'image invalide", color=Color.red()) 167 | await ctx.edit_original_response(content=None, embed=embed) 168 | 169 | async def list(self, ctx: Interaction): 170 | await ctx.response.defer() 171 | if Inventory(ctx.user).get_user_inventory is None: 172 | embed = Embed(description="Votre inventaire est vide", color=Color.red()) 173 | await ctx.followup.send(embed=embed) 174 | return 175 | a = Inventory(ctx.user).get_user_inventory 176 | embed = Embed() 177 | menu = ViewMenu( 178 | ctx, 179 | menu_type=ViewMenu.TypeEmbed, 180 | disable_items_on_timeout=True, 181 | style="Page $/&", 182 | ) 183 | embed.color = Color.random() 184 | for wallpaper in a: 185 | page_embed = Embed(title=str(wallpaper[1]), color=embed.color) 186 | page_embed.set_image(url=str(wallpaper[2])) 187 | menu.add_page(embed=page_embed) 188 | 189 | async def use_callback(): 190 | await use_function_app(ctx, menu.last_viewed.embed.title) 191 | menu.remove_all_buttons() 192 | 193 | call_followup = ViewButton.Followup( 194 | details=ViewButton.Followup.set_caller_details(use_callback) 195 | ) 196 | menu.add_button(ViewButton.go_to_first_page()) 197 | menu.add_button(ViewButton.back()) 198 | menu.add_button( 199 | ViewButton( 200 | label="Utiliser", 201 | style=ButtonStyle.green, 202 | custom_id=ViewButton.ID_CALLER, 203 | followup=call_followup, 204 | ) 205 | ) 206 | menu.add_button(ViewButton.next()) 207 | menu.add_button(ViewButton.go_to_last_page()) 208 | await menu.start() 209 | -------------------------------------------------------------------------------- /cogs/fun.py: -------------------------------------------------------------------------------- 1 | from discord import Interaction, Member, app_commands as Jeanne 2 | from discord.ext.commands import Cog, Bot 3 | from functions import ( 4 | check_botbanned_app_command, 5 | check_disabled_app_command, 6 | is_suspended, 7 | ) 8 | from assets.images import get_animeme_pic 9 | from typing import Optional 10 | import languages.en.fun as en 11 | import languages.fr.fun as fr 12 | from discord.app_commands import locale_str as T 13 | 14 | 15 | class fun(Cog, name="FunSlash"): 16 | def __init__(self, bot: Bot): 17 | self.bot = bot 18 | 19 | @Jeanne.command( 20 | name="8ball", 21 | description=T("8ball_desc"), 22 | extras={ 23 | "en": { 24 | "name": "8ball", 25 | "description": "Ask 8 ball anything and you will get your answer", 26 | "parameters": [ 27 | { 28 | "name": "question", 29 | "description": "The question you want to ask the 8ball", 30 | "required": True, 31 | } 32 | ], 33 | }, 34 | "fr": { 35 | "name": "8ball", 36 | "description": "Demandez à 8 ball n'importe quoi et vous obtiendrez votre réponse", 37 | "parameters": [ 38 | { 39 | "name": "question", 40 | "description": "Ajoutez votre question", 41 | "required": True, 42 | } 43 | ], 44 | }, 45 | }, 46 | ) 47 | @Jeanne.describe(question=T("question_parm_desc")) 48 | @Jeanne.rename(question=T("question_parm_name")) 49 | @Jeanne.check(check_botbanned_app_command) 50 | @Jeanne.check(check_disabled_app_command) 51 | @Jeanne.check(is_suspended) 52 | async def _8ball(self, ctx: Interaction, question: str): 53 | if ctx.locale.value == "fr": 54 | await fr.fun(self.bot)._8ball(ctx, question) 55 | else: 56 | await en.fun(self.bot)._8ball(ctx, question) 57 | 58 | @Jeanne.command( 59 | name=T("reverse_name"), 60 | description=T("reverse_desc"), 61 | extras={ 62 | "en": { 63 | "name": "reverse", 64 | "description": "Say something and I will say it in reversed text", 65 | "parameters": [ 66 | { 67 | "name": "text", 68 | "description": "The text you want to reverse", 69 | "required": True, 70 | } 71 | ], 72 | }, 73 | "fr": { 74 | "name": "inverse", 75 | "description": "Dites quelque chose et je le dirai dans un texte inversé", 76 | "parameters": [ 77 | { 78 | "name": "texte", 79 | "description": "Qu'est-ce que vous inversez?", 80 | "required": True, 81 | } 82 | ], 83 | }, 84 | }, 85 | ) 86 | @Jeanne.describe(text=T("text_parm_desc")) 87 | @Jeanne.rename(text=T("text_parm_name")) 88 | @Jeanne.check(check_botbanned_app_command) 89 | @Jeanne.check(check_disabled_app_command) 90 | @Jeanne.check(is_suspended) 91 | async def reverse(self, ctx: Interaction, text: str): 92 | if ctx.locale.value == "fr": 93 | await fr.fun(self.bot).reverse(ctx, text) 94 | else: 95 | await en.fun(self.bot).reverse(ctx, text) 96 | 97 | @Jeanne.command( 98 | description=T("animeme_desc"), 99 | extras={ 100 | "en": {"name": "animeme", "description": "Get a random animeme"}, 101 | "fr": { 102 | "name": "animeme", 103 | "description": "Obtenez un animeme aléatoire", 104 | }, 105 | }, 106 | ) 107 | @Jeanne.check(check_botbanned_app_command) 108 | @Jeanne.check(check_disabled_app_command) 109 | @Jeanne.check(is_suspended) 110 | async def animeme(self, ctx: Interaction): 111 | await ctx.response.defer() 112 | embed, file = get_animeme_pic(ctx) 113 | await ctx.followup.send(embed=embed, file=file) 114 | 115 | @Jeanne.command( 116 | name=T("combine_name"), 117 | description=T("combine_desc"), 118 | extras={ 119 | "en": { 120 | "name": "combine", 121 | "description": "Combine 2 words to get 2 combined words", 122 | "parameters": [ 123 | { 124 | "name": "first_word", 125 | "description": "Add first word", 126 | "required": True, 127 | }, 128 | { 129 | "name": "second_word", 130 | "description": "Add second word", 131 | "required": True, 132 | }, 133 | ], 134 | }, 135 | "fr": { 136 | "name": "combiner", 137 | "description": "Combinez deux mots en un seul", 138 | "parameters": [ 139 | { 140 | "name": "premier_mot", 141 | "description": "Le premier mot à combiner", 142 | "required": True, 143 | }, 144 | { 145 | "name": "deuxième_mot", 146 | "description": "Le deuxième mot à combiner", 147 | "required": True, 148 | }, 149 | ], 150 | }, 151 | }, 152 | ) 153 | @Jeanne.describe( 154 | first_word=T("first_word_parm_desc"), second_word=T("second_word_parm_desc") 155 | ) 156 | @Jeanne.rename( 157 | first_word=T("first_word_parm_name"), second_word=T("second_word_parm_name") 158 | ) 159 | @Jeanne.check(is_suspended) 160 | @Jeanne.check(check_botbanned_app_command) 161 | @Jeanne.check(check_disabled_app_command) 162 | async def combine(self, ctx: Interaction, first_word: str, second_word: str): 163 | if ctx.locale.value == "fr": 164 | await fr.fun(self.bot).combine(ctx, first_word, second_word) 165 | else: 166 | await en.fun(self.bot).combine(ctx, first_word, second_word) 167 | 168 | @Jeanne.command( 169 | name=T("choose_name"), 170 | description=T("choose_desc"), 171 | extras={ 172 | "en": { 173 | "name": "choose", 174 | "description": "Give me a lot of choices and I will pick one for you", 175 | "parameters": [ 176 | { 177 | "name": "choices", 178 | "description": "Add your choices here. Separate them with ','", 179 | "required": True, 180 | } 181 | ], 182 | }, 183 | "fr": { 184 | "name": "choisir", 185 | "description": "Donnez-moi beaucoup de choix et je choisirai l'un d'entre eux pour vous", 186 | "parameters": [ 187 | { 188 | "name": "choix", 189 | "description": "Ajoutez vos choix ici. Séparez-les par ','", 190 | "required": True, 191 | } 192 | ], 193 | }, 194 | }, 195 | ) 196 | @Jeanne.describe(choices=T("choices_parm_desc")) 197 | @Jeanne.rename(choices=T("choices_parm_name")) 198 | @Jeanne.check(check_botbanned_app_command) 199 | @Jeanne.check(check_disabled_app_command) 200 | @Jeanne.check(is_suspended) 201 | async def choose(self, ctx: Interaction, choices: str): 202 | if ctx.locale.value == "fr": 203 | await fr.fun(self.bot).choose(ctx, choices) 204 | else: 205 | await en.fun(self.bot).choose(ctx, choices) 206 | 207 | @Jeanne.command( 208 | description=T("simprate_desc"), 209 | extras={ 210 | "en": { 211 | "name": "simprate", 212 | "description": "Get a random simp rate for you or someone else", 213 | "parameters": [ 214 | { 215 | "name": "member", 216 | "description": "Which member?", 217 | "required": False, 218 | } 219 | ], 220 | }, 221 | "fr": { 222 | "name": "simprate", 223 | "description": "Obtenez un taux de simp aléatoire pour vous ou quelqu'un d'autre", 224 | "parameters": [ 225 | { 226 | "name": "membre", 227 | "description": "Quel membre?", 228 | "required": False, 229 | } 230 | ], 231 | }, 232 | }, 233 | ) 234 | @Jeanne.describe(member=T("member_parm_desc")) 235 | @Jeanne.rename(member=T("member_parm_name")) 236 | @Jeanne.check(check_botbanned_app_command) 237 | @Jeanne.check(check_disabled_app_command) 238 | @Jeanne.check(is_suspended) 239 | async def simprate(self, ctx: Interaction, member: Optional[Member] = None): 240 | if ctx.locale.value == "fr": 241 | await fr.fun(self.bot).simprate(ctx, member) 242 | else: 243 | await en.fun(self.bot).simprate(ctx, member) 244 | 245 | @Jeanne.command( 246 | description=T("gayrate_desc"), 247 | extras={ 248 | "en": { 249 | "name": "gayrate", 250 | "description": "Rate how gay you or a member is", 251 | "parameters": [ 252 | { 253 | "name": "member", 254 | "description": "Which member?", 255 | "required": False, 256 | } 257 | ], 258 | }, 259 | "fr": { 260 | "name": "gayrate", 261 | "description": "Obtenez un taux de gay aléatoire pour vous ou quelqu'un d'autre", 262 | "parameters": [ 263 | { 264 | "name": "membre", 265 | "description": "Quel membre?", 266 | "required": False, 267 | } 268 | ], 269 | }, 270 | }, 271 | ) 272 | @Jeanne.describe(member=T("member_parm_desc")) 273 | @Jeanne.rename(member=T("member_parm_name")) 274 | @Jeanne.check(check_botbanned_app_command) 275 | @Jeanne.check(check_disabled_app_command) 276 | @Jeanne.check(is_suspended) 277 | async def gayrate(self, ctx: Interaction, member: Optional[Member] = None): 278 | if ctx.locale.value == "fr": 279 | await fr.fun(self.bot).gayrate(ctx, member) 280 | else: 281 | await en.fun(self.bot).gayrate(ctx, member) 282 | 283 | 284 | async def setup(bot: Bot): 285 | await bot.add_cog(fun(bot)) 286 | -------------------------------------------------------------------------------- /languages/en/hentai.py: -------------------------------------------------------------------------------- 1 | from random import randint 2 | from discord import ( 3 | Color, 4 | Embed, 5 | HTTPException, 6 | Interaction, 7 | NotFound, 8 | app_commands as Jeanne, 9 | ) 10 | from discord.ext.commands import Cog, Bot 11 | from functions import ( 12 | Hentai, 13 | shorten_url, 14 | ) 15 | from typing import Optional 16 | from assets.components import ReportContent, ReportContentPlus 17 | 18 | 19 | class nsfw(Cog): 20 | def __init__(self, bot: Bot): 21 | self.bot = bot 22 | 23 | async def hentai( 24 | self, 25 | ctx: Interaction, 26 | ) -> None: 27 | await ctx.response.defer() 28 | hentai, source = await Hentai().hentai() 29 | if hentai.endswith(("mp4", "webm")): 30 | view = ReportContent(ctx, shorten_url(hentai)) 31 | await ctx.followup.send(hentai, view=view) 32 | try: 33 | await ctx.edit_original_response(view=None) 34 | except (NotFound, HTTPException): 35 | return 36 | return 37 | 38 | embed = ( 39 | Embed(color=Color.purple()) 40 | .set_image(url=hentai) 41 | .set_footer( 42 | text="Fetched from {} • Credits must go to the artist".format(source) 43 | ) 44 | ) 45 | view = ReportContent(ctx, shorten_url(hentai)) 46 | await ctx.followup.send(embed=embed, view=view) 47 | await view.wait() 48 | if view.value == None: 49 | try: 50 | await ctx.edit_original_response(view=None) 51 | except (NotFound, HTTPException): 52 | return 53 | 54 | 55 | async def yandere( 56 | self, 57 | ctx: Interaction, 58 | tag: Optional[str] = None, 59 | plus: Optional[bool] = None, 60 | ) -> None: 61 | await ctx.response.defer() 62 | if tag == "02": 63 | await ctx.followup.send( 64 | "Tag has been blacklisted due to it returning extreme content" 65 | ) 66 | return 67 | image = await Hentai(plus).yandere(tag) 68 | if plus: 69 | images = [image[randint(1, len(image)) - 1] for _ in range(4)] 70 | shortened_urls = [shorten_url(img["sample_url"]) for img in images] 71 | view = ReportContentPlus(ctx, *shortened_urls) 72 | color = Color.random() 73 | embeds = [ 74 | Embed(color=color, url="https://yande.re") 75 | .set_image(url=(str(url))) 76 | .set_footer( 77 | text="Fetched from Yande.re • Credits must go to the artist" 78 | ) 79 | for url in shortened_urls 80 | ] 81 | footer_text = "Fetched from Yande.re • Credits must go to the artist" 82 | try: 83 | await ctx.followup.send(embeds=embeds, view=view) 84 | await view.wait() 85 | if view.value == None: 86 | try: 87 | await ctx.edit_original_response(view=None) 88 | except (NotFound, HTTPException): 89 | return 90 | return 91 | except: 92 | footer_text += "\nIf you see an illegal content, please use /botreport and attach the link when reporting" 93 | for embed in embeds: 94 | embed.set_footer(text=footer_text) 95 | await ctx.followup.send(embeds=embeds) 96 | return 97 | color = Color.random() 98 | shortened_url = shorten_url(str(image)) 99 | embed = Embed(color=color, url="https://yande.re") 100 | embed.set_image(url=shortened_url) 101 | footer_text = "Fetched from Yande.re • Credits must go to the artist" 102 | try: 103 | view = ReportContent(ctx, shortened_url) 104 | embed.set_footer(text=footer_text) 105 | await ctx.followup.send(embed=embed, view=view) 106 | await view.wait() 107 | if view.value == None: 108 | try: 109 | await ctx.edit_original_response(view=None) 110 | except (NotFound, HTTPException): 111 | return 112 | return 113 | except: 114 | footer_text += "\nIf you see an illegal content, please use /botreport and attach the link when reporting" 115 | embed.set_footer(text=footer_text) 116 | await ctx.followup.send(embed=embed) 117 | 118 | async def konachan( 119 | self, 120 | ctx: Interaction, 121 | tag: Optional[str] = None, 122 | plus: Optional[bool] = None, 123 | ) -> None: 124 | await ctx.response.defer() 125 | image = await Hentai(plus).konachan(tag) 126 | if plus: 127 | images = [image[randint(1, len(image)) - 1] for _ in range(4)] 128 | try: 129 | shortened_urls = [shorten_url(img["file_url"]) for img in images] 130 | view = ReportContentPlus(ctx, *shortened_urls) 131 | color = Color.random() 132 | embeds = [ 133 | Embed(color=color, url="https://konachan.com") 134 | .set_image(url=str(url)) 135 | .set_footer( 136 | text="Fetched from Konachan • Credits must go to the artist" 137 | ) 138 | for url in shortened_urls 139 | ] 140 | footer_text = "Fetched from Konachan • Credits must go to the artist" 141 | await ctx.followup.send(embeds=embeds, view=view) 142 | await view.wait() 143 | if view.value == None: 144 | try: 145 | await ctx.edit_original_response(view=None) 146 | except (NotFound, HTTPException): 147 | return 148 | return 149 | except: 150 | color = Color.random() 151 | embeds = [ 152 | Embed(color=color, url="https://konachan.com") 153 | .set_image(url=str(url["image_url"])) 154 | .set_footer( 155 | text="Fetched from Konachan • Credits must go to the artist" 156 | ) 157 | for url in images 158 | ] 159 | footer_text += "\nIf you see an illegal content, please use /botreport and attach the link when reporting" 160 | for embed in embeds: 161 | embed.set_footer(text=footer_text) 162 | await ctx.followup.send(embeds=embeds) 163 | return 164 | color = Color.random() 165 | embed = Embed(color=color, url="https://konachan.com") 166 | embed.set_image(url=shorten_url(str(image))) 167 | footer_text = "Fetched from Konachan • Credits must go to the artist" 168 | try: 169 | view = ReportContent(ctx, shorten_url(str(image))) 170 | embed.set_footer(text=footer_text) 171 | await ctx.followup.send(embed=embed, view=view) 172 | await view.wait() 173 | if view.value == None: 174 | try: 175 | await ctx.edit_original_response(view=None) 176 | except (NotFound, HTTPException): 177 | return 178 | return 179 | except: 180 | footer_text += "\nIf you see an illegal content, please use /botreport and attach the link when reporting" 181 | embed.set_footer(text=footer_text) 182 | await ctx.followup.send(embed=embed) 183 | 184 | async def danbooru( 185 | self, 186 | ctx: Interaction, 187 | tag: Optional[str] = None, 188 | plus: Optional[bool] = None, 189 | ) -> None: 190 | await ctx.response.defer() 191 | image = await Hentai(plus).danbooru(tag) 192 | if plus: 193 | images = [img for img in (image[randint(1, len(image)) - 1] for _ in range(4)) if ".zip" not in img["file_url"]] 194 | view = ReportContentPlus(ctx, *[img["file_url"] for img in images]) 195 | vids = [i for i in images if "mp4" in i["file_url"] or "webm" in i["file_url"]] 196 | media = [j["file_url"] for j in vids] 197 | if media: 198 | await ctx.followup.send("\n".join(media), view=view) 199 | await view.wait() 200 | if view.value == None: 201 | try: 202 | await ctx.edit_original_response(view=None) 203 | except (NotFound, HTTPException): 204 | return 205 | return 206 | color = Color.random() 207 | embeds = [ 208 | Embed(color=color, url="https://danbooru.donmai.us/") 209 | .set_image(url=img["file_url"]) 210 | .set_footer( 211 | text="Fetched from Danbooru • Credits must go to the artist" 212 | ) 213 | for img in images 214 | ] 215 | await ctx.followup.send(embeds=embeds, view=view) 216 | return 217 | try: 218 | view = ReportContent(ctx, image) 219 | if str(image).endswith(("mp4", "webm")): 220 | await ctx.followup.send(image, view=view) 221 | return 222 | embed = ( 223 | Embed(color=Color.purple()) 224 | .set_image(url=image) 225 | .set_footer( 226 | text="Fetched from Danbooru • Credits must go to the artist" 227 | ) 228 | ) 229 | await ctx.followup.send(embed=embed, view=view) 230 | await view.wait() 231 | if view.value == None: 232 | try: 233 | await ctx.edit_original_response(view=None) 234 | except (NotFound, HTTPException): 235 | return 236 | return 237 | except: 238 | if str(image).endswith(("mp4", "webm")): 239 | await ctx.followup.send(image) 240 | return 241 | embed = ( 242 | Embed(color=Color.purple()) 243 | .set_image(url=image) 244 | .set_footer( 245 | text="Fetched from Danbooru • Credits must go to the artist\nIf you see an illegal content, please use /botreport and attach the link when reporting" 246 | ) 247 | ) 248 | await ctx.followup.send(embed=embed) 249 | 250 | async def Hentai_error(self, ctx: Interaction, error: Jeanne.AppCommandError, type:str): 251 | if type =="NotFound": 252 | no_tag = Embed( 253 | description="The hentai could not be found", color=Color.red() 254 | ) 255 | await ctx.followup.send(embed=no_tag) 256 | return 257 | if type=="cooldown": 258 | cooldown = Embed( 259 | description=f"WOAH! Calm down! Give me a breather!\nTry again after `{round(error.retry_after, 2)} seconds`", 260 | color=Color.red(), 261 | ) 262 | await ctx.response.send_message(embed=cooldown) 263 | 264 | 265 | -------------------------------------------------------------------------------- /cogs/owner.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime, timedelta 2 | from discord.ext.commands import ( 3 | Cog, 4 | Bot, 5 | group, 6 | is_owner, 7 | guild_only, 8 | Context, 9 | Greedy, 10 | command, 11 | ) 12 | from discord import ( 13 | ActivityType, 14 | Embed, 15 | File, 16 | Game, 17 | Activity, 18 | Object, 19 | HTTPException, 20 | User, 21 | ) 22 | from os import execv 23 | from sys import executable, argv 24 | 25 | from humanfriendly import parse_timespan 26 | from assets.components import ModuleSelect 27 | from functions import BetaTest, DevPunishment, Hentai, Partner 28 | from typing import Literal, Optional 29 | 30 | 31 | class OwnerCog(Cog, name="Owner"): 32 | def __init__(self, bot: Bot): 33 | self.bot = bot 34 | 35 | @staticmethod 36 | def restart_bot(): 37 | execv(executable, [executable] + argv) 38 | 39 | @group( 40 | invoke_without_command=True, description="Main partner command (Developer Only)" 41 | ) 42 | @is_owner() 43 | async def partner(self, ctx: Context): 44 | if DevPunishment(ctx.author).check_botbanned_user: 45 | return 46 | embed = Embed( 47 | title="This is a group command. However, the available commands for this are:", 48 | description="`partner add USER`\n`partner remove USER`", 49 | ) 50 | await ctx.send(embed=embed) 51 | 52 | @partner.command(description="Adds a partner (Developer Only)") 53 | @is_owner() 54 | async def add(self, ctx: Context, user: User): 55 | if DevPunishment(ctx.author).check_botbanned_user: 56 | return 57 | await Partner().add(user) 58 | await ctx.send(f"{user} has been added as a partner") 59 | 60 | @partner.command(description="Removes a partner (Developer Only)") 61 | @is_owner() 62 | async def remove(self, ctx: Context, user: User): 63 | if DevPunishment(ctx.author).check_botbanned_user: 64 | return 65 | await Partner().remove(user) 66 | await ctx.send(f"{user} has been removed as a partner") 67 | 68 | @group( 69 | invoke_without_command=True, description="Main beta command (Developer Only)" 70 | ) 71 | @is_owner() 72 | async def beta(self, ctx: Context): 73 | if DevPunishment(ctx.author).check_botbanned_user: 74 | return 75 | embed = Embed( 76 | title="This is a group command. However, the available commands for this are:", 77 | description="`beta add USER`\n`beta remove USER`", 78 | ) 79 | await ctx.send(embed=embed) 80 | 81 | @beta.command(description="Add a user to the Beta Programme (Developer Only)") 82 | @is_owner() 83 | async def add(self, ctx: Context, *, user: User): 84 | if DevPunishment(ctx.author).check_botbanned_user: 85 | return 86 | await BetaTest(self.bot).add(ctx, user) 87 | 88 | @beta.command(description="Removes a user from the Beta Programme (Developer Only)") 89 | @is_owner() 90 | async def remove(self, ctx: Context, *, user: User): 91 | if DevPunishment(ctx.author).check_botbanned_user: 92 | return 93 | await BetaTest(self.bot).remove(ctx, user) 94 | 95 | @group( 96 | aliases=["act", "presence"], 97 | invoke_without_command=True, 98 | description="Changes my activity (Developer Only)", 99 | ) 100 | @is_owner() 101 | async def activity(self, ctx: Context): 102 | if DevPunishment(ctx.author).check_botbanned_user: 103 | return 104 | embed = Embed( 105 | title="This is a group command. However, the available commands for this are:", 106 | description="`activity play ACTIVITY`\n`activity listen ACTIVITY`\n`activity clear`", 107 | ) 108 | await ctx.send(embed=embed) 109 | 110 | @activity.command( 111 | aliases=["playing"], description="Make me play something (Developer Only)" 112 | ) 113 | @is_owner() 114 | async def play(self, ctx: Context, *, activity: str): 115 | if DevPunishment(ctx.author).check_botbanned_user: 116 | return 117 | await self.bot.change_presence(activity=Game(name=activity)) 118 | await ctx.send(f"I am now playing `{activity}`") 119 | 120 | @activity.command( 121 | aliases=["listening"], 122 | description="Make me listen to something (Developer Only)", 123 | ) 124 | @is_owner() 125 | async def listen(self, ctx: Context, *, activity: str): 126 | if DevPunishment(ctx.author).check_botbanned_user: 127 | return 128 | await self.bot.change_presence( 129 | activity=Activity(type=ActivityType.listening, name=activity) 130 | ) 131 | await ctx.send(f"I'm now listening to `{activity}`") 132 | 133 | @activity.command( 134 | aliases=["remove", "clean", "stop"], 135 | description="Clears my activity (Developer Only)", 136 | ) 137 | @is_owner() 138 | async def clear(self, ctx: Context): 139 | if DevPunishment(ctx.author).check_botbanned_user: 140 | return 141 | await self.bot.change_presence(activity=None) 142 | await ctx.send("I have removed my activity") 143 | 144 | @command(aliases=["fuser"], description="Finds a user (Developer Only)") 145 | @is_owner() 146 | async def finduser(self, ctx: Context, user_id: int): 147 | if DevPunishment(ctx.author).check_botbanned_user: 148 | return 149 | user = await self.bot.fetch_user(user_id) 150 | botr = ":o:" if user.bot else ":x:" 151 | fuser = Embed(title="User Found", color=0xCCFF33) 152 | fuser.add_field(name="Name", value=user, inline=True) 153 | fuser.add_field( 154 | name="Creation Date", 155 | value=f"", 156 | inline=True, 157 | ) 158 | fuser.add_field(name="Mutuals", value=len(user.mutual_guilds), inline=True) 159 | fuser.add_field(name="Bot?", value=botr, inline=True) 160 | fuser.set_image(url=user.display_avatar) 161 | if user.banner is None: 162 | await ctx.send(embed=fuser) 163 | return 164 | userbanner = Embed(title="User Banner", color=0xCCFF33) 165 | userbanner.set_image(url=user.banner) 166 | await ctx.send(embeds=[fuser, userbanner]) 167 | 168 | @command( 169 | aliases=["restart", "refresh"], description="Updates the bot (Developer Only)" 170 | ) 171 | @is_owner() 172 | async def update(self, ctx: Context): 173 | if DevPunishment(ctx.author).check_botbanned_user: 174 | return 175 | await ctx.send("YAY! NEW UPDATE!") 176 | self.restart_bot() 177 | 178 | @command( 179 | aliases=["forbid", "disallow", "bban", "bb"], 180 | description="Ban a user from using Jeanne PERMANENTLY! (Developer Only)", 181 | ) 182 | @is_owner() 183 | async def botban(self, ctx: Context, user_id: int, *, reason: str): 184 | if DevPunishment(ctx.author).check_botbanned_user: 185 | return 186 | if not reason: 187 | await ctx.send("Reason missing for botban", ephemeral=True) 188 | return 189 | user = await self.bot.fetch_user(user_id) 190 | await DevPunishment(user).add_botbanned_user(reason) 191 | await ctx.send("User botbanned", ephemeral=True) 192 | orleans = await self.bot.fetch_guild(740584420645535775) 193 | ha = await self.bot.fetch_guild(925790259160166460) 194 | vhf = await self.bot.fetch_guild(974028573893595146) 195 | for server in [orleans, ha, vhf]: 196 | await server.ban(user, reason=f"DevPunishmentned - {reason}") 197 | 198 | @command( 199 | aliases=["hb", "slice"], 200 | description="Blacklists a hentai link to prevent it to be shown again (Developer Only)", 201 | ) 202 | @is_owner() 203 | async def hentaiblacklist(self, ctx: Context, link: str): 204 | if DevPunishment(ctx.author).check_botbanned_user: 205 | return 206 | await Hentai().add_blacklisted_link(link) 207 | await ctx.send("Link blacklisted") 208 | await ctx.message.delete() 209 | 210 | @command( 211 | aliases=["db", "database"], 212 | description="Sends the bot's database to the developer (Developer Only)", 213 | ) 214 | @is_owner() 215 | async def senddb(self, ctx: Context): 216 | with open("database.db", "rb") as file: 217 | try: 218 | await ctx.author.send(file=File(file)) 219 | except: 220 | content = """ 221 | # ERROR! 222 | ## Failed to send database! 223 | Make sure private messages between **me and you are opened** or check the host if the database exists""" 224 | await ctx.send(content, delete_after=10) 225 | 226 | @command(description="Synchronizes all commands to servers (Developer Only)") 227 | @guild_only() 228 | @is_owner() 229 | async def sync( 230 | self, 231 | ctx: Context, 232 | guilds: Greedy[Object], 233 | spec: Optional[Literal["~", "*", "^"]] = None, 234 | ) -> None: 235 | if DevPunishment(ctx.author).check_botbanned_user: 236 | return 237 | if not guilds: 238 | if spec == "~": 239 | synced = await self.bot.tree.sync(guild=ctx.guild) 240 | elif spec == "*": 241 | self.bot.tree.copy_global_to(guild=ctx.guild) 242 | synced = await self.bot.tree.sync(guild=ctx.guild) 243 | elif spec == "^": 244 | self.bot.tree.clear_commands(guild=ctx.guild) 245 | await self.bot.tree.sync(guild=ctx.guild) 246 | synced = [] 247 | else: 248 | synced = await self.bot.tree.sync() 249 | await ctx.send( 250 | f"Synced {len(synced)} commands {'globally' if spec is None else 'to the current guild.'}" 251 | ) 252 | return 253 | ret = 0 254 | for guild in guilds: 255 | try: 256 | await self.bot.tree.sync(guild=guild) 257 | except HTTPException: 258 | pass 259 | else: 260 | ret += 1 261 | await ctx.send(f"Synced the tree to {ret}/{len(guilds)}.") 262 | 263 | @command(description="Warn users suspected of misusing Jeanne or the commands") 264 | @guild_only() 265 | @is_owner() 266 | async def warn(self, ctx: Context, user: User, *, reason: str): 267 | if DevPunishment(ctx.author).check_botbanned_user: 268 | return 269 | devpunish = DevPunishment(user) 270 | await devpunish.warn(reason) 271 | 272 | @command(description="Suspend a user from a certain module/s") 273 | @guild_only() 274 | @is_owner() 275 | async def suspend(self, ctx: Context, user: User, duration:str, *, reason: str): 276 | if DevPunishment(ctx.author).check_botbanned_user: 277 | return 278 | seconds=parse_timespan(duration) 279 | timestamp = round((datetime.now() + timedelta(seconds=seconds)).timestamp()) 280 | view=ModuleSelect(user, reason, duration=timestamp) 281 | await ctx.send(view=view) 282 | 283 | 284 | 285 | 286 | async def setup(bot: Bot): 287 | await bot.add_cog(OwnerCog(bot)) 288 | -------------------------------------------------------------------------------- /languages/fr/hentai.py: -------------------------------------------------------------------------------- 1 | from random import randint 2 | from discord import ( 3 | Color, 4 | Embed, 5 | HTTPException, 6 | Interaction, 7 | NotFound, 8 | app_commands as Jeanne, 9 | ) 10 | from discord.ext.commands import Cog, Bot 11 | from functions import ( 12 | Hentai, 13 | shorten_url, 14 | ) 15 | from typing import Optional 16 | from assets.components import ReportContent, ReportContentPlus 17 | 18 | 19 | class nsfw(Cog): 20 | def __init__(self, bot: Bot): 21 | self.bot = bot 22 | 23 | async def hentai( 24 | self, 25 | ctx: Interaction, 26 | ) -> None: 27 | await ctx.response.defer() 28 | hentai, source = await Hentai().hentai() 29 | if hentai.endswith(("mp4", "webm")): 30 | view = ReportContent(ctx, shorten_url(hentai)) 31 | await ctx.followup.send(hentai, view=view) 32 | try: 33 | await ctx.edit_original_response(view=None) 34 | except (NotFound, HTTPException): 35 | return 36 | return 37 | 38 | embed = ( 39 | Embed(color=Color.purple()) 40 | .set_image(url=hentai) 41 | .set_footer( 42 | text="Récupéré depuis {} • Les crédits doivent revenir à l'artiste".format(source) 43 | ) 44 | ) 45 | view = ReportContent(ctx, shorten_url(hentai)) 46 | await ctx.followup.send(embed=embed, view=view) 47 | await view.wait() 48 | if view.value is None: 49 | try: 50 | await ctx.edit_original_response(view=None) 51 | except (NotFound, HTTPException): 52 | return 53 | 54 | 55 | 56 | async def yandere( 57 | self, 58 | ctx: Interaction, 59 | tag: Optional[str] = None, 60 | plus: Optional[bool] = None, 61 | ) -> None: 62 | await ctx.response.defer() 63 | if tag == "02": 64 | await ctx.followup.send( 65 | "Ce tag a été mis sur liste noire car il retourne du contenu extrême" 66 | ) 67 | return 68 | image = await Hentai(plus).yandere(tag) 69 | if plus: 70 | images = [image[randint(1, len(image)) - 1] for _ in range(4)] 71 | shortened_urls = [shorten_url(img["sample_url"]) for img in images] 72 | view = ReportContentPlus(ctx, *shortened_urls) 73 | color = Color.random() 74 | embeds = [ 75 | Embed(color=color, url="https://yande.re") 76 | .set_image(url=(str(url))) 77 | .set_footer( 78 | text="Récupéré depuis Yande.re • Les crédits doivent revenir à l'artiste" 79 | ) 80 | for url in shortened_urls 81 | ] 82 | footer_text = "Récupéré depuis Yande.re • Les crédits doivent revenir à l'artiste" 83 | try: 84 | await ctx.followup.send(embeds=embeds, view=view) 85 | await view.wait() 86 | if view.value is None: 87 | try: 88 | await ctx.edit_original_response(view=None) 89 | except (NotFound, HTTPException): 90 | return 91 | return 92 | except Exception: 93 | footer_text += "\nSi vous voyez un contenu illégal, veuillez utiliser /botreport et joindre le lien lors du signalement" 94 | for embed in embeds: 95 | embed.set_footer(text=footer_text) 96 | await ctx.followup.send(embeds=embeds) 97 | return 98 | color = Color.random() 99 | shortened_url = shorten_url(str(image)) 100 | embed = Embed(color=color, url="https://yande.re") 101 | embed.set_image(url=shortened_url) 102 | footer_text = "Récupéré depuis Yande.re • Les crédits doivent revenir à l'artiste" 103 | try: 104 | view = ReportContent(ctx, shortened_url) 105 | embed.set_footer(text=footer_text) 106 | await ctx.followup.send(embed=embed, view=view) 107 | await view.wait() 108 | if view.value is None: 109 | try: 110 | await ctx.edit_original_response(view=None) 111 | except (NotFound, HTTPException): 112 | return 113 | return 114 | except: 115 | footer_text += "\nSi vous voyez un contenu illégal, veuillez utiliser /botreport et joindre le lien lors du signalement" 116 | embed.set_footer(text=footer_text) 117 | await ctx.followup.send(embed=embed) 118 | 119 | async def konachan( 120 | self, 121 | ctx: Interaction, 122 | tag: Optional[str] = None, 123 | plus: Optional[bool] = None, 124 | ) -> None: 125 | await ctx.response.defer() 126 | image = await Hentai(plus).konachan(tag) 127 | if plus: 128 | images = [image[randint(1, len(image)) - 1] for _ in range(4)] 129 | try: 130 | shortened_urls = [shorten_url(img["file_url"]) for img in images] 131 | view = ReportContentPlus(ctx, *shortened_urls) 132 | color = Color.random() 133 | embeds = [ 134 | Embed(color=color, url="https://konachan.com") 135 | .set_image(url=str(url)) 136 | .set_footer( 137 | text="Récupéré depuis Konachan • Les crédits doivent revenir à l'artiste" 138 | ) 139 | for url in shortened_urls 140 | ] 141 | footer_text = "Récupéré depuis Konachan • Les crédits doivent revenir à l'artiste" 142 | await ctx.followup.send(embeds=embeds, view=view) 143 | await view.wait() 144 | if view.value is None: 145 | try: 146 | await ctx.edit_original_response(view=None) 147 | except (NotFound, HTTPException): 148 | return 149 | return 150 | except Exception: 151 | color = Color.random() 152 | embeds = [ 153 | Embed(color=color, url="https://konachan.com") 154 | .set_image(url=str(url["image_url"])) 155 | .set_footer( 156 | text="Récupéré depuis Konachan • Les crédits doivent revenir à l'artiste" 157 | ) 158 | for url in images 159 | ] 160 | footer_text += "\nSi vous voyez un contenu illégal, veuillez utiliser /botreport et joindre le lien lors du signalement" 161 | for embed in embeds: 162 | embed.set_footer(text=footer_text) 163 | await ctx.followup.send(embeds=embeds) 164 | return 165 | color = Color.random() 166 | embed = Embed(color=color, url="https://konachan.com") 167 | embed.set_image(url=shorten_url(str(image))) 168 | footer_text = "Récupéré depuis Konachan • Les crédits doivent revenir à l'artiste" 169 | try: 170 | view = ReportContent(ctx, shorten_url(str(image))) 171 | embed.set_footer(text=footer_text) 172 | await ctx.followup.send(embed=embed, view=view) 173 | await view.wait() 174 | if view.value is None: 175 | try: 176 | await ctx.edit_original_response(view=None) 177 | except (NotFound, HTTPException): 178 | return 179 | return 180 | except: 181 | footer_text += "\nSi vous voyez un contenu illégal, veuillez utiliser /botreport et joindre le lien lors du signalement" 182 | embed.set_footer(text=footer_text) 183 | await ctx.followup.send(embed=embed) 184 | 185 | async def danbooru( 186 | self, 187 | ctx: Interaction, 188 | tag: Optional[str] = None, 189 | plus: Optional[bool] = None, 190 | ) -> None: 191 | await ctx.response.defer() 192 | image = await Hentai(plus).danbooru(tag) 193 | if plus: 194 | images = [img for img in (image[randint(1, len(image)) - 1] for _ in range(4)) if ".zip" not in img["file_url"]] 195 | view = ReportContentPlus(ctx, *[img["file_url"] for img in images]) 196 | vids = [i for i in images if "mp4" in i["file_url"] or "webm" in i["file_url"]] 197 | media = [j["file_url"] for j in vids] 198 | if media: 199 | await ctx.followup.send("\n".join(media), view=view) 200 | await view.wait() 201 | if view.value is None: 202 | try: 203 | await ctx.edit_original_response(view=None) 204 | except (NotFound, HTTPException): 205 | return 206 | return 207 | color = Color.random() 208 | embeds = [ 209 | Embed(color=color, url="https://danbooru.donmai.us/") 210 | .set_image(url=img["file_url"]) 211 | .set_footer( 212 | text="Récupéré depuis Danbooru • Les crédits doivent revenir à l'artiste" 213 | ) 214 | for img in images 215 | ] 216 | await ctx.followup.send(embeds=embeds, view=view) 217 | return 218 | try: 219 | view = ReportContent(ctx, image) 220 | if str(image).endswith(("mp4", "webm")): 221 | await ctx.followup.send(image, view=view) 222 | return 223 | embed = ( 224 | Embed(color=Color.purple()) 225 | .set_image(url=image) 226 | .set_footer( 227 | text="Récupéré depuis Danbooru • Les crédits doivent revenir à l'artiste" 228 | ) 229 | ) 230 | await ctx.followup.send(embed=embed, view=view) 231 | await view.wait() 232 | if view.value is None: 233 | try: 234 | await ctx.edit_original_response(view=None) 235 | except (NotFound, HTTPException): 236 | return 237 | return 238 | except Exception: 239 | if str(image).endswith(("mp4", "webm")): 240 | await ctx.followup.send(image) 241 | return 242 | embed = ( 243 | Embed(color=Color.purple()) 244 | .set_image(url=image) 245 | .set_footer( 246 | text="Récupéré depuis Danbooru • Les crédits doivent revenir à l'artiste\nSi vous voyez un contenu illégal, veuillez utiliser /botreport et joindre le lien lors du signalement" 247 | ) 248 | ) 249 | await ctx.followup.send(embed=embed) 250 | 251 | async def Hentai_error(self, ctx: Interaction, error: Jeanne.AppCommandError, type:str): 252 | if type =="NotFound": 253 | no_tag = Embed( 254 | description="Le hentai n'a pas pu être trouvé", color=Color.red() 255 | ) 256 | await ctx.followup.send(embed=no_tag) 257 | return 258 | if type=="cooldown": 259 | cooldown = Embed( 260 | description=f"WOAH! Calmez-vous ! Donnez-moi une pause !\nRéessayez après `{round(error.retry_after, 2)} secondes`", 261 | color=Color.red(), 262 | ) 263 | await ctx.response.send_message(embed=cooldown) 264 | 265 | 266 | -------------------------------------------------------------------------------- /cogs/info.py: -------------------------------------------------------------------------------- 1 | from functions import ( 2 | check_botbanned_app_command, 3 | check_disabled_app_command, 4 | is_suspended, 5 | ) 6 | from discord.app_commands import locale_str as T 7 | from discord.ext.commands import Cog, Bot 8 | from discord import ( 9 | Interaction, 10 | Member, 11 | app_commands as Jeanne, 12 | ) 13 | from typing import Optional 14 | import languages.en.info as en 15 | import languages.fr.info as fr 16 | 17 | 18 | class InfoCog(Cog, name="InfoSlash"): 19 | def __init__(self, bot: Bot): 20 | self.bot = bot 21 | self.bot_version = "v5.2.0" 22 | self.userinfo_context = Jeanne.ContextMenu( 23 | name="Userinfo", callback=self.userinfo_callback 24 | ) 25 | self.bot.tree.add_command(self.userinfo_context) 26 | 27 | async def cog_unload(self) -> None: 28 | self.bot.tree.remove_command( 29 | self.userinfo_context.name, type=self.userinfo_context.type 30 | ) 31 | 32 | @Jeanne.check(check_botbanned_app_command) 33 | @Jeanne.check(check_disabled_app_command) 34 | @Jeanne.check(is_suspended) 35 | async def userinfo_callback(self, ctx: Interaction, member: Member): 36 | await self.get_userinfo(ctx, member) 37 | 38 | async def get_userinfo(self, ctx: Interaction, member: Member): 39 | if ctx.locale.value == "fr": 40 | await fr.Info(self.bot).get_userinfo(ctx, member) 41 | else: 42 | await en.Info(self.bot).get_userinfo(ctx, member) 43 | 44 | 45 | @Jeanne.command( 46 | description=T("stats_desc"), 47 | extras={ 48 | "en": { 49 | "name": "stats", 50 | "description": "See the bot's status from development to now", 51 | }, 52 | "fr": { 53 | "name": "stats", 54 | "description": "Voir l'état du bot depuis le développement jusqu'à maintenant", 55 | }, 56 | }, 57 | ) 58 | @Jeanne.check(check_botbanned_app_command) 59 | @Jeanne.check(check_disabled_app_command) 60 | @Jeanne.check(is_suspended) 61 | async def stats(self, ctx: Interaction): 62 | if ctx.locale.value == "fr": 63 | await fr.Info(self.bot).stats(ctx, self.bot_version) 64 | else: 65 | await en.Info(self.bot).stats(ctx, self.bot_version) 66 | 67 | 68 | @Jeanne.command( 69 | description=T("userinfo_desc"), 70 | extras={ 71 | "en": { 72 | "name": "userinfo", 73 | "description": "See the information of a member or yourself", 74 | "parameters": [ 75 | { 76 | "name": "member", 77 | "description": "Which member?", 78 | "required": False, 79 | } 80 | ], 81 | }, 82 | "fr": { 83 | "name": "userinfo", 84 | "description": "Voir les informations d'un membre ou de vous-même", 85 | "parameters": [ 86 | {"name": "member", "description": "Quel membre?", "required": False} 87 | ], 88 | }, 89 | }, 90 | ) 91 | @Jeanne.describe(member=T("member_parm_desc")) 92 | @Jeanne.rename(member=T("member_parm_name")) 93 | @Jeanne.check(check_botbanned_app_command) 94 | @Jeanne.check(check_disabled_app_command) 95 | @Jeanne.check(is_suspended) 96 | async def userinfo(self, ctx: Interaction, member: Optional[Member] = None) -> None: 97 | member = ctx.user if member is None else member 98 | await self.get_userinfo(ctx, member) 99 | 100 | @Jeanne.command( 101 | description=T("serverinfo_desc"), 102 | extras={ 103 | "en": { 104 | "name": "serverinfo", 105 | "description": "Get information about this server", 106 | }, 107 | "fr": { 108 | "name": "serverinfo", 109 | "description": "Obtenez des informations sur ce serveur", 110 | }, 111 | }, 112 | ) 113 | @Jeanne.check(check_botbanned_app_command) 114 | @Jeanne.check(check_disabled_app_command) 115 | @Jeanne.check(is_suspended) 116 | async def serverinfo(self, ctx: Interaction): 117 | if ctx.locale.value == "fr": 118 | await fr.Info(self.bot).serverinfo(ctx) 119 | else: 120 | await en.Info(self.bot).serverinfo(ctx) 121 | 122 | 123 | @Jeanne.command( 124 | description=T("ping_desc"), 125 | extras={ 126 | "en": { 127 | "name": "ping", 128 | "description": "Check how fast I respond to a command", 129 | }, 130 | "fr": { 131 | "name": "ping", 132 | "description": "Vérifiez la rapidité de ma réponse à une commande", 133 | }, 134 | }, 135 | ) 136 | @Jeanne.check(is_suspended) 137 | @Jeanne.check(check_botbanned_app_command) 138 | @Jeanne.check(check_disabled_app_command) 139 | @Jeanne.check(is_suspended) 140 | async def ping(self, ctx: Interaction): 141 | if ctx.locale.value == "fr": 142 | await fr.Info(self.bot).ping(ctx) 143 | else: 144 | await en.Info(self.bot).ping(ctx) 145 | 146 | 147 | @Jeanne.command( 148 | description=T("serverbanner_desc"), 149 | extras={ 150 | "en": {"name": "serverbanner", "description": "Get the server banner"}, 151 | "fr": { 152 | "name": "serverbanner", 153 | "description": "Obtenez la bannière du serveur", 154 | }, 155 | }, 156 | ) 157 | @Jeanne.check(check_botbanned_app_command) 158 | @Jeanne.check(check_disabled_app_command) 159 | @Jeanne.check(is_suspended) 160 | async def serverbanner(self, ctx: Interaction): 161 | if ctx.locale.value == "fr": 162 | await fr.Info(self.bot).serverbanner(ctx) 163 | else: 164 | await en.Info(self.bot).serverbanner(ctx) 165 | 166 | 167 | @Jeanne.command( 168 | description=T("avatar_desc"), 169 | extras={ 170 | "en": { 171 | "name": "avatar", 172 | "description": "See your avatar or another member's avatar", 173 | "parameters": [ 174 | { 175 | "name": "member", 176 | "description": "Which member?", 177 | "required": False, 178 | } 179 | ], 180 | }, 181 | "fr": { 182 | "name": "avatar", 183 | "description": "Voir votre avatar ou l'avatar d'un autre membre", 184 | "parameters": [ 185 | {"name": "member", "description": "Quel membre?", "required": False} 186 | ], 187 | }, 188 | }, 189 | ) 190 | @Jeanne.describe(member=T("member_parm_desc")) 191 | @Jeanne.rename(member=T("member_parm_name")) 192 | @Jeanne.check(check_botbanned_app_command) 193 | @Jeanne.check(check_disabled_app_command) 194 | @Jeanne.check(is_suspended) 195 | async def avatar(self, ctx: Interaction, member: Optional[Member] = None) -> None: 196 | if ctx.locale.value == "fr": 197 | await fr.Info(self.bot).avatar(ctx, member) 198 | else: 199 | await en.Info(self.bot).avatar(ctx, member) 200 | 201 | 202 | @Jeanne.command( 203 | description=T("sticker_desc"), 204 | extras={ 205 | "en": { 206 | "name": "sticker", 207 | "description": "Views a sticker", 208 | "parameters": [ 209 | { 210 | "name": "sticker", 211 | "description": "Insert message ID with the sticker or name of the sticker in the server", 212 | "required": True, 213 | } 214 | ], 215 | }, 216 | "fr": { 217 | "name": "sticker", 218 | "description": "Voir un sticker", 219 | "parameters": [ 220 | { 221 | "name": "sticker", 222 | "description": "Insérez l'ID du message avec le sticker ou le nom du sticker dans le serveur", 223 | "required": True, 224 | } 225 | ], 226 | }, 227 | }, 228 | ) 229 | @Jeanne.describe( 230 | sticker=T("sticker_parm_desc"), 231 | ) 232 | @Jeanne.check(check_botbanned_app_command) 233 | @Jeanne.check(check_disabled_app_command) 234 | @Jeanne.check(is_suspended) 235 | async def sticker(self, ctx: Interaction, sticker: str): 236 | if ctx.locale.value == "fr": 237 | await fr.Info(self.bot).sticker(ctx, sticker) 238 | else: 239 | await en.Info(self.bot).sticker(ctx, sticker) 240 | 241 | 242 | @sticker.error 243 | async def sticker_error(self, ctx: Interaction, error: Jeanne.AppCommandError): 244 | if isinstance(error, Jeanne.CommandInvokeError) and isinstance( 245 | error.original, IndexError 246 | ): 247 | if ctx.locale.value == "en-GB" or ctx.locale.value == "en-US": 248 | await en.Info(self.bot).sticker_error(ctx, error, "NoSticker") 249 | elif ctx.locale.value == "fr": 250 | await fr.Info(self.bot).sticker_error(ctx, error, "NoSticker") 251 | if isinstance(error, Jeanne.CommandInvokeError) and isinstance( 252 | error.original, AttributeError 253 | ): 254 | if ctx.locale.value == "en-GB" or ctx.locale.value == "en-US": 255 | await en.Info(self.bot).sticker_error(ctx, error, "StickerNotFound") 256 | elif ctx.locale.value == "fr": 257 | await fr.Info(self.bot).sticker_error(ctx, error, "StickerNotFound") 258 | 259 | @Jeanne.command( 260 | description=T("emoji_desc"), 261 | extras={ 262 | "en": { 263 | "name": "emoji", 264 | "description": "View an emoji", 265 | "parameters": [ 266 | { 267 | "name": "emoji", 268 | "description": "Insert the emoji name or ID", 269 | "required": True, 270 | } 271 | ], 272 | }, 273 | "fr": { 274 | "name": "emoji", 275 | "description": "Voir un emoji", 276 | "parameters": [ 277 | { 278 | "name": "emoji", 279 | "description": "Insérez le nom ou l'ID de l'emoji", 280 | "required": True, 281 | } 282 | ], 283 | }, 284 | }, 285 | ) 286 | @Jeanne.describe(emoji=T("emoji_parm_desc")) 287 | @Jeanne.check(check_botbanned_app_command) 288 | @Jeanne.check(check_disabled_app_command) 289 | @Jeanne.check(is_suspended) 290 | async def emoji(self, ctx: Interaction, emoji: str): 291 | if ctx.locale.value == "en-GB" or ctx.locale.value == "en-US": 292 | await en.Info(self.bot).emoji(ctx, emoji) 293 | elif ctx.locale.value == "fr": 294 | await fr.Info(self.bot).emoji(ctx, emoji) 295 | 296 | @emoji.error 297 | async def emoji_error(self, ctx: Interaction, error: Jeanne.AppCommandError): 298 | if isinstance(error, Jeanne.CommandInvokeError) and isinstance( 299 | error.original, AttributeError 300 | ): 301 | if ctx.locale.value == "en-GB" or ctx.locale.value == "en-US": 302 | await en.Info(self.bot).emoji_error(ctx, error) 303 | elif ctx.locale.value == "fr": 304 | await fr.Info(self.bot).emoji_error(ctx, error) 305 | 306 | 307 | async def setup(bot: Bot): 308 | await bot.add_cog(InfoCog(bot)) 309 | -------------------------------------------------------------------------------- /assets/blackjack_game.py: -------------------------------------------------------------------------------- 1 | from random import randint 2 | from typing import Optional 3 | from discord import ButtonStyle, Color, Embed, Interaction, ui 4 | from discord.ext.commands import Bot 5 | from config import TOPGG 6 | from functions import BetaTest, Currency 7 | from topgg import DBLClient 8 | 9 | values = { 10 | "2": 2, 11 | "3": 3, 12 | "4": 4, 13 | "5": 5, 14 | "6": 6, 15 | "7": 7, 16 | "8": 8, 17 | "9": 9, 18 | "10": 10, 19 | "J": 10, 20 | "Q": 10, 21 | "K": 10, 22 | "A": 11, 23 | } 24 | emoji_map = {"Hearts": "♥️", "Diamonds": "♦️", "Clubs": "♣️", "Spades": "♠️"} 25 | 26 | 27 | def calculate_hand(hand) -> int: 28 | value = sum(values[card[0]] for card in hand) 29 | num_aces = sum(1 for card in hand if card[0] == "A") 30 | while value > 21 and num_aces: 31 | value -= 10 32 | num_aces -= 1 33 | return value 34 | 35 | 36 | def deal_card(deck: list[tuple[str, str]]) -> tuple[str, str]: 37 | return deck.pop(randint(0, len(deck) - 1)) 38 | 39 | 40 | class BlackjackView(ui.View): 41 | 42 | def __init__( 43 | self, 44 | bot: Bot, 45 | ctx: Interaction, 46 | deck, 47 | player_hand, 48 | dealer_hand, 49 | bet: Optional[int] = None, 50 | ): 51 | super().__init__(timeout=60) 52 | self.deck = deck 53 | self.player_hand = player_hand 54 | self.dealer_hand = dealer_hand 55 | self.player_value = calculate_hand(player_hand) 56 | self.dealer_value = calculate_hand(dealer_hand) 57 | self.embed = self.create_embed(ctx) 58 | self.bet = bet 59 | self.value = None 60 | self.bot = bot 61 | self.topggpy = DBLClient(bot=self.bot, token=TOPGG) 62 | 63 | hit_button = ui.Button( 64 | label="Hit" if ctx.locale.value in ["en-GB", "en-US"] else "Tirer", 65 | style=ButtonStyle.primary, 66 | custom_id="blackjack_hit" 67 | ) 68 | stand_button = ui.Button( 69 | label="Stand" if ctx.locale.value in ["en-GB", "en-US"] else "Rester", 70 | style=ButtonStyle.danger, 71 | custom_id="blackjack_stand" 72 | ) 73 | 74 | async def hit_callback(ctx: Interaction): 75 | await self.hit(ctx, hit_button) 76 | 77 | async def stand_callback(ctx: Interaction): 78 | await self.stand(ctx, stand_button) 79 | 80 | hit_button.callback = hit_callback 81 | stand_button.callback = stand_callback 82 | 83 | self.add_item(hit_button) 84 | self.add_item(stand_button) 85 | 86 | 87 | def create_embed(self, ctx: Interaction): 88 | if ctx.locale.value == "en-GB" or ctx.locale.value == "en-US": 89 | embed = Embed(title="Blackjack", color=Color.green()) 90 | embed.add_field( 91 | name="Your Hand", 92 | value=self.hand_value_string(self.player_hand, self.player_value), 93 | inline=False, 94 | ) 95 | embed.add_field( 96 | name="Dealer's Hand", 97 | value=f"**?** (Hidden, {self.dealer_hand[1][0]}{emoji_map[self.dealer_hand[1][1]]})", 98 | inline=False, 99 | ) 100 | return embed 101 | elif ctx.locale.value == "fr": 102 | embed = Embed(title="Blackjack", color=Color.green()) 103 | embed.add_field( 104 | name="Votre main", 105 | value=self.hand_value_string(self.player_hand, self.player_value), 106 | inline=False, 107 | ) 108 | embed.add_field( 109 | name="Main du croupier", 110 | value=f"**?** (Cachée, {self.dealer_hand[1][0]}{emoji_map[self.dealer_hand[1][1]]})", 111 | inline=False, 112 | ) 113 | return embed 114 | 115 | def hand_to_string(self, hand): 116 | return ", ".join([f"{rank}{emoji_map[suit]}" for rank, suit in hand]) 117 | 118 | def hand_value_string(self, hand, value): 119 | return f"**{value}** ({self.hand_to_string(hand)})" 120 | 121 | 122 | async def hit( 123 | self, 124 | ctx: Interaction, 125 | button: ui.Button, 126 | ): 127 | if ctx.locale.value == "en-GB" or ctx.locale.value == "en-US": 128 | self.value = "Hit" 129 | elif ctx.locale.value == "fr": 130 | self.value = "Tirer" 131 | 132 | self.player_hand.append(deal_card(self.deck)) 133 | self.player_value = calculate_hand(self.player_hand) 134 | self.embed = self.create_embed(ctx) 135 | 136 | if self.player_value > 21: 137 | self.embed.color = Color.red() 138 | # Locale-based bust message 139 | if ctx.locale.value == "en-GB" or ctx.locale.value == "en-US": 140 | self.embed.title = "You busted! You lose." 141 | if self.bet: 142 | self.embed.description = f"Unfortunately, I have to take away {self.bet} <:quantumpiece:1161010445205905418>" 143 | elif ctx.locale.value == "fr": 144 | self.embed.title = "Vous avez dépassé 21 ! Vous perdez." 145 | if self.bet: 146 | self.embed.description = f"Malheureusement, je dois retirer {self.bet} <:quantumpiece:1161010445205905418>" 147 | if self.bet: 148 | await Currency(ctx.user).remove_qp(self.bet) 149 | for item in self.children: 150 | item.disabled = True 151 | await ctx.response.edit_message(embed=self.embed, view=self) 152 | return 153 | 154 | await ctx.response.edit_message(embed=self.embed, view=self) 155 | 156 | async def stand( 157 | self, 158 | ctx: Interaction, 159 | button: ui.Button, 160 | ): 161 | if ctx.locale.value == "en-GB" or ctx.locale.value == "en-US": 162 | self.value = "Stand" 163 | elif ctx.locale.value == "fr": 164 | self.value="Rester" 165 | for item in self.children: 166 | item.disabled = True 167 | await ctx.response.edit_message(view=self) 168 | 169 | while self.dealer_value < 17: 170 | self.dealer_hand.append(deal_card(self.deck)) 171 | self.dealer_value = calculate_hand(self.dealer_hand) 172 | 173 | # Locale-based result embed 174 | if ctx.locale.value == "en-GB" or ctx.locale.value == "en-US": 175 | result_embed = Embed(title="Blackjack Result", color=Color.red()) 176 | result_embed.add_field( 177 | name="Your Hand", 178 | value=self.hand_value_string(self.player_hand, self.player_value), 179 | inline=False, 180 | ) 181 | result_embed.add_field( 182 | name="Dealer's Hand", 183 | value=self.hand_value_string(self.dealer_hand, self.dealer_value), 184 | inline=False, 185 | ) 186 | if self.dealer_value > 21 or self.player_value > self.dealer_value: 187 | result_embed.title = "You win!" 188 | if self.bet: 189 | result_embed.description = ( 190 | f"You have won {self.bet} <:quantumpiece:1161010445205905418>" 191 | ) 192 | await Currency(ctx.user).add_qp(self.bet) 193 | if await self.topggpy.get_user_vote(ctx.user.id) == True: 194 | await Currency(ctx.user).add_qp(round((self.bet * 1.25))) 195 | result_embed.add_field( 196 | name="DiscordBotList Bonus", 197 | value=f"{round((self.bet * 1.25),2)} <:quantumpiece:1161010445205905418>", 198 | ) 199 | if await BetaTest(self.bot).check(ctx.user) == True: 200 | await Currency(ctx.user).add_qp(round((self.bet * 1.25))) 201 | result_embed.add_field( 202 | name="Beta User Bonus", 203 | value=f"{round((self.bet * 1.25))} <:quantumpiece:1161010445205905418>", 204 | ) 205 | else: 206 | result_embed.description = ( 207 | "You have won 20 <:quantumpiece:1161010445205905418>" 208 | ) 209 | await Currency(ctx.user).add_qp(20) 210 | if await self.topggpy.get_user_vote(ctx.user.id) == True: 211 | await Currency(ctx.user).add_qp(round((20 * 1.25))) 212 | result_embed.add_field( 213 | name="DiscordBotList Bonus", 214 | value=f"{round((20 * 1.25),2)} <:quantumpiece:1161010445205905418>", 215 | ) 216 | if await BetaTest(self.bot).check(ctx.user) == True: 217 | await Currency(ctx.user).add_qp(round((20 * 1.25))) 218 | result_embed.add_field( 219 | name="Beta User Bonus", 220 | value=f"{round((20 * 1.25))} <:quantumpiece:1161010445205905418>", 221 | ) 222 | result_embed.color = Color.green() 223 | elif self.player_value < self.dealer_value: 224 | result_embed.title = "You lose!" 225 | if self.bet: 226 | result_embed.description = f"Unfortunately, I have to take away {self.bet} <:quantumpiece:1161010445205905418>" 227 | await Currency(ctx.user).remove_qp(self.bet) 228 | else: 229 | result_embed.title = "It's a tie!" 230 | elif ctx.locale.value == "fr": 231 | result_embed = Embed(title="Résultat du Blackjack", color=Color.red()) 232 | result_embed.add_field( 233 | name="Votre main", 234 | value=self.hand_value_string(self.player_hand, self.player_value), 235 | inline=False, 236 | ) 237 | result_embed.add_field( 238 | name="Main du croupier", 239 | value=self.hand_value_string(self.dealer_hand, self.dealer_value), 240 | inline=False, 241 | ) 242 | if self.dealer_value > 21 or self.player_value > self.dealer_value: 243 | result_embed.title = "Vous gagnez !" 244 | if self.bet: 245 | result_embed.description = f"Vous avez gagné {self.bet} <:quantumpiece:1161010445205905418>" 246 | await Currency(ctx.user).add_qp(self.bet) 247 | if await self.topggpy.get_user_vote(ctx.user.id) == True: 248 | await Currency(ctx.user).add_qp(round((self.bet * 1.25))) 249 | result_embed.add_field( 250 | name="Bonus DiscordBotList", 251 | value=f"{round((self.bet * 1.25),2)} <:quantumpiece:1161010445205905418>", 252 | ) 253 | if await BetaTest(self.bot).check(ctx.user) == True: 254 | await Currency(ctx.user).add_qp(round((self.bet * 1.25))) 255 | result_embed.add_field( 256 | name="Bonus utilisateur bêta", 257 | value=f"{round((self.bet * 1.25))} <:quantumpiece:1161010445205905418>", 258 | ) 259 | else: 260 | result_embed.description = ( 261 | "Vous avez gagné 20 <:quantumpiece:1161010445205905418>" 262 | ) 263 | await Currency(ctx.user).add_qp(20) 264 | if await self.topggpy.get_user_vote(ctx.user.id) == True: 265 | await Currency(ctx.user).add_qp(round((20 * 1.25))) 266 | result_embed.add_field( 267 | name="Bonus DiscordBotList", 268 | value=f"{round((20 * 1.25),2)} <:quantumpiece:1161010445205905418>", 269 | ) 270 | if await BetaTest(self.bot).check(ctx.user) == True: 271 | await Currency(ctx.user).add_qp(round((20 * 1.25))) 272 | result_embed.add_field( 273 | name="Bonus utilisateur bêta", 274 | value=f"{round((20 * 1.25))} <:quantumpiece:1161010445205905418>", 275 | ) 276 | result_embed.color = Color.green() 277 | elif self.player_value < self.dealer_value: 278 | result_embed.title = "Vous perdez !" 279 | if self.bet: 280 | result_embed.description = f"Malheureusement, je dois retirer {self.bet} <:quantumpiece:1161010445205905418>" 281 | await Currency(ctx.user).remove_qp(self.bet) 282 | else: 283 | result_embed.title = "Égalité !" 284 | 285 | await ctx.edit_original_response(embed=result_embed) 286 | --------------------------------------------------------------------------------