/ clis / douyin / _shared / imagex-upload.js
imagex-upload.js
 1  /**
 2   * ImageX cover image uploader.
 3   *
 4   * Uploads a JPEG/PNG image to ByteDance ImageX via a pre-signed PUT URL
 5   * obtained from the Douyin "apply cover upload" API.
 6   */
 7  import * as fs from 'node:fs';
 8  import * as path from 'node:path';
 9  import { CommandExecutionError } from '@jackwener/opencli/errors';
10  /**
11   * Detect MIME type from file extension.
12   * Falls back to image/jpeg for unknown extensions.
13   */
14  function detectContentType(filePath) {
15      const ext = path.extname(filePath).toLowerCase();
16      switch (ext) {
17          case '.png':
18              return 'image/png';
19          case '.gif':
20              return 'image/gif';
21          case '.webp':
22              return 'image/webp';
23          default:
24              return 'image/jpeg';
25      }
26  }
27  /**
28   * Upload a cover image to ByteDance ImageX via a pre-signed PUT URL.
29   *
30   * @param imagePath - Local file path to the image (JPEG/PNG/etc.)
31   * @param uploadInfo - Upload URL and store_uri from the apply cover upload API
32   * @returns The store_uri (= image_uri for use in create_v2)
33   */
34  export async function imagexUpload(imagePath, uploadInfo) {
35      if (!fs.existsSync(imagePath)) {
36          throw new CommandExecutionError(`Cover image file not found: ${imagePath}`, 'Ensure the file path is correct and accessible.');
37      }
38      const imageBuffer = fs.readFileSync(imagePath);
39      const contentType = detectContentType(imagePath);
40      const res = await fetch(uploadInfo.upload_url, {
41          method: 'PUT',
42          headers: {
43              'Content-Type': contentType,
44              'Content-Length': String(imageBuffer.byteLength),
45          },
46          body: imageBuffer,
47      });
48      if (!res.ok) {
49          const body = await res.text().catch(() => '');
50          throw new CommandExecutionError(`ImageX upload failed with status ${res.status}: ${body}`, 'Check that the upload URL is valid and has not expired.');
51      }
52      return uploadInfo.store_uri;
53  }