reworking a bunch of stuff

This commit is contained in:
Nikolaj
2024-10-28 13:05:06 +01:00
parent f21cbba726
commit bc59bf9b05
142 changed files with 1385 additions and 845 deletions

View File

@ -10,7 +10,6 @@ Gwendolyn is a discord bot that I made. It does a bunch of stuff.
* Roll Star Wars RPG dice! * Roll Star Wars RPG dice!
* Keep track of Star Wars RPG character sheets! * Keep track of Star Wars RPG character sheets!
* Query Wolfram Alpha * Query Wolfram Alpha
* Invest fake money in the stock market
* Play trivia, connect 4, blackjack, hangman and hex * Play trivia, connect 4, blackjack, hangman and hex
And much more!!! (not really. That's pretty much all it can do. See help files in resources directory for list of commands) And much more!!! (not really. That's pretty much all it can do. See help files in resources directory for list of commands)

View File

@ -1,6 +1,5 @@
"""The main module for Gwendolyn.""" """The main module for Gwendolyn."""
# pylint: disable=invalid-name
__all__ = ["funcs", "utils", "Gwendolyn"] __all__ = ["Gwendolyn"]
from .gwendolyn_client import Gwendolyn from .gwendolyn_client import Gwendolyn

9
gwendolyn/ext/misc.py Normal file
View File

@ -0,0 +1,9 @@
from interactions import Extension, slash_command, SlashContext
class MiscExtension(Extension):
"""Contains the miscellaneous commands."""
@slash_command()
async def hello(self, ctx: SlashContext):
"""Greet the bot."""
await ctx.send(f"Hello, I'm {self.bot.user.mention}!")

View File

