Switch updater and docs from GitHub to Codeberg

This commit is contained in:
Sucukdeluxe 2026-03-01 02:40:11 +01:00
parent fe59e064f0
commit 43bc95b7fc
5 changed files with 35 additions and 33 deletions

View File

@ -43,17 +43,17 @@ Desktop downloader for **Real-Debrid, Mega-Debrid, BestDebrid, and AllDebrid** w
- Minimize-to-tray with tray menu controls. - Minimize-to-tray with tray menu controls.
- Speed limits globally or per download. - Speed limits globally or per download.
- Bandwidth schedules for time-based speed profiles. - Bandwidth schedules for time-based speed profiles.
- Built-in update checks via GitHub Releases. - Built-in update checks via Codeberg Releases.
## Installation ## Installation
### Option A: prebuilt releases (recommended) ### 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. 2. Run the installer or portable build.
3. Add your debrid tokens in Settings. 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 ### Option B: build from source
@ -113,7 +113,7 @@ The app stores runtime files in Electron's `userData` directory, including:
## Changelog ## Changelog
Release history is available in `CHANGELOG.md` and on GitHub Releases. Release history is available in `CHANGELOG.md` and on Codeberg Releases.
## License ## License

View File

@ -78,7 +78,7 @@ function createWindow(): BrowserWindow {
responseHeaders: { responseHeaders: {
...details.responseHeaders, ...details.responseHeaders,
"Content-Security-Policy": [ "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"
] ]
} }
}); });

View File

