10 Commits

Author SHA1 Message Date
6ec214f644 Move og readme to separate file
officially deprecated now i guess.
2020-09-14 12:35:56 +01:00
a4feaa5d64 add deprecation notice to program 2020-08-31 14:13:53 +01:00
237c575441 make jfa-go mention bigger
you should try it out.
2020-08-18 14:34:26 +01:00
9185b59d16 Dockerfile builds from source
Uses multi-stage build, like jfa-go. I'm hoping this'll shrink the image
aswell.
2020-08-17 12:07:28 +01:00
43c0631f9b add non interactive modes to build scripts 2020-08-17 11:45:42 +01:00
3d10a8fe06 fix email generation on windows 2020-08-04 18:14:39 +01:00
8d265879cc actually fixed windows scss build 2020-08-04 18:12:18 +01:00
a38045cefb potential fixes for windows
hopefully fixes scss and email generation on windows by fixing runcmd()
and (optionally) reading npm bin location from the 'npm bin' command.
also, config path is cast to string before being passed to configparser.
2020-08-04 01:29:29 +01:00
d5609f3870 update readme, fix setup.js setting toggle 2020-08-02 02:33:34 +01:00
f2966ef810 Fix new user defaults and bump to 0.3.9 bcs im dumb
Pushed 0.3.8 to PyPI and Docker hub thinking i'd already fixed a problem
with new user defaults. I hadn't.
2020-07-24 12:04:57 +01:00
15 changed files with 948 additions and 940 deletions

27
Dockerfile Normal file
View File

