Adds an "Open in DeepWiki" button on GitHub repo search results and repo pages
当前为
// ==UserScript==
// @name GitHub → DeepWiki Button
// @namespace http://tampermonkey.net/
// @version 1.0
// @description Adds an "Open in DeepWiki" button on GitHub repo search results and repo 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;
`;
// Global GitHub routes that should not be mistaken for "owner" names
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'
]);
// Safely extracts "owner/repo" from a path, ignoring system URLs
function getRepoPath(urlPath) {
const match = urlPath.match(/^\/([^/]+)\/([^/]+)\/?$/);
if (!match) return null;
const owner = match[1];
const repo = match[2];
// If the "owner" part is actually 'topics', 'settings', etc., bail out
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'; // Used to prevent duplicate injection
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;
}
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);
}
}
function addButtonsToSearchResults() {
const repoLinks = document.querySelectorAll(
'div[data-testid="results-list"] a[href^="/"], ' +
'li.repo-list-item a.v-align-middle, ' +
'div.search-title a'
);
repoLinks.forEach((link) => {
const href = link.getAttribute('href') || '';
const repoPath = getRepoPath(href);
// If the link is invalid or is a topic tag, getRepoPath returns null and we skip it
if (!repoPath) return;
if (link.parentElement.querySelector('.deepwiki-btn-injected')) return;
const btn = makeButton(repoPath);
link.parentElement.appendChild(btn);
});
}
function run() {
// If we're on a search page, scan the list. Otherwise try the repo page logic.
if (location.pathname.startsWith('/search') || location.search.includes('type=repositories')) {
addButtonsToSearchResults();
} else {
addButtonToRepoPage();
}
}
run();
const observer = new MutationObserver(() => run());
observer.observe(document.body, { childList: true, subtree: true });
})();