文本转JS工具,智能识别修改油猴脚本元数据
// ==UserScript==
// @name Text to js
// @namespace http://tampermonkey.net/
// @version 1.54
// @description 文本转JS工具,智能识别修改油猴脚本元数据
// @author 苳:-)
// @match *://*/*
// @grant GM_addStyle
// ==/UserScript==
(function() {
'use strict';
let codeContent = '';
let originalHeader = '';
let originalBody = '';
let detectedName = '';
let detectedVersion = '';
const version = 'v1.54';
// 更新日志(保持不变)
const changelog = [
"v1.54 修复脚本信息识别错误问题",
"v1.53 修复面板显示问题",
"v1.52 全面修复规则添加",
"v1.51 修复粘贴代码下载问题",
"v1.50 优化下载文件名自动识别",
"v1.49 优化当前基准显示"
];
// 创建 Shadow DOM 容器
const host = document.createElement('div');
document.body.appendChild(host);
const shadow = host.attachShadow({mode:'open'});
GM_addStyle(`
#text2js_panel * { all: initial !important; box-sizing: border-box !important; }
#text2js_panel { font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Arial !important; }
`);
// HTML + 样式 - 统一蓝色执行按钮版
shadow.innerHTML = `
<div id="text2js_panel">
<style>
#panel{
position:fixed;
top:60px;
right:20px;
width:90%;
max-width:400px;
background:#fff;
border:1px solid #ccc;
z-index:999999;
font-size:13px;
color:#111;
box-shadow:0 4px 12px rgba(0,0,0,0.15);
border-radius:6px;
transition:all 0.3s ease;
max-height:80vh;
overflow-y:auto;
user-select:none;
}
#panel.dragging {
opacity:0.9;
box-shadow:0 8px 24px rgba(0,0,0,0.2);
transition:none;
cursor:grabbing;
}
#panel.minimized{
transform:scale(0.8);
opacity:0.9;
cursor:pointer;
transform-origin:top right;
max-height:none;
overflow:visible;
}
#panel.minimized:hover{opacity:1;}
#panel header{
background:#111;
color:#fff;
padding:14px;
font-weight:600;
text-align:left;
border-top-left-radius:6px;
border-top-right-radius:6px;
display:flex;
justify-content:space-between;
align-items:center;
position:sticky;
top:0;
z-index:10;
cursor:grab;
user-select:none;
}
#panel header:active { cursor:grabbing; }
#panel.minimized header { cursor:pointer; }
#panel.minimized header:active { cursor:pointer; }
#panel header .title{
display:flex;
align-items:center;
gap:6px;
}
#panel header .title .script-name{
font-weight:600;
color:#fff;
}
#panel header .title .script-version{
font-weight:600;
color:#ffd700;
}
#panel header .toggle-btn{
background:transparent;
border:none;
color:#fff;
font-size:18px;
cursor:pointer;
padding:0 8px;
border-radius:4px;
line-height:1;
z-index:11;
}
#panel header .toggle-btn:hover{background:rgba(255,255,255,0.2);}
.author{
padding:12px;
border-bottom:1px solid #eee;
display:flex;
gap:12px;
align-items:center;
}
.author span{
font-weight:600;
color:#111;
}
.author a{
color:#007aff;
text-decoration:none;
}
.file{padding:12px;border-bottom:1px solid #eee;background:#fafafa;}
.active-source{padding:12px;background:#f5f5f5;border-bottom:1px solid #e0e0e0;font-size:13px;display:flex;align-items:center;gap:8px;}
.text-editor{padding:12px;border-bottom:1px solid #eee;}
.info{padding:12px;border-bottom:1px solid #eee;background:#f9f9f9;}
.edit{padding:12px;border-bottom:1px solid #eee;}
.action-buttons{padding:12px;display:flex;gap:6px;}
.file input,
.text-editor textarea,
.metadata-field input,
.metadata-field textarea {
width:100%;
border:1px solid #ccc;
border-radius:4px;
padding:8px;
font-size:13px;
box-sizing:border-box;
}
.file input {
overflow:hidden;
text-overflow:ellipsis;
white-space:nowrap;
}
.text-editor textarea {
min-height:100px;
resize:vertical;
font-family:monospace;
}
.active-source .source-label{font-weight:600;color:#333;}
.active-source .source-text{color:#1976d2;font-weight:500;}
.active-source .source-time{color:#666;font-size:12px;margin-left:auto;}
.text-editor .editor-header{
display:flex;
justify-content:space-between;
align-items:center;
margin-bottom:6px;
}
.text-editor .editor-header .editor-title{font-weight:600;font-size:13px;color:#333;}
.text-editor .editor-header .clear-text{color:#007aff;cursor:pointer;font-size:12px;}
.text-editor .editor-header .clear-text:hover{text-decoration:underline;}
.info-header{
cursor:pointer;
font-weight:600;
display:flex;
justify-content:space-between;
align-items:center;
}
.info-header .toggle-text{color:#007aff;font-size:12px;font-weight:normal;}
.info-content{
margin-top:8px;
display:none;
font-size:12px;
color:#333;
max-height:120px;
overflow-y:auto;
white-space:pre-line;
}
.edit-header{
cursor:pointer;
font-weight:600;
display:flex;
justify-content:space-between;
align-items:center;
}
.edit-header .toggle-text{color:#007aff;font-size:12px;font-weight:normal;}
.edit-content{margin-top:10px;display:none;}
.metadata-section{margin-bottom:15px;}
.metadata-section .section-header{
font-weight:600;
margin-bottom:8px;
color:#333;
font-size:12px;
padding-bottom:2px;
border-bottom:1px solid #eee;
}
.metadata-field{margin-bottom:12px;}
.metadata-field label{
display:block;
font-size:12px;
color:#666;
margin-bottom:2px;
}
.metadata-field textarea{min-height:60px;resize:vertical;}
.advanced-toggle{
margin:10px 0;
display:flex;
align-items:center;
gap:8px;
}
.advanced-toggle .toggle-switch{
position:relative;
display:inline-block;
width:36px;
height:20px;
}
.advanced-toggle .toggle-switch input{opacity:0;width:0;height:0;}
.advanced-toggle .slider{
position:absolute;
cursor:pointer;
top:0;
left:0;
right:0;
bottom:0;
background-color:#ccc;
transition:.2s;
border-radius:20px;
}
.advanced-toggle .slider:before{
position:absolute;
content:"";
height:16px;
width:16px;
left:2px;
bottom:2px;
background-color:white;
transition:.2s;
border-radius:50%;
}
.advanced-toggle input:checked + .slider{background-color:#007aff;}
.advanced-toggle input:checked + .slider:before{transform:translateX(16px);}
.advanced-toggle .toggle-label{font-size:12px;color:#666;}
.advanced-content{
margin-top:10px;
padding-top:10px;
border-top:1px solid #eee;
display:none;
}
/* 简化按钮样式 - 统一为蓝色 */
.action-buttons button{
flex:1;
padding:10px 0;
border:none;
border-radius:4px;
color:#fff;
font-size:14px;
cursor:pointer;
transition:transform 0.1s, background-color 0.2s;
font-weight:500;
}
.action-buttons button:active{transform:scale(0.95);}
/* 执行按钮 - 始终蓝色 */
.action-buttons .execute-btn{
background:#007aff; /* 统一蓝色 */
}
.action-buttons .execute-btn:hover{
background:#0051b3; /* 深蓝色悬停效果 */
}
.action-buttons .execute-btn.disabled{
background:#b0b0b0; /* 灰色表示不可用 */
cursor:not-allowed;
}
/* 清空按钮保持红色 */
.action-buttons .clear-btn{
background:#f44336;
}
.action-buttons .clear-btn:hover{
background:#d32f2f;
}
.status-badge{
background:#4CAF50;
color:#fff;
font-size:10px;
padding:2px 6px;
border-radius:10px;
margin-left:6px;
}
.status-badge.na{background:#9e9e9e;}
</style>
<div id="panel">
<header id="dragHandle">
<div class="title">
<span class="script-name">Text to js</span>
<span class="script-version">${version}</span>
</div>
<button class="toggle-btn" id="togglePanel">−</button>
</header>
<div id="panelContent">
<!-- 作者信息 -->
<div class="author">
<span>作者 苳:-)</span>
<a href="https://weibo.com/u/2809762605" target="_blank">Weibo</a>
<a href="https://x.com/nkvvo?s=21" target="_blank">Twitter</a>
</div>
<!-- 文件选择 -->
<div class="file">
<input type="file" accept=".txt,.js" id="fileInput" placeholder="选择TXT或JS文件...">
</div>
<!-- 当前基准提示 -->
<div class="active-source" id="activeSource">
<span class="source-label">当前基准</span>
<span class="source-text" id="sourceText">未选择内容</span>
<span class="source-time" id="sourceTime"></span>
</div>
<!-- 文本编辑框 -->
<div class="text-editor">
<div class="editor-header">
<span class="editor-title">填入代码</span>
<span class="clear-text" id="clearTextBtn">清空</span>
</div>
<textarea id="codeEditor" placeholder="可在此粘贴JS或TXT代码..."></textarea>
</div>
<!-- 脚本信息模块 -->
<div class="info">
<div class="info-header">
<span>脚本信息</span>
<span class="toggle-text" id="infoToggle">展开</span>
</div>
<div class="info-content" id="infoContent">
脚本介绍:文本转 JS 文件工具,支持TXT转JS和JS元数据修改。
当前版本:${version}
更新日志:
${changelog.join('\n')}
</div>
</div>
<!-- 脚本修改模块 -->
<div class="edit">
<div class="edit-header">
<div>
<span>脚本修改</span>
<span class="status-badge na" id="headerStatus">未检测</span>
</div>
<span class="toggle-text" id="editToggle">展开</span>
</div>
<div class="edit-content" id="editContent">
<!-- 基础信息 -->
<div class="metadata-section">
<div class="section-header">基础信息</div>
<div class="metadata-field">
<label>@name 脚本名称</label>
<input type="text" id="metaName" placeholder="脚本名称">
</div>
<div class="metadata-field">
<label>@namespace 命名空间</label>
<input type="text" id="metaNamespace" placeholder="http://tampermonkey.net/">
</div>
<div class="metadata-field">
<label>@version 版本号</label>
<input type="text" id="metaVersion" placeholder="1.0.0">
</div>
<div class="metadata-field">
<label>@description 描述</label>
<input type="text" id="metaDescription" placeholder="脚本描述">
</div>
<div class="metadata-field">
<label>@author 作者</label>
<input type="text" id="metaAuthor" placeholder="作者名">
</div>
</div>
<!-- 高级功能开关 -->
<div class="advanced-toggle">
<label class="toggle-switch">
<input type="checkbox" id="advancedToggle">
<span class="slider"></span>
</label>
<span class="toggle-label">高级功能(@match、@grant等)</span>
</div>
<!-- 高级功能区域 -->
<div class="advanced-content" id="advancedContent">
<div class="metadata-section">
<div class="section-header">匹配规则</div>
<div class="metadata-field">
<label>@match 匹配规则</label>
<textarea id="metaMatch" placeholder="*://*/* 每行一个规则"></textarea>
</div>
<div class="metadata-field">
<label>@include 包含规则</label>
<textarea id="metaInclude" placeholder="https://*/* 每行一个规则"></textarea>
</div>
<div class="metadata-field">
<label>@exclude 排除规则</label>
<textarea id="metaExclude" placeholder="https://*/* 每行一个规则"></textarea>
</div>
</div>
<div class="metadata-section">
<div class="section-header">权限设置</div>
<div class="metadata-field">
<label>@grant 授权</label>
<textarea id="metaGrant" placeholder="GM_addStyle GM_xmlhttpRequest 每行一个授权"></textarea>
</div>
<div class="metadata-field">
<label>@connect 连接</label>
<textarea id="metaConnect" placeholder="api.example.com 每行一个域名"></textarea>
</div>
<div class="metadata-field">
<label>@require 依赖</label>
<textarea id="metaRequire" placeholder="https://code.jquery.com/jquery-3.6.0.min.js 每行一个URL"></textarea>
</div>
</div>
<div class="metadata-section">
<div class="section-header">脚本信息</div>
<div class="metadata-field">
<label>@run-at 运行时机</label>
<input type="text" id="metaRunAt" placeholder="document-start / document-end / document-idle">
</div>
<div class="metadata-field">
<label>@noframes 框架控制</label>
<input type="text" id="metaNoframes" placeholder="输入 @noframes 启用">
</div>
<div class="metadata-field">
<label>@icon 图标</label>
<input type="text" id="metaIcon" placeholder="https://example.com/icon.png">
</div>
<div class="metadata-field">
<label>@license 许可证</label>
<input type="text" id="metaLicense" placeholder="MIT / GPL / etc">
</div>
</div>
<div class="metadata-section">
<div class="section-header">更新相关</div>
<div class="metadata-field">
<label>@downloadURL 下载地址</label>
<input type="text" id="metaDownloadURL" placeholder="https://example.com/script.user.js">
</div>
<div class="metadata-field">
<label>@updateURL 更新地址</label>
<input type="text" id="metaUpdateURL" placeholder="https://example.com/script.meta.js">
</div>
</div>
<div class="metadata-section">
<div class="section-header">其他元数据</div>
<div class="metadata-field">
<label>自定义标签</label>
<textarea id="metaOther" placeholder="@homepage https://example.com @supportURL https://example.com/support 每行一个"></textarea>
</div>
</div>
</div>
</div>
</div>
<!-- 底部功能按钮 - 简化版 -->
<div class="action-buttons">
<button class="execute-btn disabled" id="executeBtn">执行</button>
<button class="clear-btn" id="clearBtn">清空</button>
</div>
</div>
</div>
</div>
`;
// 获取所有元素
const panel = shadow.getElementById('panel');
const panelContent = shadow.getElementById('panelContent');
const toggleBtn = shadow.getElementById('togglePanel');
const dragHandle = shadow.getElementById('dragHandle');
const fileInput = shadow.getElementById('fileInput');
const codeEditor = shadow.getElementById('codeEditor');
const executeBtn = shadow.getElementById('executeBtn');
const clearBtn = shadow.getElementById('clearBtn');
const clearTextBtn = shadow.getElementById('clearTextBtn');
const activeSource = shadow.getElementById('activeSource');
const sourceText = shadow.getElementById('sourceText');
const sourceTime = shadow.getElementById('sourceTime');
// 脚本信息相关
const infoHeader = shadow.querySelector('.info-header');
const infoContent = shadow.getElementById('infoContent');
const infoToggle = shadow.getElementById('infoToggle');
// 脚本修改相关
const editHeader = shadow.querySelector('.edit-header');
const editContent = shadow.getElementById('editContent');
const editToggle = shadow.getElementById('editToggle');
const headerStatus = shadow.getElementById('headerStatus');
// 高级功能相关
const advancedToggle = shadow.getElementById('advancedToggle');
const advancedContent = shadow.getElementById('advancedContent');
// 输入字段
const metaName = shadow.getElementById('metaName');
const metaNamespace = shadow.getElementById('metaNamespace');
const metaVersion = shadow.getElementById('metaVersion');
const metaDescription = shadow.getElementById('metaDescription');
const metaAuthor = shadow.getElementById('metaAuthor');
const metaMatch = shadow.getElementById('metaMatch');
const metaInclude = shadow.getElementById('metaInclude');
const metaExclude = shadow.getElementById('metaExclude');
const metaGrant = shadow.getElementById('metaGrant');
const metaConnect = shadow.getElementById('metaConnect');
const metaRequire = shadow.getElementById('metaRequire');
const metaRunAt = shadow.getElementById('metaRunAt');
const metaNoframes = shadow.getElementById('metaNoframes');
const metaIcon = shadow.getElementById('metaIcon');
const metaLicense = shadow.getElementById('metaLicense');
const metaDownloadURL = shadow.getElementById('metaDownloadURL');
const metaUpdateURL = shadow.getElementById('metaUpdateURL');
const metaOther = shadow.getElementById('metaOther');
// 拖动功能实现
let isDragging = false;
let startX, startY, startLeft, startTop;
function initDrag() {
if (!panel || !dragHandle) return;
dragHandle.addEventListener('mousedown', startDrag);
document.addEventListener('mousemove', drag);
document.addEventListener('mouseup', stopDrag);
}
function startDrag(e) {
if (e.target === toggleBtn || toggleBtn.contains(e.target)) return;
if (panel.classList.contains('minimized')) return;
e.preventDefault();
isDragging = true;
panel.classList.add('dragging');
const rect = panel.getBoundingClientRect();
startLeft = rect.left;
startTop = rect.top;
startX = e.clientX;
startY = e.clientY;
panel.style.position = 'fixed';
panel.style.left = startLeft + 'px';
panel.style.top = startTop + 'px';
panel.style.right = 'auto';
panel.style.bottom = 'auto';
panel.style.margin = '0';
}
function drag(e) {
if (!isDragging) return;
e.preventDefault();
const dx = e.clientX - startX;
const dy = e.clientY - startY;
let newLeft = startLeft + dx;
let newTop = startTop + dy;
const maxLeft = window.innerWidth - panel.offsetWidth;
const maxTop = window.innerHeight - panel.offsetHeight;
newLeft = Math.max(0, Math.min(newLeft, maxLeft));
newTop = Math.max(0, Math.min(newTop, maxTop));
panel.style.left = newLeft + 'px';
panel.style.top = newTop + 'px';
}
function stopDrag() {
if (isDragging) {
isDragging = false;
panel.classList.remove('dragging');
}
}
// 确保所有元素都存在
if (!panel || !panelContent || !toggleBtn || !fileInput || !codeEditor || !executeBtn || !clearBtn) {
console.error('面板元素初始化失败');
return;
}
// 状态管理
let hasFile = false;
let hasText = false;
let activeSourceType = null;
let lastOperationTime = null;
let lastFileName = '';
let advancedEnabled = false;
let infoExpanded = false;
let editExpanded = false;
let isPanelMinimized = false;
let currentFileContent = '';
let originalHeaderContent = '';
let originalFormState = {};
let fileType = '';
// 初始化拖动功能
initDrag();
// 从 localStorage 读取面板状态
function loadPanelState() {
try {
const savedState = localStorage.getItem('text2js_panel_minimized');
if (savedState !== null) {
isPanelMinimized = savedState === 'true';
if (isPanelMinimized) {
panel.classList.add('minimized');
if (panelContent) panelContent.style.display = 'none';
if (toggleBtn) toggleBtn.textContent = '+';
} else {
panel.classList.remove('minimized');
if (panelContent) panelContent.style.display = 'block';
if (toggleBtn) toggleBtn.textContent = '−';
}
}
} catch (e) {
console.log('无法读取面板状态');
}
}
// 保存面板状态到 localStorage
function savePanelState() {
try {
localStorage.setItem('text2js_panel_minimized', isPanelMinimized);
} catch (e) {
console.log('无法保存面板状态');
}
}
// 更新时间显示
function updateSourceDisplay() {
if (!sourceText || !activeSource || !sourceTime) return;
if (activeSourceType === 'file') {
sourceText.textContent = `文件 ${lastFileName || '已选择文件'}`;
activeSource.style.background = '#e3f2fd';
activeSource.style.borderBottomColor = '#bbdefb';
} else if (activeSourceType === 'text') {
sourceText.textContent = '文本 粘贴的代码';
activeSource.style.background = '#fff3e0';
activeSource.style.borderBottomColor = '#ffe0b2';
} else {
sourceText.textContent = '未选择内容';
activeSource.style.background = '#f5f5f5';
activeSource.style.borderBottomColor = '#e0e0e0';
}
if (lastOperationTime) {
sourceTime.textContent = `最后操作 ${lastOperationTime}`;
} else {
sourceTime.textContent = '';
}
}
// 脚本信息展开/折叠
if (infoHeader && infoContent && infoToggle) {
infoHeader.onclick = (e) => {
e.stopPropagation();
if (infoExpanded) {
infoContent.style.display = 'none';
infoToggle.textContent = '展开';
infoExpanded = false;
} else {
infoContent.style.display = 'block';
infoToggle.textContent = '收起';
infoExpanded = true;
}
};
}
// 脚本修改展开/折叠
if (editHeader && editContent && editToggle) {
editHeader.onclick = (e) => {
e.stopPropagation();
if (editExpanded) {
editContent.style.display = 'none';
editToggle.textContent = '展开';
editExpanded = false;
} else {
editContent.style.display = 'block';
editToggle.textContent = '收起';
editExpanded = true;
}
};
}
// 高级功能开关
if (advancedToggle && advancedContent) {
advancedToggle.onchange = (e) => {
advancedEnabled = e.target.checked;
advancedContent.style.display = advancedEnabled ? 'block' : 'none';
updateExecuteButtonState();
};
}
// 保存原始表单状态
function saveOriginalFormState() {
originalFormState = {
metaName: metaName?.value || '',
metaNamespace: metaNamespace?.value || '',
metaVersion: metaVersion?.value || '',
metaDescription: metaDescription?.value || '',
metaAuthor: metaAuthor?.value || '',
metaMatch: metaMatch?.value || '',
metaInclude: metaInclude?.value || '',
metaExclude: metaExclude?.value || '',
metaGrant: metaGrant?.value || '',
metaConnect: metaConnect?.value || '',
metaRequire: metaRequire?.value || '',
metaRunAt: metaRunAt?.value || '',
metaNoframes: metaNoframes?.value || '',
metaIcon: metaIcon?.value || '',
metaLicense: metaLicense?.value || '',
metaDownloadURL: metaDownloadURL?.value || '',
metaUpdateURL: metaUpdateURL?.value || '',
metaOther: metaOther?.value || '',
advancedEnabled: advancedEnabled
};
}
// 检查表单是否被修改
function isFormModified() {
if (!activeSourceType) return false;
return (metaName?.value || '') !== originalFormState.metaName ||
(metaNamespace?.value || '') !== originalFormState.metaNamespace ||
(metaVersion?.value || '') !== originalFormState.metaVersion ||
(metaDescription?.value || '') !== originalFormState.metaDescription ||
(metaAuthor?.value || '') !== originalFormState.metaAuthor ||
(metaMatch?.value || '') !== originalFormState.metaMatch ||
(metaInclude?.value || '') !== originalFormState.metaInclude ||
(metaExclude?.value || '') !== originalFormState.metaExclude ||
(metaGrant?.value || '') !== originalFormState.metaGrant ||
(metaConnect?.value || '') !== originalFormState.metaConnect ||
(metaRequire?.value || '') !== originalFormState.metaRequire ||
(metaRunAt?.value || '') !== originalFormState.metaRunAt ||
(metaNoframes?.value || '') !== originalFormState.metaNoframes ||
(metaIcon?.value || '') !== originalFormState.metaIcon ||
(metaLicense?.value || '') !== originalFormState.metaLicense ||
(metaDownloadURL?.value || '') !== originalFormState.metaDownloadURL ||
(metaUpdateURL?.value || '') !== originalFormState.metaUpdateURL ||
(metaOther?.value || '') !== originalFormState.metaOther ||
advancedEnabled !== originalFormState.advancedEnabled;
}
// 获取当前活动的内容
function getActiveContent() {
if (activeSourceType === 'file') {
return currentFileContent;
} else if (activeSourceType === 'text') {
return codeEditor?.value || '';
}
return null;
}
// 检查是否有内容可执行
function hasContent() {
return activeSourceType !== null && getActiveContent() && getActiveContent().length > 0;
}
// 更新执行按钮状态
function updateExecuteButtonState() {
if (!executeBtn) return;
if (hasContent()) {
executeBtn.classList.remove('disabled');
executeBtn.disabled = false;
} else {
executeBtn.classList.add('disabled');
executeBtn.disabled = true;
}
}
// 从代码中解析元数据
function parseCodeToForm(code) {
if (!code) return;
let processed = code
.replace(/\r\n/g,'\n')
.replace(/[\uFEFF]/g,'')
.replace(/,/g,',')
.replace(/;/g,';')
.replace(/:/g,':')
.replace(/[""]/g,'"')
.replace(/['']/g,"'");
let headerMatch = processed.match(/\/\/\s*==UserScript==([\s\S]*?)\/\/\s*==\/UserScript==/);
if (headerMatch && headerStatus) {
headerStatus.textContent = '已检测';
headerStatus.className = 'status-badge';
originalHeaderContent = headerMatch[0];
let header = headerMatch[1];
// 清空所有字段
[metaName, metaNamespace, metaVersion, metaDescription, metaAuthor,
metaMatch, metaInclude, metaExclude, metaGrant, metaConnect,
metaRequire, metaRunAt, metaNoframes, metaIcon, metaLicense,
metaDownloadURL, metaUpdateURL, metaOther].forEach(field => {
if (field) field.value = '';
});
const lines = header.split('\n');
const matchLines = [];
const includeLines = [];
const excludeLines = [];
const grantLines = [];
const connectLines = [];
const requireLines = [];
const otherLines = [];
lines.forEach(line => {
line = line.trim();
if (!line) return;
let match = line.match(/^\/\/\s*@(\w+)\s+(.*)$/);
if (match) {
let tag = match[1];
let value = match[2].trim();
switch(tag) {
case 'name':
if (metaName) metaName.value = value;
detectedName = value;
break;
case 'namespace':
if (metaNamespace) metaNamespace.value = value;
break;
case 'version':
if (metaVersion) metaVersion.value = value;
detectedVersion = value;
break;
case 'description':
if (metaDescription) metaDescription.value = value;
break;
case 'author':
if (metaAuthor) metaAuthor.value = value;
break;
case 'match':
if (value) matchLines.push(value);
break;
case 'include':
if (value) includeLines.push(value);
break;
case 'exclude':
if (value) excludeLines.push(value);
break;
case 'grant':
if (value) grantLines.push(value);
break;
case 'connect':
if (value) connectLines.push(value);
break;
case 'require':
if (value) requireLines.push(value);
break;
case 'run-at':
if (metaRunAt) metaRunAt.value = value;
break;
case 'noframes':
if (metaNoframes) metaNoframes.value = '@noframes';
break;
case 'icon':
if (metaIcon) metaIcon.value = value;
break;
case 'license':
if (metaLicense) metaLicense.value = value;
break;
case 'downloadURL':
if (metaDownloadURL) metaDownloadURL.value = value;
break;
case 'updateURL':
if (metaUpdateURL) metaUpdateURL.value = value;
break;
default:
otherLines.push(line);
}
} else {
if (line.startsWith('//') && !line.includes('==UserScript==')) {
otherLines.push(line);
}
}
});
if (metaMatch) metaMatch.value = matchLines.join('\n');
if (metaInclude) metaInclude.value = includeLines.join('\n');
if (metaExclude) metaExclude.value = excludeLines.join('\n');
if (metaGrant) metaGrant.value = grantLines.join('\n');
if (metaConnect) metaConnect.value = connectLines.join('\n');
if (metaRequire) metaRequire.value = requireLines.join('\n');
if (metaOther) metaOther.value = otherLines.join('\n');
} else if (headerStatus) {
headerStatus.textContent = '未检测';
headerStatus.className = 'status-badge na';
originalHeaderContent = '';
if (activeSourceType === 'file') {
detectedName = lastFileName.replace(/\.(txt|js)$/,'');
} else {
detectedName = 'pasted_code';
}
detectedVersion = '1.0';
if (metaName) metaName.value = detectedName;
if (metaVersion) metaVersion.value = '1.0';
if (metaNamespace) metaNamespace.value = 'http://tampermonkey.net/';
}
}
// 从表单生成完整的头部
function generateHeaderFromForm() {
let header = '// ==UserScript==\n';
if (metaName?.value) {
header += `// @name ${metaName.value}\n`;
detectedName = metaName.value;
}
if (metaNamespace?.value) header += `// @namespace ${metaNamespace.value}\n`;
if (metaVersion?.value) {
header += `// @version ${metaVersion.value}\n`;
detectedVersion = metaVersion.value;
}
if (metaDescription?.value) header += `// @description ${metaDescription.value}\n`;
if (metaAuthor?.value) header += `// @author ${metaAuthor.value}\n`;
if (metaMatch?.value) {
metaMatch.value.split('\n').forEach(line => {
if (line.trim()) header += `// @match ${line.trim()}\n`;
});
}
if (metaInclude?.value) {
metaInclude.value.split('\n').forEach(line => {
if (line.trim()) header += `// @include ${line.trim()}\n`;
});
}
if (metaExclude?.value) {
metaExclude.value.split('\n').forEach(line => {
if (line.trim()) header += `// @exclude ${line.trim()}\n`;
});
}
if (metaGrant?.value) {
metaGrant.value.split('\n').forEach(line => {
if (line.trim()) header += `// @grant ${line.trim()}\n`;
});
}
if (metaConnect?.value) {
metaConnect.value.split('\n').forEach(line => {
if (line.trim()) header += `// @connect ${line.trim()}\n`;
});
}
if (metaRequire?.value) {
metaRequire.value.split('\n').forEach(line => {
if (line.trim()) header += `// @require ${line.trim()}\n`;
});
}
if (metaRunAt?.value) {
header += `// @run-at ${metaRunAt.value}\n`;
}
if (metaNoframes?.value) {
header += `// @noframes\n`;
}
if (metaIcon?.value) {
header += `// @icon ${metaIcon.value}\n`;
}
if (metaLicense?.value) {
header += `// @license ${metaLicense.value}\n`;
}
if (metaDownloadURL?.value) {
header += `// @downloadURL ${metaDownloadURL.value}\n`;
}
if (metaUpdateURL?.value) {
header += `// @updateURL ${metaUpdateURL.value}\n`;
}
if (metaOther?.value) {
metaOther.value.split('\n').forEach(line => {
if (line.trim()) header += `// ${line.trim()}\n`;
});
}
let hasMatchRule = (metaMatch?.value && metaMatch.value.trim()) || header.includes('@match');
if (!hasMatchRule) {
header += `// @match *://*/*\n`;
}
header += '// ==/UserScript==\n\n';
return header;
}
// 处理代码
function processCode(content) {
if (!content) return null;
let processed = content
.replace(/\r\n/g,'\n')
.replace(/[\uFEFF]/g,'')
.replace(/,/g,',')
.replace(/;/g,';')
.replace(/:/g,':')
.replace(/[""]/g,'"')
.replace(/['']/g,"'");
const newHeader = generateHeaderFromForm();
let headerMatch = processed.match(/\/\/\s*==UserScript==[\s\S]*?\/\/\s*==\/UserScript==/);
if (headerMatch) {
return processed.replace(headerMatch[0], newHeader.trim());
} else {
return newHeader + processed;
}
}
// 下载文件
function downloadFile(content) {
const safeName = (detectedName || 'script').replace(/[\\/:*?"<>|]/g,'');
const versionNum = detectedVersion || '1.0';
const finalName = `${safeName}_v${versionNum}.js`;
const blob = new Blob([content], { type:'text/javascript;charset=utf-8' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = finalName;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
}
// 清空所有内容
function clearAll() {
if (fileInput) fileInput.value = '';
if (codeEditor) codeEditor.value = '';
currentFileContent = '';
lastFileName = '';
hasFile = false;
hasText = false;
activeSourceType = null;
lastOperationTime = null;
fileType = '';
detectedName = '';
detectedVersion = '';
originalHeaderContent = '';
if (headerStatus) {
headerStatus.textContent = '未检测';
headerStatus.className = 'status-badge na';
}
[metaName, metaNamespace, metaVersion, metaDescription, metaAuthor,
metaMatch, metaInclude, metaExclude, metaGrant, metaConnect,
metaRequire, metaRunAt, metaNoframes, metaIcon, metaLicense,
metaDownloadURL, metaUpdateURL, metaOther].forEach(field => {
if (field) field.value = '';
});
saveOriginalFormState();
updateSourceDisplay();
updateExecuteButtonState();
}
// 执行按钮点击
if (executeBtn) {
executeBtn.onclick = (e) => {
e.stopPropagation();
if (!hasContent()) return;
const content = getActiveContent();
const processedCode = processCode(content);
if (processedCode) {
downloadFile(processedCode);
if (activeSourceType === 'file') {
currentFileContent = processedCode;
} else if (activeSourceType === 'text' && codeEditor) {
codeEditor.value = processedCode;
}
parseCodeToForm(processedCode);
saveOriginalFormState();
}
executeBtn.style.transform = 'scale(0.95)';
setTimeout(() => { executeBtn.style.transform = 'scale(1)'; }, 120);
};
}
// 清空按钮点击
if (clearBtn) {
clearBtn.onclick = (e) => {
e.stopPropagation();
clearAll();
clearBtn.style.transform = 'scale(0.95)';
setTimeout(() => { clearBtn.style.transform = 'scale(1)'; }, 120);
};
}
// 清空文本按钮点击
if (clearTextBtn) {
clearTextBtn.onclick = (e) => {
e.stopPropagation();
if (codeEditor) codeEditor.value = '';
hasText = false;
if (activeSourceType === 'text') {
activeSourceType = hasFile ? 'file' : null;
if (activeSourceType === 'file') {
parseCodeToForm(currentFileContent);
lastOperationTime = new Date().toLocaleTimeString();
} else {
clearAll();
return;
}
}
updateSourceDisplay();
saveOriginalFormState();
updateExecuteButtonState();
};
}
// 文本编辑框输入
if (codeEditor) {
codeEditor.addEventListener('input', (e) => {
const value = e.target.value;
hasText = value.length > 0;
if (hasText) {
activeSourceType = 'text';
lastOperationTime = new Date().toLocaleTimeString();
parseCodeToForm(value);
} else {
hasText = false;
if (hasFile) {
activeSourceType = 'file';
parseCodeToForm(currentFileContent);
lastOperationTime = new Date().toLocaleTimeString();
} else {
activeSourceType = null;
lastOperationTime = null;
clearAll();
return;
}
}
updateSourceDisplay();
saveOriginalFormState();
updateExecuteButtonState();
});
}
// 面板折叠
if (toggleBtn && panel && panelContent) {
toggleBtn.onclick = (e) => {
e.stopPropagation();
if (isPanelMinimized) {
panel.classList.remove('minimized');
panelContent.style.display = 'block';
toggleBtn.textContent = '−';
isPanelMinimized = false;
panel.style.left = '';
panel.style.top = '';
panel.style.right = '20px';
panel.style.bottom = 'auto';
} else {
panel.classList.add('minimized');
panelContent.style.display = 'none';
toggleBtn.textContent = '+';
isPanelMinimized = true;
}
savePanelState();
};
panel.onclick = (e) => {
if (isPanelMinimized && e.target === panel) {
panel.classList.remove('minimized');
panelContent.style.display = 'block';
toggleBtn.textContent = '−';
isPanelMinimized = false;
savePanelState();
}
};
}
// 文件选择
if (fileInput) {
fileInput.onchange = e => {
const file = e.target.files[0];
if (!file) return;
lastFileName = file.name;
fileType = file.name.split('.').pop().toLowerCase();
const reader = new FileReader();
reader.onload = evt => {
currentFileContent = evt.target.result;
hasFile = true;
activeSourceType = 'file';
lastOperationTime = new Date().toLocaleTimeString();
if (codeEditor) codeEditor.value = '';
hasText = false;
parseCodeToForm(currentFileContent);
saveOriginalFormState();
if (fileInput) fileInput.title = detectedName;
updateSourceDisplay();
updateExecuteButtonState();
};
reader.readAsText(file, 'utf-8');
};
}
// 初始化状态
loadPanelState();
updateExecuteButtonState();
updateSourceDisplay();
if (infoContent) infoContent.style.display = 'none';
if (infoToggle) infoToggle.textContent = '展开';
if (editContent) editContent.style.display = 'none';
if (editToggle) editToggle.textContent = '展开';
if (advancedContent) advancedContent.style.display = 'none';
saveOriginalFormState();
})();