Weekly digest
GET /v1/history returns every dated viewing, session and finish. This script
turns the last seven days into a short summary you can paste anywhere or
send yourself.
Key: library:read.
import osfrom collections import Counterfrom datetime import date, timedelta
import requests
API = "https://loomkeep.app/api/v1"HEADERS = {"Authorization": f"Bearer {os.environ['LOOMKEEP_API_KEY']}"}
since = (date.today() - timedelta(days=7)).isoformat()events, page = [], 1
while True: res = requests.get( f"{API}/history", headers=HEADERS, params={"from": since, "limit": 100, "page": page}, ) res.raise_for_status() body = res.json() events += body["items"] if not body["hasMore"]: break page += 1
minutes = sum(e["durationMinutes"] or 0 for e in events)episodes = Counter( e["work"]["title"] for e in events if e["type"] == "EPISODE_WATCHED")finished = [ e["work"]["title"] for e in events if e["type"] in ("MOVIE_WATCHED", "GAME_COMPLETED", "BOOK_FINISHED", "ALBUM_LISTENED")]
print(f"This week: {len(events)} moments, about {minutes // 60} hours.")for title, count in episodes.most_common(): print(f"- {title}: {count} episode(s)")for title in finished: print(f"- Finished {title}")const API = "https://loomkeep.app/api/v1";const headers = { Authorization: `Bearer ${process.env.LOOMKEEP_API_KEY}` };
const since = new Date(Date.now() - 7 * 86_400_000).toISOString().slice(0, 10);const events = [];
for (let page = 1; ; page++) { const params = new URLSearchParams({ from: since, limit: "100", page: String(page) }); const res = await fetch(`${API}/history?${params}`, { headers }); if (!res.ok) throw new Error((await res.json()).code); const body = await res.json(); events.push(...body.items); if (!body.hasMore) break;}
const minutes = events.reduce((sum, e) => sum + (e.durationMinutes ?? 0), 0);const episodes = new Map();for (const e of events.filter((e) => e.type === "EPISODE_WATCHED")) { episodes.set(e.work.title, (episodes.get(e.work.title) ?? 0) + 1);}const finished = events .filter((e) => ["MOVIE_WATCHED", "GAME_COMPLETED", "BOOK_FINISHED", "ALBUM_LISTENED"].includes(e.type)) .map((e) => e.work.title);
console.log(`This week: ${events.length} moments, about ${Math.floor(minutes / 60)} hours.`);for (const [title, count] of [...episodes].sort((a, b) => b[1] - a[1])) { console.log(`- ${title}: ${count} episode(s)`);}for (const title of finished) console.log(`- Finished ${title}`);Titles come in your account’s language; add lang: "en" to the parameters
to force one. Events without a date, often from an import, never fall in a
window: see History and cycles.