{ "version": 3, "sources": ["../../../../server/chat-plugins/the-studio.ts"], "sourcesContent": ["/**\r\n * The Studio room chat-plugin.\r\n * Supports scrobbling and searching for music from last.fm.\r\n * Also supports storing and suggesting recommendations.\r\n * Written by Kris, loosely based on the concept from bumbadadabum.\r\n * @author Kris\r\n */\r\n\r\nimport {FS, Net, Utils} from '../../lib';\r\nimport {YouTube, VideoData} from './youtube';\r\n\r\nconst LASTFM_DB = 'config/chat-plugins/lastfm.json';\r\nconst RECOMMENDATIONS = 'config/chat-plugins/the-studio.json';\r\nconst API_ROOT = 'http://ws.audioscrobbler.com/2.0/';\r\nconst DEFAULT_IMAGES = [\r\n\t'https://lastfm.freetls.fastly.net/i/u/34s/2a96cbd8b46e442fc41c2b86b821562f.png',\r\n\t'https://lastfm.freetls.fastly.net/i/u/64s/2a96cbd8b46e442fc41c2b86b821562f.png',\r\n\t'https://lastfm.freetls.fastly.net/i/u/174s/2a96cbd8b46e442fc41c2b86b821562f.png',\r\n\t'https://lastfm.freetls.fastly.net/i/u/300x300/2a96cbd8b46e442fc41c2b86b821562f.png',\r\n];\r\n\r\ninterface Recommendation {\r\n\tartist: string;\r\n\ttitle: string;\r\n\turl: string;\r\n\tvideoInfo: VideoData | null;\r\n\tdescription: string;\r\n\ttags: string[];\r\n\tuserData: {\r\n\t\tname: string,\r\n\t\tavatar?: string,\r\n\t};\r\n\tlikes: number;\r\n\tliked?: {\r\n\t\tips: string[],\r\n\t\tuserids: string[],\r\n\t};\r\n}\r\n\r\ninterface Recommendations {\r\n\tsuggested: Recommendation[];\r\n\tsaved: Recommendation[];\r\n}\r\n\r\nconst lastfm: {[userid: string]: string} = JSON.parse(FS(LASTFM_DB).readIfExistsSync() || \"{}\");\r\nconst recommendations: Recommendations = JSON.parse(FS(RECOMMENDATIONS).readIfExistsSync() || \"{}\");\r\n\r\nif (!recommendations.saved) recommendations.saved = [];\r\nif (!recommendations.suggested) recommendations.suggested = [];\r\nsaveRecommendations();\r\n\r\nfunction updateRecTags() {\r\n\tfor (const rec of recommendations.saved) {\r\n\t\tif (!rec.tags.map(toID).includes(toID(rec.artist))) rec.tags.push(rec.artist);\r\n\t\tif (!rec.tags.map(toID).includes(toID(rec.userData.name))) rec.tags.push(rec.userData.name);\r\n\t}\r\n\tfor (const rec of recommendations.suggested) {\r\n\t\tif (!rec.tags.map(toID).includes(toID(rec.artist))) rec.tags.push(rec.artist);\r\n\t\tif (!rec.tags.map(toID).includes(toID(rec.userData.name))) rec.tags.push(rec.userData.name);\r\n\t}\r\n\tsaveRecommendations();\r\n}\r\n\r\nupdateRecTags();\r\n\r\nfunction saveLastFM() {\r\n\tFS(LASTFM_DB).writeUpdate(() => JSON.stringify(lastfm));\r\n}\r\nfunction saveRecommendations() {\r\n\tFS(RECOMMENDATIONS).writeUpdate(() => JSON.stringify(recommendations));\r\n}\r\n\r\nexport class LastFMInterface {\r\n\tasync getScrobbleData(username: string, displayName?: string) {\r\n\t\tthis.checkHasKey();\r\n\t\tconst accountName = this.getAccountName(username);\r\n\t\tlet raw;\r\n\t\ttry {\r\n\t\t\traw = await Net(API_ROOT).get({\r\n\t\t\t\tquery: {\r\n\t\t\t\t\tmethod: 'user.getRecentTracks', user: accountName,\r\n\t\t\t\t\tlimit: 1, api_key: Config.lastfmkey, format: 'json',\r\n\t\t\t\t},\r\n\t\t\t});\r\n\t\t} catch {\r\n\t\t\tthrow new Chat.ErrorMessage(`No scrobble data found.`);\r\n\t\t}\r\n\t\tconst res = JSON.parse(raw);\r\n\t\tif (res.error) {\r\n\t\t\tthrow new Chat.ErrorMessage(`${res.message}.`);\r\n\t\t}\r\n\t\tif (!res?.recenttracks?.track?.length) throw new Chat.ErrorMessage(`last.fm account not found.`);\r\n\t\tconst track = res.recenttracks.track[0];\r\n\t\tlet buf = ``;\r\n\t\tif (track.image?.length) {\r\n\t\t\tconst imageIndex = track.image.length >= 3 ? 2 : track.image.length - 1;\r\n\t\t\tif (track.image[imageIndex]['#text']) {\r\n\t\t\t\tbuf += ``;\r\n\t\t\t}\r\n\t\t\tbuf += `
${Utils.escapeHTML(displayName || accountName)}`;\r\n\t\t\tif (track['@attr']?.nowplaying) {\r\n\t\t\t\tbuf += ` is currently listening to:`;\r\n\t\t\t} else {\r\n\t\t\t\tbuf += ` was last seen listening to:`;\r\n\t\t\t}\r\n\t\t\tbuf += `
`;\r\n\t\t\tconst trackName = `${track.artist?.['#text'] ? `${track.artist['#text']} - ` : ''}${track.name}`;\r\n\t\t\tlet videoIDs: string[] | undefined;\r\n\t\t\ttry {\r\n\t\t\t\tvideoIDs = await YouTube.searchVideo(trackName, 1);\r\n\t\t\t} catch (e: any) {\r\n\t\t\t\tthrow new Chat.ErrorMessage(`Error while fetching video data: ${e.message}`);\r\n\t\t\t}\r\n\t\t\tif (!videoIDs?.length) {\r\n\t\t\t\tthrow new Chat.ErrorMessage(`Something went wrong with the YouTube API.`);\r\n\t\t\t}\r\n\t\t\tbuf += `${Utils.escapeHTML(trackName)}`;\r\n\t\t\tbuf += `
${this.getScrobbleBadge()}`;\r\n\t\t}\r\n\t\treturn buf;\r\n\t}\r\n\r\n\taddAccountName(userid: ID, accountName: string) {\r\n\t\tthis.checkHasKey();\r\n\t\taccountName = accountName.trim();\r\n\t\tif (lastfm[userid]) {\r\n\t\t\tconst oldName = lastfm[userid];\r\n\t\t\tlastfm[userid] = accountName;\r\n\t\t\tsaveLastFM();\r\n\t\t\treturn `last.fm account name changed from '${oldName}' to '${accountName}'.`;\r\n\t\t}\r\n\t\tlastfm[userid] = accountName;\r\n\t\tsaveLastFM();\r\n\t\treturn `Registered last.fm account '${accountName}'.`;\r\n\t}\r\n\r\n\tvalidateAccountName(accountName: string) {\r\n\t\taccountName = accountName.trim();\r\n\t\tconst sanitizedName = accountName.replace(/[^-_a-zA-Z0-9]+/g, '');\r\n\t\tif (!(!accountName.includes(' ') && accountName === sanitizedName && /^[a-zA-Z]/.test(sanitizedName) &&\r\n\t\t\tsanitizedName.length > 1 && sanitizedName.length < 16)) {\r\n\t\t\tthrow new Chat.ErrorMessage(`The provided account name (${sanitizedName}) is invalid. Valid last.fm usernames are between 2-15 characters, start with a letter, and only contain letters, numbers, hyphens, and underscores.`);\r\n\t\t}\r\n\t\treturn true;\r\n\t}\r\n\r\n\tgetAccountName(username: string) {\r\n\t\tif (lastfm[toID(username)]) return lastfm[toID(username)];\r\n\t\treturn username.trim().replace(/ /g, '_').replace(/[^-_a-zA-Z0-9]/g, '');\r\n\t}\r\n\r\n\tasync tryGetTrackData(track: string, artist?: string) {\r\n\t\tthis.checkHasKey();\r\n\t\tconst query: {[k: string]: any} = {\r\n\t\t\tmethod: 'track.search', limit: 1, api_key: Config.lastfmkey, track, format: 'json',\r\n\t\t};\r\n\t\tif (artist) query.artist = artist;\r\n\t\tlet raw;\r\n\t\ttry {\r\n\t\t\traw = await Net(API_ROOT).get({query});\r\n\t\t} catch {\r\n\t\t\tthrow new Chat.ErrorMessage(`No track data found.`);\r\n\t\t}\r\n\t\tconst req = JSON.parse(raw);\r\n\t\tlet buf = ``;\r\n\t\tif (req.results?.trackmatches?.track?.length) {\r\n\t\t\tbuf += `
`;\r\n\t\t\tconst obj = req.results.trackmatches.track[0];\r\n\t\t\tconst trackName = obj.name || \"Untitled\";\r\n\t\t\tconst artistName = obj.artist || \"Unknown Artist\";\r\n\t\t\tconst searchName = `${artistName} - ${trackName}`;\r\n\t\t\tif (obj.image?.length) {\r\n\t\t\t\tconst img = obj.image;\r\n\t\t\t\tconst imageIndex = img.length >= 3 ? 2 : img.length - 1;\r\n\t\t\t\tif (img[imageIndex]['#text'] && !DEFAULT_IMAGES.includes(img[imageIndex]['#text'])) {\r\n\t\t\t\t\tbuf += ``;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\tbuf += ``;\r\n\t\t\tconst artistUrl = obj.url.split('_/')[0];\r\n\t\t\tbuf += `${artistName} - ${trackName}
`;\r\n\t\t\tlet videoIDs: string[] | undefined;\r\n\t\t\ttry {\r\n\t\t\t\tvideoIDs = await YouTube.searchVideo(searchName, 1);\r\n\t\t\t} catch (e: any) {\r\n\t\t\t\tthrow new Chat.ErrorMessage(`Error while fetching video data: ${e.message}`);\r\n\t\t\t}\r\n\t\t\tif (!videoIDs?.length) {\r\n\t\t\t\tbuf += searchName;\r\n\t\t\t} else {\r\n\t\t\t\tbuf += `YouTube link`;\r\n\t\t\t}\r\n\t\t\tbuf += `
${this.getScrobbleBadge()}`;\r\n\t\t}\r\n\t\tif (req.error) {\r\n\t\t\tthrow new Chat.ErrorMessage(`${req.message}.`);\r\n\t\t}\r\n\t\tif (!buf) {\r\n\t\t\tthrow new Chat.ErrorMessage(`No results for '${artist ? `${artist} - ` : ``}${track}' found. Check spelling?`);\r\n\t\t}\r\n\t\treturn buf;\r\n\t}\r\n\tcheckHasKey() {\r\n\t\tif (!Config.lastfmkey) {\r\n\t\t\tthrow new Chat.ErrorMessage(`This server does not support last.fm commands. If you're the owner, you can enable them by setting up Config.lastfmkey.`);\r\n\t\t}\r\n\t}\r\n\tgetScrobbleBadge() {\r\n\t\treturn `
[powered by AudioScrobbler]
`;\r\n\t}\r\n}\r\n\r\nclass RecommendationsInterface {\r\n\tgetRandomRecommendation() {\r\n\t\tconst recs = recommendations.saved;\r\n\t\treturn recs[Math.floor(Math.random() * recs.length)];\r\n\t}\r\n\r\n\tasync add(\r\n\t\tartist: string, title: string, url: string, description: string,\r\n\t\tusername: string, tags: string[], avatar?: string\r\n\t) {\r\n\t\tartist = artist.trim();\r\n\t\ttitle = title.trim();\r\n\t\tif (this.get(artist, title)) {\r\n\t\t\tthrow new Chat.ErrorMessage(`The song titled '${title}' by ${artist} is already recommended.`);\r\n\t\t}\r\n\t\tif (!/^https?:\\/\\//.test(url)) url = `https://${url}`;\r\n\t\tif (!YouTube.linkRegex.test(url)) {\r\n\t\t\tthrow new Chat.ErrorMessage(`Please provide a valid YouTube link.`);\r\n\t\t}\r\n\t\turl = url.split('&')[0];\r\n\t\tconst videoInfo = await YouTube.getVideoData(url);\r\n\t\tthis.checkTags(tags);\r\n\t\t// JUST in case\r\n\t\tif (!recommendations.saved) recommendations.saved = [];\r\n\t\tconst rec: Recommendation = {artist, title, videoInfo, url, description, tags, userData: {name: username}, likes: 0};\r\n\t\tif (!rec.tags.map(toID).includes(toID(username))) rec.tags.push(username);\r\n\t\tif (!rec.tags.map(toID).includes(toID(artist))) rec.tags.push(artist);\r\n\t\tif (avatar) rec.userData.avatar = avatar;\r\n\t\trecommendations.saved.push(rec);\r\n\t\tsaveRecommendations();\r\n\t}\r\n\r\n\tdelete(artist: string, title: string) {\r\n\t\tartist = artist.trim();\r\n\t\ttitle = title.trim();\r\n\t\tif (!recommendations.saved?.length) {\r\n\t\t\tthrow new Chat.ErrorMessage(`The song titled '${title}' by ${artist} isn't recommended.`);\r\n\t\t}\r\n\t\tconst recIndex = this.getIndex(artist, title);\r\n\t\tif (recIndex < 0) {\r\n\t\t\tthrow new Chat.ErrorMessage(`The song titled '${title}' by ${artist} isn't recommended.`);\r\n\t\t}\r\n\t\trecommendations.saved.splice(recIndex, 1);\r\n\t\tsaveRecommendations();\r\n\t}\r\n\r\n\tasync suggest(\r\n\t\tartist: string, title: string, url: string, description: string,\r\n\t\tusername: string, tags: string[], avatar?: string\r\n\t) {\r\n\t\tartist = artist.trim();\r\n\t\ttitle = title.trim();\r\n\t\tif (this.get(artist, title)) {\r\n\t\t\tthrow new Chat.ErrorMessage(`The song titled '${title}' by ${artist} is already recommended.`);\r\n\t\t}\r\n\t\tif (this.get(artist, title, null, true)) {\r\n\t\t\tthrow new Chat.ErrorMessage(`The song titled '${title}' by ${artist} is already suggested.`);\r\n\t\t}\r\n\t\tif (!/^https?:\\/\\//.test(url)) url = `https://${url}`;\r\n\t\tif (!YouTube.linkRegex.test(url)) {\r\n\t\t\tthrow new Chat.ErrorMessage(`Please provide a valid YouTube link.`);\r\n\t\t}\r\n\t\turl = url.split('&')[0];\r\n\t\tconst videoInfo = await YouTube.getVideoData(url);\r\n\t\tthis.checkTags(tags);\r\n\t\tconst rec: Recommendation = {artist, title, videoInfo, url, description, tags, userData: {name: username}, likes: 0};\r\n\t\tif (!rec.tags.map(toID).includes(toID(username))) rec.tags.push(username);\r\n\t\tif (!rec.tags.map(toID).includes(toID(artist))) rec.tags.push(artist);\r\n\t\tif (avatar) rec.userData.avatar = avatar;\r\n\t\trecommendations.suggested.push(rec);\r\n\t\tsaveRecommendations();\r\n\t}\r\n\r\n\tapproveSuggestion(submitter: string, artist: string, title: string) {\r\n\t\tartist = artist.trim();\r\n\t\ttitle = title.trim();\r\n\t\tconst rec = this.get(artist, title, submitter, true);\r\n\t\tif (!rec) {\r\n\t\t\tthrow new Chat.ErrorMessage(`There is no song titled '${title}' by ${artist} suggested from ${submitter.trim()}.`);\r\n\t\t}\r\n\t\tif (!recommendations.saved) recommendations.saved = [];\r\n\t\trecommendations.saved.push(rec);\r\n\t\trecommendations.suggested.splice(recommendations.suggested.indexOf(rec), 1);\r\n\t\tsaveRecommendations();\r\n\t}\r\n\r\n\tdenySuggestion(submitter: string, artist: string, title: string) {\r\n\t\tartist = artist.trim();\r\n\t\ttitle = title.trim();\r\n\t\tconst index = this.getIndex(artist, title, submitter, true);\r\n\t\tif (index < 0) {\r\n\t\t\tthrow new Chat.ErrorMessage(`There is no song titled '${title}' by ${artist} suggested from ${submitter.trim()}.`);\r\n\t\t}\r\n\t\trecommendations.suggested.splice(index, 1);\r\n\t\tsaveRecommendations();\r\n\t}\r\n\r\n\tasync render(rec: Recommendation, suggested = false) {\r\n\t\tlet buf = ``;\r\n\t\tbuf += `
`;\r\n\t\tbuf += ``;\r\n\t\tif (rec.videoInfo === undefined) {\r\n\t\t\trec.videoInfo = await YouTube.getVideoData(rec.videoInfo);\r\n\t\t\tsaveRecommendations();\r\n\t\t}\r\n\t\tif (rec.videoInfo) {\r\n\t\t\tbuf += ``;\r\n\t\t}\r\n\t\tbuf += Utils.html``;\r\n\t\tif (rec.userData.avatar) {\r\n\t\t\tbuf += ``;\r\n\t\t} else {\r\n\t\t\tbuf += ``;\r\n\t\t}\r\n\t\tbuf += `

`;\r\n\t\t\tbuf += `${!suggested ? `${Chat.count(rec.likes, \"points\")} | ` : ``}${rec.videoInfo.views} views
${rec.artist} - ${rec.title}`;\r\n\t\tconst tags = rec.tags.map(x => Utils.escapeHTML(x))\r\n\t\t\t.filter(x => toID(x) !== toID(rec.userData.name) && toID(x) !== toID(rec.artist));\r\n\t\tif (tags.length) {\r\n\t\t\tbuf += `
Tags: ${tags.join(', ')}`;\r\n\t\t}\r\n\t\tif (rec.description) {\r\n\t\t\tbuf += `
Description: ${Utils.escapeHTML(rec.description)}`;\r\n\t\t}\r\n\t\tif (!rec.videoInfo && !suggested) {\r\n\t\t\tbuf += `
Score: ${Chat.count(rec.likes, \"points\")}`;\r\n\t\t}\r\n\t\tif (!rec.userData.avatar) {\r\n\t\t\tbuf += `
Recommended by: ${rec.userData.name}`;\r\n\t\t}\r\n\t\tbuf += `
`;\r\n\t\tif (suggested) {\r\n\t\t\tbuf += Utils.html` | `;\r\n\t\t\tbuf += Utils.html``;\r\n\t\t} else {\r\n\t\t\tbuf += Utils.html``;\r\n\t\t}\r\n\t\tbuf += `
`;\r\n\t\t\tconst isCustom = rec.userData.avatar.startsWith('#');\r\n\t\t\tbuf += ``;\r\n\t\t\tbuf += `
Recommended by:`;\r\n\t\t\tbuf += `
${rec.userData.name}
Recommended by: ${rec.userData.name}
`;\r\n\t\tbuf += `
`;\r\n\t\treturn buf;\r\n\t}\r\n\r\n\tlikeRecommendation(artist: string, title: string, liker: User) {\r\n\t\tconst rec = this.get(artist, title);\r\n\t\tif (!rec) {\r\n\t\t\tthrow new Chat.ErrorMessage(`The song titled '${title}' by ${artist} isn't recommended.`);\r\n\t\t}\r\n\t\tif (!rec.liked) {\r\n\t\t\trec.liked = {ips: [], userids: []};\r\n\t\t}\r\n\t\tif ((!Config.noipchecks && rec.liked.ips.includes(liker.latestIp)) || rec.liked.userids.includes(liker.id)) {\r\n\t\t\tthrow new Chat.ErrorMessage(`You've already liked this recommendation.`);\r\n\t\t}\r\n\t\trec.likes++;\r\n\t\trec.liked.ips.push(liker.latestIp);\r\n\t\trec.liked.userids.push(liker.id);\r\n\t\tsaveRecommendations();\r\n\t}\r\n\r\n\tget(artist: string, title: string, submitter: string | null = null, fromSuggestions = false) {\r\n\t\tlet recs = recommendations.saved;\r\n\t\tif (fromSuggestions) recs = recommendations.suggested;\r\n\t\treturn recs.find(x => (\r\n\t\t\ttoID(x.artist) === toID(artist) &&\r\n\t\t\ttoID(x.title) === toID(title) &&\r\n\t\t\t(!submitter || toID(x.userData.name) === toID(submitter))\r\n\t\t));\r\n\t}\r\n\r\n\tgetIndex(artist: string, title: string, submitter: string | null = null, fromSuggestions = false) {\r\n\t\tlet recs = recommendations.saved;\r\n\t\tif (fromSuggestions) recs = recommendations.suggested;\r\n\t\treturn recs.findIndex(x => (\r\n\t\t\ttoID(x.artist) === toID(artist) &&\r\n\t\t\ttoID(x.title) === toID(title) &&\r\n\t\t\t(!submitter || toID(x.userData.name) === toID(submitter))\r\n\t\t));\r\n\t}\r\n\r\n\tcheckTags(tags: string[]) {\r\n\t\tconst cleansedTags = new Set();\r\n\t\tfor (const tag of tags) {\r\n\t\t\tif (!toID(tag)) throw new Chat.ErrorMessage(`Empty tag detected.`);\r\n\t\t\tif (cleansedTags.has(toID(tag))) {\r\n\t\t\t\tthrow new Chat.ErrorMessage(`Duplicate tag: ${tag.trim()}`);\r\n\t\t\t}\r\n\t\t\tcleansedTags.add(toID(tag));\r\n\t\t}\r\n\t}\r\n}\r\n\r\nexport const LastFM = new LastFMInterface();\r\nexport const Recs = new RecommendationsInterface();\r\n\r\nexport const commands: Chat.ChatCommands = {\r\n\tregisterlastfm(target, room, user) {\r\n\t\tif (!target) return this.parse(`/help registerlastfm`);\r\n\t\tthis.checkChat(target);\r\n\t\ttarget = this.filter(target) || '';\r\n\t\tif (!target) {\r\n\t\t\tthrow new Chat.ErrorMessage(`The provided account name has phrases that PS doesn't allow.`);\r\n\t\t}\r\n\t\tLastFM.validateAccountName(target);\r\n\t\tthis.sendReply(LastFM.addAccountName(user.id, target.trim()));\r\n\t},\r\n\tregisterlastfmhelp: [\r\n\t\t`/registerlastfm [username] - Adds the provided [username] to the last.fm database for scrobbling.`,\r\n\t\t`Usernames can only be 2-15 characters long, must start with a letter, and can only contain letters, numbers, hyphens, and underscores.`,\r\n\t],\r\n\r\n\tasync lastfm(target, room, user) {\r\n\t\tthis.checkChat();\r\n\t\tif (!user.autoconfirmed) return this.errorReply(`You cannot use this command while not autoconfirmed.`);\r\n\t\tthis.runBroadcast(true);\r\n\t\tconst targetUsername = this.splitUser(target).targetUsername || (user.named ? user.name : '');\r\n\t\tconst username = LastFM.getAccountName(targetUsername);\r\n\t\tthis.sendReplyBox(await LastFM.getScrobbleData(username, targetUsername));\r\n\t},\r\n\tlastfmhelp: [\r\n\t\t`/lastfm [username] - Displays the last scrobbled song for the person using the command or for [username] if provided.`,\r\n\t\t`To link up your last.fm account, check out \"/help registerlastfm\".`,\r\n\t],\r\n\r\n\tasync track(target, room, user) {\r\n\t\tif (!target) return this.parse('/help track');\r\n\t\tthis.checkChat();\r\n\t\tif (!user.autoconfirmed) return this.errorReply(`You cannot use this command while not autoconfirmed.`);\r\n\t\tconst [track, artist] = this.splitOne(target);\r\n\t\tif (!track) return this.parse('/help track');\r\n\t\tthis.runBroadcast(true);\r\n\t\tthis.sendReplyBox(await LastFM.tryGetTrackData(track, artist || undefined));\r\n\t},\r\n\ttrackhelp: [\r\n\t\t`/track [song name], [artist] - Displays the most relevant search result to the song name (and artist if specified) provided.`,\r\n\t],\r\n\r\n\taddrec: 'addrecommendation',\r\n\tasync addrecommendation(target, room, user) {\r\n\t\troom = this.requireRoom('thestudio' as RoomID);\r\n\t\tthis.checkCan('show', null, room);\r\n\t\tconst [artist, title, url, description, ...tags] = target.split('|').map(x => x.trim());\r\n\t\tif (!(artist && title && url && description && tags?.length)) {\r\n\t\t\treturn this.parse(`/help addrecommendation`);\r\n\t\t}\r\n\r\n\t\tconst cleansedTags = tags.map(x => x.trim());\r\n\t\tawait Recs.add(artist, title, url, description, user.name, cleansedTags, String(user.avatar));\r\n\r\n\t\tthis.privateModAction(`${user.name} added a recommendation for '${title}' by ${artist}.`);\r\n\t\tthis.modlog(`RECOMMENDATION`, null, `add: '${toID(title)}' by ${toID(artist)}`);\r\n\t},\r\n\taddrecommendationhelp: [\r\n\t\t`/addrecommendation artist | song title | url | description | tag1 | tag2 | ... - Adds a song recommendation. Requires: + % @ * # &`,\r\n\t],\r\n\r\n\tdelrec: 'removerecommendation',\r\n\tremoverecommendation(target, room, user) {\r\n\t\troom = this.requireRoom('thestudio' as RoomID);\t\tconst [artist, title] = target.split(`|`).map(x => x.trim());\r\n\t\tif (!(artist && title)) return this.parse(`/help removerecommendation`);\r\n\t\tconst rec = Recs.get(artist, title);\r\n\t\tif (!rec) throw new Chat.ErrorMessage(`Recommendation not found.`);\r\n\t\tif (toID(rec.userData.name) !== user.id) {\r\n\t\t\tthis.checkCan('mute', null, room);\r\n\t\t}\r\n\r\n\t\tRecs.delete(artist, title);\r\n\r\n\t\tthis.privateModAction(`${user.name} removed a recommendation for '${title}' by ${artist}.`);\r\n\t\tthis.modlog(`RECOMMENDATION`, null, `remove: '${toID(title)}' by ${toID(artist)}`);\r\n\t},\r\n\tremoverecommendationhelp: [\r\n\t\t`/removerecommendation artist | song title - Removes a song recommendation. Requires: % @ * # &`,\r\n\t\t`If you added a recommendation, you can remove it on your own without being one of the required ranks.`,\r\n\t],\r\n\r\n\tsuggestrec: 'suggestrecommendation',\r\n\tasync suggestrecommendation(target, room, user) {\r\n\t\troom = this.requireRoom('thestudio' as RoomID);\r\n\t\tif (!target) {\r\n\t\t\treturn this.parse('/help suggestrecommendation');\r\n\t\t}\r\n\t\tthis.checkChat(target);\r\n\t\tif (!user.autoconfirmed) return this.errorReply(`You cannot use this command while not autoconfirmed.`);\r\n\t\tconst [artist, title, url, description, ...tags] = target.split('|').map(x => x.trim());\r\n\t\tif (!(artist && title && url && description && tags?.length)) {\r\n\t\t\treturn this.parse(`/help suggestrecommendation`);\r\n\t\t}\r\n\r\n\t\tconst cleansedTags = tags.map(x => x.trim());\r\n\t\tawait Recs.suggest(artist, title, url, description, user.name, cleansedTags, String(user.avatar));\r\n\t\tthis.sendReply(`Your suggestion for '${title}' by ${artist} has been submitted.`);\r\n\t\tconst html = await Recs.render({\r\n\t\t\tartist, title, url, description,\r\n\t\t\tuserData: {name: user.name, avatar: String(user.avatar)},\r\n\t\t\ttags: cleansedTags, likes: 0, videoInfo: null,\r\n\t\t}, true);\r\n\t\troom.sendRankedUsers(`|html|${html}`, '%');\r\n\t},\r\n\tsuggestrecommendationhelp: [\r\n\t\t`/suggestrecommendation artist | song title | url | description | tag1 | tag2 | ... - Suggest a song recommendation.`,\r\n\t],\r\n\r\n\tapprovesuggestion(target, room, user) {\r\n\t\troom = this.requireRoom('thestudio' as RoomID);\r\n\t\tthis.checkCan('mute', null, room);\r\n\t\tconst [submitter, artist, title] = target.split('|').map(x => x.trim());\r\n\t\tif (!(submitter && artist && title)) return this.parse(`/help approvesuggestion`);\r\n\r\n\t\tRecs.approveSuggestion(submitter, artist, title);\r\n\r\n\t\tthis.privateModAction(`${user.name} approved a suggested recommendation from ${submitter} for '${title}' by ${artist}.`);\r\n\t\tthis.modlog(`RECOMMENDATION`, null, `approve: '${toID(title)}' by ${toID(artist)} from ${submitter}`);\r\n\t},\r\n\tapprovesuggestionhelp: [\r\n\t\t`/approvesuggestion submitter | artist | strong title - Approve a submitted song recommendation. Requires: % @ * # &`,\r\n\t],\r\n\r\n\tdenysuggestion(target, room, user) {\r\n\t\troom = this.requireRoom('thestudio' as RoomID);\r\n\t\tthis.checkCan('mute', null, room);\r\n\t\tconst [submitter, artist, title] = target.split('|').map(x => x.trim());\r\n\t\tif (!(submitter && artist && title)) return this.parse(`/help approvesuggestion`);\r\n\r\n\t\tRecs.denySuggestion(submitter, artist, title);\r\n\r\n\t\tthis.privateModAction(`${user.name} denied a suggested recommendation from ${submitter} for '${title}' by ${artist}.`);\r\n\t\tthis.modlog(`RECOMMENDATION`, null, `deny: '${toID(title)}' by ${toID(artist)} from ${submitter}`);\r\n\t},\r\n\tdenysuggestionhelp: [\r\n\t\t`/denysuggestion submitter | artist | strong title - Deny a submitted song recommendation. Requires: % @ * # &`,\r\n\t],\r\n\r\n\trec: 'recommendation',\r\n\tsearchrec: 'recommendation',\r\n\tviewrec: 'recommendation',\r\n\tsearchrecommendation: 'recommendation',\r\n\tviewrecommendation: 'recommendation',\r\n\trandrec: 'recommendation',\r\n\trandomrecommendation: 'recommendation',\r\n\tasync recommendation(target, room, user) {\r\n\t\tif (!recommendations.saved.length) {\r\n\t\t\tthrow new Chat.ErrorMessage(`There are no recommendations saved.`);\r\n\t\t}\r\n\t\troom = this.requireRoom('thestudio' as RoomID);\r\n\t\tthis.runBroadcast();\r\n\t\tif (!target) {\r\n\t\t\treturn this.sendReply(`|html|${await Recs.render(Recs.getRandomRecommendation())}`);\r\n\t\t}\r\n\t\tconst matches: Recommendation[] = [];\r\n\t\ttarget = target.slice(0, 300);\r\n\t\tconst args = target.split(',');\r\n\t\tfor (const rec of recommendations.saved) {\r\n\t\t\tif (!args.every(x => rec.tags.map(toID).includes(toID(x)))) continue;\r\n\t\t\tmatches.push(rec);\r\n\t\t}\r\n\t\tif (!matches.length) {\r\n\t\t\tthrow new Chat.ErrorMessage(`No matches found.`);\r\n\t\t}\r\n\t\tconst sample = Utils.shuffle(matches)[0];\r\n\t\tthis.sendReply(`|html|${await Recs.render(sample)}`);\r\n\t},\r\n\trecommendationhelp: [\r\n\t\t`/recommendation [key1, key2, key3, ...] - Displays a random recommendation that matches all keys, if one exists.`,\r\n\t\t`If no arguments are provided, a random recommendation is shown.`,\r\n\t\t`/addrecommendation artist | song title | url | description | tag1 | tag2 | ... - Adds a song recommendation. Requires: + % @ * # &`,\r\n\t\t`/removerecommendation artist | song title - Removes a song recommendation. Requires: % @ * # &`,\r\n\t\t`If you added a recommendation, you can remove it on your own without being one of the required ranks.`,\r\n\t\t`/suggestrecommendation artist | song title | url | description | tag1 | tag2 | ... - Suggest a song recommendation.`,\r\n\t],\r\n\r\n\tlikerec: 'likerecommendation',\r\n\tlikerecommendation(target, room, user, connection) {\r\n\t\troom = this.requireRoom('thestudio' as RoomID);\r\n\t\tconst [artist, title] = target.split('|').map(x => x.trim());\r\n\t\tif (!(artist && title)) return this.parse(`/help likerecommendation`);\r\n\t\tRecs.likeRecommendation(artist, title, user);\r\n\t\tthis.sendReply(`You liked '${title}' by ${artist}.`);\r\n\t},\r\n\tlikerecommendationhelp: [\r\n\t\t`/likerecommendation artist | title - Upvotes a recommendation for the provided artist and title.`,\r\n\t],\r\n\r\n\tviewrecs: 'viewrecommendations',\r\n\tviewrecommendations(target, room, user) {\r\n\t\troom = this.requireRoom('thestudio' as RoomID);\r\n\t\tthis.parse(`/j view-recommendations-${room.roomid}`);\r\n\t},\r\n\tviewrecommendationshelp: [\r\n\t\t`/viewrecommendations OR /viewrecs - View all recommended songs.`,\r\n\t],\r\n\r\n\tviewsuggestions: 'viewsuggestedrecommendations',\r\n\tviewsuggestedrecs: 'viewsuggestedrecommendations',\r\n\tviewsuggestedrecommendations(target, room, user) {\r\n\t\troom = this.requireRoom('thestudio' as RoomID);\r\n\t\tthis.parse(`/j view-suggestedrecommendations-${room.roomid}`);\r\n\t},\r\n\tviewsuggestedrecommendationshelp: [\r\n\t\t`/viewsuggestedrecommendations OR /viewsuggestions - View all suggested recommended songs. Requires: % @ * # &`,\r\n\t],\r\n};\r\n\r\nexport const pages: Chat.PageTable = {\r\n\tasync recommendations(query, user, connection) {\r\n\t\tconst room = this.requireRoom();\r\n\t\tthis.checkCan('mute', null, room);\r\n\t\tif (!user.inRooms.has(room.roomid)) throw new Chat.ErrorMessage(`You must be in ${room.title} to view this page.`);\r\n\t\tthis.title = 'Recommendations';\r\n\t\tlet buf = `
`;\r\n\t\tbuf += ``;\r\n\t\tconst recs = recommendations.saved;\r\n\t\tif (!recs?.length) {\r\n\t\t\treturn `${buf}

There are currently no recommendations.

`;\r\n\t\t}\r\n\t\tbuf += `

Recommendations (${recs.length}):

`;\r\n\t\tfor (const rec of recs) {\r\n\t\t\tbuf += `
`;\r\n\t\t\tbuf += await Recs.render(rec);\r\n\t\t\tif (user.can('mute', null, room) || toID(rec.userData.name) === user.id) {\r\n\t\t\t\tbuf += `
`;\r\n\t\t\t}\r\n\t\t\tbuf += `
`;\r\n\t\t}\r\n\t\treturn buf;\r\n\t},\r\n\tasync suggestedrecommendations(query, user, connection) {\r\n\t\tconst room = this.requireRoom();\r\n\t\tthis.checkCan('mute', null, room);\r\n\t\tif (!user.inRooms.has(room.roomid)) throw new Chat.ErrorMessage(`You must be in ${room.title} to view this page.`);\r\n\t\tthis.title = 'Suggested Recommendations';\r\n\t\tlet buf = `
`;\r\n\t\tbuf += ``;\r\n\t\tconst recs = recommendations.suggested;\r\n\t\tif (!recs?.length) {\r\n\t\t\treturn `${buf}

There are currently no suggested recommendations.

`;\r\n\t\t}\r\n\t\tbuf += `

Suggested Recommendations (${recs.length}):

`;\r\n\t\tfor (const rec of recs) {\r\n\t\t\tbuf += `
`;\r\n\t\t\tbuf += await Recs.render(rec, true);\r\n\t\t\tbuf += `
`;\r\n\t\t}\r\n\t\treturn buf;\r\n\t},\r\n};\r\n"], "mappings": "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", "names": [] }