feat: satori and resvg for rendering

This commit is contained in:
TresAbhi committed 2023-05-28 02:25:59 +00:00
1 parent e48ea4c307
commit c834f4f966
34 files changed
+670 -501

No files matched your search

+1
View File
@@ -4,6 +4,7 @@
"accountinfo",
"kranvagn",
"ownedtanks",
"Resvg",
"searchplayers",
"Smri",
"strvk",
+5 -4
View File
@@ -5,27 +5,28 @@
"main": "index.js",
"type": "module",
"scripts": {
"pw": "playwright install && playwright install-deps",
"install": "playwright install",
"start": "vite-node src/index.ts --script",
"lint": "tsc"
},
"license": "MIT",
"dependencies": {
"@resvg/resvg-js": "^2.4.1",
"discord.js": "^14.9.0",
"escape-html": "^1.0.3",
"fuzzysort": "^2.0.4",
"lodash.startcase": "^4.4.0",
"markdown-escape": "^2.0.0",
"node-fetch": "^3.3.1"
"node-fetch": "^3.3.1",
"react": "^18.2.0",
"satori": "^0.9.1"
},
"devDependencies": {
"@playwright/test": "^1.34.2",
"@total-typescript/ts-reset": "^0.4.2",
"@types/escape-html": "^1.0.2",
"@types/lodash.startcase": "^4.4.7",
"@types/markdown-escape": "^1.1.0",
"@types/node": "^20.2.1",
"@types/react": "^18.2.7",
"typescript": "^5.0.4",
"vite-node": "^0.31.1"
}
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -3,7 +3,6 @@ import {
ButtonBuilder,
SlashCommandBuilder,
} from 'discord.js';
import escapeHTML from 'escape-html';
import GenericStats from '../components/GenericStats/index.js';
import NoBattlesInPeriod from '../components/NoBattlesInPeriod.js';
import PoweredByBlitzStars from '../components/PoweredByBlitzStars.js';
@@ -19,7 +18,7 @@ import getBlitzStarsAccount from '../core/blitzstars/getBlitzStarsAccount.js';
import cmdName from '../core/interaction/cmdName.js';
import addUsernameOption from '../core/options/addUsernameOption.js';
import { args } from '../core/process/args.js';
import screenshotHTML from '../core/ui/screenshotHTML.js';
import render from '../core/ui/render.js';
import { CommandRegistry } from '../events/interactionCreate.js';
import { AccountInfo, AllStats } from '../types/accountInfo.js';
import { PlayerClanData } from '../types/playerClanData.js';
@@ -118,22 +117,27 @@ export default {
}
if (!stats) return;
const screenshot = await screenshotHTML(
Wrapper(
TitleBar(
escapeHTML(blitzStarsAccount.nickname),
clanData[id]?.clan ? `[${clanData[id]?.clan?.tag}]` : '',
clanData[id]?.clan
? `https://wotblitz-gc.gcdn.co/icons/clanEmblems1x/clan-icon-v2-${clanData[id]?.clan?.emblem_set_id}.png`
: undefined,
`${periodNames[period]} • ${new Date().toDateString()} • ${
BLITZ_SERVERS[server]
}`,
),
const image = await render(
<Wrapper>
<TitleBar
name={blitzStarsAccount.nickname}
nameDiscriminator={
clanData[id]?.clan ? `[${clanData[id]?.clan?.tag}]` : undefined
}
image={
clanData[id]?.clan
? `https://wotblitz-gc.gcdn.co/icons/clanEmblems1x/clan-icon-v2-${clanData[id]?.clan?.emblem_set_id}.png`
: undefined
}
description={`${
periodNames[period]
} • ${new Date().toDateString()} • ${BLITZ_SERVERS[server]}`}
/>
stats.all.battles === 0
? NoBattlesInPeriod()
: GenericStats([
{stats.all.battles === 0 && <NoBattlesInPeriod />}
{stats.all.battles > 0 && (
<GenericStats
stats={[
[
'Winrate',
`${(100 * (stats.all.wins / stats.all.battles)).toFixed(2)}%`,
@@ -183,10 +187,12 @@ export default {
(stats.all.battles - stats.all.survived_battles)
).toFixed(2),
],
]),
]}
/>
)}
PoweredByBlitzStars(),
),
<PoweredByBlitzStars />
</Wrapper>,
);
const actionRow = new ActionRowBuilder<ButtonBuilder>().addComponents(
@@ -195,7 +201,7 @@ export default {
);
await interaction.editReply({
files: [screenshot],
files: [image],
components: [actionRow],
});
@@ -3,8 +3,7 @@ import {
ButtonBuilder,
SlashCommandBuilder,
} from 'discord.js';
import escapeHTML from 'escape-html';
import Breakdown from '../components/Breakdown.js';
import * as Breakdown from '../components/Breakdown/index.js';
import NoBattlesInPeriod from '../components/NoBattlesInPeriod.js';
import PoweredByBlitzStars from '../components/PoweredByBlitzStars.js';
import TitleBar from '../components/TitleBar.js';
@@ -21,7 +20,7 @@ import { tankopedia } from '../core/blitzstars/tankopedia.js';
import cmdName from '../core/interaction/cmdName.js';
import addUsernameOption from '../core/options/addUsernameOption.js';
import { args } from '../core/process/args.js';
import screenshotHTML from '../core/ui/screenshotHTML.js';
import render from '../core/ui/render.js';
import { CommandRegistry } from '../events/interactionCreate.js';
import { AccountInfo, AllStats } from '../types/accountInfo.js';
import { PlayerClanData } from '../types/playerClanData.js';
@@ -30,7 +29,7 @@ import resolveTankName from '../utilities/resolveTankName.js';
export default {
inProduction: true,
inDevelopment: true,
inDevelopment: false,
inPublic: true,
command: new SlashCommandBuilder()
@@ -72,43 +71,51 @@ export default {
([tankId, tankStats]) => {
const career = careerStats[tankId as unknown as number];
return Breakdown.Row(
resolveTankName({
tank_id: tankId as unknown as number,
name: tankopedia.data[tankId as unknown as number].name,
}),
tankStats.wins / tankStats.battles,
career.wins / career.battles,
-Infinity,
-Infinity,
tankStats.damage_dealt / tankStats.battles,
career.damage_dealt / career.battles,
tankStats.survived_battles / tankStats.battles,
career.survived_battles / career.battles,
tankStats.battles,
career.battles,
tankopedia.data[tankId as unknown as number].images.normal,
return (
<Breakdown.Row
key={tankId}
name={resolveTankName({
tank_id: tankId as unknown as number,
name: tankopedia.data[tankId as unknown as number].name,
})}
winrate={tankStats.wins / tankStats.battles}
careerWinrate={career.wins / career.battles}
WN8={-Infinity}
careerWN8={-Infinity}
damage={tankStats.damage_dealt / tankStats.battles}
careerDamage={career.damage_dealt / career.battles}
survival={tankStats.survived_battles / tankStats.battles}
careerSurvival={career.survived_battles / career.battles}
battles={tankStats.battles}
careerBattles={career.battles}
icon={tankopedia.data[tankId as unknown as number].images.normal}
/>
);
},
);
const screenshot = await screenshotHTML(
Wrapper(
TitleBar(
escapeHTML(accountInfo[id].nickname),
clanData[id]?.clan ? `[${clanData[id]?.clan?.tag}]` : '',
clanData[id]?.clan
? `https://wotblitz-gc.gcdn.co/icons/clanEmblems1x/clan-icon-v2-${clanData[id]?.clan?.emblem_set_id}.png`
: undefined,
`Today's breakdown • ${new Date().toDateString()} • ${
const image = await render(
<Wrapper>
<TitleBar
name={accountInfo[id].nickname}
nameDiscriminator={
clanData[id]?.clan ? `[${clanData[id]?.clan?.tag}]` : undefined
}
image={
clanData[id]?.clan
? `https://wotblitz-gc.gcdn.co/icons/clanEmblems1x/clan-icon-v2-${clanData[id]?.clan?.emblem_set_id}.png`
: undefined
}
description={`Today's breakdown • ${new Date().toDateString()} • ${
BLITZ_SERVERS[server]
}`,
),
}`}
/>
rows.length === 0 ? NoBattlesInPeriod() : Breakdown.Root(...rows),
{rows.length === 0 && <NoBattlesInPeriod />}
{rows.length > 0 && <Breakdown.Root>{rows}</Breakdown.Root>}
PoweredByBlitzStars(),
),
<PoweredByBlitzStars />
</Wrapper>,
);
const actionRow = new ActionRowBuilder<ButtonBuilder>().addComponents(
@@ -117,7 +124,7 @@ export default {
);
await interaction.editReply({
files: [screenshot],
files: [image],
components: [actionRow],
});
-183
View File
@@ -1,183 +0,0 @@
import Children from './Children.js';
const Breakdown = {
Root(...children: string[]) {
return `
<style>
.breakdown {
display: flex;
flex-direction: column;
gap: 16px;
}
</style>
<div class="breakdown">
${Children(children)}
</div>
`;
},
Row(
name: string,
winrate: number,
careerWinrate: number,
wn8: number,
careerWn8: number,
averageDamage: number,
careerAverageDamage: number,
survival: number,
careerSurvival: number,
battles: number,
careerBattles: number,
icon?: string,
) {
return `
<style>
.breakdown-row {
display: flex;
gap: 16px;
}
</style>
<div class="breakdown-row">
${this.RowDiscriminator(name, icon)}
${this.RowStat(
'Winrate',
`${(winrate * 100).toFixed(2)}%`,
`${(careerWinrate * 100).toFixed(2)}%`,
winrate - careerWinrate,
)}
${this.RowStat(
'Damage',
averageDamage.toFixed(2),
careerAverageDamage.toFixed(2),
averageDamage - careerAverageDamage,
)}
${this.RowStat(
'Survival',
`${(survival * 100).toFixed(2)}%`,
`${(careerSurvival * 100).toFixed(2)}%`,
survival - careerSurvival,
)}
${this.RowStat('Battles', battles.toFixed(0), careerBattles.toFixed(0))}
</div>
`;
/*
${this.RowStat(
'WN8',
wn8.toFixed(0),
careerWn8.toFixed(0),
wn8 - careerWn8,
)}
*/
},
RowDiscriminator(name: string, icon?: string) {
return `
<style>
.breakdown-row-discriminator {
flex: 1;
display: flex;
flex-direction: column;
height: 100%;
box-sizing: border-box;
width: 0px;
align-items: center;
justify-content: center;
}
.breakdown-row-discriminator-icon {
width: 100%;
flex: 1;
object-fit: cover;
}
.breakdown-row-discriminator-name {
color: #A0A0A0;
font-size: 16px;
text-align: center;
display: block;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 100%;
}
</style>
<div class="breakdown-row-discriminator">
${
icon
? `<img src="${icon}" class="breakdown-row-discriminator-icon" />`
: ''
}
<span class="breakdown-row-discriminator-name">${name}</span>
</div>
`;
},
RowStat(name: string, value: string, career: string, delta?: number) {
return `
<style>
.breakdown-row-stat {
display: flex;
flex-direction: column;
gap: 4px;
align-items: center;
justify-content: center;
flex: 1;
}
.breakdown-row-stat-career {
color: #A0A0A0;
font-size: 12px;
}
.breakdown-row-stat-name {
color: #A0A0A0;
font-size: 16px;
}
.breakdown-row-stat-value {
display: flex;
gap: 4px;
align-items: center;
justify-content: center;
}
.breakdown-row-stat-value-delta {
width: 12px;
height: 12px;
}
.breakdown-row-stat-value-text {
color: white;
font-size: 16px;
font-weight: bold;
}
</style>
<div class="breakdown-row-stat">
<span class="breakdown-row-stat-career">${career}</span>
<div class="breakdown-row-stat-value">
${
delta
? `<img src="${
(delta ?? 0) > 0
? 'https://i.imgur.com/qbjiXa1.png'
: 'https://i.imgur.com/3uyNhun.png'
}" class="breakdown-row-stat-value-delta" />`
: ''
}
<span class="breakdown-row-stat-value-text">${value}</span>
</div>
<span class="breakdown-row-stat-name">${name}</span>
</div>
`;
},
};
export default Breakdown;
@@ -0,0 +1,13 @@
import { ReactNode } from 'react';
export interface RootProps {
children: ReactNode;
}
export function Root({ children }: RootProps) {
return (
<div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
{children}
</div>
);
}
@@ -0,0 +1,61 @@
import { RowDiscriminator } from './RowDiscriminator.js';
import { RowStat } from './RowStat.js';
export interface RowProps {
name: string;
winrate: number;
careerWinrate: number;
WN8: number;
careerWN8: number;
damage: number;
careerDamage: number;
survival: number;
careerSurvival: number;
battles: number;
careerBattles: number;
icon?: string;
}
export function Row({
name,
winrate,
careerWinrate,
WN8,
careerWN8,
damage: averageDamage,
careerDamage: careerAverageDamage,
survival,
careerSurvival,
battles,
careerBattles,
icon,
}: RowProps) {
return (
<div style={{ display: 'flex', gap: 16 }}>
<RowDiscriminator name={name} icon={icon} />
<RowStat
name="Winrate"
value={`${(winrate * 100).toFixed(2)}%`}
career={`${(careerWinrate * 100).toFixed(2)}%`}
delta={winrate - careerWinrate}
/>
<RowStat
name="Damage"
value={averageDamage.toFixed(2)}
career={careerAverageDamage.toFixed(2)}
delta={averageDamage - careerAverageDamage}
/>
<RowStat
name="Survival"
value={`${(survival * 100).toFixed(2)}%`}
career={`${(careerSurvival * 100).toFixed(2)}%`}
delta={survival - careerSurvival}
/>
<RowStat
name="Battles"
value={battles.toFixed(0)}
career={careerBattles.toFixed(0)}
/>
</div>
);
}
@@ -0,0 +1,42 @@
export interface RowDiscriminatorProps {
name: string;
icon?: string;
}
export function RowDiscriminator({ name, icon }: RowDiscriminatorProps) {
return (
<div
style={{
flex: 1,
display: 'flex',
flexDirection: 'column',
height: '100%',
boxSizing: 'border-box',
width: 0,
alignItems: 'center',
justifyContent: 'center',
}}
>
{icon && (
<img
src={icon}
style={{ width: '100%', flex: 1, objectFit: 'cover' }}
/>
)}
<span
style={{
color: '#A0A0A0',
fontSize: 16,
textAlign: 'center',
display: 'block',
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
maxWidth: '100%',
}}
>
{name}
</span>
</div>
);
}
@@ -0,0 +1,49 @@
export interface RowStatProps {
name: string;
value: string;
career: string;
delta?: number;
}
export function RowStat({ name, value, career, delta }: RowStatProps) {
return (
<div
style={{
display: 'flex',
flexDirection: 'column',
gap: 4,
alignItems: 'center',
justifyContent: 'center',
flex: 1,
}}
>
<span style={{ color: '#A0A0A0', fontSize: 12 }}>{career}</span>
<div
style={{
display: 'flex',
gap: 4,
alignItems: 'center',
justifyContent: 'center',
}}
>
{delta && (
<img
src={
(delta ?? 0) > 0
? 'https://i.imgur.com/qbjiXa1.png'
: 'https://i.imgur.com/3uyNhun.png'
}
style={{ width: 12, height: 12 }}
/>
)}
<span style={{ color: 'white', fontSize: 16, fontWeight: 'bold' }}>
{value}
</span>
</div>
<span style={{ color: '#A0A0A0', fontSize: 12 }}>{name}</span>
</div>
);
}
+4
View File
@@ -0,0 +1,4 @@
export * from './components/Root.js';
export * from './components/Row.js';
export * from './components/RowDiscriminator.js';
export * from './components/RowStat.js';
-5
View File
@@ -1,5 +0,0 @@
export type ChildrenArgument = string | string[];
export default function Children(children: string[]) {
return children.join('\n');
}
@@ -1,19 +0,0 @@
import { Stat } from '../index.js';
import GenericStatsRow from './GenericStatsRow.js';
export default function GenericStatsColumn(stats: Stat[]) {
return `
<style>
.generic-stats-column {
flex: 1;
display: flex;
flex-direction: column;
gap: 8px;
}
</style>
<div class="generic-stats-column">
${stats.map((stat) => GenericStatsRow(stat)).join('\n')}
</div>
`;
}
@@ -0,0 +1,16 @@
import { Stat } from '../index.js';
import GenericStatsRow from './GenericStatsRow.js';
export interface GenericStatsColumnProps {
stats: Stat[];
}
export default function GenericStatsColumn({ stats }: GenericStatsColumnProps) {
return (
<div style={{ flex: 1, display: 'flex', flexDirection: 'column', gap: 8 }}>
{stats.map((stat) => (
<GenericStatsRow key={stat[0]} stat={stat} />
))}
</div>
);
}
@@ -1,29 +0,0 @@
import { Stat } from '../index.js';
export default function GenericStatsRow(stat: Stat) {
return `
<style>
.generic-stats-row {
display: flex;
justify-content: space-between;
align-items: center;
}
.generic-stats-label {
color: #A0A0A0;
font-size: 16px;
}
.generic-stats-value {
color: white;
font-weight: bold;
font-size: 16px;
}
</style>
<div class="generic-stats-row">
<span class="generic-stats-label">${stat[0]}</span>
<span class="generic-stats-value">${stat[1]}</span>
</div>
`;
}
@@ -0,0 +1,22 @@
import { Stat } from '../index.js';
export interface GenericStatsRowProps {
stat: Stat;
}
export default function GenericStatsRow({ stat }: GenericStatsRowProps) {
return (
<div
style={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
}}
>
<span style={{ color: '#A0A0A0', fontSize: 16 }}>{stat[0]}</span>
<span style={{ color: 'white', fontWeight: 'bold', fontSize: 16 }}>
{stat[1]}
</span>
</div>
);
}
@@ -2,7 +2,15 @@ import GenericStatsColumn from './components/GenericStatsColumn.js';
export type Stat = [string, string | number];
export default function GenericStats(stats: Stat[], columnCount = 2) {
export interface GenericStatsProps {
stats: Stat[];
columnCount?: number;
}
export default function GenericStats({
stats,
columnCount = 2,
}: GenericStatsProps) {
const itemsPerRow = Math.ceil(stats.length / columnCount);
const columns: Stat[][] = [];
@@ -12,16 +20,14 @@ export default function GenericStats(stats: Stat[], columnCount = 2) {
columns[column].push(stat);
});
return `
<style>
.generic-stats {
display: flex;
gap: 32px;
}
</style>
<div class="generic-stats">
${columns.map((column) => GenericStatsColumn(column)).join('\n')}
return (
<div style={{ display: 'flex', gap: 32 }}>
{columns.map((stats) => (
<GenericStatsColumn
key={stats.map((stat) => stat[0]).join('-')}
stats={stats}
/>
))}
</div>
`;
);
}
-20
View File
@@ -1,20 +0,0 @@
export default function NoBattlesInPeriod() {
return `
<style>
.no-battles-in-period {
display: flex;
align-items: center;
justify-content: center;
}
.no-battles-in-period-text {
font-size: 16px;
color: #A0A0A0;
}
</style>
<div class="no-battles-in-period">
<span class="no-battles-in-period-text">No battles played in this period</span>
</div>
`;
}
+15
View File
@@ -0,0 +1,15 @@
export default function NoBattlesInPeriod() {
return (
<div
style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
}}
>
<span style={{ fontSize: 16, color: '#A0A0A0' }}>
No battles played in this period
</span>
</div>
);
}
-26
View File
@@ -1,26 +0,0 @@
export default function PoweredByBlitzStars() {
return `
<style>
.powered-by-blitz-stars {
display: flex;
gap: 4px;
justify-content: flex-end;
}
.powered-by-blitz-stars-icon {
height: 16px;
width: 16px;
}
.powered-by-blitz-stars-text {
font-size: 16px;
color: #A0A0A0;
}
</style>
<div class="powered-by-blitz-stars">
<img class="powered-by-blitz-stars-icon" src="https://www.blitzstars.com/assets/images/4b4cb591.TankyMcPewpew.png" />
<span class="powered-by-blitz-stars-text">Powered by BlitzStars</span>
</div>
`;
}
+13
View File
@@ -0,0 +1,13 @@
export default function PoweredByBlitzStars() {
return (
<div style={{ display: 'flex', gap: 4, justifyContent: 'flex-end' }}>
<img
src="https://www.blitzstars.com/assets/images/4b4cb591.TankyMcPewpew.png"
style={{ height: 16, width: 16 }}
/>
<span style={{ color: '#A0A0A0', fontSize: 16 }}>
Powered by BlitzStars
</span>
</div>
);
}
-26
View File
@@ -1,26 +0,0 @@
import Children from './Children.js';
export default function Root(...children: string[]) {
return `
<style>
@import url('https://fonts.googleapis.com/css2?family=Roboto+Flex:[email protected]');
@import url('https://fonts.googleapis.com/css2?family=Roboto+Mono:[email protected]');
body {
margin: 0;
}
* {
font-family: 'Roboto Flex', sans-serif;
}
#root {
display: flex;
}
</style>
<div id="root">
${Children(children)}
</div>
`;
}
-69
View File
@@ -1,69 +0,0 @@
export default function TitleBar(
name: string,
nameDiscriminator: string,
image?: string,
description?: string,
) {
return `
<style>
.title-bar {
display: flex;
justify-content: space-between;
align-items: center;
}
.title-bar-info {
display: flex;
gap: 8px;
}
.title-bar-avatar {
width: 64px;
height: 64px;
}
.title-bar-text {
display: flex;
flex-direction: column;
gap: 4px;
}
.title-bar-name {
font-size: 32px;
color: white;
font-weight: 900;
}
.title-bar-discriminator {
color: #A0A0A0;
}
.title-bar-description {
color: #A0A0A0;
font-size: 16px;
}
.title-bar-blitzkrieg {
width: 64px;
height: 64px;
}
</style>
<div class="title-bar">
<div class="title-bar-info">
${image ? `<img class="title-bar-avatar" src="${image}" />` : ''}
<div class="title-bar-text">
<span class="title-bar-name">
${name}
<span class="title-bar-discriminator">${nameDiscriminator}</span>
</span>
<span class="title-bar-description">${description}</span>
</div>
</div>
<img class="title-bar-blitzkrieg" src="https://i.imgur.com/35phKIu.png" />
</div>
`;
}
+50
View File
@@ -0,0 +1,50 @@
export interface TitleBarProps {
name: string;
nameDiscriminator?: string;
image?: string;
description?: string;
}
export default function TitleBar({
name,
nameDiscriminator,
image,
description,
}: TitleBarProps) {
return (
<div
style={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
}}
>
<div style={{ display: 'flex', gap: 8 }}>
{image && <img style={{ width: 64, height: 64 }} src={image} />}
<div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
<span
style={{
fontSize: 32,
color: 'white',
fontWeight: 900,
gap: 4,
}}
>
{name}
{nameDiscriminator && (
<span style={{ color: '#A0A0A0' }}>{nameDiscriminator}</span>
)}
</span>
<span style={{ color: '#A0A0A0', fontSize: 16 }}>{description}</span>
</div>
</div>
<img
style={{ width: 64, height: 64 }}
src="https://i.imgur.com/35phKIu.png"
/>
</div>
);
}
-21
View File
@@ -1,21 +0,0 @@
import Children from './Children.js';
export default function Wrapper(...children: string[]) {
return `
<style>
#wrapper {
display: flex;
flex-direction: column;
padding: 16px;
gap: 32px;
width: 640px;
background: black;
color: white;
}
</style>
<div id="wrapper">
${Children(children)}
</div>
`;
}
+23
View File
@@ -0,0 +1,23 @@
import { ReactNode } from 'react';
export interface WrapperProps {
children: ReactNode;
}
export default function Wrapper({ children }: WrapperProps) {
return (
<div
style={{
display: 'flex',
flexDirection: 'column',
padding: 16,
gap: 32,
width: 640,
background: 'black',
color: 'white',
}}
>
{children}
</div>
);
}
+22
View File
@@ -0,0 +1,22 @@
import { Resvg } from '@resvg/resvg-js';
import { readFileSync } from 'fs';
import { ReactNode } from 'react';
import satori from 'satori';
const robotoFlex = readFileSync('src/assets/fonts/Roboto.ttf');
const robotoFlexBold = readFileSync('src/assets/fonts/Roboto-Bold.ttf');
const robotoFlexBlack = readFileSync('src/assets/fonts/Roboto-Black.ttf');
export default async function render(element: ReactNode) {
const svg = await satori(element, {
width: 640,
fonts: [
{ data: robotoFlex, name: 'Roboto Flex', weight: 400 },
{ data: robotoFlexBold, name: 'Roboto Flex', weight: 700 },
{ data: robotoFlexBlack, name: 'Roboto Flex', weight: 900 },
],
});
const png = new Resvg(svg).render().asPng();
return png;
}
-14
View File
@@ -1,14 +0,0 @@
import Root from '../../components/Root.js';
import { browser } from '../../index.js';
export default async function screenshotHTML(children: string) {
const page = await browser.newPage();
await page.setViewportSize({ width: 640, height: 1 });
await page.setContent(Root(children));
const screenshot = await page
.locator('#root')
.screenshot({ omitBackground: true, scale: 'css' });
await page.close();
return screenshot;
}
-3
View File
@@ -1,4 +1,3 @@
import { chromium } from '@playwright/test';
import { Client, GatewayIntentBits } from 'discord.js';
import { args } from './core/process/args.js';
import { registerErrorHandlers } from './events/error.js';
@@ -17,5 +16,3 @@ client
.on('guildMemberAdd', guildMemberAdd)
.on('interactionCreate', interactionCreate)
.login(args['discord-token']);
export const browser = await chromium.launch();
View File
File renamed without changes.
+2 -1
View File
@@ -8,6 +8,7 @@
"strict": true,
"skipLibCheck": true,
"noEmit": true,
"resolveJsonModule": true
"resolveJsonModule": true,
"jsx": "react-jsx"
}
}
+245 -13
View File
@@ -156,15 +156,83 @@
resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.17.19.tgz#8cfaf2ff603e9aabb910e9c0558c26cf32744061"
integrity sha512-lAhycmKnVOuRYNtRtatQR1LPQf2oYCkRGkSFnseDAKPl8lu5SOsK/e1sXe5a0Pc5kHIHe6P2I/ilntNv2xf3cA==
"@playwright/test@^1.34.2":
version "1.34.2"
resolved "https://registry.npmjs.org/@playwright/test/-/test-1.34.2.tgz"
integrity sha512-v/LBnwzD0225q8xEv3t9DmNPX61yvNnEbiA8PoNk1fbkxApJFCWYLPpQbdVWzHaARdZD9g1PYBoOvnffortfKw==
dependencies:
"@types/node" "*"
playwright-core "1.34.2"
"@resvg/resvg-js-android-arm-eabi@2.4.1":
version "2.4.1"
resolved "https://registry.yarnpkg.com/@resvg/resvg-js-android-arm-eabi/-/resvg-js-android-arm-eabi-2.4.1.tgz#49dc9722f95096f8aff70186deae8e148d60dce5"
integrity sha512-AA6f7hS0FAPpvQMhBCf6f1oD1LdlqNXKCxAAPpKh6tR11kqV0YIB9zOlIYgITM14mq2YooLFl6XIbbvmY+jwUw==
"@resvg/[email protected]":
version "2.4.1"
resolved "https://registry.yarnpkg.com/@resvg/resvg-js-android-arm64/-/resvg-js-android-arm64-2.4.1.tgz#fdb7c9362ea27a228831d047cfd1ea240daed780"
integrity sha512-/QleoRdPfsEuH9jUjilYcDtKK/BkmWcK+1LXM8L2nsnf/CI8EnFyv7ZzCj4xAIvZGAy9dTYr/5NZBcTwxG2HQg==
"@resvg/[email protected]":
version "2.4.1"
resolved "https://registry.yarnpkg.com/@resvg/resvg-js-darwin-arm64/-/resvg-js-darwin-arm64-2.4.1.tgz#5ef093f1276149340b9f5a5c487717c4de87bee7"
integrity sha512-U1oMNhea+kAXgiEXgzo7EbFGCD1Edq5aSlQoe6LMly6UjHzgx2W3N5kEXCwU/CgN5FiQhZr7PlSJSlcr7mdhfg==
"@resvg/[email protected]":
version "2.4.1"
resolved "https://registry.yarnpkg.com/@resvg/resvg-js-darwin-x64/-/resvg-js-darwin-x64-2.4.1.tgz#18dd758184f4877be1f6ac80d2f37999581c998c"
integrity sha512-avyVh6DpebBfHHtTQTZYSr6NG1Ur6TEilk1+H0n7V+g4F7x7WPOo8zL00ZhQCeRQ5H4f8WXNWIEKL8fwqcOkYw==
"@resvg/[email protected]":
version "2.4.1"
resolved "https://registry.yarnpkg.com/@resvg/resvg-js-linux-arm-gnueabihf/-/resvg-js-linux-arm-gnueabihf-2.4.1.tgz#eee8f8166763aacc935a53e223e7b9c31b10c94a"
integrity sha512-isY/mdKoBWH4VB5v621co+8l101jxxYjuTkwOLsbW+5RK9EbLciPlCB02M99ThAHzI2MYxIUjXNmNgOW8btXvw==
"@resvg/[email protected]":
version "2.4.1"
resolved "https://registry.yarnpkg.com/@resvg/resvg-js-linux-arm64-gnu/-/resvg-js-linux-arm64-gnu-2.4.1.tgz#826569e1ee08f07879ce32850822ede2ee1192ef"
integrity sha512-uY5voSCrFI8TH95vIYBm5blpkOtltLxLRODyhKJhGfskOI7XkRw5/t1u0sWAGYD8rRSNX+CA+np86otKjubrNg==
"@resvg/[email protected]":
version "2.4.1"
resolved "https://registry.yarnpkg.com/@resvg/resvg-js-linux-arm64-musl/-/resvg-js-linux-arm64-musl-2.4.1.tgz#faf63d873666078ca74604454d40e4fecc22beb4"
integrity sha512-6mT0+JBCsermKMdi/O2mMk3m7SqOjwi9TKAwSngRZ/nQoL3Z0Z5zV+572ztgbWr0GODB422uD8e9R9zzz38dRQ==
"@resvg/[email protected]":
version "2.4.1"
resolved "https://registry.yarnpkg.com/@resvg/resvg-js-linux-x64-gnu/-/resvg-js-linux-x64-gnu-2.4.1.tgz#37b4c16c488aac3834a069c3725f017285d94b1e"
integrity sha512-60KnrscLj6VGhkYOJEmmzPlqqfcw1keDh6U+vMcNDjPhV3B5vRSkpP/D/a8sfokyeh4VEacPSYkWGezvzS2/mg==
"@resvg/[email protected]":
version "2.4.1"
resolved "https://registry.yarnpkg.com/@resvg/resvg-js-linux-x64-musl/-/resvg-js-linux-x64-musl-2.4.1.tgz#6099cf57c958832549b0671991246e06bb44df27"
integrity sha512-0AMyZSICC1D7ge115cOZQW8Pcad6PjWuZkBFF3FJuSxC6Dgok0MQnLTs2MfMdKBlAcwO9dXsf3bv9tJZj8pATA==
"@resvg/[email protected]":
version "2.4.1"
resolved "https://registry.yarnpkg.com/@resvg/resvg-js-win32-arm64-msvc/-/resvg-js-win32-arm64-msvc-2.4.1.tgz#10c9472a20198a218d8c3b6d8fbb3fcba213b670"
integrity sha512-76XDFOFSa3d0QotmcNyChh2xHwk+JTFiEQBVxMlHpHMeq7hNrQJ1IpE1zcHSQvrckvkdfLboKRrlGB86B10Qjw==
"@resvg/[email protected]":
version "2.4.1"
resolved "https://registry.yarnpkg.com/@resvg/resvg-js-win32-ia32-msvc/-/resvg-js-win32-ia32-msvc-2.4.1.tgz#bfc1d37ed485d72ca5717138b99ba40788fa2720"
integrity sha512-odyVFGrEWZIzzJ89KdaFtiYWaIJh9hJRW/frcEcG3agJ464VXkN/2oEVF5ulD+5mpGlug9qJg7htzHcKxDN8sg==
"@resvg/[email protected]":
version "2.4.1"
resolved "https://registry.yarnpkg.com/@resvg/resvg-js-win32-x64-msvc/-/resvg-js-win32-x64-msvc-2.4.1.tgz#83cae82dfe27121f85492eb4dc03f6fe33d8a703"
integrity sha512-vY4kTLH2S3bP+puU5x7hlAxHv+ulFgcK6Zn3efKSr0M0KnZ9A3qeAjZteIpkowEFfUeMPNg2dvvoFRJA9zqxSw==
"@resvg/resvg-js@^2.4.1":
version "2.4.1"
resolved "https://registry.yarnpkg.com/@resvg/resvg-js/-/resvg-js-2.4.1.tgz#07e9cd57f3c925a5fb075a313a0640fc74cba4a2"
integrity sha512-wTOf1zerZX8qYcMmLZw3czR4paI4hXqPjShNwJRh5DeHxvgffUS5KM7XwxtbIheUW6LVYT5fhT2AJiP6mU7U4A==
optionalDependencies:
fsevents "2.3.2"
"@resvg/resvg-js-android-arm-eabi" "2.4.1"
"@resvg/resvg-js-android-arm64" "2.4.1"
"@resvg/resvg-js-darwin-arm64" "2.4.1"
"@resvg/resvg-js-darwin-x64" "2.4.1"
"@resvg/resvg-js-linux-arm-gnueabihf" "2.4.1"
"@resvg/resvg-js-linux-arm64-gnu" "2.4.1"
"@resvg/resvg-js-linux-arm64-musl" "2.4.1"
"@resvg/resvg-js-linux-x64-gnu" "2.4.1"
"@resvg/resvg-js-linux-x64-musl" "2.4.1"
"@resvg/resvg-js-win32-arm64-msvc" "2.4.1"
"@resvg/resvg-js-win32-ia32-msvc" "2.4.1"
"@resvg/resvg-js-win32-x64-msvc" "2.4.1"
"@sapphire/async-queue@^1.5.0":
version "1.5.0"
@@ -184,6 +252,14 @@
resolved "https://registry.npmjs.org/@sapphire/snowflake/-/snowflake-3.4.2.tgz"
integrity sha512-KJwlv5gkGjs1uFV7/xx81n3tqgBwBJvH94n1xDyH3q+JSmtsMeSleJffarEBfG2yAFeJiFA4BnGOK6FFPHc19g==
"@shuding/[email protected]":
version "1.4.0-beta.0"
resolved "https://registry.yarnpkg.com/@shuding/opentype.js/-/opentype.js-1.4.0-beta.0.tgz#5d1e7e9e056f546aad41df1c5043f8f85d39e24b"
integrity sha512-3NgmNyH3l/Hv6EvsWJbsvpcpUba6R8IREQ83nH83cyakCw7uM1arZKNfHwv1Wz6jgqrF/j4x5ELvR6PnK9nTcA==
dependencies:
fflate "^0.7.3"
string.prototype.codepointat "^0.2.1"
"@tokenizer/token@^0.3.0":
version "0.3.0"
resolved "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz"
@@ -226,6 +302,25 @@
resolved "https://registry.npmjs.org/@types/node/-/node-20.2.1.tgz"
integrity sha512-DqJociPbZP1lbZ5SQPk4oag6W7AyaGMO6gSfRwq3PWl4PXTwJpRQJhDq4W0kzrg3w6tJ1SwlvGZ5uKFHY13LIg==
"@types/prop-types@*":
version "15.7.5"
resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.5.tgz#5f19d2b85a98e9558036f6a3cacc8819420f05cf"
integrity sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==
"@types/react@^18.2.7":
version "18.2.7"
resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.7.tgz#dfb4518042a3117a045b8c222316f83414a783b3"
integrity sha512-ojrXpSH2XFCmHm7Jy3q44nXDyN54+EYKP2lBhJ2bqfyPj6cIUW/FZW/Csdia34NQgq7KYcAlHi5184m4X88+yw==
dependencies:
"@types/prop-types" "*"
"@types/scheduler" "*"
csstype "^3.0.2"
"@types/scheduler@*":
version "0.16.3"
resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.3.tgz#cef09e3ec9af1d63d2a6cc5b383a737e24e6dcf5"
integrity sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ==
"@types/ws@^8.5.4":
version "8.5.4"
resolved "https://registry.npmjs.org/@types/ws/-/ws-8.5.4.tgz"
@@ -238,6 +333,11 @@ acorn@^8.8.2:
resolved "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz"
integrity sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==
[email protected]:
version "0.0.8"
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-0.0.8.tgz#1101e9544f4a76b1bc3b26d452ca96d7a35e7978"
integrity sha512-3XSA2cR/h/73EzlXXdU6YNycmYI7+kicTxks4eJg2g39biHR84slg2+des+p7iHYhbRg/udIS4TD53WabcOUkw==
busboy@^1.6.0:
version "1.6.0"
resolved "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz"
@@ -250,6 +350,45 @@ cac@^6.7.14:
resolved "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz"
integrity sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==
camelize@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/camelize/-/camelize-1.0.1.tgz#89b7e16884056331a35d6b5ad064332c91daa6c3"
integrity sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==
color-name@^1.1.4:
version "1.1.4"
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2"
integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
css-background-parser@^0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/css-background-parser/-/css-background-parser-0.1.0.tgz#48a17f7fe6d4d4f1bca3177ddf16c5617950741b"
integrity sha512-2EZLisiZQ+7m4wwur/qiYJRniHX4K5Tc9w93MT3AS0WS1u5kaZ4FKXlOTBhOjc+CgEgPiGY+fX1yWD8UwpEqUA==
[email protected]:
version "1.0.0-3"
resolved "https://registry.yarnpkg.com/css-box-shadow/-/css-box-shadow-1.0.0-3.tgz#9eaeb7140947bf5d649fc49a19e4bbaa5f602713"
integrity sha512-9jaqR6e7Ohds+aWwmhe6wILJ99xYQbfmK9QQB9CcMjDbTxPZjwEmUQpU91OG05Xgm8BahT5fW+svbsQGjS/zPg==
css-color-keywords@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/css-color-keywords/-/css-color-keywords-1.0.0.tgz#fea2616dc676b2962686b3af8dbdbe180b244e05"
integrity sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==
css-to-react-native@^3.0.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/css-to-react-native/-/css-to-react-native-3.2.0.tgz#cdd8099f71024e149e4f6fe17a7d46ecd55f1e32"
integrity sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==
dependencies:
camelize "^1.0.0"
css-color-keywords "^1.0.0"
postcss-value-parser "^4.0.2"
csstype@^3.0.2:
version "3.1.2"
resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.2.tgz#1d4bf9d572f11c14031f0436e1c10bc1f571f50b"
integrity sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==
data-uri-to-buffer@^4.0.0:
version "4.0.1"
resolved "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz"
@@ -286,6 +425,11 @@ discord.js@^14.9.0:
undici "^5.21.0"
ws "^8.13.0"
emoji-regex@^10.2.1:
version "10.2.1"
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-10.2.1.tgz#a41c330d957191efd3d9dfe6e1e8e1e9ab048b3f"
integrity sha512-97g6QgOk8zlDRdgq1WxwgTMgEWGVAQvB5Fdpgc1MkNy56la5SKP9GsMXKDOdqwn90/41a8yPwIGk1Y6WVbeMQA==
esbuild@^0.17.5:
version "0.17.19"
resolved "https://registry.npmjs.org/esbuild/-/esbuild-0.17.19.tgz"
@@ -332,6 +476,11 @@ fetch-blob@^3.1.2, fetch-blob@^3.1.4:
node-domexception "^1.0.0"
web-streams-polyfill "^3.0.3"
fflate@^0.7.3:
version "0.7.4"
resolved "https://registry.yarnpkg.com/fflate/-/fflate-0.7.4.tgz#61587e5d958fdabb5a9368a302c25363f4f69f50"
integrity sha512-5u2V/CDW15QM1XbbgS+0DfPxVB+jUKhWEKuuFuHncbk3tEEqzmoXL+2KyOFuKGqOnmdIy0/davWF1CkuwtibCw==
file-type@^18.2.1:
version "18.2.1"
resolved "https://registry.npmjs.org/file-type/-/file-type-18.2.1.tgz"
@@ -348,7 +497,7 @@ formdata-polyfill@^4.0.10:
dependencies:
fetch-blob "^3.1.2"
[email protected], fsevents@~2.3.2:
fsevents@~2.3.2:
version "2.3.2"
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a"
integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==
@@ -358,6 +507,11 @@ fuzzysort@^2.0.4:
resolved "https://registry.npmjs.org/fuzzysort/-/fuzzysort-2.0.4.tgz"
integrity sha512-Api1mJL+Ad7W7vnDZnWq5pGaXJjyencT+iKGia2PlHUcSsSzWwIQ3S1isiMpwpavjYtGd2FzhUIhnnhOULZgDw==
hex-rgb@^4.1.0:
version "4.3.0"
resolved "https://registry.yarnpkg.com/hex-rgb/-/hex-rgb-4.3.0.tgz#af5e974e83bb2fefe44d55182b004ec818c07776"
integrity sha512-Ox1pJVrDCyGHMG9CFg1tmrRUMRPRsAWYc/PinY0XzJU4K7y7vjNoLKIQ7BR5UJMCxNN8EM1MNDmHWA/B3aZUuw==
ieee754@^1.2.1:
version "1.2.1"
resolved "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz"
@@ -368,11 +522,24 @@ inherits@^2.0.3:
resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz"
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
"js-tokens@^3.0.0 || ^4.0.0":
version "4.0.0"
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==
jsonc-parser@^3.2.0:
version "3.2.0"
resolved "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz"
integrity sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==
linebreak@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/linebreak/-/linebreak-1.1.0.tgz#831cf378d98bced381d8ab118f852bd50d81e46b"
integrity sha512-MHp03UImeVhB7XZtjd0E4n6+3xr5Dq/9xI/5FptGk5FrbDR3zagPa2DS6U8ks/3HjbKWG9Q1M2ufOzxV2qLYSQ==
dependencies:
base64-js "0.0.8"
unicode-trie "^2.0.0"
lodash.snakecase@^4.1.1:
version "4.1.1"
resolved "https://registry.npmjs.org/lodash.snakecase/-/lodash.snakecase-4.1.1.tgz"
@@ -388,6 +555,13 @@ lodash@^4.17.21:
resolved "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz"
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
loose-envify@^1.1.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==
dependencies:
js-tokens "^3.0.0 || ^4.0.0"
markdown-escape@^2.0.0:
version "2.0.0"
resolved "https://registry.npmjs.org/markdown-escape/-/markdown-escape-2.0.0.tgz"
@@ -427,6 +601,19 @@ node-fetch@^3.3.1:
fetch-blob "^3.1.4"
formdata-polyfill "^4.0.10"
pako@^0.2.5:
version "0.2.9"
resolved "https://registry.yarnpkg.com/pako/-/pako-0.2.9.tgz#f3f7522f4ef782348da8161bad9ecfd51bf83a75"
integrity sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==
parse-css-color@^0.2.1:
version "0.2.1"
resolved "https://registry.yarnpkg.com/parse-css-color/-/parse-css-color-0.2.1.tgz#b687a583f2e42e66ffdfce80a570706966e807c9"
integrity sha512-bwS/GGIFV3b6KS4uwpzCFj4w297Yl3uqnSgIPsoQkx7GMLROXfMnWvxfNkL0oh8HVhZA4hvJoEoEIqonfJ3BWg==
dependencies:
color-name "^1.1.4"
hex-rgb "^4.1.0"
pathe@^1.1.0:
version "1.1.0"
resolved "https://registry.npmjs.org/pathe/-/pathe-1.1.0.tgz"
@@ -451,10 +638,10 @@ pkg-types@^1.0.3:
mlly "^1.2.0"
pathe "^1.1.0"
p[email protected]4.2:
version "1.34.2"
resolved "https://registry.npmjs.org/playwright-core/-/playwright-core-1.34.2.tgz"
integrity sha512-MrkgGWLANc5qthXduvIY1a/tuafROyTORVd86fwKwgoYrmnBooN/GgeZSBm7ljTLV2FCWNSXV3se7qeScKn83g==
postcss-value-parser@^4.0.2, postcss-value-parser@^4.2.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514"
integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==
postcss@^8.4.23:
version "8.4.23"
@@ -465,6 +652,13 @@ postcss@^8.4.23:
picocolors "^1.0.0"
source-map-js "^1.0.2"
react@^18.2.0:
version "18.2.0"
resolved "https://registry.yarnpkg.com/react/-/react-18.2.0.tgz#555bd98592883255fa00de14f1151a917b5d77d5"
integrity sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==
dependencies:
loose-envify "^1.1.0"
readable-stream@^3.6.0:
version "3.6.2"
resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz"
@@ -493,6 +687,21 @@ safe-buffer@~5.2.0:
resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz"
integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
satori@^0.9.1:
version "0.9.1"
resolved "https://registry.yarnpkg.com/satori/-/satori-0.9.1.tgz#59380f59be44ff825e58b5e2c8d0598240e15365"
integrity sha512-2ggMOiY8O8rIxQ6vywiw3LlBheWHbsOzu5hZdMXTDFKzE9sDHUOtJegGmGE+e9aRSO1ttttgXrcHVsHPKYU2GQ==
dependencies:
"@shuding/opentype.js" "1.4.0-beta.0"
css-background-parser "^0.1.0"
css-box-shadow "1.0.0-3"
css-to-react-native "^3.0.0"
emoji-regex "^10.2.1"
linebreak "^1.1.0"
parse-css-color "^0.2.1"
postcss-value-parser "^4.2.0"
yoga-wasm-web "^0.3.3"
source-map-js@^1.0.2:
version "1.0.2"
resolved "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz"
@@ -503,6 +712,11 @@ streamsearch@^1.1.0:
resolved "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz"
integrity sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==
string.prototype.codepointat@^0.2.1:
version "0.2.1"
resolved "https://registry.yarnpkg.com/string.prototype.codepointat/-/string.prototype.codepointat-0.2.1.tgz#004ad44c8afc727527b108cd462b4d971cd469bc"
integrity sha512-2cBVCj6I4IOvEnjgO/hWqXjqBGsY+zwPmHl12Srk9IXSZ56Jwwmy+66XO5Iut/oQVR7t5ihYdLB0GMa4alEUcg==
string_decoder@^1.1.1:
version "1.3.0"
resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz"
@@ -518,6 +732,11 @@ strtok3@^7.0.0:
"@tokenizer/token" "^0.3.0"
peek-readable "^5.0.0"
tiny-inflate@^1.0.0:
version "1.0.3"
resolved "https://registry.yarnpkg.com/tiny-inflate/-/tiny-inflate-1.0.3.tgz#122715494913a1805166aaf7c93467933eea26c4"
integrity sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==
token-types@^5.0.1:
version "5.0.1"
resolved "https://registry.npmjs.org/token-types/-/token-types-5.0.1.tgz"
@@ -553,6 +772,14 @@ undici@^5.21.0:
dependencies:
busboy "^1.6.0"
unicode-trie@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/unicode-trie/-/unicode-trie-2.0.0.tgz#8fd8845696e2e14a8b67d78fa9e0dd2cad62fec8"
integrity sha512-x7bc76x0bm4prf1VLg79uhAzKw8DVboClSN5VxJuQ+LKDOVEW9CdH+VY7SP+vX7xCYQqzzgQpFqz15zeLvAtZQ==
dependencies:
pako "^0.2.5"
tiny-inflate "^1.0.0"
util-deprecate@^1.0.1:
version "1.0.2"
resolved "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz"
@@ -590,3 +817,8 @@ ws@^8.13.0:
version "8.13.0"
resolved "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz"
integrity sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==
yoga-wasm-web@^0.3.3:
version "0.3.3"
resolved "https://registry.yarnpkg.com/yoga-wasm-web/-/yoga-wasm-web-0.3.3.tgz#eb8e9fcb18e5e651994732f19a220cb885d932ba"
integrity sha512-N+d4UJSJbt/R3wqY7Coqs5pcV0aUj2j9IaQ3rNj9bVCLld8tTGKRa2USARjnvZJWVx1NDmQev8EknoczaOQDOA==