重构猜谜活动并统一聊天室答题通知
This commit is contained in:
@@ -2,7 +2,13 @@
|
||||
// 从 Blade 内联脚本 scripts.blade.php 迁移至 Vite 模块。
|
||||
|
||||
import { escapeHtml, normalizeSafeChatUrl } from "./html.js";
|
||||
import { attachIdiomAnswerButton, removeIdiomAnswerButtons } from "./idiom-quiz.js";
|
||||
import {
|
||||
attachIdiomAnswerButton,
|
||||
buildQuizActivityTitle,
|
||||
disableIdiomAnswerButtons,
|
||||
isQuizStartMessage,
|
||||
normalizeQuizRoundPayload,
|
||||
} from "./riddle-quiz.js";
|
||||
import { isExpiredChatImageMessage } from "./message-utils.js";
|
||||
import { normalizeDailyStatus, resolveBlockedSystemSenderKey } from "./preferences-status.js";
|
||||
import { escapePresenceText } from "./vip-presence.js";
|
||||
@@ -50,6 +56,80 @@ function parseBracketUsers(content, color = "#000099") {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建统一的猜谜活动标题与题型标签。
|
||||
*/
|
||||
function buildQuizBadgeHtml(msg, accentColor = "#7c3aed") {
|
||||
const { activityLabel, typeLabel } = buildQuizActivityTitle(msg);
|
||||
|
||||
return `
|
||||
<span style="display:inline-flex;align-items:center;gap:6px;flex-wrap:wrap;">
|
||||
<span style="display:inline-flex;align-items:center;padding:2px 9px;border-radius:999px;background:${accentColor};color:#fff;font-size:11px;font-weight:800;letter-spacing:.04em;">${escapeHtml(activityLabel)}</span>
|
||||
<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;border:1px solid ${accentColor}33;">${escapeHtml(typeLabel)}</span>
|
||||
</span>
|
||||
`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 猜谜活动开题消息统一渲染为卡片。
|
||||
*/
|
||||
function buildQuizStartHtml(msg, timeStr) {
|
||||
const quizMeta = normalizeQuizRoundPayload(msg);
|
||||
const rawHint = String(quizMeta.hint || msg.content || "")
|
||||
.replace(/^🧩\s*/, "")
|
||||
.replace(/^📣\s*/, "")
|
||||
.replace(/^【[^】]+】\s*第\s*#?\d+\s*题开始!?\s*题面:\s*/u, "")
|
||||
.replace(/^【[^】]+】\s*/u, "")
|
||||
.replace(/^第\s*#?\d+\s*题开始!?\s*题面:\s*/u, "")
|
||||
.replace(/^题面:\s*/u, "")
|
||||
.trim();
|
||||
const safeHint = escapeHtml(rawHint);
|
||||
|
||||
return `
|
||||
<div style="display:flex;align-items:center;gap:8px;padding:6px 10px;border-radius:12px;background:linear-gradient(135deg,#f5f3ff,#faf5ff);border:1px solid rgba(124,58,237,.16);box-shadow:0 6px 16px rgba(124,58,237,.08);overflow:hidden;">
|
||||
<div style="width:30px;height:30px;border-radius:10px;background:linear-gradient(135deg,#7c3aed,#a78bfa);display:flex;align-items:center;justify-content:center;color:#fff;font-size:17px;box-shadow:0 5px 12px rgba(124,58,237,.18);flex-shrink:0;">🧩</div>
|
||||
<div style="min-width:0;flex:1;display:flex;align-items:center;gap:8px;flex-wrap:wrap;color:#312e81;">
|
||||
<div style="display:flex;align-items:center;gap:8px;flex-wrap:wrap;flex-shrink:0;">${buildQuizBadgeHtml(msg)}</div>
|
||||
<div data-quiz-inline-text 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;">
|
||||
<span>${safeHint}</span>
|
||||
<span class="msg-time" style="font-size:11px;color:#94a3b8;">(${timeStr})</span>
|
||||
<span data-quiz-inline-action-anchor></span>
|
||||
</div>
|
||||
<div style="display:flex;align-items:center;gap:6px;flex-wrap:wrap;color:#6d28d9;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 rgba(124,58,237,.10);white-space:nowrap;">💰 ${quizMeta.rewardGold} 金币</span>
|
||||
<span style="padding:1px 7px;border-radius:999px;background:#ffffff;box-shadow:inset 0 0 0 1px rgba(124,58,237,.10);white-space:nowrap;">⭐ ${quizMeta.rewardExp} 经验</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 猜谜活动结算消息统一渲染为结果卡片。
|
||||
*/
|
||||
function buildQuizResultHtml(msg, timeStr) {
|
||||
const quizMeta = normalizeQuizRoundPayload(msg);
|
||||
const winnerHtml = clickableUser(String(msg.winner_username || ""), "#15803d");
|
||||
const answerText = escapeHtml(quizMeta.answer || String(msg.content || ""));
|
||||
|
||||
return `
|
||||
<div style="display:flex;align-items:flex-start;gap:12px;padding:10px 12px;border-radius:14px;background:linear-gradient(135deg,#f0fdf4,#ecfccb);border:1px solid rgba(22,163,74,.18);box-shadow:0 10px 24px rgba(34,197,94,.10);">
|
||||
<div style="width:42px;height:42px;border-radius:12px;background:linear-gradient(135deg,#16a34a,#4ade80);display:flex;align-items:center;justify-content:center;color:#fff;font-size:22px;box-shadow:0 8px 18px rgba(22,163,74,.22);flex-shrink:0;">🎉</div>
|
||||
<div style="min-width:0;flex:1;">
|
||||
<div style="display:flex;align-items:center;gap:8px;flex-wrap:wrap;">
|
||||
${buildQuizBadgeHtml(msg, "#16a34a")}
|
||||
<span class="msg-time">(${timeStr})</span>
|
||||
</div>
|
||||
<div style="margin-top:8px;font-size:15px;line-height:1.75;color:#166534;font-weight:700;">【${winnerHtml}】率先答对「${answerText}」</div>
|
||||
<div style="margin-top:8px;display:flex;align-items:center;gap:8px;flex-wrap:wrap;color:#166534;font-size:12px;">
|
||||
<span style="padding:2px 8px;border-radius:999px;background:#ffffff;box-shadow:inset 0 0 0 1px rgba(22,163,74,.12);">💰 ${quizMeta.rewardGold} 金币</span>
|
||||
<span style="padding:2px 8px;border-radius:999px;background:#ffffff;box-shadow:inset 0 0 0 1px rgba(22,163,74,.12);">⭐ ${quizMeta.rewardExp} 经验</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 只保留包厢窗口最近几条猜成语答题记录,避免答题历史无限堆积。
|
||||
*/
|
||||
@@ -121,14 +201,10 @@ export function appendMessage(msg, renderBatch = null) {
|
||||
|
||||
state.trackMaxMsgId(msg.id || 0);
|
||||
|
||||
const idiomRoundId = Number.parseInt(
|
||||
String(msg.idiom_game_round_id || msg.idom_game_round_id || "0"),
|
||||
10,
|
||||
);
|
||||
const isIdiomStartMessage = idiomRoundId > 0
|
||||
&& msg.from_user === "星海小博士"
|
||||
&& !msg.action
|
||||
&& String(msg.content || "").includes("猜成语时间");
|
||||
const quizMeta = normalizeQuizRoundPayload(msg);
|
||||
const idiomRoundId = quizMeta.roundId;
|
||||
const isIdiomStartMessage = isQuizStartMessage(msg)
|
||||
&& ["星海小博士", "系统传音"].includes(String(msg.from_user || ""));
|
||||
|
||||
if (isIdiomStartMessage) {
|
||||
const existingIdiomNode = document.querySelector(`[data-idiom-round-id="${idiomRoundId}"]`);
|
||||
@@ -150,6 +226,7 @@ export function appendMessage(msg, renderBatch = null) {
|
||||
}
|
||||
if (idiomRoundId > 0) {
|
||||
div.dataset.idiomRoundId = String(idiomRoundId);
|
||||
div.dataset.quizRoundId = String(idiomRoundId);
|
||||
}
|
||||
if (blockRuleKey) {
|
||||
div.dataset.blockKey = blockRuleKey;
|
||||
@@ -210,12 +287,13 @@ export function appendMessage(msg, renderBatch = null) {
|
||||
html = `${iconImg} ${parsedContent}`;
|
||||
} else if (msg.action === "idiom_result") {
|
||||
div.dataset.idiomResult = "1";
|
||||
const winnerUsername = String(msg.winner_username || "");
|
||||
const winnerHtml = clickableUser(winnerUsername, "#16a34a");
|
||||
const answerText = escapeHtml(String(msg.idiom_answer || ""));
|
||||
const rewardGold = Number.parseInt(String(msg.idiom_result_reward_gold ?? msg.reward_gold ?? 0), 10);
|
||||
const rewardExp = Number.parseInt(String(msg.idiom_result_reward_exp ?? msg.reward_exp ?? 0), 10);
|
||||
html = `<span style="color:#16a34a;font-weight:bold;">🎉 【${winnerHtml}】率先答对成语「${answerText}」,获得 ${rewardGold} 金币、${rewardExp} 经验!</span>`;
|
||||
div.dataset.quizRoundEndedId = String(quizMeta.endedRoundId || quizMeta.roundId || 0);
|
||||
div.dataset.quizWinnerUsername = String(msg.winner_username || "");
|
||||
const parsedContent = parseBracketUsers(msg.content);
|
||||
html = `${headImg}<span style="font-weight: bold;">${clickableUser(msg.from_user, fontColor, nameClass)}:</span><span class="msg-content${textColorClass}" style="color: ${fontColor};">${parsedContent}</span>`;
|
||||
} else if (isIdiomStartMessage) {
|
||||
html = buildQuizStartHtml(msg, timeStr);
|
||||
timeStrOverride = true;
|
||||
} else if (msg.action === "vip_presence") {
|
||||
const accent = msg.presence_color || "#f59e0b";
|
||||
div.style.cssText =
|
||||
@@ -278,6 +356,7 @@ export function appendMessage(msg, renderBatch = null) {
|
||||
const isRedPacketClaimNotification = content.includes("抢到了") && content.includes("礼包");
|
||||
const isBaccaratLossCoverNotification = content.includes("【你玩游戏我买单】") || content.includes("金币补偿");
|
||||
const isDailySignInNotification = content.includes("完成今日签到") || content.includes("使用补签卡补签");
|
||||
const isQuizStartNotification = isIdiomStartMessage || content.includes("猜谜活动") || content.includes("猜成语时间");
|
||||
const isPlainNotification =
|
||||
content.includes("【百家乐】") ||
|
||||
content.includes("【赛马】") ||
|
||||
@@ -287,7 +366,23 @@ export function appendMessage(msg, renderBatch = null) {
|
||||
content.includes("【老虎机】") ||
|
||||
content.includes("购买了");
|
||||
|
||||
if (isRedPacketClaimNotification || isBaccaratLossCoverNotification || isDailySignInNotification) {
|
||||
if (isQuizStartNotification) {
|
||||
div.style.cssText =
|
||||
"background:linear-gradient(135deg,#fff7ed,#fffbeb);border:1px solid rgba(245,158,11,.28);border-left:4px solid #f59e0b;border-radius:12px;padding:8px 12px;margin:4px 0;box-shadow:0 10px 24px rgba(245,158,11,.14);";
|
||||
html = `
|
||||
<div style="display:flex;align-items:flex-start;gap:10px;">
|
||||
<div style="width:38px;height:38px;border-radius:12px;background:linear-gradient(135deg,#f59e0b,#f97316);display:flex;align-items:center;justify-content:center;color:#fff;font-size:20px;box-shadow:0 8px 18px rgba(249,115,22,.22);flex-shrink:0;">📣</div>
|
||||
<div style="min-width:0;flex:1;">
|
||||
<div style="display:flex;align-items:center;gap:8px;flex-wrap:wrap;">
|
||||
${buildQuizBadgeHtml(msg, "#d97706")}
|
||||
<span class="msg-time">(${timeStr})</span>
|
||||
</div>
|
||||
<div style="margin-top:7px;color:#9a3412;font-size:15px;font-weight:800;line-height:1.75;">${parseBracketUsers(content, "#b45309")}</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
timeStrOverride = true;
|
||||
} else if (isRedPacketClaimNotification || isBaccaratLossCoverNotification || isDailySignInNotification) {
|
||||
let plainAccentContent = parseBracketUsers(msg.content);
|
||||
html = `<span style="color: #b45309;">🌟 ${plainAccentContent}</span>`;
|
||||
} else if (isPlainNotification) {
|
||||
@@ -346,9 +441,9 @@ export function appendMessage(msg, renderBatch = null) {
|
||||
div.innerHTML = html;
|
||||
attachIdiomAnswerButton(div, msg);
|
||||
|
||||
// 历史消息恢复或实时结算时,都立即移除对应回合的旧答题按钮。
|
||||
if (Number.parseInt(String(msg.idiom_game_round_ended_id || "0"), 10) > 0) {
|
||||
removeIdiomAnswerButtons(Number.parseInt(String(msg.idiom_game_round_ended_id), 10));
|
||||
// 历史消息恢复或实时结算时,都立即把对应回合按钮置为结束态,保留消息结构便于回看。
|
||||
if (quizMeta.endedRoundId > 0) {
|
||||
disableIdiomAnswerButtons(quizMeta.endedRoundId, "本回合已结束", String(msg.winner_username || ""));
|
||||
}
|
||||
|
||||
// 命中屏蔽规则时,消息仍保留在 DOM 中,便于取消屏蔽后立即恢复显示。
|
||||
|
||||
Reference in New Issue
Block a user