wasdtv
Version:
WASD.tv API client
796 lines (786 loc) • 27.6 kB
JavaScript
'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;