|
| 1 | +/** |
| 2 | + * Google Tasks API client helpers. |
| 3 | + * |
| 4 | + * Uses the REST API directly since there's no official SDK for Workers. |
| 5 | + * https://developers.google.com/tasks/reference/rest |
| 6 | + */ |
| 7 | + |
| 8 | +const BASE_URL = "https://tasks.googleapis.com/tasks/v1"; |
| 9 | + |
| 10 | +export type GoogleTaskList = { |
| 11 | + id: string; |
| 12 | + title: string; |
| 13 | + updated: string; |
| 14 | + selfLink: string; |
| 15 | +}; |
| 16 | + |
| 17 | +export type GoogleTask = { |
| 18 | + id: string; |
| 19 | + title: string; |
| 20 | + notes?: string; |
| 21 | + status: "needsAction" | "completed"; |
| 22 | + due?: string; |
| 23 | + completed?: string; |
| 24 | + updated: string; |
| 25 | + parent?: string; |
| 26 | + position: string; |
| 27 | + selfLink: string; |
| 28 | + webViewLink?: string; |
| 29 | + links?: Array<{ type: string; description: string; link: string }>; |
| 30 | +}; |
| 31 | + |
| 32 | +type TaskListResponse = { |
| 33 | + items?: GoogleTaskList[]; |
| 34 | + nextPageToken?: string; |
| 35 | +}; |
| 36 | + |
| 37 | +type TasksResponse = { |
| 38 | + items?: GoogleTask[]; |
| 39 | + nextPageToken?: string; |
| 40 | +}; |
| 41 | + |
| 42 | +async function request<T>( |
| 43 | + token: string, |
| 44 | + path: string, |
| 45 | + options?: RequestInit |
| 46 | +): Promise<T> { |
| 47 | + const response = await fetch(`${BASE_URL}${path}`, { |
| 48 | + ...options, |
| 49 | + headers: { |
| 50 | + Authorization: `Bearer ${token}`, |
| 51 | + "Content-Type": "application/json", |
| 52 | + ...options?.headers, |
| 53 | + }, |
| 54 | + }); |
| 55 | + if (!response.ok) { |
| 56 | + const text = await response.text(); |
| 57 | + throw new Error( |
| 58 | + `Google Tasks API error ${response.status}: ${text}` |
| 59 | + ); |
| 60 | + } |
| 61 | + return response.json() as Promise<T>; |
| 62 | +} |
| 63 | + |
| 64 | +/** |
| 65 | + * List all task lists for the authenticated user. |
| 66 | + */ |
| 67 | +export async function listTaskLists( |
| 68 | + token: string |
| 69 | +): Promise<GoogleTaskList[]> { |
| 70 | + const allLists: GoogleTaskList[] = []; |
| 71 | + let pageToken: string | undefined; |
| 72 | + |
| 73 | + do { |
| 74 | + const params = new URLSearchParams({ maxResults: "100" }); |
| 75 | + if (pageToken) params.set("pageToken", pageToken); |
| 76 | + |
| 77 | + const result = await request<TaskListResponse>( |
| 78 | + token, |
| 79 | + `/users/@me/lists?${params}` |
| 80 | + ); |
| 81 | + if (result.items) { |
| 82 | + allLists.push(...result.items); |
| 83 | + } |
| 84 | + pageToken = result.nextPageToken; |
| 85 | + } while (pageToken); |
| 86 | + |
| 87 | + return allLists; |
| 88 | +} |
| 89 | + |
| 90 | +/** |
| 91 | + * List tasks in a task list. |
| 92 | + */ |
| 93 | +export async function listTasks( |
| 94 | + token: string, |
| 95 | + listId: string, |
| 96 | + options?: { |
| 97 | + showCompleted?: boolean; |
| 98 | + updatedMin?: string; |
| 99 | + pageToken?: string; |
| 100 | + maxResults?: number; |
| 101 | + } |
| 102 | +): Promise<{ tasks: GoogleTask[]; nextPageToken?: string }> { |
| 103 | + const params = new URLSearchParams({ |
| 104 | + maxResults: String(options?.maxResults ?? 50), |
| 105 | + }); |
| 106 | + if (options?.showCompleted !== undefined) { |
| 107 | + params.set("showCompleted", String(options.showCompleted)); |
| 108 | + if (options.showCompleted) { |
| 109 | + params.set("showHidden", "true"); |
| 110 | + } |
| 111 | + } |
| 112 | + if (options?.updatedMin) { |
| 113 | + params.set("updatedMin", options.updatedMin); |
| 114 | + // When using updatedMin, must show completed to get status changes |
| 115 | + params.set("showCompleted", "true"); |
| 116 | + params.set("showHidden", "true"); |
| 117 | + } |
| 118 | + if (options?.pageToken) { |
| 119 | + params.set("pageToken", options.pageToken); |
| 120 | + } |
| 121 | + |
| 122 | + const result = await request<TasksResponse>( |
| 123 | + token, |
| 124 | + `/lists/${encodeURIComponent(listId)}/tasks?${params}` |
| 125 | + ); |
| 126 | + |
| 127 | + return { |
| 128 | + tasks: result.items ?? [], |
| 129 | + nextPageToken: result.nextPageToken, |
| 130 | + }; |
| 131 | +} |
| 132 | + |
| 133 | +/** |
| 134 | + * Update a task's status. |
| 135 | + */ |
| 136 | +export async function updateTask( |
| 137 | + token: string, |
| 138 | + listId: string, |
| 139 | + taskId: string, |
| 140 | + updates: { status?: "needsAction" | "completed" } |
| 141 | +): Promise<GoogleTask> { |
| 142 | + return request<GoogleTask>( |
| 143 | + token, |
| 144 | + `/lists/${encodeURIComponent(listId)}/tasks/${encodeURIComponent(taskId)}`, |
| 145 | + { |
| 146 | + method: "PATCH", |
| 147 | + body: JSON.stringify(updates), |
| 148 | + } |
| 149 | + ); |
| 150 | +} |
0 commit comments