@ -1,286 +0,0 @@
"""
Contains functions relating to invest commands.
*Classes*
---------
Invest
Contains all the code for the invest commands.
"""
import discord # Used for embeds
from discord_slash.context import SlashContext # Used for type hints
class Invest():
"""
Contains all the invest functions.
*Methods*
---------
getPrice(symbol: str) -> int
getPortfolio(user: str) -> str
buyStock(user: str, stock: str: buyAmount: int) -> str
sellStock(user: str, stock: str, buyAmount: int) -> str
parseInvest(ctx: discord_slash.context.SlashContext,
parameters: str)
"""
def __init__(self, bot):
"""Initialize the class."""
self.bot = bot
def getPrice(self, symbol: str):
"""
Get the price of a stock.
*Parameters*
------------
symbol: str
The symbol of the stock to get the price of.
*Returns*
---------
price: int
The price of the stock.
"""
res = self.bot.finnhub_client.quote(symbol.upper())
if res == {}:
return 0
else:
return int(res["c"] * 100)
def getPortfolio(self, user: str):
"""
Get the stock portfolio of a user.
*Parameters*
------------
user: str
The id of the user to get the portfolio of.
*Returns*
---------
portfolio: str
The portfolio.
"""
investments_database = self.bot.database["investments"]
userInvestments = investments_database.find_one({"_id": user})
user_name = self.bot.database_funcs.get_name(user)
if userInvestments in [None, {}]:
return f"{user_name} does not have a stock portfolio."
else:
portfolio = f"**Stock portfolio for {user_name}**"
for key, value in list(userInvestments["investments"].items()):
purchaseValue = value["purchased for"]
stockPrice = self.getPrice(key)
valueAtPurchase = value["value at purchase"]
purchasedStock = value["purchased"]
valueChange = (stockPrice / valueAtPurchase)
currentValue = int(valueChange * purchasedStock)
portfolio += f"\n**{key}**: ___{currentValue} GwendoBucks___"
if purchaseValue != "?":
portfolio += f" (purchased for {purchaseValue})"
return portfolio
def buyStock(self, user: str, stock: str, buyAmount: int):
"""
Buy an amount of a specific stock.
*Paramaters*
------------
user: str
The id of the user buying.
stock: str
The symbol of the stock to buy.
buyAmount: int
The amount of GwendoBucks to use to buy the stock.
*Returns*
---------
send_message: str
The message to return to the user.
"""
if buyAmount < 100:
return "You cannot buy stocks for less than 100 GwendoBucks"
elif self.bot.money.checkBalance(user) < buyAmount:
return "You don't have enough money for that"
elif self.getPrice(stock) <= 0:
return f"{stock} is not traded on the american market."
else:
investments_database = self.bot.database["investments"]
stockPrice = self.getPrice(stock)
userInvestments = investments_database.find_one({"_id": user})
self.bot.money.addMoney(user, -1*buyAmount)
stock = stock.upper()
if userInvestments is not None:
userInvestments = userInvestments["investments"]
if stock in userInvestments:
value = userInvestments[stock]
valueChange = (stockPrice / value["value at purchase"])
currentValue = int(valueChange * value["purchased"])
newAmount = currentValue + buyAmount
value_path = f"investments.{stock}.value at purchase"
updater = {"$set": {value_path: stockPrice}}
investments_database.update_one({"_id": user}, updater)
purchased_path = f"investments.{stock}.purchased"
updater = {"$set": {purchased_path: newAmount}}
investments_database.update_one({"_id": user}, updater)
if value["purchased for"] != "?":
purchasedFor_path = f"investments.{stock}.purchased for"
updater = {"$set": {purchasedFor_path: buyAmount}}
investments_database.update_one({"_id": user}, updater)
else:
updater = {
"$set": {
"investments.{stock}": {
"purchased": buyAmount,
"value at purchase": stockPrice,
"purchased for": buyAmount
}
}
}
investments_database.update_one({"_id": user}, updater)
else:
new_user = {
"_id": user,
"investments": {
stock: {
"purchased": buyAmount,
"value at purchase": stockPrice,
"purchased for": buyAmount
}
}
}
investments_database.insert_one(new_user)
user_name = self.bot.database_funcs.get_name(user)
send_message = "{} bought {} GwendoBucks worth of {} stock"
send_message = send_message.format(user_name, buyAmount, stock)
return send_message
def sellStock(self, user: str, stock: str, sellAmount: int):
"""
Sell an amount of a specific stock.
*Paramaters*
------------
user: str
The id of the user selling.
stock: str
The symbol of the stock to sell.
buyAmount: int
The amount of GwendoBucks to sell for.
*Returns*
---------
send_message: str
The message to return to the user.
"""
if sellAmount <= 0:
return "no"
else:
investments_database = self.bot.database["investments"]
user_data = investments_database.find_one({"_id": user})
userInvestments = user_data["investments"]
stock = stock.upper()
if userInvestments is not None and stock in userInvestments:
value = userInvestments[stock]
stockPrice = self.getPrice(stock)
priceChange = (stockPrice / value["value at purchase"])
purchasedAmount = int(priceChange * value["purchased"])
purchased_path = f"investments.{stock}.purchased"
updater = {"$set": {purchased_path: purchasedAmount}}
investments_database.update_one({"_id": user}, updater)
valueAtPurchase_path = f"investments.{stock}.value at purchase"
updater = {"$set": {valueAtPurchase_path: stockPrice}}
investments_database.update_one({"_id": user}, updater)
if value["purchased"] >= sellAmount:
self.bot.money.addMoney(user, sellAmount)
if sellAmount < value["purchased"]:
purchased_path = f"investments.{stock}.purchased"
updater = {"$inc": {purchased_path: -sellAmount}}
investments_database.update_one({"_id": user}, updater)
purchasedFor_path = f"investments.{stock}.purchased for"
updater = {"$set": {purchasedFor_path: "?"}}
investments_database.update_one({"_id": user}, updater)
else:
updater = {"$unset": {f"investments.{stock}": ""}}
investments_database.update_one({"_id": user}, updater)
user_name = self.bot.database_funcs.get_name(user)
send_message = "{} sold {} GwendoBucks worth of {} stock"
return send_message.format(user_name, sellAmount, stock)
else:
return f"You don't have enough {stock} stocks to do that"
else:
return f"You don't have any {stock} stock"
async def parseInvest(self, ctx: SlashContext, parameters: str):
"""
Parse an invest command. TO BE DELETED.
*Parameters*
------------
ctx: discord_slash.context.SlashContext
The context of the slash command.
parameters: str
The parameters of the command.
"""
await self.bot.defer(ctx)
user = f"#{ctx.author.id}"
if parameters.startswith("check"):
commands = parameters.split(" ")
if len(commands) == 1:
response = self.getPortfolio(user)
else:
price = self.getPrice(commands[1])
if price == 0:
response = "{} is not traded on the american market."
response = response.format(commands[0].upper())
else:
price = f"{price:,}".replace(",", ".")
response = self.bot.long_strings["Stock value"]
response = response.format(commands[1].upper(), price)
elif parameters.startswith("buy"):
commands = parameters.split(" ")
if len(commands) == 3:
response = self.buyStock(user, commands[1], int(commands[2]))
else:
response = self.bot.long_strings["Stock parameters"]
elif parameters.startswith("sell"):
commands = parameters.split(" ")
if len(commands) == 3:
response = self.sellStock(user, commands[1], int(commands[2]))
else:
response = self.bot.long_strings["Stock parameters"]
else:
response = "Incorrect parameters"
if response.startswith("**"):
responses = response.split("\n")
text = "\n".join(responses[1:])
embedParams = {
"title": responses[0],
"description": text,
"colour": 0x00FF00
}
em = discord.Embed(**embedParams)
await ctx.send(embed=em)
else:
await ctx.send(response)

View File

