No description
  • TypeScript 100%
Find a file
Joshue Abance aab7b15c4d
fix(real-debrid): treat 204 empty list responses as []
RD returns 204 No Content instead of [] for empty /torrents and
/downloads lists, which made listTorrents() crash on .map of undefined.

Bump version to 0.1.1.
2026-10-10 15:31:25 +08:00
.vscode init lib 2026-08-26 00:38:37 +08:00
examples init lib 2026-08-26 00:38:37 +08:00
scripts chore: add live smoke scripts for every provider endpoint 2026-10-10 09:51:39 +08:00
src fix(real-debrid): treat 204 empty list responses as [] 2026-10-10 15:31:25 +08:00
tests fix(all-debrid): correct array params and unlock magnet file links 2026-10-10 09:51:39 +08:00
.gitignore init lib 2026-08-26 00:38:37 +08:00
bun.lock init lib 2026-08-26 00:38:37 +08:00
CONTRIBUTING.md docs: rewrite README for consumers, move build guide to CONTRIBUTING 2026-10-10 09:51:39 +08:00
LICENSE build: prepare package for npm publishing 2026-10-10 09:51:39 +08:00
package.json fix(real-debrid): treat 204 empty list responses as [] 2026-10-10 15:31:25 +08:00
pnpm-lock.yaml chore: update pnpm lockfile and package manager version 2026-10-10 14:07:08 +08:00
pnpm-workspace.yaml feat: implement service AllDebrid 2026-08-26 01:49:39 +08:00
README.md docs: rewrite README for consumers, move build guide to CONTRIBUTING 2026-10-10 09:51:39 +08:00
tsconfig.json chore: add live smoke scripts for every provider endpoint 2026-10-10 09:51:39 +08:00
tsdown.config.ts build: prepare package for npm publishing 2026-10-10 09:51:39 +08:00

debrid-ts

Typed TypeScript clients for debrid services — Real-Debrid, AllDebrid and Premiumize — with every documented endpoint as a method, plus one provider-agnostic interface so your code can swap providers by changing a constructor.

  • Zero dependencies, ESM only, works anywhere fetch exists (Node ≥ 18.17, Bun, Deno, browsers, edge runtimes)
  • Full wire types for every endpoint (Rd*, Ad*, Pm*)
  • Normalized Torrent / UserInfo / DownloadLink shapes across providers
  • Consistent errors (DebridError with HTTP status and provider error code)
  • Optional request timeout and HTTP 429 retries; injectable fetch for tests
npm install debrid-ts

Quick start

import { RealDebrid } from 'debrid-ts'

const rd = new RealDebrid(process.env.RD_TOKEN!)

const user = await rd.getUser()
console.log(user.username, user.premium, user.expiresAt)

const torrent = await rd.addMagnet('magnet:?xt=urn:btih:...')
await rd.selectTorrentFiles(torrent.id, 'all') // Real-Debrid only starts after this
// ... poll rd.getTorrent(torrent.id) until status === 'downloaded' ...
const links = await rd.getDownloadLinks(torrent.id) // direct, unrestricted URLs

One interface, any provider

Every client implements DebridProvider:

import { AllDebrid, Premiumize, RealDebrid, type DebridProvider } from 'debrid-ts'

const provider: DebridProvider =
  process.env.PROVIDER === 'ad' ? new AllDebrid(process.env.AD_KEY!)
  : process.env.PROVIDER === 'pm' ? new Premiumize(process.env.PM_KEY!)
  : new RealDebrid(process.env.RD_TOKEN!)

if (await provider.isCached(infoHash)) {
  const torrent = await provider.addMagnet(`magnet:?xt=urn:btih:${infoHash}`)
  const links = await provider.getDownloadLinks(torrent.id)
}
interface DebridProvider {
  readonly name: string
  getUser(): Promise<UserInfo>
  isCached(hashOrMagnet: string): Promise<boolean>
  addMagnet(magnet: string, options?: { host?: string }): Promise<Torrent>
  listTorrents(offset?: number, limit?: number): Promise<Torrent[]>
  getDownloadLinks(torrentId: string): Promise<DownloadLink[]>
}

