mirror of
https://github.com/hrfee/jellyfin-accounts.git
synced 2025-12-20 01:01:13 +00:00
Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 27169e4e0d | |||
| db3b992857 | |||
| 89c132e92e | |||
| 7bda2f4141 | |||
| 71f05f2348 | |||
| 94e69ad090 |
@@ -1,5 +1,5 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
__version__ = "0.3.3"
|
__version__ = "0.3.6"
|
||||||
|
|
||||||
import secrets
|
import secrets
|
||||||
import configparser
|
import configparser
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ function serializeForm(id) {
|
|||||||
case 'password':
|
case 'password':
|
||||||
case 'select-one':
|
case 'select-one':
|
||||||
case 'email':
|
case 'email':
|
||||||
|
case 'number':
|
||||||
formData[name] = el.value;
|
formData[name] = el.value;
|
||||||
break;
|
break;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ for (var i = 0; i < authRadios.length; i++) {
|
|||||||
checkAuthRadio();
|
checkAuthRadio();
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
function checkEmailRadio() {
|
function checkEmailRadio() {
|
||||||
document.getElementById('emailNextButton').href = '#page-5';
|
document.getElementById('emailNextButton').href = '#page-5';
|
||||||
document.getElementById('valBackButton').href = '#page-7';
|
document.getElementById('valBackButton').href = '#page-7';
|
||||||
@@ -35,6 +36,7 @@ for (var i = 0; i < emailRadios.length; i++) {
|
|||||||
checkEmailRadio();
|
checkEmailRadio();
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
function checkSSL() {
|
function checkSSL() {
|
||||||
var label = document.getElementById('emailSSL_TLSLabel');
|
var label = document.getElementById('emailSSL_TLSLabel');
|
||||||
if (document.getElementById('emailSSL_TLS').checked) {
|
if (document.getElementById('emailSSL_TLS').checked) {
|
||||||
@@ -101,16 +103,15 @@ document.getElementById('jfTestButton').onclick = function() {
|
|||||||
jfData['jfHost'] = document.getElementById('jfHost').value;
|
jfData['jfHost'] = document.getElementById('jfHost').value;
|
||||||
jfData['jfUser'] = document.getElementById('jfUser').value;
|
jfData['jfUser'] = document.getElementById('jfUser').value;
|
||||||
jfData['jfPassword'] = document.getElementById('jfPassword').value;
|
jfData['jfPassword'] = document.getElementById('jfPassword').value;
|
||||||
$.ajax('/testJF', {
|
var req = new XMLHttpRequest();
|
||||||
type : 'POST',
|
req.open("POST", "/testJF", true);
|
||||||
dataType : 'json',
|
req.setRequestHeader('Content-Type', 'application/json; charset=UTF-8');
|
||||||
contentType : 'application/json',
|
req.responseType = 'json';
|
||||||
data : JSON.stringify(jfData),
|
req.onreadystatechange = function() {
|
||||||
complete: function(response) {
|
if (this.readyState == 4) {
|
||||||
testButton.disabled = false;
|
testButton.disabled = false;
|
||||||
testButton.className = '';
|
testButton.className = '';
|
||||||
var success = response['responseJSON']['success'];
|
if (this.response['success'] == true) {
|
||||||
if (success == true) {
|
|
||||||
testButton.classList.add('btn', 'btn-success');
|
testButton.classList.add('btn', 'btn-success');
|
||||||
testButton.textContent = 'Success';
|
testButton.textContent = 'Success';
|
||||||
nextButton.classList.remove('disabled');
|
nextButton.classList.remove('disabled');
|
||||||
@@ -118,9 +119,10 @@ document.getElementById('jfTestButton').onclick = function() {
|
|||||||
} else {
|
} else {
|
||||||
testButton.classList.add('btn', 'btn-danger');
|
testButton.classList.add('btn', 'btn-danger');
|
||||||
testButton.textContent = 'Failed';
|
testButton.textContent = 'Failed';
|
||||||
}
|
};
|
||||||
}
|
};
|
||||||
});
|
};
|
||||||
|
req.send(JSON.stringify(jfData));
|
||||||
};
|
};
|
||||||
|
|
||||||
document.getElementById('submitButton').onclick = function() {
|
document.getElementById('submitButton').onclick = function() {
|
||||||
@@ -158,7 +160,7 @@ document.getElementById('submitButton').onclick = function() {
|
|||||||
if (document.getElementById('emailDisabledRadio').checked) {
|
if (document.getElementById('emailDisabledRadio').checked) {
|
||||||
config['password_resets']['enabled'] = 'false';
|
config['password_resets']['enabled'] = 'false';
|
||||||
config['invite_emails']['enabled'] = 'false';
|
config['invite_emails']['enabled'] = 'false';
|
||||||
} else {
|
} else {
|
||||||
if (document.getElementById('emailSMTPRadio').checked) {
|
if (document.getElementById('emailSMTPRadio').checked) {
|
||||||
if (document.getElementById('emailSSL_TLS').checked) {
|
if (document.getElementById('emailSSL_TLS').checked) {
|
||||||
config['smtp']['encryption'] = 'ssl_tls';
|
config['smtp']['encryption'] = 'ssl_tls';
|
||||||
@@ -217,18 +219,18 @@ document.getElementById('submitButton').onclick = function() {
|
|||||||
config['ui']['contact_message'] = document.getElementById('msgContact').value;
|
config['ui']['contact_message'] = document.getElementById('msgContact').value;
|
||||||
config['ui']['help_message'] = document.getElementById('msgHelp').value;
|
config['ui']['help_message'] = document.getElementById('msgHelp').value;
|
||||||
config['ui']['success_message'] = document.getElementById('msgSuccess').value;
|
config['ui']['success_message'] = document.getElementById('msgSuccess').value;
|
||||||
console.log(config);
|
// Send it
|
||||||
$.ajax('/modifyConfig', {
|
var req = new XMLHttpRequest();
|
||||||
type : 'POST',
|
req.open("POST", "/modifyConfig", true);
|
||||||
dataType : 'json',
|
req.setRequestHeader('Content-Type', 'application/json; charset=UTF-8');
|
||||||
contentType : 'application/json',
|
req.responseType = 'json';
|
||||||
data : JSON.stringify(config),
|
req.onreadystatechange = function() {
|
||||||
complete: function(response) {
|
if (this.readyState == 4) {
|
||||||
submitButton.disabled = false;
|
submitButton.disabled = false;
|
||||||
submitButton.className = '';
|
submitButton.className = '';
|
||||||
submitButton.classList.add('btn', 'btn-success');
|
submitButton.classList.add('btn', 'btn-success');
|
||||||
submitButton.textContent = 'Success';
|
submitButton.textContent = 'Success';
|
||||||
}
|
};
|
||||||
});
|
};
|
||||||
|
req.send(JSON.stringify(config));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -31,21 +31,24 @@
|
|||||||
return "";
|
return "";
|
||||||
};
|
};
|
||||||
{% if bs5 %}
|
{% if bs5 %}
|
||||||
var bsVersion = 5;
|
const bsVersion = 5;
|
||||||
{% else %}
|
{% else %}
|
||||||
var bsVersion = 4;
|
const bsVersion = 4;
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
console.log('create');
|
||||||
var css = document.createElement('link');
|
var css = document.createElement('link');
|
||||||
css.setAttribute('rel', 'stylesheet');
|
css.setAttribute('rel', 'stylesheet');
|
||||||
css.setAttribute('type', 'text/css');
|
css.setAttribute('type', 'text/css');
|
||||||
var cssCookie = getCookie("css");
|
var cssCookie = getCookie("css");
|
||||||
if (cssCookie.includes('bs' + bsVersion)) {
|
if (cssCookie.includes('bs' + bsVersion)) {
|
||||||
|
console.log('href');
|
||||||
css.setAttribute('href', cssCookie);
|
css.setAttribute('href', cssCookie);
|
||||||
} else {
|
} else {
|
||||||
|
console.log('href');
|
||||||
css.setAttribute('href', '{{ css_file }}');
|
css.setAttribute('href', '{{ css_file }}');
|
||||||
};
|
};
|
||||||
|
console.log('append');
|
||||||
document.head.appendChild(css);
|
document.head.appendChild(css);
|
||||||
// document.querySelectorAll('link[rel="stylesheet"][type="text/css"]')[0].href = cssCookie;
|
|
||||||
</script>
|
</script>
|
||||||
{% if not bs5 %}
|
{% if not bs5 %}
|
||||||
<script src="https://code.jquery.com/jquery-3.5.1.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous"></script>
|
<script src="https://code.jquery.com/jquery-3.5.1.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous"></script>
|
||||||
@@ -91,16 +94,31 @@
|
|||||||
height: 1rem;
|
height: 1rem;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
z-index: 5000;*/
|
z-index: 5000;*/
|
||||||
-webkit-transition: all 300ms cubic-bezier(0.550, 0.055, 0.675, 0.190);
|
-webkit-transition: all 300ms cubic-bezier(0.550, 0.055, 0.675, 0.190);
|
||||||
-moz-transition: all 300ms cubic-bezier(0.550, 0.055, 0.675, 0.190);
|
-moz-transition: all 300ms cubic-bezier(0.550, 0.055, 0.675, 0.190);
|
||||||
-o-transition: all 300ms cubic-bezier(0.550, 0.055, 0.675, 0.190);
|
-o-transition: all 300ms cubic-bezier(0.550, 0.055, 0.675, 0.190);
|
||||||
transition: all 300ms cubic-bezier(0.550, 0.055, 0.675, 0.190); /* easeInCubic */
|
transition: all 300ms cubic-bezier(0.550, 0.055, 0.675, 0.190); /* easeInCubic */
|
||||||
}
|
}
|
||||||
.smooth-transition {
|
.smooth-transition {
|
||||||
-webkit-transition: all 300ms cubic-bezier(0.550, 0.055, 0.675, 0.190);
|
-webkit-transition: all 300ms cubic-bezier(0.550, 0.055, 0.675, 0.190);
|
||||||
-moz-transition: all 300ms cubic-bezier(0.550, 0.055, 0.675, 0.190);
|
-moz-transition: all 300ms cubic-bezier(0.550, 0.055, 0.675, 0.190);
|
||||||
-o-transition: all 300ms cubic-bezier(0.550, 0.055, 0.675, 0.190);
|
-o-transition: all 300ms cubic-bezier(0.550, 0.055, 0.675, 0.190);
|
||||||
transition: all 300ms cubic-bezier(0.550, 0.055, 0.675, 0.190); /* easeInCubic */
|
transition: all 300ms cubic-bezier(0.550, 0.055, 0.675, 0.190); /* easeincubic */
|
||||||
|
}
|
||||||
|
.rotated {
|
||||||
|
transform: rotate(180deg);
|
||||||
|
-webkit-transition: all 150ms cubic-bezier(0.770, 0.000, 0.175, 1.000);
|
||||||
|
-moz-transition: all 150ms cubic-bezier(0.770, 0.000, 0.175, 1.000);
|
||||||
|
-o-transition: all 150ms cubic-bezier(0.770, 0.000, 0.175, 1.000);
|
||||||
|
transition: all 150ms cubic-bezier(0.770, 0.000, 0.175, 1.000); /* easeInOutQuart */
|
||||||
|
}
|
||||||
|
|
||||||
|
.not-rotated {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
-webkit-transition: all 150ms cubic-bezier(0.770, 0.000, 0.175, 1.000);
|
||||||
|
-moz-transition: all 150ms cubic-bezier(0.770, 0.000, 0.175, 1.000);
|
||||||
|
-o-transition: all 150ms cubic-bezier(0.770, 0.000, 0.175, 1.000);
|
||||||
|
transition: all 150ms cubic-bezier(0.770, 0.000, 0.175, 1.000); /* easeInOutQuart */
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<title>Admin</title>
|
<title>Admin</title>
|
||||||
@@ -234,29 +252,66 @@
|
|||||||
<div class="card mb-3">
|
<div class="card mb-3">
|
||||||
<div class="card-header">Generate Invite</div>
|
<div class="card-header">Generate Invite</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<form action="#" method="POST" id="inviteForm">
|
<form action="#" method="POST" id="inviteForm" class="container">
|
||||||
<div class="form-group">
|
<div class="row align-items-start">
|
||||||
<label for="hours">Hours</label>
|
<div class="col">
|
||||||
<select class="form-control" id="hours" name="hours">
|
<div class="form-group">
|
||||||
</select>
|
<label for="days">Days</label>
|
||||||
</div>
|
<select class="form-control form-select" id="days" name="days">
|
||||||
<div class="form-group">
|
</select>
|
||||||
<label for="minutes">Minutes</label>
|
</div>
|
||||||
<select class="form-control" id="minutes" name="minutes">
|
<div class="form-group">
|
||||||
</select>
|
<label for="hours">Hours</label>
|
||||||
</div>
|
<select class="form-control form-select" id="hours" name="hours">
|
||||||
{% if email_enabled %}
|
</select>
|
||||||
<div class="form-group">
|
</div>
|
||||||
<label for="send_to_address">Send invite to address</label>
|
<div class="form-group">
|
||||||
<div class="input-group">
|
<label for="minutes">Minutes</label>
|
||||||
<div class="input-group-text">
|
<select class="form-control form-select" id="minutes" name="minutes">
|
||||||
<input class="form-check-input" type="checkbox" onchange="document.getElementById('send_to_address').disabled = !this.checked;" aria-label="Checkbox to allow input of email address" id="send_to_address_enabled">
|
</select>
|
||||||
</div>
|
|
||||||
<input type="email" class="form-control" placeholder="example@example.com" id="send_to_address" disabled>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
<div class="col">
|
||||||
<button type="submit" id="generateSubmit" class="btn btn-primary" style="margin-top: 1rem;">Generate</button>
|
<div class="form-group">
|
||||||
|
<label for="multiUseCount">
|
||||||
|
Multiple uses
|
||||||
|
</label>
|
||||||
|
<div class="input-group">
|
||||||
|
<div class="input-group-text">
|
||||||
|
<input class="form-check-input" type="checkbox" onchange="document.getElementById('multiUseCount').disabled = !this.checked; document.getElementById('noUseLimit').disabled = !this.checked" aria-label="Checkbox to allow choice of invite usage limit" name="multiple-uses" id="multiUseEnabled">
|
||||||
|
</div>
|
||||||
|
<input type="number" class="form-control" name="remaining-uses" id="multiUseCount">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group form-check" style="margin-top: 1rem; margin-bottom: 1rem;">
|
||||||
|
<input class="form-check-input" type="checkbox" value="" name="no-limit" id="noUseLimit" onchange="document.getElementById('multiUseCount').disabled = this.checked; if (this.checked) { document.getElementById('noLimitWarning').style = 'display: block;' } else { document.getElementById('noLimitWarning').style = 'display: none;'; }">
|
||||||
|
<label class="form-check-label" for="noUseLimit">
|
||||||
|
No use limit
|
||||||
|
</label>
|
||||||
|
<div id="noLimitWarning" class="form-text" style="display: none;">Warning: Unlimited usage invites pose a risk if published online.</div>
|
||||||
|
</div>
|
||||||
|
{% if email_enabled %}
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="send_to_address">Send invite to address</label>
|
||||||
|
<div class="input-group">
|
||||||
|
<div class="input-group-text">
|
||||||
|
<input class="form-check-input" type="checkbox" onchange="document.getElementById('send_to_address').disabled = !this.checked;" aria-label="Checkbox to allow input of email address" id="send_to_address_enabled">
|
||||||
|
</div>
|
||||||
|
<input type="email" class="form-control" placeholder="example@example.com" id="send_to_address" disabled>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
<div class="form-group d-flex float-right">
|
||||||
|
<button type="submit" id="generateSubmit" class="btn btn-primary" style="margin-top: 1rem;">
|
||||||
|
Generate
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -156,6 +156,9 @@
|
|||||||
submitButton.replaceChild(newSpan, oldSpan);
|
submitButton.replaceChild(newSpan, oldSpan);
|
||||||
};
|
};
|
||||||
document.getElementById('accountForm').onsubmit = function() {
|
document.getElementById('accountForm').onsubmit = function() {
|
||||||
|
if (document.getElementById('errorMessage')) {
|
||||||
|
document.getElementById('errorMessage').remove();
|
||||||
|
}
|
||||||
toggleSpinner();
|
toggleSpinner();
|
||||||
var send = serializeForm('accountForm');
|
var send = serializeForm('accountForm');
|
||||||
send['code'] = code;
|
send['code'] = code;
|
||||||
@@ -171,12 +174,18 @@
|
|||||||
if (this.readyState == 4) {
|
if (this.readyState == 4) {
|
||||||
toggleSpinner();
|
toggleSpinner();
|
||||||
var data = this.response;
|
var data = this.response;
|
||||||
if ('error' in data) {
|
if ('error' in data || data['success'] == false) {
|
||||||
var text = document.createTextNode(data['error']);
|
if (typeof(data['error']) != 'undefined') {
|
||||||
|
var errorMessage = data['error'];
|
||||||
|
} else {
|
||||||
|
var errorMessage = 'Unknown Error';
|
||||||
|
}
|
||||||
|
var text = document.createTextNode(errorMessage);
|
||||||
var error = document.createElement('button');
|
var error = document.createElement('button');
|
||||||
error.classList.add('btn', 'btn-outline-danger');
|
error.classList.add('btn', 'btn-outline-danger');
|
||||||
error.setAttribute('disabled', '');
|
error.setAttribute('disabled', '');
|
||||||
error.appendChild(text);
|
error.appendChild(text);
|
||||||
|
error.id = 'errorMessage';
|
||||||
document.getElementById('errorBox').appendChild(error);
|
document.getElementById('errorBox').appendChild(error);
|
||||||
} else {
|
} else {
|
||||||
var valid = true
|
var valid = true
|
||||||
|
|||||||
@@ -190,7 +190,7 @@ class Jellyfin:
|
|||||||
)
|
)
|
||||||
|
|
||||||
def newUser(self, username: str, password: str):
|
def newUser(self, username: str, password: str):
|
||||||
for user in self.getUsers():
|
for user in self.getUsers(public=False):
|
||||||
if user["Name"] == username:
|
if user["Name"] == username:
|
||||||
raise self.UserExistsError
|
raise self.UserExistsError
|
||||||
response = requests.post(
|
response = requests.post(
|
||||||
|
|||||||
@@ -42,9 +42,7 @@ def static_proxy(path):
|
|||||||
if "html" not in path:
|
if "html" not in path:
|
||||||
if "admin.js" in path:
|
if "admin.js" in path:
|
||||||
return (
|
return (
|
||||||
render_template("admin.js",
|
render_template("admin.js", bsVersion=bsVersion, css_file=css_file),
|
||||||
bsVersion=bsVersion,
|
|
||||||
css_file=css_file),
|
|
||||||
200,
|
200,
|
||||||
{"Content-Type": "text/javascript"},
|
{"Content-Type": "text/javascript"},
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -20,21 +20,49 @@ from jellyfin_accounts import web_log as log
|
|||||||
from jellyfin_accounts.validate_password import PasswordValidator
|
from jellyfin_accounts.validate_password import PasswordValidator
|
||||||
|
|
||||||
|
|
||||||
def checkInvite(code, delete=False):
|
def format_datetime(dt):
|
||||||
|
result = dt.strftime(config["email"]["date_format"])
|
||||||
|
if config.getboolean("email", "use_24h"):
|
||||||
|
result += f' {dt.strftime("%H:%M")}'
|
||||||
|
else:
|
||||||
|
result += f' {dt.strftime("%I:%M %p")}'
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def checkInvite(code, used=False, username=None):
|
||||||
current_time = datetime.datetime.now()
|
current_time = datetime.datetime.now()
|
||||||
invites = dict(data_store.invites)
|
invites = dict(data_store.invites)
|
||||||
match = False
|
match = False
|
||||||
for invite in invites:
|
for invite in invites:
|
||||||
|
if (
|
||||||
|
"remaining-uses" not in invites[invite]
|
||||||
|
and "no-limit" not in invites[invite]
|
||||||
|
):
|
||||||
|
invites[invite]["remaining-uses"] = 1
|
||||||
expiry = datetime.datetime.strptime(
|
expiry = datetime.datetime.strptime(
|
||||||
invites[invite]["valid_till"], "%Y-%m-%dT%H:%M:%S.%f"
|
invites[invite]["valid_till"], "%Y-%m-%dT%H:%M:%S.%f"
|
||||||
)
|
)
|
||||||
if current_time >= expiry:
|
if current_time >= expiry or (
|
||||||
log.debug(f"Housekeeping: Deleting old invite {invite}")
|
"no-limit" not in invites[invite] and invites[invite]["remaining-uses"] < 1
|
||||||
|
):
|
||||||
|
log.debug(f"Housekeeping: Deleting expired invite {invite}")
|
||||||
del data_store.invites[invite]
|
del data_store.invites[invite]
|
||||||
elif invite == code:
|
elif invite == code:
|
||||||
match = True
|
match = True
|
||||||
if delete:
|
if used:
|
||||||
del data_store.invites[code]
|
delete = False
|
||||||
|
inv = dict(data_store.invites[code])
|
||||||
|
if "used-by" not in inv:
|
||||||
|
inv["used-by"] = []
|
||||||
|
if "remaining-uses" in inv:
|
||||||
|
if inv["remaining-uses"] == 1:
|
||||||
|
delete = True
|
||||||
|
del data_store.invites[code]
|
||||||
|
elif "no-limit" not in invites[invite]:
|
||||||
|
inv["remaining-uses"] -= 1
|
||||||
|
inv["used-by"].append([username, format_datetime(current_time)])
|
||||||
|
if not delete:
|
||||||
|
data_store.invites[code] = inv
|
||||||
return match
|
return match
|
||||||
|
|
||||||
|
|
||||||
@@ -157,7 +185,7 @@ def newUser():
|
|||||||
return jsonify({"error": error})
|
return jsonify({"error": error})
|
||||||
except:
|
except:
|
||||||
return jsonify({"error": "Unknown error"})
|
return jsonify({"error": "Unknown error"})
|
||||||
checkInvite(data["code"], delete=True)
|
checkInvite(data["code"], used=True, username=data["username"])
|
||||||
if user.status_code == 200:
|
if user.status_code == 200:
|
||||||
try:
|
try:
|
||||||
policy = data_store.user_template
|
policy = data_store.user_template
|
||||||
@@ -200,9 +228,19 @@ def newUser():
|
|||||||
def generateInvite():
|
def generateInvite():
|
||||||
current_time = datetime.datetime.now()
|
current_time = datetime.datetime.now()
|
||||||
data = request.get_json()
|
data = request.get_json()
|
||||||
delta = datetime.timedelta(hours=int(data["hours"]), minutes=int(data["minutes"]))
|
delta = datetime.timedelta(
|
||||||
|
days=int(data["days"]), hours=int(data["hours"]), minutes=int(data["minutes"])
|
||||||
|
)
|
||||||
invite_code = secrets.token_urlsafe(16)
|
invite_code = secrets.token_urlsafe(16)
|
||||||
invite = {}
|
invite = {}
|
||||||
|
invite["created"] = format_datetime(current_time)
|
||||||
|
if data["multiple-uses"]:
|
||||||
|
if data["no-limit"]:
|
||||||
|
invite["no-limit"] = True
|
||||||
|
else:
|
||||||
|
invite["remaining-uses"] = int(data["remaining-uses"])
|
||||||
|
else:
|
||||||
|
invite["remaining-uses"] = 1
|
||||||
log.debug(f"Creating new invite: {invite_code}")
|
log.debug(f"Creating new invite: {invite_code}")
|
||||||
valid_till = current_time + delta
|
valid_till = current_time + delta
|
||||||
invite["valid_till"] = valid_till.strftime("%Y-%m-%dT%H:%M:%S.%f")
|
invite["valid_till"] = valid_till.strftime("%Y-%m-%dT%H:%M:%S.%f")
|
||||||
@@ -245,9 +283,20 @@ def getInvites():
|
|||||||
valid_for = expiry - current_time
|
valid_for = expiry - current_time
|
||||||
invite = {
|
invite = {
|
||||||
"code": code,
|
"code": code,
|
||||||
|
"days": valid_for.days,
|
||||||
"hours": valid_for.seconds // 3600,
|
"hours": valid_for.seconds // 3600,
|
||||||
"minutes": (valid_for.seconds // 60) % 60,
|
"minutes": (valid_for.seconds // 60) % 60,
|
||||||
}
|
}
|
||||||
|
if "created" in invites[code]:
|
||||||
|
invite["created"] = invites[code]["created"]
|
||||||
|
if "used-by" in invites[code]:
|
||||||
|
invite["used-by"] = invites[code]["used-by"]
|
||||||
|
if "no-limit" in invites[code]:
|
||||||
|
invite["no-limit"] = invites[code]["no-limit"]
|
||||||
|
if "remaining-uses" in invites[code]:
|
||||||
|
invite["remaining-uses"] = invites[code]["remaining-uses"]
|
||||||
|
else:
|
||||||
|
invite["remaining-uses"] = 1
|
||||||
if "email" in invites[code]:
|
if "email" in invites[code]:
|
||||||
invite["email"] = invites[code]["email"]
|
invite["email"] = invites[code]["email"]
|
||||||
response["invites"].append(invite)
|
response["invites"].append(invite)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[tool.poetry]
|
[tool.poetry]
|
||||||
name = "jellyfin-accounts"
|
name = "jellyfin-accounts"
|
||||||
version = "0.3.3"
|
version = "0.3.6"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
description = "A simple account management system for Jellyfin"
|
description = "A simple account management system for Jellyfin"
|
||||||
authors = ["Harvey Tindall <harveyltindall@gmail.com>"]
|
authors = ["Harvey Tindall <harveyltindall@gmail.com>"]
|
||||||
|
|||||||
@@ -120,6 +120,10 @@ $list-group-action-active-bg: $jf-blue-focus;
|
|||||||
color: $jf-text-bold;
|
color: $jf-text-bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.icon-button {
|
||||||
|
color: $text-muted;
|
||||||
|
}
|
||||||
|
|
||||||
.text-bright {
|
.text-bright {
|
||||||
color: $jf-text-bold;
|
color: $jf-text-bold;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -120,6 +120,10 @@ $list-group-action-active-bg: $jf-blue-focus;
|
|||||||
color: $jf-text-bold;
|
color: $jf-text-bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.icon-button:active {
|
||||||
|
color: $text-muted;
|
||||||
|
}
|
||||||
|
|
||||||
.text-bright {
|
.text-bright {
|
||||||
color: $jf-text-bold;
|
color: $jf-text-bold;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user