Add files via upload
This commit is contained in:
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