UNPKG

chzzk

Version:

네이버 라이브 스트리밍 서비스 CHZZK의 비공식 API 라이브러리

332 lines (331 loc) 13.5 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.ChzzkChat = void 0; const isomorphic_ws_1 = __importDefault(require("isomorphic-ws")); const types_1 = require("./types"); const client_1 = require("../client"); const const_1 = require("../const"); class ChzzkChat { constructor(options) { var _a, _b; this.handlers = []; this.defaults = {}; this.pingTimeoutId = null; this.pollIntervalId = null; this.isReconnect = false; this._connected = false; if (options.pollInterval && !options.channelId) { throw new Error('channelId is required for polling'); } if (!options.chatChannelId && !options.channelId) { throw new Error('channelId or chatChannelId is required'); } if (const_1.IS_BROWSER && options.baseUrls == const_1.DEFAULT_BASE_URLS) { if (options.pollInterval) { throw new Error('Custom baseUrls are required for polling in browser'); } if (!options.chatChannelId) { throw new Error('chatChannelId is required in browser if not using custom baseUrls'); } if (!options.accessToken) { throw new Error('accessToken is required in browser if not using custom baseUrls'); } } this.options = options; this.options.baseUrls = (_a = options.baseUrls) !== null && _a !== void 0 ? _a : const_1.DEFAULT_BASE_URLS; this.client = (_b = options.client) !== null && _b !== void 0 ? _b : new client_1.ChzzkClient({ baseUrls: options.baseUrls }); } get connected() { return this._connected; } get chatChannelId() { return this.options.chatChannelId; } static fromClient(chatChannelId, client) { return new ChzzkChat({ chatChannelId, client, baseUrls: client.options.baseUrls }); } static fromAccessToken(chatChannelId, accessToken, uid, baseUrls) { const chzzkChat = new ChzzkChat({ chatChannelId, accessToken, baseUrls }); chzzkChat.uid = uid; return chzzkChat; } connect() { return __awaiter(this, void 0, void 0, function* () { if (this._connected) { throw new Error('Already connected'); } if (this.options.channelId && !this.options.chatChannelId) { const status = yield this.client.live.status(this.options.channelId); this.options.chatChannelId = status.chatChannelId; } if (this.options.chatChannelId && !this.options.accessToken) { this.uid = this.client.hasAuth ? yield this.client.user().then(user => user.userIdHash) : null; this.options.accessToken = yield this.client.chat.accessToken(this.options.chatChannelId) .then(token => token.accessToken); } this.defaults = { cid: this.options.chatChannelId, svcid: "game", ver: "2" }; const serverId = Math.abs(this.options.chatChannelId.split("") .map(c => c.charCodeAt(0)) .reduce((a, b) => a + b)) % 9 + 1; this.ws = new isomorphic_ws_1.default(`wss://kr-ss${serverId}.chat.naver.com/chat`); this.ws.onopen = () => { this.ws.send(JSON.stringify(Object.assign({ bdy: { accTkn: this.options.accessToken, auth: this.uid ? "SEND" : "READ", devType: 2001, uid: this.uid }, cmd: types_1.ChatCmd.CONNECT, tid: 1 }, this.defaults))); if (!this.isReconnect) { this.startPolling(); } }; this.ws.onmessage = this.handleMessage.bind(this); this.ws.onclose = () => { if (!this.isReconnect) { this.emit('disconnect', this.options.chatChannelId); this.stopPolling(); this.options.chatChannelId = null; } this.stopPingTimer(); this.ws = null; if (this._connected) { this.disconnect(); } }; }); } disconnect() { var _a; return __awaiter(this, void 0, void 0, function* () { if (!this._connected) { throw new Error('Not connected'); } (_a = this.ws) === null || _a === void 0 ? void 0 : _a.close(); this.ws = null; this.sid = null; if (this.client) { this.options.accessToken = null; this.uid = null; } this._connected = false; }); } reconnect() { return __awaiter(this, void 0, void 0, function* () { this.isReconnect = true; yield this.disconnect(); yield this.connect(); }); } requestRecentChat(count = 50) { if (!this._connected) { throw new Error('Not connected'); } this.ws.send(JSON.stringify(Object.assign({ bdy: { recentMessageCount: count }, cmd: types_1.ChatCmd.REQUEST_RECENT_CHAT, sid: this.sid, tid: 2 }, this.defaults))); } sendChat(message, emojis = {}) { if (!this._connected) { throw new Error('Not connected'); } if (!this.uid) { throw new Error('Not logged in'); } const extras = { chatType: "STREAMING", emojis, osType: "PC", streamingChannelId: this.options.chatChannelId }; this.ws.send(JSON.stringify(Object.assign({ bdy: { extras: JSON.stringify(extras), msg: message, msgTime: Date.now(), msgTypeCode: types_1.ChatType.TEXT }, retry: false, cmd: types_1.ChatCmd.SEND_CHAT, sid: this.sid, tid: 3 }, this.defaults))); } selfProfile() { return __awaiter(this, void 0, void 0, function* () { if (!this.uid) { throw new Error('Not logged in'); } return yield this.profile(this.uid); }); } profile(uid) { return __awaiter(this, void 0, void 0, function* () { if (!this._connected) { throw new Error('Not connected'); } return yield this.client.chat.profileCard(this.options.chatChannelId, uid); }); } emit(event, data) { if (this.handlers[event]) { for (const handler of this.handlers[event]) { handler(data); } } } on(event, handler) { const e = event; this.handlers[e] = this.handlers[e] || []; this.handlers[e].push(handler); } handleMessage(data) { return __awaiter(this, void 0, void 0, function* () { const json = JSON.parse(data.data); const body = json['bdy']; this.emit('raw', json); switch (json.cmd) { case types_1.ChatCmd.CONNECTED: this._connected = true; this.sid = body['sid']; if (this.isReconnect) { this.emit('reconnect', this.options.chatChannelId); this.isReconnect = false; } else { this.emit('connect', null); } break; case types_1.ChatCmd.PING: this.ws.send(JSON.stringify({ cmd: types_1.ChatCmd.PONG, ver: "2" })); break; case types_1.ChatCmd.CHAT: case types_1.ChatCmd.RECENT_CHAT: case types_1.ChatCmd.DONATION: const isRecent = json.cmd == types_1.ChatCmd.RECENT_CHAT; const chats = body['messageList'] || body; const notice = body['notice']; if (notice) { this.emit('notice', this.parseChat(notice, isRecent)); } for (const chat of chats) { const type = chat['msgTypeCode'] || chat['messageTypeCode'] || ''; const parsed = this.parseChat(chat, isRecent); switch (type) { case types_1.ChatType.TEXT: this.emit('chat', parsed); break; case types_1.ChatType.DONATION: this.emit('donation', parsed); break; case types_1.ChatType.SUBSCRIPTION: this.emit('subscription', parsed); break; case types_1.ChatType.SYSTEM_MESSAGE: this.emit('systemMessage', parsed); break; } } break; case types_1.ChatCmd.NOTICE: this.emit('notice', Object.keys(body).length != 0 ? this.parseChat(body) : null); break; case types_1.ChatCmd.BLIND: this.emit('blind', body); } if (json.cmd != types_1.ChatCmd.PONG) { this.startPingTimer(); } }); } parseChat(chat, isRecent = false) { const profile = JSON.parse(chat['profile']); const extras = chat['extras'] ? JSON.parse(chat['extras']) : null; const params = extras === null || extras === void 0 ? void 0 : extras['params']; const registerChatProfileJson = params === null || params === void 0 ? void 0 : params['registerChatProfileJson']; const targetChatProfileJson = params === null || params === void 0 ? void 0 : params['targetChatProfileJson']; if (registerChatProfileJson && targetChatProfileJson) { params['registerChatProfile'] = JSON.parse(registerChatProfileJson); params['targetChatProfile'] = JSON.parse(targetChatProfileJson); delete params['registerChatProfileJson']; delete params['targetChatProfileJson']; extras['params'] = params; } const message = chat['msg'] || chat['content']; const memberCount = chat['mbrCnt'] || chat['memberCount']; const time = chat['msgTime'] || chat['messageTime']; const hidden = (chat['msgStatusType'] || chat['messageStatusType']) == "HIDDEN"; const parsed = { profile, extras, hidden, message, time, isRecent }; if (memberCount) { parsed['memberCount'] = memberCount; } return parsed; } startPolling() { if (!this.options.pollInterval || this.pollIntervalId) return; this.pollIntervalId = setInterval(() => __awaiter(this, void 0, void 0, function* () { const chatChannelId = yield this.client.live.status(this.options.channelId) .then(status => status === null || status === void 0 ? void 0 : status.chatChannelId) .catch(() => null); if (chatChannelId && chatChannelId != this.options.chatChannelId) { this.options.chatChannelId = chatChannelId; yield this.reconnect(); } }), this.options.pollInterval); } stopPolling() { if (this.pollIntervalId) { clearInterval(this.pollIntervalId); } this.pollIntervalId = null; } startPingTimer() { if (this.pingTimeoutId) { clearTimeout(this.pingTimeoutId); } this.pingTimeoutId = setTimeout(() => this.sendPing(), 20000); } stopPingTimer() { if (this.pingTimeoutId) { clearTimeout(this.pingTimeoutId); } this.pingTimeoutId = null; } sendPing() { this.ws.send(JSON.stringify({ cmd: types_1.ChatCmd.PING, ver: "2" })); this.pingTimeoutId = setTimeout(() => this.sendPing(), 20000); } } exports.ChzzkChat = ChzzkChat;