rac-bot / rac_bot.py
Bohaska
add rac bot files
cb6590e
import aiohttp
import nextcord
from nextcord import Interaction, SlashOption, Intents, Message, ButtonStyle, Embed, Colour, Permissions, Member, \
AllowedMentions, Role, errors, Attachment, VoiceChannel, File
from nextcord.ext import commands
from nextcord.ui import View, Button, TextInput, Modal
import logging
from datetime import datetime, timezone, timedelta
from json.decoder import JSONDecodeError
import ballsdex_hash
import nation_name_generator as markov
import os
import math
import re
import random
import string
import asyncio
import functools
from motor.motor_asyncio import AsyncIOMotorClient
from dotenv import load_dotenv
# from dontuserepl import lazy_setup
from thefuzz import fuzz
from PIL import Image, ImageOps
from pymongo.errors import DuplicateKeyError
from better_view import AutoDisableView
import json
from ballsdex_hash import check_balldex_image
import soccer
load_dotenv()
RAC_SERVER_ID = 793104002224488481
OWNER_ID = 813757881723519026 # bohaska's id
uri = os.getenv('DB_URL')
bot_colour = Colour.from_rgb(131, 28, 193)
BALLSDEX_ID = 999736048596816014
SPAWN_PING_ROLE = 1221319179836330064
RARE_SPAWN_PING_ROLE = 1222110020674781184
ball_exists = False
MAP_CHANNELS = {
"Demeter": 1259478969082839061,
"Eterrasia": 1259479765589561364,
}
USER_AGENTS = [
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36 Edg/131.0.2903.86",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:134.0) Gecko/20100101 Firefox/134.0",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36 OPR/116.0.0.0",
"Mozilla/5.0 (Windows NT 10.0; WOW64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36 OPR/116.0.0.0",
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36 Vivaldi/7.0.3495.29",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 14_7_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 14_7_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36 Edg/131.0.2903.86",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 14.7; rv:134.0) Gecko/20100101 Firefox/134.0",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 14_7_2) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.4.1 Safari/605.1.15",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 14_7_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36 OPR/116.0.0.0",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 14_7_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36 Vivaldi/7.0.3495.29",
]
db_client = AsyncIOMotorClient(uri)
def is_leap_year(date_time):
return not date_time.year % 400 or not date_time.year % 4 and date_time.year % 100
def get_rac_time(now_time=None):
if not now_time:
now_time = datetime.now(timezone.utc)
# if now_time < datetime(2024, 5, 1, tzinfo=timezone.utc)
if True:
"""One RAC year every month"""
rac_year = (now_time.year - 2023) * 12 + 4061 + now_time.month
rac_year = datetime(rac_year, 1, 1, tzinfo=timezone.utc)
if is_leap_year(now_time):
"""A leap year"""
"""month_to_day index represents the month, the value represents the number of days past."""
month_to_day = [None, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
else:
"""Not a leap year"""
month_to_day = [None, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
if is_leap_year(rac_year):
days_in_year = 366
else:
days_in_year = 365
hours = now_time.hour + (now_time.minute / 60) + (now_time.second / 3600)
rac_days = ((((now_time.day - 1) * 24) + hours) / (month_to_day[now_time.month] * 24)) * days_in_year
delta = timedelta(rac_days)
rac_date = rac_year + delta
return rac_date
else:
"""One RAC year every half a month (they'll regret it)"""
rac_year = (now_time.year - 2024) * 12 * 2 + 4078 + now_time.month * 2 - 10
rac_year = datetime(rac_year, 1, 1, tzinfo=timezone.utc)
if is_leap_year(now_time):
"""A leap year"""
"""month_to_day index represents the month, the value represents the number of days past."""
month_to_day = [0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
else:
"""Not a leap year"""
month_to_day = [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
if is_leap_year(rac_year):
days_in_year = 366
else:
days_in_year = 365
hours = now_time.hour + (now_time.minute / 60) + (now_time.second / 3600)
rac_days = ((((now_time.day - 1) * 24) + hours) / (month_to_day[now_time.month] * 24)) * days_in_year
delta = timedelta(rac_days * 2)
rac_date = rac_year + delta
return rac_date
def get_irl_time(rac_time: datetime):
if True:
irl_year = math.floor((rac_time.year - 6) / 12) + 1685
irl_month = (rac_time.year - 5) % 12
if irl_month == 0:
irl_month = 12
time_passed = rac_time - datetime(rac_time.year, 1, 1, tzinfo=timezone.utc)
if not irl_year % 400 or not irl_year % 4 and irl_year % 100:
"""A leap year"""
"""month_to_day index represents the month, the value represents the number of days past."""
month_to_day = [0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
percentage = time_passed / timedelta(days=366)
else:
"""Not a leap year"""
month_to_day = [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
percentage = time_passed / timedelta(days=365)
delta_days = percentage * month_to_day[irl_month]
month_fraction = timedelta(days=delta_days)
irl_date = datetime(year=irl_year, month=irl_month, day=1, tzinfo=timezone.utc) + month_fraction
return irl_date
else:
irl_year = math.floor((rac_time.year - 6) / 12) + 1685
def format_time(date_time, am_pm, prettyprint):
if am_pm == "AM/PM" and prettyprint == "Pretty print":
return date_time.strftime("%A %B %-d, %-I:%M %p, %Y")
elif am_pm == "AM/PM":
return date_time.strftime("%Y-%m-%d %I:%M %p")
elif prettyprint == "Pretty print":
return date_time.strftime("%A %B %-d, %-H:%M, %Y")
else:
return date_time.strftime("%Y-%m-%d %H:%M")
def format_large_number(number: int):
# If the number is bigger than a trillion
if number >= 1000000000000:
# If the number is bigger than a hundred trillion
number = number / 1000000000000
if number >= 100:
return f"{int(number)} trillion"
# If the number is smaller than a hundred trillion
else:
return f"{number:.2f} trillion"
# If the number is bigger than a billion
elif number >= 1000000000:
# If the number is bigger than a hundred billion
number = number / 1000000000
if number >= 100:
return f"{int(number)} billion"
else:
return f"{number:.2f} billion"
# A million
elif number >= 1000000:
number = number / 1000000
if number >= 100:
return f"{int(number)} million"
else:
return f"{number:.2f} million"
# A thousand
elif number >= 1000:
number = number / 1000
if number >= 100:
return f"{int(number)} thousand"
else:
return f"{number:.2f} thousand"
else:
return str(number)
async def upload_image(url):
session = aiohttp.ClientSession()
params = {"key": os.getenv("IMGBB_API_KEY"), "image": url}
async with session:
url_image = await session.post("https://api.imgbb.com/1/upload", params=params)
url = await url_image.json()
url = url["data"]["url"]
return url
def read_message(msg: Message):
full_msg = msg.content
if msg.embeds:
for embed in msg.embeds:
full_msg += f"\n\n# {embed.title}\n\n{embed.description}"
return full_msg
async def get_message_from_link(message_link):
pattern = r"^https://discord\.com/channels/\d+/\d+/\d+$"
if bool(re.match(pattern, message_link)):
new_link = message_link.split("/")
server = await bot.fetch_guild(int(new_link[4]))
channel = await server.fetch_channel(int(new_link[5]))
link_message = await channel.fetch_message(int(new_link[6]))
else:
raise ValueError("That is not a valid Discord message link.")
return link_message
bot = commands.Bot(intents=Intents(guilds=True, members=True, message_content=True, messages=True))
@bot.slash_command(
description="Gets the time in RAC.",
force_global=True)
async def time(interaction: Interaction):
pass
@bot.slash_command(
description="Detect whether a text was written by an AI.",
force_global=True)
async def detect_ai(interaction: Interaction):
pass
"""@bot.slash_command(
description="Talk to an AI",
force_global=True
)
async def talk_to_ai(interaction: Interaction):
pass"""
@bot.slash_command(
description="Gamenight commands",
force_global=True,
default_member_permissions=Permissions(manage_roles=True)
)
async def game_night(interaction: Interaction):
pass
@bot.slash_command(
description="Country info lookup",
force_global=True)
async def country(interaction: Interaction):
pass
@bot.slash_command(
description="Manage your cell bank",
force_global=True)
async def cells(interaction: Interaction):
pass
@bot.slash_command(
force_global=True)
async def map(interaction: Interaction):
pass
@time.subcommand(
description="Gets the current time in RAC.", )
async def now(interaction: Interaction,
am_pm: str = SlashOption(description="Use AM/PM format or not?",
choices=("AM/PM", "24 hour"),
default="24 hour"),
prettyprint: str = SlashOption(description="Print the raw time or make it look nice?",
choices=("Pretty print", "Raw time"),
default="Raw time")):
await interaction.response.defer()
irl_unix_timestamp = int(datetime.now(tz=timezone.utc).timestamp())
rac_time = format_time(get_rac_time(), am_pm, prettyprint)
time_message = f"{rac_time}\n<t:{irl_unix_timestamp}:f>\n<t:{irl_unix_timestamp}:R>"
await interaction.followup.send(time_message)
@time.subcommand(
description="Gets the time in RAC from an IRL date. Input hour, minute and timezone for more accurate dates.")
async def custom(interaction: Interaction,
year: int = SlashOption(
description="The year of the IRL date you want to convert to RAC time.",
min_value=1,
max_value=9999,
required=True),
month: int = SlashOption(
description="The month of the IRL date you want to convert to RAC time.",
choices={"January": 1, "February": 2, "March": 3, "April": 4, "May": 5, "June": 6,
"July": 7, "August": 8, "September": 9, "October": 10, "November": 11,
"December": 12},
required=True),
day: int = SlashOption(
description="The day of the IRL date you want to convert to RAC time.",
min_value=1,
max_value=31,
required=True),
hour: int = SlashOption(
description="The hour of the IRL date you want to convert to RAC time."
" Must be given in 24 hour format.",
min_value=0,
max_value=23,
default=0),
minute: int = SlashOption(
description="The minute of the IRL date you want to convert to RAC time.",
min_value=0,
max_value=59,
default=0),
user_timezone: int = SlashOption(
description="Timezone offset from GMT/UTC. E.g. GMT +1 -> 1, GMT -2 -> -2.",
min_value=-12,
max_value=12,
default=0),
am_pm: str = SlashOption(description="Use AM/PM format or not?",
choices=("AM/PM", "24 hour"),
default="24 hour"),
prettyprint: str = SlashOption(description="Print the raw time or make it look nice?",
choices=("Pretty print", "Raw time"),
default="Raw time")):
await interaction.response.defer()
if year % 4 == 0:
"""A leap year"""
"""month_to_day index represents the month, the value represents the number of days past."""
month_to_day = [0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
else:
"""Not a leap year"""
month_to_day = [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
if month_to_day[month] < day:
await interaction.followup.send(content="The day must be smaller than the number of days"
" in that month.",
ephemeral=True)
else:
current_time = datetime(year=year, month=month, day=day, hour=hour, minute=minute,
tzinfo=timezone.utc)
delta = timedelta(hours=user_timezone)
current_time -= delta
try:
rac_time = get_rac_time(current_time)
except ValueError:
await interaction.followup.send(content="Sorry, we don't support RAC years smaller than 1 or bigger"
" than 9999 yet.")
return
irl_unix_timestamp = int(current_time.timestamp())
rac_time = format_time(rac_time, am_pm, prettyprint)
time_message = f"{rac_time}\n<t:{irl_unix_timestamp}:R>"
await interaction.followup.send(time_message)
@time.subcommand(
description="Gets the IRL time from a RAC date.")
async def rac_to_irl(interaction: Interaction,
year: int = SlashOption(
description="The year of the RAC date you want to convert to IRL time.",
min_value=1,
max_value=9999,
required=True),
month: int = SlashOption(
description="The month of the RAC date you want to convert to IRL time.",
choices={"January": 1, "February": 2, "March": 3, "April": 4, "May": 5, "June": 6,
"July": 7, "August": 8, "September": 9, "October": 10, "November": 11,
"December": 12},
required=True),
day: int = SlashOption(
description="The day of the RAC date you want to convert to IRL time.",
min_value=1,
max_value=31,
required=True),
hour: int = SlashOption(
description="The hour of the RAC date you want to convert to IRL time."
" Must be given in 24 hour format.",
min_value=0,
max_value=23,
default=0),
minute: int = SlashOption(
description="The minute of the RAC date you want to convert to IRL time.",
min_value=0,
max_value=59,
default=0),
am_pm: str = SlashOption(description="Use AM/PM format or not?",
choices=("AM/PM", "24 hour"),
default="24 hour"),
prettyprint: str = SlashOption(description="Print the raw time or make it look nice?",
choices=("Pretty print", "Raw time"),
default="Raw time")):
await interaction.response.defer()
if year % 4 == 0:
"""A leap year"""
"""month_to_day index represents the month, the value represents the number of days past."""
month_to_day = [0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
else:
"""Not a leap year"""
month_to_day = [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
if month_to_day[month] < day:
await interaction.followup.send(content="The day must be smaller than the number of days"
" in that month.",
ephemeral=True)
else:
rac_time = datetime(year=year, month=month, day=day, hour=hour, minute=minute,
tzinfo=timezone.utc)
try:
irl_time = get_irl_time(rac_time)
except ValueError:
await interaction.followup.send(content="Sorry, we don't support years smaller than 1 or bigger"
" than 9999 yet.",
ephemeral=True)
return
unix_timestamp = int(irl_time.timestamp())
irl_time = format_time(irl_time, am_pm, prettyprint)
time_message = f"{irl_time}\n<t:{unix_timestamp}:f>\n<t:{unix_timestamp}:R>"
await interaction.followup.send(time_message)
@time.subcommand(
description="Gets the time in RAC from a message link. Uses message creation date.")
async def message(interaction: Interaction,
link: str = SlashOption(
description="The link of the message.",
required=True),
am_pm: str = SlashOption(description="Use AM/PM format or not?",
choices=("AM/PM", "24 hour"),
default="24 hour"),
prettyprint: str = SlashOption(description="Print the raw time or make it look nice?",
choices=("Pretty print", "Raw time"),
default="Raw time")):
await interaction.response.defer()
try:
user_message = await get_message_from_link(link)
rac_time = get_rac_time(user_message.created_at)
irl_unix_timestamp = int(user_message.created_at.timestamp())
rac_time = format_time(rac_time, am_pm, prettyprint)
time_message = f"Message link: {user_message.jump_url}\n{rac_time}\n<t:{irl_unix_timestamp}:R>"
await interaction.followup.send(time_message)
except (errors.NotFound, ValueError):
await interaction.followup.send(content="We couldn't find the message. Did you put a message link?",
ephemeral=True)
@bot.message_command(name="Get RAC time")
async def command_rac_time(interaction: Interaction, sent_message: Message):
await interaction.response.defer()
rac_time_a = get_rac_time(sent_message.created_at)
irl_unix_timestamp = int(sent_message.created_at.timestamp())
rac_time_a = rac_time_a.strftime("%A %B %-d, %-H:%M, %Y")
time_message = f"Message link: {sent_message.jump_url}\n{rac_time_a}\n<t:{irl_unix_timestamp}:R>"
await interaction.followup.send(time_message)
def generate_random_string(length=32):
characters = string.ascii_lowercase + string.digits # Lowercase letters + numbers
return ''.join(random.choices(characters, k=length))
def generate_random_uuid():
characters = string.hexdigits.lower() # 0-9 and a-f
u = ''.join(random.choices(characters, k=44)) # Generate 44 hex characters
return f"{u[:12]}-{u[12:24]}-{u[24:30]}-{u[30:36]}-{u[36:]}" # Format like example
async def pangram_ai_csrf_token(session_id):
cookies = {
'sessionid': session_id,
}
headers = {
'User-Agent': random.choice(USER_AGENTS),
'Accept': 'application/json, text/plain, */*',
'Accept-Language': 'en-US,en;q=0.5',
'Origin': 'https://www.pangram.com',
'Sec-GPC': '1',
'Connection': 'keep-alive',
'Referer': 'https://www.pangram.com/',
'Sec-Fetch-Dest': 'empty',
'Sec-Fetch-Mode': 'cors',
'Sec-Fetch-Site': 'same-site',
}
session = aiohttp.ClientSession()
async with session:
resp = await session.get('https://web.pangram.com/api/accounts/get-csrf/', cookies=cookies, headers=headers)
resp = await resp.json(content_type=None)
return resp["csrfToken"]
async def pangram_detection_api(input_text, session_id, csrf_token):
cookies = {
'sessionid': session_id,
}
headers = {
'User-Agent': random.choice(USER_AGENTS),
'Accept': 'application/json, text/plain, */*',
'Accept-Language': 'en-US,en;q=0.5',
# 'Accept-Encoding': 'gzip, deflate, br, zstd',
'Content-Type': 'application/json',
'X-CSRFToken': csrf_token,
'Origin': 'https://www.pangram.com',
'Sec-GPC': '1',
'Connection': 'keep-alive',
'Referer': 'https://www.pangram.com/',
# 'Cookie': 'sessionid=nvghx1am2ae0yfnif8sdoi0ncgoq87p8',
'Sec-Fetch-Dest': 'empty',
'Sec-Fetch-Mode': 'cors',
'Sec-Fetch-Site': 'same-site',
'Priority': 'u=0',
}
json_data = {
'text': input_text,
'source': 'product',
'logging': False,
'distinctId': f'$device:{generate_random_uuid()}',
}
session = aiohttp.ClientSession()
async with session:
resp = await session.post('https://web.pangram.com/api/classify-text-sliding-window/', cookies=cookies, headers=headers, json=json_data)
resp = await resp.json(content_type=None)
return resp
async def pangram_detect_ai_text(input_text):
session_id = generate_random_string()
csrf_token = await pangram_ai_csrf_token(session_id)
ai_detection_resp = await pangram_detection_api(input_text, session_id, csrf_token)
return ai_detection_resp
async def pangram_detect_ai_message(input_text):
ai_detection = await pangram_detect_ai_text(input_text)
llm_model = ""
max_key = max(ai_detection["llm_prediction"], key=ai_detection["llm_prediction"].get)
if ai_detection["llm_prediction"][max_key] > 0.2:
llm_model = f"Most likely AI model: {max_key} ({ai_detection['llm_prediction'][max_key] * 100:.2f}%)\n"
ai_keywords = ""
if ai_detection["ngram"]["keywords"]:
ai_keywords = "Suspicious keywords:"
for keyword in ai_detection["ngram"]["keywords"]:
ai_keywords += f"\n{keyword['keyword']}: **{int(float(keyword['frequency']))}x** more likely in AI text"
ai_keywords = ai_keywords.strip()
followup_text = f"""### {ai_detection["prediction"]}
Estimated likelihood of AI/GPT text: {ai_detection["ai_likelihood"] * 100:.2f}%
Estimated portion of AI/GPT text: {ai_detection["fraction_ai_content"] * 100:.2f}%
{llm_model}
{ai_keywords}"""
return followup_text
async def ai_detection_api(input_text):
headers = {
'Accept': 'application/json, text/plain, */*',
'Accept-Language': 'en-GB,en-US;q=0.9,en;q=0.8,zh-Hans;q=0.7,zh;q=0.6',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
'Content-Type': 'application/json',
'Origin': 'https://www.zerogpt.com',
'Pragma': 'no-cache',
'Referer': 'https://www.zerogpt.com/',
'Sec-Fetch-Dest': 'empty',
'Sec-Fetch-Mode': 'cors',
'Sec-Fetch-Site': 'same-site',
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) '
'Chrome/110.0.0.0 Safari/537.36',
'sec-ch-ua': '"Chromium";v="110", "Not A(Brand";v="24"',
'sec-ch-ua-mobile': '?0',
'sec-ch-ua-platform': '"macOS"',
}
api_url = "https://api.zerogpt.com/api/detect/detectText"
session = aiohttp.ClientSession()
async with session:
ai_result = await session.post(api_url, json={"input_text": input_text}, headers=headers)
ai_result = await ai_result.json(content_type=None)
result_texts = {100: "Your text is human written", 87: "Your text is most likely human written",
0: "Your text is AI/GPT generated", 12: "Most of your text is AI/GPT generated",
62: "Your text is likely human written, may include parts generated by AI/GPT",
75: "Your text is most likely human written, may include parts generated by AI/GPT",
25: "Your text is most likely AI/GPT generated", 37: "Your text is likely generated by AI/GPT",
50: "Your text contains mixed signals, with some parts generated by AI/GPT"}
is_human = result_texts[int(float(ai_result["data"]["isHuman"]))]
result = {
"human_comment": is_human,
"ai_sentences": ai_result["data"]["h"],
"additional_feedback": ai_result["data"]["additional_feedback"],
"ai_percentage": ai_result["data"]["fakePercentage"]
}
return result
@detect_ai.subcommand(description="Checks a piece of text to see whether it was written by an AI.")
async def text(interaction: Interaction,
analyze_text: str = SlashOption(
description="The text you want to analyse",
required=True,
name="text")):
await interaction.response.defer()
try:
followup_text = await pangram_detect_ai_message(analyze_text)
await interaction.followup.send(content=followup_text, allowed_mentions=AllowedMentions.none())
except JSONDecodeError:
await interaction.followup.send(content="The AI text detection server failed to respond.")
@detect_ai.subcommand(description="Checks a Discord message to see whether it was written by an AI.")
async def message(interaction: Interaction,
link: str = SlashOption(
description="The message link you want to analyse",
required=True)):
await interaction.response.defer()
try:
user_message = await get_message_from_link(link)
except Exception:
await interaction.followup.send(content="An error occurred while fetching the Discord message link.")
return
try:
ai_detection = await pangram_detect_ai_message(user_message.clean_content)
followup_text = f"""Message link: {link}
{ai_detection}"""
await interaction.followup.send(content=followup_text, allowed_mentions=AllowedMentions.none())
except JSONDecodeError:
await interaction.followup.send(content="The AI text detection server failed to respond.")
@bot.message_command(name="Analyze message for AI writing")
async def message(interaction: Interaction, message: Message):
await interaction.response.defer()
try:
ai_detection = await pangram_detect_ai_message(message.clean_content)
followup_text = f"""Message link: {message.jump_url}
{ai_detection}"""
await interaction.followup.send(content=followup_text, allowed_mentions=AllowedMentions.none())
except JSONDecodeError:
await interaction.followup.send(content="The AI text detection server failed to respond.")
@bot.event
async def on_message(message: nextcord.Message):
global ball_exists
if message.author.id == bot.user.id:
return
if message.channel.id == 1004795268073541774 and message.author.id == 1004808899431497823 and len(
message.clean_content) >= 400:
try:
ai_detection = await pangram_detect_ai_text(message.clean_content)
except:
return
if ai_detection["ai_likelihood"] >= 0.5:
followup_text = f"""Hey
Why does your post have {ai_detection["ai_likelihood"] * 100:.2f}% AI text according to my AI text detector?
Kind regards,
<@{OWNER_ID}>
"""
await message.channel.send(followup_text, reference=message, allowed_mentions=AllowedMentions.all())
await message.add_reaction("🔴")
elif ai_detection["ai_likelihood"] >= 0.25:
await message.add_reaction("🟡")
else:
await message.add_reaction("🟢")
elif message.author.id == OWNER_ID and "hey rac bot" in message.clean_content:
if message.reference is not None:
await message.delete()
reference_message = await get_message_from_link(message.reference.jump_url)
await message.channel.send(f"Hey {reference_message.author.mention}, nice to meet you!",
reference=reference_message, allowed_mentions=AllowedMentions.all())
else:
await message.channel.send(f"Hey <@{OWNER_ID}>, nice to meet you!", reference=message,
allowed_mentions=AllowedMentions.all())
elif message.author.id == OWNER_ID and "rac:" in message.content[:4]:
await message.delete()
if message.reference is not None:
reference_message = await get_message_from_link(message.reference.jump_url)
await message.channel.send(message.content[4:],
reference=reference_message, allowed_mentions=AllowedMentions.all())
else:
await message.channel.send(message.content[4:], allowed_mentions=AllowedMentions.all())
elif message.author.id == OWNER_ID and "list servers" == message.content:
guild_message = "# Guilds\n"
async for guild in bot.fetch_guilds(limit=100):
guild_message += guild.name
invites = await guild.invites()
if invites:
guild_message += " Invite:" + invites[0].url + "\n"
await message.channel.send(guild_message, reference=message)
elif message.author.id == BALLSDEX_ID and message.guild.id == RAC_SERVER_ID:
global ball_exists
if message.content == "A wild countryball appeared!":
ball_exists = True
ballsdex_image = "ballsdex_" + str(message.id)
await message.attachments[0].save(ballsdex_image)
ball_name = check_balldex_image(ballsdex_image)
os.remove(ballsdex_image)
await asyncio.sleep(120)
with open("rare_countryballs.txt", "r") as rare_countryballs:
rare_countryballs_list = rare_countryballs.read().splitlines()
text_message = f"<@&{SPAWN_PING_ROLE}>\nBallsdex spawned a countryball!"
if ball_exists:
if ball_name:
text_message += f"\nCatch name: ```{ball_name}```"
if ball_name in rare_countryballs_list:
text_message = f"<@&{RARE_SPAWN_PING_ROLE}> " + text_message
await message.channel.send(text_message, reference=message, allowed_mentions=AllowedMentions.all())
elif "You caught" in message.content:
ball_exists = False
initial_ball_message = await get_message_from_link(message.reference.jump_url)
ball_name = re.findall(r'\*\*(.*?)!\*\*', message.content)[0]
image_path = "ballsdex/" + ball_name + ".png"
await initial_ball_message.attachments[0].save(image_path)
if not check_balldex_image(image_path) == ball_name:
image_hash = ballsdex_hash.hash_image(image_path)
with open(ballsdex_hash.JSON_PATH, "r") as json_file:
hash_dict = json.load(json_file)
hash_dict[image_hash] = ball_name
with open(ballsdex_hash.JSON_PATH, 'w') as write_file:
json.dump(hash_dict, write_file)
os.remove(image_path)
elif message.channel.id in MAP_CHANNELS.values():
map_attachment = None
for attachment in message.attachments:
if attachment.filename[-4:] == ".map":
map_attachment = attachment
if map_attachment is not None:
# start parsing the map
map_filename = f"azgaar_map_{message.id}"
map_text = await get_attachment(map_attachment, map_filename)
map_lines = combine_svg(map_text)
state_info = json.loads(map_lines[14])
settings = map_lines[1].split("|")
map_name = settings[20]
thread_name = map_name + " " + format_date(datetime.now()) + " map discussion"
thread = await message.create_thread(name=thread_name)
new_state_info = []
for state in state_info:
try:
if state.get("removed") is not True:
new_state_info.append(
{
'name': state['name'],
'cells': state['cells'],
'map_name': map_name,
}
)
except KeyError:
pass
new_state_info.sort(key=lambda state: state['cells'], reverse=True)
new_state_info = paginate_list(new_state_info)
view = PaginatedView(new_state_info, state_cells_embed, save_button=False)
embed = state_cells_embed(new_state_info, 0)
# finally done
message_text = "New map just dropped!"
if "ping" in message.content:
message_text = "<@&832161270932308010> New map just dropped!"
await thread.send(message_text, view=view, embed=embed,
allowed_mentions=AllowedMentions.all())
if message.interaction:
if message.interaction.name == "bump" and message.author.id == 302050872383242240:
collection = db_client.bump_tracking.balance
existing_bank = await collection.find_one({"_id": str(message.interaction.user.id)})
additional_message = ""
if existing_bank is not None:
balance = existing_bank["balance"]
history_entry = {
"time": int(datetime.now().timestamp()),
"change": 1,
"new_balance": balance + 1,
"user_id": str(bot.user.id),
"reason": f"Bumped RAC. See {message.jump_url}"
}
existing_bank["history"].append(history_entry)
updates = {
"balance": balance + 1,
"history": existing_bank["history"],
}
await collection.update_one({"_id": str(message.interaction.user.id)}, {"$set": updates})
bumps_not_redeemed = existing_bank["balance"] + 1 - (existing_bank["cells_withdrawn"] * 3)
cells_not_redeemed = int(bumps_not_redeemed / 3)
if cells_not_redeemed > 0:
additional_message = f"\n\nYou can redeem {cells_not_redeemed} cells. " \
f"Use the {withdraw.get_mention()} command to redeem your cells"
else:
bump_bank_setup = {
"_id": str(message.interaction.user.id),
"balance": 1,
"cells_withdrawn": 0,
"history": [
{
"time": int(datetime.now().timestamp()),
"change": 1,
"new_balance": 1,
"user_id": str(bot.user.id),
"reason": f"Bumped RAC. See {message.jump_url}"
},
],
}
await collection.insert_one(bump_bank_setup)
existing_bank = await collection.find_one({"_id": str(message.interaction.user.id)})
existing_bank["history"].sort(key=lambda x: x["time"], reverse=True)
history = paginate_list(existing_bank["history"])
balance_view = BankBalanceViewer(0, existing_bank["balance"], history, message.interaction.user,
currency="bumps")
balance_embed = format_balance_embed(balance_view)
msg = await message.channel.send(
content=f"Added your bump!" + additional_message,
embed=balance_embed,
view=balance_view,
reference=message,
)
@bot.slash_command(
description="View bump history.",
force_global=True)
async def bump_bank(interaction: Interaction):
pass
@bump_bank.subcommand(
name="view",
description="View a bump bank"
)
async def view(
interaction: Interaction,
user: Member = SlashOption(
description="The person whose bump bank you want to view.",
required=False,
default=None
),
):
await interaction.response.defer()
if user is None:
user = interaction.user
db = db_client.bump_tracking
collection = db.balance
existing_bank = await collection.find_one({"_id": str(user.id)})
if existing_bank is None:
await interaction.followup.send(f"{user.mention} does not have a bump bank. "
f"Bump this server at least once to get one!")
return
existing_bank["history"].sort(key=lambda x: x["time"], reverse=True)
history = paginate_list(existing_bank["history"])
balance_view = BankBalanceViewer(0, existing_bank["balance"], history, user, currency="bumps")
balance_embed = format_balance_embed(balance_view)
msg = await interaction.followup.send(
embed=balance_embed,
view=balance_view
)
balance_view.init_interaction(interaction, msg.id)
@bump_bank.subcommand(
name="withdraw",
description="Convert your bumps into cells. 3 bumps = 1 cell."
)
async def withdraw(
interaction: Interaction,
):
await interaction.response.defer()
user = interaction.user
bump_db = db_client.bump_tracking.balance
cells_db = db_client.cells.balance
bump_bank = await bump_db.find_one({"_id": str(user.id)})
cell_bank = await cells_db.find_one({"_id": str(user.id)})
if bump_bank is None:
await interaction.followup.send(f"You don't have a bump bank. "
f"Bump this server at least once to get one!")
return
if cell_bank is None:
await interaction.followup.send(f"You don't have a cell bank yet.\n\n"
f"To set up a cell bank, use the {bank_setup.get_mention(guild=None)} command.")
return
bumps_not_redeemed = bump_bank["balance"] - (bump_bank["cells_withdrawn"] * 3)
cells_not_redeemed = int(bumps_not_redeemed / 3)
if cells_not_redeemed == 0:
await interaction.followup.send(f"You're {3-bumps_not_redeemed} bumps away from getting a cell. Bump more!\n"
f"In the past, you've redeemed a total of {bump_bank['cells_withdrawn']} cells!")
return
balance = cell_bank["balance"]
history_entry = {
"time": int(datetime.now().timestamp()),
"change": cells_not_redeemed,
"new_balance": balance + cells_not_redeemed,
"user_id": str(bot.user.id),
"reason": f"Transferred {cells_not_redeemed} cells from bumping."
}
cell_bank["history"].append(history_entry)
cell_bank["balance"] += cells_not_redeemed
cell_updates = {
"balance": balance + cells_not_redeemed,
"history": cell_bank["history"],
}
bump_updates = {
"cells_withdrawn": bump_bank["cells_withdrawn"] + cells_not_redeemed
}
await cells_db.update_one({"_id": str(user.id)}, {"$set": cell_updates})
await bump_db.update_one({"_id": str(user.id)}, {"$set": bump_updates})
cell_bank["history"].sort(key=lambda x: x["time"], reverse=True)
history = paginate_list(cell_bank["history"])
balance_view = BankBalanceViewer(0, cell_bank["balance"], history, user)
balance_embed = format_balance_embed(balance_view)
msg = await interaction.followup.send(
f"Transferred {cells_not_redeemed} cells from bumping! You've earned a total of {bump_bank['cells_withdrawn'] + cells_not_redeemed} from bumping.",
embed=balance_embed,
view=balance_view,
)
balance_view.init_interaction(interaction, msg.id)
def nation_name_embed(nation_name_list: list, index: int):
return Embed(title="Here's your new random nation name!", description=nation_name_list[index],
colour=Colour.random(seed=nation_name_list[index].encode("utf-8"))).set_footer(
text=f"{index + 1}/{len(nation_name_list)}")
class LeftButton(Button):
def __init__(self):
super().__init__(style=ButtonStyle.blurple, label="Previous", row=1)
async def callback(self, interaction: Interaction):
index = self.view.index
if self.view.index != 0:
self.view.index = index - 1
else:
self.view.index = len(self.view.item_list) - 1
embed = self.view.page_function(self.view.item_list, self.view.index)
await interaction.response.edit_message(embed=embed)
class SaveButton(Button):
def __init__(self):
super().__init__(style=ButtonStyle.grey, label="Save", row=2, emoji="💾")
async def callback(self, interaction: Interaction):
await interaction.response.defer()
embed = self.view.page_function(self.view.item_list, self.view.index)
await interaction.followup.send(embed=embed)
class RightButton(Button):
def __init__(self):
super().__init__(style=ButtonStyle.blurple, label="Next", row=1)
async def callback(self, interaction: Interaction):
index = self.view.index
if index + 1 == len(self.view.item_list):
self.view.index = 0
else:
self.view.index = index + 1
embed = self.view.page_function(self.view.item_list, self.view.index)
await interaction.response.edit_message(embed=embed)
class JumpInput(TextInput):
def __init__(self, max_len: int):
super().__init__(label="Jump", max_length=max_len, min_length=1,
placeholder="The page number you want to jump to", required=True)
class JumpModal(Modal):
def __init__(self, item_list: list, index: int, page_function):
super().__init__(title="Jump", timeout=600)
self.item_list = item_list
self.index = index
self.add_item(JumpInput(len(str(index))))
self.page_function = page_function
async def callback(self, interaction: Interaction):
try:
page = int(self.children[0].value) - 1
except ValueError:
embed = self.page_function(self.item_list, self.index)
await interaction.response.edit_message(embed=embed)
return
if page < 0:
page = 0
if page < len(self.item_list):
self.index = page
embed = self.page_function(self.item_list, self.index)
await interaction.response.edit_message(embed=embed)
return
class JumpButton(Button):
def __init__(self, item_list: list):
super().__init__(style=ButtonStyle.grey, label="Jump", row=1, emoji="⏩")
self.item_list = item_list
async def callback(self, interaction: Interaction):
await interaction.response.send_modal(JumpModal(self.item_list, self.view.index, self.view.page_function))
class PaginatedView(AutoDisableView):
def __init__(self, item_list, page_function, save_button=None):
super().__init__()
self.index = 0
self.add_item(LeftButton())
self.add_item(JumpButton(item_list))
if save_button is None or save_button is True:
self.add_item(SaveButton())
self.add_item(RightButton())
self.message = None
self.timeout = 7200
self.item_list = item_list
self.page_function = page_function
@bot.slash_command(
description="Generates random country names",
force_global=True)
async def random_country_names(
interaction: Interaction,
amount: int = SlashOption(
description="Amount of country names you want",
min_value=1,
max_value=100,
default=10)
):
await interaction.response.defer()
nation_name_list = await markov.generate_country_names(amount)
view = PaginatedView(nation_name_list, nation_name_embed)
embed = nation_name_embed(nation_name_list, 0)
sent_message = await interaction.followup.send(embed=embed, view=view)
view.init_interaction(interaction, sent_message.id)
view.message = sent_message
@bot.slash_command(
description="Adds roles to new users",
force_global=True,
default_member_permissions=Permissions(manage_roles=True)
)
async def welcome(
interaction: Interaction,
user: Member = SlashOption(
description="The member you want to add roles to"
),
type: str = SlashOption(
description="What the member signed up as",
choices=["Country", "Company", "Rebellion", "Spectator"],
required=True, ),
):
await interaction.response.defer()
await interaction.guild.fetch_roles()
add_role_ids = {
"Country": [interaction.guild.get_role(793413695567822859)],
"Company": [interaction.guild.get_role(1071947885035388988)],
"Rebellion": [interaction.guild.get_role(1038842003024252990)],
"Spectator": [interaction.guild.get_role(794075195173240863)],
}
remove_role_ids = [
interaction.guild.get_role(793366797306167316), # Diaspora
interaction.guild.get_role(794075195173240863),
]
if user.bot:
await interaction.followup.send(f"You can't add roles to bots.")
elif any(role in add_role_ids[type] for role in user.roles):
await interaction.followup.send(f"That guy already has roles.")
else:
for role in add_role_ids[type]:
await user.add_roles(role)
if not type == "Spectator":
for role in remove_role_ids:
await user.remove_roles(role)
else:
await user.remove_roles(interaction.guild.get_role(793366797306167316))
await interaction.followup.send(f"Successfully added roles to user {user.mention}")
@bot.slash_command(
description="Adds planet info to new users",
force_global=True,
default_member_permissions=Permissions(manage_roles=True)
)
async def planet(
interaction: Interaction,
user: Member = SlashOption(
description="The member you want to add roles to"
),
type: str = SlashOption(
description="Location of the nation. Include colonies but exclude trading posts.",
choices=["Cheaugy", "Nivalis", "Aethenos"],
required=True, ),
):
await interaction.response.defer()
await interaction.guild.fetch_roles()
add_role_ids = {
"Cheaugy": interaction.guild.get_role(1168139509339267072),
"Nivalis": interaction.guild.get_role(1199302330525421639),
"Aethenos": interaction.guild.get_role(1168139620861620224),
}
if user.bot:
await interaction.followup.send(f"You can't add roles to bots.")
elif any(role.id == add_role_ids[type].id for role in user.roles):
# Check whether the user already has a role.
await interaction.followup.send(f"That user already has that role.")
else:
await user.add_roles(add_role_ids[type])
await interaction.followup.send(f"Successfully added roles to user {user.mention}")
@game_night.subcommand(
description="Ban people from game-nights"
)
async def ban(
interaction: Interaction,
user: Member = SlashOption(
description="The bastard who ruined your gamenight"
),
):
await interaction.response.defer()
await interaction.guild.fetch_roles()
ban_role = interaction.guild.get_role(1123429247592181831)
if user.bot:
await interaction.followup.send(f"You can't ban bots.")
elif any(ban_role == role for role in user.roles):
# Check whether the user already is banned.
await interaction.followup.send(f"That guy is already banned.")
else:
await user.add_roles(ban_role)
await interaction.followup.send(f"Successfully banned user {user.mention} from game-nights.")
@game_night.subcommand(
description="Unban people from game-nights",
)
async def unban(
interaction: Interaction,
user: Member = SlashOption(
description="The bastard who ruined your gamenight"
),
):
await interaction.response.defer()
await interaction.guild.fetch_roles()
ban_role = interaction.guild.get_role(1123429247592181831)
if any(ban_role == role for role in user.roles):
# Check whether the user already is banned.
await user.remove_roles(ban_role)
await interaction.followup.send(f"Successfully unbanned user {user.mention} from game-nights.")
else:
await interaction.followup.send(f"That guy is not banned.")
@bot.slash_command(
description="Abusing Chairs commands",
force_global=True)
async def chairs(interaction: Interaction):
pass
@chairs.subcommand(
description="Set a new round of Abusing Chairs."
)
async def new_round(
interaction: Interaction,
members: str = SlashOption(
description="List of people who can play in Abusing Chairs",
required=True)):
await interaction.response.defer()
if not interaction.user.id == OWNER_ID and not interaction.user.id == 804771379471712266:
await interaction.followup.send("You are not the event organiser of Abusing Chairs.")
return
else:
user_ids = re.findall(r'<@(.*?)>', members)
db = db_client.abusing_chairs
collection = db.chairs
await collection.delete_many({})
chair_members = []
for member in user_ids:
chair_members.append({"_id": str(member), "seat": 0})
await collection.insert_many(chair_members)
await interaction.followup.send("Successfully set up a new round of Abusing Chairs.")
@chairs.subcommand(
description="Select a chair in Abusing Chairs"
)
async def select_chair(
interaction: Interaction,
seat: int = SlashOption(
description="The number seat you want to sit in",
required=True,
min_value=1)):
await interaction.response.defer(ephemeral=True)
db = db_client.abusing_chairs
collection = db.chairs
members = []
async for row in collection.find({}):
members.append(row["_id"])
if str(interaction.user.id) in members:
if seat < len(members):
await collection.update_one({"_id": str(interaction.user.id)}, {"$set": {"seat": seat}})
await interaction.followup.send(f"Successfully selected seat {seat}.", ephemeral=True)
else:
await interaction.followup.send(f"That seat doesn't exist. There are only {len(members) - 1} seats.",
ephemeral=True)
else:
await interaction.followup.send(f"You are not in Abusing Chairs.", ephemeral=True)
@chairs.subcommand(
description="See what chair you chose. If you're Lankakabei, it shows what everyone chose."
)
async def view_chairs(
interaction: Interaction,
format: str = SlashOption(
description="Put the text in a codeblock or not? Only applies to Lankakabei.",
choices=["Codeblock", "None"],
default="None",
required=False)
):
await interaction.response.defer(ephemeral=True)
if not interaction.user.id == OWNER_ID and not interaction.user.id == 804771379471712266:
db = db_client.abusing_chairs
collection = db.chairs
selected_chairs = []
async for chair in collection.find({}):
selected_chairs.append(chair)
members = [row["_id"] for row in selected_chairs]
if str(interaction.user.id) in members:
for person in selected_chairs:
if person["_id"] == interaction.user.id:
if person["seat"]:
await interaction.followup.send(f"Your seat: {person['seat']}")
else:
await interaction.followup.send(
f"You haven't selected a seat yet. There are {len(members) - 1} "
f"seats to choose from.")
else:
await interaction.followup.send(f"You are not in Abusing Chairs.", ephemeral=True)
return
else:
db = db_client.abusing_chairs
collection = db.chairs
if format == "Codeblock":
chair_message = "```\n"
else:
chair_message = ''
selected_chairs = []
async for chair in collection.find({}):
selected_chairs.append(chair)
for chair in range(1, len(selected_chairs)):
chair_people = []
for member in selected_chairs:
if member["seat"] == chair:
chair_people.append(member)
if len(chair_people) == 0:
chair_message += f"⬜ Seat {chair}:\n"
elif len(chair_people) == 1:
chair_message += f"🟩 Seat {chair}: <@{chair_people[0]['_id']}>\n"
else:
chair_message += f"🟥 Seat {chair}: <@{chair_people[0]['_id']}>"
for person in chair_people[1:]:
chair_message += f", <@{person['_id']}>"
chair_message += "\n"
if format == "Codeblock":
chair_message += "```\n"
unselected = []
for member in selected_chairs:
if member["seat"] == 0:
unselected.append(member)
if unselected:
chair_message += f"## Remaining\n"
for member in unselected:
chair_message += f"<@{member['_id']}>\n"
await interaction.followup.send(f"# Seats\n{chair_message}", ephemeral=True)
@bot.slash_command(
description="Country Brawl commands",
force_global=True)
async def brawl(interaction: Interaction):
pass
@brawl.subcommand(
description="Start a round of Country Brawl"
)
async def new_round(
interaction: Interaction,
members: str = SlashOption(
description="List of people who will play in Country Brawl",
required=True)):
await interaction.response.defer()
if not interaction.user.id == OWNER_ID:
await interaction.followup.send("You are not the event organiser of Country Brawl.")
return
else:
user_ids = re.findall(r'<@(.*?)>', members)
db = db_client.country_brawl
collection = db.members
await collection.delete_many({})
members = [{"_id": str(member), "target": None} for member in user_ids]
await collection.insert_many(members)
await interaction.followup.send("Successfully set up a new round of Country Brawl.")
@brawl.subcommand(
description="Select a person to attack in Country Brawl"
)
async def attack(
interaction: Interaction,
person: Member = SlashOption(
description="Your unlucky victim"
), ):
await interaction.response.defer(ephemeral=True)
db = db_client.country_brawl
collection = db.members
members = []
async for row in collection.find({}):
members.append(row["_id"])
if str(interaction.user.id) in members:
if str(person.id) in members:
await collection.update_one({"_id": str(interaction.user.id)}, {"$set": {"target": str(person.id)}})
await interaction.followup.send(f"Successfully targeted {str(person.mention)}.", ephemeral=True)
else:
await interaction.followup.send(f"That person is not in Country Brawl", ephemeral=True)
else:
await interaction.followup.send(f"You are not in Country Brawl.", ephemeral=True)
@brawl.subcommand(
description="See who you targeted. If you're Lankakabei, it shows who everyone targeted."
)
async def view_brawl(
interaction: Interaction,
format: str = SlashOption(
description="Put the text in a codeblock or not? Only applies to Lankakabei.",
choices=["Codeblock", "None"],
default="None",
required=False)
):
await interaction.response.defer(ephemeral=True)
if not interaction.user.id == OWNER_ID:
db = db_client.country_brawl
collection = db.members
user_target = await collection.find_one({"_id": str(interaction.user.id)})
if user_target is None:
await interaction.followup.send(f"You are not in Country Brawl.", ephemeral=True)
return
if user_target["target"]:
await interaction.followup.send(f"Your target: <@{user_target['target']}>")
else:
user_ids = [user['_id'] async for user in collection.find({})]
user_string = ""
for user in user_ids:
user_string += f"<@{user}>\n"
await interaction.followup.send(
f"You haven't selected a target yet. Here is a list of all possible targets:\n\n"
+ user_string)
return
else:
db = db_client.country_brawl
collection = db.members
if format == "Codeblock":
chair_message = "```\n"
else:
chair_message = ''
users = [user async for user in collection.find({})]
for victim in users:
attackers = []
for attacker in users:
if attacker['target'] == victim['_id']:
attackers.append(attacker)
if len(attackers) == 1:
chair_message += "💀 "
else:
chair_message += "❤️ "
chair_message += f"<@{victim['_id']}>"
if len(attackers) > 0:
chair_message += f" 🗡️ "
for attacker in attackers:
chair_message += f"<@{attacker['_id']}>, "
chair_message = chair_message[:-2]
chair_message += "\n"
unselected = []
for member in users:
if member["target"] is None:
unselected.append(member)
if unselected:
chair_message += f"## Hasn't submitted\n"
for member in unselected:
chair_message += f"<@{member['_id']}>\n"
if format == "Codeblock":
chair_message += "```\n"
await interaction.followup.send(f"# Results\n{chair_message}", ephemeral=True)
@bot.slash_command(
description="Lists all members who have a particular role",
force_global=True,
default_member_permissions=Permissions(manage_roles=True)
)
async def rolelist(
interaction: Interaction,
role: Role = SlashOption(
description="The role you want to list members for"
),
):
await interaction.response.defer()
await interaction.guild.fetch_roles()
msg = ""
for member in role.members:
if len(msg) + len(member.mention) < 4095:
msg += member.mention
msg += "\n"
else:
embed = Embed(title=f"Members with {role.name} role", description=msg,
colour=role.colour)
sent_message = await interaction.followup.send(embed=embed, allowed_mentions=AllowedMentions.none())
msg = f"{member.mention}\n"
embed = Embed(title=f"Members with {role.name} role", description=msg,
colour=role.colour)
await interaction.followup.send(embed=embed, allowed_mentions=AllowedMentions.none())
@bot.slash_command(
description="Times out a member. Allows you to set the time more precisely",
force_global=True,
default_member_permissions=Permissions(moderate_members=True)
)
async def timeout(
interaction: Interaction,
bad_user: Member = SlashOption(
description="The person who will get trolled by mods."
),
day: int = SlashOption(
description="How many days you want to mute them for.",
min_value=0,
max_value=28,
default=0),
hour: int = SlashOption(
description="How many hours you want to mute them for.",
min_value=0,
max_value=23,
default=0),
minute: int = SlashOption(
description="How many minutes you want to mute them for.",
min_value=0,
max_value=59,
default=0),
second: int = SlashOption(
description="I'm not sure why you would need to be this specific tbh.",
min_value=0,
max_value=59,
default=0),
):
await interaction.response.defer()
if day or hour or minute or second:
if interaction.user.top_role > bad_user.top_role:
if timedelta(days=day, hours=hour, minutes=minute, seconds=second) > timedelta(days=28):
await interaction.followup.send("Timeout length too long.")
elif not interaction.guild.me.top_role > bad_user.top_role:
await interaction.followup.send(
"RAC Bot doesn't have the necessary permissions to timeout said member.")
else:
await bad_user.timeout(
timedelta(days=day, hours=hour, minutes=minute, seconds=second),
reason=f"Timed out by user {str(interaction.user)}"
)
await interaction.followup.send(f"Successfully timed out {bad_user.mention}")
else:
await interaction.followup.send("You can't timeout higher-ranking members.")
else:
await interaction.followup.send("You need to input a timeout duration.")
def compare_string_score(test_string, match_string):
return fuzz.partial_token_sort_ratio(test_string.lower(), match_string.lower())
async def country_search(interaction: Interaction, common_name: str):
db = db_client.rac_wiki
collection = db.countries
countries = []
async for entry in collection.find({}, ["_id", "alternate_names", "official_name", "owner"]):
countries.append(entry)
if not common_name:
# send the full autocomplete list
autocomplete_countries = [row["_id"] for row in countries]
await interaction.response.send_autocomplete(autocomplete_countries[:25])
return
# send a list of the nearest matches from the list of countries
autocomplete_countries = []
for row in countries:
country_score = compare_string_score(common_name, row["_id"])
if row["alternate_names"]:
for alt_name in row["alternate_names"]:
if compare_string_score(common_name, alt_name) > country_score:
country_score = compare_string_score(common_name, alt_name)
if row["official_name"]:
if compare_string_score(common_name, row["official_name"]) > country_score:
country_score = compare_string_score(common_name, row["official_name"])
if row["owner"]:
if compare_string_score(common_name, row["owner"]) == 100:
country_score = 100
if country_score > 70:
autocomplete_countries.append([row["_id"], country_score])
autocomplete_countries.sort(key=lambda country_scores: country_scores[1], reverse=True)
autocomplete_countries = [row[0] for row in autocomplete_countries]
await interaction.response.send_autocomplete(autocomplete_countries[:25])
async def fetch_country_embed(common_name, db_client):
db = db_client.rac_wiki
collection = db.countries
country_dict = await collection.find_one({"_id": common_name})
if country_dict is None:
return None
fields = [
("Common name", country_dict["_id"]),
("Official name", country_dict["official_name"]),
("Alternate names", country_dict["alternate_names"]),
("Emoji", country_dict["emoji"]),
("Capital", country_dict["capital"]),
("Owner", country_dict["owner"]),
("Leader", country_dict["leader"]),
("Languages", country_dict["language"]),
("Unions", country_dict["unions"]),
("GDP", country_dict["gdp"]),
("Population", country_dict["population"]),
("Founding date", country_dict["founding_date"]),
("End date", country_dict["end_date"])
]
if country_dict["colour"]:
colour = Colour(int(country_dict["colour"][1:], 16))
else:
colour = None
country_embed = Embed(title=common_name, colour=colour, description=country_dict["description"])
if country_dict["flag_url"] and not country_dict["flag_url"] == "None":
country_embed.set_image(country_dict["flag_url"])
for field in fields:
if field[1] or field[1] == 0:
if field[0] == "GDP":
gdp = format_large_number(field[1])
country_embed = country_embed.add_field(name=field[0], value=f"${gdp} cellen")
elif field[0] == "Population":
population = format_large_number(field[1])
country_embed = country_embed.add_field(name=field[0], value=population)
elif field[0] == "Alternate names" and not field[1] == "None":
alternate_name_text = ""
for name in field[1]:
alternate_name_text += ", " + name
country_embed = country_embed.add_field(name=field[0], value=alternate_name_text[2:])
else:
country_embed = country_embed.add_field(name=field[0], value=field[1])
if country_dict["map_url"] and not country_dict["map_url"] == "None":
country_embed = [country_embed, Embed(title="Map", colour=colour).set_image(country_dict["map_url"])]
return country_embed
@country.subcommand(
description="Add a country into the database.",
)
async def add(
interaction: Interaction,
common_name: str = SlashOption(
description="The common name for the nation.",
required=True
),
official_name: str = SlashOption(
description="The official name for the nation.",
default=None,
required=False
),
alternate_names: str = SlashOption(
description="Other names used for this nation. Separate names with commas.",
default=None,
required=False
),
emoji: str = SlashOption(
description="The flag emoji of the nation.",
default=None,
required=False
),
capital: str = SlashOption(
description="The capital of the nation.",
default=None,
required=False
),
owner: str = SlashOption(
description="The owner(s) of the nation.",
default=None,
required=False
),
leader: str = SlashOption(
description="The leader of the nation IRP.",
default=None,
required=False
),
language: str = SlashOption(
description="The official language(s) of the nation.",
default=None,
required=False
),
unions: str = SlashOption(
description="The union(s) the nation is in.",
default=None,
required=False
),
gdp: int = SlashOption(
description="The GDP of the nation.",
default=None,
required=False
),
population: int = SlashOption(
description="The population of the nation.",
default=None,
required=False
),
founding_date: str = SlashOption(
description="The RAC date the nation was founded on.",
default=None,
required=False
),
end_date: str = SlashOption(
description="The RAC date the nation ended on. If the nation still exists, leave blank.",
default=None,
required=False
),
description: str = SlashOption(
description="Describe what the nation is like to a newbie. (Multiple) message links work!",
default=None,
required=False
),
colour: str = SlashOption(
description="The hex code of the nation's colour. Used for colouring embeds.",
default=None,
required=False
),
flag: Attachment = SlashOption(
description="The flag of the nation.",
default=None,
required=False
),
map_image: Attachment = SlashOption(
description="A map of the nation.",
default=None,
required=False
),
):
await interaction.response.defer()
new_description = ""
if description:
for message_check in description.split():
try:
link_message = await get_message_from_link(message_check)
new_description += link_message.content + "\n\n"
except ValueError:
new_description += message_check
new_description += " "
db = db_client.rac_wiki
collection = db.countries
existing_country = await collection.count_documents({"_id": common_name})
if existing_country == 1:
await interaction.followup.send(f"There already exists a country with the same common name {common_name}.\n\n"
f"To edit a country, use the ``/country edit`` command.")
return
if flag:
flag = await upload_image(str(flag))
if map_image:
map_image = await upload_image(str(map_image))
if colour:
try:
Colour(int(colour, 16))
colour = "#" + colour
except ValueError:
try:
Colour(int(colour[1:], 16))
except ValueError:
colour = None
await interaction.followup.send(f"Invalid value for colour.")
if alternate_names:
alternate_names = alternate_names.split(",")
new_alternate_names = []
for name in alternate_names:
new_alternate_names.append(name.strip())
alternate_names = new_alternate_names
await collection.insert_one(
{
"_id": common_name,
"official_name": official_name,
"alternate_names": alternate_names,
"emoji": emoji,
"capital": capital,
"owner": owner,
"leader": leader,
"language": language,
"unions": unions,
"gdp": gdp,
"population": population,
"founding_date": founding_date,
"end_date": end_date,
"description": new_description,
"colour": colour,
"flag_url": flag,
"map_url": map_image,
}
)
country_embed = await fetch_country_embed(common_name, db_client)
if country_embed is None:
await interaction.followup.send(f"Failed to add country to database.")
return
if type(country_embed) == list:
await interaction.followup.send(f"Successfully added country {common_name}", embeds=country_embed,
allowed_mentions=AllowedMentions.none())
else:
await interaction.followup.send(f"Successfully added country {common_name}", embed=country_embed,
allowed_mentions=AllowedMentions.none())
@country.subcommand(
description="Edit a country's entry in the database. Use the emoji ❌ to delete an attribute.",
)
async def edit(
interaction: Interaction,
common_name: str = SlashOption(
description="The common name of the nation you want to edit.",
required=True,
autocomplete_callback=country_search
),
new_common_name: str = SlashOption(
description="The new common name of the nation you want to edit, if needed. Cannot be deleted.",
default=None,
required=False
),
official_name: str = SlashOption(
description="The official name for the nation.",
default=None,
required=False
),
alternate_names: str = SlashOption(
description="Other names used for this nation. Separate names with commas.",
default=None,
required=False
),
emoji: str = SlashOption(
description="The flag emoji of the nation.",
default=None,
required=False
),
capital: str = SlashOption(
description="The capital of the nation.",
default=None,
required=False
),
owner: str = SlashOption(
description="The owner(s) of the nation.",
default=None,
required=False
),
leader: str = SlashOption(
description="The leader of the nation IRP.",
default=None,
required=False
),
language: str = SlashOption(
description="The official language(s) of the nation.",
default=None,
required=False
),
unions: str = SlashOption(
description="The union(s) the nation is in.",
default=None,
required=False
),
gdp: int = SlashOption(
description="The GDP of the nation.",
default=None,
required=False
),
population: int = SlashOption(
description="The population of the nation.",
default=None,
required=False
),
founding_date: str = SlashOption(
description="The RAC date the nation was founded on.",
default=None,
required=False
),
end_date: str = SlashOption(
description="The RAC date the nation ended on. If the nation still exists, leave blank.",
default=None,
required=False
),
description: str = SlashOption(
description="Describe what the nation is like to a newbie. (Multiple) message links work!",
default=None,
required=False
),
colour: str = SlashOption(
description="The hex code of the nation's colour. Used for colouring embeds.",
default=None,
required=False
),
flag: Attachment = SlashOption(
description="The flag of the nation.",
default=None,
required=False
),
map_image: Attachment = SlashOption(
description="A map of the nation.",
default=None,
required=False
),
):
await interaction.response.defer()
new_description = ""
if description:
for message_check in description.split():
try:
link_message = await get_message_from_link(message_check)
new_description += link_message.content + "\n\n"
except ValueError:
new_description += message_check
new_description += " "
description = new_description
db = db_client.rac_wiki
collection = db.countries
existing_country = await collection.count_documents({"_id": common_name})
if existing_country == 0:
await interaction.followup.send(f"Could not find country {common_name}. "
f"Use ``/country add`` to add this country into the database.")
return
if alternate_names and not alternate_names == "❌":
alternate_names = alternate_names.split(",")
new_alternate_names = []
for name in alternate_names:
new_alternate_names.append(name.strip())
if colour:
try:
Colour(int(colour, 16))
colour = "#" + colour
except ValueError:
try:
Colour(int(colour[1:], 16))
except ValueError:
colour = None
await interaction.followup.send(f"Invalid value for colour.")
if not flag:
flag = ""
if not map_image:
map_image = ""
if flag:
flag = await upload_image(str(flag))
if map_image:
map_image = await upload_image(str(map_image))
fields = (
(official_name, "official_name"),
(alternate_names, "alternate_names"),
(emoji, "emoji"),
(capital, "capital"),
(owner, "owner"),
(leader, "leader"),
(language, "language"),
(unions, "unions"),
(gdp, "gdp"),
(population, "population"),
(founding_date, "founding_date"),
(end_date, "end_date"),
(description, "description"),
(colour, "colour"),
(flag, "flag_url"),
(map_image, "map_url"),
)
updates = {}
for field in fields:
if field[0] or field[0] == 0 and not field[0] == "None":
if field[0] == "❌" and not field[1] == "_id":
updates[field[1]] = None
else:
updates[field[1]] = field[0]
if new_common_name:
original_country = await collection.find_one({"_id": common_name})
original_country["_id"] = new_common_name
await collection.insert_one(original_country)
await collection.update_one({"_id": new_common_name}, {"$set": updates})
await collection.delete_one({"_id": common_name})
country_embed = await fetch_country_embed(new_common_name, db_client)
else:
await collection.update_one({"_id": common_name}, {"$set": updates})
country_embed = await fetch_country_embed(common_name, db_client)
if country_embed is None:
await interaction.followup.send(f"Could not find country {common_name}. "
f"Use ``/country add`` to add this country into the database.")
return
if type(country_embed) == list:
await interaction.followup.send(f"Successfully updated info for country {common_name}", embeds=country_embed,
allowed_mentions=AllowedMentions.none())
else:
await interaction.followup.send(f"Successfully updated info for country {common_name}", embed=country_embed,
allowed_mentions=AllowedMentions.none())
@country.subcommand(
description="See a country's entry in the database.",
)
async def view(
interaction: Interaction,
common_name: str = SlashOption(
description="The common name of the nation you want to view.",
required=True,
autocomplete_callback=country_search
),
):
await interaction.response.defer()
country_embed = await fetch_country_embed(common_name, db_client)
if country_embed is None:
await interaction.followup.send(f"Could not find country {common_name}. "
f"Use ``/country add`` to add this country into the database.")
return
if type(country_embed) == list:
await interaction.followup.send(embeds=country_embed, allowed_mentions=AllowedMentions.none())
else:
await interaction.followup.send(embed=country_embed, allowed_mentions=AllowedMentions.none())
def format_bank_history(history: list[dict], currency: str = None):
if currency is None:
currency = "cells"
history_text = ""
for entry in history:
if entry['change'] > 0:
history_text += f"## +{entry['change']} {currency}\n" \
f"Change: {entry['new_balance'] - entry['change']} {currency} " \
f"-> {entry['new_balance']} {currency}\n" \
f"User: <@{entry['user_id']}>\n" \
f"Time: <t:{entry['time']}:R>\n" \
f"Reason: {entry['reason']}\n"
else:
history_text += f"## {entry['change']} cells\n" \
f"Change: {entry['new_balance'] - entry['change']} {currency} " \
f"-> {entry['new_balance']} {currency}\n" \
f"User: <@{entry['user_id']}>\n" \
f"Time: <t:{entry['time']}:R>\n" \
f"Reason: {entry['reason']}\n"
return history_text
def format_history_embed(view: View):
currency = "cells" if view.currency is None else view.currency
history_text = format_bank_history(view.history[view.index], currency)
history_embed = Embed(
title=f"{currency[:-1].capitalize()} Bank History",
description=history_text,
colour=view.bank_owner.colour
).set_footer(text=f"{view.index + 1}/{len(view.history)}")
user_avatar = None if view.bank_owner.avatar is None else view.bank_owner.avatar.url
if view.bank_owner.discriminator == "0":
history_embed = history_embed.set_author(name=view.bank_owner.name, icon_url=user_avatar)
else:
history_embed = history_embed.set_author(name=f"{view.bank_owner.name}#{view.bank_owner.discriminator}",
icon_url=user_avatar)
return history_embed
def format_balance_embed(view: View):
currency = "cells" if view.currency is None else view.currency
balance_embed = Embed(
title=f"{currency[:-1].capitalize()} Bank Balance",
description=f"{view.balance} {currency}",
colour=view.bank_owner.colour
)
user_avatar = None if view.bank_owner.avatar is None else view.bank_owner.avatar.url
if view.bank_owner.discriminator == "0":
balance_embed = balance_embed.set_author(name=view.bank_owner.name, icon_url=user_avatar)
else:
balance_embed = balance_embed.set_author(name=f"{view.bank_owner.name}#{view.bank_owner.discriminator}",
icon_url=user_avatar)
return balance_embed
class HistoryLeftButton(Button):
def __init__(self):
super().__init__(style=ButtonStyle.blurple, label="Previous", row=1)
async def callback(self, interaction: Interaction):
if self.view.index != 0:
self.view.index -= 1
else:
self.view.index = len(self.view.history) - 1
embed = format_history_embed(self.view)
await interaction.response.edit_message(embed=embed)
class HistoryRightButton(Button):
def __init__(self):
super().__init__(style=ButtonStyle.blurple, label="Next", row=1)
async def callback(self, interaction: Interaction):
if len(self.view.history) - 1 == self.view.index:
self.view.index = 0
else:
self.view.index = self.view.index + 1
embed = format_history_embed(self.view)
await interaction.response.edit_message(embed=embed)
class HistoryToBalanceButton(Button):
def __init__(self):
super().__init__(style=ButtonStyle.grey, label="View Balance", row=2, emoji="💵")
async def callback(self, interaction: Interaction):
balance_view = BankBalanceViewer(self.view.index, self.view.balance, self.view.history, self.view.bank_owner)
balance_embed = format_balance_embed(self.view)
await interaction.response.edit_message(embed=balance_embed, view=balance_view)
balance_view.init_interaction(interaction)
class BalanceToHistoryButton(Button):
def __init__(self):
super().__init__(style=ButtonStyle.blurple, label="View History", row=1, emoji="📜")
async def callback(self, interaction: Interaction):
history_view = BankHistoryViewer(self.view.index, self.view.balance, self.view.history, self.view.bank_owner,
self.view.currency)
history_embed = format_history_embed(self.view)
await interaction.response.edit_message(embed=history_embed, view=history_view)
history_view.init_interaction(interaction)
class BankHistoryViewer(AutoDisableView):
def __init__(self, index, balance, history, bank_owner: Member, currency: str = None):
super().__init__()
self.index = index
self.add_item(HistoryLeftButton())
self.add_item(HistoryRightButton())
self.add_item(HistoryToBalanceButton())
self.message = None
self.timeout = 3600
self.balance = balance
self.history = history
self.bank_owner = bank_owner
self.currency = currency
class BankBalanceViewer(AutoDisableView):
def __init__(self, index, balance, history, bank_owner: Member, currency: str = None):
super().__init__()
self.index = index
self.add_item(BalanceToHistoryButton())
self.message = None
self.timeout = 3600
self.balance = balance
self.history = history
self.bank_owner = bank_owner
self.currency = currency
def paginate_list(array: list, entries_per_page: int = None):
if entries_per_page is None:
entries_per_page = 10
ten_array = []
newer_array = []
for entry in array:
ten_array.append(entry)
if len(ten_array) == entries_per_page:
newer_array.append(ten_array)
ten_array = []
if ten_array:
newer_array.append(ten_array)
return newer_array
@cells.subcommand(
description="Set up your cell bank.",
)
async def bank_setup(
interaction: Interaction,
cells: int = SlashOption(
description="The number of cells you have.",
required=True,
min_value=0,
),
):
await interaction.response.defer()
db = db_client.cells
collection = db.balance
existing_bank = await collection.count_documents({"_id": str(interaction.user.id)})
if existing_bank == 1:
await interaction.followup.send(f"You already have set up your cell bank.\n\n"
f"To edit your cell bank, "
f"use the {add.get_mention(guild=None)} or the {remove.get_mention(guild=None)}"
f" command.\n"
f"To view your cell bank, use the {view.get_mention(guild=None)} command.")
return
cell_bank_setup = {
"_id": str(interaction.user.id),
"balance": cells,
"history": [
{
"time": int(datetime.now().timestamp()),
"change": cells,
"new_balance": cells,
"user_id": str(interaction.user.id),
"reason": f"Set up cell bank with initial balance of {cells} cells"
},
],
}
await collection.insert_one(cell_bank_setup)
bank = await collection.find_one({"_id": str(interaction.user.id)})
bank["history"].sort(key=lambda x: x["time"], reverse=True)
history = paginate_list(bank["history"])
balance_view = BankBalanceViewer(0, bank["balance"], history, interaction.user)
balance_embed = format_balance_embed(balance_view)
msg = await interaction.followup.send(
content=f"Successfully set up cell bank for {interaction.user.mention}",
embed=balance_embed,
view=balance_view
)
balance_view.init_interaction(interaction, msg.id)
@cells.subcommand(
description="View a cell bank.",
)
async def view(
interaction: Interaction,
user: Member = SlashOption(
description="The person whose cell bank you want to view.",
required=False,
default=None
),
):
await interaction.response.defer()
if user is None:
user = interaction.user
db = db_client.cells
collection = db.balance
existing_bank = await collection.find_one({"_id": str(user.id)})
if existing_bank is None:
await interaction.followup.send(f"{user.mention} has not set up their cell bank yet.\n\n"
f"To set up a cell bank, use the {bank_setup.get_mention(guild=None)} command.")
return
existing_bank["history"].sort(key=lambda x: x["time"], reverse=True)
history = paginate_list(existing_bank["history"])
balance_view = BankBalanceViewer(0, existing_bank["balance"], history, user)
balance_embed = format_balance_embed(balance_view)
msg = await interaction.followup.send(
embed=balance_embed,
view=balance_view
)
balance_view.init_interaction(interaction, msg.id)
@cells.subcommand(
description="Add cells to a cell bank.",
)
async def add(
interaction: Interaction,
user: Member = SlashOption(
description="The person you want to add cells to.",
required=True,
),
cells: int = SlashOption(
description="The number of cells you want to add.",
required=True,
min_value=1,
),
reason: str = SlashOption(
description="Why you added these cells.",
required=True,
),
):
await interaction.response.defer()
db = db_client.cells
collection = db.balance
bank = await collection.find_one({"_id": str(user.id)})
if bank is None:
cell_bank_setup = {
"_id": str(user.id),
"balance": cells,
"history": [
{
"time": int(datetime.now().timestamp()),
"change": cells,
"new_balance": cells,
"user_id": str(interaction.user.id),
"reason": f"Set up cell bank with initial balance of {cells} cells. (Created via /add command)"
},
],
}
await collection.insert_one(cell_bank_setup)
bank = await collection.find_one({"_id": str(user.id)})
bank["history"].sort(key=lambda x: x["time"], reverse=True)
history = paginate_list(bank["history"])
balance_view = BankBalanceViewer(0, bank["balance"], history, user)
balance_embed = format_balance_embed(balance_view)
msg = await interaction.followup.send(
content=f"Created new cell bank. Successfully added {cells} cells to {user.mention}'s cell bank.",
embed=balance_embed,
view=balance_view
)
balance_view.init_interaction(interaction, msg.id)
return
else:
balance = bank["balance"]
history_entry = {
"time": int(datetime.now().timestamp()),
"change": cells,
"new_balance": balance + cells,
"user_id": str(interaction.user.id),
"reason": reason
}
bank["history"].append(history_entry)
updates = {
"balance": balance + cells,
"history": bank["history"],
}
await collection.update_one({"_id": str(user.id)}, {"$set": updates})
existing_bank = await collection.find_one({"_id": str(user.id)})
existing_bank["history"].sort(key=lambda x: x["time"], reverse=True)
history = paginate_list(existing_bank["history"])
balance_view = BankBalanceViewer(0, existing_bank["balance"], history, user)
balance_embed = format_balance_embed(balance_view)
msg = await interaction.followup.send(
content=f"Successfully added {cells} cells to {user.mention}'s cell bank",
embed=balance_embed,
view=balance_view,
allowed_mentions=AllowedMentions(everyone=False, roles=False, users=[user]),
)
balance_view.init_interaction(interaction, msg.id)
@cells.subcommand(
description="Remove cells from a cell bank.",
)
async def remove(
interaction: Interaction,
user: Member = SlashOption(
description="The person you want to remove cells from.",
required=True,
),
cells: int = SlashOption(
description="The number of cells you want to remove.",
required=True,
min_value=1,
),
reason: str = SlashOption(
description="Why you removed these cells.",
required=True,
),
):
await interaction.response.defer()
db = db_client.cells
collection = db.balance
bank = await collection.find_one({"_id": str(user.id)})
if bank is None:
await interaction.followup.send(f"{user.mention} has not set up their cell bank yet.\n\n"
f"To set up a cell bank, use the {bank_setup.get_mention(guild=None)} command.")
return
balance = bank["balance"]
if balance - cells < 0:
await interaction.followup.send(f"That would put {user.mention} into debt. They only have {balance} cells.")
return
history_entry = {
"time": int(datetime.now().timestamp()),
"change": -cells,
"new_balance": balance - cells,
"user_id": str(interaction.user.id),
"reason": reason
}
bank["history"].append(history_entry)
updates = {
"balance": balance - cells,
"history": bank["history"],
}
await collection.update_one({"_id": str(user.id)}, {"$set": updates})
existing_bank = await collection.find_one({"_id": str(user.id)})
existing_bank["history"].sort(key=lambda x: x["time"], reverse=True)
history = paginate_list(existing_bank["history"])
balance_view = BankBalanceViewer(0, existing_bank["balance"], history, user)
balance_embed = format_balance_embed(balance_view)
msg = await interaction.followup.send(
content=f"Successfully removed {cells} cells from {user.mention}'s cell bank",
embed=balance_embed,
view=balance_view,
allowed_mentions=AllowedMentions(everyone=False, roles=False, users=[user]),
)
balance_view.init_interaction(interaction, msg.id)
@cells.subcommand(
description="Transfer cells to another person's cell bank.",
)
async def transfer(
interaction: Interaction,
user: Member = SlashOption(
description="The person you want to send cells to.",
required=True,
),
cells: int = SlashOption(
description="The number of cells you want to send.",
required=True,
min_value=1,
),
reason: str = SlashOption(
description="Why you sent these cells.",
required=True,
),
):
await interaction.response.defer()
if user.id == interaction.user.id:
await interaction.followup.send("You can't transfer cells to yourself!")
return
db = db_client.cells
collection = db.balance
send_bank = await collection.find_one({"_id": str(interaction.user.id)})
receive_bank = await collection.find_one({"_id": str(user.id)})
if send_bank is None:
await interaction.followup.send(f"You have not set up your cell bank yet.\n\n"
f"To set up a cell bank, use the {bank_setup.get_mention(guild=None)} command.")
return
if receive_bank is None:
await interaction.followup.send(f"{user.mention} has not set up their cell bank yet.\n\n"
f"To set up a cell bank, use the {bank_setup.get_mention(guild=None)} command.")
return
transaction_time = int(datetime.now().timestamp())
send_balance = send_bank["balance"]
if send_balance - cells < 0:
await interaction.followup.send(f"You can't just go into debt. You only have {send_balance} cells.")
return
send_history_entry = {
"time": transaction_time,
"change": -cells,
"new_balance": send_balance - cells,
"user_id": str(interaction.user.id),
"reason": f"Transaction of {cells} cells to {user.mention}\n\nReason: " + reason
}
send_bank["history"].append(send_history_entry)
send_updates = {
"balance": send_balance - cells,
"history": send_bank["history"],
}
receive_balance = receive_bank["balance"]
receive_history_entry = {
"time": transaction_time,
"change": cells,
"new_balance": receive_balance + cells,
"user_id": str(interaction.user.id),
"reason": f"Transaction of {cells} cells from {interaction.user.mention}\n\nReason: " + reason
}
receive_bank["history"].append(receive_history_entry)
receive_updates = {
"balance": receive_balance + cells,
"history": receive_bank["history"],
}
await collection.update_one({"_id": str(user.id)}, {"$set": receive_updates})
await collection.update_one({"_id": str(interaction.user.id)}, {"$set": send_updates})
existing_bank = await collection.find_one({"_id": str(user.id)})
existing_bank["history"].sort(key=lambda x: x["time"], reverse=True)
history = paginate_list(existing_bank["history"])
balance_view = BankBalanceViewer(0, existing_bank["balance"], history, user)
balance_embed = format_balance_embed(balance_view)
msg = await interaction.followup.send(
content=f"Successfully transferred {cells} cells to {user.mention}'s cell bank. You now have {send_balance - cells} cells.",
embed=balance_embed,
view=balance_view,
allowed_mentions=AllowedMentions(everyone=False, roles=False, users=[user]),
)
balance_view.init_interaction(interaction, msg.id)
def format_date(date: datetime):
if date.day % 10 == 1 and date.day != 11:
return date.strftime("%b. %-dst, %Y")
elif date.day % 10 == 2 and date.day != 12:
return date.strftime("%b. %-dnd, %Y")
elif date.day % 10 == 3 and date.day != 13:
return date.strftime("%b. %-drd, %Y")
else:
return date.strftime("%b. %-dth, %Y")
async def update_time_channel():
while True:
loop = asyncio.get_event_loop()
rac_time = await loop.run_in_executor(None, functools.partial(get_rac_time))
new_date = await loop.run_in_executor(None, functools.partial(format_date, date=rac_time))
db = db_client.settings
collection = db.settings
time_channel_id = await collection.find_one({"_id": "rac_time_channel"})
enabled_list = await collection.find_one({"_id": "rac_time_autoupdate"})
print(enabled_list)
for server_id in enabled_list["value"]:
server = bot.get_guild(int(server_id))
time_channel = server.get_channel(time_channel_id["value"][server_id])
if enabled_list["value"][server_id] and time_channel:
await time_channel.edit(name=new_date)
next_rac_day = datetime(year=rac_time.year, month=rac_time.month, day=rac_time.day, tzinfo=timezone.utc)
next_rac_day += timedelta(days=1)
irl_next_day = await loop.run_in_executor(None, functools.partial(get_irl_time, rac_time=next_rac_day))
wait_time = 2 * 60 * 60
await asyncio.sleep(wait_time + 10)
@bot.event
async def on_ready():
# announcements = await bot.fetch_channel(815665892498866207)
# await announcements.send("FYI, you will be timeout for 1 hour automatically if you say the R word or any of its derivatives. \nYou can try it yourself if you don't believe me.", reference=announcements.get_partial_message(1205670816486924378))
await update_time_channel()
@time.subcommand(
description="Enable/disable RAC bot auto-updating the RAC time in a voice channel."
)
async def auto_update(
interaction: Interaction,
setting: str = SlashOption(
description="Enable or disable RAC Bot to show the current time in a channel?",
choices=["Enable", "Disable"],
default="None",
required=True,
),
):
await interaction.response.defer()
if interaction.user.guild_permissions.manage_guild:
db = db_client.settings
collection = db.settings
setting = {"Enable": True, "Disable": False}[setting]
await collection.update_one({"_id": "rac_time_autoupdate"},
{"$set": {f"value.{interaction.guild_id}": setting}})
if setting:
await interaction.followup.send("Successfully enabled auto-updating time.")
else:
await interaction.followup.send("Successfully disabled auto-updating time.")
else:
await interaction.followup.send("Excuse me, only the *right* people can change this setting.")
@time.subcommand(
description="If RAC bot is not updating the time, try this."
)
async def fix_channel(
interaction: Interaction,
):
await interaction.response.defer()
await interaction.followup.send(
"Started to update time channel. It should be working in a minute. If not, contact Lankakabei.")
await update_time_channel()
async def fetch_inspirobot(xmas: bool = None):
if xmas is None:
xmas = False
api_url = "https://inspirobot.me/api"
session = aiohttp.ClientSession()
async with session:
if xmas:
inspirobot_image = await session.get(api_url, params={"generate": "true", "season": "xmas"})
else:
inspirobot_image = await session.get(api_url, params={"generate": "true", })
inspirobot_image = await inspirobot_image.text()
logging.info(inspirobot_image)
return inspirobot_image
def inspirobot_embed(quote_list, index):
return Embed(title="Here's your new inspirational quote!", colour=Colour.from_rgb(93, 188, 58)) \
.set_image(quote_list[index]) \
.set_footer(text=f"{index + 1}/{len(quote_list)}")
class InspirobotLeftButton(Button):
def __init__(self):
super().__init__(style=ButtonStyle.blurple, label="Previous", row=1)
async def callback(self, interaction: Interaction):
index = self.view.index
if self.view.index != 0:
self.view.index = index - 1
else:
self.view.index = len(self.view.inspirobot_list) - 1
embed = inspirobot_embed(self.view.inspirobot_list, self.view.index)
await interaction.response.edit_message(embed=embed)
class InspirobotSaveButton(Button):
def __init__(self):
super().__init__(style=ButtonStyle.grey, label="Save", row=2, emoji="💾")
async def callback(self, interaction: Interaction):
await interaction.response.defer()
embed = inspirobot_embed(self.view.inspirobot_list, self.view.index)
await interaction.followup.send(embed=embed)
class InspirobotRightButton(Button):
def __init__(self):
super().__init__(style=ButtonStyle.blurple, label="Next", row=1)
async def callback(self, interaction: Interaction):
index = self.view.index
if index + 1 == len(self.view.inspirobot_list):
self.view.inspirobot_list.append(await fetch_inspirobot(self.view.xmas))
self.view.index = index + 1
embed = inspirobot_embed(self.view.inspirobot_list, self.view.index)
await interaction.response.edit_message(embed=embed)
class InspirobotJumpInput(TextInput):
def __init__(self, max_len: int):
super().__init__(label="Jump", max_length=max_len, min_length=1,
placeholder="The page number you want to jump to", required=True)
class InspirobotJumpModal(Modal):
def __init__(self, inspirobot_list: list, index: int):
super().__init__(title="Jump", timeout=600)
self.inspirobot_list = inspirobot_list
self.index = index
self.add_item(JumpInput(len(str(index))))
async def callback(self, interaction: Interaction):
try:
page = int(self.children[0].value) - 1
except ValueError:
embed = inspirobot_embed(self.inspirobot_list, self.index)
await interaction.response.edit_message(embed=embed)
return
if page < 0:
page = 0
if page < len(self.inspirobot_list):
self.index = page
embed = inspirobot_embed(self.inspirobot_list, self.index)
await interaction.response.edit_message(embed=embed)
return
class InspirobotJumpButton(Button):
def __init__(self):
super().__init__(style=ButtonStyle.grey, label="Jump", row=1, emoji="⏩")
async def callback(self, interaction: Interaction):
await interaction.response.send_modal(InspirobotJumpModal(self.view.inspirobot_list, self.view.index))
class InspirobotPreview(AutoDisableView):
def __init__(self, inspirobot_list, xmas):
super().__init__()
self.index = 0
self.add_item(InspirobotLeftButton())
self.add_item(InspirobotJumpButton())
self.add_item(InspirobotSaveButton())
self.add_item(InspirobotRightButton())
self.message = None
self.timeout = 3600
self.inspirobot_list = inspirobot_list
self.xmas = xmas
@bot.slash_command(description="Get inspiring quotes from Inspirobot")
async def inspirobot(
interaction: Interaction,
xmas: str = SlashOption(
description="Enable or disable Inspirobot's Xmas mode",
choices=["Xmas", "Normal"],
default="Normal",
required=False,
),
):
await interaction.response.defer()
xmas = {"Xmas": True, "Normal": False}[xmas]
inspirobot_quote = await fetch_inspirobot(xmas)
message_view = InspirobotPreview([inspirobot_quote], xmas)
embed = inspirobot_embed(message_view.inspirobot_list, 0)
sent_message = await interaction.followup.send(embed=embed, view=message_view)
message_view.message = sent_message
message_view.init_interaction(interaction, sent_message.id)
def crop_image(image_path, mask_path, output_path, fit_mask_ratio=None, crop_area=None):
# Open the image and mask
image = Image.open(image_path).convert("RGBA")
mask = Image.open(mask_path).convert("RGBA") # Open mask with alpha channel
centering_dict = {
'centre': (0.5, 0.5),
'left': (0.0, 0.0),
'right': (1.0, 1.0),
}
if fit_mask_ratio is True:
if crop_area:
resized_image = ImageOps.fit(image, mask.size, centering=centering_dict[crop_area])
else:
# Resize the image to match the mask
resized_image = image.resize(mask.size)
else:
# Resize the mask to fit the image's proportions, shrink image
resized_image = image.resize((int(image.width * mask.height / image.height), mask.height))
mask = mask.resize(resized_image.size)
# Create a new image with RGBA mode
cropped_image = Image.new("RGBA", resized_image.size)
# Composite the resized image and the mask
cropped_image = Image.composite(resized_image, cropped_image, mask)
# Save the final cropped image as PNG
cropped_image.save(output_path, "PNG")
@bot.slash_command(description="Crop regular flags into Discord emoji flags")
async def crop_flag(
interaction: Interaction,
flag: Attachment = SlashOption(
description="The flag image",
required=True
),
file_name: str = SlashOption(
description="The new file name of the flag",
default="cropped_flag.png",
required=False,
),
ratio_proportion: str = SlashOption(
description="Shrink flag to default ratio or keep ratio?",
choices=("Use default ratio", "Keep ratio"),
default="Use default ratio",
required=False,
),
crop_area: str = SlashOption(
description="Should flag be shrinked or cropped, and which part should be kept?",
choices=("Shrink", "Crop centre", "Crop left", "Crop right"),
default="Shrink",
required=False,
),
):
await interaction.response.defer()
if "image" not in flag.content_type:
await interaction.followup.send("That's not an image. Please send images only.")
return
await flag.save(f"flag_{interaction.id}")
if ratio_proportion == "Use default ratio":
crop_area_dict = {
"Shrink": False,
"Crop centre": "centre",
"Crop left": "left",
"Crop right": "right",
}
if crop_area:
crop_image(f"flag_{interaction.id}", "flag_cropping_mask.png", f"cropped_{interaction.id}.png", True,
crop_area_dict[crop_area])
else:
crop_image(f"flag_{interaction.id}", "flag_cropping_mask.png", f"cropped_{interaction.id}.png", True)
else:
crop_image(f"flag_{interaction.id}", "flag_cropping_mask.png", f"cropped_{interaction.id}.png", False)
new_attachment = File(f"cropped_{interaction.id}.png", file_name)
await interaction.followup.send(f"Here's your cropped flag!", file=new_attachment)
os.remove(f"cropped_{interaction.id}.png")
os.remove(f"flag_{interaction.id}")
def format_time_bank_history(history: list[dict]):
print(history)
history_text = ""
for entry in history:
print(entry)
history_text += f"## +{entry['seconds']} seconds\n" \
f"Change: {entry['new_balance'] - entry['seconds']} seconds " \
f"-> {entry['new_balance']} seconds\n" \
f"User: <@{entry['user']}>\n" \
f"Time: <t:{entry['collect_time']}:R>\n"
return history_text
async def collect_time(interaction: Interaction):
await interaction.response.defer()
collect_time = datetime.now(tz=timezone.utc)
db = db_client.time_is_money
setting_collection = db.game
user_id = str(interaction.user.id)
signups_open = await setting_collection.find_one({"_id": "signups_open"})
end_date = await setting_collection.find_one({"_id": "end_date"})
try:
if signups_open["value"] is False and collect_time < datetime.fromtimestamp(end_date["value"], tz=timezone.utc):
member_collection = db.member
members = member_collection.find({})
members = {d['_id']: {k: v for k, v in d.items() if k != '_id'} async for d in members}
if user_id in members.keys():
last_collect_time = await setting_collection.find_one({"_id": "last_collect_time"})
collect_seconds = int(collect_time.timestamp() - last_collect_time["value"])
if collect_seconds <= 4:
await interaction.followup.send(
f"{interaction.user.mention} You didn't collect more than 5 seconds? Try again.")
else:
if collect_seconds >= 60:
collect_seconds *= 2
if collect_seconds >= 600:
collect_seconds *= 2
msg_text = f"{interaction.user.mention} Collected {collect_seconds} seconds. " \
f"(+{int(collect_seconds / 4 * 3)} seconds for 10 minute 4x bonus.)"
else:
msg_text = f"{interaction.user.mention} Collected {collect_seconds} seconds. " \
f"(+{int(collect_seconds / 2)} seconds for 1 minute 2x bonus.)"
else:
msg_text = f"{interaction.user.mention} Collected {collect_seconds} seconds."
msg_text += f" <t:{int(collect_time.timestamp())}:R>"
await setting_collection.update_one({"_id": "last_collect_time"},
{"$set": {"value": int(collect_time.timestamp())}})
member_balance = await member_collection.find_one({"_id": user_id}, projection={"seconds": True})
time_collection = {"user": user_id, "collect_time": int(collect_time.timestamp()),
"seconds": collect_seconds,
"new_balance": collect_seconds + member_balance["seconds"]}
await setting_collection.update_one({"_id": "collections"}, {"$push": {"value": time_collection}})
await member_collection.update_one({"_id": user_id}, {"$push": {"collections": time_collection},
"$inc": {"seconds": collect_seconds}})
member_info = await member_collection.find_one({"_id": user_id}, projection=["seconds"])
# history = paginate_list(member_info["collections"])
balance_view = TimeBalanceViewer(0, member_info["seconds"], bank_owner=interaction.user)
balance_embed = format_time_balance_embed(balance_view)
msg = await interaction.followup.send(
msg_text,
embed=balance_embed,
view=balance_view,
)
else:
await interaction.followup.send(f"{interaction.user.mention} You are not in Time is Money.")
else:
await interaction.followup.send(f"{interaction.user.mention} Time is Money is not currently running.")
except KeyError:
await interaction.followup.send(f"{interaction.user.mention} Time is Money is not currently running.")
def format_time_history_embed(view: View):
history_text = format_time_bank_history(view.history[view.index])
if view.bank_owner is not None:
history_embed = Embed(
title=f"Time Collection History",
description=history_text,
colour=view.bank_owner.colour
).set_footer(text=f"{view.index + 1}/{len(view.history)}")
user_avatar = None if view.bank_owner.avatar is None else view.bank_owner.avatar.url
if view.bank_owner.discriminator == "0":
history_embed = history_embed.set_author(name=view.bank_owner.name, icon_url=user_avatar)
else:
history_embed = history_embed.set_author(name=f"{view.bank_owner.name}#{view.bank_owner.discriminator}",
icon_url=user_avatar)
else:
history_embed = Embed(
title=f"Time Collection History",
description=history_text,
colour=bot_colour
).set_footer(text=f"{view.index + 1}/{len(view.history)}")
return history_embed
def format_time_balance_embed(view: View):
if view.bank_owner is not None:
balance_embed = Embed(
title=f"Time Balance",
description=f"{view.balance} seconds",
colour=view.bank_owner.colour
)
user_avatar = None if view.bank_owner.avatar is None else view.bank_owner.avatar.url
if view.bank_owner.discriminator == "0":
balance_embed = balance_embed.set_author(name=view.bank_owner.name, icon_url=user_avatar)
else:
balance_embed = balance_embed.set_author(name=f"{view.bank_owner.name}#{view.bank_owner.discriminator}",
icon_url=user_avatar)
else:
leaderboard_text = ""
ranking = 1
for member in view.member_balances:
leaderboard_text += f"{ranking}. <@{member['_id']}>: {member['seconds']} seconds\n"
ranking += 1
balance_embed = Embed(
title=f"Leaderboard",
description=leaderboard_text,
colour=bot_colour
)
return balance_embed
class TimeHistoryLeftButton(Button):
def __init__(self):
super().__init__(style=ButtonStyle.blurple, label="Previous", row=1)
async def callback(self, interaction: Interaction):
if self.view.index != 0:
self.view.index -= 1
else:
self.view.index = len(self.view.history) - 1
embed = format_time_history_embed(self.view)
await interaction.response.edit_message(embed=embed)
class TimeHistoryRightButton(Button):
def __init__(self):
super().__init__(style=ButtonStyle.blurple, label="Next", row=1)
async def callback(self, interaction: Interaction):
if len(self.view.history) - 1 == self.view.index:
self.view.index = 0
else:
self.view.index = self.view.index + 1
embed = format_time_history_embed(self.view)
await interaction.response.edit_message(embed=embed)
class TimeHistoryToBalanceButton(Button):
def __init__(self):
super().__init__(style=ButtonStyle.grey, label="View Balance", row=2, emoji="💵")
async def callback(self, interaction: Interaction):
balance_view = TimeBalanceViewer(self.view.index, self.view.balance, self.view.history, self.view.bank_owner,
self.view.member_balances)
balance_embed = format_time_balance_embed(self.view)
await interaction.response.edit_message(embed=balance_embed, view=balance_view)
balance_view.init_interaction(interaction)
class TimeBalanceToHistoryButton(Button):
def __init__(self):
super().__init__(style=ButtonStyle.blurple, label="View History", row=1, emoji="📜")
async def callback(self, interaction: Interaction):
history_view = TimeBankHistoryViewer(self.view.index, self.view.balance, self.view.history,
self.view.bank_owner, self.view.member_balances)
history_embed = format_time_history_embed(self.view)
await interaction.response.edit_message(embed=history_embed, view=history_view)
history_view.init_interaction(interaction)
class TimeCollect(Button):
def __init__(self):
super().__init__(style=ButtonStyle.green, label="Collect Again", row=1, emoji="🧺")
async def callback(self, interaction: Interaction):
await collect_time(interaction)
class TimeBankHistoryViewer(AutoDisableView):
def __init__(self, index, balance, history, bank_owner: Member = None, member_balances=None):
super().__init__()
self.index = index
self.add_item(TimeHistoryLeftButton())
self.add_item(TimeHistoryRightButton())
self.add_item(TimeHistoryToBalanceButton())
self.message = None
self.timeout = 3600
self.balance = balance
self.history = history
self.bank_owner = bank_owner
self.member_balances = member_balances
class TimeBalanceViewer(AutoDisableView):
def __init__(self, index, balance, history=None, bank_owner: Member = None, member_balances=None):
super().__init__()
self.index = index
self.add_item(TimeCollect())
self.message = None
self.timeout = 3600
self.balance = balance
self.bank_owner = bank_owner
self.member_balances = member_balances
if history is not None:
self.history = history
self.add_item(TimeBalanceToHistoryButton())
@bot.slash_command(
description="Time is Money commands",
force_global=True)
async def seconds(interaction: Interaction):
pass
@seconds.subcommand(
description="Open signups for a new round of Time is Money"
)
async def setup(
interaction: Interaction
):
await interaction.response.defer()
if not interaction.user.id == OWNER_ID:
await interaction.followup.send("You are not the event organiser of Time is Money.")
return
else:
db = db_client.time_is_money
setting_collection = db.game
member_collection = db.member
await setting_collection.update_one({"_id": "signups_open"}, {"$set": {"value": True}}, upsert=True)
await member_collection.delete_many({})
await interaction.followup.send(f"Time is Money is now open for signups.")
@seconds.subcommand(
description="Signup for a new round of Time is Money."
)
async def signup(
interaction: Interaction,
):
await interaction.response.defer()
db = db_client.time_is_money
setting_collection = db.game
start_game = await setting_collection.find_one({"_id": "signups_open"})
if start_game is not None and start_game["value"] is True:
collection = db.member
signup = {"_id": str(interaction.user.id), "seconds": 0, "collections": []}
try:
await collection.insert_one(signup)
await interaction.followup.send(f"Successfully signed up {interaction.user.mention} for Time is Money.")
except DuplicateKeyError:
await interaction.followup.send(f"You already signed up for Time is Money.")
else:
await interaction.followup.send(f"Time is Money is not accepting new signups.")
@seconds.subcommand(
description="End signups and start the game"
)
async def start(
interaction: Interaction,
end_date: int = SlashOption(
description="End date of Time is Money, in Unix time format",
required=True,
),
):
await interaction.response.defer()
now_time = int(datetime.now(tz=timezone.utc).timestamp())
if not interaction.user.id == OWNER_ID:
await interaction.followup.send("You are not the event organiser of Time is Money.")
return
else:
db = db_client.time_is_money
setting_collection = db.game
await setting_collection.update_one({"_id": "signups_open"}, {"$set": {"value": False}})
await setting_collection.update_one({"_id": "end_date"}, {"$set": {"value": end_date}}, upsert=True)
await setting_collection.update_one({"_id": "last_collect_time"}, {"$set": {"value": now_time}}, upsert=True)
await setting_collection.update_one({"_id": "collections"}, {"$set": {"value": []}}, upsert=True)
await setting_collection.update_one({"_id": "start_time"}, {"$set": {"value": now_time}}, upsert=True)
await interaction.followup.send("Done starting a new season of Time is Money!")
member_collection = db.member
members = member_collection.find({})
member_message = ""
async for member in members:
member_message += f"<@{member['_id']}> "
member_message = f"""{member_message}
Time is Money has started! It will end <t:{end_date}:R>.
To collect time, use the {time_collect.get_mention()} command.
You get the amount of seconds that passed between the last person's collection and your collection.
For example, if I collected some time, then you used the command 5 minutes after I collected time, then you'll earn 300 seconds.
Whoever ends up with the most seconds wins!
"""
await interaction.followup.send(member_message, allowed_mentions=AllowedMentions.all())
@seconds.subcommand(
name="collect",
description="Collect some time.",
)
async def time_collect(
interaction: Interaction,
):
await collect_time(interaction)
@seconds.subcommand(
description="View other people's, or everyone's, collected time."
)
async def view_balance(
interaction: Interaction,
user: Member = SlashOption(
description="The user you want to check. Leave this blank to see everyone's time collecting.",
required=False,
)
):
await interaction.response.defer()
db = db_client.time_is_money
if user is None:
setting_collection = db.game
collecting_history = await setting_collection.find_one({"_id": "collections"})
collecting_history = paginate_list(collecting_history["value"])
start_time = await setting_collection.find_one({"_id": "start_time"})
last_collect_time = await setting_collection.find_one({"_id": "last_collect_time"})
balance = last_collect_time["value"] - start_time["value"]
member_collection = db.member
member_cursor = member_collection.find({})
member_balances = []
async for member in member_cursor:
member_balances.append(member)
member_balances = sorted(member_balances, key=lambda d: d['seconds'], reverse=True)
balance_view = TimeBalanceViewer(0, balance, history=collecting_history, member_balances=member_balances)
balance_embed = format_time_balance_embed(balance_view)
msg = await interaction.followup.send(
embed=balance_embed,
view=balance_view,
)
balance_view.init_interaction(interaction, msg.id)
else:
member_collection = db.member
user_id = str(user.id)
member_balance = await member_collection.find_one({"_id": user_id})
if member_balance is not None:
member_history = paginate_list(member_balance["collections"])
balance_view = TimeBalanceViewer(0, member_balance["seconds"], history=member_history, bank_owner=user)
balance_embed = format_time_balance_embed(balance_view)
msg = await interaction.followup.send(
embed=balance_embed,
view=balance_view,
)
balance_view.init_interaction(interaction, msg.id)
else:
await interaction.followup.send("That user is not in Time is Money.")
@cells.subcommand(
name="formula",
description="See how many cells you can keep from Dalesia/Ceveulius over to the new planets.",
)
async def formula(
interaction: Interaction,
cells: int = SlashOption(
description="The total number of cells you have, including those in your cell bank and those already claimed.",
min_value=1,
)
):
await interaction.response.defer()
new_cells = int(cells - ((cells - 64 + abs(cells - 64)) / 4) - ((cells - 192 + abs(cells - 192)) / 8) - (
(cells - 448 + abs(cells - 448)) / 16) - ((cells - 960 + abs(cells - 960)) / 32))
await interaction.followup.send(f"You would be able to keep {new_cells} cells when moving over from Cevdal")
@time.subcommand(
description="Sets the channel for auto-updating the time."
)
async def channel(
interaction: Interaction,
channel: VoiceChannel = SlashOption(
description="Enable or disable RAC Bot to show the current time in a channel?",
required=True,
),
):
await interaction.response.defer()
if interaction.user.guild_permissions.manage_guild:
db = db_client.settings
collection = db.settings
await collection.update_one({"_id": "rac_time_channel"},
{"$set": {f"value.{interaction.guild_id}": channel.id}})
loop = asyncio.get_event_loop()
rac_time = await loop.run_in_executor(None, functools.partial(get_rac_time))
new_date = await loop.run_in_executor(None, functools.partial(format_date, date=rac_time))
await channel.edit(name=new_date)
await interaction.followup.send(f"Successfully changed time channel to {channel.mention}")
else:
await interaction.followup.send("Sorry, but losers can't change this setting.")
async def generate_ai_image(prompt, size):
glif_id = {
"Square": "clsy8mkyu00008fjsr5tyzxy0",
"Wide": "clsxzqjts0008rrxs68zw8xdh",
"Tall": "clsy8rwn0000cj1ncxryy9rd0",
}
GLIF_API_KEY = os.getenv('GLIF_API_KEY')
headers = {
"Authorization": "Bearer " + GLIF_API_KEY,
}
payload = {
"inputs": [prompt]
}
glif_url = "https://simple-api.glif.app/" + glif_id[size]
async with aiohttp.ClientSession() as session:
image = await session.post(glif_url, headers=headers, json=payload)
image = await image.json()
return image
@bot.slash_command(
description="Generate AI images. Uses OpenAI's Dalle-3 image generator.",
force_global=True)
async def ai_image(
interaction: Interaction,
prompt: str = SlashOption(
description="The prompt to give the AI. For flags, mention 'an svg flag' in your prompt for better results.",
required=True,
),
size: str = SlashOption(
description="The size of image you want.",
choices=["Square", "Wide", "Tall"],
default="Square"),
):
await interaction.response.defer()
image = await generate_ai_image(prompt, size)
error_message = f"Something went wrong. Try again a few times. If the image's still not producing, it could be that your prompt was moderated, rate limits or just plain bad luck. \n\nHere's the response from the API:\n\n```json\n{image}\n```"
try:
logging.info(image)
image_url = image['output']
if image_url is None:
await interaction.followup.send(error_message)
else:
result_embed = Embed(title="Here's your new AI image!", colour=bot_colour)
result_embed.set_image(image_url)
await interaction.followup.send('', embed=result_embed)
except KeyError:
await interaction.followup.send(error_message)
async def get_attachment(attachment, name):
await attachment.save(name)
file = open(name, "r")
file_text = file.read()
file.close()
os.remove(name)
return file_text
def combine_svg(map_text):
map_lines = map_text.split("\n")
start_line = None
end_line = None
for line_index in range(len(map_lines)):
if '<svg id="map"' == map_lines[line_index][:13]:
start_line = line_index
if "</svg>" == map_lines[line_index][-6:]:
end_line = line_index
if start_line is not None and end_line is not None:
break
svg_lines = map_lines[start_line + 1:end_line + 1]
original_svg = map_lines[start_line]
for line in svg_lines:
original_svg += "\n" + line
map_lines[start_line] = original_svg
del map_lines[start_line + 1:end_line + 1]
return map_lines
def parse_map(map_text):
map_lines = combine_svg(map_text)
state_info = json.loads(map_lines[14])
settings = map_lines[1].split("|")
map_name = settings[20]
distance_unit = settings[0]
pixel_to_unit = float(settings[1])
azgaar_map = {
"name": map_name,
"settings": settings,
"distance_unit": distance_unit,
"pixel_to_unit": pixel_to_unit,
"states": state_info,
}
return azgaar_map
def find_map_attachment(discord_message: Message):
for attachment in discord_message.attachments:
if attachment.filename[-4:] == ".map":
return attachment
return
async def find_map_in_command(planet, azgaar_map, message_link, interaction):
if planet:
planet_channel = await bot.fetch_channel(planet)
async for planet_message in planet_channel.history(limit=50):
map_attachment = find_map_attachment(planet_message)
if map_attachment is not None:
return map_attachment
if azgaar_map is None:
if message_link is None:
await interaction.followup.send("Please put either an Azgaar map file or a link to a message.")
return
else:
try:
link_message = await get_message_from_link(message_link)
map_attachment = find_map_attachment(link_message)
if map_attachment is None:
await interaction.followup.send(f"That message at {message_link} doesn't have a .map attachment.")
return
else:
return map_attachment
except ValueError:
await interaction.followup.send("That message link is invalid. Please put a valid link to a message.")
return
else:
if not azgaar_map.filename[-4:] == ".map":
await interaction.followup.send("Please use an Azgaar map file.")
return
else:
return azgaar_map
def state_cells_embed(state_list: list, index: int):
leaderboard = "## Map: " + state_list[index][0]['map_name'] + "\n\n"
start_ranking = index * 10 + 1
for state_index in range(len(state_list[index])):
leaderboard += f"{start_ranking + state_index}. {state_list[index][state_index]['name']}: {state_list[index][state_index]['cells']:,} cells\n"
return Embed(title="States by cell amount", description=leaderboard,
colour=bot_colour).set_footer(
text=f"{index + 1}/{len(state_list)}")
@map.subcommand(
description="Shows the amount of cells each nation has. Don't use more than one option.",
)
async def state_cells(
interaction: Interaction,
planet: str = SlashOption(
description="The planet you want to check.",
choices=list(MAP_CHANNELS.keys()),
required=False,
),
azgaar_map: Attachment = SlashOption(
description="The map file.",
required=False
),
message_link: str = SlashOption(
description="A message with a .map attachment.",
required=False),
):
await interaction.response.defer()
if planet:
planet = MAP_CHANNELS[planet]
map_attachment = await find_map_in_command(planet, azgaar_map, message_link, interaction)
if map_attachment is None:
return
map_filename = f"azgaar_map_{interaction.id}"
map_text = await get_attachment(map_attachment, map_filename)
parsed_map = parse_map(map_text)
new_state_info = []
for state in parsed_map["states"]:
try:
if state.get("removed") is not True:
new_state_info.append(
{
'name': state['name'],
'cells': state['cells'],
'map_name': parsed_map["name"],
}
)
except KeyError:
pass
new_state_info.sort(key=lambda state: state['cells'], reverse=True)
new_state_info = paginate_list(new_state_info)
view = PaginatedView(new_state_info, state_cells_embed, save_button=False)
embed = state_cells_embed(new_state_info, 0)
sent_message = await interaction.followup.send(embed=embed, view=view)
view.init_interaction(interaction, sent_message.id)
view.message = sent_message
def state_areas_embed(state_list: list, index: int):
leaderboard = "## Map: " + state_list[index][0]['map_name'] + "\n\n"
start_ranking = index * 10 + 1
for state_index in range(len(state_list[index])):
leaderboard += f"{start_ranking + state_index}. {state_list[index][state_index]['name']}: {state_list[index][state_index]['area']:,} {state_list[index][state_index]['unit']}²\n"
return Embed(title="States by area", description=leaderboard,
colour=bot_colour).set_footer(
text=f"{index + 1}/{len(state_list)}")
@map.subcommand(
description="Shows the total area of each nation. Don't use more than one option.",
)
async def state_areas(
interaction: Interaction,
planet: str = SlashOption(
description="The planet you want to check.",
choices=list(MAP_CHANNELS.keys()),
required=False,
),
azgaar_map: Attachment = SlashOption(
description="The map file.",
required=False
),
message_link: str = SlashOption(
description="A message with a .map attachment.",
required=False),
):
await interaction.response.defer()
if planet:
planet = MAP_CHANNELS[planet]
map_attachment = await find_map_in_command(planet, azgaar_map, message_link, interaction)
if map_attachment is None:
return
map_filename = f"azgaar_map_{interaction.id}"
map_text = await get_attachment(map_attachment, map_filename)
parsed_map = parse_map(map_text)
new_state_info = []
for state in parsed_map["states"]:
try:
if state.get("removed") is not True:
new_state_info.append(
{
'name': state['name'],
'area': int(state['area'] * parsed_map["pixel_to_unit"] ** 2),
'unit': parsed_map["distance_unit"],
'map_name': parsed_map["name"],
}
)
except KeyError:
pass
new_state_info.sort(key=lambda state: state['area'], reverse=True)
new_state_info = paginate_list(new_state_info)
view = PaginatedView(new_state_info, state_areas_embed, save_button=False)
embed = state_areas_embed(new_state_info, 0)
sent_message = await interaction.followup.send(embed=embed, view=view)
view.init_interaction(interaction, sent_message.id)
view.message = sent_message
@bot.slash_command(
description="Show all RAC bot commands",
force_global=True
)
async def commands(interaction: Interaction):
await interaction.response.defer()
response_text = ""
for command in bot.get_application_commands():
try:
response_text += "- "
response_text += command.get_mention()
response_text += "\n"
except AttributeError:
continue
try:
for subcommand in command.children.values():
response_text += " - "
response_text += subcommand.get_mention()
response_text += "\n"
except AttributeError:
continue
response_embed = Embed(colour=bot_colour, title="RAC Bot Commands", description=response_text)
await interaction.followup.send(embed=response_embed)
@bot.slash_command(
description="Identify the Ballsdex ball based on message link",
force_global=True,
)
async def ballsdex_identify(
interaction: Interaction,
message_link: str = SlashOption(
description="A message with a Ballsdex image.",
required=True)
):
await interaction.response.defer()
try:
link_message = await get_message_from_link(message_link)
if link_message.attachments is None:
await interaction.followup.send(f"That message at {message_link} doesn't have an attachment.")
return
map_attachment = link_message.attachments[0]
except ValueError:
await interaction.followup.send("That message link is invalid. Please put a valid link to a message.")
return
ballsdex_image = "ballsdex_" + str(link_message.id)
await map_attachment.save(ballsdex_image)
ball_name = check_balldex_image(ballsdex_image)
os.remove(ballsdex_image)
rare_countryballs = open("rare_countryballs.txt", "r")
rare_countryballs_list = rare_countryballs.read().splitlines()
rare_countryballs.close()
text_message = "I don't know what ball this is."
if ball_name:
text_message = f"Catch name: ```{ball_name}```"
if ball_name in rare_countryballs_list:
text_message += f"\nThis is a rare countryball."
await interaction.followup.send(text_message)
async def parse_players(team_name: str, team_list: str, interaction: Interaction):
players = []
team_list += "\n"
goalies = re.findall(r"Goalie: (.*)\n", team_list) + re.findall(r"Goalkeeper: (.*)\n", team_list)
defenders = re.findall(r"Defender: (.*)\n", team_list)
midfielders = re.findall(r"Mid: (.*)\n", team_list) + re.findall(r"Midfielder: (.*)\n", team_list)
attackers = re.findall(r"Attacker: (.*)\n", team_list) + re.findall(r"Forward: (.*)\n", team_list)
player_count = len(goalies) + len(defenders) + len(midfielders) + len(attackers)
if not len(goalies) == 1:
await interaction.followup.send(f"{team_name} has {len(players)} goalkeepers. You can only have 1.")
raise ValueError(f"{team_name} has {len(players)} goalkeepers. You can only have 1.")
if len(defenders) == 0:
await interaction.followup.send(f"{team_name} doesn't have a defender. You need at least 1.")
raise ValueError(f"{team_name} doesn't have a defender. You need at least 1.")
if len(midfielders) == 0:
await interaction.followup.send(f"{team_name} doesn't have a midfielder. You need at least 1.")
raise ValueError(f"{team_name} doesn't have a midfielder. You need at least 1.")
if len(attackers) == 0:
await interaction.followup.send(f"{team_name} doesn't have a attacker. You need at least 1.")
raise ValueError(f"{team_name} doesn't have a attacker. You need at least 1.")
if not player_count == 11:
await interaction.followup.send(f"{team_name} has {player_count} players. You need exactly 11.")
raise ValueError(f"{team_name} has {player_count} players. You need exactly 11.")
for player in goalies:
players.append(soccer.Player(player, soccer.PlayerPosition.GOALKEEPER))
for player in defenders:
players.append(soccer.Player(player, soccer.PlayerPosition.DEFENDER))
for player in midfielders:
players.append(soccer.Player(player, soccer.PlayerPosition.MIDFIELDER))
for player in attackers:
players.append(soccer.Player(player, soccer.PlayerPosition.FORWARD))
return players
def team_summary(team: soccer.Team, opposing_team: soccer.Team):
yellow_cards = len(tuple(filter(lambda card: card.colour == soccer.CardColour.YELLOW, team.cards)))
red_cards = len(tuple(filter(lambda card: card.colour == soccer.CardColour.RED, team.cards)))
return f"""⚽ Shots: {team.shots}
🎯 Shots on Target: {team.shots_on_target}
🧤 Saves: {opposing_team.shots_on_target - opposing_team.goals}
🦵 Possession: {team.possession}%
😈 Fouls: {team.fouls}
🟨 Yellow cards: {yellow_cards}
🟥 Red cards: {red_cards}"""
def match_event_summary(match_events: list[soccer.Card]):
event_text = ""
for event in match_events:
if type(event) == soccer.Card:
if event.colour == soccer.CardColour.YELLOW:
event_text += f"🟨 {event.player} {str(event.time)}\n"
else:
event_text += f"🟥 {event.player} {str(event.time)}\n"
elif type(event) == soccer.Shot:
event_text += f"⚽ {event.player} {str(event.time)}\n"
if event.assister is not None:
event_text += f"👟️ {event.assister}\n"
return event_text
def penalty_summary(penalty_events: list[soccer.Shot]):
event_text = ""
for event in penalty_events:
if event.goal:
event_text += f"⚽ {event.player}\n"
else:
event_text += f"🥅 {event.player}\n"
return event_text
def match_embed(team1: soccer.Team, team2: soccer.Team):
rac_time = get_rac_time()
team1_match_events = team1.goal_objects + team1.cards
team2_match_events = team2.goal_objects + team2.cards
team1_match_events.sort(key=lambda event: event.time)
team2_match_events.sort(key=lambda event: event.time)
if team1.first_leg_goals is not None:
score = f"{team1.goals - team1.first_leg_goals}-{team2.goals - team2.first_leg_goals} ({team1.goals}-{team2.goals} on aggregate)"
else:
score = f"{team1.goals}-{team2.goals}"
if team1.goals > team2.goals:
msg_text = f"{team1.name} defeats {team2.name} " + score + "!"
elif team1.goals < team2.goals:
msg_text = f"{team2.name} defeats {team1.name} " + score + "!"
else:
msg_text = f"{team1.name} draws with {team2.name} " + score + "!"
if team1.first_leg_goals is not None:
match_embed_msg = Embed(colour=bot_colour, title=f"{team1.name} {team1.goals - team1.first_leg_goals}-{team2.goals - team2.first_leg_goals} ({team1.goals}-{team2.goals} agg.) {team2.name}", timestamp=rac_time)
else:
match_embed_msg = Embed(colour=bot_colour, title=f"{team1.name} {score} {team2.name}", timestamp=rac_time)
match_embed_msg.add_field(name=team1.name, value=team_summary(team1, team2))
match_embed_msg.add_field(name=team2.name, value=team_summary(team2, team1))
events_embed = Embed(colour=bot_colour, timestamp=rac_time)
events_embed.add_field(name="Events", value=match_event_summary(team1_match_events))
events_embed.add_field(name="Events", value=match_event_summary(team2_match_events))
return msg_text, match_embed_msg, events_embed
def penalty_match_embed(team1: soccer.Team, team2: soccer.Team):
rac_time = get_rac_time()
team1_match_events = team1.goal_objects + team1.cards
team2_match_events = team2.goal_objects + team2.cards
team1_match_events.sort(key=lambda event: event.time)
team2_match_events.sort(key=lambda event: event.time)
score = f"{team1.goals}-{team2.goals} ({team1.shootout_goals}-{team2.shootout_goals} on penalties)"
if team1.shootout_goals > team2.shootout_goals:
msg_text = f"{team1.name} defeats {team2.name} " + score + "!"
elif team1.shootout_goals < team2.shootout_goals:
msg_text = f"{team2.name} defeats {team1.name} " + score + "!"
else:
msg_text = f"{team2.name} draws with {team1.name} " + score + "!\n(If you see this, a bug has happened.)"
match_embed_msg = Embed(colour=bot_colour, title=f"{team1.name} {team1.goals}-{team2.goals} (pen {team1.shootout_goals}-{team2.shootout_goals}) {team2.name}", timestamp=rac_time)
match_embed_msg.add_field(name=team1.name, value=team_summary(team1, team2))
match_embed_msg.add_field(name=team2.name, value=team_summary(team2, team1))
events_embed = Embed(colour=bot_colour, timestamp=rac_time)
events_embed.add_field(name="Events", value=match_event_summary(team1_match_events))
events_embed.add_field(name="Events", value=match_event_summary(team2_match_events))
penalty_embed = Embed(colour=bot_colour,timestamp=rac_time)
penalty_embed.add_field(name="Penalties", value=penalty_summary(team1.penalty_objects))
penalty_embed.add_field(name="Penalties", value=penalty_summary(team2.penalty_objects))
return msg_text, match_embed_msg, events_embed, penalty_embed
def debug_team(team: soccer.Team):
return f"""Relative strength: {team.relative_strength}
Average fouls per 90 minutes: {team.potential_fouls}
Average shots per 90 minutes: {team.potential_shots}
Shot accuracy: {team.shot_accuracy * 100:.0f}%"""
def debug_embed(team1: soccer.Team, team2: soccer.Team):
debug_embed_msg = Embed(colour=bot_colour, title=f"Debug info")
debug_embed_msg.add_field(name=team1.name, value=debug_team(team1))
debug_embed_msg.add_field(name=team2.name, value=debug_team(team2))
return debug_embed_msg
def player_list(team: soccer.Team):
match_sheet = ""
for player in team.players:
if player.position == soccer.PlayerPosition.FORWARD:
match_sheet += "Attacker: "
elif player.position == soccer.PlayerPosition.MIDFIELDER:
match_sheet += "Midfielder: "
elif player.position == soccer.PlayerPosition.DEFENDER:
match_sheet += "Defender: "
elif player.position == soccer.PlayerPosition.GOALKEEPER:
match_sheet += "Goalkeeper: "
match_sheet += player.name
match_sheet += "\n"
return match_sheet
def missed_shot_desc(shot: soccer.Shot):
weights = {
f"{shot.player}'s shot goes wide of goal.": 1,
f"{shot.player}'s shot flies high into the stands.": 1,
f"{shot.player}'s shot is blocked by {shot.blocker} and deflected away from goal.": 1,
f"{shot.player}'s shot bounces off the crossbar.": 0.5,
}
return soccer.random_picker(weights)
@bot.slash_command(
description="Simulate a soccer match. A clone of http://scoresim.ilya.online",
force_global=True,
name="football",
name_localizations={
"en-US": "soccer",
}
)
async def soccer_cmd(
interaction: Interaction,
team1_name: str = SlashOption(
description="Name of the first team.",
required=True),
team2_name: str = SlashOption(
description="Name of the second team.",
required=True),
real_time: str = SlashOption(
description="Decide whether to have instant results or real_time play.",
required=False,
choices=("Instant results", "Real-time"),
default="Instant results",
),
extra_time: str = SlashOption(
description="Should extra time be played in case of a draw?",
required=False,
choices=("Extra time", "No extra time"),
default="No extra time",
),
penalties: str = SlashOption(
description="Should a penalty shootout be played in case of a draw?",
required=False,
choices=("Penalty shootout", "No penalty shootout"),
default="No penalty shootout",
),
team1_players: str = SlashOption(
description="Player list of team 1 (message link). See pins in #sports-stadium for format.",
required=False,
default="",
),
team2_players: str = SlashOption(
description="Player list of team 2 (message link). See pins in #sports-stadium for format.",
required=False,
default="",
),
relative_strength: str = SlashOption(
description="Relative strength of both teams. Don't use this with individual strength.",
choices=tuple(soccer.Team.STRENGTHS.keys()),
required=False,
default="Team 1 and Team 2 equal in class",
),
team1_strength: int = SlashOption(
description="Relative strength of team 1. 50 is equal, 60 is stronger, 40 is weaker.",
required=False,
default=50,
min_value=1,
max_value=100,
),
team2_strength: int = SlashOption(
description="Relative strength of team 2. 50 is equal, 60 is stronger, 40 is weaker.",
required=False,
default=50,
min_value=1,
max_value=100,
),
team1_morale: str = SlashOption(
description="Morale of team 1",
choices=tuple(soccer.Team.MORALES.keys()),
required=False,
default="Average",
),
team2_morale: str = SlashOption(
description="Morale of team 2",
choices=tuple(soccer.Team.MORALES.keys()),
required=False,
default="Average",
),
team1_support: str = SlashOption(
description="Crowd support of team 1",
choices=tuple(soccer.Team.CROWD_SUPPORTS.keys()),
required=False,
default="No support",
),
team2_support: str = SlashOption(
description="Crowd support of team 2",
choices=tuple(soccer.Team.CROWD_SUPPORTS.keys()),
required=False,
default="No support",
),
team1_tactic: str = SlashOption(
description="Tactic of team 1",
choices=tuple(soccer.Team.TACTICS.keys()),
required=False,
default="Attacking (recommended)",
),
team2_tactic: str = SlashOption(
description="Tactic of team 2",
choices=tuple(soccer.Team.TACTICS.keys()),
required=False,
default="Attacking (recommended)",
),
team1_score: int = SlashOption(
description="Earlier score of team 1 in this first leg.",
required=False,
default=None,
min_value=0,
max_value=100,
),
team2_score: int = SlashOption(
description="Earlier score of team 2 in this first leg.",
required=False,
default=None,
min_value=0,
max_value=100,
),
shot_highlights: str = SlashOption(
description="Choose shots that should be sent as events.",
choices=("All shots", "Shots on target"),
required=False,
default="Shots on target",
),
minute_length: int = SlashOption(
description="How long a simulated minute is, in seconds. Default is 5.",
required=False,
default=5,
min_value=1,
max_value=60,
),
debug: str = SlashOption(
description="Shows some important debug info.",
choices=("True", "False"),
required=False,
default="False",
),
):
await interaction.response.defer()
message_channel: nextcord.TextChannel = interaction.channel
extra_time = True if extra_time == "Extra time" else False
real_time = True if real_time == "Real-time" else False
penalties = True if penalties == "Penalty shootout" else False
team1_colour = Colour.from_rgb(229, 45, 45)
team2_colour = Colour.from_rgb(45, 45, 229)
if team1_strength == team2_strength == 50:
team1_strength = soccer.Team.STRENGTHS[relative_strength]
team2_strength = 100 - team1_strength
else:
pass
if team1_players:
try:
team1_players = await get_message_from_link(team1_players)
team1_players = team1_players.content
except ValueError:
await interaction.followup.send(
"Team 1 players is not a valid message link. Please send a Discord message link.")
return
try:
team1_players = await parse_players(team1_name, team1_players, interaction)
except ValueError:
return
else:
team1_players = soccer.default_players()
if team2_players:
try:
team2_players = await get_message_from_link(team2_players)
team2_players = team2_players.content
except ValueError:
await interaction.followup.send(
"Team 2 players is not a valid message link. Please send a Discord message link.")
return
try:
team2_players = await parse_players(team2_name, team2_players, interaction)
except ValueError:
return
else:
team2_players = soccer.default_players()
team1 = soccer.Team(
team1_name,
team1_players,
relative_strength=team1_strength,
morale=team1_morale,
crowd_support=team1_support,
tactic=team1_tactic,
opponent_tactic=team2_tactic,
goals=team1_score if team1_score is not None and team2_score is not None else None
)
team2 = soccer.Team(
team2_name,
team2_players,
relative_strength=team2_strength,
morale=team2_morale,
crowd_support=team2_support,
tactic=team2_tactic,
opponent_tactic=team1_tactic,
goals=team2_score if team1_score is not None and team2_score is not None else None
)
match_start_message = None
async for team1, team2, match_time, minute_events in soccer.start_match(team1, team2, extra_time=extra_time):
if real_time:
if match_time.minute == 1:
match_start_embed = Embed(colour=bot_colour, title=f"📢 Match starts",
description=f"{team1.name} vs {team2.name} has started!\n"
f"Note: Delete this message to stop the game.")
match_start_embed.add_field(name=team1.name, value=player_list(team1))
match_start_embed.add_field(name=team2.name, value=player_list(team2))
match_start_message = await interaction.followup.send(embed=match_start_embed)
if match_time.minute == 46 and match_time.half == soccer.Half.SECOND_HALF:
half_time_embed = Embed(colour=bot_colour, title=f"⏱️ Play recommences {str(match_time)}",
description="Half time has ended.")
await message_channel.send(embed=half_time_embed, reference=match_start_message)
if match_time.minute == 91 and match_time.half == soccer.Half.EXTRA_TIME:
extra_time_embed = Embed(colour=bot_colour, title=f"⏱️ Play recommences {str(match_time)}",
description=f"5 minute short break has ended.")
await message_channel.send(embed=extra_time_embed, reference=match_start_message)
await asyncio.sleep(minute_length)
if minute_events:
async with message_channel.typing():
await asyncio.sleep(5)
for event in minute_events:
event_colour = team1_colour if event.team == team1_name else team2_colour
if type(event) == soccer.Shot:
if shot_highlights == "All shots":
event_embed = Embed(colour=event_colour, title=f"🎯 Shot {str(match_time)}",
description=f"{event.player} of {event.team} shoots "
f"{'after a pass by ' + event.assister if event.assister is not None else ''}...")
await message_channel.send(embed=event_embed, reference=match_start_message)
await asyncio.sleep(5)
if event.goal is True:
event_embed = Embed(colour=event_colour, title=f"⚽ Goal! {str(match_time)}",
description=f"""## {team1.name} {team1.goals}-{team2.goals} {team2.name}
{event.player} scores for {event.team}!""")
elif event.on_target is True:
event_embed = Embed(colour=event_colour, title=f"🧤 Saved {str(match_time)}",
description=f"""{event.player}'s shot was saved.""")
else:
event_embed = Embed(colour=event_colour, title=f"❌ Missed {str(match_time)}",
description=missed_shot_desc(event))
await message_channel.send(embed=event_embed, reference=match_start_message)
else:
if event.on_target:
event_embed = Embed(colour=event_colour, title=f"🎯 Shot {str(match_time)}",
description=f"{event.player} of {event.team} shoots on target "
f"{'after a pass by ' + event.assister if event.assister is not None else ''}...")
await message_channel.send(embed=event_embed, reference=match_start_message)
await asyncio.sleep(5)
if event.goal is True:
event_embed = Embed(colour=event_colour, title=f"⚽ Goal! {str(match_time)}",
description=f"""## {team1.name} {team1.goals}-{team2.goals} {team2.name}
{event.player} scores for {event.team}!""")
else:
event_embed = Embed(colour=event_colour, title=f"🧤 Saved {str(match_time)}",
description=f"""{event.player}'s shot was saved.""")
await message_channel.send(embed=event_embed, reference=match_start_message)
elif type(event) == soccer.Card:
if event.colour == soccer.CardColour.YELLOW:
event_embed = Embed(colour=event_colour, title=f"🟨 Yellow card {str(match_time)}",
description=f"{event.player} of {event.team} given a yellow card for a foul.")
elif event.colour == soccer.CardColour.RED:
event_embed = Embed(colour=event_colour, title=f"🟥 Red card! {str(match_time)}",
description=f"{event.player} of {event.team} has been given a red card and is sent off!")
await message_channel.send(embed=event_embed, reference=match_start_message)
if match_time.minute == 45:
half_time_embed = Embed(colour=bot_colour, title=f"⏱️ Half time {str(match_time)}",
description=f"Half time. Play will recommence in 15 minutes "
f"({int(minute_length * 15 / 60)} minutes and {(minute_length * 15) % 60} seconds IRL)...")
await message_channel.send(embed=half_time_embed, reference=match_start_message)
await asyncio.sleep(minute_length * 15)
if match_time.minute == 90 and match_time.half == soccer.Half.EXTRA_TIME:
extra_time_embed = Embed(colour=bot_colour, title=f"⏱️ Extra time {str(match_time)}",
description=f"{team1.name} and {team2.name} are still tied.\nExtra time will start in 5 minutes "
f"({int(minute_length * 5 / 60)} minutes and {(minute_length * 5) % 60} seconds IRL)...")
await message_channel.send(embed=extra_time_embed, reference=match_start_message)
await asyncio.sleep(minute_length * 5)
else:
pass
if penalties and team1.goals == team2.goals:
if real_time:
penalty_start_embed = Embed(colour=bot_colour, title=f"🥅️ Penalties",
description=f"{team1.name} and {team2.name} are still tied"
f"\nA penalty shootout will be played. "
f"\nPreparing for 3 minutes "
f"({int(minute_length * 3 / 60)} minutes and {(minute_length * 3) % 60} seconds IRL)...")
await message_channel.send(embed=penalty_start_embed, reference=match_start_message)
await asyncio.sleep(minute_length * 3 - 10)
penalty_start_embed = Embed(colour=bot_colour, title=f"⏱️ Penalties",
description=f"Penalties are starting now...")
await message_channel.send(embed=penalty_start_embed, reference=match_start_message)
await asyncio.sleep(10)
async for penalty_shot, team1, team2 in soccer.penalty_shootout(team1, team2):
if real_time:
async with message_channel.typing():
start_embed = Embed(colour=bot_colour, title=f"🏃 Penalty starting...",
description=f"""{penalty_shot.player} is taking his penalty for {penalty_shot.team}...""")
await message_channel.send(embed=start_embed, reference=match_start_message)
await asyncio.sleep(5)
if penalty_shot.goal is True:
penalty_embed = Embed(colour=bot_colour, title=f"⚽ Goal!",
description=f"""## {team1.name} {team1.shootout_goals}-{team2.shootout_goals} {team2.name}
{penalty_shot.player} scores a penalty for {penalty_shot.team}!""")
else:
penalty_embed = Embed(colour=bot_colour, title=f"🧤 Saved",
description=f"""{penalty_shot.player}'s penalty for {penalty_shot.team} was saved.""")
await message_channel.send(embed=penalty_embed, reference=match_start_message)
else:
pass
msg_text, match_embed_msg, events_embed, penalty_embed = penalty_match_embed(team1, team2)
embeds = [match_embed_msg, events_embed, penalty_embed]
else:
msg_text, match_embed_msg, events_embed = match_embed(team1, team2)
embeds = [match_embed_msg, events_embed]
if debug == "True":
embeds.append(debug_embed(team1, team2))
if match_start_message:
await message_channel.send(msg_text, embeds=embeds, reference=match_start_message)
else:
await interaction.followup.send(msg_text, embeds=embeds)