← back to Allnewsdaily
initial scaffold — allnewsdaily v0.1 (97 outlets, YouTube live detection, red glowing LIVE pill)
7c2ebe766f4c5083edda78c0dcbd6c12dd45fff3 · 2026-05-20 10:43:43 -0700 · Steve Abrams
Files touched
A .deploy.confA .gitignoreA README.mdA data/outlets.jsonA package.jsonA public/index.htmlA scripts/check-live.jsA server.js
Diff
commit 7c2ebe766f4c5083edda78c0dcbd6c12dd45fff3
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Wed May 20 10:43:43 2026 -0700
initial scaffold — allnewsdaily v0.1 (97 outlets, YouTube live detection, red glowing LIVE pill)
---
.deploy.conf | 3 +
.gitignore | 10 +
README.md | 22 +
data/outlets.json | 1374 +++++++++++++++++++++++++++++++++++++++++++++++++
package.json | 17 +
public/index.html | 297 +++++++++++
scripts/check-live.js | 134 +++++
server.js | 94 ++++
8 files changed, 1951 insertions(+)
diff --git a/.deploy.conf b/.deploy.conf
new file mode 100644
index 0000000..a7c25e2
--- /dev/null
+++ b/.deploy.conf
@@ -0,0 +1,3 @@
+PROJECT_NAME=allnewsdaily
+DEPLOY_PATH=/root/Projects/allnewsdaily
+HEALTH_URL=http://127.0.0.1:9788/api/health
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..1f15e6d
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,10 @@
+node_modules/
+.env
+.env.*
+*.log
+tmp/
+dist/
+build/
+.next/
+.DS_Store
+data/live-status.json
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..c6035fa
--- /dev/null
+++ b/README.md
@@ -0,0 +1,22 @@
+# All News Daily
+
+Global news outlet directory — logo + name + country, with a **red glowing LIVE pill** on any outlet currently broadcasting live (detected via YouTube `/channel/<id>/live`).
+
+- `data/outlets.json` — curated outlet list (logos via clearbit, YouTube channel IDs for live detection)
+- `scripts/check-live.js` — polls every channel's YouTube live page, writes `data/live-status.json`
+- `server.js` — Express on `:9788`, merges outlets + live status, runs the checker every 90s
+- `public/index.html` — grid view, sort + density slider, region/lang/category filters, RED glowing LIVE pill
+
+## Run
+
+```
+npm i
+node server.js
+# → http://localhost:9788
+```
+
+## Live detection
+
+The checker hits `https://www.youtube.com/channel/<id>/live`. If the response HTML
+contains `hlsManifestUrl` (or `"isLive":true` / `"isLiveNow":true`), the outlet is
+flagged live and the LIVE pill links to that exact stream.
diff --git a/data/outlets.json b/data/outlets.json
new file mode 100644
index 0000000..040645c
--- /dev/null
+++ b/data/outlets.json
@@ -0,0 +1,1374 @@
+[
+ {
+ "id": "cnn",
+ "name": "CNN",
+ "country": "US",
+ "flag": "🇺🇸",
+ "href": "https://www.cnn.com",
+ "logo": "https://logo.clearbit.com/cnn.com",
+ "youtube": "UCupvZG-5ko_eiXAupbDfxWw",
+ "liveCheck": "youtube",
+ "liveStream": "https://www.youtube.com/@CNN/live",
+ "lang": "en",
+ "region": "North America",
+ "category": "general"
+ },
+ {
+ "id": "foxnews",
+ "name": "Fox News",
+ "country": "US",
+ "flag": "🇺🇸",
+ "href": "https://www.foxnews.com",
+ "logo": "https://logo.clearbit.com/foxnews.com",
+ "youtube": "UCXIJgqnII2ZOINSWNOGFThA",
+ "liveCheck": "youtube",
+ "liveStream": "https://www.youtube.com/@FoxNews/live",
+ "lang": "en",
+ "region": "North America",
+ "category": "general"
+ },
+ {
+ "id": "msnbc",
+ "name": "MSNBC",
+ "country": "US",
+ "flag": "🇺🇸",
+ "href": "https://www.msnbc.com",
+ "logo": "https://logo.clearbit.com/msnbc.com",
+ "youtube": "UCaXkIU1QidjPwiAYu6GcHjg",
+ "liveCheck": "youtube",
+ "liveStream": "https://www.youtube.com/@msnbc/live",
+ "lang": "en",
+ "region": "North America",
+ "category": "general"
+ },
+ {
+ "id": "nbcnews",
+ "name": "NBC News",
+ "country": "US",
+ "flag": "🇺🇸",
+ "href": "https://www.nbcnews.com",
+ "logo": "https://logo.clearbit.com/nbcnews.com",
+ "youtube": "UCeY0bbntWzzVIaj2z3QigXg",
+ "liveCheck": "youtube",
+ "liveStream": "https://www.youtube.com/@NBCNews/live",
+ "lang": "en",
+ "region": "North America",
+ "category": "general"
+ },
+ {
+ "id": "cbsnews",
+ "name": "CBS News",
+ "country": "US",
+ "flag": "🇺🇸",
+ "href": "https://www.cbsnews.com",
+ "logo": "https://logo.clearbit.com/cbsnews.com",
+ "youtube": "UC8p1vwvWtl6T73JiExfWs1g",
+ "liveCheck": "youtube",
+ "liveStream": "https://www.youtube.com/@cbsnews/live",
+ "lang": "en",
+ "region": "North America",
+ "category": "general"
+ },
+ {
+ "id": "abcnews",
+ "name": "ABC News",
+ "country": "US",
+ "flag": "🇺🇸",
+ "href": "https://abcnews.go.com",
+ "logo": "https://logo.clearbit.com/abcnews.go.com",
+ "youtube": "UCBi2mrWuNuyYy4gbM6fU18Q",
+ "liveCheck": "youtube",
+ "liveStream": "https://www.youtube.com/@ABCNews/live",
+ "lang": "en",
+ "region": "North America",
+ "category": "general"
+ },
+ {
+ "id": "pbsnewshour",
+ "name": "PBS NewsHour",
+ "country": "US",
+ "flag": "🇺🇸",
+ "href": "https://www.pbs.org/newshour/",
+ "logo": "https://logo.clearbit.com/pbs.org",
+ "youtube": "UC6ZFN9Tx6xh-skXCuRHCDpQ",
+ "liveCheck": "youtube",
+ "liveStream": "https://www.youtube.com/@PBSNewsHour/live",
+ "lang": "en",
+ "region": "North America",
+ "category": "general"
+ },
+ {
+ "id": "bloomberg",
+ "name": "Bloomberg",
+ "country": "US",
+ "flag": "🇺🇸",
+ "href": "https://www.bloomberg.com",
+ "logo": "https://logo.clearbit.com/bloomberg.com",
+ "youtube": "UCIALMKvObZNtJ6AmdCLP7Lg",
+ "liveCheck": "youtube",
+ "liveStream": "https://www.youtube.com/@markets/live",
+ "lang": "en",
+ "region": "North America",
+ "category": "finance"
+ },
+ {
+ "id": "cnbc",
+ "name": "CNBC",
+ "country": "US",
+ "flag": "🇺🇸",
+ "href": "https://www.cnbc.com",
+ "logo": "https://logo.clearbit.com/cnbc.com",
+ "youtube": "UCvJJ_dzjViJCoLf5uKUTwoA",
+ "liveCheck": "youtube",
+ "liveStream": "https://www.youtube.com/@CNBCtelevision/live",
+ "lang": "en",
+ "region": "North America",
+ "category": "finance"
+ },
+ {
+ "id": "wsj",
+ "name": "Wall Street Journal",
+ "country": "US",
+ "flag": "🇺🇸",
+ "href": "https://www.wsj.com",
+ "logo": "https://logo.clearbit.com/wsj.com",
+ "youtube": "UCK7tptUDHh-RYDsdxO1-5QQ",
+ "liveCheck": "youtube",
+ "liveStream": "https://www.youtube.com/@wsj/live",
+ "lang": "en",
+ "region": "North America",
+ "category": "finance"
+ },
+ {
+ "id": "nytimes",
+ "name": "The New York Times",
+ "country": "US",
+ "flag": "🇺🇸",
+ "href": "https://www.nytimes.com",
+ "logo": "https://logo.clearbit.com/nytimes.com",
+ "youtube": "UCqnbDFdCpuN8CMEg0VuEBqA",
+ "liveCheck": "youtube",
+ "liveStream": null,
+ "lang": "en",
+ "region": "North America",
+ "category": "general"
+ },
+ {
+ "id": "washingtonpost",
+ "name": "The Washington Post",
+ "country": "US",
+ "flag": "🇺🇸",
+ "href": "https://www.washingtonpost.com",
+ "logo": "https://logo.clearbit.com/washingtonpost.com",
+ "youtube": "UCHd62-u_v4DvJ8TCFtpi4GA",
+ "liveCheck": "youtube",
+ "liveStream": "https://www.youtube.com/@washingtonpost/live",
+ "lang": "en",
+ "region": "North America",
+ "category": "general"
+ },
+ {
+ "id": "usatoday",
+ "name": "USA Today",
+ "country": "US",
+ "flag": "🇺🇸",
+ "href": "https://www.usatoday.com",
+ "logo": "https://logo.clearbit.com/usatoday.com",
+ "youtube": "UCP6HGa63sBC7-KHtkme-p-g",
+ "liveCheck": "youtube",
+ "liveStream": null,
+ "lang": "en",
+ "region": "North America",
+ "category": "general"
+ },
+ {
+ "id": "latimes",
+ "name": "Los Angeles Times",
+ "country": "US",
+ "flag": "🇺🇸",
+ "href": "https://www.latimes.com",
+ "logo": "https://logo.clearbit.com/latimes.com",
+ "youtube": "UCTcyDLlNDvFKDgCt2tCnFog",
+ "liveCheck": "youtube",
+ "liveStream": null,
+ "lang": "en",
+ "region": "North America",
+ "category": "general"
+ },
+ {
+ "id": "npr",
+ "name": "NPR",
+ "country": "US",
+ "flag": "🇺🇸",
+ "href": "https://www.npr.org",
+ "logo": "https://logo.clearbit.com/npr.org",
+ "youtube": "UCQobI8sZHKvm9OdykIyEoVw",
+ "liveCheck": "youtube",
+ "liveStream": null,
+ "lang": "en",
+ "region": "North America",
+ "category": "general"
+ },
+ {
+ "id": "axios",
+ "name": "Axios",
+ "country": "US",
+ "flag": "🇺🇸",
+ "href": "https://www.axios.com",
+ "logo": "https://logo.clearbit.com/axios.com",
+ "youtube": null,
+ "liveCheck": null,
+ "liveStream": null,
+ "lang": "en",
+ "region": "North America",
+ "category": "general"
+ },
+ {
+ "id": "politico",
+ "name": "Politico",
+ "country": "US",
+ "flag": "🇺🇸",
+ "href": "https://www.politico.com",
+ "logo": "https://logo.clearbit.com/politico.com",
+ "youtube": "UC0E08e_oPxNZL_3Hd-yz1ZA",
+ "liveCheck": "youtube",
+ "liveStream": null,
+ "lang": "en",
+ "region": "North America",
+ "category": "politics"
+ },
+ {
+ "id": "thehill",
+ "name": "The Hill",
+ "country": "US",
+ "flag": "🇺🇸",
+ "href": "https://thehill.com",
+ "logo": "https://logo.clearbit.com/thehill.com",
+ "youtube": "UCEHvjJWoVPdMl2Iy4iqAg1A",
+ "liveCheck": "youtube",
+ "liveStream": null,
+ "lang": "en",
+ "region": "North America",
+ "category": "politics"
+ },
+ {
+ "id": "ap",
+ "name": "Associated Press",
+ "country": "US",
+ "flag": "🇺🇸",
+ "href": "https://apnews.com",
+ "logo": "https://logo.clearbit.com/apnews.com",
+ "youtube": "UC52X5wxOL_s5yw0dQk7NtgA",
+ "liveCheck": "youtube",
+ "liveStream": null,
+ "lang": "en",
+ "region": "North America",
+ "category": "newswire"
+ },
+ {
+ "id": "reuters",
+ "name": "Reuters",
+ "country": "UK",
+ "flag": "🇬🇧",
+ "href": "https://www.reuters.com",
+ "logo": "https://logo.clearbit.com/reuters.com",
+ "youtube": "UChqUTb7kYRX8-EiaN3XFrSQ",
+ "liveCheck": "youtube",
+ "liveStream": "https://www.youtube.com/@Reuters/live",
+ "lang": "en",
+ "region": "Europe",
+ "category": "newswire"
+ },
+ {
+ "id": "bbc",
+ "name": "BBC News",
+ "country": "UK",
+ "flag": "🇬🇧",
+ "href": "https://www.bbc.com/news",
+ "logo": "https://logo.clearbit.com/bbc.com",
+ "youtube": "UC16niRr50-MSBwiO3YDb3RA",
+ "liveCheck": "youtube",
+ "liveStream": "https://www.youtube.com/@BBCNews/live",
+ "lang": "en",
+ "region": "Europe",
+ "category": "general"
+ },
+ {
+ "id": "skynews",
+ "name": "Sky News",
+ "country": "UK",
+ "flag": "🇬🇧",
+ "href": "https://news.sky.com",
+ "logo": "https://logo.clearbit.com/news.sky.com",
+ "youtube": "UCoMdktPbSTixAyNGwb-UYkQ",
+ "liveCheck": "youtube",
+ "liveStream": "https://www.youtube.com/@SkyNews/live",
+ "lang": "en",
+ "region": "Europe",
+ "category": "general"
+ },
+ {
+ "id": "guardian",
+ "name": "The Guardian",
+ "country": "UK",
+ "flag": "🇬🇧",
+ "href": "https://www.theguardian.com",
+ "logo": "https://logo.clearbit.com/theguardian.com",
+ "youtube": "UCIRYBXDze5krPDzAEOxFGVA",
+ "liveCheck": "youtube",
+ "liveStream": "https://www.youtube.com/@guardian/live",
+ "lang": "en",
+ "region": "Europe",
+ "category": "general"
+ },
+ {
+ "id": "times",
+ "name": "The Times",
+ "country": "UK",
+ "flag": "🇬🇧",
+ "href": "https://www.thetimes.com",
+ "logo": "https://logo.clearbit.com/thetimes.com",
+ "youtube": "UCRl0SXuLfm5gKaWPjmIxF-w",
+ "liveCheck": "youtube",
+ "liveStream": null,
+ "lang": "en",
+ "region": "Europe",
+ "category": "general"
+ },
+ {
+ "id": "telegraph",
+ "name": "The Telegraph",
+ "country": "UK",
+ "flag": "🇬🇧",
+ "href": "https://www.telegraph.co.uk",
+ "logo": "https://logo.clearbit.com/telegraph.co.uk",
+ "youtube": "UCpVm7bg6pXKo1Pr6k5kxG9A",
+ "liveCheck": "youtube",
+ "liveStream": "https://www.youtube.com/@TheTelegraph/live",
+ "lang": "en",
+ "region": "Europe",
+ "category": "general"
+ },
+ {
+ "id": "ft",
+ "name": "Financial Times",
+ "country": "UK",
+ "flag": "🇬🇧",
+ "href": "https://www.ft.com",
+ "logo": "https://logo.clearbit.com/ft.com",
+ "youtube": "UCKZJWvV5RBxKLNPU2_v9rgg",
+ "liveCheck": "youtube",
+ "liveStream": null,
+ "lang": "en",
+ "region": "Europe",
+ "category": "finance"
+ },
+ {
+ "id": "economist",
+ "name": "The Economist",
+ "country": "UK",
+ "flag": "🇬🇧",
+ "href": "https://www.economist.com",
+ "logo": "https://logo.clearbit.com/economist.com",
+ "youtube": "UC0p5jTq6Xx_DosDFxVXnWaQ",
+ "liveCheck": "youtube",
+ "liveStream": null,
+ "lang": "en",
+ "region": "Europe",
+ "category": "general"
+ },
+ {
+ "id": "dailymail",
+ "name": "Daily Mail",
+ "country": "UK",
+ "flag": "🇬🇧",
+ "href": "https://www.dailymail.co.uk",
+ "logo": "https://logo.clearbit.com/dailymail.co.uk",
+ "youtube": "UC1Z3jOyzVgcEwY8Hk5TQJ2g",
+ "liveCheck": "youtube",
+ "liveStream": null,
+ "lang": "en",
+ "region": "Europe",
+ "category": "general"
+ },
+ {
+ "id": "independent",
+ "name": "The Independent",
+ "country": "UK",
+ "flag": "🇬🇧",
+ "href": "https://www.independent.co.uk",
+ "logo": "https://logo.clearbit.com/independent.co.uk",
+ "youtube": "UCXIJ2-RoAgyx2pc3kIcXY0Q",
+ "liveCheck": "youtube",
+ "liveStream": null,
+ "lang": "en",
+ "region": "Europe",
+ "category": "general"
+ },
+ {
+ "id": "channel4news",
+ "name": "Channel 4 News",
+ "country": "UK",
+ "flag": "🇬🇧",
+ "href": "https://www.channel4.com/news",
+ "logo": "https://logo.clearbit.com/channel4.com",
+ "youtube": "UCRfFR6ZL-DyR82RVE_0EHsg",
+ "liveCheck": "youtube",
+ "liveStream": null,
+ "lang": "en",
+ "region": "Europe",
+ "category": "general"
+ },
+ {
+ "id": "aljazeera",
+ "name": "Al Jazeera English",
+ "country": "QA",
+ "flag": "🇶🇦",
+ "href": "https://www.aljazeera.com",
+ "logo": "https://logo.clearbit.com/aljazeera.com",
+ "youtube": "UCNye-wNBqNL5ZzHSJj3l8Bg",
+ "liveCheck": "youtube",
+ "liveStream": "https://www.youtube.com/@aljazeeraenglish/live",
+ "lang": "en",
+ "region": "Middle East",
+ "category": "general"
+ },
+ {
+ "id": "france24",
+ "name": "France 24",
+ "country": "FR",
+ "flag": "🇫🇷",
+ "href": "https://www.france24.com/en/",
+ "logo": "https://logo.clearbit.com/france24.com",
+ "youtube": "UCQfwfsi5VrQ8yKZ-UWmAEFg",
+ "liveCheck": "youtube",
+ "liveStream": "https://www.youtube.com/@FRANCE24English/live",
+ "lang": "en",
+ "region": "Europe",
+ "category": "general"
+ },
+ {
+ "id": "dw",
+ "name": "DW News",
+ "country": "DE",
+ "flag": "🇩🇪",
+ "href": "https://www.dw.com",
+ "logo": "https://logo.clearbit.com/dw.com",
+ "youtube": "UCknLrEdhRCp1aegoMqRaCZg",
+ "liveCheck": "youtube",
+ "liveStream": "https://www.youtube.com/@dwnews/live",
+ "lang": "en",
+ "region": "Europe",
+ "category": "general"
+ },
+ {
+ "id": "euronews",
+ "name": "Euronews",
+ "country": "EU",
+ "flag": "🇪🇺",
+ "href": "https://www.euronews.com",
+ "logo": "https://logo.clearbit.com/euronews.com",
+ "youtube": "UCSrZ3UV4jOidv8ppoVuvW9Q",
+ "liveCheck": "youtube",
+ "liveStream": "https://www.youtube.com/@euronews/live",
+ "lang": "en",
+ "region": "Europe",
+ "category": "general"
+ },
+ {
+ "id": "rt",
+ "name": "RT (Russia Today)",
+ "country": "RU",
+ "flag": "🇷🇺",
+ "href": "https://www.rt.com",
+ "logo": "https://logo.clearbit.com/rt.com",
+ "youtube": "UCpwvZwUam-URkxB7g4USKpg",
+ "liveCheck": "youtube",
+ "liveStream": null,
+ "lang": "en",
+ "region": "Europe",
+ "category": "general"
+ },
+ {
+ "id": "spiegel",
+ "name": "Der Spiegel",
+ "country": "DE",
+ "flag": "🇩🇪",
+ "href": "https://www.spiegel.de",
+ "logo": "https://logo.clearbit.com/spiegel.de",
+ "youtube": null,
+ "liveCheck": null,
+ "liveStream": null,
+ "lang": "de",
+ "region": "Europe",
+ "category": "general"
+ },
+ {
+ "id": "lemonde",
+ "name": "Le Monde",
+ "country": "FR",
+ "flag": "🇫🇷",
+ "href": "https://www.lemonde.fr",
+ "logo": "https://logo.clearbit.com/lemonde.fr",
+ "youtube": "UCnFnzKHwxjEx5dQ-Hw6T1Lw",
+ "liveCheck": "youtube",
+ "liveStream": null,
+ "lang": "fr",
+ "region": "Europe",
+ "category": "general"
+ },
+ {
+ "id": "lefigaro",
+ "name": "Le Figaro",
+ "country": "FR",
+ "flag": "🇫🇷",
+ "href": "https://www.lefigaro.fr",
+ "logo": "https://logo.clearbit.com/lefigaro.fr",
+ "youtube": "UC1pdaJL7tOmkvAfBp-ZaiUg",
+ "liveCheck": "youtube",
+ "liveStream": null,
+ "lang": "fr",
+ "region": "Europe",
+ "category": "general"
+ },
+ {
+ "id": "elpais",
+ "name": "El País",
+ "country": "ES",
+ "flag": "🇪🇸",
+ "href": "https://elpais.com",
+ "logo": "https://logo.clearbit.com/elpais.com",
+ "youtube": "UC4S6Y4qBN5RFcZ1aQAlW9aQ",
+ "liveCheck": "youtube",
+ "liveStream": null,
+ "lang": "es",
+ "region": "Europe",
+ "category": "general"
+ },
+ {
+ "id": "corriere",
+ "name": "Corriere della Sera",
+ "country": "IT",
+ "flag": "🇮🇹",
+ "href": "https://www.corriere.it",
+ "logo": "https://logo.clearbit.com/corriere.it",
+ "youtube": null,
+ "liveCheck": null,
+ "liveStream": null,
+ "lang": "it",
+ "region": "Europe",
+ "category": "general"
+ },
+ {
+ "id": "tagesschau",
+ "name": "Tagesschau (ARD)",
+ "country": "DE",
+ "flag": "🇩🇪",
+ "href": "https://www.tagesschau.de",
+ "logo": "https://logo.clearbit.com/tagesschau.de",
+ "youtube": "UC5NOEUbkLheQcaaRldYW5GA",
+ "liveCheck": "youtube",
+ "liveStream": "https://www.youtube.com/@tagesschau/live",
+ "lang": "de",
+ "region": "Europe",
+ "category": "general"
+ },
+ {
+ "id": "nhk",
+ "name": "NHK World-Japan",
+ "country": "JP",
+ "flag": "🇯🇵",
+ "href": "https://www3.nhk.or.jp/nhkworld/",
+ "logo": "https://logo.clearbit.com/nhk.or.jp",
+ "youtube": "UCSPEjw8F2nQDtmUKPFNF7_A",
+ "liveCheck": "youtube",
+ "liveStream": "https://www.youtube.com/@NHKWORLDJAPAN/live",
+ "lang": "en",
+ "region": "Asia",
+ "category": "general"
+ },
+ {
+ "id": "asahi",
+ "name": "Asahi Shimbun",
+ "country": "JP",
+ "flag": "🇯🇵",
+ "href": "https://www.asahi.com",
+ "logo": "https://logo.clearbit.com/asahi.com",
+ "youtube": null,
+ "liveCheck": null,
+ "liveStream": null,
+ "lang": "ja",
+ "region": "Asia",
+ "category": "general"
+ },
+ {
+ "id": "scmp",
+ "name": "South China Morning Post",
+ "country": "HK",
+ "flag": "🇭🇰",
+ "href": "https://www.scmp.com",
+ "logo": "https://logo.clearbit.com/scmp.com",
+ "youtube": "UC4SUWizzKc1tptprBkWjX2Q",
+ "liveCheck": "youtube",
+ "liveStream": null,
+ "lang": "en",
+ "region": "Asia",
+ "category": "general"
+ },
+ {
+ "id": "cctv",
+ "name": "CGTN (China)",
+ "country": "CN",
+ "flag": "🇨🇳",
+ "href": "https://www.cgtn.com",
+ "logo": "https://logo.clearbit.com/cgtn.com",
+ "youtube": "UCdp4eqkLptbkpYTQ7wp9LRA",
+ "liveCheck": "youtube",
+ "liveStream": "https://www.youtube.com/@CGTN/live",
+ "lang": "en",
+ "region": "Asia",
+ "category": "general"
+ },
+ {
+ "id": "straitstimes",
+ "name": "The Straits Times",
+ "country": "SG",
+ "flag": "🇸🇬",
+ "href": "https://www.straitstimes.com",
+ "logo": "https://logo.clearbit.com/straitstimes.com",
+ "youtube": "UC4p_I9eiRewn2KoU-nawrDg",
+ "liveCheck": "youtube",
+ "liveStream": null,
+ "lang": "en",
+ "region": "Asia",
+ "category": "general"
+ },
+ {
+ "id": "channelnewsasia",
+ "name": "CNA (Channel News Asia)",
+ "country": "SG",
+ "flag": "🇸🇬",
+ "href": "https://www.channelnewsasia.com",
+ "logo": "https://logo.clearbit.com/channelnewsasia.com",
+ "youtube": "UCXKDugVmDBVtSV-DPzlT5BA",
+ "liveCheck": "youtube",
+ "liveStream": "https://www.youtube.com/@channelnewsasia/live",
+ "lang": "en",
+ "region": "Asia",
+ "category": "general"
+ },
+ {
+ "id": "abcaus",
+ "name": "ABC News (Australia)",
+ "country": "AU",
+ "flag": "🇦🇺",
+ "href": "https://www.abc.net.au/news",
+ "logo": "https://logo.clearbit.com/abc.net.au",
+ "youtube": "UCVgO39Bk5sMo66-6o6Spn6Q",
+ "liveCheck": "youtube",
+ "liveStream": "https://www.youtube.com/@ABCNewsIn90Seconds/live",
+ "lang": "en",
+ "region": "Oceania",
+ "category": "general"
+ },
+ {
+ "id": "smh",
+ "name": "Sydney Morning Herald",
+ "country": "AU",
+ "flag": "🇦🇺",
+ "href": "https://www.smh.com.au",
+ "logo": "https://logo.clearbit.com/smh.com.au",
+ "youtube": "UCmlF8rNyAQyx7gO9F0sLE5w",
+ "liveCheck": "youtube",
+ "liveStream": null,
+ "lang": "en",
+ "region": "Oceania",
+ "category": "general"
+ },
+ {
+ "id": "9news",
+ "name": "9 News Australia",
+ "country": "AU",
+ "flag": "🇦🇺",
+ "href": "https://www.9news.com.au",
+ "logo": "https://logo.clearbit.com/9news.com.au",
+ "youtube": "UCK7IIV6Q2junGSdYK3BmZMA",
+ "liveCheck": "youtube",
+ "liveStream": "https://www.youtube.com/@9NewsAUS/live",
+ "lang": "en",
+ "region": "Oceania",
+ "category": "general"
+ },
+ {
+ "id": "nzherald",
+ "name": "NZ Herald",
+ "country": "NZ",
+ "flag": "🇳🇿",
+ "href": "https://www.nzherald.co.nz",
+ "logo": "https://logo.clearbit.com/nzherald.co.nz",
+ "youtube": "UCyA-W5h99e1lwrSc7sa1JEw",
+ "liveCheck": "youtube",
+ "liveStream": null,
+ "lang": "en",
+ "region": "Oceania",
+ "category": "general"
+ },
+ {
+ "id": "cbc",
+ "name": "CBC News",
+ "country": "CA",
+ "flag": "🇨🇦",
+ "href": "https://www.cbc.ca/news",
+ "logo": "https://logo.clearbit.com/cbc.ca",
+ "youtube": "UCuFFtHWoLl5fauMMD5Ww2jA",
+ "liveCheck": "youtube",
+ "liveStream": "https://www.youtube.com/@CBCNews/live",
+ "lang": "en",
+ "region": "North America",
+ "category": "general"
+ },
+ {
+ "id": "globalnews",
+ "name": "Global News (Canada)",
+ "country": "CA",
+ "flag": "🇨🇦",
+ "href": "https://globalnews.ca",
+ "logo": "https://logo.clearbit.com/globalnews.ca",
+ "youtube": "UChLtXXpo4Ge1ReTEboVvTDg",
+ "liveCheck": "youtube",
+ "liveStream": "https://www.youtube.com/@globalnews/live",
+ "lang": "en",
+ "region": "North America",
+ "category": "general"
+ },
+ {
+ "id": "globo",
+ "name": "Globo News (Brazil)",
+ "country": "BR",
+ "flag": "🇧🇷",
+ "href": "https://g1.globo.com",
+ "logo": "https://logo.clearbit.com/globo.com",
+ "youtube": "UCo7uVfPnPSlmrJFqJTW1Vyw",
+ "liveCheck": "youtube",
+ "liveStream": null,
+ "lang": "pt",
+ "region": "South America",
+ "category": "general"
+ },
+ {
+ "id": "folha",
+ "name": "Folha de S.Paulo",
+ "country": "BR",
+ "flag": "🇧🇷",
+ "href": "https://www.folha.uol.com.br",
+ "logo": "https://logo.clearbit.com/folha.uol.com.br",
+ "youtube": "UCNqJoLb5ohR-Jt5LeJlNXvw",
+ "liveCheck": "youtube",
+ "liveStream": null,
+ "lang": "pt",
+ "region": "South America",
+ "category": "general"
+ },
+ {
+ "id": "clarin",
+ "name": "Clarín",
+ "country": "AR",
+ "flag": "🇦🇷",
+ "href": "https://www.clarin.com",
+ "logo": "https://logo.clearbit.com/clarin.com",
+ "youtube": null,
+ "liveCheck": null,
+ "liveStream": null,
+ "lang": "es",
+ "region": "South America",
+ "category": "general"
+ },
+ {
+ "id": "lanacion",
+ "name": "La Nación",
+ "country": "AR",
+ "flag": "🇦🇷",
+ "href": "https://www.lanacion.com.ar",
+ "logo": "https://logo.clearbit.com/lanacion.com.ar",
+ "youtube": null,
+ "liveCheck": null,
+ "liveStream": null,
+ "lang": "es",
+ "region": "South America",
+ "category": "general"
+ },
+ {
+ "id": "milenio",
+ "name": "Milenio",
+ "country": "MX",
+ "flag": "🇲🇽",
+ "href": "https://www.milenio.com",
+ "logo": "https://logo.clearbit.com/milenio.com",
+ "youtube": "UC2gKLqOIVDsJaLwBhCNCgmA",
+ "liveCheck": "youtube",
+ "liveStream": "https://www.youtube.com/@MilenioTV/live",
+ "lang": "es",
+ "region": "North America",
+ "category": "general"
+ },
+ {
+ "id": "reforma",
+ "name": "Reforma",
+ "country": "MX",
+ "flag": "🇲🇽",
+ "href": "https://www.reforma.com",
+ "logo": "https://logo.clearbit.com/reforma.com",
+ "youtube": null,
+ "liveCheck": null,
+ "liveStream": null,
+ "lang": "es",
+ "region": "North America",
+ "category": "general"
+ },
+ {
+ "id": "hindustantimes",
+ "name": "Hindustan Times",
+ "country": "IN",
+ "flag": "🇮🇳",
+ "href": "https://www.hindustantimes.com",
+ "logo": "https://logo.clearbit.com/hindustantimes.com",
+ "youtube": "UCqOL4u7UQqilUUjQXNkrPaw",
+ "liveCheck": "youtube",
+ "liveStream": "https://www.youtube.com/@HindustanTimes/live",
+ "lang": "en",
+ "region": "Asia",
+ "category": "general"
+ },
+ {
+ "id": "thehindu",
+ "name": "The Hindu",
+ "country": "IN",
+ "flag": "🇮🇳",
+ "href": "https://www.thehindu.com",
+ "logo": "https://logo.clearbit.com/thehindu.com",
+ "youtube": "UCJi8M0hRKjz8SLyvObNZjsw",
+ "liveCheck": "youtube",
+ "liveStream": null,
+ "lang": "en",
+ "region": "Asia",
+ "category": "general"
+ },
+ {
+ "id": "ndtv",
+ "name": "NDTV",
+ "country": "IN",
+ "flag": "🇮🇳",
+ "href": "https://www.ndtv.com",
+ "logo": "https://logo.clearbit.com/ndtv.com",
+ "youtube": "UCZFMm1mMw0F81Z37aaEzTUA",
+ "liveCheck": "youtube",
+ "liveStream": "https://www.youtube.com/@ndtv/live",
+ "lang": "en",
+ "region": "Asia",
+ "category": "general"
+ },
+ {
+ "id": "indiatoday",
+ "name": "India Today",
+ "country": "IN",
+ "flag": "🇮🇳",
+ "href": "https://www.indiatoday.in",
+ "logo": "https://logo.clearbit.com/indiatoday.in",
+ "youtube": "UCYPvAwZP8pZhSMW8qs7cVCw",
+ "liveCheck": "youtube",
+ "liveStream": "https://www.youtube.com/@IndiaToday/live",
+ "lang": "en",
+ "region": "Asia",
+ "category": "general"
+ },
+ {
+ "id": "wionews",
+ "name": "WION",
+ "country": "IN",
+ "flag": "🇮🇳",
+ "href": "https://www.wionews.com",
+ "logo": "https://logo.clearbit.com/wionews.com",
+ "youtube": "UC_gUM8rL-Lrg6O3adPW9K1g",
+ "liveCheck": "youtube",
+ "liveStream": "https://www.youtube.com/@WION/live",
+ "lang": "en",
+ "region": "Asia",
+ "category": "general"
+ },
+ {
+ "id": "haaretz",
+ "name": "Haaretz",
+ "country": "IL",
+ "flag": "🇮🇱",
+ "href": "https://www.haaretz.com",
+ "logo": "https://logo.clearbit.com/haaretz.com",
+ "youtube": null,
+ "liveCheck": null,
+ "liveStream": null,
+ "lang": "en",
+ "region": "Middle East",
+ "category": "general"
+ },
+ {
+ "id": "jpost",
+ "name": "Jerusalem Post",
+ "country": "IL",
+ "flag": "🇮🇱",
+ "href": "https://www.jpost.com",
+ "logo": "https://logo.clearbit.com/jpost.com",
+ "youtube": "UCC_LolHN2pAAg-yMjyu6Cww",
+ "liveCheck": "youtube",
+ "liveStream": null,
+ "lang": "en",
+ "region": "Middle East",
+ "category": "general"
+ },
+ {
+ "id": "i24",
+ "name": "i24NEWS",
+ "country": "IL",
+ "flag": "🇮🇱",
+ "href": "https://www.i24news.tv",
+ "logo": "https://logo.clearbit.com/i24news.tv",
+ "youtube": "UC5dQbXmDP6tDfYrBxbF6vIA",
+ "liveCheck": "youtube",
+ "liveStream": "https://www.youtube.com/@i24NEWSEnglish/live",
+ "lang": "en",
+ "region": "Middle East",
+ "category": "general"
+ },
+ {
+ "id": "trtworld",
+ "name": "TRT World",
+ "country": "TR",
+ "flag": "🇹🇷",
+ "href": "https://www.trtworld.com",
+ "logo": "https://logo.clearbit.com/trtworld.com",
+ "youtube": "UC7fWeaHhqgM4Ry-RMpM2YYw",
+ "liveCheck": "youtube",
+ "liveStream": "https://www.youtube.com/@trtworld/live",
+ "lang": "en",
+ "region": "Middle East",
+ "category": "general"
+ },
+ {
+ "id": "arabnews",
+ "name": "Arab News",
+ "country": "SA",
+ "flag": "🇸🇦",
+ "href": "https://www.arabnews.com",
+ "logo": "https://logo.clearbit.com/arabnews.com",
+ "youtube": "UC0EuvBHMyJpZbDsx3GdyAcQ",
+ "liveCheck": "youtube",
+ "liveStream": null,
+ "lang": "en",
+ "region": "Middle East",
+ "category": "general"
+ },
+ {
+ "id": "khaleej",
+ "name": "Khaleej Times",
+ "country": "AE",
+ "flag": "🇦🇪",
+ "href": "https://www.khaleejtimes.com",
+ "logo": "https://logo.clearbit.com/khaleejtimes.com",
+ "youtube": null,
+ "liveCheck": null,
+ "liveStream": null,
+ "lang": "en",
+ "region": "Middle East",
+ "category": "general"
+ },
+ {
+ "id": "thenational",
+ "name": "The National (UAE)",
+ "country": "AE",
+ "flag": "🇦🇪",
+ "href": "https://www.thenationalnews.com",
+ "logo": "https://logo.clearbit.com/thenationalnews.com",
+ "youtube": "UCNGOLOZULAOQRO1JmEFmvSw",
+ "liveCheck": "youtube",
+ "liveStream": null,
+ "lang": "en",
+ "region": "Middle East",
+ "category": "general"
+ },
+ {
+ "id": "newsafrica",
+ "name": "Africa News",
+ "country": "AFRICA",
+ "flag": "🌍",
+ "href": "https://www.africanews.com",
+ "logo": "https://logo.clearbit.com/africanews.com",
+ "youtube": "UC2tA1l9NTbX1n7CmS4hF8Bw",
+ "liveCheck": "youtube",
+ "liveStream": "https://www.youtube.com/@africanews/live",
+ "lang": "en",
+ "region": "Africa",
+ "category": "general"
+ },
+ {
+ "id": "sabc",
+ "name": "SABC News",
+ "country": "ZA",
+ "flag": "🇿🇦",
+ "href": "https://www.sabcnews.com",
+ "logo": "https://logo.clearbit.com/sabcnews.com",
+ "youtube": "UCb-zh5xWuJJkjVPe7NPiyAQ",
+ "liveCheck": "youtube",
+ "liveStream": "https://www.youtube.com/@SABCNews/live",
+ "lang": "en",
+ "region": "Africa",
+ "category": "general"
+ },
+ {
+ "id": "enca",
+ "name": "eNCA",
+ "country": "ZA",
+ "flag": "🇿🇦",
+ "href": "https://www.enca.com",
+ "logo": "https://logo.clearbit.com/enca.com",
+ "youtube": "UCPyiBnHvBwlbX-3sSZx4qOg",
+ "liveCheck": "youtube",
+ "liveStream": "https://www.youtube.com/@enca/live",
+ "lang": "en",
+ "region": "Africa",
+ "category": "general"
+ },
+ {
+ "id": "vox",
+ "name": "Vox",
+ "country": "US",
+ "flag": "🇺🇸",
+ "href": "https://www.vox.com",
+ "logo": "https://logo.clearbit.com/vox.com",
+ "youtube": "UCLXo7UDZvByw2ixzpQCufnA",
+ "liveCheck": "youtube",
+ "liveStream": null,
+ "lang": "en",
+ "region": "North America",
+ "category": "general"
+ },
+ {
+ "id": "vice",
+ "name": "VICE News",
+ "country": "US",
+ "flag": "🇺🇸",
+ "href": "https://www.vice.com/en/section/news",
+ "logo": "https://logo.clearbit.com/vice.com",
+ "youtube": "UCZaT_X_mc0BI-djXOlfhqWQ",
+ "liveCheck": "youtube",
+ "liveStream": null,
+ "lang": "en",
+ "region": "North America",
+ "category": "general"
+ },
+ {
+ "id": "newsy",
+ "name": "Scripps News",
+ "country": "US",
+ "flag": "🇺🇸",
+ "href": "https://scrippsnews.com",
+ "logo": "https://logo.clearbit.com/scrippsnews.com",
+ "youtube": "UCDR3lkqdjcRC4LkBYRkQzaQ",
+ "liveCheck": "youtube",
+ "liveStream": "https://www.youtube.com/@scrippsnews/live",
+ "lang": "en",
+ "region": "North America",
+ "category": "general"
+ },
+ {
+ "id": "ntdca",
+ "name": "NTD",
+ "country": "US",
+ "flag": "🇺🇸",
+ "href": "https://www.ntd.com",
+ "logo": "https://logo.clearbit.com/ntd.com",
+ "youtube": "UC7nLfpz1iyu6CDOlXkJpA1Q",
+ "liveCheck": "youtube",
+ "liveStream": "https://www.youtube.com/@NTDNews/live",
+ "lang": "en",
+ "region": "North America",
+ "category": "general"
+ },
+ {
+ "id": "newsmax",
+ "name": "Newsmax",
+ "country": "US",
+ "flag": "🇺🇸",
+ "href": "https://www.newsmax.com",
+ "logo": "https://logo.clearbit.com/newsmax.com",
+ "youtube": "UCx6h-dWzJ5NpAlja1YsApdg",
+ "liveCheck": "youtube",
+ "liveStream": "https://www.youtube.com/@NewsmaxTV/live",
+ "lang": "en",
+ "region": "North America",
+ "category": "general"
+ },
+ {
+ "id": "oan",
+ "name": "OAN",
+ "country": "US",
+ "flag": "🇺🇸",
+ "href": "https://www.oann.com",
+ "logo": "https://logo.clearbit.com/oann.com",
+ "youtube": null,
+ "liveCheck": null,
+ "liveStream": null,
+ "lang": "en",
+ "region": "North America",
+ "category": "general"
+ },
+ {
+ "id": "thedaily",
+ "name": "The Daily Wire",
+ "country": "US",
+ "flag": "🇺🇸",
+ "href": "https://www.dailywire.com",
+ "logo": "https://logo.clearbit.com/dailywire.com",
+ "youtube": "UCaeO5vkdj5xOQHp4UmIN6dw",
+ "liveCheck": "youtube",
+ "liveStream": null,
+ "lang": "en",
+ "region": "North America",
+ "category": "general"
+ },
+ {
+ "id": "thedrive",
+ "name": "The Daily Beast",
+ "country": "US",
+ "flag": "🇺🇸",
+ "href": "https://www.thedailybeast.com",
+ "logo": "https://logo.clearbit.com/thedailybeast.com",
+ "youtube": null,
+ "liveCheck": null,
+ "liveStream": null,
+ "lang": "en",
+ "region": "North America",
+ "category": "general"
+ },
+ {
+ "id": "huffpost",
+ "name": "HuffPost",
+ "country": "US",
+ "flag": "🇺🇸",
+ "href": "https://www.huffpost.com",
+ "logo": "https://logo.clearbit.com/huffpost.com",
+ "youtube": null,
+ "liveCheck": null,
+ "liveStream": null,
+ "lang": "en",
+ "region": "North America",
+ "category": "general"
+ },
+ {
+ "id": "theintercept",
+ "name": "The Intercept",
+ "country": "US",
+ "flag": "🇺🇸",
+ "href": "https://theintercept.com",
+ "logo": "https://logo.clearbit.com/theintercept.com",
+ "youtube": null,
+ "liveCheck": null,
+ "liveStream": null,
+ "lang": "en",
+ "region": "North America",
+ "category": "general"
+ },
+ {
+ "id": "propublica",
+ "name": "ProPublica",
+ "country": "US",
+ "flag": "🇺🇸",
+ "href": "https://www.propublica.org",
+ "logo": "https://logo.clearbit.com/propublica.org",
+ "youtube": null,
+ "liveCheck": null,
+ "liveStream": null,
+ "lang": "en",
+ "region": "North America",
+ "category": "general"
+ },
+ {
+ "id": "semafor",
+ "name": "Semafor",
+ "country": "US",
+ "flag": "🇺🇸",
+ "href": "https://www.semafor.com",
+ "logo": "https://logo.clearbit.com/semafor.com",
+ "youtube": null,
+ "liveCheck": null,
+ "liveStream": null,
+ "lang": "en",
+ "region": "North America",
+ "category": "general"
+ },
+ {
+ "id": "theatlantic",
+ "name": "The Atlantic",
+ "country": "US",
+ "flag": "🇺🇸",
+ "href": "https://www.theatlantic.com",
+ "logo": "https://logo.clearbit.com/theatlantic.com",
+ "youtube": null,
+ "liveCheck": null,
+ "liveStream": null,
+ "lang": "en",
+ "region": "North America",
+ "category": "general"
+ },
+ {
+ "id": "newyorker",
+ "name": "The New Yorker",
+ "country": "US",
+ "flag": "🇺🇸",
+ "href": "https://www.newyorker.com",
+ "logo": "https://logo.clearbit.com/newyorker.com",
+ "youtube": null,
+ "liveCheck": null,
+ "liveStream": null,
+ "lang": "en",
+ "region": "North America",
+ "category": "general"
+ },
+ {
+ "id": "yahoonews",
+ "name": "Yahoo News",
+ "country": "US",
+ "flag": "🇺🇸",
+ "href": "https://news.yahoo.com",
+ "logo": "https://logo.clearbit.com/yahoo.com",
+ "youtube": null,
+ "liveCheck": null,
+ "liveStream": null,
+ "lang": "en",
+ "region": "North America",
+ "category": "general"
+ },
+ {
+ "id": "thelocal",
+ "name": "The Local (Europe)",
+ "country": "EU",
+ "flag": "🇪🇺",
+ "href": "https://www.thelocal.com",
+ "logo": "https://logo.clearbit.com/thelocal.com",
+ "youtube": null,
+ "liveCheck": null,
+ "liveStream": null,
+ "lang": "en",
+ "region": "Europe",
+ "category": "general"
+ },
+ {
+ "id": "themoscowtimes",
+ "name": "The Moscow Times",
+ "country": "RU",
+ "flag": "🇷🇺",
+ "href": "https://www.themoscowtimes.com",
+ "logo": "https://logo.clearbit.com/themoscowtimes.com",
+ "youtube": null,
+ "liveCheck": null,
+ "liveStream": null,
+ "lang": "en",
+ "region": "Europe",
+ "category": "general"
+ },
+ {
+ "id": "kyivindependent",
+ "name": "Kyiv Independent",
+ "country": "UA",
+ "flag": "🇺🇦",
+ "href": "https://kyivindependent.com",
+ "logo": "https://logo.clearbit.com/kyivindependent.com",
+ "youtube": "UCi-NU5OqfP-c8kFGsXXxQbA",
+ "liveCheck": "youtube",
+ "liveStream": null,
+ "lang": "en",
+ "region": "Europe",
+ "category": "general"
+ },
+ {
+ "id": "ukrayinska",
+ "name": "Ukrainska Pravda",
+ "country": "UA",
+ "flag": "🇺🇦",
+ "href": "https://www.pravda.com.ua",
+ "logo": "https://logo.clearbit.com/pravda.com.ua",
+ "youtube": null,
+ "liveCheck": null,
+ "liveStream": null,
+ "lang": "uk",
+ "region": "Europe",
+ "category": "general"
+ },
+ {
+ "id": "irishtimes",
+ "name": "The Irish Times",
+ "country": "IE",
+ "flag": "🇮🇪",
+ "href": "https://www.irishtimes.com",
+ "logo": "https://logo.clearbit.com/irishtimes.com",
+ "youtube": null,
+ "liveCheck": null,
+ "liveStream": null,
+ "lang": "en",
+ "region": "Europe",
+ "category": "general"
+ },
+ {
+ "id": "rte",
+ "name": "RTÉ News (Ireland)",
+ "country": "IE",
+ "flag": "🇮🇪",
+ "href": "https://www.rte.ie/news/",
+ "logo": "https://logo.clearbit.com/rte.ie",
+ "youtube": "UCYHigQOIfH2hbgcUm0F38_g",
+ "liveCheck": "youtube",
+ "liveStream": "https://www.youtube.com/@rtenews/live",
+ "lang": "en",
+ "region": "Europe",
+ "category": "general"
+ },
+ {
+ "id": "thejournal",
+ "name": "TheJournal.ie",
+ "country": "IE",
+ "flag": "🇮🇪",
+ "href": "https://www.thejournal.ie",
+ "logo": "https://logo.clearbit.com/thejournal.ie",
+ "youtube": null,
+ "liveCheck": null,
+ "liveStream": null,
+ "lang": "en",
+ "region": "Europe",
+ "category": "general"
+ },
+ {
+ "id": "rfi",
+ "name": "RFI",
+ "country": "FR",
+ "flag": "🇫🇷",
+ "href": "https://www.rfi.fr/en/",
+ "logo": "https://logo.clearbit.com/rfi.fr",
+ "youtube": null,
+ "liveCheck": null,
+ "liveStream": null,
+ "lang": "en",
+ "region": "Europe",
+ "category": "general"
+ },
+ {
+ "id": "bbcworld",
+ "name": "BBC World Service",
+ "country": "UK",
+ "flag": "🇬🇧",
+ "href": "https://www.bbc.com/news/world",
+ "logo": "https://logo.clearbit.com/bbc.com",
+ "youtube": "UC76xPagknI0lI4Z2QbSiP4w",
+ "liveCheck": "youtube",
+ "liveStream": null,
+ "lang": "en",
+ "region": "Europe",
+ "category": "general"
+ }
+]
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..6209578
--- /dev/null
+++ b/package.json
@@ -0,0 +1,17 @@
+{
+ "name": "allnewsdaily",
+ "version": "0.1.0",
+ "description": "All News Daily — global news outlet directory with live-broadcast indicators",
+ "main": "server.js",
+ "scripts": {
+ "start": "node server.js",
+ "dev": "node server.js",
+ "check-live": "node scripts/check-live.js"
+ },
+ "dependencies": {
+ "express": "^4.21.1"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+}
diff --git a/public/index.html b/public/index.html
new file mode 100644
index 0000000..95e0da5
--- /dev/null
+++ b/public/index.html
@@ -0,0 +1,297 @@
+<!doctype html>
+<html lang="en">
+<head>
+ <meta charset="utf-8" />
+ <meta name="viewport" content="width=device-width,initial-scale=1" />
+ <title>All News Daily — global newsroom directory</title>
+ <meta name="description" content="Every major news outlet in the world — logo, link, and a red LIVE pill that glows when the network is broadcasting live." />
+ <link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64'%3E%3Crect width='64' height='64' rx='12' fill='%23dc2626'/%3E%3Ctext x='50%25' y='52%25' dominant-baseline='middle' text-anchor='middle' font-family='ui-sans-serif' font-weight='800' font-size='30' fill='white'%3EN%3C/text%3E%3C/svg%3E" />
+ <style>
+ :root {
+ --bg: #0b0c10;
+ --bg-card: #14161c;
+ --bg-card-hover: #1c1f28;
+ --fg: #e7e9ee;
+ --fg-dim: #8b91a0;
+ --border: #232733;
+ --accent: #dc2626;
+ --accent-glow: rgba(220, 38, 38, .55);
+ --cols: 6;
+ }
+ @media (prefers-color-scheme: light) {
+ :root {
+ --bg: #f6f7f9;
+ --bg-card: #ffffff;
+ --bg-card-hover: #fbfbfc;
+ --fg: #14161c;
+ --fg-dim: #5b6273;
+ --border: #e3e5ec;
+ }
+ }
+ * { box-sizing: border-box; }
+ html, body { margin: 0; padding: 0; background: var(--bg); color: var(--fg); font-family: -apple-system, BlinkMacSystemFont, "Inter", "SF Pro Text", system-ui, sans-serif; -webkit-font-smoothing: antialiased; }
+ header { position: sticky; top: 0; z-index: 10; background: color-mix(in srgb, var(--bg) 88%, transparent); backdrop-filter: saturate(180%) blur(12px); border-bottom: 1px solid var(--border); }
+ .bar { display: flex; align-items: center; gap: 14px; padding: 12px 20px; flex-wrap: wrap; }
+ .brand { display: flex; align-items: center; gap: 10px; font-weight: 800; letter-spacing: -.01em; font-size: 18px; }
+ .brand .dot { width: 12px; height: 12px; border-radius: 50%; background: var(--accent); box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 30%, transparent); animation: pulse 1.4s ease-in-out infinite; }
+ .brand small { font-weight: 500; color: var(--fg-dim); letter-spacing: 0; margin-left: 4px; font-size: 12px; }
+ .controls { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-left: auto; }
+ .controls select, .controls input[type="search"], .controls input[type="range"] { background: var(--bg-card); color: var(--fg); border: 1px solid var(--border); border-radius: 8px; padding: 7px 10px; font: inherit; font-size: 13px; }
+ .controls label { font-size: 12px; color: var(--fg-dim); display: flex; align-items: center; gap: 6px; }
+ .controls input[type="search"] { width: 200px; }
+ .controls input[type="range"] { padding: 0; width: 120px; vertical-align: middle; }
+ .controls .chip { padding: 5px 9px; border-radius: 999px; border: 1px solid var(--border); font-size: 12px; color: var(--fg-dim); }
+ .controls .chip strong { color: var(--fg); font-weight: 700; }
+ .controls .chip .live-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--accent); display: inline-block; vertical-align: middle; margin-right: 6px; box-shadow: 0 0 0 3px var(--accent-glow); animation: pulse 1.4s ease-in-out infinite; }
+
+ main { padding: 20px; }
+ .grid { display: grid; grid-template-columns: repeat(var(--cols), minmax(0, 1fr)); gap: 14px; }
+
+ .card {
+ position: relative;
+ background: var(--bg-card);
+ border: 1px solid var(--border);
+ border-radius: 12px;
+ padding: 18px 14px 14px;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: flex-start;
+ text-align: center;
+ gap: 10px;
+ transition: transform .12s ease, background .12s ease, border-color .12s ease, box-shadow .12s ease;
+ min-height: 160px;
+ }
+ .card:hover { background: var(--bg-card-hover); transform: translateY(-2px); border-color: color-mix(in srgb, var(--accent) 30%, var(--border)); }
+ .card a.outlet-link { color: inherit; text-decoration: none; display: contents; }
+ .card .logo-wrap {
+ width: 88px; height: 88px;
+ border-radius: 14px;
+ background: #fff;
+ border: 1px solid var(--border);
+ display: flex; align-items: center; justify-content: center;
+ overflow: hidden;
+ box-shadow: 0 1px 0 rgba(255,255,255,.04) inset;
+ }
+ .card .logo-wrap img { max-width: 78%; max-height: 78%; object-fit: contain; }
+ .card .logo-wrap.placeholder { background: linear-gradient(135deg, #1c1f28, #2a2e3a); color: #fff; font-weight: 800; font-size: 26px; letter-spacing: -.02em; }
+ .card .meta { display: flex; flex-direction: column; gap: 2px; align-items: center; min-height: 38px; }
+ .card .name { font-weight: 700; font-size: 14px; letter-spacing: -.01em; line-height: 1.15; }
+ .card .sub { color: var(--fg-dim); font-size: 11.5px; display: flex; align-items: center; gap: 5px; }
+ .card .flag { font-size: 14px; line-height: 1; }
+ .card .badge-row { position: absolute; top: 8px; left: 8px; right: 8px; display: flex; justify-content: space-between; align-items: center; pointer-events: none; }
+
+ /* The LIVE pill — RED, GLOWING when on live */
+ .live-pill {
+ pointer-events: auto;
+ display: inline-flex; align-items: center; gap: 5px;
+ padding: 3px 8px 3px 7px;
+ font-size: 10.5px; font-weight: 800; letter-spacing: .08em;
+ color: #fff; background: var(--accent);
+ border-radius: 999px;
+ text-transform: uppercase; text-decoration: none;
+ box-shadow: 0 0 0 3px var(--accent-glow), 0 0 18px 4px var(--accent-glow);
+ animation: pulse 1.4s ease-in-out infinite;
+ }
+ .live-pill::before {
+ content: ""; width: 6px; height: 6px; border-radius: 50%; background: #fff;
+ box-shadow: 0 0 6px #fff;
+ }
+ @keyframes pulse {
+ 0%, 100% { box-shadow: 0 0 0 3px var(--accent-glow), 0 0 18px 4px var(--accent-glow); }
+ 50% { box-shadow: 0 0 0 6px color-mix(in srgb, var(--accent) 45%, transparent), 0 0 26px 10px var(--accent-glow); }
+ }
+ .card.is-live { border-color: color-mix(in srgb, var(--accent) 55%, var(--border)); }
+ .card.is-live::after {
+ content: ""; position: absolute; inset: -1px; border-radius: 13px; pointer-events: none;
+ box-shadow: 0 0 22px 1px color-mix(in srgb, var(--accent) 35%, transparent);
+ }
+
+ .empty { padding: 60px 20px; text-align: center; color: var(--fg-dim); }
+ footer { color: var(--fg-dim); font-size: 12px; padding: 24px 20px 40px; text-align: center; }
+ footer a { color: var(--fg-dim); }
+
+ @media (max-width: 1100px) { :root { --cols: 5; } }
+ @media (max-width: 900px) { :root { --cols: 4; } }
+ @media (max-width: 680px) { :root { --cols: 3; } .controls input[type="search"] { width: 140px; } }
+ @media (max-width: 460px) { :root { --cols: 2; } }
+ </style>
+</head>
+<body>
+ <header>
+ <div class="bar">
+ <div class="brand"><span class="dot"></span>All News Daily<small>global newsroom directory</small></div>
+ <div class="controls">
+ <input id="q" type="search" placeholder="Search outlet / country…" autocomplete="off" />
+ <select id="region"><option value="">All regions</option></select>
+ <select id="lang"><option value="">All languages</option></select>
+ <select id="category"><option value="">All categories</option></select>
+ <select id="sort">
+ <option value="default">Sort: Default</option>
+ <option value="live">Live first</option>
+ <option value="name">Name A→Z</option>
+ <option value="country">Country A→Z</option>
+ <option value="region">Region A→Z</option>
+ </select>
+ <label>Density <input id="cols" type="range" min="3" max="10" value="6" /></label>
+ <span class="chip"><span class="live-dot"></span><strong id="liveCount">0</strong> live now</span>
+ <span class="chip"><strong id="totalCount">0</strong> outlets</span>
+ </div>
+ </div>
+ </header>
+
+ <main>
+ <section id="grid" class="grid"></section>
+ <div id="empty" class="empty" hidden>No outlets match those filters.</div>
+ </main>
+
+ <footer>
+ Live status refreshes every ~90s · click a logo to visit the outlet · click <span class="live-pill" style="animation:none">LIVE</span> to watch the live stream
+ </footer>
+
+ <script>
+ const grid = document.getElementById('grid');
+ const empty = document.getElementById('empty');
+ const $ = id => document.getElementById(id);
+
+ const state = {
+ outlets: [],
+ filters: { q: '', region: '', lang: '', category: '', sort: 'default' },
+ cols: parseInt(localStorage.getItem('and.cols') || '6', 10),
+ };
+
+ function applyCols(n) {
+ n = Math.max(3, Math.min(10, Number(n) || 6));
+ state.cols = n;
+ document.documentElement.style.setProperty('--cols', String(n));
+ localStorage.setItem('and.cols', String(n));
+ const slider = $('cols');
+ if (slider && Number(slider.value) !== n) slider.value = String(n);
+ }
+
+ function initials(name) {
+ return name
+ .replace(/\(.*?\)/g, '')
+ .split(/\s+/)
+ .filter(Boolean)
+ .slice(0, 2)
+ .map(s => s[0])
+ .join('')
+ .toUpperCase();
+ }
+
+ function logoHtml(o) {
+ if (!o.logo) return `<div class="logo-wrap placeholder">${initials(o.name)}</div>`;
+ return `<div class="logo-wrap"><img loading="lazy" decoding="async" alt="${o.name} logo" src="${o.logo}" onerror="this.parentElement.classList.add('placeholder');this.parentElement.innerHTML='${initials(o.name)}';"></div>`;
+ }
+
+ function cardHtml(o) {
+ const liveHref = o.liveVideoId
+ ? `https://www.youtube.com/watch?v=${o.liveVideoId}`
+ : o.liveStream || o.href;
+ const livePill = o.isLive
+ ? `<a class="live-pill" href="${liveHref}" target="_blank" rel="noopener" title="Watch ${o.name} live">Live</a>`
+ : '';
+ return `
+ <article class="card ${o.isLive ? 'is-live' : ''}" data-id="${o.id}">
+ <div class="badge-row">
+ <span></span>
+ ${livePill}
+ </div>
+ <a class="outlet-link" href="${o.href}" target="_blank" rel="noopener" title="Visit ${o.name}">
+ ${logoHtml(o)}
+ <div class="meta">
+ <div class="name">${o.name}</div>
+ <div class="sub"><span class="flag">${o.flag || ''}</span><span>${o.country} · ${o.region}</span></div>
+ </div>
+ </a>
+ </article>
+ `;
+ }
+
+ function render() {
+ const f = state.filters;
+ let list = state.outlets.slice();
+ if (f.q) {
+ const q = f.q.toLowerCase();
+ list = list.filter(o =>
+ o.name.toLowerCase().includes(q) ||
+ (o.country && o.country.toLowerCase().includes(q)) ||
+ (o.region && o.region.toLowerCase().includes(q)) ||
+ (o.lang && o.lang.toLowerCase().includes(q))
+ );
+ }
+ if (f.region) list = list.filter(o => o.region === f.region);
+ if (f.lang) list = list.filter(o => o.lang === f.lang);
+ if (f.category) list = list.filter(o => o.category === f.category);
+
+ if (f.sort === 'live') {
+ list.sort((a, b) => (b.isLive - a.isLive) || a.name.localeCompare(b.name));
+ } else if (f.sort === 'name') {
+ list.sort((a, b) => a.name.localeCompare(b.name));
+ } else if (f.sort === 'country') {
+ list.sort((a, b) => a.country.localeCompare(b.country) || a.name.localeCompare(b.name));
+ } else if (f.sort === 'region') {
+ list.sort((a, b) => a.region.localeCompare(b.region) || a.name.localeCompare(b.name));
+ } else {
+ // default — live first, then by name
+ list.sort((a, b) => (b.isLive - a.isLive) || a.name.localeCompare(b.name));
+ }
+
+ grid.innerHTML = list.map(cardHtml).join('');
+ empty.hidden = list.length > 0;
+
+ const liveCount = state.outlets.filter(o => o.isLive).length;
+ $('liveCount').textContent = liveCount;
+ $('totalCount').textContent = state.outlets.length;
+ }
+
+ function populateSelect(id, values, label) {
+ const el = $(id);
+ const cur = el.value;
+ el.innerHTML = `<option value="">${label}</option>` + values.map(v => `<option value="${v}">${v}</option>`).join('');
+ el.value = cur;
+ }
+
+ async function loadOutlets() {
+ try {
+ const r = await fetch('/api/outlets', { cache: 'no-store' });
+ const j = await r.json();
+ state.outlets = j.outlets || [];
+
+ const regions = [...new Set(state.outlets.map(o => o.region).filter(Boolean))].sort();
+ const langs = [...new Set(state.outlets.map(o => o.lang).filter(Boolean))].sort();
+ const cats = [...new Set(state.outlets.map(o => o.category).filter(Boolean))].sort();
+ populateSelect('region', regions, 'All regions');
+ populateSelect('lang', langs, 'All languages');
+ populateSelect('category', cats, 'All categories');
+
+ render();
+ } catch (e) {
+ console.error('loadOutlets failed', e);
+ }
+ }
+
+ function bindControls() {
+ $('q').addEventListener('input', e => { state.filters.q = e.target.value.trim(); render(); });
+ $('region').addEventListener('change', e => { state.filters.region = e.target.value; render(); });
+ $('lang').addEventListener('change', e => { state.filters.lang = e.target.value; render(); });
+ $('category').addEventListener('change', e => { state.filters.category = e.target.value; render(); });
+ $('sort').addEventListener('change', e => {
+ state.filters.sort = e.target.value;
+ localStorage.setItem('and.sort', e.target.value);
+ render();
+ });
+ $('cols').addEventListener('input', e => applyCols(e.target.value));
+
+ const savedSort = localStorage.getItem('and.sort');
+ if (savedSort) { $('sort').value = savedSort; state.filters.sort = savedSort; }
+ applyCols(state.cols);
+ }
+
+ bindControls();
+ loadOutlets();
+ setInterval(loadOutlets, 30000); // refresh live status every 30s on the client
+ </script>
+</body>
+</html>
diff --git a/scripts/check-live.js b/scripts/check-live.js
new file mode 100644
index 0000000..9bff5ab
--- /dev/null
+++ b/scripts/check-live.js
@@ -0,0 +1,134 @@
+#!/usr/bin/env node
+// Polls each outlet's YouTube /channel/<id>/live URL.
+// YouTube returns the channel's currently-live broadcast page (200 + watch-page HTML)
+// or a "no live stream" placeholder. We detect liveness by looking for the
+// hlsManifestUrl / "isLive":true marker in the inline ytInitialPlayerResponse JSON.
+//
+// Writes data/live-status.json keyed by outlet.id:
+// { "<id>": { isLive: bool, videoId: "...", checkedAt: ISO, error?: "..." } }
+
+const fs = require('fs');
+const path = require('path');
+const https = require('https');
+
+const OUTLETS_PATH = path.join(__dirname, '..', 'data', 'outlets.json');
+const STATUS_PATH = path.join(__dirname, '..', 'data', 'live-status.json');
+
+const UA =
+ 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 ' +
+ '(KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36';
+
+function fetchHtml(url, timeoutMs = 8000) {
+ return new Promise((resolve, reject) => {
+ const req = https.get(
+ url,
+ {
+ headers: {
+ 'User-Agent': UA,
+ 'Accept-Language': 'en-US,en;q=0.9',
+ Accept: 'text/html,application/xhtml+xml',
+ },
+ },
+ res => {
+ if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
+ // Follow one hop
+ fetchHtml(new URL(res.headers.location, url).toString(), timeoutMs).then(resolve, reject);
+ res.resume();
+ return;
+ }
+ if (res.statusCode !== 200) {
+ res.resume();
+ return reject(new Error('status ' + res.statusCode));
+ }
+ let body = '';
+ res.setEncoding('utf8');
+ res.on('data', d => {
+ body += d;
+ if (body.length > 4_000_000) {
+ req.destroy(new Error('body too big'));
+ }
+ });
+ res.on('end', () => resolve(body));
+ }
+ );
+ req.setTimeout(timeoutMs, () => req.destroy(new Error('timeout')));
+ req.on('error', reject);
+ });
+}
+
+function detectLive(html) {
+ // Marker 1 — hls manifest is present only when actively streaming
+ if (/"hlsManifestUrl":\s*"https:\/\/manifest\.googlevideo\.com/.test(html)) {
+ const m = html.match(/"videoId":\s*"([A-Za-z0-9_-]{11})"/);
+ return { isLive: true, videoId: m ? m[1] : null };
+ }
+ // Marker 2 — isLive:true inside videoDetails
+ if (/"videoDetails":\s*\{[^}]*"isLive":true/.test(html)) {
+ const m = html.match(/"videoId":\s*"([A-Za-z0-9_-]{11})"/);
+ return { isLive: true, videoId: m ? m[1] : null };
+ }
+ // Marker 3 — livestream microformat
+ if (/"liveBroadcastDetails":\s*\{[^}]*"isLiveNow":true/.test(html)) {
+ const m = html.match(/"videoId":\s*"([A-Za-z0-9_-]{11})"/);
+ return { isLive: true, videoId: m ? m[1] : null };
+ }
+ return { isLive: false, videoId: null };
+}
+
+async function checkOne(outlet) {
+ if (outlet.liveCheck !== 'youtube' || !outlet.youtube) {
+ return { id: outlet.id, isLive: false, videoId: null, skipped: true };
+ }
+ const url = `https://www.youtube.com/channel/${outlet.youtube}/live`;
+ try {
+ const html = await fetchHtml(url);
+ const { isLive, videoId } = detectLive(html);
+ return { id: outlet.id, isLive, videoId };
+ } catch (e) {
+ return { id: outlet.id, isLive: false, videoId: null, error: e.message };
+ }
+}
+
+async function runBatch(outlets, concurrency = 6) {
+ const results = {};
+ let idx = 0;
+ async function worker() {
+ while (idx < outlets.length) {
+ const o = outlets[idx++];
+ const r = await checkOne(o);
+ results[o.id] = {
+ isLive: r.isLive,
+ videoId: r.videoId,
+ checkedAt: new Date().toISOString(),
+ ...(r.error ? { error: r.error } : {}),
+ };
+ }
+ }
+ await Promise.all(Array.from({ length: concurrency }, worker));
+ return results;
+}
+
+async function main() {
+ const outlets = JSON.parse(fs.readFileSync(OUTLETS_PATH, 'utf8'));
+ const checkable = outlets.filter(o => o.liveCheck === 'youtube' && o.youtube);
+ console.log(`[check-live] checking ${checkable.length} of ${outlets.length} outlets`);
+ const t0 = Date.now();
+
+ const results = await runBatch(outlets);
+
+ const liveCount = Object.values(results).filter(r => r.isLive).length;
+ console.log(`[check-live] done in ${Date.now() - t0}ms — ${liveCount} live`);
+
+ // Merge with prior so we keep history if any outlet errored this tick
+ let prior = {};
+ try {
+ prior = JSON.parse(fs.readFileSync(STATUS_PATH, 'utf8'));
+ } catch (_) {}
+ const merged = { ...prior, ...results };
+ fs.writeFileSync(STATUS_PATH, JSON.stringify(merged, null, 2));
+}
+
+main().catch(e => {
+ console.error('[check-live] fatal', e);
+ process.exit(1);
+});
diff --git a/server.js b/server.js
new file mode 100644
index 0000000..9803b32
--- /dev/null
+++ b/server.js
@@ -0,0 +1,94 @@
+const express = require('express');
+const fs = require('fs');
+const path = require('path');
+const { spawn } = require('child_process');
+
+const app = express();
+const PORT = process.env.PORT || 9788;
+
+const OUTLETS_PATH = path.join(__dirname, 'data', 'outlets.json');
+const LIVE_STATUS_PATH = path.join(__dirname, 'data', 'live-status.json');
+
+function loadOutlets() {
+ try {
+ return JSON.parse(fs.readFileSync(OUTLETS_PATH, 'utf8'));
+ } catch (e) {
+ console.error('[outlets] load failed', e.message);
+ return [];
+ }
+}
+
+function loadLiveStatus() {
+ try {
+ if (!fs.existsSync(LIVE_STATUS_PATH)) return {};
+ return JSON.parse(fs.readFileSync(LIVE_STATUS_PATH, 'utf8'));
+ } catch (e) {
+ return {};
+ }
+}
+
+function mergeOutlets() {
+ const outlets = loadOutlets();
+ const status = loadLiveStatus();
+ return outlets.map(o => {
+ const s = status[o.id];
+ return {
+ ...o,
+ isLive: s ? !!s.isLive : false,
+ liveVideoId: s && s.videoId ? s.videoId : null,
+ lastCheck: s ? s.checkedAt : null
+ };
+ });
+}
+
+app.use(express.json());
+app.use('/static', express.static(path.join(__dirname, 'public')));
+
+app.get('/', (req, res) => {
+ res.sendFile(path.join(__dirname, 'public', 'index.html'));
+});
+
+app.get('/api/outlets', (req, res) => {
+ res.json({ outlets: mergeOutlets(), updatedAt: new Date().toISOString() });
+});
+
+app.get('/api/health', (req, res) => {
+ const outlets = loadOutlets();
+ const status = loadLiveStatus();
+ const liveCount = Object.values(status).filter(s => s && s.isLive).length;
+ res.json({
+ ok: true,
+ outlets: outlets.length,
+ liveOutlets: liveCount,
+ lastStatusFile: fs.existsSync(LIVE_STATUS_PATH)
+ ? fs.statSync(LIVE_STATUS_PATH).mtime
+ : null
+ });
+});
+
+app.get('/api/regions', (req, res) => {
+ const outlets = loadOutlets();
+ const regions = [...new Set(outlets.map(o => o.region))].sort();
+ const countries = [...new Set(outlets.map(o => o.country))].sort();
+ const langs = [...new Set(outlets.map(o => o.lang))].sort();
+ const categories = [...new Set(outlets.map(o => o.category))].sort();
+ res.json({ regions, countries, langs, categories });
+});
+
+function runChecker() {
+ const proc = spawn('node', [path.join(__dirname, 'scripts', 'check-live.js')], {
+ stdio: ['ignore', 'pipe', 'pipe']
+ });
+ proc.stdout.on('data', d => process.stdout.write('[checker] ' + d));
+ proc.stderr.on('data', d => process.stderr.write('[checker] ' + d));
+ proc.on('exit', code => console.log('[checker] exit ' + code));
+}
+
+const POLL_INTERVAL_MS = parseInt(process.env.POLL_INTERVAL_MS || '90000', 10);
+
+app.listen(PORT, () => {
+ console.log(`allnewsdaily listening on http://0.0.0.0:${PORT}`);
+ console.log(`outlets loaded: ${loadOutlets().length}`);
+ runChecker();
+ setInterval(runChecker, POLL_INTERVAL_MS);
+});
(oldest)
·
back to Allnewsdaily
·
ship: pm2-managed on :9788, 98 outlets, live-checker detecti 10cbb44 →