滴答清单/TickTick 官方API尝试

2 阅读2分钟

滴答清单官方并没有给出API的官方入口,但是从TickTick的API文档直接替换域名为dida365即可看到API文档;

官方文档,可以查找到对应的API调用方法: dida365 API doc

调用前需要创建Client ID和Client Secret; 调用过程:先获取 Code => Token => 然后读写操作 Manage Apps

截图是创建页面,填写Name之后,最重要的就是OAuth redirect URL需要添自己的重定向地址; image.png

通过Gin写了一个简单的测试,可以根据需求做调整,贴上代码:

package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"github.com/gin-gonic/gin"
	"io"
	"log"
	"net/http"
	"net/url"
	"strings"
)

const (
	clientID     = "client_id"
	clientSecret = "client_secret"
	redirectURI  = "http://127.0.0.1:8080/callback"
)

var token string

func main() {
	r := gin.Default()

        // 会跳转到授权许可
	// https://dida365.com/oauth/authorize?scope=tasks:write tasks:read&client_id=client_id&response_type=code&redirect_uri=redirect_uri&state=state
	r.GET("/", func(c *gin.Context) {
		authURL := fmt.Sprintf("https://dida365.com/oauth/authorize?scope=tasks:write tasks:read&client_id=%s&redirect_uri=%s&response_type=code&state=state", clientID, redirectURI)
		c.Redirect(http.StatusFound, authURL)
	})

        // 允许授权后,重定向到预留的重定向地址,附带需要获取的Code
	// http://http://127.0.0.1:8080/callback?code=T8GOXx&state=state
	r.GET("/callback", func(c *gin.Context) {
		code := c.Query("code")
		if code == "" {
			c.JSON(http.StatusBadRequest, gin.H{"error": "code not found"})
			return
		}

		data := url.Values{
			"client_id":     {clientID},
			"client_secret": {clientSecret},
			"redirect_uri":  {redirectURI},
			"grant_type":    {"authorization_code"},
			"scope":         {"tasks:write tasks:read"},
			"code":          {code},
		}

		req, err := http.NewRequest("POST", "https://dida365.com/oauth/token", strings.NewReader(data.Encode()))
		if err != nil {
			log.Fatal(err)
		}
		req.Header.Set("Content-Type", "application/x-www-form-urlencoded")

		client := &http.Client{}
		resp, err := client.Do(req)
		if err != nil {
			log.Fatal(err)
		}
		defer resp.Body.Close()

		body, err := io.ReadAll(resp.Body)
		if err != nil {
			log.Fatal(err)
		}

		var tokenResponse map[string]interface{}
		if err := json.Unmarshal(body, &tokenResponse); err != nil {
			log.Fatal(err)
		}

		accessToken, ok := tokenResponse["access_token"].(string)
		if !ok {
			log.Fatal("access_token not found in response")
		}

		token = accessToken

		c.JSON(http.StatusOK, gin.H{"message": "Authorization successful", "token": token})
	})
        
        // http://127.0.0.1:8080/create-task 尝试创建一个任务
	r.GET("/create-task", func(c *gin.Context) {
		if token == "" {
			c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"})
			return
		}

		task := map[string]interface{}{
			"title":   "New Task",
			"content": "This is the content of the task.",
			"desc":    "This is a new task.",
			"dueDate": "2024-06-6",
			// Add other required fields for the task creation
		}

		taskData, err := json.Marshal(task)
		if err != nil {
			log.Fatal(err)
		}

		client := &http.Client{}
		req, err := http.NewRequest("POST", "https://api.dida365.com/open/v1/task", bytes.NewBuffer(taskData))
		if err != nil {
			log.Fatal(err)
		}
		req.Header.Set("Authorization", "Bearer "+token)
		req.Header.Set("Content-Type", "application/json")

		resp, err := client.Do(req)
		if err != nil {
			log.Fatal(err)
		}
		defer resp.Body.Close()

		body, err := io.ReadAll(resp.Body)
		if err != nil {
			log.Fatal(err)
		}

		var createResponse map[string]interface{}
		if err := json.Unmarshal(body, &createResponse); err != nil {
			log.Fatal(err)
		}

		if resp.StatusCode != http.StatusOK || createResponse["errorCode"] != nil {
			c.JSON(resp.StatusCode, gin.H{
				"message":   "Failed to create task",
				"errorCode": createResponse["errorCode"],
				"errorId":   createResponse["errorId"],
				"errorMsg":  createResponse["errorMessage"],
				"data":      createResponse["data"],
			})
			return
		}

		c.JSON(http.StatusOK, gin.H{"message": "Task created successfully", "response": createResponse})
	})

	r.Run() // 在 127.0.0.1:8080 上监听
}

上面是通过gin创建的一个简单的测试流程,可以根据自己的需要修改;