@@ -0,0 +1,27 @@
FROM python:3.8.2-buster AS build
COPY . /opt/build
RUN curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py | python
RUN curl -sL https://deb.nodesource.com/setup_14.x | bash -
RUN cd /opt/build \
&& rm -rf dist \
&& apt install nodejs \
&& ~/.poetry/bin/poetry update \
&& pip install libsass \
&& python scss/get_node_deps.py \
&& python scss/compile.py -y \
&& python mail/generate.py -y \
&& ~/.poetry/bin/poetry build -f wheel
FROM python:3.8.2-buster
COPY --from=build /opt/build/dist /opt/dist
RUN pip install /opt/dist/*.whl
RUN sed -i 's#id="pwrJfPath" placeholder="Folder"#id="pwrJfPath" value="/jf" disabled#g' /usr/local/lib/python3.8/site-packages/jellyfin_accounts/data/templates/setup.html
CMD [ "python3.8", "/usr/local/bin/jf-accounts", "-d", "/data" ]

View File

@@ -1,90 +1,4 @@
# ![jellyfin-accounts](https://raw.githubusercontent.com/hrfee/jellyfin-accounts/bs5/images/jellyfin-accounts-banner-wide.svg)
## 👀 ➡️: Try [jfa-go](https://github.com/hrfee/jfa-go), a rewrite in Go. It's faster and has more features.
###### I won't be updating this version any more, and switching should be easy.
A basic account management system for [Jellyfin](https://github.com/jellyfin/jellyfin).
* Provides a web interface for creating/sending invites
* Sends out emails when a user requests a password reset
* Uses a basic python jellyfin API client for communication with the server.
* Uses [Flask](https://github.com/pallets/flask), [HTTPAuth](https://github.com/miguelgrinberg/Flask-HTTPAuth), [itsdangerous](https://github.com/pallets/itsdangerous), and [Waitress](https://github.com/Pylons/waitress)
* Frontend uses [Bootstrap](https://v5.getbootstrap.com)
* Password resets are handled using smtplib, requests, and [jinja](https://github.com/pallets/jinja)
## Interface
<p align="center">
<img src="https://raw.githubusercontent.com/hrfee/jellyfin-accounts/main/images/jfa.gif" width="100%"></img>
</p>
<p align="center">
<img src="https://raw.githubusercontent.com/hrfee/jellyfin-accounts/main/images/admin.png" width="48%" style="margin-right: 1.5%;" alt="Admin page"></img>
<img src="https://raw.githubusercontent.com/hrfee/jellyfin-accounts/main/images/create.png" width="48%" style="margin-left: 1.5%;" alt="Account creation page"></img>
</p>
## Get it
### Requirements
* This should work anywhere Python does, i've tried to not use anything OS-specific. Drop an issue if there's a problem, of course.
```
* python >= 3.6
* flask
* flask_httpauth
* jinja2
* requests
* itsdangerous
* passlib
* pyOpenSSL
* waitress
* pytz
* python-dateutil
* watchdog
* packaging
```
### Install
Usually as simple as:
```
pip install jellyfin-accounts
```
If not, or if you want to use docker, see [install](https://github.com/hrfee/jellyfin-accounts/wiki/Install).
## Usage
* Passing no arguments will run the server
```
usage: jf-accounts [-h] [-c CONFIG] [-d DATA] [--host HOST] [-p PORT] [-g]
jellyfin-accounts
optional arguments:
-h, --help show this help message and exit
-c CONFIG, --config CONFIG
specifies path to configuration file.
-d DATA, --data DATA specifies directory to store data in. defaults to
~/.jf-accounts.
--host HOST address to host web ui on.
-p PORT, --port PORT port to host web ui on.
-g, --get_defaults tool to grab a JF users policy (access, perms, etc.)
and homescreen layout and output it as json to be used
as a user template.
```
## Setup
#### New user template
* You may want to restrict a user from accessing certain libraries (e.g 4K Movies), display their account on the login screen by default, or set a default homecrseen layout. Jellyfin stores these settings in the user's policy, configuration and displayPreferences.
* Make a temporary account and configure it, then in the web UI, go into "Settings => Set new account defaults". Choose the account, and its configuration will be stored for future use.
#### Emails/Password Resets
* When someone initiates forget password on Jellyfin, a file named `passwordreset*.json` is created in its configuration directory. This directory is monitored and when created, the program reads the username, expiry time and PIN, puts it into a template and sends it to whatever address is specified in `emails.json`.
* **The default forget password popup references the `passwordreset*.json` file created. This is confusing for users, so a quick fix is to edit the `MessageForgotPasswordFileCreated` string in Jellyfin's language folder.**
* Currently, jellyfin-accounts supports generic SSL/TLS or STARTTLS secured SMTP, and the [mailgun](https://mailgun.com) REST API.
* Email html is created using [mjml](https://mjml.io), and [jinja](https://github.com/pallets/jinja) templating is used. If you wish to create your own, ensure you use the same jinja expressions (`{{ pin }}`, etc.) as used in `data/email.mjml` or `invite-email.mjml`, and also create plain text versions for legacy email clients.
### Configuration
* Note: Make sure to put this behind a reverse proxy with HTTPS.
On first run, access the setup wizard at `0.0.0.0:8056`. When finished, restart the program.
The configuration is stored at `~/.jf-accounts/config.ini`. Settings can be changed through the web UI, or by manually editing the file.
For detailed descriptions of each setting, see [setup](https://github.com/hrfee/jellyfin-accounts/wiki/Setup).
### Donations
I strongly suggest you send your money to [Jellyfin](https://opencollective.com/jellyfin) or a good charity, but for those who want to help me out, a Paypal link is below.
[Donate](https://www.paypal.me/hrfee)
You can find the old README [here](https://github.com/hrfee/jellyfin-accounts/blob/main/README.old.md).

91
README.old.md Normal file
View File

@@ -0,0 +1,91 @@
# ![jellyfin-accounts](https://raw.githubusercontent.com/hrfee/jellyfin-accounts/bs5/images/jellyfin-accounts-banner-wide.svg)
A basic account management system for [Jellyfin](https://github.com/jellyfin/jellyfin).
* Provides a web interface for creating/sending invites
* Sends out emails when a user requests a password reset
* Uses a basic python jellyfin API client for communication with the server.
* Uses [Flask](https://github.com/pallets/flask), [HTTPAuth](https://github.com/miguelgrinberg/Flask-HTTPAuth), [itsdangerous](https://github.com/pallets/itsdangerous), and [Waitress](https://github.com/Pylons/waitress)
* Frontend uses [Bootstrap](https://v5.getbootstrap.com)
* Password resets are handled using smtplib, requests, and [jinja](https://github.com/pallets/jinja)
## Interface
<p align="center">
<img src="https://raw.githubusercontent.com/hrfee/jellyfin-accounts/main/images/jfa.gif" width="100%"></img>
</p>
<p align="center">
<img src="https://raw.githubusercontent.com/hrfee/jellyfin-accounts/main/images/admin.png" width="48%" style="margin-right: 1.5%;" alt="Admin page"></img>
<img src="https://raw.githubusercontent.com/hrfee/jellyfin-accounts/main/images/create.png" width="48%" style="margin-left: 1.5%;" alt="Account creation page"></img>
</p>
## Get it
### Requirements
* This should work anywhere Python does, i've tried to not use anything OS-specific. Drop an issue if there's a problem, of course.
```
* python >= 3.6
* flask
* flask_httpauth
* jinja2
* requests
* itsdangerous
* passlib
* pyOpenSSL
* waitress
* pytz
* python-dateutil
* watchdog
* packaging
```
### Install
Usually as simple as:
```
pip install jellyfin-accounts
```
If not, or if you want to use docker, see [install](https://github.com/hrfee/jellyfin-accounts/wiki/Install).
## Usage
* Passing no arguments will run the server
```
usage: jf-accounts [-h] [-c CONFIG] [-d DATA] [--host HOST] [-p PORT] [-g]
jellyfin-accounts
optional arguments:
-h, --help show this help message and exit
-c CONFIG, --config CONFIG
specifies path to configuration file.
-d DATA, --data DATA specifies directory to store data in. defaults to
~/.jf-accounts.
--host HOST address to host web ui on.
-p PORT, --port PORT port to host web ui on.
-g, --get_defaults tool to grab a JF users policy (access, perms, etc.)
and homescreen layout and output it as json to be used
as a user template.
```
## Setup
#### New user template
* You may want to restrict a user from accessing certain libraries (e.g 4K Movies), display their account on the login screen by default, or set a default homecrseen layout. Jellyfin stores these settings in the user's policy, configuration and displayPreferences.
* Make a temporary account and configure it, then in the web UI, go into "Settings => Set new account defaults". Choose the account, and its configuration will be stored for future use.
#### Emails/Password Resets
* When someone initiates forget password on Jellyfin, a file named `passwordreset*.json` is created in its configuration directory. This directory is monitored and when created, the program reads the username, expiry time and PIN, puts it into a template and sends it to whatever address is specified in `emails.json`.
* **The default forget password popup references the `passwordreset*.json` file created. This is confusing for users, so a quick fix is to edit the `MessageForgotPasswordFileCreated` string in Jellyfin's language folder.**
* Currently, jellyfin-accounts supports generic SSL/TLS or STARTTLS secured SMTP, and the [mailgun](https://mailgun.com) REST API.
* Email html is created using [mjml](https://mjml.io), and [jinja](https://github.com/pallets/jinja) templating is used. If you wish to create your own, ensure you use the same jinja expressions (`{{ pin }}`, etc.) as used in `data/email.mjml` or `invite-email.mjml`, and also create plain text versions for legacy email clients.
### Configuration
* Note: Make sure to put this behind a reverse proxy with HTTPS.
On first run, access the setup wizard at `0.0.0.0:8056`. When finished, restart the program.
The configuration is stored at `~/.jf-accounts/config.ini`. Settings can be changed through the web UI, or by manually editing the file.
For detailed descriptions of each setting, see [setup](https://github.com/hrfee/jellyfin-accounts/wiki/Setup).
### Donations
I strongly suggest you send your money to [Jellyfin](https://opencollective.com/jellyfin) or a good charity, but for those who want to help me out, a Paypal link is below.
[Donate](https://www.paypal.me/hrfee)

View File

@@ -1,5 +1,7 @@
# Runs it!
__version__ = "0.3.7"
__version__ = "0.3.9"
print("Note: jellyfin-accounts will likely not receive many future updates. Try jfa-go, a rewrite thats fast, portable, and has more features. Find it at\nhttps://github.com/hrfee/jfa-go\n")
import secrets
import configparser
@@ -72,7 +74,7 @@ else:
# Temp config so logger knows whether to use debug mode or not
temp_config = configparser.RawConfigParser()
temp_config.read(config_path)
temp_config.read(str(config_path.resolve()))
def create_log(name):
@@ -98,7 +100,8 @@ config = Config(config_path, secrets.token_urlsafe(16), data_dir, local_dir, log
web_log = create_log("waitress")
if not first_run:
email_log = create_log("emails")
email_log = create_log("email")
pwr_log = create_log("pwr")
auth_log = create_log("auth")
if args.host is not None:
@@ -185,6 +188,7 @@ def resp(success=True, code=500):
r.status_code = code
return r
app = Flask(__name__, root_path=str(local_dir))
def main():
if args.install:
@@ -290,8 +294,6 @@ def main():
success = True
else:
global app
app = Flask(__name__, root_path=str(local_dir))
app.config["DEBUG"] = config.getboolean("ui", "debug")
app.config["SECRET_KEY"] = secrets.token_urlsafe(16)
app.config["JSON_SORT_KEYS"] = False
@@ -328,7 +330,7 @@ def main():
jellyfin_accounts.pw_reset.start()
pwr = threading.Thread(target=start_pwr, daemon=True)
log.info("Starting email thread")
log.info("Starting password reset thread")
pwr.start()
def signal_handler(sig, frame):

View File

@@ -21,16 +21,19 @@ function checkEmailRadio() {
document.getElementById('emailCommonArea').style.display = '';
document.getElementById('emailSMTPArea').style.display = '';
document.getElementById('emailMailgunArea').style.display = 'none';
document.getElementById('notificationsEnabled').checked = true;
} else if (document.getElementById('emailMailgunRadio').checked) {
document.getElementById('emailCommonArea').style.display = '';
document.getElementById('emailSMTPArea').style.display = 'none';
document.getElementById('emailMailgunArea').style.display = '';
document.getElementById('notificationsEnabled').checked = true;
} else if (document.getElementById('emailDisabledRadio').checked) {
document.getElementById('emailCommonArea').style.display = 'none';
document.getElementById('emailSMTPArea').style.display = 'none';
document.getElementById('emailMailgunArea').style.display = 'none';
document.getElementById('emailNextButton').href = '#page-8';
document.getElementById('valBackButton').href = '#page-4';
document.getElementById('notificationsEnabled').checked = false;
};
};
var emailRadios = ['emailDisabledRadio', 'emailSMTPRadio', 'emailMailgunRadio'];
@@ -165,6 +168,7 @@ document.getElementById('submitButton').onclick = function() {
if (document.getElementById('emailDisabledRadio').checked) {
config['password_resets']['enabled'] = 'false';
config['invite_emails']['enabled'] = 'false';
config['notificatons']['enabled'] = 'false';
} else {
if (document.getElementById('emailSMTPRadio').checked) {
if (document.getElementById('emailSSL_TLS').checked) {

View File

@@ -651,11 +651,11 @@ document.getElementById('openDefaultsWizard').onclick = function() {
for (user of users) {
let radio = document.createElement('div');
radio.classList.add('radio');
let checked = 'checked';
if (first) {
const checked = 'checked';
first = false;
} else {
const checked = '';
checked = '';
};
radio.innerHTML =
`<label><input type="radio" name="defaultRadios" id="default_${user['name']}" style="margin-right: 1rem;" ${checked}>${user['name']}</label>`;

View File

@@ -61,6 +61,7 @@ class Jellyfin:
self.version = version
self.device = device
self.deviceId = deviceId
self.authenticated = False
self.timeout = cacheMinutes * 60
self.userCacheAge = time.time() - self.timeout - 1
self.userCachePublicAge = self.userCacheAge
@@ -80,10 +81,10 @@ class Jellyfin:
"X-Emby-Authorization": self.auth,
}
try:
self.info = requests.get(self.server + "/System/Info/Public").json()
self.info = requests.get(f"{self.server}/System/Info/Public").json()
except:
pass
def reloadCache(self):
""" Forces a reload of the user caches """
self.userCachePublicAge = time.time() - self.timeout - 1
@@ -107,7 +108,7 @@ class Jellyfin:
"""
if public is True:
if (time.time() - self.userCachePublicAge) >= self.timeout:
response = requests.get(self.server + "/emby/Users/Public").json()
response = requests.get(f"{self.server}/Users/Public").json()
self.userCachePublic = response
self.userCachePublicAge = time.time()
else:
@@ -117,7 +118,7 @@ class Jellyfin:
):
if (time.time() - self.userCacheAge) >= self.timeout:
response = requests.get(
self.server + "/emby/Users",
f"{self.server}/Users",
headers=self.header,
params={"Username": self.username, "Pw": self.password},
)
@@ -125,7 +126,7 @@ class Jellyfin:
response = response.json()
self.userCache = response
self.userCacheAge = time.time()
else:
elif response.status_code == 401:
try:
self.authenticate(self.username, self.password)
return self.getUsers(username, userId, public)
@@ -161,12 +162,10 @@ class Jellyfin:
:param username: Plaintext username.
:param password: Plaintext password.
"""
self.username = username
self.password = password
response = requests.post(
self.server + "/emby/Users/AuthenticateByName",
f"{self.server}/Users/AuthenticateByName",
headers=self.header,
params={"Username": self.username, "Pw": self.password},
params={"Username": username, "Pw": password},
)
if response.status_code == 200:
json = response.json()
@@ -180,8 +179,11 @@ class Jellyfin:
self.auth += f", Token={self.accessToken}"
self.header["X-Emby-Authorization"] = self.auth
self.info = requests.get(
self.server + "/System/Info", headers=self.header
f"{self.server}/System/Info", headers=self.header
).json()
self.username = username
self.password = password
self.authenticated = True
return True
else:
raise self.AuthenticationError
@@ -194,7 +196,7 @@ class Jellyfin:
:param policy: User policy in dictionary form.
"""
return requests.post(
self.server + "/Users/" + userId + "/Policy",
f"{self.server}/Users/" + userId + "/Policy",
headers=self.header,
params=policy,
)
@@ -204,7 +206,7 @@ class Jellyfin:
if user["Name"] == username:
raise self.UserExistsError
response = requests.post(
self.server + "/emby/Users/New",
f"{self.server}/Users/New",
headers=self.header,
params={"Name": username, "Password": password},
)
@@ -222,7 +224,7 @@ class Jellyfin:
else:
param = ""
views = requests.get(
self.server + "/Users/" + userId + "/Views" + param, headers=self.header
f"{self.server}/Users/" + userId + "/Views" + param, headers=self.header
).json()["Items"]
orderedViews = []
for library in views:
@@ -236,7 +238,7 @@ class Jellyfin:
:param configuration: Configuration to write in dictionary form.
"""
resp = requests.post(
self.server + "/Users/" + userId + "/Configuration",
f"{self.server}/Users/" + userId + "/Configuration",
headers=self.header,
params=configuration,
)

View File

@@ -6,7 +6,7 @@ from watchdog.events import FileSystemEventHandler
from jellyfin_accounts.email import Mailgun, Smtp
from jellyfin_accounts.web_api import jf
from jellyfin_accounts import config, data_store
from jellyfin_accounts import email_log as log
from jellyfin_accounts import pwr_log as log
class Watcher:

View File

@@ -441,7 +441,7 @@ def modifyConfig():
temp_config = configparser.RawConfigParser(
comment_prefixes="/", allow_no_value=True
)
temp_config.read(config_path)
temp_config.read(str(config_path.resolve()))
for section in data:
if section in temp_config and 'restart-program' not in section:
for item in data[section]:

View File

@@ -1,13 +1,39 @@
import subprocess
import shutil
import os
import argparse
from pathlib import Path
parser = argparse.ArgumentParser()
parser.add_argument(
"-y", "--yes", help="use assumed node bin directory.", action="store_true"
)
def runcmd(cmd):
if os.name == "nt":
return subprocess.check_output(cmd, shell=True)
proc = subprocess.Popen(cmd.split(), stdout=subprocess.PIPE)
return proc.communicate()
local_path = Path(__file__).resolve().parent
node_bin = local_path.parent / 'node_modules' / '.bin'
out = runcmd("npm bin")
try:
node_bin = Path(out[0].decode('utf-8').rstrip())
except:
node_bin = Path(out.decode('utf-8').rstrip())
args = parser.parse_args()
if not args.yes:
print(f"assuming npm bin directory \"{node_bin}\". Is this correct?")
if input("[yY/nN]: ").lower() == "n":
node_bin = local_path.parent / 'node_modules' / '.bin'
print(f"this? \"{node_bin}\"")
if input("[yY/nN]: ").lower() == "n":
node_bin = input("input bin directory: ")
for mjml in [f for f in local_path.iterdir() if f.is_file() and 'mjml' in f.suffix]:
print(f'Compiling {mjml.name}')

1438
package-lock.json generated

File diff suppressed because it is too large Load Diff

110
poetry.lock generated
View File

@@ -54,7 +54,7 @@ description = "Foreign Function Interface for Python calling C code."
name = "cffi"
optional = false
python-versions = "*"
version = "1.14.0"
version = "1.14.1"
[package.dependencies]
pycparser = "*"
@@ -81,17 +81,18 @@ description = "cryptography is a package which provides cryptographic recipes an
name = "cryptography"
optional = false
python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*"
version = "2.9.2"
version = "3.0"
[package.dependencies]
cffi = ">=1.8,<1.11.3 || >1.11.3"
six = ">=1.4.1"
[package.extras]
docs = ["sphinx (>=1.6.5,<1.8.0 || >1.8.0)", "sphinx-rtd-theme"]
docs = ["sphinx (>=1.6.5,<1.8.0 || >1.8.0,<3.1.0 || >3.1.0,<3.1.1 || >3.1.1)", "sphinx-rtd-theme"]
docstest = ["doc8", "pyenchant (>=1.6.11)", "twine (>=1.12.0)", "sphinxcontrib-spelling (>=4.0.1)"]
idna = ["idna (>=2.1)"]
pep8test = ["flake8", "flake8-import-order", "pep8-naming"]
pep8test = ["black", "flake8", "flake8-import-order", "pep8-naming"]
ssh = ["bcrypt (>=3.1.5)"]
test = ["pytest (>=3.6.0,<3.9.0 || >3.9.0,<3.9.1 || >3.9.1,<3.9.2 || >3.9.2)", "pretend", "iso8601", "pytz", "hypothesis (>=1.11.4,<3.79.2 || >3.79.2)"]
[[package]]
@@ -387,7 +388,7 @@ description = "HTTP library with thread-safe connection pooling, file post, and
name = "urllib3"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4"
version = "1.25.9"
version = "1.25.10"
[package.extras]
brotli = ["brotlipy (>=0.6.0)"]
@@ -434,6 +435,7 @@ watchdog = ["watchdog"]
[metadata]
content-hash = "1c2741c9be187d9d0be662509fb4a87f5978e5f44420e5049a20504824c29a59"
lock-version = "1.0"
python-versions = "^3.6"
[metadata.files]
@@ -454,34 +456,34 @@ certifi = [
{file = "certifi-2020.6.20.tar.gz", hash = "sha256:5930595817496dd21bb8dc35dad090f1c2cd0adfaf21204bf6732ca5d8ee34d3"},
]
cffi = [
{file = "cffi-1.14.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:1cae98a7054b5c9391eb3249b86e0e99ab1e02bb0cc0575da191aedadbdf4384"},
{file = "cffi-1.14.0-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:cf16e3cf6c0a5fdd9bc10c21687e19d29ad1fe863372b5543deaec1039581a30"},
{file = "cffi-1.14.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:f2b0fa0c01d8a0c7483afd9f31d7ecf2d71760ca24499c8697aeb5ca37dc090c"},
{file = "cffi-1.14.0-cp27-cp27m-win32.whl", hash = "sha256:99f748a7e71ff382613b4e1acc0ac83bf7ad167fb3802e35e90d9763daba4d78"},
{file = "cffi-1.14.0-cp27-cp27m-win_amd64.whl", hash = "sha256:c420917b188a5582a56d8b93bdd8e0f6eca08c84ff623a4c16e809152cd35793"},
{file = "cffi-1.14.0-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:399aed636c7d3749bbed55bc907c3288cb43c65c4389964ad5ff849b6370603e"},
{file = "cffi-1.14.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:cab50b8c2250b46fe738c77dbd25ce017d5e6fb35d3407606e7a4180656a5a6a"},
{file = "cffi-1.14.0-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:001bf3242a1bb04d985d63e138230802c6c8d4db3668fb545fb5005ddf5bb5ff"},
{file = "cffi-1.14.0-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:e56c744aa6ff427a607763346e4170629caf7e48ead6921745986db3692f987f"},
{file = "cffi-1.14.0-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:b8c78301cefcf5fd914aad35d3c04c2b21ce8629b5e4f4e45ae6812e461910fa"},
{file = "cffi-1.14.0-cp35-cp35m-win32.whl", hash = "sha256:8c0ffc886aea5df6a1762d0019e9cb05f825d0eec1f520c51be9d198701daee5"},
{file = "cffi-1.14.0-cp35-cp35m-win_amd64.whl", hash = "sha256:8a6c688fefb4e1cd56feb6c511984a6c4f7ec7d2a1ff31a10254f3c817054ae4"},
{file = "cffi-1.14.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:95cd16d3dee553f882540c1ffe331d085c9e629499ceadfbda4d4fde635f4b7d"},
{file = "cffi-1.14.0-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:66e41db66b47d0d8672d8ed2708ba91b2f2524ece3dee48b5dfb36be8c2f21dc"},
{file = "cffi-1.14.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:028a579fc9aed3af38f4892bdcc7390508adabc30c6af4a6e4f611b0c680e6ac"},
{file = "cffi-1.14.0-cp36-cp36m-win32.whl", hash = "sha256:cef128cb4d5e0b3493f058f10ce32365972c554572ff821e175dbc6f8ff6924f"},
{file = "cffi-1.14.0-cp36-cp36m-win_amd64.whl", hash = "sha256:337d448e5a725bba2d8293c48d9353fc68d0e9e4088d62a9571def317797522b"},
{file = "cffi-1.14.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e577934fc5f8779c554639376beeaa5657d54349096ef24abe8c74c5d9c117c3"},
{file = "cffi-1.14.0-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:62ae9af2d069ea2698bf536dcfe1e4eed9090211dbaafeeedf5cb6c41b352f66"},
{file = "cffi-1.14.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:14491a910663bf9f13ddf2bc8f60562d6bc5315c1f09c704937ef17293fb85b0"},
{file = "cffi-1.14.0-cp37-cp37m-win32.whl", hash = "sha256:c43866529f2f06fe0edc6246eb4faa34f03fe88b64a0a9a942561c8e22f4b71f"},
{file = "cffi-1.14.0-cp37-cp37m-win_amd64.whl", hash = "sha256:2089ed025da3919d2e75a4d963d008330c96751127dd6f73c8dc0c65041b4c26"},
{file = "cffi-1.14.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3b911c2dbd4f423b4c4fcca138cadde747abdb20d196c4a48708b8a2d32b16dd"},
{file = "cffi-1.14.0-cp38-cp38-manylinux1_i686.whl", hash = "sha256:7e63cbcf2429a8dbfe48dcc2322d5f2220b77b2e17b7ba023d6166d84655da55"},
{file = "cffi-1.14.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:3d311bcc4a41408cf5854f06ef2c5cab88f9fded37a3b95936c9879c1640d4c2"},
{file = "cffi-1.14.0-cp38-cp38-win32.whl", hash = "sha256:675686925a9fb403edba0114db74e741d8181683dcf216be697d208857e04ca8"},
{file = "cffi-1.14.0-cp38-cp38-win_amd64.whl", hash = "sha256:00789914be39dffba161cfc5be31b55775de5ba2235fe49aa28c148236c4e06b"},
{file = "cffi-1.14.0.tar.gz", hash = "sha256:2d384f4a127a15ba701207f7639d94106693b6cd64173d6c8988e2c25f3ac2b6"},
{file = "cffi-1.14.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:66dd45eb9530e3dde8f7c009f84568bc7cac489b93d04ac86e3111fb46e470c2"},
{file = "cffi-1.14.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:4f53e4128c81ca3212ff4cf097c797ab44646a40b42ec02a891155cd7a2ba4d8"},
{file = "cffi-1.14.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:833401b15de1bb92791d7b6fb353d4af60dc688eaa521bd97203dcd2d124a7c1"},
{file = "cffi-1.14.1-cp27-cp27m-win32.whl", hash = "sha256:26f33e8f6a70c255767e3c3f957ccafc7f1f706b966e110b855bfe944511f1f9"},
{file = "cffi-1.14.1-cp27-cp27m-win_amd64.whl", hash = "sha256:b87dfa9f10a470eee7f24234a37d1d5f51e5f5fa9eeffda7c282e2b8f5162eb1"},
{file = "cffi-1.14.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:effd2ba52cee4ceff1a77f20d2a9f9bf8d50353c854a282b8760ac15b9833168"},
{file = "cffi-1.14.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:bac0d6f7728a9cc3c1e06d4fcbac12aaa70e9379b3025b27ec1226f0e2d404cf"},
{file = "cffi-1.14.1-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:d6033b4ffa34ef70f0b8086fd4c3df4bf801fee485a8a7d4519399818351aa8e"},
{file = "cffi-1.14.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:8416ed88ddc057bab0526d4e4e9f3660f614ac2394b5e019a628cdfff3733849"},
{file = "cffi-1.14.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:892daa86384994fdf4856cb43c93f40cbe80f7f95bb5da94971b39c7f54b3a9c"},
{file = "cffi-1.14.1-cp35-cp35m-win32.whl", hash = "sha256:c991112622baee0ae4d55c008380c32ecfd0ad417bcd0417ba432e6ba7328caa"},
{file = "cffi-1.14.1-cp35-cp35m-win_amd64.whl", hash = "sha256:fcf32bf76dc25e30ed793145a57426064520890d7c02866eb93d3e4abe516948"},
{file = "cffi-1.14.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f960375e9823ae6a07072ff7f8a85954e5a6434f97869f50d0e41649a1c8144f"},
{file = "cffi-1.14.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:a6d28e7f14ecf3b2ad67c4f106841218c8ab12a0683b1528534a6c87d2307af3"},
{file = "cffi-1.14.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:cda422d54ee7905bfc53ee6915ab68fe7b230cacf581110df4272ee10462aadc"},
{file = "cffi-1.14.1-cp36-cp36m-win32.whl", hash = "sha256:4a03416915b82b81af5502459a8a9dd62a3c299b295dcdf470877cb948d655f2"},
{file = "cffi-1.14.1-cp36-cp36m-win_amd64.whl", hash = "sha256:4ce1e995aeecf7cc32380bc11598bfdfa017d592259d5da00fc7ded11e61d022"},
{file = "cffi-1.14.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e23cb7f1d8e0f93addf0cae3c5b6f00324cccb4a7949ee558d7b6ca973ab8ae9"},
{file = "cffi-1.14.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:ddff0b2bd7edcc8c82d1adde6dbbf5e60d57ce985402541cd2985c27f7bec2a0"},
{file = "cffi-1.14.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:f90c2267101010de42f7273c94a1f026e56cbc043f9330acd8a80e64300aba33"},
{file = "cffi-1.14.1-cp37-cp37m-win32.whl", hash = "sha256:3cd2c044517f38d1b577f05927fb9729d3396f1d44d0c659a445599e79519792"},
{file = "cffi-1.14.1-cp37-cp37m-win_amd64.whl", hash = "sha256:4fa72a52a906425416f41738728268072d5acfd48cbe7796af07a923236bcf96"},
{file = "cffi-1.14.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:267adcf6e68d77ba154334a3e4fc921b8e63cbb38ca00d33d40655d4228502bc"},
{file = "cffi-1.14.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:d3148b6ba3923c5850ea197a91a42683f946dba7e8eb82dfa211ab7e708de939"},
{file = "cffi-1.14.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:98be759efdb5e5fa161e46d404f4e0ce388e72fbf7d9baf010aff16689e22abe"},
{file = "cffi-1.14.1-cp38-cp38-win32.whl", hash = "sha256:6923d077d9ae9e8bacbdb1c07ae78405a9306c8fd1af13bfa06ca891095eb995"},
{file = "cffi-1.14.1-cp38-cp38-win_amd64.whl", hash = "sha256:b1d6ebc891607e71fd9da71688fcf332a6630b7f5b7f5549e6e631821c0e5d90"},
{file = "cffi-1.14.1.tar.gz", hash = "sha256:b2a2b0d276a136146e012154baefaea2758ef1f56ae9f4e01c612b0831e0bd2f"},
]
chardet = [
{file = "chardet-3.0.4-py2.py3-none-any.whl", hash = "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"},
@@ -492,25 +494,25 @@ click = [
{file = "click-7.1.2.tar.gz", hash = "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a"},
]
cryptography = [
{file = "cryptography-2.9.2-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:daf54a4b07d67ad437ff239c8a4080cfd1cc7213df57d33c97de7b4738048d5e"},
{file = "cryptography-2.9.2-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:3b3eba865ea2754738616f87292b7f29448aec342a7c720956f8083d252bf28b"},
{file = "cryptography-2.9.2-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:c447cf087cf2dbddc1add6987bbe2f767ed5317adb2d08af940db517dd704365"},
{file = "cryptography-2.9.2-cp27-cp27m-win32.whl", hash = "sha256:f118a95c7480f5be0df8afeb9a11bd199aa20afab7a96bcf20409b411a3a85f0"},
{file = "cryptography-2.9.2-cp27-cp27m-win_amd64.whl", hash = "sha256:c4fd17d92e9d55b84707f4fd09992081ba872d1a0c610c109c18e062e06a2e55"},
{file = "cryptography-2.9.2-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:d0d5aeaedd29be304848f1c5059074a740fa9f6f26b84c5b63e8b29e73dfc270"},
{file = "cryptography-2.9.2-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:1e4014639d3d73fbc5ceff206049c5a9a849cefd106a49fa7aaaa25cc0ce35cf"},
{file = "cryptography-2.9.2-cp35-abi3-macosx_10_9_x86_64.whl", hash = "sha256:96c080ae7118c10fcbe6229ab43eb8b090fccd31a09ef55f83f690d1ef619a1d"},
{file = "cryptography-2.9.2-cp35-abi3-manylinux1_x86_64.whl", hash = "sha256:e993468c859d084d5579e2ebee101de8f5a27ce8e2159959b6673b418fd8c785"},
{file = "cryptography-2.9.2-cp35-abi3-manylinux2010_x86_64.whl", hash = "sha256:88c881dd5a147e08d1bdcf2315c04972381d026cdb803325c03fe2b4a8ed858b"},
{file = "cryptography-2.9.2-cp35-cp35m-win32.whl", hash = "sha256:651448cd2e3a6bc2bb76c3663785133c40d5e1a8c1a9c5429e4354201c6024ae"},
{file = "cryptography-2.9.2-cp35-cp35m-win_amd64.whl", hash = "sha256:726086c17f94747cedbee6efa77e99ae170caebeb1116353c6cf0ab67ea6829b"},
{file = "cryptography-2.9.2-cp36-cp36m-win32.whl", hash = "sha256:091d31c42f444c6f519485ed528d8b451d1a0c7bf30e8ca583a0cac44b8a0df6"},
{file = "cryptography-2.9.2-cp36-cp36m-win_amd64.whl", hash = "sha256:bb1f0281887d89617b4c68e8db9a2c42b9efebf2702a3c5bf70599421a8623e3"},
{file = "cryptography-2.9.2-cp37-cp37m-win32.whl", hash = "sha256:18452582a3c85b96014b45686af264563e3e5d99d226589f057ace56196ec78b"},
{file = "cryptography-2.9.2-cp37-cp37m-win_amd64.whl", hash = "sha256:22e91636a51170df0ae4dcbd250d318fd28c9f491c4e50b625a49964b24fe46e"},
{file = "cryptography-2.9.2-cp38-cp38-win32.whl", hash = "sha256:844a76bc04472e5135b909da6aed84360f522ff5dfa47f93e3dd2a0b84a89fa0"},
{file = "cryptography-2.9.2-cp38-cp38-win_amd64.whl", hash = "sha256:1dfa985f62b137909496e7fc182dac687206d8d089dd03eaeb28ae16eec8e7d5"},
{file = "cryptography-2.9.2.tar.gz", hash = "sha256:a0c30272fb4ddda5f5ffc1089d7405b7a71b0b0f51993cb4e5dbb4590b2fc229"},
{file = "cryptography-3.0-cp27-cp27m-macosx_10_10_x86_64.whl", hash = "sha256:ab49edd5bea8d8b39a44b3db618e4783ef84c19c8b47286bf05dfdb3efb01c83"},
{file = "cryptography-3.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:124af7255ffc8e964d9ff26971b3a6153e1a8a220b9a685dc407976ecb27a06a"},
{file = "cryptography-3.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:51e40123083d2f946794f9fe4adeeee2922b581fa3602128ce85ff813d85b81f"},
{file = "cryptography-3.0-cp27-cp27m-win32.whl", hash = "sha256:dea0ba7fe6f9461d244679efa968d215ea1f989b9c1957d7f10c21e5c7c09ad6"},
{file = "cryptography-3.0-cp27-cp27m-win_amd64.whl", hash = "sha256:8ecf9400d0893836ff41b6f977a33972145a855b6efeb605b49ee273c5e6469f"},
{file = "cryptography-3.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:0c608ff4d4adad9e39b5057de43657515c7da1ccb1807c3a27d4cf31fc923b4b"},
{file = "cryptography-3.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:bec7568c6970b865f2bcebbe84d547c52bb2abadf74cefce396ba07571109c67"},
{file = "cryptography-3.0-cp35-abi3-macosx_10_10_x86_64.whl", hash = "sha256:0cbfed8ea74631fe4de00630f4bb592dad564d57f73150d6f6796a24e76c76cd"},
{file = "cryptography-3.0-cp35-abi3-manylinux1_x86_64.whl", hash = "sha256:a09fd9c1cca9a46b6ad4bea0a1f86ab1de3c0c932364dbcf9a6c2a5eeb44fa77"},
{file = "cryptography-3.0-cp35-abi3-manylinux2010_x86_64.whl", hash = "sha256:ce82cc06588e5cbc2a7df3c8a9c778f2cb722f56835a23a68b5a7264726bb00c"},
{file = "cryptography-3.0-cp35-cp35m-win32.whl", hash = "sha256:9367d00e14dee8d02134c6c9524bb4bd39d4c162456343d07191e2a0b5ec8b3b"},
{file = "cryptography-3.0-cp35-cp35m-win_amd64.whl", hash = "sha256:384d7c681b1ab904fff3400a6909261cae1d0939cc483a68bdedab282fb89a07"},
{file = "cryptography-3.0-cp36-cp36m-win32.whl", hash = "sha256:4d355f2aee4a29063c10164b032d9fa8a82e2c30768737a2fd56d256146ad559"},
{file = "cryptography-3.0-cp36-cp36m-win_amd64.whl", hash = "sha256:45741f5499150593178fc98d2c1a9c6722df88b99c821ad6ae298eff0ba1ae71"},
{file = "cryptography-3.0-cp37-cp37m-win32.whl", hash = "sha256:8ecef21ac982aa78309bb6f092d1677812927e8b5ef204a10c326fc29f1367e2"},
{file = "cryptography-3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:4b9303507254ccb1181d1803a2080a798910ba89b1a3c9f53639885c90f7a756"},
{file = "cryptography-3.0-cp38-cp38-win32.whl", hash = "sha256:8713ddb888119b0d2a1462357d5946b8911be01ddbf31451e1d07eaa5077a261"},
{file = "cryptography-3.0-cp38-cp38-win_amd64.whl", hash = "sha256:bea0b0468f89cdea625bb3f692cd7a4222d80a6bdafd6fb923963f2b9da0e15f"},
{file = "cryptography-3.0.tar.gz", hash = "sha256:8e924dbc025206e97756e8903039662aa58aa9ba357d8e1d8fc29e3092322053"},
]
flask = [
{file = "Flask-1.1.2-py2.py3-none-any.whl", hash = "sha256:8a4fdd8936eba2512e9c85df320a37e694c93945b33ef33c89946a340a238557"},
@@ -738,8 +740,8 @@ typed-ast = [
{file = "typed_ast-1.4.1.tar.gz", hash = "sha256:8c8aaad94455178e3187ab22c8b01a3837f8ee50e09cf31f1ba129eb293ec30b"},
]
urllib3 = [
{file = "urllib3-1.25.9-py2.py3-none-any.whl", hash = "sha256:88206b0eb87e6d677d424843ac5209e3fb9d0190d0ee169599165ec25e9d9115"},
{file = "urllib3-1.25.9.tar.gz", hash = "sha256:3018294ebefce6572a474f0604c2021e33b3fd8006ecd11d62107a5d2a963527"},
{file = "urllib3-1.25.10-py2.py3-none-any.whl", hash = "sha256:e7983572181f5e1522d9c98453462384ee92a0be7fac5f1413a1e35c56cc0461"},
{file = "urllib3-1.25.10.tar.gz", hash = "sha256:91056c15fa70756691db97756772bb1eb9678fa585d9184f24534b100dc60f4a"},
]
waitress = [
{file = "waitress-1.4.4-py2.py3-none-any.whl", hash = "sha256:3d633e78149eb83b60a07dfabb35579c29aac2d24bb803c18b26fb2ab1a584db"},

View File

@@ -1,6 +1,6 @@
[tool.poetry]
name = "jellyfin-accounts"
version = "0.3.7"
version = "0.3.9"
readme = "README.md"
description = "A simple account management system for Jellyfin"
authors = ["Harvey Tindall <harveyltindall@gmail.com>"]

View File

@@ -2,14 +2,40 @@
import sass
import subprocess
import shutil
import os
import argparse
from pathlib import Path
parser = argparse.ArgumentParser()
parser.add_argument(
"-y", "--yes", help="use assumed node bin directory.", action="store_true"
)
def runcmd(cmd):
if os.name == "nt":
return subprocess.check_output(cmd, shell=True)
proc = subprocess.Popen(cmd.split(), stdout=subprocess.PIPE)
return proc.communicate()
local_path = Path(__file__).resolve().parent
node_bin = local_path.parent / 'node_modules' / '.bin'
out = runcmd("npm bin")
try:
node_bin = Path(out[0].decode('utf-8').rstrip())
except:
node_bin = Path(out.decode('utf-8').rstrip())
args = parser.parse_args()
if not args.yes:
print(f"assuming npm bin directory \"{node_bin}\". Is this correct?")
if input("[yY/nN]: ").lower() == "n":
node_bin = local_path.parent / 'node_modules' / '.bin'
print(f"this? \"{node_bin}\"")
if input("[yY/nN]: ").lower() == "n":
node_bin = input("input bin directory: ")
for bsv in [d for d in local_path.iterdir() if 'bs' in d.name]:
scss = bsv / f'{bsv.name}-jf.scss'
@@ -21,7 +47,11 @@ for bsv in [d for d in local_path.iterdir() if 'bs' in d.name]:
precision=6))
if css.exists():
print(f'{bsv.name}: Compiled.')
runcmd(f'{str((node_bin / "postcss").resolve())} {str(css.resolve())} --replace --use autoprefixer')
# postcss only excepts forwards slashes? weird.
cssPath = str(css.resolve())
if os.name == 'nt':
cssPath = cssPath.replace('\\', '/')
runcmd(f'{str((node_bin / "postcss").resolve())} {cssPath} --replace --use autoprefixer')
print(f'{bsv.name}: Prefixed.')
runcmd(f'{str((node_bin / "cleancss").resolve())} --level 1 --format breakWith=lf --output {str(min_css.resolve())} {str(css.resolve())}')
if min_css.exists():

View File

@@ -1,17 +1,25 @@
#!/usr/bin/env python3
import subprocess
import os
from pathlib import Path
def runcmd(cmd):
if os.name == "nt":
return subprocess.check_output(cmd, shell=True)
proc = subprocess.Popen(cmd.split(), stdout=subprocess.PIPE)
return proc.communicate()
print('Installing npm packages')
root_path = Path(__file__).parents[1]
runcmd(f'npm install --prefix {root_path}')
if os.name == 'nt':
root_path /= 'node_modules'
runcmd(f'npm install')
if (root_path / 'node_modules' / 'cleancss').exists():
print(f'Installed successfully in {str((root_path / "node_modules").resolve())}.')