polyv-live-cli
Version:
CLI tool for managing PolyV live streaming services.
446 lines (435 loc) ⢠21.1 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.validateWatermarkPosition = validateWatermarkPosition;
exports.validateWatermarkOpacity = validateWatermarkOpacity;
exports.validateYNValue = validateYNValue;
exports.validateOutputFormat = validateOutputFormat;
exports.registerPlayerCommands = registerPlayerCommands;
const player_handler_1 = require("../handlers/player.handler");
const manager_1 = require("../config/manager");
const auth_adapter_1 = require("../config/auth-adapter");
const errors_1 = require("../utils/errors");
const api_command_1 = require("../utils/api-command");
function validateWatermarkPosition(value) {
const validPositions = ['tl', 'tr', 'bl', 'br'];
if (!validPositions.includes(value)) {
throw new Error(`watermarkPosition must be one of: ${validPositions.join(', ')}`);
}
return value;
}
function validateWatermarkOpacity(value) {
const opacity = parseFloat(value);
if (isNaN(opacity) || opacity < 0 || opacity > 1) {
throw new Error('watermarkOpacity must be a number between 0 and 1');
}
return opacity;
}
function validateYNValue(value) {
if (value !== 'Y' && value !== 'N') {
throw new Error('Value must be "Y" or "N"');
}
return value;
}
function parseFontSize(value) {
if (['small', 'middle', 'large'].includes(value)) {
return value;
}
const parsed = Number(value);
if (!Number.isInteger(parsed) || parsed < 1 || parsed > 256) {
throw new Error('font size must be 1-256 or one of small, middle, large');
}
return parsed;
}
function validateAntiRecordType(value) {
if (value !== 'marquee' && value !== 'watermark') {
throw new Error('anti-record type must be marquee or watermark');
}
return value;
}
function validateAntiRecordModel(value) {
if (!['fixed', 'nickname', 'diyurl'].includes(value)) {
throw new Error('model type must be fixed, nickname, or diyurl');
}
return value;
}
function validateShowMode(value) {
if (value !== 'roll' && value !== 'flicker') {
throw new Error('show mode must be roll or flicker');
}
return value;
}
function validateHeadAdvertType(value) {
if (!['NONE', 'IMAGE', 'FLV'].includes(value)) {
throw new Error('head advert type must be NONE, IMAGE, or FLV');
}
return value;
}
function validateOutputFormat(value) {
if (!['table', 'json'].includes(value)) {
throw new Error('Output format must be either "table" or "json"');
}
return value;
}
async function loadAuthAndServiceConfig(parentOptions) {
const authResult = auth_adapter_1.authAdapter.tryGetAuthConfig(parentOptions);
if (!authResult) {
throw new Error(auth_adapter_1.authAdapter.getStatusMessage(parentOptions));
}
let configResult;
try {
configResult = await manager_1.configManager.load({
cliOptions: parentOptions,
});
}
catch (error) {
if (error instanceof Error && error.message.includes('Auth configuration is incomplete')) {
configResult = {
config: {
baseUrl: 'https://api.polyv.net',
timeout: 30000,
debug: false
}
};
}
else {
throw error;
}
}
const serviceConfig = {
baseUrl: configResult.config.baseUrl,
timeout: configResult.config.timeout,
debug: configResult.config.debug
};
const isVerbose = !!parentOptions.verbose;
if (isVerbose) {
console.log(`š Authentication Source: ${authResult.source}`);
if (authResult.accountName) {
console.log(`š¤ Account: ${authResult.accountName}`);
}
console.log('');
}
const result = {
authConfig: authResult.config,
serviceConfig,
isVerbose,
};
if (authResult.source) {
result.authSource = authResult.source;
}
if (authResult.accountName) {
result.accountName = authResult.accountName;
}
return result;
}
function registerPlayerCommands(program) {
const playerCmd = program.command('player');
playerCmd.description('Manage channel player settings');
const configCmd = playerCmd.command('config');
configCmd.description('Manage channel player configuration');
const getCmd = configCmd
.command('get')
.description('Get player configuration for a channel')
.requiredOption('-c, --channel-id <channelId>', 'channel ID')
.option('-o, --output <format>', 'output format (table|json)', validateOutputFormat, 'table')
.action(async (options) => {
try {
const parentOptions = program.opts();
const { authConfig, serviceConfig } = await loadAuthAndServiceConfig(parentOptions);
const playerHandler = new player_handler_1.PlayerHandler(authConfig, serviceConfig);
const getOptions = {
channelId: options.channelId,
output: options.output
};
await playerHandler.getConfig(getOptions);
}
catch (error) {
if (error instanceof Error && error.message.includes('Authentication')) {
const diagnostics = auth_adapter_1.authAdapter.getDiagnostics(program.opts());
console.error('\nš Authentication Diagnostics:');
diagnostics.availableSources.forEach(source => {
const status = source.appId && source.appSecret ? 'ā
' : 'ā';
console.error(` ${status} ${source.metadata.source}: ${source.type}`);
});
if (diagnostics.errors.length > 0) {
console.error('\nā Errors:');
diagnostics.errors.forEach(err => console.error(` - ${err}`));
}
}
(0, errors_1.logError)(error instanceof Error ? error : new Error(String(error)));
process.exit(1);
}
});
getCmd.addHelpText('after', `
Examples:
# Get player configuration for a channel
$ polyv-live-cli player config get -c "3151318"
# Output in JSON format
$ polyv-live-cli player config get -c "3151318" -o json
# With full parameter names
$ polyv-live-cli player config get --channel-id "3151318" --output table
Output Formats:
table Formatted table output (default)
json JSON format for programmatic use
Notes:
- Channel ID is required for all player config queries
- Configuration includes watermark, warmup image, and view data settings
`);
const updateCmd = configCmd
.command('update')
.description('Update player configuration for a channel')
.requiredOption('-c, --channel-id <channelId>', 'channel ID')
.option('--watermark-enabled <enabled>', 'watermark enabled (Y/N)', validateYNValue)
.option('--watermark-url <url>', 'watermark image URL')
.option('--watermark-position <position>', 'watermark position (tl/tr/bl/br)', validateWatermarkPosition)
.option('--watermark-opacity <opacity>', 'watermark opacity (0-1)', validateWatermarkOpacity)
.option('--warmup-enabled <enabled>', 'warmup enabled (Y/N)', validateYNValue)
.option('--warmup-image-url <url>', 'warmup image URL')
.option('--base-pv <count>', 'base page views', parseInt)
.option('-o, --output <format>', 'output format (table|json)', validateOutputFormat, 'table')
.action(async (options) => {
try {
const parentOptions = program.opts();
const { authConfig, serviceConfig } = await loadAuthAndServiceConfig(parentOptions);
const playerHandler = new player_handler_1.PlayerHandler(authConfig, serviceConfig);
const updateOptions = {
channelId: options.channelId,
output: options.output
};
if (options.watermarkEnabled !== undefined) {
updateOptions.watermarkEnabled = options.watermarkEnabled;
}
if (options.watermarkUrl !== undefined) {
updateOptions.watermarkUrl = options.watermarkUrl;
}
if (options.watermarkPosition !== undefined) {
updateOptions.watermarkPosition = options.watermarkPosition;
}
if (options.watermarkOpacity !== undefined) {
updateOptions.watermarkOpacity = options.watermarkOpacity;
}
if (options.warmupEnabled !== undefined) {
updateOptions.warmupEnabled = options.warmupEnabled;
}
if (options.warmupImageUrl !== undefined) {
updateOptions.warmupImageUrl = options.warmupImageUrl;
}
if (options.basePv !== undefined) {
updateOptions.basePv = options.basePv;
}
await playerHandler.updateConfig(updateOptions);
}
catch (error) {
if (error instanceof Error && error.message.includes('Authentication')) {
const diagnostics = auth_adapter_1.authAdapter.getDiagnostics(program.opts());
console.error('\nš Authentication Diagnostics:');
diagnostics.availableSources.forEach(source => {
const status = source.appId && source.appSecret ? 'ā
' : 'ā';
console.error(` ${status} ${source.metadata.source}: ${source.type}`);
});
if (diagnostics.errors.length > 0) {
console.error('\nā Errors:');
diagnostics.errors.forEach(err => console.error(` - ${err}`));
}
}
(0, errors_1.logError)(error instanceof Error ? error : new Error(String(error)));
process.exit(1);
}
});
updateCmd.addHelpText('after', `
Examples:
# Update watermark settings
$ polyv-live-cli player config update -c "3151318" --watermark-enabled Y --watermark-url "http://example.com/logo.png" --watermark-position br --watermark-opacity 0.8
# Update warmup settings
$ polyv-live-cli player config update -c "3151318" --warmup-enabled Y --warmup-image-url "http://example.com/warmup.jpg"
# Update base page views
$ polyv-live-cli player config update -c "3151318" --base-pv 1000
# Combined update with JSON output
$ polyv-live-cli player config update -c "3151318" --watermark-enabled Y --base-pv 1000 -o json
Watermark Position:
--watermark-position Watermark position on the player
tl: Top-left
tr: Top-right
bl: Bottom-left
br: Bottom-right (default)
Watermark Opacity:
--watermark-opacity Watermark opacity from 0 (transparent) to 1 (opaque)
Y/N Values:
--watermark-enabled Enable/disable watermark (Y or N)
--warmup-enabled Enable/disable warmup image (Y or N)
Output Formats:
table Formatted output (default)
json JSON format for programmatic use
`);
const warmupCmd = playerCmd.command('warmup').description('Manage player warmup settings');
warmupCmd.command('switch-update')
.description('Update the warmup enabled switch')
.requiredOption('-c, --channel-id <channelId>', 'channel ID')
.requiredOption('--warm-up-enabled <value>', 'warmup enabled (Y|N)', validateYNValue)
.option('-f, --force', 'skip confirmation prompt')
.option('-o, --output <format>', 'output format (table|json)', validateOutputFormat, 'table')
.action(async (options) => {
try {
const { authConfig, serviceConfig } = await loadAuthAndServiceConfig(program.opts());
await new player_handler_1.PlayerHandler(authConfig, serviceConfig).updateWarmupSwitch(options);
}
catch (error) {
(0, errors_1.logError)(error instanceof Error ? error : new Error(String(error)));
process.exit(1);
}
});
const skinCmd = playerCmd.command('skin').description('Manage V4 player skin settings');
skinCmd.command('update-batch')
.description('Batch update channel player skin')
.requiredOption('--channel-ids <ids>', 'channel IDs, comma-separated, max 500', api_command_1.parseStringList)
.requiredOption('--skin <skin>', 'skin: black|red|blue|white|green|golden')
.option('-f, --force', 'skip confirmation prompt')
.option('-o, --output <format>', 'output format (table|json)', validateOutputFormat, 'table')
.action(async (options) => {
try {
const { authConfig, serviceConfig } = await loadAuthAndServiceConfig(program.opts());
await new player_handler_1.PlayerHandler(authConfig, serviceConfig).updateSkinBatch(options);
}
catch (error) {
(0, errors_1.logError)(error instanceof Error ? error : new Error(String(error)));
process.exit(1);
}
});
const antiRecordCmd = playerCmd.command('anti-record').description('Manage anti-record settings');
antiRecordCmd.command('get')
.description('Get anti-record settings')
.requiredOption('-c, --channel-id <channelId>', 'channel ID')
.option('-o, --output <format>', 'output format (table|json)', validateOutputFormat, 'table')
.action(async (options) => {
try {
const { authConfig, serviceConfig } = await loadAuthAndServiceConfig(program.opts());
await new player_handler_1.PlayerHandler(authConfig, serviceConfig).getAntiRecord(options);
}
catch (error) {
(0, errors_1.logError)(error instanceof Error ? error : new Error(String(error)));
process.exit(1);
}
});
antiRecordCmd.command('update')
.description('Update anti-record settings')
.requiredOption('-c, --channel-id <channelId>', 'channel ID (required)')
.requiredOption('--anti-record-type <type>', 'anti-record type (marquee|watermark, required)', validateAntiRecordType)
.requiredOption('--model-type <type>', 'model type (fixed|nickname|diyurl, required)', validateAntiRecordModel)
.requiredOption('--content <content>', 'content text or DIY URL (required)')
.requiredOption('--font-size <size>', 'font size (required)', parseFontSize)
.option('--opacity <opacity>', 'opacity 0-100', api_command_1.parseNonNegativeNumber)
.option('--font-color <color>', 'font color, hex format')
.option('--show-mode <mode>', 'show mode (roll|flicker)', validateShowMode)
.option('--double-enabled <value>', 'double enabled (Y|N)', validateYNValue)
.option('--auto-zoom-enabled <value>', 'auto zoom enabled (Y|N)', validateYNValue)
.option('-f, --force', 'skip confirmation prompt')
.option('-o, --output <format>', 'output format (table|json)', validateOutputFormat, 'table')
.action(async (options) => {
try {
const { authConfig, serviceConfig } = await loadAuthAndServiceConfig(program.opts());
await new player_handler_1.PlayerHandler(authConfig, serviceConfig).updateAntiRecord(options);
}
catch (error) {
(0, errors_1.logError)(error instanceof Error ? error : new Error(String(error)));
process.exit(1);
}
});
playerCmd.command('marquee-url')
.description('Set marquee URL restriction')
.requiredOption('-c, --channel-id <channelId>', 'channel ID')
.requiredOption('--marquee-restrict <value>', 'marquee restrict (Y|N)', validateYNValue)
.option('--url <url>', 'marquee URL')
.option('-f, --force', 'skip confirmation prompt')
.option('-o, --output <format>', 'output format (table|json)', validateOutputFormat, 'table')
.action(async (options) => {
try {
const { authConfig, serviceConfig } = await loadAuthAndServiceConfig(program.opts());
await new player_handler_1.PlayerHandler(authConfig, serviceConfig).setMarqueeUrl(options);
}
catch (error) {
(0, errors_1.logError)(error instanceof Error ? error : new Error(String(error)));
process.exit(1);
}
});
const advertCmd = playerCmd.command('advert').description('Manage player adverts');
advertCmd.command('head-update')
.description('Update player head advert')
.requiredOption('-c, --channel-id <channelId>', 'channel ID')
.requiredOption('--head-advert-type <type>', 'head advert type (NONE|IMAGE|FLV)', validateHeadAdvertType)
.option('--head-advert-media-url <url>', 'head advert media URL (image or video address)')
.option('--head-advert-image <url>', 'deprecated: use --head-advert-media-url')
.option('--head-advert-flv <url>', 'deprecated: use --head-advert-media-url')
.option('--head-advert-href <url>', 'head advert click URL')
.option('--head-advert-duration <seconds>', 'duration seconds', api_command_1.parsePositiveInteger)
.option('--head-advert-width <width>', 'advert width', api_command_1.parsePositiveInteger)
.option('--head-advert-height <height>', 'advert height', api_command_1.parsePositiveInteger)
.option('--enabled <value>', 'enabled (Y|N)', validateYNValue)
.option('-f, --force', 'skip confirmation prompt')
.option('-o, --output <format>', 'output format (table|json)', validateOutputFormat, 'table')
.action(async (options) => {
try {
const { authConfig, serviceConfig } = await loadAuthAndServiceConfig(program.opts());
await new player_handler_1.PlayerHandler(authConfig, serviceConfig).updateHeadAdvert({
...options,
headAdvertMediaUrl: options.headAdvertMediaUrl || options.headAdvertImage || options.headAdvertFlv,
});
}
catch (error) {
(0, errors_1.logError)(error instanceof Error ? error : new Error(String(error)));
process.exit(1);
}
});
advertCmd.command('stop-update')
.description('Update player stop advert')
.requiredOption('-c, --channel-id <channelId>', 'channel ID')
.option('--enabled <value>', 'enabled (Y|N)', validateYNValue)
.option('--stop-advert-image <url>', 'stop advert image URL')
.option('--stop-advert-href <url>', 'stop advert click URL')
.option('-f, --force', 'skip confirmation prompt')
.option('-o, --output <format>', 'output format (table|json)', validateOutputFormat, 'table')
.action(async (options) => {
try {
const { authConfig, serviceConfig } = await loadAuthAndServiceConfig(program.opts());
await new player_handler_1.PlayerHandler(authConfig, serviceConfig).updateStopAdvert(options);
}
catch (error) {
(0, errors_1.logError)(error instanceof Error ? error : new Error(String(error)));
process.exit(1);
}
});
playerCmd.command('logo-update')
.description('Update player logo settings')
.requiredOption('-c, --channel-id <channelId>', 'channel ID')
.requiredOption('--logo-image <url>', 'logo image URL')
.option('--logo-opacity <opacity>', 'logo opacity', api_command_1.parseNonNegativeNumber)
.option('--logo-position <position>', 'logo position (tl|tr|bl|br)', validateWatermarkPosition)
.option('--logo-href <url>', 'logo click URL')
.option('-f, --force', 'skip confirmation prompt')
.option('-o, --output <format>', 'output format (table|json)', validateOutputFormat, 'table')
.action(async (options) => {
try {
const { authConfig, serviceConfig } = await loadAuthAndServiceConfig(program.opts());
await new player_handler_1.PlayerHandler(authConfig, serviceConfig).updateLogo(options);
}
catch (error) {
(0, errors_1.logError)(error instanceof Error ? error : new Error(String(error)));
process.exit(1);
}
});
playerCmd.command('watch-feedback-list')
.description('List watch feedback records')
.option('-c, --channel-id <channelId>', 'channel ID')
.option('--page-number <page>', 'page number', api_command_1.parsePositiveInteger)
.option('--page-size <size>', 'page size', api_command_1.parsePositiveInteger)
.option('-o, --output <format>', 'output format (table|json)', validateOutputFormat, 'table')
.action(async (options) => {
try {
const { authConfig, serviceConfig } = await loadAuthAndServiceConfig(program.opts());
await new player_handler_1.PlayerHandler(authConfig, serviceConfig).listWatchFeedback(options);
}
catch (error) {
(0, errors_1.logError)(error instanceof Error ? error : new Error(String(error)));
process.exit(1);
}
});
}
//# sourceMappingURL=player.commands.js.map