"use strict"; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); var youtube_exports = {}; __export(youtube_exports, { GroupWatch: () => GroupWatch, Twitch: () => Twitch, YouTube: () => YouTube, YoutubeInterface: () => YoutubeInterface, commands: () => commands, destroy: () => destroy, pages: () => pages, searchDataCache: () => searchDataCache, videoDataCache: () => videoDataCache }); module.exports = __toCommonJS(youtube_exports); var import_lib = require("../../lib"); const ROOT = "https://www.googleapis.com/youtube/v3/"; const STORAGE_PATH = "config/chat-plugins/youtube.json"; const GROUPWATCH_ROOMS = ["youtube", "pokemongames", "videogames", "smashbros", "pokemongo", "hindi"]; const videoDataCache = Chat.oldPlugins.youtube?.videoDataCache || /* @__PURE__ */ new Map(); const searchDataCache = Chat.oldPlugins.youtube?.searchDataCache || /* @__PURE__ */ new Map(); function loadData() { const raw = JSON.parse((0, import_lib.FS)(STORAGE_PATH).readIfExistsSync() || "{}"); if (!(raw.channels && raw.categories)) { const data = {}; data.channels = raw; data.categories = []; (0, import_lib.FS)(STORAGE_PATH).writeUpdate(() => JSON.stringify(data)); return data; } return raw; } const channelData = loadData(); class YoutubeInterface { constructor(data) { this.linkRegex = /^(https?:\/\/)?(www\.)?(youtube\.com|youtu\.be)(\/|$)/i; this.data = data ? data : { categories: [], channels: {} }; this.interval = null; this.intervalTime = 0; if (data?.intervalTime) { this.runInterval(`${data.intervalTime}`); } } async getChannelData(link, username) { if (!Config.youtubeKey) { throw new Chat.ErrorMessage(`This server does not support YouTube commands. If you're the owner, you can enable them by setting up Config.youtubekey.`); } const id = this.getId(link); const raw = await (0, import_lib.Net)(`${ROOT}channels`).get({ query: { part: "snippet,statistics", id, key: Config.youtubeKey } }); const res = JSON.parse(raw); if (!res?.items || res.items.length < 1) { throw new Chat.ErrorMessage(`Channel not found.`); } const data = res.items[0]; const cache = { name: data.snippet.title, description: data.snippet.description, url: data.snippet.customUrl, icon: data.snippet.thumbnails.medium.url, videos: Number(data.statistics.videoCount), subs: Number(data.statistics.subscriberCount), views: Number(data.statistics.viewCount), username }; this.data.channels[id] = { ...cache }; this.save(); return cache; } async generateChannelDisplay(link) { const id = this.getId(link); const { name, description, icon, videos, subs, views, username } = await this.get(id); let buf = `
`; buf += `
`; buf += `
`; buf += ``; buf += `

`; buf += `${name}`; buf += `

`; buf += `

`; buf += `${videos} videos | ${subs} subscribers | ${views} video views

`; buf += `

`; buf += `${description.slice(0, 400).replace(/\n/g, " ")}${description.length > 400 ? "(...)" : ""}

`; if (username) { buf += `

PS username: ${username}

`; } else { buf += ""; } return buf; } randChannel(cat) { let channels = Object.keys(this.data.channels); if (channels.length < 1) { throw new Chat.ErrorMessage(`There are no channels in the database.`); } if (cat) { cat = toID(cat); const categoryIDs = this.data.categories.map(toID); if (!categoryIDs.includes(cat)) { throw new Chat.ErrorMessage(`Invalid category.`); } channels = channels.filter((id2) => { const channel = this.data.channels[id2]; return channel.category && toID(channel.category) === cat; }); } const id = import_lib.Utils.shuffle(channels)[0]; return this.generateChannelDisplay(id); } get(id, username) { if (!(id in this.data.channels)) return this.getChannelData(id, username); return Promise.resolve({ ...this.data.channels[id] }); } async getVideoData(id) { const cached = videoDataCache.get(id); if (cached) return cached; let raw; try { raw = await (0, import_lib.Net)(`${ROOT}videos`).get({ query: { part: "snippet,statistics", id, key: Config.youtubeKey } }); } catch (e) { throw new Chat.ErrorMessage(`Failed to retrieve video data: ${e.message}.`); } const res = JSON.parse(raw); if (!res?.items || res.items.length < 1) return null; const video = res.items[0]; const data = { title: video.snippet.title, id, date: new Date(video.snippet.publishedAt).toString(), description: video.snippet.description, channelTitle: video.snippet.channelTitle, channelUrl: video.snippet.channelId, views: video.statistics.viewCount, thumbnail: video.snippet.thumbnails.default.url, likes: video.statistics.likeCount, dislikes: video.statistics.dislikeCount }; videoDataCache.set(id, data); return data; } channelSearch(search) { let channel; if (this.data.channels[search]) { channel = search; } else { for (const id of Object.keys(this.data.channels)) { const name = toID(this.data.channels[id].name); const username = this.data.channels[id].username; if (name === toID(search) || username && toID(username) === toID(search)) { channel = id; break; } } } return channel; } getId(link) { let id = ""; if (!link) throw new Chat.ErrorMessage("You must provide a YouTube link."); if (this.data.channels[link]) return link; if (!link.includes("channel/")) { if (link.includes("youtube")) { id = link.split("v=")[1] || ""; } else if (link.includes("youtu.be")) { id = link.split("/")[3] || ""; } else { throw new Chat.ErrorMessage("Invalid YouTube channel link."); } } else { id = link.split("channel/")[1] || ""; } if (id.includes("&")) id = id.split("&")[0]; if (id.includes("?")) id = id.split("?")[0]; return id; } async generateVideoDisplay(link, fullInfo = false, broadcasting = false) { if (!Config.youtubeKey) { throw new Chat.ErrorMessage(`This server does not support YouTube commands. If you're the owner, you can enable them by setting up Config.youtubekey.`); } const id = this.getId(link); const info = await this.getVideoData(id); if (!info) throw new Chat.ErrorMessage(`Video not found.`); if (!fullInfo) { let buf2 = `${info.title} `; buf2 += `(${info.channelTitle})
`; if (broadcasting) { buf2 += ``; } else { buf2 += ``; } return buf2; } let buf = ``; buf += ``; return buf; } save() { return (0, import_lib.FS)(STORAGE_PATH).writeUpdate(() => JSON.stringify(this.data)); } async searchVideo(name, limit) { const cached = searchDataCache.get(toID(name)); if (cached) { return cached.slice(0, limit); } const raw = await (0, import_lib.Net)(`${ROOT}search`).get({ query: { part: "snippet", q: name, key: Config.youtubeKey, order: "relevance" } }); const result = JSON.parse(raw); const resultArray = result.items?.map((item) => item?.id?.videoId).filter(Boolean); searchDataCache.set(toID(name), resultArray); return resultArray.slice(0, limit); } async searchChannel(name, limit = 10) { const raw = await (0, import_lib.Net)(`${ROOT}search`).get({ query: { part: "snippet", q: name, type: "channel", key: Config.youtubeKey, order: "relevance", maxResults: limit } }); const result = JSON.parse(raw); return result?.items.map((item) => item?.snippet?.channelId); } runInterval(time) { let interval = Number(time); if (interval < 10) throw new Chat.ErrorMessage(`${interval} is too low - set it above 10 minutes.`); this.intervalTime = interval; this.data.intervalTime = interval; interval = interval * 60 * 1e3; if (this.interval) clearInterval(this.interval); this.interval = setInterval(() => { void (async () => { const room = Rooms.get("youtube"); if (!room) return; const res = await YouTube.randChannel(); room.add(`|html|${res}`).update(); })(); }, interval); return this.interval; } async createGroupWatch(url, baseRoom, title) { const videoInfo = await this.getGroupwatchData(url); const num = baseRoom.nextGameNumber(); baseRoom.saveSettings(); return new GroupWatch(baseRoom, num, url, title, videoInfo); } async getGroupwatchData(url) { if (!Chat.isLink(url)) { throw new Chat.ErrorMessage("Invalid URL: " + url); } const urlData = new URL(url); const host = urlData.hostname; let videoInfo; if (["youtu.be", "www.youtube.com"].includes(host)) { const id = this.getId(url); const data = await this.getVideoData(id); if (!data) throw new Chat.ErrorMessage(`Video not found.`); videoInfo = Object.assign(data, { groupwatchType: "youtube" }); } else if (host === "www.twitch.tv") { const data = await Twitch.getChannel(urlData.pathname.slice(1)); if (!data) throw new Chat.ErrorMessage(`Channel not found`); videoInfo = Object.assign(data, { groupwatchType: "twitch" }); } else { throw new Chat.ErrorMessage(`Invalid URL: must be either a Youtube or Twitch link.`); } return videoInfo; } } const Twitch = new class { constructor() { this.linkRegex = /(https?:\/\/)?twitch.tv\/([A-Za-z0-9]+)/i; } async getChannel(channel) { if (!Config.twitchKey || typeof Config.twitchKey !== "object") { throw new Chat.ErrorMessage(`Twitch is not enabled.`); } channel = toID(channel); let res; try { res = await (0, import_lib.Net)(`https://api.twitch.tv/helix/search/channels`).get({ headers: { "Authorization": `Bearer ${Config.twitchKey.key}`, "Client-Id": Config.twitchKey.id, "Content-Type": "application/json", "Accept": "application/vnd.twitchtv.v5+json" }, query: { query: channel } }); } catch (e) { throw new Chat.ErrorMessage(`Error retrieving twitch channel: ${e.message}`); } const data = JSON.parse(res); import_lib.Utils.sortBy(data.channels, (c) => -c.followers); return data?.channels?.[0]; } visualizeChannel(info) { let buf = `
`; buf += `
`; buf += ``; buf += `

`; buf += `${info.title}`; buf += `

`; buf += `

`; buf += `${info.likes} likes | ${info.dislikes} dislikes | ${info.views} video views

`; buf += `Published on ${info.date} | ID: ${id}
Uploaded by: ${info.channelTitle}

`; buf += `
Video Description

`; buf += `

`; buf += `${info.description.slice(0, 400).replace(/\n/g, " ")}${info.description.length > 400 ? "(...)" : ""}

`; buf += `
`; buf += `
`; buf += ``; buf += `

`; buf += `${info.display_name}`; buf += `

`; buf += `

`; const created = new Date(info.created_at); buf += `${info.followers} subscribers | ${info.views} stream views | created ${Chat.toTimestamp(created).split(" ")[0]}

`; buf += `

Last seen playing ${info.game} (Status: ${info.status})

`; buf += `

`; buf += `${info.description.slice(0, 400).replace(/\n/g, " ")}${info.description.length > 400 ? "..." : ""}

`; buf += "
"; return buf; } }(); const _GroupWatch = class extends Rooms.SimpleRoomGame { constructor(room, num, url, title, videoInfo) { super(room); this.gameid = "groupwatch"; this.started = null; this.title = title; this.id = `${room.roomid}-${num}`; _GroupWatch.groupwatches.set(this.id, this); this.url = url; this.info = videoInfo; } onJoin(user) { const hints = this.hints(); for (const hint of hints) { user.sendTo(this.room.roomid, `|html|${hint}`); } } start() { if (this.started) throw new Chat.ErrorMessage(`We've already started.`); this.started = Date.now(); this.update(); } hints() { const title = this.info.groupwatchType === "youtube" ? this.info.title : this.info.display_name; const hints = [ `To watch, all you need to do is click play on the video once staff have started it!`, `We are currently watching: ${title}` ]; if (this.started && this.info.groupwatchType === "youtube") { const diff = Date.now() - this.started; hints.push(`Video is currently at ${Chat.toDurationString(diff)} (${Math.floor(diff / 1e3)} seconds)`); } return hints; } getStatsDisplay() { if (this.info.groupwatchType === "twitch") { let buf = `

`; buf += `Watching ${this.info.display_name}
`; buf += `${Chat.count(Object.keys(this.room.users).length, "users")} watching
`; buf += `Playing: ${this.info.game}`; return buf; } let controlsHTML = `

${this.info.title}

`; controlsHTML += `
Channel: `; controlsHTML += `${this.info.channelTitle}
`; controlsHTML += `Likes: ${this.info.likes} | Dislikes: ${this.info.dislikes}
`; controlsHTML += `Uploaded:
`; controlsHTML += `
Description${this.info.description.replace(/\n/ig, "
")}
`; controlsHTML += `
`; return controlsHTML; } getVideoDisplay() { if (this.info.groupwatchType === "twitch") { let buf2 = `

`; buf2 += ``; return buf2; } let buf = `

`; buf += `

${this.info.title}
`; const id = YouTube.getId(this.url); const url = `https://youtube.com/watch?v=${id}`; let addendum = ""; if (this.started) { const diff = Date.now() - this.started; addendum = `&start=${Math.floor(diff / 1e3)}`; } buf += ``; buf += `
`.repeat(4); buf += `

`; return buf; } display() { return import_lib.Utils.html`
${this.room.title} Groupwatch - ${this.title}

` + `

${this.started ? this.getVideoDisplay() : ""}


${this.started ? this.getStatsDisplay() : "Waiting to start the video..."}

${this.hints().join("
")}

`; } update() { for (const user of Object.values(this.room.users)) { for (const conn of user.connections) { if (conn.openPages?.has(`groupwatch-${this.id}`)) { void Chat.parse(`/j view-groupwatch-${this.id}`, this.room, user, conn); } } } } async changeVideo(url) { const info = await YouTube.getGroupwatchData(url); if (!info) throw new Chat.ErrorMessage(`Could not retrieve data for URL ${url}`); this.url = url; this.started = Date.now(); this.info = info; this.update(); } destroy() { _GroupWatch.groupwatches.delete(this.id); this.room.game = null; this.room = null; } }; let GroupWatch = _GroupWatch; GroupWatch.groupwatches = /* @__PURE__ */ new Map(); const YouTube = new YoutubeInterface(channelData); function destroy() { if (YouTube.interval) clearInterval(YouTube.interval); } const commands = { async randchannel(target, room, user) { room = this.requireRoom("youtube"); if (Object.keys(YouTube.data.channels).length < 1) return this.errorReply(`No channels in the database.`); target = toID(target); this.runBroadcast(); const data = await YouTube.randChannel(target); return this.sendReply(`|html|${data}`); }, randchannelhelp: [`/randchannel - View data of a random channel from the YouTube database.`], yt: "youtube", youtube: { async addchannel(target, room, user) { room = this.requireRoom("youtube"); this.checkCan("mute", null, room); const [id, name] = target.split(",").map((t) => t.trim()); if (!id) return this.errorReply("Specify a channel ID."); await YouTube.getChannelData(id, name); this.modlog("ADDCHANNEL", null, `${id} ${name ? `username: ${name}` : ""}`); return this.privateModAction( `${user.name} added channel with id ${id} ${name ? `and username (${name}) ` : ""} to the random channel pool.` ); }, addchannelhelp: [`/addchannel - Add channel data to the YouTube database. Requires: % @ #`], removechannel(target, room, user) { room = this.requireRoom("youtube"); this.checkCan("mute", null, room); const id = YouTube.channelSearch(target); if (!id) return this.errorReply(`Channel with ID or name ${target} not found.`); delete YouTube.data.channels[id]; YouTube.save(); this.privateModAction(`${user.name} deleted channel with ID or name ${target}.`); return this.modlog(`REMOVECHANNEL`, null, id); }, removechannelhelp: [`/youtube removechannel - Delete channel data from the YouTube database. Requires: % @ #`], async channel(target, room, user) { room = this.requireRoom("youtube"); const channel = YouTube.channelSearch(target); if (!channel) return this.errorReply(`No channels with ID or name ${target} found.`); const data = await YouTube.generateChannelDisplay(channel); this.runBroadcast(); return this.sendReply(`|html|${data}`); }, channelhelp: [ "/youtube channel - View the data of a specified channel. Can be either channel ID or channel name." ], async video(target, room, user) { room = this.requireRoom("youtube"); this.checkCan("mute", null, room); const buffer = await YouTube.generateVideoDisplay(target, true); this.runBroadcast(); this.sendReplyBox(buffer); }, channels(target, room, user) { target = toID(target); return this.parse(`/j view-channels${target ? `-${target}` : ""}`); }, help(target, room, user) { return this.parse("/help youtube"); }, categories() { return this.parse(`/j view-channels-categories`); }, update(target, room, user) { room = this.requireRoom("youtube"); this.checkCan("mute", null, room); const [channel, name] = target.split(","); const id = YouTube.channelSearch(channel); if (!id) return this.errorReply(`Channel ${channel} is not in the database.`); YouTube.data.channels[id].username = name; this.modlog(`UPDATECHANNEL`, null, name); this.privateModAction(`${user.name} updated channel ${id}'s username to ${name}.`); YouTube.save(); }, interval: "repeat", repeat(target, room, user) { room = this.requireRoom("youtube"); this.checkCan("declare", null, room); if (!target) { if (!YouTube.interval) return this.errorReply(`The YouTube plugin is not currently running an interval.`); return this.sendReply(`Interval is currently set to ${Chat.toDurationString(YouTube.intervalTime * 60 * 1e3)}.`); } if (this.meansNo(target)) { if (!YouTube.interval) return this.errorReply(`The interval is not currently running`); clearInterval(YouTube.interval); delete YouTube.data.intervalTime; YouTube.save(); this.privateModAction(`${user.name} turned off the YouTube interval`); return this.modlog(`YOUTUBE INTERVAL`, null, "OFF"); } if (Object.keys(channelData).length < 1) return this.errorReply(`No channels in the database.`); if (isNaN(parseInt(target))) return this.errorReply(`Specify a number (in minutes) for the interval.`); YouTube.runInterval(target); YouTube.save(); this.privateModAction(`${user.name} set a randchannel interval to ${target} minutes`); return this.modlog(`CHANNELINTERVAL`, null, `${target} minutes`); }, addcategory(target, room, user) { room = this.requireRoom("youtube"); this.checkCan("mute", null, room); const categoryID = toID(target); if (!categoryID) return this.parse(`/help youtube`); if (YouTube.data.categories.map(toID).includes(categoryID)) { return this.errorReply(`This category is already added. To change it, remove it and re-add it.`); } YouTube.data.categories.push(target); this.modlog(`YOUTUBE ADDCATEGORY`, null, target); this.privateModAction(`${user.name} added category '${target}' to the categories list.`); YouTube.save(); }, removecategory(target, room, user) { room = this.requireRoom("youtube"); this.checkCan("mute", null, room); const categoryID = toID(target); if (!categoryID) return this.parse(`/help youtube`); const index = YouTube.data.categories.indexOf(target); if (index < 0) { return this.errorReply(`${target} is not a valid category.`); } for (const id in YouTube.data.channels) { const channel = YouTube.data.channels[id]; if (channel.category === target) delete YouTube.data.channels[id].category; } YouTube.save(); this.privateModAction(`${user.name} removed the category '${target}' from the category list.`); this.modlog(`YOUTUBE REMOVECATEGORY`, null, target); }, setcategory(target, room, user) { room = this.requireRoom("youtube"); this.checkCan("mute", null, room); target = target.trim(); const [category, id] = import_lib.Utils.splitFirst(target, ",").map((item) => item.trim()); if (!target || !category || !id) { return this.parse("/help youtube"); } if (!YouTube.data.categories.includes(category)) { return this.errorReply(`Invalid category.`); } const name = YouTube.channelSearch(id); if (!name) return this.errorReply(`Invalid channel.`); const channel = YouTube.data.channels[name]; YouTube.data.channels[name].category = category; YouTube.save(); this.modlog(`YOUTUBE SETCATEGORY`, null, `${id}: to category ${category}`); this.privateModAction(`${user.name} set the channel ${channel.name}'s category to '${category}'.`); }, decategorize(target, room, user) { room = this.requireRoom("youtube"); this.checkCan("mute", null, room); target = target.trim(); if (!target) { return this.parse("/help youtube"); } const name = YouTube.channelSearch(target); if (!name) return this.errorReply(`Invalid channel.`); const channel = YouTube.data.channels[name]; const category = channel.category; if (!category) return this.errorReply(`That channel does not have a category.`); delete channel.category; YouTube.save(); this.modlog(`YOUTUBE DECATEGORIZE`, null, target); this.privateModAction(`${user.name} removed the channel ${channel.name} from the category ${category}.`); } }, youtubehelp: [ `YouTube commands:`, `/randchannel [optional category]- View data of a random channel from the YouTube database. If a category is given, the random channel will be in the given category.`, `/youtube addchannel [channel] - Add channel data to the YouTube database. Requires: % @ #`, `/youtube removechannel [channel]- Delete channel data from the YouTube database. Requires: % @ #`, `/youtube channel [channel] - View the data of a specified channel. Can be either channel ID or channel name.`, `/youtube video [video] - View data of a specified video. Can be either channel ID or channel name.`, `/youtube update [channel], [name] - sets a channel's PS username to [name]. Requires: % @ #`, `/youtube repeat [time] - Sets an interval for [time] minutes, showing a random channel each time. Requires: # &`, `/youtube addcategory [name] - Adds the [category] to the channel category list. Requires: @ # &`, `/youtube removecategory [name] - Removes the [category] from the channel category list. Requires: @ # &`, `/youtube setcategory [category], [channel name] - Sets the category for [channel] to [category]. Requires: @ # &`, `/youtube decategorize [channel name] - Removes the category for the [channel], if there is one. Requires: @ # &`, `/youtube categores - View all channels sorted by category.` ], groupwatch: { async create(target, room, user) { room = this.requireRoom(); if (!GROUPWATCH_ROOMS.includes(room.roomid)) { return this.errorReply(`This room is not allowed to use the groupwatch function.`); } this.checkCan("mute", null, room); const [url, title] = import_lib.Utils.splitFirst(target, ",").map((p) => p.trim()); if (!url || !title) return this.errorReply(`You must specify a video to watch and a title for the group watch.`); const game = await YouTube.createGroupWatch(url, room, title); this.modlog(`YOUTUBE GROUPWATCH`, null, `${url} (${title})`); room.add( `|uhtml|${game.id}|` ); room.send(`|tempnotify|youtube|New groupwatch - ${title}!`); this.update(); }, end(target, room, user) { room = this.requireRoom(); this.checkCan("mute", null, room); const game = this.requireGame(GroupWatch); this.modlog(`GROUPWATCH END`); this.add(`|uhtmlchange|${game.id}|`); game.destroy(); }, start(target, room, user) { room = this.requireRoom(); this.checkCan("mute", null, room); const game = this.requireGame(GroupWatch); game.start(); game.update(); }, async edit(target, room, user) { room = this.requireRoom(); this.checkCan("mute", null, room); const game = this.requireGame(GroupWatch); await game.changeVideo(target); }, list() { let buf = `Ongoing groupwatches:
`; for (const curRoom of Rooms.rooms.values()) { if (!curRoom.getGame(GroupWatch)) continue; buf += ``; } this.runBroadcast(); this.sendReplyBox(buf); } }, groupwatchhelp: [ `/groupwatch create [link],[title] - create a groupwatch for the given Youtube or Twitch [link] with the [title]. Requires: % @ & #`, `/groupwatch end - End the current room's groupwatch, if one exists. Requires: % @ & #`, `/groupwatch start - Begin playback for the current groupwatch. Requires: % @ & #`, `/groupwatch edit [link] - Change the current groupwatch, if one exists, to be viewing the given [link]. Requires: % @ & #` ], twitch: { async channel(target, room, user) { room = this.requireRoom("youtube"); if (!Config.twitchKey) return this.errorReply(`Twitch is not configured`); const data = await Twitch.getChannel(target); if (!data) return this.errorReply(`Channel not found`); const html = Twitch.visualizeChannel(data); this.runBroadcast(); return this.sendReplyBox(html); } } }; const pages = { async channels(args, user) { const [type] = args; if (!Config.youtubeKey) return `

Youtube is not configured.

`; const titles = { all: "All channels", categories: "by category" }; const title = titles[type] || "Usernames only"; this.title = `[Channels] ${title}`; let buffer = `

Channels in the YouTube database: (${title})`; buffer += ` `; buffer += `


`; switch (toID(type)) { case "categories": if (!YouTube.data.categories.length) { return this.errorReply(`There are currently no categories in the Youtube channel database.`); } const sorted = {}; const channels = YouTube.data.channels; for (const [id, channel] of Object.entries(channels)) { const category = channel.category || "No category"; if (!sorted[category]) { sorted[category] = []; } sorted[category].push(id); } for (const cat in sorted) { buffer += `

${cat}:

`; for (const id of sorted[cat]) { const channel = channels[id]; buffer += `
${channel.name}`; buffer += await YouTube.generateChannelDisplay(id); buffer += `

`; } } break; default: for (const id of import_lib.Utils.shuffle(Object.keys(YouTube.data.channels))) { const { name, username } = await YouTube.get(id); if (toID(type) !== "all" && !username) continue; buffer += `
${name}`; buffer += ` (Channel ID: ${id})`; if (username) buffer += ` (PS name: ${username})`; buffer += ``; buffer += await YouTube.generateChannelDisplay(id); buffer += `

`; } break; } buffer += `
`; return buffer; }, groupwatch(query, user, connection) { if (!user.named) return Rooms.RETRY_AFTER_LOGIN; const [roomid, num] = query; const watch = GroupWatch.groupwatches.get(`${roomid}-${num}`); if (!watch) return this.errorReply(`Groupwatch ${roomid}-${num} not found.`); this.title = `[Groupwatch] ${watch.title}`; return watch.display(); } }; //# sourceMappingURL=youtube.js.map