Last active
January 22, 2026 12:02
-
-
Save zhasm/e17e15429260f0d341503a8e8253ad1c to your computer and use it in GitHub Desktop.
fanfou user switch
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // ==UserScript== | |
| // @name SpaceFanfou light | |
| // @namespace http://tampermonkey.net/ | |
| // @version 1.1.3 | |
| // @description Switch between saved Fanfou accounts with a dropdown menu and background login dialog | |
| // @author AutoGenerated | |
| // @match https://*.fanfou.com/* | |
| // @grant GM_setValue | |
| // @grant GM_getValue | |
| // @grant GM_addStyle | |
| // @grant GM_xmlhttpRequest | |
| // @run-at document-end | |
| // ==/UserScript== | |
| // logs | |
| // 1.1.0 switch user; | |
| // 1.1.1 add paste image from clipboard feature | |
| // 1.1.2 add image URL cleaner feature (removes @suffix from ZoomBox images) | |
| // 1.1.3 fix cookie extension logic | |
| (function () { | |
| 'use strict'; | |
| const STORAGE_KEY = 'switch_user_allUserData'; | |
| const COOKIE_DOMAIN = '.fanfou.com'; | |
| const CSS = ` | |
| #user_top.sf-is-ready { | |
| position: absolute; | |
| width: 202px; | |
| /* 这个z-index在弹出的图片中,显示异常,暂时注释掉 */ | |
| /* z-index: 1000; */ | |
| border-radius: 3px; | |
| margin: -5px 0 0 -5px; | |
| padding: 5px; | |
| transition: background-color 0.2s, box-shadow 0.2s; | |
| } | |
| #user_top.sf-is-ready:hover { | |
| box-shadow: 0 8px 16px rgba(0, 0, 0, 0.1); | |
| background-color: #fff; | |
| } | |
| #user_top.sf-is-ready + #reminder { | |
| margin-top: 47px; | |
| } | |
| #user_top > a img { | |
| width: 32px; | |
| height: 32px; | |
| margin-right: 10px; | |
| } | |
| #user_top > h3::after { | |
| content: "\\25BE"; | |
| margin-left: 7px; | |
| opacity: 0.5; | |
| } | |
| #sf-user-switcher { | |
| display: none; | |
| position: relative; | |
| margin-top: 6px; | |
| border-top: 1px solid #e5e5e5; | |
| font-size: 12px; | |
| line-height: 24px; | |
| padding-top: 5px; | |
| } | |
| #user_top.sf-is-ready:hover #sf-user-switcher { | |
| display: block; | |
| } | |
| #sf-user-switcher .sf-user-item { | |
| display: table; | |
| width: 100%; | |
| padding: 2px 0; | |
| transition: background-color 0.1s; | |
| } | |
| #sf-user-switcher .sf-user-item:hover { | |
| background-color: rgba(0, 0, 0, 0.025); | |
| } | |
| #sf-user-switcher .sf-user-info, | |
| #sf-user-switcher .sf-del-icon { | |
| display: table-cell; | |
| vertical-align: middle; | |
| } | |
| #sf-user-switcher .sf-user-info { | |
| width: 100%; | |
| height: auto; | |
| cursor: pointer; | |
| color: #333; | |
| text-decoration: none; | |
| } | |
| #sf-user-switcher .sf-user-info img { | |
| float: left; | |
| width: 16px; | |
| height: 16px; | |
| margin: 4px 5px; | |
| border-radius: 2px; | |
| } | |
| #sf-user-switcher .sf-del-icon { | |
| width: 20px; | |
| text-align: center; | |
| cursor: pointer; | |
| color: #ccc; | |
| font-size: 14px; | |
| visibility: hidden; | |
| } | |
| #sf-user-switcher .sf-user-item:hover .sf-del-icon { | |
| visibility: visible; | |
| } | |
| #sf-user-switcher .sf-del-icon:hover { | |
| color: #c00; | |
| } | |
| #sf-user-switcher .sf-add-new-user { | |
| padding: 8px 0 4px; | |
| border-top: 1px solid #e5e5e5; | |
| text-align: center; | |
| } | |
| #sf-user-switcher .formbutton { | |
| letter-spacing: 0; | |
| padding: 3px 10px; | |
| cursor: pointer; | |
| background: #f0f0f0; | |
| border: 1px solid #ccc; | |
| border-radius: 3px; | |
| font-size: 12px; | |
| } | |
| #sf-user-switcher .formbutton:hover { | |
| background: #e5e5e5; | |
| } | |
| /* Login Dialog Styles */ | |
| #sf-login-dialog-overlay { | |
| position: fixed; | |
| top: 0; | |
| left: 0; | |
| width: 100%; | |
| height: 100%; | |
| background: rgba(0,0,0,0.5); | |
| z-index: 10001; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| } | |
| #sf-login-dialog { | |
| background: #fff; | |
| padding: 20px; | |
| border-radius: 5px; | |
| width: 300px; | |
| box-shadow: 0 4px 12px rgba(0,0,0,0.2); | |
| } | |
| #sf-login-dialog h2 { | |
| margin: 0 0 15px; | |
| font-size: 18px; | |
| text-align: center; | |
| } | |
| #sf-login-dialog .field { | |
| margin-bottom: 10px; | |
| } | |
| #sf-login-dialog label { | |
| display: block; | |
| margin-bottom: 5px; | |
| font-size: 12px; | |
| } | |
| #sf-login-dialog input[type="text"], | |
| #sf-login-dialog input[type="password"] { | |
| width: 100%; | |
| padding: 6px; | |
| box-sizing: border-box; | |
| border: 1px solid #ccc; | |
| border-radius: 3px; | |
| } | |
| #sf-login-dialog .buttons { | |
| margin-top: 15px; | |
| text-align: right; | |
| } | |
| #sf-login-dialog .buttons button { | |
| padding: 5px 15px; | |
| margin-left: 10px; | |
| cursor: pointer; | |
| } | |
| #sf-login-dialog .error { | |
| color: #c00; | |
| font-size: 12px; | |
| margin-bottom: 10px; | |
| text-align: center; | |
| } | |
| #sf-login-dialog .captcha-container { | |
| text-align: center; | |
| margin-bottom: 10px; | |
| } | |
| #sf-login-dialog .captcha-container img { | |
| cursor: pointer; | |
| max-width: 100%; | |
| } | |
| /* Hide specific page elements */ | |
| #goodapp { | |
| display: none !important; | |
| } | |
| #reminder li a { | |
| display: none !important; | |
| } | |
| `; | |
| GM_addStyle(CSS); | |
| // Cookie helpers | |
| function getCookies() { | |
| return document.cookie.split(';').reduce((obj, pair) => { | |
| const [k, v] = pair.trim().split('='); | |
| if (k) obj[k] = v; | |
| return obj; | |
| }, {}); | |
| } | |
| function setCookie(k, v, expiresDays = 30) { | |
| const expires = new Date(); | |
| expires.setTime(expires.getTime() + expiresDays * 24 * 60 * 60 * 1000); | |
| document.cookie = `${k}=${v};domain=${COOKIE_DOMAIN};expires=${expires.toUTCString()};path=/`; | |
| } | |
| function deleteCookie(k) { | |
| document.cookie = `${k}=;domain=${COOKIE_DOMAIN};expires=Thu, 01 Jan 1970 00:00:00 GMT;path=/`; | |
| } | |
| // Storage helpers | |
| function loadAllUserData() { | |
| return GM_getValue(STORAGE_KEY, []); | |
| } | |
| function saveAllUserData(data) { | |
| GM_setValue(STORAGE_KEY, data); | |
| } | |
| function getLoggedInUserId() { | |
| const cookies = getCookies(); | |
| return cookies['u'] || ''; | |
| } | |
| async function addOrUpdateCurrentUser() { | |
| const userId = getLoggedInUserId(); | |
| if (!userId) return; | |
| const nickname = document.querySelector('#user_top h3')?.textContent?.replace(/▾$/, '').trim() || ''; | |
| const avatarUrl = document.querySelector('#user_top img')?.src || ''; | |
| const allCookies = getCookies(); | |
| const filteredCookies = Object.fromEntries( | |
| Object.entries(allCookies).filter(([k]) => !(k.startsWith('_') || k === 'uuid')) | |
| ); | |
| const userData = { userId, nickname, avatarUrl, cookies: filteredCookies, lastUpdated: Date.now() }; | |
| let allData = loadAllUserData(); | |
| allData = allData.filter(u => u.userId !== userId); | |
| allData.unshift(userData); | |
| saveAllUserData(allData); | |
| } | |
| function switchToUser(userId) { | |
| const allData = loadAllUserData(); | |
| const user = allData.find(u => u.userId === userId); | |
| if (!user) return; | |
| for (const [k, v] of Object.entries(user.cookies)) { | |
| setCookie(k, v); | |
| } | |
| // Update the lastUpdated timestamp for this user | |
| user.lastUpdated = Date.now(); | |
| saveAllUserData(allData); | |
| window.location.href = '/home'; | |
| } | |
| function extendExpiringCookies() { | |
| const allData = loadAllUserData(); | |
| const now = Date.now(); | |
| const SEVEN_DAYS_MS = 7 * 24 * 60 * 60 * 1000; | |
| const THIRTY_DAYS_MS = 30 * 24 * 60 * 60 * 1000; | |
| const EXPIRATION_THRESHOLD = THIRTY_DAYS_MS - SEVEN_DAYS_MS; // 23 days | |
| let updated = false; | |
| allData.forEach(user => { | |
| // If no lastUpdated timestamp, set it to now (for backward compatibility) | |
| if (!user.lastUpdated) { | |
| user.lastUpdated = now; | |
| updated = true; | |
| return; | |
| } | |
| const age = now - user.lastUpdated; | |
| // If cookies are older than 23 days (will expire in less than 7 days), extend them | |
| if (age > EXPIRATION_THRESHOLD) { | |
| console.log(`Extending cookies for user: ${user.nickname} (age: ${Math.round(age / (24 * 60 * 60 * 1000))} days)`); | |
| // Re-set all cookies with fresh 30-day expiration | |
| for (const [k, v] of Object.entries(user.cookies)) { | |
| setCookie(k, v); | |
| } | |
| user.lastUpdated = now; | |
| updated = true; | |
| } | |
| }); | |
| if (updated) { | |
| saveAllUserData(allData); | |
| } | |
| } | |
| function removeUser(userId) { | |
| const allData = loadAllUserData(); | |
| const filtered = allData.filter(u => u.userId !== userId); | |
| saveAllUserData(filtered); | |
| renderSwitcher(); | |
| } | |
| // UI Rendering | |
| function renderSwitcher() { | |
| const userTop = document.querySelector('#user_top'); | |
| if (!userTop) return; | |
| userTop.classList.add('sf-is-ready'); | |
| let switcher = document.getElementById('sf-user-switcher'); | |
| if (switcher) switcher.remove(); | |
| switcher = document.createElement('ul'); | |
| switcher.id = 'sf-user-switcher'; | |
| const allData = loadAllUserData(); | |
| const currentUserId = getLoggedInUserId(); | |
| allData.forEach(user => { | |
| if (user.userId === currentUserId) return; | |
| const li = document.createElement('li'); | |
| li.className = 'sf-user-item'; | |
| const info = document.createElement('a'); | |
| info.className = 'sf-user-info'; | |
| info.href = 'javascript:void(0)'; | |
| info.innerHTML = `<img src="${user.avatarUrl}" alt="">${user.nickname}`; | |
| info.addEventListener('click', () => switchToUser(user.userId)); | |
| const del = document.createElement('span'); | |
| del.className = 'sf-del-icon'; | |
| del.textContent = '×'; | |
| del.title = '删除此用户'; | |
| del.addEventListener('click', (e) => { | |
| e.stopPropagation(); | |
| if (confirm(`确定要从切换列表中删除 @${user.nickname} 吗?`)) { | |
| removeUser(user.userId); | |
| } | |
| }); | |
| li.appendChild(info); | |
| li.appendChild(del); | |
| switcher.appendChild(li); | |
| }); | |
| const addLi = document.createElement('li'); | |
| addLi.className = 'sf-add-new-user'; | |
| const addBtn = document.createElement('input'); | |
| addBtn.type = 'button'; | |
| addBtn.value = '登入另一个……'; | |
| addBtn.className = 'formbutton'; | |
| addBtn.addEventListener('click', showLoginDialog); | |
| addLi.appendChild(addBtn); | |
| switcher.appendChild(addLi); | |
| userTop.appendChild(switcher); | |
| } | |
| // Login Dialog Implementation | |
| let loginDialogToken = ''; | |
| function showLoginDialog() { | |
| const overlay = document.createElement('div'); | |
| overlay.id = 'sf-login-dialog-overlay'; | |
| const dialog = document.createElement('div'); | |
| dialog.id = 'sf-login-dialog'; | |
| dialog.innerHTML = ` | |
| <h2>添加账户</h2> | |
| <div id="sf-login-error" class="error"></div> | |
| <div class="field"> | |
| <label>用户名或 Email</label> | |
| <input type="text" id="sf-login-name"> | |
| </div> | |
| <div class="field"> | |
| <label>密码</label> | |
| <input type="password" id="sf-login-pass"> | |
| </div> | |
| <div id="sf-captcha-container" class="captcha-container" style="display:none"> | |
| <img id="sf-captcha-img" title="点击刷新验证码"> | |
| <input type="text" id="sf-captcha-val" placeholder="输入验证码"> | |
| </div> | |
| <div class="buttons"> | |
| <button id="sf-login-cancel">取消</button> | |
| <button id="sf-login-submit" style="background:#007bff;color:#fff;border:none;border-radius:3px;">登录</button> | |
| </div> | |
| `; | |
| overlay.appendChild(dialog); | |
| document.body.appendChild(overlay); | |
| const errorEl = dialog.querySelector('#sf-login-error'); | |
| const captchaContainer = dialog.querySelector('#sf-captcha-container'); | |
| const captchaImg = dialog.querySelector('#sf-captcha-img'); | |
| const updateCaptcha = () => { | |
| captchaImg.src = `https://fanfou.com/captcha.png?${Date.now()}`; | |
| captchaContainer.style.display = 'block'; | |
| }; | |
| captchaImg.onclick = updateCaptcha; | |
| // Get CSRF Token from login page | |
| GM_xmlhttpRequest({ | |
| method: "GET", | |
| url: "https://fanfou.com/login", | |
| onload: function (res) { | |
| const doc = new DOMParser().parseFromString(res.responseText, "text/html"); | |
| loginDialogToken = doc.querySelector('input[name="token"]')?.value || ''; | |
| if (res.responseText.includes('captcha')) { | |
| updateCaptcha(); | |
| } | |
| } | |
| }); | |
| dialog.querySelector('#sf-login-cancel').onclick = () => overlay.remove(); | |
| dialog.querySelector('#sf-login-submit').onclick = async () => { | |
| const loginname = dialog.querySelector('#sf-login-name').value; | |
| const loginpass = dialog.querySelector('#sf-login-pass').value; | |
| const captcha = dialog.querySelector('#sf-captcha-val').value; | |
| if (!loginname || !loginpass) { | |
| errorEl.textContent = '请输入用户名和密码'; | |
| return; | |
| } | |
| errorEl.textContent = '登录中...'; | |
| // Backup current cookies | |
| const originalCookies = getCookies(); | |
| // Perform Login POST | |
| const formData = new URLSearchParams(); | |
| formData.append('loginname', loginname); | |
| formData.append('loginpass', loginpass); | |
| formData.append('action', 'login'); | |
| formData.append('token', loginDialogToken); | |
| if (captcha) formData.append('captcha', captcha); | |
| formData.append('auto_login', 'on'); | |
| GM_xmlhttpRequest({ | |
| method: "POST", | |
| url: "https://fanfou.com/login", | |
| data: formData.toString(), | |
| headers: { "Content-Type": "application/x-www-form-urlencoded" }, | |
| onload: function (res) { | |
| // Check if login was successful by checking the redirected page or Set-Cookie | |
| // GM_xmlhttpRequest might not give us the result of Set-Cookie in a helpful way, | |
| // but if it worked, the document.cookie on fanfou.com will have changed. | |
| // We wait a bit to ensure browser has processed cookies from the request | |
| setTimeout(async () => { | |
| const newCookies = getCookies(); | |
| const newUserId = newCookies['u']; | |
| if (newUserId && newUserId !== originalCookies['u']) { | |
| // Success! Add to switcher | |
| const userData = { | |
| userId: newUserId, | |
| nickname: loginname, // Fallback, will be updated when user actually switches | |
| avatarUrl: 'https://fanfou.com/img/default_avatar_32.png', // Temporary | |
| cookies: Object.fromEntries( | |
| Object.entries(newCookies).filter(([k]) => !(k.startsWith('_') || k === 'uuid')) | |
| ) | |
| }; | |
| // Fetch profile to get real nickname/avatar | |
| GM_xmlhttpRequest({ | |
| method: "GET", | |
| url: "https://fanfou.com/home", | |
| onload: function (profileRes) { | |
| const pDoc = new DOMParser().parseFromString(profileRes.responseText, "text/html"); | |
| userData.nickname = pDoc.querySelector('#user_top h3')?.textContent?.replace(/▾$/, '').trim() || userData.nickname; | |
| userData.avatarUrl = pDoc.querySelector('#user_top img')?.src || userData.avatarUrl; | |
| let allData = loadAllUserData(); | |
| allData = allData.filter(u => u.userId !== userData.userId); | |
| allData.unshift(userData); | |
| saveAllUserData(allData); | |
| // Restore original cookies | |
| restoreCookies(originalCookies); | |
| overlay.remove(); | |
| renderSwitcher(); | |
| } | |
| }); | |
| } else { | |
| errorEl.textContent = '登录失败,请检查账号密码或验证码'; | |
| // If captcha was needed but wrong, refresh it | |
| updateCaptcha(); | |
| // Restore original cookies just in case | |
| restoreCookies(originalCookies); | |
| } | |
| }, 1000); | |
| } | |
| }); | |
| }; | |
| } | |
| function restoreCookies(cookies) { | |
| // Clear current cookies first to be safe | |
| const current = getCookies(); | |
| for (const k in current) { | |
| deleteCookie(k); | |
| } | |
| // Set backup cookies | |
| for (const [k, v] of Object.entries(cookies)) { | |
| setCookie(k, v); | |
| } | |
| } | |
| // ==================================== | |
| // 图片 URL 清理功能 | |
| // ==================================== | |
| function initImageUrlCleaner() { | |
| /** | |
| * 功能说明: | |
| * 1. 监视 #ZoomBox 中的图片元素 | |
| * 2. 自动移除图片 URL 中的 @suffix 部分(如 @596w_1l.jpg) | |
| * 3. 使用 MutationObserver 实时监控 DOM 变化 | |
| */ | |
| // 辅助函数:移除 URL 中的 @suffix | |
| function stripAtSuffix(url) { | |
| if (!url) return url; | |
| // 在最后一个 '/' 之后查找 '@',如果存在则移除 @ 及其后面的内容 | |
| // 示例:.../image.jpg@596w_1l.jpg -> .../image.jpg | |
| const lastSlash = url.lastIndexOf('/'); | |
| const atPos = url.indexOf('@', lastSlash + 1); | |
| if (atPos === -1) return url; | |
| return url.slice(0, atPos); | |
| } | |
| // 处理单个图片元素 | |
| function processImage(img) { | |
| if (!img || !img.src) return; | |
| const newSrc = stripAtSuffix(img.src); | |
| if (newSrc !== img.src) { | |
| img.src = newSrc; | |
| // 移除可能冲突的 srcset 属性 | |
| if (img.getAttribute('srcset')) { | |
| img.removeAttribute('srcset'); | |
| } | |
| // 处理常见的 lazy loading 属性 | |
| const lazyAttrs = ['data-src', 'data-original', 'data-lazy', 'data-srcset']; | |
| lazyAttrs.forEach(attr => { | |
| const value = img.getAttribute(attr); | |
| if (value) { | |
| img.setAttribute(attr, stripAtSuffix(value)); | |
| } | |
| }); | |
| console.log('Space Fanfou Image URL Cleaner: Cleaned image URL'); | |
| } | |
| } | |
| // 查找并处理目标图片 | |
| function processZoomBoxImage() { | |
| const img = document.querySelector('#ZoomBox img'); | |
| if (img) { | |
| processImage(img); | |
| } | |
| } | |
| // 创建 MutationObserver 监视 DOM 变化 | |
| const observer = new MutationObserver(mutations => { | |
| for (const mutation of mutations) { | |
| // 处理新添加的节点 | |
| if (mutation.addedNodes && mutation.addedNodes.length) { | |
| for (const node of mutation.addedNodes) { | |
| if (!(node instanceof HTMLElement)) continue; | |
| // 检查是否为 #ZoomBox 元素 | |
| if (node.matches && node.matches('#ZoomBox')) { | |
| const img = node.querySelector('img'); | |
| if (img) processImage(img); | |
| } else { | |
| // 检查子元素中是否有 #ZoomBox img | |
| const img = node.querySelector && node.querySelector('#ZoomBox img'); | |
| if (img) processImage(img); | |
| // 检查节点本身是否为目标图片 | |
| if (node.matches && node.matches('#ZoomBox img')) { | |
| processImage(node); | |
| } | |
| } | |
| } | |
| } | |
| // 处理属性变化 | |
| if (mutation.type === 'attributes' && mutation.target) { | |
| const target = mutation.target; | |
| if (target instanceof HTMLImageElement && target.closest && target.closest('#ZoomBox')) { | |
| if (mutation.attributeName === 'src' || mutation.attributeName === 'srcset') { | |
| processImage(target); | |
| } | |
| } | |
| } | |
| } | |
| }); | |
| // 开始观察 DOM | |
| observer.observe(document.documentElement, { | |
| childList: true, | |
| subtree: true, | |
| attributes: true, | |
| attributeFilter: ['src', 'srcset'] | |
| }); | |
| // 页面加载完成后立即处理一次 | |
| processZoomBoxImage(); | |
| console.log('Space Fanfou Image URL Cleaner: Feature initialized'); | |
| } | |
| // ==================================== | |
| // 粘贴图片功能 | |
| // ==================================== | |
| function initPasteImageFeature() { | |
| /** | |
| * 功能说明: | |
| * 1. 监听窗口的 paste 事件 | |
| * 2. 从剪贴板获取图片数据 | |
| * 3. 将图片转换为 base64 格式 | |
| * 4. 修改表单参数,准备上传图片 | |
| */ | |
| // 辅助函数:检查文件类型是否为图片 | |
| function isImage(type) { | |
| return /^image\/(jpe?g|png|gif|bmp)$/i.test(type); | |
| } | |
| // 辅助函数:将 Blob 转换为 base64 | |
| function blobToBase64(blob) { | |
| return new Promise((resolve, reject) => { | |
| const reader = new FileReader(); | |
| reader.onload = () => resolve(reader.result); | |
| reader.onerror = () => reject(reader.error); | |
| reader.readAsDataURL(blob); | |
| }); | |
| } | |
| // 辅助函数:显示元素 | |
| function showElement(element) { | |
| if (!element) return; | |
| if (element.style.display === 'none') { | |
| element.style.display = ''; | |
| } | |
| const display = window.getComputedStyle(element).display; | |
| if (display === 'none') { | |
| element.style.display = 'inline'; | |
| } | |
| } | |
| // 粘贴事件处理函数 | |
| async function onPaste(event) { | |
| // 获取剪贴板项目 | |
| const items = Array.from(event.clipboardData.items); | |
| // 将项目转换为文件,过滤出 Blob 对象 | |
| const files = items.map(item => item.getAsFile()).filter(x => x instanceof Blob); | |
| // 找出第一个图片文件 | |
| const imageBlob = files.find(file => isImage(file.type)); | |
| // 如果没有图片,直接返回 | |
| if (!imageBlob) return; | |
| // 提取图片类型(如 jpeg, png 等) | |
| const imageType = imageBlob.type.replace('image/', ''); | |
| // 查找页面上的关键元素 | |
| const uploadFilename = document.querySelector('#upload-filename'); | |
| const closeHandle = document.querySelector('#ul_close'); | |
| const messageForm = document.querySelector('#message'); | |
| const actionField = document.querySelector('#phupdate input[name="action"]'); | |
| const textarea = document.querySelector('#phupdate textarea'); | |
| const base64Input = document.querySelector('#upload-base64'); | |
| const uploadWrapper = document.querySelector('#upload-wrapper'); | |
| // 如果关键元素不存在,说明不在正确的页面,直接返回 | |
| if (!uploadFilename || !messageForm || !actionField || !textarea || !base64Input) { | |
| console.log('Space Fanfou Paste Image: Required elements not found'); | |
| return; | |
| } | |
| // 设置上传的文件名 | |
| uploadFilename.textContent = `image-from-clipboard.${imageType}`; | |
| showElement(uploadFilename); | |
| // 显示关闭按钮 | |
| if (closeHandle) { | |
| showElement(closeHandle); | |
| } | |
| // 修改表单属性,准备上传图片 | |
| messageForm.setAttribute('action', '/home/upload'); | |
| messageForm.setAttribute('enctype', 'multipart/form-data'); | |
| // 修改 action 字段的值 | |
| actionField.value = 'photo.upload'; | |
| // 修改 textarea 的 name 属性 | |
| textarea.setAttribute('name', 'desc'); | |
| // 将图片转换为 base64 并设置到隐藏字段 | |
| try { | |
| const base64Data = await blobToBase64(imageBlob); | |
| base64Input.value = base64Data; | |
| // 显示上传区域 | |
| if (uploadWrapper) { | |
| showElement(uploadWrapper); | |
| } | |
| console.log('Space Fanfou Paste Image: Image pasted successfully'); | |
| } catch (error) { | |
| console.error('Space Fanfou Paste Image: Failed to convert image to base64', error); | |
| } | |
| } | |
| // 等待页面加载完成后,检查是否有发消息的文本框 | |
| function checkAndAttachListener() { | |
| const textarea = document.querySelector('#phupdate textarea'); | |
| if (textarea) { | |
| // 添加粘贴事件监听器 | |
| window.addEventListener('paste', onPaste); | |
| console.log('Space Fanfou Paste Image: Feature initialized'); | |
| } else { | |
| console.log('Space Fanfou Paste Image: Textarea not found, feature not initialized'); | |
| } | |
| } | |
| // 页面加载完成后初始化 | |
| if (document.readyState === 'loading') { | |
| document.addEventListener('DOMContentLoaded', checkAndAttachListener); | |
| } else { | |
| checkAndAttachListener(); | |
| } | |
| } | |
| function init() { | |
| // Extend cookies for users that will expire within 7 days | |
| extendExpiringCookies(); | |
| addOrUpdateCurrentUser(); | |
| renderSwitcher(); | |
| // Hook logout | |
| const logoutLink = Array.from(document.querySelectorAll('#navigation a')).find(a => a.textContent.includes('退出')); | |
| if (logoutLink) { | |
| logoutLink.addEventListener('click', () => { | |
| const uid = getLoggedInUserId(); | |
| if (uid) { | |
| const allData = loadAllUserData(); | |
| saveAllUserData(allData.filter(u => u.userId !== uid)); | |
| } | |
| }); | |
| } | |
| } | |
| // 初始化所有功能模块 | |
| init(); | |
| initPasteImageFeature(); | |
| initImageUrlCleaner(); | |
| })(); | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment