mirror of
https://github.com/NanmiCoder/cc-haha
synced 2026-07-16 13:03:31 +08:00
feat(pairing): implement core pairing logic module
Add adapters/common/pairing.ts with generatePairingCode(), isPaired(), and tryPair() — one-time-use 6-char code flow with atomic config writes. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
88e7e14598
commit
4bcac9d25f
107
adapters/common/pairing.ts
Normal file
107
adapters/common/pairing.ts
Normal file
@ -0,0 +1,107 @@
|
||||
/**
|
||||
* 配对核心逻辑
|
||||
*
|
||||
* - generatePairingCode(): 生成 6 位安全配对码
|
||||
* - isPaired(): 检查用户是否已配对(pairedUsers + allowedUsers 并集)
|
||||
* - tryPair(): 验证配对码,成功则写入 pairedUsers 并清除 code
|
||||
*/
|
||||
|
||||
import * as fs from 'node:fs'
|
||||
import * as os from 'node:os'
|
||||
import * as path from 'node:path'
|
||||
import * as crypto from 'node:crypto'
|
||||
import type { PairedUser, PairingState } from './config.js'
|
||||
|
||||
const SAFE_ALPHABET = 'ABCDEFGHJKMNPQRSTUVWXYZ23456789' // 排除 0/O/1/I/L
|
||||
const CODE_LENGTH = 6
|
||||
const CODE_TTL_MS = 60 * 60 * 1000 // 60 minutes
|
||||
|
||||
function getConfigPath(): string {
|
||||
const configDir = process.env.CLAUDE_CONFIG_DIR || path.join(os.homedir(), '.claude')
|
||||
return path.join(configDir, 'adapters.json')
|
||||
}
|
||||
|
||||
function readConfigFile(): Record<string, any> {
|
||||
try {
|
||||
return JSON.parse(fs.readFileSync(getConfigPath(), 'utf-8'))
|
||||
} catch {
|
||||
return {}
|
||||
}
|
||||
}
|
||||
|
||||
function writeConfigFile(data: Record<string, any>): void {
|
||||
const filePath = getConfigPath()
|
||||
const dir = path.dirname(filePath)
|
||||
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true })
|
||||
const tmp = `${filePath}.tmp.${Date.now()}`
|
||||
fs.writeFileSync(tmp, JSON.stringify(data, null, 2) + '\n', 'utf-8')
|
||||
fs.renameSync(tmp, filePath)
|
||||
}
|
||||
|
||||
export function generatePairingCode(): string {
|
||||
let code = ''
|
||||
for (let i = 0; i < CODE_LENGTH; i++) {
|
||||
code += SAFE_ALPHABET[crypto.randomInt(SAFE_ALPHABET.length)]
|
||||
}
|
||||
return code
|
||||
}
|
||||
|
||||
/** 检查用户是否已配对(pairedUsers + allowedUsers 并集) */
|
||||
export function isPaired(
|
||||
platform: 'telegram' | 'feishu',
|
||||
userId: string | number,
|
||||
config: Record<string, any>,
|
||||
): boolean {
|
||||
const platformConfig = config[platform] ?? {}
|
||||
const allowedUsers: (string | number)[] = platformConfig.allowedUsers ?? []
|
||||
const pairedUsers: PairedUser[] = platformConfig.pairedUsers ?? []
|
||||
|
||||
// allowedUsers 非空时检查
|
||||
if (allowedUsers.length > 0 && allowedUsers.includes(userId)) return true
|
||||
// allowedUsers 为空且 pairedUsers 也为空 => 旧行为(允许所有人)
|
||||
if (pairedUsers.length === 0 && allowedUsers.length === 0) return true
|
||||
|
||||
return pairedUsers.some((p) => String(p.userId) === String(userId))
|
||||
}
|
||||
|
||||
/**
|
||||
* 尝试配对:验证消息文本是否匹配当前有效配对码。
|
||||
* 成功则写入 pairedUsers 并清除 pairing.code,返回 true。
|
||||
*/
|
||||
export function tryPair(
|
||||
messageText: string,
|
||||
senderInfo: { userId: string | number; displayName: string },
|
||||
platform: 'telegram' | 'feishu',
|
||||
): boolean {
|
||||
const file = readConfigFile()
|
||||
const pairing: PairingState = file.pairing ?? { code: null, expiresAt: null, createdAt: null }
|
||||
|
||||
// 检查配对码是否有效
|
||||
if (!pairing.code || !pairing.expiresAt) return false
|
||||
if (Date.now() > pairing.expiresAt) return false
|
||||
|
||||
// 比较(忽略大小写和空格)
|
||||
const input = messageText.trim().toUpperCase()
|
||||
if (input !== pairing.code.toUpperCase()) return false
|
||||
|
||||
// 配对成功:写入 pairedUsers
|
||||
const platformConfig = file[platform] ?? {}
|
||||
const pairedUsers: PairedUser[] = platformConfig.pairedUsers ?? []
|
||||
|
||||
// 避免重复
|
||||
const exists = pairedUsers.some((p) => String(p.userId) === String(senderInfo.userId))
|
||||
if (!exists) {
|
||||
pairedUsers.push({
|
||||
userId: senderInfo.userId,
|
||||
displayName: senderInfo.displayName,
|
||||
pairedAt: Date.now(),
|
||||
})
|
||||
}
|
||||
|
||||
// 更新 config
|
||||
file[platform] = { ...platformConfig, pairedUsers }
|
||||
file.pairing = { code: null, expiresAt: null, createdAt: null } // 一次性使用
|
||||
writeConfigFile(file)
|
||||
|
||||
return true
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user