diff --git a/app/bot/cogs/app.py b/app/bot/cogs/app.py index ec09f24..741bd3e 100644 --- a/app/bot/cogs/app.py +++ b/app/bot/cogs/app.py @@ -2,116 +2,30 @@ from pickle import FALSE import app.bot.helper.jellyfinhelper as jelly import discord from discord.ext import commands +from discord import app_commands import asyncio from plexapi.myplex import MyPlexAccount -from discord import Webhook, AsyncWebhookAdapter import app.bot.helper.db as db import app.bot.helper.plexhelper as plexhelper import app.bot.helper.jellyfinhelper as jelly import texttable -import os -from os import path -import configparser +from app.bot.helper.message import * +from app.bot.helper.confighelper import * + CONFIG_PATH = 'app/config/config.ini' BOT_SECTION = 'bot_envs' -# settings -plex_roles = None -PLEXUSER = "" -PLEXPASS = "" -PLEX_SERVER_NAME = "" -Plex_LIBS = None - plex_configured = True jellyfin_configured = True -if(path.exists(CONFIG_PATH)): - config = configparser.ConfigParser() - config.read(CONFIG_PATH) - - # Get Plex config - try: - PLEXUSER = config.get(BOT_SECTION, 'plex_user') - PLEXPASS = config.get(BOT_SECTION, 'plex_pass') - PLEX_SERVER_NAME = config.get(BOT_SECTION, 'plex_server_name') - except: - print("Could not load plex config") - plex_configured = False - - # Get Plex roles config - try: - plex_roles = config.get(BOT_SECTION, 'plex_roles') - except: - print("Could not get Plex roles config") - plex_roles = None - if plex_roles is not None: - plex_roles = list(plex_roles.split(',')) - else: - plex_roles = [] - - # Get Plex libs config - try: - Plex_LIBS = config.get(BOT_SECTION, 'plex_libs') - except: - print("Could not get Plex libs config. Defaulting to all libraries.") - Plex_LIBS = None - if Plex_LIBS is None: - Plex_LIBS = ["all"] - else: - Plex_LIBS = list(Plex_LIBS.split(',')) - - # Get Jellyfin config - try: - JELLYFIN_SERVER_URL = config.get(BOT_SECTION, 'jellyfin_server_url') - JELLYFIN_API_KEY = config.get(BOT_SECTION, "jellyfin_api_key") - except: - print("Could not load Jellyfin config") - jellyfin_configured = False - - # Get Jellyfin roles config - try: - jellyfin_roles = config.get(BOT_SECTION, 'jellyfin_roles') - except: - print("Could not get Jellyfin roles config") - jellyfin_roles = None - if jellyfin_roles is not None: - jellyfin_roles = list(jellyfin_roles.split(',')) - else: - jellyfin_roles = [] - - # Get Jellyfin libs config - try: - jellyfin_libs = config.get(BOT_SECTION, 'jellyfin_libs') - except: - print("Could not get Jellyfin libs config. Defaulting to all libraries.") - jellyfin_libs = None - if jellyfin_libs is None: - jellyfin_libs = ["all"] - else: - jellyfin_libs = list(jellyfin_libs.split(',')) - - # Get Enable config - try: - USE_JELLYFIN = config.get(BOT_SECTION, 'jellyfin_enabled') - USE_JELLYFIN = USE_JELLYFIN.lower() == "true" - except: - print("Could not get Jellyfin enable config. Defaulting to False") - USE_JELLYFIN = False - - try: - USE_PLEX = config.get(BOT_SECTION, "plex_enabled") - USE_PLEX = USE_PLEX.lower() == "true" - except: - print("Could not get Plex enable config. Defaulting to False") - USE_PLEX = False - - if USE_PLEX and plex_configured: try: + print("Connecting to Plex......") account = MyPlexAccount(PLEXUSER, PLEXPASS) plex = account.resource(PLEX_SERVER_NAME).connect() # returns a PlexServer instance print('Logged into plex!') except Exception as e: + # probably rate limited. print('Error with plex login. Please check username and password and Plex server name or setup plex in the bot.') print(f'Error: {e}') else: @@ -119,37 +33,28 @@ else: class app(commands.Cog): - + # App command groups + plex_commands = app_commands.Group(name="plex", description="Membarr Plex commands") + jellyfin_commands = app_commands.Group(name="jellyfin", description="Membarr Jellyfin commands") + membarr_commands = app_commands.Group(name="membarr", description="Membarr general commands") + def __init__(self, bot): self.bot = bot @commands.Cog.listener() async def on_ready(self): - print('Made by Sleepingpirate https://github.com/Sleepingpirates/') - print('Jellyfin implementation by Yoruio https://github.com/Yoruio/') + print('Made by Yoruio https://github.com/Yoruio/') + print('Forked from Invitarr https://github.com/Sleepingpirates/Invitarr') + print('Named by lordfransie') print(f'Logged in as {self.bot.user} (ID: {self.bot.user.id})') print('------') if plex_roles is None: print('Configure Plex roles to enable auto invite to Plex after a role is assigned.') - async def embederror(self, author, message): - embed1 = discord.Embed(title="ERROR",description=message, color=0xf50000) - await author.send(embed=embed1) - - async def embedinfo(self, author, message): - embed1 = discord.Embed(title=message, color=0x00F500) - await author.send(embed=embed1) - - async def embedcustom(self, recipient, title, fields): - embed = discord.Embed(title=title) - for k in fields: - embed.add_field(name=str(k), value=str(fields[k]), inline=True) - await recipient.send(embed=embed) - async def getemail(self, after): email = None - await self.embedinfo(after,'Welcome To '+ PLEX_SERVER_NAME +'. Just reply with your email so we can add you to Plex!') - await self.embedinfo(after,'I will wait 24 hours for your message, if you do not send it by then I will cancel the command.') + await embedinfo(after,'Welcome To '+ PLEX_SERVER_NAME +'. Just reply with your email so we can add you to Plex!') + await embedinfo(after,'I will wait 24 hours for your message, if you do not send it by then I will cancel the command.') while(email == None): def check(m): return m.author == after and not m.guild @@ -160,17 +65,17 @@ class app(commands.Cog): else: email = None message = "Invalid email. Please just type in your email and nothing else." - await self.embederror(after, message) + await embederror(after, message) continue except asyncio.TimeoutError: message = "Timed Out. Message Server Admin with your email so They Can Add You Manually." - await self.embederror(after, message) + await embederror(after, message) return None async def getusername(self, after): username = None - await self.embedinfo(after, f"Welcome To Jellyfin! Just reply with a username for Jellyfin so we can add you!") - await self.embedinfo(after, f"I will wait 24 hours for your message, if you do not send it by then I will cancel the command.") + await embedinfo(after, f"Welcome To Jellyfin! Just reply with a username for Jellyfin so we can add you!") + await embedinfo(after, f"I will wait 24 hours for your message, if you do not send it by then I will cancel the command.") while (username is None): def check(m): return m.author == after and not m.guild @@ -181,69 +86,70 @@ class app(commands.Cog): else: username = None message = "This username is already choosen. Please select another Username." - await self.embederror(after, message) + await embederror(after, message) continue except asyncio.TimeoutError: message = "Timed Out. Message Server Admin with your preferred username so They Can Add You Manually." - await self.embederror(after, message) + print("Jellyfin user prompt timed out") + await embederror(after, message) return None except Exception as e: - await self.embederror(after, "Something went wrong. Please try again with another username.") + await embederror(after, "Something went wrong. Please try again with another username.") print (e) username = None - async def addtoplex(self, email, channel): + async def addtoplex(self, email, response): if(plexhelper.verifyemail(email)): if plexhelper.plexadd(plex,email,Plex_LIBS): - await self.embedinfo(channel, 'This email address has been added to plex') + await embedinfo(response, 'This email address has been added to plex') return True else: - await self.embederror(channel, 'There was an error adding this email address. Check logs.') + await embederror(response, 'There was an error adding this email address. Check logs.') return False else: - await self.embederror(channel, 'Invalid email.') + await embederror(response, 'Invalid email.') return False - async def removefromplex(self, email, channel): + async def removefromplex(self, email, response): if(plexhelper.verifyemail(email)): if plexhelper.plexremove(plex,email): - await self.embedinfo(channel, 'This email address has been removed from plex.') + await embedinfo(response, 'This email address has been removed from plex.') return True else: - await self.embederror(channel, 'There was an error removing this email address. Check logs.') + await embederror(response, 'There was an error removing this email address. Check logs.') return False else: - await self.embederror(channel, 'Invalid email.') + await embederror(response, 'Invalid email.') return False - async def addtojellyfin(self, username, password, channel): + async def addtojellyfin(self, username, password, response): if not jelly.verify_username(JELLYFIN_SERVER_URL, JELLYFIN_API_KEY, username): - await self.embederror(channel, f'An account with username {username} already exists.') - return - - if jelly.add_user(JELLYFIN_SERVER_URL, JELLYFIN_API_KEY, username, password, jellyfin_libs): - await self.embedinfo(channel, 'User successfully added to Jellyfin') - return True - else: - await self.embederror(channel, 'There was an error adding this user to Jellyfin. Check logs for more info.') + await embederror(response, f'An account with username {username} already exists.') return False - async def removefromjellyfin(self, username, channel): + if jelly.add_user(JELLYFIN_SERVER_URL, JELLYFIN_API_KEY, username, password, jellyfin_libs): + return True + else: + await embederror(response, 'There was an error adding this user to Jellyfin. Check logs for more info.') + return False + + async def removefromjellyfin(self, username, response): if jelly.verify_username(JELLYFIN_SERVER_URL, JELLYFIN_API_KEY, username): - await self.embederror(channel, f'Could not find account with username {username}.') + await embederror(response, f'Could not find account with username {username}.') return if jelly.remove_user(JELLYFIN_SERVER_URL, JELLYFIN_API_KEY, username): - await self.embedinfo(channel, f'Successfully removed user {username} from Jellyfin.') + await embedinfo(response, f'Successfully removed user {username} from Jellyfin.') return True else: - await self.embederror(channel, f'There was an error removing this user from Jellyfin. Check logs for more info.') + await embederror(response, f'There was an error removing this user from Jellyfin. Check logs for more info.') return False @commands.Cog.listener() async def on_member_update(self, before, after): - if plex_roles is None: + print(type(after)) + if plex_roles is None and jellyfin_roles is None: return roles_in_guild = after.guild.roles role = None @@ -262,13 +168,13 @@ class app(commands.Cog): if role is not None and (role in after.roles and role not in before.roles): email = await self.getemail(after) if email is not None: - await self.embedinfo(after, "Got it we will be adding your email to plex shortly!") + await embedinfo(after, "Got it we will be adding your email to plex shortly!") if plexhelper.plexadd(plex,email,Plex_LIBS): db.save_user_email(str(after.id), email) await asyncio.sleep(5) - await self.embedinfo(after, 'You have Been Added To Plex! Login to plex and accept the invite!') + await embedinfo(after, 'You have Been Added To Plex! Login to plex and accept the invite!') else: - await self.embedinfo(after, 'There was an error adding this email address. Message Server Admin.') + await embedinfo(after, 'There was an error adding this email address. Message Server Admin.') plex_processed = True break @@ -284,7 +190,7 @@ class app(commands.Cog): #await secure.send(plexname + ' ' + after.mention + ' was removed from plex') else: print("Cannot remove Plex from this user.") - await self.embedinfo(after, "You have been removed from Plex") + await embedinfo(after, "You have been removed from Plex") except Exception as e: print(e) print("{} Cannot remove this user from plex.".format(email)) @@ -293,6 +199,7 @@ class app(commands.Cog): if plex_processed: break + role = None # Check Jellyfin roles if jellyfin_configured and USE_JELLYFIN: for role_for_app in jellyfin_roles: @@ -302,22 +209,27 @@ class app(commands.Cog): # Jellyfin role was added if role is not None and (role in after.roles and role not in before.roles): + print("Jellyfin role added") username = await self.getusername(after) + print("Username retrieved from user") if username is not None: - await self.embedinfo(after, "Got it we will be creating your Jellyfin account shortly!") + after.send("BREAKPOINT") + print("BREAKPOINT") + await embedinfo(after, "Got it we will be creating your Jellyfin account shortly!") password = jelly.generate_password(16) if jelly.add_user(JELLYFIN_SERVER_URL, JELLYFIN_API_KEY, username, password, jellyfin_libs): db.save_user_jellyfin(str(after.id), username) await asyncio.sleep(5) - await self.embedcustom(after, "You have been added to Jellyfin!", {'Username': username, 'Password': f"||{password}||"}) - await self.embedinfo(after, f"Go to {JELLYFIN_SERVER_URL} to log in!") + await embedcustom(after, "You have been added to Jellyfin!", {'Username': username, 'Password': f"||{password}||"}) + await embedinfo(after, f"Go to {JELLYFIN_SERVER_URL} to log in!") else: - await self.embedinfo(after, 'There was an error adding this user to Jellyfin. Message Server Admin.') + await embedinfo(after, 'There was an error adding this user to Jellyfin. Message Server Admin.') jellyfin_processed = True break # Jellyfin role was removed elif role is not None and (role not in after.roles and role in before.roles): + print("Jellyfin role removed") try: user_id = after.id username = db.get_jellyfin_username(user_id) @@ -328,7 +240,7 @@ class app(commands.Cog): #await secure.send(plexname + ' ' + after.mention + ' was removed from plex') else: print("Cannot remove Jellyfin from this user") - await self.embedinfo(after, "You have been removed from Jellyfin") + await embedinfo(after, "You have been removed from Jellyfin") except Exception as e: print(e) print("{} Cannot remove this user from Jellyfin.".format(username)) @@ -341,58 +253,58 @@ class app(commands.Cog): async def on_member_remove(self, member): email = db.get_useremail(member.id) plexhelper.plexremove(plex,email) + jellyfin_username = db.get_jellyfin_username(member.id) + jelly.remove_user(jellyfin_username) deleted = db.delete_user(member.id) if deleted: print("Removed {} from db because user left discord server.".format(email)) @commands.has_permissions(administrator=True) - @commands.command(aliases=['plexadd']) - async def plexinvite(self, ctx, email): - await self.addtoplex(email, ctx.channel) + @plex_commands.command(name="invite", description="Invite a user to Plex") + async def plexinvite(self, interaction: discord.Interaction, email: str): + await self.addtoplex(email, interaction.response) @commands.has_permissions(administrator=True) - @commands.command(aliases=['plexrm']) - async def plexremove(self, ctx, email): - await self.removefromplex(email, ctx.channel) + @plex_commands.command(name="remove", description="Remove a user from Plex") + async def plexremove(self, interaction: discord.Interaction, email: str): + await self.removefromplex(email, interaction.response) @commands.has_permissions(administrator=True) - @commands.command(aliases=['jellyadd']) - async def jellyfininvite(self, ctx, username): + @jellyfin_commands.command(name="invite", description="Invite a user to Jellyfin") + async def jellyfininvite(self, interaction: discord.Interaction, username: str): password = jelly.generate_password(16) - if await self.addtojellyfin(username, password, ctx.channel): - await self.embedcustom(ctx.author, "Jellyfin user created!", {'Username': username, 'Password': f"||{password}||"}) + if await self.addtojellyfin(username, password, interaction.response): + await embedcustom(interaction.response, "Jellyfin user created!", {'Username': username, 'Password': f"||{password}||"}) @commands.has_permissions(administrator=True) - @commands.command(aliases=['jellyrm']) - async def jellyfinremove(self, ctx, username): - await self.removefromjellyfin(username, ctx.channel) + @jellyfin_commands.command(name="remove", description="Remove a user from Jellyfin") + async def jellyfinremove(self, interaction: discord.Interaction, username: str): + await self.removefromjellyfin(username, interaction.response) @commands.has_permissions(administrator=True) - @commands.command() - async def dbadd(self, ctx, member: discord.Member, email, jellyfin_username): + @membarr_commands.command(name="dbadd", description="Add a user to the Membarr database") + async def dbadd(self, interaction: discord.Interaction, member: discord.Member, email: str = "", jellyfin_username: str = ""): email = email.strip() jellyfin_username = jellyfin_username.strip() - await self.embedinfo(ctx.channel, f"username: {member.name} email: {email} jellyfin: {jellyfin_username}") - #await self.addtoplex(email, ctx.channel) # Check email if provided if email and not plexhelper.verifyemail(email): - await self.embederror(ctx.channel, "Invalid email.") + await embederror(interaction.response, "Invalid email.") return try: db.save_user_all(str(member.id), email, jellyfin_username) - await self.embedinfo(ctx.channel,'User was added to the database.') + await embedinfo(interaction.response,'User was added to the database.') except Exception as e: - await self.embedinfo(ctx.channel, 'There was an error adding this user to database.') + await embedinfo(interaction.response, 'There was an error adding this user to database. Check Membarr logs for more info') print(e) @commands.has_permissions(administrator=True) - @commands.command() - async def dbls(self, ctx): + @membarr_commands.command(name="dbls", description="View Membarr database") + async def dbls(self, interaction: discord.Interaction): - embed = discord.Embed(title='Invitarr Database.') - all = db.read_useremail() + embed = discord.Embed(title='Membarr Database.') + all = db.read_all() table = texttable.Texttable() table.set_cols_dtype(["t", "t", "t", "t"]) table.set_cols_align(["c", "c", "c", "c"]) @@ -417,21 +329,16 @@ class app(commands.Cog): f = open("db.txt", "w") f.write(table.draw()) f.close() - await ctx.channel.send("Database too large! Total: {total}".format(total = total),file=discord.File('db.txt')) + await interaction.response.send_message("Database too large! Total: {total}".format(total = total),file=discord.File('db.txt'), ephemeral=True) else: - await ctx.channel.send(embed = embed) + await interaction.response.send_message(embed = embed, ephemeral=True) @commands.has_permissions(administrator=True) - @commands.command() - async def dbrm(self, ctx, position): - embed = discord.Embed(title='Invitarr Database.') - all = db.read_useremail() - table = texttable.Texttable() - table.set_cols_dtype(["t", "t", "t", "t"]) - table.set_cols_align(["c", "c", "c", "c"]) - header = ("#", "Name", "Email", "Jellyfin") - table.add_row(header) + @membarr_commands.command(name="dbrm", description="Remove user from Membarr database") + async def dbrm(self, interaction: discord.Interaction, position: int): + embed = discord.Embed(title='Membarr Database.') + all = db.read_all() for index, peoples in enumerate(all): index = index + 1 id = int(peoples[1]) @@ -443,7 +350,6 @@ class app(commands.Cog): except: username = "User Not Found." embed.add_field(name=f"**{index}. {username}**", value=dbemail+'\n'+dbjellyfin+'\n', inline=False) - table.add_row((index, username, dbemail, dbjellyfin)) try: position = int(position) - 1 @@ -453,11 +359,11 @@ class app(commands.Cog): deleted = db.delete_user(id) if deleted: print("Removed {} from db".format(username)) - await self.embedinfo(ctx.channel,"Removed {} from db".format(username)) + await embedinfo(interaction.response,"Removed {} from db".format(username)) else: - await self.embederror(ctx.channel,"Cannot remove this user from db.") + await embederror(interaction.response,"Cannot remove this user from db.") except Exception as e: print(e) -def setup(bot): - bot.add_cog(app(bot)) \ No newline at end of file +async def setup(bot): + await bot.add_cog(app(bot)) \ No newline at end of file diff --git a/app/bot/helper/confighelper.py b/app/bot/helper/confighelper.py index e28efff..c8cc620 100644 --- a/app/bot/helper/confighelper.py +++ b/app/bot/helper/confighelper.py @@ -2,14 +2,17 @@ import configparser import os from os import environ, path from dotenv import load_dotenv + CONFIG_PATH = 'app/config/config.ini' BOT_SECTION = 'bot_envs' +MEMBARR_VERSION = 1.1 + config = configparser.ConfigParser() CONFIG_KEYS = ['username', 'password', 'discord_bot_token', 'plex_user', 'plex_pass', 'plex_roles', 'plex_server_name', 'plex_libs', 'owner_id', 'channel_id', 'auto_remove_user', 'jellyfin_api_key', 'jellyfin_server_url', 'jellyfin_roles', - 'jellyfin_libs', 'plex_enabled', 'jellyfin_enabled'] + 'jellyfin_libs', 'plex_enabled', 'jellyfin_enabled', 'sync_version'] # settings Discord_bot_token = "" @@ -22,9 +25,12 @@ JELLYFIN_SERVER_URL = "" JELLYFIN_API_KEY = "" jellyfin_libs = "" jellyfin_roles = None +plex_configured = True +jellyfin_configured = True switch = 0 +# TODO: make this into a class if(path.exists('bot.env')): try: @@ -53,19 +59,29 @@ if(path.exists(CONFIG_PATH)): PLEX_SERVER_NAME = config.get(BOT_SECTION, 'plex_server_name') except: print("Could not load plex config") + plex_configured = False # Get Plex roles config try: plex_roles = config.get(BOT_SECTION, 'plex_roles') except: print("Could not get Plex roles config") + plex_roles = None + if plex_roles: + plex_roles = list(plex_roles.split(',')) + else: + plex_roles = [] # Get Plex libs config try: Plex_LIBS = config.get(BOT_SECTION, 'plex_libs') except: - print("Could not get Plex libs config") - + print("Could not get Plex libs config. Defaulting to all libraries.") + Plex_LIBS = None + if Plex_LIBS is None: + Plex_LIBS = ["all"] + else: + Plex_LIBS = list(Plex_LIBS.split(',')) # Get Jellyfin config try: @@ -73,31 +89,56 @@ if(path.exists(CONFIG_PATH)): JELLYFIN_API_KEY = config.get(BOT_SECTION, "jellyfin_api_key") except: print("Could not load Jellyfin config") + jellyfin_configured = False # Get Jellyfin roles config try: jellyfin_roles = config.get(BOT_SECTION, 'jellyfin_roles') except: print("Could not get Jellyfin roles config") + jellyfin_roles = None + if jellyfin_roles: + jellyfin_roles = list(jellyfin_roles.split(',')) + else: + jellyfin_roles = [] # Get Jellyfin libs config try: jellyfin_libs = config.get(BOT_SECTION, 'jellyfin_libs') except: - print("Could not get Jellyfin libs config") + print("Could not get Jellyfin libs config. Defaulting to all libraries.") + jellyfin_libs = None + if jellyfin_libs is None: + jellyfin_libs = ["all"] + else: + jellyfin_libs = list(jellyfin_libs.split(',')) # Get Enable config try: USE_JELLYFIN = config.get(BOT_SECTION, 'jellyfin_enabled') + USE_JELLYFIN = USE_JELLYFIN.lower() == "true" except: print("Could not get Jellyfin enable config. Defaulting to False") - USE_Jellyfin = False + USE_JELLYFIN = False try: USE_PLEX = config.get(BOT_SECTION, "plex_enabled") + USE_PLEX = USE_PLEX.lower() == "true" except: print("Could not get Plex enable config. Defaulting to False") USE_PLEX = False + + try: + synced = not (float(config.get(BOT_SECTION, "sync_version")) < MEMBARR_VERSION) + except: + print("Could not find previously synced version. Setting synced to false.") + synced = False + + try: + GUILD_ID = config.get(BOT_SECTION, "guild") + except: + print("Could not get guild. Defaulting to global. Command syncing may take up to 24 hours to complete.") + GUILD_ID = None def get_config(): """ diff --git a/app/bot/helper/db.py b/app/bot/helper/db.py index 31be475..8b08aef 100644 --- a/app/bot/helper/db.py +++ b/app/bot/helper/db.py @@ -160,7 +160,7 @@ def delete_user(username): else: return "username cannot be empty" -def read_useremail(): +def read_all(): cur = conn.cursor() cur.execute("SELECT * FROM clients") rows = cur.fetchall() diff --git a/app/bot/helper/message.py b/app/bot/helper/message.py new file mode 100644 index 0000000..3846b47 --- /dev/null +++ b/app/bot/helper/message.py @@ -0,0 +1,28 @@ +import discord + +# these were copied from the app object. They could be made static instead but I'm lazy. +async def embederror(recipient, message, ephemeral=True): + embed = discord.Embed(title="ERROR",description=message, color=0xf50000) + await send_embed(recipient, embed, ephemeral) + +async def embedinfo(recipient, message, ephemeral=True): + embed = discord.Embed(title=message, color=0x00F500) + await send_embed(recipient, embed, ephemeral) + +async def embedcustom(recipient, title, fields, ephemeral=True): + embed = discord.Embed(title=title) + for k in fields: + embed.add_field(name=str(k), value=str(fields[k]), inline=True) + await send_embed(recipient, embed, ephemeral) + +async def send_info(recipient, message, ephemeral=True): + if isinstance(recipient, discord.InteractionResponse): + await recipient.send_message(message, ephemeral=ephemeral) + elif isinstance(recipient, discord.User) or isinstance(recipient, discord.member.Member): + await recipient.send(message) + +async def send_embed(recipient, embed, ephemeral=True): + if isinstance(recipient, discord.User) or isinstance(recipient, discord.member.Member): + await recipient.send(embed=embed) + elif isinstance(recipient, discord.InteractionResponse): + await recipient.send_message(embed=embed, ephemeral = ephemeral) \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index fdf4964..da9c94b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,5 @@ -discord.py plex.py==0.9.0 PlexAPI==4.0.0 texttable python-dotenv -jellyfin-apiclient-python -requests \ No newline at end of file +requests diff --git a/run.py b/run.py index f276724..6bd4389 100644 --- a/run.py +++ b/run.py @@ -1,68 +1,59 @@ +from pydoc import describe import discord import os from discord.ext import commands, tasks from discord.utils import get +from discord.ui import Button, View, Select +from discord import app_commands import asyncio import sys -from app.bot.helper.confighelper import switch, Discord_bot_token, plex_roles, jellyfin_roles +from app.bot.helper.confighelper import GUILD_ID, MEMBARR_VERSION, switch, Discord_bot_token, plex_roles, jellyfin_roles import app.bot.helper.confighelper as confighelper import app.bot.helper.jellyfinhelper as jelly +from app.bot.helper.message import * maxroles = 10 print(f"Discord Bot Token: {Discord_bot_token}") -if plex_roles is None: - plex_roles = [] -else: - plex_roles = list(plex_roles.split(',')) - -if jellyfin_roles is None: - jellyfin_roles = [] -else: - jellyfin_roles = list(jellyfin_roles.split(',')) - if switch == 0: print("Missing Config.") sys.exit() -print("V 1.1") +print(f"V {MEMBARR_VERSION}") -intents = discord.Intents.default() -intents.members = True -bot = commands.Bot(command_prefix=".", intents = intents) -bot.remove_command('help') +class Bot(commands.Bot): + def __init__(self) -> None: + print("bot init") + intents = discord.Intents.default() + intents.members = True + super().__init__(command_prefix=".", intents=intents) -@bot.event -async def on_ready(): - print("bot is online.") + async def on_ready(self): + print("bot is online.") + for guild in self.guilds: + print("Syncing commands to " + guild.name) + self.tree.copy_global_to(guild=guild) + await self.tree.sync(guild=guild) + + async def on_guild_join(self, guild): + print(f"Joined guild {guild.name}") + print(f"Syncing commands to {guild.name}") + + async def setup_hook(self): + print("Loading media server connectors") + await self.load_extension(f'app.bot.cogs.app') +bot = Bot() -@bot.event -async def on_message(message): - if message.author.id == bot.user.id: - return - if not message.guild: - return - await bot.process_commands(message) +async def reload(): + await bot.reload_extension(f'app.bot.cogs.app') -# these were copied from the app object. They could be made static instead but I'm lazy. -async def embederror(author, message): - embed1 = discord.Embed(title="ERROR",description=message, color=0xf50000) - await author.send(embed=embed1) - -async def embedinfo(author, message): - embed1 = discord.Embed(title=message, color=0x00F500) - await author.send(embed=embed1) - -def reload(): - bot.reload_extension(f'app.bot.cogs.app') - -async def getuser(ctx, server, type): +async def getuser(interaction, server, type): value = None - await ctx.author.send("Please reply with your {} {}:".format(server, type)) + await interaction.user.send("Please reply with your {} {}:".format(server, type)) while(value == None): def check(m): - return m.author == ctx.author and not m.guild + return m.author == interaction.user and not m.guild try: value = await bot.wait_for('message', timeout=200, check=check) return value.content @@ -70,171 +61,224 @@ async def getuser(ctx, server, type): message = "Timed Out. Try again." return None -@bot.command() +plex_commands = app_commands.Group(name="plexsettings", description="Membarr Plex commands") +jellyfin_commands = app_commands.Group(name="jellyfinsettings", description="Membarr Jellyfin commands") + +@plex_commands.command(name="addrole", description="Add a role to automatically add users to Plex") @commands.has_permissions(administrator=True) -async def plexroleadd(ctx, role: discord.Role): +async def plexroleadd(interaction: discord.Interaction, role: discord.Role): if len(plex_roles) <= maxroles: + # Do not add roles multiple times. + if role.name in plex_roles: + await embederror(interaction.response, f"Plex role \"{role.name}\" already added.") + return + plex_roles.append(role.name) saveroles = ",".join(plex_roles) + plex_button = Button(label = "Plex") + view = View() + view.add_item(plex_button) confighelper.change_config("plex_roles", saveroles) - await ctx.author.send("Updated Plex roles. Bot is restarting. Please wait.") - print("Plex roles updated. Restarting bot.") + await interaction.response.send_message("Updated Plex roles. Bot is restarting. Please wait.", ephemeral=True) + print("Plex roles updated. Restarting bot, Give it a few seconds.") reload() - await ctx.author.send("Bot has been restarted. Give it a few seconds.") print("Bot has been restarted. Give it a few seconds.") -@bot.command() +@plex_commands.command(name="removerole", description="Stop adding users with a role to Plex") @commands.has_permissions(administrator=True) -async def setupplex(ctx): - username = None - password = None - servername = None - username = await getuser(ctx, "Plex", "username") - if username is None: +async def plexroleremove(interaction: discord.Interaction, role: discord.Role): + if role.name not in plex_roles: + await embederror(interaction.response, f"\"{role.name}\" is currently not a Plex role.") return - else: - password = await getuser(ctx, "Plex", "password") - if password is None: - return - else: - servername = await getuser(ctx, "Plex", "servername") - if servername is None: - return - else: - confighelper.change_config("plex_user", str(username)) - confighelper.change_config("plex_pass", str(password)) - confighelper.change_config("plex_server_name", str(servername)) - print("Plex username, password, and plex server name updated. Restarting bot.") - await ctx.author.send("Plex username, password, and plex server name updated. Restarting bot. Please wait.") - reload() - await ctx.author.send("Bot has been restarted. Give it a few seconds. Please check logs and make sure you see the line: `Logged into plex`. If not run this command again and make sure you enter the right values. ") - print("Bot has been restarted. Give it a few seconds.") + plex_roles.remove(role.name) + confighelper.change_config("jellyfin_roles", ",".join(plex_roles)) + await interaction.response.send_message(f"Membarr will stop auto-adding \"{role.name}\" to Plex", ephemeral=True) -@bot.command() +@plex_commands.command(name="listroles", description="List all roles whose members will be automatically added to Plex") @commands.has_permissions(administrator=True) -async def jellyroleadd(ctx, role: discord.Role): +async def plexrolels(interaction: discord.Interaction): + await interaction.response.send_message( + "The following roles are being automatically added to Plex:\n" + + ", ".join(plex_roles), ephemeral=True + ) + +@plex_commands.command(name="setup", description="Setup Plex integration") +@commands.has_permissions(administrator=True) +async def setupplex(interaction: discord.Interaction, username: str, password: str, server_name: str): + confighelper.change_config("plex_user", str(username)) + confighelper.change_config("plex_pass", str(password)) + confighelper.change_config("plex_server_name", str(server_name)) + print("Plex username, password, and plex server name updated. Restarting bot.") + await interaction.response.send_message( + "Plex username, password, and plex server name updated. Restarting bot. Please wait.\n" + + "Please check logs and make sure you see the line: `Logged into plex`. If not run this command again and make sure you enter the right values.", + ephemeral=True + ) + reload() + print("Bot has been restarted. Give it a few seconds.") + +@jellyfin_commands.command(name="addrole", description="Add a role to automatically add users to Jellyfin") +@commands.has_permissions(administrator=True) +async def jellyroleadd(interaction: discord.Interaction, role: discord.Role): if len(jellyfin_roles) <= maxroles: + # Do not add roles multiple times. + if role.name in jellyfin_roles: + await embederror(interaction.response, f"Jellyfin role \"{role.name}\" already added.") + return + jellyfin_roles.append(role.name) print (f"new jellyfin roles: {jellyfin_roles}") saveroles = ",".join(jellyfin_roles) print (f"saveroles: {saveroles}") confighelper.change_config("jellyfin_roles", saveroles) - await ctx.author.send("Updated Jellyfin roles. Bot is restarting. Please wait.") + await interaction.response.send_message("Updated Jellyfin roles. Bot is restarting. Please wait a few seconds.", ephemeral=True) print("Jellyfin roles updated. Restarting bot.") reload() - await ctx.author.send("Bot has been restarted. Give it a few seconds.") print("Bot has been restarted. Give it a few seconds.") -@bot.command() +@jellyfin_commands.command(name="removerole", description="Stop adding users with a role to Jellyfin") @commands.has_permissions(administrator=True) -async def setupjelly(ctx): - jellyfin_api_key = None - jellyfin_server_url = None - - jellyfin_server_url = await getuser(ctx, "Jellyfin", "Server Url") - if jellyfin_server_url is None: +async def jellyroleremove(interaction: discord.Interaction, role: discord.Role): + if role.name not in jellyfin_roles: + await embederror(interaction.response, f"\"{role.name}\" is currently not a Jellyfin role.") return + jellyfin_roles.remove(role.name) + confighelper.change_config("jellyfin_roles", ",".join(jellyfin_roles)) + await interaction.response.send_message(f"Membarr will stop auto-adding \"{role.name}\" to Jellyfin", ephemeral=True) - jellyfin_api_key = await getuser(ctx, "Jellyfin", "API Key") - if jellyfin_api_key is None: - return +@jellyfin_commands.command(name="listroles", description="List all roles whose members will be automatically added to Jellyfin") +@commands.has_permissions(administrator=True) +async def jellyrolels(interaction: discord.Interaction): + await interaction.response.send_message( + "The following roles are being automatically added to Jellyfin:\n" + + ", ".join(jellyfin_roles), ephemeral=True + ) + +@jellyfin_commands.command(name="setup", description="Setup Jellyfin integration") +@commands.has_permissions(administrator=True) +async def setupjelly(interaction: discord.Interaction, server_url: str, api_key: str): + # get rid of training slashes + server_url = server_url.rstrip('/') try: - server_status = jelly.get_status(jellyfin_server_url, jellyfin_api_key) + server_status = jelly.get_status(server_url, api_key) if server_status == 200: pass elif server_status == 401: # Unauthorized - await embederror(ctx.author, "API key provided is invalid") + await embederror(interaction.response, "API key provided is invalid") return elif server_status == 403: # Forbidden - await embederror(ctx.author, "API key provided does not have permissions") + await embederror(interaction.response, "API key provided does not have permissions") return elif server_status == 404: # page not found - await embederror(ctx.author, "Server endpoint provided was not found") + await embederror(interaction.response, "Server endpoint provided was not found") return + else: + await embederror(interaction.response, "Unknown error occurred while connecting to server. Check Membarr logs.") except Exception as e: print("Exception while testing Jellyfin connection") print(e) - await embederror(ctx.author, "Could not connect to server. Check logs for more details.") + await embederror(interaction.response, "Could not connect to server. Check logs for more details.") return - - - jellyfin_server_url = jellyfin_server_url.rstrip('/') - confighelper.change_config("jellyfin_server_url", str(jellyfin_server_url)) - confighelper.change_config("jellyfin_api_key", str(jellyfin_api_key)) + + confighelper.change_config("jellyfin_server_url", str(server_url)) + confighelper.change_config("jellyfin_api_key", str(api_key)) print("Jellyfin server URL and API key updated. Restarting bot.") - await ctx.author.send("Jellyfin server URL and API key updated. Restarting bot.") + await interaction.interaction.send_message("Jellyfin server URL and API key updated. Restarting bot.", ephemeral=True) reload() - await ctx.author.send("Bot has been restarted. Give it a few seconds. Please check logs and make sure you see the line: `Connected to Jellyfin`. If not run this command again and make sure you enter the right values. ") print("Bot has been restarted. Give it a few seconds.") -@bot.command() +@plex_commands.command(name="setuplibs", description="Setup libraries that new users can access") @commands.has_permissions(administrator=True) -async def setupplexlibs(ctx): - libs = await getuser(ctx, "Plex", "libs") - if libs is None: +async def setupplexlibs(interaction: discord.Interaction, libraries:str): + if not libraries: + await embederror(interaction.response, "libraries string is empty.") return else: - confighelper.change_config("plex_libs", str(libs)) + # Do some fancy python to remove spaces from libraries string, but only where wanted. + libraries = ",".join(list(map(lambda lib: lib.strip(),libraries.split(",")))) + confighelper.change_config("plex_libs", str(libraries)) print("Plex libraries updated. Restarting bot. Please wait.") + await interaction.response.send_message("Jellyfin libraries updated. Please wait a few seconds for bot to restart.", ephemeral=True) reload() - await ctx.author.send("Bot has been restarted. Give it a few seconds.") print("Bot has been restarted. Give it a few seconds.") -@bot.command() +@jellyfin_commands.command(name="setuplibs", description="Setup libraries that new users can access") @commands.has_permissions(administrator=True) -async def setupjellylibs(ctx): - libs = await getuser(ctx, "Jellyfin", "libs") - if libs is None: +async def setupjellylibs(interaction: discord.Interaction, libraries:str): + if not libraries is None: + await embederror(interaction.response, "libraries string is empty.") return else: - confighelper.change_config("jellyfin_libs", str(libs)) + # Do some fancy python to remove spaces from libraries string, but only where wanted. + libraries = ",".join(list(map(lambda lib: lib.strip(),libraries.split(",")))) + confighelper.change_config("jellyfin_libs", str(libraries)) print("Jellyfin libraries updated. Restarting bot. Please wait.") + await interaction.response.send_message("Jellyfin libraries updated. Please wait a few seconds for bot to restart.", ephemeral=True) reload() - await ctx.author.send("Bot has been restarted. Give it a few seconds.") print("Bot has been restarted. Give it a few seconds.") # Enable / Disable Plex integration -@bot.command(aliases=["plexenable"]) +@plex_commands.command(name="enable", description="Enable auto-adding users to Plex") @commands.has_permissions(administrator=True) -async def enableplex(ctx): +async def enableplex(interaction: discord.Interaction): + if confighelper.USE_PLEX: + await interaction.response.send_message("Plex already enabled.", ephemeral=True) + return confighelper.change_config("plex_enabled", True) print("Plex enabled, reloading server") reload() - await ctx.author.send("Bot has restarted. Give it a few seconds.") + confighelper.USE_PLEX = True + await interaction.response.send_message("Plex enabled. Restarting server. Give it a few seconds.", ephemeral=True) print("Bot has restarted. Give it a few seconds.") -@bot.command(aliases=["plexdisable"]) +@plex_commands.command(name="disable", description="Disable adding users to Plex") @commands.has_permissions(administrator=True) -async def disableplex(ctx): +async def disableplex(interaction: discord.Interaction): + if not confighelper.USE_PLEX: + await interaction.response.send_message("Plex already disabled.", ephemeral=True) + return confighelper.change_config("plex_enabled", False) print("Plex disabled, reloading server") reload() - await ctx.author.send("Bot has restarted. Give it a few seconds.") + confighelper.USE_PLEX = False + await interaction.response.send_message("Plex disabled. Restarting server. Give it a few seconds.", ephemeral=True) print("Bot has restarted. Give it a few seconds.") # Enable / Disable Jellyfin integration -@bot.command(aliases=["jellyenable"]) +@jellyfin_commands.command(name="enable", description="Enable adding users to Jellyfin") @commands.has_permissions(administrator=True) -async def enablejellyfin(ctx): +async def enablejellyfin(interaction: discord.Interaction): + if confighelper.USE_JELLYFIN: + await interaction.response.send_message("Jellyfin already enabled.", ephemeral=True) + return confighelper.change_config("jellyfin_enabled", True) print("Jellyfin enabled, reloading server") + confighelper.USE_JELLYFIN = True reload() - await ctx.author.send("Bot has restarted. Give it a few seconds.") + await interaction.response.send_message("Jellyfin enabled. Restarting server. Give it a few seconds.", ephemeral=True) print("Bot has restarted. Give it a few seconds.") -@bot.command(aliases=["jellydisable"]) +@jellyfin_commands.command(name="disable", description = "Disable adding users to Jellyfin") @commands.has_permissions(administrator=True) -async def disablejellyfin(ctx): +async def disablejellyfin(interaction: discord.Interaction): + if not confighelper.USE_JELLYFIN: + await interaction.response.send_message("Jellyfin already disabled.", ephemeral=True) + return confighelper.change_config("jellyfin_enabled", False) print("Jellyfin disabled, reloading server") reload() - await ctx.author.send("Bot has restarted. Give it a few seconds.") + confighelper.USE_JELLYFIN = False + await interaction.response.send_message("Jellyfin disabled. Restarting server. Give it a few seconds.", ephemeral=True) print("Bot has restarted. Give it a few seconds.") -bot.load_extension(f'app.bot.cogs.app') + +bot.tree.add_command(plex_commands) +bot.tree.add_command(jellyfin_commands) + +print("running bot") bot.run(Discord_bot_token) \ No newline at end of file