filoor f4a18ce8b4 feat(debug): beveilig debug functionaliteit met debug_mode
Debug functionaliteit is nu alleen toegankelijk wanneer debug_mode is ingeschakeld. Dit omvat het omleiden van gebruikers naar de hoofdpagina als debug_mode uitgeschakeld is en het tonen van een foutmelding bij een niet-geautoriseerde API-aanroep. Ook is de debug knop in de UI alleen zichtbaar als debug_mode aan staat. Dit verhoogt de veiligheid en beperkt toegang tot debugging opties.
2025-05-28 07:33:16 +02:00

645 lines
24 KiB
Python

from flask import Flask, render_template, request, redirect, send_from_directory, jsonify, request as flask_request
import requests, json, os, random
from datetime import datetime
from config import *
from hijridate import Gregorian
app = Flask(__name__)
# Voeg cache-control headers toe voor statische bestanden
@app.after_request
def add_header(response):
if 'Cache-Control' not in response.headers:
response.headers['Cache-Control'] = 'no-store, no-cache, must-revalidate, max-age=0'
return response
# Specifieke route voor statische bestanden met cache-control
@app.route('/static/<path:filename>')
def serve_static(filename):
return send_from_directory('static', filename, cache_timeout=0)
SETTINGS_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'settings.json')
HADITHS_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'static', 'hadiths.json')
VUMG_API = 'https://www.vumg.nl/?rest_route=/dpt/v1/prayertime&filter=today'
CLIPS_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'static', 'clips')
SONOS_API_IP = '192.168.0.114' # IP van host waar Sonos API draait
def fetch_weather_data():
"""Haalt weersinformatie op voor de geconfigureerde locatie"""
try:
params = {
'q': WEATHER_LOCATION,
'appid': OPENWEATHER_API_KEY,
'units': 'metric',
'lang': 'nl'
}
response = requests.get('https://api.openweathermap.org/data/2.5/weather', params=params, timeout=5)
response.raise_for_status()
data = response.json()
weather_info = {
'temperature': round(data['main']['temp']),
'feels_like': round(data['main']['feels_like']),
'description': data['weather'][0]['description'].capitalize(),
'humidity': data['main']['humidity'],
'wind_speed': round(data['wind']['speed'] * 3.6), # m/s naar km/h
'icon': data['weather'][0]['icon']
}
return weather_info
except Exception as e:
print(f"⚠️ Fout bij ophalen weerdata: {e}")
return {
'temperature': '--',
'feels_like': '--',
'description': 'Weer niet beschikbaar',
'humidity': '--',
'wind_speed': '--',
'icon': '01d'
}
def load_hadith():
try:
with open(HADITHS_PATH) as f:
hadiths = json.load(f)['hadiths']
return random.choice(hadiths)
except Exception as e:
print(f"⚠️ Fout bij laden van hadith: {e}")
return {"text": "De daden die Allah het meest liefheeft zijn degenen die regelmatig worden verricht, zelfs als ze klein zijn.", "bron": "Sahih al-Bukhari"}
def load_settings():
try:
if os.path.exists(SETTINGS_PATH) and os.path.getsize(SETTINGS_PATH) > 0:
with open(SETTINGS_PATH) as f:
return json.load(f)
except Exception as e:
print(f"⚠️ Fout bij laden van settings: {e}")
return {"volume": 30, "zones": ["Woonkamer"], "audio_clip": "adhan1.mp3"}
def get_current_volume(settings):
"""Bepaal het juiste volume op basis van de huidige tijd"""
from datetime import datetime
# Fallback naar oude volume als nieuwe instellingen niet bestaan
if 'volume_day' not in settings or 'volume_night' not in settings:
return settings.get('volume', 30)
current_time = datetime.now().strftime('%H:%M')
night_start = settings.get('night_start', '20:00')
day_start = settings.get('day_start', '07:00')
# Converteer tijden naar vergelijkbare format
current_minutes = int(current_time[:2]) * 60 + int(current_time[3:5])
night_minutes = int(night_start[:2]) * 60 + int(night_start[3:5])
day_minutes = int(day_start[:2]) * 60 + int(day_start[3:5])
# Bepaal of het avond is
if night_minutes > day_minutes: # Normale dag/nacht cyclus (bijv. dag 07:00-20:00)
is_night = current_minutes >= night_minutes or current_minutes < day_minutes
else: # Avond gaat over middernacht (bijv. avond 20:00-07:00)
is_night = current_minutes >= night_minutes and current_minutes < day_minutes
if is_night:
return settings.get('volume_night', settings.get('volume', 30) // 2)
else:
return settings.get('volume_day', settings.get('volume', 30))
def apply_prayer_offsets(gebedstijden, settings):
"""Pas offsets toe op gebedstijden"""
from datetime import datetime, timedelta
offsets = {
'Fajr': settings.get('fajr_offset', 0),
'Zuhr': settings.get('zuhr_offset', 0),
'Asr': settings.get('asr_offset', 0),
'Maghrib': settings.get('maghrib_offset', 0),
'Isha': settings.get('isha_offset', 0)
}
adjusted_times = {}
for prayer, original_time in gebedstijden.items():
offset_minutes = offsets.get(prayer, 0)
if offset_minutes == 0:
adjusted_times[prayer] = original_time
continue
try:
# Parse de originele tijd
time_obj = datetime.strptime(original_time, '%H:%M')
# Voeg offset toe
adjusted_time = time_obj + timedelta(minutes=offset_minutes)
# Converteer terug naar string
adjusted_times[prayer] = adjusted_time.strftime('%H:%M')
if offset_minutes != 0:
print(f"📅 {prayer}: {original_time}{adjusted_times[prayer]} ({offset_minutes:+d} min)")
except Exception as e:
print(f"⚠️ Fout bij aanpassen {prayer} tijd: {e}")
adjusted_times[prayer] = original_time
return adjusted_times
def fetch_sonos_zones():
try:
res = requests.get(f'http://{SONOS_API_IP}:5005/zones', timeout=5)
res.raise_for_status()
data = res.json()
zones = []
for group in data:
for player in group['members']:
zones.append(player['roomName'])
return sorted(set(zones))
except Exception as e:
print(f'Fout bij ophalen Sonos-zones: {e}')
return ['Woonkamer', 'Slaapkamer', 'Keuken']
def get_date_info():
"""Genereert zowel Gregoriaanse als Hijri datum informatie"""
now = datetime.now()
# Gregoriaanse datum
gregorian_date = now.strftime('%A %d %B %Y')
gregorian_short = now.strftime('%d-%m-%Y')
# Nederlandse maandnamen
dutch_months = {
'January': 'januari', 'February': 'februari', 'March': 'maart',
'April': 'april', 'May': 'mei', 'June': 'juni',
'July': 'juli', 'August': 'augustus', 'September': 'september',
'October': 'oktober', 'November': 'november', 'December': 'december'
}
# Nederlandse dagnamen
dutch_days = {
'Monday': 'maandag', 'Tuesday': 'dinsdag', 'Wednesday': 'woensdag',
'Thursday': 'donderdag', 'Friday': 'vrijdag', 'Saturday': 'zaterdag', 'Sunday': 'zondag'
}
# Vervang Engelse namen door Nederlandse
for eng, nl in dutch_months.items():
gregorian_date = gregorian_date.replace(eng, nl)
for eng, nl in dutch_days.items():
gregorian_date = gregorian_date.replace(eng, nl)
try:
# Converteer naar Hijri datum
hijri_date = Gregorian(now.year, now.month, now.day).to_hijri()
# Arabische maandnamen
hijri_months = [
'محرم', 'صفر', 'ربيع الأول', 'ربيع الثاني', 'جمادى الأولى', 'جمادى الثانية',
'رجب', 'شعبان', 'رمضان', 'شوال', 'ذو القعدة', 'ذو الحجة'
]
# Nederlandse maandnamen voor Hijri
hijri_months_nl = [
'Muharram', 'Safar', 'Rabi al-Awwal', 'Rabi al-Thani', 'Jumada al-Awwal', 'Jumada al-Thani',
'Rajab', 'Sha\'ban', 'Ramadan', 'Shawwal', 'Dhu al-Qi\'dah', 'Dhu al-Hijjah'
]
hijri_arabic = f"{hijri_date.day} {hijri_months[hijri_date.month - 1]} {hijri_date.year} هـ"
hijri_dutch = f"{hijri_date.day} {hijri_months_nl[hijri_date.month - 1]} {hijri_date.year} AH"
except Exception as e:
print(f"⚠️ Fout bij Hijri conversie: {e}")
hijri_arabic = "التاريخ الهجري غير متاح"
hijri_dutch = "Hijri datum niet beschikbaar"
return {
'gregorian_full': gregorian_date,
'gregorian_short': gregorian_short,
'hijri_arabic': hijri_arabic,
'hijri_dutch': hijri_dutch,
'current_time': now.strftime('%H:%M')
}
@app.route('/')
def index():
settings = load_settings()
next_time = "Onbekend"
next_name = "Onbekend"
debug_data = {}
gebedstijden = {}
hadith = load_hadith()
weather = fetch_weather_data()
date_info = get_date_info()
try:
res = requests.get(VUMG_API)
res.raise_for_status()
data = res.json()[0]
gebedstijden = {
"Fajr": data.get("fajr_jamah", "00:00")[:5], # Haal seconden weg
"Zuhr": data.get("zuhr_jamah", "00:00")[:5], # Haal seconden weg
"Asr": data.get("asr_jamah", "00:00")[:5], # Haal seconden weg
"Maghrib": data.get("maghrib_jamah", "00:00")[:5], # Haal seconden weg
"Isha": data.get("isha_jamah", "00:00")[:5] # Haal seconden weg
}
# Pas offsets toe op gebedstijden
gebedstijden = apply_prayer_offsets(gebedstijden, settings)
now = datetime.now().strftime('%H:%M')
for naam, tijd in gebedstijden.items():
if tijd > now:
next_time = tijd
next_name = naam
break
else:
next_time = list(gebedstijden.values())[0]
next_name = list(gebedstijden.keys())[0]
debug_data = {
"api_response": data,
"gebedstijden": gebedstijden,
"now": now,
"next_time": next_time,
"next_name": next_name
}
except Exception as e:
print("❌ Fout bij ophalen/verwerken van gebedstijden:", e)
dua = "اللّهُمَّ اجْعَلْ صَلاتِي نُورًا"
return render_template('index.html',
next_time=next_time,
next_name=next_name,
dua=dua,
hadith=hadith,
gebedstijden=gebedstijden,
debug=debug_data,
settings=settings,
weather=weather,
date_info=date_info)
@app.route('/instellingen', methods=['GET', 'POST'])
def instellingen():
settings = load_settings()
clips = [f for f in os.listdir(CLIPS_PATH) if f.endswith('.mp3') or f.endswith('.wav')]
alle_zones = fetch_sonos_zones()
if request.method == 'POST':
# Nieuwe volume instellingen
if 'volume_day' in request.form and 'volume_night' in request.form:
settings['volume_day'] = int(request.form['volume_day'])
settings['volume_night'] = int(request.form['volume_night'])
settings['night_start'] = request.form['night_start']
settings['day_start'] = request.form['day_start']
# Update oude volume voor backward compatibility
settings['volume'] = settings['volume_day']
else:
# Fallback: gebruik default waarden als velden ontbreken
settings['volume_day'] = settings.get('volume_day', 15)
settings['volume_night'] = settings.get('volume_night', 8)
settings['night_start'] = settings.get('night_start', '20:00')
settings['day_start'] = settings.get('day_start', '07:00')
settings['volume'] = settings['volume_day']
# Gebedstijd offsets
settings['fajr_offset'] = int(request.form.get('fajr_offset', 0))
settings['zuhr_offset'] = int(request.form.get('zuhr_offset', 0))
settings['asr_offset'] = int(request.form.get('asr_offset', 0))
settings['maghrib_offset'] = int(request.form.get('maghrib_offset', 0))
settings['isha_offset'] = int(request.form.get('isha_offset', 0))
# Adzkaar instellingen
settings['adzkaar_enabled'] = 'adzkaar_enabled' in request.form
settings['adzkaar_duration'] = int(request.form.get('adzkaar_duration', 5))
settings['zones'] = request.form.getlist('zones')
settings['audio_clip'] = request.form['audio_clip']
with open(SETTINGS_PATH, 'w') as f:
json.dump(settings, f, indent=2)
if 'test_clip' in request.form:
# Gebruik het juiste volume voor de test
test_volume = get_current_volume(settings)
for zone in settings['zones']:
url = f"http://{SONOS_API_IP}:5005/{zone}/clip/{settings['audio_clip']}/{test_volume}"
try:
r = requests.get(url, timeout=3)
print(f"🎵 Test clip verzonden naar {zone}, status: {r.status_code}")
except Exception as e:
print(f"❌ Fout bij afspelen op zone {zone}: {e}")
return redirect('/instellingen')
return render_template('settings.html',
settings=settings,
alle_zones=alle_zones,
clips=clips)
@app.route('/api/hadith')
def api_hadith():
hadith = load_hadith()
return jsonify(hadith)
@app.route('/api/mute', methods=['POST'])
def toggle_mute():
try:
data = flask_request.get_json()
mute = data.get('mute', False)
with open(SETTINGS_PATH, 'r+') as f:
settings = json.load(f)
settings['mute'] = mute
f.seek(0)
json.dump(settings, f, indent=2)
f.truncate()
return jsonify({'success': True, 'mute': mute})
except Exception as e:
return jsonify({'success': False, 'error': str(e)}), 500
@app.route('/api/weather')
def api_weather():
"""API endpoint voor het ophalen van actuele weerdata"""
weather = fetch_weather_data()
return jsonify(weather)
@app.route('/api/date-info')
def api_date_info():
"""API endpoint voor het ophalen van actuele datum informatie"""
date_info = get_date_info()
return jsonify(date_info)
@app.route('/api/test-audio', methods=['POST'])
def test_audio():
"""Test een audio bestand lokaal in de browser"""
try:
data = flask_request.get_json()
audio_file = data.get('audio_file', 'adhan1.mp3')
# Controleer of het bestand bestaat
audio_path = os.path.join(CLIPS_PATH, audio_file)
if not os.path.exists(audio_path):
return jsonify({'success': False, 'error': 'Audio bestand niet gevonden'}), 404
return jsonify({
'success': True,
'message': f'Audio bestand {audio_file} is beschikbaar',
'url': f'/static/clips/{audio_file}'
})
except Exception as e:
return jsonify({'success': False, 'error': str(e)}), 500
@app.route('/debug')
def debug():
"""Debug pagina voor het testen van adhaan functionaliteit"""
settings = load_settings()
# Check of debug mode is ingeschakeld
if not settings.get('debug_mode', False):
return redirect('/')
gebedstijden = {}
try:
res = requests.get(VUMG_API)
res.raise_for_status()
data = res.json()[0]
gebedstijden = {
"Fajr": data.get("fajr_jamah", "00:00")[:5], # Haal seconden weg
"Zuhr": data.get("zuhr_jamah", "00:00")[:5], # Haal seconden weg
"Asr": data.get("asr_jamah", "00:00")[:5], # Haal seconden weg
"Maghrib": data.get("maghrib_jamah", "00:00")[:5], # Haal seconden weg
"Isha": data.get("isha_jamah", "00:00")[:5] # Haal seconden weg
}
# Pas offsets toe op gebedstijden
gebedstijden = apply_prayer_offsets(gebedstijden, settings)
except Exception as e:
print("❌ Fout bij ophalen gebedstijden voor debug:", e)
# Fallback tijden voor testing
gebedstijden = {
"Fajr": "06:00",
"Zuhr": "12:30",
"Asr": "15:00",
"Maghrib": "17:30",
"Isha": "19:00"
}
# Pas ook offsets toe op fallback tijden
gebedstijden = apply_prayer_offsets(gebedstijden, settings)
return render_template('debug.html',
gebedstijden=gebedstijden,
settings=settings)
@app.route('/api/debug-adhaan', methods=['POST'])
def debug_adhaan():
"""API endpoint voor debug adhaan testing"""
import time
# Check of debug mode is ingeschakeld
settings = load_settings()
if not settings.get('debug_mode', False):
return jsonify({'success': False, 'error': 'Debug mode is niet ingeschakeld'}), 403
try:
data = flask_request.get_json()
prayer_name = data.get('prayer', 'Test')
test_time = data.get('time', 'Onbekend')
# Check mute status
if settings.get('mute', False):
return jsonify({
'success': False,
'error': 'Adhaan niet afgespeeld: mute staat aan',
'muted': True
})
# Verbeterde Sonos afspeling
success_count = 0
error_messages = []
zones = settings.get('zones', ['Woonkamer'])
audio_clip = settings.get('audio_clip', 'adhan1.mp3')
volume = get_current_volume(settings)
for zone in zones:
try:
# Stop eerst alle audio op deze zone
stop_url = f"http://{SONOS_API_IP}:5005/{zone}/pause"
requests.get(stop_url, timeout=2)
time.sleep(0.5) # Korte pauze
# Speel de clip af
clip_url = f"http://{SONOS_API_IP}:5005/{zone}/clip/{audio_clip}/{volume}"
response = requests.get(clip_url, timeout=10)
if response.status_code == 200:
success_count += 1
print(f"🔧 DEBUG: Adhaan afgespeeld op {zone} voor {prayer_name} om {test_time}")
else:
error_messages.append(f"{zone}: HTTP {response.status_code}")
except requests.exceptions.Timeout:
error_messages.append(f"{zone}: Timeout")
except Exception as e:
error_messages.append(f"{zone}: {str(e)}")
# Pauze tussen zones
if len(zones) > 1:
time.sleep(1)
if success_count > 0:
return jsonify({
'success': True,
'message': f'Adhaan afgespeeld op {success_count}/{len(zones)} zone(s)',
'errors': error_messages if error_messages else None
})
else:
return jsonify({
'success': False,
'error': f'Geen zones bereikbaar: {", ".join(error_messages)}'
})
except Exception as e:
return jsonify({'success': False, 'error': str(e)}), 500
@app.route('/api/sonos-status')
def sonos_status():
"""API endpoint voor Sonos status controle"""
try:
settings = load_settings()
zones = settings.get('zones', ['Woonkamer'])
status_info = {}
for zone in zones:
try:
# Haal zone status op
status_url = f"http://{SONOS_API_IP}:5005/{zone}/state"
response = requests.get(status_url, timeout=3)
if response.status_code == 200:
zone_data = response.json()
status_info[zone] = {
'status': 'online',
'playbackState': zone_data.get('playbackState', 'unknown'),
'currentTrack': zone_data.get('currentTrack', {}).get('title', 'Geen track'),
'volume': zone_data.get('volume', 0)
}
else:
status_info[zone] = {'status': 'error', 'error': f'HTTP {response.status_code}'}
except Exception as e:
status_info[zone] = {'status': 'offline', 'error': str(e)}
return jsonify({
'success': True,
'zones': status_info,
'api_ip': SONOS_API_IP
})
except Exception as e:
return jsonify({'success': False, 'error': str(e)}), 500
@app.route('/api/sonos-stop', methods=['POST'])
def sonos_stop():
"""API endpoint om alle Sonos zones te stoppen"""
try:
settings = load_settings()
zones = settings.get('zones', ['Woonkamer'])
stopped_zones = []
for zone in zones:
try:
stop_url = f"http://{SONOS_API_IP}:5005/{zone}/pause"
response = requests.get(stop_url, timeout=3)
if response.status_code == 200:
stopped_zones.append(zone)
except Exception as e:
print(f"Fout bij stoppen {zone}: {e}")
return jsonify({
'success': True,
'message': f'Audio gestopt op {len(stopped_zones)} zone(s)',
'stopped_zones': stopped_zones
})
except Exception as e:
return jsonify({'success': False, 'error': str(e)}), 500
@app.route('/quran')
def quran():
"""Quran speler pagina"""
settings = load_settings()
gebedstijden = {}
try:
res = requests.get(VUMG_API)
res.raise_for_status()
data = res.json()[0]
gebedstijden = {
"Fajr": data.get("fajr_jamah", "00:00")[:5], # Haal seconden weg
"Zuhr": data.get("zuhr_jamah", "00:00")[:5], # Haal seconden weg
"Asr": data.get("asr_jamah", "00:00")[:5], # Haal seconden weg
"Maghrib": data.get("maghrib_jamah", "00:00")[:5], # Haal seconden weg
"Isha": data.get("isha_jamah", "00:00")[:5] # Haal seconden weg
}
# Pas offsets toe op gebedstijden
gebedstijden = apply_prayer_offsets(gebedstijden, settings)
except Exception as e:
print("❌ Fout bij ophalen gebedstijden voor quran pagina:", e)
# Fallback tijden
gebedstijden = {
"Fajr": "06:00",
"Zuhr": "12:30",
"Asr": "15:00",
"Maghrib": "17:30",
"Isha": "19:00"
}
# Pas ook offsets toe op fallback tijden
gebedstijden = apply_prayer_offsets(gebedstijden, settings)
return render_template('quran.html',
gebedstijden=gebedstijden,
settings=settings)
@app.route('/adzkaar')
def adzkaar():
"""Adzkaar pagina na gebed"""
settings = load_settings()
duration_minutes = settings.get('adzkaar_duration', 5) # Default 5 minuten
return render_template('adzkaar.html',
duration_minutes=duration_minutes,
settings=settings)
@app.route('/api/trigger-adzkaar', methods=['POST'])
def trigger_adzkaar():
"""API endpoint om Adzkaar scherm te triggeren (voor debug)"""
try:
settings = load_settings()
# Check of Adzkaar is ingeschakeld
if not settings.get('adzkaar_enabled', True):
return jsonify({
'success': False,
'error': 'Adzkaar is uitgeschakeld in instellingen'
})
duration = settings.get('adzkaar_duration', 5)
return jsonify({
'success': True,
'message': f'Adzkaar scherm wordt getoond voor {duration} minuten',
'duration': duration,
'url': '/adzkaar'
})
except Exception as e:
return jsonify({'success': False, 'error': str(e)}), 500
if __name__ == '__main__':
app.run(host='0.0.0.0', port=80)