Compare commits
7 Commits
master
...
complete-r
Author | SHA1 | Date | |
---|---|---|---|
7be6103a91 | |||
e71ba34371 | |||
2f4e606fbf | |||
e955ef4e28 | |||
eb2960aa10 | |||
bc59bf9b05 | |||
f21cbba726 |
4
.gitignore
vendored
@ -150,9 +150,7 @@ static
|
||||
|
||||
|
||||
.vscode/
|
||||
token.txt
|
||||
credentials.txt
|
||||
options.txt
|
||||
.env
|
||||
gwendolyn/resources/star_wars/destinyPoints.txt
|
||||
gwendolyn/resources/plex/
|
||||
gwendolyn/resources/games/hilo/
|
||||
|
@ -1,6 +1,4 @@
|
||||
# Gwendolyn
|
||||
[](https://www.codacy.com?utm_source=github.com&utm_medium=referral&utm_content=NikolajDanger/Gwendolyn&utm_campaign=Badge_Grade)
|
||||
|
||||
Gwendolyn is a discord bot that I made. It does a bunch of stuff.
|
||||
|
||||
## Stuff it can do
|
||||
@ -12,7 +10,6 @@ Gwendolyn is a discord bot that I made. It does a bunch of stuff.
|
||||
* Roll Star Wars RPG dice!
|
||||
* Keep track of Star Wars RPG character sheets!
|
||||
* Query Wolfram Alpha
|
||||
* Invest fake money in the stock market
|
||||
* 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)
|
||||
|
@ -1,6 +1,5 @@
|
||||
"""The main module for Gwendolyn."""
|
||||
# pylint: disable=invalid-name
|
||||
|
||||
__all__ = ["funcs", "utils", "Gwendolyn"]
|
||||
__all__ = ["Gwendolyn"]
|
||||
|
||||
from .gwendolyn_client import Gwendolyn
|
||||
|
@ -1,11 +1,14 @@
|
||||
"""Exceptions for Gwendolyn"""
|
||||
|
||||
class GameNotInDatabase(Exception):
|
||||
def __init__(self, game: str, channel: str):
|
||||
self.message = f"There is no {game} game in channel {channel}"
|
||||
class NoToken(Exception):
|
||||
def __init__(self) -> None:
|
||||
self.message = "No discord bot token has been set in the .env file"
|
||||
super().__init__(self.message)
|
||||
|
||||
class InvalidInteraction(Exception):
|
||||
def __init__(self, custom_id: str, decoded: str):
|
||||
self.message = f"{custom_id = }, {decoded = }"
|
||||
class CannotConnectToService(Exception):
|
||||
def __init__(self, service: str) -> None:
|
||||
self.message = f"Cannot connect to {service}"
|
||||
super().__init__(self.message)
|
||||
|
||||
class ServiceNotProvided(Exception):
|
||||
def __init__(self, service: str) -> None:
|
||||
self.message = f"A {service} instance was not provided"
|
||||
super().__init__(self.message)
|
12
gwendolyn/ext/better_netflix.py
Normal file
@ -0,0 +1,12 @@
|
||||
from interactions import Extension, slash_command, SlashContext
|
||||
from gwendolyn.utils import PARAMS as params
|
||||
|
||||
class BetterNeflixExtension(Extension):
|
||||
"""Contains the Better Netflix commands."""
|
||||
@slash_command(**params["better_netflix"]["movie"])
|
||||
async def movie(self, ctx: SlashContext):
|
||||
await self.bot.better_netflix.movie(ctx)
|
||||
|
||||
@slash_command(**params["better_netflix"]["show"])
|
||||
async def show(self, ctx: SlashContext):
|
||||
await self.bot.better_netflix.show(ctx)
|
21
gwendolyn/ext/events.py
Normal file
@ -0,0 +1,21 @@
|
||||
from interactions import Extension, listen, Status, Activity, ActivityType
|
||||
from interactions.api.events import Startup, Error
|
||||
|
||||
class EventExtension(Extension):
|
||||
"""Listens to miscellaneous events."""
|
||||
|
||||
@listen(Startup)
|
||||
async def startup(self, _):
|
||||
"""Log and sets status when it logs in."""
|
||||
name = self.bot.user.username
|
||||
userid = str(self.bot.user.id)
|
||||
logged_in_message = f"Logged in as {name}, {userid}"
|
||||
self.bot.log(logged_in_message, level=25)
|
||||
command_list = ",".join([str(i.name) for i in self.bot.application_commands])
|
||||
self.bot.log(f"Commands: {command_list}")
|
||||
activity = Activity("custom", ActivityType.CUSTOM, state="Type `/help` for commands")
|
||||
await self.bot.change_presence(activity=activity, status=Status.ONLINE)
|
||||
|
||||
@listen(Error)
|
||||
async def error(self, err: Error):
|
||||
self.bot.log(f"Error at {err.source}", level=25)
|
56
gwendolyn/ext/misc.py
Normal file
@ -0,0 +1,56 @@
|
||||
from interactions import Extension, slash_command, SlashContext, Status
|
||||
from gwendolyn.utils import PARAMS as params
|
||||
|
||||
class MiscExtension(Extension):
|
||||
"""Contains the miscellaneous commands."""
|
||||
@slash_command(**params["misc"]["gen_name"])
|
||||
async def gen_name(self, ctx: SlashContext):
|
||||
await self.bot.other.generate_name(ctx)
|
||||
|
||||
@slash_command(**params["misc"]["hello"])
|
||||
async def hello(self, ctx: SlashContext):
|
||||
"""Greet the bot."""
|
||||
await self.bot.other.hello_func(ctx)
|
||||
|
||||
@slash_command(**params["misc"]["help"])
|
||||
async def help(self, ctx: SlashContext, command=""):
|
||||
"""Get help for commands."""
|
||||
await self.bot.other.help_func(ctx, command)
|
||||
|
||||
@slash_command(**params["misc"]["ping"])
|
||||
async def ping(self, ctx: SlashContext):
|
||||
"""Send the bot's latency."""
|
||||
latency = self.bot.latency
|
||||
if latency > 100:
|
||||
await ctx.send("Cannot measure latency :(")
|
||||
else:
|
||||
await ctx.send(f"Pong!\nLatency is {round(self.bot.latency * 1000)}ms")
|
||||
|
||||
@slash_command(**params["misc"]["roll"])
|
||||
async def roll(self, ctx: SlashContext, dice: str = "1d20"):
|
||||
await self.bot.other.roll_dice(ctx, dice)
|
||||
|
||||
@slash_command(**params["misc"]["thank"])
|
||||
async def thank(self, ctx: SlashContext):
|
||||
"""Thank the bot."""
|
||||
await ctx.send("You're welcome :blush:")
|
||||
|
||||
@slash_command(**params["misc"]["stop"])
|
||||
async def stop(self, ctx: SlashContext):
|
||||
if f"{ctx.author.id}" in self.bot.admins:
|
||||
await ctx.send("Goodnight...")
|
||||
|
||||
await self.bot.change_presence(status=Status.INVISIBLE)
|
||||
|
||||
# self.bot.database_funcs.wipe_games()
|
||||
|
||||
self.bot.log("Logging out", level=25)
|
||||
await self.bot.stop()
|
||||
else:
|
||||
log_message = f"{ctx.author.display_name} tried to stop me!"
|
||||
self.bot.log(log_message, str(ctx.channel_id))
|
||||
await ctx.send(f"I don't think I will, {ctx.author.display_name}")
|
||||
|
||||
@slash_command(**params["misc"]["echo"])
|
||||
async def echo(self, ctx: SlashContext, text: str):
|
||||
await ctx.send(text)
|
@ -1,11 +1,6 @@
|
||||
"""A collection of all Gwendolyn functions."""
|
||||
|
||||
__all__ = ["Games", "Money", "LookupFuncs", "StarWars"]
|
||||
|
||||
from .games import Money, Games
|
||||
|
||||
from .lookup import LookupFuncs
|
||||
__all__ = ["Other","BetterNetflix", "Sonarr", "Radarr", "TMDb", "QBittorrent"]
|
||||
|
||||
from .other import Other
|
||||
|
||||
from .star_wars_funcs import StarWars
|
||||
from .better_netflix import Radarr, Sonarr, BetterNetflix, TMDb, QBittorrent
|
5
gwendolyn/funcs/better_netflix/__init__.py
Normal file
@ -0,0 +1,5 @@
|
||||
"""Better Netflix functions for Gwendolyn."""
|
||||
|
||||
__all__ = ["BetterNetflix", "Sonarr", "Radarr", "TMDb", "QBittorrent"]
|
||||
|
||||
from .better_netflix import BetterNetflix, Sonarr, Radarr, TMDb, QBittorrent
|
480
gwendolyn/funcs/better_netflix/better_netflix.py
Normal file
@ -0,0 +1,480 @@
|
||||
from requests import get
|
||||
from random import choice
|
||||
import io
|
||||
from math import floor
|
||||
from time import time
|
||||
|
||||
from PIL import Image
|
||||
from interactions import SlashContext, Embed, EmbedFooter, EmbedAttachment, File
|
||||
|
||||
from gwendolyn.exceptions import CannotConnectToService, ServiceNotProvided
|
||||
|
||||
class Service():
|
||||
def __init__(self, ip: str, port: str, api_key: str) -> None:
|
||||
self.ip = ip
|
||||
self.port = port
|
||||
self.header = {
|
||||
"accept": "application/json",
|
||||
"X-Api-Key": api_key
|
||||
}
|
||||
|
||||
def test(self):
|
||||
return get(f"http://{self.ip}:{self.port}/api", headers=self.header).ok
|
||||
|
||||
class Radarr(Service):
|
||||
def movies(self):
|
||||
response = get(f"http://{self.ip}:{self.port}/api/v3/movie", headers=self.header)
|
||||
if not response.ok:
|
||||
return []
|
||||
|
||||
return [m for m in response.json() if m["hasFile"]]
|
||||
|
||||
class Sonarr(Service):
|
||||
def shows(self):
|
||||
response = get(f"http://{self.ip}:{self.port}/api/v3/series", headers=self.header)
|
||||
if not response.ok:
|
||||
return []
|
||||
|
||||
return response.json()
|
||||
|
||||
class QBittorrent(Service):
|
||||
def __init__(self, ip: str, port: str, username: str, password: str) -> None:
|
||||
self.ip = ip
|
||||
self.port = port
|
||||
response = get(f"http://{ip}:{port}/api/v2/auth/login?username={username}&password={password}")
|
||||
self.logged_in = response.ok
|
||||
self.cookie = {"SID": response.cookies.values()[0]}
|
||||
|
||||
def test(self):
|
||||
return self.logged_in
|
||||
|
||||
def get_torrents(self):
|
||||
response = get(
|
||||
f"http://{self.ip}:{self.port}/api/v2/torrents/info",
|
||||
cookies=self.cookie
|
||||
)
|
||||
return response.json()
|
||||
|
||||
class TMDb():
|
||||
def __init__(self, api_token: str) -> None:
|
||||
self.header = {
|
||||
"accept": "application/json",
|
||||
"Authorization": "Bearer "+api_token
|
||||
}
|
||||
|
||||
def test(self):
|
||||
return get(f"https://api.themoviedb.org/3/account", headers=self.header).ok
|
||||
|
||||
|
||||
class BetterNetflix():
|
||||
def __init__(self, bot, **kwargs) -> None:
|
||||
self.bot = bot
|
||||
|
||||
services = ["radarr","sonarr","tmdb","qbittorrent"]
|
||||
|
||||
for service in services:
|
||||
try:
|
||||
setattr(self,service,kwargs[service])
|
||||
except:
|
||||
raise ServiceNotProvided(service)
|
||||
|
||||
if getattr(self,service).test():
|
||||
self.bot.log(f"Connected to {service}")
|
||||
else:
|
||||
raise CannotConnectToService(service)
|
||||
|
||||
|
||||
def _poster_color(self, poster_url: str):
|
||||
response = get(poster_url, stream=True)
|
||||
img = Image.open(io.BytesIO(response.content))
|
||||
img.thumbnail((150, 100))
|
||||
|
||||
# Reduce colors (uses k-means internally)
|
||||
paletted = img.convert('P', palette=Image.ADAPTIVE, colors=6)
|
||||
|
||||
buf = io.BytesIO()
|
||||
img2 = paletted.convert("RGB")
|
||||
img2.save("gwendolyn/resources/temp.jpg")
|
||||
|
||||
# Find the color that occurs most often
|
||||
palette = paletted.getpalette()
|
||||
color_counts = sorted(paletted.getcolors(), reverse=True)
|
||||
palette_index = color_counts[0][1]
|
||||
dominant_color = palette[palette_index*3:palette_index*3+3]
|
||||
|
||||
return dominant_color
|
||||
|
||||
async def movie(self, ctx: SlashContext):
|
||||
msg = await ctx.send("Finding a random movie...")
|
||||
movies = self.radarr.movies()
|
||||
if len(movies) == 0:
|
||||
await msg.edit(content="Unable to find any movies")
|
||||
return
|
||||
|
||||
picked_movie = choice(movies)
|
||||
description = ""
|
||||
|
||||
if "imdb" in picked_movie['ratings']:
|
||||
description += f"<:imdb:1301506320603676782> {picked_movie['ratings']['imdb']['value']}"
|
||||
description += "\u1CBC\u1CBC"
|
||||
|
||||
if "rottenTomatoes" in picked_movie['ratings']:
|
||||
rt_value = picked_movie['ratings']['rottenTomatoes']['value']
|
||||
rt_icon = "<:fresh:1301509701338660894>" if rt_value >= 60 else "<:rotten:1301509685907820607>"
|
||||
description += f"{rt_icon} {rt_value}%"
|
||||
|
||||
if description != "":
|
||||
description += "\n\n"
|
||||
|
||||
year = EmbedFooter(str(picked_movie["year"])) if "year" in picked_movie else None
|
||||
has_poster = len(picked_movie["images"]) > 0 and "remoteUrl" in picked_movie["images"][0]
|
||||
poster = EmbedAttachment(picked_movie["images"][0]["remoteUrl"]) if has_poster else None
|
||||
|
||||
color = self._poster_color(picked_movie["images"][0]["remoteUrl"]) if has_poster else "#ff0000"
|
||||
|
||||
description += picked_movie["overview"]
|
||||
embed = Embed(
|
||||
picked_movie["title"],
|
||||
description,
|
||||
color,
|
||||
thumbnail=poster,
|
||||
footer=year
|
||||
)
|
||||
await msg.edit(content="",embed=embed)
|
||||
|
||||
async def show(self, ctx: SlashContext):
|
||||
msg = await ctx.send("Finding a random show...")
|
||||
shows = self.sonarr.shows()
|
||||
if len(shows) == 0:
|
||||
await msg.edit(content="Unable to find any shows")
|
||||
return
|
||||
|
||||
picked_show = choice(shows)
|
||||
description = ""
|
||||
|
||||
if "imdb" in picked_show['ratings']:
|
||||
description += f"<:imdb:1301506320603676782> {picked_show['ratings']['imdb']['value']}"
|
||||
description += "\u1CBC\u1CBC"
|
||||
|
||||
if "rottenTomatoes" in picked_show['ratings']:
|
||||
rt_value = picked_show['ratings']['rottenTomatoes']['value']
|
||||
rt_icon = "<:fresh:1301509701338660894>" if rt_value >= 60 else "<:rotten:1301509685907820607>"
|
||||
description += f"{rt_icon} {rt_value}%"
|
||||
|
||||
if description != "":
|
||||
description += "\n\n"
|
||||
|
||||
year = EmbedFooter(str(picked_show["year"])) if "year" in picked_show else None
|
||||
images = {i["coverType"]:i for i in picked_show["images"]}
|
||||
has_poster = "poster" in images and "remoteUrl" in images["poster"]
|
||||
poster = EmbedAttachment(images["poster"]["remoteUrl"]) if has_poster else None
|
||||
|
||||
color = self._poster_color(images["poster"]["remoteUrl"]) if has_poster else "#ff0000"
|
||||
|
||||
description += picked_show["overview"]
|
||||
embed = Embed(
|
||||
picked_show["title"],
|
||||
description,
|
||||
color,
|
||||
thumbnail=poster,
|
||||
footer=year
|
||||
)
|
||||
await msg.edit(content="",embed=embed)
|
||||
|
||||
def _draw_torrent_list(self, title_width: int):
|
||||
def ratio_to_bar(ratio):
|
||||
progress_bar = "|"+("█"*floor(download_ratio*20))
|
||||
while len(progress_bar) < 21:
|
||||
progress_bar += " "
|
||||
|
||||
progress_bar += "| "+str(floor(download_ratio*100))+"%"
|
||||
|
||||
while len(progress_bar) < 27:
|
||||
progress_bar += " "
|
||||
|
||||
return progress_bar
|
||||
|
||||
message = [""]
|
||||
all_downloaded = True
|
||||
|
||||
dm_section_title = "*Torrent Downloads*"
|
||||
dm_section_title_line = "-"*((title_width-len(dm_section_title))//2)
|
||||
message.append(
|
||||
dm_section_title_line+dm_section_title+dm_section_title_line
|
||||
)
|
||||
|
||||
torrent_list = self.qbittorrent.get_torrents()
|
||||
|
||||
if len(torrent_list) == 0:
|
||||
message.append("No torrents currently downloading")
|
||||
return message, all_downloaded
|
||||
|
||||
for torrent in torrent_list:
|
||||
if torrent['category'] not in ["radarr", "tv-sonarr"]:
|
||||
break
|
||||
|
||||
torrent_name = torrent["name"]
|
||||
if len(torrent_name) > 30:
|
||||
if torrent_name[26] == " ":
|
||||
torrent_name = torrent_name[:26]+"...."
|
||||
else:
|
||||
torrent_name = torrent_name[:27]+"..."
|
||||
while len(torrent_name) < 30:
|
||||
torrent_name += " "
|
||||
|
||||
if torrent["size"] == 0:
|
||||
download_ratio = 0
|
||||
elif torrent["amount_left"] == 0:
|
||||
download_ratio = 1
|
||||
else:
|
||||
download_ratio = min(
|
||||
torrent["downloaded"]/torrent["size"],
|
||||
1
|
||||
)
|
||||
|
||||
progress_bar = ratio_to_bar(download_ratio)
|
||||
|
||||
eta_in_seconds = torrent["eta"]
|
||||
|
||||
if eta_in_seconds >= 8640000:
|
||||
eta = "∞"
|
||||
else:
|
||||
eta = ""
|
||||
if eta_in_seconds >= 86400:
|
||||
eta += str(floor(eta_in_seconds/86400))+"d "
|
||||
if eta_in_seconds >= 3600:
|
||||
eta += str(floor((eta_in_seconds%86400)/3600))+"h "
|
||||
if eta_in_seconds >= 60:
|
||||
eta += str(floor((eta_in_seconds%3600)/60))+"m "
|
||||
|
||||
eta += str(eta_in_seconds%60)+"s"
|
||||
|
||||
torrent_info = f"{torrent_name} {progress_bar} "
|
||||
torrent_info += f"(Eta: {eta})"
|
||||
|
||||
if torrent["state"] == "stalledDL":
|
||||
torrent_info += " (Stalled)"
|
||||
|
||||
if not (download_ratio == 1 and
|
||||
torrent["last_activity"] < time()-7200):
|
||||
message.append(torrent_info)
|
||||
|
||||
if download_ratio < 1 and torrent["state"] != "stalledDL":
|
||||
all_downloaded = False
|
||||
|
||||
return message, all_downloaded
|
||||
|
||||
async def _generate_download_list(self, show_dm, show_movies, show_shows,
|
||||
episodes):
|
||||
"""Generate a list of all torrents.
|
||||
|
||||
*Returns*
|
||||
message_text: str
|
||||
A formatted list of all torrents
|
||||
|
||||
all_downloaded: bool
|
||||
Whether all torrents are downloaded
|
||||
"""
|
||||
self.bot.log("Generating torrent list")
|
||||
title_width = 100
|
||||
message = []
|
||||
|
||||
if show_dm:
|
||||
m, all_downloaded = self._draw_torrent_list(title_width)
|
||||
message += m
|
||||
|
||||
# if show_movies:
|
||||
# message.append("")
|
||||
# movies_section_title = "*Missing movies not downloading*"
|
||||
# movies_section_line = (
|
||||
# "-"*((title_width-len(movies_section_title))//2)
|
||||
# )
|
||||
# message.append(
|
||||
# movies_section_line+movies_section_title+movies_section_line
|
||||
# )
|
||||
# movie_list = requests.get(
|
||||
# self.radarr_url+"movie?apiKey="+self.credentials["radarr_key"]
|
||||
# ).json()
|
||||
# print(
|
||||
# self.radarr_url+"movie?apiKey="+self.credentials["radarr_key"]
|
||||
# )
|
||||
# movie_queue = requests.get(
|
||||
# self.radarr_url+"queue?apiKey="+self.credentials["radarr_key"]
|
||||
# ).json()
|
||||
# movie_queue_ids = []
|
||||
|
||||
# for queue_item in movie_queue["records"]:
|
||||
# movie_queue_ids.append(queue_item["movieId"])
|
||||
|
||||
# for movie in movie_list:
|
||||
# if (not movie["hasFile"] and
|
||||
# movie["id"] not in movie_queue_ids):
|
||||
# movie_name = movie["title"]
|
||||
# if len(movie_name) > 40:
|
||||
# if movie_name[36] == " ":
|
||||
# movie_name = movie_name[:36]+"...."
|
||||
# else:
|
||||
# movie_name = movie_name[:37]+"..."
|
||||
|
||||
# while len(movie_name) < 41:
|
||||
# movie_name += " "
|
||||
|
||||
# if movie["monitored"]:
|
||||
# movie_info = movie_name+"Could not find a torrent"
|
||||
# else:
|
||||
# movie_info = self.long_strings["No torrent"].format(
|
||||
# movie_name
|
||||
# )
|
||||
|
||||
# message.append(movie_info)
|
||||
|
||||
# if show_shows:
|
||||
# message.append("")
|
||||
# show_section_title = "*Missing shows not downloading*"
|
||||
# show_section_line = "-"*((title_width-len(show_section_title))//2)
|
||||
# message.append(
|
||||
# show_section_line+show_section_title+show_section_line
|
||||
# )
|
||||
|
||||
# show_list = requests.get(
|
||||
# self.sonarr_url+"series?apiKey="+self.credentials["sonarr_key"]
|
||||
# ).json()
|
||||
|
||||
# for show in show_list:
|
||||
# if show["seasons"][0]["seasonNumber"] == 0:
|
||||
# seasons = show["seasons"][1:]
|
||||
# else:
|
||||
# seasons = show["seasons"]
|
||||
# if any(
|
||||
# (
|
||||
# i["statistics"]["episodeCount"] !=
|
||||
# i["statistics"]["totalEpisodeCount"]
|
||||
# ) for i in seasons):
|
||||
# if all(
|
||||
# i["statistics"]["episodeCount"] == 0 for i in seasons
|
||||
# ):
|
||||
# message.append(show["title"] + " (all episodes)")
|
||||
# else:
|
||||
# if episodes:
|
||||
# missing_episodes = sum(
|
||||
# (i["statistics"]["totalEpisodeCount"] -
|
||||
# i["statistics"]["episodeCount"])
|
||||
# for i in seasons)
|
||||
# message.append(
|
||||
# f"{show['title']} ({missing_episodes} episodes)"
|
||||
# )
|
||||
|
||||
message.append("-"*title_width)
|
||||
|
||||
message_text = "```"+"\n".join(message[1:])+"```"
|
||||
if message_text == "``````":
|
||||
message_text = self.long_strings["No torrents downloading"]
|
||||
|
||||
return message_text, all_downloaded
|
||||
|
||||
# async def downloading(self, ctx, content):
|
||||
# """Send message with list of all downloading torrents."""
|
||||
# async def send_long_message(ctx,message_text):
|
||||
# if len(message_text) <= 1994:
|
||||
# await ctx.send("```"+message_text+"```")
|
||||
# else:
|
||||
# cut_off_index = message_text[:1994].rfind("\n")
|
||||
# await ctx.send("```"+message_text[:cut_off_index]+"```")
|
||||
# await send_long_message(ctx,message_text[cut_off_index+1:])
|
||||
|
||||
# await self.bot.defer(ctx)
|
||||
|
||||
# # showDM, showMovies, showShows, episodes
|
||||
# parameters = [False, False, False, False]
|
||||
# show_dm_args = ["d", "dm", "downloading", "downloadmanager"]
|
||||
# show_movies_args = ["m", "movies"]
|
||||
# show_shows_args = ["s", "shows", "series"]
|
||||
# show_episode_args = ["e", "episodes"]
|
||||
# arg_list = [
|
||||
# show_dm_args, show_movies_args, show_shows_args, show_episode_args
|
||||
# ]
|
||||
# input_args = []
|
||||
# valid_arguments = True
|
||||
|
||||
# while content != "" and valid_arguments:
|
||||
# if content[0] == " ":
|
||||
# content = content[1:]
|
||||
# elif content[0] == "-":
|
||||
# if content[1] == "-":
|
||||
# arg_start = 2
|
||||
# if " " in content:
|
||||
# arg_stop = content.find(" ")
|
||||
# else:
|
||||
# arg_stop = None
|
||||
# else:
|
||||
# arg_start = 1
|
||||
# arg_stop = 2
|
||||
|
||||
# input_args.append(content[arg_start:arg_stop])
|
||||
# if arg_stop is None:
|
||||
# content = ""
|
||||
# else:
|
||||
# content = content[arg_stop:]
|
||||
# else:
|
||||
# valid_arguments = False
|
||||
|
||||
# if valid_arguments:
|
||||
# for arg_index, arg_aliases in enumerate(arg_list):
|
||||
# arg_in_input = [i in input_args for i in arg_aliases]
|
||||
# if any(arg_in_input):
|
||||
# input_args.remove(arg_aliases[arg_in_input.index(True)])
|
||||
# parameters[arg_index] = True
|
||||
|
||||
# if len(input_args) != 0 or (not parameters[2] and parameters[3]):
|
||||
# valid_arguments = False
|
||||
|
||||
# show_anything = any(i for i in parameters)
|
||||
# if not (valid_arguments and show_anything):
|
||||
# await ctx.send(self.long_strings["Invalid parameters"])
|
||||
# else:
|
||||
# message_text, all_downloaded = await self.__generate_download_list(
|
||||
# *parameters
|
||||
# )
|
||||
# if not message_text.startswith("```"):
|
||||
# await ctx.send(message_text)
|
||||
|
||||
# elif len(message_text) > 2000:
|
||||
# message_text = message_text[3:-3]
|
||||
# await send_long_message(ctx,message_text)
|
||||
|
||||
# elif all_downloaded:
|
||||
# await ctx.send(message_text)
|
||||
|
||||
# else:
|
||||
# updates_left = 60
|
||||
# message_text = self.long_strings["Update"].format(
|
||||
# message_text[:-3], ceil(updates_left/6)
|
||||
# )
|
||||
# old_message = await ctx.send(message_text)
|
||||
|
||||
# while ((not all_downloaded) and updates_left > 0):
|
||||
# await asyncio.sleep(10)
|
||||
# updates_left -= 1
|
||||
# message_text, all_downloaded = await (
|
||||
# self.__generate_download_list(*parameters)
|
||||
# )
|
||||
# message_text = self.long_strings["Update"].format(
|
||||
# message_text[:-3],
|
||||
# ceil(updates_left/6)
|
||||
# )
|
||||
# await old_message.edit(content = message_text)
|
||||
|
||||
# message_text, all_downloaded = await (
|
||||
# self.__generate_download_list(*parameters)
|
||||
# )
|
||||
|
||||
# if message_text.startswith("```"):
|
||||
# if all_downloaded:
|
||||
# self.bot.log("All torrents are downloaded")
|
||||
# else:
|
||||
# message_text = self.long_strings["No updates"].format(
|
||||
# message_text[:-3]
|
||||
# )
|
||||
# self.bot.log("The message updated 20 times")
|
||||
|
||||
# await old_message.edit(content = message_text)
|
@ -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)
|
44
gwendolyn/funcs/other/name_generator.py
Normal file
@ -0,0 +1,44 @@
|
||||
import random
|
||||
|
||||
class NameGenerator():
|
||||
def __init__(self) -> None:
|
||||
with open("gwendolyn/resources/names.txt", "r", encoding="UTF-8") as file_pointer:
|
||||
self._names = file_pointer.read().lower().split("\n")
|
||||
|
||||
self._gen_letter_dict()
|
||||
|
||||
def _gen_letter_dict(self):
|
||||
list_of_names = []
|
||||
for name in self._names:
|
||||
if (name != ""):
|
||||
list_of_names.append("__" + name + "__")
|
||||
|
||||
dict_of_names = {}
|
||||
for name in list_of_names:
|
||||
for i in range(len(name)-3):
|
||||
combination = name[i:i+2]
|
||||
if combination not in dict_of_names:
|
||||
dict_of_names[combination] = []
|
||||
dict_of_names[combination].append(name[i+2])
|
||||
|
||||
self._letter_dict = dict_of_names
|
||||
|
||||
def generate(self):
|
||||
combination = "__"
|
||||
next_letter = ""
|
||||
result = ""
|
||||
|
||||
while True:
|
||||
number_of_letters = len(self._letter_dict[combination])
|
||||
index = random.randint(0,number_of_letters - 1)
|
||||
next_letter = self._letter_dict[combination][index]
|
||||
if next_letter == "_":
|
||||
break
|
||||
else:
|
||||
result = result + next_letter
|
||||
combination = combination[1] + next_letter
|
||||
|
||||
return result.title()
|
||||
|
||||
if __name__ == "__main__":
|
||||
print(NameGenerator().generate())
|
@ -1,69 +1,22 @@
|
||||
import random # Used in movie_func
|
||||
import datetime # Used in hello_func
|
||||
import urllib # Used in image_func
|
||||
import ast
|
||||
|
||||
import imdb # Used in movie_func
|
||||
import discord # Used in movie_func
|
||||
import lxml # Used in image_func
|
||||
import fandom # Used in find_wiki_page
|
||||
import d20 # Used in roll_dice
|
||||
from interactions import SlashContext, Embed, SlashCommand
|
||||
|
||||
from .plex import Plex
|
||||
from .nerd_shit import NerdShit
|
||||
from .generators import Generators
|
||||
from .name_generator import NameGenerator
|
||||
|
||||
fandom.set_lang("da")
|
||||
fandom.set_wiki("senkulpa")
|
||||
from .roll import DieRoller
|
||||
|
||||
class MyStringifier(d20.MarkdownStringifier):
|
||||
def _str_expression(self, node):
|
||||
if node.comment is None:
|
||||
result_text = "Result"
|
||||
else:
|
||||
result_text = node.comment.capitalize()
|
||||
|
||||
return f"**{result_text}**: {self._stringify(node.roll)}\n**Total**: {int(node.total)}"
|
||||
def gen_help_text(commands: list[SlashCommand]):
|
||||
return '\n'.join([f"`/{i.name}`\t— {i.description}" for i in commands])
|
||||
|
||||
class Other():
|
||||
def __init__(self, bot):
|
||||
self.bot = bot
|
||||
self.plex = Plex(self.bot)
|
||||
self.nerd_shit = NerdShit(self.bot)
|
||||
self.generators = Generators(self.bot)
|
||||
|
||||
# Picks a random movie and returns information about it
|
||||
async def movie_func(self, ctx):
|
||||
await self.bot.defer(ctx)
|
||||
|
||||
self.bot.log("Creating IMDb object")
|
||||
imdb_client = imdb.IMDb()
|
||||
|
||||
self.bot.log("Picking a movie")
|
||||
with open("gwendolyn/resources/movies.txt", "r") as file_pointer:
|
||||
movie_list = file_pointer.read().split("\n")
|
||||
movie_name = random.choice(movie_list)
|
||||
|
||||
self.bot.log(f"Searching for {movie_name}")
|
||||
search_result = imdb_client.search_movie(movie_name)
|
||||
|
||||
self.bot.log("Getting the data")
|
||||
movie = search_result[0]
|
||||
imdb_client.update(movie)
|
||||
|
||||
self.bot.log("Successfully ran /movie")
|
||||
|
||||
title = movie["title"]
|
||||
plot = movie['plot'][0].split("::")[0]
|
||||
cover = movie['cover url'].replace("150","600").replace("101","404")
|
||||
cast = ", ".join([i["name"] for i in movie['cast'][:5]])
|
||||
embed = discord.Embed(title=title, description=plot, color=0x24ec19)
|
||||
embed.set_thumbnail(url=cover)
|
||||
embed.add_field(name="Cast", value=cast,inline = True)
|
||||
await ctx.send(embed = embed)
|
||||
self._name_generator = NameGenerator()
|
||||
self._roller = DieRoller()
|
||||
|
||||
# Responds with a greeting of a time-appropriate maner
|
||||
async def hello_func(self, ctx):
|
||||
async def hello_func(self, ctx: SlashContext):
|
||||
def time_in_range(start, end, i):
|
||||
# Return true if i is in the range [start, end]
|
||||
if start <= end:
|
||||
@ -86,107 +39,27 @@ class Other():
|
||||
|
||||
await ctx.send(send_message)
|
||||
|
||||
# Finds a random picture online
|
||||
async def image_func(self, ctx):
|
||||
# Picks a type of camera, which decides the naming scheme
|
||||
cams = ("one","two","three","four")
|
||||
cam = random.choice(cams)
|
||||
self.bot.log("Chose cam type "+cam)
|
||||
if cam == "one":
|
||||
|
||||
search = "img_" + ''.join(
|
||||
[str(random.randint(0,9)) for _ in range(4)]
|
||||
)
|
||||
elif cam == "two":
|
||||
year = str(random.randint(2012,2016))
|
||||
month = str(random.randint(1,12)).zfill(2)
|
||||
day = str(random.randint(1,29)).zfill(2)
|
||||
search = f"IMG_{year}{month}{day}"
|
||||
elif cam == "three":
|
||||
search = f"IMAG_{str(random.randint(1,500)).zfill(4)}"
|
||||
elif cam == "four":
|
||||
search = "DSC_" + ''.join(
|
||||
[str(random.randint(0,9)) for _ in range(4)]
|
||||
)
|
||||
|
||||
self.bot.log("Searching for "+search)
|
||||
|
||||
# Searches for the image and reads the resulting web page
|
||||
page = urllib.request.urlopen("https://www.bing.com/images/search?q="+search+"&safesearch=off")
|
||||
read = page.read()
|
||||
tree = lxml.etree.HTML(read)
|
||||
images = tree.xpath('//a[@class = "iusc"]/@m')
|
||||
|
||||
if len(images) == 0:
|
||||
await ctx.send("Found no images")
|
||||
else:
|
||||
# Picks an image
|
||||
number = random.randint(1,len(images))-1
|
||||
image = ast.literal_eval(str(images[number]))
|
||||
image_url = image["murl"]
|
||||
|
||||
self.bot.log("Picked image number "+str(number))
|
||||
|
||||
# Returns the image
|
||||
self.bot.log("Successfully returned an image")
|
||||
|
||||
await ctx.send(image_url)
|
||||
|
||||
# Finds a page from the Senkulpa Wikia
|
||||
async def find_wiki_page(self, ctx, search : str):
|
||||
await self.bot.defer(ctx)
|
||||
found_page = False
|
||||
|
||||
if search != "":
|
||||
self.bot.log("Trying to find wiki page for "+search)
|
||||
search_results = fandom.search(search)
|
||||
if len(search_results) > 0:
|
||||
found_page = True
|
||||
search_result = search_results[0]
|
||||
else:
|
||||
self.bot.log("Couldn't find the page")
|
||||
await ctx.send("Couldn't find page")
|
||||
else:
|
||||
found_page = True
|
||||
self.bot.log("Searching for a random page")
|
||||
search_result = fandom.random()
|
||||
|
||||
if found_page:
|
||||
self.bot.log(f"Found page \"{search_result[0]}\"")
|
||||
page = fandom.page(pageid = search_result[1])
|
||||
content = page.summary
|
||||
|
||||
images = page.images
|
||||
if len(images) > 0:
|
||||
image = images[0]
|
||||
else:
|
||||
image = ""
|
||||
self.bot.log("Sending the embedded message",str(ctx.channel_id))
|
||||
content += f"\n[Læs mere]({page.url})"
|
||||
embed = discord.Embed(title = page.title, description = content, colour=0xDEADBF)
|
||||
if image != "":
|
||||
embed.set_thumbnail(url=image)
|
||||
|
||||
await ctx.send(embed = embed)
|
||||
|
||||
async def roll_dice(self, ctx, roll_string):
|
||||
user = ctx.author.display_name
|
||||
while len(roll_string) > 1 and roll_string[0] == " ":
|
||||
roll_string = roll_string[1:]
|
||||
|
||||
roll = d20.roll(roll_string, allow_comments=True, stringifier=MyStringifier())
|
||||
await ctx.send(f"{user} :game_die:\n{roll}")
|
||||
|
||||
async def help_func(self, ctx, command):
|
||||
async def help_func(self, ctx: SlashContext, command: str):
|
||||
if command == "":
|
||||
with open("gwendolyn/resources/help/help.txt",encoding="utf-8") as file_pointer:
|
||||
text = file_pointer.read()
|
||||
embed = discord.Embed(title = "Help", description = text,colour = 0x59f442)
|
||||
text = gen_help_text([i for i in self.bot.application_commands if isinstance(i,SlashCommand)])
|
||||
embed = Embed(title = "Help", description = text,color = 0x59f442)
|
||||
await ctx.send(embed = embed)
|
||||
else:
|
||||
self.bot.log(f"Looking for help-{command}.txt",str(ctx.channel_id))
|
||||
with open(f"gwendolyn/resources/help/help-{command}.txt",encoding="utf-8") as file_pointer:
|
||||
text = file_pointer.read()
|
||||
embed = discord.Embed(title = command.capitalize(), description = text,colour = 0x59f442)
|
||||
await ctx.send(embed = embed)
|
||||
try:
|
||||
with open(f"gwendolyn/resources/help/help-{command}.txt",encoding="utf-8") as file_pointer:
|
||||
text = file_pointer.read()
|
||||
embed = Embed(title = command.capitalize(), description = text,color = 0x59f442)
|
||||
await ctx.send(embed = embed)
|
||||
except:
|
||||
await ctx.send(f"Could not find a help file for the command '/{command}'")
|
||||
|
||||
async def generate_name(self, ctx: SlashContext):
|
||||
await ctx.send(self._name_generator.generate())
|
||||
|
||||
async def roll_dice(self, ctx: SlashContext, dice: str):
|
||||
try:
|
||||
roll = self._roller.roll(dice)
|
||||
await ctx.send(f":game_die:Rolling dice `{dice}`\n{roll}")
|
||||
except:
|
||||
await ctx.send("There was an error in the rolling")
|
||||
|
1
gwendolyn/funcs/other/roll/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
from .roll import DieRoller
|
737
gwendolyn/funcs/other/roll/ast_nodes.py
Normal file
@ -0,0 +1,737 @@
|
||||
from __future__ import annotations
|
||||
from random import randint
|
||||
|
||||
from rply.token import BaseBox
|
||||
|
||||
def show_exp(exp, vtable):
|
||||
r = exp.show(vtable)
|
||||
|
||||
if type(exp) not in [ExpInt,ExpRoll,ExpVar]:
|
||||
r = f"({r})"
|
||||
|
||||
return r
|
||||
|
||||
class Exp(BaseBox):
|
||||
def eval(self, vtable, *args):
|
||||
return self._eval(vtable.copy(), *args)
|
||||
|
||||
def show(self, vtable, *args):
|
||||
return self._show(vtable.copy(), *args)
|
||||
|
||||
def _eval(self, _):
|
||||
return None
|
||||
|
||||
def _show(self, _):
|
||||
return ""
|
||||
|
||||
def includes(self, _):
|
||||
return False
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return "exp()"
|
||||
|
||||
def __eq__(self, other: Exp) -> bool:
|
||||
return False
|
||||
|
||||
class ExpInt(Exp):
|
||||
def __init__(self, value: int):
|
||||
self.value = value
|
||||
|
||||
def _eval(self, _):
|
||||
return self.value
|
||||
|
||||
def _show(self, _):
|
||||
return str(self.value)
|
||||
|
||||
def includes(self, _):
|
||||
return False
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"exp_int({self.value})"
|
||||
|
||||
def __eq__(self, other: Exp) -> bool:
|
||||
return isinstance(other, ExpInt) and self.value == other.value
|
||||
|
||||
class ExpMin(Exp):
|
||||
def __init__(self, exp1: Exp, exp2: Exp):
|
||||
self.exp1 = exp1
|
||||
self.exp2 = exp2
|
||||
|
||||
def _eval(self, vtable):
|
||||
r1 = self.exp1.eval(vtable)
|
||||
r2 = self.exp2.eval(vtable)
|
||||
|
||||
if not (isinstance(r1,int) and isinstance(r2,int)):
|
||||
return None
|
||||
|
||||
return max(r1, r2)
|
||||
|
||||
def _show(self, vtable):
|
||||
r1 = show_exp(self.exp1, vtable)
|
||||
r2 = show_exp(self.exp2, vtable)
|
||||
|
||||
return f"{r1}min{r2}"
|
||||
|
||||
def includes(self, var: str):
|
||||
return self.exp1.includes(var) or self.exp2.includes(var)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"exp_min({self.exp1},{self.exp2})"
|
||||
|
||||
def __eq__(self, other: Exp) -> bool:
|
||||
return (
|
||||
isinstance(other, ExpMin) and
|
||||
self.exp1 == other.exp1 and
|
||||
self.exp2 == other.exp2
|
||||
)
|
||||
|
||||
class ExpMax(Exp):
|
||||
def __init__(self, exp1: Exp, exp2: Exp):
|
||||
self.exp1 = exp1
|
||||
self.exp2 = exp2
|
||||
|
||||
def _eval(self, vtable):
|
||||
r1 = self.exp1.eval(vtable)
|
||||
r2 = self.exp2.eval(vtable)
|
||||
|
||||
if not (isinstance(r1,int) and isinstance(r2,int)):
|
||||
return None
|
||||
|
||||
return min(r1, r2)
|
||||
|
||||
def _show(self, vtable):
|
||||
r1 = show_exp(self.exp1, vtable)
|
||||
r2 = show_exp(self.exp2, vtable)
|
||||
|
||||
return f"{r1}max{r2}"
|
||||
|
||||
def includes(self, var: str):
|
||||
return self.exp1.includes(var) or self.exp2.includes(var)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"exp_max({self.exp1},{self.exp2})"
|
||||
|
||||
def __eq__(self, other: Exp) -> bool:
|
||||
return (
|
||||
isinstance(other, ExpMax) and
|
||||
self.exp1 == other.exp1 and
|
||||
self.exp2 == other.exp2
|
||||
)
|
||||
|
||||
class ExpBinop(Exp):
|
||||
def __init__(self, op: str, exp1: Exp, exp2: Exp):
|
||||
self.op = op
|
||||
self.exp1 = exp1
|
||||
self.exp2 = exp2
|
||||
|
||||
def _eval(self, vtable):
|
||||
r1 = self.exp1.eval(vtable)
|
||||
r2 = self.exp2.eval(vtable)
|
||||
|
||||
if not (isinstance(r1,int) and isinstance(r2,int)):
|
||||
return None
|
||||
|
||||
if self.op == "+":
|
||||
return r1+r2
|
||||
elif self.op == "-":
|
||||
return r1-r2
|
||||
elif self.op == "*":
|
||||
return r1*r2
|
||||
elif self.op == "/":
|
||||
return r1//r2
|
||||
else:
|
||||
raise Exception(f"Unknown binop {self.op}")
|
||||
|
||||
def _show(self, vtable):
|
||||
r1 = show_exp(self.exp1, vtable)
|
||||
r2 = show_exp(self.exp2, vtable)
|
||||
|
||||
return f"{r1}{self.op}{r2}"
|
||||
|
||||
def includes(self, var: str):
|
||||
return self.exp1.includes(var) or self.exp2.includes(var)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"exp_binop({self.op}, {self.exp1}, {self.exp2})"
|
||||
|
||||
def __eq__(self, other: Exp) -> bool:
|
||||
return (
|
||||
isinstance(other, ExpBinop) and
|
||||
self.op == other.op and
|
||||
self.exp1 == other.exp1 and
|
||||
self.exp2 == other.exp2
|
||||
)
|
||||
|
||||
class ExpNeg(Exp):
|
||||
def __init__(self, exp: Exp):
|
||||
self.exp = exp
|
||||
|
||||
def _eval(self, vtable):
|
||||
r = self.exp.eval(vtable)
|
||||
|
||||
if not isinstance(r,int):
|
||||
return None
|
||||
|
||||
return -r
|
||||
|
||||
def _show(self, vtable):
|
||||
r = show_exp(self.exp, vtable)
|
||||
|
||||
return f"-{r}"
|
||||
|
||||
def includes(self, var: str):
|
||||
return self.exp.includes(var)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"exp_neg({self.exp})"
|
||||
|
||||
def __eq__(self, other: Exp) -> bool:
|
||||
return (
|
||||
isinstance(other, ExpNeg) and
|
||||
self.exp == other.exp
|
||||
)
|
||||
|
||||
class ExpLet(Exp):
|
||||
def __init__(self, var: str, exp1: Exp, exp2: Exp):
|
||||
self.var = var
|
||||
self.exp1 = exp1
|
||||
self.exp2 = exp2
|
||||
|
||||
def _eval(self, vtable):
|
||||
r1 = self.exp1.eval(vtable)
|
||||
|
||||
vtable[self.var] = r1
|
||||
|
||||
return self.exp2.eval(vtable)
|
||||
|
||||
def _show(self, vtable):
|
||||
r1 = show_exp(self.exp1, vtable)
|
||||
|
||||
vtable[self.var] = self.exp1.eval(vtable)
|
||||
|
||||
r2 = show_exp(self.exp2, vtable)
|
||||
|
||||
if self.exp2.includes(self.var):
|
||||
return f"let {self.var}={r1} in {r2}"
|
||||
else:
|
||||
return r2
|
||||
|
||||
def includes(self, var: str):
|
||||
return self.exp1.includes(var) or self.exp2.includes(var)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"exp_let({self.var}, {self.exp1}, {self.exp2})"
|
||||
|
||||
def __eq__(self, other: Exp) -> bool:
|
||||
return (
|
||||
isinstance(other, ExpLet) and
|
||||
self.var == other.var and
|
||||
self.exp1 == other.exp1 and
|
||||
self.exp2 == other.exp2
|
||||
)
|
||||
|
||||
class ExpIf(Exp):
|
||||
def __init__(self, exp1: Exp, exp2: Exp, exp3: Exp):
|
||||
self.exp1 = exp1
|
||||
self.exp2 = exp2
|
||||
self.exp3 = exp3
|
||||
|
||||
def _eval(self, vtable):
|
||||
r1 = self.exp1.eval(vtable)
|
||||
|
||||
if r1 > 0:
|
||||
return self.exp2.eval(vtable)
|
||||
else:
|
||||
return self.exp3.eval(vtable)
|
||||
|
||||
def _show(self, vtable):
|
||||
r1 = show_exp(self.exp1, vtable)
|
||||
r2 = show_exp(self.exp2, vtable)
|
||||
r3 = self.exp3.show(vtable)
|
||||
|
||||
return f"if {r1} then {r2} else {r3}"
|
||||
|
||||
def includes(self, var: str):
|
||||
return self.exp1.includes(var) or self.exp2.includes(var) or self.exp3.includes(var)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"exp_if({self.exp1}, {self.exp2}, {self.exp3})"
|
||||
|
||||
def __eq__(self, other: Exp) -> bool:
|
||||
return (
|
||||
isinstance(other, ExpIf) and
|
||||
self.exp1 == other.exp1 and
|
||||
self.exp2 == other.exp2 and
|
||||
self.exp3 == other.exp3
|
||||
)
|
||||
|
||||
class ExpLambda(Exp):
|
||||
def __init__(self, var: str, exp: Exp):
|
||||
self.var = var
|
||||
self.exp = exp
|
||||
|
||||
def _eval(self, _):
|
||||
return (self.exp, self.var)
|
||||
|
||||
def _show(self, vtable):
|
||||
r = show_exp(self.exp, vtable)
|
||||
|
||||
return f"\\{self.var} -> {r}"
|
||||
|
||||
def includes(self, var: str):
|
||||
return self.exp.includes(var)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"exp_lambda({self.var}, {self.exp})"
|
||||
|
||||
def __eq__(self, other: Exp) -> bool:
|
||||
return (
|
||||
isinstance(other, ExpLambda) and
|
||||
self.var == other.var and
|
||||
self.exp == other.exp
|
||||
)
|
||||
|
||||
class ExpApply(Exp):
|
||||
def __init__(self, exp1: Exp, exp2: Exp):
|
||||
self.exp1 = exp1
|
||||
self.exp2 = exp2
|
||||
|
||||
def _eval(self, vtable):
|
||||
r1 = self.exp1.eval(vtable)
|
||||
if isinstance(r1, tuple):
|
||||
r2 = self.exp2.eval(vtable)
|
||||
vtable[r1[1]] = r2
|
||||
return r1[0].eval(vtable)
|
||||
else:
|
||||
return None
|
||||
|
||||
def _show(self, vtable):
|
||||
r1 = show_exp(self.exp1, vtable)
|
||||
r2 = self.exp2.show(vtable)
|
||||
return f"{r1}({r2})"
|
||||
|
||||
def includes(self, var: str):
|
||||
return self.exp1.includes(var) or self.exp2.includes(var)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"exp_apply({self.exp1}, {self.exp2})"
|
||||
|
||||
def __eq__(self, other: Exp) -> bool:
|
||||
return (
|
||||
isinstance(other, ExpApply) and
|
||||
self.exp1 == other.exp1 and
|
||||
self.exp2 == other.exp2
|
||||
)
|
||||
|
||||
class ExpVar(Exp):
|
||||
def __init__(self, var: str):
|
||||
self.var = var
|
||||
|
||||
def _eval(self, vtable):
|
||||
return vtable[self.var] if self.var in vtable else None
|
||||
|
||||
def _show(self, vtable):
|
||||
return self.var
|
||||
|
||||
def includes(self, var: str):
|
||||
return var == self.var
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"exp_var({self.var})"
|
||||
|
||||
def __eq__(self, other: Exp) -> bool:
|
||||
return (
|
||||
isinstance(other, ExpVar) and
|
||||
self.var == other.var
|
||||
)
|
||||
|
||||
class ComparePoint(Exp):
|
||||
def __init__(self, comp_op: str, exp: Exp) -> None:
|
||||
self.comp_op = comp_op
|
||||
self.exp = exp
|
||||
|
||||
def _eval(self, vtable, val: int):
|
||||
r = self.exp.eval(vtable)
|
||||
|
||||
if not isinstance(r,int):
|
||||
return None
|
||||
if self.comp_op == "=":
|
||||
return 1 if val == r else 0
|
||||
if self.comp_op == "<":
|
||||
return 1 if val < r else 0
|
||||
if self.comp_op == "<=":
|
||||
return 1 if val <= r else 0
|
||||
if self.comp_op == ">":
|
||||
return 1 if val <= r else 0
|
||||
if self.comp_op == ">=":
|
||||
return 1 if val <= r else 0
|
||||
else:
|
||||
raise Exception(f"Unknown binop {self.op}")
|
||||
|
||||
def _show(self, vtable):
|
||||
r = show_exp(self.exp, vtable)
|
||||
|
||||
return f"{self.comp_op}{r}"
|
||||
|
||||
def includes(self, var: str):
|
||||
return self.exp.includes(var)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"comp({self.comp_op},{self.exp})"
|
||||
|
||||
def __eq__(self, other: Exp) -> bool:
|
||||
return (
|
||||
isinstance(other, ComparePoint) and
|
||||
self.comp_op == other.comp_op and
|
||||
self.exp == other.exp
|
||||
)
|
||||
|
||||
class ExpTest(Exp):
|
||||
def __init__(self, exp: Exp, comp: ComparePoint) -> None:
|
||||
self.exp = exp
|
||||
self.comp = comp
|
||||
|
||||
def _eval(self, vtable):
|
||||
r = self.exp.eval(vtable)
|
||||
|
||||
return self.comp.eval(vtable, r)
|
||||
|
||||
def _show(self, vtable):
|
||||
r = show_exp(self.exp, vtable)
|
||||
c = self.comp.show(vtable)
|
||||
|
||||
return f"{r}{c}"
|
||||
|
||||
def includes(self, var: str):
|
||||
return self.exp.includes(var) or self.comp.includes(var)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"test({self.exp},{self.comp})"
|
||||
|
||||
def __eq__(self, other: Exp) -> bool:
|
||||
return (
|
||||
isinstance(other, ExpTest) and
|
||||
self.exp == other.exp and
|
||||
self.comp == other.comp
|
||||
)
|
||||
|
||||
|
||||
class ExpRoll(Exp):
|
||||
def __init__(self, roll: Roll):
|
||||
self.roll = roll
|
||||
|
||||
def _eval(self, vtable):
|
||||
return sum(self.roll.eval(vtable))
|
||||
|
||||
def _show(self, vtable):
|
||||
return f"[{','.join(self.roll.show(vtable))}]"
|
||||
|
||||
def includes(self, _):
|
||||
return False
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"sum({self.roll})"
|
||||
|
||||
def __eq__(self, other: Exp) -> bool:
|
||||
return (
|
||||
isinstance(other, ExpRoll) and
|
||||
self.roll == other.roll
|
||||
)
|
||||
|
||||
class Roll(Exp):
|
||||
def __init__(self, exp1: Exp, exp2: Exp):
|
||||
self.exp1 = exp1
|
||||
self.exp2 = exp2
|
||||
self.result = None
|
||||
|
||||
def _eval(self, vtable):
|
||||
if self.result is not None:
|
||||
return self.result
|
||||
|
||||
r1 = self.exp1.eval(vtable)
|
||||
r2 = self.exp2.eval(vtable)
|
||||
|
||||
if not (isinstance(r1,int) and isinstance(r2,int)):
|
||||
return []
|
||||
|
||||
self.die_type = r2
|
||||
|
||||
self.result = [randint(1,r2) for _ in range(r1)]
|
||||
self.show_list = [str(i) for i in self.result]
|
||||
return self.result
|
||||
|
||||
def _show(self, vtable):
|
||||
self.eval(vtable)
|
||||
return self.show_list
|
||||
|
||||
@property
|
||||
def die(self) -> int:
|
||||
if hasattr(self, "die_type"):
|
||||
return self.die_type
|
||||
elif hasattr(self, "roll"):
|
||||
return self.roll.die_type
|
||||
else:
|
||||
return 0
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"roll({self.exp1},{self.exp2})"
|
||||
|
||||
def __eq__(self, other: Exp) -> bool:
|
||||
return (
|
||||
isinstance(other, Roll) and
|
||||
self.exp1 == other.exp1 and
|
||||
self.exp2 == other.exp2
|
||||
)
|
||||
|
||||
class RollKeepHighest(Roll):
|
||||
def __init__(self, roll: Roll, exp: Exp):
|
||||
self.roll = roll
|
||||
self.exp = exp
|
||||
self.result = None
|
||||
self.show_list = None
|
||||
|
||||
def _eval(self, vtable):
|
||||
if self.result is not None:
|
||||
return self.result
|
||||
|
||||
r1 = self.roll.eval(vtable)
|
||||
r2 = self.exp.eval(vtable)
|
||||
|
||||
if not ((isinstance(r1,list) and all(isinstance(i,int) for i in r1)) and isinstance(r2,int)):
|
||||
return []
|
||||
|
||||
max_indices = []
|
||||
for i, n in enumerate(r1):
|
||||
if len(max_indices) < r2:
|
||||
max_indices.append(i)
|
||||
elif not all([n <= r1[j] for j in max_indices]):
|
||||
max_indices.remove(min(max_indices,key=lambda x: r1[x]))
|
||||
max_indices.append(i)
|
||||
|
||||
self.result = [r1[i] for i in max_indices]
|
||||
self.show_list = [str(n) if i in max_indices else f"~~{n}~~" for i, n in enumerate(r1)]
|
||||
return self.result
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"kep_highest({self.roll},{self.exp})"
|
||||
|
||||
def __eq__(self, other: Exp) -> bool:
|
||||
return (
|
||||
isinstance(other, RollKeepHighest) and
|
||||
self.roll == other.roll and
|
||||
self.exp == other.exp
|
||||
)
|
||||
|
||||
class RollKeepLowest(Roll):
|
||||
def __init__(self, roll: Roll, exp: Exp):
|
||||
self.roll = roll
|
||||
self.exp = exp
|
||||
self.result = None
|
||||
self.show_list = None
|
||||
|
||||
def _eval(self, vtable):
|
||||
if self.result is not None:
|
||||
return self.result
|
||||
|
||||
r1 = self.roll.eval(vtable)
|
||||
r2 = self.exp.eval(vtable)
|
||||
|
||||
if not ((isinstance(r1,list) and all(isinstance(i,int) for i in r1)) and isinstance(r2,int)):
|
||||
return []
|
||||
|
||||
min_indices = []
|
||||
for i, n in enumerate(r1):
|
||||
if len(min_indices) < r2:
|
||||
min_indices.append(i)
|
||||
elif not all([n >= r1[j] for j in min_indices]):
|
||||
min_indices.remove(max(min_indices,key=lambda x: r1[x]))
|
||||
min_indices.append(i)
|
||||
|
||||
self.result = [r1[i] for i in min_indices]
|
||||
self.show_list = [str(n) if i in min_indices else f"~~{n}~~" for i, n in enumerate(r1)]
|
||||
return self.result
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"kep_lowest({self.roll},{self.exp})"
|
||||
|
||||
def __eq__(self, other: Exp) -> bool:
|
||||
return (
|
||||
isinstance(other, RollKeepLowest) and
|
||||
self.roll == other.roll and
|
||||
self.exp == other.exp
|
||||
)
|
||||
|
||||
class RollMin(Roll):
|
||||
def __init__(self, roll: Roll, exp: Exp):
|
||||
self.roll = roll
|
||||
self.exp = exp
|
||||
self.result = None
|
||||
self.show_list = None
|
||||
|
||||
def _eval(self, vtable):
|
||||
if self.result is not None:
|
||||
return self.result
|
||||
|
||||
r1 = self.roll.eval(vtable)
|
||||
r2 = self.exp.eval(vtable)
|
||||
|
||||
if not ((isinstance(r1,list) and all(isinstance(i,int) for i in r1)) and isinstance(r2,int)):
|
||||
return []
|
||||
|
||||
self.show_list = []
|
||||
|
||||
for i in range(len(r1)):
|
||||
if r1[i] < r2:
|
||||
r1[i] = r2
|
||||
self.show_list.append(f"{r2}^")
|
||||
else:
|
||||
self.show_list.append(str(r1[i]))
|
||||
|
||||
self.result = r1
|
||||
return self.result
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"min({self.roll},{self.exp})"
|
||||
|
||||
def __eq__(self, other: Exp) -> bool:
|
||||
return (
|
||||
isinstance(other, RollMin) and
|
||||
self.roll == other.roll and
|
||||
self.exp == other.exp
|
||||
)
|
||||
|
||||
class RollMax(Roll):
|
||||
def __init__(self, roll: Roll, exp: Exp):
|
||||
self.roll = roll
|
||||
self.exp = exp
|
||||
self.result = None
|
||||
self.show_list = None
|
||||
|
||||
def _eval(self, vtable):
|
||||
if self.result is not None:
|
||||
return self.result
|
||||
|
||||
r1 = self.roll.eval(vtable)
|
||||
r2 = self.exp.eval(vtable)
|
||||
|
||||
if not ((isinstance(r1,list) and all(isinstance(i,int) for i in r1)) and isinstance(r2,int)):
|
||||
return []
|
||||
|
||||
self.show_list = []
|
||||
|
||||
for i in range(len(r1)):
|
||||
if r1[i] > r2:
|
||||
r1[i] = r2
|
||||
self.show_list.append(f"{r2}v")
|
||||
else:
|
||||
self.show_list.append(str(r1[i]))
|
||||
|
||||
self.result = r1
|
||||
return self.result
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"max({self.roll},{self.exp})"
|
||||
|
||||
def __eq__(self, other: Exp) -> bool:
|
||||
return (
|
||||
isinstance(other, RollMax) and
|
||||
self.roll == other.roll and
|
||||
self.exp == other.exp
|
||||
)
|
||||
|
||||
class RollExplode(Roll):
|
||||
def __init__(self, roll: Roll, comp: ComparePoint = None):
|
||||
self.roll = roll
|
||||
self.comp = comp
|
||||
self.result = None
|
||||
self.show_list = None
|
||||
|
||||
def _eval(self, vtable):
|
||||
if self.result is not None:
|
||||
return self.result
|
||||
|
||||
r1 = self.roll.eval(vtable)
|
||||
|
||||
if not (isinstance(r1,list) and all(isinstance(i,int) for i in r1)):
|
||||
return []
|
||||
|
||||
|
||||
d = self.die
|
||||
|
||||
if self.comp is None:
|
||||
self.comp = ComparePoint("=", ExpInt(d))
|
||||
|
||||
self.result = []
|
||||
self.show_list = []
|
||||
|
||||
def compare(n):
|
||||
if self.comp.eval(vtable, n):
|
||||
self.result.append(n)
|
||||
self.show_list.append(f"{n}!")
|
||||
compare(randint(1,d))
|
||||
else:
|
||||
self.result.append(n)
|
||||
self.show_list.append(str(n))
|
||||
|
||||
for n in r1:
|
||||
compare(n)
|
||||
|
||||
return self.result
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"max({self.roll},{self.exp})"
|
||||
|
||||
def __eq__(self, other: Exp) -> bool:
|
||||
return (
|
||||
isinstance(other, RollMax) and
|
||||
self.roll == other.roll and
|
||||
self.exp == other.exp
|
||||
)
|
||||
|
||||
class RollReroll(Roll):
|
||||
def __init__(self, roll: Roll, comp: ComparePoint = None, once: bool = False):
|
||||
self.roll = roll
|
||||
self.comp = comp
|
||||
self.once = once
|
||||
self.result = None
|
||||
self.show_list = None
|
||||
|
||||
def _eval(self, vtable):
|
||||
if self.result is not None:
|
||||
return self.result
|
||||
|
||||
r1 = self.roll.eval(vtable)
|
||||
|
||||
if not (isinstance(r1,list) and all(isinstance(i,int) for i in r1)):
|
||||
return []
|
||||
|
||||
d = self.die
|
||||
|
||||
if self.comp is None:
|
||||
self.comp = ComparePoint("=", ExpInt(1))
|
||||
|
||||
self.result = []
|
||||
self.show_list = []
|
||||
|
||||
def compare(n, rerolled):
|
||||
if self.comp.eval(vtable, n) and not (rerolled and self.once):
|
||||
self.show_list.append(f"~~{n}~~")
|
||||
compare(randint(1,d), True)
|
||||
else:
|
||||
self.result.append(n)
|
||||
self.show_list.append(str(n))
|
||||
|
||||
for n in r1:
|
||||
compare(n, False)
|
||||
|
||||
return self.result
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"max({self.roll},{self.exp})"
|
||||
|
||||
def __eq__(self, other: Exp) -> bool:
|
||||
return (
|
||||
isinstance(other, RollMax) and
|
||||
self.roll == other.roll and
|
||||
self.exp == other.exp
|
||||
)
|
52
gwendolyn/funcs/other/roll/lexer.py
Normal file
@ -0,0 +1,52 @@
|
||||
|
||||
from string import ascii_letters, digits
|
||||
from rply import LexerGenerator
|
||||
|
||||
VALID_CHARACTERS = ascii_letters+"_"+digits
|
||||
|
||||
TOKENS = [
|
||||
("ROLL_DIE", r"d"),
|
||||
("ROLL_KEEP_LOWEST", r"kl"),
|
||||
("ROLL_KEEP_HIGHEST", r"kh?"),
|
||||
("ROLL_REROLL_ONCE", r"ro"),
|
||||
("ROLL_REROLL", r"r"),
|
||||
("ROLL_MIN", r"min"),
|
||||
("ROLL_MAX", r"max"),
|
||||
("ROLL_EXPLODE", r"!"),
|
||||
|
||||
("SYMBOL_LE", r"\<\="),
|
||||
("SYMBOL_GE", r"\>\="),
|
||||
("SYMBOL_LT", r"\<"),
|
||||
("SYMBOL_GT", r"\>"),
|
||||
("SYMBOL_EQUALS", r"\="),
|
||||
("SYMBOL_ARROW", r"\-\>"),
|
||||
("SYMBOL_BACKSLASH", r"\\"),
|
||||
("SYMBOL_LPARENS", r"\("),
|
||||
("SYMBOL_RPARENS", r"\)"),
|
||||
("SYMBOL_PLUS", r"\+"),
|
||||
("SYMBOL_MINUS", r"\-"),
|
||||
("SYMBOL_TIMES", r"\*"),
|
||||
("SYMBOL_DIVIDE", r"\/"),
|
||||
|
||||
("KEYWORD_LET", r"let"),
|
||||
("KEYWORD_IN", r"in"),
|
||||
("KEYWORD_IF", r"if"),
|
||||
("KEYWORD_THEN", r"then"),
|
||||
("KEYWORD_ELSE", r"else"),
|
||||
|
||||
("INT", r"\d+"),
|
||||
("ID", f"[{ascii_letters}][{VALID_CHARACTERS}]*")
|
||||
]
|
||||
|
||||
class Lexer():
|
||||
def __init__(self):
|
||||
self.lexer = LexerGenerator()
|
||||
|
||||
def _add_tokens(self):
|
||||
for token in TOKENS:
|
||||
self.lexer.add(*token)
|
||||
self.lexer.ignore(r"\s+")
|
||||
|
||||
def get_lexer(self):
|
||||
self._add_tokens()
|
||||
return self.lexer.build()
|
153
gwendolyn/funcs/other/roll/parser.py
Normal file
@ -0,0 +1,153 @@
|
||||
from rply import ParserGenerator
|
||||
|
||||
from lexer import TOKENS
|
||||
from ast_nodes import Exp, ExpInt, ExpBinop, ExpLet, ExpVar, ExpMin, ExpMax, ExpRoll, Roll, RollKeepHighest, RollKeepLowest, RollMin, RollMax, RollExplode, ComparePoint, ExpIf, ExpTest, ExpApply, ExpLambda, ExpNeg, RollReroll
|
||||
|
||||
|
||||
class Parser():
|
||||
def __init__(self):
|
||||
self.pg = ParserGenerator(
|
||||
[i[0] for i in TOKENS],
|
||||
precedence=[
|
||||
('left', ["SYMBOL_BACKSLASH","SYMBOL_ARROW"]),
|
||||
('left', ["KEYWORD_LET", "KEYWORD_IN", "KEYWORD_IF", "KEYWORD_THEN", "KEYWORD_ELSE"]),
|
||||
('left', ["SYMBOL_EQUALS", "SYMBOL_LT", "SYMBOL_LE", "SYMBOL_GT", "SYMBOL_GE"]),
|
||||
('left', ["SYMBOL_PLUS", "SYMBOL_MINUS"]),
|
||||
('left', ["SYMBOL_TIMES", "SYMBOL_DIVIDE"]),
|
||||
('left', ["ROLL_DIE"]),
|
||||
('right', ["ROLL_KEEP_HIGHEST","ROLL_KEEP_LOWEST","ROLL_MIN","ROLL_MAX","ROLL_EXPLODE","ROLL_REROLL","ROLL_REROLL_ONCE"])
|
||||
]
|
||||
)
|
||||
self._get_parser()
|
||||
|
||||
def parse(self, token_input):
|
||||
return self.parser.parse(token_input)
|
||||
|
||||
def _get_parser(self) -> Exp:
|
||||
# Expressions
|
||||
@self.pg.production('exp : exp SYMBOL_PLUS exp')
|
||||
@self.pg.production('exp : exp SYMBOL_MINUS exp')
|
||||
@self.pg.production('exp : exp SYMBOL_TIMES exp')
|
||||
@self.pg.production('exp : exp SYMBOL_DIVIDE exp')
|
||||
def exp_mul_div(tokens):
|
||||
return ExpBinop(tokens[1].value, tokens[0], tokens[2])
|
||||
|
||||
@self.pg.production('exp : atom')
|
||||
def exp_atom(tokens):
|
||||
return tokens[0]
|
||||
|
||||
@self.pg.production("exp : SYMBOL_MINUS atom")
|
||||
def exp_neg(tokens):
|
||||
return ExpNeg(tokens[1])
|
||||
|
||||
@self.pg.production('exp : KEYWORD_LET ID SYMBOL_EQUALS exp KEYWORD_IN exp')
|
||||
def exp_let(tokens):
|
||||
return ExpLet(tokens[1].value, tokens[3], tokens[5])
|
||||
|
||||
@self.pg.production('exp : roll')
|
||||
def exp_roll(tokens):
|
||||
return ExpRoll(tokens[0])
|
||||
|
||||
@self.pg.production("exp : atom ROLL_MIN atom")
|
||||
def exp_min(tokens):
|
||||
return ExpMin(tokens[0], tokens[2])
|
||||
|
||||
@self.pg.production("exp : atom ROLL_MAX atom")
|
||||
def exp_max(tokens):
|
||||
return ExpMax(tokens[0], tokens[2])
|
||||
|
||||
@self.pg.production("exp : atom comp")
|
||||
def exp_test(tokens):
|
||||
return ExpTest(tokens[0], tokens[1])
|
||||
|
||||
@self.pg.production("exp : KEYWORD_IF exp KEYWORD_THEN exp KEYWORD_ELSE exp")
|
||||
def exp_if(tokens):
|
||||
return ExpIf(tokens[1],tokens[3],tokens[5])
|
||||
|
||||
@self.pg.production("exp : SYMBOL_BACKSLASH ID SYMBOL_ARROW exp")
|
||||
def exp_lamda(tokens):
|
||||
return ExpLambda(tokens[1].value, tokens[3])
|
||||
|
||||
@self.pg.production("exp : atom atom")
|
||||
def apply(tokens):
|
||||
return ExpApply(tokens[0],tokens[1])
|
||||
|
||||
# Rolls
|
||||
@self.pg.production('roll : roll ROLL_KEEP_HIGHEST atom')
|
||||
def roll_keep_highest(tokens):
|
||||
return RollKeepHighest(tokens[0], tokens[2])
|
||||
|
||||
@self.pg.production('roll : roll ROLL_KEEP_LOWEST atom')
|
||||
def roll_keep_lowest(tokens):
|
||||
return RollKeepLowest(tokens[0], tokens[2])
|
||||
|
||||
@self.pg.production('roll : roll ROLL_MIN atom')
|
||||
def roll_min(tokens):
|
||||
return RollMin(tokens[0], tokens[2])
|
||||
|
||||
@self.pg.production('roll : roll ROLL_MAX atom')
|
||||
def roll_max(tokens):
|
||||
return RollMax(tokens[0], tokens[2])
|
||||
|
||||
@self.pg.production('roll : roll ROLL_EXPLODE ')
|
||||
def roll_explode(tokens):
|
||||
return RollExplode(tokens[0])
|
||||
|
||||
@self.pg.production('roll : roll ROLL_EXPLODE comp')
|
||||
def roll_explode_comp(tokens):
|
||||
return RollExplode(tokens[0], tokens[2])
|
||||
|
||||
@self.pg.production('roll : roll ROLL_REROLL ')
|
||||
def roll_reroll(tokens):
|
||||
return RollReroll(tokens[0])
|
||||
|
||||
@self.pg.production('roll : roll ROLL_REROLL comp')
|
||||
def roll_reroll_comp(tokens):
|
||||
return RollReroll(tokens[0], tokens[2])
|
||||
|
||||
@self.pg.production('roll : roll ROLL_REROLL_ONCE')
|
||||
def roll_reroll_once(tokens):
|
||||
return RollReroll(tokens[0], None, True)
|
||||
|
||||
@self.pg.production('roll : roll ROLL_REROLL_ONCE comp')
|
||||
def roll_reroll_once_comp(tokens):
|
||||
return RollReroll(tokens[0], tokens[2], True)
|
||||
|
||||
@self.pg.production('roll : atom ROLL_DIE atom')
|
||||
def roll(tokens):
|
||||
return Roll(tokens[0], tokens[2])
|
||||
|
||||
@self.pg.production('roll : ROLL_DIE atom')
|
||||
def roll_no_amount(tokens):
|
||||
return Roll(ExpInt(1), tokens[1])
|
||||
|
||||
# Compare Points
|
||||
|
||||
@self.pg.production("comp : SYMBOL_EQUALS atom")
|
||||
@self.pg.production("comp : SYMBOL_LT atom")
|
||||
@self.pg.production("comp : SYMBOL_LE atom")
|
||||
@self.pg.production("comp : SYMBOL_GT atom")
|
||||
@self.pg.production("comp : SYMBOL_GE atom")
|
||||
def comp_point(tokens):
|
||||
return ComparePoint(tokens[0].value,tokens[1])
|
||||
|
||||
# Atoms
|
||||
@self.pg.production("atom : INT")
|
||||
def atom_int(tokens):
|
||||
return ExpInt(int(tokens[0].value))
|
||||
|
||||
@self.pg.production("atom : SYMBOL_LPARENS exp SYMBOL_RPARENS")
|
||||
def atom_exp(tokens):
|
||||
return tokens[1]
|
||||
|
||||
@self.pg.production('atom : ID')
|
||||
def atom_var(tokens):
|
||||
return ExpVar(tokens[0].value)
|
||||
|
||||
## Error Handling ##
|
||||
@self.pg.error
|
||||
def error_handle(token):
|
||||
raise Exception(f"Unexpected token '{token.value}' ({token.name}) at line {token.source_pos.lineno}, column {token.source_pos.colno}.")
|
||||
|
||||
## Finish ##
|
||||
self.parser = self.pg.build()
|
58
gwendolyn/funcs/other/roll/roll.py
Normal file
@ -0,0 +1,58 @@
|
||||
import os, sys; sys.path.append(os.path.dirname(os.path.realpath(__file__)))
|
||||
|
||||
from lexer import Lexer
|
||||
from parser import Parser
|
||||
from ast_nodes import Exp, ExpInt, ExpBinop, ExpLet, ExpVar, ExpRoll, Roll, RollKeepHighest, RollKeepLowest, RollMin, RollMax, ExpMin, ExpMax
|
||||
|
||||
|
||||
class DieRoller():
|
||||
def __init__(self) -> None:
|
||||
self.lexer = Lexer().get_lexer()
|
||||
self.parser = Parser()
|
||||
|
||||
def parse(self, string: str) -> Exp:
|
||||
tokens = self.lexer.lex(string)
|
||||
expression = self.parser.parse(tokens)
|
||||
return expression
|
||||
|
||||
def roll(self, string: str):
|
||||
exp = self.parse(string)
|
||||
# print(exp)
|
||||
# exit()
|
||||
show = exp.show({})
|
||||
eval = exp.eval({})
|
||||
if show is None or eval is None or isinstance(eval,tuple):
|
||||
raise Exception("Something went wrong")
|
||||
return f"**Result**: {show}\n**Total**: {eval}"
|
||||
|
||||
if __name__ == "__main__":
|
||||
d = DieRoller()
|
||||
test_strings = [
|
||||
("4", ExpInt(4)),
|
||||
("1 + 1", ExpBinop("+",ExpInt(1),ExpInt(1))),
|
||||
("5*5", ExpBinop("*",ExpInt(5),ExpInt(5))),
|
||||
("1+5/5", ExpBinop("+",ExpInt(1),ExpBinop("/",ExpInt(5),ExpInt(5)))),
|
||||
("(1+5)/6", ExpBinop("/",ExpBinop("+",ExpInt(1),ExpInt(5)),ExpInt(6))),
|
||||
("let x=5 in x*2", ExpLet("x",ExpInt(5),ExpBinop("*",ExpVar("x"),ExpInt(2)))),
|
||||
("let x= let y = 2 in y in let z = 5 in x*z", ExpLet("x",ExpLet("y",ExpInt(2),ExpVar("y")),ExpLet("z",ExpInt(5),ExpBinop("*",ExpVar("x"),ExpVar("z"))))),
|
||||
("4d6", ExpRoll(Roll(ExpInt(4),ExpInt(6)))),
|
||||
("d6", ExpRoll(Roll(ExpInt(1),ExpInt(6)))),
|
||||
("(2+2)d(3*2)", ExpRoll(Roll(ExpBinop("+",ExpInt(2),ExpInt(2)),ExpBinop("*",ExpInt(3),ExpInt(2))))),
|
||||
("4d3*2", ExpBinop("*",ExpRoll(Roll(ExpInt(4),ExpInt(3))),ExpInt(2))),
|
||||
("4d6kh3", ExpRoll(RollKeepHighest(Roll(ExpInt(4),ExpInt(6)),ExpInt(3)))),
|
||||
("2d20kl1", ExpRoll(RollKeepLowest(Roll(ExpInt(2),ExpInt(20)),ExpInt(1)))),
|
||||
("11d10kh6kl1", ExpRoll(RollKeepLowest(RollKeepHighest(Roll(ExpInt(11),ExpInt(10)),ExpInt(6)),ExpInt(1)))),
|
||||
("3d6min6", ExpRoll(RollMin(Roll(ExpInt(3),ExpInt(6)),ExpInt(6)))),
|
||||
("3d6max1", ExpRoll(RollMax(Roll(ExpInt(3),ExpInt(6)),ExpInt(1)))),
|
||||
("let x = 1d4 in (x)min3", ExpLet("x",ExpRoll(Roll(ExpInt(1),ExpInt(4))),ExpMin(ExpVar("x"),ExpInt(3)))),
|
||||
("10max(2d10)",ExpMax(ExpInt(10),ExpRoll(Roll(ExpInt(2),ExpInt(10)))))
|
||||
]
|
||||
for s, t in test_strings:
|
||||
r = d.parse(s)
|
||||
correct = r == t
|
||||
print(f"{str(s) : <20}: {'✅' if correct else '❌'} ({r.eval({})})")
|
||||
if not correct:
|
||||
print(f"Expected: {t}")
|
||||
print(f"Actual: {r}\n")
|
||||
|
||||
print(d.roll(input(":")))
|
@ -1,135 +1,76 @@
|
||||
"""
|
||||
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 finnhub # Used to add a finhub client to the bot
|
||||
import discord # Used for discord.Intents and discord.Status
|
||||
import discord_slash # 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.funcs import Money, StarWars, Games, Other, LookupFuncs
|
||||
from gwendolyn.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: discord_slash.context.SlashContext)
|
||||
defer(ctx: discord_slash.context.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()
|
||||
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")
|
||||
from dotenv import load_dotenv
|
||||
from os import getenv, listdir
|
||||
|
||||
from interactions import Client, Status
|
||||
from pymongo import MongoClient # Used for database management
|
||||
|
||||
from gwendolyn.utils import log
|
||||
from gwendolyn.exceptions import NoToken, CannotConnectToService
|
||||
from gwendolyn.funcs import Other, BetterNetflix, Sonarr, Radarr, TMDb, QBittorrent
|
||||
|
||||
class Gwendolyn(Client):
|
||||
def __init__(self, testing: bool = True):
|
||||
"""Initialize the bot."""
|
||||
initiation_parameters = {
|
||||
"status": Status.DND,
|
||||
"delete_unused_application_cmds": True
|
||||
}
|
||||
super().__init__(**initiation_parameters)
|
||||
|
||||
self.testing = testing
|
||||
|
||||
self._add_clients_and_options()
|
||||
self._add_functions()
|
||||
self._add_extensions()
|
||||
|
||||
def _add_clients_and_options(self):
|
||||
"""Add all the client, option and credentials objects."""
|
||||
load_dotenv()
|
||||
self.bot_token = getenv("DISCORD_TOKEN")
|
||||
|
||||
if self.bot_token == "TOKEN":
|
||||
raise NoToken()
|
||||
|
||||
self.admins = getenv("ADMINS").split(",")
|
||||
|
||||
mongo_user = getenv("MONGODB_USER")
|
||||
mongo_password = getenv("MONGODB_PASSWORD")
|
||||
mongo_url = f"mongodb+srv://{mongo_user}:{mongo_password}@gwendolyn"
|
||||
mongo_url += ".qkwfy.mongodb.net/Gwendolyn?retryWrites=true&w=majority"
|
||||
database_client = MongoClient(mongo_url)
|
||||
|
||||
try:
|
||||
database_client.admin.command("ping")
|
||||
self.log("Connected to Mango Client")
|
||||
except:
|
||||
raise CannotConnectToService("Mango Client")
|
||||
|
||||
if self.testing:
|
||||
self.log("Testing mode")
|
||||
self.database = database_client["Gwendolyn-Test"]
|
||||
self.load_extension("interactions.ext.jurigged")
|
||||
else:
|
||||
self.database = database_client["Gwendolyn"]
|
||||
|
||||
def _add_functions(self):
|
||||
self.other = Other(self)
|
||||
self.better_netflix = BetterNetflix(
|
||||
self,
|
||||
radarr=Radarr(getenv("RADARR_IP"),getenv("RADARR_PORT"),getenv("RADARR_API_KEY")),
|
||||
sonarr=Sonarr(getenv("SONARR_IP"),getenv("SONARR_PORT"),getenv("SONARR_API_KEY")),
|
||||
tmdb=TMDb(getenv("TMDB_API_ACCESS_TOKEN")),
|
||||
qbittorrent=QBittorrent(getenv("QBITTORRENT_IP"),getenv("QBITTORRENT_PORT"),getenv("QBITTORRENT_USERNAME"),getenv("QBITTORRENT_PASSWORD"))
|
||||
)
|
||||
|
||||
def _add_extensions(self):
|
||||
"""Load cogs."""
|
||||
for filename in listdir("./gwendolyn/ext"):
|
||||
if filename.endswith(".py"):
|
||||
self.load_extension(f"gwendolyn.ext.{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(messages, channel, level)
|
||||
|
||||
def start(self):
|
||||
super().start(self.bot_token)
|
@ -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.
|
@ -1,413 +1,70 @@
|
||||
{
|
||||
"add_movie" : {
|
||||
"name" : "add_movie",
|
||||
"description" : "Request a movie for Plex",
|
||||
"options" : [
|
||||
{
|
||||
"name" : "movie",
|
||||
"description" : "The movie to request",
|
||||
"type" : 3,
|
||||
"required" : "true"
|
||||
}
|
||||
]
|
||||
"misc": {
|
||||
"hello" : {
|
||||
"name" : "hello",
|
||||
"description" : "Greet Gwendolyn"
|
||||
},
|
||||
"help" : {
|
||||
"name" : "help",
|
||||
"description" : "Get help with a command",
|
||||
"options" : [
|
||||
{
|
||||
"name" : "command",
|
||||
"description" : "The command you want help with",
|
||||
"type" : 3,
|
||||
"required" : "false"
|
||||
}
|
||||
]
|
||||
},
|
||||
"echo" : {
|
||||
"name": "echo",
|
||||
"description": "Make Gwendolyn repeat something",
|
||||
"options" : [
|
||||
{
|
||||
"name" : "text",
|
||||
"description" : "The text you want Gwendolyn to repeat",
|
||||
"type" : 3,
|
||||
"required" : "true"
|
||||
}
|
||||
]
|
||||
},
|
||||
"gen_name" : {
|
||||
"name": "gen_name",
|
||||
"description": "Generate a random name"
|
||||
},
|
||||
"ping" : {
|
||||
"name" : "ping",
|
||||
"description" : "Get the Gwendolyn's latency to the server"
|
||||
},
|
||||
"roll" : {
|
||||
"name": "roll",
|
||||
"description": "Roll dice",
|
||||
"options": [
|
||||
{
|
||||
"name": "dice",
|
||||
"description": "The dice to be rolled",
|
||||
"type": 3,
|
||||
"required": "false"
|
||||
}
|
||||
]
|
||||
},
|
||||
"stop" : {
|
||||
"name" : "stop",
|
||||
"description" : "Stop Gwendolyn"
|
||||
},
|
||||
"thank" : {
|
||||
"name" : "thank",
|
||||
"description" : "Thank Gwendolyn for her service"
|
||||
}
|
||||
},
|
||||
"add_show" : {
|
||||
"name" : "add_show",
|
||||
"description" : "Request a show for Plex",
|
||||
"options" : [
|
||||
{
|
||||
"name" : "show",
|
||||
"description" : "The show to request",
|
||||
"type" : 3,
|
||||
"required" : "true"
|
||||
}
|
||||
]
|
||||
},
|
||||
"balance" : {
|
||||
"name" : "balance",
|
||||
"description" : "See your balance of GwendoBucks"
|
||||
},
|
||||
"blackjack_bet" : {
|
||||
"base" : "blackjack",
|
||||
"name" : "bet",
|
||||
"description" : "Enter the current blackjack game with a bet",
|
||||
"options" : [
|
||||
{
|
||||
"name" : "bet",
|
||||
"description" : "Your bet",
|
||||
"type" : 4,
|
||||
"required" : "true"
|
||||
}
|
||||
]
|
||||
},
|
||||
"blackjack_cards" : {
|
||||
"base" : "blackjack",
|
||||
"name" : "cards",
|
||||
"description" : "Get a count of the cards used in blackjack games"
|
||||
},
|
||||
"blackjack_hilo" : {
|
||||
"base" : "blackjack",
|
||||
"name" : "hilo",
|
||||
"description" : "Get the current hi-lo value for the cards used in blackjack games"
|
||||
},
|
||||
"blackjack_shuffle" : {
|
||||
"base" : "blackjack",
|
||||
"name" : "shuffle",
|
||||
"description" : "Shuffle the cards used in blackjack games"
|
||||
},
|
||||
"blackjack_start" : {
|
||||
"base" : "blackjack",
|
||||
"name" : "start",
|
||||
"description" : "Start a game of blackjack"
|
||||
},
|
||||
"connect_four_start_gwendolyn" : {
|
||||
"base" : "connect_four",
|
||||
"subcommand_group" : "start",
|
||||
"name" : "Gwendolyn",
|
||||
"description" : "Start a game of connect four against Gwendolyn",
|
||||
"options" : [
|
||||
{
|
||||
"name" : "difficulty",
|
||||
"description" : "The difficulty of Gwendolyn's AI",
|
||||
"type" : 4,
|
||||
"required" : "false"
|
||||
}
|
||||
]
|
||||
},
|
||||
"connect_four_start_user" : {
|
||||
"base" : "connect_four",
|
||||
"subcommand_group" : "start",
|
||||
"name" : "user",
|
||||
"description" : "Start a game of connect four against another user",
|
||||
"options" : [
|
||||
{
|
||||
"name" : "user",
|
||||
"description" : "The user to start a game against",
|
||||
"type" : 6,
|
||||
"required" : "true"
|
||||
}
|
||||
]
|
||||
},
|
||||
"downloading" : {
|
||||
"name" : "downloading",
|
||||
"description" : "See current downloads for Plex",
|
||||
"options" : [
|
||||
{
|
||||
"name" : "parameters",
|
||||
"description" : "Parameters for the command",
|
||||
"type" : 3,
|
||||
"required" : "false"
|
||||
}
|
||||
]
|
||||
},
|
||||
"game" : {
|
||||
"name" : "game",
|
||||
"description" : "Set the 'playing' text for Gwendolyn",
|
||||
"options" : [
|
||||
{
|
||||
"name" : "game_text",
|
||||
"description" : "The game to set the 'playing' text to",
|
||||
"type" : 3,
|
||||
"required" : "true"
|
||||
}
|
||||
]
|
||||
},
|
||||
"give" : {
|
||||
"name" : "give",
|
||||
"description" : "Give GwendoBucks to another user",
|
||||
"options" : [
|
||||
{
|
||||
"name" : "user",
|
||||
"description" : "The user you're sending GwendoBucks to",
|
||||
"type" : 6,
|
||||
"required" : "true"
|
||||
},
|
||||
{
|
||||
"name" : "amount",
|
||||
"description" : "The number of GwendoBucks you're sending",
|
||||
"type" : 4,
|
||||
"required" : "true"
|
||||
}
|
||||
]
|
||||
},
|
||||
"hangman" : {
|
||||
"name" : "hangman",
|
||||
"description" : "Start a game of hangman"
|
||||
},
|
||||
"hello" : {
|
||||
"name" : "hello",
|
||||
"description" : "Greet Gwendolyn"
|
||||
},
|
||||
"help" : {
|
||||
"name" : "help",
|
||||
"description" : "Get help with a command",
|
||||
"options" : [
|
||||
{
|
||||
"name" : "command",
|
||||
"description" : "The command you want help with",
|
||||
"type" : 3,
|
||||
"required" : "false"
|
||||
}
|
||||
]
|
||||
},
|
||||
"hex_place" : {
|
||||
"base" : "hex",
|
||||
"name" : "place",
|
||||
"description" : "Place a piece on the hex board",
|
||||
"options" : [
|
||||
{
|
||||
"name" : "coordinates",
|
||||
"description" : "The coordinates to place the piece at",
|
||||
"type" : 3,
|
||||
"required" : "true"
|
||||
}
|
||||
]
|
||||
},
|
||||
"hex_start_gwendolyn" : {
|
||||
"base" : "hex",
|
||||
"subcommand_group" : "start",
|
||||
"name" : "Gwendolyn",
|
||||
"description" : "Start a game of hex against Gwendolyn",
|
||||
"options" : [
|
||||
{
|
||||
"name" : "difficulty",
|
||||
"description" : "The difficulty of Gwendolyn's AI",
|
||||
"type" : 4,
|
||||
"required" : "false"
|
||||
}
|
||||
]
|
||||
},
|
||||
"hex_start_user" : {
|
||||
"base" : "hex",
|
||||
"subcommand_group" : "start",
|
||||
"name" : "user",
|
||||
"description" : "Start a game of hex against another user",
|
||||
"options" : [
|
||||
{
|
||||
"name" : "user",
|
||||
"description" : "The user to start a game against",
|
||||
"type" : 6,
|
||||
"required" : "true"
|
||||
}
|
||||
]
|
||||
},
|
||||
"hex_surrender" : {
|
||||
"base" : "hex",
|
||||
"name" : "surrender",
|
||||
"description" : "Surrender the game of hex"
|
||||
},
|
||||
"hex_swap" : {
|
||||
"base" : "hex",
|
||||
"name" : "swap",
|
||||
"description" : "Perform a hex swap"
|
||||
},
|
||||
"hex_undo" : {
|
||||
"base" : "hex",
|
||||
"name" : "undo",
|
||||
"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" : {
|
||||
"name" : "image",
|
||||
"description" : "Get a random image from Bing"
|
||||
},
|
||||
"monster" : {
|
||||
"name" : "monster",
|
||||
"description" : "Look up a monster",
|
||||
"options" : [
|
||||
{
|
||||
"name" : "query",
|
||||
"description" : "The monster to look up",
|
||||
"type" : 3,
|
||||
"required" : "true"
|
||||
}
|
||||
]
|
||||
},
|
||||
"movie" : {
|
||||
"name" : "movie",
|
||||
"description" : "Get the name and information of a random movie"
|
||||
},
|
||||
"name" : {
|
||||
"name" : "name",
|
||||
"description" : "Generate a random name"
|
||||
},
|
||||
"ping" : {
|
||||
"name" : "ping",
|
||||
"description" : "Get the Gwendolyn's latency to the server"
|
||||
},
|
||||
"roll" : {
|
||||
"name" : "roll",
|
||||
"description" : "Roll rpg dice",
|
||||
"options" : [
|
||||
{
|
||||
"name" : "dice",
|
||||
"description" : "The dice to roll",
|
||||
"type" : 3,
|
||||
"required" : "false"
|
||||
}
|
||||
]
|
||||
},
|
||||
"spell" : {
|
||||
"name" : "spell",
|
||||
"description" : "Look up a spell",
|
||||
"options" : [
|
||||
{
|
||||
"name" : "query",
|
||||
"description" : "The spell to look up",
|
||||
"type" : 3,
|
||||
"required" : "true"
|
||||
}
|
||||
]
|
||||
},
|
||||
"star_wars_character" : {
|
||||
"name" : "star_wars_character",
|
||||
"description" : "Manage your Star Wars character sheet",
|
||||
"options" : [
|
||||
{
|
||||
"name" : "parameters",
|
||||
"description" : "The parameters for the command",
|
||||
"type" : 3,
|
||||
"required" : "false"
|
||||
}
|
||||
]
|
||||
},
|
||||
"star_wars_crit" : {
|
||||
"name" : "star_wars_crit",
|
||||
"description" : "Roll a Star Wars critical injury",
|
||||
"options" : [
|
||||
{
|
||||
"name" : "severity",
|
||||
"description" : "The severity of the hit",
|
||||
"type" : 4,
|
||||
"required" : "true"
|
||||
}
|
||||
]
|
||||
},
|
||||
"star_wars_destiny" : {
|
||||
"name" : "star_wars_destiny",
|
||||
"description" : "Use and see Star Wars Destiny points",
|
||||
"options" : [
|
||||
{
|
||||
"name" : "parameters",
|
||||
"description" : "The parameters for the command",
|
||||
"type" : 3,
|
||||
"required" : "false"
|
||||
}
|
||||
]
|
||||
},
|
||||
"star_wars_roll" : {
|
||||
"name" : "star_wars_roll",
|
||||
"description" : "Roll Star Wars dice",
|
||||
"options" : [
|
||||
{
|
||||
"name" : "dice",
|
||||
"description" : "The dice, or ability, to roll",
|
||||
"type" : 3,
|
||||
"required" : "false"
|
||||
}
|
||||
]
|
||||
},
|
||||
"stop" : {
|
||||
"name" : "stop",
|
||||
"description" : "Restart Gwendolyn"
|
||||
},
|
||||
"tavern" : {
|
||||
"name" : "tavern",
|
||||
"description" : "Generate a random tavern"
|
||||
},
|
||||
"thank" : {
|
||||
"name" : "thank",
|
||||
"description" : "Thank Gwendolyn for her service"
|
||||
},
|
||||
"trivia" : {
|
||||
"name" : "trivia",
|
||||
"description" : "Play a game of trivia",
|
||||
"options" : [
|
||||
{
|
||||
"name" : "answer",
|
||||
"description" : "Your answer to the trivia question",
|
||||
"type" : 3,
|
||||
"required" : "false",
|
||||
"choices" : [
|
||||
{
|
||||
"name" : "a",
|
||||
"value" : "a"
|
||||
},
|
||||
{
|
||||
"name" : "b",
|
||||
"value" : "b"
|
||||
},
|
||||
{
|
||||
"name" : "c",
|
||||
"value" : "c"
|
||||
},
|
||||
{
|
||||
"name" : "d",
|
||||
"value" : "d"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"wiki" : {
|
||||
"name" : "wiki",
|
||||
"description" : "Searches for and gets the info for a wiki page",
|
||||
"options" : [
|
||||
{
|
||||
"name" : "page",
|
||||
"description" : "The page to find",
|
||||
"type" : 3,
|
||||
"required" : "false"
|
||||
}
|
||||
]
|
||||
},
|
||||
"wolf" : {
|
||||
"name" : "wolf",
|
||||
"description" : "Performs a search on Wolfram Alpha",
|
||||
"options" : [
|
||||
{
|
||||
"name" : "query",
|
||||
"description" : "What to search for on Wolfram Alpha",
|
||||
"type" : 3,
|
||||
"required" : "true"
|
||||
}
|
||||
]
|
||||
},
|
||||
"wordle_start": {
|
||||
"base": "wordle",
|
||||
"name" : "start",
|
||||
"description": "Start a game of wordle",
|
||||
"options": [
|
||||
{
|
||||
"name": "letters",
|
||||
"description" : "How many letters the word should be",
|
||||
"type": 4,
|
||||
"required": "false"
|
||||
}
|
||||
]
|
||||
},
|
||||
"wordle_guess": {
|
||||
"base": "wordle",
|
||||
"name" : "guess",
|
||||
"description": "Guess a word in wordle",
|
||||
"options": [
|
||||
{
|
||||
"name": "guess",
|
||||
"description" : "Your guess",
|
||||
"type": 3,
|
||||
"required": "true"
|
||||
}
|
||||
]
|
||||
"better_netflix": {
|
||||
"movie": {
|
||||
"name": "movie",
|
||||
"description": "Get a random movie from Better Netflix"
|
||||
},
|
||||
"show": {
|
||||
"name": "show",
|
||||
"description": "Get a random show from Better Netflix"
|
||||
}
|
||||
}
|
||||
}
|
@ -56,11 +56,8 @@
|
||||
]
|
||||
},
|
||||
"txt": {
|
||||
"gwendolyn/resources/star_wars/destinyPoints.txt": "",
|
||||
"gwendolyn/resources/movies.txt": "The Room",
|
||||
"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",
|
||||
"options.txt" : "Testing: True\nTesting guild ids:\nAdmins:"
|
||||
".env" : "# Discord Bot Token\nDISCORD_TOKEN=TOKEN\n\n# Wordnik API Key\nWORDNIK_API_KEY=KEY\n\n# MongoDB Credentials\nMONGODB_USER=USERNAME\nMONGODB_PASSWORD=PASSWORD\n\n# WolframAlpha AppID\nWOLFRAM_APPID=APPID\n\n# Radarr API Key\nRADARR_API_KEY=KEY\n\n# Sonarr API Key\nSONARR_API_KEY=KEY\n\n# qBittorrent Credentials\nQBITTORRENT_USERNAME=USER\nQBITTORRENT_PASSWORD=PASSWORD\n\n# Admins (comma-separated list of Discord user IDs)\nADMINS="
|
||||
},
|
||||
"folder" : [
|
||||
"gwendolyn/resources/lookup",
|
||||
@ -68,7 +65,6 @@
|
||||
"gwendolyn/resources/games/connect_four_boards",
|
||||
"gwendolyn/resources/games/hex_boards",
|
||||
"gwendolyn/resources/games/hangman_boards",
|
||||
"gwendolyn/resources/plex",
|
||||
"gwendolyn/resources/games/old_images"
|
||||
]
|
||||
}
|
BIN
gwendolyn/resources/temp.jpg
Normal file
After Width: | Height: | Size: 2.8 KiB |
@ -1,12 +1,5 @@
|
||||
"""A collections of utilities used by Gwendolyn and her functions."""
|
||||
|
||||
__all__ = ["get_options", "get_credentials", "DatabaseFuncs", "EventHandler",
|
||||
"ErrorHandler", "get_params", "log_this", "cap", "make_files",
|
||||
"replace_multiple", "emoji_to_command"]
|
||||
__all__ = ["make_files","log","PARAMS"]
|
||||
|
||||
from .helper_classes import DatabaseFuncs
|
||||
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)
|
||||
from .util_functions import make_files, log, PARAMS
|
||||
|
@ -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 logging # Used for logging
|
||||
import os # Used by make_files() to check if files exist
|
||||
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
|
||||
FORMAT = " %(asctime)s | %(name)-16s | %(levelname)-8s | %(message)s"
|
||||
@ -53,115 +23,6 @@ handler.setFormatter(logging.Formatter(fmt=PRINTFORMAT, datefmt=DATEFORMAT))
|
||||
printer.addHandler(handler)
|
||||
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):
|
||||
"""
|
||||
Sanitize and create a dictionary from a string.
|
||||
|
||||
Each element is created from a line with a : in it. The key is left
|
||||
of the :, the value is right of it.
|
||||
|
||||
*Parameters*
|
||||
------------
|
||||
data: str
|
||||
The string to create a dict from.
|
||||
lower_case_value: bool = False
|
||||
Whether the value of each element should be lowercase.
|
||||
|
||||
*Returns*
|
||||
---------
|
||||
dct: dict
|
||||
The sanitized dictionary of elements.
|
||||
|
||||
"""
|
||||
data = data.splitlines()
|
||||
dct = {}
|
||||
for line in data:
|
||||
if line[0] != "#" and ":" in line:
|
||||
line_values = line.split(":")
|
||||
line_values[0] = line_values[0].lower()
|
||||
line_values[1] = line_values[1].replace(" ", "")
|
||||
if lower_case_value:
|
||||
line_values[1] = line_values[1].lower()
|
||||
|
||||
if line_values[0] in ["testing guild ids", "admins"]:
|
||||
line_values[1] = line_values[1].split(",")
|
||||
if all(i.isnumeric() for i in line_values[1]):
|
||||
line_values[1] = [int(i) for i in line_values[1]]
|
||||
|
||||
if any(i == line_values[1] for i in ["true", "false"]):
|
||||
line_values[1] = (line_values[1] == "true")
|
||||
|
||||
dct[line_values[0]] = line_values[1]
|
||||
|
||||
return dct
|
||||
|
||||
|
||||
def get_options():
|
||||
"""
|
||||
Get the bot options as dict.
|
||||
|
||||
*Returns*
|
||||
---------
|
||||
options: dict
|
||||
The options of the bot.
|
||||
"""
|
||||
with open("options.txt", "r", encoding="utf-8") as file_pointer:
|
||||
data = sanitize(file_pointer.read(), True)
|
||||
|
||||
options = {}
|
||||
|
||||
options["testing"] = data["testing"]
|
||||
options["guild_ids"] = data["testing guild ids"]
|
||||
options["admins"] = data["admins"]
|
||||
return options
|
||||
|
||||
def get_credentials():
|
||||
"""
|
||||
Returns the credentials used by the bot as a dict.
|
||||
|
||||
*Returns*
|
||||
---------
|
||||
credentials: dict
|
||||
The credentials used by the bot.
|
||||
"""
|
||||
with open("credentials.txt", "r", encoding="utf-8") as file_pointer:
|
||||
data = sanitize(file_pointer.read())
|
||||
|
||||
credentials = {}
|
||||
|
||||
credentials["token"] = data["bot token"]
|
||||
credentials["finnhub_key"] = data["finnhub api key"]
|
||||
credentials["wordnik_key"] = data["wordnik api key"]
|
||||
credentials["mongo_db_user"] = data["mongodb user"]
|
||||
credentials["mongo_db_password"] = data["mongodb password"]
|
||||
credentials["wolfram_alpha_key"] = data["wolframalpha appid"]
|
||||
credentials["radarr_key"] = data["radarr api key"]
|
||||
credentials["sonarr_key"] = data["sonarr api key"]
|
||||
credentials["qbittorrent_username"] = data["qbittorrent username"]
|
||||
credentials["qbittorrent_password"] = data["qbittorrent password"]
|
||||
|
||||
return credentials
|
||||
|
||||
def long_strings():
|
||||
"""
|
||||
Get the data from gwendolyn/resources/long_strings.json.
|
||||
|
||||
*Returns*
|
||||
---------
|
||||
data: dict
|
||||
The long strings and their keys.
|
||||
"""
|
||||
long_strings_path = "gwendolyn/resources/long_strings.json"
|
||||
with open(long_strings_path, "r", encoding="utf-8") as file_pointer:
|
||||
data = json.load(file_pointer)
|
||||
|
||||
return data
|
||||
|
||||
|
||||
def get_params():
|
||||
"""
|
||||
Get the slash command parameters.
|
||||
@ -175,16 +36,11 @@ def get_params():
|
||||
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
|
||||
|
||||
PARAMS = get_params()
|
||||
|
||||
def log_this(messages, channel: str = "", level: int = 20):
|
||||
def log(messages, channel: str = "", level: int = 20):
|
||||
"""
|
||||
Log something in Gwendolyn's logs.
|
||||
|
||||
@ -222,49 +78,19 @@ def log_this(messages, channel: str = "", level: int = 20):
|
||||
for log_message in messages:
|
||||
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():
|
||||
"""Create all the files and directories needed by Gwendolyn."""
|
||||
def make_json_file(path, content):
|
||||
"""Create json file if it doesn't exist."""
|
||||
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:
|
||||
json.dump(content, file_pointer, indent=4)
|
||||
|
||||
def make_txt_file(path, content):
|
||||
"""Create txt file if it doesn't exist."""
|
||||
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:
|
||||
file_pointer.write(content)
|
||||
|
||||
@ -272,121 +98,18 @@ def make_files():
|
||||
"""Create directory if it doesn't exist."""
|
||||
if not os.path.isdir(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"
|
||||
with open(file_path, "r", encoding="utf-8") as file_pointer:
|
||||
data = json.load(file_pointer)
|
||||
|
||||
for path in data["folder"]:
|
||||
directory(path)
|
||||
|
||||
for path, content in data["json"].items():
|
||||
make_json_file(path, content)
|
||||
|
||||
for path, content in data["txt"].items():
|
||||
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(':')
|
||||
|
6
gwendolyn_old/__init__.py
Normal file
@ -0,0 +1,6 @@
|
||||
"""The main module for Gwendolyn."""
|
||||
# pylint: disable=invalid-name
|
||||
|
||||
__all__ = ["funcs", "utils", "Gwendolyn"]
|
||||
|
||||
from .gwendolyn_client import Gwendolyn
|
@ -1,8 +1,8 @@
|
||||
"""Contains all the cogs that deal with game commands."""
|
||||
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()
|
||||
|
||||
@ -24,11 +24,6 @@ class GamesCog(commands.Cog):
|
||||
"""Give another user an amount of GwendoBucks."""
|
||||
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"])
|
||||
async def trivia(self, ctx, answer=""):
|
||||
"""Run a game of trivia."""
|
@ -1,8 +1,8 @@
|
||||
"""Contains the LookupCog, which deals with the lookup commands."""
|
||||
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()
|
||||
|
@ -1,9 +1,9 @@
|
||||
"""Contains the MiscCog, which deals with miscellaneous commands."""
|
||||
from discord.ext import commands # Has the cog class
|
||||
from discord_slash import cog_ext # Used for slash commands
|
||||
from discord_slash.context import SlashContext
|
||||
from interactions import cog_ext # Used for slash commands
|
||||
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()
|
||||
|
@ -1,8 +1,8 @@
|
||||
"""Contains the StarWarsCog, which deals with Star Wars 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()
|
||||
|
11
gwendolyn_old/exceptions.py
Normal file
@ -0,0 +1,11 @@
|
||||
"""Exceptions for Gwendolyn"""
|
||||
|
||||
class GameNotInDatabase(Exception):
|
||||
def __init__(self, game: str, channel: str):
|
||||
self.message = f"There is no {game} game in channel {channel}"
|
||||
super().__init__(self.message)
|
||||
|
||||
class InvalidInteraction(Exception):
|
||||
def __init__(self, custom_id: str, decoded: str):
|
||||
self.message = f"{custom_id = }, {decoded = }"
|
||||
super().__init__(self.message)
|
11
gwendolyn_old/funcs/__init__.py
Normal file
@ -0,0 +1,11 @@
|
||||
"""A collection of all Gwendolyn functions."""
|
||||
|
||||
__all__ = ["Games", "Money", "LookupFuncs", "StarWars"]
|
||||
|
||||
from .games import Money, Games
|
||||
|
||||
from .lookup import LookupFuncs
|
||||
|
||||
from .other import Other
|
||||
|
||||
from .star_wars_funcs import StarWars
|
@ -12,13 +12,13 @@ import math # Used for flooring decimal numbers
|
||||
import datetime # Used to generate the game id
|
||||
import asyncio # Used for sleeping
|
||||
|
||||
from discord_slash.context import InteractionContext as IntCont # Used for
|
||||
from interactions import InteractionContext as IntCont # Used for
|
||||
# typehints
|
||||
from discord_slash.context import ComponentContext
|
||||
from interactions import ComponentContext
|
||||
from discord.abc import Messageable
|
||||
from PIL import Image
|
||||
|
||||
from gwendolyn.utils import replace_multiple
|
||||
from gwendolyn_old.utils import replace_multiple
|
||||
|
||||
from .game_base import CardGame, CardDrawer
|
||||
|
@ -15,13 +15,13 @@ import discord # Used for typehints, discord.file and to check whether
|
||||
# the opponent in ConnectFour.start is a discord.User
|
||||
|
||||
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
|
||||
from discord_slash.utils.manage_components import (create_button,
|
||||
from interactions.utils.manage_components import (create_button,
|
||||
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
|
||||
|
||||
ROWCOUNT = 6
|
@ -4,14 +4,13 @@ from typing import Union
|
||||
|
||||
from discord import File, User
|
||||
from discord.abc import Messageable
|
||||
from discord_slash.utils.manage_components import (create_button,
|
||||
create_actionrow)
|
||||
from discord_slash.context import InteractionContext as IntCont
|
||||
from interactions import Button, ActionRow
|
||||
from interactions import InteractionContext as IntCont
|
||||
|
||||
from PIL import ImageFont, Image, ImageDraw
|
||||
|
||||
from gwendolyn.exceptions import GameNotInDatabase
|
||||
from gwendolyn.utils import encode_id
|
||||
from gwendolyn_old.exceptions import GameNotInDatabase
|
||||
from gwendolyn_old.utils import encode_id
|
||||
|
||||
class GameBase():
|
||||
"""The base class for the games."""
|
||||
@ -29,7 +28,7 @@ class GameBase():
|
||||
button_objects = []
|
||||
for label, data, style in buttons:
|
||||
custom_id = encode_id([self.game_name] + data)
|
||||
button_objects.append(create_button(
|
||||
button_objects.append(Button(
|
||||
style=style,
|
||||
label=label,
|
||||
custom_id=custom_id
|
||||
@ -37,7 +36,7 @@ class GameBase():
|
||||
|
||||
action_rows = []
|
||||
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
|
||||
|
@ -8,7 +8,6 @@ Has a container for game functions.
|
||||
"""
|
||||
|
||||
|
||||
from .invest import Invest
|
||||
from .trivia import Trivia
|
||||
from .blackjack import Blackjack
|
||||
from .connect_four import ConnectFour
|
||||
@ -25,8 +24,6 @@ class Games():
|
||||
------------
|
||||
bot: Gwendolyn
|
||||
The instance of Gwendolyn.
|
||||
invest
|
||||
Contains investment functions.
|
||||
blackjack
|
||||
Contains blackjack functions.
|
||||
connect_four
|
||||
@ -41,7 +38,6 @@ class Games():
|
||||
"""Initialize the container."""
|
||||
self.bot = bot
|
||||
|
||||
self.invest = Invest(bot)
|
||||
self.trivia = Trivia(bot)
|
||||
self.blackjack = Blackjack(bot)
|
||||
self.connect_four = ConnectFour(bot)
|
@ -16,14 +16,14 @@ import random # Used to draw poorly
|
||||
import requests # Used for getting the word in Hangman.start()
|
||||
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)
|
||||
from discord_slash.model import ButtonStyle
|
||||
from discord_slash.context import SlashContext, ComponentContext
|
||||
from interactions.model import ButtonStyle
|
||||
from interactions import SlashContext, ComponentContext
|
||||
# Used for typehints
|
||||
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():
|
||||
"""
|
@ -6,7 +6,7 @@ Contains the code that deals with money.
|
||||
Money
|
||||
Deals with money.
|
||||
"""
|
||||
import discord_slash # Used for typehints
|
||||
import interactions # Used for typehints
|
||||
import discord # Used for typehints
|
||||
|
||||
|
||||
@ -17,9 +17,9 @@ class Money():
|
||||
*Methods*
|
||||
---------
|
||||
checkBalance(user: str)
|
||||
sendBalance(ctx: discord_slash.context.SlashContext)
|
||||
sendBalance(ctx: interactions.SlashContext)
|
||||
addMoney(user: str, amount: int)
|
||||
giveMoney(ctx: discord_slash.context.SlashContext, user: discord.User,
|
||||
giveMoney(ctx: interactions.SlashContext, user: discord.User,
|
||||
amount: int)
|
||||
|
||||
*Attributes*
|
||||
@ -58,13 +58,13 @@ class Money():
|
||||
else:
|
||||
return 0
|
||||
|
||||
async def sendBalance(self, ctx: discord_slash.context.SlashContext):
|
||||
async def sendBalance(self, ctx: interactions.SlashContext):
|
||||
"""
|
||||
Get your own account balance.
|
||||
|
||||
*Parameters*
|
||||
------------
|
||||
ctx: discord_slash.context.SlashContext
|
||||
ctx: interactions.SlashContext
|
||||
The context of the command.
|
||||
"""
|
||||
await self.bot.defer(ctx)
|
||||
@ -104,14 +104,14 @@ class Money():
|
||||
self.database["users"].insert_one(new_user)
|
||||
|
||||
# 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):
|
||||
"""
|
||||
Give someone else money from your account.
|
||||
|
||||
*Parameters*
|
||||
------------
|
||||
ctx: discord_slash.context.SlashContext
|
||||
ctx: interactions.SlashContext
|
||||
The context of the command.
|
||||
user: discord.User
|
||||
The user to give money.
|
@ -11,7 +11,7 @@ import json # Used to read data from api
|
||||
import random # Used to shuffle answers
|
||||
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():
|
@ -5,7 +5,7 @@ from PIL import Image, ImageDraw
|
||||
|
||||
from .game_base import DatabaseGame, BaseDrawer
|
||||
|
||||
from gwendolyn.exceptions import GameNotInDatabase
|
||||
from gwendolyn_old.exceptions import GameNotInDatabase
|
||||
|
||||
IMAGE_MARGIN = 90
|
||||
SQUARE_SIZE = 150
|
@ -2,7 +2,7 @@ import math
|
||||
import json
|
||||
import discord
|
||||
|
||||
from gwendolyn.utils import cap
|
||||
from gwendolyn_old.utils import cap
|
||||
|
||||
STATS = [
|
||||
"strength",
|
5
gwendolyn_old/funcs/other/__init__.py
Normal file
@ -0,0 +1,5 @@
|
||||
"""Misc. functions for Gwendolyn."""
|
||||
|
||||
__all__ = ["Other"]
|
||||
|
||||
from .other import Other
|
192
gwendolyn_old/funcs/other/other.py
Normal file
@ -0,0 +1,192 @@
|
||||
import random # Used in movie_func
|
||||
import datetime # Used in hello_func
|
||||
import urllib # Used in image_func
|
||||
import ast
|
||||
|
||||
import imdb # Used in movie_func
|
||||
import discord # Used in movie_func
|
||||
import lxml # Used in image_func
|
||||
import fandom # Used in find_wiki_page
|
||||
import d20 # Used in roll_dice
|
||||
|
||||
from .plex import Plex
|
||||
from .nerd_shit import NerdShit
|
||||
from .generators import Generators
|
||||
|
||||
fandom.set_lang("da")
|
||||
fandom.set_wiki("senkulpa")
|
||||
|
||||
class MyStringifier(d20.MarkdownStringifier):
|
||||
def _str_expression(self, node):
|
||||
if node.comment is None:
|
||||
result_text = "Result"
|
||||
else:
|
||||
result_text = node.comment.capitalize()
|
||||
|
||||
return f"**{result_text}**: {self._stringify(node.roll)}\n**Total**: {int(node.total)}"
|
||||
|
||||
class Other():
|
||||
def __init__(self, bot):
|
||||
self.bot = bot
|
||||
self.plex = Plex(self.bot)
|
||||
self.nerd_shit = NerdShit(self.bot)
|
||||
self.generators = Generators(self.bot)
|
||||
|
||||
# Picks a random movie and returns information about it
|
||||
async def movie_func(self, ctx):
|
||||
await self.bot.defer(ctx)
|
||||
|
||||
self.bot.log("Creating IMDb object")
|
||||
imdb_client = imdb.IMDb()
|
||||
|
||||
self.bot.log("Picking a movie")
|
||||
with open("gwendolyn/resources/movies.txt", "r") as file_pointer:
|
||||
movie_list = file_pointer.read().split("\n")
|
||||
movie_name = random.choice(movie_list)
|
||||
|
||||
self.bot.log(f"Searching for {movie_name}")
|
||||
search_result = imdb_client.search_movie(movie_name)
|
||||
|
||||
self.bot.log("Getting the data")
|
||||
movie = search_result[0]
|
||||
imdb_client.update(movie)
|
||||
|
||||
self.bot.log("Successfully ran /movie")
|
||||
|
||||
title = movie["title"]
|
||||
plot = movie['plot'][0].split("::")[0]
|
||||
cover = movie['cover url'].replace("150","600").replace("101","404")
|
||||
cast = ", ".join([i["name"] for i in movie['cast'][:5]])
|
||||
embed = discord.Embed(title=title, description=plot, color=0x24ec19)
|
||||
embed.set_thumbnail(url=cover)
|
||||
embed.add_field(name="Cast", value=cast,inline = True)
|
||||
await ctx.send(embed = embed)
|
||||
|
||||
# Responds with a greeting of a time-appropriate maner
|
||||
async def hello_func(self, ctx):
|
||||
def time_in_range(start, end, i):
|
||||
# Return true if i is in the range [start, end]
|
||||
if start <= end:
|
||||
return start <= i <= end
|
||||
else:
|
||||
return start <= i or i <= end
|
||||
|
||||
author = ctx.author.display_name
|
||||
now = datetime.datetime.now()
|
||||
if time_in_range(now.replace(hour=5, minute=0, second=0, microsecond=0),now.replace(hour=10, minute=0, second=0, microsecond=0), now):
|
||||
send_message = "Good morning, "+str(author)
|
||||
elif time_in_range(now.replace(hour=13, minute=0, second=0, microsecond=0),now.replace(hour=18, minute=0, second=0, microsecond=0), now):
|
||||
send_message = "Good afternoon, "+str(author)
|
||||
elif time_in_range(now.replace(hour=18, minute=0, second=0, microsecond=0),now.replace(hour=22, minute=0, second=0, microsecond=0), now):
|
||||
send_message = "Good evening, "+str(author)
|
||||
elif time_in_range(now.replace(hour=22, minute=0, second=0, microsecond=0),now.replace(hour=23, minute=59, second=59, microsecond=0), now):
|
||||
send_message = "Good night, "+str(author)
|
||||
else:
|
||||
send_message = "Hello, "+str(author)
|
||||
|
||||
await ctx.send(send_message)
|
||||
|
||||
# Finds a random picture online
|
||||
async def image_func(self, ctx):
|
||||
# Picks a type of camera, which decides the naming scheme
|
||||
cams = ("one","two","three","four")
|
||||
cam = random.choice(cams)
|
||||
self.bot.log("Chose cam type "+cam)
|
||||
if cam == "one":
|
||||
|
||||
search = "img_" + ''.join(
|
||||
[str(random.randint(0,9)) for _ in range(4)]
|
||||
)
|
||||
elif cam == "two":
|
||||
year = str(random.randint(2012,2016))
|
||||
month = str(random.randint(1,12)).zfill(2)
|
||||
day = str(random.randint(1,29)).zfill(2)
|
||||
search = f"IMG_{year}{month}{day}"
|
||||
elif cam == "three":
|
||||
search = f"IMAG_{str(random.randint(1,500)).zfill(4)}"
|
||||
elif cam == "four":
|
||||
search = "DSC_" + ''.join(
|
||||
[str(random.randint(0,9)) for _ in range(4)]
|
||||
)
|
||||
|
||||
self.bot.log("Searching for "+search)
|
||||
|
||||
# Searches for the image and reads the resulting web page
|
||||
page = urllib.request.urlopen("https://www.bing.com/images/search?q="+search+"&safesearch=off")
|
||||
read = page.read()
|
||||
tree = lxml.etree.HTML(read)
|
||||
images = tree.xpath('//a[@class = "iusc"]/@m')
|
||||
|
||||
if len(images) == 0:
|
||||
await ctx.send("Found no images")
|
||||
else:
|
||||
# Picks an image
|
||||
number = random.randint(1,len(images))-1
|
||||
image = ast.literal_eval(str(images[number]))
|
||||
image_url = image["murl"]
|
||||
|
||||
self.bot.log("Picked image number "+str(number))
|
||||
|
||||
# Returns the image
|
||||
self.bot.log("Successfully returned an image")
|
||||
|
||||
await ctx.send(image_url)
|
||||
|
||||
# Finds a page from the Senkulpa Wikia
|
||||
async def find_wiki_page(self, ctx, search : str):
|
||||
await self.bot.defer(ctx)
|
||||
found_page = False
|
||||
|
||||
if search != "":
|
||||
self.bot.log("Trying to find wiki page for "+search)
|
||||
search_results = fandom.search(search)
|
||||
if len(search_results) > 0:
|
||||
found_page = True
|
||||
search_result = search_results[0]
|
||||
else:
|
||||
self.bot.log("Couldn't find the page")
|
||||
await ctx.send("Couldn't find page")
|
||||
else:
|
||||
found_page = True
|
||||
self.bot.log("Searching for a random page")
|
||||
search_result = fandom.random()
|
||||
|
||||
if found_page:
|
||||
self.bot.log(f"Found page \"{search_result[0]}\"")
|
||||
page = fandom.page(pageid = search_result[1])
|
||||
content = page.summary
|
||||
|
||||
images = page.images
|
||||
if len(images) > 0:
|
||||
image = images[0]
|
||||
else:
|
||||
image = ""
|
||||
self.bot.log("Sending the embedded message",str(ctx.channel_id))
|
||||
content += f"\n[Læs mere]({page.url})"
|
||||
embed = discord.Embed(title = page.title, description = content, colour=0xDEADBF)
|
||||
if image != "":
|
||||
embed.set_thumbnail(url=image)
|
||||
|
||||
await ctx.send(embed = embed)
|
||||
|
||||
async def roll_dice(self, ctx, roll_string):
|
||||
user = ctx.author.display_name
|
||||
while len(roll_string) > 1 and roll_string[0] == " ":
|
||||
roll_string = roll_string[1:]
|
||||
|
||||
roll = d20.roll(roll_string, allow_comments=True, stringifier=MyStringifier())
|
||||
await ctx.send(f"{user} :game_die:\n{roll}")
|
||||
|
||||
async def help_func(self, ctx, command):
|
||||
if command == "":
|
||||
with open("gwendolyn/resources/help/help.txt",encoding="utf-8") as file_pointer:
|
||||
text = file_pointer.read()
|
||||
embed = discord.Embed(title = "Help", description = text,colour = 0x59f442)
|
||||
await ctx.send(embed = embed)
|
||||
else:
|
||||
self.bot.log(f"Looking for help-{command}.txt",str(ctx.channel_id))
|
||||
with open(f"gwendolyn/resources/help/help-{command}.txt",encoding="utf-8") as file_pointer:
|
||||
text = file_pointer.read()
|
||||
embed = discord.Embed(title = command.capitalize(), description = text,colour = 0x59f442)
|
||||
await ctx.send(embed = embed)
|
||||
|
@ -7,11 +7,11 @@ import imdb
|
||||
import discord
|
||||
import xmltodict
|
||||
|
||||
from discord_slash.utils.manage_components import (create_button,
|
||||
from interactions.utils.manage_components import (create_button,
|
||||
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():
|
||||
"""Container for Plex functions and commands."""
|
132
gwendolyn_old/gwendolyn_client.py
Normal 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")
|
BIN
gwendolyn_old/resources/fonts/comic-sans-bold.ttf
Normal file
BIN
gwendolyn_old/resources/fonts/futura-bold.ttf
Normal file
BIN
gwendolyn_old/resources/fonts/times-new-roman.ttf
Normal file
BIN
gwendolyn_old/resources/games/cards/0C.png
Normal file
After Width: | Height: | Size: 38 KiB |
BIN
gwendolyn_old/resources/games/cards/0D.png
Normal file
After Width: | Height: | Size: 30 KiB |
BIN
gwendolyn_old/resources/games/cards/0H.png
Normal file
After Width: | Height: | Size: 29 KiB |
BIN
gwendolyn_old/resources/games/cards/0S.png
Normal file
After Width: | Height: | Size: 30 KiB |
BIN
gwendolyn_old/resources/games/cards/2C.png
Normal file
After Width: | Height: | Size: 18 KiB |
BIN
gwendolyn_old/resources/games/cards/2D.png
Normal file
After Width: | Height: | Size: 17 KiB |
BIN
gwendolyn_old/resources/games/cards/2H.png
Normal file
After Width: | Height: | Size: 17 KiB |
BIN
gwendolyn_old/resources/games/cards/2S.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
gwendolyn_old/resources/games/cards/3C.png
Normal file
After Width: | Height: | Size: 22 KiB |
BIN
gwendolyn_old/resources/games/cards/3D.png
Normal file
After Width: | Height: | Size: 20 KiB |
BIN
gwendolyn_old/resources/games/cards/3H.png
Normal file
After Width: | Height: | Size: 20 KiB |
BIN
gwendolyn_old/resources/games/cards/3S.png
Normal file
After Width: | Height: | Size: 19 KiB |
BIN
gwendolyn_old/resources/games/cards/4C.png
Normal file
After Width: | Height: | Size: 23 KiB |
BIN
gwendolyn_old/resources/games/cards/4D.png
Normal file
After Width: | Height: | Size: 18 KiB |
BIN
gwendolyn_old/resources/games/cards/4H.png
Normal file
After Width: | Height: | Size: 19 KiB |
BIN
gwendolyn_old/resources/games/cards/4S.png
Normal file
After Width: | Height: | Size: 18 KiB |
BIN
gwendolyn_old/resources/games/cards/5C.png
Normal file
After Width: | Height: | Size: 27 KiB |
BIN
gwendolyn_old/resources/games/cards/5D.png
Normal file
After Width: | Height: | Size: 21 KiB |
BIN
gwendolyn_old/resources/games/cards/5H.png
Normal file
After Width: | Height: | Size: 23 KiB |
BIN
gwendolyn_old/resources/games/cards/5S.png
Normal file
After Width: | Height: | Size: 21 KiB |
BIN
gwendolyn_old/resources/games/cards/6C.png
Normal file
After Width: | Height: | Size: 30 KiB |
BIN
gwendolyn_old/resources/games/cards/6D.png
Normal file
After Width: | Height: | Size: 25 KiB |
BIN
gwendolyn_old/resources/games/cards/6H.png
Normal file
After Width: | Height: | Size: 26 KiB |
BIN
gwendolyn_old/resources/games/cards/6S.png
Normal file
After Width: | Height: | Size: 24 KiB |
BIN
gwendolyn_old/resources/games/cards/7C.png
Normal file
After Width: | Height: | Size: 31 KiB |
BIN
gwendolyn_old/resources/games/cards/7D.png
Normal file
After Width: | Height: | Size: 25 KiB |
BIN
gwendolyn_old/resources/games/cards/7H.png
Normal file
After Width: | Height: | Size: 26 KiB |
BIN
gwendolyn_old/resources/games/cards/7S.png
Normal file
After Width: | Height: | Size: 24 KiB |
BIN
gwendolyn_old/resources/games/cards/8C.png
Normal file
After Width: | Height: | Size: 36 KiB |
BIN
gwendolyn_old/resources/games/cards/8D.png
Normal file
After Width: | Height: | Size: 29 KiB |
BIN
gwendolyn_old/resources/games/cards/8H.png
Normal file
After Width: | Height: | Size: 29 KiB |
BIN
gwendolyn_old/resources/games/cards/8S.png
Normal file
After Width: | Height: | Size: 29 KiB |
BIN
gwendolyn_old/resources/games/cards/9C.png
Normal file
After Width: | Height: | Size: 37 KiB |
BIN
gwendolyn_old/resources/games/cards/9D.png
Normal file
After Width: | Height: | Size: 30 KiB |
BIN
gwendolyn_old/resources/games/cards/9H.png
Normal file
After Width: | Height: | Size: 29 KiB |
BIN
gwendolyn_old/resources/games/cards/9S.png
Normal file
After Width: | Height: | Size: 29 KiB |
BIN
gwendolyn_old/resources/games/cards/AC.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
gwendolyn_old/resources/games/cards/AD.png
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
gwendolyn_old/resources/games/cards/AH.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
gwendolyn_old/resources/games/cards/AS.png
Normal file
After Width: | Height: | Size: 28 KiB |
BIN
gwendolyn_old/resources/games/cards/JC.png
Normal file
After Width: | Height: | Size: 174 KiB |