2024-08-16 23:29:59 +02:00
|
|
|
from __future__ import annotations
|
|
|
|
|
|
2024-08-15 16:50:56 +02:00
|
|
|
import urllib.parse
|
2024-08-16 23:29:59 +02:00
|
|
|
from dataclasses import asdict, dataclass
|
|
|
|
|
from typing import TYPE_CHECKING
|
|
|
|
|
|
|
|
|
|
if TYPE_CHECKING:
|
|
|
|
|
from datetime import datetime
|
2024-08-15 16:50:56 +02:00
|
|
|
|
2024-08-21 21:11:29 +02:00
|
|
|
import httpx
|
2024-08-15 16:50:56 +02:00
|
|
|
|
|
|
|
|
from src.config import Config
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class TickTick:
|
2024-08-16 23:29:59 +02:00
|
|
|
@dataclass
|
|
|
|
|
class Task:
|
|
|
|
|
projectId: str # noqa: N815
|
|
|
|
|
title: str
|
|
|
|
|
dueDate: str | None = None # noqa: N815
|
|
|
|
|
content: str | None = None
|
|
|
|
|
desc: str | None = None
|
|
|
|
|
|
2024-08-15 16:50:56 +02:00
|
|
|
def __init__(self) -> None:
|
|
|
|
|
print("Initializing TickTick...")
|
|
|
|
|
if Config.get_env("TICKTICK_ACCESS_TOKEN") is None:
|
|
|
|
|
self._begin_auth()
|
2024-08-16 23:29:59 +02:00
|
|
|
else:
|
|
|
|
|
self._access_token = Config.get_env("TICKTICK_ACCESS_TOKEN")
|
2024-08-15 16:50:56 +02:00
|
|
|
|
|
|
|
|
def _begin_auth(self) -> None:
|
|
|
|
|
ticktick_code_auth_url = "https://ticktick.com/oauth/authorize?"
|
|
|
|
|
ticktick_code_auth_params = {
|
|
|
|
|
"client_id": Config.get_env("TICKTICK_CLIENT_ID"),
|
|
|
|
|
"scope": "tasks:read tasks:write",
|
|
|
|
|
"state": "begin_auth",
|
|
|
|
|
"redirect_uri": Config.get_env("TICKTICK_CODE_REDIRECT_URI"),
|
|
|
|
|
"response_type": "code",
|
|
|
|
|
}
|
|
|
|
|
ticktick_auth_url_encoded = urllib.parse.urlencode(ticktick_code_auth_params)
|
|
|
|
|
print("Visit: ", ticktick_code_auth_url + ticktick_auth_url_encoded, " to authenticate.")
|
|
|
|
|
|
2024-08-21 21:11:29 +02:00
|
|
|
async def retrieve_access_token(self, code: str, state: str) -> bool:
|
2024-08-15 16:50:56 +02:00
|
|
|
if state != "begin_auth":
|
|
|
|
|
print("Invalid state.")
|
|
|
|
|
return False
|
|
|
|
|
ticktick_token_url = "https://ticktick.com/oauth/token" # noqa: S105
|
2024-08-21 21:11:29 +02:00
|
|
|
ticktick_token_auth_params: dict[str, str] = {
|
2024-08-15 16:50:56 +02:00
|
|
|
"code": code,
|
|
|
|
|
"grant_type": "authorization_code",
|
|
|
|
|
"scope": "tasks:write tasks:read",
|
|
|
|
|
"redirect_uri": Config.get_env("TICKTICK_CODE_REDIRECT_URI"),
|
|
|
|
|
}
|
|
|
|
|
client_id = Config.get_env("TICKTICK_CLIENT_ID")
|
|
|
|
|
client_secret = Config.get_env("TICKTICK_CLIENT_SECRET")
|
2024-08-21 21:11:29 +02:00
|
|
|
response = await httpx.AsyncClient().post(
|
|
|
|
|
ticktick_token_url,
|
|
|
|
|
data=ticktick_token_auth_params,
|
|
|
|
|
auth=httpx.BasicAuth(username=client_id, password=client_secret),
|
|
|
|
|
timeout=10,
|
|
|
|
|
)
|
|
|
|
|
Config.update_env("TICKTICK_ACCESS_TOKEN", response.json()["access_token"])
|
2024-08-15 16:50:56 +02:00
|
|
|
return True
|
2024-08-16 23:29:59 +02:00
|
|
|
|
2024-08-21 21:11:29 +02:00
|
|
|
async def get_tasks(self, project_id: str) -> list[dict]:
|
2024-08-16 23:29:59 +02:00
|
|
|
ticktick_get_tasks_url = "https://api.ticktick.com/open/v1/project/" + project_id + "/data"
|
|
|
|
|
header: dict[str, str] = {"Authorization": f"Bearer {self._access_token}"}
|
2024-08-21 21:11:29 +02:00
|
|
|
response = await httpx.AsyncClient().get(ticktick_get_tasks_url, headers=header, timeout=10)
|
2024-08-16 23:29:59 +02:00
|
|
|
return response.json()["tasks"]
|
|
|
|
|
|
2024-08-21 21:11:29 +02:00
|
|
|
async def has_duplicate_task(self, project_id: str, task_title: str) -> bool:
|
|
|
|
|
tasks = await self.get_tasks(project_id=project_id)
|
2024-08-16 23:29:59 +02:00
|
|
|
return any(task["title"] == task_title for task in tasks)
|
|
|
|
|
|
2024-08-21 21:11:29 +02:00
|
|
|
async def create_task(self, task: TickTick.Task) -> dict[str, str]:
|
|
|
|
|
if not await self.has_duplicate_task(project_id=task.projectId, task_title=task.title):
|
2024-08-16 23:29:59 +02:00
|
|
|
ticktick_task_creation_url = "https://api.ticktick.com/open/v1/task"
|
|
|
|
|
header: dict[str, str] = {"Authorization": f"Bearer {self._access_token}"}
|
2024-08-21 21:11:29 +02:00
|
|
|
await httpx.AsyncClient().post(ticktick_task_creation_url, headers=header, json=asdict(task), timeout=10)
|
2024-08-16 23:29:59 +02:00
|
|
|
return {"title": task.title}
|
|
|
|
|
|
2024-08-19 12:05:46 +02:00
|
|
|
@staticmethod
|
|
|
|
|
def datetime_to_ticktick_format(datetime: datetime) -> str:
|
2024-08-16 23:29:59 +02:00
|
|
|
return datetime.strftime("%Y-%m-%dT%H:%M:%S") + "+0000"
|