Nginx Autoindex 智能美化 + 搜索

自动识别 Nginx autoindex 页面并格式化显示完整文件名与下载链接,支持模糊搜索

// ==UserScript==
// @name         Nginx Autoindex 智能美化 + 搜索
// @namespace    http://tampermonkey.net/
// @version      1.4
// @description  自动识别 Nginx autoindex 页面并格式化显示完整文件名与下载链接,支持模糊搜索
// @author       凡雲
// @match        http://*/*
// @match        https://*/*
// @license      MIT
// @grant        none
// ==/UserScript==

(function () {
  'use strict';

  const h1 = document.querySelector('body > h1');
  const pre = document.querySelector('pre');
  if (!h1 || !pre) return;

  const isNginxIndexPage = h1.textContent.trim().startsWith('Index of /');
  if (!isNginxIndexPage) return;

  const links = [...pre.querySelectorAll('a')];
  const files = links.filter(a => !a.getAttribute('href').endsWith('/../'));

  // 添加样式
  const style = document.createElement('style');
  style.textContent = `
    table tbody tr:hover {
      background-color: #f0f8ff;
    }
    .icon-cell {
      width: 40px;
      text-align: center;
      font-size: 18px;
    }
    .filename-cell {
      font-family: monospace;
    }
    .download-button {
      padding: 4px 10px;
      font-size: 14px;
      border: 1px solid #3a87ad;
      border-radius: 4px;
      background-color: #3a87ad;
      color: white;
      cursor: pointer;
    }
    .download-button:hover {
      background-color: #2d6a8c;
    }
  `;
  document.head.appendChild(style);

  // 搜索框
  const searchBox = document.createElement('input');
  searchBox.type = 'text';
  searchBox.placeholder = '搜索文件名...';
  searchBox.style.width = '300px';
  searchBox.style.margin = '10px 0';
  searchBox.style.padding = '6px';
  searchBox.style.fontSize = '16px';
  searchBox.style.border = '1px solid #ccc';
  searchBox.style.borderRadius = '6px';
  document.body.insertBefore(searchBox, pre);

  // 表格
  const table = document.createElement('table');
  table.style.width = '100%';
  table.style.borderCollapse = 'collapse';
  table.innerHTML = `
    <thead>
      <tr>
        <th class="icon-cell"></th>
        <th style="text-align: left; padding: 8px; border-bottom: 2px solid #ccc;">文件名</th>
        <th style="text-align: left; padding: 8px; border-bottom: 2px solid #ccc;">操作</th>
      </tr>
    </thead>
    <tbody></tbody>
  `;

  const tbody = table.querySelector('tbody');

  function renderTable(filter = '') {
    tbody.innerHTML = '';
    files.forEach(link => {
      const href = link.getAttribute('href');
      const fullName = decodeURIComponent(href);
      if (!fullName.toLowerCase().includes(filter.toLowerCase())) return;

      const isDirectory = href.endsWith('/');
      const row = document.createElement('tr');
      row.style.cursor = isDirectory ? 'pointer' : 'default';

      const icon = isDirectory ? '📁' : '📄';

      row.innerHTML = `
        <td class="icon-cell">${icon}</td>
        <td class="filename-cell" style="padding: 6px; border-bottom: 1px solid #eee;">${fullName}</td>
        <td style="padding: 6px; border-bottom: 1px solid #eee;">
          ${isDirectory ? '' : `<button class="download-button">下载</button>`}
        </td>
      `;

      if (isDirectory) {
        row.addEventListener('click', () => {
          window.location.href = href;
        });
      } else {
        const btn = row.querySelector('.download-button');
        btn?.addEventListener('click', e => {
          e.stopPropagation(); // 防止触发行点击
          const a = document.createElement('a');
          a.href = href;
          a.download = '';
          document.body.appendChild(a);
          a.click();
          document.body.removeChild(a);
        });
      }

      tbody.appendChild(row);
    });
  }

  searchBox.addEventListener('input', () => {
    renderTable(searchBox.value);
  });

  pre.replaceWith(table);
  renderTable();
  document.title = '文件下载列表';
})();