This commit is contained in:
2024-10-31 22:38:59 +01:00
parent 2f4e606fbf
commit e71ba34371
12 changed files with 304 additions and 35 deletions

View File

@ -3,7 +3,7 @@ class NoToken(Exception):
self.message = "No discord bot token has been set in the .env file"
super().__init__(self.message)
class MongoCannotConnect(Exception):
def __init__(self) -> None:
self.message = "Cannot connect to the mongo client"
class CannotConnectToService(Exception):
def __init__(self, service: str) -> None:
self.message = f"Cannot connect to {service}"
super().__init__(self.message)

View 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)

View File

@ -50,3 +50,7 @@ class MiscExtension(Extension):
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)

View File

@ -1,5 +1,6 @@
"""A collection of all Gwendolyn functions."""
__all__ = ["Other"]
__all__ = ["Other","BetterNetflix", "Sonarr", "Radarr", "TMDb"]
from .other import Other
from .better_netflix import Radarr, Sonarr, BetterNetflix, TMDb

View File

@ -0,0 +1,5 @@
"""Better Netflix functions for Gwendolyn."""
__all__ = ["BetterNetflix", "Sonarr", "Radarr", "TMDb"]
from .better_netflix import BetterNetflix, Sonarr, Radarr, TMDb

View File

@ -0,0 +1,159 @@
from requests import get
from random import choice
import io
from PIL import Image
from interactions import SlashContext, Embed, EmbedFooter, EmbedAttachment, File
from gwendolyn.exceptions import CannotConnectToService
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 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, radarr: Radarr, sonarr: Sonarr, tmdb: TMDb, bot) -> None:
self.radarr = radarr
self.sonarr = sonarr
self.tmdb = tmdb
self.bot = bot
services = ["radarr","sonarr","tmdb"]
for service in services:
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)

View File

@ -357,8 +357,14 @@ class ComparePoint(Exp):
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}")
@ -451,11 +457,12 @@ class Roll(Exp):
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):
result = self.eval(vtable)
return [str(i) for i in result]
self.eval(vtable)
return self.show_list
@property
def die(self) -> int:
@ -505,10 +512,6 @@ class RollKeepHighest(Roll):
self.show_list = [str(n) if i in max_indices else f"~~{n}~~" for i, n in enumerate(r1)]
return self.result
def _show(self, vtable):
self.eval(vtable)
return self.show_list
def __repr__(self) -> str:
return f"kep_highest({self.roll},{self.exp})"
@ -548,10 +551,6 @@ class RollKeepLowest(Roll):
self.show_list = [str(n) if i in min_indices else f"~~{n}~~" for i, n in enumerate(r1)]
return self.result
def _show(self, vtable):
self.eval(vtable)
return self.show_list
def __repr__(self) -> str:
return f"kep_lowest({self.roll},{self.exp})"
@ -591,10 +590,6 @@ class RollMin(Roll):
self.result = r1
return self.result
def _show(self, vtable):
self.eval(vtable)
return self.show_list
def __repr__(self) -> str:
return f"min({self.roll},{self.exp})"
@ -634,10 +629,6 @@ class RollMax(Roll):
self.result = r1
return self.result
def _show(self, vtable):
self.eval(vtable)
return self.show_list
def __repr__(self) -> str:
return f"max({self.roll},{self.exp})"
@ -687,9 +678,53 @@ class RollExplode(Roll):
return self.result
def _show(self, vtable):
self.eval(vtable)
return self.show_list
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})"

View File

@ -2,18 +2,22 @@
from string import ascii_letters, digits
from rply import LexerGenerator
VALID_CHARACTERS = ascii_letters[:3]+ascii_letters[4:]+"_"+digits
VALID_CHARACTERS = ascii_letters+"_"+digits
TOKENS = [
("ROLL_DIE", r"d"),
("ROLL_KEEP_HIGHEST", r"kh"),
("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_GE", r"\>\="),
("SYMBOL_LT", r"\<"),
("SYMBOL_GT", r"\>"),
("SYMBOL_EQUALS", r"\="),
("SYMBOL_ARROW", r"\-\>"),
("SYMBOL_BACKSLASH", r"\\"),

View File

@ -1,7 +1,7 @@
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
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():
@ -11,11 +11,11 @@ class Parser():
precedence=[
('left', ["SYMBOL_BACKSLASH","SYMBOL_ARROW"]),
('left', ["KEYWORD_LET", "KEYWORD_IN", "KEYWORD_IF", "KEYWORD_THEN", "KEYWORD_ELSE"]),
('left', ["SYMBOL_EQUALS", "SYMBOL_LE"]),
('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"])
('right', ["ROLL_KEEP_HIGHEST","ROLL_KEEP_LOWEST","ROLL_MIN","ROLL_MAX","ROLL_EXPLODE","ROLL_REROLL","ROLL_REROLL_ONCE"])
]
)
self._get_parser()
@ -97,6 +97,22 @@ class Parser():
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])
@ -108,7 +124,10 @@ class Parser():
# 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])

View File

@ -5,8 +5,8 @@ from interactions import Client, Status
from pymongo import MongoClient # Used for database management
from gwendolyn.utils import log
from gwendolyn.exceptions import NoToken, MongoCannotConnect
from gwendolyn.funcs import Other
from gwendolyn.exceptions import NoToken, CannotConnectToService
from gwendolyn.funcs import Other, BetterNetflix, Sonarr, Radarr, TMDb
class Gwendolyn(Client):
def __init__(self, testing: bool = True):
@ -41,17 +41,25 @@ class Gwendolyn(Client):
try:
database_client.admin.command("ping")
self.log("Connected to Mango Client")
except:
raise MongoCannotConnect()
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(
Radarr(getenv("RADARR_IP"),getenv("RADARR_PORT"),getenv("RADARR_API_KEY")),
Sonarr(getenv("SONARR_IP"),getenv("SONARR_PORT"),getenv("SONARR_API_KEY")),
TMDb(getenv("TMDB_API_ACCESS_TOKEN")),
self
)
def _add_extensions(self):
"""Load cogs."""

View File

@ -16,6 +16,18 @@
}
]
},
"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"
@ -44,5 +56,15 @@
"name" : "thank",
"description" : "Thank Gwendolyn for her service"
}
},
"better_netflix": {
"movie": {
"name": "movie",
"description": "Get a random movie from Better Netflix"
},
"show": {
"name": "show",
"description": "Get a random show from Better Netflix"
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB