Skip to main content
Glama
utils.js6.92 kB
/** * Things URL Scheme 工具函数 * 用于构建Things URL并进行参数编码 */ /** * 参数名映射:将驼峰式参数名转换为Things URL Scheme的参数名 */ const PARAM_NAME_MAP = { // add/add-project 通用 checklistItems: 'checklist-items', listId: 'list-id', headingId: 'heading-id', showQuickEntry: 'show-quick-entry', creationDate: 'creation-date', completionDate: 'completion-date', // add-project 特定 areaId: 'area-id', todos: 'to-dos', // update 通用 authToken: 'auth-token', prependNotes: 'prepend-notes', appendNotes: 'append-notes', addTags: 'add-tags', prependChecklistItems: 'prepend-checklist-items', appendChecklistItems: 'append-checklist-items', }; /** * URL编码字符串 * @param {string} str - 要编码的字符串 * @returns {string} 编码后的字符串 */ function encodeParam(str) { if (str === null || str === undefined) { return ''; } return encodeURIComponent(String(str)); } /** * 将对象转换为URL查询字符串 * @param {object} params - 参数对象 * @returns {string} 查询字符串 */ function buildQueryString(params) { const queryParts = []; for (let [key, value] of Object.entries(params)) { // 跳过未定义和null的值 if (value === undefined || value === null) { continue; } // 转换参数名 const paramName = PARAM_NAME_MAP[key] || key; // 处理数组类型 if (Array.isArray(value)) { // 数组用换行符连接 value = value.join('\n'); } // 处理布尔值 if (typeof value === 'boolean') { value = value ? 'true' : 'false'; } // 空字符串也要保留(用于清除字段) queryParts.push(`${paramName}=${encodeParam(value)}`); } return queryParts.join('&'); } /** * 构建Things URL * @param {string} command - Things命令名称 * @param {object} params - 参数对象 * @returns {string} 完整的Things URL */ export function buildThingsUrl(command, params = {}) { const baseUrl = `things:///${command}`; const queryString = buildQueryString(params); if (!queryString) { return baseUrl; } return `${baseUrl}?${queryString}`; } /** * 验证日期字符串格式 * @param {string} dateStr - 日期字符串 * @returns {boolean} 是否有效 */ export function isValidDateString(dateStr) { // 支持的格式: // - today, tomorrow, evening, anytime, someday // - yyyy-mm-dd // - yyyy-mm-dd@HH:mm // - 自然语言(英文) const specialValues = ['today', 'tomorrow', 'evening', 'anytime', 'someday']; if (specialValues.includes(dateStr.toLowerCase())) { return true; } // ISO日期格式 const isoDatePattern = /^\d{4}-\d{2}-\d{2}$/; if (isoDatePattern.test(dateStr)) { return true; } // 日期时间格式 const dateTimePattern = /^\d{4}-\d{2}-\d{2}@\d{2}:\d{2}$/; if (dateTimePattern.test(dateStr)) { return true; } // 其他情况视为自然语言,由Things处理 return true; } /** * 验证待办事项ID格式 * @param {string} id - ID字符串 * @returns {boolean} 是否有效 */ export function isValidThingsId(id) { // Things ID通常是UUID格式 const uuidPattern = /^[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}$/i; return uuidPattern.test(id); } /** * 验证内置列表ID * @param {string} id - ID字符串 * @returns {boolean} 是否有效 */ export function isValidBuiltInListId(id) { const builtInIds = [ 'inbox', 'today', 'anytime', 'upcoming', 'someday', 'logbook', 'tomorrow', 'deadlines', 'repeating', 'all-projects', 'logged-projects', ]; return builtInIds.includes(id.toLowerCase()); } /** * 构建JSON导入的URL(需要特殊处理) * @param {Array} data - JSON数据数组 * @param {object} options - 其他选项 * @returns {string} Things URL */ export function buildJsonUrl(data, options = {}) { // JSON需要先序列化再编码 const jsonString = JSON.stringify(data); const params = { data: jsonString, ...options, }; return buildThingsUrl('json', params); } /** * 解析Things URL的回调参数 * @param {string} callbackUrl - x-success回调URL * @returns {object} 解析后的参数 */ export function parseCallbackParams(callbackUrl) { try { const url = new URL(callbackUrl); const params = {}; for (const [key, value] of url.searchParams) { // 解码参数 params[key] = decodeURIComponent(value); } // 处理特殊参数 if (params['x-things-id']) { // 可能是逗号分隔的ID列表 params.ids = params['x-things-id'].split(','); } if (params['x-things-ids']) { // JSON格式的ID数组 try { params.ids = JSON.parse(params['x-things-ids']); } catch (e) { // 解析失败,保持原值 } } return params; } catch (error) { console.error('解析回调URL失败:', error); return {}; } } /** * 创建待办事项对象(用于JSON导入) * @param {object} attributes - 待办事项属性 * @param {string} operation - 操作类型: 'create' 或 'update' * @param {string} id - 待办事项ID(更新时需要) * @returns {object} 待办事项对象 */ export function createTodoObject(attributes, operation = 'create', id = null) { const todo = { type: 'to-do', attributes, }; if (operation === 'update') { todo.operation = 'update'; if (!id) { throw new Error('更新操作需要提供ID'); } todo.id = id; } return todo; } /** * 创建项目对象(用于JSON导入) * @param {object} attributes - 项目属性 * @param {string} operation - 操作类型: 'create' 或 'update' * @param {string} id - 项目ID(更新时需要) * @returns {object} 项目对象 */ export function createProjectObject(attributes, operation = 'create', id = null) { const project = { type: 'project', attributes, }; if (operation === 'update') { project.operation = 'update'; if (!id) { throw new Error('更新操作需要提供ID'); } project.id = id; } return project; } /** * 创建标题对象(用于JSON导入的项目内) * @param {string} title - 标题文本 * @param {boolean} archived - 是否存档 * @returns {object} 标题对象 */ export function createHeadingObject(title, archived = false) { return { type: 'heading', attributes: { title, archived, }, }; } /** * 创建清单项对象 * @param {string} title - 清单项文本 * @param {boolean} completed - 是否完成 * @param {boolean} canceled - 是否取消 * @returns {object} 清单项对象 */ export function createChecklistItemObject(title, completed = false, canceled = false) { return { type: 'checklist-item', attributes: { title, completed, canceled, }, }; }

Implementation Reference

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Mieluoxxx/things_mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server