Implement basic caching

This commit is contained in:
2023-04-23 14:23:20 +07:00
parent 89c5b2fa8b
commit 59554b9fd8
3 changed files with 181 additions and 81 deletions
+30 -32
View File
@@ -31,22 +31,22 @@ const EMBEDS = {
export default class VRChatUser { export default class VRChatUser {
public static async run(data: HybridInteractionMessage, args: any) { public static async run(data: HybridInteractionMessage, args: any) {
let user; let query;
if (data.isMessage()) { if (data.isMessage()) {
if (typeof args[1] === 'undefined') if (typeof args[1] === 'undefined')
return await sendHybridInteractionMessageResponse(data, { return await sendHybridInteractionMessageResponse(data, {
embeds: [EMBEDS.NO_USER_MENTIONED(data)] embeds: [EMBEDS.NO_USER_MENTIONED(data)]
}); });
const [removed, ...newArgs] = args; const [removed, ...newArgs] = args;
user = newArgs.join(' '); query = newArgs.join(' ');
} else if (data.isApplicationCommand()) user = data.getSlashCommand().options.get('user')?.value?.toString(); } else if (data.isApplicationCommand()) query = data.getSlashCommand().options.get('user')?.value?.toString();
let result; let user;
try { try {
if (user.startsWith('usr_')) result = await VRChatAPI.client!.UsersApi.getUser(user); if (query.startsWith('usr_')) user = await VRChatAPI.getCachedUserById(query);
else { else {
let search_result = await VRChatAPI.client!.UsersApi.searchUsers(user, undefined, 100); let search_result = await VRChatAPI.client!.UsersApi.searchUsers(query, undefined, 100);
if (search_result.data.length === 0) if (search_result.data.length === 0)
return await sendHybridInteractionMessageResponse(data, { return await sendHybridInteractionMessageResponse(data, {
embeds: [EMBEDS.NO_USER_FOUND(data)] embeds: [EMBEDS.NO_USER_FOUND(data)]
@@ -54,14 +54,14 @@ export default class VRChatUser {
let foundExact = false; let foundExact = false;
for (let i = 0; i < search_result.data.length; i++) { for (let i = 0; i < search_result.data.length; i++) {
if (search_result.data[i].displayName.toLowerCase() === user.toLowerCase()) { if (search_result.data[i].displayName.toLowerCase() === query.toLowerCase()) {
result = await VRChatAPI.client!.UsersApi.getUser(search_result.data[i].id); user = await VRChatAPI.getCachedUserById(search_result.data[i].id);
foundExact = true; foundExact = true;
break; break;
} }
} }
if (!foundExact) result = await VRChatAPI.client!.UsersApi.getUser(search_result.data[0].id); if (!foundExact) user = await VRChatAPI.getCachedUserById(search_result.data[0].id);
} }
} catch (err: any) { } catch (err: any) {
// If 404 // If 404
@@ -75,7 +75,7 @@ export default class VRChatUser {
}); });
} }
if (!result) if (!user)
return await sendHybridInteractionMessageResponse(data, { return await sendHybridInteractionMessageResponse(data, {
embeds: [EMBEDS.NO_USER_FOUND(data)] embeds: [EMBEDS.NO_USER_FOUND(data)]
}); });
@@ -86,54 +86,52 @@ export default class VRChatUser {
const embed = makeInfoEmbed({ const embed = makeInfoEmbed({
icon: null, icon: null,
title: `**${result.data.displayName}**`, title: `**${user.displayName}**`,
description: `**${ description: `**${user.isFriend ? `${this.getState(user.status, user.state)}` : `⚪ Unknown`}**\n\u200b`,
result.data.isFriend ? `${this.getState(result.data.status, result.data.state)}` : `⚪ Unknown`
}**\n\u200b`,
fields: [ fields: [
{ {
name: `${this.getTrustRankEmoji(result.data.tags)} ${this.getTrustRank( name: `${this.getTrustRankEmoji(user.tags)} ${this.getTrustRank(user.tags)}${this.getExtraRanks(
result.data.tags user.tags
)}${this.getExtraRanks(result.data.tags)}`, )}`,
value: `\u200b`, value: `\u200b`,
inline: false inline: false
}, },
{ {
name: '✨ Status', name: '✨ Status',
value: `${result.data.statusDescription || 'None'}\n\u200b`, value: `${user.statusDescription || 'None'}\n\u200b`,
inline: true inline: true
}, },
{ {
name: '🌏 Language', name: '🌏 Language',
value: `${this.listLanguages(result.data.tags)}\n\u200b`, value: `${this.listLanguages(user.tags)}\n\u200b`,
inline: true inline: true
}, },
{ {
name: '✨ Bio', name: '✨ Bio',
value: `${result.data.bio}\n\u200b` value: `${user.bio}\n\u200b`
}, },
{ {
name: `❤ Account Information`, name: `❤ Account Information`,
value: `Joined: <t:${Math.round( value: `Joined: <t:${Math.round(new Date(user.date_joined).getTime() / 1000)}:R>, <t:${Math.round(
new Date(result.data.date_joined).getTime() / 1000 new Date(user.date_joined).getTime() / 1000
)}:R>, <t:${Math.round(new Date(result.data.date_joined).getTime() / 1000)}:f> )}:f>
Avatar Cloning: ${result.data.allowAvatarCopying ? 'Enabled' : 'Disabled'} Avatar Cloning: ${user.allowAvatarCopying ? 'Enabled' : 'Disabled'}
User ID: \`\`${result.data.id}\`\`\n` User ID: \`\`${user.id}\`\`\n`
} }
], ],
user: data.getUser() user: data.getUser()
}); });
embed.setAuthor({ embed.setAuthor({
name: `VRChat Profile`, name: `VRChat Profile`,
url: `https://vrchat.com/home/user/${result.data.id}`, url: `https://vrchat.com/home/user/${user.id}`,
iconURL: iconURL:
'https://www.theladders.com/s3proxy/company-photo.theladders.com/68949/5721f0dd-d662-4b5e-9c0b-236b8a41b0d3.png' 'https://www.theladders.com/s3proxy/company-photo.theladders.com/68949/5721f0dd-d662-4b5e-9c0b-236b8a41b0d3.png'
}); });
let userMainImage; let userMainImage;
if (result.data.userIcon) userMainImage = result.data.userIcon; if (user.userIcon) userMainImage = user.userIcon;
else if (result.data.currentAvatarImageUrl) userMainImage = result.data.currentAvatarImageUrl; else if (user.currentAvatarImageUrl) userMainImage = user.currentAvatarImageUrl;
if (userMainImage) if (userMainImage)
embed.setThumbnail( embed.setThumbnail(
@@ -141,9 +139,9 @@ export default class VRChatUser {
'https://osu.ppy.sh/images/layout/avatar-guest.png' 'https://osu.ppy.sh/images/layout/avatar-guest.png'
); );
if (result.data.profilePicOverride) if (user.profilePicOverride)
embed.setImage( embed.setImage(
ImagePorxy.signImageProxyURL(result.data.profilePicOverride, 'resize:fill:960:540:0') || ImagePorxy.signImageProxyURL(user.profilePicOverride, 'resize:fill:960:540:0') ||
'https://osu.ppy.sh/images/layout/avatar-guest.png' 'https://osu.ppy.sh/images/layout/avatar-guest.png'
); );
@@ -151,11 +149,11 @@ export default class VRChatUser {
new ButtonBuilder() new ButtonBuilder()
.setEmoji('🔗') .setEmoji('🔗')
.setLabel(' Open Profile') .setLabel(' Open Profile')
.setURL(`https://vrchat.com/home/user/${result.data.id}`) .setURL(`https://vrchat.com/home/user/${user.id}`)
.setStyle(ButtonStyle.Link) .setStyle(ButtonStyle.Link)
]); ]);
for (let link of result.data.bioLinks) { for (let link of user.bioLinks) {
this.parseBioLink(row, link); this.parseBioLink(row, link);
} }
+40 -36
View File
@@ -38,20 +38,20 @@ const EMBEDS = {
export default class VRChatWorld { export default class VRChatWorld {
public static async run(data: HybridInteractionMessage, args: any) { public static async run(data: HybridInteractionMessage, args: any) {
let world; let query;
if (data.isMessage()) { if (data.isMessage()) {
if (typeof args[1] === 'undefined') if (typeof args[1] === 'undefined')
return await sendHybridInteractionMessageResponse(data, { return await sendHybridInteractionMessageResponse(data, {
embeds: [EMBEDS.NO_WORLD_MENTIONED(data)] embeds: [EMBEDS.NO_WORLD_MENTIONED(data)]
}); });
const [removed, ...newArgs] = args; const [removed, ...newArgs] = args;
world = newArgs.join(' '); query = newArgs.join(' ');
} else if (data.isApplicationCommand()) world = data.getSlashCommand().options.get('world')?.value?.toString(); } else if (data.isApplicationCommand()) query = data.getSlashCommand().options.get('world')?.value?.toString();
let result; let result;
try { try {
if (world.startsWith('wrld_')) result = await VRChatAPI.client!.WorldsApi.getWorld(world); if (query.startsWith('wrld_')) result = await VRChatAPI.getCachedWorldById(query);
else { else {
let search_result = await VRChatAPI.client!.WorldsApi.searchWorlds( let search_result = await VRChatAPI.client!.WorldsApi.searchWorlds(
undefined, undefined,
@@ -61,7 +61,7 @@ export default class VRChatWorld {
100, 100,
undefined, //VRChat.OrderOption.Descending, undefined, //VRChat.OrderOption.Descending,
undefined, undefined,
world query
); );
if (search_result.data.length === 0) if (search_result.data.length === 0)
return await sendHybridInteractionMessageResponse(data, { return await sendHybridInteractionMessageResponse(data, {
@@ -70,14 +70,14 @@ export default class VRChatWorld {
let foundExact = false; let foundExact = false;
for (let i = 0; i < search_result.data.length; i++) { for (let i = 0; i < search_result.data.length; i++) {
if (search_result.data[i].name.toLowerCase() === world.toLowerCase()) { if (search_result.data[i].name.toLowerCase() === query.toLowerCase()) {
result = await VRChatAPI.client!.WorldsApi.getWorld(search_result.data[i].id); result = await VRChatAPI.getCachedWorldById(search_result.data[i].id);
foundExact = true; foundExact = true;
break; break;
} }
} }
if (!foundExact) result = await VRChatAPI.client!.WorldsApi.getWorld(search_result.data[0].id); if (!foundExact) result = await VRChatAPI.getCachedWorldById(search_result.data[0].id);
} }
} catch (err: any) { } catch (err: any) {
if (err.response?.status === 404) if (err.response?.status === 404)
@@ -99,76 +99,80 @@ export default class VRChatWorld {
await data.getSlashCommand().deferReply(); await data.getSlashCommand().deferReply();
} }
let author = await VRChatAPI.client!.UsersApi.getUser(result.data.authorId); let author = await VRChatAPI.getCachedUserById(result.authorId);
if (!author)
return await sendHybridInteractionMessageResponse(data, {
embeds: [EMBEDS.ERROR(data)]
});
const embed = makeInfoEmbed({ const embed = makeInfoEmbed({
icon: null, icon: null,
title: `**${result.data.name}**`, title: `**${result.name}**`,
description: `Created by **[${result.data.authorName}](https://vrchat.com/home/user/${author.data.id})**\n\u200b`, description: `Created by **[${result.authorName}](https://vrchat.com/home/user/${author.id})**\n\u200b`,
fields: [ fields: [
{ {
name: '🌎 World Description', name: '🌎 World Description',
value: `${result.data.description}\n\u200b`, value: `${result.description}\n\u200b`,
inline: false inline: false
}, },
{ {
name: `${this.getReleaseStatusEmoji(result.data)} Visiblity`, name: `${this.getReleaseStatusEmoji(result)} Visiblity`,
value: `${this.getReleaseStatus(result.data)}\n\u200b`, value: `${this.getReleaseStatus(result)}\n\u200b`,
inline: true inline: true
}, },
{ {
name: '✨ Visits', name: '✨ Visits',
value: `${this.numberWithCommas(result.data.visits)}\n\u200b`, value: `${this.numberWithCommas(result.visits)}\n\u200b`,
inline: true inline: true
}, },
{ {
name: '💕 Favorites', name: '💕 Favorites',
value: `${result.data.favorites ? this.numberWithCommas(result.data.favorites) : 0}\n\u200b`, value: `${result.favorites ? this.numberWithCommas(result.favorites) : 0}\n\u200b`,
inline: true inline: true
}, },
{ {
name: '👥 Players', name: '👥 Players',
value: `${result.data.occupants ? this.numberWithCommas(result.data.occupants) : 0} ${ value: `${result.occupants ? this.numberWithCommas(result.occupants) : 0} ${
result.data.instances result.instances
? `(${this.numberWithCommas( ? `(${this.numberWithCommas(result.publicOccupants || 0)} public, ${this.numberWithCommas(
result.data.publicOccupants || 0 result.privateOccupants || 0
)} public, ${this.numberWithCommas(result.data.privateOccupants || 0)} private)` )} private)`
: '' : ''
}\n\u200b`, }\n\u200b`,
inline: true inline: true
}, },
{ {
name: '💖 Popularity', name: '💖 Popularity',
value: `${this.numberWithCommas(result.data.popularity)}\n\u200b`, value: `${this.numberWithCommas(result.popularity)}\n\u200b`,
inline: true inline: true
}, },
{ {
name: '🔥 Heat', name: '🔥 Heat',
value: `${result.data.heat}\n\u200b`, value: `${result.heat}\n\u200b`,
inline: true inline: true
}, },
{ {
name: '📥 Capacity', name: '📥 Capacity',
value: `${result.data.capacity}\n\u200b`, value: `${result.capacity}\n\u200b`,
inline: true inline: true
}, },
{ {
name: '📅 Last updated', name: '📅 Last updated',
value: `<t:${Math.round(new Date(result.data.updated_at).getTime() / 1000)}:R>\n<t:${Math.round( value: `<t:${Math.round(new Date(result.updated_at).getTime() / 1000)}:R>\n<t:${Math.round(
new Date(result.data.updated_at).getTime() / 1000 new Date(result.updated_at).getTime() / 1000
)}:f>\n\u200b`, )}:f>\n\u200b`,
inline: true inline: true
}, },
{ {
name: '📅 Created at', name: '📅 Created at',
value: `<t:${Math.round(new Date(result.data.created_at).getTime() / 1000)}:R>\n<t:${Math.round( value: `<t:${Math.round(new Date(result.created_at).getTime() / 1000)}:R>\n<t:${Math.round(
new Date(result.data.created_at).getTime() / 1000 new Date(result.created_at).getTime() / 1000
)}:f>\n\u200b`, )}:f>\n\u200b`,
inline: true inline: true
}, },
{ {
name: `❤ World Information`, name: `❤ World Information`,
value: `World ID: \`\`${result.data.id}\`\`\nCreator ID: \`\`${result.data.authorId}\`\`\n` value: `World ID: \`\`${result.id}\`\`\nCreator ID: \`\`${result.authorId}\`\`\n`
} }
], ],
user: data.getUser() user: data.getUser()
@@ -176,14 +180,14 @@ export default class VRChatWorld {
embed.setAuthor({ embed.setAuthor({
name: `VRChat World`, name: `VRChat World`,
url: `https://vrchat.com/home/world/${result.data.id}`, url: `https://vrchat.com/home/world/${result.id}`,
iconURL: iconURL:
'https://www.theladders.com/s3proxy/company-photo.theladders.com/68949/5721f0dd-d662-4b5e-9c0b-236b8a41b0d3.png' 'https://www.theladders.com/s3proxy/company-photo.theladders.com/68949/5721f0dd-d662-4b5e-9c0b-236b8a41b0d3.png'
}); });
let mainImage; let mainImage;
if (result.data.thumbnailImageUrl) mainImage = result.data.thumbnailImageUrl; if (result.thumbnailImageUrl) mainImage = result.thumbnailImageUrl;
if (mainImage) if (mainImage)
embed.setImage( embed.setImage(
@@ -193,8 +197,8 @@ export default class VRChatWorld {
let userMainImage; let userMainImage;
if (author.data.userIcon) userMainImage = author.data.userIcon; if (author.userIcon) userMainImage = author.userIcon;
else if (author.data.currentAvatarImageUrl) userMainImage = author.data.currentAvatarImageUrl; else if (author.currentAvatarImageUrl) userMainImage = author.currentAvatarImageUrl;
if (userMainImage) if (userMainImage)
embed.setThumbnail( embed.setThumbnail(
@@ -206,12 +210,12 @@ export default class VRChatWorld {
new ButtonBuilder() new ButtonBuilder()
.setEmoji('🔗') .setEmoji('🔗')
.setLabel(' Open World') .setLabel(' Open World')
.setURL(`https://vrchat.com/home/world/${result.data.id}`) .setURL(`https://vrchat.com/home/world/${result.id}`)
.setStyle(ButtonStyle.Link), .setStyle(ButtonStyle.Link),
new ButtonBuilder() new ButtonBuilder()
.setEmoji('🔗') .setEmoji('🔗')
.setLabel(` ${author.data.displayName}'s Profile`) .setLabel(` ${author.displayName}'s Profile`)
.setURL(`https://vrchat.com/home/user/${author.data.id}`) .setURL(`https://vrchat.com/home/user/${author.id}`)
.setStyle(ButtonStyle.Link) .setStyle(ButtonStyle.Link)
]); ]);
+111 -13
View File
@@ -1,34 +1,58 @@
import { Configuration, AuthenticationApi, UsersApi, WorldsApi } from 'vrchat'; import * as VRChat from 'vrchat';
import Environment from './Environment'; import Environment from './Environment';
import Logger from '../libs/Logger'; import Logger from '../libs/Logger';
import App from './App'; import App from './App';
const VRC_CACHE_DURATION = 5 * 60 * 1000; // 5 minutes
interface VRChatAPIs { interface VRChatAPIs {
AuthenticationApi: AuthenticationApi; AuthenticationApi: VRChat.AuthenticationApi;
UsersApi: UsersApi; UsersApi: VRChat.UsersApi;
WorldsApi: WorldsApi; WorldsApi: VRChat.WorldsApi;
}
interface UserCache {
user: VRChat.User;
expires: Date;
}
interface WorldCache {
world: VRChat.World;
expires: Date;
}
interface Cache {
Users: { [key: string]: UserCache };
Worlds: { [key: string]: WorldCache };
} }
class VRChatAPI { class VRChatAPI {
public client: VRChatAPIs | null; public client: VRChatAPIs | null;
public configuration: Configuration | null; public configuration: VRChat.Configuration | null;
private cache: Cache;
private useragent: string | null = null;
constructor() { constructor() {
this.cache = {
Users: {},
Worlds: {}
};
this.client = null; this.client = null;
this.configuration = null; this.configuration = null;
} }
public async init() { public async init() {
this.configuration = new Configuration({ this.useragent = `Yumi/${App.version.replaceAll('/', '').replaceAll(' ', '-').replaceAll('--', '-')} ${
Environment.get().VRC_CONTACT_EMAIL
}`;
Logger.debug(`[VRChat] Using user agent ${this.useragent}`);
this.configuration = new VRChat.Configuration({
//username: Environment.get().VRC_USERNAME, //username: Environment.get().VRC_USERNAME,
//;password: Environment.get().VRC_PASSWORD, //;password: Environment.get().VRC_PASSWORD,
apiKey: Environment.get().VRC_API_KEY, apiKey: Environment.get().VRC_API_KEY,
baseOptions: { baseOptions: {
headers: { headers: {
'User-Agent': `Yumi/${App.version.replaceAll('/', '').replaceAll(' ', '-').replaceAll('--', '-')} ${ 'User-Agent': this.useragent,
Environment.get().VRC_CONTACT_EMAIL
}`,
'Content-Type': 'application/json', 'Content-Type': 'application/json',
Cookie: Environment.get().VRC_COOKIE || '' Cookie: Environment.get().VRC_COOKIE || ''
} }
@@ -36,15 +60,89 @@ class VRChatAPI {
}); });
this.client = { this.client = {
AuthenticationApi: new AuthenticationApi(this.configuration), AuthenticationApi: new VRChat.AuthenticationApi(this.configuration),
UsersApi: new UsersApi(this.configuration), UsersApi: new VRChat.UsersApi(this.configuration),
WorldsApi: new WorldsApi(this.configuration) WorldsApi: new VRChat.WorldsApi(this.configuration)
}; };
Logger.info('Logging in to VRChat'); Logger.info('Logging in to VRChat');
let res = await this.client.AuthenticationApi.getCurrentUser(); let res = await this.client.AuthenticationApi.getCurrentUser();
Logger.info('Logged in to VRChat as ' + res.data.displayName); Logger.info('Logged in to VRChat as ' + res.data.displayName);
setInterval(() => {
for (let key in this.cache.Users) {
if (this.cache.Users[key].expires < new Date()) {
Logger.debug('[VRChat] [Cache] Removing user ' + key + ' from cache');
delete this.cache.Users[key];
}
}
for (let key in this.cache.Worlds) {
if (this.cache.Worlds[key].expires < new Date()) {
Logger.debug('[VRChat] [Cache] Removing world ' + key + ' from cache');
delete this.cache.Worlds[key];
}
}
}, VRC_CACHE_DURATION);
}
public async getCachedUserById(id: string) {
if (typeof this.cache.Users[id] !== 'undefined') {
Logger.debug('[VRChat] [Cache] Cache hit for user ' + id);
if (this.cache.Users[id].expires > new Date()) {
return this.cache.Users[id].user;
}
}
Logger.debug('[VRChat] [Cache] Cache miss for ' + id);
let user;
try {
user = await this.client!.UsersApi.getUser(id);
} catch (err: any) {
if (err.response?.status === 404) return null;
throw err;
}
if (!user) {
return null;
}
Logger.debug('[VRChat] [Cache] Caching user ' + id);
this.cache.Users[id] = {
user: user.data,
expires: new Date(Date.now() + VRC_CACHE_DURATION)
};
return user.data;
}
public async getCachedWorldById(id: string) {
if (typeof this.cache.Worlds[id] !== 'undefined') {
Logger.debug('[VRChat] [Cache] Cache hit for world ' + id);
if (this.cache.Worlds[id].expires > new Date()) {
return this.cache.Worlds[id].world;
}
}
Logger.debug('[VRChat] [Cache] Cache miss for ' + id);
let world;
try {
world = await this.client!.WorldsApi.getWorld(id);
} catch (err: any) {
if (err.response?.status === 404) return null;
throw err;
}
if (!world) {
return null;
}
Logger.debug('[VRChat] [Cache] Caching world ' + id);
this.cache.Worlds[id] = {
world: world.data,
expires: new Date(Date.now() + VRC_CACHE_DURATION)
};
return world.data;
} }
public end(): void {} public end(): void {}