polyv-live-cli
Version:
CLI tool for managing PolyV live streaming services.
716 lines • 33.1 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ViewerHandler = void 0;
const base_handler_1 = require("./base.handler");
const viewer_service_1 = require("../services/viewer-service");
const errors_1 = require("../utils/errors");
const api_command_1 = require("../utils/api-command");
class ViewerHandler extends base_handler_1.BaseHandler {
constructor(authConfig, serviceConfig) {
super();
this.viewerService = new viewer_service_1.ViewerServiceSdk(authConfig, serviceConfig);
}
async getViewer(options) {
return this.executeWithErrorHandling(async () => {
this.validateGetOptions(options);
const result = await this.viewerService.getViewerRecord({
viewerUnionId: options.viewerId,
});
this.displayGetResult(result, options);
}, 'viewer.get');
}
async listViewers(options) {
return this.executeWithErrorHandling(async () => {
this.validatePaginationOptions(options);
const params = {
pageNumber: options.page ?? 1,
pageSize: options.size ?? 10,
};
if (options.source) {
params.source = options.source;
}
if (options.mobile) {
params.mobile = options.mobile;
}
if (options.email) {
params.email = options.email;
}
if (options.area) {
params.area = options.area;
}
const result = await this.viewerService.listViewerRecords(params);
this.displayListResult(result, options);
}, 'viewer.list');
}
async createViewer(options) {
return this.executeWithErrorHandling(async () => {
this.validateRequiredStringOptions(options, ['nickname', 'mobile']);
this.validateOutputOption(options.output);
await (0, api_command_1.confirmWrite)(options.force, `Create viewer "${options.nickname}"?`);
const params = this.compactParams({
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 ? this.parseJsonObject(options.followUsers, 'followUsers') : undefined,
});
const result = await this.viewerService.createViewerRecord(params);
this.displayWriteResult('观众创建成功', result, options.output);
}, 'viewer.create');
}
async updateViewer(options) {
return this.executeWithErrorHandling(async () => {
this.validateRequiredStringOptions(options, ['viewerUnionId']);
this.validateOutputOption(options.output);
await (0, api_command_1.confirmWrite)(options.force, `Update viewer ${options.viewerUnionId}?`);
const params = this.compactParams({
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,
});
await this.viewerService.updateViewerRecord(params);
this.displayWriteResult('观众更新成功', params, options.output);
}, 'viewer.update');
}
async deleteViewer(options) {
return this.executeWithErrorHandling(async () => {
this.validateRequiredStringOptions(options, ['viewerUnionId']);
this.validateOutputOption(options.output);
await (0, api_command_1.confirmWrite)(options.force, `Delete viewer ${options.viewerUnionId}?`);
await this.viewerService.deleteViewerRecord({ viewerUnionId: options.viewerUnionId });
this.displayWriteResult('观众删除成功', { viewerUnionId: options.viewerUnionId }, options.output);
}, 'viewer.delete');
}
async importExternalViewers(options) {
return this.executeWithErrorHandling(async () => {
this.validateOutputOption(options.output);
const viewers = this.buildExternalViewers(options);
await (0, api_command_1.confirmWrite)(options.force, `Import ${viewers.length} external viewer(s)?`);
const result = await this.viewerService.importExternalViewer(viewers);
this.displayWriteResult('外部观众导入成功', result, options.output);
}, 'viewer.importExternal');
}
async updateViewerConfig(options) {
return this.executeWithErrorHandling(async () => {
this.validateRequiredStringOptions(options, ['mobileLoginEnabled', 'wxWorkLoginEnabled']);
this.validateYnOptions(options, [
'mobileLoginEnabled',
'wxWorkLoginEnabled',
'collectMobileEnabled',
'guestModeEnabled',
'touristExternalHrefEnabled',
]);
this.validateViewerConfigOptions(options);
this.validateOutputOption(options.output);
await (0, api_command_1.confirmWrite)(options.force, 'Update viewer user system config?');
const params = this.compactParams({
mobileLoginEnabled: options.mobileLoginEnabled,
wxWorkLoginEnabled: options.wxWorkLoginEnabled,
viewerWeixinAuthExpired: options.viewerWeixinAuthExpired,
collectMobileEnabled: options.collectMobileEnabled,
guestModeEnabled: options.guestModeEnabled,
touristExternalHrefEnabled: options.touristExternalHrefEnabled,
touristExternalHrefConfig: options.touristExternalHrefConfig
? this.parseJsonObject(options.touristExternalHrefConfig, 'touristExternalHrefConfig')
: undefined,
});
await this.viewerService.updateViewerUserSystemConfig(params);
this.displayWriteResult('观众系统配置更新成功', params, options.output);
}, 'viewer.config.update');
}
async listViewerLotteryWins(options) {
return this.executeWithErrorHandling(async () => {
this.validateRequiredStringOptions(options, ['viewerId']);
this.validatePaginationOptions(options);
const result = await this.viewerService.viewerLotteryWin({
viewerId: options.viewerId,
pageNumber: options.page ?? 1,
pageSize: options.size ?? 10,
});
this.displayViewerLotteryWins(result, options);
}, 'viewer.lotteryWins');
}
async listViewerTags(options) {
return this.executeWithErrorHandling(async () => {
this.validatePaginationOptions(options);
const result = await this.viewerService.listViewerLabels();
let contents = (result?.contents || []).map(label => this.normalizeViewerLabel(label));
if (options.keyword) {
const keywordLower = options.keyword.toLowerCase();
contents = contents.filter((label) => label.labelName?.toLowerCase().includes(keywordLower));
}
const page = options.page ?? 1;
const size = options.size ?? 10;
const startIndex = (page - 1) * size;
const paginatedContents = contents.slice(startIndex, startIndex + size);
this.displayTagListResult({
contents: paginatedContents,
totalItems: contents.length,
pageNumber: page,
pageSize: size,
totalPages: Math.ceil(contents.length / size),
}, options);
}, 'viewer.tag.list');
}
async createViewerTag(options) {
return this.executeWithErrorHandling(async () => {
this.validateRequiredStringOptions(options, ['labels']);
this.validateOutputOption(options.output);
const labels = this.parseStringList(options.labels, '标签名称列表');
await (0, api_command_1.confirmWrite)(options.force, `Create ${labels.length} viewer tag(s)?`);
const result = await this.viewerService.createViewerLabel({ labels });
this.displayWriteResult('观众标签创建成功', result, options.output);
}, 'viewer.tag.create');
}
async updateViewerTag(options) {
return this.executeWithErrorHandling(async () => {
this.validatePositiveNumber(options.id, '标签ID');
this.validateOutputOption(options.output);
await (0, api_command_1.confirmWrite)(options.force, `Update viewer tag ${options.id}?`);
const params = this.compactParams({ id: options.id, label: options.label });
await this.viewerService.updateViewerLabel(params);
this.displayWriteResult('观众标签更新成功', params, options.output);
}, 'viewer.tag.update');
}
async deleteViewerTag(options) {
return this.executeWithErrorHandling(async () => {
this.validatePositiveNumber(options.id, '标签ID');
this.validateOutputOption(options.output);
await (0, api_command_1.confirmWrite)(options.force, `Delete viewer tag ${options.id}?`);
await this.viewerService.deleteViewerLabel({ id: options.id });
this.displayWriteResult('观众标签删除成功', { id: options.id }, options.output);
}, 'viewer.tag.delete');
}
async addViewerTag(options) {
return this.executeWithErrorHandling(async () => {
const { viewerIds, labelIds } = this.parseAndValidateTagOptions(options);
await (0, api_command_1.confirmWrite)(options.force, `Add ${labelIds.length} tag(s) to ${viewerIds.length} viewer(s)?`);
const total = viewerIds.length * labelIds.length;
if (total > 10) {
this.displayInfo(`正在为 ${viewerIds.length} 个观众添加 ${labelIds.length} 个标签 (共 ${total} 个操作)...`);
}
const summary = await this.viewerService.addViewersLabels(viewerIds, labelIds, total > 50 ? (completed, total) => {
if (completed % 10 === 0 || completed === total) {
this.displayInfo(`进度: ${completed}/${total}`);
}
} : undefined);
this.displayTagActionResult('添加', summary, options);
}, 'viewer.tag.add');
}
async removeViewerTag(options) {
return this.executeWithErrorHandling(async () => {
const { viewerIds, labelIds } = this.parseAndValidateTagOptions(options);
await (0, api_command_1.confirmWrite)(options.force, `Remove ${labelIds.length} tag(s) from ${viewerIds.length} viewer(s)?`);
const total = viewerIds.length * labelIds.length;
if (total > 10) {
this.displayInfo(`正在为 ${viewerIds.length} 个观众移除 ${labelIds.length} 个标签 (共 ${total} 个操作)...`);
}
const summary = await this.viewerService.removeViewersLabels(viewerIds, labelIds, total > 50 ? (completed, total) => {
if (completed % 10 === 0 || completed === total) {
this.displayInfo(`进度: ${completed}/${total}`);
}
} : undefined);
this.displayTagActionResult('移除', summary, options);
}, 'viewer.tag.remove');
}
async listLabels(options) {
return this.executeWithErrorHandling(async () => {
this.validatePaginationOptions(options);
const result = await this.viewerService.listLabels({
pageNumber: options.page ?? 1,
pageSize: options.size ?? 10,
});
this.displayAccountLabels(result, options);
}, 'viewer.label.list');
}
async createLabel(options) {
return this.executeWithErrorHandling(async () => {
this.validateRequiredStringOptions(options, ['labelName']);
this.validateAccountLabelName(options.labelName);
this.validateOutputOption(options.output);
await (0, api_command_1.confirmWrite)(options.force, `Create account label "${options.labelName}"?`);
const result = await this.viewerService.createLabel({ labelName: options.labelName });
this.displayWriteResult('账号标签创建成功', result, options.output);
}, 'viewer.label.create');
}
async updateLabel(options) {
return this.executeWithErrorHandling(async () => {
this.validateRequiredStringOptions({ labelId: options.labelId }, ['labelId']);
this.validateRequiredStringOptions(options, ['labelName']);
this.validateOutputOption(options.output);
await (0, api_command_1.confirmWrite)(options.force, `Update account label ${options.labelId}?`);
const params = { labelId: String(options.labelId), labelName: options.labelName };
await this.viewerService.updateLabel(params);
this.displayWriteResult('账号标签更新成功', params, options.output);
}, 'viewer.label.update');
}
async deleteLabel(options) {
return this.executeWithErrorHandling(async () => {
this.validateRequiredStringOptions({ labelId: options.labelId }, ['labelId']);
this.validateOutputOption(options.output);
await (0, api_command_1.confirmWrite)(options.force, `Delete account label ${options.labelId}?`);
await this.viewerService.deleteLabel({ labelId: String(options.labelId) });
this.displayWriteResult('账号标签删除成功', { labelId: options.labelId }, options.output);
}, 'viewer.label.delete');
}
async addChannelLabelRefs(options) {
return this.executeWithErrorHandling(async () => {
this.validateRequiredStringOptions(options, ['channelIds', 'labelIds']);
this.validateOutputOption(options.output);
const channelIds = this.parseStringList(options.channelIds, '频道ID列表');
const labelIds = this.parseStringList(options.labelIds, '标签ID列表');
this.validateChannelLabelRefs(channelIds, labelIds);
await (0, api_command_1.confirmWrite)(options.force, `Add ${labelIds.length} label(s) to ${channelIds.length} channel(s)?`);
const params = { channelIds, labelIds };
await this.viewerService.addChannelLabelRefs(params);
this.displayWriteResult('频道标签关联成功', params, options.output);
}, 'viewer.label.channelRef.add');
}
compactParams(params) {
return Object.fromEntries(Object.entries(params).filter(([, value]) => value !== undefined && value !== ''));
}
validateRequiredStringOptions(options, fields) {
const record = options;
const errors = fields
.filter(field => typeof record[field] !== 'string' || String(record[field]).trim() === '')
.map(field => `${field} 是必需的`);
if (errors.length > 0) {
throw new errors_1.PolyVValidationError(errors.join(', '), 'options', options, 'validation_failed');
}
}
validateOutputOption(output) {
if (output && !['table', 'json'].includes(output)) {
throw new errors_1.PolyVValidationError('输出格式必须是 "table" 或 "json"', 'output', output, 'validation_failed');
}
}
validateYnOptions(options, fields) {
const record = options;
const invalid = fields.filter(field => {
const value = record[field];
return value !== undefined && value !== 'Y' && value !== 'N';
});
if (invalid.length > 0) {
throw new errors_1.PolyVValidationError(`${invalid.join(', ')} 必须是 Y 或 N`, 'options', options, 'validation_failed');
}
}
validatePositiveNumber(value, fieldName) {
if (typeof value !== 'number' || !Number.isInteger(value) || value <= 0) {
throw new errors_1.PolyVValidationError(`${fieldName}必须是正整数`, fieldName, value, 'validation_failed');
}
}
validateAccountLabelName(labelName) {
if (Array.from(labelName.trim()).length > 8) {
throw new errors_1.PolyVValidationError('标签名称最大长度为 8', 'labelName', labelName, 'length_exceeded');
}
}
validateViewerConfigOptions(options) {
const errors = [];
if (options.viewerWeixinAuthExpired !== undefined) {
const value = options.viewerWeixinAuthExpired;
if (!Number.isInteger(value) || value < 0 || value > 180) {
errors.push('微信授权有效期必须是 0 到 180 之间的整数');
}
}
if (options.touristExternalHrefConfig) {
try {
this.parseJsonObject(options.touristExternalHrefConfig, 'touristExternalHrefConfig');
}
catch (error) {
errors.push(error instanceof Error ? error.message : String(error));
}
}
if (errors.length > 0) {
throw new errors_1.PolyVValidationError(errors.join(', '), 'options', options, 'validation_failed');
}
}
validateChannelLabelRefs(channelIds, labelIds) {
const errors = [];
if (channelIds.length > 100) {
errors.push('频道ID数量不能超过 100');
}
if (labelIds.length > 50) {
errors.push('标签ID数量不能超过 50');
}
if (errors.length > 0) {
throw new errors_1.PolyVValidationError(errors.join(', '), 'options', { channelIds, labelIds }, 'validation_failed');
}
}
parseJsonObject(value, fieldName) {
try {
const parsed = JSON.parse(value);
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
throw new Error(`${fieldName} 必须是 JSON 对象`);
}
return parsed;
}
catch (error) {
if (error instanceof errors_1.PolyVValidationError) {
throw error;
}
throw new errors_1.PolyVValidationError(error instanceof Error ? error.message : `${fieldName} JSON 解析失败`, fieldName, value, 'validation_failed');
}
}
parseStringList(value, fieldName) {
const list = value.split(',').map(item => item.trim()).filter(Boolean);
if (list.length === 0) {
throw new errors_1.PolyVValidationError(`${fieldName}不能为空`, fieldName, value, 'validation_failed');
}
return Array.from(new Set(list));
}
buildExternalViewers(options) {
const errors = [];
if (options.viewers) {
try {
const parsed = JSON.parse(options.viewers);
if (!Array.isArray(parsed) || parsed.length === 0) {
errors.push('viewers 必须是非空 JSON 数组');
}
else {
for (const [index, item] of parsed.entries()) {
if (!item || typeof item !== 'object' || Array.isArray(item)) {
errors.push(`viewers[${index}] 必须是 JSON 对象`);
continue;
}
const viewer = item;
if (typeof viewer.externalViewerId !== 'string' || viewer.externalViewerId.trim() === '') {
errors.push(`viewers[${index}].externalViewerId 是必需的`);
}
if (typeof viewer.nickname !== 'string' || viewer.nickname.trim() === '') {
errors.push(`viewers[${index}].nickname 是必需的`);
}
}
if (errors.length === 0) {
return parsed;
}
}
}
catch (error) {
errors.push(error instanceof Error ? error.message : 'viewers JSON 解析失败');
}
}
else {
if (!options.externalViewerId || options.externalViewerId.trim() === '') {
errors.push('externalViewerId 是必需的');
}
if (!options.nickname || options.nickname.trim() === '') {
errors.push('nickname 是必需的');
}
if (errors.length === 0) {
const viewer = {
externalViewerId: options.externalViewerId,
nickname: options.nickname,
};
if (options.labelIds) {
viewer.labelIds = this.parseStringList(options.labelIds, '标签ID列表');
}
if (options.followUserId) {
viewer.followUsers = {
userId: options.followUserId,
...(options.followUserType ? { type: options.followUserType } : {}),
};
}
return [viewer];
}
}
throw new errors_1.PolyVValidationError(errors.join(', '), 'options', options, 'validation_failed');
}
validateGetOptions(options) {
const errors = [];
if (!options.viewerId || options.viewerId.trim() === '') {
errors.push('观众ID是必需的');
}
if (options.output && !['table', 'json'].includes(options.output)) {
errors.push('输出格式必须是 "table" 或 "json"');
}
if (errors.length > 0) {
throw new errors_1.PolyVValidationError(errors.join(', '), 'options', options, 'validation_failed');
}
}
validatePaginationOptions(options) {
const errors = [];
if (options.page !== undefined) {
if (typeof options.page !== 'number' || !Number.isInteger(options.page) || options.page < 1) {
errors.push('页码必须是正整数');
}
}
if (options.size !== undefined) {
if (typeof options.size !== 'number' || !Number.isInteger(options.size) || options.size < 1) {
errors.push('每页数量必须是正整数');
}
if (options.size > 1000) {
errors.push('每页数量不能超过 1000');
}
}
if (options.output && !['table', 'json'].includes(options.output)) {
errors.push('输出格式必须是 "table" 或 "json"');
}
if (errors.length > 0) {
throw new errors_1.PolyVValidationError(errors.join(', '), 'options', options, 'validation_failed');
}
}
parseAndValidateTagOptions(options) {
const errors = [];
if (!options.viewerIds || options.viewerIds.trim() === '') {
errors.push('观众ID列表是必需的');
}
if (!options.labelIds || options.labelIds.trim() === '') {
errors.push('标签ID列表是必需的');
}
let viewerIds = [];
if (options.viewerIds && options.viewerIds.trim() !== '') {
viewerIds = options.viewerIds
.split(',')
.map(id => id.trim())
.filter(id => id.length > 0);
if (viewerIds.length === 0) {
errors.push('观众ID列表不能为空');
}
}
let labelIds = [];
if (options.labelIds && options.labelIds.trim() !== '') {
const rawLabelIds = Array.from(new Set(options.labelIds
.split(',')
.map(id => id.trim())
.filter(id => id.length > 0)));
if (rawLabelIds.length === 0) {
errors.push('标签ID列表不能为空');
}
rawLabelIds.forEach(id => {
const numericId = Number(id);
if (!Number.isInteger(numericId) || numericId <= 0) {
errors.push(`无效的标签ID: ${id},请使用 viewer tag list 返回的数字标签ID`);
}
else {
labelIds.push(numericId);
}
});
}
if (options.output && !['table', 'json'].includes(options.output)) {
errors.push('输出格式必须是 "table" 或 "json"');
}
if (errors.length > 0) {
throw new errors_1.PolyVValidationError(errors.join(', '), 'options', options, 'validation_failed');
}
return { viewerIds, labelIds };
}
displayGetResult(result, options) {
if (!result) {
this.displayInfo(`未找到观众: ${options.viewerId}`);
return;
}
const output = options.output || 'table';
if (output === 'json') {
this.displayData(result, 'json');
}
else {
console.log('观众详情:');
const tableData = {
'观众ID': this.truncate(result.viewerUnionId || '-', 30),
'昵称': result.nickname || '-',
'手机号': result.mobile || '-',
'来源': result.source || '-',
'姓名': result.name || '-',
'邮箱': result.email || '-',
'地区': result.area || '-',
'观看时长': this.formatDuration(result.watchDuration),
'观看频道数': result.watchChannelCount?.toString() || '-',
'创建时间': this.formatTime(result.createTime),
};
this.displayAsTable(tableData);
}
}
displayListResult(result, options) {
const contents = result?.contents || [];
const totalItems = result?.totalItems || 0;
if (totalItems === 0) {
this.displayInfo('未找到观众');
return;
}
const output = options.output || 'table';
if (output === 'json') {
this.displayData({
pageNumber: result.pageNumber,
pageSize: result.pageSize,
totalPages: result.totalPages,
totalItems: result.totalItems,
contents: contents
}, 'json');
}
else {
const tableData = contents.map((item) => ({
'观众ID': this.truncate(item.viewerUnionId || '-', 20),
'昵称': this.truncate(item.nickname || '-', 20),
'手机号': item.mobile || '-',
'来源': item.source || '-',
'观看时长': this.formatDuration(item.watchDuration),
'频道数': item.watchChannelCount?.toString() || '-',
'创建时间': this.formatTime(item.createTime),
}));
console.log(`找到 ${totalItems} 个观众`);
console.log(`页码: ${result.pageNumber}, 每页: ${result.pageSize}`);
console.log(`总页数: ${result.totalPages}`);
if (tableData.length > 0) {
this.displayAsTable(tableData);
}
}
}
displayWriteResult(message, data, output) {
const format = output || 'table';
if (format === 'json') {
this.displayData({
success: true,
data,
}, 'json');
}
else {
this.displaySuccess(message, data, 'table');
}
}
displayViewerLotteryWins(result, options) {
const contents = result?.contents || [];
const totalItems = result?.totalItems || 0;
const output = options.output || 'table';
if (output === 'json') {
this.displayData(result, 'json');
return;
}
if (totalItems === 0) {
this.displayInfo('未找到中奖记录');
return;
}
console.log(`找到 ${totalItems} 条中奖记录`);
console.log(`页码: ${result.pageNumber}, 每页: ${result.pageSize}`);
this.displayAsTable(contents.map(item => ({
'频道ID': item.channelId?.toString() || '-',
'频道名称': this.truncate(item.channelName || '-', 24),
'活动名称': this.truncate(item.activityName || '-', 24),
'奖品': this.truncate(item.prize || '-', 24),
'中奖码': item.winnerCode || '-',
'已领奖': item.received === undefined ? '-' : (item.received ? '是' : '否'),
'中奖时间': this.formatTime(item.createdTime),
})));
}
displayAccountLabels(result, options) {
const contents = result?.contents || [];
const totalItems = result?.totalItems || 0;
const output = options.output || 'table';
if (output === 'json') {
this.displayData(result, 'json');
return;
}
if (totalItems === 0) {
this.displayInfo('未找到账号标签');
return;
}
console.log(`找到 ${totalItems} 个账号标签`);
console.log(`页码: ${result.pageNumber}, 每页: ${result.pageSize}`);
this.displayAsTable(contents.map(item => ({
'标签ID': item.id?.toString() || '-',
'标签名称': this.truncate(item.name || '-', 30),
})));
}
displayTagListResult(result, options) {
const contents = result?.contents || [];
const totalItems = result?.totalItems || 0;
if (totalItems === 0) {
const message = options.keyword
? `未找到包含关键词 "${options.keyword}" 的标签`
: '未找到标签';
this.displayInfo(message);
return;
}
const output = options.output || 'table';
if (output === 'json') {
this.displayData({
pageNumber: result.pageNumber,
pageSize: result.pageSize,
totalPages: result.totalPages,
totalItems: result.totalItems,
contents: contents
}, 'json');
}
else {
const tableData = contents.map((item) => ({
'标签ID': item.labelId?.toString() || '-',
'标签名称': this.truncate(item.labelName || '-', 30),
}));
console.log(`找到 ${totalItems} 个标签`);
console.log(`页码: ${result.pageNumber}, 每页: ${result.pageSize}`);
if (tableData.length > 0) {
this.displayAsTable(tableData);
}
}
}
displayTagActionResult(action, summary, options) {
const output = options.output || 'table';
if (output === 'json') {
this.displayData({
action,
succeeded: summary.succeeded,
failed: summary.failed,
total: summary.total,
results: summary.results,
}, 'json');
}
else {
if (summary.failed === 0) {
this.displaySuccess(`成功${action} ${summary.succeeded} 个操作`);
}
else {
this.displayWarning(`${action}完成: 成功 ${summary.succeeded} 个, 失败 ${summary.failed} 个`);
const failures = summary.results.filter(r => !r.success);
if (failures.length > 0 && failures.length <= 5) {
for (const f of failures) {
console.log(` - 观众 ${f.viewerUnionId}, 标签 ${f.labelId}: ${f.error}`);
}
}
}
}
}
normalizeViewerLabel(label) {
const id = label.id;
const labelId = typeof id === 'number' ? id : String(id);
return {
labelId,
labelName: label.label,
};
}
truncate(str, maxLength) {
if (!str)
return '-';
return str.length > maxLength ? str.substring(0, maxLength) + '...' : str;
}
formatDuration(seconds) {
if (!seconds)
return '-';
const hours = Math.floor(seconds / 3600);
const minutes = Math.floor((seconds % 3600) / 60);
const secs = seconds % 60;
if (hours > 0) {
return `${hours}小时 ${minutes}分钟`;
}
else if (minutes > 0) {
return `${minutes}分钟 ${secs}秒`;
}
else {
return `${secs}秒`;
}
}
formatTime(timestamp) {
if (!timestamp)
return '-';
return new Date(timestamp).toLocaleString();
}
}
exports.ViewerHandler = ViewerHandler;
//# sourceMappingURL=viewer.handler.js.map