Merge remote-tracking branch 'upstream/HEAD'

This commit is contained in:
2025-12-27 17:44:24 +08:00
12 changed files with 3275 additions and 114 deletions

View File

@@ -47,6 +47,8 @@ const UPDATE_CHECK_URL =
const MESSAGE_BOARD_URL =
"https://amilyservice.amily49.cc/amily2_message_board.json";
let lastMessageId = null;
export async function fetchMessageBoardContent() {
if (!MESSAGE_BOARD_URL) {
@@ -54,11 +56,28 @@ export async function fetchMessageBoardContent() {
return null;
}
try {
const response = await fetch(MESSAGE_BOARD_URL, { cache: 'no-store' });
let url = MESSAGE_BOARD_URL;
if (lastMessageId) {
const separator = url.includes('?') ? '&' : '?';
url += `${separator}nowId=${encodeURIComponent(lastMessageId)}`;
}
const response = await fetch(url, { cache: 'no-store' });
if (response.status === 304) {
console.log('[Amily2号-内务府] 留言板内容未变更 (304)。');
return null;
}
if (!response.ok) {
throw new Error(`服务器响应异常: ${response.status}`);
}
const data = await response.json();
if (data && data.id) {
lastMessageId = data.id;
}
return data;
} catch (error) {
console.error('[Amily2号-内务府] 获取留言板内容失败:', error);

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,8 +1,8 @@
import {
world_names,
loadWorldInfo,
saveWorldInfo,
createNewWorldInfo,
import {
world_names,
loadWorldInfo,
saveWorldInfo,
createNewWorldInfo,
createWorldInfoEntry
} from "/scripts/world-info.js";
@@ -20,9 +20,9 @@ let reloadEditor = () => {
console.warn("[Amily助手] 动态导入 reloadEditor 失败,将使用空函数。错误信息:", error.message);
}
})();
import {
characters,
eventSource,
import {
characters,
eventSource,
event_types,
chat,
reloadCurrentChat,
@@ -46,14 +46,14 @@ class AmilyHelper {
getChatMessages(range, options = {}) {
const { role = 'all', hide_state = 'all', include_swipes = false, include_swipe = false } = options;
const includeSwipes = include_swipes || include_swipe;
if (!chat || !Array.isArray(chat)) {
throw new Error('聊天数组不可用');
}
let start, end;
const rangeStr = String(range);
if (rangeStr.match(/^(-?\d+)$/)) {
const value = Number(rangeStr);
start = end = value < 0 ? chat.length + value : value;
@@ -186,7 +186,7 @@ class AmilyHelper {
refresh = 'display_and_render_current'
} = {}) {
field_values = typeof field_values === 'string' ? { message: field_values } : field_values;
if (typeof swipe_id !== 'number' && swipe_id !== 'current') {
throw new Error(`提供的 swipe_id 无效, 请提供 'current' 或序号, 你提供的是: ${swipe_id}`);
}
@@ -279,7 +279,7 @@ class AmilyHelper {
const should_update_swipe = add_swipes_if_required();
update_chat_message();
await saveChatConditional();
if (refresh == 'all') {
await reloadCurrentChat();
} else {
@@ -378,12 +378,12 @@ class AmilyHelper {
if (!bookData || !bookData.entries) {
return [];
}
const positionMap = {
0: 'before_character_definition',
1: 'after_character_definition',
2: 'before_author_note',
3: 'after_author_note',
4: 'at_depth_as_system'
const positionMap = {
0: 'before_character_definition',
1: 'after_character_definition',
2: 'before_author_note',
3: 'after_author_note',
4: 'at_depth_as_system'
};
return Object.entries(bookData.entries).map(([uid, entry]) => ({
uid: parseInt(uid),
@@ -393,7 +393,7 @@ class AmilyHelper {
keys: entry.key || [],
enabled: !entry.disable,
constant: entry.constant || false,
position: positionMap[entry.position] || 'at_depth_as_system',
position: positionMap[entry.position] || 'at_depth_as_system',
depth: entry.depth || 998,
}));
} catch (error) {
@@ -421,13 +421,13 @@ class AmilyHelper {
if (entryUpdate.type === 'constant') existingEntry.constant = true;
if (entryUpdate.type === 'selective') existingEntry.constant = false;
if (entryUpdate.position !== undefined) {
const positionMap = {
'before_character_definition': 0,
'after_character_definition': 1,
'before_author_note': 2,
'after_author_note': 3,
'at_depth': 4,
'at_depth_as_system': 4
const positionMap = {
'before_character_definition': 0,
'after_character_definition': 1,
'before_author_note': 2,
'after_author_note': 3,
'at_depth': 4,
'at_depth_as_system': 4
};
existingEntry.position = positionMap[entryUpdate.position] ?? 4;
}
@@ -462,13 +462,13 @@ class AmilyHelper {
for (const newEntryData of entries) {
const newEntry = createWorldInfoEntry(bookName, bookData);
const positionMap = {
'before_character_definition': 0,
'after_character_definition': 1,
'before_author_note': 2,
'after_author_note': 3,
'at_depth': 4,
'at_depth_as_system': 4
const positionMap = {
'before_character_definition': 0,
'after_character_definition': 1,
'before_author_note': 2,
'after_author_note': 3,
'at_depth': 4,
'at_depth_as_system': 4
};
Object.assign(newEntry, {
comment: newEntryData.comment || '新条目',
@@ -499,7 +499,7 @@ class AmilyHelper {
if (!bookData || !bookData.entries) {
return false;
}
let deletedCount = 0;
for (const uid of uids) {
if (bookData.entries[uid]) {
@@ -507,7 +507,7 @@ class AmilyHelper {
deletedCount++;
}
}
if (deletedCount > 0) {
await saveWorldInfo(bookName, bookData, true);
reloadEditor(bookName);
@@ -583,7 +583,7 @@ class AmilyHelper {
const char = characters[this_chid];
if (!char.data) char.data = {};
if (!char.data.extensions) char.data.extensions = {};
// 确保 world 字段是数组
let worlds = char.data.extensions.world;
if (!Array.isArray(worlds)) {
@@ -594,7 +594,7 @@ class AmilyHelper {
worlds.push(bookName);
char.data.extensions.world = worlds;
console.log(`[Amily助手] 已将世界书《${bookName}》绑定到角色 ${char.name}`);
if (typeof saveCharacterDebounced === 'function') {
saveCharacterDebounced();
return true;
@@ -652,12 +652,13 @@ export function makeRequest(request, data) {
reject(new Error(`请求 '${request}' 超时 (30秒)`));
}, 30000);
const targetOrigin = window.location.origin === 'null' ? '*' : window.location.origin;
window.parent.postMessage({
source: 'amily2-iframe-request',
request: request,
uid: uid,
data: data
}, window.location.origin);
}, targetOrigin);
});
}
@@ -676,9 +677,9 @@ export function registerApiHandler(request, handler) {
export function initializeApiListener() {
window.addEventListener('message', async (event) => {
// 安全修复:严格验证消息来源,防止跨源消息伪造
// 'null' 是 srcdoc 或 blob URL iframe 的 origin
if (event.origin !== window.location.origin && event.origin !== 'null') {
if (window.location.origin !== 'null' && event.origin !== window.location.origin) {
console.warn(`[Amily2-IframeAPI] 拒绝来自未知来源的请求: ${event.origin}`);
return;
}
@@ -696,6 +697,7 @@ export function initializeApiListener() {
const handler = apiHandlers.get(data.request);
const callbackRequest = `${data.request}_callback`;
const targetOrigin = event.origin === 'null' ? '*' : event.origin;
if (!handler) {
console.error(`[Amily2-IframeAPI] 收到未知请求: ${data.request}`);
@@ -703,7 +705,7 @@ export function initializeApiListener() {
request: callbackRequest,
uid: data.uid,
error: `未注册请求 '${data.request}' 的处理器`
}, event.origin); // 安全修复:回复到确切的来源,而不是 '*'
}, targetOrigin);
return;
}
@@ -713,15 +715,15 @@ export function initializeApiListener() {
request: callbackRequest,
uid: data.uid,
result: result
}, event.origin); // 安全修复:回复到确切的来源
}, targetOrigin);
} catch (error) {
console.error(`[Amily2-IframeAPI] 执行处理器 '${data.request}' 时出错:`, error);
event.source.postMessage({
request: callbackRequest,
uid: data.uid,
error: error.message || String(error)
}, event.origin); // 安全修复:回复到确切的来源
}, targetOrigin);
}
});
console.log('[Amily2-IframeAPI] 主窗口监听器已初始化');
console.log('[Amily2-IframeAPI] 主窗口监听器已初始化 (已启用安全验证)');
}