Files
xget-now/content.js
T
2025-07-09 17:58:55 +08:00

367 lines
9.3 KiB
JavaScript

// Content script to inject download interception on supported platforms
// Platform detection and URL transformation
const PLATFORMS = {
gh: {
base: "https://github.com",
name: "GitHub",
pattern: /^https:\/\/github\.com\//,
},
gl: {
base: "https://gitlab.com",
name: "GitLab",
pattern: /^https:\/\/gitlab\.com\//,
},
hf: {
base: "https://huggingface.co",
name: "Hugging Face",
pattern: /^https:\/\/huggingface\.co\//,
},
};
// Initialize content script
(async function () {
console.log("Xget for Chrome: Content script loaded");
// Listen for messages from background script
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
if (request.action === "showNotification") {
showNotification(request.message, request.showRefreshButton);
sendResponse({ success: true });
}
});
// Check if extension is enabled and configured
const settings = await getSettings();
if (!settings.enabled || !settings.xgetDomain) {
return;
}
// Find current platform
const currentPlatform = detectPlatform(window.location.href);
if (!currentPlatform || !settings.enabledPlatforms[currentPlatform]) {
return;
}
// Add download interception
interceptDownloadLinks();
// Monitor for dynamically added content
observePageChanges();
})();
async function getSettings() {
try {
return await new Promise((resolve) => {
chrome.runtime.sendMessage({ action: "getSettings" }, resolve);
});
} catch (error) {
console.error("Error getting settings:", error);
return { enabled: false };
}
}
function detectPlatform(url) {
for (const [key, platform] of Object.entries(PLATFORMS)) {
if (platform.pattern.test(url)) {
return key;
}
}
return null;
}
function interceptDownloadLinks() {
// Intercept clicks on download links
document.addEventListener(
"click",
async (event) => {
const link = event.target.closest("a");
if (!link || !link.href) return;
// Check if this is a download link
if (isDownloadLink(link)) {
event.preventDefault();
event.stopPropagation();
await handleDownloadLink(link.href);
}
},
true
);
}
function isDownloadLink(link) {
const href = link.href.toLowerCase();
const url = new URL(link.href);
const pathname = url.pathname.toLowerCase();
// First check: explicit download attributes
if (link.download || link.hasAttribute("download")) {
return true;
}
// Second check: file extensions that indicate downloadable files
const fileExtensions = [
".zip",
".tar.gz",
".tar.bz2",
".tar.xz",
".7z",
".rar",
".gz",
".bz2",
".exe",
".msi",
".dmg",
".pkg",
".deb",
".rpm",
".apk",
".jar",
".war",
".ear",
".iso",
".img",
".pdf",
".doc",
".docx",
".xls",
".xlsx",
".ppt",
".pptx",
".mp4",
".avi",
".mkv",
".mov",
".wmv",
".flv",
".mp3",
".wav",
".flac",
".ogg",
".jpg",
".jpeg",
".png",
".gif",
".bmp",
".tiff",
".svg",
".whl",
".egg",
".gem",
".nupkg",
];
// Check if URL ends with a file extension
if (fileExtensions.some((ext) => pathname.endsWith(ext))) {
return true;
}
// Third check: GitHub-specific patterns
if (href.includes("github.com")) {
// GitHub release asset download URLs follow pattern: /releases/download/
if (pathname.includes("/releases/download/")) {
return true;
}
// GitHub archive download URLs
if (
pathname.includes("/archive/") &&
(pathname.endsWith(".zip") || pathname.endsWith(".tar.gz"))
) {
return true;
}
// GitHub raw file URLs - NEW: support for raw file links
if (pathname.includes("/raw/")) {
return true;
}
// Exclude navigation to releases page (just /releases or /releases/)
if (pathname.endsWith("/releases") || pathname.endsWith("/releases/")) {
return false;
}
}
// Fourth check: GitLab-specific patterns
if (href.includes("gitlab.com")) {
// GitLab archive downloads
if (pathname.includes("/-/archive/")) {
return true;
}
// GitLab release downloads
if (pathname.includes("/-/releases/") && pathname.includes("/downloads/")) {
return true;
}
}
// Fifth check: Hugging Face file downloads
if (href.includes("huggingface.co")) {
// HF file download URLs contain /resolve/
if (pathname.includes("/resolve/")) {
return true;
}
}
// Seventh check: explicit download text indicators (be more specific)
const downloadTextIndicators = ["download", "download file", "get file"];
const linkText = link.textContent.toLowerCase().trim();
if (
downloadTextIndicators.some(
(indicator) =>
linkText === indicator || linkText.startsWith(indicator + " ")
)
) {
return true;
}
// Default: not a download link
return false;
}
async function handleDownloadLink(url) {
try {
const settings = await getSettings();
if (!settings.enabled || !settings.xgetDomain) return;
const transformedUrl = transformUrl(url, settings);
if (transformedUrl) {
// Show notification
showNotification(`Download redirected through Xget`);
// Trigger download
window.location.href = transformedUrl;
}
} catch (error) {
console.error("Error handling download:", error);
}
}
function transformUrl(url, settings) {
try {
const platform = detectPlatform(url);
if (!platform || !settings.enabledPlatforms[platform]) {
return null;
}
const urlObj = new URL(url);
const path = urlObj.pathname + urlObj.search + urlObj.hash;
return `https://${settings.xgetDomain}/${platform}${path}`;
} catch (error) {
console.error("Error transforming URL:", error);
return null;
}
}
function observePageChanges() {
const observer = new MutationObserver(() => {
// Monitor for page changes (no additional actions needed)
});
observer.observe(document.body, {
childList: true,
subtree: true,
});
}
function showNotification(message, showRefreshButton = false) {
try {
// Remove any existing notifications first
const existingNotifications =
document.querySelectorAll(".xget-notification");
existingNotifications.forEach((notification) => notification.remove());
// Create a simple notification
const notification = document.createElement("div");
notification.className = "xget-notification";
// Create message container
const messageDiv = document.createElement("div");
messageDiv.textContent = message;
messageDiv.style.marginBottom = showRefreshButton ? "8px" : "0";
notification.appendChild(messageDiv);
// Add refresh button if needed
if (showRefreshButton) {
const refreshButton = document.createElement("button");
refreshButton.textContent = "🔄 Refresh Page";
refreshButton.style.cssText = `
background: rgba(255, 255, 255, 0.2);
border: 1px solid rgba(255, 255, 255, 0.3);
color: white;
padding: 4px 8px;
border-radius: 4px;
font-size: 12px;
cursor: pointer;
margin-top: 4px;
width: 100%;
transition: background 0.2s;
`;
refreshButton.addEventListener("mouseenter", () => {
refreshButton.style.background = "rgba(255, 255, 255, 0.3)";
});
refreshButton.addEventListener("mouseleave", () => {
refreshButton.style.background = "rgba(255, 255, 255, 0.2)";
});
refreshButton.addEventListener("click", () => {
window.location.reload();
});
notification.appendChild(refreshButton);
}
notification.style.cssText = `
position: fixed;
top: 20px;
right: 20px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 12px 16px;
border-radius: 8px;
font-size: 14px;
font-weight: 500;
box-shadow: 0 4px 12px rgba(0,0,0,0.2);
z-index: 2147483647;
animation: xgetSlideIn 0.3s ease-out;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
max-width: 280px;
word-wrap: break-word;
`;
// Add animation styles if not already present
if (!document.getElementById("xget-notification-styles")) {
const style = document.createElement("style");
style.id = "xget-notification-styles";
style.textContent = `
@keyframes xgetSlideIn {
from { transform: translateX(100%); opacity: 0; }
to { transform: translateX(0); opacity: 1; }
}
@keyframes xgetSlideOut {
from { transform: translateX(0); opacity: 1; }
to { transform: translateX(100%); opacity: 0; }
}
`;
document.head.appendChild(style);
}
document.body.appendChild(notification);
// Remove after longer time if has refresh button, shorter if not
const removeDelay = showRefreshButton ? 8000 : 4000;
setTimeout(() => {
notification.style.animation = "xgetSlideOut 0.3s ease-in";
setTimeout(() => {
if (notification.parentNode) {
notification.remove();
}
}, 300);
}, removeDelay);
} catch (error) {
console.error("Error showing notification:", error);
// Fallback to console log if DOM manipulation fails
console.log("Xget notification:", message);
}
}