diff --git a/README.md b/README.md index a714014..0bf2968 100644 --- a/README.md +++ b/README.md @@ -43,17 +43,17 @@ Desktop downloader for **Real-Debrid, Mega-Debrid, BestDebrid, and AllDebrid** w - Minimize-to-tray with tray menu controls. - Speed limits globally or per download. - Bandwidth schedules for time-based speed profiles. -- Built-in update checks via GitHub Releases. +- Built-in update checks via Codeberg Releases. ## Installation ### Option A: prebuilt releases (recommended) -1. Download a release from the GitHub Releases page. +1. Download a release from the Codeberg Releases page. 2. Run the installer or portable build. 3. Add your debrid tokens in Settings. -Releases: `https://github.com/Sucukdeluxe/real-debrid-downloader/releases` +Releases: `https://codeberg.org/Sucukdeluxe/real-debrid-downloader/releases` ### Option B: build from source @@ -113,7 +113,7 @@ The app stores runtime files in Electron's `userData` directory, including: ## Changelog -Release history is available in `CHANGELOG.md` and on GitHub Releases. +Release history is available in `CHANGELOG.md` and on Codeberg Releases. ## License diff --git a/src/main/main.ts b/src/main/main.ts index 915e2ee..0599275 100644 --- a/src/main/main.ts +++ b/src/main/main.ts @@ -78,7 +78,7 @@ function createWindow(): BrowserWindow { responseHeaders: { ...details.responseHeaders, "Content-Security-Policy": [ - "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; connect-src 'self' https://api.real-debrid.com https://api.github.com https://bestdebrid.com https://api.alldebrid.com https://www.mega-debrid.eu" + "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; connect-src 'self' https://api.real-debrid.com https://codeberg.org https://bestdebrid.com https://api.alldebrid.com https://www.mega-debrid.eu" ] } }); diff --git a/src/main/update.ts b/src/main/update.ts index fef59c1..89843eb 100644 --- a/src/main/update.ts +++ b/src/main/update.ts @@ -17,6 +17,8 @@ const DOWNLOAD_BODY_IDLE_TIMEOUT_MS = 45000; const RETRIES_PER_CANDIDATE = 3; const RETRY_DELAY_MS = 1500; const UPDATE_USER_AGENT = `RD-Node-Downloader/${APP_VERSION}`; +const UPDATE_WEB_BASE = "https://codeberg.org"; +const UPDATE_API_BASE = "https://codeberg.org/api/v1"; let activeUpdateAbortController: AbortController | null = null; @@ -45,9 +47,9 @@ export function normalizeUpdateRepo(repo: string): string { const normalizeParts = (input: string): string => { const cleaned = input - .replace(/^https?:\/\/(?:www\.)?github\.com\//i, "") - .replace(/^(?:www\.)?github\.com\//i, "") - .replace(/^git@github\.com:/i, "") + .replace(/^https?:\/\/(?:www\.)?(?:codeberg\.org|github\.com)\//i, "") + .replace(/^(?:www\.)?(?:codeberg\.org|github\.com)\//i, "") + .replace(/^git@(?:codeberg\.org|github\.com):/i, "") .replace(/\.git$/i, "") .replace(/^\/+|\/+$/g, ""); const parts = cleaned.split("/").filter(Boolean); @@ -64,7 +66,7 @@ export function normalizeUpdateRepo(repo: string): string { try { const url = new URL(raw); const host = url.hostname.toLowerCase(); - if (host === "github.com" || host === "www.github.com") { + if (host === "codeberg.org" || host === "www.codeberg.org" || host === "github.com" || host === "www.github.com") { const normalized = normalizeParts(url.pathname); if (normalized) { return normalized; @@ -158,7 +160,7 @@ function createFallbackResult(repo: string): UpdateCheckResult { currentVersion: APP_VERSION, latestVersion: APP_VERSION, latestTag: `v${APP_VERSION}`, - releaseUrl: `https://github.com/${safeRepo}/releases/latest` + releaseUrl: `${UPDATE_WEB_BASE}/${safeRepo}/releases/latest` }; } @@ -210,7 +212,7 @@ async function fetchReleasePayload(safeRepo: string, endpoint: string): Promise< const timeout = timeoutController(RELEASE_FETCH_TIMEOUT_MS); let response: Response; try { - response = await fetch(`https://api.github.com/repos/${safeRepo}/${endpoint}`, { + response = await fetch(`${UPDATE_API_BASE}/repos/${safeRepo}/${endpoint}`, { headers: { Accept: "application/vnd.github+json", "User-Agent": UPDATE_USER_AGENT @@ -250,9 +252,9 @@ function buildDownloadCandidates(safeRepo: string, check: UpdateCheckResult): st const candidates = [setupAssetUrl]; if (setupAssetName) { const encodedName = encodeURIComponent(setupAssetName); - candidates.push(`https://github.com/${safeRepo}/releases/latest/download/${encodedName}`); + candidates.push(`${UPDATE_WEB_BASE}/${safeRepo}/releases/latest/download/${encodedName}`); if (latestTag) { - candidates.push(`https://github.com/${safeRepo}/releases/download/${encodeURIComponent(latestTag)}/${encodedName}`); + candidates.push(`${UPDATE_WEB_BASE}/${safeRepo}/releases/download/${encodeURIComponent(latestTag)}/${encodedName}`); } } diff --git a/src/renderer/App.tsx b/src/renderer/App.tsx index 75cb69c..6cce6d6 100644 --- a/src/renderer/App.tsx +++ b/src/renderer/App.tsx @@ -1527,7 +1527,7 @@ export function App(): ReactElement { - + setText("updateRepo", e.target.value)} /> diff --git a/tests/update.test.ts b/tests/update.test.ts index 2b9ed3c..4cfcd92 100644 --- a/tests/update.test.ts +++ b/tests/update.test.ts @@ -20,21 +20,21 @@ describe("update", () => { it("normalizes update repo input", () => { expect(normalizeUpdateRepo("")).toBe("Sucukdeluxe/real-debrid-downloader"); expect(normalizeUpdateRepo("owner/repo")).toBe("owner/repo"); - expect(normalizeUpdateRepo("https://github.com/owner/repo")).toBe("owner/repo"); - expect(normalizeUpdateRepo("https://www.github.com/owner/repo")).toBe("owner/repo"); - expect(normalizeUpdateRepo("https://github.com/owner/repo/releases/tag/v1.2.3")).toBe("owner/repo"); - expect(normalizeUpdateRepo("github.com/owner/repo.git")).toBe("owner/repo"); - expect(normalizeUpdateRepo("git@github.com:owner/repo.git")).toBe("owner/repo"); + expect(normalizeUpdateRepo("https://codeberg.org/owner/repo")).toBe("owner/repo"); + expect(normalizeUpdateRepo("https://www.codeberg.org/owner/repo")).toBe("owner/repo"); + expect(normalizeUpdateRepo("https://codeberg.org/owner/repo/releases/tag/v1.2.3")).toBe("owner/repo"); + expect(normalizeUpdateRepo("codeberg.org/owner/repo.git")).toBe("owner/repo"); + expect(normalizeUpdateRepo("git@codeberg.org:owner/repo.git")).toBe("owner/repo"); }); - it("uses normalized repo slug for GitHub API requests", async () => { + it("uses normalized repo slug for Codeberg API requests", async () => { let requestedUrl = ""; globalThis.fetch = (async (input: RequestInfo | URL): Promise => { requestedUrl = typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url; return new Response( JSON.stringify({ tag_name: `v${APP_VERSION}`, - html_url: "https://github.com/owner/repo/releases/tag/v1.0.0", + html_url: "https://codeberg.org/owner/repo/releases/tag/v1.0.0", assets: [] }), { @@ -44,8 +44,8 @@ describe("update", () => { ); }) as typeof fetch; - const result = await checkGitHubUpdate("https://github.com/owner/repo/releases"); - expect(requestedUrl).toBe("https://api.github.com/repos/owner/repo/releases/latest"); + const result = await checkGitHubUpdate("https://codeberg.org/owner/repo/releases"); + expect(requestedUrl).toBe("https://codeberg.org/api/v1/repos/owner/repo/releases/latest"); expect(result.currentVersion).toBe(APP_VERSION); expect(result.latestVersion).toBe(APP_VERSION); expect(result.updateAvailable).toBe(false); @@ -55,7 +55,7 @@ describe("update", () => { globalThis.fetch = (async (): Promise => new Response( JSON.stringify({ tag_name: "v9.9.9", - html_url: "https://github.com/owner/repo/releases/tag/v9.9.9", + html_url: "https://codeberg.org/owner/repo/releases/tag/v9.9.9", assets: [ { name: "Real-Debrid-Downloader 9.9.9.exe", @@ -105,7 +105,7 @@ describe("update", () => { currentVersion: APP_VERSION, latestVersion: "9.9.9", latestTag: "v9.9.9", - releaseUrl: "https://github.com/owner/repo/releases/tag/v9.9.9", + releaseUrl: "https://codeberg.org/owner/repo/releases/tag/v9.9.9", setupAssetUrl: "https://example.invalid/stale-setup.exe", setupAssetName: "Real-Debrid-Downloader Setup 9.9.9.exe", setupAssetDigest: `sha256:${executableDigest}` @@ -176,7 +176,7 @@ describe("update", () => { currentVersion: APP_VERSION, latestVersion: "9.9.9", latestTag: "v9.9.9", - releaseUrl: "https://github.com/owner/repo/releases/tag/v9.9.9", + releaseUrl: "https://codeberg.org/owner/repo/releases/tag/v9.9.9", setupAssetUrl: "", setupAssetName: "" }; @@ -240,7 +240,7 @@ describe("update", () => { currentVersion: APP_VERSION, latestVersion: "9.9.9", latestTag: "v9.9.9", - releaseUrl: "https://github.com/owner/repo/releases/tag/v9.9.9", + releaseUrl: "https://codeberg.org/owner/repo/releases/tag/v9.9.9", setupAssetUrl: "https://example.invalid/hang-setup.exe", setupAssetName: "", setupAssetDigest: "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" @@ -276,7 +276,7 @@ describe("update", () => { currentVersion: APP_VERSION, latestVersion: "9.9.9", latestTag: "v9.9.9", - releaseUrl: "https://github.com/owner/repo/releases/tag/v9.9.9", + releaseUrl: "https://codeberg.org/owner/repo/releases/tag/v9.9.9", setupAssetUrl: "https://example.invalid/mismatch-setup.exe", setupAssetName: "setup.exe", setupAssetDigest: "sha256:1111111111111111111111111111111111111111111111111111111111111111" @@ -292,11 +292,11 @@ describe("normalizeUpdateRepo extended", () => { it("handles trailing slashes and extra path segments", () => { expect(normalizeUpdateRepo("owner/repo/")).toBe("owner/repo"); expect(normalizeUpdateRepo("/owner/repo/")).toBe("owner/repo"); - expect(normalizeUpdateRepo("https://github.com/owner/repo/tree/main/src")).toBe("owner/repo"); + expect(normalizeUpdateRepo("https://codeberg.org/owner/repo/tree/main/src")).toBe("owner/repo"); }); it("handles ssh-style git URLs", () => { - expect(normalizeUpdateRepo("git@github.com:user/project.git")).toBe("user/project"); + expect(normalizeUpdateRepo("git@codeberg.org:user/project.git")).toBe("user/project"); }); it("returns default for malformed inputs", () => { @@ -307,12 +307,12 @@ describe("normalizeUpdateRepo extended", () => { it("rejects traversal-like owner or repo segments", () => { expect(normalizeUpdateRepo("../owner/repo")).toBe("Sucukdeluxe/real-debrid-downloader"); expect(normalizeUpdateRepo("owner/../repo")).toBe("Sucukdeluxe/real-debrid-downloader"); - expect(normalizeUpdateRepo("https://github.com/owner/../../repo")).toBe("Sucukdeluxe/real-debrid-downloader"); + expect(normalizeUpdateRepo("https://codeberg.org/owner/../../repo")).toBe("Sucukdeluxe/real-debrid-downloader"); }); it("handles www prefix", () => { - expect(normalizeUpdateRepo("https://www.github.com/owner/repo")).toBe("owner/repo"); - expect(normalizeUpdateRepo("www.github.com/owner/repo")).toBe("owner/repo"); + expect(normalizeUpdateRepo("https://www.codeberg.org/owner/repo")).toBe("owner/repo"); + expect(normalizeUpdateRepo("www.codeberg.org/owner/repo")).toBe("owner/repo"); }); });