simple_client.py•6.43 kB
#!/usr/bin/env python3
"""
Simple TickTick Client - No OAuth Required!
This uses an existing token file, so other projects can add tasks
without going through the OAuth flow.
Usage from another project:
from simple_client import add_ticktick_task
add_ticktick_task("Task title", content="Description", priority=5)
"""
import os
import json
import requests
from pathlib import Path
class SimpleTickTickClient:
"""Simple client that uses existing token - no OAuth flow needed"""
BASE_URL = "https://api.ticktick.com/open/v1"
def __init__(self, token_file=None):
"""
Initialize with existing token file
Args:
token_file: Path to .ticktick-token.json (default: looks in current dir and parent dirs)
"""
if token_file is None:
token_file = self._find_token_file()
if not token_file or not os.path.exists(token_file):
raise FileNotFoundError(
"No token file found. Please run authentication first:\n"
" python ticktick_rest_api.py --auth\n"
"This will create .ticktick-token.json"
)
self.token_file = token_file
self._load_token()
def _find_token_file(self):
"""Search for token file in current dir and up to 3 parent directories"""
current = Path.cwd()
# Check current directory and up to 3 levels up
for _ in range(4):
token_path = current / '.ticktick-token.json'
if token_path.exists():
return str(token_path)
current = current.parent
return None
def _load_token(self):
"""Load access token from file"""
with open(self.token_file, 'r') as f:
data = json.load(f)
self.access_token = data.get('access_token')
if not self.access_token:
raise ValueError("Invalid token file - no access_token found")
def _request(self, method, endpoint, **kwargs):
"""Make authenticated API request"""
url = f"{self.BASE_URL}/{endpoint}"
headers = kwargs.pop('headers', {})
headers['Authorization'] = f"Bearer {self.access_token}"
response = requests.request(method, url, headers=headers, **kwargs)
if response.status_code == 401:
raise Exception(
"Token expired. Please re-authenticate:\n"
" python ticktick_rest_api.py --auth"
)
return response
def get_projects(self):
"""Get all projects"""
response = self._request('GET', 'project')
response.raise_for_status()
return response.json()
def create_task(self, title, content=None, project=None, priority=0, due_date=None):
"""
Create a task in TickTick
Args:
title (str): Task title (required)
content (str): Task description/notes
project (str): Project name (e.g., "Health", "Work")
priority (int): 0=None, 1=Low, 3=Medium, 5=High
due_date (str): ISO format "2025-11-20T23:59:59+0000" or None
Returns:
dict: Created task data
"""
data = {'title': title}
if content:
data['content'] = content
if priority:
data['priority'] = priority
if due_date:
data['dueDate'] = due_date
data['isAllDay'] = True
# Look up project ID if project name provided
if project:
projects = self.get_projects()
matching = [p for p in projects if p['name'].lower() == project.lower()]
if matching:
data['projectId'] = matching[0]['id']
else:
raise ValueError(
f"Project '{project}' not found. "
f"Available: {', '.join([p['name'] for p in projects])}"
)
response = self._request('POST', 'task', json=data)
response.raise_for_status()
return response.json()
# Convenience function for easy importing
def add_ticktick_task(title, content=None, project=None, priority=0, due_date=None, token_file=None):
"""
Simple function to add a task to TickTick
Example:
from simple_client import add_ticktick_task
add_ticktick_task("Buy groceries", priority=3)
add_ticktick_task("Exercise", project="Health", priority=5)
Args:
title (str): Task title
content (str): Task description
project (str): Project name (e.g., "Health")
priority (int): 0, 1, 3, or 5
due_date (str): ISO format date or None
token_file (str): Path to token file (auto-detected if None)
Returns:
dict: Created task data
"""
client = SimpleTickTickClient(token_file=token_file)
return client.create_task(title, content, project, priority, due_date)
def main():
"""CLI interface for simple_client.py"""
import argparse
parser = argparse.ArgumentParser(description="Simple TickTick task creator (uses existing token)")
parser.add_argument("title", help="Task title")
parser.add_argument("--content", help="Task description")
parser.add_argument("--project", help="Project name")
parser.add_argument("--priority", type=int, choices=[0, 1, 3, 5], default=0, help="Priority (0-5)")
parser.add_argument("--due", help="Due date (YYYY-MM-DD)")
parser.add_argument("--token", help="Path to token file (default: auto-detect)")
args = parser.parse_args()
# Format due date if provided
due_date = None
if args.due:
from datetime import datetime
try:
dt = datetime.strptime(args.due, '%Y-%m-%d')
due_date = dt.strftime("%Y-%m-%dT23:59:59+0000")
except ValueError:
print(f"❌ Invalid date format: {args.due}. Use YYYY-MM-DD")
return
try:
task = add_ticktick_task(
title=args.title,
content=args.content,
project=args.project,
priority=args.priority,
due_date=due_date,
token_file=args.token
)
print(f"✅ Task created: {task['title']}")
print(f" ID: {task['id']}")
except FileNotFoundError as e:
print(f"❌ {e}")
except Exception as e:
print(f"❌ Error: {e}")
if __name__ == "__main__":
main()