// ==UserScript==
// @name 图床上传脚本
// @namespace http://21zys.com/
// @version 1.3.6
// @description 在右下角添加悬浮球,点击对应图床按钮弹出上传表单对话框(目前支持 imgURL/SMMS 图床)
// @match *://*/*
// @author 21zys
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_setClipboard
// @require https://cdnjs.cloudflare.com/ajax/libs/dayjs/1.11.13/dayjs.min.js
// @require https://cdnjs.cloudflare.com/ajax/libs/uuid/8.3.2/uuid.min.js
// @require https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.2.0/crypto-js.min.js
// @connect sm.ms
// @connect smms.app
// @grant GM_xmlhttpRequest
// @license MIT
// ==/UserScript==
(function() {
'use strict';
// 检查当前窗口是否为主窗口,如果是iframe则不执行
if (window !== window.top) {
return; // 如果是在iframe中,直接退出脚本执行
}
// 加载时从localStorage中获取悬浮球和对话框的位置
var savedPosition = JSON.parse(localStorage.getItem('floatingBallPosition')) || { right: '20px', bottom: '20px' };
const myJson = JSON;
const imgUrlDialog = initImgUrlDialog();
const smmsDialog = initSmmsDialog();
// 创建悬浮球容器
var floatingContainer = document.createElement('div');
floatingContainer.style.position = 'fixed';
floatingContainer.style.right = savedPosition.right;
floatingContainer.style.bottom = savedPosition.bottom;
floatingContainer.style.left = savedPosition.left || 'auto';
floatingContainer.style.top = savedPosition.top || 'auto';
floatingContainer.style.padding = '25px';
floatingContainer.style.zIndex = '99999';
// 创建悬浮球
var floatingBall = document.createElement('div');
floatingBall.style.width = '50px';
floatingBall.style.height = '50px';
floatingBall.style.borderRadius = '50%';
floatingBall.style.backgroundColor = '#007bff';
floatingBall.style.color = '#fff';
floatingBall.style.textAlign = 'center';
floatingBall.style.lineHeight = '50px';
floatingBall.style.cursor = 'pointer';
// floatingBall.style.boxShadow = '0 4px 8px rgba(0, 0, 0, 0.2)';
floatingBall.style.fontSize = '24px';
floatingBall.style.zIndex = '99999';
floatingBall.innerHTML = '+';
floatingBall.style.userSelect = 'none';
floatingBall.style.webkitUserSelect = 'none'; // Chrome, Safari, Edge
floatingBall.style.mozUserSelect = 'none'; // Firefox
floatingBall.style.msUserSelect = 'none'; // Internet Explorer/Edge
floatingBall.style.backdropFilter = 'blur(6px)';
floatingBall.style.webkitBackdropFilter = 'blur(6px)';
floatingBall.style.boxShadow = 'rgba(90, 90, 90, 1) 2px 2px 9px 0px';
// floatingBall.style.webkitBoxShadow = 'rgba(142, 142, 142, .5) 6px 6px 6px 0px';
// floatingBall.style.borderRadius = '5px';
// floatingBall.style.webkitBorderRadius = '.5rem';
floatingBall.style.border = 'none';
floatingContainer.appendChild(floatingBall);
document.body.appendChild(floatingContainer);
let isDraggingBall = false;
let startX, startY;
// 添加拖拽功能并保存悬浮球位置
floatingContainer.addEventListener('mousedown', function(e) {
startX = e.clientX;
startY = e.clientY;
var offsetX = e.clientX - floatingContainer.getBoundingClientRect().left;
var offsetY = e.clientY - floatingContainer.getBoundingClientRect().top;
function onMouseMove(e) {
const dx = e.clientX - startX;
const dy = e.clientY - startY;
if (!isDraggingBall && (Math.abs(dx) > 5 || Math.abs(dy) > 5)) { // 设置拖拽灵敏度阈值为5px
isDraggingBall = true;
}
if (isDraggingBall) {
var newLeft = e.clientX - offsetX;
var newTop = e.clientY - offsetY;
floatingContainer.style.left = newLeft + 'px';
floatingContainer.style.top = newTop + 'px';
floatingContainer.style.right = 'auto';
floatingContainer.style.bottom = 'auto';
// 保存位置到localStorage
localStorage.setItem('floatingBallPosition', JSON.stringify({
left: floatingContainer.style.left,
top: floatingContainer.style.top
}));
}
}
function onMouseUp() {
document.removeEventListener('mousemove', onMouseMove);
document.removeEventListener('mouseup', onMouseUp);
setTimeout(() => isDraggingBall = false, 100); // 延迟100ms重置拖拽状态
}
document.addEventListener('mousemove', onMouseMove);
document.addEventListener('mouseup', onMouseUp);
});
// 创建imgUrl按钮
var imgUrlButton = document.createElement('div');
imgUrlButton.style.position = 'relative';
imgUrlButton.style.bottom = '90px';
imgUrlButton.style.left = '35px';
imgUrlButton.style.width = '50px';
imgUrlButton.style.height = '50px';
imgUrlButton.style.background = "url('https://www.imgurl.org/favicon.ico') no-repeat center center";
imgUrlButton.style.backgroundSize = "50% 50%";
imgUrlButton.style.backgroundColor = 'rgba(0, 0, 0, 0)';
imgUrlButton.style.cursor = 'pointer';
imgUrlButton.style.display = 'none';
imgUrlButton.style.zIndex = '99999';
floatingBall.appendChild(imgUrlButton);
var smmsButton = document.createElement('div');
smmsButton.style.position = 'relative';
smmsButton.style.bottom = '90px';
smmsButton.style.left = '50px';
smmsButton.style.width = '50px';
smmsButton.style.height = '50px';
smmsButton.style.background = "url('https://smms.app/favicon-32x32.png') no-repeat center center";
smmsButton.style.backgroundSize = "50% 50%";
smmsButton.style.backgroundColor = 'rgba(0, 0, 0, 0)';
smmsButton.style.cursor = 'pointer';
smmsButton.style.display = 'none';
imgUrlButton.style.zIndex = '99999';
floatingBall.appendChild(smmsButton);
// 鼠标移入悬浮球时,显示小按钮
floatingContainer.addEventListener('mouseenter', function() {
imgUrlButton.style.display = 'block';
smmsButton.style.display = 'block';
});
// 鼠标移出悬浮球时,隐藏小按钮
floatingContainer.addEventListener('mouseleave', function() {
imgUrlButton.style.display = 'none'
smmsButton.style.display = 'none'
});
// 点击小悬浮球按钮,打开对话框
imgUrlButton.addEventListener('click', function() {
openDialog(imgUrlDialog);
});
// 点击小悬浮球按钮,打开对话框
smmsButton.addEventListener('click', function() {
openDialog(smmsDialog);
});
// 打开对话框的函数,带淡入效果
function openDialog(dialog) {
dialog.style.display = 'block';
setTimeout(function() {
dialog.style.opacity = '1';
}, 10);
}
// 关闭对话框的函数,带淡出效果
function closeDialog(dialog) {
dialog.style.opacity = '0';
setTimeout(function() {
dialog.style.display = 'none';
}, 300);
}
// 4.1 imgURL 图床对话框元素初始化
function initImgUrlDialog() {
// 使用统一的存储键名,确保在所有域名下都共享数据
const GLOBAL_STORAGE_KEY = 'globalImgUrlUploadData';
const DIALOG_POSITION = 'imgUrlDialogPosition';
// 从localStorage中获取全局持久化数据
const globalData = JSON.parse(GM_getValue(GLOBAL_STORAGE_KEY, null) || localStorage.getItem(GLOBAL_STORAGE_KEY)) || {
uid: '您的UID',
token: '您的Token',
uploadDate: new Date().toISOString().split('T')[0],
uploadCount: 0,
selectedTab: 'imgURL',
selectedAlbumId: 'default',
albumList: []
};
const today = new Date().toISOString().split('T')[0];
// 如果日期已变,重置上传数量
if (globalData.uploadDate !== today) {
globalData.uploadDate = today;
globalData.uploadCount = 0;
saveGlobalData();
}
function saveGlobalData() {
GM_setValue(GLOBAL_STORAGE_KEY, JSON.stringify(globalData));
localStorage.setItem(GLOBAL_STORAGE_KEY, JSON.stringify(globalData));
}
var savedDialogPosition = JSON.parse(localStorage.getItem(DIALOG_POSITION)) || null;
// 创建对话框主体,绑定拖拽功能
var dialog = document.createElement('div');
dialog.style.position = 'fixed';
// 默认居中对话框
if (savedDialogPosition) {
dialog.style.left = savedDialogPosition.left;
dialog.style.top = savedDialogPosition.top;
dialog.style.transform = 'none';
} else {
dialog.style.left = '50%';
dialog.style.top = '50%';
dialog.style.transform = 'translate(-50%, -50%)';
}
dialog.style.width = '400px';
dialog.style.padding = '20px';
dialog.style.backgroundColor = 'rgba(255, 255, 255, 0.8)';
dialog.style.borderRadius = '12px';
dialog.style.backdropFilter = 'blur(10px)';
dialog.style.boxShadow = '0 8px 32px rgba(0, 0, 0, 0.37)';
dialog.style.display = 'none';
dialog.style.opacity = '0';
dialog.style.zIndex = '999999';
dialog.style.fontFamily = 'Arial, sans-serif';
dialog.style.transition = 'opacity 0.3s ease';
document.body.appendChild(dialog);
let isDraggingDialog = false;
dialog.addEventListener('mousedown', function(e) {
if (e.target !== dialog) return; // 只允许在对话框的直接点击区域触发拖拽
var offsetX = e.clientX - dialog.getBoundingClientRect().left;
var offsetY = e.clientY - dialog.getBoundingClientRect().top;
// 允许在边缘20px内触发拖拽
const edgeThreshold = 25;
if (offsetX < edgeThreshold || offsetX > dialog.clientWidth - edgeThreshold ||
offsetY < edgeThreshold || offsetY > dialog.clientHeight - edgeThreshold) {
function onMouseMove(e) {
isDraggingDialog = true;
var newLeft = e.clientX - offsetX;
var newTop = e.clientY - offsetY;
dialog.style.left = newLeft + 'px';
dialog.style.top = newTop + 'px';
dialog.style.transform = 'none';
// 保存对话框位置到localStorage
localStorage.setItem(DIALOG_POSITION, JSON.stringify({
left: dialog.style.left,
top: dialog.style.top
}));
}
function onMouseUp() {
document.removeEventListener('mousemove', onMouseMove);
document.removeEventListener('mouseup', onMouseUp);
setTimeout(() => isDraggingDialog = false, 100); // 延迟100ms重置拖拽状态
}
// 将监听器绑定到document, 确保拖拽行为不会中断
document.addEventListener('mousemove', onMouseMove);
document.addEventListener('mouseup', onMouseUp);
}
});
// 创建关闭按钮(X图标)
var closeButton = document.createElement('span');
closeButton.innerHTML = '×';
closeButton.style.position = 'absolute';
closeButton.style.top = '10px';
closeButton.style.right = '10px';
closeButton.style.cursor = 'pointer';
closeButton.style.fontSize = '20px';
closeButton.style.color = '#333';
closeButton.addEventListener('click', function() {
closeDialog(dialog);
});
dialog.appendChild(closeButton);
// 创建表单
var form = document.createElement('form');
form.enctype = 'multipart/form-data';
form.method = 'post';
form.action = 'https://www.imgurl.org/api/v2/upload';
form.id = 'upform';
form.style.display = 'grid';
form.style.gap = '10px';
// 创建UID字段
var uidLabel = document.createElement('label');
uidLabel.innerText = 'UID:';
uidLabel.style.fontWeight = 'bold';
uidLabel.style.color = '#333';
uidLabel.style.display = 'inline-block';
uidLabel.style.minHeight = 'none';
uidLabel.style.padding = 'none';
var uidInput = document.createElement('input');
uidInput.type = 'text';
uidInput.name = 'uid';
uidInput.value = globalData.uid ? globalData.uid : '';
uidInput.style.padding = '8px';
uidInput.style.border = '1px solid #ccc';
uidInput.style.borderRadius = '4px';
uidInput.style.width = 'auto';
uidInput.style.maxWidth = 'none';
form.appendChild(uidLabel);
form.appendChild(uidInput);
// 创建Token字段
var tokenLabel = document.createElement('label');
tokenLabel.innerText = 'Token:';
tokenLabel.style.fontWeight = 'bold';
tokenLabel.style.color = '#333';
tokenLabel.style.display = 'inline-block';
tokenLabel.style.minHeight = 'none';
tokenLabel.style.padding = 'none';
var tokenInput = document.createElement('input');
tokenInput.type = 'text';
tokenInput.name = 'token';
tokenInput.value = globalData.token ? globalData.token : '';
tokenInput.style.padding = '8px';
tokenInput.style.border = '1px solid #ccc';
tokenInput.style.borderRadius = '4px';
tokenInput.style.width = 'auto';
tokenInput.style.maxWidth = 'none';
form.appendChild(tokenLabel);
form.appendChild(tokenInput);
// 创建刷新相册按钮
var albumSelectContainer = document.createElement('div');
albumSelectContainer.style.marginTop = '10px';
albumSelectContainer.style.textAlign = 'center';
var albumBtn = document.createElement('button');
albumBtn.type = 'button';
albumBtn.innerText = '刷新相册';
albumBtn.style.padding = '8px 16px';
albumBtn.style.border = '1px solid #ccc';
albumBtn.style.borderRadius = '4px';
albumBtn.style.cursor = 'pointer';
albumBtn.style.backgroundColor = '#f8f9fa';
albumSelectContainer.appendChild(albumBtn);
// 创建相册下拉列表(表单外)
var albumSelect = document.createElement('select');
albumSelect.style.width= '120px';
albumSelect.style.height = '34px';
albumSelect.style.marginTop = '-3px';
albumSelect.style.marginRight = '3px';
albumSelect.style.textAlign = 'center';
albumSelect.style.border = '1px solid #ccc';
albumSelect.style.borderRadius = '4px';
albumSelect.style.overflow = 'hidden';
albumSelectContainer.appendChild(albumSelect);
// 创建水印文本输入框
var waterInput = document.createElement('input');
waterInput.type = 'text';
waterInput.name = 'water';
waterInput.style.width= '120px';
waterInput.style.height = '34px';
waterInput.style.marginTop = '-3px';
waterInput.style.textAlign = 'left';
waterInput.style.border = '1px solid #ccc';
waterInput.style.borderRadius = '4px';
waterInput.placeholder = '请输入水印文本';
waterInput.value = globalData.water ? globalData.water : '';
albumSelectContainer.appendChild(waterInput);
dialog.appendChild(albumSelectContainer); // 将相册下拉列表放在表单外
// 加载持久化相册列表和选择
loadAlbumList();
// 刷新相册列表
albumBtn.addEventListener('click', function() {
fetchAlbums();
});
albumSelect.addEventListener('change', function() {
globalData.selectedAlbumId = albumSelect.value;
saveGlobalData()
});
function loadAlbumList() {
albumSelect.innerHTML = ''; // 清空下拉列表
var defaultOption = document.createElement('option');
defaultOption.value = 'default';
defaultOption.textContent = '默认相册';
albumSelect.appendChild(defaultOption);
if (globalData.albumList.length > 0) {
globalData.albumList.forEach(function(album) {
var option = document.createElement('option');
option.value = album.album_id;
option.textContent = album.name;
albumSelect.appendChild(option);
});
}
// 选择持久化的相册
albumSelect.value = globalData.selectedAlbumId;
}
function fetchAlbums() {
var xhr = new XMLHttpRequest();
xhr.open('POST', 'https://www.imgurl.org/api/v2/albums', true);
// 构造FormData对象,将UID和Token放入其中
var formData = new FormData();
formData.append('uid', uidInput.value);
formData.append('token', tokenInput.value);
xhr.onreadystatechange = function() {
if (xhr.readyState === 4 && xhr.status === 200) {
var response = JSON.parse(xhr.responseText);
if (response && response.data && response.data.length) {
globalData.albumList = response.data;
saveGlobalData() // 持久化保存相册列表
loadAlbumList(); // 重新加载相册列表
} else {
albumSelect.innerHTML = '<option value="">未找到相册</option>';
}
} else if (xhr.readyState === 4) {
albumSelect.innerHTML = '<option value="">获取相册失败</option>';
}
};
// 发送POST请求
xhr.send(formData);
}
// 创建文件上传输入框
var fileLabel = document.createElement('label');
fileLabel.innerText = '选择文件:';
fileLabel.style.fontWeight = 'bold';
fileLabel.style.color = '#333';
fileLabel.style.display = 'inline-block';
fileLabel.style.minHeight = 'none';
fileLabel.style.padding = 'none';
var fileInput = document.createElement('input');
fileInput.type = 'file';
fileInput.name = 'file';
fileInput.style.padding = '8px';
fileInput.style.border = '1px solid #ccc';
fileInput.style.borderRadius = '4px';
fileInput.style.width = 'auto';
fileInput.style.maxWidth = 'none';
form.appendChild(fileLabel);
form.appendChild(fileInput);
// 上传和清空按钮容器
var buttonContainer = document.createElement('div');
buttonContainer.style.marginTop = '10px';
buttonContainer.style.textAlign = 'right';
// 创建今日上传数量label
var uploadCountLabel = document.createElement('label');
uploadCountLabel.textContent = `今日已上传 ${globalData.uploadCount} 张图片`;
uploadCountLabel.style.fontSize = '1rem';
uploadCountLabel.style.fontWeight = 'bold';
uploadCountLabel.style.color = 'rgb(51, 51, 51)';
uploadCountLabel.style.marginRight = '10px';
uploadCountLabel.style.display = 'inline-block';
uploadCountLabel.style.minHeight = 'none';
uploadCountLabel.style.padding = 'none';
buttonContainer.appendChild(uploadCountLabel);
// 创建上传按钮
var uploadBtn = document.createElement('input');
uploadBtn.type = 'submit';
uploadBtn.id = 'upload-btn';
uploadBtn.value = '开始上传';
uploadBtn.className = 'upload-btn';
uploadBtn.style.width = 'auto';
uploadBtn.style.maxWidth = 'none';
uploadBtn.style.padding = '8px 16px';
uploadBtn.style.border = 'none';
uploadBtn.style.backgroundColor = '#007bff';
uploadBtn.style.color = '#fff';
uploadBtn.style.borderRadius = '4px';
uploadBtn.style.cursor = 'pointer';
uploadBtn.style.marginRight = '10px';
uploadBtn.style.transition = 'background-color 0.3s ease';
uploadBtn.addEventListener('mouseover', function() {
uploadBtn.style.backgroundColor = '#0056b3';
});
uploadBtn.addEventListener('mouseout', function() {
uploadBtn.style.backgroundColor = '#007bff';
});
buttonContainer.appendChild(uploadBtn);
// 创建清空按钮
var clearBtn = document.createElement('input');
clearBtn.type = 'button';
clearBtn.id = 'clear-btn';
clearBtn.value = '清空';
clearBtn.className = 'clear-btn';
clearBtn.style.width = 'auto';
clearBtn.style.maxWidth = 'none';
clearBtn.style.padding = '8px 16px';
clearBtn.style.border = 'none';
clearBtn.style.backgroundColor = '#6c757d';
clearBtn.style.color = '#fff';
clearBtn.style.borderRadius = '4px';
clearBtn.style.cursor = 'pointer';
clearBtn.style.transition = 'background-color 0.3s ease';
clearBtn.addEventListener('mouseover', function() {
clearBtn.style.backgroundColor = '#5a6268';
});
clearBtn.addEventListener('mouseout', function() {
clearBtn.style.backgroundColor = '#6c757d';
});
clearBtn.addEventListener('click', function() {
fileInput.value = ''; // 清空文件输入框
resultInput.value = ''; // 清空上传结果文本框
resultInput.style.color = ''; // 重置文本框颜色
delete resultInput.dataset.url; // 删除已存储的URL
});
buttonContainer.appendChild(clearBtn);
form.appendChild(buttonContainer);
// 创建进度条容器,移出表单
var progressContainer = document.createElement('div');
progressContainer.style.marginTop = '10px';
progressContainer.style.display = 'none';
progressContainer.style.width = '100%';
// 创建进度条
var progressBar = document.createElement('progress');
progressBar.value = '0';
progressBar.max = '100';
progressBar.style.width = '100%';
progressBar.style.height = '20px';
progressContainer.appendChild(progressBar);
// 创建选项卡容器,移出表单
var tabContainer = document.createElement('div');
tabContainer.style.display = 'flex';
tabContainer.style.justifyContent = 'space-around';
tabContainer.style.marginTop = '10px';
// 创建选项卡
var tabs = ['HTML', 'imgURL', 'MarkDown', 'BBCode'];
tabs.forEach(function(tab) {
var tabButton = document.createElement('button');
tabButton.textContent = tab;
tabButton.style.flex = '1';
tabButton.style.padding = '10px';
tabButton.style.border = '1px solid #ccc';
tabButton.style.borderRadius = '4px 4px 0 0';
tabButton.style.cursor = 'pointer';
tabButton.style.backgroundColor = tab === globalData.selectedTab ? '#007bff' : '#f8f9fa';
tabButton.style.color = tab === globalData.selectedTab ? '#fff' : '#333';
tabButton.addEventListener('click', function() {
selectTab(tab);
});
tabContainer.appendChild(tabButton);
});
// 创建用于回显上传结果的文本框容器
var resultContainer = document.createElement('div');
resultContainer.style.marginTop = '10px';
resultContainer.style.textAlign = 'center';
// 创建回显上传结果的文本框
var resultInput = document.createElement('input');
resultInput.type = 'text';
resultInput.id = 'result-box';
resultInput.placeholder = '上传结果';
resultInput.style.width = '100%';
resultInput.style.maxWidth = 'none';
resultInput.style.padding = '8px';
resultInput.style.border = '1px solid #ccc';
resultInput.style.borderRadius = '4px';
resultInput.readOnly = true; // 设置为不可编辑状态
resultInput.style.cursor = 'pointer';
resultContainer.appendChild(resultInput);
// 单击文本框时复制里面的文本
resultInput.addEventListener('click', function() {
GM_setClipboard(resultInput.value);
});
// 将表单、选项卡容器、进度条容器、结果容器添加到对话框中
dialog.appendChild(form);
dialog.appendChild(tabContainer);
dialog.appendChild(progressContainer);
dialog.appendChild(resultContainer);
// 处理选项卡切换
function selectTab(tab) {
globalData.selectedTab = tab;
saveGlobalData(); // 持久化选项卡选择
updateTabUI(tab);
updateResultFormat(tab);
}
function updateTabUI(selectedTab) {
tabContainer.querySelectorAll('button').forEach(function(button) {
if (button.textContent === selectedTab) {
button.style.backgroundColor = '#007bff';
button.style.color = '#fff';
} else {
button.style.backgroundColor = '#f8f9fa';
button.style.color = '#333';
}
});
}
function updateResultFormat(tab) {
const url = resultInput.dataset.url; // 使用保存的上传成功后的URL
if (!url) return;
let formattedText = '';
switch (tab) {
case 'HTML':
formattedText = `<img src="${url}" alt="image">`;
break;
case 'imgURL':
formattedText = url;
break;
case 'MarkDown':
formattedText = ``;
break;
case 'BBCode':
formattedText = `[img]${url}[/img]`;
break;
}
if (resultInput.value !== formattedText) {
resultInput.value = formattedText; // 只有在内容不匹配时才更新文本框内容
}
}
// 初始化选项卡UI和结果格式
updateTabUI(globalData.selectedTab);
// 处理表单提交逻辑
form.addEventListener('submit', function(event) {
event.preventDefault(); // 阻止表单默认提交行为
// 获取UID和Token
var uid = uidInput.value.trim();
var token = tokenInput.value.trim();
var water = waterInput.value.trim();
// 检查UID和Token是否为空
if (!uid || !token) {
resultInput.value = '请填写UID和Token后再试。';
resultInput.style.color = 'red';
return;
}
// 保存UID和Token到全局持久化数据中
globalData.uid = uid;
globalData.token = token;
globalData.selectedAlbumId = albumSelect.value;
globalData.water = water;
saveGlobalData();
// 检查是否选择了文件
if (!fileInput.files.length) {
resultInput.value = '请选择要上传的文件。';
resultInput.style.color = 'red';
return;
}
// 检查文件格式是否支持
var allowedFormats = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp'];
var file = fileInput.files[0];
var fileExtension = file.name.split('.').pop().toLowerCase();
if (!allowedFormats.includes(fileExtension)) {
resultInput.value = '仅支持格式: ' + allowedFormats.join(', ');
resultInput.style.color = 'red';
return;
}
// 添加水印并生成新图片
addWatermark(file, 'res.21zys.com', function(watermarkedBlob) {
// 显示进度条
progressContainer.style.display = 'block';
// 创建异步请求
var xhr = new XMLHttpRequest();
xhr.open('POST', form.action, true); // 异步请求
// 监听上传进度
xhr.upload.addEventListener('progress', function(e) {
if (e.lengthComputable) {
var percentComplete = (e.loaded / e.total) * 100;
progressBar.value = percentComplete;
}
});
// 错误处理
xhr.addEventListener('load', function() {
if (xhr.status === 200) {
var response = JSON.parse(xhr.responseText);
if (response && response.data && response.data.url) {
resultInput.dataset.url = response.data.url; // 保存上传成功后的URL
updateResultFormat(globalData.selectedTab); // 更新结果格式
resultInput.style.color = 'green';
// 更新并保存上传数量
globalData.uploadCount++;
saveGlobalData();
uploadCountLabel.textContent = `今日已上传 ${globalData.uploadCount} 张图片`;
} else {
resultInput.value = '上传失败: ' + (response.message || '未知错误');
resultInput.style.color = 'red';
}
} else {
resultInput.value = '上传失败: ' + xhr.statusText + ' (' + xhr.status + ')';
resultInput.style.color = 'red';
}
progressContainer.style.display = 'none';
});
xhr.addEventListener('error', function() {
resultInput.value = '上传失败: 网络错误';
resultInput.style.color = 'red';
progressContainer.style.display = 'none';
});
// 将带有水印的Blob作为文件上传
var formData = new FormData();
formData.append('file', watermarkedBlob, file.name);
formData.append('uid', globalData.uid);
formData.append('token', globalData.token);
if (albumSelect.value !== 'default') { // 仅当选择了具体相册时才附加album_id
formData.append('album_id', albumSelect.value);
}
xhr.send(formData);
});
});
return dialog;
}
// sm.ms 图床对话框初始化
function initSmmsDialog() {
const GLOBAL_STORAGE_KEY = 'globalSmmsUploadData';
const DIALOG_POSITION = 'smmsDialogPosition';
// 从localStorage中获取全局持久化数据
const globalData = JSON.parse(GM_getValue(GLOBAL_STORAGE_KEY, null) || localStorage.getItem(GLOBAL_STORAGE_KEY)) || {
token: '您的Token',
uploadDate: new Date().toISOString().split('T')[0],
uploadCount: 0,
selectedTab: 'imgURL',
water: '',
renamePattern: '',
autoIncrement: 0
};
const today = new Date().toISOString().split('T')[0];
if (globalData.uploadDate !== today) {
globalData.uploadDate = today;
globalData.uploadCount = 0;
saveGlobalData();
}
function saveGlobalData() {
GM_setValue(GLOBAL_STORAGE_KEY, JSON.stringify(globalData));
localStorage.setItem(GLOBAL_STORAGE_KEY, JSON.stringify(globalData));
}
var savedDialogPosition = JSON.parse(localStorage.getItem(DIALOG_POSITION)) || null;
// 创建对话框主体,绑定拖拽功能
var dialog = document.createElement('div');
dialog.style.position = 'fixed';
// 默认居中对话框
if (savedDialogPosition) {
dialog.style.left = savedDialogPosition.left;
dialog.style.top = savedDialogPosition.top;
dialog.style.transform = 'none';
} else {
dialog.style.left = '50%';
dialog.style.top = '50%';
dialog.style.transform = 'translate(-50%, -50%)';
}
dialog.style.width = '400px';
dialog.style.padding = '20px';
dialog.style.backgroundColor = 'rgba(255, 255, 255, 0.8)';
dialog.style.borderRadius = '12px';
dialog.style.backdropFilter = 'blur(10px)';
dialog.style.boxShadow = '0 8px 32px rgba(0, 0, 0, 0.37)';
dialog.style.display = 'none';
dialog.style.opacity = '0';
dialog.style.zIndex = '999999';
dialog.style.fontFamily = 'Arial, sans-serif';
dialog.style.transition = 'opacity 0.3s ease';
document.body.appendChild(dialog);
let isDraggingDialog = false;
dialog.addEventListener('mousedown', function(e) {
if (e.target !== dialog) return;
var offsetX = e.clientX - dialog.getBoundingClientRect().left;
var offsetY = e.clientY - dialog.getBoundingClientRect().top;
const edgeThreshold = 25;
if (offsetX < edgeThreshold || offsetX > dialog.clientWidth - edgeThreshold ||
offsetY < edgeThreshold || offsetY > dialog.clientHeight - edgeThreshold) {
function onMouseMove(e) {
isDraggingDialog = true;
var newLeft = e.clientX - offsetX;
var newTop = e.clientY - offsetY;
dialog.style.left = newLeft + 'px';
dialog.style.top = newTop + 'px';
dialog.style.transform = 'none';
localStorage.setItem(DIALOG_POSITION, JSON.stringify({
left: dialog.style.left,
top: dialog.style.top
}));
}
function onMouseUp() {
document.removeEventListener('mousemove', onMouseMove);
document.removeEventListener('mouseup', onMouseUp);
setTimeout(() => isDraggingDialog = false, 100);
}
document.addEventListener('mousemove', onMouseMove);
document.addEventListener('mouseup', onMouseUp);
}
});
var closeButton = document.createElement('span');
closeButton.innerHTML = '×';
closeButton.style.position = 'absolute';
closeButton.style.top = '10px';
closeButton.style.right = '10px';
closeButton.style.cursor = 'pointer';
closeButton.style.fontSize = '20px';
closeButton.style.color = '#333';
closeButton.addEventListener('click', function() {
closeDialog(dialog);
});
dialog.appendChild(closeButton);
// 创建表单
var form = document.createElement('form');
form.enctype = 'multipart/form-data';
form.method = 'post';
form.id = 'smms-upform';
form.style.display = 'grid';
form.style.gap = '10px';
// 创建水印字段
var waterLabel = document.createElement('label');
waterLabel.innerText = '文本水印:';
waterLabel.style.fontWeight = 'bold';
waterLabel.style.color = '#333';
waterLabel.style.display = 'inline-block';
waterLabel.style.minHeight = 'none';
waterLabel.style.padding = 'none';
var waterInput = document.createElement('input');
waterInput.type = 'text';
waterInput.name = 'smms-water';
waterInput.value = globalData.water ? globalData.water : '';
waterInput.placeholder = '请输入需要添加的文本水印';
waterInput.style.padding = '8px';
waterInput.style.border = '1px solid #ccc';
waterInput.style.borderRadius = '4px';
waterInput.style.width = 'auto';
waterInput.style.maxWidth = 'none';
form.appendChild(waterLabel);
form.appendChild(waterInput);
// 创建高级文件重命名字段
var renameLabel = document.createElement('label');
renameLabel.innerText = '高级文件重命名:';
renameLabel.style.fontWeight = 'bold';
renameLabel.style.color = '#333';
renameLabel.style.display = 'inline-block';
renameLabel.style.minHeight = 'none';
renameLabel.style.padding = 'none';
var renameInput = document.createElement('input');
renameInput.type = 'text';
renameInput.name = 'smms-rename';
renameInput.value = globalData.renamePattern ? globalData.renamePattern : '';
renameInput.placeholder = '请输入重命名格式(忽略请留空)';
renameInput.style.padding = '8px';
renameInput.style.border = '1px solid #ccc';
renameInput.style.borderRadius = '4px';
renameInput.style.width = 'auto';
renameInput.style.maxWidth = 'none';
form.appendChild(renameLabel);
form.appendChild(renameInput);
// 创建Token字段
var tokenLabel = document.createElement('label');
tokenLabel.innerText = 'Token:';
tokenLabel.style.fontWeight = 'bold';
tokenLabel.style.color = '#333';
tokenLabel.style.display = 'inline-block';
tokenLabel.style.minHeight = 'none';
tokenLabel.style.padding = 'none';
var tokenInput = document.createElement('input');
tokenInput.type = 'text';
tokenInput.name = 'token';
tokenInput.value = globalData.token ? globalData.token : '';
tokenInput.style.padding = '8px';
tokenInput.style.border = '1px solid #ccc';
tokenInput.style.borderRadius = '4px';
tokenInput.style.width = 'auto';
tokenInput.style.maxWidth = 'none';
form.appendChild(tokenLabel);
form.appendChild(tokenInput);
// 创建文件上传输入框
var fileLabel = document.createElement('label');
fileLabel.innerText = '选择文件:';
fileLabel.style.fontWeight = 'bold';
fileLabel.style.color = '#333';
fileLabel.style.display = 'inline-block';
fileLabel.style.minHeight = 'none';
fileLabel.style.padding = 'none';
var fileInput = document.createElement('input');
fileInput.type = 'file';
fileInput.name = 'file';
fileInput.style.padding = '8px';
fileInput.style.border = '1px solid #ccc';
fileInput.style.borderRadius = '4px';
fileInput.style.width = 'auto';
fileInput.style.maxWidth = 'none';
form.appendChild(fileLabel);
form.appendChild(fileInput);
// 上传和清空按钮容器
var buttonContainer = document.createElement('div');
buttonContainer.style.marginTop = '10px';
buttonContainer.style.textAlign = 'right';
// 创建今日上传数量label
var uploadCountLabel = document.createElement('label');
uploadCountLabel.textContent = `今日已上传 ${globalData.uploadCount} 张图片`;
uploadCountLabel.style.fontSize = '1rem';
uploadCountLabel.style.fontWeight = 'bold';
uploadCountLabel.style.color = 'rgb(51, 51, 51)';
uploadCountLabel.style.marginRight = '10px';
uploadCountLabel.style.display = 'inline-block';
uploadCountLabel.style.minHeight = 'none';
uploadCountLabel.style.padding = 'none';
buttonContainer.appendChild(uploadCountLabel);
// 创建上传按钮
var uploadBtn = document.createElement('input');
uploadBtn.type = 'submit';
uploadBtn.id = 'upload-btn';
uploadBtn.value = '开始上传';
uploadBtn.className = 'upload-btn';
uploadBtn.style.width = 'auto';
uploadBtn.style.maxWidth = 'none';
uploadBtn.style.padding = '8px 16px';
uploadBtn.style.border = 'none';
uploadBtn.style.backgroundColor = '#007bff';
uploadBtn.style.color = '#fff';
uploadBtn.style.borderRadius = '4px';
uploadBtn.style.cursor = 'pointer';
uploadBtn.style.marginRight = '10px';
uploadBtn.style.transition = 'background-color 0.3s ease';
uploadBtn.addEventListener('mouseover', function() {
uploadBtn.style.backgroundColor = '#0056b3';
});
uploadBtn.addEventListener('mouseout', function() {
uploadBtn.style.backgroundColor = '#007bff';
});
buttonContainer.appendChild(uploadBtn);
// 创建清空按钮
var clearBtn = document.createElement('input');
clearBtn.type = 'button';
clearBtn.id = 'clear-btn';
clearBtn.value = '清空';
clearBtn.className = 'clear-btn';
clearBtn.style.width = 'auto';
clearBtn.style.maxWidth = 'none';
clearBtn.style.padding = '8px 16px';
clearBtn.style.border = 'none';
clearBtn.style.backgroundColor = '#6c757d';
clearBtn.style.color = '#fff';
clearBtn.style.borderRadius = '4px';
clearBtn.style.cursor = 'pointer';
clearBtn.style.transition = 'background-color 0.3s ease';
clearBtn.addEventListener('mouseover', function() {
clearBtn.style.backgroundColor = '#5a6268';
});
clearBtn.addEventListener('mouseout', function() {
clearBtn.style.backgroundColor = '#6c757d';
});
clearBtn.addEventListener('click', function() {
fileInput.value = ''; // 清空文件输入框
resultInput.value = ''; // 清空上传结果文本框
resultInput.style.color = ''; // 重置文本框颜色
delete resultInput.dataset.url; // 删除已存储的URL
});
buttonContainer.appendChild(clearBtn);
form.appendChild(buttonContainer);
// 创建进度条容器,移出表单
var progressContainer = document.createElement('div');
progressContainer.style.marginTop = '10px';
progressContainer.style.display = 'none';
progressContainer.style.width = '100%';
// 创建进度条
var progressBar = document.createElement('progress');
progressBar.value = '0';
progressBar.max = '100';
progressBar.style.width = '100%';
progressBar.style.height = '20px';
progressContainer.appendChild(progressBar);
// 创建选项卡容器,移出表单
var tabContainer = document.createElement('div');
tabContainer.style.display = 'flex';
tabContainer.style.justifyContent = 'space-around';
tabContainer.style.marginTop = '10px';
tabContainer.style.marginBottom = '3px';
// 创建选项卡
var tabs = ['HTML', 'imgURL', 'MarkDown', 'BBCode'];
tabs.forEach(function(tab) {
var tabButton = document.createElement('button');
tabButton.textContent = tab;
tabButton.style.flex = '1';
tabButton.style.padding = '10px';
tabButton.style.border = '1px solid #ccc';
tabButton.style.borderRadius = '4px 4px 0 0';
tabButton.style.cursor = 'pointer';
tabButton.style.backgroundColor = tab === globalData.selectedTab ? '#007bff' : '#f8f9fa';
tabButton.style.color = tab === globalData.selectedTab ? '#fff' : '#333';
tabButton.addEventListener('click', function() {
selectTab(tab);
});
tabContainer.appendChild(tabButton);
});
// 创建用于回显上传结果的文本框容器
var resultContainer = document.createElement('div');
resultContainer.style.marginTop = '10px';
resultContainer.style.textAlign = 'center';
// 创建回显上传结果的文本框
var resultInput = document.createElement('input');
resultInput.type = 'text';
resultInput.id = 'result-box';
resultInput.placeholder = '上传结果';
resultInput.style.width = '100%';
resultInput.style.maxWidth = 'none';
resultInput.style.padding = '8px';
resultInput.style.border = '1px solid #ccc';
resultInput.style.borderRadius = '4px';
resultInput.readOnly = true; // 设置为不可编辑状态
resultInput.style.cursor = 'pointer';
resultContainer.appendChild(resultInput);
// 单击文本框时复制里面的文本
resultInput.addEventListener('click', function() {
GM_setClipboard(resultInput.value);
});
// 将表单、选项卡容器、进度条容器、结果容器添加到对话框中
dialog.appendChild(form);
dialog.appendChild(tabContainer);
dialog.appendChild(progressContainer);
dialog.appendChild(resultContainer);
// 处理选项卡切换
function selectTab(tab) {
globalData.selectedTab = tab;
saveGlobalData(); // 持久化选项卡选择
updateTabUI(tab);
updateResultFormat(tab);
}
function updateTabUI(selectedTab) {
tabContainer.querySelectorAll('button').forEach(function(button) {
if (button.textContent === selectedTab) {
button.style.backgroundColor = '#007bff';
button.style.color = '#fff';
} else {
button.style.backgroundColor = '#f8f9fa';
button.style.color = '#333';
}
});
}
function updateResultFormat(tab) {
const url = resultInput.dataset.url; // 使用保存的上传成功后的URL
if (!url) return;
let formattedText = '';
switch (tab) {
case 'HTML':
formattedText = `<img src="${url}" alt="image">`;
break;
case 'imgURL':
formattedText = url;
break;
case 'MarkDown':
formattedText = ``;
break;
case 'BBCode':
formattedText = `[URL=${url}][IMG]${url}[/IMG][/URL]`;
break;
}
if (resultInput.value !== formattedText) {
resultInput.value = formattedText; // 只有在内容不匹配时才更新文本框内容
}
}
// 初始化选项卡UI和结果格式
updateTabUI(globalData.selectedTab);
// 处理表单提交逻辑
form.addEventListener('submit', function(event) {
event.preventDefault();
var token = tokenInput.value.trim();
var water = waterInput.value.trim();
var renamePattern = renameInput.value.trim();
var autoIncrement = globalData.autoIncrement;
autoIncrement++;
if (!token) {
resultInput.value = '请填写Token后再试。';
resultInput.style.color = 'red';
return;
}
globalData.token = token;
globalData.water = water;
globalData.renamePattern = renamePattern;
globalData.autoIncrement = autoIncrement;
saveGlobalData();
if (!fileInput.files.length) {
resultInput.value = '请选择要上传的文件。';
resultInput.style.color = 'red';
return;
}
var file = fileInput.files[0];
var allowedFormats = ['jpg', 'jpeg', 'png', 'gif', 'bmp'];
var fileExtension = file.name.split('.').pop().toLowerCase();
if (!allowedFormats.includes(fileExtension)) {
resultInput.value = '仅支持格式: ' + allowedFormats.join(', ');
resultInput.style.color = 'red';
return;
}
// 添加水印并生成新图片
addWatermark(file, water, function(watermarkedBlob) {
// 显示进度条
progressContainer.style.display = 'block';
var formData = new FormData();
formData.append('smfile', watermarkedBlob, superPictureRename(file.name, renamePattern, autoIncrement));
formData.append('format', 'json');
progressBar.value = 30;
GM_xmlhttpRequest({
method: 'POST',
url: 'https://sm.ms/api/v2/upload',
headers: {
'Authorization': `${globalData.token}`
},
data: formData,
onprogress: function(e) {
if (e.lengthComputable) {
var percentComplete = (e.loaded / e.total) * 100;
progressBar.value = percentComplete;
}
},
onload: function(response) {
var responseData = myJson.parse(response.responseText);
if (responseData.success && responseData.data && responseData.data.url) {
progressBar.value = 80;
resultInput.dataset.url = responseData.data.url; // 保存上传成功后的URL
updateResultFormat(globalData.selectedTab); // 更新结果格式
resultInput.style.color = 'green';
// 更新并保存上传数量
globalData.uploadCount++;
saveGlobalData();
uploadCountLabel.textContent = `今日已上传 ${globalData.uploadCount} 张图片`;
} else {
resultInput.value = '上传失败: ' + (responseData.message || '未知错误');
resultInput.style.color = 'red';
}
progressBar.value = 100;
progressContainer.style.display = 'none';
},
onerror: function() {
resultInput.value = '上传失败: 网络错误';
resultInput.style.color = 'red';
progressContainer.style.display = 'none';
}
});
});
});
return dialog;
}
// 添加水印功能
function addWatermark(file, text, callback) {
const reader = new FileReader();
reader.onload = function(event) {
const img = new Image();
img.src = event.target.result;
img.onload = function() {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
// 设置canvas尺寸与图片一致
canvas.width = img.width;
canvas.height = img.height;
// 绘制原始图片
ctx.drawImage(img, 0, 0);
// 字体大小,根据图片大小改变水印文案字体大小
var fontSize = img.width * 0.1;
ctx.font = `${fontSize}px Arial`;
ctx.fillStyle = 'rgba(0, 0, 0, 0.4)'; // 黑色,透明度0.4
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
// 计算水印位置并倾斜45度
ctx.translate(canvas.width / 2, canvas.height / 2);
ctx.rotate(-Math.PI / 4);
// 添加水印
ctx.fillText(text, 0, 0);
// 恢复canvas状态
ctx.rotate(Math.PI / 4);
ctx.translate(-canvas.width / 2, -canvas.height / 2);
// 导出带水印的新图片
canvas.toBlob(callback, file.type);
};
};
reader.readAsDataURL(file);
}
// 图片高级重命名
function superPictureRename(filename, pattern, autoIncrement) {
if (!pattern) return filename;
// 去除文件后缀名
const extIndex = filename.lastIndexOf('.');
const ext = extIndex > -1 ? filename.substring(extIndex) : '';
const baseFilename = extIndex > -1 ? filename.substring(0, extIndex) : filename;
// 生成当前日期时间
const now = dayjs();
// 生成随机字符串
function randomString(length) {
let result = '';
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
const charactersLength = characters.length;
for (let i = 0; i < length; i++) {
result += characters.charAt(Math.floor(Math.random() * charactersLength));
}
return result;
}
// 替换占位符
const formatted = pattern.replace(/{Y}/g, now.format('YYYY'))
.replace(/{y}/g, now.format('YY'))
.replace(/{m}/g, now.format('MM'))
.replace(/{d}/g, now.format('DD'))
.replace(/{h}/g, now.format('HH'))
.replace(/{i}/g, now.format('mm'))
.replace(/{s}/g, now.format('ss'))
.replace(/{ms}/g, now.format('SSS'))
.replace(/{timestamp}/g, now.valueOf())
.replace(/{md5}/g, CryptoJS.MD5(randomString(32)).toString())
.replace(/{md5-16}/g, CryptoJS.MD5(randomString(16)).toString().substring(0, 16))
.replace(/{uuid}/g, uuid.v4())
.replace(/{str-(\d+)}/g, (match, p1) => randomString(parseInt(p1)))
.replace(/{filename}/g, baseFilename)
.replace(/{auto}/g, () => autoIncrement);
// 返回格式化后的文件名
return formatted + ext;
}
})();