├── 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 |
--------------------------------------------------------------------------------