mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-28 04:59:50 +01:00
feat: automatic videos
This commit is contained in:
1 parent
9da723ed31
commit
9ead3eb627
4 files changed
+99
-37
No files matched your search
Vendored
+1
@@ -164,6 +164,7 @@
|
|||||||
"xkdkw",
|
"xkdkw",
|
||||||
"XMLDVPL",
|
"XMLDVPL",
|
||||||
"YAMLDVPL",
|
"YAMLDVPL",
|
||||||
|
"youtubers",
|
||||||
"zustand"
|
"zustand"
|
||||||
],
|
],
|
||||||
"deno.enable": false,
|
"deno.enable": false,
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ import { DATA, POI } from './constants';
|
|||||||
import { Avatar } from './skillIcons';
|
import { Avatar } from './skillIcons';
|
||||||
import { TankParameters } from './tankIcons';
|
import { TankParameters } from './tankIcons';
|
||||||
|
|
||||||
interface Strings {
|
export interface BlitzStrings {
|
||||||
[key: string]: string;
|
[key: string]: string;
|
||||||
}
|
}
|
||||||
type ShellKind =
|
type ShellKind =
|
||||||
@@ -334,7 +334,7 @@ const blitzShellKindToBLitzkrieg: Record<ShellKind, ShellType> = {
|
|||||||
const missingStrings: Record<string, string> = {
|
const missingStrings: Record<string, string> = {
|
||||||
'#artefacts:tungstentip/name': 'Tungsten Shells',
|
'#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) {
|
export async function definitions(production: boolean) {
|
||||||
console.log('Building definitions...');
|
console.log('Building definitions...');
|
||||||
@@ -361,12 +361,12 @@ export async function definitions(production: boolean) {
|
|||||||
(nations) => nations.filter((nation) => nation !== 'common'),
|
(nations) => nations.filter((nation) => nation !== 'common'),
|
||||||
);
|
);
|
||||||
const tankStringIdMap: Record<string, number> = {};
|
const tankStringIdMap: Record<string, number> = {};
|
||||||
const stringsPreInstalled = await readYAMLDVPL<Strings>(
|
const stringsPreInstalled = await readYAMLDVPL<BlitzStrings>(
|
||||||
`${DATA}/${POI.strings}/en.yaml.dvpl`,
|
`${DATA}/${POI.strings}/en.yaml.dvpl`,
|
||||||
);
|
);
|
||||||
const stringsCache = await fetch(POI.cachedStrings)
|
const stringsCache = await fetch(POI.cachedStrings)
|
||||||
.then((response) => response.text())
|
.then((response) => response.text())
|
||||||
.then((string) => parseYaml(string) as Strings);
|
.then((string) => parseYaml(string) as BlitzStrings);
|
||||||
const optionalDevices = await readXMLDVPL<{ root: OptionalDevices }>(
|
const optionalDevices = await readXMLDVPL<{ root: OptionalDevices }>(
|
||||||
`${DATA}/${POI.optionalDevices}.dvpl`,
|
`${DATA}/${POI.optionalDevices}.dvpl`,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,45 +1,96 @@
|
|||||||
|
import { readdir } from 'fs/promises';
|
||||||
import { google } from 'googleapis';
|
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 { 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) {
|
export async function videos(production: boolean) {
|
||||||
console.log('Building videos...');
|
console.log('Building videos...');
|
||||||
|
|
||||||
const auth = await google.auth.getClient({
|
const auth = await google.auth.getClient({
|
||||||
scopes: ['https://www.googleapis.com/auth/spreadsheets.readonly'],
|
scopes: ['https://www.googleapis.com/auth/youtube.readonly'],
|
||||||
});
|
|
||||||
const sheets = google.sheets({ version: 'v4', auth });
|
|
||||||
const response = await sheets.spreadsheets.values.get({
|
|
||||||
spreadsheetId: env.VIDEOS_SHEET,
|
|
||||||
range: 'Sheet1!A:Z',
|
|
||||||
});
|
});
|
||||||
|
const youtube = google.youtube({ version: 'v3', auth });
|
||||||
|
|
||||||
if (!response.data.values) {
|
async function search(channel: string, search: string) {
|
||||||
console.warn('Google returned no data; skipping videos...');
|
(
|
||||||
return;
|
await youtube.search.list({
|
||||||
|
channelId: channel,
|
||||||
|
maxResults: 1,
|
||||||
|
part: ['snippet'],
|
||||||
|
q: search,
|
||||||
|
})
|
||||||
|
).data.items?.at(0)?.id ?? null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const content = response.data.values
|
const nations = await readdir(`${DATA}/${POI.vehicleDefinitions}`).then(
|
||||||
.slice(1)
|
(nations) => nations.filter((nation) => nation !== 'common'),
|
||||||
.map((row) => {
|
);
|
||||||
const [idString, ...videosRaw] = row.slice(1);
|
const stringsPreInstalled = await readYAMLDVPL<BlitzStrings>(
|
||||||
const id = Number(idString);
|
`${DATA}/${POI.strings}/en.yaml.dvpl`,
|
||||||
const videos = videosRaw
|
);
|
||||||
.map((raw) => new URL(raw).searchParams.get('v'))
|
const stringsCache = await fetch(POI.cachedStrings)
|
||||||
.filter((video) => video !== null);
|
.then((response) => response.text())
|
||||||
return `${id},${videos.join(',')}`;
|
.then((string) => parseYaml(string) as BlitzStrings);
|
||||||
})
|
const strings = {
|
||||||
.join('\n');
|
...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(
|
await commitAssets(
|
||||||
'videos',
|
'videos',
|
||||||
[
|
[{ content, encoding: 'utf-8', path: 'definitions/videos.csv' }],
|
||||||
{
|
|
||||||
content,
|
|
||||||
encoding: 'utf-8',
|
|
||||||
path: 'definitions/videos.csv',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
production,
|
production,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { PlusIcon } from '@radix-ui/react-icons';
|
import { InfoCircledIcon, PlusIcon } from '@radix-ui/react-icons';
|
||||||
import { Card, Flex, Heading, Link, Text } from '@radix-ui/themes';
|
import { Callout, Card, Flex, Heading, Link, Text } from '@radix-ui/themes';
|
||||||
import { use } from 'react';
|
import { use } from 'react';
|
||||||
import { videoDefinitions } from '../../../../../core/blitzkrieg/videos';
|
import { videoDefinitions } from '../../../../../core/blitzkrieg/videos';
|
||||||
import { useFullScreen } from '../../../../../hooks/useFullScreen';
|
import { useFullScreen } from '../../../../../hooks/useFullScreen';
|
||||||
@@ -7,14 +7,14 @@ import { useDuel } from '../../../../../stores/duel';
|
|||||||
|
|
||||||
export function Videos() {
|
export function Videos() {
|
||||||
const isFullScreen = useFullScreen();
|
const isFullScreen = useFullScreen();
|
||||||
const id = useDuel((state) => state.protagonist!.tank.id);
|
const tank = useDuel((state) => state.protagonist!.tank);
|
||||||
const awaitedVideoDefinitions = use(videoDefinitions);
|
const awaitedVideoDefinitions = use(videoDefinitions);
|
||||||
const videos = awaitedVideoDefinitions[id] ?? [];
|
const videos = awaitedVideoDefinitions[tank.id] ?? [];
|
||||||
|
|
||||||
if (isFullScreen) return null;
|
if (isFullScreen) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex direction="column" gap="2" mt="4">
|
<Flex direction="column" gap="2" mt="4" align="start">
|
||||||
<Heading size="6">Review videos</Heading>
|
<Heading size="6">Review videos</Heading>
|
||||||
|
|
||||||
<Flex gap="3" wrap="wrap">
|
<Flex gap="3" wrap="wrap">
|
||||||
@@ -59,6 +59,16 @@ export function Videos() {
|
|||||||
</Card>
|
</Card>
|
||||||
</Link>
|
</Link>
|
||||||
</Flex>
|
</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>
|
</Flex>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user