@ -1,135 +1,40 @@
""" from interactions import Client, Status
Contains the Gwendolyn class, a subclass of the discord command bot. from gwendolyn.utils import get_options, get_credentials, long_strings, log
*Classes* from pymongo import MongoClient # Used for database management
---------
Gwendolyn(discord.ext.commands.Bot) class Gwendolyn(Client):
"""
import os # Used for loading cogs in Gwendolyn.addCogs def __init__(self):
import finnhub # Used to add a finhub client to the bot """Initialize the bot."""
import discord # Used for discord.Intents and discord.Status initiation_parameters = {
import discord_slash # Used to initialized SlashCommands object "status": Status.DND,
import git # Used to pull when stopping "delete_unused_application_cmds": True
}
from discord.ext import commands # Used to inherit from commands.bot super().__init__(**initiation_parameters)
from pymongo import MongoClient # Used for database management
from gwendolyn.funcs import Money, StarWars, Games, Other, LookupFuncs self._add_clients_and_options()
from gwendolyn.utils import (get_options, get_credentials, log_this, # self._add_util_classes()
DatabaseFuncs, EventHandler, ErrorHandler, # self._add_function_containers()
long_strings) # self._add_cogs()
def _add_clients_and_options(self):
class Gwendolyn(commands.Bot): """Add all the client, option and credentials objects."""
""" self.long_strings = long_strings()
A multifunctional Discord bot. self.options = get_options()
self.credentials = get_credentials()
*Methods* mongo_user = self.credentials["mongo_db_user"]
--------- mongo_password = self.credentials["mongo_db_password"]
log(messages: Union[str, list], channel: str = "", mongo_url = f"mongodb+srv://{mongo_user}:{mongo_password}@gwendolyn"
level: int = 20) mongo_url += ".qkwfy.mongodb.net/Gwendolyn?retryWrites=true&w=majority"
stop(ctx: discord_slash.context.SlashContext) database_client = MongoClient(mongo_url)
defer(ctx: discord_slash.context.SlashContext)
""" if self.options["testing"]:
# pylint: disable=too-many-instance-attributes self.log("Testing mode")
self.database = database_client["Gwendolyn-Test"]
def __init__(self): else:
"""Initialize the bot.""" self.database = database_client["Gwendolyn"]
intents = discord.Intents.default()
intents.members = True def log(self, messages, channel: str = "", level: int = 20): # pylint:disable=no-self-use
initiation_parameters = { """Log a message. Described in utils/util_functions.py."""
"command_prefix": " ", log(messages, channel, level)
"case_insensitive": True,
"intents": intents,
"status": discord.Status.dnd
}
super().__init__(**initiation_parameters)
self._add_clients_and_options()
self._add_util_classes()
self._add_function_containers()
self._add_cogs()
def _add_clients_and_options(self):
"""Add all the client, option and credentials objects."""
self.long_strings = long_strings()
self.options = get_options()
self.credentials = get_credentials()
finnhub_key = self.credentials["finnhub_key"]
self.finnhub_client = finnhub.Client(api_key=finnhub_key)
mongo_user = self.credentials["mongo_db_user"]
mongo_password = self.credentials["mongo_db_password"]
mongo_url = f"mongodb+srv://{mongo_user}:{mongo_password}@gwendolyn"
mongo_url += ".qkwfy.mongodb.net/Gwendolyn?retryWrites=true&w=majority"
database_clint = MongoClient(mongo_url)
if self.options["testing"]:
self.log("Testing mode")
self.database = database_clint["Gwendolyn-Test"]
else:
self.database = database_clint["Gwendolyn"]
def _add_util_classes(self):
"""Add all the classes used as utility."""
self.database_funcs = DatabaseFuncs(self)
self.event_handler = EventHandler(self)
self.error_handler = ErrorHandler(self)
slash_parameters = {
"sync_commands": True,
"sync_on_cog_reload": True,
"override_type": True
}
self.slash = discord_slash.SlashCommand(self, **slash_parameters)
def _add_function_containers(self):
"""Add all the function containers used for commands."""
self.star_wars = StarWars(self)
self.other = Other(self)
self.lookup_funcs = LookupFuncs(self)
self.games = Games(self)
self.money = Money(self)
def _add_cogs(self):
"""Load cogs."""
for filename in os.listdir("./gwendolyn/cogs"):
if filename.endswith(".py"):
self.load_extension(f"gwendolyn.cogs.{filename[:-3]}")
def log(self, messages, channel: str = "", level: int = 20): # pylint:disable=no-self-use
"""Log a message. Described in utils/util_functions.py."""
log_this(messages, channel, level)
async def stop(self, ctx: discord_slash.context.SlashContext):
"""
Stop the bot, and stop running games.
Only stops the bot if the user in ctx.author is one of the
admins given in options.txt.
*parameters*
------------
ctx: discord_slash.context.SlashContext
The context of the "/stop" slash command.
"""
if f"#{ctx.author.id}" in self.options["admins"]:
await ctx.send("Pulling git repo and restarting...")
await self.change_presence(status=discord.Status.offline)
self.database_funcs.wipe_games()
if not self.options["testing"]:
git_client = git.cmd.Git("")
git_client.pull()
self.log("Logging out", level=25)
await self.close()
else:
log_message = f"{ctx.author.display_name} tried to stop me!"
self.log(log_message, str(ctx.channel_id))
await ctx.send(f"I don't think I will, {ctx.author.display_name}")
async def defer(self, ctx: discord_slash.context.SlashContext):
"""Send a "Gwendolyn is thinking" message to the user."""
try:
await ctx.defer()
except discord_slash.error.AlreadyResponded:
self.log("defer failed")

View File

@ -1,3 +0,0 @@
`/invest` vil vise dig hvilke aktier du har. `/invest check [symbol]` viser dig en akties nuværende pris, hvor [symbol] er forkortelsen for firmaet. GwendoBucks er lig med 1 amerikans cent.
`/invest buy [symbol] [pris]` lader dig købe aktier. [pris] er mængden af GwendoBucks du bruger på at købe. Du kan købe for færre GwendoBucks end en enkelt akties pris, men ikke for mindre end 100 GwendoBucks.
`/invest buy [symbol] [pris]` lader dig sælge dine aktier. Du kan godt sælge for mindre end 100 GwendoBucks.

View File

@ -10,7 +10,6 @@
`/star_wars_character` - Lader dig lave en Star Wars karakter. `/star_wars_character` - Lader dig lave en Star Wars karakter.
`/star_wars_roll` - Lader dig rulle Star Wars terninger. `/star_wars_roll` - Lader dig rulle Star Wars terninger.
`/balance` - Viser dig hvor mange GwendoBucks du har. `/balance` - Viser dig hvor mange GwendoBucks du har.
`/invest` - Lader dig investere dine GwendoBucks i aktiemarkedet.
`/blackjack` - Lader dig spille et spil blackjack. `/blackjack` - Lader dig spille et spil blackjack.
`/trivia` - Lader dig spille et spil trivia, hvor du kan tjene GwendoBucks. `/trivia` - Lader dig spille et spil trivia, hvor du kan tjene GwendoBucks.
`/connect_four` - Lader dig spille et spil fire på stribe. `/connect_four` - Lader dig spille et spil fire på stribe.

