mirror of
https://github.com/NanmiCoder/cc-haha
synced 2026-07-16 13:03:31 +08:00
Text-only IM channels forced users to copy long permission request IDs from mobile chat, which made approval slow and error-prone. This keeps the requestId-based authorization protocol intact while adding short replies for the single-pending-request case and preserving full command fallbacks for ambiguous cases. Constraint: IM authorization must still resolve through the existing requestId permission_response path Rejected: Replace request IDs with global numeric IDs | concurrent permission prompts would make numeric IDs ambiguous across chats Confidence: high Scope-risk: moderate Directive: Do not allow short numeric replies when more than one permission request is pending in the chat Tested: cd adapters && bun test common/ telegram/ feishu/ dingtalk/ wechat/ Tested: cd adapters && bunx tsc --noEmit Tested: bun run check:adapters Not-tested: Real WeChat or Telegram account end-to-end message delivery
71 lines
2.4 KiB
TypeScript
71 lines
2.4 KiB
TypeScript
export type PermissionDecision = {
|
||
requestId: string
|
||
allowed: boolean
|
||
rule?: 'always'
|
||
}
|
||
|
||
function getSinglePendingRequestId(requestIds?: Iterable<string> | null): string | null {
|
||
if (!requestIds) return null
|
||
const ids = Array.from(requestIds)
|
||
return ids.length === 1 ? ids[0]! : null
|
||
}
|
||
|
||
export function parsePermissionCommand(
|
||
text: string,
|
||
pendingRequestIds?: Iterable<string> | null,
|
||
): PermissionDecision | null {
|
||
const trimmed = text.trim()
|
||
const match = text.trim().match(/^\/(allow|always|allow-always|deny)\s+(\S+)/i)
|
||
if (match) {
|
||
const action = match[1]!.toLowerCase()
|
||
const requestId = match[2]!
|
||
if (action === 'deny') return { requestId, allowed: false }
|
||
if (action === 'always' || action === 'allow-always') return { requestId, allowed: true, rule: 'always' }
|
||
return { requestId, allowed: true }
|
||
}
|
||
|
||
const requestId = getSinglePendingRequestId(pendingRequestIds)
|
||
if (!requestId) return null
|
||
|
||
const shortcut = trimmed.toLowerCase()
|
||
if (['1', '/1', 'allow', '/allow', 'y', 'yes', '允许', '允许一次', '同意', '批准'].includes(shortcut)) {
|
||
return { requestId, allowed: true }
|
||
}
|
||
if (['2', '/2', 'always', '/always', 'allow-always', '/allow-always', '永久允许', '一直允许'].includes(shortcut)) {
|
||
return { requestId, allowed: true, rule: 'always' }
|
||
}
|
||
if (['3', '/3', 'deny', '/deny', 'n', 'no', '拒绝', '不允许', '否'].includes(shortcut)) {
|
||
return { requestId, allowed: false }
|
||
}
|
||
|
||
return null
|
||
}
|
||
|
||
export function parsePermitCallbackData(data: string): PermissionDecision | null {
|
||
const parts = data.split(':')
|
||
if (parts.length !== 3 || parts[0] !== 'permit' || !parts[1]) return null
|
||
|
||
switch (parts[2]) {
|
||
case 'yes':
|
||
return { requestId: parts[1], allowed: true }
|
||
case 'always':
|
||
return { requestId: parts[1], allowed: true, rule: 'always' }
|
||
case 'no':
|
||
return { requestId: parts[1], allowed: false }
|
||
default:
|
||
return null
|
||
}
|
||
}
|
||
|
||
export function formatPermissionInstructions(requestId: string): string {
|
||
return [
|
||
'回复 1 允许一次,2 永久允许,3 拒绝。',
|
||
`也可回复 /allow ${requestId}、/always ${requestId}、/deny ${requestId}。`,
|
||
].join('\n')
|
||
}
|
||
|
||
export function formatPermissionDecisionStatus(decision: Pick<PermissionDecision, 'allowed' | 'rule'>): string {
|
||
if (!decision.allowed) return '❌ 已拒绝'
|
||
return decision.rule === 'always' ? '♾️ 已永久允许' : '✅ 已允许'
|
||
}
|