diff --git a/package/secubox/luci-app-cyberfeed/Makefile b/package/secubox/luci-app-cyberfeed/Makefile index f9e77cbf..33703b52 100644 --- a/package/secubox/luci-app-cyberfeed/Makefile +++ b/package/secubox/luci-app-cyberfeed/Makefile @@ -10,8 +10,8 @@ LUCI_DEPENDS:=+secubox-app-cyberfeed +luci-base +luci-compat LUCI_PKGARCH:=all PKG_NAME:=luci-app-cyberfeed -PKG_VERSION:=0.1.0 -PKG_RELEASE:=3 +PKG_VERSION:=0.1.1 +PKG_RELEASE:=1 PKG_MAINTAINER:=CyberMind PKG_LICENSE:=MIT diff --git a/package/secubox/luci-app-cyberfeed/htdocs/luci-static/resources/view/cyberfeed/settings.js b/package/secubox/luci-app-cyberfeed/htdocs/luci-static/resources/view/cyberfeed/settings.js index 4399e1cc..23741780 100644 --- a/package/secubox/luci-app-cyberfeed/htdocs/luci-static/resources/view/cyberfeed/settings.js +++ b/package/secubox/luci-app-cyberfeed/htdocs/luci-static/resources/view/cyberfeed/settings.js @@ -51,10 +51,14 @@ return view.extend({ E('div', { 'class': 'cf-grid cf-grid-2', 'style': 'gap: 20px;' }, [ E('div', { 'class': 'cf-form-group' }, [ E('label', { 'class': 'cf-form-label' }, 'Service Enabled'), - E('select', { 'id': 'cfg-enabled', 'class': 'cf-form-input' }, [ - E('option', { 'value': '1', 'selected': config.enabled == 1 }, 'Enabled'), - E('option', { 'value': '0', 'selected': config.enabled == 0 }, 'Disabled') - ]) + (function() { + var sel = E('select', { 'id': 'cfg-enabled', 'class': 'cf-form-input' }, [ + E('option', { 'value': '1' }, 'Enabled'), + E('option', { 'value': '0' }, 'Disabled') + ]); + sel.value = (config.enabled == 1) ? '1' : '0'; + return sel; + })() ]), E('div', { 'class': 'cf-form-group' }, [ E('label', { 'class': 'cf-form-label' }, 'Refresh Interval (minutes)'), @@ -110,10 +114,14 @@ return view.extend({ E('div', { 'class': 'cf-grid cf-grid-2', 'style': 'gap: 20px;' }, [ E('div', { 'class': 'cf-form-group' }, [ E('label', { 'class': 'cf-form-label' }, 'RSS-Bridge Enabled'), - E('select', { 'id': 'cfg-rssbridge-enabled', 'class': 'cf-form-input' }, [ - E('option', { 'value': '1', 'selected': config.rssbridge_enabled == 1 }, 'Enabled'), - E('option', { 'value': '0', 'selected': config.rssbridge_enabled == 0 }, 'Disabled') - ]) + (function() { + var sel = E('select', { 'id': 'cfg-rssbridge-enabled', 'class': 'cf-form-input' }, [ + E('option', { 'value': '1' }, 'Enabled'), + E('option', { 'value': '0' }, 'Disabled') + ]); + sel.value = (config.rssbridge_enabled == 1) ? '1' : '0'; + return sel; + })() ]), E('div', { 'class': 'cf-form-group' }, [ E('label', { 'class': 'cf-form-label' }, 'RSS-Bridge Port'), diff --git a/package/secubox/secubox-app-cyberfeed/Makefile b/package/secubox/secubox-app-cyberfeed/Makefile index 53f16b27..7e75eed6 100644 --- a/package/secubox/secubox-app-cyberfeed/Makefile +++ b/package/secubox/secubox-app-cyberfeed/Makefile @@ -5,7 +5,7 @@ include $(TOPDIR)/rules.mk PKG_NAME:=secubox-app-cyberfeed -PKG_VERSION:=0.1.0 +PKG_VERSION:=0.2.1 PKG_RELEASE:=1 PKG_MAINTAINER:=CyberMind diff --git a/package/secubox/secubox-app-cyberfeed/files/etc/config/cyberfeed b/package/secubox/secubox-app-cyberfeed/files/etc/config/cyberfeed index f86600f1..fe5229ac 100644 --- a/package/secubox/secubox-app-cyberfeed/files/etc/config/cyberfeed +++ b/package/secubox/secubox-app-cyberfeed/files/etc/config/cyberfeed @@ -5,6 +5,10 @@ config cyberfeed 'main' option cache_ttl '300' option output_dir '/tmp/cyberfeed/output' option theme 'cyberpunk' + option download_media '0' + option media_dir '/srv/cyberfeed/media' + option history_file '/var/lib/cyberfeed/history.json' + option generate_timeline '1' config rssbridge 'rssbridge' option enabled '0' diff --git a/package/secubox/secubox-app-cyberfeed/files/usr/bin/cyberfeed b/package/secubox/secubox-app-cyberfeed/files/usr/bin/cyberfeed index 4c9b7221..65ecd547 100644 --- a/package/secubox/secubox-app-cyberfeed/files/usr/bin/cyberfeed +++ b/package/secubox/secubox-app-cyberfeed/files/usr/bin/cyberfeed @@ -1,7 +1,7 @@ #!/bin/sh # ╔═══════════════════════════════════════════════════════════════════╗ -# ║ ⚡ CYBERFEED v0.1 - RSS Aggregator for OpenWrt/SecuBox ⚡ ║ -# ║ Cyberpunk Social Feed Analyzer with Emoji Enhancement ║ +# ║ ⚡ CYBERFEED v0.2 - RSS Aggregator for OpenWrt/SecuBox ⚡ ║ +# ║ Cyberpunk Feed Analyzer with Timeline & Audio Preview ║ # ║ Author: CyberMind.FR | License: MIT ║ # ╚═══════════════════════════════════════════════════════════════════╝ @@ -12,9 +12,12 @@ CYBERFEED_DIR="/tmp/cyberfeed" CACHE_DIR="${CYBERFEED_DIR}/cache" OUTPUT_DIR="${CYBERFEED_DIR}/output" CONFIG_FILE="/etc/cyberfeed/feeds.conf" -TEMPLATE_FILE="/usr/share/cyberfeed/template.html" +HISTORY_FILE="/var/lib/cyberfeed/history.json" +MEDIA_DIR="/srv/cyberfeed/media" MAX_ITEMS=20 CACHE_TTL=300 +DOWNLOAD_MEDIA=0 +GENERATE_TIMELINE=1 # Load UCI config load_config() { @@ -22,33 +25,55 @@ load_config() { config_get MAX_ITEMS main max_items 20 config_get CACHE_TTL main cache_ttl 300 config_get OUTPUT_DIR main output_dir "/tmp/cyberfeed/output" + config_get HISTORY_FILE main history_file "/var/lib/cyberfeed/history.json" + config_get MEDIA_DIR main media_dir "/srv/cyberfeed/media" + config_get DOWNLOAD_MEDIA main download_media 0 + config_get GENERATE_TIMELINE main generate_timeline 1 +} + +# === HISTORY MANAGEMENT === +init_history() { + local dir=$(dirname "$HISTORY_FILE") + mkdir -p "$dir" + [ -f "$HISTORY_FILE" ] || echo '{"seen":[],"downloaded":[]}' > "$HISTORY_FILE" +} + +is_seen() { + local id="$1" + grep -q "\"$id\"" "$HISTORY_FILE" 2>/dev/null +} + +mark_seen() { + local id="$1" + if [ -f "$HISTORY_FILE" ]; then + local seen=$(jsonfilter -i "$HISTORY_FILE" -e '@.seen' 2>/dev/null || echo '[]') + # Simple append (proper JSON manipulation would need jq) + sed -i "s/\"seen\":\[/\"seen\":[\"$id\",/" "$HISTORY_FILE" 2>/dev/null + fi } # === CYBERPUNK EMOJI MAPPING === cyberpunk_emojify() { local text="$1" - - # Security/Hacking themes echo "$text" | sed -E ' - s/(hack|breach|exploit|vulnerab)/\xf0\x9f\x94\x93\1/gi - s/(secur|protect|defense|firewall)/\xf0\x9f\x9b\xa1\xef\xb8\x8f\1/gi - s/(cyber|digital|virtual)/\xe2\x9a\xa1\1/gi - s/(encrypt|crypto|cipher)/\xf0\x9f\x94\x90\1/gi - s/(malware|virus|trojan)/\xe2\x98\xa0\xef\xb8\x8f\1/gi - s/(alert|warning|danger)/\xe2\x9a\xa0\xef\xb8\x8f\1/gi - s/(attack|threat|risk)/\xf0\x9f\x92\x80\1/gi - s/(network|connect|link)/\xf0\x9f\x8c\x90\1/gi - s/(server|cloud|data)/\xf0\x9f\x92\xbe\1/gi - s/(code|program|script)/\xf0\x9f\x92\xbb\1/gi - s/(linux|opensource|github)/\xf0\x9f\x90\xa7\1/gi - s/(robot|automat|ai|machine)/\xf0\x9f\xa4\x96\1/gi - s/(update|upgrade|patch)/\xf0\x9f\x93\xa1\1/gi - s/(launch|deploy|release)/\xf0\x9f\x9a\x80\1/gi - s/(new|annonce|breaking)/\xe2\x9c\xa8\1/gi - s/(success|win|achieve)/\xf0\x9f\x8f\x86\1/gi - s/(fail|error|bug)/\xf0\x9f\x90\x9b\1/gi - s/(magic|mystiq|oracle)/\xf0\x9f\x94\xae\1/gi - s/(energy|power|force)/\xe2\x9a\xa1\1/gi + s/(hack|breach|exploit|vulnerab)/🔓\1/gi + s/(secur|protect|defense|firewall)/🛡️\1/gi + s/(cyber|digital|virtual)/⚡\1/gi + s/(encrypt|crypto|cipher)/🔐\1/gi + s/(malware|virus|trojan)/☠️\1/gi + s/(alert|warning|danger)/⚠️\1/gi + s/(attack|threat|risk)/💀\1/gi + s/(network|connect|link)/🌐\1/gi + s/(server|cloud|data)/💾\1/gi + s/(code|program|script)/💻\1/gi + s/(linux|opensource|github)/🐧\1/gi + s/(robot|automat|ai|machine)/🤖\1/gi + s/(update|upgrade|patch)/📡\1/gi + s/(launch|deploy|release)/🚀\1/gi + s/(podcast|radio|audio)/🎧\1/gi + s/(video|stream|watch)/📺\1/gi + s/(music|song|album)/🎵\1/gi + s/(new|annonce|breaking)/✨\1/gi ' } @@ -58,7 +83,6 @@ fetch_feed() { local name="$2" local cache_file="${CACHE_DIR}/${name}.xml" - # Check cache freshness if [ -f "$cache_file" ]; then local file_time=$(stat -c %Y "$cache_file" 2>/dev/null || echo 0) local now=$(date +%s) @@ -69,7 +93,6 @@ fetch_feed() { fi fi - # Fetch with wget (OpenWrt standard) wget -q -T 15 -O "$cache_file" "$url" 2>/dev/null if [ -f "$cache_file" ] && [ -s "$cache_file" ]; then @@ -80,63 +103,321 @@ fetch_feed() { fi } -# === RSS PARSER (Pure AWK for OpenWrt) === +# === ENHANCED RSS PARSER (BusyBox AWK compatible) === parse_rss() { local xml="$1" local source="$2" local category="$3" echo "$xml" | awk -v source="$source" -v category="$category" -v max="$MAX_ITEMS" ' + # Helper: extract content between XML tags + function extract_tag(str, tag, start, end, rest, content) { + # Try content + if (match(str, "<" tag "[^>]*>")) { + start = RSTART + RLENGTH + rest = substr(str, start) + if (match(rest, "")) { + content = substr(rest, 1, RSTART - 1) + # Handle CDATA + if (match(content, "^$/, "", content) + } + return content + } + } + return "" + } + + # Helper: extract attribute value + function extract_attr(str, tag, attr, tagstart, tagend, tagstr, attrpos, rest, val) { + if (match(str, "<" tag "[^>]*>")) { + tagstr = substr(str, RSTART, RLENGTH) + if (match(tagstr, attr "=\"[^\"]*\"")) { + val = substr(tagstr, RSTART + length(attr) + 2) + sub(/".*/, "", val) + return val + } + } + return "" + } + BEGIN { RS="|" item_count=0 } { - title="" - link="" - date="" - desc="" + if (item_count >= max) next - # Extract title - if (match($0, /]*>([^<]+)<\/title>/, arr)) title=arr[1] - else if (match($0, /]*><\/title>/, arr)) title=arr[1] + title = extract_tag($0, "title") + link = extract_tag($0, "link") - # Extract link - if (match($0, /]*>([^<]+)<\/link>/, arr)) link=arr[1] - else if (match($0, /]*href="([^"]+)"/, arr)) link=arr[1] + # Atom links use href attribute + if (link == "") { + link = extract_attr($0, "link", "href") + } - # Extract date - if (match($0, /([^<]+)([^<]+)([^<]+)]*>([^<]+)]*>([^<]+)]*>]+>/, "", desc) desc = substr(desc, 1, 280) - printf "{\"title\":\"%s\",\"link\":\"%s\",\"date\":\"%s\",\"desc\":\"%s\",\"source\":\"%s\",\"category\":\"%s\"},", title, link, date, desc, source, category + gsub(/\\/, "\\\\", link) + gsub(/"/, "\\\"", link) + + gsub(/\\/, "\\\\", enclosure) + gsub(/"/, "\\\"", enclosure) + + # Determine media type + media_type = "" + if (enclosure_type ~ /audio/) media_type = "audio" + else if (enclosure_type ~ /video/) media_type = "video" + else if (enclosure ~ /\.mp3|\.m4a|\.ogg|\.wav/) media_type = "audio" + else if (enclosure ~ /\.mp4|\.webm|\.mkv/) media_type = "video" + + printf "{\"title\":\"%s\",\"link\":\"%s\",\"date\":\"%s\",\"desc\":\"%s\",\"source\":\"%s\",\"category\":\"%s\",\"enclosure\":\"%s\",\"media_type\":\"%s\",\"duration\":\"%s\",\"guid\":\"%s\"},", title, link, date, desc, source, category, enclosure, media_type, duration, guid item_count++ } } ' } -# === HTML GENERATOR === +# === TIMELINE HTML GENERATOR === +generate_timeline() { + local json_file="$1" + local output_file="${OUTPUT_DIR}/timeline.html" + local timestamp=$(date '+%Y-%m-%d %H:%M:%S') + + cat > "$output_file" << 'TIMELINEHTML' + + + + + +⚡ CYBERFEED TIMELINE ⚡ + + + +
+

⚡ TIMELINE ⚡

+

Chronological Feed History

+
+ +
+

Loading timeline...

+
+ + + +TIMELINEHTML +} + +# === MAIN HTML GENERATOR (with audio player) === generate_html() { local json_file="$1" local output_file="${OUTPUT_DIR}/index.html" - local timestamp=$(date '+%Y-%m-%d %H:%M:%S') - local feed_count=$(grep -c '"title"' "$json_file" 2>/dev/null || echo 0) cat > "$output_file" << 'HTMLEOF' @@ -152,7 +433,6 @@ generate_html() { --neon-magenta: #f0f; --neon-yellow: #ff0; --dark-bg: #0a0a0f; - --darker-bg: #050508; --grid-color: rgba(0, 255, 255, 0.03); --text-primary: #e0e0e0; --text-dim: #606080; @@ -181,15 +461,6 @@ body::before { 0% { transform: translateY(0); } 100% { transform: translateY(50px); } } -body::after { - content: ''; - position: fixed; - top: 0; left: 0; - width: 100%; height: 100%; - background: repeating-linear-gradient(0deg, transparent, transparent 2px, rgba(0,0,0,0.1) 2px, rgba(0,0,0,0.1) 4px); - pointer-events: none; - z-index: 1000; -} .cyber-header { text-align: center; padding: 2rem; @@ -204,13 +475,6 @@ body::after { letter-spacing: 0.2em; color: var(--neon-cyan); text-shadow: 0 0 10px var(--neon-cyan), 0 0 20px var(--neon-cyan), 0 0 40px var(--neon-cyan); - animation: flicker 3s infinite; -} -@keyframes flicker { - 0%, 100% { opacity: 1; } - 92% { opacity: 1; } - 93% { opacity: 0.8; } - 94% { opacity: 1; } } .cyber-header .subtitle { font-size: 0.85rem; @@ -218,6 +482,26 @@ body::after { margin-top: 0.5rem; letter-spacing: 0.4em; } +.nav-bar { + display: flex; + justify-content: center; + gap: 20px; + padding: 15px; + background: rgba(0,0,0,0.3); + border-bottom: 1px solid rgba(0,255,255,0.2); +} +.nav-bar a { + color: var(--neon-cyan); + text-decoration: none; + padding: 8px 16px; + border: 1px solid var(--neon-cyan); + transition: all 0.3s; +} +.nav-bar a:hover { + background: var(--neon-cyan); + color: var(--dark-bg); + box-shadow: 0 0 15px var(--neon-cyan); +} .status-bar { display: flex; justify-content: center; @@ -264,22 +548,13 @@ body::after { padding: 1.2rem; transition: all 0.3s ease; position: relative; - overflow: hidden; -} -.feed-item::before { - content: ''; - position: absolute; - top: 0; left: 0; - width: 100%; height: 100%; - background: linear-gradient(90deg, transparent, rgba(0,255,255,0.1), transparent); - transform: translateX(-100%); - transition: transform 0.5s ease; } +.feed-item.has-audio { border-left-color: var(--neon-magenta); } +.feed-item.has-video { border-left-color: var(--neon-yellow); } .feed-item:hover { border-color: var(--neon-magenta); - box-shadow: 0 0 20px rgba(0,255,255,0.2), inset 0 0 20px rgba(255,0,255,0.05); + box-shadow: 0 0 20px rgba(0,255,255,0.2); } -.feed-item:hover::before { transform: translateX(100%); } .feed-item .meta { display: flex; justify-content: space-between; @@ -300,11 +575,10 @@ body::after { padding: 0.15rem 0.5rem; font-size: 0.6rem; text-transform: uppercase; - letter-spacing: 0.1em; } -.feed-item .category-tag { - background: rgba(0,255,255,0.2); - border: 1px solid var(--neon-cyan); +.feed-item .media-tag { + background: rgba(255,255,0,0.2); + border: 1px solid var(--neon-yellow); padding: 0.15rem 0.5rem; font-size: 0.6rem; text-transform: uppercase; @@ -320,7 +594,6 @@ body::after { .feed-item .title a { color: inherit; text-decoration: none; - transition: text-shadow 0.3s ease; } .feed-item .title a:hover { text-shadow: 0 0 10px var(--neon-cyan); } .feed-item .description { @@ -329,6 +602,14 @@ body::after { line-height: 1.5; opacity: 0.85; } +.audio-player { + margin-top: 12px; +} +.audio-player audio { + width: 100%; + height: 40px; + border-radius: 20px; +} .cyber-footer { text-align: center; padding: 2rem; @@ -337,17 +618,6 @@ body::after { color: var(--text-dim); } .cyber-footer a { color: var(--neon-cyan); text-decoration: none; } -.empty-state { - text-align: center; - padding: 4rem 2rem; - color: var(--text-dim); -} -.empty-state .icon { font-size: 4rem; margin-bottom: 1rem; } -@media (max-width: 600px) { - .feed-container { padding: 1rem; } - .feed-item { padding: 1rem; } - .status-bar { gap: 1rem; } -} @@ -360,21 +630,26 @@ body::after { STATUS: ONLINE +
+
-
-
🔮
+
+

🔮

Awaiting Neural Feed Connection...

-

⚡ CYBERFEED v0.1 | Powered by CyberMind.FR | SecuBox Module ⚡

+

⚡ CYBERFEED v0.2 | Powered by CyberMind.FR | SecuBox Module ⚡

░▒▓ JACK IN TO THE MATRIX ▓▒░