UNPKG

wasdtv

Version:

WASD.tv API client

796 lines (786 loc) 27.6 kB
'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); var EventEmitter = require('events'); var axios = require('axios'); var m3u8stream = require('m3u8stream'); var io = require('socket.io-client'); function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; } var EventEmitter__default = /*#__PURE__*/_interopDefaultLegacy(EventEmitter); var axios__default = /*#__PURE__*/_interopDefaultLegacy(axios); var m3u8stream__default = /*#__PURE__*/_interopDefaultLegacy(m3u8stream); var io__default = /*#__PURE__*/_interopDefaultLegacy(io); class HttpError extends Error { } class ApiError extends Error { } class SocketError extends Error { } class RestClient { token; _axios; constructor(token) { this.token = token; this._axios = axios__default["default"].create({ baseURL: 'https://wasd.tv/api/', headers: { Authorization: `Token ${this.token}`, }, }); } async getJWT() { return this._axios .post('auth/chat-token') .then(({ data }) => { return data.result; }) .catch((err) => this.errorHandler(err)); } async getChannelInfo(channel_name) { return this._axios .get('v2/broadcasts/public', { params: { channel_name: channel_name, }, }) .then(({ data }) => { return data.result; }) .catch((err) => this.errorHandler(err)); } async getStreamChatMessages(stream_id, message_type, limit = 500, offset = 0) { return this._axios .get(`chat/streams/${stream_id}/messages`, { params: { type: message_type.toLocaleUpperCase(), limit: limit, offset: offset, }, }) .then(({ data }) => { return data.result; }) .catch((err) => this.errorHandler(err)); } async setChatSettings(stream_id, chat_role_limit, chat_delay) { return this._axios .post(`chat/streams/${stream_id}/settings`, { chatRoleLimitMode: chat_role_limit, chatDelayLimitMode: chat_delay, }) .then(({ data }) => { return data.result; }) .catch((err) => this.errorHandler(err)); } async addModerator(channel_id, user_id) { return this._axios .put(`channels/${channel_id}/moderators`, { user_id: user_id }) .then(({ data }) => { return data.result; }) .catch((err) => this.errorHandler(err)); } async deleteModerator(channel_id, user_id) { return this._axios .delete(`api/channels/${channel_id}/moderators/${user_id}`) .then(({ data }) => { return data.result; }) .catch((err) => this.errorHandler(err)); } async listModerator(streamer_id) { return this._axios .get(`chat/streamers/${streamer_id}/moderators`) .then(({ data }) => { return data.result; }) .catch((err) => this.errorHandler(err)); } async banUser(channel_id, user_id, stream_id, keep_messages = true, duration = 10) { return this._axios .put(`api/channels/${channel_id}/banned-users`, { user_id: user_id, stream_id: stream_id, keep_messages: keep_messages, duration: duration, }) .then(({ data }) => { return data.result; }) .catch((err) => this.errorHandler(err)); } async unbanUser(channel_id, user_id) { return this._axios .delete(`api/channels/${channel_id}/users/${user_id}`) .then(({ data }) => { return data.result; }) .catch((err) => this.errorHandler(err)); } async listBanned(streamer_id) { return this._axios .get(`chat/streamers/${streamer_id}/ban`) .then(({ data }) => { return data.result; }) .catch((err) => this.errorHandler(err)); } async getGifts(media_container_id) { return this._axios .get('v2/gifts', { params: { media_container_id: media_container_id, }, }) .then(({ data }) => { return data.result; }) .catch((err) => this.errorHandler(err)); } async productsToSell(user_id) { return this._axios .get('v2/shop/products-to-sell', { params: { user_id: user_id, }, }) .then(({ data }) => { return data.result; }) .catch((err) => this.errorHandler(err)); } async getWebCustomBlocks(channel_id) { return this._axios .get(`v2/channels/${channel_id}/custom_blocks`) .then(({ data }) => { return data.result; }) .catch((err) => this.errorHandler(err)); } async getStickerPack(streamer_id, limit = 100, offset = 0) { return this._axios .get(`api/chat/streamers/${streamer_id}/stickerpack`, { params: { limit: limit, offset: offset, }, }) .then(({ data }) => { return data.result; }) .catch((err) => this.errorHandler(err)); } async getPopularClips() { return this._axios .get(`v2/main/clips/popular`) .then(({ data }) => { return data.result; }) .catch((err) => this.errorHandler(err)); } async getClips(channel_id, limit, offset) { return this._axios .get('v2/clips', { params: { clip_channel_id: channel_id, limit: limit, offset: offset, }, }) .then(({ data }) => { return data.result; }) .catch((err) => this.errorHandler(err)); } async getGames(limit = 20, offset = 0) { return this._axios .get('games', { params: { limit: limit, offset: offset, }, }) .then(({ data }) => { return data.result; }) .catch((err) => this.errorHandler(err)); } async getMediaContainers(media_container_status, media_container_type, game_id, limit = 20, offset = 0) { return this._axios .get(`v2/media-containers`, { params: { media_container_status: media_container_status, media_container_type: media_container_type, game_id: game_id, limit: limit, offset: offset, }, }) .then(({ data }) => { return data.result; }) .catch((err) => this.errorHandler(err)); } async getChannelMediaContainers(media_container_status, media_container_type, channel_id, limit = 20, offset = 0) { return this._axios .get(`v2/media-containers/plain`, { params: { media_container_status: media_container_status, media_container_type: media_container_type, channel_id: channel_id, limit: limit, offset: offset, }, }) .then(({ data }) => { return data.result; }) .catch((err) => this.errorHandler(err)); } async getProfile() { return this._axios .get('v2/profiles/current') .then(({ data }) => { return data.result; }) .catch((err) => this.errorHandler(err)); } async getNotifications() { return this._axios .get('v2/notifications/bell') .then(({ data }) => { return data.result; }) .catch((err) => this.errorHandler(err)); } async getChannelLinks(channel_id) { return this._axios .get(`channels/${channel_id}/links`) .then(({ data }) => { return data.result; }) .catch((err) => this.errorHandler(err)); } async searchGames(search_phrase, limit, offset) { return this._axios .get('search/games', { params: { search_phrase: search_phrase, limit: limit, offset: offset, }, }) .then(({ data }) => { return data.result; }) .catch((err) => this.errorHandler(err)); } async searchProfile(search_phrase, limit, offset) { return this._axios .get('search/profiles', { params: { search_phrase: search_phrase, limit: limit, offset: offset, }, }) .then(({ data }) => { return data.result; }) .catch((err) => this.errorHandler(err)); } async searchChannel(search_phrase, limit, offset) { return this._axios .get('search/channels', { params: { search_phrase: search_phrase, limit: limit, offset: offset, }, }) .then(({ data }) => { return data.result; }) .catch((err) => this.errorHandler(err)); } async searchMediaContainer(media_container_name, media_container_status, limit = 20, offset = 0) { return this._axios .get('v2/search/media-containers', { params: { media_container_name: media_container_name, media_container_status: media_container_status, limit: limit, offset: offset, }, }) .then(({ data }) => { return data.result; }) .catch((err) => this.errorHandler(err)); } async getTags(type, limit = 30, offset = 0) { return this._axios .get('v2/tags', { params: { type: type, limit: limit, offset: offset, }, }) .then(({ data }) => { return data.result; }) .catch((err) => this.errorHandler(err)); } async getStreamPushUrl() { return this._axios .get('auth/users/current/stream-push-url') .then(({ data }) => { return data.result; }) .catch((err) => this.errorHandler(err)); } async getStreamClosedViewUrl() { return this._axios .get('v2/profiles/current/broadcasts/closed-view-url') .then(({ data }) => { return data.result; }) .catch((err) => this.errorHandler(err)); } async getBroadcastLimits() { return this._axios .get('v2/broadcast-limits') .then(({ data }) => { return data.result; }) .catch((err) => this.errorHandler(err)); } // TODO: investigate possible search_value types // eslint-disable-next-line @typescript-eslint/no-explicit-any async setSetting(setting_key, setting_value) { return this._axios .patch('profiles/current/settings', { new_settings: [ { setting_key: setting_key, setting_value: setting_value, }, ], }) .then(({ data }) => { return data.result; }) .catch((err) => this.errorHandler(err)); } async getPosts(user_id, limit, offset) { return this._axios .get('v2/posts', { params: { user_id: user_id, limit: limit, offset: offset, }, }) .then(({ data }) => { return data.result; }) .catch((err) => this.errorHandler(err)); } async getSchedule(user_id) { return this._axios .get(`schedules/weekly/${user_id}`) .then(({ data }) => { return data.result; }) .catch((err) => this.errorHandler(err)); } // TODO: add options to method downloadLiveMediaStream(user_id) { return this.downloadMediaByUrl(`https://cdn-curie.wasd.tv/live/${user_id}/tracks-v1a1/mono.m3u8`); } downloadMediaByUrl(url) { return m3u8stream__default["default"](url); } downloadVod(media_container) { if (media_container.media_container_status !== 'STOPPED' && media_container.media_container_type !== 'SINGLE' && media_container.media_container_online_status !== 'PUBLIC') { throw new ApiError('MediaContanier must have status STOPPED and type SINGLE'); } const archive_url = media_container.media_container_streams[0]?.stream_media[0]?.media_meta.media_archive_url; if (archive_url === undefined) { throw new ApiError('Unable to fetch media url'); } const download_url = archive_url?.replace('/index-', '/tracks-v1a1/index-').replace('cdn.wasd.tv', 'cdn-volta.wasd.tv'); return this.downloadMediaByUrl(download_url); } async getMediaStreamMetadata(user_id) { return axios__default["default"] .get(`https://cdn.wasd.tv/live/${user_id}/index.m3u8`) .then(({ data }) => { const parse = { bandwidth: data .match(/(,BANDWIDTH=[0-9]*)/gm) ?.at(0) ?.replace(',BANDWIDTH=', ''), average_bandwidth: data .match(/(,AVERAGE-BANDWIDTH=[0-9]*)/gm) .at(0) ?.replace(',AVERAGE-BANDWIDTH=', ''), codecs: data .match(/(,CODECS=".*")/gm) ?.at(0) ?.replace(',CODECS="', '') .replace('"', ''), resolution: data .match(/(,RESOLUTION=[0-9]*x[0-9]*)/gm) ?.at(0) ?.replace(',RESOLUTION=', ''), closed_captions: data .match(/(,CLOSED-CAPTIONS=[A-Z]*)/gm) ?.at(0) ?.replace(',CLOSED-CAPTIONS=', ''), frame_rate: data .match(/(,FRAME-RATE=[0-9]*\.[0-9]*)/gm) ?.at(0) ?.replace(',FRAME-RATE=', ''), source_url: data.match(/(https:\/\/.*m3u8)/gm)?.at(0), }; return { bandwidth: parse.bandwidth ? Number(parse.bandwidth) : undefined, average_bandwidth: parse.average_bandwidth ? Number(parse.average_bandwidth) : undefined, codecs: parse.codecs, resolution: parse.resolution, closed_captions: parse.closed_captions, frame_rate: parse.frame_rate ? Number(parse.frame_rate) : undefined, source_url: parse.source_url, }; }) .catch((err) => { this.errorHandler(err); return { bandwidth: undefined, average_bandwidth: undefined, codecs: undefined, resolution: undefined, closed_captions: undefined, frame_rate: undefined, source_url: undefined, }; }); } // eslint-disable-next-line @typescript-eslint/no-explicit-any errorHandler(err) { if (err instanceof axios.AxiosError) { if (err.response?.data?.error !== undefined) { throw new ApiError(JSON.stringify(err.response.data.error, null, 2)); } else { console.log(err); throw new HttpError(err.message); } } } } class SocketClient extends EventEmitter__default["default"] { jwt; heartbeat = 25; socketio; _subs = []; _originalOn = EventEmitter__default["default"].prototype.on; _originalOff = EventEmitter__default["default"].prototype.off; constructor(jwt) { super(); this.jwt = jwt; try { this.socketio = io__default["default"]('wss://chat.wasd.tv', { path: '/socket.io', transports: ['websocket'], query: { EIO: 3, }, }); } catch (err) { this.errorHandler(err); } } connect(cb) { try { this.socketio = io__default["default"]('wss://chat.wasd.tv', { path: '/socket.io', transports: ['websocket'], query: { EIO: 3, }, }); } catch (err) { this.errorHandler(err); } this.listen(cb); } join(stream_id, channel_id) { if (this.socketio?.connected) { this.socketio.emit('join', { streamId: stream_id, channelId: channel_id, jwt: this.jwt, excludeStickers: true, }); } else { this.connect(() => { this.join(stream_id, channel_id); }); } } leave(stream_id) { if (this.socketio?.connected) { this.socketio.emit('leave', { streamId: stream_id, }); } else { this.connect(() => { this.leave(stream_id); }); } } sendMessage(stream_id, message, channel_id, streamer_id) { if (this.socketio?.connected) { this.socketio.emit('message', { channelId: channel_id, hash: this.messageHash(), jwt: this.jwt, message: message, streamId: stream_id, streamerId: streamer_id, }); } else { throw new SocketError('Socket is not connected at this moment. Use this method only after "joined" event.'); } } sendSticker(stream_id, sticker_id, channel_id, streamer_id) { if (this.socketio?.connected) { this.socketio.emit('sticker', { channel_id: channel_id, hash: this.messageHash(), sticker_id: sticker_id, stream_id: stream_id, streamer_id: streamer_id, }); } else { throw new SocketError('Socket is not connected at this moment. Use this method only after "joined" event.'); } } messageHash(length = 25) { let result = ''; const characters = 'abcdefghijklmnopqrstuvwxyz0123456789'; const charactersLength = characters.length; for (let i = 0; i < length; i++) { result += characters.charAt(Math.floor(Math.random() * charactersLength)); } return result; } listen(cb) { this.socketio.on('connect', () => { this.ping(); if (cb !== undefined) { cb(); } this.emit('connect', null); console.log('connected'); this._subs.forEach((event) => { this.socketio.on(event, (ctx) => { this.emit(event, ctx); }); }); }); } on(event, listener) { this._subs.push(event); return this._originalOn(event, listener); } off(event, listener) { this._subs = this._subs.filter((e) => e !== event); return this._originalOff(event, listener); } ping() { setTimeout(() => { if (this.socketio.connected) { this.socketio.send(2); this.ping(); } }, this.heartbeat * 1000); } errorHandler(err) { throw new SocketError(`${err.name}: ${err.message}`); } } class WasdTv extends EventEmitter__default["default"] { token; _rest; _socket; _jwt; _subs = []; _originalOn = EventEmitter__default["default"].prototype.on; _originalOff = EventEmitter__default["default"].prototype.off; constructor(token) { super(); this.token = token; this.init(); } init() { this._rest = new RestClient(this.token); } async joinToChat(stream_id, channel_id) { await this.connectGuard(); this._socket.join(stream_id, channel_id); this._subs.forEach((event) => { this._socket.on(event, (ctx) => { this.emit(event, ctx); }); }); } async leaveChat(stream_id) { await this.connectGuard(); this._socket.leave(stream_id); } sendMessage(stream_id, message, channel_id = 1, streamer_id = 1) { return this._socket.sendMessage(stream_id, message, channel_id, streamer_id); } sendSticker(stream_id, sticker_id, channel_id = 1, streamer_id = 1) { return this._socket.sendSticker(stream_id, sticker_id, channel_id, streamer_id); } async connectGuard() { if (this._socket === undefined) { this._jwt = await this._rest.getJWT(); this._socket = new SocketClient(this._jwt); } } on(event, listener) { this._subs.push(event); return this._originalOn(event, listener); } off(event, listener) { this._subs = this._subs.filter((e) => e !== event); return this._originalOff(event, listener); } async getJWT() { return this._rest.getJWT(); } async getChannelInfo(channel_name) { return this._rest.getChannelInfo(channel_name); } async getStreamChatMessages(stream_id, limit = 500, offset = 0) { return this._rest.getStreamChatMessages(stream_id, 'MESSAGE', limit, offset); } async getStreamChatStickers(stream_id, limit = 500, offset = 0) { return this._rest.getStreamChatMessages(stream_id, 'STICKER', limit, offset); } async getStreamChatSubscribers(stream_id, limit = 500, offset = 0) { return this._rest.getStreamChatMessages(stream_id, 'SUBSCRIBE', limit, offset); } async getStreamChatHighlightedMessage(stream_id, limit = 500, offset = 0) { return this._rest.getStreamChatMessages(stream_id, 'HIGHLIGHTED_MESSAGE', limit, offset); } async addModerator(channel_id, user_id) { return this._rest.addModerator(channel_id, user_id); } async deleteModerator(channel_id, user_id) { return this._rest.deleteModerator(channel_id, user_id); } async listModerator(streamer_id) { return this._rest.listModerator(streamer_id); } async banUser(channel_id, user_id, stream_id, keep_messages = true, duration = 10) { return this._rest.banUser(channel_id, user_id, stream_id, keep_messages, duration); } async unbanUser(channel_id, user_id) { return this._rest.unbanUser(channel_id, user_id); } async listBanned(streamer_id) { return this._rest.listBanned(streamer_id); } async getGifts(media_container_id) { return this._rest.getGifts(media_container_id); } async productsToSell(user_id) { return this._rest.productsToSell(user_id); } async getWebCustomBlocks(channel_id) { return this._rest.getWebCustomBlocks(channel_id); } async getPopularClips() { return this._rest.getPopularClips(); } async getClips(channel_id, limit = 20, offset = 0) { return this._rest.getClips(channel_id, limit, offset); } async getGames(limit = 20, offset = 0) { return this._rest.getGames(limit, offset); } async getMediaContainers(media_container_status = 'RUNNING', media_container_type = 'SINGLE', game_id, limit = 20, offset = 0) { return this._rest.getMediaContainers(media_container_status, media_container_type, game_id, limit, offset); } async getChannelMediaContainers(media_container_status = 'RUNNING', media_container_type = 'SINGLE', channel_id, limit = 20, offset = 0) { return this._rest.getChannelMediaContainers(media_container_status, media_container_type, channel_id, limit, offset); } async getChannelLinks(channel_id) { return this._rest.getChannelLinks(channel_id); } async getProfile() { return this._rest.getProfile(); } async getNotifications() { return this._rest.getNotifications(); } async searchGames(search_phrase, limit = 20, offset = 0) { return this._rest.searchGames(search_phrase, limit, offset); } async searchProfile(search_phrase, limit = 20, offset = 0) { return this._rest.searchProfile(search_phrase, limit, offset); } async searchChannel(search_phrase, limit = 20, offset = 0) { return this._rest.searchChannel(search_phrase, limit, offset); } async searchMediaContainer(media_container_name, media_container_status, limit = 20, offset = 0) { return this._rest.searchMediaContainer(media_container_name, media_container_status, limit, offset); } async getTags(type, limit = 30, offset = 0) { return this._rest.getTags(type, limit, offset); } async getStreamPushUrl() { return this._rest.getStreamPushUrl(); } async getStreamClosedViewUrl() { return this._rest.getStreamClosedViewUrl(); } async getBroadcastLimits() { return this._rest.getBroadcastLimits(); } // eslint-disable-next-line @typescript-eslint/no-explicit-any async setSetting(setting_key, setting_value) { return this._rest.setSetting(setting_key, setting_value); } async getPosts(user_id, limit = 20, offset = 0) { return this._rest.getPosts(user_id, limit, offset); } donwloadLiveMediaStream(user_id) { return this._rest.downloadLiveMediaStream(user_id); } downloadMediaByUrl(url) { return this._rest.downloadMediaByUrl(url); } downloadVod(media_container) { return this._rest.downloadVod(media_container); } async getMediaStreamMetadata(user_id) { return this._rest.getMediaStreamMetadata(user_id); } } exports.WasdTv = WasdTv;