foxygit / infodisplay Log in
commits tags

/kiosk/addons/ticker/routes.py · 5.33 KB

raw
import re
import urllib.request as _urlreq
import xml.etree.ElementTree as _ET
from datetime import datetime as _dt, timedelta as _td
from flask import Blueprint, jsonify, request, abort
import db
import extensions
from auth import login_required

bp = Blueprint('ticker', __name__, url_prefix='/api')

_HEX_COLOR = re.compile(r'^#[0-9a-fA-F]{6}$')
_rss_cache = {'url': None, 'items': [], 'fetched_at': None}


def _fetch_rss(url, max_items=30):
    global _rss_cache
    now = _dt.now()
    if (_rss_cache['url'] == url and _rss_cache['fetched_at']
            and now - _rss_cache['fetched_at'] < _td(minutes=2)):
        return _rss_cache['items']
    try:
        req = _urlreq.Request(url, headers={'User-Agent': 'KioskRSS/1.0'})
        with _urlreq.urlopen(req, timeout=10) as r:
            raw = r.read()
        root = _ET.fromstring(raw)
        items = []
        # RSS 2.0
        for item in root.findall('.//item'):
            t = (item.findtext('title') or '').strip()
            if t:
                items.append(t)
        # Atom
        if not items:
            ns = {'a': 'http://www.w3.org/2005/Atom'}
            for entry in root.findall('.//a:entry', ns):
                t = (entry.findtext('a:title', namespaces=ns) or '').strip()
                if t:
                    items.append(t)
        _rss_cache = {'url': url, 'items': items[:max_items], 'fetched_at': now}
    except Exception:
        pass  # keep stale cache on error
    return _rss_cache['items']


@bp.route('/ticker/rss')
def get_ticker_rss():
    state = db.get_state()
    url   = state.get('ticker_rss_url', '').strip()
    if not url:
        return jsonify({'items': [], 'error': 'Ingen RSS-URL konfigurerad'})
    items = _fetch_rss(url)
    return jsonify({'items': items})


@bp.route('/ticker', methods=['GET'])
def get_ticker():
    state = db.get_state()
    return jsonify({
        'enabled':      state.get('ticker_enabled', '0') == '1',
        'source':       state.get('ticker_source', 'text'),
        'text':         state.get('ticker_text', ''),
        'rss_url':      state.get('ticker_rss_url', ''),
        'rss_interval': int(state.get('ticker_rss_interval', '10')),
        'pps':          int(state.get('ticker_pps', '150')),
        'mode':         state.get('ticker_mode', 'sequential'),
        'separator':    state.get('ticker_separator', '◆'),
        'position':     state.get('ticker_position', 'bottom'),
        'text_color':   state.get('ticker_text_color', '#ffffff'),
        'font_size':    float(state.get('ticker_font_size', '2.0')),
        'font_family':  state.get('ticker_font_family', 'sans-serif'),
        'bar_color':    state.get('ticker_bar_color', '#000000'),
        'bar_opacity':  int(state.get('ticker_bar_opacity', '72')),
        'bar_padding':  float(state.get('ticker_bar_padding', '0.45')),
    })


@bp.route('/ticker', methods=['POST'])
@login_required
def set_ticker():
    data = request.get_json()
    if not data:
        abort(400)
    enabled      = bool(data.get('enabled', False))
    source       = 'rss' if data.get('source') == 'rss' else 'text'
    text         = str(data.get('text', ''))
    rss_url      = str(data.get('rss_url', ''))
    rss_interval = max(1, min(1440, int(data.get('rss_interval', 10))))
    pps          = max(30, min(800, int(data.get('pps', 150))))
    mode         = 'continuous' if data.get('mode') == 'continuous' else 'sequential'
    separator    = str(data.get('separator', '◆'))[:20]
    position     = 'top' if data.get('position') == 'top' else 'bottom'
    text_color   = str(data.get('text_color', '#ffffff'))
    font_size    = max(0.5, min(6.0, float(data.get('font_size', 2.0))))
    font_family  = str(data.get('font_family', 'sans-serif'))
    bar_color    = str(data.get('bar_color', '#000000'))
    bar_opacity  = max(0, min(100, int(data.get('bar_opacity', 72))))
    bar_padding  = max(0.1, min(2.0, float(data.get('bar_padding', 0.45))))
    if not _HEX_COLOR.match(text_color): text_color = '#ffffff'
    if not _HEX_COLOR.match(bar_color):  bar_color  = '#000000'
    db.set_state('ticker_enabled',      '1' if enabled else '0')
    db.set_state('ticker_source',       source)
    db.set_state('ticker_text',         text)
    db.set_state('ticker_rss_url',      rss_url)
    db.set_state('ticker_rss_interval', str(rss_interval))
    db.set_state('ticker_pps',          str(pps))
    db.set_state('ticker_mode',         mode)
    db.set_state('ticker_separator',    separator)
    db.set_state('ticker_position',     position)
    db.set_state('ticker_text_color',   text_color)
    db.set_state('ticker_font_size',    str(font_size))
    db.set_state('ticker_font_family',  font_family)
    db.set_state('ticker_bar_color',    bar_color)
    db.set_state('ticker_bar_opacity',  str(bar_opacity))
    db.set_state('ticker_bar_padding',  str(bar_padding))
    # Invalidate RSS cache when URL changes
    if source == 'rss':
        _rss_cache['fetched_at'] = None
    payload = {
        'enabled': enabled, 'source': source,
        'text': text, 'rss_url': rss_url, 'rss_interval': rss_interval,
        'pps': pps, 'mode': mode, 'separator': separator, 'position': position,
        'text_color': text_color, 'font_size': font_size, 'font_family': font_family,
        'bar_color': bar_color, 'bar_opacity': bar_opacity, 'bar_padding': bar_padding,
    }
    extensions.socketio.emit('ticker', payload)
    return '', 204