✨
This commit is contained in:
@ -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)
|
12
gwendolyn/ext/better_netflix.py
Normal file
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)
|
@ -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)
|
@ -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
|
5
gwendolyn/funcs/better_netflix/__init__.py
Normal file
5
gwendolyn/funcs/better_netflix/__init__.py
Normal file
@ -0,0 +1,5 @@
|
||||
"""Better Netflix functions for Gwendolyn."""
|
||||
|
||||
__all__ = ["BetterNetflix", "Sonarr", "Radarr", "TMDb"]
|
||||
|
||||
from .better_netflix import BetterNetflix, Sonarr, Radarr, TMDb
|
159
gwendolyn/funcs/better_netflix/better_netflix.py
Normal file
159
gwendolyn/funcs/better_netflix/better_netflix.py
Normal 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)
|
@ -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})"
|
||||
|
@ -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"\\"),
|
||||
|
@ -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])
|
||||
|
||||
|
@ -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."""
|
||||
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
BIN
gwendolyn/resources/temp.jpg
Normal file
BIN
gwendolyn/resources/temp.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.8 KiB |
Reference in New Issue
Block a user