Skip to content

Commit

Permalink
Studio: Only request YT video data once (smogon#7595)
Browse files Browse the repository at this point in the history
  • Loading branch information
mia-pi-git authored and Quanyails committed May 12, 2021
1 parent c3ed132 commit 66a2438
Show file tree
Hide file tree
Showing 2 changed files with 35 additions and 23 deletions.
45 changes: 25 additions & 20 deletions server/chat-plugins/the-studio.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import {FS} from '../../lib/fs';
import {Net} from '../../lib/net';
import {Utils} from '../../lib/utils';
import {YouTube} from './youtube';
import {YouTube, VideoData} from './youtube';

const LASTFM_DB = 'config/chat-plugins/lastfm.json';
const RECOMMENDATIONS = 'config/chat-plugins/the-studio.json';
Expand All @@ -25,6 +25,7 @@ interface Recommendation {
artist: string;
title: string;
url: string;
videoInfo: VideoData | null;
description: string;
tags: string[];
userData: {
Expand Down Expand Up @@ -223,7 +224,7 @@ class RecommendationsInterface {
}
}

add(
async add(
artist: string, title: string, url: string, description: string,
username: string, tags: string[], avatar?: string
) {
Expand All @@ -237,10 +238,11 @@ class RecommendationsInterface {
throw new Chat.ErrorMessage(`Please provide a valid YouTube link.`);
}
url = url.split('&')[0];
const videoInfo = await YouTube.getVideoData(url);
this.checkTags(tags);
// JUST in case
if (!recommendations.saved) recommendations.saved = [];
const rec: Recommendation = {artist, title, url, description, tags, userData: {name: username}, likes: 0};
const rec: Recommendation = {artist, title, videoInfo, url, description, tags, userData: {name: username}, likes: 0};
if (!rec.tags.map(toID).includes(toID(username))) rec.tags.push(username);
if (!rec.tags.map(toID).includes(toID(artist))) rec.tags.push(artist);
if (avatar) rec.userData.avatar = avatar;
Expand All @@ -262,7 +264,7 @@ class RecommendationsInterface {
saveRecommendations();
}

suggest(
async suggest(
artist: string, title: string, url: string, description: string,
username: string, tags: string[], avatar?: string
) {
Expand All @@ -279,8 +281,9 @@ class RecommendationsInterface {
throw new Chat.ErrorMessage(`Please provide a valid YouTube link.`);
}
url = url.split('&')[0];
const videoInfo = await YouTube.getVideoData(url);
this.checkTags(tags);
const rec: Recommendation = {artist, title, url, description, tags, userData: {name: username}, likes: 0};
const rec: Recommendation = {artist, title, videoInfo, url, description, tags, userData: {name: username}, likes: 0};
if (!rec.tags.map(toID).includes(toID(username))) rec.tags.push(username);
if (!rec.tags.map(toID).includes(toID(artist))) rec.tags.push(artist);
if (avatar) rec.userData.avatar = avatar;
Expand Down Expand Up @@ -313,18 +316,16 @@ class RecommendationsInterface {
}

async render(rec: Recommendation, suggested = false) {
let videoInfo = null;
try {
videoInfo = await YouTube.getVideoData(rec.url);
} catch (e) {
throw new Chat.ErrorMessage(`Error while fetching recommendation URL: ${e.message}`);
}
let buf = ``;
buf += `<div style="color:#000;background:linear-gradient(rgba(210,210,210),rgba(225,225,225))">`;
buf += `<table style="margin:auto;background:rgba(255,255,255,0.25);padding:3px;"><tbody><tr>`;
if (videoInfo) {
buf += `<td style="text-align:center;"><img src="${videoInfo.thumbnail}" width="120" height="67" /><br />`;
buf += `<small><em>${!suggested ? `${Chat.count(rec.likes, "points")} | ` : ``}${videoInfo.views} views</em></small></td>`;
if (rec.videoInfo === undefined) {
rec.videoInfo = await YouTube.getVideoData(rec.videoInfo);
saveRecommendations();
}
if (rec.videoInfo) {
buf += `<td style="text-align:center;"><img src="${rec.videoInfo.thumbnail}" width="120" height="67" /><br />`;
buf += `<small><em>${!suggested ? `${Chat.count(rec.likes, "points")} | ` : ``}${rec.videoInfo.views} views</em></small></td>`;
}
buf += Utils.html`<td style="max-width:300px"><a href="${rec.url}" style="color:#000;font-weight:bold;">${rec.artist} - ${rec.title}</a>`;
const tags = rec.tags.map(x => Utils.escapeHTML(x))
Expand All @@ -335,7 +336,7 @@ class RecommendationsInterface {
if (rec.description) {
buf += `<br /><span style="display:inline-block;line-height:1.15em;"><strong>Description:</strong> ${Utils.escapeHTML(rec.description)}</span>`;
}
if (!videoInfo && !suggested) {
if (!rec.videoInfo && !suggested) {
buf += `<br /><strong>Score:</strong> ${Chat.count(rec.likes, "points")}`;
}
if (!rec.userData.avatar) {
Expand Down Expand Up @@ -434,6 +435,8 @@ export const commands: ChatCommands = {
],

async lastfm(target, room, user) {
this.checkChat();
if (!user.autoconfirmed) return this.errorReply(`You cannot use this command while not autoconfirmed.`);
this.runBroadcast(true);
this.splitTarget(target, true);
const username = LastFM.getAccountName(target ? target : user.name);
Expand All @@ -448,7 +451,8 @@ export const commands: ChatCommands = {

async track(target, room, user) {
if (!target) return this.parse('/help track');
this.checkChat(target);
this.checkChat();
if (!user.autoconfirmed) return this.errorReply(`You cannot use this command while not autoconfirmed.`);
const [track, artist] = this.splitOne(target);
if (!track) return this.parse('/help track');
this.runBroadcast(true);
Expand All @@ -459,7 +463,7 @@ export const commands: ChatCommands = {
],

addrec: 'addrecommendation',
addrecommendation(target, room, user) {
async addrecommendation(target, room, user) {
room = this.requireRoom('thestudio' as RoomID);
this.checkCan('show', null, room);
const [artist, title, url, description, ...tags] = target.split('|').map(x => x.trim());
Expand All @@ -468,7 +472,7 @@ export const commands: ChatCommands = {
}

const cleansedTags = tags.map(x => x.trim());
Recs.add(artist, title, url, description, user.name, cleansedTags, String(user.avatar));
await Recs.add(artist, title, url, description, user.name, cleansedTags, String(user.avatar));

this.privateModAction(`${user.name} added a recommendation for '${title}' by ${artist}.`);
this.modlog(`RECOMMENDATION`, null, `add: '${toID(title)}' by ${toID(artist)}`);
Expand Down Expand Up @@ -507,18 +511,19 @@ export const commands: ChatCommands = {
return this.parse('/help suggestrecommendation');
}
this.checkChat(target);
if (!user.autoconfirmed) return this.errorReply(`You cannot use this command while not autoconfirmed.`);
const [artist, title, url, description, ...tags] = target.split('|').map(x => x.trim());
if (!(artist && title && url && description && tags?.length)) {
return this.parse(`/help suggestrecommendation`);
}

const cleansedTags = tags.map(x => x.trim());
Recs.suggest(artist, title, url, description, user.name, cleansedTags, String(user.avatar));
await Recs.suggest(artist, title, url, description, user.name, cleansedTags, String(user.avatar));
this.sendReply(`Your suggestion for '${title}' by ${artist} has been submitted.`);
const html = await Recs.render({
artist, title, url, description,
userData: {name: user.name, avatar: String(user.avatar)},
tags: cleansedTags, likes: 0,
tags: cleansedTags, likes: 0, videoInfo: null,
}, true);
room.sendRankedUsers(`|html|${html}`, '%');
},
Expand Down
13 changes: 10 additions & 3 deletions server/chat-plugins/youtube.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const ROOT = 'https://www.googleapis.com/youtube/v3/';
const STORAGE_PATH = 'config/chat-plugins/youtube.json';

export const videoDataCache: Map<string, VideoData> = Chat.oldPlugins.youtube?.videoDataCache || new Map();
export const searchDataCache: Map<string, string[]> = Chat.oldPlugins.youtube?.searchDataCache || new Map();

interface ChannelEntry {
name: string;
Expand All @@ -27,7 +28,7 @@ interface ChannelEntry {
category?: string;
}

interface VideoData {
export interface VideoData {
id: string;
title: string;
date: string;
Expand Down Expand Up @@ -240,14 +241,20 @@ export class YoutubeInterface {
return FS(STORAGE_PATH).writeUpdate(() => JSON.stringify(this.data));
}
async searchVideo(name: string, limit?: number): Promise<string[] | undefined> {
const cached = searchDataCache.get(toID(name));
if (cached) {
return cached.slice(0, limit);
}
const raw = await Net(`${ROOT}search`).get({
query: {
part: 'snippet', q: name,
key: Config.youtubeKey, order: 'relevance', maxResults: limit || 10,
key: Config.youtubeKey, order: 'relevance',
},
});
const result = JSON.parse(raw);
return result.items?.map((item: AnyObject) => item?.id?.videoId).filter(Boolean);
const resultArray = result.items?.map((item: AnyObject) => item?.id?.videoId).filter(Boolean);
searchDataCache.set(toID(name), resultArray);
return resultArray.slice(0, limit);
}
async searchChannel(name: string, limit = 10): Promise<string[] | undefined> {
const raw = await Net(`${ROOT}search`).get({
Expand Down

0 comments on commit 66a2438

Please sign in to comment.