debrid-ts
v0.1.1
Published
Typed API clients for debrid services (Real-Debrid, AllDebrid, Premiumize) behind one provider-agnostic interface.
Maintainers
Readme
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-tsQuick 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 URLsOne 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 tokenCovers 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 tokenCovers 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.
