// cloudflare-workers-bottle-relay.js
import { kv } from '
@cloudflare/kv';
import { v4 as uuidv4 } from 'uuid';
// 配置参数(需替换为实际值)
const CONFIG = {
cfAccountId: 'YOUR_CF_ACCOUNT_ID',
qqAppId: 'YOUR_QQ_APP_ID',
qqClientSecret: 'YOUR_QQ_CLIENT_SECRET',
qqBotToken: 'YOUR_MIRAI_BOT_TOKEN',
phpApiBaseUrl: 'https://your-php-domain.com/',
sharedSecret: 'YOUR_SHARED_SECRET', // 与PHP端保持一致的密钥
kvNamespace: 'BOTTLE_RELAY_KV' // 已创建的KV命名空间
};
// 初始化KV存储
const kvStore = kv.namespace(CONFIG.kvNamespace);
// 定时刷新Access Token(每30分钟检查一次)
setInterval(async () => {
await refreshToken();
}, 1800000); // 30分钟
// 获取/刷新Access Token
async function refreshToken() {
const now = Date.now() / 1000;
const storedToken = await kvStore.get('qq_access_token');
if (storedToken && storedToken.expire > now + 60) {
return storedToken.token;
}
try {
const response = await fetch('https://bots.qq.com/app/getAppAccessToken', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
appId: CONFIG.qqAppId,
clientSecret: CONFIG.qqClientSecret
})
});
if (!response.ok) throw new Error('Token获取失败');
const data = await response.json();
if (data.code !== 0) throw new Error(data.message);
await kvStore.put('qq_access_token', {
token: data.access_token,
expire: now + data.expires_in - 60 // 提前60秒刷新
});
return data.access_token;
} catch (error) {
console.error('Token刷新错误:', error);
throw error;
}
}
// 处理QQ机器人消息
export async function handleQQMessage(request) {
try {
const token = await refreshToken();
const authHeader = `Bearer ${token}`;
const event = await request.json();
const message = event.messageChain.find(item => item.type === 'Plain')?.text;
const chatId = event.sender.id;
if (!message) {
return new Response('无效消息格式', { status: 400 });
}
// 解析命令
let command, params;
if (/^\/throw\s/.test(message)) {
command = 'throw';
params = message.replace(/^\/throw\s*/, '');
} else if (message === '/pick') {
command = 'pick';
params = '';
} else {
return new Response('未知命令', { status: 400 });
}
// 生成签名
const timestamp = Date.now();
const signData = `content=${encodeURIComponent(params)}×tamp=${timestamp}`;
const signature = crypto.subtle.digest('SHA-256', new TextEncoder().encode(signData + CONFIG.sharedSecret))
.then(buffer => Array.from(new Uint8Array(buffer))
.map(b => b.toString(16).padStart(2, '0')).join('')
);
// 转发请求到PHP API
const phpResponse = await fetch(`${CONFIG.phpApiBaseUrl}${command}.php`, {
method: 'POST',
headers: {
'Authorization': authHeader,
'Content-Type': 'application/x-www-form-urlencoded',
'X-Signature': await signature,
'X-Timestamp': timestamp
},
body: new URLSearchParams({
content: params,
sender_id: chatId
}).toString()
});
if (!phpResponse.ok) {
throw new Error(`PHP API错误: ${phpResponse.status} ${await phpResponse.text()}`);
}
const responseBody = await phpResponse.text();
return new Response(responseBody, { status: 200 });
} catch (error) {
console.error('处理错误:', error);
return new Response('服务器内部错误', { status: 500 });
}
}
// Cloudflare Workers入口函数
addEventListener('fetch', event => {
event.respondWith(handleQQMessage(event.request));
});