hexo-safego-thun888
Version:
✨Hexo plugin for safe external link redirection with base64 encoding, domain whitelisting, and support for a custom redirect page.
523 lines (464 loc) • 16.6 kB
HTML
<html lang="zh">
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1,user-scalable=no">
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="robots" content="noindex, nofollow" />
<title>{{title}}</title>
<style type="text/css">
:root {
--theme-color: {{theme_color}};
--theme-color-darker: {{theme_color}}e6; /* 90% 透明度 */
--theme-color-hover: {{theme_color}}cc; /* 80% 透明度 */
}
body {
display: flex;
align-items: center;
justify-content: center;
height: 100vh;
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Helvetica, Arial, sans-serif;
overflow: hidden;
flex-direction: column;
background-color: #f9fafb;
}
.avatar-container {
margin-bottom: 16px;
text-align: center;
}
.avatar {
width: 48px;
height: 48px;
border-radius: 50%;
display: none;
border: 1px solid #eee;
}
.avatar-placeholder {
width: 48px;
height: 48px;
border-radius: 50%;
background-color: #eaeaea;
display: inline-block;
}
.header {
margin-bottom: 8px;
text-align: center;
}
.logo {
width: 64px;
height: 30px;
}
.description {
font-size: 18px;
color: #333;
margin-bottom: 4px;
text-align: center;
}
.subtitle {
font-size: 12px;
margin-bottom: 20px;
color: #8590a6;
text-align: center;
}
.card {
text-align: left;
padding: 24px;
border-radius: 2px;
background-color: #fff;
box-shadow: 0 1px 3px rgba(18, 18, 18, 0.1);
width: 400px;
max-width: 90%;
}
.title {
font-size: 18px;
font-weight: 600;
color: #121212;
margin-bottom: 8px;
}
.message {
font-size: 14px;
color: #646464;
line-height: 1.6;
margin-bottom: 24px;
}
.link-container {
margin-bottom: 20px;
padding: 12px 12px;
background-color: #f6f6f6;
border-radius: 2px;
word-break: break-all;
}
.link {
font-size: 14px;
color: #121212;
text-decoration: none;
}
.countdown-container {
font-size: 13px;
color: #8590a6;
margin-bottom: 16px;
text-align: center;
}
.progress-bar {
width: 100%;
height: 3px;
background-color: #f0f2f5;
border-radius: 2px;
overflow: hidden;
margin-bottom: 24px;
}
.progress {
height: 100%;
background-color: var(--theme-color);
width: 100%;
transition: width 1s linear;
}
.divider {
height: 1px;
background-color: #EBEBEB;
margin: 16px 0;
}
.button-container {
display: flex;
justify-content: space-between;
gap: 12px;
}
.button {
display: inline-block;
padding: 8px 0;
border-radius: 3px;
border: none;
cursor: pointer;
font-size: 14px;
text-align: center;
flex: 1;
}
.cancel-button {
background-color: #f7f8fa;
color: #444;
border: 1px solid #edeeef;
}
.confirm-button {
background-color: var(--theme-color);
color: white;
}
.confirm-button:hover {
background-color: var(--theme-color-hover);
}
.site-info {
display: flex;
align-items: center;
padding: 12px;
background-color: #f9f9f9;
border-radius: 4px;
margin-bottom: 20px;
position: relative;
cursor: pointer;
}
.site-favicon {
width: 16px;
height: 16px;
margin-right: 8px;
border-radius: 2px;
}
.site-title {
font-size: 14px;
color: #333;
flex: 1;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.site-info-loading {
display: flex;
align-items: center;
justify-content: center;
height: 40px;
font-size: 13px;
color: #8590a6;
}
.site-desc {
font-size: 12px;
color: #333;
margin-top: 8px;
padding: 12px;
display: none;
margin-bottom: 20px;
background-color: #f9f9f9;
border-radius: 4px;
}
/* 深色模式 */
@media (prefers-color-scheme: dark) {
body {
background-color: #1A1A1A;
}
.card {
background-color: #2A2A2A;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
}
.avatar-placeholder {
background-color: #3a3a3a;
}
.avatar {
border-color: #444;
}
.description {
color: #d3d3d3;
}
.subtitle {
color: #999;
}
.title {
color: #F6F6F6;
}
.message {
color: #ACACAC;
}
.link-container {
background-color: #3A3A3A;
}
.link {
color: #F6F6F6;
}
.countdown-container {
color: #999;
}
.progress-bar {
background-color: #444;
}
.divider {
background-color: #3A3A3A;
}
.cancel-button {
background-color: #3a3a3a;
color: #d3d3d3;
border-color: #444;
}
.site-info {
background-color: #3A3A3A;
}
.site-title {
color: #F6F6F6;
}
.site-info-loading {
color: #999;
}
}
</style>
{% if siteinfo_enable and siteinfo_api %}
<script type="text/javascript">
// 网站信息API获取脚本
async function fetchSiteInfo(url) {
try {
const response = await fetch('{{siteinfo_api}}'.replace('{href}', encodeURIComponent(url)));
return await response.json();
} catch (error) {
console.error('获取网站信息失败:', error);
return null;
}
}
function getFaviconUrl(url) {
try {
const urlObj = new URL(url);
return `${urlObj.protocol}//${urlObj.hostname}/favicon.ico`;
} catch (error) {
return '';
}
}
function displaySiteInfo(data, url) {
if (!data || !data.title) {
document.getElementById('site-info-container').style.display = 'none';
return;
}
const siteTitle = document.getElementById('site-title');
const siteFavicon = document.getElementById('site-favicon');
const siteInfo = document.getElementById('site-info');
const siteInfoLoading = document.getElementById('site-info-loading');
const siteDesc = document.getElementById('site-desc');
// 设置网站标题
siteTitle.textContent = data.title;
// 如果有描述信息,添加为title属性实现悬浮提示
if (data.desc) {
siteInfo.title = data.desc;
// 同时添加到描述区域
siteDesc.textContent = data.desc;
// 添加点击事件,点击时切换显示/隐藏描述
siteInfo.addEventListener('click', function() {
if (siteDesc.style.display === 'block') {
siteDesc.style.display = 'none';
} else {
siteDesc.style.display = 'block';
}
});
}
// 设置网站图标
if (data.icon) {
siteFavicon.src = data.icon;
} else {
siteFavicon.src = getFaviconUrl(url);
siteFavicon.onerror = function() {
siteFavicon.style.display = 'none';
};
}
// 显示网站信息,隐藏加载提示
siteInfoLoading.style.display = 'none';
siteInfo.style.display = 'flex';
}
function initSiteInfo(url) {
fetchSiteInfo(url).then(data => {
displaySiteInfo(data, url);
}).catch(() => {
document.getElementById('site-info-container').style.display = 'none';
});
}
</script>
{% endif %}
<script type="text/javascript">
const host = window.location.host;
function GetQueryString(name) {
var reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)", "i");
var r = window.location.search.substr(1).match(reg);
return r ? decodeURI(r[2]) : null
}
function base64Decode(input) {
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
let str = input.replace(/=+$/, '');
let output = '';
if (str.length % 4 == 1) {
throw new Error("'atob' failed: The string to be decoded is not correctly encoded.");
}
for (let bc = 0, bs, buffer, idx = 0; buffer = str.charAt(idx++); ~buffer && (bs = bc % 4 ? bs * 64 + buffer : buffer, bc++ % 4) ? output += String.fromCharCode(255 & bs >> (-2 * bc & 6)) : 0) {
buffer = chars.indexOf(buffer)
}
return output
}
let countdown = {{countdown}};
let jump_url = GetQueryString('{{url_param_name}}');
{% if enable_base64_encode %}
jump_url = base64Decode(jump_url);
{% endif %}
const UrlReg = '^((http|https|thunder|qqdl|ed2k|Flashget|qbrowser|ftp|rtsp|mms)://)';
if (!jump_url || !jump_url.match(UrlReg)) {
document.title = '参数错误,正在返回首页...';
jump_url = location.origin
}
let progressElement;
let countdownElement;
function updateCountdown() {
if (countdown < 0) {
return;
}
countdownElement.textContent = countdown;
progressElement.style.width = (countdown / {{countdown}} * 100) + '%';
{% if auto_redirect %}
if (countdown === 0) {
jump();
} else {
countdown--;
setTimeout(updateCountdown, 1000);
}
{% else %}
// 非自动跳转模式下,不继续倒计时
{% endif %}
}
function jump() {
location.href = jump_url;
}
function closeWindow() {
window.opener = null;
window.close();
}
function loadAvatar() {
const avatarImg = document.querySelector('.avatar');
const placeholder = document.querySelector('.avatar-placeholder');
const img = new Image();
img.src = '{{avatar}}';
img.onload = function() {
avatarImg.src = img.src;
avatarImg.style.display = 'block';
placeholder.style.display = 'none';
}
}
window.addEventListener('load', function() {
loadAvatar();
progressElement = document.getElementById('progress');
countdownElement = document.getElementById('countdown');
const jumpUrlElement = document.getElementById('jump-url');
jumpUrlElement.textContent = jump_url;
jumpUrlElement.href = jump_url;
{% if siteinfo_enable and siteinfo_api %}
// 初始化网站信息
initSiteInfo(jump_url);
{% else %}
// 不显示网站信息容器
const siteInfoContainer = document.getElementById('site-info-container');
if (siteInfoContainer) {
siteInfoContainer.style.display = 'none';
}
{% endif %}
});
</script>
</head>
<body>
<!-- <div class="avatar-container">
<div class="avatar-placeholder"></div>
<img src="" alt="头像" class="avatar">
</div>
<div class="description">{{title}}</div>
<div class="subtitle">{{subtitle}}</div> -->
<div class="card">
<div class="title">
<img src="{{avatar}}" alt="网站图标" style="width: 20px; height: 20px; border-radius: 4px; vertical-align: middle; margin-right: 8px;">
<span>{{title}}</span>
</div>
<div class="message">
{% if auto_redirect %}
您即将离开「{{subtitle}}」,系统将自动为您跳转。<br>请注意您的账号和财产安全。
{% else %}
您即将离开「{{subtitle}}」,请点击下方按钮前往。<br>注意保护您的账号和财产安全。
{% endif %}
</div>
{% if siteinfo_enable and siteinfo_api %}
<div id="site-info-container">
<div id="site-info-loading" class="site-info-loading">
正在加载网站信息...
</div>
<div id="site-info" class="site-info" style="display: none;">
<img id="site-favicon" class="site-favicon" src="" alt="网站图标">
<div id="site-title" class="site-title"></div>
</div>
<div id="site-desc" class="site-desc"></div>
</div>
{% endif %}
<div class="link-container">
<a class="link" id="jump-url" target="_blank"></a>
</div>
{% if auto_redirect %}
<div class="countdown-container">
将在 <span id="countdown"></span> 秒后自动跳转
</div>
<div class="progress-bar">
<div class="progress" id="progress"></div>
</div>
{% else %}
<div class="countdown-container" style="display: none;">
将在 <span id="countdown"></span> 秒后自动跳转
</div>
<div class="progress-bar" style="display: none;">
<div class="progress" id="progress"></div>
</div>
{% endif %}
<div class="button-container">
<button class="button cancel-button" onclick="closeWindow()">取消</button>
<button class="button confirm-button" onclick="jump()">
{% if auto_redirect %}
立即前往
{% else %}
前往链接
{% endif %}
</button>
</div>
</div>
</body>
</html>