// ==UserScript==
// @name NS 简单图床快捷上传
// @namespace http://tampermonkey.net/
// @version 1.0
// @description 在 Nodeseek.com 的发送评论按钮旁添加图片上传功能,支持粘贴/拖拽上传,提示在右下角
// @author BreezeZhang
// @match https://*.nodeseek.com/*
// @grant GM_setValue
// @grant GM_getValue
// @grant unsafeWindow
// @connect nodeseek.com
// @license GPL-3.0
// ==/UserScript==
(function() {
'use strict';
let EASYIMAGE_API_URL = GM_getValue('EASYIMAGE_API_URL', '');
let EASYIMAGE_TOKEN = GM_getValue('EASYIMAGE_TOKEN', '');
if (!EASYIMAGE_API_URL || !EASYIMAGE_TOKEN) {
showConfigModal();
return;
}
initScript();
// 提取域名(仅用于日志和提示)
function extractDomain(url) {
try {
const urlObj = new URL(url);
return urlObj.hostname;
} catch (e) {
console.error('无效的 API URL:', url, e);
return '';
}
}
function showConfigModal(isUpdate = false) {
const modal = document.createElement('div');
modal.style.cssText = 'position:fixed;top:0;left:0;width:100%;height:100%;background-color:rgba(0,0,0,0.5);display:flex;justify-content:center;align-items:center;z-index:1000;';
const modalContent = document.createElement('div');
modalContent.style.cssText = 'background-color:white;padding:20px;border-radius:5px;width:300px;box-shadow:0 0 10px rgba(0,0,0,0.5);';
const title = document.createElement('h3');
title.textContent = isUpdate ? '更新配置' : '初始配置';
title.style.cssText = 'margin-bottom:15px;text-align:center';
const apiLabel = document.createElement('label');
apiLabel.textContent = 'EasyImages2.0 API URL: ';
apiLabel.style.cssText = 'display:block;margin-bottom:5px';
const apiInput = document.createElement('input');
apiInput.type = 'text';
apiInput.value = EASYIMAGE_API_URL || 'https://example.com/api/index.php';
apiInput.style.cssText = 'width:100%;padding:5px;margin-bottom:10px';
const tokenLabel = document.createElement('label');
tokenLabel.textContent = 'EasyImages2.0 Token: ';
tokenLabel.style.cssText = 'display:block;margin-bottom:5px';
const tokenInput = document.createElement('input');
tokenInput.type = 'text';
tokenInput.value = EASYIMAGE_TOKEN || '';
tokenInput.style.cssText = 'width:100%;padding:5px;margin-bottom:10px';
const buttonContainer = document.createElement('div');
buttonContainer.style.cssText = 'text-align:right';
const confirmBtn = document.createElement('button');
confirmBtn.textContent = '保存';
confirmBtn.style.cssText = 'padding:5px 15px;background-color:#4CAF50;color:white;border:none;border-radius:3px;cursor:pointer;margin-right:10px';
confirmBtn.addEventListener('click', () => {
const apiUrl = apiInput.value.trim();
const token = tokenInput.value.trim();
if (!apiUrl || !token) {
alert('API URL 和 Token 不能为空!');
return;
}
EASYIMAGE_API_URL = apiUrl;
EASYIMAGE_TOKEN = token;
GM_setValue('EASYIMAGE_API_URL', EASYIMAGE_API_URL);
GM_setValue('EASYIMAGE_TOKEN', EASYIMAGE_TOKEN);
document.body.removeChild(modal);
if (isUpdate) alert('配置更新成功!');
initScript();
});
const cancelBtn = document.createElement('button');
cancelBtn.textContent = '取消';
cancelBtn.style.cssText = 'padding:5px 15px;background-color:#f44336;color:white;border:none;border-radius:3px;cursor:pointer';
cancelBtn.addEventListener('click', () => {
document.body.removeChild(modal);
if (!isUpdate) alert('配置未完成,脚本将无法运行!');
});
buttonContainer.appendChild(confirmBtn);
buttonContainer.appendChild(cancelBtn);
modalContent.appendChild(title);
modalContent.appendChild(apiLabel);
modalContent.appendChild(apiInput);
modalContent.appendChild(tokenLabel);
modalContent.appendChild(tokenInput);
modalContent.appendChild(buttonContainer);
modal.appendChild(modalContent);
document.body.appendChild(modal);
modal.addEventListener('click', (e) => {
if (e.target === modal) return;
e.stopPropagation();
});
}
function initScript() {
createUploadButton();
setupPasteAndDrop();
addPlaceholder();
const observer = new MutationObserver(() => {
createUploadButton();
setupPasteAndDrop();
addPlaceholder();
});
observer.observe(document.body, { childList: true, subtree: true });
}
function createUploadButton() {
const topicSelect = document.querySelector('.topic-select');
if (!topicSelect || topicSelect.querySelector('.easyimage-upload-container')) return;
const submitBtn = topicSelect.querySelector('.submit.btn');
if (!submitBtn) return;
const container = document.createElement('div');
container.className = 'easyimage-upload-container';
container.style.cssText = 'margin:5px 0;display:inline-block';
const uploadButton = document.createElement('button');
uploadButton.textContent = '📷 上传图片';
uploadButton.style.cssText = 'padding:5px 10px;background-color:#4CAF50;color:white;border:none;border-radius:3px;cursor:pointer;margin-right:10px';
const updateButton = document.createElement('button');
updateButton.textContent = '⚙️ 更新配置';
updateButton.style.cssText = 'padding:5px 10px;background-color:#2196F3;color:white;border:none;border-radius:3px;cursor:pointer;margin-right:10px';
const fileInput = document.createElement('input');
fileInput.type = 'file';
fileInput.accept = 'image/*';
fileInput.style.display = 'none';
container.appendChild(uploadButton);
container.appendChild(updateButton);
container.appendChild(fileInput);
submitBtn.parentElement.insertBefore(container, submitBtn);
uploadButton.addEventListener('click', () => fileInput.click());
fileInput.addEventListener('change', () => {
if (fileInput.files.length === 0) return;
const file = fileInput.files[0];
uploadButton.textContent = '正在上传...';
uploadButton.disabled = true;
uploadImage(file, (success, result) => {
uploadButton.textContent = '📷 上传图片';
uploadButton.disabled = false;
if (success) insertMarkdown(result);
else alert('上传失败:' + result);
});
});
updateButton.addEventListener('click', () => showConfigModal(true));
}
function setupPasteAndDrop() {
const editor = document.querySelector('.CodeMirror');
if (!editor) return;
const cm = editor.CodeMirror;
if (!cm) return;
if (editor.dataset.pasteDropSetup) return;
editor.dataset.pasteDropSetup = 'true';
cm.on('paste', (cmInstance, event) => {
const items = (event.clipboardData || window.clipboardData).items;
for (let i = 0; i < items.length; i++) {
if (items[i].type.indexOf('image') === 0) {
event.preventDefault();
const file = items[i].getAsFile();
uploadImage(file, (success, result) => {
if (success) insertMarkdown(result);
else alert('上传失败:' + result);
});
break;
}
}
});
let uploadInProgress = false;
editor.addEventListener('dragover', (event) => {
event.preventDefault();
event.dataTransfer.dropEffect = 'copy';
});
editor.addEventListener('drop', (event) => {
event.preventDefault();
event.stopPropagation();
if (uploadInProgress) {
console.log('Drop event skipped: upload in progress');
return;
}
const files = event.dataTransfer.files;
if (files.length > 0 && files[0].type.indexOf('image') === 0) {
const file = files[0];
console.log('Starting upload for file:', file.name);
uploadInProgress = true;
uploadImage(file, (success, result) => {
uploadInProgress = false;
if (success) {
insertMarkdown(result);
console.log('Upload completed for:', file.name);
} else {
alert('上传失败:' + result);
console.log('Upload failed for:', file.name, result);
}
});
} else {
console.log('No image file dropped');
}
});
}
function addPlaceholder() {
const editor = document.querySelector('.CodeMirror');
if (!editor || editor.querySelector('.easyimage-placeholder')) return;
const placeholder = document.createElement('div');
placeholder.className = 'easyimage-placeholder';
placeholder.textContent = '拖拽或粘贴图片可以上传图片';
placeholder.style.cssText = 'position:absolute;bottom:10px;right:10px;color:#aaa;font-size:12px;pointer-events:none;z-index:1';
editor.appendChild(placeholder);
const cm = editor.CodeMirror;
if (cm) {
cm.on('change', () => {
placeholder.style.display = cm.getValue().trim() ? 'none' : 'block';
});
}
}
function uploadImage(file, callback) {
const fileKey = `${file.name}-${file.size}-${file.lastModified}`;
if (window.uploadedFiles && window.uploadedFiles[fileKey]) {
console.log('File already uploaded:', fileKey);
return callback(true, window.uploadedFiles[fileKey]);
}
const apiDomain = extractDomain(EASYIMAGE_API_URL);
console.log('Uploading to:', EASYIMAGE_API_URL, 'Domain:', apiDomain, 'with token:', EASYIMAGE_TOKEN);
const formData = new FormData();
formData.append('image', file);
formData.append('token', EASYIMAGE_TOKEN);
// 使用 unsafeWindow.fetch 发起请求,绕过 Tampermonkey 的 @connect 限制
unsafeWindow.fetch(EASYIMAGE_API_URL, {
method: 'POST',
body: formData,
timeout: 10000 // 10 秒超时(fetch 本身不支持 timeout,需手动实现)
})
.then(response => {
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
return response.json();
})
.then(result => {
console.log('Response:', result);
if (result.result === 'success' && result.code === 200) {
if (!window.uploadedFiles) window.uploadedFiles = {};
window.uploadedFiles[fileKey] = result.url;
callback(true, result.url);
} else {
callback(false, result.message || '服务器返回错误');
}
})
.catch(error => {
console.error('Fetch error:', error);
callback(false, '网络错误:' + error.message);
});
}
function insertMarkdown(url) {
const markdown = ``;
const editor = document.querySelector('.CodeMirror');
if (editor) {
const cm = editor.CodeMirror;
if (cm) {
cm.replaceRange(markdown, cm.getCursor());
cm.focus();
showSuccessMessage();
}
}
}
function showSuccessMessage() {
const topicSelect = document.querySelector('.topic-select');
const successMsg = document.createElement('div');
successMsg.textContent = '🎉 图片上传成功!';
successMsg.style.cssText = 'color:#4CAF50;margin:5px 0';
topicSelect.insertBefore(successMsg, topicSelect.firstChild);
setTimeout(() => successMsg.remove(), 2000);
}
})();