/**
* 群聊红包/转账功能模块
* 支持拼手气红包、指定成员红包、群聊转账
*/
import { getSettings } from './config.js';
import { requestSave } from './save-manager.js';
import { showToast } from './toast.js';
import { escapeHtml, sleep } from './utils.js';
import { refreshChatList, getUserAvatarHTML } from './ui.js';
import { deductFromWallet, addToWallet, getWalletBalance, generateRedPacketId } from './red-packet.js';
import { generateTransferId } from './transfer.js';
import { getCurrentGroupIndex, enforceGroupChatMemberLimit, appendGroupMessage, showGroupTypingIndicator, hideGroupTypingIndicator } from './group-chat.js';
import { buildSystemPrompt } from './ai.js';
// ============ 状态变量 ============
// 群红包状态
let groupRedPacketType = 'random'; // 'random' | 'designated'
let groupRedPacketAmount = '';
let groupRedPacketCount = '';
let groupRedPacketMessage = '恭喜发财,大吉大利';
let groupRedPacketSelectedMembers = []; // 指定成员红包的目标成员ID列表
// 群转账状态
let groupTransferAmount = '';
let groupTransferDescription = '';
let groupTransferTargetMemberId = null;
// 待领取的群红包
let pendingGroupRedPacket = null;
let pendingGroupRedPacketIndex = -1;
// ============ 工具函数 ============
function getTimeStr() {
const now = new Date();
return `${now.getFullYear()}-${(now.getMonth()+1).toString().padStart(2,'0')}-${now.getDate().toString().padStart(2,'0')} ${now.getHours().toString().padStart(2,'0')}:${now.getMinutes().toString().padStart(2,'0')}`;
}
/**
* 随机分配红包金额(拼手气)
* @param {number} totalAmount 总金额
* @param {number} count 红包个数
* @returns {number[]} 每个红包的金额数组
*/
function distributeRandomAmounts(totalAmount, count) {
if (count <= 0 || totalAmount <= 0) return [];
if (count === 1) return [totalAmount];
const amounts = [];
let remaining = Math.round(totalAmount * 100); // 转为分,避免浮点数精度问题
const minAmount = 1; // 最小1分
for (let i = 0; i < count - 1; i++) {
const maxForThis = remaining - (count - i - 1) * minAmount;
if (maxForThis <= minAmount) {
amounts.push(minAmount);
remaining -= minAmount;
continue;
}
// 20% 概率只给 0.01 元(1分)
if (Math.random() < 0.2) {
amounts.push(minAmount);
remaining -= minAmount;
continue;
}
// 正常随机分配(使用二倍均值法变体)
const avgRemaining = remaining / (count - i);
const maxRandom = Math.min(maxForThis, Math.floor(avgRemaining * 2));
const randomAmount = Math.max(minAmount, Math.floor(Math.random() * maxRandom));
amounts.push(randomAmount);
remaining -= randomAmount;
}
// 最后一个红包拿走剩余金额
amounts.push(remaining);
// 打乱顺序
for (let i = amounts.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[amounts[i], amounts[j]] = [amounts[j], amounts[i]];
}
// 转回元
return amounts.map(a => a / 100);
}
// ============ 群红包类型选择页面 ============
/**
* 显示群红包类型选择页面
*/
export function showGroupRedPacketTypePage() {
const page = document.getElementById('wechat-group-rp-type-page');
if (!page) {
createGroupRedPacketPages();
}
// 重置状态
groupRedPacketType = 'random';
groupRedPacketAmount = '';
groupRedPacketCount = '';
groupRedPacketMessage = '恭喜发财,大吉大利';
groupRedPacketSelectedMembers = [];
document.getElementById('wechat-group-rp-type-page')?.classList.remove('hidden');
}
/**
* 隐藏群红包类型选择页面
*/
export function hideGroupRedPacketTypePage() {
document.getElementById('wechat-group-rp-type-page')?.classList.add('hidden');
}
// ============ 拼手气红包页面 ============
/**
* 显示拼手气红包页面
*/
export function showGroupRandomRedPacketPage() {
hideGroupRedPacketTypePage();
groupRedPacketType = 'random';
groupRedPacketAmount = '';
groupRedPacketCount = '';
const page = document.getElementById('wechat-group-random-rp-page');
if (page) {
page.classList.remove('hidden');
updateGroupRandomRedPacketDisplay();
}
}
/**
* 隐藏拼手气红包页面
*/
export function hideGroupRandomRedPacketPage() {
document.getElementById('wechat-group-random-rp-page')?.classList.add('hidden');
document.getElementById('wechat-group-rp-keyboard')?.classList.add('hidden');
}
/**
* 更新拼手气红包显示
*/
function updateGroupRandomRedPacketDisplay() {
const amountEl = document.getElementById('wechat-group-rp-amount-value');
const countEl = document.getElementById('wechat-group-rp-count-value');
const totalEl = document.getElementById('wechat-group-rp-total-display');
if (amountEl) {
amountEl.textContent = groupRedPacketAmount || '0.00';
}
if (countEl) {
countEl.textContent = groupRedPacketCount || '0';
}
if (totalEl) {
const amount = parseFloat(groupRedPacketAmount) || 0;
totalEl.textContent = '¥' + amount.toFixed(2);
}
}
// ============ 指定成员红包页面 ============
/**
* 显示指定成员红包页面
*/
export function showGroupDesignatedRedPacketPage() {
hideGroupRedPacketTypePage();
groupRedPacketType = 'designated';
groupRedPacketAmount = '';
groupRedPacketSelectedMembers = [];
const settings = getSettings();
const groupIndex = getCurrentGroupIndex();
const groupChat = settings.groupChats?.[groupIndex];
if (!groupChat) return;
const { memberIds } = enforceGroupChatMemberLimit(groupChat);
const members = memberIds.map(id => settings.contacts.find(c => c.id === id)).filter(Boolean);
// 渲染成员列表
const listContainer = document.getElementById('wechat-group-designated-member-list');
if (listContainer) {
listContainer.innerHTML = members.map(member => {
const firstChar = member.name?.charAt(0) || '?';
const avatarHtml = member.avatar
? `
`
: firstChar;
return `
${avatarHtml}
${escapeHtml(member.name)}
`;
}).join('');
// 绑定点击事件
listContainer.querySelectorAll('.wechat-group-designated-member-item').forEach(item => {
item.addEventListener('click', (e) => {
const checkbox = item.querySelector('input[type="checkbox"]');
if (e.target !== checkbox) {
checkbox.checked = !checkbox.checked;
}
updateGroupDesignatedSelection();
});
});
}
document.getElementById('wechat-group-designated-rp-page')?.classList.remove('hidden');
updateGroupDesignatedRedPacketDisplay();
}
/**
* 更新指定成员选择
*/
function updateGroupDesignatedSelection() {
const checkboxes = document.querySelectorAll('#wechat-group-designated-member-list input[type="checkbox"]:checked');
groupRedPacketSelectedMembers = Array.from(checkboxes).map(cb => cb.dataset.memberId);
const countEl = document.getElementById('wechat-group-designated-count');
if (countEl) {
countEl.textContent = groupRedPacketSelectedMembers.length;
}
}
/**
* 隐藏指定成员红包页面
*/
export function hideGroupDesignatedRedPacketPage() {
document.getElementById('wechat-group-designated-rp-page')?.classList.add('hidden');
document.getElementById('wechat-group-designated-keyboard')?.classList.add('hidden');
}
/**
* 更新指定成员红包显示
*/
function updateGroupDesignatedRedPacketDisplay() {
const amountEl = document.getElementById('wechat-group-designated-amount-value');
const countEl = document.getElementById('wechat-group-designated-count');
const totalEl = document.getElementById('wechat-group-designated-total-display');
const amount = parseFloat(groupRedPacketAmount) || 0;
const count = groupRedPacketSelectedMembers.length;
if (amountEl) {
amountEl.textContent = groupRedPacketAmount || '0.00';
}
if (countEl) {
countEl.textContent = count;
}
if (totalEl) {
totalEl.textContent = '¥' + (amount * count).toFixed(2);
}
}
// ============ 群转账成员选择页面 ============
/**
* 显示群转账成员选择页面
*/
export function showGroupTransferSelectPage() {
groupTransferAmount = '';
groupTransferDescription = '';
groupTransferTargetMemberId = null;
const settings = getSettings();
const groupIndex = getCurrentGroupIndex();
const groupChat = settings.groupChats?.[groupIndex];
if (!groupChat) return;
const { memberIds } = enforceGroupChatMemberLimit(groupChat);
const members = memberIds.map(id => settings.contacts.find(c => c.id === id)).filter(Boolean);
// 渲染成员列表
const listContainer = document.getElementById('wechat-group-transfer-member-list');
if (listContainer) {
listContainer.innerHTML = members.map(member => {
const firstChar = member.name?.charAt(0) || '?';
const avatarHtml = member.avatar
? `
`
: firstChar;
return `
${avatarHtml}
${escapeHtml(member.name)}
`;
}).join('');
// 绑定点击事件
listContainer.querySelectorAll('.wechat-group-transfer-member-item').forEach(item => {
item.addEventListener('click', () => {
groupTransferTargetMemberId = item.dataset.memberId;
hideGroupTransferSelectPage();
showGroupTransferAmountPage();
});
});
}
document.getElementById('wechat-group-transfer-select-page')?.classList.remove('hidden');
}
/**
* 隐藏群转账成员选择页面
*/
export function hideGroupTransferSelectPage() {
document.getElementById('wechat-group-transfer-select-page')?.classList.add('hidden');
}
// ============ 群转账金额输入页面 ============
/**
* 显示群转账金额输入页面
*/
export function showGroupTransferAmountPage() {
const settings = getSettings();
const targetMember = settings.contacts?.find(c => c.id === groupTransferTargetMemberId);
if (!targetMember) {
showToast('请先选择转账对象', 'info');
return;
}
// 更新页面标题显示目标成员
const titleEl = document.getElementById('wechat-group-transfer-target-name');
if (titleEl) {
titleEl.textContent = `向 ${targetMember.name} 转账`;
}
groupTransferAmount = '';
groupTransferDescription = '';
document.getElementById('wechat-group-transfer-amount-page')?.classList.remove('hidden');
updateGroupTransferAmountDisplay();
}
/**
* 隐藏群转账金额输入页面
*/
export function hideGroupTransferAmountPage() {
document.getElementById('wechat-group-transfer-amount-page')?.classList.add('hidden');
document.getElementById('wechat-group-transfer-keyboard')?.classList.add('hidden');
}
/**
* 更新群转账金额显示
*/
function updateGroupTransferAmountDisplay() {
const amountEl = document.getElementById('wechat-group-transfer-amount-value');
const displayEl = document.getElementById('wechat-group-transfer-amount-display');
const amount = parseFloat(groupTransferAmount) || 0;
if (amountEl) {
amountEl.textContent = groupTransferAmount || '0.00';
}
if (displayEl) {
displayEl.textContent = '¥' + amount.toFixed(2);
}
}
// ============ 键盘处理 ============
let currentKeyboardTarget = null; // 'random-amount' | 'random-count' | 'designated-amount' | 'transfer-amount'
/**
* 显示数字键盘
*/
export function showGroupKeyboard(target) {
currentKeyboardTarget = target;
let keyboardId;
if (target === 'random-amount' || target === 'random-count') {
keyboardId = 'wechat-group-rp-keyboard';
} else if (target === 'designated-amount') {
keyboardId = 'wechat-group-designated-keyboard';
} else if (target === 'transfer-amount') {
keyboardId = 'wechat-group-transfer-keyboard';
}
const keyboard = document.getElementById(keyboardId);
if (keyboard) {
keyboard.classList.remove('hidden');
}
}
/**
* 隐藏数字键盘
*/
export function hideGroupKeyboard() {
document.getElementById('wechat-group-rp-keyboard')?.classList.add('hidden');
document.getElementById('wechat-group-designated-keyboard')?.classList.add('hidden');
document.getElementById('wechat-group-transfer-keyboard')?.classList.add('hidden');
currentKeyboardTarget = null;
}
/**
* 处理键盘输入
*/
export function handleGroupKeyboardInput(key) {
if (!currentKeyboardTarget) return;
let currentValue;
let isCount = currentKeyboardTarget === 'random-count';
if (currentKeyboardTarget === 'random-amount') {
currentValue = groupRedPacketAmount;
} else if (currentKeyboardTarget === 'random-count') {
currentValue = groupRedPacketCount;
} else if (currentKeyboardTarget === 'designated-amount') {
currentValue = groupRedPacketAmount;
} else if (currentKeyboardTarget === 'transfer-amount') {
currentValue = groupTransferAmount;
}
if (key === 'backspace') {
currentValue = currentValue.slice(0, -1);
} else if (key === 'confirm') {
hideGroupKeyboard();
return;
} else if (key === '.') {
if (isCount) return; // 红包个数不允许小数点
if (!currentValue.includes('.') && currentValue.length > 0) {
currentValue += '.';
}
} else {
if (isCount) {
// 红包个数:整数,最多2位
if (currentValue.length < 2) {
currentValue += key;
}
} else {
// 金额
const dotIndex = currentValue.indexOf('.');
if (dotIndex !== -1) {
if (currentValue.length - dotIndex <= 2) {
currentValue += key;
}
} else {
if (currentValue.length < 6) {
currentValue += key;
}
}
}
}
// 更新状态
if (currentKeyboardTarget === 'random-amount') {
groupRedPacketAmount = currentValue;
updateGroupRandomRedPacketDisplay();
} else if (currentKeyboardTarget === 'random-count') {
groupRedPacketCount = currentValue;
updateGroupRandomRedPacketDisplay();
} else if (currentKeyboardTarget === 'designated-amount') {
groupRedPacketAmount = currentValue;
updateGroupDesignatedRedPacketDisplay();
} else if (currentKeyboardTarget === 'transfer-amount') {
groupTransferAmount = currentValue;
updateGroupTransferAmountDisplay();
}
}
// ============ 密码验证 ============
let pendingGroupAction = null; // { type: 'random-rp' | 'designated-rp' | 'transfer', ... }
/**
* 显示群聊密码输入弹窗
*/
export function showGroupPasswordModal(actionType, extraData = {}) {
pendingGroupAction = { type: actionType, ...extraData };
const modal = document.getElementById('wechat-group-password-modal');
if (modal) {
modal.classList.remove('hidden');
// 清空密码
const dots = modal.querySelectorAll('.wechat-password-dot');
dots.forEach(dot => dot.classList.remove('filled'));
modal.dataset.password = '';
}
}
/**
* 隐藏群聊密码输入弹窗
*/
export function hideGroupPasswordModal() {
const modal = document.getElementById('wechat-group-password-modal');
if (modal) {
modal.classList.add('hidden');
}
pendingGroupAction = null;
}
/**
* 处理密码输入
*/
export function handleGroupPasswordInput(key) {
const modal = document.getElementById('wechat-group-password-modal');
if (!modal) return;
let password = modal.dataset.password || '';
if (key === 'backspace') {
password = password.slice(0, -1);
} else if (password.length < 6) {
password += key;
}
modal.dataset.password = password;
// 更新密码点显示
const dots = modal.querySelectorAll('.wechat-password-dot');
dots.forEach((dot, index) => {
dot.classList.toggle('filled', index < password.length);
});
// 6位密码输入完成,验证
if (password.length === 6) {
const settings = getSettings();
const correctPassword = settings.paymentPassword || '666666';
if (password === correctPassword) {
hideGroupPasswordModal();
executeGroupAction();
} else {
showToast('密码错误', 'info');
modal.dataset.password = '';
dots.forEach(dot => dot.classList.remove('filled'));
}
}
}
/**
* 执行群聊操作
*/
async function executeGroupAction() {
if (!pendingGroupAction) return;
const actionType = pendingGroupAction.type;
if (actionType === 'random-rp') {
await sendGroupRandomRedPacket();
} else if (actionType === 'designated-rp') {
await sendGroupDesignatedRedPacket();
} else if (actionType === 'transfer') {
await sendGroupTransfer();
}
pendingGroupAction = null;
}
// ============ 发送群红包 ============
/**
* 提交拼手气红包(显示密码输入)
*/
export function submitGroupRandomRedPacket() {
const amount = parseFloat(groupRedPacketAmount) || 0;
const count = parseInt(groupRedPacketCount) || 0;
if (amount <= 0) {
showToast('请输入红包金额', 'info');
return;
}
if (count <= 0) {
showToast('请输入红包个数', 'info');
return;
}
if (amount > 200) {
showToast('单个红包最多200元', 'info');
return;
}
if (amount > getWalletBalance()) {
showToast('余额不足', 'info');
return;
}
// 获取祝福语
const messageInput = document.getElementById('wechat-group-rp-message');
if (messageInput && messageInput.value.trim()) {
groupRedPacketMessage = messageInput.value.trim();
}
showGroupPasswordModal('random-rp');
}
/**
* 发送拼手气红包
*/
async function sendGroupRandomRedPacket() {
const amount = parseFloat(groupRedPacketAmount) || 0;
const count = parseInt(groupRedPacketCount) || 0;
// 扣款
const result = deductFromWallet(amount);
if (!result.success) {
showToast(result.message, 'info');
return;
}
hideGroupRandomRedPacketPage();
const settings = getSettings();
const groupIndex = getCurrentGroupIndex();
const groupChat = settings.groupChats?.[groupIndex];
if (!groupChat) return;
const { memberIds } = enforceGroupChatMemberLimit(groupChat);
const members = memberIds.map(id => settings.contacts.find(c => c.id === id)).filter(Boolean);
// 分配金额
const distributedAmounts = distributeRandomAmounts(amount, count);
// 创建红包信息
const rpInfo = {
id: generateRedPacketId(),
type: 'random',
totalAmount: amount,
count: count,
message: groupRedPacketMessage,
senderName: settings.userName || 'User',
distributedAmounts: distributedAmounts,
claimedBy: [], // { memberId, memberName, amount, claimedAt }
status: 'pending',
expireAt: Date.now() + 24 * 60 * 60 * 1000
};
// 保存到聊天记录
if (!groupChat.chatHistory) groupChat.chatHistory = [];
groupChat.chatHistory.push({
role: 'user',
content: `[群红包] ${groupRedPacketMessage}`,
time: getTimeStr(),
timestamp: Date.now(),
isGroupRedPacket: true,
groupRedPacketInfo: rpInfo
});
// 显示红包消息
appendGroupRedPacketMessage('user', rpInfo);
requestSave();
refreshChatList();
// AI 领取红包(随机延迟)
await processAIClaimGroupRedPacket(rpInfo, groupChat, members);
}
/**
* 提交指定成员红包(显示密码输入)
*/
export function submitGroupDesignatedRedPacket() {
const amount = parseFloat(groupRedPacketAmount) || 0;
const count = groupRedPacketSelectedMembers.length;
if (amount <= 0) {
showToast('请输入红包金额', 'info');
return;
}
if (count <= 0) {
showToast('请选择接收成员', 'info');
return;
}
if (amount > 200) {
showToast('单个红包最多200元', 'info');
return;
}
const totalAmount = amount * count;
if (totalAmount > getWalletBalance()) {
showToast('余额不足', 'info');
return;
}
// 获取祝福语
const messageInput = document.getElementById('wechat-group-designated-message');
if (messageInput && messageInput.value.trim()) {
groupRedPacketMessage = messageInput.value.trim();
}
showGroupPasswordModal('designated-rp');
}
/**
* 发送指定成员红包
*/
async function sendGroupDesignatedRedPacket() {
const amount = parseFloat(groupRedPacketAmount) || 0;
const count = groupRedPacketSelectedMembers.length;
const totalAmount = amount * count;
// 扣款
const settings = getSettings();
const current = getWalletBalance();
if (totalAmount > current) {
showToast('余额不足', 'info');
return;
}
settings.walletAmount = (current - totalAmount).toFixed(2);
requestSave();
hideGroupDesignatedRedPacketPage();
const groupIndex = getCurrentGroupIndex();
const groupChat = settings.groupChats?.[groupIndex];
if (!groupChat) return;
const { memberIds } = enforceGroupChatMemberLimit(groupChat);
const members = memberIds.map(id => settings.contacts.find(c => c.id === id)).filter(Boolean);
// 获取指定成员名称
const targetMembers = groupRedPacketSelectedMembers.map(id => {
const member = settings.contacts.find(c => c.id === id);
return member?.name || '未知';
});
// 创建红包信息
const rpInfo = {
id: generateRedPacketId(),
type: 'designated',
totalAmount: totalAmount,
amountPerPerson: amount,
count: count,
message: groupRedPacketMessage,
senderName: settings.userName || 'User',
targetMemberIds: [...groupRedPacketSelectedMembers],
targetMemberNames: targetMembers,
claimedBy: [],
status: 'pending',
expireAt: Date.now() + 24 * 60 * 60 * 1000
};
// 保存到聊天记录
if (!groupChat.chatHistory) groupChat.chatHistory = [];
groupChat.chatHistory.push({
role: 'user',
content: `[专属红包] 给${targetMembers.join('、')}的红包`,
time: getTimeStr(),
timestamp: Date.now(),
isGroupRedPacket: true,
groupRedPacketInfo: rpInfo
});
// 显示红包消息
appendGroupRedPacketMessage('user', rpInfo);
requestSave();
refreshChatList();
// AI 领取红包
await processAIClaimGroupRedPacket(rpInfo, groupChat, members);
}
// ============ 发送群转账 ============
/**
* 提交群转账(显示密码输入)
*/
export function submitGroupTransfer() {
const amount = parseFloat(groupTransferAmount) || 0;
if (amount <= 0) {
showToast('请输入转账金额', 'info');
return;
}
if (amount > getWalletBalance()) {
showToast('余额不足', 'info');
return;
}
// 获取转账说明
const descInput = document.getElementById('wechat-group-transfer-description');
if (descInput && descInput.value.trim()) {
groupTransferDescription = descInput.value.trim();
}
showGroupPasswordModal('transfer');
}
/**
* 发送群转账
*/
async function sendGroupTransfer() {
const amount = parseFloat(groupTransferAmount) || 0;
// 扣款
const settings = getSettings();
const current = getWalletBalance();
if (amount > current) {
showToast('余额不足', 'info');
return;
}
settings.walletAmount = (current - amount).toFixed(2);
requestSave();
hideGroupTransferAmountPage();
const groupIndex = getCurrentGroupIndex();
const groupChat = settings.groupChats?.[groupIndex];
if (!groupChat) return;
const targetMember = settings.contacts?.find(c => c.id === groupTransferTargetMemberId);
if (!targetMember) return;
// 创建转账信息
const tfInfo = {
id: generateTransferId(),
amount: amount,
description: groupTransferDescription || '',
senderName: settings.userName || 'User',
targetMemberId: groupTransferTargetMemberId,
targetMemberName: targetMember.name,
status: 'pending',
receivedAt: null,
refundedAt: null,
expireAt: Date.now() + 24 * 60 * 60 * 1000
};
// 保存到聊天记录
if (!groupChat.chatHistory) groupChat.chatHistory = [];
groupChat.chatHistory.push({
role: 'user',
content: `[转账] 向${targetMember.name}发起了一笔转账`,
time: getTimeStr(),
timestamp: Date.now(),
isGroupTransfer: true,
groupTransferInfo: tfInfo
});
// 显示转账消息
appendGroupTransferMessage('user', tfInfo);
requestSave();
refreshChatList();
// AI 收款
await processAIReceiveGroupTransfer(tfInfo, groupChat, targetMember);
}
// ============ AI 领取红包 ============
/**
* AI 领取群红包
*/
async function processAIClaimGroupRedPacket(rpInfo, groupChat, members) {
// 随机延迟 2-5 秒
const claimDelay = 2000 + Math.random() * 3000;
await sleep(claimDelay);
const settings = getSettings();
const timeStr = getTimeStr();
if (rpInfo.type === 'random') {
// 拼手气红包:按顺序领取
const availableMembers = members.filter(m => !rpInfo.claimedBy.some(c => c.memberId === m.id));
const claimCount = Math.min(availableMembers.length, rpInfo.distributedAmounts.length - rpInfo.claimedBy.length);
for (let i = 0; i < claimCount; i++) {
const member = availableMembers[i];
const amountIndex = rpInfo.claimedBy.length;
const claimAmount = rpInfo.distributedAmounts[amountIndex];
rpInfo.claimedBy.push({
memberId: member.id,
memberName: member.name,
amount: claimAmount,
claimedAt: Date.now()
});
// 更新界面
updateGroupRedPacketBubbleStatus(rpInfo.id);
// 显示领取提示
appendGroupRedPacketClaimNotice(member.name, settings.userName || 'User');
// AI 感谢消息
await sleep(800 + Math.random() * 500);
showGroupTypingIndicator(member.name, member.id);
await sleep(600 + Math.random() * 400);
hideGroupTypingIndicator();
try {
const thankMsg = await generateAIThankMessage(member, rpInfo, claimAmount);
if (thankMsg) {
groupChat.chatHistory.push({
role: 'assistant',
content: thankMsg,
characterName: member.name,
characterId: member.id,
time: timeStr,
timestamp: Date.now()
});
appendGroupMessage('assistant', thankMsg, member.name, member.id);
}
} catch (e) {
console.error('[可乐] AI感谢红包失败:', e);
}
if (i < claimCount - 1) {
await sleep(1000 + Math.random() * 1000);
}
}
// 检查是否全部领完
if (rpInfo.claimedBy.length >= rpInfo.count) {
rpInfo.status = 'claimed';
}
} else if (rpInfo.type === 'designated') {
// 指定成员红包:只有指定成员可以领取
for (const memberId of rpInfo.targetMemberIds) {
const member = members.find(m => m.id === memberId);
if (!member) continue;
if (rpInfo.claimedBy.some(c => c.memberId === memberId)) continue;
rpInfo.claimedBy.push({
memberId: member.id,
memberName: member.name,
amount: rpInfo.amountPerPerson,
claimedAt: Date.now()
});
// 更新界面
updateGroupRedPacketBubbleStatus(rpInfo.id);
// 显示领取提示
appendGroupRedPacketClaimNotice(member.name, settings.userName || 'User');
// AI 感谢消息
await sleep(800 + Math.random() * 500);
showGroupTypingIndicator(member.name, member.id);
await sleep(600 + Math.random() * 400);
hideGroupTypingIndicator();
try {
const thankMsg = await generateAIThankMessage(member, rpInfo, rpInfo.amountPerPerson);
if (thankMsg) {
groupChat.chatHistory.push({
role: 'assistant',
content: thankMsg,
characterName: member.name,
characterId: member.id,
time: timeStr,
timestamp: Date.now()
});
appendGroupMessage('assistant', thankMsg, member.name, member.id);
}
} catch (e) {
console.error('[可乐] AI感谢红包失败:', e);
}
await sleep(1000 + Math.random() * 1000);
}
// 检查是否全部领完
if (rpInfo.claimedBy.length >= rpInfo.count) {
rpInfo.status = 'claimed';
}
}
requestSave();
refreshChatList();
}
/**
* AI 收取群转账
*/
async function processAIReceiveGroupTransfer(tfInfo, groupChat, targetMember) {
// 随机延迟 2-5 秒
const receiveDelay = 2000 + Math.random() * 3000;
await sleep(receiveDelay);
const settings = getSettings();
const timeStr = getTimeStr();
// 更新转账状态
tfInfo.status = 'received';
tfInfo.receivedAt = Date.now();
// 更新界面
updateGroupTransferBubbleStatus(tfInfo.id, 'received');
// AI 感谢消息
await sleep(500);
showGroupTypingIndicator(targetMember.name, targetMember.id);
await sleep(600 + Math.random() * 400);
hideGroupTypingIndicator();
try {
const thankMsg = await generateAITransferThankMessage(targetMember, tfInfo);
if (thankMsg) {
groupChat.chatHistory.push({
role: 'assistant',
content: thankMsg,
characterName: targetMember.name,
characterId: targetMember.id,
time: timeStr,
timestamp: Date.now()
});
appendGroupMessage('assistant', thankMsg, targetMember.name, targetMember.id);
}
} catch (e) {
console.error('[可乐] AI感谢转账失败:', e);
}
requestSave();
refreshChatList();
}
// ============ AI 消息生成 ============
/**
* 生成 AI 红包感谢消息
*/
async function generateAIThankMessage(member, rpInfo, claimAmount) {
if (!member.useCustomApi || !member.customApiUrl || !member.customModel) {
// 没有配置独立API,返回简单消息
return `谢谢红包!抢到了${claimAmount.toFixed(2)}元~`;
}
try {
const systemPrompt = buildSystemPrompt(member, { allowStickers: false, allowMusicShare: false, allowCallRequests: false });
const userPrompt = `用户给群里发了一个${rpInfo.totalAmount}元的红包,祝福语是"${rpInfo.message}"。你抢到了${claimAmount.toFixed(2)}元,请自然地表示感谢,不要使用任何特殊格式标签。回复要简短自然(10字以内)。`;
const chatUrl = member.customApiUrl.replace(/\/+$/, '') + '/chat/completions';
const headers = { 'Content-Type': 'application/json' };
if (member.customApiKey) {
headers['Authorization'] = `Bearer ${member.customApiKey}`;
}
const response = await fetch(chatUrl, {
method: 'POST',
headers,
body: JSON.stringify({
model: member.customModel,
messages: [
{ role: 'system', content: systemPrompt },
{ role: 'user', content: userPrompt }
],
temperature: 1,
max_tokens: 256
})
});
if (!response.ok) {
throw new Error('API请求失败');
}
const data = await response.json();
let reply = data.choices?.[0]?.message?.content || '';
// 取第一条消息
reply = reply.split('|||')[0].trim();
// 移除可能的格式标签
reply = reply.replace(/^\[.*?\]\s*/, '').trim();
return reply || `谢谢红包!${claimAmount.toFixed(2)}元~`;
} catch (e) {
console.error('[可乐] AI红包感谢消息生成失败:', e);
return `谢谢红包!抢到了${claimAmount.toFixed(2)}元~`;
}
}
/**
* 生成 AI 转账感谢消息
*/
async function generateAITransferThankMessage(member, tfInfo) {
if (!member.useCustomApi || !member.customApiUrl || !member.customModel) {
return `收到转账${tfInfo.amount.toFixed(2)}元,谢谢~`;
}
try {
const systemPrompt = buildSystemPrompt(member, { allowStickers: false, allowMusicShare: false, allowCallRequests: false });
const userPrompt = tfInfo.description
? `用户给你转账了${tfInfo.amount}元,备注是"${tfInfo.description}",请自然地表示感谢,不要使用任何特殊格式标签。回复要简短自然(10字以内)。`
: `用户给你转账了${tfInfo.amount}元,请自然地表示感谢,不要使用任何特殊格式标签。回复要简短自然(10字以内)。`;
const chatUrl = member.customApiUrl.replace(/\/+$/, '') + '/chat/completions';
const headers = { 'Content-Type': 'application/json' };
if (member.customApiKey) {
headers['Authorization'] = `Bearer ${member.customApiKey}`;
}
const response = await fetch(chatUrl, {
method: 'POST',
headers,
body: JSON.stringify({
model: member.customModel,
messages: [
{ role: 'system', content: systemPrompt },
{ role: 'user', content: userPrompt }
],
temperature: 1,
max_tokens: 256
})
});
if (!response.ok) {
throw new Error('API请求失败');
}
const data = await response.json();
let reply = data.choices?.[0]?.message?.content || '';
reply = reply.split('|||')[0].trim();
reply = reply.replace(/^\[.*?\]\s*/, '').trim();
return reply || `收到啦,谢谢~`;
} catch (e) {
console.error('[可乐] AI转账感谢消息生成失败:', e);
return `收到转账${tfInfo.amount.toFixed(2)}元,谢谢~`;
}
}
// ============ UI 渲染 ============
/**
* 追加群红包消息到界面
*/
export function appendGroupRedPacketMessage(role, rpInfo) {
const messagesContainer = document.getElementById('wechat-chat-messages');
if (!messagesContainer) return;
const messageDiv = document.createElement('div');
messageDiv.className = `wechat-message ${role === 'user' ? 'self' : ''}`;
const isDesignated = rpInfo.type === 'designated';
const isClaimed = rpInfo.status === 'claimed' || (rpInfo.claimedBy && rpInfo.claimedBy.length >= rpInfo.count);
const statusClass = isClaimed ? 'claimed' : '';
// 指定成员红包显示特殊样式
const designatedLabel = isDesignated ? `给${rpInfo.targetMemberNames?.join('、') || '指定成员'}的红包
` : '';
const bubbleHTML = `
${escapeHtml(rpInfo.message || '恭喜发财,大吉大利')}
${designatedLabel}
${isClaimed ? '已领完' : ''}
`;
if (role === 'user') {
messageDiv.innerHTML = `
${getUserAvatarHTML()}
${bubbleHTML}
`;
} else {
const settings = getSettings();
const contact = settings.contacts?.find(c => c.name === rpInfo.senderName);
const firstChar = rpInfo.senderName?.charAt(0) || '?';
const avatarContent = contact?.avatar
? `
`
: firstChar;
messageDiv.innerHTML = `
${avatarContent}
${escapeHtml(rpInfo.senderName)}
${bubbleHTML}
`;
}
messagesContainer.appendChild(messageDiv);
messagesContainer.scrollTop = messagesContainer.scrollHeight;
// 绑定点击事件(查看详情)
const bubble = messageDiv.querySelector('.wechat-group-red-packet-bubble');
bubble?.addEventListener('click', () => {
showGroupRedPacketDetail(rpInfo);
});
}
/**
* 追加群转账消息到界面
*/
export function appendGroupTransferMessage(role, tfInfo) {
const messagesContainer = document.getElementById('wechat-chat-messages');
if (!messagesContainer) return;
const messageDiv = document.createElement('div');
messageDiv.className = `wechat-message ${role === 'user' ? 'self' : ''}`;
const statusText = tfInfo.status === 'received' ? '已收款' :
tfInfo.status === 'refunded' ? '已退还' : '待收款';
const statusClass = tfInfo.status || 'pending';
const bubbleHTML = `
¥${tfInfo.amount.toFixed(2)}
向${escapeHtml(tfInfo.targetMemberName)}转账
${escapeHtml(tfInfo.description) || '转账'}
${statusText}
`;
if (role === 'user') {
messageDiv.innerHTML = `
${getUserAvatarHTML()}
${bubbleHTML}
`;
} else {
const settings = getSettings();
const contact = settings.contacts?.find(c => c.name === tfInfo.senderName);
const firstChar = tfInfo.senderName?.charAt(0) || '?';
const avatarContent = contact?.avatar
? `
`
: firstChar;
messageDiv.innerHTML = `
${avatarContent}
${escapeHtml(tfInfo.senderName)}
${bubbleHTML}
`;
}
messagesContainer.appendChild(messageDiv);
messagesContainer.scrollTop = messagesContainer.scrollHeight;
}
/**
* 追加群红包领取提示
*/
function appendGroupRedPacketClaimNotice(claimerName, senderName) {
const messagesContainer = document.getElementById('wechat-chat-messages');
if (!messagesContainer) return;
const noticeDiv = document.createElement('div');
noticeDiv.className = 'wechat-msg-notice';
noticeDiv.innerHTML = `${escapeHtml(claimerName)}领取了${escapeHtml(senderName)}的红包`;
messagesContainer.appendChild(noticeDiv);
messagesContainer.scrollTop = messagesContainer.scrollHeight;
}
/**
* 更新群红包气泡状态
*/
function updateGroupRedPacketBubbleStatus(rpId) {
const bubble = document.querySelector(`.wechat-group-red-packet-bubble[data-rp-id="${rpId}"]`);
if (!bubble) return;
// 从聊天记录中找到红包信息
const settings = getSettings();
const groupIndex = getCurrentGroupIndex();
const groupChat = settings.groupChats?.[groupIndex];
if (!groupChat) return;
const rpMsg = groupChat.chatHistory?.find(m => m.groupRedPacketInfo?.id === rpId);
const rpInfo = rpMsg?.groupRedPacketInfo;
if (!rpInfo) return;
const isClaimed = rpInfo.status === 'claimed' || (rpInfo.claimedBy && rpInfo.claimedBy.length >= rpInfo.count);
if (isClaimed) {
bubble.classList.add('claimed');
const statusEl = bubble.querySelector('.wechat-group-rp-status');
if (statusEl) {
statusEl.textContent = '已领完';
statusEl.classList.remove('hidden');
}
}
}
/**
* 更新群转账气泡状态
*/
function updateGroupTransferBubbleStatus(tfId, status) {
const bubble = document.querySelector(`.wechat-group-transfer-bubble[data-tf-id="${tfId}"]`);
if (!bubble) return;
bubble.classList.remove('pending', 'received', 'refunded');
bubble.classList.add(status);
const statusEl = bubble.querySelector('.wechat-group-tf-status');
if (statusEl) {
statusEl.textContent = status === 'received' ? '已收款' :
status === 'refunded' ? '已退还' : '待收款';
}
}
// ============ 群红包详情页面 ============
/**
* 显示群红包详情
*/
export function showGroupRedPacketDetail(rpInfo) {
const page = document.getElementById('wechat-group-rp-detail-page');
if (!page) return;
const settings = getSettings();
// 更新详情页内容
const senderEl = document.getElementById('wechat-group-rp-detail-sender');
const messageEl = document.getElementById('wechat-group-rp-detail-message');
const totalEl = document.getElementById('wechat-group-rp-detail-total');
const countEl = document.getElementById('wechat-group-rp-detail-count');
const listEl = document.getElementById('wechat-group-rp-detail-list');
if (senderEl) {
senderEl.textContent = `${rpInfo.senderName}的红包`;
}
if (messageEl) {
messageEl.textContent = rpInfo.message || '恭喜发财,大吉大利';
}
if (totalEl) {
totalEl.textContent = '¥' + rpInfo.totalAmount.toFixed(2);
}
if (countEl) {
const claimed = rpInfo.claimedBy?.length || 0;
countEl.textContent = `${claimed}/${rpInfo.count}个红包`;
}
// 渲染领取列表
if (listEl) {
if (rpInfo.claimedBy && rpInfo.claimedBy.length > 0) {
// 找出最佳手气(金额最高的)
let maxAmount = 0;
let maxIndex = -1;
rpInfo.claimedBy.forEach((claim, idx) => {
if (claim.amount > maxAmount) {
maxAmount = claim.amount;
maxIndex = idx;
}
});
listEl.innerHTML = rpInfo.claimedBy.map((claim, idx) => {
const member = settings.contacts?.find(c => c.id === claim.memberId);
const firstChar = claim.memberName?.charAt(0) || '?';
const avatarHtml = member?.avatar
? `
`
: firstChar;
const isBest = rpInfo.type === 'random' && idx === maxIndex && rpInfo.claimedBy.length > 1;
const bestLabel = isBest ? '手气最佳' : '';
const claimTime = new Date(claim.claimedAt);
const timeStr = `${claimTime.getHours().toString().padStart(2, '0')}:${claimTime.getMinutes().toString().padStart(2, '0')}`;
return `
${avatarHtml}
${escapeHtml(claim.memberName)} ${bestLabel}
${timeStr}
${claim.amount.toFixed(2)}元
`;
}).join('');
} else {
listEl.innerHTML = '暂无人领取
';
}
}
page.classList.remove('hidden');
}
/**
* 隐藏群红包详情
*/
export function hideGroupRedPacketDetail() {
document.getElementById('wechat-group-rp-detail-page')?.classList.add('hidden');
}
// ============ 创建页面HTML ============
/**
* 创建群红包/转账相关页面(动态注入)
*/
export function createGroupRedPacketPages() {
const phone = document.getElementById('wechat-phone');
if (!phone) return;
// 检查是否已存在
if (document.getElementById('wechat-group-rp-type-page')) return;
const pagesHTML = `
`;
phone.insertAdjacentHTML('beforeend', pagesHTML);
// 绑定事件
bindGroupRedPacketEvents();
}
/**
* 绑定群红包相关事件
*/
function bindGroupRedPacketEvents() {
// 类型选择页面
document.getElementById('wechat-group-rp-type-back')?.addEventListener('click', hideGroupRedPacketTypePage);
document.getElementById('wechat-group-rp-type-random')?.addEventListener('click', showGroupRandomRedPacketPage);
document.getElementById('wechat-group-rp-type-designated')?.addEventListener('click', showGroupDesignatedRedPacketPage);
// 拼手气红包页面
document.getElementById('wechat-group-random-rp-back')?.addEventListener('click', hideGroupRandomRedPacketPage);
document.getElementById('wechat-group-rp-amount-row')?.addEventListener('click', () => showGroupKeyboard('random-amount'));
document.getElementById('wechat-group-rp-count-row')?.addEventListener('click', () => showGroupKeyboard('random-count'));
document.getElementById('wechat-group-random-rp-submit')?.addEventListener('click', submitGroupRandomRedPacket);
// 拼手气红包键盘
document.querySelectorAll('#wechat-group-rp-keyboard .wechat-rp-keyboard-key').forEach(key => {
key.addEventListener('click', () => {
handleGroupKeyboardInput(key.dataset.key);
});
});
// 指定成员红包页面
document.getElementById('wechat-group-designated-rp-back')?.addEventListener('click', hideGroupDesignatedRedPacketPage);
document.getElementById('wechat-group-designated-amount-row')?.addEventListener('click', () => showGroupKeyboard('designated-amount'));
document.getElementById('wechat-group-designated-rp-submit')?.addEventListener('click', submitGroupDesignatedRedPacket);
// 指定成员红包键盘
document.querySelectorAll('#wechat-group-designated-keyboard .wechat-rp-keyboard-key').forEach(key => {
key.addEventListener('click', () => {
handleGroupKeyboardInput(key.dataset.key);
});
});
// 群转账成员选择页面
document.getElementById('wechat-group-transfer-select-back')?.addEventListener('click', hideGroupTransferSelectPage);
// 群转账金额输入页面
document.getElementById('wechat-group-transfer-amount-back')?.addEventListener('click', hideGroupTransferAmountPage);
document.getElementById('wechat-group-transfer-amount-row')?.addEventListener('click', () => showGroupKeyboard('transfer-amount'));
document.getElementById('wechat-group-transfer-submit')?.addEventListener('click', submitGroupTransfer);
// 群转账键盘
document.querySelectorAll('#wechat-group-transfer-keyboard .wechat-rp-keyboard-key').forEach(key => {
key.addEventListener('click', () => {
handleGroupKeyboardInput(key.dataset.key);
});
});
// 群红包详情页面
document.getElementById('wechat-group-rp-detail-back')?.addEventListener('click', hideGroupRedPacketDetail);
// 密码弹窗
document.getElementById('wechat-group-password-close')?.addEventListener('click', hideGroupPasswordModal);
document.querySelectorAll('#wechat-group-password-modal .wechat-password-key').forEach(key => {
key.addEventListener('click', () => {
const value = key.dataset.key;
if (value) {
handleGroupPasswordInput(value);
}
});
});
}
// ============ 初始化 ============
/**
* 初始化群红包功能
*/
export function initGroupRedPacket() {
createGroupRedPacketPages();
}