diff --git a/.gitignore b/.gitignore index 286fcb6..f317673 100644 --- a/.gitignore +++ b/.gitignore @@ -1,12 +1,11 @@ # FOLDERS ############################### -node_modules/ + dist/ -build/ -offline/ -/package -.next/ +node_modules/ +package/ # FILES ################################# + package-lock.json yarn-lock.json pnpm-lock.yaml diff --git a/examples/dev-example.ts b/examples/dev-example.ts index 2b946a9..d163995 100644 --- a/examples/dev-example.ts +++ b/examples/dev-example.ts @@ -1,204 +1,234 @@ -import { Tick } from '../src/index' -import dotenv from 'dotenv' -import { ITask } from '../src/types/Task' -dotenv.config() -import { isValid } from '../src/utils/validate' - -let allAllTasks: ITask[] = []; -let donePrinted: string[] = []; - -export async function testTick() { - try { - const envUSERNAME = process.env.TICK_USERNAME - const envPASSWORD = process.env.TICK_PASSWORD - if (!envUSERNAME || !envPASSWORD){ - throw new Error('parameters username/password were not set in .env file.'); - } - - const projectID = "65159d5d8f08a002fd167ce8"; - const tickSession = new Tick({ username: envUSERNAME, password: envPASSWORD }); - const hasLoggedIn = await tickSession.login(); - if (!hasLoggedIn) { - throw new Error('Coudnt login with this username/password.'); - } - // let myTask: ITask = { - // "projectId": projectID, - // "title": "Trying too many things. #otherNewTest 10/21/2023 -- latest.", - // "tags": ["NewTest"] - // } - // let addResult = await tickSession.addTask(myTask); - // console.log("Add Result : ", addResult); - - // let newTaskId = "6515b1642b3163961425bb73"; - // let newTaskTitle = ""; - // console.log("New Id: ", newTaskId, "New Title: ", newTaskTitle); - // let addedTask = await tickSession.getTask(newTaskId, projectID) - // console.log("getTask result: ", addedTask); - - // let myTask: ITask = { - // "projectId": projectID, - // "title": addedTask.title + " just add something.", - // // "tags": ["NewTest"], - // "id": newTaskId, - // "status": 3 - // } - // let addResult = await tickSession.updateTask(myTask); - // console.log("Update Result : ", addResult); - // const allAllTasks = await tickSession.getAllTasks(); - // if (allAllTasks !== undefined && allAllTasks !== null) { - // console.log("==== allAllTasks") - // console.log(allAllTasks.map((item) => item.title)); - // console.log("==== items of allAllTasks") - // console.log(allAllTasks.map((item) => item.items)) - // console.log("==== content of allAllTasks") - // console.log(allAllTasks.map((item) => item.content)) - // }else { - // console.log("==== No allAllTasks") - // } - allAllTasks = await tickSession.getAllTasks(); - if (allAllTasks !== undefined && allAllTasks !== null) { - allAllTasks = allAllTasks.filter((currentTask) => currentTask.status == 0); - //make sure top level tasks are first - allAllTasks.sort((a, b) => { - if (isValid(a.parentId) && isValid(b.parentId)) { - return (a.parentId < b.parentId) - } else if (isValid(a.parentId)) { - return 1; - } else { - return -1; - } - }); - // allAllTasks.forEach(task => console.log(task.title)) - - - console.log(allAllTasks.map((currentTask) => `${currentTask.title}, ${currentTask.sortOrder}`)) - printTasks(allAllTasks); - // let oneLevelTasks = allAllTasks.filter((currentTask) => !isAChild(currentTask.parentId)); - // let taskWithChildren = allAllTasks.filter((currentTask) => isAChild(currentTask.parentId)); - // console.log(`No Parent tasks: \n ${oneLevelTasks.map((currentTask) => currentTask.title)}\n`) - // console.log(`Parent tasks:\n ${taskWithChildren.map((currentTask) => currentTask.title)}\n`) - - // allAllTasks.forEach(task => { - // if (task.status == 0) { - // // if (!(isValid(task.parentId))) { - // console.log(`${task.status} -- ${task.title} -- ${task.id} -- ${isAChild(task.parentId) ? 'is a parent' : 'is not a parent'}`); - // // console.log(task); - // // } - // if (isValid(task.childIds)) { - // task.childIds.forEach(child => { - // console.log(`\t ${child}`) - // let childrens = allAllTasks.filter((currentTask) => currentTask.id==child) - // console.log(`====children of ${child}========`) - // console.log(childrens) - // }); - // } - // } - // }); - }else { - console.log("==== No allAllTasks") - } - - // const projects = await tickSession.getProjects(); - // if (projects !== undefined && projects !== null) { - // console.log("==== projects") - // // console.log(projects.map((item) => item.name)); - // console.log(`Got ${projects.length} projects.`) - // projects.forEach(async project => { - // console.log("Prj nm: ",project.name); - // const sections = await tickSession.getProjectSections(project.id); - // // console.log(`Project: ${project.name} -- ${sections}`); - // if (sections !== undefined && sections !== null && sections.length > 0) { - // sections.forEach(section => { - // console.log(project.name + '--' + section.name); - // }) - // } else { - // console.log(project.name + '--' + 'no sections') - // } - // }) - - // // projects.forEach(project => { - // // console.log(project.name); - // // const sections = await tickSession.getProjectSections(project.id); - // // if (sections !== undefined && sections !== null) { - // // console.log(sections); - // // }); - // // } - - // } else { - // console.log("==== No projects") - // } - } catch (e: any) { - console.log(e.message); - } - console.log("\n\nDone") -} - -function isAChild(idToCheck: string) { - console.log(`input: ${idToCheck}`); - if (!isValid(idToCheck)) { - console.log('is a child.') - return false; - } else { - console.log("is not a child") - return true - } -} - -function printTasks(allTheTasks: ITask[]) { - console.log("=== Print Tasks =="); - - allTheTasks.forEach(task => { - // if (!isAChild(task.parentId)) { - // console.log(task.title); - // } else { - if (donePrinted.indexOf(task.id) < 1) { - printTaskAndChildren(task, 0); - } - // } - }); -} - -function printTaskAndChildren(task: ITask, depth: number) { - // console.log(`printTasks+ ${task.title}, ${depth}`) - let childCount = 0; - //There's probably a more js way of doing this - let tabs: string = ''; - childCount = childCount + depth; - for (let index = 0; index < childCount; index++) { - tabs += '\t'; - }; - // console.log(`[${tabs}], ${tabs.length}, ${childCount}`); - if (donePrinted.indexOf(task.id) < 1) { - console.log(`${tabs}title: ${task.title} -- ${task.id} -- parent ${task.parentId} --- project: ${task.projectId}`) - donePrinted.push(task.id); - } - if (isValid(task.childIds)) - { - // console.log("-- has children") - let newDepth = depth + 1; - // console.log(`new depth: ${newDepth}`); - task.childIds.forEach(currentChildId => { - let childTask: ITask = allAllTasks.find(currentTask => currentTask.id == currentChildId); - if (isValid(childTask)) { - printTaskAndChildren(childTask, newDepth); - } else { - console.log(`found no child: for ${task.title}`) - } - - - }); - // printTaskAndChildren(---) - - } -} +import Ticktick from '../src/index'; +import dotenv from 'dotenv'; +dotenv.config(); +async function main() { + const envUSERNAME = process.env.TICK_USERNAME ?? 'your_username'; + const envPASSWORD = process.env.TICK_PASSWORD ?? 'your_password'; + const tickInstance = new Ticktick({ username: envUSERNAME, password: envPASSWORD }); + await tickInstance.user.login(); + // TASKS ===================================================================== + console.log(await tickInstance.tasks.getAllTasks()); + console.log(await tickInstance.tasks.getTasksStatus()); + console.log(await tickInstance.tasks.getAllCompletedItems()); +} - -testTick(); - - - - +main(); + +// export function isValid(variable: any) +// { +// // console.log(`${variable} is ${typeof variable}`) +// let retValue = false; +// if (variable != null && variable !== 'undefined') { +// if (Array.isArray(variable) && variable.length > 0) { +// retValue = true; +// } else { +// // console.log(typeof variable) +// if (typeof variable == 'string') { +// retValue = variable.length > 0; +// } else if (typeof variable == 'object') { +// // console.log(Object.keys(variable)) +// retValue = Object.keys(variable).length !== 0; +// } +// } +// retValue = true; +// } else { +// retValue = false; +// } +// // console.log(`is valid: ${retValue}`) +// return retValue; +// } + +// import { Tick } from '../src/index'; +// import dotenv from 'dotenv'; +// import { TTask } from '../src/types/Task'; +// dotenv.config(); +// import { isValid } from '../src/utils/validate'; + +// let allAllTasks: TTask[] = []; +// let donePrinted: string[] = []; + +// export async function testTick() { +// try { +// const envUSERNAME = process.env.TICK_USERNAME; +// const envPASSWORD = process.env.TICK_PASSWORD; +// if (!envUSERNAME || !envPASSWORD) { +// throw new Error('parameters username/password were not set in .env file.'); +// } + +// const projectID = '65159d5d8f08a002fd167ce8'; +// const tickSession = new Tick({ username: envUSERNAME, password: envPASSWORD }); +// const hasLoggedIn = await tickSession.login(); +// if (!hasLoggedIn) { +// throw new Error('Coudnt login with this username/password.'); +// } +// // let myTask: TTask = { +// // "projectId": projectID, +// // "title": "Trying too many things. #otherNewTest 10/21/2023 -- latest.", +// // "tags": ["NewTest"] +// // } +// // let addResult = await tickSession.addTask(myTask); +// // console.log("Add Result : ", addResult); + +// // let newTaskId = "6515b1642b3163961425bb73"; +// // let newTaskTitle = ""; +// // console.log("New Id: ", newTaskId, "New Title: ", newTaskTitle); +// // let addedTask = await tickSession.getTask(newTaskId, projectID) +// // console.log("getTask result: ", addedTask); + +// // let myTask: TTask = { +// // "projectId": projectID, +// // "title": addedTask.title + " just add something.", +// // // "tags": ["NewTest"], +// // "id": newTaskId, +// // "status": 3 +// // } +// // let addResult = await tickSession.updateTask(myTask); +// // console.log("Update Result : ", addResult); +// // const allAllTasks = await tickSession.getAllTasks(); +// // if (allAllTasks !== undefined && allAllTasks !== null) { +// // console.log("==== allAllTasks") +// // console.log(allAllTasks.map((item) => item.title)); +// // console.log("==== items of allAllTasks") +// // console.log(allAllTasks.map((item) => item.items)) +// // console.log("==== content of allAllTasks") +// // console.log(allAllTasks.map((item) => item.content)) +// // }else { +// // console.log("==== No allAllTasks") +// // } +// allAllTasks = await tickSession.getAllTasks(); +// if (allAllTasks !== undefined && allAllTasks !== null) { +// allAllTasks = allAllTasks.filter((currentTask) => currentTask.status == 0); +// //make sure top level tasks are first +// // allAllTasks.sort((a, b) => { +// // if (isValid(a.parentId) && isValid(b.parentId)) { +// // return a.parentId < b.parentId; +// // } else if (isValid(a.parentId)) { +// // return 1; +// // } else { +// // return -1; +// // } +// // }); +// // allAllTasks.forEach(task => console.log(task.title)) + +// console.log(allAllTasks.map((currentTask) => `${currentTask.title}, ${currentTask.sortOrder}`)); +// printTasks(allAllTasks); +// // let oneLevelTasks = allAllTasks.filter((currentTask) => !isAChild(currentTask.parentId)); +// // let taskWithChildren = allAllTasks.filter((currentTask) => isAChild(currentTask.parentId)); +// // console.log(`No Parent tasks: \n ${oneLevelTasks.map((currentTask) => currentTask.title)}\n`) +// // console.log(`Parent tasks:\n ${taskWithChildren.map((currentTask) => currentTask.title)}\n`) + +// // allAllTasks.forEach(task => { +// // if (task.status == 0) { +// // // if (!(isValid(task.parentId))) { +// // console.log(`${task.status} -- ${task.title} -- ${task.id} -- ${isAChild(task.parentId) ? 'is a parent' : 'is not a parent'}`); +// // // console.log(task); +// // // } +// // if (isValid(task.childIds)) { +// // task.childIds.forEach(child => { +// // console.log(`\t ${child}`) +// // let childrens = allAllTasks.filter((currentTask) => currentTask.id==child) +// // console.log(`====children of ${child}========`) +// // console.log(childrens) +// // }); +// // } +// // } +// // }); +// } else { +// console.log('==== No allAllTasks'); +// } + +// // const projects = await tickSession.getProjects(); +// // if (projects !== undefined && projects !== null) { +// // console.log("==== projects") +// // // console.log(projects.map((item) => item.name)); +// // console.log(`Got ${projects.length} projects.`) +// // projects.forEach(async project => { +// // console.log("Prj nm: ",project.name); +// // const sections = await tickSession.getProjectSections(project.id); +// // // console.log(`Project: ${project.name} -- ${sections}`); +// // if (sections !== undefined && sections !== null && sections.length > 0) { +// // sections.forEach(section => { +// // console.log(project.name + '--' + section.name); +// // }) +// // } else { +// // console.log(project.name + '--' + 'no sections') +// // } +// // }) + +// // // projects.forEach(project => { +// // // console.log(project.name); +// // // const sections = await tickSession.getProjectSections(project.id); +// // // if (sections !== undefined && sections !== null) { +// // // console.log(sections); +// // // }); +// // // } + +// // } else { +// // console.log("==== No projects") +// // } +// } catch (e: any) { +// console.log(e.message); +// } +// console.log('\n\nDone'); +// } + +// function isAChild(idToCheck: string) { +// console.log(`input: ${idToCheck}`); +// if (!isValid(idToCheck)) { +// console.log('is a child.'); +// return false; +// } else { +// console.log('is not a child'); +// return true; +// } +// } + +// function printTasks(allTheTasks: TTask[]) { +// console.log('=== Print Tasks =='); + +// allTheTasks.forEach((task) => { +// // if (!isAChild(task.parentId)) { +// // console.log(task.title); +// // } else { +// if (donePrinted.indexOf(task.id) < 1) { +// printTaskAndChildren(task, 0); +// } +// // } +// }); +// } + +// function printTaskAndChildren(task: TTask, depth: number) { +// // console.log(`printTasks+ ${task.title}, ${depth}`) +// let childCount = 0; +// //There's probably a more js way of doing this +// let tabs: string = ''; +// childCount = childCount + depth; +// for (let index = 0; index < childCount; index++) { +// tabs += '\t'; +// } +// // console.log(`[${tabs}], ${tabs.length}, ${childCount}`); +// if (donePrinted.indexOf(task.id) < 1) { +// console.log(`${tabs}title: ${task.title} -- ${task.id} -- parent ${task.parentId} --- project: ${task.projectId}`); +// donePrinted.push(task.id); +// } +// if (isValid(task.childIds)) { +// // console.log("-- has children") +// let newDepth = depth + 1; +// // console.log(`new depth: ${newDepth}`); +// task.childIds.forEach((currentChildId) => { +// // let childTask: TTask = allAllTasks.find((currentTask) => currentTask.id == currentChildId); +// // if (isValid(childTask)) { +// // printTaskAndChildren(childTask, newDepth); +// // } else { +// // console.log(`found no child: for ${task.title}`); +// // } +// }); +// // printTaskAndChildren(---) +// } +// } + +// testTick(); diff --git a/examples/js-example.js b/examples/js-example.js deleted file mode 100644 index 20864c8..0000000 --- a/examples/js-example.js +++ /dev/null @@ -1,43 +0,0 @@ -const { Tick } = require('ticktick-api-lvt'); - -async function main() { - try { - const USERNAME = 'USERNAME'; - const PASSWORD = 'PASSWORD'; - - const tickSession = new Tick({ username: USERNAME, password: PASSWORD }); - const hasLoggedIn = await tickSession.login(); - if (!hasLoggedIn) { - throw new Error('Coudnt login with this username/password.'); - } - - const userPreferences = await tickSession.getUserSettings() - console.log(Object.keys(userPreferences)) - - const allAllTasks = await tickSession.getAllTasks(); - console.log(allAllTasks.map((item) => item.title)); - - const tasks = await tickSession.getTasks(); - console.log(tasks.map((item) => item.title)); - - const filters = await tickSession.getFilters(); - console.log(filters.map((item) => item.name)); - - const projectGroups = await tickSession.getProjectGroups(); - console.log(projectGroups.map((item) => item.name)); - - const projects = await tickSession.getProjects(); - console.log(projects.map((item) => item.name)); - - const habits = await tickSession.getHabits(); - console.log(habits.map((item) => item.name)); - - const tags = await tickSession.getTags(); - console.log(tags); - - } catch (e) { - console.log(e.message); - } -} - -main(); diff --git a/examples/ts-example.ts b/examples/ts-example.ts deleted file mode 100644 index c41cd4f..0000000 --- a/examples/ts-example.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { Tick } from 'ticktick-api-lvt' - -async function main() { - try { - const USERNAME = 'USERNAME'; - const PASSWORD = 'PASSWORD'; - - const tickSession = new Tick({ username: USERNAME, password: PASSWORD }); - const hasLoggedIn = await tickSession.login(); - if (!hasLoggedIn) { - throw new Error('Coudnt login with this username/password.'); - } - - const userPreferences = await tickSession.getUserSettings() - console.log(Object.keys(userPreferences)) - - const allAllTasks = await tickSession.getAllTasks(); - console.log(allAllTasks.map((item) => item.title)); - - const tasks = await tickSession.getTasks(); - console.log(tasks.map((item) => item.title)); - - const filters = await tickSession.getFilters(); - console.log(filters.map((item) => item.name)); - - const projectGroups = await tickSession.getProjectGroups(); - console.log(projectGroups.map((item) => item.name)); - - const projects = await tickSession.getProjects(); - console.log(projects.map((item) => item.name)); - - const habits = await tickSession.getHabits(); - console.log(habits.map((item) => item.name)); - - const tags = await tickSession.getTags(); - console.log(tags); - - } catch (e: any) { - console.log(e.message); - } -} - -main(); diff --git a/package.json b/package.json index 3300f9c..0a572ad 100644 --- a/package.json +++ b/package.json @@ -25,10 +25,8 @@ "main": "dist/index.js", "types": "dist/index.d.ts", "scripts": { - "prepare": "npm run build", "start": "node dist/index.js", - "dev_example": "ts-node-dev --respawn --transpile-only ./examples/dev-example.ts", - "dev": "npm run dev_example", + "dev": "tsx ./examples/dev-example.ts", "build": "tsc", "check_package": "npm pack && tar -xvzf *.tgz && rm -rf package *.tgz", "prepub": "echo prepublish scripts && npm run build && npm run check_package", @@ -55,7 +53,7 @@ "husky": "^8.0.3", "prettier": "^3.1.1", "semantic-release": "^22.0.12", - "ts-node-dev": "^2.0.0", + "tsx": "^2.0.0", "typescript": "^5.3.3", "lint-staged": "^15.2.0" } diff --git a/src/entities/Base.ts b/src/entities/Base.ts new file mode 100644 index 0000000..c9c029d --- /dev/null +++ b/src/entities/Base.ts @@ -0,0 +1,5 @@ +import { TExtendedConfigs } from '../utils/configs'; + +export default class Base { + constructor(public configs: TExtendedConfigs) {} +} diff --git a/src/entities/Filters.ts b/src/entities/Filters.ts new file mode 100644 index 0000000..7d7deb6 --- /dev/null +++ b/src/entities/Filters.ts @@ -0,0 +1,30 @@ +import { getRequestOptions } from 'src/utils/get_request_options'; +import { API_ROUTES } from '../utils/api_routes'; +import Base from './Base'; + +type TFilter = { + id: string; + name: string; + rule: string; + sortOrder: any; + sortType: string; + viewMode: any; + timeline: any; + etag: string; + createdTime: string; + modifiedTime: string; +}; + +export default class Filters extends Base { + async getFilters(): Promise { + return new Promise((resolve) => { + const url = `${this.configs.apiUrl}/${API_ROUTES.generalDetailsEndPoint}`; + const options = getRequestOptions(url); + + this.configs.request(options, (error, response, body) => { + body = JSON.parse(body); + resolve(body.filters); + }); + }); + } +} diff --git a/src/entities/Habits.ts b/src/entities/Habits.ts new file mode 100644 index 0000000..98eea64 --- /dev/null +++ b/src/entities/Habits.ts @@ -0,0 +1,46 @@ +import { API_ROUTES } from '../utils/api_routes'; +import Base from './Base'; +import { getRequestOptions } from '../utils/get_request_options'; + +type THabit = { + id: string; + name: string; + iconRes: string; + color: string; + sortOrder: any; + status: number; + encouragement: string; + totalCheckIns: number; + createdTime: string; + modifiedTime: string; + type: string; + goal: number; + step: number; + unit: string; + etag: string; + repeatRule: string; + reminders: string[]; + recordEnable: boolean; + sectionId: string; + targetDays: number; + targetStartDate: number; + completedCycles: number; +}; + +export default class Habits extends Base { + async getHabits(): Promise { + return new Promise((resolve) => { + try { + const url = `${this.configs.apiUrl}/${API_ROUTES.allHabitsEndPoint}`; + const options = getRequestOptions(url); + + this.configs.request(options, (error, response, body) => { + const parsedBody = JSON.parse(body); + resolve(parsedBody); + }); + } catch (e) { + resolve([]); + } + }); + } +} diff --git a/src/entities/Projects.ts b/src/entities/Projects.ts new file mode 100644 index 0000000..eeafbb2 --- /dev/null +++ b/src/entities/Projects.ts @@ -0,0 +1,129 @@ +import Base from './Base'; +import { API_ROUTES } from '../utils/api_routes'; +import { getRequestOptions } from 'src/utils/get_request_options'; + +type TProjectGroup = { + id: string; + etag: string; + name: string; + showAll: boolean; + sortOrder: any; + viewMode: any; + deleted: number; + userId: number; + sortType: string; + teamId: any; + timeline: any; +}; + +type TProject = { + id: string; + name: string; + isOwner: boolean; + color: string; + inAll: boolean; + sortOrder: any; + sortType: string; + userCount: number; + etag: string; + modifiedTime: string; + closed: any; + muted: boolean; + transferred: any; + groupId: any; + viewMode: string; + notificationOptions: any; + teamId: any; + permission: any; + kind: string; + timeline: any; +}; + +type TSections = { + id: number; + projectId: number; + name: string; + sortOrder: number; +}; + +export default class Projects extends Base { + async getProjectGroups(): Promise { + return new Promise((resolve) => { + const url = `${this.configs.apiUrl}/${API_ROUTES.generalDetailsEndPoint}`; + const options = getRequestOptions(url); + + this.configs.request(options, (error, response, body) => { + if (error) { + console.error('Error on getProjectGroups', error); + resolve([]); + } else { + body = JSON.parse(body); + resolve(body.projectGroups); + } + }); + }); + } + + async getProjects(): Promise { + return new Promise((resolve) => { + try { + const url = `${this.configs.apiUrl}/${API_ROUTES.allProjectsEndPoint}`; + const options = getRequestOptions(url); + + this.configs.request(options, (error, response, body) => { + if (error) { + console.error('Error on getProjects', error); + resolve([]); + } else { + const parsedBody = JSON.parse(body); + resolve(parsedBody); + } + }); + } catch (e) { + console.error('did we get a weird body: ', e); + resolve([]); + } + }); + } + + async getProjectSections(projectId: string): Promise { + return new Promise((resolve) => { + try { + const url = `${this.configs.apiUrl}/${API_ROUTES.getSections}/${projectId}`; + const options = getRequestOptions(url); + + this.configs.request(options, (error, response, body) => { + if (error) { + console.error('Error on getProjectSections', error); + resolve([]); + } else { + const parsedBody = JSON.parse(body); + resolve(parsedBody); + } + }); + } catch (e) { + console.error(e); + resolve([]); + } + }); + } + + //This may have worked at some point, but it doesn't any more. + // async getProject(projectId: string) : Promise { + // return new Promise((resolve) => { + // try { + // const url = `${this.apiUrl}/${getProject}/${projectId}`; + // this.request(url, (error, response, body) => { + // if (body !== undefined && body!== null && body.length > 0) + // { + // const parsedBody = JSON.parse(body); + // resolve(parsedBody); + // } + // }); + // } catch (e) { + // console.error(e) + // resolve([]); + // } + // }); + // } +} diff --git a/src/entities/Tags.ts b/src/entities/Tags.ts new file mode 100644 index 0000000..4e59c65 --- /dev/null +++ b/src/entities/Tags.ts @@ -0,0 +1,26 @@ +import { getRequestOptions } from 'src/utils/get_request_options'; +import { API_ROUTES } from '../utils/api_routes'; +import Base from './Base'; + +type TTag = { + name: string; + label: string; + sortOrder: any; + sortType: string; + color: string; + etag: string; +}; + +export default class Tags extends Base { + async getTags(): Promise { + return new Promise((resolve) => { + const url = `${this.configs.apiUrl}/${API_ROUTES.allTagsEndPoint}`; + const options = getRequestOptions(url); + + this.configs.request(options, (error, response, body) => { + body = JSON.parse(body); + resolve(body); + }); + }); + } +} diff --git a/src/entities/Tasks.ts b/src/entities/Tasks.ts new file mode 100644 index 0000000..e4f04cc --- /dev/null +++ b/src/entities/Tasks.ts @@ -0,0 +1,281 @@ +import ObjectID from 'bson-objectid'; +import { API_ROUTES } from '../utils/api_routes'; +import Base from './Base'; +import { getRequestOptions } from '../utils/get_request_options'; + +export type TTask = { + id: string; + projectId: string; + sortOrder: any; + title: string; + content: string; + startDate: string; + dueDate: string; + timeZone: string; + isFloating?: boolean; + isAllDay: boolean; + reminder: string; // we only get a set + reminders: any[]; + repeatFirstDate?: string; + repeatFlag: string; + exDate?: any[]; + completedTime?: string; + completedUserId?: any; + repeatTaskId?: string; + + priority: number; + status: number; + items: any[]; + progress: number; + modifiedTime: string; + etag?: string; + deleted: number; + createdTime?: string; + creator?: any; + repeatFrom?: string; + focusSummaries?: any[]; + columnId?: string; + kind?: string; + + assignee?: any; + isDirty?: boolean; + local?: boolean; + remindTime?: any; + tags?: any[]; + childIds: string[]; + parentId: string; +}; + +export default class Tasks extends Base { + async getTasksStatus(): Promise { + const url = `${this.configs.apiUrl}/${API_ROUTES.allTasksEndPoint}`; + const options = { + method: 'GET', + url: url, + headers: { + Origin: 'https://ticktick.com' + } + }; + + return new Promise((resolve) => { + this.configs.request(options, function (error, response, body) { + if (body) { + body = JSON.parse(body); + const tasks: TTask[] = body['syncTaskBean']; + resolve(tasks); + } else { + console.error('Get Task Status: No body received in response.'); + } + }); + }); + } + + async getAllTasks(): Promise { + const url = `${this.configs.apiUrl}/${API_ROUTES.allTasks}`; + const options = getRequestOptions(url); + + return new Promise((resolve) => { + this.configs.request(options, (error, response, body) => { + body = JSON.parse(body); + const tasks: TTask[] = body['syncTaskBean']['update']; + resolve(tasks); + }); + }); + } + + async getTasks(): Promise { + return new Promise((resolve) => { + const url = `${this.configs.apiUrl}/${API_ROUTES.generalDetailsEndPoint}`; + const options = getRequestOptions(url); + + this.configs.request(options, (error, response, body) => { + body = JSON.parse(body); + resolve(body.syncTaskBean.update); + }); + }); + } + + async getTask(taskID: string, projectID: string): Promise { + return new Promise((resolve) => { + const url = `${this.configs.apiUrl}/${API_ROUTES.TaskEndPoint}/${taskID}?projectID=${projectID}`; + const options = getRequestOptions(url); + + this.configs.request(options, (error, response, body) => { + body = JSON.parse(body); + resolve(body); + }); + }); + } + + async getAllCompletedItems(): Promise { + return new Promise((resolve) => { + const url = `${this.configs.apiUrl}/${API_ROUTES.getAllCompletedItems}`; + const options = getRequestOptions(url); + + this.configs.request(options, (error, response, body) => { + body = JSON.parse(body); + resolve(body); + }); + }); + } + + addTask(jsonOptions: any): Promise { + const thisTask: TTask = { + id: jsonOptions.id ? jsonOptions.id : ObjectID(), + projectId: jsonOptions.projectId ? jsonOptions.projectId : this.configs.inboxProperties.id, + sortOrder: jsonOptions.sortOrder ? jsonOptions.sortOrder : this.configs.inboxProperties.sortOrder, + title: jsonOptions.title, + content: jsonOptions.content ? jsonOptions.content : '', + startDate: jsonOptions.startDate ? jsonOptions.startDate : null, + dueDate: jsonOptions.dueDate ? jsonOptions.dueDate : null, + timeZone: jsonOptions.timeZone ? jsonOptions.timeZone : 'America/New_York', // This needs to be updated to grab dynamically + isAllDay: jsonOptions.isAllDay ? jsonOptions.isAllDay : null, + reminder: jsonOptions.reminder ? jsonOptions.reminder : null, + reminders: jsonOptions.reminders ? jsonOptions.reminders : [{ id: ObjectID(), trigger: 'TRIGGER:PT0S' }], + repeatFlag: jsonOptions.repeatFlag ? jsonOptions.repeatFlag : null, + priority: jsonOptions.priority ? jsonOptions.priority : 0, + status: jsonOptions.status ? jsonOptions.status : 0, + items: jsonOptions.items ? jsonOptions.items : [], + progress: jsonOptions.progress ? jsonOptions.progress : 0, + modifiedTime: jsonOptions.modifiedTime ? jsonOptions.modifiedTime : new Date().toISOString().replace('Z', '+0000'), //"2017-08-12T17:04:51.982+0000", + deleted: jsonOptions.deleted ? jsonOptions.deleted : 0, + assignee: jsonOptions.assignee ? jsonOptions.assignee : null, + isDirty: jsonOptions.isDirty ? jsonOptions.isDirty : true, + local: jsonOptions.local ? jsonOptions.local : true, + remindTime: jsonOptions.remindTime ? jsonOptions.remindTime : null, + tags: jsonOptions.tags ? jsonOptions.tags : [], + childIds: jsonOptions.childIds ? jsonOptions.childIds : [], + parentId: jsonOptions.parentId ? jsonOptions.parentId : null + }; + + let taskBody: any; + taskBody = thisTask; + + const options = { + method: 'POST', + url: `${this.configs.apiUrl}/${API_ROUTES.TaskEndPoint}`, + headers: { + 'Content-Type': 'application/json', + Origin: 'https://ticktick.com', + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/117.0', + 'X-Device': '{"platform":"web","os":"Windows 10","device":"Firefox 117.0","name":"","version":4576,"id":"64fc9b22cbb2c305b2df7ad6","channel":"website","campaign":"","websocket":"6500a8a3bf02224e648ef8bd"}' + }, + json: taskBody + }; + + return new Promise((resolve) => { + this.configs.request(options, (error, response, body) => { + if (error) { + console.error('Error on addTask', error); + resolve([]); + } else { + let bodySortOrder; + bodySortOrder = body.sortOrder; + this.configs.inboxProperties.sortOrder = bodySortOrder - 1; + resolve(body); + } + }); + }); + } + + updateTask(jsonOptions: any): Promise { + const thisTask: TTask = { + id: jsonOptions.id ? jsonOptions.id : ObjectID(), + projectId: jsonOptions.projectId ? jsonOptions.projectId : this.configs.inboxProperties.id, + sortOrder: jsonOptions.sortOrder ? jsonOptions.sortOrder : this.configs.inboxProperties.sortOrder, + title: jsonOptions.title, + content: jsonOptions.content ? jsonOptions.content : '', + startDate: jsonOptions.startDate ? jsonOptions.startDate : null, + dueDate: jsonOptions.dueDate ? jsonOptions.dueDate : null, + timeZone: jsonOptions.timeZone ? jsonOptions.timeZone : 'America/New_York', // This needs to be updated to grab dynamically + isAllDay: jsonOptions.isAllDay ? jsonOptions.isAllDay : null, + reminder: jsonOptions.reminder ? jsonOptions.reminder : null, + reminders: jsonOptions.reminders ? jsonOptions.reminders : [{ id: ObjectID(), trigger: 'TRIGGER:PT0S' }], + repeatFlag: jsonOptions.repeatFlag ? jsonOptions.repeatFlag : null, + priority: jsonOptions.priority ? jsonOptions.priority : 0, + status: jsonOptions.status ? jsonOptions.status : 0, + items: jsonOptions.items ? jsonOptions.items : [], + progress: jsonOptions.progress ? jsonOptions.progress : 0, + modifiedTime: jsonOptions.modifiedTime ? jsonOptions.modifiedTime : new Date().toISOString().replace('Z', '+0000'), //"2017-08-12T17:04:51.982+0000", + deleted: jsonOptions.deleted ? jsonOptions.deleted : 0, + assignee: jsonOptions.assignee ? jsonOptions.assignee : null, + isDirty: jsonOptions.isDirty ? jsonOptions.isDirty : true, + local: jsonOptions.local ? jsonOptions.local : true, + remindTime: jsonOptions.remindTime ? jsonOptions.remindTime : null, + tags: jsonOptions.tags ? jsonOptions.tags : [], + childIds: jsonOptions.childIds ? jsonOptions.childIds : [], + parentId: jsonOptions.parentId ? jsonOptions.parentId : null + }; + + let taskBody: any; + taskBody = { + add: [], + addAttachments: [], + delete: [], + deleteAttachments: [], + updateAttachments: [], + update: [thisTask] + }; + + const options = { + method: 'POST', + url: `${this.configs.apiUrl}/${API_ROUTES.updateTaskEndPoint}`, + headers: { + 'Content-Type': 'application/json', + Origin: 'https://ticktick.com', + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/117.0', + 'X-Device': '{"platform":"web","os":"Windows 10","device":"Firefox 117.0","name":"","version":4576,"id":"64fc9b22cbb2c305b2df7ad6","channel":"website","campaign":"","websocket":"6500a8a3bf02224e648ef8bd"}' + }, + json: taskBody + }; + + return new Promise((resolve) => { + this.configs.request(options, (error, response, body) => { + if (error) { + console.error('Error on updateTask', error); + resolve([]); + } else { + this.configs.inboxProperties.sortOrder = body.sortOrder - 1; + resolve(body); + } + }); + }); + } + + deleteTask(deleteTaskId: string, deletedTaskprojectId: string): Promise { + if (!deleteTaskId || !deletedTaskprojectId) { + throw new Error('Both Task Id and Project ID are required for a delete, otherwise TickTick will fail silently.'); + } + + const taskToDelete = { taskId: deleteTaskId, projectId: deletedTaskprojectId }; + + let taskBody: any; + taskBody = { + add: [], + addAttachments: [], + delete: [taskToDelete], + deleteAttachments: [], + updateAttachments: [], + update: [] + }; + + const options = { + method: 'POST', + url: `${this.configs.apiUrl}/${API_ROUTES.updateTaskEndPoint}`, + headers: { + 'Content-Type': 'application/json', + Origin: 'https://ticktick.com', + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/117.0', + 'X-Device': '{"platform":"web","os":"Windows 10","device":"Firefox 117.0","name":"","version":4576,"id":"64fc9b22cbb2c305b2df7ad6","channel":"website","campaign":"","websocket":"6500a8a3bf02224e648ef8bd"}' + }, + json: taskBody + }; + + return new Promise((resolve) => { + this.configs.request(options, (error, response, body) => { + this.configs.inboxProperties.sortOrder = body.sortOrder - 1; + resolve(body); + }); + }); + } +} diff --git a/src/entities/User.ts b/src/entities/User.ts new file mode 100644 index 0000000..7b78abb --- /dev/null +++ b/src/entities/User.ts @@ -0,0 +1,87 @@ +import { getRequestOptions } from 'src/utils/get_request_options'; +import { API_ROUTES } from '../utils/api_routes'; +import Base from './Base'; + +export default class User extends Base { + async login(): Promise { + try { + const url = `${this.configs.apiUrl}/${API_ROUTES.login}`; + const options = { + method: 'POST', + url: url, + headers: { + Origin: 'https://ticktick.com', + 'Content-Type': 'application/json', + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/117.0', + 'x-device': '{"platform":"web","os":"Windows 10","device":"Firefox 117.0","name":"","version":4576,"id":"64f9effe6edff918986b5f71","channel":"website","campaign":"","websocket":""}' + }, + json: { + username: this.configs.username, + password: this.configs.password + } + }; + + return new Promise((resolve) => { + this.configs.request(options, async (error, request, body) => { + if (!body || body.errorMessage) { + console.error(`login error: ${body ? body.errorMessage : 'Probably timeout.'}`); + resolve(false); + } else { + await this.getInboxProperties() + .then((data) => { + resolve(true); + }) + .catch((err) => { + resolve(false); + }); + } + }); + }); + } catch (e) { + return false; + } + } + + async getUserSettings(): Promise { + return new Promise((resolve) => { + const url = `${this.configs.apiUrl}/${API_ROUTES.userPreferencesEndPoint}`; + const options = getRequestOptions(url); + + this.configs.request(options, (error, response, body) => { + body = JSON.parse(body); + resolve(body); + }); + }); + } + + private async getInboxProperties(): Promise { + return new Promise((resolve) => { + try { + const url = `${this.configs.apiUrl}/${API_ROUTES.generalDetailsEndPoint}`; + const options = getRequestOptions(url); + + this.configs.request(options, (error, response, body) => { + if (error) { + console.error(error); + resolve(false); + } + if (body) { + body = JSON.parse(body); + + this.configs.inboxProperties.id = body.inboxId; + body.syncTaskBean.update.forEach((task: any) => { + if (task.projectId == this.configs.inboxProperties.id && task.sortOrder < this.configs.inboxProperties.sortOrder) { + this.configs.inboxProperties.sortOrder = task.sortOrder; + } + }); + this.configs.inboxProperties.sortOrder--; + } + resolve(true); + }); + } catch (e) { + console.error('Get Inbox Properties failed: ', e); + resolve(false); + } + }); + } +} diff --git a/src/index.ts b/src/index.ts index 71717ea..0df042e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,520 +1,40 @@ 'use strict'; -import ObjectID from 'bson-objectid'; import request from 'request'; - -import { IFilter } from './types/Filter'; -import { IHabit } from './types/Habit'; -import { IProject, ISections } from './types/Project'; -import { IProjectGroup } from './types/ProjectGroup'; -import { ITag } from './types/Tag'; -import { ITask } from './types/Task'; - -import { API_ENDPOINTS } from './utils/get-api-endpoints'; - -const { ticktickApiBaseUrl, TaskEndPoint, updateTaskEndPoint, allTagsEndPoint, generalDetailsEndPoint, allHabitsEndPoint, allProjectsEndPoint, allTasksEndPoint, singnInEndPoint, userPreferencesEndPoint, getSections, getAllCompletedItems } = API_ENDPOINTS; - -interface IoptionsProps { - username: string; - password: string; - baseUrl?: string; -} - -export class Tick { - request: any; - username: string; - password: string; - inboxProperties: { - id: string; - sortOrder: number; - }; - apiUrl: string; - - constructor({ username, password, baseUrl }: IoptionsProps) { - this.request = request.defaults({ jar: true }); - this.username = username; - this.password = password; - this.inboxProperties = { - id: '', - sortOrder: 0 - }; - this.apiUrl = baseUrl || ticktickApiBaseUrl; - } - - // USER ====================================================================== - - async login(): Promise { - try { - const url = `${this.apiUrl}/${singnInEndPoint}`; - const options = { - method: 'POST', - url: url, - headers: { - Origin: 'https://ticktick.com', - 'Content-Type': 'application/json', - 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/117.0', - 'x-device': '{"platform":"web","os":"Windows 10","device":"Firefox 117.0","name":"","version":4576,"id":"64f9effe6edff918986b5f71","channel":"website","campaign":"","websocket":""}' - }, - json: { - username: this.username, - password: this.password - } - }; - const reqObj = this.request; - - return new Promise((resolve) => { - reqObj(options, async (error: any, request: any, body: any) => { - let gotInboxProperties = false; - if (!body || body.errorMessage) { - console.error(`login error: ${body ? body.errorMessage : 'Probably timeout.'}`); - resolve(false); - } else { - await this.getInboxProperties() - .then((data) => { - resolve(true); - }) - .catch((err) => { - resolve(false); - }); - } - }); - }); - } catch (e: any) { - return false; - } - } - - async getUserSettings(): Promise { - return new Promise((resolve) => { - const url = `${this.apiUrl}/${userPreferencesEndPoint}`; - this.request(url, (error: any, response: any, body: any) => { - body = JSON.parse(body); - resolve(body); - }); - }); - } - - private async getInboxProperties(): Promise { - return new Promise((resolve) => { - try { - const url = `${this.apiUrl}/${generalDetailsEndPoint}`; - - this.request(url, (error: any, response: any, body: any) => { - if (error) { - console.error(error); - resolve(false); - } - if (body) { - body = JSON.parse(body); - - this.inboxProperties.id = body.inboxId; - body.syncTaskBean.update.forEach((task: any) => { - if (task.projectId == this.inboxProperties.id && task.sortOrder < this.inboxProperties.sortOrder) { - this.inboxProperties.sortOrder = task.sortOrder; - } - }); - this.inboxProperties.sortOrder--; - } - resolve(true); - }); - } catch (e) { - console.error('Get Inbox Properties failed: ', e); - resolve(false); - } - }); - } - - // FILTERS =================================================================== - - async getFilters(): Promise { - return new Promise((resolve) => { - const url = `${this.apiUrl}/${generalDetailsEndPoint}`; - - this.request(url, (error: any, response: any, body: any) => { - body = JSON.parse(body); - resolve(body.filters); - }); - }); - } - - // TAGS ====================================================================== - - async getTags(): Promise { - return new Promise((resolve) => { - const url = `${this.apiUrl}/${allTagsEndPoint}`; - this.request(url, (error: any, response: any, body: any) => { - body = JSON.parse(body); - resolve(body); - }); - }); - } - - // HABITS ==================================================================== - - async getHabits(): Promise { - return new Promise((resolve) => { - try { - const url = `${this.apiUrl}/${allHabitsEndPoint}`; - this.request(url, (error: any, response: any, body: any) => { - const parsedBody = JSON.parse(body); - resolve(parsedBody); - }); - } catch (e) { - resolve([]); - } - }); - } - - // PROJECTS ================================================================== - - async getProjectGroups(): Promise { - return new Promise((resolve) => { - const url = `${this.apiUrl}/${generalDetailsEndPoint}`; - - this.request(url, (error: any, response: any, body: any) => { - if (error) { - console.error('Error on getProjectGroups', error); - resolve([]); - } else { - body = JSON.parse(body); - resolve(body.projectGroups); - } - }); - }); - } - - async getProjects(): Promise { - return new Promise((resolve) => { - try { - const url = `${this.apiUrl}/${allProjectsEndPoint}`; - this.request(url, (error: any, response: any, body: any) => { - if (error) { - console.error('Error on getProjects', error); - resolve([]); - } else { - const parsedBody = JSON.parse(body); - resolve(parsedBody); - } - }); - } catch (e) { - console.error('did we get a weird body: ', e); - resolve([]); - } - }); - } - - //This may have worked at some point, but it doesn't any more. - // async getProject(projectId: string) : Promise { - // return new Promise((resolve) => { - // try { - // const url = `${this.apiUrl}/${getProject}/${projectId}`; - // this.request(url, (error: any, response: any, body: any) => { - // if (body !== undefined && body!== null && body.length > 0) - // { - // const parsedBody = JSON.parse(body); - // resolve(parsedBody); - // } - // }); - // } catch (e) { - // console.error(e) - // resolve([]); - // } - // }); - // } - - async getProjectSections(projectId: string): Promise { - return new Promise((resolve) => { - try { - const url = `${this.apiUrl}/${getSections}/${projectId}`; - this.request(url, (error: any, response: any, body: any) => { - if (error) { - console.error('Error on getProjectSections', error); - resolve([]); - } else { - const parsedBody = JSON.parse(body); - resolve(parsedBody); - } - }); - } catch (e) { - console.error(e); - resolve([]); - } - }); - } - - // RESOURCES ================================================================= - async getAllResources(): Promise { - const url = `${this.apiUrl}/${allTasksEndPoint}`; - const options = { - method: 'GET', - url: url, - headers: { - Origin: 'https://ticktick.com' +import Filters from './entities/Filters'; +import Habits from './entities/Habits'; +import Projects from './entities/Projects'; +import Tags from './entities/Tags'; +import Tasks from './entities/Tasks'; +import User from './entities/User'; +import { TTicktickConfigs, TExtendedConfigs, INITIAL_CONFIGS } from './utils/configs'; + +// ============================================================================= + +export default class Ticktick { + filters: Filters; + habits: Habits; + projects: Projects; + tags: Tags; + tasks: Tasks; + user: User; + + constructor(configs: TTicktickConfigs) { + const extendedConfigs: TExtendedConfigs = { + ...configs, + request: request.defaults({ jar: true }), + apiUrl: configs.apiUrl ?? INITIAL_CONFIGS.api_url, + inboxProperties: { + id: '', + sortOrder: 0 } }; - return new Promise((resolve) => { - this.request(options, function (error: any, response: any, body: any) { - if (error) { - console.error('Get all resources failed: ', error); - resolve([]); - } else { - if (body) { - body = JSON.parse(body); - } else { - console.error('Did not get a response on get all resources.'); - resolve([]); - } - } - //TODO: Do we have to have a finer grained definition, or trust the client? - resolve(body); - }); - }); - } - - // TASKS ===================================================================== - - async getTasksStatus(): Promise { - const url = `${this.apiUrl}/${allTasksEndPoint}`; - const options = { - method: 'GET', - url: url, - headers: { - Origin: 'https://ticktick.com' - } - }; - - return new Promise((resolve) => { - this.request(options, function (error: any, response: any, body: any) { - if (body) { - body = JSON.parse(body); - const tasks: ITask[] = body['syncTaskBean']; - resolve(tasks); - } else { - console.error('Get Task Status: No body received in response.'); - } - }); - }); - } - - async getAllTasks(): Promise { - const url = `${this.apiUrl}/${allTasksEndPoint}`; - const options = { - method: 'GET', - url: url, - headers: { - Origin: 'https://ticktick.com' - } - }; - - return new Promise((resolve) => { - this.request(options, function (error: any, response: any, body: any) { - body = JSON.parse(body); - const tasks: ITask[] = body['syncTaskBean']['update']; - resolve(tasks); - }); - }); - } - - async getTasks(): Promise { - return new Promise((resolve) => { - const url = `${this.apiUrl}/${generalDetailsEndPoint}`; - - this.request(url, (error: any, response: any, body: any) => { - body = JSON.parse(body); - resolve(body.syncTaskBean.update); - }); - }); - } - - async getTask(taskID: string, projectID: string): Promise { - return new Promise((resolve) => { - const url = `${this.apiUrl}/${TaskEndPoint}/${taskID}?projectID=${projectID}`; - - this.request(url, (error: any, response: any, body: any) => { - body = JSON.parse(body); - resolve(body); - }); - }); - } - - async getAllCompletedItems(): Promise { - return new Promise((resolve) => { - const url = `${this.apiUrl}/${getAllCompletedItems}`; - - this.request(url, (error: any, response: any, body: any) => { - body = JSON.parse(body); - resolve(body); - }); - }); - } - - addTask(jsonOptions: any): Promise { - const thisTask: ITask = { - id: jsonOptions.id ? jsonOptions.id : ObjectID(), - projectId: jsonOptions.projectId ? jsonOptions.projectId : this.inboxProperties.id, - sortOrder: jsonOptions.sortOrder ? jsonOptions.sortOrder : this.inboxProperties.sortOrder, - title: jsonOptions.title, - content: jsonOptions.content ? jsonOptions.content : '', - startDate: jsonOptions.startDate ? jsonOptions.startDate : null, - dueDate: jsonOptions.dueDate ? jsonOptions.dueDate : null, - timeZone: jsonOptions.timeZone ? jsonOptions.timeZone : 'America/New_York', // This needs to be updated to grab dynamically - isAllDay: jsonOptions.isAllDay ? jsonOptions.isAllDay : null, - reminder: jsonOptions.reminder ? jsonOptions.reminder : null, - reminders: jsonOptions.reminders ? jsonOptions.reminders : [{ id: ObjectID(), trigger: 'TRIGGER:PT0S' }], - repeatFlag: jsonOptions.repeatFlag ? jsonOptions.repeatFlag : null, - priority: jsonOptions.priority ? jsonOptions.priority : 0, - status: jsonOptions.status ? jsonOptions.status : 0, - items: jsonOptions.items ? jsonOptions.items : [], - progress: jsonOptions.progress ? jsonOptions.progress : 0, - modifiedTime: jsonOptions.modifiedTime ? jsonOptions.modifiedTime : new Date().toISOString().replace('Z', '+0000'), //"2017-08-12T17:04:51.982+0000", - deleted: jsonOptions.deleted ? jsonOptions.deleted : 0, - assignee: jsonOptions.assignee ? jsonOptions.assignee : null, - isDirty: jsonOptions.isDirty ? jsonOptions.isDirty : true, - local: jsonOptions.local ? jsonOptions.local : true, - remindTime: jsonOptions.remindTime ? jsonOptions.remindTime : null, - tags: jsonOptions.tags ? jsonOptions.tags : [], - childIds: jsonOptions.childIds ? jsonOptions.childIds : [], - parentId: jsonOptions.parentId ? jsonOptions.parentId : null - }; - - let taskBody: any; - taskBody = thisTask; - - const options = { - method: 'POST', - url: `${this.apiUrl}/${TaskEndPoint}`, - headers: { - 'Content-Type': 'application/json', - Origin: 'https://ticktick.com', - 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/117.0', - 'X-Device': '{"platform":"web","os":"Windows 10","device":"Firefox 117.0","name":"","version":4576,"id":"64fc9b22cbb2c305b2df7ad6","channel":"website","campaign":"","websocket":"6500a8a3bf02224e648ef8bd"}' - }, - json: taskBody - }; - - return new Promise((resolve) => { - this.request(options, (error: any, response: any, body: any) => { - // console.log("add === ") - // console.log("\n\nerror: ", error), - // console.log("\n\nresponse: ", response) - // console.log("\n\nbody: :",body) - // console.log("=== add end ") - if (error) { - console.error('Error on addTask', error); - resolve([]); - } else { - let bodySortOrder; - bodySortOrder = body.sortOrder; - this.inboxProperties.sortOrder = bodySortOrder - 1; - resolve(body); - } - }); - }); - } - - updateTask(jsonOptions: any): Promise { - const thisTask: ITask = { - id: jsonOptions.id ? jsonOptions.id : ObjectID(), - projectId: jsonOptions.projectId ? jsonOptions.projectId : this.inboxProperties.id, - sortOrder: jsonOptions.sortOrder ? jsonOptions.sortOrder : this.inboxProperties.sortOrder, - title: jsonOptions.title, - content: jsonOptions.content ? jsonOptions.content : '', - startDate: jsonOptions.startDate ? jsonOptions.startDate : null, - dueDate: jsonOptions.dueDate ? jsonOptions.dueDate : null, - timeZone: jsonOptions.timeZone ? jsonOptions.timeZone : 'America/New_York', // This needs to be updated to grab dynamically - isAllDay: jsonOptions.isAllDay ? jsonOptions.isAllDay : null, - reminder: jsonOptions.reminder ? jsonOptions.reminder : null, - reminders: jsonOptions.reminders ? jsonOptions.reminders : [{ id: ObjectID(), trigger: 'TRIGGER:PT0S' }], - repeatFlag: jsonOptions.repeatFlag ? jsonOptions.repeatFlag : null, - priority: jsonOptions.priority ? jsonOptions.priority : 0, - status: jsonOptions.status ? jsonOptions.status : 0, - items: jsonOptions.items ? jsonOptions.items : [], - progress: jsonOptions.progress ? jsonOptions.progress : 0, - modifiedTime: jsonOptions.modifiedTime ? jsonOptions.modifiedTime : new Date().toISOString().replace('Z', '+0000'), //"2017-08-12T17:04:51.982+0000", - deleted: jsonOptions.deleted ? jsonOptions.deleted : 0, - assignee: jsonOptions.assignee ? jsonOptions.assignee : null, - isDirty: jsonOptions.isDirty ? jsonOptions.isDirty : true, - local: jsonOptions.local ? jsonOptions.local : true, - remindTime: jsonOptions.remindTime ? jsonOptions.remindTime : null, - tags: jsonOptions.tags ? jsonOptions.tags : [], - childIds: jsonOptions.childIds ? jsonOptions.childIds : [], - parentId: jsonOptions.parentId ? jsonOptions.parentId : null - }; - - let taskBody: any; - taskBody = { - add: [], - addAttachments: [], - delete: [], - deleteAttachments: [], - updateAttachments: [], - update: [thisTask] - }; - - const options = { - method: 'POST', - url: `${this.apiUrl}/${updateTaskEndPoint}`, - headers: { - 'Content-Type': 'application/json', - Origin: 'https://ticktick.com', - 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/117.0', - 'X-Device': '{"platform":"web","os":"Windows 10","device":"Firefox 117.0","name":"","version":4576,"id":"64fc9b22cbb2c305b2df7ad6","channel":"website","campaign":"","websocket":"6500a8a3bf02224e648ef8bd"}' - }, - json: taskBody - }; - - return new Promise((resolve) => { - this.request(options, (error: any, response: any, body: any) => { - // console.log("add === ") - // console.log("\n\nerror: ", error), - // console.log("\n\nresponse: ", response) - // console.log("\n\nbody: :",body) - // console.log("=== add end ") - if (error) { - console.error('Error on updateTask', error); - resolve([]); - } else { - this.inboxProperties.sortOrder = body.sortOrder - 1; - resolve(body); - } - }); - }); - } - deleteTask(deleteTaskId: string, deletedTaskprojectId: string): Promise { - if (!deleteTaskId || !deletedTaskprojectId) { - throw new Error('Both Task Id and Project ID are required for a delete, otherwise TickTick will fail silently.'); - } - - const taskToDelete = { taskId: deleteTaskId, projectId: deletedTaskprojectId }; - - let taskBody: any; - taskBody = { - add: [], - addAttachments: [], - delete: [taskToDelete], - deleteAttachments: [], - updateAttachments: [], - update: [] - }; - - const options = { - method: 'POST', - url: `${this.apiUrl}/${updateTaskEndPoint}`, - headers: { - 'Content-Type': 'application/json', - Origin: 'https://ticktick.com', - 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/117.0', - 'X-Device': '{"platform":"web","os":"Windows 10","device":"Firefox 117.0","name":"","version":4576,"id":"64fc9b22cbb2c305b2df7ad6","channel":"website","campaign":"","websocket":"6500a8a3bf02224e648ef8bd"}' - }, - json: taskBody - }; - - return new Promise((resolve) => { - this.request(options, (error: any, response: any, body: any) => { - this.inboxProperties.sortOrder = body.sortOrder - 1; - resolve(body); - }); - }); + this.filters = new Filters(extendedConfigs); + this.habits = new Habits(extendedConfigs); + this.tags = new Tags(extendedConfigs); + this.user = new User(extendedConfigs); + this.tasks = new Tasks(extendedConfigs); + this.projects = new Projects(extendedConfigs); } } diff --git a/src/types/Filter.ts b/src/types/Filter.ts deleted file mode 100644 index 700c718..0000000 --- a/src/types/Filter.ts +++ /dev/null @@ -1,14 +0,0 @@ -export { IFilter }; - -interface IFilter { - id: string; - name: string; - rule: string; - sortOrder: any; - sortType: string; - viewMode: any; - timeline: any; - etag: string; - createdTime: string; - modifiedTime: string; -} diff --git a/src/types/Habit.ts b/src/types/Habit.ts deleted file mode 100644 index 976ff9b..0000000 --- a/src/types/Habit.ts +++ /dev/null @@ -1,26 +0,0 @@ -export { IHabit }; - -interface IHabit { - id: string; - name: string; - iconRes: string; - color: string; - sortOrder: any; - status: number; - encouragement: string; - totalCheckIns: number; - createdTime: string; - modifiedTime: string; - type: string; - goal: number; - step: number; - unit: string; - etag: string; - repeatRule: string; - reminders: string[]; - recordEnable: boolean; - sectionId: string; - targetDays: number; - targetStartDate: number; - completedCycles: number; -} diff --git a/src/types/Project.ts b/src/types/Project.ts deleted file mode 100644 index 47f6991..0000000 --- a/src/types/Project.ts +++ /dev/null @@ -1,29 +0,0 @@ -export interface IProject { - id: string - name: string - isOwner: boolean - color: string - inAll: boolean - sortOrder: any - sortType: string - userCount: number - etag: string - modifiedTime: string - closed: any - muted: boolean - transferred: any - groupId: any - viewMode: string - notificationOptions: any - teamId: any - permission: any - kind: string - timeline: any -} -export interface ISections -{ - id: number, - projectId: number, - name: string, - sortOrder: number, -} diff --git a/src/types/ProjectGroup.ts b/src/types/ProjectGroup.ts deleted file mode 100644 index d22705b..0000000 --- a/src/types/ProjectGroup.ts +++ /dev/null @@ -1,15 +0,0 @@ -export { IProjectGroup }; - -interface IProjectGroup { - id: string; - etag: string; - name: string; - showAll: boolean; - sortOrder: any; - viewMode: any; - deleted: number; - userId: number; - sortType: string; - teamId: any; - timeline: any; -} diff --git a/src/types/Tag.ts b/src/types/Tag.ts deleted file mode 100644 index 44bc33c..0000000 --- a/src/types/Tag.ts +++ /dev/null @@ -1,10 +0,0 @@ -export { ITag } - -interface ITag { - name: string - label: string - sortOrder: any - sortType: string - color: string - etag: string -} diff --git a/src/types/Task.ts b/src/types/Task.ts deleted file mode 100644 index 66561ac..0000000 --- a/src/types/Task.ts +++ /dev/null @@ -1,53 +0,0 @@ -export interface ITask { - id: string; - projectId: string; - sortOrder: any; - title: string; - content: string; - startDate: string; - dueDate: string; - timeZone: string; - isFloating?: boolean; - isAllDay: boolean; - reminder: string; // we only get a set - reminders: any[]; - repeatFirstDate?: string; - repeatFlag: string; - exDate?: any[]; - completedTime?: string; - completedUserId?: any; - repeatTaskId?: string; - - priority: number; - status: number; - items: any[]; - progress: number; - modifiedTime: string; - etag?: string; - deleted: number; - createdTime?: string; - creator?: any; - repeatFrom?: string; - focusSummaries?: any[]; - columnId?: string; - kind?: string; - - assignee?: any; - isDirty?: boolean; - local?: boolean; - remindTime?: any; - tags?: any[]; - childIds: string[]; - parentId: string; - -} - -export interface IUpdate -{ - "add": ITask[], - "addAttachments": [], - "delete": ITask[], - "deleteAttachments": [], - "update": ITask[], - "updateAttachments": [] -} \ No newline at end of file diff --git a/src/utils/api_routes.ts b/src/utils/api_routes.ts new file mode 100644 index 0000000..aedff31 --- /dev/null +++ b/src/utils/api_routes.ts @@ -0,0 +1,14 @@ +export const API_ROUTES = { + login: 'user/signon?wc=true&remember=true', + allTasks: 'batch/check/1', + generalDetailsEndPoint: 'batch/check/0', + TaskEndPoint: 'task', + getAllCompletedItems: 'project/all/completedInAll/', + updateTaskEndPoint: 'batch/task', + allHabitsEndPoint: 'habits', + allTagsEndPoint: 'tags', + allTasksEndPoint: 'batch/check/1', + getSections: 'column/project/', + userPreferencesEndPoint: 'user/preferences/settings', + allProjectsEndPoint: 'projects' +}; diff --git a/src/utils/configs.ts b/src/utils/configs.ts new file mode 100644 index 0000000..5d049c7 --- /dev/null +++ b/src/utils/configs.ts @@ -0,0 +1,22 @@ +import { RequestAPI } from 'request'; + +export const INITIAL_CONFIGS = { + origin: 'https://ticktick.com', + api_url: 'https://api.ticktick.com/api/v2', + browser_agent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/117.0' +}; + +export type TTicktickConfigs = { + username: string; + password: string; + apiUrl?: string; +}; + +export type TExtendedConfigs = TTicktickConfigs & { + apiUrl: string; + request: RequestAPI; + inboxProperties: { + id: string; + sortOrder: number; + }; +}; diff --git a/src/utils/convert-date-to-ticktick-format.ts b/src/utils/convert-date-to-ticktick-format.ts deleted file mode 100644 index ef95f1f..0000000 --- a/src/utils/convert-date-to-ticktick-format.ts +++ /dev/null @@ -1,16 +0,0 @@ -export { convertDateToTickTickFormat }; - -function convertDateToTickTickFormat(date: any) { - if (date === undefined) { - return date; - } - - let dateString = date; - if (date instanceof Date) { - dateString = date.toISOString(); - } else if (typeof dateString !== 'string') { - throw new Error(`The provided date "${date}" is invalid`); - } - - return dateString.replace('Z', '+0000'); -} diff --git a/src/utils/get-api-endpoints.ts b/src/utils/get-api-endpoints.ts deleted file mode 100644 index 2254f9d..0000000 --- a/src/utils/get-api-endpoints.ts +++ /dev/null @@ -1,29 +0,0 @@ -export { API_ENDPOINTS }; - -const API_ENDPOINTS = { - ticktickApiBaseUrl: 'https://api.ticktick.com/api/v2', - singnInEndPoint: 'user/signon?wc=true&remember=true', - userPreferencesEndPoint: 'user/preferences/settings', - generalDetailsEndPoint: 'batch/check/0', - allProjectsEndPoint: 'projects', - allHabitsEndPoint: 'habits', - allTagsEndPoint: 'tags', - allTasksEndPoint: 'batch/check/1', - TaskEndPoint: 'task', - updateTaskEndPoint: 'batch/task', - //If this ever existed, it's gone now. use getSections. That's the only project detail anyway. - //getProject: 'project/', - getSections: 'column/project/', - getAllCompletedItems: "project/all/completedInAll/" -}; - -/* -/batch/taskProject -/batch/task -/task?+query -/projects -/user/status -/project/all/trash/pagination?start= -/project/all/completedInAll/ -/habits/batch -*/ diff --git a/src/utils/get_request_options.ts b/src/utils/get_request_options.ts new file mode 100644 index 0000000..e89eb55 --- /dev/null +++ b/src/utils/get_request_options.ts @@ -0,0 +1,7 @@ +export const getRequestOptions = (url: string) => ({ + method: 'GET', + url: url, + headers: { + Origin: 'https://ticktick.com' + } +}); diff --git a/src/utils/validate.ts b/src/utils/validate.ts deleted file mode 100644 index ca441ef..0000000 --- a/src/utils/validate.ts +++ /dev/null @@ -1,24 +0,0 @@ - -export function isValid(variable: any) -{ - // console.log(`${variable} is ${typeof variable}`) - let retValue = false; - if (variable != null && variable !== 'undefined') { - if (Array.isArray(variable) && variable.length > 0) { - retValue = true; - } else { - // console.log(typeof variable) - if (typeof variable == 'string') { - retValue = variable.length > 0; - } else if (typeof variable == 'object') { - // console.log(Object.keys(variable)) - retValue = Object.keys(variable).length !== 0; - } - } - retValue = true; - } else { - retValue = false; - } - // console.log(`is valid: ${retValue}`) - return retValue; -} \ No newline at end of file