const BASE_URL = 'https://api.ticktick.com/open/v1';
export interface Task {
id: string;
projectId: string;
title: string;
content?: string;
startDate?: string;
dueDate?: string;
timeZone?: string;
isAllDay?: boolean;
priority?: number;
status?: number;
}
export interface Project {
id: string;
name: string;
color?: string;
}
export interface ProjectData {
project: Project;
tasks: Task[];
}
export class TickTickClient {
private accessToken: string;
constructor(accessToken: string) {
this.accessToken = accessToken;
}
private async request<T>(
method: string,
endpoint: string,
body?: unknown
): Promise<T> {
const url = `${BASE_URL}${endpoint}`;
const headers: Record<string, string> = {
'Authorization': `Bearer ${this.accessToken}`,
'Content-Type': 'application/json',
};
const options: RequestInit = {
method,
headers,
};
if (body) {
options.body = JSON.stringify(body);
}
const response = await fetch(url, options);
if (!response.ok) {
const errorText = await response.text();
throw new Error(`TickTick API error: ${response.status} - ${errorText}`);
}
const text = await response.text();
if (!text) {
return {} as T;
}
return JSON.parse(text) as T;
}
async listProjects(): Promise<Project[]> {
return this.request<Project[]>('GET', '/project');
}
async getProjectData(projectId: string): Promise<ProjectData> {
return this.request<ProjectData>('GET', `/project/${projectId}/data`);
}
async createTask(params: {
title: string;
content?: string;
projectId?: string;
dueDate?: string;
startDate?: string;
isAllDay?: boolean;
priority?: number;
}): Promise<Task> {
return this.request<Task>('POST', '/task', params);
}
async updateTask(params: {
taskId: string;
projectId: string;
title?: string;
content?: string;
dueDate?: string;
priority?: number;
}): Promise<Task> {
const { taskId, projectId, ...updateData } = params;
return this.request<Task>('POST', `/task/${taskId}`, {
id: taskId,
projectId,
...updateData,
});
}
async completeTask(projectId: string, taskId: string): Promise<void> {
await this.request<void>('POST', `/project/${projectId}/task/${taskId}/complete`);
}
async deleteTask(projectId: string, taskId: string): Promise<void> {
await this.request<void>('DELETE', `/project/${projectId}/task/${taskId}`);
}
async getAllTasks(): Promise<{ project: Project; tasks: Task[] }[]> {
const projects = await this.listProjects();
const results: { project: Project; tasks: Task[] }[] = [];
for (const project of projects) {
try {
const projectData = await this.getProjectData(project.id);
results.push({
project: projectData.project,
tasks: projectData.tasks || [],
});
} catch {
results.push({ project, tasks: [] });
}
}
return results;
}
}