View File

@ -206,18 +206,6 @@
"name" : "undo", "name" : "undo",
"description" : "Undo your last hex move" "description" : "Undo your last hex move"
}, },
"invest" : {
"name" : "invest",
"description" : "Invest GwendoBucks in the stock market",
"options" : [
{
"name" : "parameters",
"description" : "The parameters for the command",
"type" : 3,
"required" : "false"
}
]
},
"image" : { "image" : {
"name" : "image", "name" : "image",
"description" : "Get a random image from Bing" "description" : "Get a random image from Bing"

View File

@ -59,7 +59,7 @@
"gwendolyn/resources/star_wars/destinyPoints.txt": "", "gwendolyn/resources/star_wars/destinyPoints.txt": "",
"gwendolyn/resources/movies.txt": "The Room", "gwendolyn/resources/movies.txt": "The Room",
"gwendolyn/resources/names.txt": "Gandalf\n", "gwendolyn/resources/names.txt": "Gandalf\n",
"credentials.txt" : "Bot token: TOKEN\nFinnhub API key: KEY\nWordnik API Key: KEY\nMongoDB user: USERNAME\nMongoDB password: PASSWORD\nWolframAlpha AppID: APPID\nRadarr API key: KEY\nSonarr API key: KEY\nqBittorrent username: USER\nqBittorrent password: PASSWORD", "credentials.txt" : "Bot token: TOKEN\nWordnik API Key: KEY\nMongoDB user: USERNAME\nMongoDB password: PASSWORD\nWolframAlpha AppID: APPID\nRadarr API key: KEY\nSonarr API key: KEY\nqBittorrent username: USER\nqBittorrent password: PASSWORD",
"options.txt" : "Testing: True\nTesting guild ids:\nAdmins:" "options.txt" : "Testing: True\nTesting guild ids:\nAdmins:"
}, },
"folder" : [ "folder" : [

View File

@ -1,12 +1,5 @@
"""A collections of utilities used by Gwendolyn and her functions.""" """A collections of utilities used by Gwendolyn and her functions."""
__all__ = ["get_options", "get_credentials", "DatabaseFuncs", "EventHandler", __all__ = ["make_files","get_options", "get_credentials", "long_strings", "log"]
"ErrorHandler", "get_params", "log_this", "cap", "make_files",
"replace_multiple", "emoji_to_command"]
from .helper_classes import DatabaseFuncs from .util_functions import make_files, get_options, get_credentials, long_strings, log
from .event_handlers import EventHandler, ErrorHandler
from .util_functions import (get_params, log_this, cap, make_files,
replace_multiple, emoji_to_command, long_strings,
sanitize, get_options, get_credentials, encode_id,
decode_id)

View File

@ -1,37 +1,7 @@
"""
Contains utility functions used by parts of the bot.
*Functions*
-----------
sanitize(data: str, lower_case_value: bool = false) -> dict
get_options() -> dict
get_credentials() -> dict
long_strings() -> dict
get_params() -> dict
log_this(messages: Union[str, list], channel: str = "",
level: int = 20)
cap(s: str) -> str
make_files()
replace_multiple(main_string: str, to_be_replaced: list,
new_string: str) -> str
emoji_to_command(emoji: str) -> str
"""
import string
import json # Used by long_strings(), get_params() and make_files() import json # Used by long_strings(), get_params() and make_files()
import logging # Used for logging import logging # Used for logging
import os # Used by make_files() to check if files exist import os # Used by make_files() to check if files exist
import sys # Used to specify printing for logging import sys # Used to specify printing for logging
import imdb # Used to disable logging for the module
BASE_37 = ":" + string.digits + string.ascii_uppercase
BASE_128 = list(
string.digits +
string.ascii_letters +
"!#$€£¢¥¤&%()*+,-./;:<=>?@[]_{|}~ `¦§©®«»±µ·¿əʒ" +
"ÆØÅÐÉÈÊÇÑÖ" +
"æøåðéèêçñö"
)
# All of this is logging configuration # All of this is logging configuration
FORMAT = " %(asctime)s | %(name)-16s | %(levelname)-8s | %(message)s" FORMAT = " %(asctime)s | %(name)-16s | %(levelname)-8s | %(message)s"
@ -53,10 +23,6 @@ handler.setFormatter(logging.Formatter(fmt=PRINTFORMAT, datefmt=DATEFORMAT))
printer.addHandler(handler) printer.addHandler(handler)
printer.propagate = False printer.propagate = False
imdb._logging.setLevel("CRITICAL") # pylint: disable=protected-access
# Basically disables imdbpy logging, since it's being printed to the
# terminal.
def sanitize(data: str, lower_case_value: bool = False): def sanitize(data: str, lower_case_value: bool = False):
""" """
Sanitize and create a dictionary from a string. Sanitize and create a dictionary from a string.
@ -134,7 +100,6 @@ def get_credentials():
credentials = {} credentials = {}
credentials["token"] = data["bot token"] credentials["token"] = data["bot token"]
credentials["finnhub_key"] = data["finnhub api key"]
credentials["wordnik_key"] = data["wordnik api key"] credentials["wordnik_key"] = data["wordnik api key"]
credentials["mongo_db_user"] = data["mongodb user"] credentials["mongo_db_user"] = data["mongodb user"]
credentials["mongo_db_password"] = data["mongodb password"] credentials["mongo_db_password"] = data["mongodb password"]
@ -161,30 +126,7 @@ def long_strings():
return data return data
def log(messages, channel: str = "", level: int = 20):
def get_params():
"""
Get the slash command parameters.
*Returns*
---------
params: dict
The parameters for every slash command.
"""
path = "gwendolyn/resources/slash_parameters.json"
with open(path, "r", encoding="utf-8") as file_pointer:
slash_parameters = json.load(file_pointer)
options = get_options()
if options["testing"]:
for parameter in slash_parameters:
slash_parameters[parameter]["guild_ids"] = options["guild_ids"]
return slash_parameters
def log_this(messages, channel: str = "", level: int = 20):
""" """
Log something in Gwendolyn's logs. Log something in Gwendolyn's logs.
@ -222,49 +164,19 @@ def log_this(messages, channel: str = "", level: int = 20):
for log_message in messages: for log_message in messages:
logger.log(level, log_message) logger.log(level, log_message)
def cap(input_string: str):
"""
Capitalize a string like a movie title.
That means "of" and "the" are not capitalized.
*Parameters*
------------
input_string: str
The string to capitalized.
*Returns*
---------
return_string: str
The capitalized string.
"""
no_caps_list = ["of", "the"]
word_number = 0
string_list = input_string.split()
return_string = ''
for word in string_list:
word_number += 1
if word not in no_caps_list or word_number == 1:
word = word.capitalize()
return_string += word+" "
return_string = return_string[:-1]
return return_string
def make_files(): def make_files():
"""Create all the files and directories needed by Gwendolyn.""" """Create all the files and directories needed by Gwendolyn."""
def make_json_file(path, content): def make_json_file(path, content):
"""Create json file if it doesn't exist.""" """Create json file if it doesn't exist."""
if not os.path.isfile(path): if not os.path.isfile(path):
log_this(path.split("/")[-1]+" didn't exist. Making it now.") log(path.split("/")[-1]+" didn't exist. Making it now.")
with open(path, "w", encoding="utf-8") as file_pointer: with open(path, "w", encoding="utf-8") as file_pointer:
json.dump(content, file_pointer, indent=4) json.dump(content, file_pointer, indent=4)
def make_txt_file(path, content): def make_txt_file(path, content):
"""Create txt file if it doesn't exist.""" """Create txt file if it doesn't exist."""
if not os.path.isfile(path): if not os.path.isfile(path):
log_this(path.split("/")[-1]+" didn't exist. Making it now.") log(path.split("/")[-1]+" didn't exist. Making it now.")
with open(path, "w", encoding="utf-8") as file_pointer: with open(path, "w", encoding="utf-8") as file_pointer:
file_pointer.write(content) file_pointer.write(content)
@ -272,121 +184,18 @@ def make_files():
"""Create directory if it doesn't exist.""" """Create directory if it doesn't exist."""
if not os.path.isdir(path): if not os.path.isdir(path):
os.makedirs(path) os.makedirs(path)
log_this("The "+path.split("/")[-1]+" directory didn't exist") log("The "+path.split("/")[-1]+" directory didn't exist")
file_path = "gwendolyn/resources/starting_files.json" file_path = "gwendolyn/resources/starting_files.json"
with open(file_path, "r", encoding="utf-8") as file_pointer: with open(file_path, "r", encoding="utf-8") as file_pointer:
data = json.load(file_pointer) data = json.load(file_pointer)
for path in data["folder"]:
directory(path)
for path, content in data["json"].items(): for path, content in data["json"].items():
make_json_file(path, content) make_json_file(path, content)
for path, content in data["txt"].items(): for path, content in data["txt"].items():
make_txt_file(path, content) make_txt_file(path, content)
for path in data["folder"]:
directory(path)
def replace_multiple(main_string: str, to_be_replaced: list, new_string: str):
"""
Replace multiple substrings in a string with the same substring.
*Parameters*
------------
main_string: str
The string to replace substrings in.
to_be_replaced: list
The substrings to replace.
new_string: str
The string to replace the substrings with.
*Returns*
---------
main_string: str
The string with the substrings replaced.
"""
# Iterate over the strings to be replaced
for elem in to_be_replaced:
# Check if string is in the main string
if elem in main_string:
# Replace the string
main_string = main_string.replace(elem, new_string)
return main_string
def emoji_to_command(emoji: str):
"""
Convert emoji to text.
*Parameters*
------------
emoji: str
The emoji to decipher.
*Returns*
---------
: str
The deciphered string.
"""
if emoji == "1":
return_value = 1
elif emoji == "2":
return_value = 2
elif emoji == "3":
return_value = 3
elif emoji == "4":
return_value = 4
elif emoji == "5":
return_value = 5
elif emoji == "6":
return_value = 6
elif emoji == "7":
return_value = 7
elif emoji == "🎲":
return_value = "roll"
elif emoji == "":
return_value = "none"
elif emoji == "✔️":
return_value = 1
else:
return_value = ""
return return_value
def encode_id(info: list):
letters = list(":".join(info))
dec = 0
for i, letter in enumerate(letters):
try:
dec += (37**i) * BASE_37.index(letter.upper())
except ValueError:
log_this(f"Could not encode letter {letter}", level=30)
custom_id = []
while dec:
custom_id.append(BASE_128[dec % 128])
dec = dec // 128
custom_id = ''.join(custom_id)
log_this(f"Encoded {info} to {custom_id}")
return custom_id
def decode_id(custom_id: str):
letters = list(custom_id)
dec = 0
for i, letter in enumerate(letters):
dec += (128**i) * BASE_128.index(letter)
info_string = []
while dec:
info_string.append(BASE_37[dec % 37])
dec = dec // 37
info = ''.join(info_string).split(':')
log_this(f"Decoded {custom_id} to be {info}")
return ''.join(info_string).split(':')

View File

@ -0,0 +1,6 @@
"""The main module for Gwendolyn."""
# pylint: disable=invalid-name
__all__ = ["funcs", "utils", "Gwendolyn"]
from .gwendolyn_client import Gwendolyn

View File

@ -1,8 +1,8 @@
"""Contains all the cogs that deal with game commands.""" """Contains all the cogs that deal with game commands."""
from discord.ext import commands # Has the cog class from discord.ext import commands # Has the cog class
from discord_slash import cog_ext # Used for slash commands from interactions import cog_ext # Used for slash commands
from gwendolyn.utils import get_params # pylint: disable=import-error from gwendolyn_old.utils import get_params # pylint: disable=import-error
params = get_params() params = get_params()
@ -24,11 +24,6 @@ class GamesCog(commands.Cog):
"""Give another user an amount of GwendoBucks.""" """Give another user an amount of GwendoBucks."""
await self.bot.money.giveMoney(ctx, user, amount) await self.bot.money.giveMoney(ctx, user, amount)
@cog_ext.cog_slash(**params["invest"])
async def invest(self, ctx, parameters="check"):
"""Invest GwendoBucks in the stock market."""
await self.bot.games.invest.parseInvest(ctx, parameters)
@cog_ext.cog_slash(**params["trivia"]) @cog_ext.cog_slash(**params["trivia"])
async def trivia(self, ctx, answer=""): async def trivia(self, ctx, answer=""):
"""Run a game of trivia.""" """Run a game of trivia."""

View File

@ -1,8 +1,8 @@
"""Contains the LookupCog, which deals with the lookup commands.""" """Contains the LookupCog, which deals with the lookup commands."""
from discord.ext import commands # Has the cog class from discord.ext import commands # Has the cog class
from discord_slash import cog_ext # Used for slash commands from interactions import cog_ext # Used for slash commands
from gwendolyn.utils import get_params # pylint: disable=import-error from gwendolyn_old.utils import get_params # pylint: disable=import-error
params = get_params() params = get_params()

View File

@ -1,9 +1,9 @@
"""Contains the MiscCog, which deals with miscellaneous commands.""" """Contains the MiscCog, which deals with miscellaneous commands."""
from discord.ext import commands # Has the cog class from discord.ext import commands # Has the cog class
from discord_slash import cog_ext # Used for slash commands from interactions import cog_ext # Used for slash commands
from discord_slash.context import SlashContext from interactions import SlashContext
from gwendolyn.utils import get_params # pylint: disable=import-error from gwendolyn_old.utils import get_params # pylint: disable=import-error
params = get_params() params = get_params()

View File

@ -1,8 +1,8 @@
"""Contains the StarWarsCog, which deals with Star Wars commands.""" """Contains the StarWarsCog, which deals with Star Wars commands."""
from discord.ext import commands from discord.ext import commands
from discord_slash import cog_ext from interactions import cog_ext
from gwendolyn.utils import get_params # pylint: disable=import-error from gwendolyn_old.utils import get_params # pylint: disable=import-error
params = get_params() params = get_params()

View File

@ -12,13 +12,13 @@ import math # Used for flooring decimal numbers
import datetime # Used to generate the game id import datetime # Used to generate the game id
import asyncio # Used for sleeping import asyncio # Used for sleeping
from discord_slash.context import InteractionContext as IntCont # Used for from interactions import InteractionContext as IntCont # Used for
# typehints # typehints
from discord_slash.context import ComponentContext from interactions import ComponentContext
from discord.abc import Messageable from discord.abc import Messageable
from PIL import Image from PIL import Image
from gwendolyn.utils import replace_multiple from gwendolyn_old.utils import replace_multiple
from .game_base import CardGame, CardDrawer from .game_base import CardGame, CardDrawer

View File

@ -15,13 +15,13 @@ import discord # Used for typehints, discord.file and to check whether
# the opponent in ConnectFour.start is a discord.User # the opponent in ConnectFour.start is a discord.User
from PIL import Image, ImageDraw, ImageFont # Used by DrawConnectFour() from PIL import Image, ImageDraw, ImageFont # Used by DrawConnectFour()
from discord_slash.context import SlashContext, ComponentContext # Used for from interactions import SlashContext, ComponentContext # Used for
# typehints # typehints
from discord_slash.utils.manage_components import (create_button, from interactions.utils.manage_components import (create_button,
create_actionrow) create_actionrow)
from discord_slash.model import ButtonStyle from interactions.model import ButtonStyle
from gwendolyn.utils import encode_id from gwendolyn_old.utils import encode_id
from .game_base import BoardGame from .game_base import BoardGame
ROWCOUNT = 6 ROWCOUNT = 6

View File

@ -4,14 +4,13 @@ from typing import Union
from discord import File, User from discord import File, User
from discord.abc import Messageable from discord.abc import Messageable
from discord_slash.utils.manage_components import (create_button, from interactions import Button, ActionRow
create_actionrow) from interactions import InteractionContext as IntCont
from discord_slash.context import InteractionContext as IntCont
from PIL import ImageFont, Image, ImageDraw from PIL import ImageFont, Image, ImageDraw
from gwendolyn.exceptions import GameNotInDatabase from gwendolyn_old.exceptions import GameNotInDatabase
from gwendolyn.utils import encode_id from gwendolyn_old.utils import encode_id
class GameBase(): class GameBase():
"""The base class for the games.""" """The base class for the games."""
@ -29,7 +28,7 @@ class GameBase():
button_objects = [] button_objects = []
for label, data, style in buttons: for label, data, style in buttons:
custom_id = encode_id([self.game_name] + data) custom_id = encode_id([self.game_name] + data)
button_objects.append(create_button( button_objects.append(Button(
style=style, style=style,
label=label, label=label,
custom_id=custom_id custom_id=custom_id
@ -37,7 +36,7 @@ class GameBase():
action_rows = [] action_rows = []
for i in range(((len(button_objects)-1)//5)+1): for i in range(((len(button_objects)-1)//5)+1):
action_rows.append(create_actionrow(*button_objects[i*5:i*5+5])) action_rows.append(ActionRow(*button_objects[i*5:i*5+5]))
return action_rows return action_rows

View File

@ -8,7 +8,6 @@ Has a container for game functions.
""" """
from .invest import Invest
from .trivia import Trivia from .trivia import Trivia
from .blackjack import Blackjack from .blackjack import Blackjack
from .connect_four import ConnectFour from .connect_four import ConnectFour
@ -25,8 +24,6 @@ class Games():
------------ ------------
bot: Gwendolyn bot: Gwendolyn
The instance of Gwendolyn. The instance of Gwendolyn.
invest
Contains investment functions.
blackjack blackjack
Contains blackjack functions. Contains blackjack functions.
connect_four connect_four
@ -41,7 +38,6 @@ class Games():
"""Initialize the container.""" """Initialize the container."""
self.bot = bot self.bot = bot
self.invest = Invest(bot)
self.trivia = Trivia(bot) self.trivia = Trivia(bot)
self.blackjack = Blackjack(bot) self.blackjack = Blackjack(bot)
self.connect_four = ConnectFour(bot) self.connect_four = ConnectFour(bot)

View File

@ -16,14 +16,14 @@ import random # Used to draw poorly
import requests # Used for getting the word in Hangman.start() import requests # Used for getting the word in Hangman.start()
import discord # Used for discord.file and type hints import discord # Used for discord.file and type hints
from discord_slash.utils.manage_components import (create_button, from interactions.utils.manage_components import (create_button,
create_actionrow) create_actionrow)
from discord_slash.model import ButtonStyle from interactions.model import ButtonStyle
from discord_slash.context import SlashContext, ComponentContext from interactions import SlashContext, ComponentContext
# Used for typehints # Used for typehints
from PIL import ImageDraw, Image, ImageFont # Used to draw the image from PIL import ImageDraw, Image, ImageFont # Used to draw the image
from gwendolyn.utils import encode_id from gwendolyn_old.utils import encode_id
class Hangman(): class Hangman():
""" """

View File

@ -6,7 +6,7 @@ Contains the code that deals with money.
Money Money
Deals with money. Deals with money.
""" """
import discord_slash # Used for typehints import interactions # Used for typehints
import discord # Used for typehints import discord # Used for typehints
@ -17,9 +17,9 @@ class Money():
*Methods* *Methods*
--------- ---------
checkBalance(user: str) checkBalance(user: str)
sendBalance(ctx: discord_slash.context.SlashContext) sendBalance(ctx: interactions.SlashContext)
addMoney(user: str, amount: int) addMoney(user: str, amount: int)
giveMoney(ctx: discord_slash.context.SlashContext, user: discord.User, giveMoney(ctx: interactions.SlashContext, user: discord.User,
amount: int) amount: int)
*Attributes* *Attributes*
@ -58,13 +58,13 @@ class Money():
else: else:
return 0 return 0
async def sendBalance(self, ctx: discord_slash.context.SlashContext): async def sendBalance(self, ctx: interactions.SlashContext):
""" """
Get your own account balance. Get your own account balance.
*Parameters* *Parameters*
------------ ------------
ctx: discord_slash.context.SlashContext ctx: interactions.SlashContext
The context of the command. The context of the command.
""" """
await self.bot.defer(ctx) await self.bot.defer(ctx)
@ -104,14 +104,14 @@ class Money():
self.database["users"].insert_one(new_user) self.database["users"].insert_one(new_user)
# Transfers money from one user to another # Transfers money from one user to another
async def giveMoney(self, ctx: discord_slash.context.SlashContext, async def giveMoney(self, ctx: interactions.SlashContext,
user: discord.User, amount: int): user: discord.User, amount: int):
""" """
Give someone else money from your account. Give someone else money from your account.
*Parameters* *Parameters*
------------ ------------
ctx: discord_slash.context.SlashContext ctx: interactions.SlashContext
The context of the command. The context of the command.
user: discord.User user: discord.User
The user to give money. The user to give money.

View File

@ -11,7 +11,7 @@ import json # Used to read data from api
import random # Used to shuffle answers import random # Used to shuffle answers
import asyncio # Used to sleep import asyncio # Used to sleep
from discord_slash.context import SlashContext # Used for type hints from interactions import SlashContext # Used for type hints
class Trivia(): class Trivia():

View File

@ -5,7 +5,7 @@ from PIL import Image, ImageDraw
from .game_base import DatabaseGame, BaseDrawer from .game_base import DatabaseGame, BaseDrawer
from gwendolyn.exceptions import GameNotInDatabase from gwendolyn_old.exceptions import GameNotInDatabase
IMAGE_MARGIN = 90 IMAGE_MARGIN = 90
SQUARE_SIZE = 150 SQUARE_SIZE = 150

View File

@ -2,7 +2,7 @@ import math
import json import json
import discord import discord
from gwendolyn.utils import cap from gwendolyn_old.utils import cap
STATS = [ STATS = [
"strength", "strength",

View File

@ -7,11 +7,11 @@ import imdb
import discord import discord
import xmltodict import xmltodict
from discord_slash.utils.manage_components import (create_button, from interactions.utils.manage_components import (create_button,
create_actionrow) create_actionrow)
from discord_slash.model import ButtonStyle from interactions.model import ButtonStyle
from gwendolyn.utils import encode_id from gwendolyn_old.utils import encode_id
class Plex(): class Plex():
"""Container for Plex functions and commands.""" """Container for Plex functions and commands."""

View File

@ -0,0 +1,132 @@
"""
Contains the Gwendolyn class, a subclass of the discord command bot.
*Classes*
---------
Gwendolyn(discord.ext.commands.Bot)
"""
import os # Used for loading cogs in Gwendolyn.addCogs
import discord # Used for discord.Intents and discord.Status
import interactions # Used to initialized SlashCommands object
import git # Used to pull when stopping
from discord.ext import commands # Used to inherit from commands.bot
from pymongo import MongoClient # Used for database management
from gwendolyn_old.funcs import Money, StarWars, Games, Other, LookupFuncs
from gwendolyn_old.utils import (get_options, get_credentials, log_this,
DatabaseFuncs, EventHandler, ErrorHandler,
long_strings)
class Gwendolyn(commands.Bot):
"""
A multifunctional Discord bot.
*Methods*
---------
log(messages: Union[str, list], channel: str = "",
level: int = 20)
stop(ctx: interactions.SlashContext)
defer(ctx: interactions.SlashContext)
"""
# pylint: disable=too-many-instance-attributes
def __init__(self):
"""Initialize the bot."""
intents = discord.Intents.default()
intents.members = True
initiation_parameters = {
"command_prefix": " ",
"case_insensitive": True,
"intents": intents,
"status": discord.Status.dnd
}
super().__init__(**initiation_parameters)
self._add_clients_and_options()
self._add_util_classes()
self._add_function_containers()
self._add_cogs()
def _add_clients_and_options(self):
"""Add all the client, option and credentials objects."""
self.long_strings = long_strings()
self.options = get_options()
self.credentials = get_credentials()
mongo_user = self.credentials["mongo_db_user"]
mongo_password = self.credentials["mongo_db_password"]
mongo_url = f"mongodb+srv://{mongo_user}:{mongo_password}@gwendolyn"
mongo_url += ".qkwfy.mongodb.net/Gwendolyn?retryWrites=true&w=majority"
database_clint = MongoClient(mongo_url)
if self.options["testing"]:
self.log("Testing mode")
self.database = database_clint["Gwendolyn-Test"]
else:
self.database = database_clint["Gwendolyn"]
def _add_util_classes(self):
"""Add all the classes used as utility."""
self.database_funcs = DatabaseFuncs(self)
self.event_handler = EventHandler(self)
self.error_handler = ErrorHandler(self)
slash_parameters = {
"sync_commands": True,
"sync_on_cog_reload": True,
"override_type": True
}
self.slash = interactions.SlashCommand(self, **slash_parameters)
def _add_function_containers(self):
"""Add all the function containers used for commands."""
self.star_wars = StarWars(self)
self.other = Other(self)
self.lookup_funcs = LookupFuncs(self)
self.games = Games(self)
self.money = Money(self)
def _add_cogs(self):
"""Load cogs."""
for filename in os.listdir("./gwendolyn/cogs"):
if filename.endswith(".py"):
self.load_extension(f"gwendolyn.cogs.{filename[:-3]}")
def log(self, messages, channel: str = "", level: int = 20): # pylint:disable=no-self-use
"""Log a message. Described in utils/util_functions.py."""
log_this(messages, channel, level)
async def stop(self, ctx: interactions.SlashContext):
"""
Stop the bot, and stop running games.
Only stops the bot if the user in ctx.author is one of the
admins given in options.txt.
*parameters*
------------
ctx: interactions.SlashContext
The context of the "/stop" slash command.
"""
if f"#{ctx.author.id}" in self.options["admins"]:
await ctx.send("Pulling git repo and restarting...")
await self.change_presence(status=discord.Status.offline)
self.database_funcs.wipe_games()
if not self.options["testing"]:
git_client = git.cmd.Git("")
git_client.pull()
self.log("Logging out", level=25)
await self.close()
else:
log_message = f"{ctx.author.display_name} tried to stop me!"
self.log(log_message, str(ctx.channel_id))
await ctx.send(f"I don't think I will, {ctx.author.display_name}")
async def defer(self, ctx: interactions.SlashContext):
"""Send a "Gwendolyn is thinking" message to the user."""
try:
await ctx.defer()
except interactions.error.AlreadyResponded:
self.log("defer failed")

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 174 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 178 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 182 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 178 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 158 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 168 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 180 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 176 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 179 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 183 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 190 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 160 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

Some files were not shown because too many files have changed in this diff Show More