完善游戏通知文案与屏蔽逻辑

This commit is contained in:
pllx
2026-04-29 15:06:01 +08:00
parent 6ae452c4b9
commit c640a31302
7 changed files with 199 additions and 45 deletions
@@ -35,7 +35,7 @@ class UpdateChatPreferencesRequest extends FormRequest
'blocked_system_senders' => ['nullable', 'array'],
'blocked_system_senders.*' => [
'string',
Rule::in(['钓鱼播报', '猜成语', '猜谜活动', '星海小博士', '百家乐', '跑马', '神秘箱子']),
Rule::in(['钓鱼播报', '猜成语', '猜谜活动', '星海小博士', '百家乐', '跑马', '神秘箱子', '五子棋', '老虎机', '双色球彩票']),
],
'sound_muted' => ['required', 'boolean'],
];
+1 -5
View File
@@ -290,12 +290,8 @@ class AiBaccaratBetJob implements ShouldQueue
*/
private function broadcastPassMessage(User $user, int $roomId, string $reason): void
{
if (empty($reason)) {
$reason = '风大雨大,保本最大,这把我决定观望一下!';
}
$chatState = app(ChatStateService::class);
$content = "🌟 🎲 【百家乐】 <b>{$user->username}</b> 本局选择挂机观望:✨ <br/><span style='color:#666;'>[🤖 策略分析] {$reason}</span>";
$content = "🎲 【百家乐】 {$user->username} 本局选择挂机观望";
$msg = [
'id' => $chatState->nextMessageId($roomId),
+8 -1
View File
@@ -50,12 +50,19 @@ function appendFishingMessage(html) {
return;
}
const blockedSet = window.chatState?.blockedSystemSenders;
const isBlocked = blockedSet instanceof Set && blockedSet.has("钓鱼播报");
const line = document.createElement("div");
line.className = "msg-line";
line.dataset.blockKey = "钓鱼播报";
if (isBlocked) {
line.dataset.blockHidden = "1";
line.style.display = "none";
}
line.innerHTML = html;
container.appendChild(line);
if (shouldAutoScroll()) {
if (!isBlocked && shouldAutoScroll()) {
container.scrollTop = container.scrollHeight;
}
}
+117 -31
View File
@@ -63,6 +63,59 @@ function buildGameLabelChipHtml(label, accentColor) {
return `<span style="display:inline-flex;align-items:center;padding:2px 9px;border-radius:999px;background:${accentColor};color:#fff;font-size:11px;font-weight:700;line-height:1;border:1px solid ${accentColor};">${escapeHtml(label)}</span>`;
}
/**
* 判断当前是否为礼包发放公告。
*/
function isRedPacketAnnouncementMessage(msg) {
const content = String(msg?.content || "");
return String(msg?.from_user || "") === "系统公告"
&& content.includes("发出了一个")
&& content.includes("礼包")
&& content.includes("立即抢包");
}
/**
* 构建礼包发放公告的紧凑卡片,整体比例对齐猜谜活动。
*/
function buildRedPacketAnnouncementHtml(msg, timeStr) {
const rawContent = String(msg?.content || "");
const isExpPacket = rawContent.includes("经验的礼包");
const accentColor = isExpPacket ? "#7c3aed" : "#dc2626";
const typeLabel = isExpPacket ? "经验礼包" : "金币礼包";
const icon = isExpPacket ? "✨" : "🧧";
const buttonMatch = rawContent.match(/<button\b([^>]*)>([\s\S]*?)<\/button>/iu);
const buttonLabel = String(buttonMatch?.[2] || "立即抢包").trim();
const onclickMatch = String(buttonMatch?.[1] || "").match(/\bonclick=(["'])([\s\S]*?)\1/iu);
const buttonOnclick = onclickMatch ? onclickMatch[2] : "";
const textOnlyContent = rawContent
.replace(/<button\b[\s\S]*?<\/button>/giu, "")
.replace(/<\/?b>/giu, "")
.replace(/^🧧\s*/u, "")
.trim();
const summary = escapeHtml(textOnlyContent);
const actionButtonHtml = `<button type="button"${buttonOnclick ? ` onclick="${escapeHtml(buttonOnclick)}"` : ""} style="display:inline-flex;align-items:center;padding:2px 9px;border-radius:999px;background:${accentColor};color:#fff;font-size:11px;font-weight:700;line-height:1;border:1px solid ${accentColor};cursor:pointer;box-shadow:none;vertical-align:middle;">${escapeHtml(buttonLabel)}</button>`;
return `
<div style="display:flex;align-items:center;gap:7px;padding:5px 9px;border-radius:11px;background:linear-gradient(135deg,${isExpPacket ? "#f5f3ff,#faf5ff" : "#fef2f2,#fff7ed"});border:1px solid ${isExpPacket ? "rgba(124,58,237,.16)" : "rgba(220,38,38,.16)"};box-shadow:0 4px 12px rgba(15,23,42,.045);overflow:hidden;">
<div style="width:23px;height:23px;border-radius:7px;background:${accentColor};display:flex;align-items:center;justify-content:center;color:#fff;font-size:13px;box-shadow:0 2px 6px ${isExpPacket ? "rgba(124,58,237,.16)" : "rgba(220,38,38,.16)"};flex-shrink:0;">${icon}</div>
<div style="min-width:0;flex:1;display:flex;align-items:center;gap:7px;flex-wrap:wrap;color:${isExpPacket ? "#5b21b6" : "#b91c1c"};">
<div style="display:flex;align-items:center;gap:6px;flex-wrap:wrap;flex-shrink:0;">
${buildGameLabelChipHtml("礼包红包", accentColor)}
<span style="display:inline-flex;align-items:center;padding:2px 9px;border-radius:999px;background:${accentColor}1A;color:${accentColor};font-size:11px;font-weight:700;line-height:1;border:1px solid ${accentColor}33;">${escapeHtml(typeLabel)}</span>
</div>
<div style="display:flex;align-items:center;gap:5px;flex-wrap:wrap;font-size:12px;line-height:1.25;font-weight:700;min-width:200px;flex:1;">
<span>${summary}</span>
<span class="msg-time" style="font-size:10px;color:#94a3b8;">(${timeStr})</span>
${actionButtonHtml}
</div>
</div>
</div>
`;
}
/**
* 构建统一的猜谜活动标题与题型标签。
*/
@@ -206,40 +259,68 @@ function extractSystemGameCardSummary(content, meta) {
.replace(/^[📦💎☠️]\s*/u, "")
.replace(/^【神秘箱子】/u, "")
.replace(/^开箱播报[:]\s*/u, "")
.replace(/投放了一个神秘箱子/u, "投放")
.replace(/抢到了神秘/u, "抢到")
.replace(/即可开箱!?/u, "开箱")
.trim();
}
if (meta.label === "百家乐") {
if (normalizedContent.includes("开局:")) {
return normalizedContent
.replace(/^[🎲]+\s*/u, "")
.replace(/【百家乐】/u, "")
.replace(/\s+/gu, " ")
.trim();
}
if (/第\s*#?\d+\s*局开奖/u.test(normalizedContent)) {
return normalizedContent
.replace(/^[🎲🎉]+\s*/u, "")
.replace(/^【百家乐】/u, "")
.replace(/^第\s*#?\d+\s*局开奖!?\s*/u, "开奖:")
.replace(/总点\s*(\d+)\s*→\s*/u, "$1点 · ")
.replace(/本局无人获奖。?/u, "无人获奖")
.replace(/\s+/gu, " ")
.trim();
}
return normalizedContent
.replace(/^[🎲🎉]\s*/u, "")
.replace(/^[🎲🎉]+\s*/u, "")
.replace(/^【百家乐】/u, "")
.replace(/^开局:/u, "")
.replace(/^第\s*#?\d+\s*局开奖!?\s*/u, "")
.replace(/^第\s*#?\d+\s*局开始!?\s*/u, "")
.replace(/总点\s*(\d+)\s*→\s*/u, "$1点 · ")
.replace(/本局无人获奖。?/u, "无人获奖")
.replace(/\s+/gu, " ")
.trim();
}
if (meta.label === "赛马") {
if (normalizedContent.startsWith("🐎 开赛:")) {
return normalizedContent.replace(/^[🐎]+\s*/u, "").trim();
}
if (normalizedContent.startsWith("🏇 比赛开始:")) {
return normalizedContent.replace(/^[🏇]+\s*/u, "").trim();
}
if (normalizedContent.startsWith("🏆 冠军:")) {
return normalizedContent.replace(/^[🏆]+\s*/u, "").trim();
}
return normalizedContent
.replace(/^[🐎🏇🏆]\s*/u, "")
.replace(/^[🐎🏇🏆]+\s*/u, "")
.replace(/^【赛马】/u, "")
.replace(/^第\s*#?\d+\s*场开始!?\s*/u, "")
.replace(/^第\s*#?\d+\s*场押注截止!?\s*/u, "")
.replace(/^第\s*#?\d+\s*场结束!?\s*/u, "")
.replace(/^第\s*#?\d+\s*场开始!?\s*/u, "开赛:")
.replace(/^第\s*#?\d+\s*场押注截止!?\s*/u, "比赛开始:")
.replace(/^第\s*#?\d+\s*场结束!?\s*/u, "冠军:")
.replace(/马匹已进入跑道,比赛开始!?/u, "比赛开始")
.trim();
}
if (meta.label === "双色球彩票") {
if (normalizedContent.includes("超级期")) {
return normalizedContent.replace(/^[🎊🎟️]+\s*/u, "").trim();
}
return normalizedContent
.replace(/^[🎟️🎊]+\s*/u, "")
.replace(/^【双色球[^】]*】/u, "")
.replace(/^(\d+\s*期:)/u, "开奖:$1")
.trim();
}
@@ -247,16 +328,16 @@ function extractSystemGameCardSummary(content, meta) {
return normalizedContent
.replace(/^[♟️🏆]+\s*/u, "")
.replace(/^【五子棋】/u, "")
.replace(/^玩家对战结果!/u, "")
.replace(/^棋神降临!/u, "")
.replace(/^AI 大获全胜!/u, "")
.replace(/^玩家对战结果!/u, "对战结果:")
.replace(/^棋神降临!/u, "人机获胜:")
.replace(/^AI 大获全胜!/u, "AI获胜:")
.trim();
}
if (meta.label === "老虎机") {
return normalizedContent
.replace(/^[🎰🎉]+\s*/u, "")
.replace(/^【老虎机大奖】/u, "")
.replace(/^【老虎机大奖】/u, "大奖:")
.trim();
}
@@ -378,18 +459,18 @@ function buildQuizResultHtml(msg, timeStr) {
: `第 #${quizMeta.endedRoundId || quizMeta.roundId || 0} 题已超时结束,正确答案:${answerText}`;
return `
<div style="display:flex;align-items:center;gap:8px;padding:6px 10px;border-radius:12px;background:${accentBackground};border:1px solid ${accentBorder};box-shadow:0 6px 16px rgba(15,23,42,.05);overflow:hidden;">
<div style="width:30px;height:30px;border-radius:10px;background:${iconBackground};display:flex;align-items:center;justify-content:center;color:#fff;font-size:16px;box-shadow:0 5px 12px ${accentBorder};flex-shrink:0;">${icon}</div>
<div style="min-width:0;flex:1;display:flex;align-items:center;gap:8px;flex-wrap:wrap;color:${textColor};">
<div style="display:flex;align-items:center;gap:8px;flex-wrap:wrap;flex-shrink:0;">${buildQuizBadgeHtml(msg, badgeColor)}</div>
<div style="display:flex;align-items:center;gap:6px;flex-wrap:wrap;font-size:13px;line-height:1.35;font-weight:700;min-width:220px;flex:1;">
<div style="display:flex;align-items:center;gap:7px;padding:5px 9px;border-radius:11px;background:${accentBackground};border:1px solid ${accentBorder};box-shadow:0 4px 12px rgba(15,23,42,.045);overflow:hidden;">
<div style="width:23px;height:23px;border-radius:7px;background:${iconBackground};display:flex;align-items:center;justify-content:center;color:#fff;font-size:13px;box-shadow:0 2px 6px ${accentBorder};flex-shrink:0;">${icon}</div>
<div style="min-width:0;flex:1;display:flex;align-items:center;gap:7px;flex-wrap:wrap;color:${textColor};">
<div style="display:flex;align-items:center;gap:7px;flex-wrap:wrap;flex-shrink:0;">${buildQuizBadgeHtml(msg, badgeColor)}</div>
<div style="display:flex;align-items:center;gap:5px;flex-wrap:wrap;font-size:12px;line-height:1.25;font-weight:700;min-width:200px;flex:1;">
<span>${summaryHtml}</span>
<span class="msg-time" style="font-size:11px;color:#94a3b8;">(${timeStr})</span>
<span class="msg-time" style="font-size:10px;color:#94a3b8;">(${timeStr})</span>
</div>
${isAnsweredResult ? `
<div style="display:flex;align-items:center;gap:6px;flex-wrap:wrap;color:${textColor};font-size:11px;flex-shrink:0;margin-left:auto;">
<span style="padding:1px 7px;border-radius:999px;background:#ffffff;box-shadow:inset 0 0 0 1px ${isAnsweredResult ? "rgba(22,163,74,.12)" : "rgba(217,119,6,.12)"};white-space:nowrap;">💰 ${quizMeta.rewardGold} 金币</span>
<span style="padding:1px 7px;border-radius:999px;background:#ffffff;box-shadow:inset 0 0 0 1px ${isAnsweredResult ? "rgba(22,163,74,.12)" : "rgba(217,119,6,.12)"};white-space:nowrap;">⭐ ${quizMeta.rewardExp} 经验</span>
<div style="display:flex;align-items:center;gap:5px;flex-wrap:wrap;color:${textColor};font-size:10px;flex-shrink:0;margin-left:auto;">
<span style="padding:1px 6px;border-radius:999px;background:#ffffff;box-shadow:inset 0 0 0 1px ${isAnsweredResult ? "rgba(22,163,74,.12)" : "rgba(217,119,6,.12)"};white-space:nowrap;">💰 ${quizMeta.rewardGold} 金币</span>
<span style="padding:1px 6px;border-radius:999px;background:#ffffff;box-shadow:inset 0 0 0 1px ${isAnsweredResult ? "rgba(22,163,74,.12)" : "rgba(217,119,6,.12)"};white-space:nowrap;">⭐ ${quizMeta.rewardExp} 经验</span>
</div>
` : ""}
</div>
@@ -613,11 +694,16 @@ export function appendMessage(msg, renderBatch = null) {
timeStrOverride = true;
} else if (SYSTEM_USERS.includes(msg.from_user)) {
if (msg.from_user === "系统公告") {
div.style.cssText =
"background: linear-gradient(135deg, #fef2f2, #fff1f2); border: 2px solid #ef4444; border-radius: 8px; padding: 10px 14px; margin: 6px 0; box-shadow: 0 3px 8px rgba(239,68,68,0.16);";
const parsedContent = parseBracketUsers(msg.content, "#dc2626");
html = `<div style="font-size: 18px; line-height: 1.75; font-weight: 800; color: #dc2626;">${parsedContent} <span style="color: #999; font-size: 14px; font-weight: 500;">(${timeStr})</span></div>`;
timeStrOverride = true;
if (isRedPacketAnnouncementMessage(msg)) {
html = buildRedPacketAnnouncementHtml(msg, timeStr);
timeStrOverride = true;
} else {
div.style.cssText =
"background: linear-gradient(135deg, #fef2f2, #fff1f2); border: 2px solid #ef4444; border-radius: 8px; padding: 10px 14px; margin: 6px 0; box-shadow: 0 3px 8px rgba(239,68,68,0.16);";
const parsedContent = parseBracketUsers(msg.content, "#dc2626");
html = `<div style="font-size: 18px; line-height: 1.75; font-weight: 800; color: #dc2626;">${parsedContent} <span style="color: #999; font-size: 14px; font-weight: 500;">(${timeStr})</span></div>`;
timeStrOverride = true;
}
} else if (msg.from_user === "系统传音") {
const content = msg.content || "";
const isRedPacketClaimNotification = content.includes("抢到了") && content.includes("礼包");
+56 -6
View File
@@ -1,6 +1,6 @@
// 聊天室偏好与每日状态工具,承接从 Blade 内联脚本迁移出的纯数据规整逻辑。
export const BLOCKABLE_SYSTEM_SENDERS = ["钓鱼播报", "猜成语", "星海小博士", "百家乐", "跑马", "神秘箱子"];
export const BLOCKABLE_SYSTEM_SENDERS = ["钓鱼播报", "猜成语", "星海小博士", "百家乐", "跑马", "神秘箱子", "五子棋", "老虎机", "双色球彩票"];
export const BLOCKED_SYSTEM_SENDERS_STORAGE_KEY = "chat_blocked_system_senders";
export const CHAT_SOUND_MUTED_STORAGE_KEY = "chat_sound_muted";
// 白名单、localStorage key 与绑定标记共同保证偏好读取可控、事件只注册一次。
@@ -483,6 +483,7 @@ export function resolveBlockedSystemSenderKey(msg) {
);
const quizType = String(msg?.quiz_type || "");
const quizTypeLabel = String(msg?.quiz_type_label || "");
const isSystemBroadcast = fromUser === "系统传音" || fromUser === "系统";
// 猜谜活动消息独立作为一个通知类型管理,不再复用“星海小博士”的屏蔽规则。
if (
@@ -491,7 +492,7 @@ export function resolveBlockedSystemSenderKey(msg) {
quizType === "idiom" ||
quizTypeLabel.includes("成语") ||
(fromUser === "星海小博士" && (content.includes("猜成语") || content.includes("猜谜活动"))) ||
((fromUser === "系统传音" || fromUser === "系统") && (content.includes("猜成语") || content.includes("猜谜活动")))
(isSystemBroadcast && (content.includes("猜成语") || content.includes("猜谜活动")))
) {
return "猜成语";
}
@@ -509,22 +510,68 @@ export function resolveBlockedSystemSenderKey(msg) {
}
// 兼容旧版自动钓鱼卡购买通知:历史上该消息曾以"系统传音"发送,但正文里带有"钓鱼播报"字样。
if ((fromUser === "系统传音" || fromUser === "系统") && (content.includes("钓鱼播报") || content.includes("自动钓鱼模式"))) {
if (isSystemBroadcast && (content.includes("钓鱼播报") || content.includes("自动钓鱼模式"))) {
return "钓鱼播报";
}
if ((fromUser === "系统传音" || fromUser === "系统") && content.includes("神秘箱子")) {
// 神秘箱子公告已精简为“《普通箱》...暗号...”等格式,不能再只依赖“神秘箱子”字样。
if (
isSystemBroadcast && (
content.includes("神秘箱子") ||
(content.includes("暗号") && content.includes("《")) ||
content.includes("抢到普通箱") ||
content.includes("抢到黑化箱") ||
content.includes("抢到神秘") ||
content.includes("箱子消失")
)
) {
return "神秘箱子";
}
if ((fromUser === "系统传音" || fromUser === "系统") && content.includes("百家乐")) {
// 百家乐通知已缩短为“开局:...”“🎲 9点...”这类格式,这里同步兼容新旧文案。
if (
isSystemBroadcast && (
content.includes("百家乐") ||
(content.includes("开局:") && content.includes("点收割")) ||
(content.startsWith("🎲") && content.includes("点")) ||
(content.includes("快速参与") && content.includes("1:24"))
)
) {
return "百家乐";
}
if ((fromUser === "系统传音" || fromUser === "系统") && (content.includes("赛马") || content.includes("跑马"))) {
// 赛马通知已缩短为“开赛:...”“比赛开始:...”“冠军:...”等格式。
if (
isSystemBroadcast && (
content.includes("赛马") ||
content.includes("跑马") ||
content.startsWith("🐎 开赛:") ||
content.startsWith("🏇 比赛开始:") ||
content.startsWith("🏆 冠军:")
)
) {
return "跑马";
}
if (isSystemBroadcast && (content.includes("【五子棋】") || content.includes("五子棋"))) {
return "五子棋";
}
if (isSystemBroadcast && (content.includes("老虎机") || content.includes("【老虎机大奖】"))) {
return "老虎机";
}
if (
isSystemBroadcast && (
content.includes("双色球") ||
/购买\s+\d+\s*期/u.test(content) ||
/\d+\s*期:\s*🔴/u.test(content) ||
content.includes("超级期")
)
) {
return "双色球彩票";
}
return null;
}
@@ -605,6 +652,9 @@ export function syncBlockedSystemSenderCheckboxes() {
"block-sender-baccarat": "百家乐",
"block-sender-horse-race": "跑马",
"block-sender-mystery-box": "神秘箱子",
"block-sender-gomoku": "五子棋",
"block-sender-slot-machine": "老虎机",
"block-sender-lottery": "双色球彩票",
};
Object.entries(checkboxMap).forEach(([id, sender]) => {
@@ -160,6 +160,21 @@ $welcomeMessages = [
<input type="checkbox" id="block-sender-mystery-box" data-chat-block-sender="神秘箱子">
神秘箱子
</label>
<label
style="display:flex;align-items:center;gap:6px;font-size:12px;color:#1e293b;cursor:pointer;padding:4px 2px;">
<input type="checkbox" id="block-sender-gomoku" data-chat-block-sender="五子棋">
五子棋
</label>
<label
style="display:flex;align-items:center;gap:6px;font-size:12px;color:#1e293b;cursor:pointer;padding:4px 2px;">
<input type="checkbox" id="block-sender-slot-machine" data-chat-block-sender="老虎机">
老虎机
</label>
<label
style="display:flex;align-items:center;gap:6px;font-size:12px;color:#1e293b;cursor:pointer;padding:4px 2px;">
<input type="checkbox" id="block-sender-lottery" data-chat-block-sender="双色球彩票">
双色球彩票
</label>
</div>
</div>
@@ -164,7 +164,7 @@
const CHAT_SOUND_MUTED_KEY = window.ChatRoomTools?.CHAT_SOUND_MUTED_STORAGE_KEY || 'chat_sound_muted';
const BLOCKED_SENDERS_KEY = window.ChatRoomTools?.BLOCKED_SYSTEM_SENDERS_STORAGE_KEY || 'chat_blocked_system_senders';
const BLOCKABLE_SENDERS = window.ChatRoomTools?.BLOCKABLE_SYSTEM_SENDERS || ['钓鱼播报', '猜成语', '星海小博士', '百家乐', '跑马', '神秘箱子'];
const BLOCKABLE_SENDERS = window.ChatRoomTools?.BLOCKABLE_SYSTEM_SENDERS || ['钓鱼播报', '猜成语', '星海小博士', '百家乐', '跑马', '神秘箱子', '五子棋', '老虎机', '双色球彩票'];
const initialChatPreferences = window.ChatRoomTools?.normalizeChatPreferences
? window.ChatRoomTools.normalizeChatPreferences(window.chatContext?.chatPreferences || {}, BLOCKABLE_SENDERS)