Annoucement Web -Depreciated
This commit is contained in:
@@ -0,0 +1,375 @@
|
||||
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)
|
||||
Reference in New Issue
Block a user