feat: automatic videos

This commit is contained in:
TrèsAbhi committed 2024-04-21 19:40:38 -05:00
1 parent 9da723ed31
commit 9ead3eb627
4 files changed
+99 -37

No files matched your search

+1
View File
@@ -164,6 +164,7 @@
"xkdkw",
"XMLDVPL",
"YAMLDVPL",
"youtubers",
"zustand"
],
"deno.enable": false,
+4 -4
View File
@@ -38,7 +38,7 @@ import { DATA, POI } from './constants';
import { Avatar } from './skillIcons';
import { TankParameters } from './tankIcons';
interface Strings {
export interface BlitzStrings {
[key: string]: string;
}
type ShellKind =
@@ -334,7 +334,7 @@ const blitzShellKindToBLitzkrieg: Record<ShellKind, ShellType> = {
const missingStrings: Record<string, string> = {
'#artefacts:tungstentip/name': 'Tungsten Shells',
};
const botPattern = /^.+((tutorial_bot(\d+)?)|(TU(R?)))$/;
export const botPattern = /^.+((tutorial_bot(\d+)?)|(TU(R?)))$/;
export async function definitions(production: boolean) {
console.log('Building definitions...');
@@ -361,12 +361,12 @@ export async function definitions(production: boolean) {
(nations) => nations.filter((nation) => nation !== 'common'),
);
const tankStringIdMap: Record<string, number> = {};
const stringsPreInstalled = await readYAMLDVPL<Strings>(
const stringsPreInstalled = await readYAMLDVPL<BlitzStrings>(
`${DATA}/${POI.strings}/en.yaml.dvpl`,
);
const stringsCache = await fetch(POI.cachedStrings)
.then((response) => response.text())
.then((string) => parseYaml(string) as Strings);
.then((string) => parseYaml(string) as BlitzStrings);
const optionalDevices = await readXMLDVPL<{ root: OptionalDevices }>(
`${DATA}/${POI.optionalDevices}.dvpl`,
);
+79 -28
View File
@@ -1,45 +1,96 @@
import { readdir } from 'fs/promises';
import { google } from 'googleapis';
import { env } from 'process';
import { parse as parseYaml } from 'yaml';
import { readXMLDVPL } from '../../src/core/blitz/readXMLDVPL';
import { readYAMLDVPL } from '../../src/core/blitz/readYAMLDVPL';
import { toUniqueId } from '../../src/core/blitz/toUniqueId';
import { commitAssets } from '../../src/core/blitzkrieg/commitAssets';
import { DATA, POI } from './constants';
import { BlitzStrings, botPattern, VehicleDefinitionList } from './definitions';
const youtubers = [
'UCKBYXp4Xn2I2tL1UL4fpbhw', // droodles
'UCrQ-Dy8lVsm11u8pCJ8W7Tw', // fatness
];
export async function videos(production: boolean) {
console.log('Building videos...');
const auth = await google.auth.getClient({
scopes: ['https://www.googleapis.com/auth/spreadsheets.readonly'],
});
const sheets = google.sheets({ version: 'v4', auth });
const response = await sheets.spreadsheets.values.get({
spreadsheetId: env.VIDEOS_SHEET,
range: 'Sheet1!A:Z',
scopes: ['https://www.googleapis.com/auth/youtube.readonly'],
});
const youtube = google.youtube({ version: 'v3', auth });
if (!response.data.values) {
console.warn('Google returned no data; skipping videos...');
return;
async function search(channel: string, search: string) {
(
await youtube.search.list({
channelId: channel,
maxResults: 1,
part: ['snippet'],
q: search,
})
).data.items?.at(0)?.id ?? null;
}
const content = response.data.values
.slice(1)
.map((row) => {
const [idString, ...videosRaw] = row.slice(1);
const id = Number(idString);
const videos = videosRaw
.map((raw) => new URL(raw).searchParams.get('v'))
.filter((video) => video !== null);
return `${id},${videos.join(',')}`;
})
.join('\n');
const nations = await readdir(`${DATA}/${POI.vehicleDefinitions}`).then(
(nations) => nations.filter((nation) => nation !== 'common'),
);
const stringsPreInstalled = await readYAMLDVPL<BlitzStrings>(
`${DATA}/${POI.strings}/en.yaml.dvpl`,
);
const stringsCache = await fetch(POI.cachedStrings)
.then((response) => response.text())
.then((string) => parseYaml(string) as BlitzStrings);
const strings = {
...stringsPreInstalled,
...stringsCache,
};
const tanks: { id: number; name: string }[] = [];
await Promise.all(
nations.map(async (nation) => {
const tankList = await readXMLDVPL<{ root: VehicleDefinitionList }>(
`${DATA}/${POI.vehicleDefinitions}/${nation}/list.xml.dvpl`,
);
await Promise.all(
Object.entries(tankList.root).map(async ([tankKey, tank]) => {
if (botPattern.test(tankKey)) return null;
const id = toUniqueId(nation, tank.id);
const name =
(tank.shortUserString
? strings[tank.shortUserString]
: undefined) ?? strings[tank.userString];
tanks.push({ id, name });
}),
);
}),
);
let content = '';
let done = 0;
for (const tank of tanks) {
const results = await Promise.all(
youtubers.map((channel) => search(channel, tank.name)),
).then((results) => results.filter((result) => result !== null));
if (results.length === 0) continue;
const line = `${tank.id},${results.join(',')}`;
content += `${line}\n`;
console.log(`${++done} / ${tanks.length} done`, tank.name, line);
await new Promise((resolve) =>
setTimeout(resolve, (1000 / 5) * youtubers.length),
);
}
console.log(content);
await commitAssets(
'videos',
[
{
content,
encoding: 'utf-8',
path: 'definitions/videos.csv',
},
],
[{ content, encoding: 'utf-8', path: 'definitions/videos.csv' }],
production,
);
}
@@ -1,5 +1,5 @@
import { PlusIcon } from '@radix-ui/react-icons';
import { Card, Flex, Heading, Link, Text } from '@radix-ui/themes';
import { InfoCircledIcon, PlusIcon } from '@radix-ui/react-icons';
import { Callout, Card, Flex, Heading, Link, Text } from '@radix-ui/themes';
import { use } from 'react';
import { videoDefinitions } from '../../../../../core/blitzkrieg/videos';
import { useFullScreen } from '../../../../../hooks/useFullScreen';
@@ -7,14 +7,14 @@ import { useDuel } from '../../../../../stores/duel';
export function Videos() {
const isFullScreen = useFullScreen();
const id = useDuel((state) => state.protagonist!.tank.id);
const tank = useDuel((state) => state.protagonist!.tank);
const awaitedVideoDefinitions = use(videoDefinitions);
const videos = awaitedVideoDefinitions[id] ?? [];
const videos = awaitedVideoDefinitions[tank.id] ?? [];
if (isFullScreen) return null;
return (
<Flex direction="column" gap="2" mt="4">
<Flex direction="column" gap="2" mt="4" align="start">
<Heading size="6">Review videos</Heading>
<Flex gap="3" wrap="wrap">
@@ -59,6 +59,16 @@ export function Videos() {
</Card>
</Link>
</Flex>
<Callout.Root>
<Callout.Icon>
<InfoCircledIcon />
</Callout.Icon>
<Callout.Text>
Some YouTubers may not have videos on the {tank.name} leading to
off-topic videos.
</Callout.Text>
</Callout.Root>
</Flex>
);
}