Skip to content

Commit cf56a2c

Browse files
committed
Merge branch 'add-briefing-service-recipe' of https://github.com/jshelley/calibre
2 parents d19824b + bd7757d commit cf56a2c

1 file changed

Lines changed: 79 additions & 0 deletions

File tree

recipes/briefing_service.recipe

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
#!/usr/bin/env python
2+
# vim:fileencoding=utf-8
3+
__license__ = 'GPL v3'
4+
__copyright__ = '2026, Jordan Shelley'
5+
6+
import json
7+
8+
from calibre.web.feeds.news import BasicNewsRecipe
9+
10+
BASE = 'https://briefing-service.wholemind.workers.dev/v1/briefings/'
11+
12+
SECTIONS = (
13+
('ai', 'AI Briefing'),
14+
('labs', 'Frontier Labs'),
15+
('finance', 'Markets'),
16+
('sports', 'US Sports'),
17+
('soccer', 'European Football'),
18+
('us', 'US News'),
19+
('world', 'World News'),
20+
)
21+
22+
23+
class BriefingService(BasicNewsRecipe):
24+
title = 'Briefing Service'
25+
__author__ = 'Jordan Shelley'
26+
description = (
27+
'Hourly LLM-ranked news briefings: AI, frontier labs, markets, '
28+
'US sports, European football, US and world news'
29+
)
30+
publisher = 'Briefing Service'
31+
category = 'news, ai, finance, sports'
32+
language = 'en'
33+
publication_type = 'newspaper'
34+
oldest_article = 1
35+
max_articles_per_feed = 30
36+
no_stylesheets = True
37+
timefmt = ''
38+
auto_cleanup = True
39+
use_embedded_content = False
40+
remove_empty_feeds = True
41+
ignore_duplicate_articles = {'url'}
42+
cover_url = BASE + 'ai/pages/0.png'
43+
44+
def parse_index(self):
45+
feeds = []
46+
for key, name in SECTIONS:
47+
try:
48+
raw = self.index_to_soup(BASE + key + '/summary', raw=True)
49+
data = json.loads(raw)
50+
except Exception as e:
51+
self.log.warn('Failed to fetch section', name, ':', e)
52+
continue
53+
ranked_at = (data.get('ranked_at') or '')[:16].replace('T', ' ')
54+
items = []
55+
if data.get('lead'):
56+
items.append(data['lead'])
57+
items.extend(data.get('stories') or [])
58+
items.extend(data.get('research') or [])
59+
articles = []
60+
for story in items:
61+
url = story.get('link')
62+
if not url:
63+
continue
64+
desc = story.get('summary') or ''
65+
src = story.get('source')
66+
if src:
67+
desc = '{} [{}]'.format(desc, src).strip()
68+
articles.append({
69+
'title': story.get('headline') or url,
70+
'url': url,
71+
'date': story.get('published') or ranked_at,
72+
'description': desc,
73+
})
74+
if articles:
75+
title = data.get('name') or name
76+
if ranked_at:
77+
title = '{} ({} UTC)'.format(title, ranked_at)
78+
feeds.append((title, articles))
79+
return feeds

0 commit comments

Comments
 (0)