jellyfin-accounts/jf-accounts
Harvey Tindall e8ad3f98d6 added option to send invite to an address
The admin page now has the option to send an invite to an email address.
Since there are now two email types (invites and pw resets), the new
sections have been added to config.ini, and email_template and
email_plaintext have been renamed to email_html and email_text
respectively.
2020-04-19 22:35:51 +01:00

164 lines
5.7 KiB
Python
Executable File

#!/usr/bin/env python3
import secrets
import configparser
import shutil
import argparse
import logging
import threading
import signal
import sys
from pathlib import Path
from flask import Flask, g
parser = argparse.ArgumentParser(description="jellyfin-accounts")
parser.add_argument("-c", "--config",
help="specifies path to configuration file.")
parser.add_argument("-d", "--data",
help=("specifies directory to store data in. " +
"defaults to ~/.jf-accounts."))
parser.add_argument("--host",
help="address to host web ui on.")
parser.add_argument("-p", "--port",
help="port to host web ui on.")
parser.add_argument("-g", "--get_policy",
help=("tool to grab a JF users " +
"policy (access, perms, etc.) and " +
"output as json to be used as a user template."),
action='store_true')
args, leftovers = parser.parse_known_args()
if args.data is not None:
data_dir = Path(args.data)
else:
data_dir = Path.home() / '.jf-accounts'
local_dir = (Path(__file__).parents[2] / 'data').resolve()
if not data_dir.exists():
Path.mkdir(data_dir)
print(f'Config dir not found, so created at {str(data_dir)}')
if args.config is None:
config_path = data_dir / 'config.ini'
shutil.copy(str(local_dir / 'config-default.ini'),
str(config_path))
print("Edit the configuration and restart.")
raise SystemExit
else:
config_path = Path(args.config)
print(f'config.ini can be found at {str(config_path)}')
else:
config_path = data_dir / 'config.ini'
config = configparser.RawConfigParser()
config.read(config_path)
def create_log(name):
log = logging.getLogger(name)
handler = logging.StreamHandler(sys.stdout)
if config.getboolean('ui', 'debug'):
log.setLevel(logging.DEBUG)
handler.setLevel(logging.DEBUG)
else:
log.setLevel(logging.INFO)
handler.setLevel(logging.INFO)
fmt = ' %(name)s - %(levelname)s - %(message)s'
format = logging.Formatter(fmt)
handler.setFormatter(format)
log.addHandler(handler)
log.propagate = False
return log
log = create_log('main')
email_log = create_log('emails')
web_log = create_log('waitress')
auth_log = create_log('auth')
if args.host is not None:
log.debug(f'Using specified host {args.host}')
config['ui']['host'] = args.host
if args.port is not None:
log.debug(f'Using specified port {args.port}')
config['ui']['port'] = args.port
for key in config['files']:
if config['files'][key] == '':
log.debug(f'Using default {key}')
config['files'][key] = str(data_dir / (key + '.json'))
if ('email_html' not in config['password_resets'] or
config['password_resets']['email_html'] == ''):
log.debug('Using default password reset email HTML template')
config['password_resets']['email_html'] = str(local_dir / 'email.html')
if ('email_text' not in config['password_resets'] or
config['password_resets']['email_text'] == ''):
log.debug('Using default password reset email plaintext template')
config['password_resets']['email_text'] = str(local_dir / 'email.txt')
if ('email_html' not in config['invite_emails'] or
config['invite_emails']['email_html'] == ''):
log.debug('Using default invite email HTML template')
config['invite_emails']['email_html'] = str(local_dir /
'invite-email.html')
if ('email_text' not in config['invite_emails'] or
config['invite_emails']['email_text'] == ''):
log.debug('Using default invite email plaintext template')
config['invite_emails']['email_text'] = str(local_dir /
'invite-email.txt')
if args.get_policy:
import json
from jellyfin_accounts.jf_api import Jellyfin
jf = Jellyfin(config['jellyfin']['server'],
config['jellyfin']['client'],
config['jellyfin']['version'],
config['jellyfin']['device'],
config['jellyfin']['device_id'])
# No auth needed.
print("Make sure the user is publicly visible!")
users = jf.getUsers()
for index, user in enumerate(users):
print(f'{index+1}) {user["Name"]}')
success = False
while not success:
try:
policy = users[int(input(">: "))-1]['Policy']
success = True
except (ValueError, IndexError):
pass
with open(config['files']['user_template'], 'w') as f:
f.write(json.dumps(policy, indent=4))
print(f'Policy written to "{config["files"]["user_template"]}".')
print('In future, this policy will be copied to all new users.')
else:
def signal_handler(sig, frame):
print('Quitting...')
sys.exit(0)
signal.signal(signal.SIGINT, signal_handler)
signal.signal(signal.SIGTERM, signal_handler)
app = Flask(__name__, root_path=str(local_dir))
app.config['DEBUG'] = config.getboolean('ui', 'debug')
app.config['SECRET_KEY'] = secrets.token_urlsafe(16)
if __name__ == '__main__':
import jellyfin_accounts.web_api
import jellyfin_accounts.web
from waitress import serve
host = config['ui']['host']
port = config['ui']['port']
log.info(f'Starting web UI on {host}:{port}')
if config.getboolean('password_resets', 'enabled'):
def start_pwr():
import jellyfin_accounts.pw_reset
jellyfin_accounts.pw_reset.start()
pwr = threading.Thread(target=start_pwr, daemon=True)
log.info('Starting email thread')
pwr.start()
serve(app,
host=host,
port=int(port))