This commit is contained in:
jakciehan
2026-05-12 08:03:21 +08:00
parent d263c7bf48
commit c4bd390478
7 changed files with 292 additions and 198 deletions
@@ -19,6 +19,14 @@ const SENSITIVE_WORDS = {
'颠覆国家', '推翻政权', '分裂国家', '恐怖组织', '极端主义',
'反动', '暴乱', '煽动颠覆', '分裂势力', '恐怖袭击',
'邪教组织', '法轮', '法轮功', '台独', '藏独', '疆独',
// Political figure names and common variants (homophone / split-char evasion)
'习近平', '刁近平', '习大大', '习主席', '习总',
'XiJinping', 'xijinping', '习近', '近平',
'李强', '王岐山', '栗战书', '汪洋', '韩正',
'李克强', '胡锦涛', '江泽民', '温家宝', '朱镕基',
'邓小平', '毛泽东', '周恩来', '刘少奇', '彭德怀',
'薄熙来', '周永康', '徐才厚', '郭伯雄', '令计划',
'孙政才', '赵乐际', '王沪宁', '丁薛祥', '蔡奇',
],
pornography: [
@@ -98,10 +106,17 @@ function checkText(text) {
const matchedWords = [];
const categories = new Set();
const lowerText = text.toLowerCase();
// Strip common evasion characters for split-char detection
const strippedText = text.replace(/[\s\u3000.,;:!?·…—\-_\|\\/~`@#$%^&*+=<>()\[\]{}""''「」『』【】()〈〕\u200b\u200c\u200d\ufeff]/g, '').toLowerCase();
for (const [category, words] of Object.entries(SENSITIVE_WORDS)) {
for (const word of words) {
if (lowerText.includes(word.toLowerCase())) {
const lowerWord = word.toLowerCase();
if (lowerText.includes(lowerWord)) {
matchedWords.push(word);
categories.add(category);
} else if (word.length >= 2 && strippedText.includes(lowerWord)) {
// Split-char evasion detected
matchedWords.push(word);
categories.add(category);
}
@@ -120,7 +135,7 @@ function checkText(text) {
* @returns {string}
*/
function getVersion() {
return '2026-05-11-v1';
return '2026-05-12-v2';
}
module.exports = {
+2 -2
View File
@@ -7,7 +7,8 @@ module.exports = {
// ============================================================
// Common
// ============================================================
'common.back': '← Back',
'common.back': '← Back',
'common.cancel': 'Cancel',
'common.joinBtn': 'Join',
'common.cannotConnect': 'Cannot connect to server',
'common.connectFailed': 'Connection failed',
@@ -208,7 +209,6 @@ module.exports = {
'settings.sound': 'Sound',
'settings.music': 'Music',
'settings.vibration': 'Vibration',
'settings.nickname': 'Display Name',
'settings.profile': 'Profile',
// ============================================================
+2 -2
View File
@@ -7,7 +7,8 @@ module.exports = {
// ============================================================
// Common
// ============================================================
'common.back': '← 返回',
'common.back': '← 返回',
'common.cancel': '取消',
'common.joinBtn': '加入',
'common.cannotConnect': '无法连接服务器',
'common.connectFailed': '连接失败',
@@ -208,7 +209,6 @@ module.exports = {
'settings.sound': '音效',
'settings.music': '音乐',
'settings.vibration': '振动',
'settings.nickname': '显示名字',
'settings.profile': '个人资料',
// ============================================================
+20 -2
View File
@@ -52,6 +52,13 @@ const FALLBACK_WORDS = [
// Politics
'颠覆国家', '推翻政权', '分裂国家', '恐怖组织', '极端主义',
'法轮', '法轮功', '台独', '藏独', '疆独',
'习近平', '刁近平', '习大大', '习主席', '习总',
'XiJinping', 'xijinping', '习近', '近平',
'李强', '王岐山', '栗战书', '汪洋', '韩正',
'李克强', '胡锦涛', '江泽民', '温家宝', '朱镕基',
'邓小平', '毛泽东', '周恩来', '刘少奇', '彭德怀',
'薄熙来', '周永康', '徐才厚', '郭伯雄', '令计划',
'孙政才', '赵乐际', '王沪宁', '丁薛祥', '蔡奇',
// Pornography
'色情', '淫秽', '裸体', '卖淫', '嫖娼',
'约炮', '援交', '一夜情', '黄色视频',
@@ -130,13 +137,24 @@ class ContentSecurityManager {
const startTime = Date.now();
const lowerContent = content.toLowerCase();
// Strip common evasion characters (punctuation, spaces, zero-width chars) for split-char detection
const strippedContent = content.replace(/[\s\u3000.,;:!?·…—\-_\|\\/~`@#$%^&*+=<>()\[\]{}""''「」『』【】()〈〕\u200b\u200c\u200d\ufeff]/g, '').toLowerCase();
const matchedWords = [];
for (let i = 0; i < this._words.length; i++) {
const word = this._words[i];
if (word && lowerContent.includes(word.toLowerCase())) {
if (!word) continue;
const lowerWord = word.toLowerCase();
// Direct match
if (lowerContent.includes(lowerWord)) {
matchedWords.push(word);
if (matchedWords.length >= 3) break;
continue;
}
// Split-char evasion match: check if word chars appear in order with evasion chars between
// Only for multi-char words (length >= 2)
if (word.length >= 2 && strippedContent.includes(lowerWord)) {
matchedWords.push(word);
// Early exit if we find violations (performance optimization)
if (matchedWords.length >= 3) break;
}
}
+233 -75
View File
@@ -44,6 +44,7 @@ const ProfileScene = {
// Current editing state
_editingField: null, // Which field is being edited
_inputText: '', // Current input text
_originalText: '', // Original text before editing (for change detection)
_errorMessage: '', // Error message to display
_isSubmitting: false, // Whether a submission is in progress
_localViolation: false, // Whether local check found violation
@@ -66,6 +67,7 @@ const ProfileScene = {
_descriptionRect: null,
_avatarRect: null,
_saveBtnRect: null,
_cancelBtnRect: null,
enter() {
this._editingField = null;
@@ -154,18 +156,15 @@ const ProfileScene = {
// Description field
this._renderDescriptionField(ctx);
// Error message
if (this._errorMessage) {
// Error message (only show when NOT editing to avoid overlap)
if (this._errorMessage && !this._editingField) {
ctx.fillStyle = '#FF4444';
ctx.font = '12px Arial';
ctx.textAlign = 'center';
ctx.fillText(this._errorMessage, cx, SCREEN_HEIGHT - 140);
}
// Save button
this._renderSaveButton(ctx);
// Editing overlay
// Editing overlay (includes save/cancel buttons)
if (this._editingField) {
this._renderEditOverlay(ctx);
}
@@ -215,16 +214,13 @@ const ProfileScene = {
}
},
// ============================================================
// Editing
// ============================================================
_startEditing(field, currentValue) {
const csm = GameGlobal.contentSecurityManager;
if (!csm || !csm.isInitialized()) return;
this._editingField = field;
this._inputText = currentValue || '';
this._originalText = currentValue || '';
this._errorMessage = '';
this._localViolation = false;
@@ -234,55 +230,51 @@ const ProfileScene = {
if (field === FIELD.SIGNATURE) maxLength = SIGNATURE_MAX;
if (field === FIELD.DESCRIPTION) maxLength = DESCRIPTION_MAX;
// Show keyboard
// Show keyboard with empty defaultValue, so the native "Done" button highlights
// as soon as user types any character. The original value is preserved in
// _originalText and shown in the overlay preview for reference.
wx.showKeyboard({
defaultValue: this._inputText,
defaultValue: '',
maxLength,
multiple: field === FIELD.DESCRIPTION,
confirmHold: false,
confirmType: 'done',
});
// Reset _inputText so we know if user actually typed something
this._inputText = '';
// Listen for keyboard input
this._onKeyboardInput = (res) => {
this._inputText = res.value;
this._inputText = res.value || '';
this._validateInput(field);
};
// Real-time local sensitive word check
if (this._inputText) {
const localCheck = csm.checkLocalText(this._inputText);
if (localCheck.hasViolation) {
this._localViolation = true;
this._errorMessage = '内容包含违规信息,请修改';
} else {
this._localViolation = false;
this._errorMessage = '';
}
// Length validation
if (field === FIELD.NICKNAME) {
if (this._inputText.length < NICKNAME_MIN) {
this._errorMessage = `昵称至少需要${NICKNAME_MIN}个字符`;
this._localViolation = true;
} else if (this._inputText.length > NICKNAME_MAX) {
this._errorMessage = `昵称不能超过${NICKNAME_MAX}个字符`;
this._localViolation = true;
}
} else if (field === FIELD.SIGNATURE && this._inputText.length > SIGNATURE_MAX) {
this._errorMessage = `签名不能超过${SIGNATURE_MAX}个字符`;
this._localViolation = true;
} else if (field === FIELD.DESCRIPTION && this._inputText.length > DESCRIPTION_MAX) {
this._errorMessage = `描述不能超过${DESCRIPTION_MAX}个字符`;
this._localViolation = true;
}
this._onKeyboardConfirm = (res) => {
// Ensure we have the final value on confirm
if (res && res.value !== undefined) {
this._inputText = res.value;
}
this._validateInput(field);
// Only auto-submit if no violations
if (!this._localViolation) {
this._handleSubmit();
}
};
this._onKeyboardConfirm = () => {
this._handleSubmit();
this._onKeyboardComplete = (res) => {
// Keyboard closed - get final value
if (res && res.value !== undefined) {
this._inputText = res.value;
this._validateInput(field);
}
};
wx.onKeyboardInput(this._onKeyboardInput);
wx.onKeyboardConfirm(this._onKeyboardConfirm);
wx.onKeyboardComplete(this._onKeyboardComplete);
// Initial validation to set correct button state
this._validateInput(field);
},
_stopEditing() {
@@ -294,10 +286,61 @@ const ProfileScene = {
wx.offKeyboardConfirm(this._onKeyboardConfirm);
this._onKeyboardConfirm = null;
}
if (this._onKeyboardComplete) {
wx.offKeyboardComplete(this._onKeyboardComplete);
this._onKeyboardComplete = null;
}
wx.hideKeyboard();
this._editingField = null;
},
_validateInput(field) {
const csm = GameGlobal.contentSecurityManager;
const text = this._inputText || '';
// Reset violation state first
this._localViolation = false;
this._errorMessage = '';
// Empty check
if (!text || text.trim().length === 0) {
this._localViolation = true;
this._errorMessage = '内容不能为空';
return;
}
// Sensitive word check
if (csm && csm.isInitialized()) {
const localCheck = csm.checkLocalText(text);
if (localCheck.hasViolation) {
this._localViolation = true;
this._errorMessage = '内容包含违规信息,请修改';
return;
}
}
// Length validation
if (field === FIELD.NICKNAME) {
if (text.length < NICKNAME_MIN) {
this._localViolation = true;
this._errorMessage = `昵称至少需要${NICKNAME_MIN}个字符`;
} else if (text.length > NICKNAME_MAX) {
this._localViolation = true;
this._errorMessage = `昵称不能超过${NICKNAME_MAX}个字符`;
}
} else if (field === FIELD.SIGNATURE) {
if (text.length > SIGNATURE_MAX) {
this._localViolation = true;
this._errorMessage = `签名不能超过${SIGNATURE_MAX}个字符`;
}
} else if (field === FIELD.DESCRIPTION) {
if (text.length > DESCRIPTION_MAX) {
this._localViolation = true;
this._errorMessage = `描述不能超过${DESCRIPTION_MAX}个字符`;
}
}
},
async _handleSubmit() {
if (this._isSubmitting || this._localViolation) return;
@@ -478,66 +521,167 @@ const ProfileScene = {
}
},
_renderSaveButton(ctx) {
const rect = this._saveBtnRect;
const isActive = this._editingField && !this._localViolation && !this._isSubmitting;
ctx.fillStyle = isActive ? '#4a90d9' : '#555555';
ctx.fillRect(rect.x, rect.y, rect.w, rect.h);
ctx.fillStyle = '#FFFFFF';
ctx.font = 'bold 14px Arial';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText(this._isSubmitting ? '审核中...' : t('profile.save'), rect.x + rect.w / 2, rect.y + rect.h / 2);
},
_renderEditOverlay(ctx) {
// Semi-transparent overlay at bottom
const overlayY = SCREEN_HEIGHT * 0.6;
ctx.fillStyle = 'rgba(0, 0, 0, 0.7)';
ctx.fillRect(0, overlayY, SCREEN_WIDTH, SCREEN_HEIGHT - overlayY);
// Place overlay at TOP of screen, so save/cancel buttons are NOT hidden by keyboard.
// The keyboard occupies the bottom portion of the screen when shown.
const overlayY = 0;
const overlayH = Math.min(SCREEN_HEIGHT * 0.4, 220);
ctx.fillStyle = 'rgba(0, 0, 0, 0.92)';
ctx.fillRect(0, overlayY, SCREEN_WIDTH, overlayH);
// Current input text preview
ctx.fillStyle = '#FFFFFF';
ctx.font = '14px Arial';
// Field label
const fieldLabels = {
[FIELD.NICKNAME]: t('profile.nickname'),
[FIELD.SIGNATURE]: t('profile.signature'),
[FIELD.DESCRIPTION]: t('profile.description'),
};
ctx.fillStyle = '#AAAAAA';
ctx.font = '12px Arial';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText('编辑 ' + (fieldLabels[this._editingField] || ''), CENTER_X, overlayY + 14);
// Input text preview area
const previewBoxY = overlayY + 30;
const previewBoxH = 50;
ctx.fillStyle = 'rgba(255, 255, 255, 0.08)';
ctx.fillRect(20, previewBoxY, SCREEN_WIDTH - 40, previewBoxH);
ctx.strokeStyle = '#4a90d9';
ctx.lineWidth = 1;
ctx.strokeRect(20, previewBoxY, SCREEN_WIDTH - 40, previewBoxH);
// Current input text
ctx.fillStyle = '#FFFFFF';
ctx.font = '15px Arial';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
const previewText = this._inputText || '';
const lines = this._wrapText(ctx, previewText, INPUT_WIDTH, 3);
for (let i = 0; i < lines.length; i++) {
ctx.fillText(lines[i], CENTER_X, overlayY + 30 + i * 20);
if (previewText) {
const lines = this._wrapText(ctx, previewText, SCREEN_WIDTH - 60, 2);
for (let i = 0; i < lines.length; i++) {
ctx.fillText(lines[i], CENTER_X, previewBoxY + 16 + i * 18);
}
} else {
// Placeholder
ctx.fillStyle = '#888888';
ctx.font = '13px Arial';
ctx.fillText(
this._originalText ? `当前: ${this._originalText}(在下方键盘输入新内容)` : '请在下方键盘输入',
CENTER_X,
previewBoxY + previewBoxH / 2
);
}
// Character count
let maxLen = 200;
if (this._editingField === FIELD.NICKNAME) maxLen = NICKNAME_MAX;
if (this._editingField === FIELD.SIGNATURE) maxLen = SIGNATURE_MAX;
ctx.fillStyle = '#888888';
ctx.font = '11px Arial';
ctx.fillText(`${this._inputText.length}/${maxLen}`, CENTER_X, overlayY + 100);
ctx.textAlign = 'right';
ctx.fillText(`${this._inputText.length}/${maxLen}`, SCREEN_WIDTH - 24, previewBoxY + previewBoxH + 12);
// Violation warning
if (this._localViolation) {
if (this._localViolation && this._errorMessage) {
ctx.fillStyle = '#FF4444';
ctx.font = 'bold 12px Arial';
ctx.fillText('⚠ ' + this._errorMessage, CENTER_X, overlayY + 120);
ctx.textAlign = 'center';
ctx.fillText('⚠ ' + this._errorMessage, CENTER_X, previewBoxY + previewBoxH + 12);
}
// Buttons row at bottom of overlay
const btnRowY = overlayY + overlayH - BTN_HEIGHT - 12;
const btnGap = 12;
const btnW = (SCREEN_WIDTH - 40 - btnGap) / 2;
// Cancel button (left)
this._cancelBtnRect = {
x: 20,
y: btnRowY,
w: btnW,
h: BTN_HEIGHT,
};
// Save button (right)
this._saveBtnRect = {
x: 20 + btnW + btnGap,
y: btnRowY,
w: btnW,
h: BTN_HEIGHT,
};
const btnR = 6;
// Cancel button render
ctx.fillStyle = '#3a3a3a';
this._drawRoundRect(ctx, this._cancelBtnRect.x, this._cancelBtnRect.y, this._cancelBtnRect.w, this._cancelBtnRect.h, btnR);
ctx.fill();
ctx.strokeStyle = '#666666';
ctx.lineWidth = 1;
this._drawRoundRect(ctx, this._cancelBtnRect.x, this._cancelBtnRect.y, this._cancelBtnRect.w, this._cancelBtnRect.h, btnR);
ctx.stroke();
ctx.fillStyle = '#CCCCCC';
ctx.font = '14px Arial';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText(t('common.cancel') || '取消', this._cancelBtnRect.x + this._cancelBtnRect.w / 2, this._cancelBtnRect.y + this._cancelBtnRect.h / 2);
// Save button render: always highlighted unless submitting
const isActive = !this._isSubmitting;
if (isActive) {
ctx.shadowColor = '#00CC66';
ctx.shadowBlur = 16;
ctx.shadowOffsetX = 0;
ctx.shadowOffsetY = 0;
ctx.fillStyle = '#00CC66';
} else {
ctx.shadowColor = 'transparent';
ctx.shadowBlur = 0;
ctx.fillStyle = '#3a3a3a';
}
this._drawRoundRect(ctx, this._saveBtnRect.x, this._saveBtnRect.y, this._saveBtnRect.w, this._saveBtnRect.h, btnR);
ctx.fill();
ctx.shadowColor = 'transparent';
ctx.shadowBlur = 0;
if (isActive) {
ctx.strokeStyle = '#33FF99';
ctx.lineWidth = 2;
} else {
ctx.strokeStyle = '#555555';
ctx.lineWidth = 1;
}
this._drawRoundRect(ctx, this._saveBtnRect.x, this._saveBtnRect.y, this._saveBtnRect.w, this._saveBtnRect.h, btnR);
ctx.stroke();
ctx.fillStyle = isActive ? '#FFFFFF' : '#666666';
ctx.font = 'bold 15px Arial';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText(this._isSubmitting ? '审核中...' : t('profile.save'), this._saveBtnRect.x + this._saveBtnRect.w / 2, this._saveBtnRect.y + this._saveBtnRect.h / 2);
},
_handleEditOverlayTouch(x, y) {
// Save button
// Save button - always allow click, _handleSubmit will validate
if (this._hitTest(x, y, this._saveBtnRect)) {
if (!this._localViolation && !this._isSubmitting) {
this._handleSubmit();
if (!this._isSubmitting) {
// Re-validate before submit (in case keyboard callbacks did not fire)
this._validateInput(this._editingField);
if (!this._localViolation) {
this._handleSubmit();
}
// If validation fails, error message is now shown via _localViolation/_errorMessage
}
return;
}
// Tap outside to cancel
this._stopEditing();
// Cancel button
if (this._hitTest(x, y, this._cancelBtnRect)) {
this._stopEditing();
return;
}
// Ignore taps elsewhere inside the overlay (do NOT auto-cancel)
},
// ============================================================
@@ -548,6 +692,20 @@ const ProfileScene = {
return rect && x >= rect.x && x <= rect.x + rect.w && y >= rect.y && y <= rect.y + rect.h;
},
_drawRoundRect(ctx, x, y, w, h, r) {
ctx.beginPath();
ctx.moveTo(x + r, y);
ctx.lineTo(x + w - r, y);
ctx.arcTo(x + w, y, x + w, y + r, r);
ctx.lineTo(x + w, y + h - r);
ctx.arcTo(x + w, y + h, x + w - r, y + h, r);
ctx.lineTo(x + r, y + h);
ctx.arcTo(x, y + h, x, y + h - r, r);
ctx.lineTo(x, y + r);
ctx.arcTo(x, y, x + r, y, r);
ctx.closePath();
},
_wrapText(ctx, text, maxWidth, maxLines) {
const words = text.split('');
const lines = [];
+1 -113
View File
@@ -68,7 +68,6 @@ const SettingsScene = {
// Rows: nickname + 3 toggles. Distribute evenly between title and back btn.
const rows = [
{ type: 'nickname' },
{ type: 'toggle', key: 'soundEnabled', label: t('settings.sound'), icon: '🔊' },
{ type: 'toggle', key: 'musicEnabled', label: t('settings.music'), icon: '🎵' },
{ type: 'toggle', key: 'vibrationEnabled', label: t('settings.vibration'), icon: '📳' },
@@ -83,11 +82,7 @@ const SettingsScene = {
for (let i = 0; i < rows.length; i++) {
const row = rows[i];
const cy = firstCenterY + i * step;
if (row.type === 'nickname') {
this._renderNicknameRow(ctx, cx, cy);
} else {
this._renderToggle(ctx, cx, cy, row);
}
this._renderToggle(ctx, cx, cy, row);
}
// Profile entry button (below the last toggle row)
@@ -98,47 +93,6 @@ const SettingsScene = {
this._renderBackButton(ctx, cx, backCenterY);
},
_renderNicknameRow(ctx, cx, y) {
const w = SCREEN_WIDTH * 0.7;
const h = 50;
const x = cx - w / 2;
this._buttons['nickname'] = { x, y: y - h / 2, w, h };
// Background
ctx.fillStyle = '#1e1e3a';
ctx.fillRect(x, y - h / 2, w, h);
ctx.strokeStyle = '#333366';
ctx.lineWidth = 1;
ctx.strokeRect(x, y - h / 2, w, h);
// Icon + label (left)
ctx.fillStyle = COLORS.HUD_TEXT;
ctx.font = '16px Arial';
ctx.textAlign = 'left';
ctx.textBaseline = 'middle';
const leftLabel = `👤 ${t('settings.nickname') || '显示名字'}`;
ctx.fillText(leftLabel, x + 15, y);
// Current value + chevron (right)
const profile = GameGlobal.playerProfile;
let shown = '';
if (profile) {
if (profile.granted && profile.nickname) {
shown = profile.truncate ? profile.truncate(profile.nickname, 5) : profile.nickname;
} else if (typeof profile.getDisplayName === 'function') {
const pid = (GameGlobal.networkManager && GameGlobal.networkManager.playerId) || '';
shown = profile.getDisplayName(pid);
}
}
if (!shown) shown = 'Tanker';
ctx.fillStyle = profile && profile.granted ? '#FFD700' : '#8899AA';
ctx.font = '13px Arial';
ctx.textAlign = 'right';
ctx.fillText(`${shown} `, x + w - 15, y);
},
_renderToggle(ctx, cx, y, toggle) {
const w = SCREEN_WIDTH * 0.7;
const h = 50;
@@ -243,10 +197,6 @@ const SettingsScene = {
if (tx >= rect.x && tx <= rect.x + rect.w && ty >= rect.y && ty <= rect.y + rect.h) {
if (key === 'back') {
GameGlobal.sceneManager.switchTo(SCENE.MENU);
} else if (key === 'nickname') {
// IMPORTANT: wx.getUserProfile must be called synchronously from a
// user tap handler; invoking it here is fine (touchstart is a tap).
this._requestNicknameAuth();
} else if (key === 'profile') {
const sm = GameGlobal.sceneManager;
if (!sm._scenes.has(SCENE.PROFILE)) {
@@ -264,68 +214,6 @@ const SettingsScene = {
}
},
// ============================================================
// Nickname acquisition (moved from MenuScene)
// ============================================================
_requestNicknameAuth() {
const profile = GameGlobal.playerProfile;
if (!profile) return;
const onDone = (ok) => {
if (ok) {
try {
wx.showToast({
title: `欢迎 ${profile.nickname}`,
icon: 'none',
duration: 1500,
});
} catch (e) { /* ignore */ }
}
};
if (typeof profile.requestUserProfile === 'function') {
profile.requestUserProfile().then((ok) => {
if (ok) {
onDone(true);
} else {
this._promptManualNickname(onDone);
}
}).catch(() => this._promptManualNickname(onDone));
} else {
this._promptManualNickname(onDone);
}
},
_promptManualNickname(cb) {
try {
if (typeof wx === 'undefined' || typeof wx.showModal !== 'function') {
cb && cb(false);
return;
}
wx.showModal({
title: '设置昵称',
content: '输入在对战中显示的名字(最长16字)',
editable: true,
placeholderText: '例如:坏蹄子',
confirmText: '确定',
cancelText: '取消',
success: (res) => {
if (res.confirm) {
const profile = GameGlobal.playerProfile;
const ok = profile && typeof profile.setManualNickname === 'function'
&& profile.setManualNickname(res.content || '');
cb && cb(!!ok);
} else {
cb && cb(false);
}
},
fail: () => cb && cb(false),
});
} catch (e) {
console.warn('[Settings] showModal failed:', e && e.message);
cb && cb(false);
}
},
};
module.exports = SettingsScene;
+17 -2
View File
@@ -19,6 +19,14 @@ const SENSITIVE_WORDS = {
'颠覆国家', '推翻政权', '分裂国家', '恐怖组织', '极端主义',
'反动', '暴乱', '煽动颠覆', '分裂势力', '恐怖袭击',
'邪教组织', '法轮', '法轮功', '台独', '藏独', '疆独',
// Political figure names and common variants (homophone / split-char evasion)
'习近平', '刁近平', '习大大', '习主席', '习总',
'XiJinping', 'xijinping', '习近', '近平',
'李强', '王岐山', '栗战书', '汪洋', '韩正',
'李克强', '胡锦涛', '江泽民', '温家宝', '朱镕基',
'邓小平', '毛泽东', '周恩来', '刘少奇', '彭德怀',
'薄熙来', '周永康', '徐才厚', '郭伯雄', '令计划',
'孙政才', '赵乐际', '王沪宁', '丁薛祥', '蔡奇',
],
pornography: [
@@ -98,10 +106,17 @@ function checkText(text) {
const matchedWords = [];
const categories = new Set();
const lowerText = text.toLowerCase();
// Strip common evasion characters for split-char detection
const strippedText = text.replace(/[\s\u3000.,;:!?·…—\-_\|\\/~`@#$%^&*+=<>()\[\]{}""''「」『』【】()〈〕\u200b\u200c\u200d\ufeff]/g, '').toLowerCase();
for (const [category, words] of Object.entries(SENSITIVE_WORDS)) {
for (const word of words) {
if (lowerText.includes(word.toLowerCase())) {
const lowerWord = word.toLowerCase();
if (lowerText.includes(lowerWord)) {
matchedWords.push(word);
categories.add(category);
} else if (word.length >= 2 && strippedText.includes(lowerWord)) {
// Split-char evasion detected
matchedWords.push(word);
categories.add(category);
}
@@ -120,7 +135,7 @@ function checkText(text) {
* @returns {string}
*/
function getVersion() {
return '2026-05-11-v1';
return '2026-05-12-v2';
}
module.exports = {