// ==UserScript==
// @name 漫画导入器优化版
// @namespace http://tampermonkey.net/
// @version 2.1
// @description 将漫画信息导入到Notion数据库(优化移动端体验)
// @author pipi
// @match *://*/*
// @grant GM_xmlhttpRequest
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_deleteValue
// @grant GM_addStyle
// @grant GM_notification
// @connect api.notion.com
// @license pipi
// ==/UserScript==
(function() {
'use strict';
// 配置存储
const CONFIG_KEY = 'NotionImporterConfig';
const SITE_SELECTORS_KEY = 'NotionImporterSiteSelectors';
const IMPORTED_PAGES_KEY = 'NotionImporterImportedPages';
// 默认配置
const defaultConfig = {
apiKey: '',
mangaDbId: ''
};
// 粉色主题颜色
const PRIMARY_COLOR = '#fd9abd';
const SECONDARY_COLOR = '#ffb3d1';
const DANGER_COLOR = '#ff6b8b';
const SUCCESS_COLOR = '#a8e6cf';
const INFO_COLOR = '#84c7f9';
// 获取或初始化配置
function getConfig() {
const savedConfig = GM_getValue(CONFIG_KEY, JSON.stringify(defaultConfig));
return JSON.parse(savedConfig);
}
// 保存配置
function saveConfig(config) {
GM_setValue(CONFIG_KEY, JSON.stringify(config));
}
// 获取站点选择器配置
function getSiteSelectors(domain) {
const selectors = GM_getValue(SITE_SELECTORS_KEY, '{}');
return JSON.parse(selectors)[domain] || {};
}
// 保存站点选择器配置
function saveSiteSelectors(domain, selectorConfig) {
const allSelectors = JSON.parse(GM_getValue(SITE_SELECTORS_KEY, '{}'));
allSelectors[domain] = selectorConfig;
GM_setValue(SITE_SELECTORS_KEY, JSON.stringify(allSelectors));
}
// 获取已导入页面
function getImportedPages() {
const pages = GM_getValue(IMPORTED_PAGES_KEY, '{}');
return JSON.parse(pages);
}
// 保存已导入页面
function saveImportedPage(url, pageId) {
const pages = getImportedPages();
pages[url] = pageId;
GM_setValue(IMPORTED_PAGES_KEY, JSON.stringify(pages));
}
// 创建悬浮按钮
function createFloatingButton(isImported = false) {
const existingBtn = document.getElementById('notion-importer-floating-btn');
if (existingBtn) existingBtn.remove();
const floatingBtn = document.createElement('div');
floatingBtn.id = 'notion-importer-floating-btn';
floatingBtn.style.position = 'fixed';
floatingBtn.style.right = '20px';
floatingBtn.style.bottom = '20px';
floatingBtn.style.zIndex = '9999';
floatingBtn.style.width = '50px';
floatingBtn.style.height = '50px';
floatingBtn.style.borderRadius = '50%';
floatingBtn.style.backgroundColor = isImported ? SUCCESS_COLOR : PRIMARY_COLOR;
floatingBtn.style.color = 'white';
floatingBtn.style.display = 'flex';
floatingBtn.style.justifyContent = 'center';
floatingBtn.style.alignItems = 'center';
floatingBtn.style.cursor = 'pointer';
floatingBtn.style.boxShadow = '0 2px 10px rgba(0,0,0,0.2)';
floatingBtn.innerHTML = isImported ?
'<span style="font-size: 24px;">✓</span>' :
'<span style="font-size: 24px;">+</span>';
floatingBtn.style.touchAction = 'manipulation';
document.body.appendChild(floatingBtn);
return floatingBtn;
}
// 创建确认对话框
function createConfirmDialog(message, onConfirm, onCancel) {
const overlay = document.createElement('div');
overlay.style.position = 'fixed';
overlay.style.top = '0';
overlay.style.left = '0';
overlay.style.width = '100%';
overlay.style.height = '100%';
overlay.style.backgroundColor = 'rgba(0,0,0,0.5)';
overlay.style.zIndex = '10000';
overlay.style.display = 'flex';
overlay.style.justifyContent = 'center';
overlay.style.alignItems = 'center';
const dialog = document.createElement('div');
dialog.style.backgroundColor = 'white';
dialog.style.padding = '20px';
dialog.style.borderRadius = '8px';
dialog.style.width = '80%';
dialog.style.maxWidth = '400px';
const messageEl = document.createElement('div');
messageEl.textContent = message;
messageEl.style.marginBottom = '20px';
messageEl.style.fontSize = '16px';
const buttonContainer = document.createElement('div');
buttonContainer.style.display = 'flex';
buttonContainer.style.justifyContent = 'flex-end';
buttonContainer.style.gap = '10px';
const confirmBtn = document.createElement('button');
confirmBtn.textContent = '确定';
confirmBtn.style.padding = '10px 20px';
confirmBtn.style.fontSize = '16px';
confirmBtn.style.backgroundColor = PRIMARY_COLOR;
confirmBtn.style.color = 'white';
confirmBtn.style.border = 'none';
confirmBtn.style.borderRadius = '6px';
confirmBtn.onclick = function() {
document.body.removeChild(overlay);
onConfirm && onConfirm();
};
const cancelBtn = document.createElement('button');
cancelBtn.textContent = '取消';
cancelBtn.style.padding = '10px 20px';
cancelBtn.style.fontSize = '16px';
cancelBtn.style.backgroundColor = DANGER_COLOR;
cancelBtn.style.color = 'white';
cancelBtn.style.border = 'none';
cancelBtn.style.borderRadius = '6px';
cancelBtn.onclick = function() {
document.body.removeChild(overlay);
onCancel && onCancel();
};
buttonContainer.appendChild(cancelBtn);
buttonContainer.appendChild(confirmBtn);
dialog.appendChild(messageEl);
dialog.appendChild(buttonContainer);
overlay.appendChild(dialog);
document.body.appendChild(overlay);
return overlay;
}
// 去除文本中的括号内容
function cleanText(text) {
if (!text) return '';
return text.replace(/\[.*?\]/g, '').replace(/\(.*?\)/g, '').trim();
}
// 获取元素值
function getElementValue(selector) {
if (!selector) return '';
try {
const element = document.querySelector(selector);
return element ? cleanText(getTextContent(element)) : '';
} catch (e) {
console.error('获取元素值出错:', e);
return '';
}
}
// 获取文本内容
function getTextContent(element) {
if (!element) return '';
// 如果是输入元素
if (element.tagName.toLowerCase() === 'input' || element.tagName.toLowerCase() === 'textarea') {
return element.value || '';
}
// 如果是选择元素
if (element.tagName.toLowerCase() === 'select') {
return element.options[element.selectedIndex]?.text || '';
}
// 默认获取文本内容
return element.textContent?.trim() || '';
}
// 获取图片URL(自动匹配第一张大图)
function getImageUrl(selector) {
// 优先使用选择器指定的图片
if (selector) {
try {
const element = document.querySelector(selector);
if (element) {
// 如果是img标签
if (element.tagName.toLowerCase() === 'img') {
return element.src || '';
}
// 如果是背景图
const backgroundImage = window.getComputedStyle(element).backgroundImage;
if (backgroundImage && backgroundImage !== 'none') {
const urlMatch = backgroundImage.match(/url\(["']?(.*?)["']?\)/);
if (urlMatch && urlMatch[1]) {
return urlMatch[1];
}
}
}
} catch (e) {
console.error('获取图片URL出错:', e);
}
}
// 如果没有选择器或选择器无效,自动获取页面第一张大图
const images = document.querySelectorAll('img');
for (let img of images) {
// 过滤小图标(宽度或高度大于100px)
if (img.naturalWidth > 100 && img.naturalHeight > 100) {
return img.src || '';
}
}
return '';
}
// Notion API请求
function notionApiRequest(endpoint, data, method = 'POST') {
const config = getConfig();
const apiKey = config.apiKey;
return new Promise((resolve, reject) => {
GM_xmlhttpRequest({
method: method,
url: `https://api.notion.com/v1/${endpoint}`,
headers: {
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json',
'Notion-Version': '2022-06-28'
},
data: JSON.stringify(data),
onload: function(response) {
if (response.status >= 200 && response.status < 300) {
try {
resolve(JSON.parse(response.responseText));
} catch (e) {
resolve(response.responseText);
}
} else {
try {
const error = JSON.parse(response.responseText);
reject(new Error(error.message || 'Notion API错误'));
} catch (e) {
reject(new Error(`HTTP ${response.status}: ${response.statusText}`));
}
}
},
onerror: function(error) {
reject(error);
}
});
});
}
// 更新Notion页面
async function updateNotionPage(pageId, data) {
try {
const response = await notionApiRequest(`pages/${pageId}`, data, 'PATCH');
return response;
} catch (error) {
console.error('更新Notion页面出错:', error);
throw error;
}
}
// 创建选择器界面
function createSelectorInterface(propertyName, callback) {
// 隐藏悬浮按钮
const floatingBtn = document.getElementById('notion-importer-floating-btn');
if (floatingBtn) floatingBtn.style.display = 'none';
// 创建选择器覆盖层
const selectorOverlay = document.createElement('div');
selectorOverlay.id = 'notion-importer-selector-overlay';
selectorOverlay.style.position = 'fixed';
selectorOverlay.style.top = '0';
selectorOverlay.style.left = '0';
selectorOverlay.style.width = '100%';
selectorOverlay.style.height = '100%';
selectorOverlay.style.zIndex = '10000';
selectorOverlay.style.pointerEvents = 'none';
// 创建提示框
const tooltip = document.createElement('div');
tooltip.style.position = 'fixed';
tooltip.style.bottom = '20px';
tooltip.style.left = '50%';
tooltip.style.transform = 'translateX(-50%)';
tooltip.style.backgroundColor = 'rgba(0,0,0,0.7)';
tooltip.style.color = 'white';
tooltip.style.padding = '15px 20px';
tooltip.style.borderRadius = '8px';
tooltip.style.zIndex = '10001';
tooltip.style.maxWidth = '90%';
tooltip.style.textAlign = 'center';
tooltip.style.fontSize = '16px';
tooltip.innerHTML = `
<div style="font-weight: bold; margin-bottom: 8px;">正在选择: ${propertyName}</div>
<div style="margin-bottom: 8px;">点击页面元素进行选择</div>
<div style="margin-bottom: 8px;">按住Shift可多选</div>
<div id="notion-importer-current-selector" style="margin-top: 8px; font-family: monospace; word-break: break-all;"></div>
`;
selectorOverlay.appendChild(tooltip);
document.body.appendChild(selectorOverlay);
// 高亮元素
function highlightElement(element) {
if (!element) return;
element.style.outline = '2px solid ' + PRIMARY_COLOR;
element.style.outlineOffset = '2px';
}
// 取消高亮
function unhighlightElement(element) {
if (!element) return;
element.style.outline = '';
element.style.outlineOffset = '';
}
let selectedElements = [];
let currentSelector = '';
let hoveredElement = null;
let shiftPressed = false;
// 更新当前悬停元素的提示
function updateHoveredElementInfo(element) {
const selectorInfo = document.getElementById('notion-importer-current-selector');
if (element) {
selectorInfo.textContent = generateSelector(element, false);
} else {
selectorInfo.textContent = '未悬停在元素上';
}
}
// 生成选择器
function generateSelector(element, isMultiSelect = false) {
if (!element || !element.tagName) return '';
// 多选模式直接返回通用选择器
if (isMultiSelect) {
// 优先返回类选择器
if (element.className && typeof element.className === 'string') {
const classes = element.className.split(/\s+/).filter(c => c);
if (classes.length > 0) {
return `.${classes[0]}`;
}
}
// 没有类名则返回标签名
return element.tagName.toLowerCase();
}
// 单选模式生成精确选择器
const path = [];
let current = element;
while (current && current !== document.body) {
const siblings = Array.from(current.parentNode.children);
const index = siblings.indexOf(current) + 1;
const tag = current.tagName.toLowerCase();
// 优先使用带位置的选择器
path.unshift(`${tag}:nth-child(${index})`);
// 如果有类名则添加
if (current.className && typeof current.className === 'string') {
const classes = current.className.split(/\s+/).filter(c => c);
if (classes.length > 0) {
path[0] = `${tag}.${classes[0]}:nth-child(${index})`;
}
}
current = current.parentNode;
}
return path.join(' > ');
}
// 元素悬停逻辑
function handleElementHover(e) {
const target = e.target;
// 如果悬停在选择器界面本身,不处理
if (selectorOverlay.contains(target)) {
if (hoveredElement) {
unhighlightElement(hoveredElement);
hoveredElement = null;
updateHoveredElementInfo(null);
}
return;
}
// 如果悬停的元素变化了
if (target !== hoveredElement) {
if (hoveredElement) {
unhighlightElement(hoveredElement);
}
hoveredElement = target;
highlightElement(hoveredElement);
updateHoveredElementInfo(hoveredElement);
}
}
// 元素选择逻辑
document.addEventListener('keydown', function(e) {
if (e.key === 'Shift') {
shiftPressed = true;
}
});
document.addEventListener('keyup', function(e) {
if (e.key === 'Shift') {
shiftPressed = false;
}
});
function handleElementClick(e) {
e.preventDefault();
e.stopPropagation();
const target = e.target;
// 如果点击的是选择器界面本身,不处理
if (selectorOverlay.contains(target)) {
return;
}
if (shiftPressed) {
// 多选模式
if (!selectedElements.includes(target)) {
selectedElements.push(target);
highlightElement(target);
}
} else {
// 单选模式
selectedElements.forEach(el => unhighlightElement(el));
selectedElements = [target];
highlightElement(target);
}
// 更新选择器
if (selectedElements.length > 0) {
currentSelector = generateSelector(selectedElements[0], shiftPressed);
createConfirmationInterface();
}
}
// 创建确认界面
function createConfirmationInterface() {
document.body.removeChild(selectorOverlay);
document.removeEventListener('mouseover', handleElementHover);
document.removeEventListener('click', handleElementClick, true);
document.removeEventListener('keydown', handleKeyDown);
const overlay = document.createElement('div');
overlay.className = 'notion-importer-interface';
overlay.style.position = 'fixed';
overlay.style.top = '0';
overlay.style.left = '0';
overlay.style.width = '100%';
overlay.style.height = '100%';
overlay.style.backgroundColor = 'rgba(0,0,0,0.5)';
overlay.style.zIndex = '10000';
overlay.style.display = 'flex';
overlay.style.flexDirection = 'column';
overlay.style.alignItems = 'center';
overlay.style.justifyContent = 'center';
const container = document.createElement('div');
container.style.backgroundColor = 'white';
container.style.padding = '20px';
container.style.borderRadius = '12px';
container.style.width = '90%';
container.style.maxWidth = '500px';
container.style.maxHeight = '80vh';
container.style.overflowY = 'auto';
const title = document.createElement('h3');
title.textContent = `确认选择: ${propertyName}`;
title.style.marginTop = '0';
title.style.marginBottom = '15px';
title.style.color = PRIMARY_COLOR;
title.style.fontSize = '18px';
const selectedInfo = document.createElement('div');
selectedInfo.style.marginBottom = '20px';
selectedInfo.style.padding = '15px';
selectedInfo.style.backgroundColor = '#f9f9f9';
selectedInfo.style.borderRadius = '8px';
selectedInfo.style.fontSize = '16px';
if (selectedElements.length === 0) {
selectedInfo.innerHTML = '<p>未选择任何元素</p>';
} else {
let infoHTML = `<p style="margin-bottom: 10px;">已选择 ${selectedElements.length} 个元素:</p>`;
const allTexts = [];
selectedElements.forEach((el, index) => {
const text = cleanText(el.textContent?.trim().substring(0, 50)) || '空内容';
allTexts.push(text);
infoHTML += `<div style="margin-top: 8px;">
<strong>元素 ${index + 1}:</strong> ${text}
</div>`;
});
infoHTML += `<div style="margin-top: 15px;">
<strong>选择器:</strong> ${currentSelector}
</div>`;
if (selectedElements.length > 1) {
infoHTML += `<div style="margin-top: 15px;">
<strong>合并文本:</strong> ${allTexts.join(', ')}
</div>`;
}
selectedInfo.innerHTML = infoHTML;
}
const inputGroup = document.createElement('div');
inputGroup.style.display = 'flex';
inputGroup.style.flexDirection = 'column';
inputGroup.style.marginBottom = '20px';
const selectorLabel = document.createElement('label');
selectorLabel.textContent = '选择器 (可编辑)';
selectorLabel.style.marginBottom = '8px';
selectorLabel.style.fontSize = '16px';
const selectorInput = document.createElement('input');
selectorInput.type = 'text';
selectorInput.value = currentSelector;
selectorInput.placeholder = '点击选择按钮选择元素或直接输入选择器';
selectorInput.style.padding = '12px';
selectorInput.style.border = '1px solid #ddd';
selectorInput.style.borderRadius = '6px';
selectorInput.style.fontSize = '16px';
selectorInput.style.marginBottom = '10px';
const selectButton = document.createElement('button');
selectButton.textContent = '重新选择';
selectButton.style.padding = '12px';
selectButton.style.fontSize = '16px';
selectButton.style.backgroundColor = SECONDARY_COLOR;
selectButton.style.color = 'white';
selectButton.style.border = 'none';
selectButton.style.borderRadius = '6px';
selectButton.style.cursor = 'pointer';
selectButton.onclick = function() {
document.body.removeChild(overlay);
createSelectorInterface(propertyName, callback);
};
inputGroup.appendChild(selectorLabel);
inputGroup.appendChild(selectorInput);
inputGroup.appendChild(selectButton);
const buttonContainer = document.createElement('div');
buttonContainer.style.display = 'flex';
buttonContainer.style.justifyContent = 'flex-end';
buttonContainer.style.marginTop = '20px';
buttonContainer.style.gap = '10px';
const confirmButton = document.createElement('button');
confirmButton.textContent = '确认';
confirmButton.style.padding = '12px 20px';
confirmButton.style.fontSize = '16px';
confirmButton.style.backgroundColor = PRIMARY_COLOR;
confirmButton.style.color = 'white';
confirmButton.style.border = 'none';
confirmButton.style.borderRadius = '6px';
confirmButton.style.cursor = 'pointer';
const cancelButton = document.createElement('button');
cancelButton.textContent = '取消';
cancelButton.style.padding = '12px 20px';
cancelButton.style.fontSize = '16px';
cancelButton.style.backgroundColor = DANGER_COLOR;
cancelButton.style.color = 'white';
cancelButton.style.border = 'none';
cancelButton.style.borderRadius = '6px';
cancelButton.style.cursor = 'pointer';
buttonContainer.appendChild(cancelButton);
buttonContainer.appendChild(confirmButton);
container.appendChild(title);
container.appendChild(selectedInfo);
container.appendChild(inputGroup);
container.appendChild(buttonContainer);
overlay.appendChild(container);
document.body.appendChild(overlay);
// 确认选择
confirmButton.onclick = function() {
const finalSelector = selectorInput.value.trim();
if (!finalSelector) {
alert('请提供有效的选择器');
return;
}
// 清除高亮
selectedElements.forEach(el => unhighlightElement(el));
// 如果是多选模式,合并文本内容
let result;
if (selectedElements.length > 1) {
const allTexts = selectedElements.map(el => cleanText(getTextContent(el))).filter(t => t);
result = allTexts.join(', ');
} else {
result = cleanText(getTextContent(selectedElements[0]));
}
// 执行回调
if (callback) {
callback(finalSelector, result);
}
// 关闭界面
document.body.removeChild(overlay);
// 恢复悬浮按钮
const floatingBtn = document.getElementById('notion-importer-floating-btn');
if (floatingBtn) floatingBtn.style.display = 'flex';
};
// 取消选择
cancelButton.onclick = function() {
// 清除高亮
selectedElements.forEach(el => unhighlightElement(el));
// 关闭界面
document.body.removeChild(overlay);
// 恢复悬浮按钮
const floatingBtn = document.getElementById('notion-importer-floating-btn');
if (floatingBtn) floatingBtn.style.display = 'flex';
};
// 点击外部关闭
overlay.onclick = function(e) {
if (e.target === overlay) {
// 清除高亮
selectedElements.forEach(el => unhighlightElement(el));
document.body.removeChild(overlay);
// 恢复悬浮按钮
const floatingBtn = document.getElementById('notion-importer-floating-btn');
if (floatingBtn) floatingBtn.style.display = 'flex';
}
};
}
// 添加事件监听器
document.addEventListener('mouseover', handleElementHover);
document.addEventListener('click', handleElementClick, true);
// 添加键盘快捷键 (ESC取消)
function handleKeyDown(e) {
if (e.key === 'Escape') {
// 取消选择
selectedElements.forEach(el => unhighlightElement(el));
document.body.removeChild(selectorOverlay);
document.removeEventListener('mouseover', handleElementHover);
document.removeEventListener('click', handleElementClick, true);
document.removeEventListener('keydown', handleKeyDown);
// 恢复悬浮按钮
const floatingBtn = document.getElementById('notion-importer-floating-btn');
if (floatingBtn) floatingBtn.style.display = 'flex';
}
}
document.addEventListener('keydown', handleKeyDown);
}
// 创建漫画导入界面
function createMangaImportInterface(isUpdate = false, pageId = null) {
const domain = window.location.hostname;
const currentUrl = window.location.href;
const selectors = getSiteSelectors(domain);
const isConfigured = Object.keys(selectors).length > 0;
const overlay = document.createElement('div');
overlay.className = 'notion-importer-interface';
overlay.style.position = 'fixed';
overlay.style.top = '0';
overlay.style.left = '0';
overlay.style.width = '100%';
overlay.style.height = '100%';
overlay.style.backgroundColor = 'rgba(0,0,0,0.5)';
overlay.style.zIndex = '10000';
overlay.style.display = 'flex';
overlay.style.flexDirection = 'column';
overlay.style.alignItems = 'center';
overlay.style.justifyContent = 'center';
const container = document.createElement('div');
container.style.backgroundColor = 'white';
container.style.padding = '20px';
container.style.borderRadius = '12px';
container.style.width = '90%';
container.style.maxWidth = '500px';
container.style.maxHeight = '80vh';
container.style.overflowY = 'auto';
const title = document.createElement('h3');
title.textContent = isUpdate ? '更新漫画信息' : (isConfigured ? '导入漫画到Notion' : '配置漫画选择器');
title.style.marginTop = '0';
title.style.marginBottom = '15px';
title.style.color = PRIMARY_COLOR;
title.style.fontSize = '18px';
const form = document.createElement('div');
form.style.display = 'flex';
form.style.flexDirection = 'column';
form.style.gap = '15px';
// 创建表单组
function createFormGroup(label, currentSelector, onSelect) {
const group = document.createElement('div');
group.style.display = 'flex';
group.style.flexDirection = 'column';
const labelElement = document.createElement('label');
labelElement.textContent = label;
labelElement.style.marginBottom = '8px';
labelElement.style.fontSize = '16px';
const input = document.createElement('input');
input.type = 'text';
input.value = currentSelector || '';
input.placeholder = '点击选择按钮选择元素或直接输入选择器';
input.style.padding = '12px';
input.style.border = '1px solid #ddd';
input.style.borderRadius = '6px';
input.style.fontSize = '16px';
input.style.marginBottom = '10px';
const selectButton = document.createElement('button');
selectButton.textContent = '选择';
selectButton.style.padding = '12px';
selectButton.style.fontSize = '16px';
selectButton.style.backgroundColor = PRIMARY_COLOR;
selectButton.style.color = 'white';
selectButton.style.border = 'none';
selectButton.style.borderRadius = '6px';
selectButton.style.cursor = 'pointer';
selectButton.onclick = function(e) {
e.stopPropagation();
// 隐藏整个导入界面
overlay.style.display = 'none';
createSelectorInterface(label, function(selector, value) {
// 选择完成后恢复导入界面
overlay.style.display = 'flex';
input.value = selector;
if (onSelect) onSelect(selector);
});
};
group.appendChild(labelElement);
group.appendChild(input);
group.appendChild(selectButton);
return group;
}
// 漫画名
const nameGroup = createFormGroup('漫画名', selectors['漫画名'] || '', (selector) => {
selectors['漫画名'] = selector;
saveSiteSelectors(domain, selectors);
});
// 作者
const authorGroup = createFormGroup('作者', selectors['作者'] || '', (selector) => {
selectors['作者'] = selector;
saveSiteSelectors(domain, selectors);
});
// 简介
const descriptionGroup = createFormGroup('简介', selectors['简介'] || '', (selector) => {
selectors['简介'] = selector;
saveSiteSelectors(domain, selectors);
});
// 最新章节
const latestChapterGroup = createFormGroup('最新章节', selectors['最新章节'] || '', (selector) => {
selectors['最新章节'] = selector;
saveSiteSelectors(domain, selectors);
});
// 更新状态
const statusGroup = createFormGroup('更新状态', selectors['更新状态'] || '', (selector) => {
selectors['更新状态'] = selector;
saveSiteSelectors(domain, selectors);
});
// 封面
const coverGroup = createFormGroup('封面', selectors['封面'] || '', (selector) => {
selectors['封面'] = selector;
saveSiteSelectors(domain, selectors);
});
// 追更
const trackingGroup = createFormGroup('追更', selectors['追更'] || '', (selector) => {
selectors['追更'] = selector;
saveSiteSelectors(domain, selectors);
});
// 类型
const typeGroup = createFormGroup('类型', selectors['类型'] || '', (selector) => {
selectors['类型'] = selector;
saveSiteSelectors(domain, selectors);
});
// 别名
const aliasGroup = createFormGroup('别名', selectors['别名'] || '', (selector) => {
selectors['别名'] = selector;
saveSiteSelectors(domain, selectors);
});
// 地区
const regionGroup = createFormGroup('地区', selectors['地区'] || '', (selector) => {
selectors['地区'] = selector;
saveSiteSelectors(domain, selectors);
});
form.appendChild(nameGroup);
form.appendChild(authorGroup);
form.appendChild(descriptionGroup);
form.appendChild(latestChapterGroup);
form.appendChild(statusGroup);
form.appendChild(coverGroup);
form.appendChild(trackingGroup);
form.appendChild(typeGroup);
form.appendChild(aliasGroup);
form.appendChild(regionGroup);
const buttonContainer = document.createElement('div');
buttonContainer.style.display = 'flex';
buttonContainer.style.justifyContent = 'flex-end';
buttonContainer.style.marginTop = '20px';
buttonContainer.style.gap = '10px';
const importButton = document.createElement('button');
importButton.textContent = isUpdate ? '更新' : '导入';
importButton.style.padding = '12px 20px';
importButton.style.fontSize = '16px';
importButton.style.backgroundColor = PRIMARY_COLOR;
importButton.style.color = 'white';
importButton.style.border = 'none';
importButton.style.borderRadius = '6px';
importButton.style.cursor = 'pointer';
const cancelButton = document.createElement('button');
cancelButton.textContent = '取消';
cancelButton.style.padding = '12px 20px';
cancelButton.style.fontSize = '16px';
cancelButton.style.backgroundColor = DANGER_COLOR;
cancelButton.style.color = 'white';
cancelButton.style.border = 'none';
cancelButton.style.borderRadius = '6px';
cancelButton.style.cursor = 'pointer';
buttonContainer.appendChild(cancelButton);
buttonContainer.appendChild(importButton);
container.appendChild(title);
container.appendChild(form);
container.appendChild(buttonContainer);
overlay.appendChild(container);
document.body.appendChild(overlay);
// 导入/更新逻辑
importButton.onclick = async function() {
const config = getConfig();
if (!config.apiKey || !config.mangaDbId) {
alert('请先配置Notion API和数据库ID');
return;
}
try {
// 获取所有输入框的值
const inputs = container.querySelectorAll('input');
const selectorValues = {};
inputs.forEach(input => {
const label = input.closest('div').firstChild.textContent;
selectorValues[label] = input.value;
});
// 保存选择器配置
Object.keys(selectorValues).forEach(key => {
selectors[key] = selectorValues[key];
});
saveSiteSelectors(domain, selectors);
// 获取数据
const mangaData = {
'漫画名': selectorValues['漫画名'] ? getElementValue(selectorValues['漫画名']) : '',
'作者': selectorValues['作者'] ? getElementValue(selectorValues['作者']) : '',
'简介': selectorValues['简介'] ? getElementValue(selectorValues['简介']) : '',
'最新章节': selectorValues['最新章节'] ? getElementValue(selectorValues['最新章节']) : '',
'更新状态': selectorValues['更新状态'] ? getElementValue(selectorValues['更新状态']) : '',
'封面': getImageUrl(selectorValues['封面']), // 自动获取封面
'追更': selectorValues['追更'] ? getElementValue(selectorValues['追更']) : '',
'类型': selectorValues['类型'] ? getElementValue(selectorValues['类型']) : '',
'别名': selectorValues['别名'] ? getElementValue(selectorValues['别名']) : '',
'地区': selectorValues['地区'] ? getElementValue(selectorValues['地区']) : ''
};
const requestData = {
parent: { database_id: config.mangaDbId },
properties: {
'漫画名': {
title: [
{
text: {
content: mangaData['漫画名'] || '未命名'
}
}
]
},
'作者': {
rich_text: [
{
text: {
content: mangaData['作者'] || ''
}
}
]
},
'简介': {
rich_text: [
{
text: {
content: mangaData['简介'] || ''
}
}
]
},
'最新章节': {
rich_text: [
{
text: {
content: mangaData['最新章节'] || ''
}
}
]
},
'更新状态': {
select: {
name: mangaData['更新状态'] || '未知'
}
},
'追更': {
rich_text: [
{
text: {
content: mangaData['追更'] || ''
}
}
]
},
'类型': {
select: {
name: mangaData['类型'] || '其他'
}
},
'别名': {
rich_text: [
{
text: {
content: mangaData['别名'] || ''
}
}
]
},
'地区': {
select: {
name: mangaData['地区'] || '其他'
}
}
},
cover: mangaData['封面'] ? {
type: "external",
external: {
url: mangaData['封面']
}
} : null
};
let response;
if (isUpdate && pageId) {
// 更新现有页面
delete requestData.parent; // 更新时不需要parent
response = await updateNotionPage(pageId, requestData);
} else {
// 创建新页面
response = await notionApiRequest('pages', requestData);
}
if (response && response.id) {
// 保存已导入页面
saveImportedPage(currentUrl, response.id);
// 更新悬浮按钮状态
createFloatingButton(true);
// 显示成功通知
GM_notification({
text: isUpdate ? '更新成功!' : '导入成功!',
title: 'Notion导入器',
image: 'https://www.notion.so/images/favicon.ico'
});
document.body.removeChild(overlay);
} else {
alert(isUpdate ? '更新失败: ' + (response.message || '未知错误') : '导入失败: ' + (response.message || '未知错误'));
}
} catch (error) {
alert((isUpdate ? '更新出错: ' : '导入出错: ') + error.message);
console.error('Notion API错误:', error);
}
};
// 取消
cancelButton.onclick = function() {
document.body.removeChild(overlay);
// 恢复悬浮按钮
const floatingBtn = document.getElementById('notion-importer-floating-btn');
if (floatingBtn) floatingBtn.style.display = 'flex';
};
// 点击外部关闭
overlay.onclick = function(e) {
if (e.target === overlay) {
document.body.removeChild(overlay);
// 恢复悬浮按钮
const floatingBtn = document.getElementById('notion-importer-floating-btn');
if (floatingBtn) floatingBtn.style.display = 'flex';
}
};
}
// 初始化
function init() {
// 添加CSS样式
GM_addStyle(`
.notion-importer-highlighted {
outline: 2px solid ${PRIMARY_COLOR} !important;
outline-offset: 2px !important;
}
#notion-importer-floating-btn {
background-color: ${PRIMARY_COLOR} !important;
transition: transform 0.2s;
touch-action: manipulation;
}
#notion-importer-floating-btn:hover {
transform: scale(1.1);
background-color: ${SECONDARY_COLOR} !important;
}
.notion-importer-interface {
font-size: 16px;
}
.notion-importer-interface input,
.notion-importer-interface button,
.notion-importer-interface select {
font-size: 16px;
min-height: 44px;
}
.notion-importer-interface button {
padding: 12px;
margin: 5px 0;
}
.notion-importer-interface input {
padding: 12px;
margin-bottom: 10px;
}
@media (max-width: 768px) {
#notion-importer-floating-btn {
width: 60px !important;
height: 60px !important;
right: 15px !important;
bottom: 15px !important;
}
.notion-importer-interface {
width: 90% !important;
padding: 15px !important;
}
.notion-importer-interface h3 {
font-size: 18px !important;
}
.notion-importer-interface label {
font-size: 16px !important;
}
}
`);
// 检查是否已导入当前页面
const currentUrl = window.location.href;
const importedPages = getImportedPages();
const isImported = importedPages.hasOwnProperty(currentUrl);
// 创建悬浮按钮
const floatingBtn = createFloatingButton(isImported);
// 点击悬浮按钮
floatingBtn.addEventListener('click', function(e) {
e.stopPropagation();
if (isImported) {
// 如果已导入,询问是否更新
createConfirmDialog(
'此页面已导入过,是否要更新数据?',
function() {
// 更新数据
createMangaImportInterface(true, importedPages[currentUrl]);
},
function() {
// 取消操作
floatingBtn.style.display = 'flex';
}
);
} else {
// 首次导入
createMangaImportInterface();
}
});
// 点击外部隐藏菜单
document.addEventListener('click', function(e) {
const floatingBtn = document.getElementById('notion-importer-floating-btn');
if (floatingBtn && !floatingBtn.contains(e.target)) {
floatingBtn.style.display = 'flex';
}
});
}
// 启动脚本
init();
})();