Add fetch_evening_tasks.py script and shared notion_helpers module
Refactors weekly-template's Notion client setup into a shared helper (with .env/dotenv support and full pagination), and adds a dedicated evening-prep script so both commands can fall back to script-based fetching instead of the MCP search/fetch pattern. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -30,12 +30,11 @@ Let me know how much unscheduled time I have.
|
|||||||
* Directly beneath the Agenda table, if Step 1b found a workout scheduled tomorrow, add a one-line **Workout** note stating whether the 9 AM block is clear or naming the conflicting meeting.
|
* Directly beneath the Agenda table, if Step 1b found a workout scheduled tomorrow, add a one-line **Workout** note stating whether the 9 AM block is clear or naming the conflicting meeting.
|
||||||
|
|
||||||
## Step 3: Get task data
|
## Step 3: Get task data
|
||||||
This Notion MCP has no filter/query tool and `view://` URLs aren't fetchable, so use the search → fetch → filter pattern (see CLAUDE.md "HOW TO QUERY THESE DATABASES"). Do NOT try to fetch views and do NOT print any "falling back" message.
|
|
||||||
|
|
||||||
1. **Get this week's focus projects** — read the current Weekly Note (`Weekly Notes/Weekly Note - <Sunday's date>.md`) and use its listed focus projects + weekly goals. (Only if that note is missing: `notion-search` the Projects collection `collection://2c0abd6c-450a-807a-a3d7-000b8d8aaecc` with `page_size: 25`, fetch the results in parallel, and keep those with `Focus this week` = `__YES__` and `Status` = "Active".)
|
1. **Get this week's focus projects** — read the current Weekly Note (`Weekly Notes/Weekly Note - <Sunday's date>.md`) and use its listed focus projects + weekly goals. (Only if that note is missing: `notion-search` the Projects collection `collection://2c0abd6c-450a-807a-a3d7-000b8d8aaecc` with `page_size: 25`, fetch the results in parallel, and keep those with `Focus this week` = `__YES__` and `Status` = "Active".)
|
||||||
2. **Find candidate tasks** — `notion-search` the Tasks collection `collection://2c0abd6c-450a-8085-80a0-000ba136a154` with `page_size: 25`, `max_highlight_length: 0`, and a `query` seeded with the focus-project names plus "blocking due this week suggested today".
|
2. **Get candidate tasks** — run `python3 scripts/fetch_evening_tasks.py`. This already excludes `Status` = "Done" or "Blocked" and paginates through the full Tasks database, so no candidate is missed as the tracker grows.
|
||||||
3. **Fetch candidate task pages in parallel** to read their real properties (`Status`, `Suggested for today`, `Due date`, `Blocking`, `Priority`, `Project`).
|
3. **Rank locally**: by (1) `has_blocking` true, (2) `suggested_for_today` ≤ tomorrow, (3) soonest `due_date`.
|
||||||
4. **Filter & rank locally**: drop `Status` = "Done" or "Blocked". Rank the rest by (1) `Blocking` populated, (2) `Suggested for today` ≤ tomorrow, (3) soonest `Due date`.
|
|
||||||
|
**If the script errors or is unavailable** (e.g. missing `.env`/`NOTION_TOKEN` on this machine), fall back instead of failing the command: `notion-search` the Tasks collection `collection://2c0abd6c-450a-8085-80a0-000ba136a154` with `page_size: 25`, `max_highlight_length: 0`, seeded with the focus-project names plus "blocking due this week suggested today" (this Notion MCP has no filter/query tool and `view://` URLs aren't fetchable — see CLAUDE.md "HOW TO QUERY THESE DATABASES"). Fetch candidate pages in parallel to read `Status`, `Suggested for today`, `Due date`, `Blocking`, `Priority`, `Project`, then filter/rank as in step 3 above. Do NOT print any "falling back" message for `view://` fetches, but DO tell me the script failed and you're using the fallback.
|
||||||
|
|
||||||
Format the note with the following sections:
|
Format the note with the following sections:
|
||||||
1. Under **Suggested Tasks** list up to 3 tasks from the ranked set above (Blocking-others tasks always come first). For each, show the task name, related project, and a Notion link.
|
1. Under **Suggested Tasks** list up to 3 tasks from the ranked set above (Blocking-others tasks always come first). For each, show the task name, related project, and a Notion link.
|
||||||
@@ -46,8 +45,8 @@ Format the note with the following sections:
|
|||||||
The Tasks Tracker has a `Top Task` checkbox property and a `Top Tasks` view (filtered to `Top Task = true` AND `Status != Done`) for surfacing the current day's Big 3 at a glance. At most 3 tasks may be checked at any time.
|
The Tasks Tracker has a `Top Task` checkbox property and a `Top Tasks` view (filtered to `Top Task = true` AND `Status != Done`) for surfacing the current day's Big 3 at a glance. At most 3 tasks may be checked at any time.
|
||||||
|
|
||||||
1. Present the Suggested Tasks from Step 3 to AJ and let him confirm or swap any of them before finalizing.
|
1. Present the Suggested Tasks from Step 3 to AJ and let him confirm or swap any of them before finalizing.
|
||||||
2. `notion-search` the Tasks collection (`collection://2c0abd6c-450a-8085-80a0-000ba136a154`) filtered/queried for `Top Task` = true (or check the pages already known from recent notes) to find any tasks still marked from a prior day.
|
2. Use `marked_top_tasks` from the Step 3 script output (already every task with `Top Task = true`, any status — no extra search needed). If the Step 3 fallback was used instead, `notion-search` the Tasks collection (`collection://2c0abd6c-450a-8085-80a0-000ba136a154`) filtered/queried for `Top Task` = true.
|
||||||
3. Any currently-marked task whose `Status` = "Done" gets `Top Task` set to false immediately — regardless of whether it's in today's agreed set (it can't be, since Step 3.4 already drops Done tasks). Don't wait for a replacement to unmark a completed task.
|
3. Any currently-marked task whose `status` = "Done" gets `Top Task` set to false immediately — regardless of whether it's in today's agreed set. Don't wait for a replacement to unmark a completed task.
|
||||||
4. For each other currently-marked task that is NOT in today's agreed set, `notion-update-page` to set `Top Task` = false.
|
4. For each other currently-marked task that is NOT in today's agreed set, `notion-update-page` to set `Top Task` = false.
|
||||||
5. For each of the (up to 3) agreed tasks, `notion-update-page` to set `Top Task` = true.
|
5. For each of the (up to 3) agreed tasks, `notion-update-page` to set `Top Task` = true.
|
||||||
6. Never have more than 3 tasks checked at once — unmark old ones before marking new ones.
|
6. Never have more than 3 tasks checked at once — unmark old ones before marking new ones.
|
||||||
|
|||||||
@@ -11,15 +11,14 @@ Also remove all prior weekly notes that are more than 1 week old.
|
|||||||
Please create a new note in the "Weekly Notes" folder.
|
Please create a new note in the "Weekly Notes" folder.
|
||||||
The title should be "Weekly Note - Month, Day Year" using today's date.
|
The title should be "Weekly Note - Month, Day Year" using today's date.
|
||||||
|
|
||||||
## Step 2: Review Notion data and create the note
|
## Step 2: Fetch Notion data and create the note
|
||||||
This Notion MCP has no filter/query tool and `view://` URLs aren't fetchable, so use the search → fetch → filter pattern (see CLAUDE.md "HOW TO QUERY THESE DATABASES"). Do NOT try to fetch views and do NOT print any "falling back" message.
|
Run `python3 scripts/fetch_weekly_data.py` to get `projects`, `tasks`, and `reading` in one JSON payload (this reduces MCP round-trips and inference cost). `projects` is already filtered to active, non-personal projects and includes each one's current `focus_this_week` state. `tasks` is already filtered to non-Done tasks due within 7 days. `reading` is already filtered to "To Read"/"In Progress".
|
||||||
|
|
||||||
1. **Active projects** — `notion-search` the Projects collection `collection://2c0abd6c-450a-807a-a3d7-000b8d8aaecc` with `page_size: 25`, `max_highlight_length: 0`. Fetch the returned pages **in parallel**, then keep those with `Status` = "Active" and `Type` ≠ "Personal".
|
**If the script errors or is unavailable** (e.g. missing `.env`/`NOTION_TOKEN` on this machine), fall back to the search → fetch → filter pattern instead of failing the command (see CLAUDE.md "HOW TO QUERY THESE DATABASES"): `notion-search` the Projects collection `collection://2c0abd6c-450a-807a-a3d7-000b8d8aaecc` and Tasks collection `collection://2c0abd6c-450a-8085-80a0-000ba136a154` (both `page_size: 25`, `max_highlight_length: 0`), fetch results in parallel, and apply the same filters manually. Do NOT try to fetch `view://` URLs and do NOT print any "falling back" message for those — but DO mention to me that the script failed and you're using the fallback.
|
||||||
2. **This week's tasks** — `notion-search` the Tasks collection `collection://2c0abd6c-450a-8085-80a0-000ba136a154` with `page_size: 25`, `max_highlight_length: 0`, seeding `query` with the active-project names plus "due this week blocking". Fetch the results in parallel and drop anything with `Status` = "Done".
|
|
||||||
|
|
||||||
List all active projects and ask me which 3 I want to prioritize for the upcoming week.
|
List all active projects and ask me which 3 I want to prioritize for the upcoming week.
|
||||||
If there isn't a weekly goal for the selected projects, ask me to write one.
|
If there isn't a weekly goal for the selected projects, ask me to write one.
|
||||||
Update the Focus this week field to true for the selected projects (and set to false for any previously selected projects not in my new list).
|
Update the Focus this week field to true for the selected projects (and false for any project where `focus_this_week` was already true but isn't in my new list) via the Notion MCP — the script is read-only and can't make this update.
|
||||||
|
|
||||||
Then add the following to the note
|
Then add the following to the note
|
||||||
|
|
||||||
@@ -43,9 +42,9 @@ Fetch the Runna ical feed (see CLAUDE.md "Data Sources") with `curl -sL <url>`.
|
|||||||
Add a **Workouts this week** section: a table with columns Date, Workout, Block (e.g. 9:00–9:50 AM), and Status (Clear, or "Conflict: <meeting name> <time>"). Skip days with no scheduled workout.
|
Add a **Workouts this week** section: a table with columns Date, Workout, Block (e.g. 9:00–9:50 AM), and Status (Clear, or "Conflict: <meeting name> <time>"). Skip days with no scheduled workout.
|
||||||
|
|
||||||
## Step 4: Generate my reading list
|
## Step 4: Generate my reading list
|
||||||
Using the same search → fetch → filter pattern: `notion-search` the Reading List collection `collection://2c4abd6c-450a-804a-809a-000bc77d02c1` with `page_size: 25`, `max_highlight_length: 0`. Fetch the returned pages in parallel and keep those with `Status` = "To Read" or "In Progress". (Don't fetch `view://` URLs.)
|
Use the `reading` list already fetched in Step 2 (from `fetch_weekly_data.py`, or its fallback). If the Step 2 fallback was used and didn't cover reading, `notion-search` the Reading List collection `collection://2c4abd6c-450a-804a-809a-000bc77d02c1` with `page_size: 25`, `max_highlight_length: 0`, fetch the returned pages in parallel, and keep those with `Status` = "To Read" or "In Progress". (Don't fetch `view://` URLs.)
|
||||||
|
|
||||||
3. Under **Suggested Reading** recommend 2 articles that fit the available time blocks:
|
Recommend 2 articles under **Suggested Reading** that fit the available time blocks:
|
||||||
- Look at the unscheduled time across the week (from Step 3) to determine what reading time is appropriate
|
- Look at the unscheduled time across the week (from Step 3) to determine what reading time is appropriate
|
||||||
- Match articles based on Read time field:
|
- Match articles based on Read time field:
|
||||||
* Short = <5 minutes (fits in small gaps)
|
* Short = <5 minutes (fits in small gaps)
|
||||||
|
|||||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -46,3 +46,7 @@ Temporary Items
|
|||||||
|
|
||||||
# Environment
|
# Environment
|
||||||
.env
|
.env
|
||||||
|
|
||||||
|
# Python
|
||||||
|
__pycache__/
|
||||||
|
*.pyc
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# Weekly Planning Scripts
|
# Daily/Weekly Planning Scripts
|
||||||
|
|
||||||
## Setup
|
## Setup
|
||||||
|
|
||||||
@@ -24,30 +24,38 @@ For each database (Projects, Tasks Tracker, Reading List):
|
|||||||
2. Click "..." menu → "Connections"
|
2. Click "..." menu → "Connections"
|
||||||
3. Add your "Weekly Planning Script" integration
|
3. Add your "Weekly Planning Script" integration
|
||||||
|
|
||||||
### 4. Set environment variable
|
### 4. Set your Notion token
|
||||||
|
|
||||||
Add to your `~/.zshrc` or `~/.bash_profile`:
|
Create a `.env` file in the project root:
|
||||||
```bash
|
```bash
|
||||||
export NOTION_TOKEN="secret_your_token_here"
|
NOTION_TOKEN="secret_your_token_here"
|
||||||
```
|
```
|
||||||
|
|
||||||
Then reload: `source ~/.zshrc`
|
(`.env` is gitignored, and the script loads it automatically via `python-dotenv`.)
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
Run the script to fetch weekly planning data:
|
### `fetch_weekly_data.py`
|
||||||
```bash
|
```bash
|
||||||
python3 scripts/fetch_weekly_data.py
|
python3 scripts/fetch_weekly_data.py
|
||||||
```
|
```
|
||||||
|
|
||||||
Or add it to your PATH and run directly:
|
|
||||||
```bash
|
|
||||||
./scripts/fetch_weekly_data.py
|
|
||||||
```
|
|
||||||
|
|
||||||
This outputs JSON with:
|
This outputs JSON with:
|
||||||
- Projects marked "Focus this week"
|
- Active, non-personal projects (candidates for the weekly focus selection)
|
||||||
- Tasks due within the next 7 days
|
- Tasks due within the next 7 days
|
||||||
- Reading list articles (To Read or In Progress)
|
- Reading list articles (To Read or In Progress)
|
||||||
|
|
||||||
The weekly-template skill automatically calls this script to generate your weekly note, reducing AI inference costs.
|
The `/weekly-template` command calls this script to generate your weekly note, reducing AI inference costs. If the script fails, the command falls back to the Notion MCP search/fetch pattern.
|
||||||
|
|
||||||
|
### `fetch_evening_tasks.py`
|
||||||
|
```bash
|
||||||
|
python3 scripts/fetch_evening_tasks.py
|
||||||
|
```
|
||||||
|
|
||||||
|
This outputs JSON with:
|
||||||
|
- All open tasks (not Done, not Blocked) — paginated, so it stays complete as the tracker grows
|
||||||
|
- Every task currently marked `Top Task = true` (any status), for unmarking stale ones
|
||||||
|
|
||||||
|
The `/evening-prep` command calls this script to build the day's Suggested Tasks and manage the `Top Task` checkbox, with the same MCP fallback if the script fails.
|
||||||
|
|
||||||
|
Both scripts share Notion client setup, database IDs, and pagination logic in `notion_helpers.py`.
|
||||||
|
|||||||
125
scripts/fetch_evening_tasks.py
Normal file
125
scripts/fetch_evening_tasks.py
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Fetch candidate tasks for evening planning.
|
||||||
|
Returns JSON with all open (non-Done, non-Blocked) tasks, for local ranking
|
||||||
|
by blocking status, "Suggested for today", and due date.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import json
|
||||||
|
import sys
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
from notion_helpers import notion, get_data_source_id, query_all, TASKS_DB
|
||||||
|
|
||||||
|
def get_evening_tasks():
|
||||||
|
"""Get all tasks that are not Done and not Blocked."""
|
||||||
|
pages = query_all(
|
||||||
|
get_data_source_id(TASKS_DB),
|
||||||
|
filter={
|
||||||
|
"and": [
|
||||||
|
{
|
||||||
|
"property": "Status",
|
||||||
|
"status": {
|
||||||
|
"does_not_equal": "Done"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"property": "Status",
|
||||||
|
"status": {
|
||||||
|
"does_not_equal": "Blocked"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
sorts=[
|
||||||
|
{
|
||||||
|
"property": "Due date",
|
||||||
|
"direction": "ascending"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
tasks = []
|
||||||
|
for page in pages:
|
||||||
|
props = page["properties"]
|
||||||
|
|
||||||
|
due_date = None
|
||||||
|
if props["Due date"]["date"]:
|
||||||
|
due_date = props["Due date"]["date"]["start"]
|
||||||
|
|
||||||
|
suggested_for_today = None
|
||||||
|
if props["Suggested for today"]["date"]:
|
||||||
|
suggested_for_today = props["Suggested for today"]["date"]["start"]
|
||||||
|
|
||||||
|
project_names = []
|
||||||
|
if props["Project"]["relation"]:
|
||||||
|
for rel in props["Project"]["relation"]:
|
||||||
|
try:
|
||||||
|
project = notion.pages.retrieve(page_id=rel["id"])
|
||||||
|
project_name = project["properties"]["Name"]["title"][0]["plain_text"]
|
||||||
|
project_names.append(project_name)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
has_blocking = len(props["Blocking"]["relation"]) > 0 if props["Blocking"]["relation"] else False
|
||||||
|
|
||||||
|
tasks.append({
|
||||||
|
"id": page["id"],
|
||||||
|
"url": page["url"],
|
||||||
|
"name": props["Task name"]["title"][0]["plain_text"] if props["Task name"]["title"] else "",
|
||||||
|
"due_date": due_date,
|
||||||
|
"suggested_for_today": suggested_for_today,
|
||||||
|
"status": props["Status"]["status"]["name"] if props["Status"]["status"] else "",
|
||||||
|
"priority": props["Priority"]["select"]["name"] if props["Priority"]["select"] else "",
|
||||||
|
"effort_level": props["Effort level"]["select"]["name"] if props["Effort level"]["select"] else "",
|
||||||
|
"energy_type": props["Energy type"]["select"]["name"] if props["Energy type"]["select"] else "",
|
||||||
|
"task_type": [t["name"] for t in props["Task type"]["multi_select"]],
|
||||||
|
"project": project_names[0] if project_names else "",
|
||||||
|
"has_blocking": has_blocking,
|
||||||
|
"top_task": props["Top Task"]["checkbox"]
|
||||||
|
})
|
||||||
|
|
||||||
|
return tasks
|
||||||
|
|
||||||
|
def get_marked_top_tasks():
|
||||||
|
"""Get all tasks currently marked Top Task = true, regardless of status.
|
||||||
|
|
||||||
|
Queried separately (without the Done/Blocked exclusion) so a stale Top Task
|
||||||
|
left checked on a now-Done task is still found and can be unmarked.
|
||||||
|
"""
|
||||||
|
pages = query_all(
|
||||||
|
get_data_source_id(TASKS_DB),
|
||||||
|
filter={
|
||||||
|
"property": "Top Task",
|
||||||
|
"checkbox": {
|
||||||
|
"equals": True
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
"id": page["id"],
|
||||||
|
"name": page["properties"]["Task name"]["title"][0]["plain_text"] if page["properties"]["Task name"]["title"] else "",
|
||||||
|
"status": page["properties"]["Status"]["status"]["name"] if page["properties"]["Status"]["status"] else ""
|
||||||
|
}
|
||||||
|
for page in pages
|
||||||
|
]
|
||||||
|
|
||||||
|
def main():
|
||||||
|
try:
|
||||||
|
data = {
|
||||||
|
"tasks": get_evening_tasks(),
|
||||||
|
"marked_top_tasks": get_marked_top_tasks(),
|
||||||
|
"generated_at": datetime.now().isoformat()
|
||||||
|
}
|
||||||
|
|
||||||
|
print(json.dumps(data, indent=2))
|
||||||
|
return 0
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(json.dumps({"error": str(e)}), file=sys.stderr)
|
||||||
|
return 1
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main())
|
||||||
@@ -5,54 +5,42 @@ Returns JSON with projects, tasks, and reading suggestions.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import json
|
import json
|
||||||
import os
|
|
||||||
import sys
|
import sys
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from notion_client import Client
|
|
||||||
|
|
||||||
# Initialize Notion client - will use NOTION_TOKEN env var
|
from notion_helpers import notion, get_data_source_id, query_all, PROJECTS_DB, TASKS_DB, READING_LIST_DB
|
||||||
notion_token = os.getenv('NOTION_TOKEN')
|
|
||||||
if not notion_token:
|
|
||||||
print(json.dumps({"error": "NOTION_TOKEN environment variable not set. See scripts/README.md for setup instructions."}), file=sys.stderr)
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
notion = Client(auth=notion_token)
|
def get_active_projects():
|
||||||
|
"""Get non-personal projects with Status = Active (candidates for weekly focus selection)."""
|
||||||
# Database IDs
|
pages = query_all(
|
||||||
PROJECTS_DB = "2c0abd6c450a8090aca3e0b2b0373c17"
|
get_data_source_id(PROJECTS_DB),
|
||||||
TASKS_DB = "2c0abd6c450a805098d3cc0e7d3dfccf"
|
|
||||||
READING_LIST_DB = "2c4abd6c450a80cbae55c440dd9e2427"
|
|
||||||
|
|
||||||
def get_focus_projects():
|
|
||||||
"""Get projects with 'Focus this week' = true and Status = Active."""
|
|
||||||
results = notion.databases.query(
|
|
||||||
database_id=PROJECTS_DB,
|
|
||||||
filter={
|
filter={
|
||||||
"and": [
|
"and": [
|
||||||
{
|
|
||||||
"property": "Focus this week",
|
|
||||||
"checkbox": {
|
|
||||||
"equals": True
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"property": "Status",
|
"property": "Status",
|
||||||
"select": {
|
"select": {
|
||||||
"equals": "Active"
|
"equals": "Active"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"property": "Type",
|
||||||
|
"select": {
|
||||||
|
"does_not_equal": "Personal"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
projects = []
|
projects = []
|
||||||
for page in results["results"]:
|
for page in pages:
|
||||||
props = page["properties"]
|
props = page["properties"]
|
||||||
projects.append({
|
projects.append({
|
||||||
"id": page["id"],
|
"id": page["id"],
|
||||||
"url": page["url"],
|
"url": page["url"],
|
||||||
"name": props["Name"]["title"][0]["plain_text"] if props["Name"]["title"] else "",
|
"name": props["Name"]["title"][0]["plain_text"] if props["Name"]["title"] else "",
|
||||||
"weekly_goal": props["Weekly goal"]["rich_text"][0]["plain_text"] if props["Weekly goal"]["rich_text"] else "",
|
"weekly_goal": props["Weekly goal"]["rich_text"][0]["plain_text"] if props["Weekly goal"]["rich_text"] else "",
|
||||||
|
"focus_this_week": props["Focus this week"]["checkbox"],
|
||||||
"icon": page.get("icon", {}).get("emoji", "")
|
"icon": page.get("icon", {}).get("emoji", "")
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -64,8 +52,8 @@ def get_weekly_tasks():
|
|||||||
today = datetime.now().date()
|
today = datetime.now().date()
|
||||||
week_end = today + timedelta(days=7)
|
week_end = today + timedelta(days=7)
|
||||||
|
|
||||||
results = notion.databases.query(
|
pages = query_all(
|
||||||
database_id=TASKS_DB,
|
get_data_source_id(TASKS_DB),
|
||||||
filter={
|
filter={
|
||||||
"and": [
|
"and": [
|
||||||
{
|
{
|
||||||
@@ -91,7 +79,7 @@ def get_weekly_tasks():
|
|||||||
)
|
)
|
||||||
|
|
||||||
tasks = []
|
tasks = []
|
||||||
for page in results["results"]:
|
for page in pages:
|
||||||
props = page["properties"]
|
props = page["properties"]
|
||||||
|
|
||||||
# Get due date
|
# Get due date
|
||||||
@@ -131,8 +119,8 @@ def get_weekly_tasks():
|
|||||||
|
|
||||||
def get_reading_suggestions():
|
def get_reading_suggestions():
|
||||||
"""Get articles with Status = 'To Read' or 'In Progress'."""
|
"""Get articles with Status = 'To Read' or 'In Progress'."""
|
||||||
results = notion.databases.query(
|
pages = query_all(
|
||||||
database_id=READING_LIST_DB,
|
get_data_source_id(READING_LIST_DB),
|
||||||
filter={
|
filter={
|
||||||
"or": [
|
"or": [
|
||||||
{
|
{
|
||||||
@@ -152,7 +140,7 @@ def get_reading_suggestions():
|
|||||||
)
|
)
|
||||||
|
|
||||||
articles = []
|
articles = []
|
||||||
for page in results["results"]:
|
for page in pages:
|
||||||
props = page["properties"]
|
props = page["properties"]
|
||||||
articles.append({
|
articles.append({
|
||||||
"id": page["id"],
|
"id": page["id"],
|
||||||
@@ -168,7 +156,7 @@ def get_reading_suggestions():
|
|||||||
def main():
|
def main():
|
||||||
try:
|
try:
|
||||||
data = {
|
data = {
|
||||||
"projects": get_focus_projects(),
|
"projects": get_active_projects(),
|
||||||
"tasks": get_weekly_tasks(),
|
"tasks": get_weekly_tasks(),
|
||||||
"reading": get_reading_suggestions(),
|
"reading": get_reading_suggestions(),
|
||||||
"generated_at": datetime.now().isoformat()
|
"generated_at": datetime.now().isoformat()
|
||||||
|
|||||||
46
scripts/notion_helpers.py
Normal file
46
scripts/notion_helpers.py
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
"""Shared Notion client setup and query helpers for the FullFocus scripts."""
|
||||||
|
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
from notion_client import Client
|
||||||
|
|
||||||
|
load_dotenv(Path(__file__).resolve().parent.parent / ".env")
|
||||||
|
|
||||||
|
_notion_token = os.getenv('NOTION_TOKEN')
|
||||||
|
if not _notion_token:
|
||||||
|
print(json.dumps({"error": "NOTION_TOKEN environment variable not set. See scripts/README.md for setup instructions."}), file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
notion = Client(auth=_notion_token)
|
||||||
|
|
||||||
|
# Database IDs
|
||||||
|
PROJECTS_DB = "2c0abd6c450a8090aca3e0b2b0373c17"
|
||||||
|
TASKS_DB = "2c0abd6c450a805098d3cc0e7d3dfccf"
|
||||||
|
READING_LIST_DB = "2c4abd6c450a80cbae55c440dd9e2427"
|
||||||
|
|
||||||
|
_data_source_id_cache = {}
|
||||||
|
|
||||||
|
def get_data_source_id(database_id):
|
||||||
|
"""Resolve a database ID to its first data source ID (notion-client 3.x API)."""
|
||||||
|
if database_id not in _data_source_id_cache:
|
||||||
|
db = notion.databases.retrieve(database_id=database_id)
|
||||||
|
_data_source_id_cache[database_id] = db["data_sources"][0]["id"]
|
||||||
|
return _data_source_id_cache[database_id]
|
||||||
|
|
||||||
|
def query_all(data_source_id, **kwargs):
|
||||||
|
"""Query a data source, following pagination until all matching pages are collected."""
|
||||||
|
pages = []
|
||||||
|
start_cursor = None
|
||||||
|
while True:
|
||||||
|
if start_cursor:
|
||||||
|
kwargs["start_cursor"] = start_cursor
|
||||||
|
results = notion.data_sources.query(data_source_id=data_source_id, **kwargs)
|
||||||
|
pages.extend(results["results"])
|
||||||
|
if not results.get("has_more"):
|
||||||
|
break
|
||||||
|
start_cursor = results["next_cursor"]
|
||||||
|
return pages
|
||||||
@@ -1 +1,2 @@
|
|||||||
notion-client>=2.0.0
|
notion-client>=2.0.0
|
||||||
|
python-dotenv>=1.0.0
|
||||||
|
|||||||
Reference in New Issue
Block a user