Skip to content

Commit

Permalink
feat(cache): cache game thumbnails (#5430)
Browse files Browse the repository at this point in the history
* feat(cache): cache game thumbnails

* update
  • Loading branch information
sogehige committed Aug 29, 2022
1 parent 40218a5 commit 267b62b
Show file tree
Hide file tree
Showing 10 changed files with 157 additions and 7 deletions.
3 changes: 2 additions & 1 deletion d.ts/src/helpers/socket.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import { QuickActions } from '../../../src/database/entity/dashboard';
import { WidgetCustomInterface, WidgetSocialInterface } from '../../../src/database/entity/widget';

import { AliasGroup, Alias } from '~/database/entity/alias';
import { CacheGamesInterface } from '~/database/entity/cacheGames';
import { Plugin } from '~/database/entity/plugins';
import { MenuItem } from '~/helpers/panel';

Expand Down Expand Up @@ -102,7 +103,7 @@ export type ClientToServerEventsWithNamespace = {
'updateGameAndTitle': (emit: { game: string; title: string; tags: never[]; }, cb: (error: Error | string | null) => void) => void,
'cleanupGameAndTitle': () => void,
'getGameFromTwitch': (value: string, cb: (values: string[]) => void) => void,
'getUserTwitchGames': (cb: (values: CacheTitlesInterface[]) => void) => void,
'getUserTwitchGames': (cb: (values: CacheTitlesInterface[], thumbnails: CacheGamesInterface[]) => void) => void,
'integration::obswebsocket::generic::getOne': generic<OBSWebsocketInterface>['getOne'],
'integration::obswebsocket::generic::getAll': generic<OBSWebsocketInterface>['getAll'],
'integration::obswebsocket::generic::save': generic<OBSWebsocketInterface>['save'],
Expand Down
6 changes: 4 additions & 2 deletions src/database/entity/cacheGames.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@ import { EntitySchema } from 'typeorm';
export interface CacheGamesInterface {
id?: number;
name: string;
thumbnail: string | null;
}

export const CacheGames = new EntitySchema<Readonly<Required<CacheGamesInterface>>>({
name: 'cache_games',
columns: {
id: { type: Number, primary: true },
name: { type: String },
id: { type: Number, primary: true },
name: { type: String },
thumbnail: { type: String, default: null, nullable: true },
},
indices: [
{ name: 'IDX_f37be3c66dbd449a8cb4fe7d59', columns: ['name'] },
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { MigrationInterface, QueryRunner } from 'typeorm';

export class cacheTitleChangeToIdAddThumbnail1661348421631 implements MigrationInterface {
name = 'cacheTitleChangeToIdAddThumbnail1661348421631';

public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE \`cache_games\` ADD \`thumbnail\` varchar(255) NULL`);
}

public async down(queryRunner: QueryRunner): Promise<void> {
return;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { MigrationInterface, QueryRunner } from 'typeorm';

export class cacheTitleChangeToIdAddThumbnail1661348421631 implements MigrationInterface {
name = 'cacheTitleChangeToIdAddThumbnail1661348421631';

public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "cache_games" ADD "thumbnail" character varying`);
}

public async down(queryRunner: QueryRunner): Promise<void> {
return;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { MigrationInterface, QueryRunner } from 'typeorm';

import { insertItemIntoTable } from '~/database/insertItemIntoTable';

export class cacheTitleChangeToIdAddThumbnail1661348421631 implements MigrationInterface {
name = 'cacheTitleChangeToIdAddThumbnail1661348421631';

public async up(queryRunner: QueryRunner): Promise<void> {
const items = await queryRunner.query(`SELECT * from "cache_games"`);
const items2 = await queryRunner.query(`SELECT * from "cache_titles"`);
await queryRunner.query(`DELETE from "cache_titles" WHERE 1=1`);
await queryRunner.query(`DROP TABLE "cache_games"`);
await queryRunner.query(`CREATE TABLE "cache_games" ("id" integer PRIMARY KEY NOT NULL, "name" varchar NOT NULL, "thumbnail" varchar)`);
await queryRunner.query(`CREATE INDEX "IDX_f37be3c66dbd449a8cb4fe7d59" ON "cache_games" ("name") `);

for (const title of items2) {
const game = items.find((o: any) => o.name === title.game);
if (game) {
title.game = game.name;
await insertItemIntoTable('cache_titles', {
...title,
}, queryRunner);
}
}

for (const item of items) {
await insertItemIntoTable('cache_games', {
...item,
}, queryRunner);
}
}

public async down(queryRunner: QueryRunner): Promise<void> {
return;
}

}
23 changes: 21 additions & 2 deletions src/panel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { possibleLists } from '../d.ts/src/helpers/socket.js';
import emitter from './helpers/interfaceEmitter.js';

import Core from '~/_interface';
import { CacheGames, CacheGamesInterface } from '~/database/entity/cacheGames.js';
import { CacheTitles } from '~/database/entity/cacheTitles';
import { Translation } from '~/database/entity/translation';
import { TwitchTag, TwitchTagInterface } from '~/database/entity/twitch';
Expand Down Expand Up @@ -53,6 +54,7 @@ import * as changelog from '~/helpers/user/changelog.js';
import lastfm from '~/integrations/lastfm';
import spotify from '~/integrations/spotify';
import Parser from '~/parser';
import { getGameThumbnailFromName } from '~/services/twitch/calls/getGameThumbnailFromName.js';
import { sendGameFromTwitch } from '~/services/twitch/calls/sendGameFromTwitch';
import { setTags } from '~/services/twitch/calls/setTags';
import { setTitleAndGame } from '~/services/twitch/calls/setTitleAndGame';
Expand Down Expand Up @@ -334,8 +336,25 @@ class Panel extends Core {
sendGameFromTwitch(game).then((data) => cb(data));
});
socket.on('getUserTwitchGames', async (cb) => {
const titles = await getRepository(CacheTitles).find();
cb(titles);
let titles = await getRepository(CacheTitles).find();
const cachedGames = await getRepository(CacheGames).find();

// we need to cleanup titles if game is not in cache
for (const title of titles) {
if (!cachedGames.map(o => o.name).includes(title.game)) {
await getRepository(CacheTitles).delete({ game: title.game });
}
}

const games: CacheGamesInterface[] = [];
for (const game of cachedGames) {
games.push({
...game,
thumbnail: await getGameThumbnailFromName(game.name) || '',
});
}
titles = await getRepository(CacheTitles).find();
cb(titles, games);
});
socket.on('cleanupGameAndTitle', async () => {
// remove empty titles
Expand Down
2 changes: 1 addition & 1 deletion src/services/twitch/calls/getGameIdFromName.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ async function getGameIdFromName (name: string): Promise<string | undefined> {
}
// add id->game to cache
const id = Number(getGameByName.id);
await getRepository(CacheGames).save({ id, name });
await getRepository(CacheGames).save({ id, name, thumbnail: getGameByName.boxArtUrl });
return String(id);
} catch (e: unknown) {
if (e instanceof Error) {
Expand Down
2 changes: 1 addition & 1 deletion src/services/twitch/calls/getGameNameFromId.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ async function getGameNameFromId (id: number) {
if (!getGameById) {
throw new Error(`Couldn't find name of game for gid ${id} - fallback to ${stats.value.currentGame}`);
}
await getRepository(CacheGames).save({ id, name: getGameById.name });
await getRepository(CacheGames).save({ id, name: getGameById.name, thumbnail: getGameById.boxArtUrl });
return getGameById.name;
} catch (e: unknown) {
if (e instanceof Error) {
Expand Down
36 changes: 36 additions & 0 deletions src/services/twitch/calls/getGameThumbnailFromName.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { CacheGames } from '@entity/cacheGames';
import { getRepository } from 'typeorm';

import client from '../api/client';

import { debug, isDebugEnabled, warning } from '~/helpers/log';

async function getGameThumbnailFromName (name: string): Promise<string | undefined> {
if (isDebugEnabled('api.calls')) {
debug('api.calls', new Error().stack);
}
const gameFromDb = await getRepository(CacheGames).findOne({ name });
// check if name is cached
if (gameFromDb && gameFromDb.thumbnail) {
return String(gameFromDb.thumbnail);
}

try {
const clientBot = await client('bot');
const getGameByName = await clientBot.games.getGameByName(name);
if (!getGameByName) {
return undefined;
}
// add id->game to cache
const id = Number(getGameByName.id);
await getRepository(CacheGames).save({ id, name, thumbnail: getGameByName.boxArtUrl });
return String(id);
} catch (e: unknown) {
if (e instanceof Error) {
warning(`getGameThumbnailFromName => ${e.stack ?? e.message}`);
}
return undefined;
}
}

export { getGameThumbnailFromName };
27 changes: 27 additions & 0 deletions src/services/twitch/calls/searchCategoriesPaginated.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import client from '../api/client';
import { refresh } from '../token/refresh.js';

import { getFunctionName } from '~/helpers/getFunctionName';
import { debug, error, isDebugEnabled, warning } from '~/helpers/log';

async function searchCategoriesPaginated (game: string) {
if (isDebugEnabled('api.calls')) {
debug('api.calls', new Error().stack);
}
try {
const clientBot = await client('bot');
return await clientBot.search.searchCategoriesPaginated(game).getAll();
} catch (e) {
if (e instanceof Error) {
if (e.message.includes('Invalid OAuth token')) {
warning(`${getFunctionName()} => Invalid OAuth token - attempting to refresh token`);
await refresh('bot');
} else {
error(`${getFunctionName()} => ${e.stack ?? e.message}`);
}
}
return;
}
}

export { searchCategoriesPaginated };

0 comments on commit 267b62b

Please sign in to comment.