您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
在浙大 WebVPN 页面右下角提供网址转化工具。
// ==UserScript== // @name 网址(WebVPN)转化工具 // @namespace http://tampermonkey.net/ // @version 1.1 // @description 在浙大 WebVPN 页面右下角提供网址转化工具。 // @match *://*.webvpn.zju.edu.cn/* // @author Slowist // @grant GM_addStyle // @run-at document-end // @license Apache 2.0 // ==/UserScript== (function() { 'use strict'; GM_addStyle(` #converter-container-unique { --bg-color: #ffffff; --text-color: #212529; --io-bg-color: #ffffff; --io-text-color: #000000; --border-color: #dcdcdc; --shadow-color: rgba(0,0,0,0.15); --focus-ring-color: rgba(0, 123, 255, 0.25); --primary-accent-bg: #007bff; --primary-accent-hover-bg: #0056b3; --secondary-accent-bg: #28a745; --secondary-accent-hover-bg: #218838; --danger-color: #dc3545; } @media (prefers-color-scheme: dark) { #converter-container-unique { --bg-color: #2d2d2d; --text-color: #e0e0e0; --io-bg-color: #3a3a3a; --io-text-color: #f0f0f0; --border-color: #555555; --shadow-color: rgba(0,0,0,0.4); --focus-ring-color: rgba(0, 123, 255, 0.4); } } html[data-color-mode="dark"] #converter-container-unique, html[data-theme="dark"] #converter-container-unique { --bg-color: #2d2d2d; --text-color: #e0e0e0; --io-bg-color: #3a3a3a; --io-text-color: #f0f0f0; --border-color: #555555; --shadow-color: rgba(0,0,0,0.4); --focus-ring-color: rgba(0, 123, 255, 0.4); } #converter-container-unique { position: fixed; bottom: 25px; right: 25px; z-index: 99999; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; color-scheme: light dark; } #converter-container-unique button, #converter-container-unique textarea { font-family: inherit; font-size: 15px; } #converter-toggle-button-unique { width: 50px; height: 50px; border-radius: 50%; background-color: var(--primary-accent-bg); color: white; border: none; cursor: pointer; font-size: 24px; display: flex; align-items: center; justify-content: center; box-shadow: 0 4px 12px var(--shadow-color); transition: all 0.3s ease; } #converter-toggle-button-unique:hover { background-color: var(--primary-accent-hover-bg); transform: translateY(-2px); } #converter-window-unique { position: absolute; bottom: 65px; right: 0; width: 320px; background-color: var(--bg-color); color: var(--text-color); border-radius: 12px; box-shadow: 0 6px 25px var(--shadow-color); display: none; flex-direction: column; padding: 15px 20px; transform-origin: bottom right; } @keyframes scale-in-fade { from { transform: scale(0.8); opacity: 0; } to { transform: scale(1); opacity: 1; } } @keyframes scale-out-fade { from { transform: scale(1); opacity: 1; } to { transform: scale(0.8); opacity: 0; } } #converter-window-unique.visible { display: flex; animation: scale-in-fade 0.25s cubic-bezier(0.25, 0.46, 0.45, 0.94) forwards; } #converter-window-unique.hiding { animation: scale-out-fade 0.15s cubic-bezier(0.55, 0.085, 0.68, 0.53) forwards; } #converter-header-unique { display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px; } #converter-title-unique { font-size: 16px; font-weight: 600; } .converter-header-controls-unique { display: flex; align-items: center; } .converter-header-button-unique { background: none; border: none; cursor: pointer; color: var(--text-color); opacity: 0.6; width: 28px; height: 28px; display:flex; align-items: center; justify-content: center; padding: 0; } .converter-header-button-unique:hover { opacity: 1; } #converter-disable-button-unique:hover { color: var(--danger-color); } .converter-main-content-unique { display: flex; flex-direction: column; gap: 12px; } .converter-output-wrapper-unique { position: relative; } #converter-copy-button-unique { position: absolute; top: 6px; right: 6px; width: 28px; height: 28px; display: flex; align-items: center; justify-content: center; background-color: var(--io-bg-color); border: 1px solid var(--border-color); color: var(--text-color); border-radius: 6px; cursor: pointer; transition: all 0.2s ease; opacity: 0.7; } #converter-copy-button-unique:hover { opacity: 1; border-color: var(--primary-accent-bg); } #converter-copy-button-unique.copied { color: var(--secondary-accent-bg); border-color: var(--secondary-accent-bg); } .converter-io-unique { width: 100%; padding: 10px; border: 1px solid var(--border-color); background-color: var(--io-bg-color); color: var(--io-text-color); border-radius: 6px; box-sizing: border-box; resize: vertical; transition: border-color 0.3s, box-shadow 0.3s; } #converter-output-unique { padding-right: 40px; } .converter-io-unique::placeholder { color: #888; } .converter-io-unique:focus { outline: none; border-color: var(--primary-accent-bg); box-shadow: 0 0 0 2px var(--focus-ring-color); } #converter-execute-button-unique { padding: 10px 15px; background-color: var(--secondary-accent-bg); color: white; border: none; border-radius: 6px; cursor: pointer; font-weight: 500; transition: background-color 0.3s; width: 100%; box-sizing: border-box; } #converter-execute-button-unique:hover { background-color: var(--secondary-accent-hover-bg); } #converter-footer-unique { text-align: center; padding-top: 4px; } #converter-footer-unique a { font-size: 13px; color: var(--primary-accent-bg); text-decoration: none; } #converter-footer-unique a:hover { text-decoration: underline; } `); const SVG_ICON_COPY = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path></svg>`; const SVG_ICON_CHECK = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"></polyline></svg>`; const SVG_ICON_MINIMIZE = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><line x1="5" y1="12" x2="19" y2="12"></line></svg>`; const SVG_ICON_CLOSE = `<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>`; const container = document.createElement('div'); container.id = 'converter-container-unique'; document.body.appendChild(container); const toggleButton = document.createElement('button'); toggleButton.id = 'converter-toggle-button-unique'; toggleButton.innerHTML = '⇄'; container.appendChild(toggleButton); const windowDiv = document.createElement('div'); windowDiv.id = 'converter-window-unique'; windowDiv.innerHTML = ` <div id="converter-header-unique"> <span id="converter-title-unique">URL Converter</span> <div class="converter-header-controls-unique"> <button id="converter-minimize-button-unique" class="converter-header-button-unique" title="Hide Window">${SVG_ICON_MINIMIZE}</button> <button id="converter-disable-button-unique" class="converter-header-button-unique" title="Hide until next refresh">${SVG_ICON_CLOSE}</button> </div> </div> <div class="converter-main-content-unique"> <textarea id="converter-input-unique" class="converter-io-unique" rows="4" placeholder="Input Website Here..."></textarea> <button id="converter-execute-button-unique">Bake!</button> <div class="converter-output-wrapper-unique"> <textarea id="converter-output-unique" class="converter-io-unique" rows="4" placeholder="Result..." readonly></textarea> <button id="converter-copy-button-unique" title="Copy to clipboard">${SVG_ICON_COPY}</button> </div> <div id="converter-footer-unique"> <a href="https://webvpn.zju.edu.cn/" target="_blank">WebVPN 入口</a> </div> </div> `; container.appendChild(windowDiv); const executeButton = document.getElementById('converter-execute-button-unique'); const inputArea = document.getElementById('converter-input-unique'); const outputArea = document.getElementById('converter-output-unique'); const copyButton = document.getElementById('converter-copy-button-unique'); const disableButton = document.getElementById('converter-disable-button-unique'); const minimizeButton = document.getElementById('converter-minimize-button-unique'); function hideWindowWithAnimation() { if (!windowDiv.classList.contains('visible')) return; windowDiv.classList.add('hiding'); setTimeout(() => { windowDiv.classList.remove('visible'); windowDiv.classList.remove('hiding'); }, 150); } toggleButton.addEventListener('click', (event) => { event.stopPropagation(); if (windowDiv.classList.contains('visible')) { hideWindowWithAnimation(); } else { windowDiv.classList.add('visible'); } }); windowDiv.addEventListener('click', (event) => event.stopPropagation()); document.addEventListener('click', () => { hideWindowWithAnimation(); }); executeButton.addEventListener('click', () => { const inputText = inputArea.value; const regex = /^https?:\/\/([a-zA-Z0-9-]+)\.webvpn\.zju\.edu\.cn(?::\d+)?(\/.*)?$/; const matchResult = inputText.match(regex); let outputText; if (matchResult && matchResult[1]) { const capturedPart = matchResult[1]; const chunks = capturedPart.split('-'); const lastChunk = chunks[chunks.length - 1]; if (lastChunk === 's') { outputText = "https://" + chunks.slice(0, -1).join("."); } else { outputText = "http://" + chunks.join("."); } outputText += (matchResult[2] || ""); } else { outputText = inputText; } outputArea.value = outputText; }); copyButton.addEventListener('click', () => { if (!outputArea.value) return; navigator.clipboard.writeText(outputArea.value).then(() => { copyButton.innerHTML = SVG_ICON_CHECK; copyButton.classList.add('copied'); setTimeout(() => { copyButton.innerHTML = SVG_ICON_COPY; copyButton.classList.remove('copied'); }, 2000); }).catch(err => console.error('Failed to copy text: ', err)); }); minimizeButton.addEventListener('click', (event) => { event.stopPropagation(); hideWindowWithAnimation(); }); disableButton.addEventListener('click', () => { container.remove(); }); })();