内容注入
约 6271 字大约 21 分钟
2025-11-11
在 Nginx 在反向代理或 CDN 层做内容注入(content injection) 的典型需求,例如给网站自动插入监控脚本、统计代码、或 CSP 报告器脚本等。
可以实现,但需要明确一点:Nginx 本身不会修改 HTML 内容,除非你启用额外的模块或通过 Lua/NJS 扩展。
有几种常见方式可以实现在 HTML 页面中自动插入 <script> 标签:
| 方式 | 可行性 | 性能 | 说明 |
|---|---|---|---|
① 使用 sub_filter 模块 | 简单直接 | 很快 | 适合替换或插入少量静态脚本 |
② 使用 ngx_http_lua_module | 灵活 | 中等 | 可动态判断 URL、User-Agent、域名等 |
③ 使用 njs(JavaScript for Nginx) | 较灵活 | 中等 | 官方支持的 JavaScript 脚本语言 |
| ④ 使用 CDN(如 Cloudflare Workers) | 最推荐 | 较优 | 可在边缘节点动态修改 HTML |
sub_filter
如果只是想在 HTML 中的特定位置(例如 </head> 前)插入一段脚本,ngx_http_sub_module 足够用了。
启用模块(大多数 Nginx 已内置)
先确认模块是否可用:
nginx -V 2>&1 | grep sub如果看到 --with-http_sub_module,说明已启用。
server {
listen 80;
server_name example.com;
location / {
proxy_pass http://127.0.0.1:8080;
sub_filter '</head>' '<script src="https://xxx.xxxx/xxx.js"></script></head>';
sub_filter_once on; # 只替换第一次出现的 </head>
sub_filter_types text/html;
}
}原理:
- 当 Nginx 代理后端返回 HTML 时,它会扫描响应体,将匹配到的
</head>替换为你的脚本+原文本。 - 这种方法最适合简单静态注入,比如插入统计脚本或监控脚本。
注意:
sub_filter仅对文本类型有效 (text/html)。- 对 gzip 压缩的响应需先禁用压缩或解压后再替换。
可在上方配置中添加:
proxy_set_header Accept-Encoding "";防止后端发送 gzip。
Lua 注入
如果需要:
- 只给特定 URL 注入;
- 动态生成脚本内容;
- 根据 Cookie / Header 条件决定是否注入;
可以使用 ngx_http_lua_module。
lua_need_request_body on;
server {
listen 80;
server_name example.com;
location / {
proxy_pass http://127.0.0.1:8080;
body_filter_by_lua_block {
local chunk = ngx.arg[1]
if ngx.ctx.injected then
ngx.arg[1] = chunk
return
end
if chunk and string.find(chunk, "</head>") then
chunk = string.gsub(chunk, "</head>", '<script src="https://xxx.xx/xx.js"></script></head>')
ngx.ctx.injected = true
end
ngx.arg[1] = chunk
}
}
}这段脚本:
- 会扫描返回的 HTML 块;
- 找到
</head>时插入<script>; - 可加入逻辑判断(例如只在某域名、某路径下生效)。
njs(官方 JS 模块)
js_import main from /etc/nginx/html/inject.js;
server {
listen 80;
location / {
proxy_pass http://127.0.0.1:8080;
js_body_filter main.inject;
}
}/etc/nginx/html/inject.js 内容:
function inject(r, data, flags) {
if (data.indexOf('</head>') !== -1) {
data = data.replace('</head>', '<script src="https://xxx.xxx.xxx/xx.js"></script></head>');
}
return data;
}
export default { inject };CDN/边缘层注入
如果网站有 CDN 层(例如 Cloudflare、Vercel、Netlify Edge Functions 等),可以直接在边缘节点处理响应 HTML:
示例(Cloudflare Workers):
export default {
async fetch(request, env, ctx) {
let res = await fetch(request)
let html = await res.text()
html = html.replace('</head>', '<script src="https://monitor.yumeng.icu/track.js"></script></head>')
return new Response(html, res)
}
}这种方式无需改服务器配置,还能分发到全球节点,非常适合监控脚本注入。
注意事项
HTTPS 混合内容
如果你的站是 HTTPS,注入的脚本也必须是 HTTPS。
安全性
小心 XSS 风险,不要注入任何未经验证的外部脚本。
gzip 问题
对压缩过的响应,需要禁用压缩或先解压再替换。
缓存影响
CDN 或浏览器缓存可能导致脚本注入失效,要配置合适的缓存策略。
广告脚本
1
/**
* 高级广告注入脚本
* 参考 Google 公告栏设计,支持多位置、防删除、自定义样式
*/
(function() {
'use strict';
// ==================== 配置区域 ====================
const CONFIG = {
// 基础配置
position: 'top', // 位置: top, bottom, left, right, top-left, top-right, bottom-left, bottom-right
message: ` <div class="server-ad">
<div class="ad-tag">限时特惠</div>
<div class="ad-content">
<div class="ad-left">
<h3>企业级云服务器 限时特惠</h3>
<p>99.99%可用 | 万兆带宽 | 弹性扩容 | 数据加密</p>
</div>
<div class="ad-right">
<span class="original">¥1299/月</span>
<span class="discount">¥599/月起</span>
<button class="ad-btn">立即开通</button>
</div>
</div>
</div>`, // 支持HTML
linkText: '更多', // 链接文字
linkUrl: 'https://yumg.cn', // 链接地址
closeText: '关闭', // 关闭按钮文字
// 样式配置
width: 'full', // 宽度: auto, full, 或具体数值如 '800px'
height: 'auto', // 高度: auto 或具体数值如 '80px'
backgroundColor: '#f8f9fa',
textColor: '#202124',
primaryColor: '#1a73e8',
borderColor: '#dadce0',
// 功能配置
collapsible: true, // 是否可折叠
defaultCollapsed: false, // 默认是否折叠
closeDelay: 3, // 关闭延迟(秒)
showCloseButton: true, // 是否显示关闭按钮
enableAntiDelete: true, // 是否启用防删除
// 关闭后行为配置
afterClose: 'remove', // 关闭后行为: 'remove' (完全移除), 'hide-sidebar' (隐藏到侧边显示小标签)
autoRemoveDelay: 0, // 无交互后自动移除延迟(秒),0表示不自动移除
sidebarText: '公告', // 侧边栏显示的文字
// 图标配置
icon: `<svg t="1762864465847" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5368" width="27" height="27"><path d="M512.670266 959.469288c-246.343571 0-446.760137-200.63146-446.760137-447.240067S266.325671 64.979944 512.670266 64.979944s446.760137 200.641693 446.760137 447.2503S759.013837 959.469288 512.670266 959.469288zM512.670266 130.429585c-210.29147 0-381.381104 171.283038-381.381104 381.800659S302.378795 894.019647 512.670266 894.019647s381.381104-171.272805 381.381104-381.790426S722.961736 130.429585 512.670266 130.429585z" fill="#276BC0" p-id="5369"></path><path d="M447.290209 317.17171a63.891 63.959 0 1 0 130.760113 0 63.891 63.959 0 1 0-130.760113 0Z" fill="#276BC0" p-id="5370"></path><path d="M512.197498 820.218804c-30.093389 0-54.235229-24.416086-54.235229-54.541197L457.96227 482.062154c0-30.126134 24.14184-54.541197 54.235229-54.541197 30.093389 0 54.235229 24.416086 54.235229 54.541197l0 283.615453C566.432727 795.802718 542.290887 820.218804 512.197498 820.218804z" fill="#2f80e9" p-id="5371"></path></svg>`, // 默认信息图标
// 自定义CSS
customCSS: `
.server-ad {
width: 100%;
height: 100px;
background: linear-gradient(90deg, #0f172a, #1e40af);
border-radius: 6px;
display: flex;
flex-direction: column;
justify-content: center;
padding: 0 25px;
color: #fff;
font-family: 'Segoe UI', sans-serif;
box-shadow: 0 3px 8px rgba(0,0,0,0.12);
position: relative;
}
.ad-tag {
position: absolute;
top: 9px;
left: 25px;
background: #f94316ff;
color: #fff;
font-size: 11px;
padding: 1px 8px;
border-radius: 10px;
font-weight: 600;
}
.ad-content {
display: flex;
align-items: center;
justify-content: space-between;
margin-top: 10px;
}
.ad-left h3 {
font-size: 16px;
margin-bottom: 4px;
font-weight: 700;
color: #f0f9ff;
}
.ad-left p {
font-size: 12px;
opacity: 0.8;
display: flex;
gap: 12px;
}
.ad-right {
display: flex;
align-items: center;
gap: 18px;
}
.original {
font-size: 11px;
text-decoration: line-through;
opacity: 0.6;
}
.discount {
font-size: 18px;
font-weight: 700;
color: #bfdbfe;
}
.ad-btn {
background: #3b82f6;
color: #fff;
border: none;
padding: 6px 18px;
border-radius: 4px;
font-size: 13px;
font-weight: 600;
cursor: pointer;
transition: all 0.2s ease;
}
.ad-btn:hover {
background: #2563eb;
transform: translateY(-1px);
box-shadow: 0 2px 6px rgba(59, 130, 246, 0.3);
}
/* 适配小屏幕 */
@media (max-width: 768px) {
.server-ad {
height: auto;
padding: 15px;
}
.ad-content {
flex-direction: column;
gap: 10px;
text-align: center;
}
.ad-left p {
justify-content: center;
flex-wrap: wrap;
}
.ad-tag {
top: 10px;
left: 50%;
transform: translateX(-50%);
}
}
`,
// 自定义JS(在广告插入后执行)
customJS: function(adElement) {
// console.log('广告已插入:', adElement);
},
// 回调函数
onDelete: function() {
console.warn('⚠️ 检测到广告被删除,正在恢复...');
// 可以在这里添加自定义逻辑,比如记录删除行为
},
onClose: function() {
console.log('广告已关闭');
},
onCollapse: function(isCollapsed) {
console.log('广告折叠状态:', isCollapsed);
}
};
// ==================== 核心类 ====================
class AdvancedAdInjector {
constructor(config) {
this.config = config;
this.adElement = null;
this.sidebarElement = null;
this.isCollapsed = config.defaultCollapsed;
this.isClosed = false;
this.isInjecting = false; // 防止重复注入
this.closeTimer = null;
this.autoRemoveTimer = null;
this.closeCountdown = config.closeDelay;
this.observer = null;
this.isMobile = window.innerWidth <= 768;
this.lastInteractionTime = Date.now();
this.init();
}
init() {
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => this.inject());
} else {
this.inject();
}
// 监听窗口大小变化
window.addEventListener('resize', () => {
const wasMobile = this.isMobile;
this.isMobile = window.innerWidth <= 768;
if (wasMobile !== this.isMobile && this.adElement) {
this.updateMobileStyles();
}
});
// 监听用户交互以重置自动移除计时器
if (this.config.autoRemoveDelay > 0) {
['click', 'mousemove', 'keydown', 'scroll', 'touchstart'].forEach(event => {
document.addEventListener(event, () => this.resetAutoRemoveTimer(), { passive: true });
});
}
}
inject() {
if (this.isInjecting) return;
this.isInjecting = true;
try {
this.injectStyles();
this.createAdElement();
this.insertAdElement();
this.startCloseTimer();
this.setupAntiDelete();
this.startAutoRemoveTimer();
// 执行自定义JS
if (typeof this.config.customJS === 'function') {
this.config.customJS(this.adElement);
}
} finally {
this.isInjecting = false;
}
}
injectStyles() {
const style = document.createElement('style');
style.id = 'advanced-ad-styles';
style.textContent = `
.advanced-ad-container {
position: fixed;
z-index: 999999;
background: ${this.config.backgroundColor};
color: ${this.config.textColor};
border: 1px solid ${this.config.borderColor};
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Arial, sans-serif;
font-size: 14px;
line-height: 1.5;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
/* 允许折叠按钮等元素超出容器显示,确保按钮跟随容器边缘并不被裁剪 */
overflow: visible;
}
.advanced-ad-container.position-top,
.advanced-ad-container.position-bottom {
left: 50%;
transform: translateX(-50%);
${this.config.width === 'full' ? 'width: 100%; left: 0; transform: none;' :
this.config.width === 'auto' ? 'max-width: 90%; width: auto;' :
`width: ${this.config.width};`}
}
.advanced-ad-container.position-top {
top: 0;
border-radius: 0 0 8px 8px;
border-top: none;
}
.advanced-ad-container.position-bottom {
bottom: 0;
border-radius: 8px 8px 0 0;
border-bottom: none;
}
.advanced-ad-container.position-left,
.advanced-ad-container.position-right {
top: 50%;
transform: translateY(-50%);
max-width: 400px;
}
.advanced-ad-container.position-left {
left: 0;
border-radius: 0 8px 8px 0;
border-left: none;
}
.advanced-ad-container.position-right {
right: 0;
border-radius: 8px 0 0 8px;
border-right: none;
}
.advanced-ad-container.position-top-left,
.advanced-ad-container.position-top-right,
.advanced-ad-container.position-bottom-left,
.advanced-ad-container.position-bottom-right {
max-width: 400px;
}
.advanced-ad-container.position-top-left {
top: 20px;
left: 20px;
border-radius: 8px;
}
.advanced-ad-container.position-top-right {
top: 20px;
right: 20px;
border-radius: 8px;
}
.advanced-ad-container.position-bottom-left {
bottom: 20px;
left: 20px;
border-radius: 8px;
}
.advanced-ad-container.position-bottom-right {
bottom: 20px;
right: 20px;
border-radius: 8px;
}
.advanced-ad-container.collapsed .advanced-ad-content {
max-height: 0;
opacity: 0;
padding: 0 20px;
margin: 0;
}
.advanced-ad-content {
max-height: 500px;
opacity: 1;
padding: 16px 20px;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
display: flex;
align-items: center;
gap: 16px;
}
.advanced-ad-icon {
flex-shrink: 0;
color: ${this.config.primaryColor};
display: flex;
align-items: center;
}
.advanced-ad-message {
flex: 1;
min-width: 0;
}
.advanced-ad-actions {
display: flex;
align-items: center;
gap: 12px;
flex-shrink: 0;
}
.advanced-ad-link {
color: ${this.config.primaryColor};
text-decoration: none;
font-weight: 500;
white-space: nowrap;
display: flex;
align-items: center;
gap: 4px;
transition: opacity 0.2s;
}
.advanced-ad-link:hover {
opacity: 0.8;
}
.advanced-ad-close-btn {
background: transparent;
border: 1px solid ${this.config.borderColor};
color: ${this.config.textColor};
padding: 6px 12px;
border-radius: 4px;
cursor: not-allowed;
font-size: 13px;
white-space: nowrap;
transition: all 0.2s;
display: flex;
align-items: center;
gap: 4px;
}
.advanced-ad-close-btn.enabled {
cursor: pointer;
background: ${this.config.primaryColor};
color: white;
border-color: ${this.config.primaryColor};
}
.advanced-ad-close-btn.enabled:hover {
opacity: 0.9;
}
.advanced-ad-collapse-btn {
position: absolute;
left: 50%;
transform: translateX(-50%);
background: ${this.config.backgroundColor};
border: 1px solid ${this.config.borderColor};
border-radius: 50%;
width: 24px;
height: 24px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.2s;
box-shadow: 0 2px 4px rgba(0,0,0,0.12);
/* 确保按钮在最前层且不被裁切 */
z-index: 1000001;
overflow: visible;
}
/* 根据位置动态调整折叠按钮位置 */
.advanced-ad-container.position-top .advanced-ad-collapse-btn,
.advanced-ad-container.position-top-left .advanced-ad-collapse-btn,
.advanced-ad-container.position-top-right .advanced-ad-collapse-btn {
bottom: -12px;
top: auto;
}
.advanced-ad-container.position-bottom .advanced-ad-collapse-btn,
.advanced-ad-container.position-bottom-left .advanced-ad-collapse-btn,
.advanced-ad-container.position-bottom-right .advanced-ad-collapse-btn {
top: -12px;
bottom: auto;
}
.advanced-ad-container.position-left .advanced-ad-collapse-btn,
.advanced-ad-container.position-right .advanced-ad-collapse-btn {
top: 50%;
transform: translate(-50%, -50%);
}
.advanced-ad-collapse-btn:hover {
background: ${this.config.borderColor};
}
.advanced-ad-collapse-btn svg {
width: 16px;
height: 16px;
transition: transform 0.3s;
}
.advanced-ad-container.collapsed .advanced-ad-collapse-btn svg {
transform: rotate(180deg);
}
/* 侧边栏标签样式 */
.advanced-ad-sidebar {
position: fixed;
z-index: 999999;
background: ${this.config.primaryColor};
color: white;
padding: 12px 8px;
font-size: 12px;
font-weight: 500;
cursor: pointer;
transition: all 0.3s;
writing-mode: vertical-rl;
text-orientation: mixed;
border-radius: 0 4px 4px 0;
box-shadow: 2px 0 8px rgba(0,0,0,0.15);
opacity: 0;
transform: translateX(-100%);
}
.advanced-ad-sidebar.show {
opacity: 1;
transform: translateX(0);
}
.advanced-ad-sidebar:hover {
background: ${this.config.primaryColor}dd;
padding-right: 12px;
}
.advanced-ad-sidebar.position-left {
left: 0;
top: 50%;
transform: translateY(-50%) translateX(-100%);
}
.advanced-ad-sidebar.position-left.show {
transform: translateY(-50%) translateX(0);
}
.advanced-ad-sidebar.position-right {
right: 0;
top: 50%;
transform: translateY(-50%) translateX(100%);
border-radius: 4px 0 0 4px;
writing-mode: vertical-lr;
}
.advanced-ad-sidebar.position-right.show {
transform: translateY(-50%) translateX(0);
}
.advanced-ad-sidebar.position-right:hover {
padding-left: 12px;
padding-right: 8px;
}
/* 移动端样式 */
@media (max-width: 768px) {
.advanced-ad-container {
max-width: 100% !important;
width: 100% !important;
}
.advanced-ad-container.position-top,
.advanced-ad-container.position-bottom {
left: 0 !important;
transform: none !important;
border-radius: 0 !important;
}
.advanced-ad-container.position-left,
.advanced-ad-container.position-right,
.advanced-ad-container.position-top-left,
.advanced-ad-container.position-top-right,
.advanced-ad-container.position-bottom-left,
.advanced-ad-container.position-bottom-right {
max-width: 100% !important;
width: 100% !important;
left: 0 !important;
right: auto !important;
border-radius: 0 !important;
}
.advanced-ad-content {
padding: 12px 16px;
gap: 12px;
}
.advanced-ad-link-text,
.advanced-ad-close-text {
display: none;
}
.advanced-ad-actions {
gap: 8px;
}
.advanced-ad-close-btn {
padding: 6px 8px;
min-width: 32px;
justify-content: center;
}
}
${this.config.customCSS}
`;
document.head.appendChild(style);
}
createAdElement() {
const container = document.createElement('div');
container.className = `advanced-ad-container position-${this.config.position}`;
container.id = 'advanced-ad-injected';
// 如果支持折叠,添加标记类,便于样式或脚本针对折叠行为做处理
if (this.config.collapsible) {
container.classList.add('has-collapse');
}
if (this.config.height !== 'auto') {
container.style.height = this.config.height;
}
// 内容区域
const content = document.createElement('div');
content.className = 'advanced-ad-content';
// 图标
if (this.config.icon) {
const icon = document.createElement('div');
icon.className = 'advanced-ad-icon';
icon.innerHTML = this.config.icon;
content.appendChild(icon);
}
// 消息
const message = document.createElement('div');
message.className = 'advanced-ad-message';
message.innerHTML = this.config.message;
content.appendChild(message);
// 操作按钮
const actions = document.createElement('div');
actions.className = 'advanced-ad-actions';
// 链接
if (this.config.linkUrl) {
const link = document.createElement('a');
link.className = 'advanced-ad-link';
link.href = this.config.linkUrl;
link.target = '_blank';
link.innerHTML = `
<span class="advanced-ad-link-text">${this.config.linkText}</span>
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"></path>
<polyline points="15 3 21 3 21 9"></polyline>
<line x1="10" y1="14" x2="21" y2="3"></line>
</svg>
`;
actions.appendChild(link);
}
// 关闭按钮
if (this.config.showCloseButton) {
const closeBtn = document.createElement('button');
closeBtn.className = 'advanced-ad-close-btn';
closeBtn.innerHTML = `
<span class="advanced-ad-close-text">${this.config.closeText} (${this.closeCountdown}s)</span>
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<line x1="18" y1="6" x2="6" y2="18"></line>
<line x1="6" y1="6" x2="18" y2="18"></line>
</svg>
`;
closeBtn.addEventListener('click', () => this.closeAd());
actions.appendChild(closeBtn);
this.closeBtnElement = closeBtn;
}
content.appendChild(actions);
container.appendChild(content);
// 折叠按钮
if (this.config.collapsible) {
const collapseBtn = document.createElement('button');
collapseBtn.className = 'advanced-ad-collapse-btn';
// 根据位置决定图标方向
const isBottom = this.config.position.includes('bottom');
collapseBtn.innerHTML = `
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<polyline points="6 9 12 15 18 9"></polyline>
</svg>
`;
collapseBtn.addEventListener('click', (e) => {
e.stopPropagation();
this.toggleCollapse();
this.resetAutoRemoveTimer();
});
container.appendChild(collapseBtn);
}
if (this.isCollapsed) {
container.classList.add('collapsed');
}
this.adElement = container;
}
createSidebarTab() {
if (this.sidebarElement) return;
const sidebar = document.createElement('div');
sidebar.className = 'advanced-ad-sidebar';
sidebar.id = 'advanced-ad-sidebar';
// 根据原广告位置决定侧边栏位置
const pos = this.config.position;
if (pos.includes('right')) {
sidebar.classList.add('position-right');
} else {
sidebar.classList.add('position-left');
}
sidebar.textContent = this.config.sidebarText;
sidebar.addEventListener('click', () => this.reopenAd());
document.body.appendChild(sidebar);
this.sidebarElement = sidebar;
// 延迟显示动画
setTimeout(() => sidebar.classList.add('show'), 100);
}
insertAdElement() {
if (!this.adElement) return;
// 检查是否已存在
const existing = document.getElementById('advanced-ad-injected');
if (existing) {
existing.remove();
}
document.body.appendChild(this.adElement);
}
startCloseTimer() {
if (!this.config.showCloseButton) return;
this.closeTimer = setInterval(() => {
this.closeCountdown--;
if (this.closeBtnElement) {
const textSpan = this.closeBtnElement.querySelector('.advanced-ad-close-text');
if (textSpan) {
textSpan.textContent = `${this.config.closeText} (${this.closeCountdown}s)`;
}
}
if (this.closeCountdown <= 0) {
clearInterval(this.closeTimer);
if (this.closeBtnElement) {
this.closeBtnElement.classList.add('enabled');
this.closeBtnElement.style.cursor = 'pointer';
const textSpan = this.closeBtnElement.querySelector('.advanced-ad-close-text');
if (textSpan) {
textSpan.textContent = this.config.closeText;
}
}
}
}, 1000);
}
toggleCollapse() {
this.isCollapsed = !this.isCollapsed;
if (this.adElement) {
this.adElement.classList.toggle('collapsed', this.isCollapsed);
}
if (typeof this.config.onCollapse === 'function') {
this.config.onCollapse(this.isCollapsed);
}
}
closeAd() {
if (this.closeCountdown > 0) return;
if (this.isClosed) return;
this.isClosed = true;
if (this.adElement) {
this.adElement.style.opacity = '0';
this.adElement.style.transform = this.getCloseTransform();
setTimeout(() => {
if (this.adElement && this.adElement.parentNode) {
this.adElement.remove();
this.adElement = null;
}
// 根据配置决定关闭后行为
if (this.config.afterClose === 'hide-sidebar') {
this.createSidebarTab();
} else if (this.config.afterClose === 'remove') {
this.stopAntiDelete();
this.stopAutoRemoveTimer();
}
}, 300);
}
if (this.closeTimer) {
clearInterval(this.closeTimer);
}
if (typeof this.config.onClose === 'function') {
this.config.onClose();
}
}
reopenAd() {
if (this.sidebarElement) {
this.sidebarElement.classList.remove('show');
setTimeout(() => {
if (this.sidebarElement && this.sidebarElement.parentNode) {
this.sidebarElement.remove();
this.sidebarElement = null;
}
}, 300);
}
this.isClosed = false;
this.closeCountdown = this.config.closeDelay;
this.inject();
}
getCloseTransform() {
const pos = this.config.position;
if (pos.includes('top')) return 'translateY(-100%)';
if (pos.includes('bottom')) return 'translateY(100%)';
if (pos === 'left') return 'translateX(-100%)';
if (pos === 'right') return 'translateX(100%)';
return 'scale(0.8)';
}
setupAntiDelete() {
if (!this.config.enableAntiDelete) return;
// 使用 MutationObserver 监听DOM变化
this.observer = new MutationObserver((mutations) => {
// 防止在注入过程中触发
if (this.isInjecting || this.isClosed) return;
for (const mutation of mutations) {
if (mutation.type === 'childList') {
for (const node of mutation.removedNodes) {
// 检测主容器或侧边栏被删除
if (node === this.adElement ||
(node.id && node.id === 'advanced-ad-injected') ||
node === this.sidebarElement ||
(node.id && node.id === 'advanced-ad-sidebar')) {
console.warn('⚠️ 检测到广告被删除,正在恢复...');
if (typeof this.config.onDelete === 'function') {
this.config.onDelete();
}
// 短暂延迟后重新插入
setTimeout(() => {
if (!document.getElementById('advanced-ad-injected') && !this.isClosed) {
this.inject();
}
if (!document.getElementById('advanced-ad-sidebar') && this.isClosed && this.config.afterClose === 'hide-sidebar') {
this.createSidebarTab();
}
}, 100);
return; // 只处理一次
}
}
}
}
});
// 观察 body 和整个文档树
this.observer.observe(document.body, {
childList: true,
subtree: true
});
// 定期检查(备用方案)
this.checkInterval = setInterval(() => {
if (this.isInjecting || this.isClosed) return;
if (!document.getElementById('advanced-ad-injected') && this.adElement) {
console.warn('⚠️ 定期检查:广告丢失,正在恢复...');
if (typeof this.config.onDelete === 'function') {
this.config.onDelete();
}
this.inject();
}
if (this.isClosed && this.config.afterClose === 'hide-sidebar' && !document.getElementById('advanced-ad-sidebar')) {
this.createSidebarTab();
}
}, 2000);
}
stopAntiDelete() {
if (this.observer) {
this.observer.disconnect();
this.observer = null;
}
if (this.checkInterval) {
clearInterval(this.checkInterval);
this.checkInterval = null;
}
}
startAutoRemoveTimer() {
if (this.config.autoRemoveDelay <= 0) return;
this.resetAutoRemoveTimer();
}
resetAutoRemoveTimer() {
this.lastInteractionTime = Date.now();
if (this.autoRemoveTimer) {
clearTimeout(this.autoRemoveTimer);
}
if (this.config.autoRemoveDelay > 0 && !this.isClosed) {
this.autoRemoveTimer = setTimeout(() => {
if (Date.now() - this.lastInteractionTime >= this.config.autoRemoveDelay * 1000) {
console.log('⏱️ 无交互超时,自动移除广告');
this.forceRemove();
}
}, this.config.autoRemoveDelay * 1000);
}
}
stopAutoRemoveTimer() {
if (this.autoRemoveTimer) {
clearTimeout(this.autoRemoveTimer);
this.autoRemoveTimer = null;
}
}
forceRemove() {
this.isClosed = true;
this.stopAntiDelete();
this.stopAutoRemoveTimer();
if (this.adElement && this.adElement.parentNode) {
this.adElement.remove();
this.adElement = null;
}
if (this.sidebarElement && this.sidebarElement.parentNode) {
this.sidebarElement.remove();
this.sidebarElement = null;
}
if (typeof this.config.onClose === 'function') {
this.config.onClose();
}
}
updateMobileStyles() {
// 移动端样式已在CSS中处理,这里可以添加额外逻辑
}
}
// ==================== 启动 ====================
new AdvancedAdInjector(CONFIG);
})();2
/**
* 高级广告注入脚本
* 参考 Google 公告栏设计,支持多位置、防删除、自定义样式
*/
(function() {
'use strict';
// ==================== 配置区域 ====================
const CONFIG = {
// 基础配置
position: 'bottom', // 位置: top, bottom, left, right, top-left, top-right, bottom-left, bottom-right
message: ` <div class="server-ad">
<div class="ad-tag">限时特惠</div>
<div class="ad-content">
<div class="ad-left">
<h3>企业级云服务器 限时特惠</h3>
<p>99.99%可用 | 万兆带宽 | 弹性扩容 | 数据加密</p>
</div>
<div class="ad-right">
<span class="original">¥1299/月</span>
<span class="discount">¥599/月起</span>
<button class="ad-btn">立即开通</button>
</div>
</div>
</div>`, // 支持HTML
linkText: '更多', // 链接文字
linkUrl: 'https://yumg.cn', // 链接地址
closeText: '关闭', // 关闭按钮文字
// 样式配置
width: 'full', // 宽度: auto, full, 或具体数值如 '800px'
height: 'auto', // 高度: auto 或具体数值如 '80px'
backgroundColor: '#f8f9fa',
textColor: '#202124',
primaryColor: '#1a73e8',
borderColor: '#dadce0',
// 功能配置
collapsible: true, // 是否可折叠
defaultCollapsed: false, // 默认是否折叠
closeDelay: 3, // 关闭延迟(秒)
showCloseButton: true, // 是否显示关闭按钮
enableAntiDelete: true, // 是否启用防删除
// 关闭后行为配置
afterClose: 'remove', // 关闭后行为: 'remove' (完全移除), 'hide-sidebar' (隐藏到侧边显示小标签)
autoRemoveDelay: 0, // 无交互后自动移除延迟(秒),0表示不自动移除
sidebarText: '公告', // 侧边栏显示的文字
// 图标配置
icon: `<svg t="1762864465847" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5368" width="27" height="27"><path d="M512.670266 959.469288c-246.343571 0-446.760137-200.63146-446.760137-447.240067S266.325671 64.979944 512.670266 64.979944s446.760137 200.641693 446.760137 447.2503S759.013837 959.469288 512.670266 959.469288zM512.670266 130.429585c-210.29147 0-381.381104 171.283038-381.381104 381.800659S302.378795 894.019647 512.670266 894.019647s381.381104-171.272805 381.381104-381.790426S722.961736 130.429585 512.670266 130.429585z" fill="#276BC0" p-id="5369"></path><path d="M447.290209 317.17171a63.891 63.959 0 1 0 130.760113 0 63.891 63.959 0 1 0-130.760113 0Z" fill="#276BC0" p-id="5370"></path><path d="M512.197498 820.218804c-30.093389 0-54.235229-24.416086-54.235229-54.541197L457.96227 482.062154c0-30.126134 24.14184-54.541197 54.235229-54.541197 30.093389 0 54.235229 24.416086 54.235229 54.541197l0 283.615453C566.432727 795.802718 542.290887 820.218804 512.197498 820.218804z" fill="#2f80e9" p-id="5371"></path></svg>`, // 默认信息图标
// 自定义CSS
customCSS: `
.server-ad {
// width: 100%;
height: 100px;
// background: #fff;
// border: 1px solid #eee;
border-radius: 6px;
display: flex;
flex-direction: column;
justify-content: center;
padding: 0 25px;
color: #000;
font-family: 'Segoe UI', sans-serif;
// box-shadow: 0 3px 8px rgba(0,0,0,0.05);
position: relative;
}
.ad-tag {
position: absolute;
top: 9px;
left: 25px;
background: #e22d0eff;
color: #fff;
font-size: 11px;
padding: 2px 8px;
border-radius: 10px;
font-weight: 600;
z-index: 1;
}
.ad-content {
display: flex;
align-items: center;
justify-content: space-between;
margin-top: 10px;
}
.ad-left h3 {
font-size: 16px;
margin-bottom: 4px;
font-weight: 700;
color: #000;
}
.ad-left p {
font-size: 12px;
opacity: 0.7;
display: flex;
gap: 12px;
color: #333;
}
.ad-right {
display: flex;
align-items: center;
gap: 18px;
}
.original {
font-size: 11px;
text-decoration: line-through;
opacity: 0.5;
color: #666;
}
.discount {
font-size: 18px;
font-weight: 700;
color: #000;
}
.ad-btn {
background: #000;
color: #fff;
border: none;
padding: 6px 18px;
border-radius: 4px;
font-size: 13px;
font-weight: 600;
cursor: pointer;
transition: all 0.2s ease;
}
.ad-btn:hover {
background: #333;
transform: translateY(-1px);
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.15);
}
/* 适配小屏幕 */
@media (max-width: 768px) {
.server-ad {
height: auto;
padding: 15px;
}
.ad-content {
flex-direction: column;
gap: 10px;
text-align: center;
}
.ad-left p {
justify-content: center;
flex-wrap: wrap;
}
.ad-tag {
top: 10px;
left: 50%;
transform: translateX(-50%);
}
}
`,
// 自定义JS(在广告插入后执行)
customJS: function(adElement) {
// console.log('广告已插入:', adElement);
},
// 回调函数
onDelete: function() {
console.warn('⚠️ 检测到广告被删除,正在恢复...');
// 可以在这里添加自定义逻辑,比如记录删除行为
},
onClose: function() {
console.log('广告已关闭');
},
onCollapse: function(isCollapsed) {
console.log('广告折叠状态:', isCollapsed);
}
};
// ==================== 核心类 ====================
class AdvancedAdInjector {
constructor(config) {
this.config = config;
this.adElement = null;
this.sidebarElement = null;
this.isCollapsed = config.defaultCollapsed;
this.isClosed = false;
this.isInjecting = false; // 防止重复注入
this.closeTimer = null;
this.autoRemoveTimer = null;
this.closeCountdown = config.closeDelay;
this.observer = null;
this.isMobile = window.innerWidth <= 768;
this.lastInteractionTime = Date.now();
this.init();
}
init() {
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => this.inject());
} else {
this.inject();
}
// 监听窗口大小变化
window.addEventListener('resize', () => {
const wasMobile = this.isMobile;
this.isMobile = window.innerWidth <= 768;
if (wasMobile !== this.isMobile && this.adElement) {
this.updateMobileStyles();
}
});
// 监听用户交互以重置自动移除计时器
if (this.config.autoRemoveDelay > 0) {
['click', 'mousemove', 'keydown', 'scroll', 'touchstart'].forEach(event => {
document.addEventListener(event, () => this.resetAutoRemoveTimer(), { passive: true });
});
}
}
inject() {
if (this.isInjecting) return;
this.isInjecting = true;
try {
this.injectStyles();
this.createAdElement();
this.insertAdElement();
this.startCloseTimer();
this.setupAntiDelete();
this.startAutoRemoveTimer();
// 执行自定义JS
if (typeof this.config.customJS === 'function') {
this.config.customJS(this.adElement);
}
} finally {
this.isInjecting = false;
}
}
injectStyles() {
const style = document.createElement('style');
style.id = 'advanced-ad-styles';
style.textContent = `
.advanced-ad-container {
position: fixed;
z-index: 999999;
background: ${this.config.backgroundColor};
color: ${this.config.textColor};
border: 1px solid ${this.config.borderColor};
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Arial, sans-serif;
font-size: 14px;
line-height: 1.5;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
/* 允许折叠按钮等元素超出容器显示,确保按钮跟随容器边缘并不被裁剪 */
overflow: visible;
}
.advanced-ad-container.position-top,
.advanced-ad-container.position-bottom {
left: 50%;
transform: translateX(-50%);
${this.config.width === 'full' ? 'width: 100%; left: 0; transform: none;' :
this.config.width === 'auto' ? 'max-width: 90%; width: auto;' :
`width: ${this.config.width};`}
}
.advanced-ad-container.position-top {
top: 0;
border-radius: 0 0 8px 8px;
border-top: none;
}
.advanced-ad-container.position-bottom {
bottom: 0;
border-radius: 8px 8px 0 0;
border-bottom: none;
}
.advanced-ad-container.position-left,
.advanced-ad-container.position-right {
top: 50%;
transform: translateY(-50%);
max-width: 400px;
}
.advanced-ad-container.position-left {
left: 0;
border-radius: 0 8px 8px 0;
border-left: none;
}
.advanced-ad-container.position-right {
right: 0;
border-radius: 8px 0 0 8px;
border-right: none;
}
.advanced-ad-container.position-top-left,
.advanced-ad-container.position-top-right,
.advanced-ad-container.position-bottom-left,
.advanced-ad-container.position-bottom-right {
max-width: 400px;
}
.advanced-ad-container.position-top-left {
top: 20px;
left: 20px;
border-radius: 8px;
}
.advanced-ad-container.position-top-right {
top: 20px;
right: 20px;
border-radius: 8px;
}
.advanced-ad-container.position-bottom-left {
bottom: 20px;
left: 20px;
border-radius: 8px;
}
.advanced-ad-container.position-bottom-right {
bottom: 20px;
right: 20px;
border-radius: 8px;
}
.advanced-ad-container.collapsed .advanced-ad-content {
max-height: 0;
opacity: 0;
padding: 0 20px;
margin: 0;
}
.advanced-ad-content {
max-height: 500px;
opacity: 1;
padding: 16px 20px;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
display: flex;
align-items: center;
gap: 16px;
}
.advanced-ad-icon {
flex-shrink: 0;
color: ${this.config.primaryColor};
display: flex;
align-items: center;
}
.advanced-ad-message {
flex: 1;
min-width: 0;
}
.advanced-ad-actions {
display: flex;
align-items: center;
gap: 12px;
flex-shrink: 0;
}
.advanced-ad-link {
color: ${this.config.primaryColor};
text-decoration: none;
font-weight: 500;
white-space: nowrap;
display: flex;
align-items: center;
gap: 4px;
transition: opacity 0.2s;
}
.advanced-ad-link:hover {
opacity: 0.8;
}
.advanced-ad-close-btn {
background: transparent;
border: 1px solid ${this.config.borderColor};
color: ${this.config.textColor};
padding: 6px 12px;
border-radius: 4px;
cursor: not-allowed;
font-size: 13px;
white-space: nowrap;
transition: all 0.2s;
display: flex;
align-items: center;
gap: 4px;
}
.advanced-ad-close-btn.enabled {
cursor: pointer;
background: ${this.config.primaryColor};
color: white;
border-color: ${this.config.primaryColor};
}
.advanced-ad-close-btn.enabled:hover {
opacity: 0.9;
}
.advanced-ad-collapse-btn {
position: absolute;
left: 50%;
transform: translateX(-50%);
background: ${this.config.backgroundColor};
border: 1px solid ${this.config.borderColor};
border-radius: 50%;
width: 24px;
height: 24px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.2s;
box-shadow: 0 2px 4px rgba(0,0,0,0.12);
/* 确保按钮在最前层且不被裁切 */
z-index: 1000001;
overflow: visible;
}
/* 根据位置动态调整折叠按钮位置 */
.advanced-ad-container.position-top .advanced-ad-collapse-btn,
.advanced-ad-container.position-top-left .advanced-ad-collapse-btn,
.advanced-ad-container.position-top-right .advanced-ad-collapse-btn {
bottom: -12px;
top: auto;
}
.advanced-ad-container.position-bottom .advanced-ad-collapse-btn,
.advanced-ad-container.position-bottom-left .advanced-ad-collapse-btn,
.advanced-ad-container.position-bottom-right .advanced-ad-collapse-btn {
top: -12px;
bottom: auto;
}
.advanced-ad-container.position-left .advanced-ad-collapse-btn,
.advanced-ad-container.position-right .advanced-ad-collapse-btn {
top: 50%;
transform: translate(-50%, -50%);
}
.advanced-ad-collapse-btn:hover {
background: ${this.config.borderColor};
}
.advanced-ad-collapse-btn svg {
width: 16px;
height: 16px;
transition: transform 0.3s;
}
.advanced-ad-container.collapsed .advanced-ad-collapse-btn svg {
transform: rotate(180deg);
}
/* 侧边栏标签样式 */
.advanced-ad-sidebar {
position: fixed;
z-index: 999999;
background: ${this.config.primaryColor};
color: white;
padding: 12px 8px;
font-size: 12px;
font-weight: 500;
cursor: pointer;
transition: all 0.3s;
writing-mode: vertical-rl;
text-orientation: mixed;
border-radius: 0 4px 4px 0;
box-shadow: 2px 0 8px rgba(0,0,0,0.15);
opacity: 0;
transform: translateX(-100%);
}
.advanced-ad-sidebar.show {
opacity: 1;
transform: translateX(0);
}
.advanced-ad-sidebar:hover {
background: ${this.config.primaryColor}dd;
padding-right: 12px;
}
.advanced-ad-sidebar.position-left {
left: 0;
top: 50%;
transform: translateY(-50%) translateX(-100%);
}
.advanced-ad-sidebar.position-left.show {
transform: translateY(-50%) translateX(0);
}
.advanced-ad-sidebar.position-right {
right: 0;
top: 50%;
transform: translateY(-50%) translateX(100%);
border-radius: 4px 0 0 4px;
writing-mode: vertical-lr;
}
.advanced-ad-sidebar.position-right.show {
transform: translateY(-50%) translateX(0);
}
.advanced-ad-sidebar.position-right:hover {
padding-left: 12px;
padding-right: 8px;
}
/* 移动端样式 */
@media (max-width: 768px) {
.advanced-ad-container {
max-width: 100% !important;
width: 100% !important;
}
.advanced-ad-container.position-top,
.advanced-ad-container.position-bottom {
left: 0 !important;
transform: none !important;
border-radius: 0 !important;
}
.advanced-ad-container.position-left,
.advanced-ad-container.position-right,
.advanced-ad-container.position-top-left,
.advanced-ad-container.position-top-right,
.advanced-ad-container.position-bottom-left,
.advanced-ad-container.position-bottom-right {
max-width: 100% !important;
width: 100% !important;
left: 0 !important;
right: auto !important;
border-radius: 0 !important;
}
.advanced-ad-content {
padding: 12px 16px;
gap: 12px;
}
.advanced-ad-link-text,
.advanced-ad-close-text {
display: none;
}
.advanced-ad-actions {
gap: 8px;
}
.advanced-ad-close-btn {
padding: 6px 8px;
min-width: 32px;
justify-content: center;
}
}
${this.config.customCSS}
`;
document.head.appendChild(style);
}
createAdElement() {
const container = document.createElement('div');
container.className = `advanced-ad-container position-${this.config.position}`;
container.id = 'advanced-ad-injected';
// 如果支持折叠,添加标记类,便于样式或脚本针对折叠行为做处理
if (this.config.collapsible) {
container.classList.add('has-collapse');
}
if (this.config.height !== 'auto') {
container.style.height = this.config.height;
}
// 内容区域
const content = document.createElement('div');
content.className = 'advanced-ad-content';
// 图标
if (this.config.icon) {
const icon = document.createElement('div');
icon.className = 'advanced-ad-icon';
icon.innerHTML = this.config.icon;
content.appendChild(icon);
}
// 消息
const message = document.createElement('div');
message.className = 'advanced-ad-message';
message.innerHTML = this.config.message;
content.appendChild(message);
// 操作按钮
const actions = document.createElement('div');
actions.className = 'advanced-ad-actions';
// 链接
if (this.config.linkUrl) {
const link = document.createElement('a');
link.className = 'advanced-ad-link';
link.href = this.config.linkUrl;
link.target = '_blank';
link.innerHTML = `
<span class="advanced-ad-link-text">${this.config.linkText}</span>
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"></path>
<polyline points="15 3 21 3 21 9"></polyline>
<line x1="10" y1="14" x2="21" y2="3"></line>
</svg>
`;
actions.appendChild(link);
}
// 关闭按钮
if (this.config.showCloseButton) {
const closeBtn = document.createElement('button');
closeBtn.className = 'advanced-ad-close-btn';
closeBtn.innerHTML = `
<span class="advanced-ad-close-text">${this.config.closeText} (${this.closeCountdown}s)</span>
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<line x1="18" y1="6" x2="6" y2="18"></line>
<line x1="6" y1="6" x2="18" y2="18"></line>
</svg>
`;
closeBtn.addEventListener('click', () => this.closeAd());
actions.appendChild(closeBtn);
this.closeBtnElement = closeBtn;
}
content.appendChild(actions);
container.appendChild(content);
// 折叠按钮
if (this.config.collapsible) {
const collapseBtn = document.createElement('button');
collapseBtn.className = 'advanced-ad-collapse-btn';
// 根据位置决定图标方向
const isBottom = this.config.position.includes('bottom');
collapseBtn.innerHTML = `
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<polyline points="6 9 12 15 18 9"></polyline>
</svg>
`;
collapseBtn.addEventListener('click', (e) => {
e.stopPropagation();
this.toggleCollapse();
this.resetAutoRemoveTimer();
});
container.appendChild(collapseBtn);
}
if (this.isCollapsed) {
container.classList.add('collapsed');
}
this.adElement = container;
}
createSidebarTab() {
if (this.sidebarElement) return;
const sidebar = document.createElement('div');
sidebar.className = 'advanced-ad-sidebar';
sidebar.id = 'advanced-ad-sidebar';
// 根据原广告位置决定侧边栏位置
const pos = this.config.position;
if (pos.includes('right')) {
sidebar.classList.add('position-right');
} else {
sidebar.classList.add('position-left');
}
sidebar.textContent = this.config.sidebarText;
sidebar.addEventListener('click', () => this.reopenAd());
document.body.appendChild(sidebar);
this.sidebarElement = sidebar;
// 延迟显示动画
setTimeout(() => sidebar.classList.add('show'), 100);
}
insertAdElement() {
if (!this.adElement) return;
// 检查是否已存在
const existing = document.getElementById('advanced-ad-injected');
if (existing) {
existing.remove();
}
document.body.appendChild(this.adElement);
}
startCloseTimer() {
if (!this.config.showCloseButton) return;
this.closeTimer = setInterval(() => {
this.closeCountdown--;
if (this.closeBtnElement) {
const textSpan = this.closeBtnElement.querySelector('.advanced-ad-close-text');
if (textSpan) {
textSpan.textContent = `${this.config.closeText} (${this.closeCountdown}s)`;
}
}
if (this.closeCountdown <= 0) {
clearInterval(this.closeTimer);
if (this.closeBtnElement) {
this.closeBtnElement.classList.add('enabled');
this.closeBtnElement.style.cursor = 'pointer';
const textSpan = this.closeBtnElement.querySelector('.advanced-ad-close-text');
if (textSpan) {
textSpan.textContent = this.config.closeText;
}
}
}
}, 1000);
}
toggleCollapse() {
this.isCollapsed = !this.isCollapsed;
if (this.adElement) {
this.adElement.classList.toggle('collapsed', this.isCollapsed);
}
if (typeof this.config.onCollapse === 'function') {
this.config.onCollapse(this.isCollapsed);
}
}
closeAd() {
if (this.closeCountdown > 0) return;
if (this.isClosed) return;
this.isClosed = true;
if (this.adElement) {
this.adElement.style.opacity = '0';
this.adElement.style.transform = this.getCloseTransform();
setTimeout(() => {
if (this.adElement && this.adElement.parentNode) {
this.adElement.remove();
this.adElement = null;
}
// 根据配置决定关闭后行为
if (this.config.afterClose === 'hide-sidebar') {
this.createSidebarTab();
} else if (this.config.afterClose === 'remove') {
this.stopAntiDelete();
this.stopAutoRemoveTimer();
}
}, 300);
}
if (this.closeTimer) {
clearInterval(this.closeTimer);
}
if (typeof this.config.onClose === 'function') {
this.config.onClose();
}
}
reopenAd() {
if (this.sidebarElement) {
this.sidebarElement.classList.remove('show');
setTimeout(() => {
if (this.sidebarElement && this.sidebarElement.parentNode) {
this.sidebarElement.remove();
this.sidebarElement = null;
}
}, 300);
}
this.isClosed = false;
this.closeCountdown = this.config.closeDelay;
this.inject();
}
getCloseTransform() {
const pos = this.config.position;
if (pos.includes('top')) return 'translateY(-100%)';
if (pos.includes('bottom')) return 'translateY(100%)';
if (pos === 'left') return 'translateX(-100%)';
if (pos === 'right') return 'translateX(100%)';
return 'scale(0.8)';
}
setupAntiDelete() {
if (!this.config.enableAntiDelete) return;
// 使用 MutationObserver 监听DOM变化
this.observer = new MutationObserver((mutations) => {
// 防止在注入过程中触发
if (this.isInjecting || this.isClosed) return;
for (const mutation of mutations) {
if (mutation.type === 'childList') {
for (const node of mutation.removedNodes) {
// 检测主容器或侧边栏被删除
if (node === this.adElement ||
(node.id && node.id === 'advanced-ad-injected') ||
node === this.sidebarElement ||
(node.id && node.id === 'advanced-ad-sidebar')) {
console.warn('⚠️ 检测到广告被删除,正在恢复...');
if (typeof this.config.onDelete === 'function') {
this.config.onDelete();
}
// 短暂延迟后重新插入
setTimeout(() => {
if (!document.getElementById('advanced-ad-injected') && !this.isClosed) {
this.inject();
}
if (!document.getElementById('advanced-ad-sidebar') && this.isClosed && this.config.afterClose === 'hide-sidebar') {
this.createSidebarTab();
}
}, 100);
return; // 只处理一次
}
}
}
}
});
// 观察 body 和整个文档树
this.observer.observe(document.body, {
childList: true,
subtree: true
});
// 定期检查(备用方案)
this.checkInterval = setInterval(() => {
if (this.isInjecting || this.isClosed) return;
if (!document.getElementById('advanced-ad-injected') && this.adElement) {
console.warn('⚠️ 定期检查:广告丢失,正在恢复...');
if (typeof this.config.onDelete === 'function') {
this.config.onDelete();
}
this.inject();
}
if (this.isClosed && this.config.afterClose === 'hide-sidebar' && !document.getElementById('advanced-ad-sidebar')) {
this.createSidebarTab();
}
}, 2000);
}
stopAntiDelete() {
if (this.observer) {
this.observer.disconnect();
this.observer = null;
}
if (this.checkInterval) {
clearInterval(this.checkInterval);
this.checkInterval = null;
}
}
startAutoRemoveTimer() {
if (this.config.autoRemoveDelay <= 0) return;
this.resetAutoRemoveTimer();
}
resetAutoRemoveTimer() {
this.lastInteractionTime = Date.now();
if (this.autoRemoveTimer) {
clearTimeout(this.autoRemoveTimer);
}
if (this.config.autoRemoveDelay > 0 && !this.isClosed) {
this.autoRemoveTimer = setTimeout(() => {
if (Date.now() - this.lastInteractionTime >= this.config.autoRemoveDelay * 1000) {
console.log('⏱️ 无交互超时,自动移除广告');
this.forceRemove();
}
}, this.config.autoRemoveDelay * 1000);
}
}
stopAutoRemoveTimer() {
if (this.autoRemoveTimer) {
clearTimeout(this.autoRemoveTimer);
this.autoRemoveTimer = null;
}
}
forceRemove() {
this.isClosed = true;
this.stopAntiDelete();
this.stopAutoRemoveTimer();
if (this.adElement && this.adElement.parentNode) {
this.adElement.remove();
this.adElement = null;
}
if (this.sidebarElement && this.sidebarElement.parentNode) {
this.sidebarElement.remove();
this.sidebarElement = null;
}
if (typeof this.config.onClose === 'function') {
this.config.onClose();
}
}
updateMobileStyles() {
// 移动端样式已在CSS中处理,这里可以添加额外逻辑
}
}
// ==================== 启动 ====================
new AdvancedAdInjector(CONFIG);
})();