Provider differences worth knowing

Real-Debrid AllDebrid Premiumize
isCached() Probe: adds the magnet, selects all files, checks for downloaded, deletes it. An uncached torrent may start for a few seconds. (RD disabled its cache endpoint.) Probe: uploads the magnet, reads ready, deletes it. Real lookup via /cache/check, no side effects.
After addMagnet() Call selectTorrentFiles() to start. Starts automatically. Starts automatically.
getDownloadLinks() Unrestricts each host link (one request per file). Unlocks each /f/ file link (one request per file, delayed links are awaited). Reads the resulting file/folder; links are direct.
listTorrents() paging Server-side. Client-side slice (API returns all). Client-side slice (API returns all).
Torrent.hash / bytes ✅ ✅ Empty — the transfer list has neither.

Providers

Real-Debrid

const rd = new RealDebrid(token) // https://real-debrid.com/apitoken, or an OAuth access token

Covers every endpoint in the API docs: /disable_access_token, /time, /user, /unrestrict, /traffic, /streaming, /downloads, /torrents, /hosts, /settings. Raw methods return wire types (RdUser, RdTorrentInfo, …).

OAuth helpers are static. For scripts and opensource apps (no client secret), use the device flow with Real-Debrid's public opensource client id:

const device = await RealDebrid.getDeviceCode(undefined, { newCredentials: true })
console.log(`Open ${device.verification_url} and enter ${device.user_code}`)

const { credentials, token } = await RealDebrid.waitForDeviceToken({
  deviceCode: device.device_code,
  expiresIn: device.expires_in,
  interval: device.interval,
})
const rd = new RealDebrid(token.access_token)

// later, when the access token expires:
const refreshed = await RealDebrid.refreshToken({
  clientId: credentials.client_id,
  clientSecret: credentials.client_secret,
  refreshToken: token.refresh_token,
})

Also available: getAuthorizeUrl() + exchangeCode() (website flow), getDeviceToken() / getDeviceCredentials() (single polls) and getTokenWithPassword().

AllDebrid

const ad = new AllDebrid(apiKey) // https://alldebrid.com/apikeys/

Covers every non-deprecated endpoint in the API docs: hosts, user, links (unlock, streaming, delayed, redirector), magnets (upload, .torrent upload, status incl. live mode, files, delete, restart), saved/recent links and reseller vouchers. The { status, data } envelope is unwrapped for you; { status: "error" } throws DebridError.

Get an apikey from a user with the PIN flow:

const pin = await AllDebrid.getPin()
console.log(`Open ${pin.user_url}`)
const apiKey = await AllDebrid.waitForPin(pin)

Premiumize

const pm = new Premiumize(apiKey) // https://www.premiumize.me/account, or an OAuth access token

Covers every endpoint in the API docs: /account, /folder (incl. search and upload), /item, /transfer, /cache, /services, /zip, plus OAuth 2.0 statics (PKCE / auth code via getAuthorizeUrl() + exchangeCode(), device flow via getDeviceCode() + waitForDeviceToken(), and the legacy password grant).

Options

Every client (and every static OAuth helper) accepts:

new RealDebrid(token, {
  timeout: 15_000, // per-request timeout in ms (default: none)
  maxRetries: 2,   // retry HTTP 429 responses, honoring Retry-After (default: 0)
  fetch: myFetch,  // custom fetch (proxies, tests, ...)
})

Errors

Every failure — HTTP errors and errors reported inside a 200 envelope — is a DebridError:

import { DebridError } from 'debrid-ts'

try {
  await ad.unlockLink(url)
} catch (error) {
  if (error instanceof DebridError) {
    error.status  // HTTP status (200 for envelope errors)
    error.code    // provider code: 'bad_token', 'LINK_DOWN', 'not_found', ...
    error.details // raw error payload from the provider
  }
}

Contributing

See CONTRIBUTING.md.

License

MIT