9b395f5cc3
Build Chrome Extension / build (push) Waiting to run
Trigger Website Rebuild (Docs Updated) / dispatch (push) Waiting to run
E2E Headed Chrome / e2e-headed (macos-15) (push) Has been cancelled
E2E Headed Chrome / e2e-headed (ubuntu-latest) (push) Has been cancelled
E2E Headed Chrome / e2e-headed (windows-latest) (push) Has been cancelled
CI / build (macos-latest) (push) Has been cancelled
CI / build (ubuntu-latest) (push) Has been cancelled
CI / build (windows-latest) (push) Has been cancelled
CI / unit-test (push) Has been cancelled
CI / bun-test (push) Has been cancelled
CI / adapter-test (push) Has been cancelled
CI / smoke-test (macos-latest) (push) Has been cancelled
CI / smoke-test (ubuntu-latest) (push) Has been cancelled
Security Audit / audit (push) Has been cancelled
78 lines
4.0 KiB
JavaScript
78 lines
4.0 KiB
JavaScript
import * as fs from 'node:fs';
|
|
import * as path from 'node:path';
|
|
import { cli, Strategy } from '@jackwener/opencli/registry';
|
|
import { ArgumentError, CliError } from '@jackwener/opencli/errors';
|
|
import { loadXiaoyuzhouCredentials, requestXiaoyuzhouJson, fetchXiaoyuzhouTranscriptBody, extractTranscriptText } from './auth.js';
|
|
|
|
cli({
|
|
site: 'xiaoyuzhou',
|
|
name: 'transcript',
|
|
access: 'read',
|
|
description: 'Download Xiaoyuzhou transcript as JSON and text (requires local credentials)',
|
|
domain: 'www.xiaoyuzhoufm.com',
|
|
strategy: Strategy.PUBLIC,
|
|
browser: false,
|
|
args: [
|
|
{ name: 'id', positional: true, required: true, help: 'Episode ID (eid from podcast-episodes output)' },
|
|
{ name: 'output', default: './xiaoyuzhou-transcripts', help: 'Output directory' },
|
|
{ name: 'json', type: 'boolean', default: true, help: 'Save transcript JSON file' },
|
|
{ name: 'text', type: 'boolean', default: true, help: 'Save extracted transcript text file' },
|
|
],
|
|
columns: ['title', 'podcast', 'status', 'segments', 'json_file', 'text_file'],
|
|
func: async (kwargs) => {
|
|
if (kwargs.json === false && kwargs.text === false) {
|
|
throw new ArgumentError('At least one of --json or --text must be enabled', 'Example: opencli xiaoyuzhou transcript 69dd0c98e2c8be31551f6a33 --text true');
|
|
}
|
|
let credentials = loadXiaoyuzhouCredentials();
|
|
const episodeResponse = await requestXiaoyuzhouJson('/v1/episode/get', {
|
|
query: { eid: kwargs.id },
|
|
credentials,
|
|
});
|
|
credentials = episodeResponse.credentials;
|
|
const episode = episodeResponse.data;
|
|
if (!episode) {
|
|
throw new CliError('NOT_FOUND', 'Episode not found', 'Please check the episode ID');
|
|
}
|
|
const mediaId = String(episode.transcript?.mediaId || episode.media?.id || episode.transcriptMediaId || '').trim();
|
|
if (!mediaId) {
|
|
throw new CliError('PARSE_ERROR', 'mediaId not found in episode payload', 'Transcript metadata requires episode.transcript.mediaId, episode.media.id, or episode.transcriptMediaId');
|
|
}
|
|
const transcriptResponse = await requestXiaoyuzhouJson('/v1/episode-transcript/get', {
|
|
method: 'POST',
|
|
body: {
|
|
eid: kwargs.id,
|
|
mediaId,
|
|
},
|
|
credentials,
|
|
});
|
|
const transcriptMeta = transcriptResponse.data;
|
|
const transcriptUrl = String(transcriptMeta?.transcriptUrl || transcriptMeta?.url || '').trim();
|
|
if (!transcriptUrl) {
|
|
throw new CliError('EMPTY_RESULT', 'Transcript URL not found', 'This episode may not have transcript data available');
|
|
}
|
|
const transcriptBody = await fetchXiaoyuzhouTranscriptBody(transcriptUrl);
|
|
const { text, segmentCount } = extractTranscriptText(transcriptBody);
|
|
if (kwargs.text !== false && transcriptBody.trim() && !text.trim()) {
|
|
throw new CliError('PARSE_ERROR', 'Failed to extract transcript text', 'Transcript payload format is unsupported. Re-run with --json true to inspect the raw payload.');
|
|
}
|
|
const outputDir = path.join(String(kwargs.output || './xiaoyuzhou-transcripts'), String(kwargs.id));
|
|
fs.mkdirSync(outputDir, { recursive: true });
|
|
const jsonPath = path.join(outputDir, 'transcript.json');
|
|
const textPath = path.join(outputDir, 'transcript.txt');
|
|
if (kwargs.json !== false) {
|
|
fs.writeFileSync(jsonPath, transcriptBody, 'utf-8');
|
|
}
|
|
if (kwargs.text !== false) {
|
|
fs.writeFileSync(textPath, text, 'utf-8');
|
|
}
|
|
return [{
|
|
title: episode.title || 'episode',
|
|
podcast: episode.podcast?.title || '',
|
|
status: 'success',
|
|
segments: kwargs.text === false ? '-' : String(segmentCount),
|
|
json_file: kwargs.json === false ? '-' : jsonPath,
|
|
text_file: kwargs.text === false ? '-' : textPath,
|
|
}];
|
|
},
|
|
});
|