Add files via upload
This commit is contained in:
7
Dockerfile
Normal file
7
Dockerfile
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
FROM tiangolo/uwsgi-nginx-flask:python3.8-alpine
|
||||||
|
RUN apk update && apk add gcc libc-dev make git libffi-dev openssl-dev python3-dev libxml2-dev libxslt-dev
|
||||||
|
ENV LISTEN_PORT 5001
|
||||||
|
EXPOSE 5001
|
||||||
|
COPY ./app /app
|
||||||
|
RUN pip install -r requirements.txt && python3 setup.py
|
||||||
|
ENV STATIC_PATH /app/app/static
|
||||||
19
app/app/__init__.py
Normal file
19
app/app/__init__.py
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import os
|
||||||
|
from flask import Flask
|
||||||
|
from flask_sqlalchemy import SQLAlchemy
|
||||||
|
from flask_bcrypt import Bcrypt
|
||||||
|
from flask_login import LoginManager
|
||||||
|
|
||||||
|
app = Flask(__name__)
|
||||||
|
|
||||||
|
SECRET_KEY = os.urandom(32)
|
||||||
|
app.config['SECRET_KEY'] = SECRET_KEY
|
||||||
|
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///bot/app.db'
|
||||||
|
|
||||||
|
db = SQLAlchemy(app)
|
||||||
|
bcrypt = Bcrypt(app)
|
||||||
|
login_manager = LoginManager(app)
|
||||||
|
login_manager.login_view = 'login'
|
||||||
|
login_manager.login_message_category = 'info'
|
||||||
|
|
||||||
|
from app import routes
|
||||||
194
app/app/bot/Invitarr.py
Normal file
194
app/app/bot/Invitarr.py
Normal file
@@ -0,0 +1,194 @@
|
|||||||
|
#Copyright 2020 Sleepingpirate.
|
||||||
|
import os
|
||||||
|
from os import environ
|
||||||
|
import discord
|
||||||
|
from discord.ext import commands
|
||||||
|
import asyncio
|
||||||
|
from plexapi.myplex import MyPlexAccount
|
||||||
|
from discord import Webhook, AsyncWebhookAdapter
|
||||||
|
import aiohttp
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
import configparser
|
||||||
|
CONFIG_PATH = 'app/config/config.ini'
|
||||||
|
BOT_SECTION = 'bot_envs'
|
||||||
|
|
||||||
|
try:
|
||||||
|
config = configparser.ConfigParser()
|
||||||
|
config.read(CONFIG_PATH)
|
||||||
|
except:
|
||||||
|
print("Cannot find config")
|
||||||
|
|
||||||
|
# settings
|
||||||
|
Discord_bot_token = config.get(BOT_SECTION, 'discord_bot_token')
|
||||||
|
roleid = int(config.get(BOT_SECTION, 'role_id'))
|
||||||
|
PLEXUSER = config.get(BOT_SECTION, 'plex_user')
|
||||||
|
PLEXPASS = config.get(BOT_SECTION, 'plex_pass')
|
||||||
|
PLEX_SERVER_NAME = config.get(BOT_SECTION, 'plex_server_name')
|
||||||
|
Plex_LIBS = config.get(BOT_SECTION, 'plex_libs')
|
||||||
|
chan = int(config.get(BOT_SECTION, 'channel_id'))
|
||||||
|
ownerid = int(config.get(BOT_SECTION, 'owner_id'))
|
||||||
|
auto_remove_user = config.get(BOT_SECTION, 'auto_remove_user') if config.get(BOT_SECTION, 'auto_remove_user') else False
|
||||||
|
|
||||||
|
li = list(Plex_LIBS.split(','))
|
||||||
|
Plex_LIBS = li
|
||||||
|
|
||||||
|
#rolenames = list(roleid.split(','))
|
||||||
|
|
||||||
|
if auto_remove_user:
|
||||||
|
print("auto remove user = True")
|
||||||
|
import db as db
|
||||||
|
|
||||||
|
account = MyPlexAccount(PLEXUSER, PLEXPASS)
|
||||||
|
plex = account.resource(PLEX_SERVER_NAME).connect() # returns a PlexServer instance
|
||||||
|
|
||||||
|
def plexadd(plexname):
|
||||||
|
try:
|
||||||
|
plex.myPlexAccount().inviteFriend(user=plexname, server=plex, sections=Plex_LIBS, allowSync=False,
|
||||||
|
allowCameraUpload=False, allowChannels=False, filterMovies=None,
|
||||||
|
filterTelevision=None, filterMusic=None)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(e)
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
print(plexname +' has been added to plex (☞ຈل͜ຈ)☞')
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def plexremove(plexname):
|
||||||
|
try:
|
||||||
|
plex.myPlexAccount().removeFriend(user=plexname)
|
||||||
|
except Exception as e:
|
||||||
|
print(e)
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
print(plexname +' has been removed from plex (☞ຈل͜ຈ)☞')
|
||||||
|
return True
|
||||||
|
|
||||||
|
class MyClient(discord.Client):
|
||||||
|
async def on_ready(self):
|
||||||
|
print('Made by Sleepingpirate https://github.com/Sleepingpirates/')
|
||||||
|
print('Logged in as')
|
||||||
|
print(self.user.name)
|
||||||
|
print(self.user.id)
|
||||||
|
print('------')
|
||||||
|
await client.change_presence(activity=discord.Game(name="Do -help in {}".format(client.get_channel(chan))))
|
||||||
|
|
||||||
|
async def on_member_update(self, before, after):
|
||||||
|
role = after.guild.get_role(roleid)
|
||||||
|
secure = client.get_channel(chan)
|
||||||
|
if (role in after.roles and role not in before.roles):
|
||||||
|
await after.send('Welcome To '+ PLEX_SERVER_NAME +'. Just reply with your email so we can add you to Plex!')
|
||||||
|
await after.send('I will wait 10 minutes for your message, if you do not send it by then I will cancel the command.')
|
||||||
|
def check(m):
|
||||||
|
return m.author == after and not m.guild
|
||||||
|
try:
|
||||||
|
email = await client.wait_for('message', timeout=600, check=check)
|
||||||
|
except asyncio.TimeoutError:
|
||||||
|
await after.send('Timed Out. Message Server Admin So They Can Add You Manually.')
|
||||||
|
else:
|
||||||
|
await asyncio.sleep(5)
|
||||||
|
await after.send('Got it we will be processing your email shortly')
|
||||||
|
print(email.content) #make it go to a log channel
|
||||||
|
plexname = str(email.content)
|
||||||
|
if plexadd(plexname):
|
||||||
|
if auto_remove_user:
|
||||||
|
db.save_user(str(after.id), email.content)
|
||||||
|
await asyncio.sleep(20)
|
||||||
|
await after.send('You have Been Added To Plex!')
|
||||||
|
await secure.send(plexname + ' ' + after.mention + ' was added to plex')
|
||||||
|
else:
|
||||||
|
await after.send('There was an error adding this email address. Message Server Admin.')
|
||||||
|
elif(role not in after.roles and role in before.roles):
|
||||||
|
if auto_remove_user:
|
||||||
|
try:
|
||||||
|
user_id = after.id
|
||||||
|
email = db.get_useremail(user_id)
|
||||||
|
plexremove(email)
|
||||||
|
deleted = db.delete_user(user_id)
|
||||||
|
if deleted:
|
||||||
|
print("Removed {} from db".format(email))
|
||||||
|
await secure.send(plexname + ' ' + after.mention + ' was removed from plex')
|
||||||
|
else:
|
||||||
|
print("Cannot remove this user from db.")
|
||||||
|
except:
|
||||||
|
print("Cannot remove this user from plex.")
|
||||||
|
|
||||||
|
|
||||||
|
async def on_message(self, message):
|
||||||
|
secure = client.get_channel(chan)
|
||||||
|
if message.author.id == self.user.id:
|
||||||
|
return
|
||||||
|
|
||||||
|
if message.author.id == ownerid:
|
||||||
|
if message.content.startswith('-db add'):
|
||||||
|
mgs = message.content.replace('-db add ','')
|
||||||
|
try:
|
||||||
|
mgs = mgs.split(' ')
|
||||||
|
email = mgs[0]
|
||||||
|
bad_chars = ['<','>','@','!']
|
||||||
|
user_id = mgs[1]
|
||||||
|
for i in bad_chars:
|
||||||
|
user_id = user_id.replace(i, '')
|
||||||
|
db.save_user(user_id, email)
|
||||||
|
await secure.send(email + ' ' + mgs[1] + ' was added to plex')
|
||||||
|
except:
|
||||||
|
await message.channel.send('Cannot add this user to db.')
|
||||||
|
print("Cannot add this user to db.")
|
||||||
|
await message.delete()
|
||||||
|
|
||||||
|
if str(message.channel) == str(secure):
|
||||||
|
if message.content.startswith('-db ls') or message.content.startswith('-db rm'):
|
||||||
|
embed = discord.Embed(title='Invitarr Database.')
|
||||||
|
all = db.read_useremail()
|
||||||
|
for index, peoples in enumerate(all):
|
||||||
|
index = index + 1
|
||||||
|
id = int(peoples[1])
|
||||||
|
dbuser = client.get_user(id)
|
||||||
|
dbemail = peoples[2]
|
||||||
|
embed.add_field(name=f"**{index}. {dbuser.name}**", value=dbemail+'\n', inline=False)
|
||||||
|
if message.content.startswith('-db ls'):
|
||||||
|
await secure.send(embed = embed)
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
position = message.content.replace("-db rm", "")
|
||||||
|
position = int(position) - 1
|
||||||
|
id = all[position][1]
|
||||||
|
email = db.get_useremail(id)
|
||||||
|
deleted = db.delete_user(id)
|
||||||
|
if deleted:
|
||||||
|
print("Removed {} from db".format(email))
|
||||||
|
await secure.send("Removed {} from db".format(email))
|
||||||
|
else:
|
||||||
|
print("Cannot remove this user from db.")
|
||||||
|
except Exception as e:
|
||||||
|
print(e)
|
||||||
|
|
||||||
|
if message.content.startswith('-help'):
|
||||||
|
embed = discord.Embed(title='Invitarr Bot Commands', description='Made by [Sleepingpirates](https://github.com/Sleepingpirates/Invitarr), [Join Discord Server](https://discord.gg/vcxCytN)')
|
||||||
|
embed.add_field(name='-plexadd <email>', value='This command is used to add an email to plex', inline=False)
|
||||||
|
embed.add_field(name='-plexrm <email>', value='This command is used to remove an email from plex', inline=False)
|
||||||
|
embed.add_field(name='-db ls', value='This command is used list Invitarrs database', inline=False)
|
||||||
|
embed.add_field(name='-db add <email> <@user>', value='This command is used add exsisting users email and discord id to the DB.', inline=False)
|
||||||
|
embed.add_field(name='-db rm <position>', value='This command is used remove a record from the Db. Use -db ls to determine record position. ex: -db rm 1', inline=False)
|
||||||
|
await secure.send(embed = embed)
|
||||||
|
|
||||||
|
|
||||||
|
async def on_member_remove(self, member):
|
||||||
|
if auto_remove_user:
|
||||||
|
try:
|
||||||
|
user_id = member.id ## not there
|
||||||
|
email = db.get_useremail(user_id)
|
||||||
|
plexremove(email)
|
||||||
|
deleted = db.delete_user(user_id)
|
||||||
|
if deleted:
|
||||||
|
print("Removed {} from db".format(email))
|
||||||
|
secure = client.get_channel(chan)
|
||||||
|
await secure.send(email + ' ' + member.mention + 'was removed from plex because they left the server')
|
||||||
|
else:
|
||||||
|
print("Cannot remove this user from db.")
|
||||||
|
except:
|
||||||
|
print("Cannot remove this user from plex.")
|
||||||
|
|
||||||
|
client = MyClient()
|
||||||
|
client.run(Discord_bot_token)
|
||||||
83
app/app/bot/db.py
Normal file
83
app/app/bot/db.py
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
import sqlite3
|
||||||
|
|
||||||
|
DB_URL = 'app/config/app.db'
|
||||||
|
|
||||||
|
def create_connection(db_file):
|
||||||
|
""" create a database connection to a SQLite database """
|
||||||
|
conn = None
|
||||||
|
try:
|
||||||
|
conn = sqlite3.connect(db_file)
|
||||||
|
print("Connected to db")
|
||||||
|
except Error as e:
|
||||||
|
print("error in connecting to db")
|
||||||
|
finally:
|
||||||
|
if conn:
|
||||||
|
return conn
|
||||||
|
|
||||||
|
def checkTableExists(dbcon, tablename):
|
||||||
|
dbcur = dbcon.cursor()
|
||||||
|
dbcur.execute("""SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name='{0}';""".format(tablename.replace('\'', '\'\'')))
|
||||||
|
if dbcur.fetchone()[0] == 1:
|
||||||
|
dbcur.close()
|
||||||
|
return True
|
||||||
|
dbcur.close()
|
||||||
|
return False
|
||||||
|
|
||||||
|
conn = create_connection(DB_URL)
|
||||||
|
|
||||||
|
# Checking if table exists
|
||||||
|
if checkTableExists(conn, 'clients'):
|
||||||
|
print('Table exists.')
|
||||||
|
else:
|
||||||
|
conn.execute('''
|
||||||
|
CREATE TABLE "clients" (
|
||||||
|
"id" INTEGER NOT NULL UNIQUE,
|
||||||
|
"discord_username" TEXT NOT NULL UNIQUE,
|
||||||
|
"email" TEXT NOT NULL,
|
||||||
|
PRIMARY KEY("id" AUTOINCREMENT)
|
||||||
|
);
|
||||||
|
''')
|
||||||
|
|
||||||
|
def save_user(username, email):
|
||||||
|
if username and email:
|
||||||
|
conn.execute("INSERT INTO clients (discord_username, email) VALUES ('"+ username +"', '" + email + "')");
|
||||||
|
conn.commit()
|
||||||
|
print("User added to db.")
|
||||||
|
else:
|
||||||
|
return "Username or email cannot be empty"
|
||||||
|
|
||||||
|
def get_useremail(username):
|
||||||
|
if username:
|
||||||
|
try:
|
||||||
|
cursor = conn.execute('SELECT discord_username, email from clients where discord_username="{}";'.format(username))
|
||||||
|
for row in cursor:
|
||||||
|
email = row[1]
|
||||||
|
if email:
|
||||||
|
return email
|
||||||
|
else:
|
||||||
|
return "No users found"
|
||||||
|
except:
|
||||||
|
return "error in fetching from db"
|
||||||
|
else:
|
||||||
|
return "username cannot be empty"
|
||||||
|
|
||||||
|
def delete_user(username):
|
||||||
|
if username:
|
||||||
|
try:
|
||||||
|
conn.execute('DELETE from clients where discord_username="{}";'.format(username))
|
||||||
|
conn.commit()
|
||||||
|
return True
|
||||||
|
except:
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
return "username cannot be empty"
|
||||||
|
|
||||||
|
def read_useremail():
|
||||||
|
cur = conn.cursor()
|
||||||
|
cur.execute("SELECT * FROM clients")
|
||||||
|
rows = cur.fetchall()
|
||||||
|
all = []
|
||||||
|
for row in rows:
|
||||||
|
#print(row[1]+' '+row[2])
|
||||||
|
all.append(row)
|
||||||
|
return all
|
||||||
56
app/app/configHandler.py
Normal file
56
app/app/configHandler.py
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
import configparser
|
||||||
|
|
||||||
|
config = configparser.ConfigParser()
|
||||||
|
|
||||||
|
CONFIG_PATH = 'app/config/config.ini'
|
||||||
|
BOT_SECTION = 'bot_envs'
|
||||||
|
CONFIG_KEYS = ['username', 'password', 'discord_bot_token', 'plex_user', 'plex_pass',
|
||||||
|
'role_id', 'plex_server_name', 'plex_libs', 'owner_id', 'channel_id',
|
||||||
|
'auto_remove_user']
|
||||||
|
|
||||||
|
def get_config():
|
||||||
|
"""
|
||||||
|
Function to return current config
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
config.read(CONFIG_PATH)
|
||||||
|
return config
|
||||||
|
except Exception as e:
|
||||||
|
print(e)
|
||||||
|
print('error in reading config')
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def change_config(key, value):
|
||||||
|
"""
|
||||||
|
Function to change the key, value pair in config
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
config = configparser.ConfigParser()
|
||||||
|
config.read(CONFIG_PATH)
|
||||||
|
except Exception as e:
|
||||||
|
print(e)
|
||||||
|
print("Cannot Read config.")
|
||||||
|
|
||||||
|
try:
|
||||||
|
config.set(BOT_SECTION, key, str(value))
|
||||||
|
except Exception as e:
|
||||||
|
config.add_section(BOT_SECTION)
|
||||||
|
config.set(BOT_SECTION, key, str(value))
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(CONFIG_PATH, 'w') as configfile:
|
||||||
|
config.write(configfile)
|
||||||
|
except Exception as e:
|
||||||
|
print(e)
|
||||||
|
print("Cannot write to config.")
|
||||||
|
|
||||||
|
def change_config_form(form_output):
|
||||||
|
"""
|
||||||
|
Function to change config on the basis of form_output from web
|
||||||
|
"""
|
||||||
|
for key in CONFIG_KEYS:
|
||||||
|
try:
|
||||||
|
change_config(key, form_output[key].data)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
52
app/app/forms.py
Normal file
52
app/app/forms.py
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
from flask_wtf import FlaskForm
|
||||||
|
from wtforms import StringField, PasswordField, SubmitField, BooleanField, IntegerField
|
||||||
|
from wtforms.validators import DataRequired
|
||||||
|
|
||||||
|
class LoginForm(FlaskForm):
|
||||||
|
|
||||||
|
username = StringField('Username',
|
||||||
|
validators=[DataRequired()])
|
||||||
|
|
||||||
|
password = PasswordField('Password',
|
||||||
|
validators=[DataRequired()])
|
||||||
|
|
||||||
|
submit = SubmitField('Save Changes')
|
||||||
|
|
||||||
|
class GeneralForm(FlaskForm):
|
||||||
|
|
||||||
|
username = StringField('Username',
|
||||||
|
validators=[DataRequired()])
|
||||||
|
|
||||||
|
password = PasswordField('Password',
|
||||||
|
validators=[DataRequired()])
|
||||||
|
|
||||||
|
submit = SubmitField('Save Changes')
|
||||||
|
|
||||||
|
class BotForm(FlaskForm):
|
||||||
|
|
||||||
|
discord_bot_token = StringField('Discord Bot Token',
|
||||||
|
validators=[DataRequired()])
|
||||||
|
|
||||||
|
role_id = IntegerField('Role Id',
|
||||||
|
validators=[DataRequired()])
|
||||||
|
|
||||||
|
channel_id = IntegerField('Channel Id',
|
||||||
|
validators=[DataRequired()])
|
||||||
|
owner_id = IntegerField('Owner Id',
|
||||||
|
validators=[DataRequired()])
|
||||||
|
# Auto Remove User
|
||||||
|
auto_remove_user = BooleanField('On')
|
||||||
|
|
||||||
|
submit = SubmitField('Save Changes')
|
||||||
|
|
||||||
|
class PlexForm(FlaskForm):
|
||||||
|
|
||||||
|
plex_user = StringField('Plex User',
|
||||||
|
validators=[DataRequired()])
|
||||||
|
plex_pass = StringField('Plex Pass',
|
||||||
|
validators=[DataRequired()])
|
||||||
|
plex_server_name = StringField('Plex Server Name',
|
||||||
|
validators=[DataRequired()])
|
||||||
|
plex_libs = StringField('Plex Libs',
|
||||||
|
validators=[DataRequired()])
|
||||||
|
submit = SubmitField('Save Changes')
|
||||||
15
app/app/models.py
Normal file
15
app/app/models.py
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
from app import db, login_manager
|
||||||
|
from flask_login import UserMixin
|
||||||
|
|
||||||
|
@login_manager.user_loader
|
||||||
|
def load_user(user_id):
|
||||||
|
return User.query.get(int(user_id))
|
||||||
|
|
||||||
|
|
||||||
|
class User(db.Model, UserMixin):
|
||||||
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
|
username = db.Column(db.String(20), unique=True, nullable=False)
|
||||||
|
password = db.Column(db.String(60), nullable=False)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"User('{self.username}')"
|
||||||
120
app/app/routes.py
Normal file
120
app/app/routes.py
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
from flask import render_template, flash, request, redirect, url_for
|
||||||
|
from flask_login import login_user, current_user, logout_user, login_required
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
from app import app, db, bcrypt
|
||||||
|
from app.models import User
|
||||||
|
from app.forms import LoginForm, GeneralForm, BotForm, PlexForm
|
||||||
|
from app import configHandler
|
||||||
|
|
||||||
|
db.create_all()
|
||||||
|
BOT_SECTION = 'bot_envs'
|
||||||
|
proc = None
|
||||||
|
|
||||||
|
def manage_bot(option):
|
||||||
|
global proc
|
||||||
|
if option == 'start':
|
||||||
|
proc = subprocess.Popen(["python", "app/bot/Invitarr.py"])
|
||||||
|
elif option == 'kill':
|
||||||
|
if proc:
|
||||||
|
proc.terminate()
|
||||||
|
|
||||||
|
try:
|
||||||
|
manage_bot('start')
|
||||||
|
except:
|
||||||
|
print("Some error in starting bot. Please check the config vars")
|
||||||
|
|
||||||
|
@app.route("/login", methods=['GET', 'POST'])
|
||||||
|
def login():
|
||||||
|
if current_user.is_authenticated:
|
||||||
|
return redirect(url_for('home'))
|
||||||
|
form = LoginForm()
|
||||||
|
if form.validate_on_submit():
|
||||||
|
user = User.query.filter_by(username=form.username.data).first()
|
||||||
|
if user and bcrypt.check_password_hash(user.password, form.password.data):
|
||||||
|
login_user(user, remember=True)
|
||||||
|
next_page = request.args.get('next')
|
||||||
|
return redirect(next_page) if next_page else redirect(url_for('home'))
|
||||||
|
else:
|
||||||
|
flash('Login Unsuccessful. Please check email and password')
|
||||||
|
return render_template('login.html', title='Login', form=form)
|
||||||
|
|
||||||
|
@app.route('/logout')
|
||||||
|
@login_required
|
||||||
|
def logout():
|
||||||
|
flash('Logged out.')
|
||||||
|
logout_user()
|
||||||
|
return redirect(url_for('login'))
|
||||||
|
|
||||||
|
@app.route('/', methods=['GET', 'POST'])
|
||||||
|
@login_required
|
||||||
|
def home():
|
||||||
|
form = GeneralForm()
|
||||||
|
if request.method == 'GET':
|
||||||
|
user = User.query.all()[0]
|
||||||
|
form.username.data = user.username
|
||||||
|
form.password.data = user.password
|
||||||
|
elif request.method == 'POST':
|
||||||
|
if form.validate_on_submit():
|
||||||
|
user = User.query.all()[0]
|
||||||
|
user.username = form.username.data
|
||||||
|
hashed_password = bcrypt.generate_password_hash(form.password.data).decode('utf-8')
|
||||||
|
user.password = hashed_password
|
||||||
|
db.session.commit()
|
||||||
|
logout_user()
|
||||||
|
flash('Details updated.')
|
||||||
|
return redirect(url_for('login'))
|
||||||
|
return render_template('index.html', form=form)
|
||||||
|
|
||||||
|
@app.route('/bot', methods=['GET', 'POST'])
|
||||||
|
@login_required
|
||||||
|
def bot():
|
||||||
|
form = BotForm()
|
||||||
|
if request.method == 'GET':
|
||||||
|
try:
|
||||||
|
config = configHandler.get_config()
|
||||||
|
form.discord_bot_token.data = config.get(BOT_SECTION, 'discord_bot_token')
|
||||||
|
form.role_id.data = config.get(BOT_SECTION, 'role_id')
|
||||||
|
form.channel_id.data = config.get(BOT_SECTION, 'channel_id')
|
||||||
|
form.owner_id.data = config.get(BOT_SECTION, 'owner_id')
|
||||||
|
form.auto_remove_user.data = config.get(BOT_SECTION, 'auto_remove_user')
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
elif request.method == 'POST':
|
||||||
|
if form.validate_on_submit():
|
||||||
|
try:
|
||||||
|
configHandler.change_config_form(form)
|
||||||
|
flash('Settings updated.')
|
||||||
|
manage_bot('kill')
|
||||||
|
manage_bot('start')
|
||||||
|
except:
|
||||||
|
flash('Some error in updating settings')
|
||||||
|
return render_template('bot.html', form = form)
|
||||||
|
|
||||||
|
@app.route('/plex', methods=['GET', 'POST'])
|
||||||
|
@login_required
|
||||||
|
def plex():
|
||||||
|
form = PlexForm()
|
||||||
|
if request.method == 'GET':
|
||||||
|
try:
|
||||||
|
config = configHandler.get_config()
|
||||||
|
form.plex_user.data = config.get(BOT_SECTION, 'plex_user')
|
||||||
|
form.plex_pass.data = config.get(BOT_SECTION, 'plex_pass')
|
||||||
|
form.plex_server_name.data = config.get(BOT_SECTION, 'plex_server_name')
|
||||||
|
form.plex_libs.data = config.get(BOT_SECTION, 'plex_libs')
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
elif request.method == 'POST':
|
||||||
|
if form.validate_on_submit():
|
||||||
|
try:
|
||||||
|
configHandler.change_config_form(form)
|
||||||
|
flash('Settings updated.')
|
||||||
|
except:
|
||||||
|
flash('Some error in updating settings')
|
||||||
|
try:
|
||||||
|
manage_bot('kill')
|
||||||
|
manage_bot('start')
|
||||||
|
except Exception as e:
|
||||||
|
raise Exception(e)
|
||||||
|
|
||||||
|
return render_template('plex.html', form = form)
|
||||||
BIN
app/app/static/favicon.ico
Normal file
BIN
app/app/static/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
45
app/app/static/img/logo.svg
Normal file
45
app/app/static/img/logo.svg
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Generator: Adobe Illustrator 24.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||||
|
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
|
viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve">
|
||||||
|
<style type="text/css">
|
||||||
|
.st0{fill:#FF4A4F;}
|
||||||
|
.st1{fill:#E94A4F;}
|
||||||
|
.st2{fill:#FFFFFF;}
|
||||||
|
.st3{fill:#2A2A2C;}
|
||||||
|
.st4{fill:#171719;}
|
||||||
|
.st5{fill:#57555C;}
|
||||||
|
.st6{fill:#3C3B41;}
|
||||||
|
.st7{fill:#C8C6CC;}
|
||||||
|
</style>
|
||||||
|
<circle class="st0" cx="256" cy="256" r="256"/>
|
||||||
|
<path class="st1" d="M512,256c0-13.4-1-26.6-3-39.4L397.9,105.5H108.4H82.9v241.4v97.6l0,0C128.5,486.4,189.2,512,256,512
|
||||||
|
C397.4,512,512,397.4,512,256z"/>
|
||||||
|
<path class="st2" d="M82.9,105.5v339.1c8,7.4,16.6,14.3,25.5,20.6V105.5L82.9,105.5L82.9,105.5z"/>
|
||||||
|
<rect x="334.6" y="152.9" class="st3" width="105.8" height="241.5"/>
|
||||||
|
<polygon class="st4" points="397.9,105.5 334.6,152.9 334.6,394.4 397.9,346.9 "/>
|
||||||
|
<rect x="82.9" y="105.5" class="st5" width="315" height="241.5"/>
|
||||||
|
<rect x="240.4" y="105.5" class="st6" width="157.5" height="241.5"/>
|
||||||
|
<path class="st2" d="M187.4,158.4l14,14c-6,5.2-11,11.6-14.6,18.8l-16.1-16.1c-6,5.8-15.4,5.7-21.3-0.2c-5.9-5.9-5.9-15.6,0-21.5
|
||||||
|
c3.3-3.3,7.7-4.7,11.9-4.4c-0.3-4.3,1.1-8.7,4.4-11.9c5.9-5.9,15.6-5.9,21.5,0C193.1,143,193.2,152.5,187.4,158.4z"/>
|
||||||
|
<path class="st7" d="M331.4,302.7c-3.3,3.3-7.7,4.7-11.9,4.4c0.3,4.3-1.1,8.7-4.4,11.9c-5.9,5.9-15.6,5.9-21.5,0
|
||||||
|
c-5.9-5.9-5.9-15.4-0.2-21.3L272,276.3v-5.2c1.6,0.3,3.2,0.5,4.8,0.5c6.6,0,12.5-2.7,16.8-7L310,281c5.9-5.8,15.4-5.7,21.3,0.2
|
||||||
|
C337.3,287.1,337.3,296.8,331.4,302.7z"/>
|
||||||
|
<path class="st2" d="M204,271.6c1.6,0,3.3-0.2,4.8-0.5v5.2l-21.4,21.4c5.8,5.9,5.7,15.4-0.2,21.3c-5.9,5.9-15.6,5.9-21.5,0
|
||||||
|
c-3.3-3.3-4.7-7.7-4.4-11.9c-4.3,0.3-8.7-1.1-11.9-4.4c-5.9-5.9-5.9-15.6,0-21.5c5.9-5.9,15.4-5.9,21.3-0.2l16.5-16.5
|
||||||
|
C191.5,268.9,197.4,271.6,204,271.6z"/>
|
||||||
|
<path class="st7" d="M331.4,174.9c-5.9,5.9-15.4,5.9-21.3,0.2l-16.1,16.1c-3.6-7.2-8.6-13.6-14.6-18.8l14-14
|
||||||
|
c-5.8-6-5.7-15.4,0.2-21.3c5.9-5.9,15.6-5.9,21.5,0c3.3,3.3,4.7,7.7,4.4,11.9c4.3-0.3,8.7,1.1,11.9,4.4
|
||||||
|
C337.3,159.4,337.3,169,331.4,174.9z"/>
|
||||||
|
<path class="st2" d="M286.7,237.7c2.6-6.1,4.1-12.9,4.1-20c0-27.9-22.6-50.5-50.5-50.5s-50.5,22.6-50.5,50.5c0,7.1,1.5,13.8,4.1,20
|
||||||
|
c-2.7,2.6-4.4,6.3-4.4,10.4c0,7.9,6.4,14.4,14.4,14.4c3.1,0,6-1,8.4-2.7c1.8,1.2,3.7,2.3,5.7,3.3v15.8c0,3.8,3.1,6.8,6.8,6.8h0.8
|
||||||
|
v-18.3h6.6v18.3h4.8v-18.3h6.6v18.3h4.8v-18.3h6.6v18.3h0.8c3.8,0,6.8-3.1,6.8-6.8v-15.8c2-1,3.9-2.1,5.7-3.3
|
||||||
|
c2.4,1.7,5.3,2.7,8.4,2.7c7.9,0,14.4-6.4,14.4-14.4C291.2,243.9,289.5,240.3,286.7,237.7z M222.7,226.2h-5c-5.3,0-9.6-4.3-9.6-9.6
|
||||||
|
v-5c0-5.3,4.3-9.6,9.6-9.6h5c5.3,0,9.6,4.3,9.6,9.6v5C232.4,221.8,228,226.2,222.7,226.2z M246.5,246.1h-6.1h-6.1
|
||||||
|
c-2.9,0-4.7-3.1-3.3-5.6l3-5.3l3-5.3c1.4-2.5,5.1-2.5,6.5,0l3,5.3l3,5.3C251.2,243,249.4,246.1,246.5,246.1z M272.7,216.5
|
||||||
|
c0,5.3-4.3,9.6-9.6,9.6h-5c-5.3,0-9.6-4.3-9.6-9.6v-5c0-5.3,4.3-9.6,9.6-9.6h5c5.3,0,9.6,4.3,9.6,9.6V216.5z"/>
|
||||||
|
<path class="st7" d="M286.7,237.7c2.6-6.1,4.1-12.9,4.1-20c0-27.9-22.6-50.5-50.5-50.5v60.8c1.3,0,2.5,0.6,3.3,1.9l3,5.3l3,5.3
|
||||||
|
c1.4,2.5-0.4,5.6-3.3,5.6h-6.1v21.2h3.3v18.3h4.8v-18.3h6.6v18.3h0.8c3.8,0,6.8-3.1,6.8-6.8v-15.9c2-1,3.9-2.1,5.7-3.3
|
||||||
|
c2.4,1.7,5.3,2.7,8.4,2.7c7.9,0,14.4-6.4,14.4-14.4C291.2,243.9,289.5,240.3,286.7,237.7z M272.7,216.5c0,5.3-4.3,9.6-9.6,9.6h-5
|
||||||
|
c-5.3,0-9.6-4.3-9.6-9.6v-5c0-5.3,4.3-9.6,9.6-9.6h5c5.3,0,9.6,4.3,9.6,9.6V216.5z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 3.4 KiB |
86
app/app/static/main.css
Normal file
86
app/app/static/main.css
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
/* Universal */
|
||||||
|
:root {
|
||||||
|
--main-bg-color: #000000;
|
||||||
|
--content-background: #17151C;
|
||||||
|
--secondary-content-background: #2A2731;
|
||||||
|
--primary-text-color: #FFFFFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
body{
|
||||||
|
background-color: var(--main-bg-color);
|
||||||
|
color: var(--primary-text-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Header */
|
||||||
|
h3{
|
||||||
|
font-weight: bold;
|
||||||
|
display: inline-block;
|
||||||
|
margin: 0.5em 0 1.5em 0.25em;
|
||||||
|
padding-top: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header img{
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.nav-link{
|
||||||
|
color: var(--primary-text-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.header button{
|
||||||
|
max-height: 50px;
|
||||||
|
/* margin: 1em 0.5em; */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Form */
|
||||||
|
.form-container{
|
||||||
|
padding: 1.5em;
|
||||||
|
padding-top: 2em;
|
||||||
|
border-radius: 0 0 10px 10px;
|
||||||
|
background-color: var(--content-background);
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-stringfield{
|
||||||
|
background-color: var(--secondary-content-background);
|
||||||
|
color: var(--primary-text-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-stringfield:focus{
|
||||||
|
background-color: var(--secondary-content-background);
|
||||||
|
color: var(--primary-text-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-checkbox{
|
||||||
|
padding-left: 3em;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Modal */
|
||||||
|
.modal-content{
|
||||||
|
background-color: var(--secondary-content-background);
|
||||||
|
padding: 1em !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-body{
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-header{
|
||||||
|
padding: 1em;
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-header h4{
|
||||||
|
color: var(--primary-text-color)
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Login Page */
|
||||||
|
.login{
|
||||||
|
margin: auto;
|
||||||
|
margin-top: 5em;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login h2{
|
||||||
|
margin-top: 1em;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
194
app/app/templates/bot.html
Normal file
194
app/app/templates/bot.html
Normal file
@@ -0,0 +1,194 @@
|
|||||||
|
{%extends 'layout.html'%}
|
||||||
|
{% block content %}
|
||||||
|
<div class="header mt-5 d-flex">
|
||||||
|
<img src="{{ url_for('static', filename='img/logo.svg') }}" class="img align-self-center" height="45">
|
||||||
|
<h3 class="align-self-center">Invitarr Configuration</h3>
|
||||||
|
<!-- <button class="ml-auto btn btn-danger align-self-center">Restart bot</button> -->
|
||||||
|
</div>
|
||||||
|
<nav>
|
||||||
|
<ul class="nav nav-tabs">
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="{{ url_for('home') }}">General</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link active" href="#">Bot</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="{{ url_for('plex') }}">Plex</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item ml-auto">
|
||||||
|
<a class="nav-link" href="{{ url_for('logout') }}">Logout</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
<div class="form-container">
|
||||||
|
<!-- Form Section -->
|
||||||
|
<form method="POST" action="">
|
||||||
|
{{ form.hidden_tag() }}
|
||||||
|
<div class="form-group mb-3">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-3">
|
||||||
|
{{ form.discord_bot_token.label(class="form-control-label")}}
|
||||||
|
</div>
|
||||||
|
<div class="col-md-1"></div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
{% if form.discord_bot_token.errors %}
|
||||||
|
{{ form.discord_bot_token(class="form-control form-stringfield")}}
|
||||||
|
<small id="emailHelp" class="form-text text-muted">Some text describing this field</small>
|
||||||
|
<div class="invalid-feedback">
|
||||||
|
{% for error in form.discord_bot_token.errors %}
|
||||||
|
<span>{{ error }}</span>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
{{ form.discord_bot_token(class="form-control form-stringfield")}}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group my-3">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-3">
|
||||||
|
{{ form.role_id.label(class="form-control-label")}}
|
||||||
|
</div>
|
||||||
|
<div class="col-md-1"></div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
{% if form.role_id.errors %}
|
||||||
|
{{ form.role_id(class="form-control form-stringfield")}}
|
||||||
|
<div class="invalid-feedback">
|
||||||
|
{% for error in form.role_id.errors %}
|
||||||
|
<span>{{ error }}</span>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
{{ form.role_id(class="form-control form-stringfield")}}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group my-3">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-3">
|
||||||
|
{{ form.channel_id.label(class="form-control-label")}}
|
||||||
|
</div>
|
||||||
|
<div class="col-md-1"></div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
{% if form.channel_id.errors %}
|
||||||
|
{{ form.channel_id(class="form-control form-stringfield")}}
|
||||||
|
<div class="invalid-feedback">
|
||||||
|
{% for error in form.channel_id.errors %}
|
||||||
|
<span>{{ error }}</span>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
{{ form.channel_id(class="form-control form-stringfield")}}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group my-3">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-3">
|
||||||
|
{{ form.owner_id.label(class="form-control-label")}}
|
||||||
|
</div>
|
||||||
|
<div class="col-md-1"></div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
{% if form.owner_id.errors %}
|
||||||
|
{{ form.owner_id(class="form-control form-stringfield")}}
|
||||||
|
<div class="invalid-feedback">
|
||||||
|
{% for error in form.owner_id.errors %}
|
||||||
|
<span>{{ error }}</span>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
{{ form.owner_id(class="form-control form-stringfield")}}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="my-3">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-3">
|
||||||
|
<p>Auto Remove User</p>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-1"></div>
|
||||||
|
<div class="col-md-1 custom-control custom-control-inline custom-switch custom-checkbox">
|
||||||
|
{% if form.auto_remove_user.errors %}
|
||||||
|
{{ form.auto_remove_user(class="custom-control-input form-stringfield", id="removeSwitch")}}
|
||||||
|
{{ form.auto_remove_user.label(class="custom-control-label", for="removeSwitch")}}
|
||||||
|
<div class="invalid-feedback">
|
||||||
|
{% for error in form.auto_remove_user.errors %}
|
||||||
|
<span>{{ error }}</span>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
{{ form.auto_remove_user(class="custom-control-input form-stringfield", id="removeSwitch")}}
|
||||||
|
{{ form.auto_remove_user.label(class="custom-control-label", for="removeSwitch")}}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group my-5 submit-btn">
|
||||||
|
{{ form.submit(class="btn btn-primary submit-btn") }}
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div id="myModal" class="modal fade" role="dialog">
|
||||||
|
<div class="modal-dialog modal-sm">
|
||||||
|
|
||||||
|
<!-- Modal content-->
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
{% with messages = get_flashed_messages() %}
|
||||||
|
{% if messages %}
|
||||||
|
<h4>
|
||||||
|
{% for message in messages %}
|
||||||
|
{{ message }}
|
||||||
|
{% endfor %}
|
||||||
|
</h4>
|
||||||
|
{% endif %}
|
||||||
|
{% endwith %}
|
||||||
|
<button type="button" class="close" data-dismiss="modal">×</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock content %}
|
||||||
|
|
||||||
|
{% block script %}
|
||||||
|
<script>
|
||||||
|
$(document).ready(()=> {
|
||||||
|
|
||||||
|
// Trigger flash modal
|
||||||
|
let messages = "{{ get_flashed_messages() }}";
|
||||||
|
|
||||||
|
if (typeof messages != 'undefined' && messages != '[]') {
|
||||||
|
$("#myModal").modal();
|
||||||
|
};
|
||||||
|
|
||||||
|
// Adding Validation
|
||||||
|
number_validaitons = ['role_id', 'channel_id', 'owner_id']
|
||||||
|
number_validaitons.forEach(element => {
|
||||||
|
$('#' + element).keyup(function () {
|
||||||
|
this.value = this.value.replace(/[^0-9\.]/g,'');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
let checkbox_state = $('input[name=auto_remove_user]').is(':checked');
|
||||||
|
|
||||||
|
if(checkbox_state == false){
|
||||||
|
$('.custom-control-label').text('Off');
|
||||||
|
}
|
||||||
|
|
||||||
|
$('#removeSwitch').on('change', (e)=> {
|
||||||
|
checkbox_state = $('input[name=auto_remove_user]').is(':checked');
|
||||||
|
if(checkbox_state == false){
|
||||||
|
$('.custom-control-label').text('Off');
|
||||||
|
} else {
|
||||||
|
$('.custom-control-label').text('On');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{% endblock script %}
|
||||||
131
app/app/templates/index.html
Normal file
131
app/app/templates/index.html
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
{%extends 'layout.html'%}
|
||||||
|
{% block content %}
|
||||||
|
<div class="header mt-5">
|
||||||
|
<img src="{{ url_for('static', filename='img/logo.svg') }}" class="img" height="45">
|
||||||
|
<h3>Invitarr Configuration</h3>
|
||||||
|
</div>
|
||||||
|
<nav>
|
||||||
|
<ul class="nav nav-tabs">
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link active" href="#">General</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link " href="{{ url_for('bot') }}">Bot</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="{{ url_for('plex') }}">Plex</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item ml-auto">
|
||||||
|
<a class="nav-link" href="{{ url_for('logout') }}">Logout</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
<div class="form-container">
|
||||||
|
<!-- Form Section -->
|
||||||
|
<form method="POST" action="">
|
||||||
|
{{ form.hidden_tag() }}
|
||||||
|
<div class="form-group mb-3">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-3">
|
||||||
|
{{ form.username.label(class="form-control-label")}}
|
||||||
|
</div>
|
||||||
|
<div class="col-md-1"></div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
{% if form.username.errors %}
|
||||||
|
{{ form.username(class="form-control form-stringfield")}}
|
||||||
|
<small id="emailHelp" class="form-text text-muted">Some text describing this field</small>
|
||||||
|
<div class="invalid-feedback">
|
||||||
|
{% for error in form.username.errors %}
|
||||||
|
<span>{{ error }}</span>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
{{ form.username(class="form-control form-stringfield")}}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group my-3">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-3">
|
||||||
|
{{ form.password.label(class="form-control-label")}}
|
||||||
|
</div>
|
||||||
|
<div class="col-md-1"></div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
{% if form.password.errors %}
|
||||||
|
{{ form.password(class="form-control form-stringfield")}}
|
||||||
|
<div class="invalid-feedback">
|
||||||
|
{% for error in form.password.errors %}
|
||||||
|
<span>{{ error }}</span>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
{{ form.password(class="form-control form-stringfield")}}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group my-5 submit-btn">
|
||||||
|
{{ form.submit(class="btn btn-primary submit-btn") }}
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div id="myModal" class="modal fade" role="dialog">
|
||||||
|
<div class="modal-dialog modal-sm">
|
||||||
|
|
||||||
|
<!-- Modal content-->
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
{% with messages = get_flashed_messages() %}
|
||||||
|
{% if messages %}
|
||||||
|
<h4>
|
||||||
|
{% for message in messages %}
|
||||||
|
{{ message }}
|
||||||
|
{% endfor %}
|
||||||
|
</h4>
|
||||||
|
{% endif %}
|
||||||
|
{% endwith %}
|
||||||
|
<button type="button" class="close" data-dismiss="modal">×</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock content %}
|
||||||
|
|
||||||
|
{% block script %}
|
||||||
|
<script>
|
||||||
|
$(document).ready(()=> {
|
||||||
|
|
||||||
|
// Trigger flash modal
|
||||||
|
let messages = "{{ get_flashed_messages() }}";
|
||||||
|
|
||||||
|
if (typeof messages != 'undefined' && messages != '[]') {
|
||||||
|
$("#myModal").modal();
|
||||||
|
};
|
||||||
|
|
||||||
|
// Adding Validation
|
||||||
|
number_validaitons = ['role_id', 'channel_id', 'owner_id']
|
||||||
|
number_validaitons.forEach(element => {
|
||||||
|
$('#' + element).keyup(function () {
|
||||||
|
this.value = this.value.replace(/[^0-9\.]/g,'');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
let checkbox_state = $('input[name=auto_remove_user]').is(':checked');
|
||||||
|
|
||||||
|
if(checkbox_state == false){
|
||||||
|
$('.custom-control-label').text('Off');
|
||||||
|
}
|
||||||
|
|
||||||
|
$('#removeSwitch').on('change', (e)=> {
|
||||||
|
checkbox_state = $('input[name=auto_remove_user]').is(':checked');
|
||||||
|
if(checkbox_state == false){
|
||||||
|
$('.custom-control-label').text('Off');
|
||||||
|
} else {
|
||||||
|
$('.custom-control-label').text('On');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{% endblock script %}
|
||||||
42
app/app/templates/layout.html
Normal file
42
app/app/templates/layout.html
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<!-- Required meta tags -->
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||||
|
|
||||||
|
<link rel="shortcut icon" href="{{ url_for('static', filename='favicon.ico') }}">
|
||||||
|
|
||||||
|
<!-- Bootstrap CSS -->
|
||||||
|
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="{{ url_for('static', filename='main.css') }}">
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/select2@4.1.0-beta.1/dist/css/select2.min.css" rel="stylesheet" />
|
||||||
|
{% if title %}
|
||||||
|
<title>{{ title }} - Invitarr</title>
|
||||||
|
{% else %}
|
||||||
|
<title>Invitarr</title>
|
||||||
|
{% endif %}
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
{% block content %}{% endblock %}
|
||||||
|
</div>
|
||||||
|
<!-- Optional JavaScript -->
|
||||||
|
<!-- jQuery first, then Popper.js, then Bootstrap JS -->
|
||||||
|
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script>
|
||||||
|
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/select2@4.1.0-beta.1/dist/js/select2.min.js"></script>
|
||||||
|
{% block script %}{% endblock %}
|
||||||
|
<script>
|
||||||
|
$( document ).ready(function() {
|
||||||
|
$(".topbar-quicklinks").hide();
|
||||||
|
$(".topbar-toggle").click(() => {
|
||||||
|
$(".topbar-quicklinks").toggle();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
||||||
105
app/app/templates/login.html
Normal file
105
app/app/templates/login.html
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
{%extends 'layout.html'%}
|
||||||
|
{% block content %}
|
||||||
|
<div class="login">
|
||||||
|
<img src="{{url_for('static', filename='img/logo.svg')}}" class="img" height="120">
|
||||||
|
<h2 class="mb-5">Invitarr</h2>
|
||||||
|
<form method="POST" action="">
|
||||||
|
{{ form.hidden_tag() }}
|
||||||
|
<div class="form-group mb-3">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-4 offset-4">
|
||||||
|
{% if form.username.errors %}
|
||||||
|
{{ form.username(class="form-control form-stringfield", placeholder="Username*")}}
|
||||||
|
<small id="emailHelp" class="form-text text-muted">Some text describing this field</small>
|
||||||
|
<div class="invalid-feedback">
|
||||||
|
{% for error in form.username.errors %}
|
||||||
|
<span>{{ error }}</span>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
{{ form.username(class="form-control form-stringfield", placeholder="Username*")}}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group my-3">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-4 offset-4">
|
||||||
|
{% if form.password.errors %}
|
||||||
|
{{ form.password(class="form-control form-stringfield", placeholder="Password*")}}
|
||||||
|
<div class="invalid-feedback">
|
||||||
|
{% for error in form.password.errors %}
|
||||||
|
<span>{{ error }}</span>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
{{ form.password(class="form-control form-stringfield", placeholder="Password*")}}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group my-5 submit-btn">
|
||||||
|
{{ form.submit(class="btn btn-primary submit-btn") }}
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div id="myModal" class="modal fade" role="dialog">
|
||||||
|
<div class="modal-dialog modal-sm">
|
||||||
|
|
||||||
|
<!-- Modal content-->
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
{% with messages = get_flashed_messages() %}
|
||||||
|
{% if messages %}
|
||||||
|
<h4>
|
||||||
|
{% for message in messages %}
|
||||||
|
{{ message }}
|
||||||
|
{% endfor %}
|
||||||
|
</h4>
|
||||||
|
{% endif %}
|
||||||
|
{% endwith %}
|
||||||
|
<button type="button" class="close" data-dismiss="modal">×</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock content %}
|
||||||
|
|
||||||
|
|
||||||
|
{% block script %}
|
||||||
|
<script>
|
||||||
|
$(document).ready(()=> {
|
||||||
|
|
||||||
|
// Trigger flash modal
|
||||||
|
let messages = "{{ get_flashed_messages() }}";
|
||||||
|
|
||||||
|
if (typeof messages != 'undefined' && messages != '[]') {
|
||||||
|
$("#myModal").modal();
|
||||||
|
};
|
||||||
|
|
||||||
|
// Adding Validation
|
||||||
|
number_validaitons = ['role_id', 'channel_id', 'owner_id']
|
||||||
|
number_validaitons.forEach(element => {
|
||||||
|
$('#' + element).keyup(function () {
|
||||||
|
this.value = this.value.replace(/[^0-9\.]/g,'');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
let checkbox_state = $('input[name=auto_remove_user]').is(':checked');
|
||||||
|
|
||||||
|
if(checkbox_state == false){
|
||||||
|
$('.custom-control-label').text('Off');
|
||||||
|
}
|
||||||
|
|
||||||
|
$('#removeSwitch').on('change', (e)=> {
|
||||||
|
checkbox_state = $('input[name=auto_remove_user]').is(':checked');
|
||||||
|
if(checkbox_state == false){
|
||||||
|
$('.custom-control-label').text('Off');
|
||||||
|
} else {
|
||||||
|
$('.custom-control-label').text('On');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{% endblock script %}
|
||||||
155
app/app/templates/plex.html
Normal file
155
app/app/templates/plex.html
Normal file
@@ -0,0 +1,155 @@
|
|||||||
|
{%extends 'layout.html'%}
|
||||||
|
{% block content %}
|
||||||
|
<div class="header mt-5">
|
||||||
|
<img src="{{ url_for('static', filename='img/logo.svg') }}" class="img" height="45">
|
||||||
|
<h3>Invitarr Configuration</h3>
|
||||||
|
</div>
|
||||||
|
<nav>
|
||||||
|
<ul class="nav nav-tabs">
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="{{ url_for('home') }}">General</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="{{ url_for('bot') }}">Bot</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link active" href="#">Plex</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item ml-auto">
|
||||||
|
<a class="nav-link" href="{{ url_for('logout') }}">Logout</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
<div class="form-container">
|
||||||
|
<!-- Form Section -->
|
||||||
|
<form method="POST" action="">
|
||||||
|
{{ form.hidden_tag() }}
|
||||||
|
<div class="form-group mb-3">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-3">
|
||||||
|
{{ form.plex_user.label(class="form-control-label")}}
|
||||||
|
</div>
|
||||||
|
<div class="col-md-1"></div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
{% if form.plex_user.errors %}
|
||||||
|
{{ form.plex_user(class="form-control form-stringfield")}}
|
||||||
|
<small id="emailHelp" class="form-text text-muted">Some text describing this field</small>
|
||||||
|
<div class="invalid-feedback">
|
||||||
|
{% for error in form.plex_user.errors %}
|
||||||
|
<span>{{ error }}</span>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
{{ form.plex_user(class="form-control form-stringfield")}}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group my-3">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-3">
|
||||||
|
{{ form.plex_pass.label(class="form-control-label")}}
|
||||||
|
</div>
|
||||||
|
<div class="col-md-1"></div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
{% if form.plex_pass.errors %}
|
||||||
|
{{ form.plex_pass(class="form-control form-stringfield")}}
|
||||||
|
<div class="invalid-feedback">
|
||||||
|
{% for error in form.plex_pass.errors %}
|
||||||
|
<span>{{ error }}</span>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
{{ form.plex_pass(class="form-control form-stringfield")}}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group my-3">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-3">
|
||||||
|
{{ form.plex_server_name.label(class="form-control-label")}}
|
||||||
|
</div>
|
||||||
|
<div class="col-md-1"></div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
{% if form.plex_server_name.errors %}
|
||||||
|
{{ form.plex_server_name(class="form-control form-stringfield")}}
|
||||||
|
<div class="invalid-feedback">
|
||||||
|
{% for error in form.plex_server_name.errors %}
|
||||||
|
<span>{{ error }}</span>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
{{ form.plex_server_name(class="form-control form-stringfield")}}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group my-3">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-3">
|
||||||
|
{{ form.plex_libs.label(class="form-control-label")}}
|
||||||
|
</div>
|
||||||
|
<div class="col-md-1"></div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
{% if form.plex_libs.errors %}
|
||||||
|
{{ form.plex_libs(class="form-control form-stringfield")}}
|
||||||
|
<div class="invalid-feedback">
|
||||||
|
{% for error in form.plex_libs.errors %}
|
||||||
|
<span>{{ error }}</span>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
{{ form.plex_libs(class="form-control form-stringfield")}}
|
||||||
|
{% endif %}
|
||||||
|
<small id="emailHelp" class="form-text text-muted">Enter comma seperated values</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group my-5 submit-btn">
|
||||||
|
{{ form.submit(class="btn btn-primary submit-btn") }}
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div id="myModal" class="modal fade" role="dialog">
|
||||||
|
<div class="modal-dialog modal-sm">
|
||||||
|
|
||||||
|
<!-- Modal content-->
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
{% with messages = get_flashed_messages() %}
|
||||||
|
{% if messages %}
|
||||||
|
<h4>
|
||||||
|
{% for message in messages %}
|
||||||
|
{{ message }}
|
||||||
|
{% endfor %}
|
||||||
|
</h4>
|
||||||
|
{% endif %}
|
||||||
|
{% endwith %}
|
||||||
|
<button type="button" class="close" data-dismiss="modal">×</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock content %}
|
||||||
|
|
||||||
|
{% block script %}
|
||||||
|
<script>
|
||||||
|
$(document).ready(()=> {
|
||||||
|
|
||||||
|
// Trigger flash modal
|
||||||
|
let messages = "{{ get_flashed_messages() }}";
|
||||||
|
|
||||||
|
if (typeof messages != 'undefined' && messages != '[]') {
|
||||||
|
$("#myModal").modal();
|
||||||
|
};
|
||||||
|
|
||||||
|
$(".plex-libs").select2({
|
||||||
|
theme: "classic",
|
||||||
|
tags: true,
|
||||||
|
tokenSeparators: [',', ' ']
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{% endblock script %}
|
||||||
33
app/requirements.txt
Normal file
33
app/requirements.txt
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
aiohttp==3.6.2
|
||||||
|
async-timeout==3.0.1
|
||||||
|
attrs==19.3.0
|
||||||
|
bcrypt==3.1.7
|
||||||
|
certifi==2020.4.5.2
|
||||||
|
cffi==1.14.0
|
||||||
|
chardet==3.0.4
|
||||||
|
click==7.1.2
|
||||||
|
discord.py==1.3.3
|
||||||
|
Flask==1.1.2
|
||||||
|
Flask-Bcrypt==0.7.1
|
||||||
|
Flask-Login==0.5.0
|
||||||
|
Flask-SQLAlchemy==2.4.3
|
||||||
|
Flask-WTF==0.14.3
|
||||||
|
idna==2.9
|
||||||
|
idna-ssl==1.1.0
|
||||||
|
itsdangerous==1.1.0
|
||||||
|
Jinja2==2.11.2
|
||||||
|
MarkupSafe==1.1.1
|
||||||
|
multidict==4.7.6
|
||||||
|
plex.py==0.9.0
|
||||||
|
PlexAPI==4.0.0
|
||||||
|
pycparser==2.20
|
||||||
|
python-dotenv==0.13.0
|
||||||
|
requests==2.23.0
|
||||||
|
six==1.15.0
|
||||||
|
SQLAlchemy==1.3.17
|
||||||
|
typing-extensions==3.7.4.2
|
||||||
|
urllib3==1.25.9
|
||||||
|
websockets==8.1
|
||||||
|
Werkzeug==1.0.1
|
||||||
|
WTForms==2.3.1
|
||||||
|
yarl==1.4.2
|
||||||
17
app/run.py
Normal file
17
app/run.py
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
from app import app
|
||||||
|
from app import db, bcrypt
|
||||||
|
from app.models import User
|
||||||
|
from os import path
|
||||||
|
'''
|
||||||
|
if path.exists("app.config.app.db") is False:
|
||||||
|
db.create_all()
|
||||||
|
try:
|
||||||
|
hashed_password = bcrypt.generate_password_hash(DEFAULT_PASS).decode('utf-8')
|
||||||
|
user = User(username=DEFAULT_USER, password=hashed_password)
|
||||||
|
db.session.add(user)
|
||||||
|
db.session.commit()
|
||||||
|
except:
|
||||||
|
print("Some error in setting up.")
|
||||||
|
'''
|
||||||
|
if __name__ == "__main__":
|
||||||
|
app.run(debug=True)
|
||||||
14
app/setup.py
Normal file
14
app/setup.py
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
from app import db, bcrypt
|
||||||
|
from app.models import User
|
||||||
|
|
||||||
|
DEFAULT_USER = "admin"
|
||||||
|
DEFAULT_PASS = "admin"
|
||||||
|
db.create_all()
|
||||||
|
|
||||||
|
try:
|
||||||
|
hashed_password = bcrypt.generate_password_hash(DEFAULT_PASS).decode('utf-8')
|
||||||
|
user = User(username=DEFAULT_USER, password=hashed_password)
|
||||||
|
db.session.add(user)
|
||||||
|
db.session.commit()
|
||||||
|
except:
|
||||||
|
print("Some error in setting up.")
|
||||||
4
app/uwsgi.ini
Normal file
4
app/uwsgi.ini
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
[uwsgi]
|
||||||
|
module = run
|
||||||
|
callable = app
|
||||||
|
enable-threads = true
|
||||||
Reference in New Issue
Block a user