您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
在 V2EX 评论区快速上传图片并插入链接
- // ==UserScript==
- // @name V2EX Image Uploader
- // @namespace http://tampermonkey.net/1436051
- // @version 1.0
- // @description 在 V2EX 评论区快速上传图片并插入链接
- // @author Dogxi
- // @match https://www.v2ex.com/t/*
- // @match https://v2ex.com/t/*
- // @icon https://www.google.com/s2/favicons?sz=64&domain=v2ex.com
- // @grant GM_xmlhttpRequest
- // @grant GM_setValue
- // @grant GM_getValue
- // @connect api.imgur.com
- // @license MIT
- // ==/UserScript==
- (function() {
- 'use strict';
- const IMGUR_CLIENT_ID_KEY = 'imgurClientId';
- let CLIENT_ID = GM_getValue(IMGUR_CLIENT_ID_KEY, null);
- const STYLE = `
- .imgur-upload-btn {
- background: none;
- border: none;
- color: #778087;
- cursor: pointer;
- font-size: 13px;
- padding: 0;
- margin-left: 15px;
- text-decoration: none;
- transition: color 0.2s ease;
- }
- .imgur-upload-btn:hover {
- color: #4d5256;
- text-decoration: underline;
- }
- .hidden {
- display: none !important;
- }
- .imgur-upload-modal {
- 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: 9999;
- }
- .imgur-upload-modal-content {
- background-color: #fff;
- padding: 20px;
- border-radius: 3px;
- box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
- max-width: 450px;
- width: 90%;
- position: relative;
- font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
- }
- .imgur-upload-modal-header {
- display: flex;
- justify-content: space-between;
- align-items: center;
- margin-bottom: 15px;
- padding-bottom: 10px;
- border-bottom: 1px solid #e2e2e2;
- }
- .imgur-upload-modal-title {
- font-size: 15px;
- font-weight: normal;
- color: #000;
- }
- .imgur-upload-modal-close {
- cursor: pointer;
- font-size: 18px;
- color: #ccc;
- width: 20px;
- height: 20px;
- display: flex;
- align-items: center;
- justify-content: center;
- transition: color 0.2s ease;
- }
- .imgur-upload-modal-close:hover {
- color: #999;
- }
- .imgur-upload-dropzone {
- border: 1px dashed #ccc;
- padding: 25px;
- text-align: center;
- margin-bottom: 15px;
- cursor: pointer;
- border-radius: 3px;
- transition: border-color 0.2s ease;
- font-size: 13px;
- color: #666;
- }
- .imgur-upload-dropzone:hover {
- border-color: #999;
- }
- .imgur-upload-dropzone.dragover {
- border-color: #778087;
- background-color: #f9f9f9;
- }
- .imgur-upload-preview {
- margin-top: 10px;
- max-width: 100%;
- max-height: 150px;
- border-radius: 2px;
- }
- .imgur-upload-actions {
- display: flex;
- justify-content: space-between;
- align-items: center;
- margin-top: 15px;
- padding-top: 10px;
- border-top: 1px solid #e2e2e2;
- }
- .imgur-upload-config-btn {
- background: none;
- border: none;
- color: #778087;
- cursor: pointer;
- font-size: 12px;
- padding: 0;
- }
- .imgur-upload-config-btn:hover {
- color: #4d5256;
- text-decoration: underline;
- }
- .imgur-upload-submit-btn {
- background-color: #f5f5f5;
- border: 1px solid #ccc;
- border-radius: 3px;
- color: #333;
- cursor: pointer;
- font-size: 12px;
- padding: 6px 12px;
- transition: all 0.2s ease;
- }
- .imgur-upload-submit-btn:hover {
- background-color: #e8e8e8;
- }
- .imgur-upload-submit-btn:disabled {
- background-color: #f9f9f9;
- color: #ccc;
- cursor: not-allowed;
- }
- .imgur-upload-config-panel {
- margin-top: 10px;
- padding: 10px;
- background-color: #f9f9f9;
- border-radius: 3px;
- border: 1px solid #e2e2e2;
- }
- .imgur-upload-config-row {
- display: flex;
- align-items: center;
- margin-bottom: 8px;
- }
- .imgur-upload-config-row:last-child {
- margin-bottom: 0;
- }
- .imgur-upload-config-label {
- font-size: 12px;
- color: #666;
- width: 70px;
- flex-shrink: 0;
- }
- .imgur-upload-config-input {
- flex: 1;
- padding: 3px 6px;
- border: 1px solid #ccc;
- border-radius: 2px;
- font-size: 12px;
- }
- .imgur-upload-config-save {
- background-color: #f5f5f5;
- border: 1px solid #ccc;
- border-radius: 2px;
- color: #333;
- cursor: pointer;
- font-size: 11px;
- margin-left: 6px;
- padding: 3px 8px;
- }
- .imgur-upload-config-save:hover {
- background-color: #e8e8e8;
- }
- .imgur-upload-modal-status {
- color: #666;
- font-size: 12px;
- text-align: center;
- }
- .imgur-upload-modal-status.success {
- color: #5cb85c;
- }
- .imgur-upload-modal-status.error {
- color: #d9534f;
- }
- `;
- // 添加样式到页面
- function addStyle() {
- const styleElement = document.createElement('style');
- styleElement.textContent = STYLE;
- document.head.appendChild(styleElement);
- }
- // 创建上传弹窗
- function createUploadModal(textareaElement) {
- const modal = document.createElement('div');
- modal.className = 'imgur-upload-modal';
- const content = document.createElement('div');
- content.className = 'imgur-upload-modal-content';
- content.innerHTML = `
- <div class="imgur-upload-modal-header">
- <div class="imgur-upload-modal-title">上传图片</div>
- <div class="imgur-upload-modal-close">×</div>
- </div>
- <div class="imgur-upload-dropzone">
- <div>点击选择图片或拖拽图片到此处</div>
- <div style="font-size: 11px; color: #999; margin-top: 5px;">支持 JPG, PNG, GIF 格式</div>
- </div>
- <div class="imgur-upload-actions">
- <button class="imgur-upload-config-btn">⚙️ 配置</button>
- <button class="imgur-upload-submit-btn" disabled>确认上传</button>
- </div>
- <div class="imgur-upload-config-panel hidden">
- <div class="imgur-upload-config-row">
- <div class="imgur-upload-config-label">Imgur ID:</div>
- <input type="text" class="imgur-upload-config-input" placeholder="请输入 Imgur Client ID" value="${CLIENT_ID || ''}">
- <button class="imgur-upload-config-save">保存</button>
- </div>
- <div style="font-size: 11px; color: #666; margin-top: 8px;">
- 在 <a href="https://api.imgur.com/oauth2/addclient" target="_blank">https://api.imgur.com/oauth2/addclient</a> 注册(不可用)获取(无回调)
- </div>
- </div>
- `;
- modal.appendChild(content);
- document.body.appendChild(modal);
- setupModalEvents(modal, textareaElement);
- return modal;
- }
- // 设置弹窗事件监听
- function setupModalEvents(modal, textareaElement) {
- const closeBtn = modal.querySelector('.imgur-upload-modal-close');
- const dropzone = modal.querySelector('.imgur-upload-dropzone');
- const configBtn = modal.querySelector('.imgur-upload-config-btn');
- const configPanel = modal.querySelector('.imgur-upload-config-panel');
- const configInput = modal.querySelector('.imgur-upload-config-input');
- const configSave = modal.querySelector('.imgur-upload-config-save');
- const submitBtn = modal.querySelector('.imgur-upload-submit-btn');
- let selectedFile = null;
- function closeModal() {
- document.body.removeChild(modal);
- }
- closeBtn.addEventListener('click', closeModal);
- modal.addEventListener('click', function(e) {
- if (e.target === modal) closeModal();
- });
- configBtn.addEventListener('click', function() {
- configPanel.classList.toggle('hidden');
- });
- configSave.addEventListener('click', function() {
- const newClientId = configInput.value.trim();
- if (newClientId) {
- GM_setValue(IMGUR_CLIENT_ID_KEY, newClientId);
- CLIENT_ID = newClientId;
- configPanel.classList.add('hidden');
- showStatusInModal(modal, '配置已保存', 'success');
- }
- });
- const fileInput = document.createElement('input');
- fileInput.type = 'file';
- fileInput.accept = 'image/*';
- fileInput.style.display = 'none';
- modal.appendChild(fileInput);
- dropzone.addEventListener('click', () => fileInput.click());
- fileInput.addEventListener('change', function(e) {
- handleFileSelect(e.target.files[0]);
- });
- dropzone.addEventListener('dragover', function(e) {
- e.preventDefault();
- dropzone.classList.add('dragover');
- });
- dropzone.addEventListener('dragleave', function(e) {
- e.preventDefault();
- dropzone.classList.remove('dragover');
- });
- dropzone.addEventListener('drop', function(e) {
- e.preventDefault();
- dropzone.classList.remove('dragover');
- const files = e.dataTransfer.files;
- if (files.length > 0) {
- handleFileSelect(files[0]);
- }
- });
- // 处理文件选择
- function handleFileSelect(file) {
- if (!file || !file.type.match(/image\/.*/)) {
- showStatusInModal(modal, '请选择图片文件', 'error');
- return;
- }
- selectedFile = file;
- const reader = new FileReader();
- reader.onload = function(e) {
- const preview = modal.querySelector('.imgur-upload-preview');
- if (preview) preview.remove();
- const img = document.createElement('img');
- img.src = e.target.result;
- img.className = 'imgur-upload-preview';
- dropzone.appendChild(img);
- submitBtn.disabled = false;
- dropzone.querySelector('div').textContent = '已选择: ' + file.name;
- };
- reader.readAsDataURL(file);
- }
- submitBtn.addEventListener('click', function() {
- if (!selectedFile) return;
- if (!CLIENT_ID) {
- showStatusInModal(modal, '请先配置 Imgur Client ID', 'error');
- configPanel.classList.remove('hidden');
- return;
- }
- submitBtn.disabled = true;
- submitBtn.textContent = '上传中...';
- uploadToImgur(selectedFile, textareaElement, modal);
- });
- }
- // 在弹窗中显示状态信息
- function showStatusInModal(modal, message, type) {
- let statusEl = modal.querySelector('.imgur-upload-modal-status');
- if (!statusEl) {
- statusEl = document.createElement('div');
- statusEl.className = 'imgur-upload-modal-status';
- statusEl.style.cssText = 'margin-top: 10px; font-size: 12px; text-align: center;';
- modal.querySelector('.imgur-upload-modal-content').appendChild(statusEl);
- }
- statusEl.textContent = message;
- statusEl.className = 'imgur-upload-modal-status ' + (type || '');
- if (type === 'success') {
- setTimeout(() => statusEl.textContent = '', 3000);
- }
- }
- // 上传图片到 Imgur
- function uploadToImgur(file, textareaElement, modal) {
- if (!file.type.match(/image\/.*/)) {
- showStatusInModal(modal, '请选择图片文件', 'error');
- const submitBtn = modal.querySelector('.imgur-upload-submit-btn');
- submitBtn.disabled = false;
- submitBtn.textContent = '确认上传';
- return;
- }
- const formData = new FormData();
- formData.append('image', file);
- GM_xmlhttpRequest({
- method: "POST",
- url: "https://api.imgur.com/3/image",
- headers: {
- "Authorization": "Client-ID " + CLIENT_ID
- },
- data: formData,
- responseType: "json",
- onload: function(response) {
- const submitBtn = modal.querySelector('.imgur-upload-submit-btn');
- try {
- let responseData;
- if (typeof response.response === 'string') {
- responseData = JSON.parse(response.response);
- } else {
- responseData = response.response;
- }
- if (response.status === 200 && responseData && responseData.success) {
- const imageUrl = responseData.data.link;
- insertLinkIntoTextarea(textareaElement, imageUrl, file.name);
- showStatusInModal(modal, '上传成功!', 'success');
- setTimeout(() => {
- document.body.removeChild(modal);
- }, 1500);
- } else {
- let errorMessage = '';
- if (response.status === 400) {
- if (responseData && responseData.data && responseData.data.error) {
- if (responseData.data.error === 'These actions are forbidden.') {
- errorMessage = 'Client ID 无效或已被禁用,请检查配置';
- } else {
- errorMessage = responseData.data.error;
- }
- } else {
- errorMessage = 'Client ID 配置错误';
- }
- } else if (response.status === 403) {
- errorMessage = '访问被拒绝,请检查 Client ID 权限';
- } else if (response.status === 429) {
- errorMessage = '请求过于频繁,请稍后再试';
- } else {
- errorMessage = `上传失败 (${response.status})`;
- }
- console.error('Imgur 上传错误:', response);
- showStatusInModal(modal, errorMessage, 'error');
- if (response.status === 400 || response.status === 403) {
- const configPanel = modal.querySelector('.imgur-upload-config-panel');
- configPanel.classList.remove('hidden');
- }
- submitBtn.disabled = false;
- submitBtn.textContent = '确认上传';
- }
- } catch (e) {
- console.error('解析响应失败:', e, response);
- showStatusInModal(modal, '响应解析失败,请重试', 'error');
- submitBtn.disabled = false;
- submitBtn.textContent = '确认上传';
- }
- },
- onerror: function(error) {
- console.error('GM_xmlhttpRequest 错误:', error);
- showStatusInModal(modal, '网络请求失败,请检查连接', 'error');
- const submitBtn = modal.querySelector('.imgur-upload-submit-btn');
- submitBtn.disabled = false;
- submitBtn.textContent = '确认上传';
- },
- ontimeout: function() {
- console.error('Imgur 上传超时');
- showStatusInModal(modal, '上传超时,请重试', 'error');
- const submitBtn = modal.querySelector('.imgur-upload-submit-btn');
- submitBtn.disabled = false;
- submitBtn.textContent = '确认上传';
- }
- });
- }
- // 将图片链接插入到文本框
- function insertLinkIntoTextarea(textareaElement, imageUrl, fileName) {
- const altText = fileName ? fileName.split('.')[0] : 'image';
- const textToInsert = imageUrl;
- const currentValue = textareaElement.value;
- const selectionStart = textareaElement.selectionStart;
- const selectionEnd = textareaElement.selectionEnd;
- const newText = currentValue.substring(0, selectionStart) + textToInsert + currentValue.substring(selectionEnd);
- textareaElement.value = newText;
- const newCursorPosition = selectionStart + textToInsert.length;
- textareaElement.selectionStart = newCursorPosition;
- textareaElement.selectionEnd = newCursorPosition;
- textareaElement.focus();
- textareaElement.dispatchEvent(new Event('input', { bubbles: true, cancelable: true }));
- }
- // 在页面头部添加上传按钮
- function addUploadButtonToHeader() {
- const replyBox = document.getElementById('reply-box');
- if (!replyBox) return;
- const headerCell = replyBox.querySelector('.cell.flex-one-row');
- if (!headerCell) return;
- if (headerCell.querySelector('.imgur-upload-btn')) return;
- const leftDiv = headerCell.querySelector('div:first-child');
- if (leftDiv) {
- const uploadBtn = document.createElement('a');
- uploadBtn.className = 'imgur-upload-btn';
- uploadBtn.textContent = '上传';
- uploadBtn.href = 'javascript:void(0);';
- uploadBtn.title = '上传图片';
- uploadBtn.style.marginLeft = '10px';
- leftDiv.appendChild(uploadBtn);
- uploadBtn.addEventListener('click', function(e) {
- e.preventDefault();
- const textarea = document.getElementById('reply_content');
- if (textarea) {
- createUploadModal(textarea);
- }
- });
- }
- }
- // 查找并添加上传按钮
- function findTextareasAndAddButtons() {
- addUploadButtonToHeader();
- setupMutationObserver();
- }
- // 监听DOM变化
- function setupMutationObserver() {
- const observer = new MutationObserver(function(mutations) {
- let shouldCheck = false;
- mutations.forEach(function(mutation) {
- mutation.addedNodes.forEach(function(node) {
- if (node.nodeType === Node.ELEMENT_NODE &&
- (node.id === 'reply-box' || node.querySelector('#reply-box'))) {
- shouldCheck = true;
- }
- });
- });
- if (shouldCheck) {
- setTimeout(addUploadButtonToHeader, 100);
- }
- });
- observer.observe(document.body, {
- childList: true,
- subtree: true
- });
- }
- // 初始化脚本
- function init() {
- addStyle();
- setTimeout(findTextareasAndAddButtons, 100);
- }
- if (document.readyState === "loading") {
- document.addEventListener("DOMContentLoaded", init);
- } else {
- init();
- }
- })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址