
用 corsair-dev/habitica 插件把 Habitica 接入 Corsair70 端点、双凭证认证与本地镜像实战指南【免费下载链接】corsairConnect your users to their apps项目地址: https://gitcode.com/GitHub_Trending/corsa/corsair本指南围绕 Corsair 仓库中的 Habitica 插件packages/habitica展开讲解如何将 Habitica 这个把任务与目标游戏化的习惯追踪平台通过一套完整端点安全地接入你自己的应用从安装、双凭证API Token User ID认证、70 端点清单与风险分级到速率限制、错误处理、本地数据镜像与审计日志的底层实现。读完你将掌握该插件的完整调用面、配置方式与源码级工作原理可直接基于此构建自己的 Habitica 集成。Habitica 插件是什么corsair-dev/habitica是 Corsair 的官方插件之一见 packages/habitica/package.json包描述为 Habitica plugin for Corsair。Habitica 是一个游戏化习惯追踪器把日常任务和人生目标包装成角色扮演游戏见 plugin-docs.yaml 中的description。插件让 Corsair 应用能够代理、缓存并审计用户对 Habitica API 的访问典型场景包括为你的用户展示他们的待办清单与习惯、让 AI Agent 代替用户创建任务/打分会话、围绕挑战与公会party/guild构建协作类功能。从源码结构看插件遵循 Corsair 的标准插件形态index.ts 是插件的唯一入口它导出一个habitica()工厂函数组装出id: habitica、认证配置、端点集合、端点元数据、schema 与错误处理器等完整插件对象。安装与依赖在包管理器中以依赖方式安装pnpm add corsair-dev/habitica按照 package.json 的声明它对外导出 ESM 构建产物dist/index.js与类型dist/index.d.ts并声明两个peerDependenciesPeer 依赖版本要求用途corsair0.1.0插件运行时的核心框架端点、上下文、事件日志、HTTP 传输zod^4.1.13端点输入/输出与实体 schema 校验仓库内提供pnpm buildtsc --build --force tsup、pnpm typecheck、pnpm testJest 单元测试与pnpm test:live真实账号集成测试见下文测试一节等脚本。在应用中启用插件与仓库中其他插件一致插件通过habitica()工厂函数实例化。核心类型定义于 index.tsimport { habitica } from corsair-dev/habitica; export const habiticaPlugin habitica({ // authType: api_key, // 默认即 api_key可省略 // key: ..., // 可选固定 API Token // userId: ..., // 可选固定账号 User ID // errorHandlers: { ... }, // 可选覆盖/追加错误处理器 // permissions: { ... }, // 可选按端点配置权限 });各配置项说明选项类型说明authTypePickAuthapi_key认证方式默认且仅支持api_key源码用as const satisfies AuthTypes收窄字面量类型见 index.tskeystring固定的 Habitica API Token仅在keyBuilder的source endpoint时优先返回见 index.tsuserIdstring账号 User ID以x-api-user发送不配置时回退到存储的user_idkeyhooksInternalHabiticaPlugin[hooks]插件钩子errorHandlersCorsairErrorHandler追加的自定义错误处理器会与内置处理器合并permissionsPluginPermissionsConfig...端点级权限配置双凭证认证为什么 userId 是第二个凭证Habitica 的凭证由两半组成x-api-userUser ID与x-api-keyAPI Token二者都会被服务端校验。源码在 client.ts 中明确指出拿着有效 Token 配另一个账号的 User ID会被 401 拒绝There is no account that uses those credentials.所以 User ID 不是Token 可推导的路由提示而是独立的第二凭证。插件把 User ID 声明为账号级 keyaccount-scoped key使其与 Token 一同存储而无需每次调用都传// index.ts export const habiticaAuthConfig { api_key: { account: [user_id] as const, }, } as const satisfies PluginAuthConfig;凭证解析逻辑位于 endpoints/shared.ts配置优先其次存储的 key且故意没有发现discovery回退——这与 Harvest 插件不同Harvest 可以询问 Token 能触达哪些账号而 Habitica 的每一条需要认证的路由都要求先有 User ID因此不存在任何能发现它的路由。两处都拿不到时抛出HabiticaUserIdMissingError错误信息会明确提示在插件选项中设置userId或在user_idkey 下存储一个值见 client.ts这比发一个必然 401 的请求友好得多。端点全景71 个操作的完整清单插件端点按模块.操作两级命名如tasks.create对应 index.ts 中的habiticaEndpointsNested结构。每个端点同时具备输入/输出 zod schemahabiticaEndpointSchemas、风险等级与描述元数据habiticaEndpointMeta。以下按模块完整列出 README 与源码确认的全部端点认证 auth3OperationOperation IDRiskDescriptionauth.loginhabitica.api.auth.loginreadExchange a password for an API tokenauth.registerhabitica.api.auth.registerwriteRegister a new Habitica account and mint its credentialauth.socialhabitica.api.auth.socialreadAuthenticate through a social provider任务 tasks14OperationOperation IDRiskDescriptiontasks.addTaghabitica.api.tasks.addTagwriteApply an existing tag to a tasktasks.createhabitica.api.tasks.createwriteCreate a habit, daily, todo or rewardtasks.createChallengeTaskhabitica.api.tasks.createChallengeTaskwriteAdd a task to a challengetasks.deletehabitica.api.tasks.deletedestructivePermanently delete a tasktasks.deleteChecklistItemhabitica.api.tasks.deleteChecklistItemwriteRemove a checklist item from a tasktasks.gethabitica.api.tasks.getreadRetrieve any task by idtasks.listhabitica.api.tasks.listreadList the accounts taskstasks.listChallengeTaskshabitica.api.tasks.listChallengeTasksreadList a challenges taskstasks.movehabitica.api.tasks.movewriteMove a task to a position in its listtasks.scorehabitica.api.tasks.scorewriteScore a task up or downtasks.unlinkAllChallengeTaskshabitica.api.tasks.unlinkAllChallengeTasksdestructiveUnlink every task of a challenge, optionally deleting members copiestasks.updatehabitica.api.tasks.updatewriteUpdate a tasktasks.updateChecklistItemhabitica.api.tasks.updateChecklistItemwriteUpdate a checklist items text共 14 项含tasks.create/list/get/update/delete/score/move/updateChecklistItem/deleteChecklistItem/addTag/createChallengeTask/listChallengeTasks/unlinkAllChallengeTasks及tasks.get可检索挑战任务。挑战 challenges9OperationOperation IDRiskDescriptionchallenges.clonehabitica.api.challenges.clonewriteDuplicate a challengechallenges.createhabitica.api.challenges.createwriteCreate a challenge in a groupchallenges.deletehabitica.api.challenges.deletedestructivePermanently delete a challenge and its taskschallenges.exportCsvhabitica.api.challenges.exportCsvreadExport a challenge as CSVchallenges.gethabitica.api.challenges.getreadRetrieve a challengechallenges.joinhabitica.api.challenges.joinwriteJoin a challengechallenges.leavehabitica.api.challenges.leavewriteLeave a challengechallenges.listByGrouphabitica.api.challenges.listByGroupreadList a groups challengeschallenges.listForUserhabitica.api.challenges.listForUserreadList the challenges the account takes part in公会/队伍 groups11OperationOperation IDRiskDescriptiongroups.createhabitica.api.groups.createwriteCreate a party or guildgroups.gethabitica.api.groups.getreadRetrieve a group by idgroups.getPartyhabitica.api.groups.getPartyreadRetrieve the accounts partygroups.getTavernhabitica.api.groups.getTavernreadRetrieve the Taverngroups.invitehabitica.api.groups.invitewriteInvite people to a groupgroups.inviteToQuesthabitica.api.groups.inviteToQuestwriteInvite the party to a questgroups.leavehabitica.api.groups.leavewriteLeave a groupgroups.listhabitica.api.groups.listreadList groups by typegroups.listMembershabitica.api.groups.listMembersreadList a groups membersgroups.removeMemberhabitica.api.groups.removeMemberwriteRemove a member from the partygroups.updatehabitica.api.groups.updatewriteUpdate a groups properties聊天 chat3OperationOperation IDRiskDescriptionchat.deleteMessagehabitica.api.chat.deleteMessagedestructivePermanently delete a chat messagechat.listhabitica.api.chat.listreadRead a groups chat messageschat.markSeenhabitica.api.chat.markSeenwriteMark a groups chat as read用户 user11OperationOperation IDRiskDescriptionuser.addPushDevicehabitica.api.user.addPushDevicewriteRegister a push-notification deviceuser.deleteMessagehabitica.api.user.deleteMessagedestructivePermanently delete an inbox messageuser.deletePushDevicehabitica.api.user.deletePushDevicewriteUnregister a push-notification deviceuser.equiphabitica.api.user.equipwriteEquip or unequip gear, a pet, a mount or a costumeuser.gethabitica.api.user.getreadRead the accounts user documentuser.markNotificationSeenhabitica.api.user.markNotificationSeenwriteMark one notification as seenuser.markNotificationsSeenhabitica.api.user.markNotificationsSeenwriteMark several notifications as seenuser.movePinnedItemhabitica.api.user.movePinnedItemwriteReorder a pinned rewarduser.readCardhabitica.api.user.readCardwriteMark a received card as readuser.resethabitica.api.user.resetdestructiveReset the account, deleting every task and returning to level 1user.updatehabitica.api.user.updatewriteUpdate user fields by dot path内容 content10OperationOperation IDRiskDescriptioncontent.dismissNewshabitica.api.content.dismissNewswriteDismiss the current announcementcontent.gethabitica.api.content.getreadFetch the whole game content cataloguecontent.getByTypehabitica.api.content.getByTypereadFetch the content catalogue with named categories EXCLUDEDcontent.marketGearhabitica.api.content.marketGearreadList gear for sale in the marketcontent.modelPathshabitica.api.content.modelPathsreadList a models field paths and typescontent.newshabitica.api.content.newsreadRead the latest Bailey announcementcontent.statushabitica.api.content.statusreadCheck that the Habitica API is upcontent.timeTravelershabitica.api.content.timeTravelersreadList the Time Travellers shop stockcontent.validateCouponhabitica.api.content.validateCouponreadCheck whether a coupon code is validcontent.worldStatehabitica.api.content.worldStatereadRead world events and the world boss导出 exports3OperationOperation IDRiskDescriptionexports.historyhabitica.api.exports.historyreadExport task history as CSVexports.inboxhabitica.api.exports.inboxreadExport the inbox as HTMLexports.userDatahabitica.api.exports.userDatareadExport the whole account as JSON (contains the account email)标签 tags4OperationOperation IDRiskDescriptiontags.createhabitica.api.tags.createwriteCreate a tagtags.deletehabitica.api.tags.deletedestructivePermanently delete a tagtags.listhabitica.api.tags.listreadList every tag on the accounttags.updatehabitica.api.tags.updatewriteRename a tagWebhook webhooks3OperationOperation IDRiskDescriptionwebhooks.createhabitica.api.webhooks.createwriteRegister an outbound webhookwebhooks.listhabitica.api.webhooks.listreadList the accounts outbound webhookswebhooks.subscribehabitica.api.webhooks.subscribewriteEnable an existing webhook风险等级体系read / write / destructive每个端点的元数据都标注了风险等级见 index.ts 的说明注释与habiticaEndpointMetaread只取数据不改状态。write改变状态但可撤销或可重复执行而无损失。destructive保留给 Habitica无法恢复的删除操作——Habitica 是硬删除没有软删除标记、没有回收站——外加账号重置。值得注意的两个反直觉归类源码注释明确解释了设计理由tasks.score是write而非read。它看起来只是读取角色数值但它是全部操作中唯一重放会改变结果的POST /tasks/:taskId/score/:direction打分会改变经验与金币打两次就是打两次因此归类为write见 endpoints/tasks.ts。auth.login是read。它用密码换取 API Token不改状态auth.register则是write因为它真实创建了账号。destructive共覆盖challenges.delete、chat.deleteMessage、tasks.delete、tasks.unlinkAllChallengeTasks、user.deleteMessage、user.reset、tags.delete七项。在集成这类端点时应配合 Corsair 的权限配置permissions选项做最小化授权。传输层实现Base URL、限流与 x-client所有请求都经由 client.ts 中的四个传输函数它们封装了 Habitica API 的种种怪癖版本化 Base 与越界的导出路径常规 API Base 是https://habitica.com/api/v3版本号在路径里而非 Header。三个数据导出操作exports.userData/exports.history/exports.inbox位于版本化 Base 之外即https://habitica.com/export/*。源码注释特别提醒Habitica 服务端源码用authWithSession而非authWithHeaders中间件路由它们看起来像要求浏览器会话但实测源码标注为 2026-08-15 验证用与/api/v3完全相同的x-api-user/x-api-key头即可 200 访问因此插件用独立的makeHabiticaExportRequest以同样的头部认证直连。速率限制30 次/分钟/账号源码注释记录了实测结论Habitica 每个 User ID 每分钟允许30 次已认证请求超过返回 429TooManyRequests实测第 30 次请求被拒文档数字精确无误。插件的限流配置client.tsconst HABITICA_RATE_LIMIT_CONFIG: RateLimitConfig { enabled: true, maxRetries: 3, initialRetryDelay: 1000, backoffMultiplier: 2, headerNames: { retryAfter: retry-after, remaining: x-ratelimit-remaining, limit: x-ratelimit-limit, }, };两个 Header 细节决定了为何不能直接套默认配置x-ratelimit-reset故意不配置Habitica 发送的是Date.toString()格式如Sat Aug 15 2026 16:43:00 GMT0000 ...共享辅助函数对其parseInt得到NaN会被丢弃配置它只会暗示插件按 reset 时间自调节而实际不能插件改由retry-after驱动。retry-after是小数秒实测21.069共享传输的parseInt会截断为 21导致第一次重试提前零点几秒、可能再吃一个 429但指数退避会在maxRetries内收敛属预期而非缺陷。而走裸fetch的四个非 JSON 路径三个导出 挑战 CSV由插件自行解析保留小数并向上取整绝不在窗口内重试见parseRetryAfterMsclient.ts。x-client每次请求都带Habitica 要求x-client头文档格式UserID-AppName但服务端并不校验格式。插件固定发送corsair源码注释解释这是稳定且诚实的选择——不伪装成服务端从不检查的格式也不携带 User ID从而不向请求日志泄露账号信息。该头无条件发送因为连无需认证的/api/v3/content缺了它都会 400Missing x-client headers.client.ts。导出请求的 60 秒超时导出是整个账号的文档而非一页行慢响应是常态而非故障因此makeHabiticaExportRequest/makeHabiticaTextRequest使用 60 秒超时HABITICA_EXPORT_TIMEOUT_MS高于共享传输的 20 秒client.ts。响应解包与请求构造的工程细节endpoints/shared.ts 承载了所有端点的公共逻辑unwrap/api/v3的每个响应都是{success:true,data:...}信封端点返回data而不是信封——success与传输层已检查的状态码冗余调用方不应穿透无信息的包装层。非信封形状如导出文档、挑战 CSV原样返回。compactBody/compactQuery剔除值为undefined的键。Habitica 的更新路由区分字段缺失与显式 nullPUT /tasks/:taskId省略字段即保持不变而序列化undefined两者都不是所以未设字段必须在构造 body 前移除。pathSegment对插入 URL 路径的值做encodeURIComponent。多个 Habitica 路径会插值非不透明 ID——优惠券码、任务 key、置顶奖励的点分path、装备key——原样拼接时若含/或?会静默指向别的路由。withRedactedPathValue为两个把敏感值放进路径的操作POST /coupons/validate/:code——有效优惠券是可兑现的凭证DELETE /user/push-devices/:regId——设备标识符提供错误脱敏。共享传输的ApiError只脱敏敏感查询参数、不碰路径段因此这里把原始与百分号编码两种形态都掩码为[REDACTED]并保留状态码以便错误分类器仍能识别shared.ts。匿名路由与认证路由分离habiticaAnonymousCall服务于/status、/content、/models/:model/paths等无需认证的路由以及三个铸证操作注册/登录/社交认证——后者的路由是authOptional调用者还没有账号不可能携带凭证。分离的意义在于一个缺失的 User ID 不能拖垮一个根本不需要凭证的匿名操作shared.ts。错误处理识别 Habitica 的 429 与 401error-handlers.ts 定义了四个处理器匹配规则基于 Habitica 的真实错误词表源码标注全部为 2026-08-15 实测HTTP 状态error字段400BadRequest401NotAuthorized,invalid_credentials404NotFound429TooManyRequestsRATE_LIMIT_ERROR429maxRetries设为 5 而非传输层的 3因为限流是固定的 1 分钟窗口等待在这里是真正充分的策略区别于一天都不重置的配额。若错误携带retryAfterApiError或裸路径的HabiticaHttpError都可能带则原样传递毫秒值。AUTH_ERROR401永不重试——同一个凭证再试还会失败。诊断要点Habitica 对错误 Token和错误 User ID给出同一个401invalid_credentials/ There is no account that uses those credentials.两半凭证无法从响应区分所以一次 401 意味着两半里有一半错了。缺 Header 则是另一种 401NotAuthorized/ Missing authentication headers.错误信息会明说。CLIENT_HEADER_ERROR缺x-client头是400 而非 401。插件总是发送该头所以实践中见到此错误指向传输被绕过而非调用方输入重试无济于事。DEFAULT兜底不重试。HabiticaHttpError裸fetch路径使用故意不附加响应体——失败的导出可能仍携带账号数据userdata.json含账号持有者的邮箱而这个错误对象最可能进入日志因此只留状态行client.ts。本地镜像Mirror缓存、驱逐与账号重置插件把读取到的实体镜像到 Corsair 的实体存储entity store实现原理见 endpoints/persist.tscacheEntity/cacheEntities写入前先用实体 schema 做safeParseHabitica 返回的形状不被 schema 识别时跳过而非写入——缓存永不持有插件无法读回的数据schema 缺口表现为缺行而非脏数据。写入是尽力而为best-effort失败仅告警不拖垮插件调用。批量写入按 16 并发分批CACHE_WRITE_CONCURRENCY因为GET /tasks/user不分页、一次返回全部任务长期账号可能一次数百行。字段投影实体 schema 用.loose()因此parsed.data仍携带未知属性。projectForCache以 schema 形状为白名单并额外剔除两类字段组的chat与 webhook 的urlOMIT_FROM_CACHE见 persist.ts——webhook URL 是调用方自己的端点可能把密钥藏在路径或查询里不进缓存。evictEntityHabitica 是硬删除——没有软删除、没有deleted_at、没有包含已删的列表删除后下次读取即 404。因此删除操作后的镜像驱逐是必需的tasks.delete用required: true若本地镜像删不掉则抛HabiticaMirrorEvictionError明确告知远端已删、本地副本还在因为重试删除会 404需要处理的是镜像而非远端persist.ts。注意日志在驱逐之前写入tasks.ts避免必需的驱逐抛错时丢掉一条确已发生的破坏性变更审计记录。clearMirroredTasksuser.reset一次调用删除账号所有任务并返回重置后的用户而非被删列表因此没有 ID 可逐条驱逐。它通过先完整读出全部镜像 ID、再逐条deleteByEntityId清空分页删除会因游标重排跳掉约一半。重置本身已成功失败仅告警而不抛错。任务 schema 字段见 schema/database.tsHabiticaTaskEntity覆盖typehabit/daily/todo/reward、text、notes、tags、value、priority、challenge、group、reminders、checklist、history、repeat、streak、completed等 Habitica 官方 JSON 键名字段名与官方 API 一致并标注意value/history/completed是时间点快照——tasks.score后镜像不做回读刷新因为花一个请求去刷新调用方没要的快照对 30 次/分钟的预算不划算。审计日志哪些内容绝不入日志所有端点调用都会通过logEventFromContext写入corsair_events。由于这些行继承事件日志的保留期Habitica 插件的日志策略格外谨慎endpoints/logging.tsauditPayload(input, identifierKeys)只记录显式列出的标识字段如taskId、challengeId其余已提供字段只记字段名列表fields数组而不记值——操作者能看到请求了什么日志却不变成账号隐私数据的副本。因此任务标题与备注、聊天与收件箱消息、个人资料文本、以邮箱/用户名给出的群邀请等一律不入日志。集合类值如任务 checklist、邀请收件人列表用countOf记计数而非内容。三个铸证操作register/login/social使用更严格的credentialAuditPayload()只记录发生过一次尝试——不记邮箱、用户名甚至不记字段名因为fields: [username,password]出现在保留日志里本身就是诱使后人把字段名放宽成字段值的邀请函logging.ts。Webhooks插件不消费事件只管理用户的出站 webhookREADME 明确No webhooks源码把这一点讲得更透index.ts 与 endpoints/webhooks.ts目录中没有 Habitica 的触发器插件不注册任何 webhook handler、matcher 或租户解析器脚手架生成的 webhook 文件被移除而非留下暗示存在该表面的空 stub。三个 webhook 操作是用户自己的出站 webhook——Habitica 回调用户自选的 URL——被当作普通操作实现。表面只有 create / list / subscribe即 enable三项API 虽有通用 update 和 delete但目录未列出插件不发明目录之外的兄弟端点保持与消费同一目录的其他插件一致。webhooks.list值得镜像failures字段这是 API 唯一的健康信号Habitica 在连续失败达 10 次时会禁用 webhookwebhooks.ts。webhooks.subscribe实为PUT /user/webhook/:id且 body 只有{ enabled: true }幂等。测试单元测试与真实账号集成测试包的测试策略integration.test.ts 文件头注释说明单元测试pnpm test覆盖客户端、端点 schema、错误处理器等见 client.test.ts、endpoints.test.ts、error-handlers.test.ts、schema.test.ts。集成测试pnpm test:live针对真实 Habitica 账号默认与 CI 均排除、无凭证时自跳过HABITICA_USER_IDuuid HABITICA_API_TOKENtoken pnpm test:live关键约束Habitica 限 30 次已认证请求/分钟/账号测试套件用paced()以 2.6 秒间隔节流——早期 2.1 秒版本仍吃到 429因为限流统计的是滚动分钟内的整体运行而非调用间隔2.65 MB 的内容目录也只拉一次而非每个测试各拉一次。故意永不实跑的操作user.reset不可撤销、auth.register/login/social输入是凭证注册会创建真实账号、tasks.score永久改变角色经验与金币仅开发期手工跑过一次、exports.userData含账号持有者邮箱仅断言其可达性而不读 body、以及任何删除他人所属群组/成员/聊天消息的操作。注意把文件名作为位置参数传给 jest 会被当作--testPathIgnorePatterns的值而静默排除该文件必须用--testPathPattern。源码地图与进一步阅读插件入口与端点注册packages/habitica/index.tsHTTP 传输、限流与凭证packages/habitica/client.ts错误分类器packages/habitica/error-handlers.ts公共请求/解包/脱敏辅助packages/habitica/endpoints/shared.ts本地镜像与驱逐packages/habitica/endpoints/persist.ts端点实现packages/habitica/endpoints/下的tasks.ts、challenges.ts、groups.ts、chat.ts、user.ts、auth.ts、content.ts、exports.ts、tags.ts、webhooks.ts实体与输入/输出 schemapackages/habitica/schema/database.ts、packages/habitica/endpoints/types.ts测试client.test.ts、endpoints.test.ts、error-handlers.test.ts、schema.test.ts、integration.test.ts插件元信息plugin-docs.yamldisplayName/description许可证插件以 Apache-2.0 许可发布见 README.md 与 package.json 的license字段。【免费下载链接】corsairConnect your users to their apps项目地址: https://gitcode.com/GitHub_Trending/corsa/corsair创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考