From 28bab868af1f4d28f28a4622ab88e45541803481 Mon Sep 17 00:00:00 2001 From: Yoruio Date: Mon, 11 Jul 2022 20:31:54 -0600 Subject: [PATCH 1/3] feat: Reduces docker image size Drastically reduces Docker image size by changing from discord.py base image to python base image, reducing number of redundant dependencies downloaded. (Also because migration to discord.py v2) task: none --- Dockerfile | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 4cffba7..8c4f5ec 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,16 @@ -FROM gorialis/discord.py +FROM python:3.9.1-alpine + +RUN \ + echo "http://dl-8.alpinelinux.org/alpine/edge/community" >> /etc/apk/repositories && \ + echo "http://dl-8.alpinelinux.org/alpine/edge/testing" >> /etc/apk/repositories + +# Install basic dependencies +RUN \ + apk --no-cache add -q git cloc openssl openssl-dev openssh alpine-sdk bash gettext sudo build-base gnupg linux-headers xz + +# install discord.py - this done outside of requirements.txt to lower build times +RUN pip3 install git+https://github.com/Rapptz/discord.py@e0341c9#egg=discord.py + WORKDIR /app COPY . . RUN pip install -Ur requirements.txt From 67af1bb1541de1cecb28f0a1526921cb340463d5 Mon Sep 17 00:00:00 2001 From: Yoruio Date: Mon, 11 Jul 2022 20:33:19 -0600 Subject: [PATCH 2/3] feat: implements slash commands Implements slash commands. task: gh-1 --- app/bot/cogs/app.py | 284 +++++++++++--------------------- app/bot/helper/confighelper.py | 51 +++++- app/bot/helper/db.py | 2 +- app/bot/helper/message.py | 28 ++++ requirements.txt | 4 +- run.py | 286 +++++++++++++++++++-------------- 6 files changed, 336 insertions(+), 319 deletions(-) create mode 100644 app/bot/helper/message.py 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 From f66b20e2e06b934b9362599ff7150dca71332eb4 Mon Sep 17 00:00:00 2001 From: Roy Du Date: Mon, 11 Jul 2022 20:46:33 -0600 Subject: [PATCH 3/3] Update README.md --- README.md | 52 ++++++++++++++++++++++++++++++---------------------- 1 file changed, 30 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index 688939c..6c6e705 100644 --- a/README.md +++ b/README.md @@ -18,20 +18,20 @@ Membarr is a fork of Invitarr that invites discord users to Plex and Jellyfin. Y Commands: ``` -.plexinvite / .plexadd +/plex invite This command is used to add an email to plex -.plexremove / .plexrm +/plex remove This command is used to remove an email from plex -.jellyfininvite / .jellyadd +/jellyfin invite This command is used to add a user to Jellyfin. -.jellyfinremove / .jellyrm +/jellyfin remove This command is used to remove a user from Jellyfin. -.dbls -This command is used to list Invitarrs database -.dbadd <@user> "" "" -This command is used to add exsisting users email and discord id to the DB. -.dbrm -This command is used to remove a record from the Db. Use -db ls to determine record position. ex: -db rm 1 +/membarr dbls +This command is used to list Membarr's database +/membarr dbadd <@user> +This command is used to add exsisting plex emails, jellyfin users and discord id to the DB. +/membarr dbrm +This command is used to remove a record from the Db. Use /membarr dbls to determine record position. ex: /membarr dbrm 1 ``` # Unraid Installation @@ -74,26 +74,34 @@ docker run -d --restart unless-stopped --name invitarr -v /path to config:/app/a # Plex Setup Commands: ``` -.setupplex +/plexsettings setup This command is used to setup plex login. -.plexroleadd <@role> +/plexsettings addrole <@role> These role(s) will be used as the role(s) to automatically invite user to plex -.setupplexlibs (optional) -This command is used to setup plex libraries. Default is set to all. -.plexdisable +/plexsettings removerole <@role> +This command is used to remove a role that is being used to automatically invite uses to plex +/plexsettings setuplibs +This command is used to setup plex libraries. Default is set to all. Libraries is a comma separated list. +/plexsettings enable +This command enables the Plex integration (currently only enables auto-add / auto-remove) +/plexsettings disable This command disables the Plex integration (currently only disables auto-add / auto-remove) ``` # Jellyfin Setup Commands: ``` -.setupjelly -This command is used to setup Jellyfin API. -.jellyroleadd <@role> +/jellyfinsettings setup +This command is used to setup the Jellyfin server +/jellyfinsettings addrole <@role> These role(s) will be used as the role(s) to automatically invite user to Jellyfin -.setupjellylibs (optional) -This command is used to setup jelly libraries. Default is set to all. -.jellydisable -this command disables the Jellyfin integration (currently only disables auto-add / auto-remove) +/jellyfinsettings removerole <@role> +This command is used to remove a role that is being used to automatically invite uses to Jellyfin +/jellyfinsettings setuplibs +This command is used to setup Jellyfin libraries. Default is set to all. Libraries is a comma separated list. +/jellyfinsettings enable +This command enables the Jellyfin integration (currently only enables auto-add / auto-remove) +/jellyfinsettings disable +This command disables the Jellyfin integration (currently only disables auto-add / auto-remove) ``` Refer to the [Wiki](https://github.com/Sleepingpirates/Invitarr/wiki) for detailed steps.