├── .gitignore ├── LICENSE ├── README.md ├── demo.js ├── package.json └── src └── tick.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | package-lock.json -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Raymond Berger 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TickTick API 2 | 3 | A Node.js module for using the UNOFFICIAL API for TickTick.com 4 | 5 | There is no official API available so this uses the API based on calls their site makes. Since it's undocumented it may change at any time and this module may stop working. 6 | 7 | ## Usage 8 | 9 | All you need is your TickTick login and you can begin adding tasks. 10 | 11 | ```javascript 12 | let tick = require('./tick.js') 13 | 14 | async function main() { 15 | let t = await new tick({ username: "email@email.com", password: "supersecurestuff" }); 16 | let due = new Date("04 August 2018 14:48"); 17 | due = due.toISOString().replace("Z", "+0000"); // The api only accepts dates in this format 18 | options = {title: "Update the API wrapper", dueDate: due} 19 | await t.addTask(options); 20 | } 21 | 22 | main(); 23 | ``` 24 | 25 | ## Promises 26 | 27 | Each function returns a promise. The login function returns a promise with a new object that has the cookies stored for the login session. 28 | 29 | If an error occurs at any part of the request a request will be thrown. 30 | 31 | ## API 32 | 33 | ### tick.addTask(options); 34 | 35 | Adds a task to the inbox of the logged in user. 36 | 37 | ```javascript 38 | tick.addTask({title: "my great task"}); 39 | ``` 40 | ## Contributing 41 | As you can see, this repo is still in it's infancy. If you're like to contribute feel free to open an issue or make a pull request. 42 | 43 | ## TODO 44 | These are features I may add in the future 45 | * Mark task completed 46 | * Get list of current tasks 47 | * Add task with due date 48 | 49 | -------------------------------------------------------------------------------- /demo.js: -------------------------------------------------------------------------------- 1 | /* 2 | Example of how this library could be used 3 | */ 4 | 5 | let tick = require('./src/tick.js') 6 | 7 | async function main() { 8 | try { 9 | let t = await new tick({ username: "email@email.com", password: "supersecurestuff" }); 10 | let due = new Date("04 August 2018 14:48"); 11 | due = due.toISOString().replace("Z", "+0000"); // The api only accepts dates in this format 12 | options = {title: "put on aawww yeah ", dueDate: due} 13 | await t.addTask(options); 14 | } catch (e) { 15 | console.log(e); 16 | } 17 | } 18 | 19 | main(); 20 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ticktick-api", 3 | "version": "1.0.0", 4 | "description": "wrapper for ticktick api", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/RayBB/node-ticktick-api.git" 12 | }, 13 | "author": "RayBB", 14 | "license": "MIT", 15 | "bugs": { 16 | "url": "https://github.com/RayBB/node-ticktick-api/issues" 17 | }, 18 | "homepage": "https://github.com/RayBB/node-ticktick-api#readme", 19 | "dependencies": { 20 | "bson-objectid": "^1.1.5", 21 | "request": "^2.81.0" 22 | }, 23 | "engines": { 24 | "node": ">=7.6" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/tick.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var request = require('request'); 3 | var ObjectID = require("bson-objectid"); 4 | 5 | module.exports = class tick { 6 | constructor(options) { 7 | /* 8 | options should include { 9 | username: "email@email.com" 10 | password: "user password here" 11 | } 12 | */ 13 | this.sortOrder = 0; 14 | this.request = request.defaults({ 'jar': true }); 15 | return new Promise((resolve, reject) => { 16 | this.login(options.username, options.password) 17 | .then(async () => { 18 | await this.getSortOrder(); 19 | resolve(this); 20 | }) 21 | }) 22 | } 23 | 24 | login(username, password) { 25 | const url = "https://ticktick.com/api/v2/user/signon?wc=true&remember=true"; 26 | console.log("login started"); 27 | const options = { 28 | method: "POST", 29 | url: url, 30 | headers: { 31 | "Content-Type": "application/json", 32 | Origin: "https://ticktick.com" 33 | }, 34 | json: { 35 | username: username, 36 | password: password 37 | } 38 | }; 39 | return new Promise((resolve, reject) => { 40 | this.request(options, function (error, request, body) { 41 | console.log("login done"); 42 | if (body.username !== undefined) { 43 | resolve(); 44 | } else { 45 | throw new Error("Could not login"); 46 | } 47 | }); 48 | }); 49 | } 50 | getSortOrder() { 51 | console.log("get sort order started"); 52 | return new Promise((resolve, reject) => { 53 | var parent = this; 54 | const url = "https://ticktick.com/api/v2/batch/check/0"; 55 | 56 | this.request(url, function (error, response, body) { 57 | body = JSON.parse(body); 58 | parent.inboxId = body.inboxId; 59 | body.syncTaskBean.update.forEach(task => { 60 | if (task.projectId == parent.inboxId && task.sortOrder < parent.sortOrder) { 61 | parent.sortOrder = task.sortOrder; 62 | } 63 | }); 64 | parent.sortOrder--; 65 | console.log("the sort order is: ", parent.sortOrder); 66 | resolve(); 67 | }); 68 | }); 69 | } 70 | getAllUncompletedTasks() { 71 | console.log("Get all Uncompleted tasks started") 72 | const url = "https://ticktick.com/api/v2/batch/check/1"; 73 | const options = { 74 | method: "GET", 75 | url: url, 76 | headers: { 77 | Origin: "https://ticktick.com" 78 | }, 79 | }; 80 | 81 | return new Promise((resolve, reject) => { 82 | this.request(options, function (error, response, body) { 83 | body = JSON.parse(body); 84 | var tasks = body["syncTaskBean"]["update"]; 85 | console.log("Retrevied all uncompleted tasks"); 86 | resolve(tasks); 87 | }); 88 | }); 89 | } 90 | //the default list will be inbox 91 | addTask(jsonOptions) { 92 | const url = "https://ticktick.com/api/v2/task"; 93 | console.log("Add task started"); 94 | const options = { 95 | method: "POST", 96 | url: url, 97 | headers: { 98 | "Content-Type": "application/json", 99 | Origin: "https://ticktick.com" 100 | }, 101 | json: { 102 | assignee: (jsonOptions.assignee) ? jsonOptions.assignee : null, 103 | content: (jsonOptions.content) ? jsonOptions.content : "", 104 | deleted: (jsonOptions.deleted) ? jsonOptions.deleted : 0, 105 | dueDate: (jsonOptions.dueDate) ? jsonOptions.dueDate : null, 106 | id: (jsonOptions.id) ? jsonOptions.id : ObjectID(), 107 | isAllDay: (jsonOptions.isAllDay) ? jsonOptions.isAllDay : null, 108 | isDirty: (jsonOptions.isDirty) ? jsonOptions.isDirty : true, 109 | items: (jsonOptions.items) ? jsonOptions.items : [], 110 | local: (jsonOptions.local) ? jsonOptions.local : true, 111 | modifiedTime: (jsonOptions.modifiedTime) ? jsonOptions.modifiedTime : new Date().toISOString().replace("Z", "+0000"), //"2017-08-12T17:04:51.982+0000", 112 | priority: (jsonOptions.priority) ? jsonOptions.priority :0, 113 | progress: (jsonOptions.progress) ? jsonOptions.progress : 0, 114 | projectId: (jsonOptions.projectId) ? jsonOptions.projectId : this.inboxId, 115 | reminder: (jsonOptions.reminder) ? jsonOptions.reminder : null, 116 | reminders: (jsonOptions.reminders) ? jsonOptions.reminders : [{id:ObjectID(),trigger:"TRIGGER:PT0S"}], 117 | remindTime: (jsonOptions.remindTime) ? jsonOptions.remindTime : null, 118 | repeatFlag: (jsonOptions.repeatFlag) ? jsonOptions.repeatFlag : null, 119 | sortOrder: (jsonOptions.sortOrder) ? jsonOptions.sortOrder : this.sortOrder, 120 | startDate: (jsonOptions.startDate) ? jsonOptions.startDate : null, 121 | status: (jsonOptions.status) ? jsonOptions.status : 0, 122 | tags: (jsonOptions.tags) ? jsonOptions.tags : [], 123 | timeZone: (jsonOptions.timeZone) ? jsonOptions.timeZone : "America/New_York", // This needs to be updated to grab dynamically 124 | title: jsonOptions.title, 125 | } 126 | }; 127 | 128 | return new Promise((resolve, reject) => { 129 | this.request(options, function (error, response, body) { 130 | console.log("Added: " + jsonOptions.title); 131 | this.sortOrder = body.sortOrder - 1; 132 | resolve(body); 133 | }); 134 | }); 135 | } 136 | } 137 | --------------------------------------------------------------------------------