polyv-live-cli
Version:
CLI tool for managing PolyV live streaming services.
531 lines (513 loc) • 23.2 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.validateOutputFormat = validateOutputFormat;
exports.registerViewerCommands = registerViewerCommands;
const viewer_handler_1 = require("../handlers/viewer.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 validateOutputFormat(value) {
if (!['table', 'json'].includes(value)) {
throw new Error('Output format must be either "table" or "json"');
}
return value;
}
function registerViewerCommands(program) {
const viewerCmd = program.command('viewer');
viewerCmd.description('Manage viewer information queries');
const getCmd = viewerCmd
.command('get')
.description('Get single viewer details')
.requiredOption('-i, --viewer-id <id>', 'viewer unique 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 viewerHandler = new viewer_handler_1.ViewerHandler(authConfig, serviceConfig);
await viewerHandler.getViewer({
viewerId: options.viewerId,
output: options.output,
});
}
catch (error) {
(0, errors_1.logError)(error instanceof Error ? error : new Error(String(error)));
process.exit(1);
}
});
getCmd.addHelpText('after', `
Examples:
# Get viewer details
$ polyv-live-cli viewer get -i "2_v378gn997yovtl3p8h77db9e224t6hg9"
# JSON output
$ polyv-live-cli viewer get -i "2_v378gn997yovtl3p8h77db9e224t6hg9" -o json
Output Formats:
table - Formatted table output (default)
json - JSON format for programmatic use
`);
const listCmd = viewerCmd
.command('list')
.description('List viewers with pagination and filters')
.option('--source <type>', 'filter by source (IMPORT|WX|MOBILE)')
.option('--mobile <number>', 'filter by mobile number')
.option('--email <email>', 'filter by email')
.option('--area <area>', 'filter by area')
.option('--page <number>', 'page number', parseInt)
.option('--size <number>', 'page size', 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 viewerHandler = new viewer_handler_1.ViewerHandler(authConfig, serviceConfig);
await viewerHandler.listViewers({
source: options.source,
mobile: options.mobile,
email: options.email,
area: options.area,
page: options.page,
size: options.size,
output: options.output,
});
}
catch (error) {
(0, errors_1.logError)(error instanceof Error ? error : new Error(String(error)));
process.exit(1);
}
});
listCmd.addHelpText('after', `
Examples:
# List all viewers (default pagination)
$ polyv-live-cli viewer list
# List with custom pagination
$ polyv-live-cli viewer list --page 1 --size 20
# Filter by source
$ polyv-live-cli viewer list --source IMPORT
$ polyv-live-cli viewer list --source WX
$ polyv-live-cli viewer list --source MOBILE
# Filter by mobile number
$ polyv-live-cli viewer list --mobile "13800138000"
# Filter by email
$ polyv-live-cli viewer list --email "user@example.com"
# Filter by area
$ polyv-live-cli viewer list --area "Beijing"
# Combine multiple filters
$ polyv-live-cli viewer list --source IMPORT --page 1 --size 50 -o json
Output Formats:
table - Formatted table output (default)
json - JSON format for programmatic use
`);
viewerCmd
.command('create')
.description('Create a viewer record')
.requiredOption('--nickname <nickname>', 'viewer nickname')
.requiredOption('--mobile <mobile>', 'viewer mobile number')
.option('--name <name>', 'viewer real name')
.option('--last-collect-mobile <mobile>', 'last collected mobile number')
.option('--email <email>', 'viewer email')
.option('--area <area>', 'viewer area')
.option('--latest-access-ip <ip>', 'latest access IP')
.option('--device <device>', 'viewer device')
.option('--follow-users <json>', 'follow users JSON object')
.option('-f, --force', 'skip confirmation prompt')
.option('-o, --output <format>', 'output format (table|json)', validateOutputFormat, 'table')
.action(async (options) => {
await executeViewerCommand(program, (viewerHandler) => viewerHandler.createViewer({
nickname: options.nickname,
mobile: options.mobile,
name: options.name,
lastCollectMobile: options.lastCollectMobile,
email: options.email,
area: options.area,
latestAccessIp: options.latestAccessIp,
device: options.device,
followUsers: options.followUsers,
force: options.force,
output: options.output,
}));
});
viewerCmd
.command('update')
.description('Update a viewer record')
.requiredOption('-u, --viewer-union-id <id>', 'viewer union ID')
.option('--nickname <nickname>', 'viewer nickname')
.option('--mobile <mobile>', 'viewer mobile number')
.option('--name <name>', 'viewer real name')
.option('--last-collect-mobile <mobile>', 'last collected mobile number')
.option('--email <email>', 'viewer email')
.option('--area <area>', 'viewer area')
.option('--latest-access-ip <ip>', 'latest access IP')
.option('--device <device>', 'viewer device')
.option('-f, --force', 'skip confirmation prompt')
.option('-o, --output <format>', 'output format (table|json)', validateOutputFormat, 'table')
.action(async (options) => {
await executeViewerCommand(program, (viewerHandler) => viewerHandler.updateViewer({
viewerUnionId: options.viewerUnionId,
nickname: options.nickname,
mobile: options.mobile,
name: options.name,
lastCollectMobile: options.lastCollectMobile,
email: options.email,
area: options.area,
latestAccessIp: options.latestAccessIp,
device: options.device,
force: options.force,
output: options.output,
}));
});
viewerCmd
.command('delete')
.description('Delete a viewer record')
.requiredOption('-u, --viewer-union-id <id>', 'viewer union ID')
.option('-f, --force', 'skip confirmation prompt')
.option('-o, --output <format>', 'output format (table|json)', validateOutputFormat, 'table')
.action(async (options) => {
await executeViewerCommand(program, (viewerHandler) => viewerHandler.deleteViewer({
viewerUnionId: options.viewerUnionId,
force: options.force,
output: options.output,
}));
});
viewerCmd
.command('import-external')
.description('Import external viewer records')
.option('--viewers <json>', 'JSON array of external viewer objects')
.option('--external-viewer-id <id>', 'external viewer ID for single import')
.option('--nickname <nickname>', 'viewer nickname for single import')
.option('--label-ids <ids>', 'comma-separated label IDs for single import')
.option('--follow-user-id <id>', 'follow user ID for single import')
.option('--follow-user-type <type>', 'follow user type for single import')
.option('-f, --force', 'skip confirmation prompt')
.option('-o, --output <format>', 'output format (table|json)', validateOutputFormat, 'table')
.action(async (options) => {
await executeViewerCommand(program, (viewerHandler) => viewerHandler.importExternalViewers({
viewers: options.viewers,
externalViewerId: options.externalViewerId,
nickname: options.nickname,
labelIds: options.labelIds,
followUserId: options.followUserId,
followUserType: options.followUserType,
force: options.force,
output: options.output,
}));
});
const configCmd = viewerCmd.command('config');
configCmd.description('Manage viewer user system config');
configCmd
.command('update')
.description('Update viewer user system config')
.requiredOption('--mobile-login-enabled <Y|N>', 'mobile login enabled', api_command_1.validateYn)
.requiredOption('--wx-work-login-enabled <Y|N>', 'WeCom login enabled', api_command_1.validateYn)
.option('--viewer-weixin-auth-expired <days>', 'WeChat auth expiry days (0-180)', parseZeroBasedInteger)
.option('--collect-mobile-enabled <Y|N>', 'collect mobile enabled', api_command_1.validateYn)
.option('--guest-mode-enabled <Y|N>', 'guest mode enabled', api_command_1.validateYn)
.option('--tourist-external-href-enabled <Y|N>', 'external tourist link enabled', api_command_1.validateYn)
.option('--tourist-external-href-config <json>', 'external tourist link config JSON object')
.option('-f, --force', 'skip confirmation prompt')
.option('-o, --output <format>', 'output format (table|json)', validateOutputFormat, 'table')
.action(async (options) => {
await executeViewerCommand(program, (viewerHandler) => viewerHandler.updateViewerConfig({
mobileLoginEnabled: options.mobileLoginEnabled,
wxWorkLoginEnabled: options.wxWorkLoginEnabled,
viewerWeixinAuthExpired: options.viewerWeixinAuthExpired,
collectMobileEnabled: options.collectMobileEnabled,
guestModeEnabled: options.guestModeEnabled,
touristExternalHrefEnabled: options.touristExternalHrefEnabled,
touristExternalHrefConfig: options.touristExternalHrefConfig,
force: options.force,
output: options.output,
}));
});
viewerCmd
.command('lottery-wins')
.description('List viewer lottery win records')
.requiredOption('-i, --viewer-id <id>', 'viewer ID')
.option('--page <number>', 'page number', api_command_1.parsePositiveInteger)
.option('--size <number>', 'page size', api_command_1.parsePositiveInteger)
.option('-o, --output <format>', 'output format (table|json)', validateOutputFormat, 'table')
.action(async (options) => {
await executeViewerCommand(program, (viewerHandler) => viewerHandler.listViewerLotteryWins({
viewerId: options.viewerId,
page: options.page,
size: options.size,
output: options.output,
}));
});
const tagCmd = viewerCmd.command('tag');
tagCmd.description('Manage viewer tags');
const tagListCmd = tagCmd
.command('list')
.description('List all viewer tags')
.option('-k, --keyword <keyword>', 'keyword to search tag name')
.option('--page <number>', 'page number', parseInt)
.option('--size <number>', 'page size', 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 viewerHandler = new viewer_handler_1.ViewerHandler(authConfig, serviceConfig);
await viewerHandler.listViewerTags({
keyword: options.keyword,
page: options.page,
size: options.size,
output: options.output,
});
}
catch (error) {
(0, errors_1.logError)(error instanceof Error ? error : new Error(String(error)));
process.exit(1);
}
});
tagListCmd.addHelpText('after', `
Examples:
# List all tags
$ polyv-live-cli viewer tag list
# Search tags by keyword
$ polyv-live-cli viewer tag list -k "VIP"
# Paginated list
$ polyv-live-cli viewer tag list --page 1 --size 20
# JSON output
$ polyv-live-cli viewer tag list -o json
`);
tagCmd
.command('create')
.description('Create viewer tags')
.requiredOption('-l, --labels <names>', 'comma-separated tag names')
.option('-f, --force', 'skip confirmation prompt')
.option('-o, --output <format>', 'output format (table|json)', validateOutputFormat, 'table')
.action(async (options) => {
await executeViewerCommand(program, (viewerHandler) => viewerHandler.createViewerTag({
labels: options.labels,
force: options.force,
output: options.output,
}));
});
tagCmd
.command('update')
.description('Update a viewer tag')
.requiredOption('--id <id>', 'viewer tag ID', api_command_1.parsePositiveInteger)
.option('-l, --label <name>', 'new tag name')
.option('-f, --force', 'skip confirmation prompt')
.option('-o, --output <format>', 'output format (table|json)', validateOutputFormat, 'table')
.action(async (options) => {
await executeViewerCommand(program, (viewerHandler) => viewerHandler.updateViewerTag({
id: options.id,
label: options.label,
force: options.force,
output: options.output,
}));
});
tagCmd
.command('delete')
.description('Delete a viewer tag')
.requiredOption('--id <id>', 'viewer tag ID', api_command_1.parsePositiveInteger)
.option('-f, --force', 'skip confirmation prompt')
.option('-o, --output <format>', 'output format (table|json)', validateOutputFormat, 'table')
.action(async (options) => {
await executeViewerCommand(program, (viewerHandler) => viewerHandler.deleteViewerTag({
id: options.id,
force: options.force,
output: options.output,
}));
});
const tagAddCmd = tagCmd
.command('add')
.description('Add tags to viewers')
.requiredOption('-V, --viewer-ids <ids>', 'comma-separated viewer IDs')
.requiredOption('-l, --label-ids <ids>', 'comma-separated numeric tag IDs from viewer tag list')
.option('-f, --force', 'skip confirmation prompt')
.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 viewerHandler = new viewer_handler_1.ViewerHandler(authConfig, serviceConfig);
await viewerHandler.addViewerTag({
viewerIds: options.viewerIds,
labelIds: options.labelIds,
force: options.force,
output: options.output,
});
}
catch (error) {
(0, errors_1.logError)(error instanceof Error ? error : new Error(String(error)));
process.exit(1);
}
});
tagAddCmd.addHelpText('after', `
Examples:
# Add single tag to single viewer
$ polyv-live-cli viewer tag add -V "viewer1" -l "1"
# Add multiple tags to single viewer
$ polyv-live-cli viewer tag add -V "viewer1" -l "1,2"
# Add tags to multiple viewers
$ polyv-live-cli viewer tag add -V "viewer1,viewer2,viewer3" -l "1"
# JSON output
$ polyv-live-cli viewer tag add -V "viewer1" -l "1" -o json
`);
const tagRemoveCmd = tagCmd
.command('remove')
.description('Remove tags from viewers')
.requiredOption('-V, --viewer-ids <ids>', 'comma-separated viewer IDs')
.requiredOption('-l, --label-ids <ids>', 'comma-separated numeric tag IDs from viewer tag list')
.option('-f, --force', 'skip confirmation prompt')
.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 viewerHandler = new viewer_handler_1.ViewerHandler(authConfig, serviceConfig);
await viewerHandler.removeViewerTag({
viewerIds: options.viewerIds,
labelIds: options.labelIds,
force: options.force,
output: options.output,
});
}
catch (error) {
(0, errors_1.logError)(error instanceof Error ? error : new Error(String(error)));
process.exit(1);
}
});
tagRemoveCmd.addHelpText('after', `
Examples:
# Remove single tag from single viewer
$ polyv-live-cli viewer tag remove -V "viewer1" -l "1"
# Remove multiple tags from single viewer
$ polyv-live-cli viewer tag remove -V "viewer1" -l "1,2"
# Remove tags from multiple viewers
$ polyv-live-cli viewer tag remove -V "viewer1,viewer2" -l "1"
# JSON output
$ polyv-live-cli viewer tag remove -V "viewer1" -l "1" -o json
`);
const labelCmd = viewerCmd.command('label');
labelCmd.description('Manage account labels and channel label refs');
labelCmd
.command('list')
.description('List account labels')
.option('--page <number>', 'page number', api_command_1.parsePositiveInteger)
.option('--size <number>', 'page size', api_command_1.parsePositiveInteger)
.option('-o, --output <format>', 'output format (table|json)', validateOutputFormat, 'table')
.action(async (options) => {
await executeViewerCommand(program, (viewerHandler) => viewerHandler.listLabels({
page: options.page,
size: options.size,
output: options.output,
}));
});
labelCmd
.command('create')
.description('Create an account label')
.requiredOption('--label-name <name>', 'account label name (required, max 8 characters)')
.option('-f, --force', 'skip confirmation prompt')
.option('-o, --output <format>', 'output format (table|json)', validateOutputFormat, 'table')
.action(async (options) => {
await executeViewerCommand(program, (viewerHandler) => viewerHandler.createLabel({
labelName: options.labelName,
force: options.force,
output: options.output,
}));
});
labelCmd
.command('update')
.description('Update an account label')
.requiredOption('--label-id <id>', 'account label ID (string, from `viewer label list`)')
.requiredOption('--label-name <name>', 'account label name')
.option('-f, --force', 'skip confirmation prompt')
.option('-o, --output <format>', 'output format (table|json)', validateOutputFormat, 'table')
.action(async (options) => {
await executeViewerCommand(program, (viewerHandler) => viewerHandler.updateLabel({
labelId: options.labelId,
labelName: options.labelName,
force: options.force,
output: options.output,
}));
});
labelCmd
.command('delete')
.description('Delete an account label')
.requiredOption('--label-id <id>', 'account label ID (string, from `viewer label list`)')
.option('-f, --force', 'skip confirmation prompt')
.option('-o, --output <format>', 'output format (table|json)', validateOutputFormat, 'table')
.action(async (options) => {
await executeViewerCommand(program, (viewerHandler) => viewerHandler.deleteLabel({
labelId: options.labelId,
force: options.force,
output: options.output,
}));
});
const channelRefCmd = labelCmd.command('channel-ref');
channelRefCmd.description('Manage channel label refs');
channelRefCmd
.command('add')
.description('Add account label refs to channels')
.requiredOption('-c, --channel-ids <ids>', 'comma-separated channel IDs')
.requiredOption('-l, --label-ids <ids>', 'comma-separated account label IDs from viewer label list')
.option('-f, --force', 'skip confirmation prompt')
.option('-o, --output <format>', 'output format (table|json)', validateOutputFormat, 'table')
.action(async (options) => {
await executeViewerCommand(program, (viewerHandler) => viewerHandler.addChannelLabelRefs({
channelIds: options.channelIds,
labelIds: options.labelIds,
force: options.force,
output: options.output,
}));
});
}
function parseZeroBasedInteger(value) {
const parsed = Number(value);
if (!Number.isInteger(parsed) || parsed < 0) {
throw new Error('value must be a non-negative integer');
}
return parsed;
}
async function executeViewerCommand(program, action) {
try {
const parentOptions = program.opts();
const { authConfig, serviceConfig } = await loadAuthAndServiceConfig(parentOptions);
const viewerHandler = new viewer_handler_1.ViewerHandler(authConfig, serviceConfig);
await action(viewerHandler);
}
catch (error) {
(0, errors_1.logError)(error instanceof Error ? error : new Error(String(error)));
process.exit(1);
}
}
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,
};
return {
authConfig: authResult.config,
serviceConfig,
};
}
//# sourceMappingURL=viewer.commands.js.map