- TypeScript 100%
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. |
||
|---|---|---|
| .vscode | ||
| examples | ||
| scripts | ||
| src | ||
| tests | ||
| .gitignore | ||
| bun.lock | ||
| CONTRIBUTING.md | ||
| LICENSE | ||
| package.json | ||
| pnpm-lock.yaml | ||
| pnpm-workspace.yaml | ||
| README.md | ||
| tsconfig.json | ||
| tsdown.config.ts | ||
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
fetchexists (Node ≥ 18.17, Bun, Deno, browsers, edge runtimes) - Full wire types for every endpoint (
Rd*,Ad*,Pm*) - Normalized
Torrent/UserInfo/DownloadLinkshapes across providers - Consistent errors (
DebridErrorwith HTTP status and provider error code) - Optional request timeout and HTTP 429 retries; injectable
fetchfor 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.