@ -17,6 +17,8 @@ const DOWNLOAD_BODY_IDLE_TIMEOUT_MS = 45000;
const RETRIES_PER_CANDIDATE = 3; const RETRIES_PER_CANDIDATE = 3;
const RETRY_DELAY_MS = 1500; const RETRY_DELAY_MS = 1500;
const UPDATE_USER_AGENT = `RD-Node-Downloader/${APP_VERSION}`; 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; let activeUpdateAbortController: AbortController | null = null;
@ -45,9 +47,9 @@ export function normalizeUpdateRepo(repo: string): string {
const normalizeParts = (input: string): string => { const normalizeParts = (input: string): string => {
const cleaned = input const cleaned = input
.replace(/^https?:\/\/(?:www\.)?github\.com\//i, "") .replace(/^https?:\/\/(?:www\.)?(?:codeberg\.org|github\.com)\//i, "")
.replace(/^(?:www\.)?github\.com\//i, "") .replace(/^(?:www\.)?(?:codeberg\.org|github\.com)\//i, "")
.replace(/^git@github\.com:/i, "") .replace(/^git@(?:codeberg\.org|github\.com):/i, "")
.replace(/\.git$/i, "") .replace(/\.git$/i, "")
.replace(/^\/+|\/+$/g, ""); .replace(/^\/+|\/+$/g, "");
const parts = cleaned.split("/").filter(Boolean); const parts = cleaned.split("/").filter(Boolean);
@ -64,7 +66,7 @@ export function normalizeUpdateRepo(repo: string): string {
try { try {
const url = new URL(raw); const url = new URL(raw);
const host = url.hostname.toLowerCase(); 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); const normalized = normalizeParts(url.pathname);
if (normalized) { if (normalized) {
return normalized; return normalized;
@ -158,7 +160,7 @@ function createFallbackResult(repo: string): UpdateCheckResult {
currentVersion: APP_VERSION, currentVersion: APP_VERSION,
latestVersion: APP_VERSION, latestVersion: APP_VERSION,
latestTag: `v${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); const timeout = timeoutController(RELEASE_FETCH_TIMEOUT_MS);
let response: Response; let response: Response;
try { try {
response = await fetch(`https://api.github.com/repos/${safeRepo}/${endpoint}`, { response = await fetch(`${UPDATE_API_BASE}/repos/${safeRepo}/${endpoint}`, {
headers: { headers: {
Accept: "application/vnd.github+json", Accept: "application/vnd.github+json",
"User-Agent": UPDATE_USER_AGENT "User-Agent": UPDATE_USER_AGENT
@ -250,9 +252,9 @@ function buildDownloadCandidates(safeRepo: string, check: UpdateCheckResult): st
const candidates = [setupAssetUrl]; const candidates = [setupAssetUrl];
if (setupAssetName) { if (setupAssetName) {
const encodedName = encodeURIComponent(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) { if (latestTag) {
candidates.push(`https://github.com/${safeRepo}/releases/download/${encodeURIComponent(latestTag)}/${encodedName}`); candidates.push(`${UPDATE_WEB_BASE}/${safeRepo}/releases/download/${encodeURIComponent(latestTag)}/${encodedName}`);
} }
} }

View File

@ -1527,7 +1527,7 @@ export function App(): ReactElement {
<option value="ask">nachfragen</option> <option value="ask">nachfragen</option>
</select></div> </select></div>
</div> </div>
<label>GitHub Repo</label> <label>Codeberg Repo</label>
<input value={settingsDraft.updateRepo} onChange={(e) => setText("updateRepo", e.target.value)} /> <input value={settingsDraft.updateRepo} onChange={(e) => setText("updateRepo", e.target.value)} />
<label className="toggle-line"><input type="checkbox" checked={settingsDraft.autoUpdateCheck} onChange={(e) => setBool("autoUpdateCheck", e.target.checked)} /> Beim Start auf Updates prüfen</label> <label className="toggle-line"><input type="checkbox" checked={settingsDraft.autoUpdateCheck} onChange={(e) => setBool("autoUpdateCheck", e.target.checked)} /> Beim Start auf Updates prüfen</label>
</article> </article>

View File

@ -20,21 +20,21 @@ describe("update", () => {
it("normalizes update repo input", () => { it("normalizes update repo input", () => {
expect(normalizeUpdateRepo("")).toBe("Sucukdeluxe/real-debrid-downloader"); expect(normalizeUpdateRepo("")).toBe("Sucukdeluxe/real-debrid-downloader");
expect(normalizeUpdateRepo("owner/repo")).toBe("owner/repo"); expect(normalizeUpdateRepo("owner/repo")).toBe("owner/repo");
expect(normalizeUpdateRepo("https://github.com/owner/repo")).toBe("owner/repo"); expect(normalizeUpdateRepo("https://codeberg.org/owner/repo")).toBe("owner/repo");
expect(normalizeUpdateRepo("https://www.github.com/owner/repo")).toBe("owner/repo"); expect(normalizeUpdateRepo("https://www.codeberg.org/owner/repo")).toBe("owner/repo");
expect(normalizeUpdateRepo("https://github.com/owner/repo/releases/tag/v1.2.3")).toBe("owner/repo"); expect(normalizeUpdateRepo("https://codeberg.org/owner/repo/releases/tag/v1.2.3")).toBe("owner/repo");
expect(normalizeUpdateRepo("github.com/owner/repo.git")).toBe("owner/repo"); expect(normalizeUpdateRepo("codeberg.org/owner/repo.git")).toBe("owner/repo");
expect(normalizeUpdateRepo("git@github.com: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 = ""; let requestedUrl = "";
globalThis.fetch = (async (input: RequestInfo | URL): Promise<Response> => { globalThis.fetch = (async (input: RequestInfo | URL): Promise<Response> => {
requestedUrl = typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url; requestedUrl = typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url;
return new Response( return new Response(
JSON.stringify({ JSON.stringify({
tag_name: `v${APP_VERSION}`, 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: [] assets: []
}), }),
{ {
@ -44,8 +44,8 @@ describe("update", () => {
); );
}) as typeof fetch; }) as typeof fetch;
const result = await checkGitHubUpdate("https://github.com/owner/repo/releases"); const result = await checkGitHubUpdate("https://codeberg.org/owner/repo/releases");
expect(requestedUrl).toBe("https://api.github.com/repos/owner/repo/releases/latest"); expect(requestedUrl).toBe("https://codeberg.org/api/v1/repos/owner/repo/releases/latest");
expect(result.currentVersion).toBe(APP_VERSION); expect(result.currentVersion).toBe(APP_VERSION);
expect(result.latestVersion).toBe(APP_VERSION); expect(result.latestVersion).toBe(APP_VERSION);
expect(result.updateAvailable).toBe(false); expect(result.updateAvailable).toBe(false);
@ -55,7 +55,7 @@ describe("update", () => {
globalThis.fetch = (async (): Promise<Response> => new Response( globalThis.fetch = (async (): Promise<Response> => new Response(
JSON.stringify({ JSON.stringify({
tag_name: "v9.9.9", 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: [ assets: [
{ {
name: "Real-Debrid-Downloader 9.9.9.exe", name: "Real-Debrid-Downloader 9.9.9.exe",
@ -105,7 +105,7 @@ describe("update", () => {
currentVersion: APP_VERSION, currentVersion: APP_VERSION,
latestVersion: "9.9.9", latestVersion: "9.9.9",
latestTag: "v9.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", setupAssetUrl: "https://example.invalid/stale-setup.exe",
setupAssetName: "Real-Debrid-Downloader Setup 9.9.9.exe", setupAssetName: "Real-Debrid-Downloader Setup 9.9.9.exe",
setupAssetDigest: `sha256:${executableDigest}` setupAssetDigest: `sha256:${executableDigest}`
@ -176,7 +176,7 @@ describe("update", () => {
currentVersion: APP_VERSION, currentVersion: APP_VERSION,
latestVersion: "9.9.9", latestVersion: "9.9.9",
latestTag: "v9.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: "", setupAssetUrl: "",
setupAssetName: "" setupAssetName: ""
}; };
@ -240,7 +240,7 @@ describe("update", () => {
currentVersion: APP_VERSION, currentVersion: APP_VERSION,
latestVersion: "9.9.9", latestVersion: "9.9.9",
latestTag: "v9.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", setupAssetUrl: "https://example.invalid/hang-setup.exe",
setupAssetName: "", setupAssetName: "",
setupAssetDigest: "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" setupAssetDigest: "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
@ -276,7 +276,7 @@ describe("update", () => {
currentVersion: APP_VERSION, currentVersion: APP_VERSION,
latestVersion: "9.9.9", latestVersion: "9.9.9",
latestTag: "v9.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", setupAssetUrl: "https://example.invalid/mismatch-setup.exe",
setupAssetName: "setup.exe", setupAssetName: "setup.exe",
setupAssetDigest: "sha256:1111111111111111111111111111111111111111111111111111111111111111" setupAssetDigest: "sha256:1111111111111111111111111111111111111111111111111111111111111111"
@ -292,11 +292,11 @@ describe("normalizeUpdateRepo extended", () => {
it("handles trailing slashes and extra path segments", () => { it("handles trailing slashes and extra path segments", () => {
expect(normalizeUpdateRepo("owner/repo/")).toBe("owner/repo"); expect(normalizeUpdateRepo("owner/repo/")).toBe("owner/repo");
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", () => { 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", () => { it("returns default for malformed inputs", () => {
@ -307,12 +307,12 @@ describe("normalizeUpdateRepo extended", () => {
it("rejects traversal-like owner or repo segments", () => { 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("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", () => { it("handles www prefix", () => {
expect(normalizeUpdateRepo("https://www.github.com/owner/repo")).toBe("owner/repo"); expect(normalizeUpdateRepo("https://www.codeberg.org/owner/repo")).toBe("owner/repo");
expect(normalizeUpdateRepo("www.github.com/owner/repo")).toBe("owner/repo"); expect(normalizeUpdateRepo("www.codeberg.org/owner/repo")).toBe("owner/repo");
}); });
}); });