// ==UserScript==
// @name 剪贴板图片上传lsky图床-Nodeseek
// @namespace http://tampermonkey.net/
// @version 1.6.4
// @description Upload clipboard images to a configurable image host, auto-fetch token if missing or expired
// @author You
// @match *://*.nodeseek.com/*
// @grant GM_xmlhttpRequest
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_registerMenuCommand
// @grant GM_addStyle
// @connect *
// @license MIT
// ==/UserScript==
(function () {
'use strict';
let config = {
baseUrl: GM_getValue('baseUrl', 'http://'),
token: GM_getValue('token', ''),
email: GM_getValue('email', ''),
password: GM_getValue('password', ''),
strategyId: GM_getValue('strategyId', 1)
};
function createSettingsPanel() {
let panel = document.createElement('div');
panel.id = 'gm-settings-panel';
panel.innerHTML = `
<div class="gm-panel">
<h2>lsky 图床 配置</h2>
<label>图床地址: <input id="gm-baseUrl" type="text" value="${config.baseUrl}"></label>
<label>邮箱: <input id="gm-email" type="text" value="${config.email}"></label>
<label>密码: <input id="gm-password" type="password" value="${config.password}"></label>
<label>策略 ID: <input id="gm-strategyId" type="number" value="${config.strategyId}"></label>
<button id="gm-save-settings">保存</button>
<button id="gm-close-settings">关闭</button>
</div>
`;
document.body.appendChild(panel);
document.getElementById('gm-save-settings').addEventListener('click', () => {
let baseUrl = document.getElementById('gm-baseUrl').value;
// Check if baseUrl doesn't start with "http://" or "https://"
if (!baseUrl.startsWith('http://') && !baseUrl.startsWith('https://')) {
baseUrl = 'http://' + baseUrl; // Automatically add "http://"
}
// Save the baseUrl along with other values
GM_setValue('baseUrl', baseUrl);
GM_setValue('email', document.getElementById('gm-email').value);
GM_setValue('password', document.getElementById('gm-password').value);
GM_setValue('strategyId', Number(document.getElementById('gm-strategyId').value));
// Update the config object
config.baseUrl = GM_getValue('baseUrl');
config.email = GM_getValue('email');
config.password = GM_getValue('password');
config.strategyId = GM_getValue('strategyId');
panel.remove();
showSuccessMessage("配置已保存!");
});
document.getElementById('gm-close-settings').addEventListener('click', () => {
panel.remove();
});
GM_addStyle(`
#gm-settings-panel {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.3);
z-index: 10000;
}
.gm-panel h2 { margin: 0 0 10px; font-size: 18px; }
.gm-panel label { display: block; margin-bottom: 10px; }
.gm-panel input { width: 100%; padding: 5px; margin-top: 5px; border: 1px solid #ddd; border-radius: 4px; }
.gm-panel button { margin-right: 10px; padding: 5px 10px; cursor: pointer; }
`);
}
GM_registerMenuCommand("⚙️ 配置 lsky 图床", createSettingsPanel);
async function getToken() {
return new Promise((resolve, reject) => {
GM_xmlhttpRequest({
method: 'POST',
url: `${config.baseUrl}/api/v1/tokens`,
headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' },
data: JSON.stringify({ email: config.email, password: config.password }),
onload: function (response) {
try {
let res = JSON.parse(response.responseText);
if (res.status && res.data?.token) {
GM_setValue('token', res.data.token);
config.token = res.data.token;
resolve(res.data.token);
} else {
reject(res.message || "获取 Token 失败");
}
} catch (e) {
reject("返回的不是有效的 JSON 响应:" + response.responseText);
}
},
onerror: () => reject("网络错误")
});
});
}
function showUploadModal() {
let modal = document.createElement('div');
modal.id = 'upload-modal';
modal.innerHTML = `
<div class="modal-content">
<div class="loader"></div>
<p>上传中,请稍等...</p>
</div>
`;
document.body.appendChild(modal);
GM_addStyle(`
#upload-modal {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background-color: rgba(0, 0, 0, 0.5);
padding: 20px;
border-radius: 8px;
z-index: 9999;
display: flex;
justify-content: center;
align-items: center;
}
.modal-content {
background-color: white;
padding: 20px;
border-radius: 8px;
text-align: center;
}
.loader {
border: 4px solid #f3f3f3;
border-top: 4px solid #3498db;
border-radius: 50%;
width: 50px;
height: 50px;
animation: spin 1s linear infinite;
margin: auto;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
`);
setTimeout(() => {
let modal = document.getElementById('upload-modal');
if (modal) {
modal.remove();
}
}, 10000);
}
function hideUploadModal() {
let modal = document.getElementById('upload-modal');
if (modal) {
modal.remove();
}
}
function showSuccessMessage(message) {
let successModal = document.createElement('div');
successModal.id = 'success-modal';
successModal.innerHTML = `
<div class="success-modal-content">
<p>${message}</p>
</div>
`;
document.body.appendChild(successModal);
GM_addStyle(`
#success-modal {
position: fixed;
top: 20px;
left: 50%;
transform: translateX(-50%);
background-color: rgba(0, 128, 0, 0.8);
padding: 10px 20px;
border-radius: 8px;
color: white;
z-index: 10000;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.3);
font-size: 16px;
}
#success-modal .success-modal-content {
text-align: center;
}
`);
setTimeout(() => {
let modal = document.getElementById('success-modal');
if (modal) {
modal.remove();
}
}, 3000);
}
function showFailureMessage(message) {
let failureModal = document.createElement('div');
failureModal.id = 'failure-modal';
failureModal.innerHTML = `
<div class="failure-modal-content">
<p>${message}</p>
</div>
`;
document.body.appendChild(failureModal);
GM_addStyle(`
#failure-modal {
position: fixed;
top: 20px;
left: 50%;
transform: translateX(-50%);
background-color: rgba(255, 0, 0, 0.8);
padding: 10px 20px;
border-radius: 8px;
color: white;
z-index: 10000;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.3);
font-size: 16px;
}
#failure-modal .failure-modal-content {
text-align: center;
}
`);
setTimeout(() => {
let modal = document.getElementById('failure-modal');
if (modal) {
modal.remove();
}
}, 3000);
}
document.addEventListener('paste', async (event) => {
let items = (event.clipboardData || event.originalEvent.clipboardData).items;
for (let item of items) {
if (item.type.indexOf('image') !== -1) {
// 弹出确认框,确认是否上传
let confirmUpload = window.confirm("确认上传图片吗?");
if (!confirmUpload) {
return; // 如果用户选择取消,退出上传流程
}
showUploadModal();
let file = item.getAsFile();
if (!config.token) {
await getToken().catch((error) => {
hideUploadModal();
console.error("获取 Token 失败");
showFailureMessage("获取 Token 失败");
return; // Early exit if token can't be fetched
});
}
// Proceed with image upload if the token is valid
uploadImage(file).then(insertText)
.catch((error) => {
console.error(error);
showFailureMessage("上传失败");
})
.finally(() => {
hideUploadModal(); // Ensure the upload modal is hidden when upload finishes or fails
});
}
}
});
async function uploadImage(file) {
return new Promise((resolve, reject) => {
let formData = new FormData();
formData.append('file', file);
GM_xmlhttpRequest({
method: 'POST',
url: `${config.baseUrl}/api/v1/upload`,
headers: { 'Authorization': `Bearer ${config.token}` },
data: formData,
onload: function (response) {
try {
let res = JSON.parse(response.responseText);
if (res.status && res.data?.links?.markdown) {
resolve(res.data.links.markdown);
showSuccessMessage("上传成功!");
} else {
reject(res.message || "上传失败");
}
} catch (e) {
reject("返回的不是有效的 JSON 响应:" + response.responseText);
}
},
onerror: () => reject("网络错误")
});
});
}
function insertText(text) {
let activeElement = document.activeElement;
if (activeElement && (activeElement.tagName === 'TEXTAREA' || activeElement.tagName === 'INPUT')) {
let start = activeElement.selectionStart;
let end = activeElement.selectionEnd;
activeElement.value = activeElement.value.substring(0, start) + text + activeElement.value.substring(end);
activeElement.selectionStart = activeElement.selectionEnd = start + text.length;
} else if (activeElement?.isContentEditable) {
document.execCommand('insertText', false, text);
}
}
})();