- // ==UserScript==
- // [url=home.php?mod=space&uid=42664]@name[/url] KLPBBS 去重定向
- // [url=home.php?mod=space&uid=281872]@namespace[/url] https://klpbbs.com/
- // [url=home.php?mod=space&uid=657908]@version[/url] 1.3
- // [url=home.php?mod=space&uid=585275]@Description[/url] 去除 klpbbs.com 的 url.php 重定向,点击站外链接时顶部显示圆角绿色横幅(滑入动画),倒计时跳动,自动隐藏后在新标签页打开链接。
- // [url=home.php?mod=space&uid=324283]@Author[/url] Helper
- // [url=home.php?mod=space&uid=788734]@MATCH[/url] *://klpbbs.com/*
- // @match *://*.klpbbs.com/*
- // [url=home.php?mod=space&uid=423753]@Grant[/url] none
- // ==/UserScript==
- (function() {
- 'use strict';
- const SITE_DOMAIN = 'klpbbs.com';
- let bannerTimeout = null;
- let bannerInterval = null;
- let bannerDiv = null;
- let isClosing = false; // 防止重复关闭
- // 获取域名(用于横幅显示)
- function getHostname(url) {
- try {
- const a = document.createElement('a');
- a.href = url;
- return a.hostname;
- } catch(e) {
- return '';
- }
- }
- // 关闭横幅(带退场动画)
- function closeBanner(doJump, targetUrl) {
- if (!bannerDiv || isClosing) return;
- isClosing = true;
- // 清除所有计时器
- if (bannerTimeout) clearTimeout(bannerTimeout);
- if (bannerInterval) clearInterval(bannerInterval);
- // 添加退场动画类
- bannerDiv.classList.add('banner-hide');
- // 等待动画结束再移除横幅
- const onTransitionEnd = () => {
- bannerDiv.removeEventListener('transitionend', onTransitionEnd);
- if (bannerDiv && bannerDiv.parentNode) bannerDiv.parentNode.removeChild(bannerDiv);
- bannerDiv = null;
- isClosing = false;
- // 执行跳转(如果需要)
- if (doJump && targetUrl && !targetUrl.startsWith('javascript:')) {
- window.open(targetUrl, '_blank');
- }
- };
- bannerDiv.addEventListener('transitionend', onTransitionEnd, { once: true });
- // 防止某些情况下transitionend不触发,设置后备定时器
- setTimeout(() => {
- if (bannerDiv && bannerDiv.parentNode) {
- bannerDiv.removeEventListener('transitionend', onTransitionEnd);
- bannerDiv.parentNode.removeChild(bannerDiv);
- bannerDiv = null;
- isClosing = false;
- if (doJump && targetUrl && !targetUrl.startsWith('javascript:')) {
- window.open(targetUrl, '_blank');
- }
- }
- }, 500);
- }
- // 显示绿色横幅(动画版)
- function showBanner(targetUrl) {
- if (bannerDiv) closeBanner(false); // 如果有旧横幅,先关闭(无跳转)
- // 创建横幅元素
- bannerDiv = document.createElement('div');
- bannerDiv.id = 'ext-link-banner';
- bannerDiv.className = 'banner-show'; // 初始无动画,稍后添加入场类
- bannerDiv.style.cssText = `
- position: fixed;
- top: 10px;
- left: 10px;
- right: 10px;
- background-color: #4caf50;
- color: white;
- text-align: center;
- padding: 12px 40px 12px 20px;
- font-size: 16px;
- z-index: 999999;
- font-family: sans-serif;
- box-shadow: 0 4px 12px rgba(0,0,0,0.15);
- border-radius: 12px;
- cursor: default;
- backdrop-filter: blur(2px);
- transform: translateY(-120%);
- opacity: 0;
- transition: transform 0.25s cubic-bezier(0.2, 0.9, 0.4, 1.1), opacity 0.2s ease;
- `;
- // 创建内容容器
- const contentSpan = document.createElement('span');
- const host = getHostname(targetUrl);
- contentSpan.innerHTML = `即将跳转到外部网站:${host},<span id="countdown-num" style="display:inline-block; min-width:24px;">3</span> 秒后自动在新标签页打开...`;
- // 取消按钮
- const closeBtn = document.createElement('span');
- closeBtn.textContent = '✕ 取消';
- closeBtn.style.cssText = `
- position: absolute;
- right: 12px;
- top: 50%;
- transform: translateY(-50%);
- cursor: pointer;
- background-color: rgba(0,0,0,0.2);
- padding: 4px 10px;
- border-radius: 20px;
- font-size: 14px;
- transition: background-color 0.2s;
- `;
- closeBtn.onmouseover = () => closeBtn.style.backgroundColor = 'rgba(0,0,0,0.35)';
- closeBtn.onmouseout = () => closeBtn.style.backgroundColor = 'rgba(0,0,0,0.2)';
- closeBtn.onclick = (e) => {
- e.stopPropagation();
- closeBanner(false); // 取消跳转
- };
- bannerDiv.appendChild(contentSpan);
- bannerDiv.appendChild(closeBtn);
- document.body.appendChild(bannerDiv);
- // 强制重绘后添加入场动画类
- requestAnimationFrame(() => {
- bannerDiv.style.transform = 'translateY(0)';
- bannerDiv.style.opacity = '1';
- });
- // 倒计时逻辑
- let countdown = 3;
- const countdownSpan = document.getElementById('countdown-num');
- if (!countdownSpan) return; // 安全检测
- // 跳动动画CSS(动态添加)
- const styleId = 'countdown-bounce-style';
- if (!document.getElementById(styleId)) {
- const style = document.createElement('style');
- style.id = styleId;
- style.textContent = `
- .countdown-bounce {
- animation: bounceAnim 0.3s ease;
- }
- @keyframes bounceAnim {
- 0% { transform: scale(1); }
- 50% { transform: scale(1.4); color: #fff3b0; }
- 100% { transform: scale(1); }
- }
- `;
- document.head.appendChild(style);
- }
- const updateCountdown = () => {
- if (!countdownSpan || !bannerDiv) return;
- countdownSpan.textContent = countdown;
- countdownSpan.classList.add('countdown-bounce');
- setTimeout(() => countdownSpan.classList.remove('countdown-bounce'), 300);
- if (countdown <= 0) {
- // 倒计时结束,关闭横幅并跳转
- if (bannerInterval) clearInterval(bannerInterval);
- closeBanner(true, targetUrl);
- }
- countdown--;
- };
- updateCountdown(); // 立即显示3
- bannerInterval = setInterval(updateCountdown, 1000);
- // 设置超时保护(防止interval意外不触发)
- bannerTimeout = setTimeout(() => {
- if (bannerInterval) clearInterval(bannerInterval);
- closeBanner(true, targetUrl);
- }, 3000);
- }
- // 从 url.php 提取真实链接
- function extractRealUrl(url) {
- if (url.includes('/url.php?url=')) {
- try {
- const urlObj = new URL(url);
- const real = urlObj.searchParams.get('url');
- if (real) return decodeURIComponent(real);
- } catch(e) {}
- }
- return null;
- }
- // 判断是否为站外链接(不含重定向链接本身)
- function isExternalLink(href) {
- if (!href) return false;
- if (href.startsWith('javascript:') || href.startsWith('mailto:') || href.startsWith('tel:')) return false;
- try {
- const a = document.createElement('a');
- a.href = href;
- const linkHost = a.hostname;
- if (!linkHost) return false;
- if (linkHost === SITE_DOMAIN || linkHost.endsWith('.' + SITE_DOMAIN)) return false;
- return true;
- } catch(e) {
- return false;
- }
- }
- // 全局点击拦截(捕获阶段)
- document.addEventListener('click', function(e) {
- if (e.button !== 0) return; // 仅左键
- if (e.ctrlKey || e.shiftKey || e.metaKey) return; // 保留新窗口快捷键
- let target = e.target;
- while (target && target.tagName !== 'A') target = target.parentElement;
- if (!target || !target.href) return;
- const href = target.href;
- const real = extractRealUrl(href);
- if (real) {
- e.preventDefault();
- e.stopPropagation();
- showBanner(real);
- return;
- }
- if (isExternalLink(href)) {
- e.preventDefault();
- e.stopPropagation();
- showBanner(href);
- }
- }, true);
- })();
复制代码 |