This commit is contained in:
telangpu
2026-04-28 00:42:28 +08:00
parent 2fd1a741cf
commit cf55c2cad6
2522 changed files with 566733 additions and 13 deletions

View File

@@ -0,0 +1,643 @@
import type { ComponentInternalInstance, Plugin, App } from 'vue';
/**
* 全局文本混淆插件
* 自动查找并替换所有文本节点
*/
// 修改Vue混淆插件以确保处理整个组件树
export const TextObfuscatorPlugin: Plugin = {
install(app: App) {
// 安装全局样式和解码脚本
addObfuscationStyle();
// 注册全局指令,直接处理元素内容
app.directive('odata', {
mounted(el) {
processTextNodes(el, null);
if (window.decodeObfuscatedContent) {
setTimeout(() => window.decodeObfuscatedContent(el), 0);
}
setupMutationObserver(el);
},
updated(el) {
processTextNodes(el, null);
if (window.decodeObfuscatedContent) {
setTimeout(() => window.decodeObfuscatedContent(el), 0);
}
}
});
// 在 app.mixin 中修改处理 $el 的部分
app.mixin({
mounted() {
// 安全地获取组件的根元素(s)
const rootElements = this.$el ?
(this.$el.nodeType === Node.ELEMENT_NODE ?
[this.$el] :
(Array.isArray(this.$el) ? this.$el : [])) :
[];
// 处理每个根元素
rootElements.forEach((rootElement: Element) => {
if (!rootElement || !(rootElement instanceof Element)) return;
// 1. 首先处理当前组件的根元素
processTextNodes(rootElement, this);
// 2. 递归处理所有子元素,确保覆盖所有文本节点
const processAllChildNodes = (element: Element) => {
if (!(element instanceof Element)) return;
try {
// 为每个子元素单独处理文本节点
const childElements = element.querySelectorAll('*');
childElements.forEach(childEl => {
processTextNodes(childEl, null);
});
// 立即解码当前处理的元素
if (window.decodeObfuscatedContent) {
setTimeout(() => window.decodeObfuscatedContent(element), 0);
}
} catch (error) {
console.error('Error processing child nodes:', error, element);
}
};
// 处理整个组件树
processAllChildNodes(rootElement);
// 设置监听
setupMutationObserver(rootElement);
});
// 添加:深度扫描所有包含纯文本的元素
setTimeout(() => {
const scanPureTextElements = (rootElement: Element) => {
// 跳过已处理过的元素
if (rootElement.hasAttribute && rootElement.hasAttribute('data-obfuscated')) {
return;
}
// 检查是否为包含纯文本的元素(只有文本节点,没有元素节点)
let hasOnlyTextNodes = false;
let hasElementNodes = false;
if (rootElement.childNodes && rootElement.childNodes.length > 0) {
hasOnlyTextNodes = Array.from(rootElement.childNodes).some(
node => node.nodeType === Node.TEXT_NODE && node.textContent && node.textContent.trim()
);
hasElementNodes = Array.from(rootElement.childNodes).some(
node => node.nodeType === Node.ELEMENT_NODE
);
}
// 如果元素只包含文本节点,并且不包含其他元素节点,则混淆它
if (hasOnlyTextNodes && !hasElementNodes &&
!SKIP_TAGS.includes(rootElement.tagName) &&
!Array.from(rootElement.classList || []).some(cls => SKIP_CLASSES.includes(cls))) {
processTextNodes(rootElement, null);
if (window.decodeObfuscatedContent) {
window.decodeObfuscatedContent(rootElement);
}
}
// 递归处理子元素
if (rootElement.children) {
Array.from(rootElement.children).forEach(child => {
scanPureTextElements(child);
});
}
};
// 从body开始扫描
scanPureTextElements(document.body);
}, 10); // 给DOM足够的时间渲染
},
updated() {
// 安全地获取组件的根元素(s)
const rootElements = this.$el ?
(this.$el.nodeType === Node.ELEMENT_NODE ?
[this.$el] :
(Array.isArray(this.$el) ? this.$el : [])) :
[];
// 处理每个根元素
rootElements.forEach((rootElement: Element | undefined) => {
if (!rootElement || !(rootElement instanceof Element)) return;
processTextNodes(rootElement, this);
try {
// 递归处理所有子元素
const childElements = rootElement.querySelectorAll('*');
childElements.forEach((childEl: Element) => {
processTextNodes(childEl, null);
});
if (window.decodeObfuscatedContent) {
setTimeout(() => window.decodeObfuscatedContent(rootElement), 0);
}
} catch (error) {
console.error('Error processing updated component:', error, rootElement);
}
});
}
});
}
};
// 声明全局函数类型
declare global {
interface Window {
decodeObfuscatedContent: (rootElement?: Element) => void;
}
}
// 需要跳过的标签
const SKIP_TAGS = ['SCRIPT', 'STYLE', 'TEXTAREA', 'INPUT', 'PRE', 'CODE'];
// 需要跳过的类名
const SKIP_CLASSES = ['no-obfuscate'];
/**
* 处理元素中的所有文本节点
*/
function processTextNodes(element: Element, instance: ComponentInternalInstance | null) {
if (!element || element.nodeType !== Node.ELEMENT_NODE) {
return;
}
// 为 DIV 元素增加优先处理逻辑
if (element.tagName === 'DIV' && !element.hasAttribute('data-obfuscated')) {
// 对于DIV元素特殊处理其直接子文本节点
let hasTextContent = false;
for (let i = 0; i < element.childNodes.length; i++) {
const node = element.childNodes[i];
if (node.nodeType === Node.TEXT_NODE && node.textContent && node.textContent.trim() !== '') {
hasTextContent = true;
break;
}
}
// 如果DIV中有直接的文本内容标记它需要被处理
if (hasTextContent) {
// 只处理未混淆过的DIV
const textContent = Array.from(element.childNodes)
.filter(node => node.nodeType === Node.TEXT_NODE && node.textContent)
.map(node => node.textContent).join('').trim();
if (textContent) {
// 替换整个DIV的内容
const originalHTML = element.innerHTML;
const processedHTML = obfuscateText(textContent);
element.innerHTML = processedHTML + originalHTML.replace(textContent, '');
element.setAttribute('data-obfuscated', 'true');
}
}
}
// 跳过带有特定标记的元素(避免重复处理)
if (element.hasAttribute('data-obfuscated') ||
!element ||
SKIP_TAGS.includes(element.tagName) ||
Array.from(element.classList).some(cls => SKIP_CLASSES.includes(cls))) {
return;
}
// 使用 TreeWalker 遍历所有文本节点
const walker = document.createTreeWalker(
element,
NodeFilter.SHOW_TEXT,
{
acceptNode(node) {
// 不处理完全空的文本节点
if (!node.textContent) {
return NodeFilter.FILTER_REJECT;
}
// 检查父节点是否应该被跳过
const parent = node.parentElement;
if (parent && (
SKIP_TAGS.includes(parent.tagName) ||
parent.hasAttribute('data-obfuscated') ||
Array.from(parent.classList || []).some(cls => SKIP_CLASSES.includes(cls))
)) {
return NodeFilter.FILTER_REJECT;
}
// 如果只包含空白且不是段落的首个节点,则跳过
if (node.textContent.trim() === '' &&
!(parent?.tagName === 'P' && node === parent.firstChild)) {
return NodeFilter.FILTER_REJECT;
}
return NodeFilter.FILTER_ACCEPT;
}
}
);
// 收集需要处理的文本节点
const textNodes: Text[] = [];
let currentNode: Node | null = walker.nextNode();
while (currentNode) {
textNodes.push(currentNode as Text);
currentNode = walker.nextNode();
}
// 预先计算所有混淆内容减少DOM操作次数
const fragments: DocumentFragment[] = [];
const nodesToReplace: Text[] = [];
// 在处理文本节点前,移除所有前导空格
for (const textNode of textNodes) {
const text = textNode.textContent;
if (!text) continue;
// 重要修改:检测是否是段落的首个文本节点,无条件移除开头的空白
let processedText = text;
if (textNode.parentElement?.tagName === 'P' &&
textNode === textNode.parentElement.firstChild) {
// 去除开头的空白,无论什么情况
processedText = text.replace(/^\s+/, '');
// 如果去除空白后为空,直接跳过这个节点
if (!processedText) continue;
}
try {
// 创建文档碎片来存储混淆后的内容
const fragment = document.createDocumentFragment();
const tempContainer = document.createElement('div');
tempContainer.innerHTML = obfuscateText(processedText);
// 将内容移动到碎片中
while (tempContainer.firstChild) {
fragment.appendChild(tempContainer.firstChild);
}
fragments.push(fragment);
nodesToReplace.push(textNode);
} catch (error) {
console.error('Error processing text node:', error);
}
}
// 批量替换节点,减少回流
for (let i = 0; i < nodesToReplace.length; i++) {
const textNode = nodesToReplace[i];
const fragment = fragments[i];
if (textNode.parentNode) {
textNode.parentNode.replaceChild(fragment, textNode);
}
}
}
/**
* 设置 MutationObserver 来监听 DOM 变化
*/
function setupMutationObserver(element: Element) {
if (!element) return;
const observer = new MutationObserver((mutations) => {
for (const mutation of mutations) {
if (mutation.type === 'childList' && mutation.addedNodes.length) {
mutation.addedNodes.forEach((node) => {
if (node.nodeType === Node.ELEMENT_NODE) {
processTextNodes(node as Element, null);
// 观察到新元素后立即解码
if (window.decodeObfuscatedContent) {
window.decodeObfuscatedContent(node as Element);
}
}
});
}
}
});
observer.observe(element, {
childList: true,
subtree: true
});
}
/**
* 生成随机噪声标签和注释
*/
function getRandomNoise() {
const noiseTypes = [
// HTML注释
() => `<!--${Math.random().toString(36).substring(2, 6)}-->`,
// 空的自定义元素
() => {
const tags = ['z-nil', 'z-void', 'z-null', 'z-fake', 'z-empty'];
const tag = tags[Math.floor(Math.random() * tags.length)];
return `<${tag}></${tag}>`;
},
// 带随机属性的自定义元素
() => {
const attr = `data-${Math.random().toString(36).substring(2, 7)}`;
const value = Math.random().toString(36).substring(2, 10);
return `<z-attr ${attr}="${value}"></z-attr>`;
},
// 带随机文本的隐藏元素
() => {
const text = Math.random().toString(36).substring(2, 8);
return `<z-text>${text}</z-text>`;
}
];
// 随机选择一种噪声类型
const noiseGenerator = noiseTypes[Math.floor(Math.random() * noiseTypes.length)];
return noiseGenerator();
}
/**
* 混淆文本 - 将文本拆分成单词并分别存储使用自定义z-标签
*/
function obfuscateText(text: string): string {
if (!text) return '';
// 首先移除文本开头的所有空白字符以解决段落首行缩进问题
text = text.replace(/^\s+/, '');
if (!text) return '';
// 使用正则表达式将文本拆分为单词和空格,保持完整性
const words = text.split(/(\s+)/);
let result = '<z-wrap data-obfuscated="true">';
// 过滤掉空字符串避免生成空的z-span元素
const filteredWords = words.filter(word => word.length > 0);
// 为每个单词创建一个独立的自定义元素
filteredWords.forEach((word, index) => {
// 对空格和特殊字符进行特殊处理
if (/^\s+$/.test(word)) {
// 改进:更精确地处理各种换行符
if (/[\n\r]/.test(word)) {
// 将所有类型的换行符分割出来但仅在实际有换行符时才添加z-break元素
result += '<z-break></z-break>';
} else {
// 纯空格的情况 - 对连续空格合并处理避免添加过多z-space
result += '<z-space></z-space>';
}
return;
}
// 随机在单词前添加噪声,但减少频率
if (Math.random() > 0.85) {
result += getRandomNoise();
}
// 为了提高效率,固定属性名
const attrId = `data`;
// 将单词编码为Base64
const encodedWord = btoa(encodeURIComponent(word));
// 随机选择z-span或z-strong标签增加混淆度
const tagName = Math.random() > 0.5 ? 'z-span' : 'z-strong';
// 添加标签开始部分
result += `<${tagName} data-${attrId}="${encodedWord}" data-preload="true">`;
// 随机在标签内添加隐藏内容,但减少频率
if (Math.random() > 0.8) {
const fakeText = Math.random().toString(36).substring(2, 5 + Math.floor(Math.random() * 5));
result += `<z-hidden>${fakeText}</z-hidden>`;
}
// 闭合标签
result += `</${tagName}>`;
// 随机在单词后添加噪声,但减少频率
if (Math.random() > 0.85) {
result += getRandomNoise();
}
});
result += '</z-wrap>';
return result;
}
/**
* 添加显示混淆内容的样式和解码脚本
*/
function addObfuscationStyle() {
// 添加样式
if (!document.getElementById('obfuscation-style')) {
const style = document.createElement('style');
style.id = 'obfuscation-style';
style.textContent = `
/* 自定义元素基本样式 */
z-wrap {
display: inline;
white-space: normal;
user-select: none; /* 阻止文本选择 */
text-indent: 0 !important; /* 确保无缩进 */
}
/* 添加一个类来允许选择文本的情况 */
.allow-select z-wrap {
user-select: text;
}
/* 确保段落中的混淆内容没有开头缩进 */
p > z-wrap:first-child {
text-indent: 0 !important;
margin-left: 0 !important;
padding-left: 0 !important;
}
/* 处理所有p标签确保没有多余空间 */
p {
text-indent: 0;
}
z-span, z-strong {
display: inline-block;
position: relative;
opacity: 1;
transition: opacity 0.1s ease;
margin: 0;
padding: 0;
}
/* 控制右侧间距 */
z-span + z-span, z-strong + z-strong, z-span + z-strong, z-strong + z-span {
margin-left: 0.1em;
}
/* 移除最后一个元素的右侧间距 */
z-span:last-child, z-strong:last-child {
margin-right: 0;
}
z-span::after, z-strong::after {
content: attr(data-content);
position: relative;
pointer-events: none;
}
/* 空格元素 - 精确控制宽度 */
z-space {
display: inline-block;
width: 0.25em;
margin: 0;
padding: 0;
}
/* 空的z-span/z-strong元素不应该显示 */
z-span:not([data-content]), z-strong:not([data-content]) {
display: none;
}
/* 换行元素 - 强制换行且完全没有尺寸 */
z-break {
display: block !important;
height: 0 !important;
width: 0 !important;
margin: 0 !important;
padding: 0 !important;
border: none !important;
line-height: 0 !important;
font-size: 0 !important;
overflow: hidden !important;
}
/* 隐藏所有噪声元素 */
z-nil, z-void, z-null, z-fake, z-empty, z-attr, z-text, z-hidden {
display: none;
width: 0;
height: 0;
opacity: 0;
overflow: hidden;
position: absolute;
visibility: hidden;
}
/* 预加载状态 */
[data-preload="true"] {
min-width: 0.5em;
min-height: 1em;
}
`;
document.head.appendChild(style);
}
// 添加自定义元素注册,确保所有浏览器都能正确处理
const scriptCustomElements = document.createElement('script');
scriptCustomElements.textContent = `
// 注册所有自定义元素
if ('customElements' in window) {
customElements.define('z-wrap', class extends HTMLElement {});
customElements.define('z-span', class extends HTMLElement {});
customElements.define('z-strong', class extends HTMLElement {});
customElements.define('z-space', class extends HTMLElement {});
customElements.define('z-break', class extends HTMLElement {});
// 注册噪声元素
customElements.define('z-nil', class extends HTMLElement {});
customElements.define('z-void', class extends HTMLElement {});
customElements.define('z-null', class extends HTMLElement {});
customElements.define('z-fake', class extends HTMLElement {});
customElements.define('z-empty', class extends HTMLElement {});
customElements.define('z-attr', class extends HTMLElement {});
customElements.define('z-text', class extends HTMLElement {});
customElements.define('z-hidden', class extends HTMLElement {});
}
`;
document.head.appendChild(scriptCustomElements);
// 添加提前解码的脚本,放在<head>顶部优先加载
if (!document.getElementById('obfuscation-script')) {
const script = document.createElement('script');
script.id = 'obfuscation-script';
script.textContent = `
(function() {
// 定义解码函数并暴露为全局函数
window.decodeObfuscatedContent = function(rootElement) {
const root = rootElement || document.body;
const elements = root.querySelectorAll('z-span[data-preload="true"], z-strong[data-preload="true"]');
if (elements.length === 0) return;
// 使用requestIdleCallback或setTimeout在空闲时运行避免阻塞渲染
const runWhenIdle = window.requestIdleCallback ||
function(cb) { setTimeout(cb, 1); };
runWhenIdle(() => {
elements.forEach(el => {
// 避免重复解码
if (el.hasAttribute('data-content')) return;
// 找到编码数据
const dataAttr = el.getAttribute('data-data');
if (dataAttr) {
try {
// 解码并设置
const decodedWord = decodeURIComponent(atob(dataAttr));
el.setAttribute('data-content', decodedWord);
el.removeAttribute('data-preload');
} catch (e) {
// 解码失败时跳过
}
}
});
});
};
// 页面加载完成后立即执行一次全局解码
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', function() {
window.decodeObfuscatedContent();
});
} else {
window.decodeObfuscatedContent();
}
// 使用IntersectionObserver优化解码性能
if ('IntersectionObserver' in window) {
const decodeObserver = new IntersectionObserver(
(entries, observer) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
// 容器进入视口时解码其内容
window.decodeObfuscatedContent(entry.target);
observer.unobserve(entry.target);
}
});
},
{ rootMargin: '200px 0px' } // 提前200像素开始解码
);
// 监听所有混淆容器
function observeContainers() {
document.querySelectorAll('z-wrap').forEach(container => {
decodeObserver.observe(container);
});
}
// 初始观察
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', observeContainers);
} else {
observeContainers();
}
// 定期检查新容器
setInterval(observeContainers, 2000);
} else {
// 降级方案:定期全局检查
setInterval(() => window.decodeObfuscatedContent(), 1000);
}
})();
`;
// 将脚本添加到head的最前面确保尽早加载
if (document.head.firstChild) {
document.head.insertBefore(script, document.head.firstChild);
} else {
document.head.appendChild(script);
}
}
}