GitHub → DeepWiki Button

Adds an "Open in DeepWiki" button on GitHub repo pages, search results, topics, and explore pages

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         GitHub → DeepWiki Button
// @namespace    http://tampermonkey.net/
// @version      1.2
// @description  Adds an "Open in DeepWiki" button on GitHub repo pages, search results, topics, and explore pages
// @author       Michael Farah
// @license      MIT
// @match        https://github.com/*
// @grant        none
// ==/UserScript==


(function () {
  "use strict";

  const BUTTON_STYLE = `
    display: inline-flex;
    align-items: center;
    gap: 4px;
    padding: 3px 10px;
    font-size: 12px;
    font-weight: 500;
    color: #fff;
    background-color: #1a6ae4;
    border: none;
    border-radius: 6px;
    cursor: pointer;
    text-decoration: none;
    white-space: nowrap;
    vertical-align: middle;
    margin-left: 6px;
    line-height: 20px;
  `;

  const RESERVED_OWNERS = new Set([
    "topics",
    "search",
    "settings",
    "orgs",
    "users",
    "explore",
    "trending",
    "marketplace",
    "sponsors",
    "notifications",
    "about",
    "pricing",
    "enterprise",
    "blog",
    "readme",
    "security",
    "contact",
    "features",
    "join",
    "login",
    "pulls",
    "issues",
    "stars",
  ]);

  function getRepoPath(urlPath) {
    const match = urlPath.match(/^\/([^/]+)\/([^/]+)\/?$/);
    if (!match) return null;
    const owner = match[1];
    const repo = match[2];
    if (RESERVED_OWNERS.has(owner.toLowerCase())) return null;
    return `${owner}/${repo}`;
  }

  function makeButton(repoPath) {
    const url = `https://deepwiki.com/${repoPath}`;
    const btn = document.createElement("a");
    btn.href = url;
    btn.target = "_blank";
    btn.rel = "noopener noreferrer";
    btn.setAttribute("style", BUTTON_STYLE);
    btn.className = "deepwiki-btn-injected";
    btn.innerHTML = `
      <svg width="12" height="12" viewBox="0 0 16 16" fill="currentColor">
        <path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38
          0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13
          -.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66
          .07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15
          -.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82a7.65 7.65 0 0 1 2-.27
          c.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12
          .51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48
          0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0 0 16 8c0-4.42-3.58-8-8-8z"/>
      </svg>
      DeepWiki
    `;
    btn.title = `Open on DeepWiki: ${url}`;
    return btn;
  }

  // ── Repo page ──────────────────────────────────────────────────────────────
  function addButtonToRepoPage() {
    const repoPath = getRepoPath(location.pathname);
    if (!repoPath) return;
    if (document.getElementById("deepwiki-repo-btn")) return;

    const heading = document.querySelector('[itemprop="name"] a, h1.d-flex a');
    if (heading) {
      const btn = makeButton(repoPath);
      btn.id = "deepwiki-repo-btn";
      heading.parentElement.appendChild(btn);
    }
  }

  // ── Shared helper: inject buttons next to a set of anchor elements ─────────
  function injectButtonsForLinks(links) {
    links.forEach((link) => {
      const href = link.getAttribute("href") || "";
      const repoPath = getRepoPath(href);
      if (!repoPath) return;
      if (link.parentElement.querySelector(".deepwiki-btn-injected")) return;
      link.parentElement.appendChild(makeButton(repoPath));
    });
  }

  // ── Search results page ────────────────────────────────────────────────────
  function addButtonsToSearchResults() {
    const links = document.querySelectorAll(
      'div[data-testid="results-list"] a[href^="/"], ' +
        "li.repo-list-item a.v-align-middle, " +
        "div.search-title a"
    );
    injectButtonsForLinks(links);
  }

  // ── Topics page  (github.com/topics/<name>) ────────────────────────────────
  function addButtonsToTopicsPage() {
    // Each repo card is an <article>; find all of them
    document.querySelectorAll("article.border").forEach((card) => {
      // Already injected
      if (card.querySelector(".deepwiki-btn-injected")) return;

      const links = card.querySelectorAll('h3 a[href^="/"]');
      if (links.length < 2) return;

      // The second link contains the full repository path (e.g., "/rust-lang/rust")
      const repoPath = getRepoPath(links[1].getAttribute("href"));

      // If the path is invalid or belongs to a reserved owner, skip it
      if (!repoPath) return;

      const btn = makeButton(repoPath);

      // Append button after the h3 heading, inside the card
      const heading = card.querySelector("h3");
      if (heading) heading.appendChild(btn);
    });
  }

  // ── Explore page  (github.com/explore) ────────────────────────────────────
  function addButtonsToExplorePage() {
    const links = document.querySelectorAll(
      'article a[href^="/"][data-ga-click], ' +
        'div.explore-content a.text-bold[href^="/"]'
    );
    injectButtonsForLinks(links);
  }

  // ── Router ─────────────────────────────────────────────────────────────────
  function run() {
    const path = location.pathname;
    const search = location.search;

    if (path.startsWith("/search") || search.includes("type=repositories")) {
      addButtonsToSearchResults();
    } else if (path.startsWith("/topics/") || path === "/topics") {
      // /topics/<name> — list of repos tagged with that topic
      // Note: /topics itself is just a browse page with no repo cards, but
      // running the injector is harmless; it simply won't find matching links.
      addButtonsToTopicsPage();
    } else if (path.startsWith("/explore")) {
      addButtonsToExplorePage();
    } else {
      addButtonToRepoPage();
    }
  }

  run();

  // Debounced observer — avoids hammering run() on every tiny DOM mutation
  let debounceTimer;
  const observer = new MutationObserver(() => {
    clearTimeout(debounceTimer);
    debounceTimer = setTimeout(run, 300);
  });
  observer.observe(document.body, { childList: true, subtree: true });
})();