From bc6092aa21544ea1fb6c304de1b7e7a29c7bdc3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mario=20Beltr=C3=A1n?= Date: Sat, 2 May 2026 00:28:31 +0200 Subject: [PATCH] feat: add getthumbslinks client method MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds the JSON batch counterpart to getthumblink — accepts a list of fileids and returns a ThumbResult[] of assembled thumbnail URLs. Per-file failures (non-zero per-file result, or empty hosts) are silently dropped, mirroring getthumbsfileids' behavior. --- README.md | 1 + src/client.ts | 2 + src/methods/getthumbslinks.ts | 38 ++++++++++ src/methods/index.ts | 1 + test/unit/methods/getthumbslinks.test.ts | 92 ++++++++++++++++++++++++ 5 files changed, 134 insertions(+) create mode 100644 src/methods/getthumbslinks.ts create mode 100644 test/unit/methods/getthumbslinks.test.ts diff --git a/README.md b/README.md index 5ba0113..2a3969b 100644 --- a/README.md +++ b/README.md @@ -137,6 +137,7 @@ These are fully typed and bound directly on the client: | `downloadfile` | `(fileid: number, destination: string \| WritableStream, options?: DownloadOptions)` | `Promise` | | `remoteupload` | `(url: string, folderid?: number, options?: RemoteUploadOptions)` | `Promise<{ metadata: FileMetadata }>` | | `getthumbsfileids` | `(fileids: number[], receiveThumb: (thumb: ThumbResult) => void, options?: ThumbOptions)` | `Promise` | +| `getthumbslinks` | `(fileids: number[], options?: ThumbOptions)` | `Promise` | ## Uploads diff --git a/src/client.ts b/src/client.ts index ce3cdf7..4f633c3 100644 --- a/src/client.ts +++ b/src/client.ts @@ -66,6 +66,7 @@ export interface Client { renamefolder(folderid: number, toname: string): Promise getfilelink(fileid: number): Promise getthumblink(fileid: number, options?: ThumbOptions): Promise + getthumbslinks(fileids: number[], options?: ThumbOptions): Promise sharefolder( folderid: number, mail: string, @@ -174,6 +175,7 @@ export function createClient(opts: CreateClientOptions): Client { renamefolder: methods.renamefolder(ctx), getfilelink: methods.getfilelink(ctx), getthumblink: methods.getthumblink(ctx), + getthumbslinks: methods.getthumbslinks(ctx), sharefolder: methods.sharefolder(ctx), appshare: methods.appshare(ctx), login: methods.login(ctx), diff --git a/src/methods/getthumbslinks.ts b/src/methods/getthumbslinks.ts new file mode 100644 index 0000000..80df89a --- /dev/null +++ b/src/methods/getthumbslinks.ts @@ -0,0 +1,38 @@ +import type { ClientContext } from '../client' +import type { ThumbResult } from '../types/api' +import type { ThumbOptions } from '../types/options' + +import { assert } from '../util/assert' + +interface ThumbsLinksEntry { + result: number + fileid: number + hosts?: string[] + path?: string +} + +export function getthumbslinks(ctx: ClientContext) { + return async (fileids: number[], options: ThumbOptions = {}): Promise => { + assert(Array.isArray(fileids) && fileids.length > 0, '`fileids` must be a non-empty array') + + const thumbType = options.thumbType ?? 'auto' + const size = options.size ?? '120x120' + const crop = options.crop ?? true + + const res = await ctx.call<{ thumbs: ThumbsLinksEntry[] }>('getthumbslinks', { + fileids: fileids.join(','), + type: thumbType, + size, + crop: crop ? 1 : 0, + }) + + const thumbs: ThumbResult[] = [] + for (const entry of res.thumbs) { + if (entry.result !== 0) continue + const host = entry.hosts?.[0] + if (!host || !entry.path) continue + thumbs.push({ fileid: entry.fileid, url: `https://${host}${entry.path}` }) + } + return thumbs + } +} diff --git a/src/methods/index.ts b/src/methods/index.ts index fb6aa17..b900c6e 100644 --- a/src/methods/index.ts +++ b/src/methods/index.ts @@ -9,6 +9,7 @@ export { renamefile } from './renamefile' export { renamefolder } from './renamefolder' export { getfilelink } from './getfilelink' export { getthumblink } from './getthumblink' +export { getthumbslinks } from './getthumbslinks' export { sharefolder } from './sharefolder' export { appshare } from './appshare' export { login } from './login' diff --git a/test/unit/methods/getthumbslinks.test.ts b/test/unit/methods/getthumbslinks.test.ts new file mode 100644 index 0000000..2ee45fa --- /dev/null +++ b/test/unit/methods/getthumbslinks.test.ts @@ -0,0 +1,92 @@ +import { describe, it, expect, vi } from 'vitest' + +import { createClient } from '../../../src/client' +import { apiOk, mockFetch } from '../../fixtures/responses' + +const TWO_OK = { + thumbs: [ + { result: 0, fileid: 42, hosts: ['p-host.pcloud.com'], path: '/cBL42/thumb.jpg' }, + { result: 0, fileid: 43, hosts: ['p-host.pcloud.com'], path: '/cBL43/thumb.jpg' }, + ], +} + +describe('getthumbslinks', () => { + it('returns assembled https URLs for all successful entries', async () => { + vi.stubGlobal('fetch', mockFetch(apiOk(TWO_OK))) + const client = createClient({ token: 'test-token' }) + const thumbs = await client.getthumbslinks([42, 43]) + expect(thumbs).toEqual([ + { fileid: 42, url: 'https://p-host.pcloud.com/cBL42/thumb.jpg' }, + { fileid: 43, url: 'https://p-host.pcloud.com/cBL43/thumb.jpg' }, + ]) + }) + + it('sends fileids, type=auto, size=120x120, crop=1 by default', async () => { + let capturedUrl = '' + vi.stubGlobal('fetch', (url: string) => { + capturedUrl = url + return Promise.resolve(new Response(JSON.stringify(apiOk(TWO_OK)), { status: 200 })) + }) + + const client = createClient({ token: 'test-token' }) + await client.getthumbslinks([42, 43]) + expect(capturedUrl).toContain('fileids=42%2C43') + expect(capturedUrl).toContain('type=auto') + expect(capturedUrl).toContain('size=120x120') + expect(capturedUrl).toContain('crop=1') + }) + + it('honors custom thumbType, size, and crop=false', async () => { + let capturedUrl = '' + vi.stubGlobal('fetch', (url: string) => { + capturedUrl = url + return Promise.resolve(new Response(JSON.stringify(apiOk(TWO_OK)), { status: 200 })) + }) + + const client = createClient({ token: 'test-token' }) + await client.getthumbslinks([42], { thumbType: 'png', size: '32x32', crop: false }) + expect(capturedUrl).toContain('type=png') + expect(capturedUrl).toContain('size=32x32') + expect(capturedUrl).toContain('crop=0') + }) + + it('drops entries whose per-file result is non-zero', async () => { + vi.stubGlobal( + 'fetch', + mockFetch( + apiOk({ + thumbs: [ + { result: 0, fileid: 42, hosts: ['p-host.pcloud.com'], path: '/cBL42/thumb.jpg' }, + { result: 2009, fileid: 99 }, + ], + }), + ), + ) + const client = createClient({ token: 'test-token' }) + const thumbs = await client.getthumbslinks([42, 99]) + expect(thumbs).toEqual([{ fileid: 42, url: 'https://p-host.pcloud.com/cBL42/thumb.jpg' }]) + }) + + it('drops entries with empty hosts array', async () => { + vi.stubGlobal( + 'fetch', + mockFetch( + apiOk({ + thumbs: [ + { result: 0, fileid: 42, hosts: [], path: '/cBL42/thumb.jpg' }, + { result: 0, fileid: 43, hosts: ['p-host.pcloud.com'], path: '/cBL43/thumb.jpg' }, + ], + }), + ), + ) + const client = createClient({ token: 'test-token' }) + const thumbs = await client.getthumbslinks([42, 43]) + expect(thumbs).toEqual([{ fileid: 43, url: 'https://p-host.pcloud.com/cBL43/thumb.jpg' }]) + }) + + it('throws TypeError when fileids is empty', async () => { + const client = createClient({ token: 'test-token' }) + await expect(client.getthumbslinks([])).rejects.toThrow(TypeError) + await expect(client.getthumbslinks([])).rejects.toThrow('`fileids` must be a non-empty array') + }) +})