UNPKG

polyv-live-cli

Version:

CLI tool for managing PolyV live streaming services.

672 lines 31 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.ChannelService = void 0; const axios_1 = __importDefault(require("axios")); const signature_1 = require("../utils/signature"); const errors_1 = require("../utils/errors"); class ChannelService { constructor(authConfig, serviceConfig) { this.authConfig = authConfig; this.config = serviceConfig; this.httpClient = this.createHttpClient(); } async createChannel(request) { try { this.validateCreateRequest(request); const timestamp = Date.now(); const signatureParams = { appId: this.authConfig.appId, timestamp, ...(this.authConfig.userId && { userId: this.authConfig.userId }) }; const signatureResult = (0, signature_1.generateSignature)(signatureParams, { appSecret: this.authConfig.appSecret, debug: this.config.debug }); const url = '/live/v4/channel/create'; const params = { appId: this.authConfig.appId, timestamp: signatureResult.timestamp, sign: signatureResult.signature, ...(this.authConfig.userId && { userId: this.authConfig.userId }) }; if (this.config.debug) { console.log('[ChannelService] Creating channel with request:', request); console.log('[ChannelService] API URL:', `${this.config.baseUrl}${url}`); console.log('[ChannelService] Auth params:', params); } const response = await this.httpClient.post(url, request, { params }); return this.transformToChannelModel(response.data, request); } catch (error) { if (error instanceof errors_1.PolyVError) { throw error; } if (axios_1.default.isAxiosError(error)) { const apiError = this.transformAxiosError(error); throw apiError; } throw new errors_1.PolyVError('Failed to create channel due to unexpected error', 'CHANNEL_CREATE_UNEXPECTED_ERROR', 500, { originalError: error instanceof Error ? error.message : String(error), request: this.sanitizeRequest(request) }); } } async listChannels(request = {}) { try { this.validateListRequest(request); const pageNumber = request.page ?? 1; const pageSize = request.limit ?? 20; const timestamp = Date.now(); const signatureParams = { appId: this.authConfig.appId, timestamp, pageNumber, pageSize, ...(this.authConfig.userId && { userId: this.authConfig.userId }) }; if (request.categoryId) { signatureParams.categoryId = request.categoryId; } if (request.keyword) { signatureParams.keyword = request.keyword; } const signatureResult = (0, signature_1.generateSignature)(signatureParams, { appSecret: this.authConfig.appSecret, debug: this.config.debug }); const url = '/live/v4/channel/detail/list'; const params = { appId: this.authConfig.appId, timestamp: signatureResult.timestamp, sign: signatureResult.signature, pageNumber, pageSize, ...(this.authConfig.userId && { userId: this.authConfig.userId }) }; if (request.categoryId) { params.categoryId = request.categoryId; } if (request.keyword) { params.keyword = request.keyword; } if (this.config.debug) { console.log('[ChannelService] Listing channels with request:', request); console.log('[ChannelService] API URL:', `${this.config.baseUrl}${url}`); console.log('[ChannelService] Auth params:', params); } const response = await this.httpClient.get(url, { params }); if (!response.data || response.data.code !== 200) { const errorMessage = response.data?.error?.desc || 'Unknown API error'; throw new errors_1.PolyVAPIError(`Failed to list channels: ${errorMessage}`, 'CHANNEL_LIST_API_ERROR', response.data?.code || 500, { polyvCode: response.data?.error?.code, polyvMessage: errorMessage }); } if (!response.data.data?.contents || response.data.data.contents.length === 0) { return []; } const channelDetails = response.data.data.contents.map((channel) => ({ channelId: String(channel.channelId), name: channel.name, status: channel.watchStatus, createdAt: new Date(channel.startTime || Date.now()), scene: channel.newScene || channel.scene, template: channel.template, description: channel.content || '', ...(channel.maxViewer && channel.maxViewer > 0 && { maxViewers: channel.maxViewer }) })); return channelDetails; } catch (error) { if (error instanceof errors_1.PolyVError) { throw error; } if (axios_1.default.isAxiosError(error)) { const apiError = this.transformAxiosError(error); throw apiError; } throw new errors_1.PolyVError('Failed to list channels due to unexpected error', 'CHANNEL_LIST_UNEXPECTED_ERROR', 500, { originalError: error instanceof Error ? error.message : String(error), request: this.sanitizeRequest(request) }); } } async getChannelDetail(request) { try { this.validateChannelDetailRequest(request); const timestamp = Date.now(); const signatureParams = { appId: this.authConfig.appId, timestamp, channelId: request.channelId, ...(this.authConfig.userId && { userId: this.authConfig.userId }) }; const signatureResult = (0, signature_1.generateSignature)(signatureParams, { appSecret: this.authConfig.appSecret, debug: this.config.debug }); const url = '/live/v4/channel/basic/get'; const params = { appId: this.authConfig.appId, timestamp: signatureResult.timestamp, sign: signatureResult.signature, channelId: request.channelId, ...(this.authConfig.userId && { userId: this.authConfig.userId }) }; if (this.config.debug) { console.log('[ChannelService] Getting channel detail with request:', request); console.log('[ChannelService] API URL:', `${this.config.baseUrl}${url}`); console.log('[ChannelService] Auth params:', params); } const response = await this.httpClient.get(url, { params }); if (!response.data || response.data.code !== 200) { const errorMessage = response.data?.error?.desc || 'Unknown API error'; throw new errors_1.PolyVAPIError(`Failed to get channel detail: ${errorMessage}`, 'CHANNEL_DETAIL_API_ERROR', response.data?.code || 500, { polyvCode: response.data?.error?.code, polyvMessage: errorMessage, channelId: request.channelId }); } if (!response.data.data) { throw new errors_1.PolyVAPIError('Invalid API response: missing channel data', 'MISSING_CHANNEL_DATA', 500, { response: response.data, channelId: request.channelId }); } return response.data.data; } catch (error) { if (error instanceof errors_1.PolyVError) { throw error; } if (axios_1.default.isAxiosError(error)) { const apiError = this.transformAxiosError(error); throw apiError; } throw new errors_1.PolyVError('Failed to get channel detail due to unexpected error', 'CHANNEL_DETAIL_UNEXPECTED_ERROR', 500, { originalError: error instanceof Error ? error.message : String(error), request: this.sanitizeRequest(request) }); } } async updateChannel(request) { try { this.validateChannelUpdateRequest(request); const timestamp = Date.now(); const signatureParams = { appId: this.authConfig.appId, timestamp, channelId: request.channelId, ...(this.authConfig.userId && { userId: this.authConfig.userId }) }; const signatureResult = (0, signature_1.generateSignature)(signatureParams, { appSecret: this.authConfig.appSecret, debug: this.config.debug }); const url = '/live/v3/channel/basic/update'; const params = { appId: this.authConfig.appId, timestamp: signatureResult.timestamp, sign: signatureResult.signature, channelId: request.channelId, ...(this.authConfig.userId && { userId: this.authConfig.userId }) }; const requestBody = {}; if (request.basicSetting) { requestBody.basicSetting = request.basicSetting; } if (request.authSettings) { requestBody.authSettings = request.authSettings; } if (this.config.debug) { console.log('[ChannelService] Updating channel with request:', request); console.log('[ChannelService] API URL:', `${this.config.baseUrl}${url}`); console.log('[ChannelService] Auth params:', params); console.log('[ChannelService] Request body:', this.sanitizeRequest(requestBody)); } const response = await this.httpClient.post(url, requestBody, { params }); if (!response.data || response.data.code !== 200) { const errorMessage = response.data?.error?.desc || 'Unknown API error'; throw new errors_1.PolyVAPIError(`Failed to update channel: ${errorMessage}`, 'CHANNEL_UPDATE_API_ERROR', response.data?.code || 500, { polyvCode: response.data?.error?.code, polyvMessage: errorMessage, channelId: request.channelId }); } return response.data; } catch (error) { if (error instanceof errors_1.PolyVError) { throw error; } if (axios_1.default.isAxiosError(error)) { const apiError = this.transformAxiosError(error); throw apiError; } throw new errors_1.PolyVError('Failed to update channel due to unexpected error', 'CHANNEL_UPDATE_UNEXPECTED_ERROR', 500, { originalError: error instanceof Error ? error.message : String(error), request: this.sanitizeRequest(request) }); } } async deleteChannel(channelId) { try { this.validateChannelId(channelId); const deleteRequest = { channelIds: [channelId] }; const response = await this.batchDeleteChannels(deleteRequest); if (this.config.debug) { console.log('[ChannelService] Single channel delete completed:', { channelId, success: response.data === true }); } return response; } catch (error) { if (error instanceof errors_1.PolyVError) { throw error; } throw new errors_1.PolyVError('Failed to delete channel due to unexpected error', 'CHANNEL_DELETE_UNEXPECTED_ERROR', 500, { originalError: error instanceof Error ? error.message : String(error), channelId }); } } async batchDeleteChannels(request) { try { this.validateBatchDeleteRequest(request); const timestamp = Date.now(); const signatureParams = { appId: this.authConfig.appId, timestamp, ...(this.authConfig.userId && { userId: this.authConfig.userId }) }; const signatureResult = (0, signature_1.generateSignature)(signatureParams, { appSecret: this.authConfig.appSecret, debug: this.config.debug }); const url = '/live/v3/channel/basic/batch-delete'; const params = { appId: this.authConfig.appId, timestamp: signatureResult.timestamp, sign: signatureResult.signature, ...(this.authConfig.userId && { userId: this.authConfig.userId }) }; const requestBody = { channelIds: request.channelIds }; if (this.config.debug) { console.log('[ChannelService] Batch deleting channels with request:', request); console.log('[ChannelService] API URL:', `${this.config.baseUrl}${url}`); console.log('[ChannelService] Auth params:', params); console.log('[ChannelService] Request body:', requestBody); } const response = await this.httpClient.post(url, requestBody, { params, headers: { 'Content-Type': 'application/json' } }); if (!response.data || response.data.code !== 200) { const errorMessage = response.data?.message || 'Unknown API error'; throw new errors_1.PolyVAPIError(`Failed to delete channels: ${errorMessage}`, 'CHANNEL_DELETE_API_ERROR', response.data?.code || 500, { polyvMessage: errorMessage, channelIds: request.channelIds }); } return response.data; } catch (error) { if (error instanceof errors_1.PolyVError) { throw error; } if (axios_1.default.isAxiosError(error)) { const apiError = this.transformAxiosError(error); throw apiError; } throw new errors_1.PolyVError('Failed to delete channels due to unexpected error', 'CHANNEL_DELETE_UNEXPECTED_ERROR', 500, { originalError: error instanceof Error ? error.message : String(error), request: this.sanitizeRequest(request) }); } } validateListRequest(request) { const errors = []; if (request.page !== undefined) { if (typeof request.page !== 'number' || !Number.isInteger(request.page) || request.page < 1) { errors.push('page must be a positive integer (minimum 1)'); } } if (request.limit !== undefined) { if (typeof request.limit !== 'number' || !Number.isInteger(request.limit) || request.limit < 1 || request.limit > 100) { errors.push('limit must be an integer between 1 and 100'); } } if (request.categoryId !== undefined && (typeof request.categoryId !== 'string' || request.categoryId.trim().length === 0)) { errors.push('categoryId must be a non-empty string'); } if (request.keyword !== undefined && (typeof request.keyword !== 'string' || request.keyword.trim().length === 0)) { errors.push('keyword must be a non-empty string'); } if (request.labelId !== undefined && (typeof request.labelId !== 'string' || request.labelId.trim().length === 0)) { errors.push('labelId must be a non-empty string'); } if (errors.length > 0) { throw new errors_1.PolyVValidationError(`Channel list request validation failed: ${errors.join(', ')}`, 'request', request, 'validation_failed'); } } validateChannelDetailRequest(request) { const errors = []; if (!request.channelId) { errors.push('channelId is required'); } else if (typeof request.channelId !== 'string') { errors.push('channelId must be a string'); } else if (request.channelId.trim().length === 0) { errors.push('channelId cannot be empty'); } if (errors.length > 0) { throw new errors_1.PolyVValidationError(`Channel detail request validation failed: ${errors.join(', ')}`, 'request', request, 'validation_failed'); } } validateChannelUpdateRequest(request) { const errors = []; if (!request.channelId) { errors.push('channelId is required'); } else if (typeof request.channelId !== 'string') { errors.push('channelId must be a string'); } else if (request.channelId.trim().length === 0) { errors.push('channelId cannot be empty'); } if (!request.basicSetting && !request.authSettings) { errors.push('at least one update field (basicSetting or authSettings) must be provided'); } if (request.basicSetting) { const basicSetting = request.basicSetting; if (basicSetting.name !== undefined) { if (typeof basicSetting.name !== 'string') { errors.push('basicSetting.name must be a string'); } else if (basicSetting.name.length > 100) { errors.push('basicSetting.name cannot exceed 100 characters'); } } if (basicSetting.channelPasswd !== undefined) { if (typeof basicSetting.channelPasswd !== 'string') { errors.push('basicSetting.channelPasswd must be a string'); } else if (basicSetting.channelPasswd.length < 6 || basicSetting.channelPasswd.length > 16) { errors.push('basicSetting.channelPasswd must be 6-16 characters long'); } else if (!/^[a-zA-Z0-9]+$/.test(basicSetting.channelPasswd)) { errors.push('basicSetting.channelPasswd must contain only alphanumeric characters'); } } if (basicSetting.publisher !== undefined && typeof basicSetting.publisher !== 'string') { errors.push('basicSetting.publisher must be a string'); } if (basicSetting.desc !== undefined) { if (typeof basicSetting.desc !== 'string') { errors.push('basicSetting.desc must be a string'); } else if (basicSetting.desc.length > 500) { errors.push('basicSetting.desc cannot exceed 500 characters'); } } if (basicSetting.startTime !== undefined) { if (typeof basicSetting.startTime !== 'number' || basicSetting.startTime < 0) { errors.push('basicSetting.startTime must be a non-negative timestamp'); } } if (basicSetting.endTime !== undefined) { if (typeof basicSetting.endTime !== 'number' || basicSetting.endTime < 0) { errors.push('basicSetting.endTime must be a non-negative timestamp'); } if (basicSetting.startTime !== undefined && basicSetting.endTime <= basicSetting.startTime) { errors.push('basicSetting.endTime must be greater than startTime'); } } if (basicSetting.pageView !== undefined) { if (typeof basicSetting.pageView !== 'number' || basicSetting.pageView < 0) { errors.push('basicSetting.pageView must be a non-negative number'); } } if (basicSetting.likes !== undefined) { if (typeof basicSetting.likes !== 'number' || basicSetting.likes < 0) { errors.push('basicSetting.likes must be a non-negative number'); } } if (basicSetting.maxViewer !== undefined) { if (typeof basicSetting.maxViewer !== 'number' || basicSetting.maxViewer <= 0) { errors.push('basicSetting.maxViewer must be a positive number'); } } if (basicSetting.maxViewerRestrict !== undefined && !['Y', 'N'].includes(basicSetting.maxViewerRestrict)) { errors.push('basicSetting.maxViewerRestrict must be "Y" or "N"'); } if (basicSetting.closeDanmu !== undefined && !['Y', 'N'].includes(basicSetting.closeDanmu)) { errors.push('basicSetting.closeDanmu must be "Y" or "N"'); } if (basicSetting.coverImg !== undefined && typeof basicSetting.coverImg !== 'string') { errors.push('basicSetting.coverImg must be a string'); } if (basicSetting.splashImg !== undefined && typeof basicSetting.splashImg !== 'string') { errors.push('basicSetting.splashImg must be a string'); } } if (request.authSettings) { if (!Array.isArray(request.authSettings)) { errors.push('authSettings must be an array'); } } if (errors.length > 0) { throw new errors_1.PolyVValidationError(`Channel update request validation failed: ${errors.join(', ')}`, 'request', request, 'validation_failed'); } } validateChannelId(channelId) { const errors = []; if (!channelId) { errors.push('channelId is required'); } else if (typeof channelId !== 'string') { errors.push('channelId must be a string'); } else if (channelId.trim().length === 0) { errors.push('channelId cannot be empty'); } if (errors.length > 0) { throw new errors_1.PolyVValidationError(`Channel ID validation failed: ${errors.join(', ')}`, 'channelId', channelId, 'validation_failed'); } } validateBatchDeleteRequest(request) { const errors = []; if (!request.channelIds) { errors.push('channelIds is required'); } else if (!Array.isArray(request.channelIds)) { errors.push('channelIds must be an array'); } else if (request.channelIds.length === 0) { errors.push('channelIds cannot be empty'); } else if (request.channelIds.length > 100) { errors.push('channelIds cannot exceed 100 channels per batch'); } else { for (let i = 0; i < request.channelIds.length; i++) { const channelId = request.channelIds[i]; if (typeof channelId !== 'string') { errors.push(`channelIds[${i}] must be a string`); } else if (channelId.trim().length === 0) { errors.push(`channelIds[${i}] cannot be empty`); } } } if (errors.length > 0) { throw new errors_1.PolyVValidationError(`Channel batch delete request validation failed: ${errors.join(', ')}`, 'request', request, 'validation_failed'); } } createHttpClient() { const client = axios_1.default.create({ baseURL: this.config.baseUrl, timeout: this.config.timeout, headers: { 'Content-Type': 'application/json', 'User-Agent': 'PolyV-CLI/2.1.0' } }); if (this.config.debug) { client.interceptors.request.use((config) => { console.log('[ChannelService] HTTP Request:', { method: config.method?.toUpperCase(), url: config.url, params: config.params, data: config.data ? this.sanitizeRequest(config.data) : undefined }); return config; }, (error) => { console.error('[ChannelService] Request error:', error); return Promise.reject(error); }); } client.interceptors.response.use((response) => { if (this.config.debug) { console.log('[ChannelService] HTTP Response:', { status: response.status, statusText: response.statusText, data: response.data }); } return response; }, (error) => { if (this.config.debug) { console.error('[ChannelService] Response error:', { status: error.response?.status, statusText: error.response?.statusText, data: error.response?.data }); } return Promise.reject(error); }); return client; } validateCreateRequest(request) { const errors = []; if (!request.name || typeof request.name !== 'string') { errors.push('Channel name is required and must be a string'); } else if (request.name.length === 0) { errors.push('Channel name cannot be empty'); } else if (request.name.length > 100) { errors.push('Channel name cannot exceed 100 characters'); } if (!request.newScene || typeof request.newScene !== 'string') { errors.push('newScene is required and must be a string'); } else if (!['topclass', 'cloudclass', 'telecast', 'akt'].includes(request.newScene)) { errors.push('newScene must be one of: topclass, cloudclass, telecast, akt'); } if (!request.template || typeof request.template !== 'string') { errors.push('template is required and must be a string'); } else if (!['ppt', 'video'].includes(request.template)) { errors.push('template must be one of: ppt, video'); } if (request.channelPasswd !== undefined) { if (typeof request.channelPasswd !== 'string') { errors.push('channelPasswd must be a string'); } else if (request.channelPasswd.length < 6 || request.channelPasswd.length > 16) { errors.push('channelPasswd must be 6-16 characters long'); } else if (!/^[a-zA-Z0-9]+$/.test(request.channelPasswd)) { errors.push('channelPasswd must contain only alphanumeric characters'); } } if (request.linkMicLimit !== undefined) { if (typeof request.linkMicLimit !== 'number' || request.linkMicLimit < 0) { errors.push('linkMicLimit must be a non-negative number'); } } if (request.startTime !== undefined) { if (typeof request.startTime !== 'number' || request.startTime < 0) { errors.push('startTime must be a non-negative timestamp'); } } if (request.endTime !== undefined) { if (typeof request.endTime !== 'number' || request.endTime < 0) { errors.push('endTime must be a non-negative timestamp'); } if (request.startTime !== undefined && request.endTime <= request.startTime) { errors.push('endTime must be greater than startTime'); } } if (errors.length > 0) { throw new errors_1.PolyVValidationError(`Channel creation request validation failed: ${errors.join(', ')}`, 'request', request, 'validation_failed'); } } transformToChannelModel(response, originalRequest) { if (!response.data || typeof response.data !== 'object') { throw new errors_1.PolyVAPIError('Invalid API response: missing data field', 'INVALID_RESPONSE_FORMAT', 500, { response }); } if (!response.data.channelId) { throw new errors_1.PolyVAPIError('Invalid API response: missing channelId', 'MISSING_CHANNEL_ID', 500, { response }); } return { channelId: response.data.channelId, name: originalRequest.name, userId: response.data.userId || this.authConfig.userId || '', channelPasswd: response.data.channelPasswd || '', newScene: originalRequest.newScene, template: originalRequest.template, status: 'waiting', createdAt: new Date() }; } transformAxiosError(error) { if (error.response) { const status = error.response.status; const data = error.response.data; if (data && typeof data === 'object' && data.code && data.message) { return new errors_1.PolyVAPIError(`PolyV API error: ${data.message}`, 'POLYV_API_ERROR', status, { polyvCode: data.code, polyvMessage: data.message, polyvData: data.data }); } return new errors_1.PolyVAPIError(`HTTP ${status} error: ${error.response.statusText}`, 'HTTP_ERROR', status, { responseData: data }); } if (error.request) { return new errors_1.PolyVAPIError('Network error: Unable to reach PolyV API', 'NETWORK_ERROR', 0, { request: error.config }); } return new errors_1.PolyVAPIError(`Request setup error: ${error.message}`, 'REQUEST_ERROR', 0, { originalError: error.message }); } sanitizeRequest(request) { if (!request || typeof request !== 'object') { return request; } const sanitized = { ...request }; if (sanitized.channelPasswd) { sanitized.channelPasswd = '***masked***'; } return sanitized; } } exports.ChannelService = ChannelService; //# sourceMappingURL=channel.service.js.map