Files
Announcements/app.py
T

375 lines
13 KiB
Python
Executable File

import os
import json
import re
import random
import time # for generating a simple unique id
from datetime import datetime, timezone
from flask import Flask, render_template, request, url_for, jsonify
from flask_socketio import SocketIO
from flask_cors import CORS
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__)
# Adjust SERVER_NAME and SQLALCHEMY_DATABASE_URI as needed.
app.config['SERVER_NAME'] = '10.30.0.2:5000'
app.config['PREFERRED_URL_SCHEME'] = 'http'
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:////Users/mediarogc/Documents/Annoucements Web/bible.db'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
CORS(app)
socketio = SocketIO(app, cors_allowed_origins="*")
db = SQLAlchemy(app)
# ---------------------------
# Global configuration & Folders
# ---------------------------
# Bible verse linking and frequency features have been removed.
STATUS_FILE = 'slides_status.json'
SLIDES_FOLDER = os.path.join('static', 'slides')
ALLOWED_EXTENSIONS = {'.png', '.jpg', '.jpeg', '.gif'}
# Folder for Bible backgrounds (used elsewhere).
BIBLE_BACKGROUNDS_FOLDER = os.path.join('static', 'bible_backgrounds')
ALLOWED_BG_EXTENSIONS = {'.png', '.jpg', '.jpeg', '.gif'}
# Bible JSON file for added Bible verses (will store verse text and reference)
BIBLE_JSON_FILE = 'bible.json'
def get_slides():
if not os.path.exists(SLIDES_FOLDER):
os.makedirs(SLIDES_FOLDER)
slides = []
for filename in os.listdir(SLIDES_FOLDER):
ext = os.path.splitext(filename)[1].lower()
if ext in ALLOWED_EXTENSIONS:
slides.append(filename)
return slides
def load_slide_statuses():
if os.path.exists(STATUS_FILE):
with open(STATUS_FILE, 'r') as f:
return json.load(f)
return {}
def save_slide_statuses(statuses):
with open(STATUS_FILE, 'w') as f:
json.dump(statuses, f)
def get_slide_statuses():
statuses = load_slide_statuses()
slides = get_slides()
new_statuses = {}
max_order = max((data.get("order", 0) for data in statuses.values()), default=0)
for slide in slides:
if slide in statuses:
new_statuses[slide] = statuses[slide]
else:
max_order += 1
new_statuses[slide] = {
"active": True,
"order": max_order,
"duration": 7,
"title": slide.split('.')[0]
}
save_slide_statuses(new_statuses)
return new_statuses
# ---------------------------
# Announcement Endpoints
# ---------------------------
@app.route('/')
def index():
statuses = get_slide_statuses()
sorted_slides = sorted(statuses.items(), key=lambda x: x[1]['order'])
active_slides = [slide for slide, data in sorted_slides if data.get("active", False)]
slide_urls = [url_for('static', filename='slides/' + slide) for slide in active_slides]
return render_template('index.html', slide_urls=slide_urls)
@app.route('/control')
def control():
statuses = get_slide_statuses()
slide_urls = {slide: url_for('static', filename='slides/' + slide) for slide in statuses}
sorted_statuses = sorted(statuses.items(), key=lambda item: item[1]["order"])
return render_template('control.html', statuses=sorted_statuses, slide_urls=slide_urls)
@app.route('/update_order', methods=['POST'])
def update_order():
statuses = get_slide_statuses()
data = request.get_json()
order_list = data.get("order", [])
counter = 1
for slide in order_list:
if slide in statuses:
statuses[slide]["order"] = counter
counter += 1
remaining = [slide for slide in statuses if slide not in order_list]
remaining.sort(key=lambda s: statuses[s]["order"])
for slide in remaining:
statuses[slide]["order"] = counter
counter += 1
save_slide_statuses(statuses)
socketio.emit('refresh_index')
return jsonify({"status": "success"})
@app.route('/update_active', methods=['POST'])
def update_active():
statuses = get_slide_statuses()
data = request.get_json()
slide = data.get("slide")
active = data.get("active")
if slide in statuses:
statuses[slide]["active"] = active
save_slide_statuses(statuses)
return jsonify({"status": "success"})
return jsonify({"status": "error", "message": "Slide not found."}), 400
@app.route('/update_duration', methods=['POST'])
def update_duration():
statuses = get_slide_statuses()
data = request.get_json()
slide = data.get("slide")
duration = data.get("duration")
if slide in statuses:
try:
duration_val = float(duration)
except ValueError:
return jsonify({"status": "error", "message": "Invalid duration."}), 400
statuses[slide]["duration"] = duration_val
save_slide_statuses(statuses)
return jsonify({"status": "success"})
return jsonify({"status": "error", "message": "Slide not found."}), 400
@app.route('/update_title', methods=['POST'])
def update_title():
statuses = get_slide_statuses()
data = request.get_json()
slide = data.get("slide")
title = data.get("title")
if slide in statuses:
statuses[slide]["title"] = title
save_slide_statuses(statuses)
return jsonify({"status": "success"})
return jsonify({"status": "error", "message": "Slide not found."}), 400
@app.route('/update_end_date', methods=['POST'])
def update_end_date():
statuses = get_slide_statuses()
data = request.get_json()
slide = data.get("slide")
end_date = data.get("end_date")
if slide in statuses:
statuses[slide]["end_date"] = end_date
save_slide_statuses(statuses)
return jsonify({"status": "success"})
return jsonify({"status": "error", "message": "Slide not found."}), 400
@app.route('/delete_slide', methods=['POST'])
def delete_slide():
statuses = get_slide_statuses()
data = request.get_json()
slide = data.get("slide")
if slide in statuses:
del statuses[slide]
save_slide_statuses(statuses)
file_path = os.path.join(SLIDES_FOLDER, slide)
if os.path.exists(file_path):
os.remove(file_path)
return jsonify({"status": "success"})
return jsonify({"status": "error", "message": "Slide not found."}), 400
# ---------------------------
# Bible Verses Integration (Library and Addition to Announcements)
# ---------------------------
class BibleVerse(db.Model):
__tablename__ = 'bible_verse'
id = db.Column(db.Integer, primary_key=True)
book = db.Column(db.String(64), nullable=False)
chapter = db.Column(db.Integer, nullable=False)
verse = db.Column(db.Integer, nullable=False)
text = db.Column(db.Text, nullable=False)
translation = db.Column(db.String(64), nullable=False, default='KJV')
# Removed color and background_url since background handling is via bible.json
active_in_rotation = db.Column(db.Boolean, default=False)
@app.route('/search_verses')
def search_verses():
query = request.args.get('q', '').strip()
translation = request.args.get('translation', None)
pattern = r'^([A-Za-z0-9\s]+)\s+(\d+):(\d+)$'
if re.match(pattern, query):
match = re.match(pattern, query)
book, chapter, verse = match.groups()
chapter = int(chapter)
verse = int(verse)
q = BibleVerse.query.filter(
BibleVerse.book.ilike(book.strip()),
BibleVerse.chapter == chapter,
BibleVerse.verse == verse
)
if translation:
q = q.filter(BibleVerse.translation == translation)
verses = q.all()
else:
if query == "":
verses = BibleVerse.query.filter_by(active_in_rotation=True).all()
else:
q = BibleVerse.query.filter(BibleVerse.text.ilike(f'%{query}%'))
if translation:
q = q.filter(BibleVerse.translation == translation)
verses = q.all()
# Only return the required fields.
results = [{
'book': v.book,
'chapter': v.chapter,
'verse': v.verse,
'text': v.text,
'translation': v.translation
} for v in verses]
return jsonify(results)
def load_bible_json():
if os.path.exists(BIBLE_JSON_FILE):
with open(BIBLE_JSON_FILE, 'r') as f:
try:
return json.load(f)
except Exception as e:
print("Error loading bible.json:", e)
return []
return []
def save_bible_json(data):
with open(BIBLE_JSON_FILE, 'w') as f:
json.dump(data, f)
@app.route('/add_bible_verse', methods=['POST'])
def add_bible_verse():
"""
Expects JSON payload:
{
"verse": "For God so loved the word that whomever believes in Him shall not perish but have everlasting life.",
"reference": "John 3:16"
}
"""
data = request.get_json()
new_entry = {
"id": int(time.time()), # Simple unique id generation; adjust if needed.
"verse": data.get("verse"),
"reference": data.get("reference"),
"background": "" # By default, an empty background.
}
bible_data = load_bible_json()
bible_data.append(new_entry)
save_bible_json(bible_data)
return jsonify({"status": "success", "entry": new_entry})
@app.route('/remove_bible_verse', methods=['POST'])
def remove_bible_verse():
"""
Expects JSON payload:
{ "id": 1234567890 }
"""
data = request.get_json()
remove_id = data.get("id")
bible_data = load_bible_json()
# Filter out the entry with matching id
new_data = [entry for entry in bible_data if entry.get("id") != remove_id]
save_bible_json(new_data)
return jsonify({"status": "success"})
@app.route('/bible_registry')
def bible_registry():
"""
Returns the current list of added bible verses (from bible.json).
"""
data = load_bible_json()
return jsonify(data)
# ---------------------------
# Bible Background Helpers
# ---------------------------
def get_bible_background_images():
if not os.path.exists(BIBLE_BACKGROUNDS_FOLDER):
os.makedirs(BIBLE_BACKGROUNDS_FOLDER)
images = []
for filename in os.listdir(BIBLE_BACKGROUNDS_FOLDER):
ext = os.path.splitext(filename)[1].lower()
if ext in ALLOWED_BG_EXTENSIONS:
images.append(filename)
return images
def get_random_bible_background():
imgs = get_bible_background_images()
if imgs:
bg = random.choice(imgs)
return url_for('static', filename='bible_backgrounds/' + bg)
return ""
# ---------------------------
# Background Tasks
# ---------------------------
current_slide_index = 0
def background_slide_sync():
global current_slide_index
while True:
with app.app_context():
statuses = get_slide_statuses()
sorted_slides = sorted(statuses.items(), key=lambda x: x[1]['order'])
active_list = [(slide, data) for slide, data in sorted_slides if data.get("active", False)]
if active_list:
current_slide_index = (current_slide_index + 1) % len(active_list)
slide, data = active_list[current_slide_index]
slide_url = url_for('static', filename='slides/' + slide)
socketio.emit('sync_slide', {"current_slide_index": current_slide_index, "slide_url": slide_url})
duration = float(data.get("duration", 7))
else:
duration = 7
socketio.sleep(duration)
@socketio.on('connect')
def on_connect(auth=None):
global current_slide_index
with app.app_context():
statuses = get_slide_statuses()
sorted_slides = sorted(statuses.items(), key=lambda x: x[1]['order'])
active_list = [(slide, data) for slide, data in sorted_slides if data.get("active", False)]
if active_list:
index = current_slide_index % len(active_list)
slide, data = active_list[index]
slide_url = url_for('static', filename='slides/' + slide)
socketio.emit('sync_slide', {"current_slide_index": index, "slide_url": slide_url})
def scheduled_deletion_check():
while True:
with app.app_context():
statuses = get_slide_statuses()
to_delete = []
now = datetime.now(timezone.utc)
for slide, data in statuses.items():
end_date_str = data.get("end_date")
if end_date_str:
try:
end_date = datetime.fromisoformat(end_date_str)
if end_date.tzinfo is None:
end_date = end_date.replace(tzinfo=timezone.utc)
if now >= end_date:
to_delete.append(slide)
except Exception as e:
print(f"Error parsing end_date for {slide}: {e}")
if to_delete:
for slide in to_delete:
del statuses[slide]
file_path = os.path.join(SLIDES_FOLDER, slide)
if os.path.exists(file_path):
os.remove(file_path)
save_slide_statuses(statuses)
socketio.emit('refresh_index')
socketio.sleep(60)
if __name__ == '__main__':
# Uncomment the following line on first run (or when models change)
# db.create_all()
socketio.start_background_task(background_slide_sync)
socketio.start_background_task(scheduled_deletion_check)
socketio.run(app, host='0.0.0.0', port=5000, debug=True, use_reloader=False)