Update the News source catalog for quality (#5)
Adds Techmeme, Lobsters, The Decoder and Simon Willison, all on by default. Hacker News now reads hnrss.org's front page with 150+ points. t3n, ComputerBase, TechCrunch and The Verge stay in the catalog but are off by default. heise Security and heise Developer are described as sections of heise online. WIRED and Engadget are removed. Saved toggles for Sources that are no longer in the catalog are dropped on launch, so they don't count towards "n of m enabled". The README source table matches the catalog. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
8c8ffca5dc
commit
c86609424c
5 files changed
+138
-42
No files matched your search
@@ -33,7 +33,7 @@ or write a daily briefing. It's meant to replace the n8n → Discord workflow.
|
||||
same request as Golem's own "Zustimmen" button (`/abo/setconsentcookie.php`,
|
||||
valid for a day) and reuses the cookie for reader view and the in-app page.
|
||||
With it off, the app shows the teaser and an "Open website anyway" option.
|
||||
- **Sources**: 21 curated feeds (12 German, 9 English), toggle each one, or add any
|
||||
- **Sources**: 23 curated feeds (13 German, 10 English), toggle each one, or add any
|
||||
RSS/Atom feed (it's validated before being added).
|
||||
- **Dark mode**: System / Light / Dark in Settings (applies instantly). The
|
||||
original-page view follows it too: when the phone is dark, sites use their own
|
||||
@@ -145,34 +145,40 @@ existing AI nodes.
|
||||
## News sources
|
||||
|
||||
Every feed URL below was checked to load and parse. The ★ sources are enabled by
|
||||
default.
|
||||
default: a small set of high-signal News sources. The others stay in the catalog
|
||||
to switch on in the Sources tab.
|
||||
|
||||
| | Source | Feed | Focus |
|
||||
|---|---|---|---|
|
||||
| 🇩🇪 ★ | heise online | `heise.de/rss/heise-atom.xml` | Broad IT news, high volume (~150/day) |
|
||||
| 🇩🇪 ★ | Golem.de | `rss.golem.de/rss.php?feed=ATOM1.0` | IT news for professionals |
|
||||
| 🇩🇪 ★ | t3n | `t3n.de/rss.xml` | Digital business, startups, AI |
|
||||
| 🇩🇪 ★ | ComputerBase | `computerbase.de/rss/news.xml` | Hardware, software, gaming |
|
||||
| 🇩🇪 ★ | The Decoder | `the-decoder.de/feed/` | AI news: models, research, industry |
|
||||
| 🇩🇪 | t3n | `t3n.de/rss.xml` | Digital business, startups, AI |
|
||||
| 🇩🇪 | ComputerBase | `computerbase.de/rss/news.xml` | Hardware, software, gaming |
|
||||
| 🇩🇪 | SPIEGEL Netzwelt | `spiegel.de/netzwelt/index.rss` | Mainstream news magazine view |
|
||||
| 🇩🇪 | ZEIT Digital | `newsfeed.zeit.de/digital/index` | Digital policy and society |
|
||||
| 🇩🇪 | netzpolitik.org | `netzpolitik.org/feed/` | Digital rights, privacy |
|
||||
| 🇩🇪 | heise Security | `heise.de/security/rss/news-atom.xml` | Security vulnerabilities |
|
||||
| 🇩🇪 | heise Developer | `heise.de/developer/rss/news-atom.xml` | Software development |
|
||||
| 🇩🇪 | heise Security | `heise.de/security/rss/news-atom.xml` | Security section of heise online (turn heise online off to use it) |
|
||||
| 🇩🇪 | heise Developer | `heise.de/developer/rss/news-atom.xml` | Developer section of heise online (turn heise online off to use it) |
|
||||
| 🇩🇪 | Caschys Blog | `stadt-bremerhaven.de/feed/` | Consumer tech, smartphones |
|
||||
| 🇩🇪 | derStandard Web | `derstandard.at/rss/web` | Austrian daily's tech section |
|
||||
| 🇩🇪 | Borns IT- und Windows-Blog | `borncity.com/blog/feed/` | Windows, patches, admin topics |
|
||||
| 🇬🇧 ★ | The Verge | `theverge.com/rss/index.xml` | Consumer tech, policy |
|
||||
| 🇬🇧 | The Verge | `theverge.com/rss/index.xml` | Consumer tech, policy |
|
||||
| 🇬🇧 ★ | Ars Technica | `feeds.arstechnica.com/arstechnica/index` | In-depth tech and science |
|
||||
| 🇬🇧 ★ | TechCrunch | `techcrunch.com/feed/` | Startups, funding |
|
||||
| 🇬🇧 ★ | Hacker News | `news.ycombinator.com/rss` | Front page links |
|
||||
| 🇬🇧 | TechCrunch | `techcrunch.com/feed/` | Startups, funding |
|
||||
| 🇬🇧 ★ | Hacker News | `hnrss.org/frontpage?points=150` | Front page stories with 150+ points |
|
||||
| 🇬🇧 ★ | Techmeme | `techmeme.com/feed.xml` | The day's top tech stories, picked by editors |
|
||||
| 🇬🇧 ★ | Lobsters | `lobste.rs/rss` | Computing links, voted by a programmer community |
|
||||
| 🇬🇧 ★ | Simon Willison | `simonwillison.net/atom/entries/` | LLMs and AI tools, tried out hands-on |
|
||||
| 🇬🇧 | MIT Technology Review | `technologyreview.com/feed/` | AI, biotech, climate |
|
||||
| 🇬🇧 | WIRED | `wired.com/feed/rss` | Tech and culture |
|
||||
| 🇬🇧 | Engadget | `engadget.com/rss.xml` | Gadgets |
|
||||
| 🇬🇧 | The Register | `theregister.com/headlines.atom` | Enterprise IT |
|
||||
| 🇬🇧 | BleepingComputer | `bleepingcomputer.com/feed/` | Security, malware |
|
||||
|
||||
Reader view works for 20 of the 21 sources. ZEIT Digital blocks non-browser
|
||||
requests, so the app shows the teaser and offers the original page.
|
||||
Reader view works for the publishers' own articles except ZEIT Digital, which
|
||||
blocks non-browser requests, so the app shows the teaser and offers the original
|
||||
page. Hacker News and Lobsters link to articles all over the web, so reader view
|
||||
depends on the linked site. Techmeme links to its own summary page, which only
|
||||
has the teaser; the original page links on to the full story.
|
||||
|
||||
## Project layout
|
||||
|
||||
|
||||
@@ -22,6 +22,15 @@ const defaultSources = <NewsSource>[
|
||||
description: 'IT-News für Profis',
|
||||
enabledByDefault: true,
|
||||
),
|
||||
NewsSource(
|
||||
id: 'the-decoder',
|
||||
name: 'The Decoder',
|
||||
feedUrl: 'https://the-decoder.de/feed/',
|
||||
homepage: 'https://the-decoder.de',
|
||||
language: SourceLanguage.de,
|
||||
description: 'KI-News: Modelle, Forschung, Branche',
|
||||
enabledByDefault: true,
|
||||
),
|
||||
NewsSource(
|
||||
id: 't3n',
|
||||
name: 't3n',
|
||||
@@ -29,7 +38,6 @@ const defaultSources = <NewsSource>[
|
||||
homepage: 'https://t3n.de',
|
||||
language: SourceLanguage.de,
|
||||
description: 'Digitales Business, Startups, KI',
|
||||
enabledByDefault: true,
|
||||
checkPaywallOnPage: true,
|
||||
),
|
||||
NewsSource(
|
||||
@@ -39,7 +47,6 @@ const defaultSources = <NewsSource>[
|
||||
homepage: 'https://www.computerbase.de',
|
||||
language: SourceLanguage.de,
|
||||
description: 'Hardware, Software, Gaming',
|
||||
enabledByDefault: true,
|
||||
),
|
||||
NewsSource(
|
||||
id: 'spiegel-netzwelt',
|
||||
@@ -72,7 +79,7 @@ const defaultSources = <NewsSource>[
|
||||
feedUrl: 'https://www.heise.de/security/rss/news-atom.xml',
|
||||
homepage: 'https://www.heise.de/security/',
|
||||
language: SourceLanguage.de,
|
||||
description: 'Sicherheitslücken und Security-News',
|
||||
description: 'Security-Teil von heise online (heise online ausschalten, um ihn einzeln zu lesen)',
|
||||
),
|
||||
NewsSource(
|
||||
id: 'heise-developer',
|
||||
@@ -80,7 +87,7 @@ const defaultSources = <NewsSource>[
|
||||
feedUrl: 'https://www.heise.de/developer/rss/news-atom.xml',
|
||||
homepage: 'https://www.heise.de/developer/',
|
||||
language: SourceLanguage.de,
|
||||
description: 'Softwareentwicklung',
|
||||
description: 'Developer-Teil von heise online (heise online ausschalten, um ihn einzeln zu lesen)',
|
||||
),
|
||||
NewsSource(
|
||||
id: 'caschy',
|
||||
@@ -115,7 +122,6 @@ const defaultSources = <NewsSource>[
|
||||
homepage: 'https://www.theverge.com',
|
||||
language: SourceLanguage.en,
|
||||
description: 'Consumer tech, culture, policy',
|
||||
enabledByDefault: true,
|
||||
),
|
||||
NewsSource(
|
||||
id: 'arstechnica',
|
||||
@@ -133,15 +139,41 @@ const defaultSources = <NewsSource>[
|
||||
homepage: 'https://techcrunch.com',
|
||||
language: SourceLanguage.en,
|
||||
description: 'Startups, funding, big tech',
|
||||
enabledByDefault: true,
|
||||
),
|
||||
NewsSource(
|
||||
id: 'hackernews',
|
||||
name: 'Hacker News',
|
||||
feedUrl: 'https://news.ycombinator.com/rss',
|
||||
feedUrl: 'https://hnrss.org/frontpage?points=150',
|
||||
homepage: 'https://news.ycombinator.com',
|
||||
language: SourceLanguage.en,
|
||||
description: 'Front page links, ranked by readers',
|
||||
description: 'Front page stories with 150+ points',
|
||||
enabledByDefault: true,
|
||||
),
|
||||
NewsSource(
|
||||
id: 'techmeme',
|
||||
name: 'Techmeme',
|
||||
feedUrl: 'https://www.techmeme.com/feed.xml',
|
||||
homepage: 'https://www.techmeme.com',
|
||||
language: SourceLanguage.en,
|
||||
description: "The day's top tech stories, picked by editors",
|
||||
enabledByDefault: true,
|
||||
),
|
||||
NewsSource(
|
||||
id: 'lobsters',
|
||||
name: 'Lobsters',
|
||||
feedUrl: 'https://lobste.rs/rss',
|
||||
homepage: 'https://lobste.rs',
|
||||
language: SourceLanguage.en,
|
||||
description: 'Computing links, voted by a programmer community',
|
||||
enabledByDefault: true,
|
||||
),
|
||||
NewsSource(
|
||||
id: 'simonwillison',
|
||||
name: 'Simon Willison',
|
||||
feedUrl: 'https://simonwillison.net/atom/entries/',
|
||||
homepage: 'https://simonwillison.net',
|
||||
language: SourceLanguage.en,
|
||||
description: 'LLMs and AI tools, tried out hands-on',
|
||||
enabledByDefault: true,
|
||||
),
|
||||
NewsSource(
|
||||
@@ -152,22 +184,6 @@ const defaultSources = <NewsSource>[
|
||||
language: SourceLanguage.en,
|
||||
description: 'AI, biotech, climate tech',
|
||||
),
|
||||
NewsSource(
|
||||
id: 'wired',
|
||||
name: 'WIRED',
|
||||
feedUrl: 'https://www.wired.com/feed/rss',
|
||||
homepage: 'https://www.wired.com',
|
||||
language: SourceLanguage.en,
|
||||
description: 'Tech, science and culture',
|
||||
),
|
||||
NewsSource(
|
||||
id: 'engadget',
|
||||
name: 'Engadget',
|
||||
feedUrl: 'https://www.engadget.com/rss.xml',
|
||||
homepage: 'https://www.engadget.com',
|
||||
language: SourceLanguage.en,
|
||||
description: 'Gadgets and consumer electronics',
|
||||
),
|
||||
NewsSource(
|
||||
id: 'theregister',
|
||||
name: 'The Register',
|
||||
|
||||
@@ -120,18 +120,21 @@ class AppState extends ChangeNotifier {
|
||||
|
||||
/// Switches on curated sources that are on by default the first time the
|
||||
/// app sees them, so they also reach installs with saved toggles. Sources
|
||||
/// seen before keep whatever the user chose.
|
||||
/// seen before keep whatever the user chose. Saved ids of Sources that are
|
||||
/// no longer in the catalog are dropped.
|
||||
Future<void> _enableNewDefaultSources() async {
|
||||
final saved = _storage.loadEnabledSourceIds();
|
||||
final seen = _storage.loadSeenSourceIds() ??
|
||||
(saved == null ? const <String>{} : legacySeenSourceIds);
|
||||
final unseen = _curated.where((s) => !seen.contains(s.id)).toList();
|
||||
final known = {for (final s in allSources) s.id};
|
||||
enabledSourceIds = {
|
||||
...?saved,
|
||||
...?saved?.where(known.contains),
|
||||
for (final s in unseen)
|
||||
if (s.enabledByDefault) s.id,
|
||||
};
|
||||
if (unseen.isEmpty) return;
|
||||
final dropped = saved != null && saved.any((id) => !known.contains(id));
|
||||
if (unseen.isEmpty && !dropped) return;
|
||||
await _storage.saveSeenSourceIds({...seen, ...unseen.map((s) => s.id)});
|
||||
await _storage.saveEnabledSourceIds(enabledSourceIds);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
import 'package:daily_tech_news/data/default_sources.dart';
|
||||
import 'package:daily_tech_news/models/news_source.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
NewsSource _byId(String id) => defaultSources.singleWhere((s) => s.id == id);
|
||||
|
||||
void main() {
|
||||
test('the News sources on by default are the curated quality set', () {
|
||||
final onByDefault = {
|
||||
for (final s in defaultSources)
|
||||
if (s.enabledByDefault) s.id,
|
||||
};
|
||||
|
||||
expect(onByDefault, {
|
||||
'heise', 'golem', 'arstechnica', 'hackernews',
|
||||
'techmeme', 'lobsters', 'the-decoder', 'simonwillison',
|
||||
});
|
||||
});
|
||||
|
||||
test('the added Sources read the agreed feeds', () {
|
||||
final feeds = {
|
||||
for (final id in ['techmeme', 'lobsters', 'the-decoder', 'simonwillison'])
|
||||
id: (_byId(id).feedUrl, _byId(id).language),
|
||||
};
|
||||
|
||||
expect(feeds, {
|
||||
'techmeme': ('https://www.techmeme.com/feed.xml', SourceLanguage.en),
|
||||
'lobsters': ('https://lobste.rs/rss', SourceLanguage.en),
|
||||
'the-decoder': ('https://the-decoder.de/feed/', SourceLanguage.de),
|
||||
'simonwillison': ('https://simonwillison.net/atom/entries/', SourceLanguage.en),
|
||||
});
|
||||
});
|
||||
|
||||
test('Hacker News only brings well-voted front page stories', () {
|
||||
expect(_byId('hackernews').feedUrl, 'https://hnrss.org/frontpage?points=150');
|
||||
});
|
||||
|
||||
test('switched-off Sources stay in the catalog, WIRED and Engadget are gone', () {
|
||||
final ids = defaultSources.map((s) => s.id).toSet();
|
||||
|
||||
expect(ids, containsAll(['t3n', 'computerbase', 'techcrunch', 'verge']));
|
||||
expect(ids, isNot(contains('wired')));
|
||||
expect(ids, isNot(contains('engadget')));
|
||||
});
|
||||
|
||||
test('every Source has its own id', () {
|
||||
final ids = defaultSources.map((s) => s.id).toList();
|
||||
|
||||
expect(ids.toSet().length, ids.length);
|
||||
});
|
||||
}
|
||||
@@ -78,12 +78,32 @@ void main() {
|
||||
test('an install from before the app remembered seen Sources keeps its toggles', () async {
|
||||
// Toggles saved by an earlier version: golem, t3n and others switched off.
|
||||
SharedPreferences.setMockInitialValues({
|
||||
'enabled_sources': ['heise', 'verge', 'wired'],
|
||||
'enabled_sources': ['heise', 'verge', 'mit-tr'],
|
||||
});
|
||||
|
||||
final newSource = _source('new-source', onByDefault: true);
|
||||
final state = await _launch([...defaultSources, newSource]);
|
||||
|
||||
expect(state.enabledSourceIds, {'heise', 'verge', 'wired', 'new-source'});
|
||||
expect(state.enabledSourceIds, {
|
||||
'heise', 'verge', 'mit-tr', 'new-source',
|
||||
// Added to the catalog later, on by default.
|
||||
'techmeme', 'lobsters', 'the-decoder', 'simonwillison',
|
||||
});
|
||||
});
|
||||
|
||||
test('an install from before the catalog update gets the new Sources and drops removed ones',
|
||||
() async {
|
||||
// Toggles saved by an earlier version, with WIRED and Engadget switched on.
|
||||
SharedPreferences.setMockInitialValues({
|
||||
'enabled_sources': ['heise', 'golem', 't3n', 'verge', 'wired', 'engadget'],
|
||||
});
|
||||
|
||||
final state = await _launch(defaultSources);
|
||||
|
||||
expect(state.enabledSourceIds, {
|
||||
'heise', 'golem', 't3n', 'verge',
|
||||
'techmeme', 'lobsters', 'the-decoder', 'simonwillison',
|
||||
});
|
||||
expect(state.enabledSources.map((s) => s.id), isNot(contains('wired')));
|
||||
});
|
||||
}
|
||||
Reference in new issue
Block a user