Compare commits
No commits in common. "032acb76643522f799c7d79bb936cb0efe57d3b4" and "45c50d68a7f9e4bf36c738ab7a8a2617700f15b7" have entirely different histories.
032acb7664
...
45c50d68a7
48
app.py
48
app.py
@ -27,7 +27,6 @@ import base64
|
|||||||
import json
|
import json
|
||||||
import io
|
import io
|
||||||
import search
|
import search
|
||||||
import urllib.request
|
|
||||||
import auth
|
import auth
|
||||||
import analytics as a
|
import analytics as a
|
||||||
import folder_secret_config_editor as fsce
|
import folder_secret_config_editor as fsce
|
||||||
@ -462,30 +461,6 @@ def create_calendar_entry():
|
|||||||
'details': details
|
'details': details
|
||||||
}), 201
|
}), 201
|
||||||
|
|
||||||
@app.route('/api/livestream/status', methods=['GET'])
|
|
||||||
@auth.require_secret
|
|
||||||
def livestream_status():
|
|
||||||
status_url = auth.return_app_config().get('LIVESTREAM_STATUS_URL')
|
|
||||||
result = {"audio_live": False, "live": False}
|
|
||||||
if not status_url:
|
|
||||||
reply = jsonify(result)
|
|
||||||
reply.headers['Cache-Control'] = 'no-store'
|
|
||||||
return reply
|
|
||||||
try:
|
|
||||||
with urllib.request.urlopen(status_url, timeout=5) as response:
|
|
||||||
if response.status != 200:
|
|
||||||
raise ValueError(f"Status endpoint returned {response.status}")
|
|
||||||
payload = response.read()
|
|
||||||
parsed = json.loads(payload.decode('utf-8'))
|
|
||||||
result["audio_live"] = bool(parsed.get("audio_live", False))
|
|
||||||
result["live"] = bool(parsed.get("live", False))
|
|
||||||
except Exception as err:
|
|
||||||
app.logger.warning("Livestream status proxy failed: %s", err)
|
|
||||||
|
|
||||||
reply = jsonify(result)
|
|
||||||
reply.headers['Cache-Control'] = 'no-store'
|
|
||||||
return reply
|
|
||||||
|
|
||||||
# Grab the HOST_RULE environment variable
|
# Grab the HOST_RULE environment variable
|
||||||
host_rule = os.getenv("HOST_RULE", "")
|
host_rule = os.getenv("HOST_RULE", "")
|
||||||
# Use a regex to extract domain names between backticks in patterns like Host(`something`)
|
# Use a regex to extract domain names between backticks in patterns like Host(`something`)
|
||||||
@ -807,15 +782,9 @@ def serve_file(subpath):
|
|||||||
root, *relative_parts = subpath.split('/')
|
root, *relative_parts = subpath.split('/')
|
||||||
|
|
||||||
dltoken = request.args.get('dltoken')
|
dltoken = request.args.get('dltoken')
|
||||||
token_payload = None
|
|
||||||
if dltoken:
|
if dltoken:
|
||||||
as_attachment = True
|
as_attachment = True
|
||||||
try:
|
full_path = auth.decode_token(dltoken)['filename']
|
||||||
token_payload = auth.decode_token(dltoken)
|
|
||||||
full_path = token_payload['filename']
|
|
||||||
except Exception as e:
|
|
||||||
app.logger.warning(f"Invalid dltoken: {e}")
|
|
||||||
return jsonify({'Unauthorized': 'Invalid token'}), 403
|
|
||||||
else:
|
else:
|
||||||
as_attachment = False
|
as_attachment = False
|
||||||
base_path = session['folders'].get(root)
|
base_path = session['folders'].get(root)
|
||||||
@ -842,8 +811,6 @@ def serve_file(subpath):
|
|||||||
user_agent = request.headers.get('User-Agent')
|
user_agent = request.headers.get('User-Agent')
|
||||||
range_header = request.headers.get('Range', '')
|
range_header = request.headers.get('Range', '')
|
||||||
req_id = request.args.get('req') or request.headers.get('X-Request-Id')
|
req_id = request.args.get('req') or request.headers.get('X-Request-Id')
|
||||||
token_device_id = token_payload.get('device_id') if token_payload else None
|
|
||||||
device_id = token_device_id or session.get('device_id')
|
|
||||||
|
|
||||||
def is_range_prefetch(header, ua):
|
def is_range_prefetch(header, ua):
|
||||||
"""
|
"""
|
||||||
@ -965,7 +932,7 @@ def serve_file(subpath):
|
|||||||
mime,
|
mime,
|
||||||
ip_address,
|
ip_address,
|
||||||
user_agent,
|
user_agent,
|
||||||
device_id,
|
session['device_id'],
|
||||||
cached_hit,
|
cached_hit,
|
||||||
request.method
|
request.method
|
||||||
)
|
)
|
||||||
@ -1028,7 +995,7 @@ def serve_file(subpath):
|
|||||||
mime,
|
mime,
|
||||||
ip_address,
|
ip_address,
|
||||||
user_agent,
|
user_agent,
|
||||||
device_id,
|
session['device_id'],
|
||||||
cached_hit,
|
cached_hit,
|
||||||
request.method
|
request.method
|
||||||
)
|
)
|
||||||
@ -1178,11 +1145,6 @@ def create_dltoken(subpath):
|
|||||||
root, *relative_parts = subpath.split('/')
|
root, *relative_parts = subpath.split('/')
|
||||||
base_path = session['folders'].get(root)
|
base_path = session['folders'].get(root)
|
||||||
full_path = os.path.join(base_path or '', *relative_parts)
|
full_path = os.path.join(base_path or '', *relative_parts)
|
||||||
|
|
||||||
device_id = session.get('device_id')
|
|
||||||
if not device_id:
|
|
||||||
device_id = os.urandom(32).hex()
|
|
||||||
session['device_id'] = device_id
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
full_path = check_path(full_path)
|
full_path = check_path(full_path)
|
||||||
@ -1196,8 +1158,7 @@ def create_dltoken(subpath):
|
|||||||
validity_date = datetime.now().strftime('%d.%m.%Y')
|
validity_date = datetime.now().strftime('%d.%m.%Y')
|
||||||
data = {
|
data = {
|
||||||
"validity": validity_date,
|
"validity": validity_date,
|
||||||
"filename": str(full_path),
|
"filename": str(full_path)
|
||||||
"device_id": device_id
|
|
||||||
}
|
}
|
||||||
|
|
||||||
token = auth.generate_token(data)
|
token = auth.generate_token(data)
|
||||||
@ -1314,7 +1275,6 @@ def index(path):
|
|||||||
features=app_config.get('FEATURES', None),
|
features=app_config.get('FEATURES', None),
|
||||||
og_title=og_title,
|
og_title=og_title,
|
||||||
og_description=og_description,
|
og_description=og_description,
|
||||||
livestream_url=app_config.get('LIVESTREAM_URL') or '',
|
|
||||||
admin_enabled=auth.is_admin()
|
admin_enabled=auth.is_admin()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
2
auth.py
2
auth.py
@ -354,8 +354,6 @@ KEY_MAP = {
|
|||||||
"folders": "f",
|
"folders": "f",
|
||||||
"foldername": "n",
|
"foldername": "n",
|
||||||
"folderpath": "p",
|
"folderpath": "p",
|
||||||
"filename": "fn",
|
|
||||||
"device_id": "d",
|
|
||||||
}
|
}
|
||||||
# Build the inverse map automatically
|
# Build the inverse map automatically
|
||||||
INV_KEY_MAP = {short: long for long, short in KEY_MAP.items()}
|
INV_KEY_MAP = {short: long for long, short in KEY_MAP.items()}
|
||||||
|
|||||||
@ -4,11 +4,9 @@
|
|||||||
"ADMIN_KEY": "THIS_IS_USED_TO_AUTHENTICATE_ADMIN",
|
"ADMIN_KEY": "THIS_IS_USED_TO_AUTHENTICATE_ADMIN",
|
||||||
"TITLE_SHORT": "Gottesdienste",
|
"TITLE_SHORT": "Gottesdienste",
|
||||||
"TITLE_LONG": "Gottesdienste App",
|
"TITLE_LONG": "Gottesdienste App",
|
||||||
"LIVESTREAM_URL": "https://streaming.bethaus-speyer.de",
|
|
||||||
"LIVESTREAM_STATUS_URL": "https://streaming.bethaus-speyer.de/status",
|
|
||||||
"BASE_DIR": "/mnt",
|
"BASE_DIR": "/mnt",
|
||||||
"filecache_size_limit_audio": 16,
|
"filecache_size_limit_audio": 16,
|
||||||
"filecache_size_limit_image": 16,
|
"filecache_size_limit_image": 16,
|
||||||
"filecache_size_limit_video": 16,
|
"filecache_size_limit_video": 16,
|
||||||
"filecache_size_limit_other": 16
|
"filecache_size_limit_other": 16
|
||||||
}
|
}
|
||||||
@ -128,46 +128,6 @@ main > * {
|
|||||||
margin: 12px 0;
|
margin: 12px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.livestream-alert {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
gap: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.livestream-button {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
padding: 8px 14px;
|
|
||||||
border-radius: 999px;
|
|
||||||
background: rgba(0, 0, 0, 0.12);
|
|
||||||
border: 1px solid rgba(0, 0, 0, 0.2);
|
|
||||||
color: inherit;
|
|
||||||
font-weight: 600;
|
|
||||||
text-decoration: none;
|
|
||||||
transition: background-color 0.2s ease, transform 0.2s ease, box-shadow 0.2s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.livestream-button:hover,
|
|
||||||
.livestream-button:focus-visible {
|
|
||||||
background: rgba(0, 0, 0, 0.2);
|
|
||||||
box-shadow: 0 6px 14px rgba(0, 0, 0, 0.15);
|
|
||||||
transform: translateY(-1px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.livestream-button:active {
|
|
||||||
transform: translateY(0);
|
|
||||||
box-shadow: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 640px) {
|
|
||||||
.livestream-alert {
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: flex-start;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Search view container (custom element) */
|
/* Search view container (custom element) */
|
||||||
search {
|
search {
|
||||||
display: block;
|
display: block;
|
||||||
|
|||||||
@ -796,60 +796,9 @@ function initTranscriptModal() {
|
|||||||
modal.dataset.bound = '1';
|
modal.dataset.bound = '1';
|
||||||
}
|
}
|
||||||
|
|
||||||
const LIVESTREAM_STATUS_URL = '/api/livestream/status';
|
|
||||||
const LIVESTREAM_POLL_INTERVAL_MS = 60000;
|
|
||||||
let livestreamStatusIntervalId = null;
|
|
||||||
let livestreamStatusErrorLogged = false;
|
|
||||||
|
|
||||||
function setLivestreamAlertVisible(isVisible) {
|
|
||||||
const alertEl = document.getElementById('livestream-alert');
|
|
||||||
if (!alertEl) return;
|
|
||||||
alertEl.classList.toggle('d-none', !isVisible);
|
|
||||||
alertEl.setAttribute('aria-hidden', String(!isVisible));
|
|
||||||
}
|
|
||||||
|
|
||||||
async function checkLivestreamStatus() {
|
|
||||||
const alertEl = document.getElementById('livestream-alert');
|
|
||||||
if (!alertEl) return;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await fetch(LIVESTREAM_STATUS_URL, {
|
|
||||||
cache: 'no-store',
|
|
||||||
credentials: 'same-origin',
|
|
||||||
headers: { 'Accept': 'application/json' }
|
|
||||||
});
|
|
||||||
if (!response.ok) throw new Error(`Livestream status request failed (${response.status})`);
|
|
||||||
const contentType = response.headers.get('content-type') || '';
|
|
||||||
if (!contentType.includes('application/json')) {
|
|
||||||
const text = await response.text();
|
|
||||||
throw new Error(`Livestream status returned non-JSON: ${text.slice(0, 120)}`);
|
|
||||||
}
|
|
||||||
const data = await response.json();
|
|
||||||
const isLive = Boolean(data && (data.live || data.audio_live));
|
|
||||||
setLivestreamAlertVisible(isLive);
|
|
||||||
} catch (err) {
|
|
||||||
if (!livestreamStatusErrorLogged) {
|
|
||||||
console.warn('Livestream status check failed', err);
|
|
||||||
livestreamStatusErrorLogged = true;
|
|
||||||
}
|
|
||||||
setLivestreamAlertVisible(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function initLivestreamStatus() {
|
|
||||||
const alertEl = document.getElementById('livestream-alert');
|
|
||||||
if (!alertEl) return;
|
|
||||||
|
|
||||||
checkLivestreamStatus();
|
|
||||||
if (!livestreamStatusIntervalId) {
|
|
||||||
livestreamStatusIntervalId = setInterval(checkLivestreamStatus, LIVESTREAM_POLL_INTERVAL_MS);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function initAppPage() {
|
function initAppPage() {
|
||||||
attachEventListeners();
|
attachEventListeners();
|
||||||
initTranscriptModal();
|
initTranscriptModal();
|
||||||
initLivestreamStatus();
|
|
||||||
if (typeof window.initSearch === 'function') window.initSearch();
|
if (typeof window.initSearch === 'function') window.initSearch();
|
||||||
if (typeof window.initMessages === 'function') window.initMessages();
|
if (typeof window.initMessages === 'function') window.initMessages();
|
||||||
if (typeof window.initGallery === 'function') window.initGallery();
|
if (typeof window.initGallery === 'function') window.initGallery();
|
||||||
|
|||||||
@ -21,13 +21,6 @@
|
|||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="container">
|
|
||||||
<div id="livestream-alert" class="alert alert-warning livestream-alert d-none" role="status" aria-live="polite">
|
|
||||||
<span>Zurzeit findet ein Livestream statt.</span>
|
|
||||||
<a class="alert-link livestream-button" id="livestream-link" href="{{ livestream_url }}" target="_blank" rel="noopener">Zum Livestream wechseln</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Tab Navigation -->
|
<!-- Tab Navigation -->
|
||||||
{% if features %}
|
{% if features %}
|
||||||
<nav class="main-tabs">
|
<nav class="main-tabs">
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user