1
0
mirror of https://github.com/tcgdex/cards-database.git synced 2025-06-14 00:29:19 +00:00

feat: Add support for Asians Pokémon Cards (#481)

This commit is contained in:
2024-06-07 12:53:08 +02:00
committed by GitHub
parent a35fadd50c
commit a26ef0e5eb
8069 changed files with 379200 additions and 423 deletions

Binary file not shown.

View File

@ -4,7 +4,10 @@ import { cardToCardSingle, getCards } from '../utils/cardUtil'
const fn: FileFunction = async (lang: SupportedLanguages) => {
const common = await getCards(lang)
return await Promise.all(common.map((card) => cardToCardSingle(card[0], card[1], lang)))
return await Promise.all(common.map((card) => cardToCardSingle(card[0], card[1], lang).catch((e) => {
console.log('error compiling card', `${card[1].set.id}-${card[0]}`)
throw e
})))
}
export default fn

View File

@ -1,8 +1,9 @@
import { getSets, setToSetSingle } from '../utils/setUtil'
import { SupportedLanguages } from '../../../interfaces'
import { Set } from '../../../meta/definitions/api'
import { FileFunction } from '../compilerInterfaces'
import { getCards } from '../utils/cardUtil'
import { getSeries } from '../utils/serieUtil'
import { getSets, setToSetSingle } from '../utils/setUtil'
interface Stats {
count: number
@ -14,10 +15,20 @@ interface Stats {
const fn: FileFunction = async (lang: SupportedLanguages) => {
const stats: Partial<Stats> = {}
stats.count = (await getCards(lang)).length
// temporary fix until a better solution is found
const referenceLang = ['ja', 'ko', 'zh-tw', 'id', 'th', 'zh-cn'].includes(lang) ? 'ja' : 'en'
const langSets = await Promise.all(await getSets(undefined, lang).then((sets) => sets.map(async (set) => await setToSetSingle(set, lang))))
const englishSets = await Promise.all(await getSets(undefined, 'en').then((sets) => sets.map(async (set) => await setToSetSingle(set, 'en'))))
stats.total = langSets.reduce((p, set) => p + (englishSets.find((s) => set.id === s.id)?.cardCount?.total ?? 0), 0)
const englishSets = await Promise.all(await getSets(undefined, referenceLang).then((sets) => sets.map(async (set) => await setToSetSingle(set, referenceLang))))
function max(lSet?: Set, refSet?: Set) {
if (!lSet) return refSet!.cardCount.total
if (!refSet) return lSet.cardCount.total
if (lSet.cardCount.total > refSet.cardCount.total) {
return lSet.cardCount.total
}
return refSet.cardCount.total
}
stats.total = langSets.reduce((p, set) => p + max(set, englishSets.find((s) => set.id === s.id)), 0)
stats.images = langSets.reduce((p1, set) => p1 + (set.cards.reduce((p2, card) => p2 + (card.image ? 1 : 0), 0)), 0)
stats.sets = {}

View File

@ -4,7 +4,10 @@ import { SupportedLanguages } from '../../interfaces'
import { FileFunction } from './compilerInterfaces'
import { fetchRemoteFile, loadLastEdits } from './utils/util'
const LANGS: Array<SupportedLanguages> = ['en', 'fr', 'es', 'it', 'pt', 'de']
const LANGS: Array<SupportedLanguages> = [
'en', 'fr', 'es', 'it', 'pt', 'pt-br', 'pt-pt', 'de', 'nl', 'pl', 'ru',
'ja', 'ko', 'zh-tw', 'id', 'th', 'zh-cn'
]
const DIST_FOLDER = './generated'
@ -12,7 +15,7 @@ const DIST_FOLDER = './generated'
const paths = (await fs.readdir('./compiler/endpoints')).filter((p) => p.endsWith('.ts'))
// Prefetch the pictures at the start as it can bug because of bad connection
console.log('Prefetching pictures')
console.log('1. Prefetching pictures datas')
await fetchRemoteFile('https://assets.tcgdex.net/datas.json')
// Delete dist folder to be sure to have a clean base
@ -20,14 +23,14 @@ const DIST_FOLDER = './generated'
await fs.rm(DIST_FOLDER, {recursive: true})
} catch {}
console.log('Loading files last edit')
console.log('\n2. Loading informations from GIT')
await loadLastEdits()
console.log('Let\'s GO !')
console.log('\n3. Compiling Files')
// Process each languages
let progressIndex = 0
for await (const lang of LANGS) {
console.log('Processing', lang)
// loop through """endpoints"""
for await (const file of paths) {
@ -41,7 +44,7 @@ const DIST_FOLDER = './generated'
const fn = (await import(`./endpoints/${file}`)).default as FileFunction
// Run the function
console.log(file, 'Running...')
console.log(' ', 'Compiling', lang, file)
const item = await fn(lang)
// Write to file
@ -49,10 +52,11 @@ const DIST_FOLDER = './generated'
item
))
console.log(file, 'Finished!')
console.log(`${(++progressIndex / (LANGS.length * paths.length) * 100).toFixed(2).padStart(5, '0')}%`, 'Compiled ', lang, file)
}
}
console.log('4. Copying static files to public folder')
// Finally copy definitions files to the public folder :D
for await (const file of await fs.readdir('../meta/definitions')) {
await fs.copyFile('../meta/definitions/' + file, './public/v2/' + file)

View File

@ -2,9 +2,9 @@
import { exec } from 'child_process'
import { Card, Set, SupportedLanguages, Types } from '../../../interfaces'
import { CardResume, Card as CardSingle } from '../../../meta/definitions/api'
import { setToSetSimple } from './setUtil'
import { getSet, setToSetSimple } from './setUtil'
import translate from './translationUtil'
import { DB_PATH, cardIsLegal, fetchRemoteFile, getLastEdit, smartGlob } from './util'
import { DB_PATH, cardIsLegal, fetchRemoteFile, getDataFolder, getLastEdit, smartGlob } from './util'
export async function getCardPictures(cardId: string, card: Card, lang: SupportedLanguages): Promise<string | undefined> {
try {
@ -108,7 +108,7 @@ export async function cardToCardSingle(localId: string, card: Card, lang: Suppor
standard: cardIsLegal('standard', card, localId),
expanded: cardIsLegal('expanded', card, localId)
},
updated: await getCardLastEdit(localId, card)
updated: await getCardLastEdit(localId, card, lang)
}
}
@ -118,8 +118,12 @@ export async function cardToCardSingle(localId: string, card: Card, lang: Suppor
* @param id the local id of the card
* @returns [the local id, the Card object]
*/
export async function getCard(serie: string, setName: string, id: string): Promise<Card> {
return (await import(`../../${DB_PATH}/data/${serie}/${setName}/${id}.ts`)).default
export async function getCard(set: Set, id: string, lang: SupportedLanguages): Promise<Card> {
try {
return (await import(`../../${DB_PATH}/${getDataFolder(lang)}/${set.serie.name.en ?? set.serie.name[lang]}/${set.name.en ?? set.name[lang]}/${id}.ts`)).default
} catch {
return (await import(`../../${DB_PATH}/${getDataFolder(lang)}/${set.serie.id}/${set.id}/${id}.ts`)).default
}
}
/**
@ -129,7 +133,10 @@ export async function getCard(serie: string, setName: string, id: string): Promi
* @returns An array with the 0 = localId, 1 = Card Object
*/
export async function getCards(lang: SupportedLanguages, set?: Set): Promise<Array<[string, Card]>> {
const cards = await smartGlob(`${DB_PATH}/data/${(set && set.serie.name.en) ?? '*'}/${(set && set.name.en) ?? '*'}/*.ts`)
let cards = await smartGlob(`${DB_PATH}/${getDataFolder(lang)}/${(set && (set.serie.name.en ?? set.serie.name[lang])) ?? '*'}/${(set && (set.name.en ?? set.name[lang])) ?? '*'}/*.ts`)
if (cards.length === 0) {
cards = await smartGlob(`${DB_PATH}/${getDataFolder(lang)}/${(set && set.serie.id) ?? '*'}/${(set && set.id) ?? '*'}/*.ts`)
}
const list: Array<[string, Card]> = []
for (const path of cards) {
let items = path.split('/')
@ -140,12 +147,19 @@ export async function getCards(lang: SupportedLanguages, set?: Set): Promise<Arr
id = id.substring(0, id.lastIndexOf('.'))
// get it's set name
const setName = (set && set.name.en) ?? items[1]
const setName = items[1]
// get it's serie name
const serieName = (set && set.serie.name.en) ?? items[0]
// console.log(path, id, setName)
const c = await getCard(serieName, setName, id)
const serieName = items[0]
const set = await getSet(setName, serieName, lang)
if (!(lang in set.name)) {
continue
}
// console.log(path, id, set, lang)
const c = await getCard(set, id, lang)
if (!c.name[lang]) {
continue
}
@ -163,7 +177,18 @@ export async function getCards(lang: SupportedLanguages, set?: Set): Promise<Arr
})
}
async function getCardLastEdit(localId: string, card: Card): Promise<string> {
const path = `../data/${card.set.serie.name.en}/${card.set.name.en ?? card.set.name.fr}/${localId}.ts`
return getLastEdit(path)
export async function getCardLastEdit(localId: string, card: Card, lang: SupportedLanguages): Promise<string> {
try {
const path = `../${getDataFolder(lang)}/${card.set.serie.name.en}/${card.set.name.en ?? card.set.name.fr}/${localId}.ts`
return getLastEdit(path)
} catch (e) {
try {
const path = `../${getDataFolder(lang)}/${card.set.serie.id}/${card.set.id}/${localId}.ts`
return getLastEdit(path)
} catch (e2) {
console.error(card)
console.error(e)
throw e2
}
}
}

View File

@ -1,10 +1,10 @@
import { Serie, Set, SupportedLanguages } from '../../../interfaces'
import { SerieResume, Serie as SerieSingle } from '../../../meta/definitions/api'
import { getSets, setToSetSimple } from './setUtil'
import { DB_PATH, smartGlob } from './util'
import { DB_PATH, getDataFolder, smartGlob } from './util'
export async function getSerie(name: string): Promise<Serie> {
return (await import(`../../${DB_PATH}/data/${name}.ts`)).default
export async function getSerie(name: string, lang: SupportedLanguages): Promise<Serie> {
return (await import(`../../${DB_PATH}/${getDataFolder(lang)}/${name}.ts`)).default
}
export async function isSerieAvailable(serie: Serie, lang: SupportedLanguages): Promise<boolean> {
@ -16,11 +16,11 @@ export async function isSerieAvailable(serie: Serie, lang: SupportedLanguages):
}
export async function getSeries(lang: SupportedLanguages): Promise<Array<Serie>> {
let series: Array<Serie> = (await Promise.all((await smartGlob(`${DB_PATH}/data/*.ts`))
let series: Array<Serie> = (await Promise.all((await smartGlob(`${DB_PATH}/${getDataFolder(lang)}/*.ts`))
// Find Serie's name
.map((it) => it.substring(it.lastIndexOf('/') + 1, it.length - 3))
// Fetch the Serie
.map((it) => getSerie(it))))
.map((it) => getSerie(it, lang))))
// Filter the serie if no name's exists in the selected lang
.filter((serie) => Boolean(serie.name[lang]))

View File

@ -1,7 +1,8 @@
import { objectKeys } from '@dzeio/object-util'
import { Set, SupportedLanguages } from '../../../interfaces'
import { SetResume, Set as SetSingle } from '../../../meta/definitions/api'
import { cardToCardSimple, getCards } from './cardUtil'
import { DB_PATH, fetchRemoteFile, setIsLegal, smartGlob } from './util'
import { DB_PATH, fetchRemoteFile, getDataFolder, setIsLegal, smartGlob } from './util'
interface t {
[key: string]: Set
@ -17,14 +18,16 @@ export function isSetAvailable(set: Set, lang: SupportedLanguages): boolean {
* Return the set
* @param name the name of the set
*/
export async function getSet(name: string, serie = '*'): Promise<Set> {
export async function getSet(name: string, serie = '*', lang: SupportedLanguages): Promise<Set> {
if (!setCache[name]) {
const file = `${DB_PATH}/${getDataFolder(lang)}/${serie}/${name}.ts`
try {
const [path] = await smartGlob(`${DB_PATH}/data/${serie}/${name}.ts`)
const [path] = await smartGlob(file)
// console.log(`${DB_PATH}/${getDataFolder(lang)}/${serie}/${name}.ts`)
setCache[name] = (await import(`../../${path}`)).default
} catch (error) {
console.error(error)
console.error(`Error trying to import importing (${`db/data/${serie}/${name}.ts`})`)
console.error(`Error trying to import importing (${file})`)
process.exit(1)
}
}
@ -34,9 +37,9 @@ export async function getSet(name: string, serie = '*'): Promise<Set> {
// Dont use cache as it wont necessary have them all
export async function getSets(serie = '*', lang: SupportedLanguages): Promise<Array<Set>> {
// list sets names
const rawSets = (await smartGlob(`${DB_PATH}/data/${serie}/*.ts`)).map((set) => set.substring(set.lastIndexOf('/') + 1, set.lastIndexOf('.')))
const rawSets = (await smartGlob(`${DB_PATH}/${getDataFolder(lang)}/${serie}/*.ts`)).map((set) => set.substring(set.lastIndexOf('/') + 1, set.lastIndexOf('.')))
// Fetch sets
const sets = (await Promise.all(rawSets.map((set) => getSet(set, serie))))
const sets = (await Promise.all(rawSets.map((set) => getSet(set, serie, lang))))
// Filter sets
.filter((set) => isSetAvailable(set, lang))
// Sort sets by release date
@ -93,7 +96,7 @@ export async function setToSetSingle(set: Set, lang: SupportedLanguages): Promis
},
logo: pics[0],
name: set.name[lang] as string,
releaseDate: set.releaseDate,
releaseDate: typeof set.releaseDate === 'object' ? set.releaseDate[lang] ?? set.releaseDate[objectKeys(set.releaseDate)[0]]! : set.releaseDate,
serie: {
id: set.serie.id,
name: set.serie.name[lang] as string

View File

@ -1,6 +1,8 @@
import { exec } from 'node:child_process'
import { objectSize } from '@dzeio/object-util'
import { glob } from 'glob'
import { Card, Set } from '../../../interfaces'
import { exec, spawn } from 'node:child_process'
import { writeFileSync } from 'node:fs'
import { Card, Set, SupportedLanguages } from '../../../interfaces'
import * as legals from '../../../meta/legals'
interface fileCacheInterface {
@ -81,11 +83,42 @@ export function setIsLegal(type: 'standard' | 'expanded', set: Set): boolean {
return false
}
function runCommand(command: string): Promise<string> {
export function getDataFolder(lang: SupportedLanguages) {
return ['ja', 'ko', 'zh-tw', 'id', 'th', 'zh-cn'].includes(lang) ? 'data-asia' : 'data'
}
/**
* run a command on the OS, it uses Spawn by default because exec seems to have a bug linked to the Buffer
*
* @param command the command to run
* @param useSpawn select the method to use to run the command
* @returns a string with the stdout
*/
function runCommand(command: string, useSpawn = true): Promise<string> {
if (!useSpawn) {
return new Promise<string>((res, rej) => {
exec(command, (err, out) => {
if (err) {
rej(err)
}
res(out)
})
})
}
const splitted = command.split(' ')
command = splitted.shift()!
return new Promise<string>((res, rej) => {
exec(command, (err, out) => {
if (err) {
rej(err)
const cmd = spawn(command, splitted)
let out: string = ''
cmd.stdout.on('data', (data) => {
out += data.toString()
})
cmd.on('close', (code) => {
if (code !== 0) {
console.log(`command exited with code ${code}`);
rej(code)
return
}
res(out)
@ -95,24 +128,28 @@ function runCommand(command: string): Promise<string> {
let lastEditsCache: Record<string, string> = {}
export async function loadLastEdits() {
console.log('Loading Git File Tree...')
const firstCommand = 'git ls-tree -r --name-only HEAD ../data'
const files = (await runCommand(firstCommand)).split('\n')
const secondCommand = 'git ls-tree -r --name-only HEAD ../data-asia'
files.push(...(await runCommand(secondCommand)).split('\n'))
console.log('Loaded files tree', files.length, 'files')
console.log('Loading their last edit time')
let processed = 0
for (let file of files) {
file = file.replace(/"/g, '').replace("\\303\\251", "é")
try {
lastEditsCache[file] = await runCommand(`git log -1 --pretty="format:%cd" --date=iso-strict "${file}"`)
// don't really know why but it does not correctly execute the command when using Spawn
lastEditsCache[file] = await runCommand(`git log -1 --pretty="format:%cd" --date=iso-strict "${file}"`, false)
} catch {
console.warn('could not load file', file, 'hope it does not break everything else lol')
}
processed++
if (processed % 1000 === 0) {
console.log('loaded', processed, 'out of', files.length, 'files')
console.log('loaded', processed, 'out of', files.length, 'files', `(${(processed / files.length * 100).toFixed(0)}%)`)
}
}
console.log('done loading files')
console.log('done loading files', objectSize(lastEditsCache))
}
export function getLastEdit(path: string): string {

View File

@ -1,30 +1,30 @@
{
"name": "@tcgdex/server",
"private": true,
"main": "dist/index.js",
"scripts": {
"compile": "bun compiler/index.ts",
"dev": "bun --watch src/index.ts",
"validate": "tsc --noEmit --project ./tsconfig.json",
"start": "bun src/index.ts"
},
"license": "MIT",
"dependencies": {
"@dzeio/config": "^1",
"@dzeio/object-util": "^1",
"@sentry/node": "^7",
"@tcgdex/sdk": "^2",
"apicache": "^1",
"express": "^4",
"graphql": "^15",
"graphql-http": "^1.22.1",
"ruru": "^2.0.0-beta.11"
},
"devDependencies": {
"@types/apicache": "^1",
"@types/express": "^4",
"@types/node": "^20",
"glob": "^10",
"typescript": "^5"
}
"name": "@tcgdex/server",
"private": true,
"main": "dist/index.js",
"scripts": {
"compile": "bun compiler/index.ts",
"dev": "bun --watch src/index.ts",
"validate": "tsc --noEmit --project ./tsconfig.json",
"start": "bun src/index.ts"
},
"license": "MIT",
"dependencies": {
"@dzeio/config": "^1",
"@dzeio/object-util": "^1",
"@sentry/node": "^7",
"@tcgdex/sdk": "^2",
"apicache": "^1",
"express": "^4",
"graphql": "^15",
"graphql-http": "^1.22.1",
"ruru": "^2.0.0-beta.11"
},
"devDependencies": {
"@types/apicache": "^1",
"@types/express": "^4",
"@types/node": "^20",
"glob": "^10",
"typescript": "^4"
}
}

View File

@ -4,21 +4,42 @@ import { Query } from '../../interfaces'
import { handlePagination, handleSort, handleValidation } from '../../util'
import Set from './Set'
import deCards from '../../../generated/de/cards.json'
import enCards from '../../../generated/en/cards.json'
import esCards from '../../../generated/es/cards.json'
import frCards from '../../../generated/fr/cards.json'
import itCards from '../../../generated/it/cards.json'
import ptCards from '../../../generated/pt/cards.json'
import en from '../../../generated/en/cards.json'
import fr from '../../../generated/fr/cards.json'
import es from '../../../generated/es/cards.json'
import it from '../../../generated/it/cards.json'
import pt from '../../../generated/pt/cards.json'
import ptbr from '../../../generated/pt-br/cards.json'
import ptpt from '../../../generated/pt-pt/cards.json'
import de from '../../../generated/de/cards.json'
import nl from '../../../generated/nl/cards.json'
import pl from '../../../generated/pl/cards.json'
import ru from '../../../generated/ru/cards.json'
import ja from '../../../generated/ja/cards.json'
import ko from '../../../generated/ko/cards.json'
import zhtw from '../../../generated/zh-tw/cards.json'
import id from '../../../generated/id/cards.json'
import th from '../../../generated/th/cards.json'
import zhcn from '../../../generated/zh-cn/cards.json'
const cards = {
de: deCards,
en: enCards,
es: esCards,
fr: frCards,
it: itCards,
pt: ptCards
'en': en,
'fr': fr,
'es': es,
'it': it,
'pt': pt,
'pt-br': ptbr,
'pt-pt': ptpt,
'de': de,
'nl': nl,
'pl': pl,
'ru': ru,
'ja': ja,
'ko': ko,
'zh-tw': zhtw,
'id': id,
'th': th,
'zh-cn': zhcn,
} as const
type LocalCard = Omit<SDKCard, 'set'> & {set: () => Set}

View File

@ -4,20 +4,43 @@ import { Query } from '../../interfaces'
import { handlePagination, handleSort, handleValidation } from '../../util'
import Set from './Set'
import deSeries from '../../../generated/de/series.json'
import enSeries from '../../../generated/en/series.json'
import esSeries from '../../../generated/es/series.json'
import frSeries from '../../../generated/fr/series.json'
import itSeries from '../../../generated/it/series.json'
import ptSeries from '../../../generated/pt/series.json'
import en from '../../../generated/en/series.json'
import fr from '../../../generated/fr/series.json'
import es from '../../../generated/es/series.json'
import it from '../../../generated/it/series.json'
import pt from '../../../generated/pt/series.json'
import ptbr from '../../../generated/pt-br/series.json'
import ptpt from '../../../generated/pt-pt/series.json'
import de from '../../../generated/de/series.json'
import nl from '../../../generated/nl/series.json'
import pl from '../../../generated/pl/series.json'
import ru from '../../../generated/ru/series.json'
import ja from '../../../generated/ja/series.json'
import ko from '../../../generated/ko/series.json'
import zhtw from '../../../generated/zh-tw/series.json'
import id from '../../../generated/id/series.json'
import th from '../../../generated/th/series.json'
import zhcn from '../../../generated/zh-cn/series.json'
const series = {
de: deSeries,
en: enSeries,
es: esSeries,
fr: frSeries,
it: itSeries,
pt: ptSeries
'en': en,
'fr': fr,
'es': es,
'it': it,
'pt': pt,
'pt-br': ptbr,
'pt-pt': ptpt,
'de': de,
'nl': nl,
'pl': pl,
'ru': ru,
'ja': ja,
'ko': ko,
'zh-tw': zhtw,
'id': id,
'th': th,
'zh-cn': zhcn,
} as const
type LocalSerie = Omit<SDKSerie, 'sets'> & {sets: () => Array<Set>}

View File

@ -5,20 +5,42 @@ import { handlePagination, handleSort, handleValidation } from '../../util'
import Card from './Card'
import Serie from './Serie'
import deSets from '../../../generated/de/sets.json'
import enSets from '../../../generated/en/sets.json'
import esSets from '../../../generated/es/sets.json'
import frSets from '../../../generated/fr/sets.json'
import itSets from '../../../generated/it/sets.json'
import ptSets from '../../../generated/pt/sets.json'
import en from '../../../generated/en/sets.json'
import fr from '../../../generated/fr/sets.json'
import es from '../../../generated/es/sets.json'
import it from '../../../generated/it/sets.json'
import pt from '../../../generated/pt/sets.json'
import ptbr from '../../../generated/pt-br/sets.json'
import ptpt from '../../../generated/pt-pt/sets.json'
import de from '../../../generated/de/sets.json'
import nl from '../../../generated/nl/sets.json'
import pl from '../../../generated/pl/sets.json'
import ru from '../../../generated/ru/sets.json'
import ja from '../../../generated/ja/sets.json'
import ko from '../../../generated/ko/sets.json'
import zhtw from '../../../generated/zh-tw/sets.json'
import id from '../../../generated/id/sets.json'
import th from '../../../generated/th/sets.json'
import zhcn from '../../../generated/zh-cn/sets.json'
const sets = {
de: deSets,
en: enSets,
es: esSets,
fr: frSets,
it: itSets,
pt: ptSets
'en': en,
'fr': fr,
'es': es,
'it': it,
'pt': pt,
'pt-br': ptbr,
'pt-pt': ptpt,
'de': de,
'nl': nl,
'pl': pl,
'ru': ru,
'ja': ja,
'ko': ko,
'zh-tw': zhtw,
'id': id,
'th': th,
'zh-cn': zhcn,
} as const
interface variants {

View File

@ -68,7 +68,7 @@ server.use((req, res, next) => {
})
server.get('/', (_, res) => {
res.redirect('https://www.tcgdex.dev/?ref=api.tccgdex.net')
res.redirect('https://www.tcgdex.dev/?ref=api.tcgdex.net')
})
server.use(express.static('./public'))

View File

@ -1,15 +1,66 @@
import { objectLoop, objectMap } from '@dzeio/object-util'
import { SupportedLanguages } from '@tcgdex/sdk'
import { objectKeys, objectLoop, objectMap } from '@dzeio/object-util'
import express from 'express'
import Serie from './V2/Components/Serie'
import Set from './V2/Components/Set'
import deStats from '../generated/de/stats.json'
import enStats from '../generated/en/stats.json'
import esStats from '../generated/es/stats.json'
import frStats from '../generated/fr/stats.json'
import itStats from '../generated/it/stats.json'
import ptStats from '../generated/pt/stats.json'
import de from '../generated/de/stats.json'
import en from '../generated/en/stats.json'
import es from '../generated/es/stats.json'
import fr from '../generated/fr/stats.json'
import id from '../generated/id/stats.json'
import it from '../generated/it/stats.json'
import ja from '../generated/ja/stats.json'
import ko from '../generated/ko/stats.json'
import nl from '../generated/nl/stats.json'
import pl from '../generated/pl/stats.json'
// import ptbr from '../generated/pt-br/stats.json'
import ptpt from '../generated/pt-pt/stats.json'
import pt from '../generated/pt/stats.json'
import ru from '../generated/ru/stats.json'
import th from '../generated/th/stats.json'
import zhcn from '../generated/zh-cn/stats.json'
import zhtw from '../generated/zh-tw/stats.json'
const langs = {
'zh-cn': zhcn,
'zh-tw': zhtw,
'nl': nl,
'en': en,
'fr': fr,
'de': de,
'id': id,
'it': it,
'ja': ja,
'ko': ko,
'pl': pl,
'pt': pt,
// 'pt-br': ptbr,
'pt-pt': ptpt,
'ru': ru,
'es': es,
'th': th,
} as const
const langsToName = {
'zh-cn': 'Chinese (simplified)',
'zh-tw': 'Chinese (traditionnal)',
'nl': 'Dutch',
'en': 'English',
'fr': 'French',
'de': 'German',
'id': 'Indonesian',
'it': 'Italian',
'ja': 'Japanese',
'ko': 'Korean',
'pl': 'Polish',
'pt': 'Portuguese (Brazil)',
// 'pt-br': 'Portuguese (brazil)',
'pt-pt': 'Portuguese (Portugal)',
'ru': 'Russian',
'es': 'Spanish',
'th': 'Thai',
} as const
/**
* This file is meant to contains the TCGdex Project status page.
@ -19,9 +70,27 @@ import ptStats from '../generated/pt/stats.json'
* Simple calculation of maximum and current count globally
*/
const totalStats = {
count: enStats.count + frStats.count + deStats.count + itStats.count + ptStats.count + esStats.count,
total: enStats.total + frStats.total + deStats.total + itStats.total + ptStats.total + esStats.total,
images: enStats.images + frStats.images + deStats.images + itStats.images + ptStats.images + esStats.images,
count: objectMap(langs, (it) => it.count).reduce((p, c) => p + c, 0),
total: objectMap(langs, (it) => it.total).reduce((p, c) => p + c, 0),
images: objectMap(langs, (it) => it.images).reduce((p, c) => p + c, 0),
}
const asiaLangs = [
'ja',
'ko',
'zh-tw',
'id',
'th',
'zh-cn',
]
const totalInter = {
count: objectMap(langs, (it, key) => asiaLangs.includes(key) ? 0 : it.count).reduce((p, c) => p + c, 0),
total: objectMap(langs, (it, key) => asiaLangs.includes(key) ? 0 : it.total).reduce((p, c) => p + c, 0),
images: objectMap(langs, (it, key) => asiaLangs.includes(key) ? 0 : it.images).reduce((p, c) => p + c, 0),
}
const totalAsia = {
count: objectMap(langs, (it, key) => asiaLangs.includes(key) ? it.count : 0).reduce((p, c) => p + c, 0),
total: objectMap(langs, (it, key) => asiaLangs.includes(key) ? it.total : 0).reduce((p, c) => p + c, 0),
images: objectMap(langs, (it, key) => asiaLangs.includes(key) ? it.images : 0).reduce((p, c) => p + c, 0),
}
/**
@ -32,7 +101,7 @@ const totalStats = {
*/
const setsData: Record<string, Record<string, Array<string>>> = {}
function preProcessSets(t: any, lang: SupportedLanguages) {
function preProcessSets(t: any, lang: keyof typeof langsToName) {
objectLoop(t.sets, (sets, serieId: string) => {
if (!(serieId in setsData)) {
setsData[serieId] = {}
@ -46,304 +115,293 @@ function preProcessSets(t: any, lang: SupportedLanguages) {
})
}
preProcessSets(enStats, 'en')
preProcessSets(frStats, 'fr')
preProcessSets(esStats, 'es')
preProcessSets(itStats, 'it')
preProcessSets(ptStats, 'pt')
preProcessSets(deStats, 'de')
objectLoop(langs, (stats, key) => preProcessSets(stats, key))
// <text fill="black"><tspan x="145.5" y="80.944">${enStats.count} of ${enStats.total}&#10;</tspan><tspan x="145.5" y="101.944">${(100 * enStats.count / enStats.total).toFixed(2)}%</tspan></text>
// Yes this is ugly
export default express.Router()
.get('/github.svg', (_, res): void => {
res.setHeader('Content-Type', 'image/svg+xml')
res.send(`<svg width="1052" height="239" viewBox="0 0 1052 239" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="1052" height="239" rx="16" fill="white"/>
<rect width="1052" height="50" rx="16" fill="#F4F4F4"/>
<text fill="#999999" font-weight="bold" ><tspan x="145.5" y="30.944">English</tspan></text>
<text fill="#999999" font-weight="bold" ><tspan x="275" y="30.944">French</tspan></text>
<text fill="#999999" font-weight="bold" ><tspan x="404.5" y="30.944">German</tspan></text>
<text fill="#999999" font-weight="bold" ><tspan x="534" y="30.944">Italian</tspan></text>
<text fill="#999999" font-weight="bold" ><tspan x="663.5" y="30.944">Portuguese</tspan></text>
<text fill="#999999" font-weight="bold" ><tspan x="793" y="30.944">Spanish</tspan></text>
<text fill="#999999" font-weight="bold" ><tspan x="922.5" y="30.944">Total</tspan></text>
<text fill="black"><tspan x="16" y="91.444">Card Progress</tspan></text>
<text fill="black"><tspan x="145.5" y="80.944">${enStats.count} of ${enStats.total}&#10;</tspan><tspan x="145.5" y="101.944">${(100 * enStats.count / enStats.total).toFixed(2)}%</tspan></text>
<text fill="black"><tspan x="275" y="80.944">${frStats.count} of ${frStats.total}&#10;</tspan><tspan x="275" y="101.944">${(100 * frStats.count / frStats.total).toFixed(2)}%</tspan></text>
<text fill="black"><tspan x="404.5" y="80.944">${deStats.count} of ${deStats.total}&#10;</tspan><tspan x="404.5" y="101.944">${(100 * deStats.count / deStats.total).toFixed(2)}%</tspan></text>
<text fill="black"><tspan x="534" y="80.944">${itStats.count} of ${itStats.total}&#10;</tspan><tspan x="534" y="101.944">${(100 * itStats.count / itStats.total).toFixed(2)}%</tspan></text>
<text fill="black"><tspan x="663.5" y="80.944">${ptStats.count} of ${ptStats.total}&#10;</tspan><tspan x="663.5" y="101.944">${(100 * ptStats.count / ptStats.total).toFixed(2)}%</tspan></text>
<text fill="black"><tspan x="793" y="80.944">${esStats.count} of ${esStats.total}&#10;</tspan><tspan x="793" y="101.944">${(100 * esStats.count / esStats.total).toFixed(2)}%</tspan></text>
<text fill="black"><tspan x="922.5" y="80.944">${totalStats.count} of ${totalStats.total}&#10;</tspan><tspan x="922.5" y="101.944">${(100 * totalStats.count / totalStats.total).toFixed(2)}%</tspan></text>
<text fill="black"><tspan x="16" y="151.944">Images </tspan><tspan x="16" y="169.944">Progress</tspan></text>
<text fill="black"><tspan x="145.5" y="150.444">${enStats.images} of ${enStats.total}&#10;</tspan><tspan x="145.5" y="171.444">${(100 * enStats.images / enStats.total).toFixed(2)}%</tspan></text>
<text fill="black"><tspan x="275" y="150.444">${frStats.images} of ${frStats.total}&#10;</tspan><tspan x="275" y="171.444">${(100 * frStats.images / frStats.total).toFixed(2)}%</tspan></text>
<text fill="black"><tspan x="404.5" y="150.444">${deStats.images} of ${deStats.total}&#10;</tspan><tspan x="404.5" y="171.444">${(100 * deStats.images / deStats.total).toFixed(2)}%</tspan></text>
<text fill="black"><tspan x="534" y="150.444">${itStats.images} of ${itStats.total}&#10;</tspan><tspan x="534" y="171.444">${(100 * itStats.images / itStats.total).toFixed(2)}%</tspan></text>
<text fill="black"><tspan x="663.5" y="150.444">${ptStats.images} of ${ptStats.total}&#10;</tspan><tspan x="663.5" y="171.444">${(100 * ptStats.images / ptStats.total).toFixed(2)}%</tspan></text>
<text fill="black"><tspan x="793" y="150.444">${esStats.images} of ${esStats.total}&#10;</tspan><tspan x="793" y="171.444">${(100 * esStats.images / esStats.total).toFixed(2)}%</tspan></text>
<text fill="black"><tspan x="922.5" y="150.444">${totalStats.images} of ${totalStats.total}&#10;</tspan><tspan x="922.5" y="171.444">${(100 * totalStats.images / totalStats.total).toFixed(2)}%</tspan></text>
<text fill="black"><tspan x="16" y="219.944">Total Progress</tspan></text>
<text fill="black"><tspan x="145.5" y="209.444">${enStats.images + enStats.count} of ${enStats.total * 2}&#10;</tspan><tspan x="145.5" y="230.444">${(100 * (enStats.images + enStats.count) / (enStats.total * 2)).toFixed(2)}%</tspan></text>
<text fill="black"><tspan x="275" y="209.444">${frStats.images + frStats.count} of ${frStats.total * 2}&#10;</tspan><tspan x="275" y="230.444">${(100 * (frStats.images + frStats.count) / (frStats.total * 2)).toFixed(2)}%</tspan></text>
<text fill="black"><tspan x="404.5" y="209.444">${deStats.images + deStats.count} of ${deStats.total * 2}&#10;</tspan><tspan x="404.5" y="230.444">${(100 * (deStats.images + deStats.count) / (deStats.total * 2)).toFixed(2)}%</tspan></text>
<text fill="black"><tspan x="534" y="209.444">${itStats.images + itStats.count} of ${itStats.total * 2}&#10;</tspan><tspan x="534" y="230.444">${(100 * (itStats.images + itStats.count) / (itStats.total * 2)).toFixed(2)}%</tspan></text>
<text fill="black"><tspan x="663.5" y="209.444">${ptStats.images + ptStats.count} of ${ptStats.total * 2}&#10;</tspan><tspan x="663.5" y="230.444">${(100 * (ptStats.images + ptStats.count) / (ptStats.total * 2)).toFixed(2)}%</tspan></text>
<text fill="black"><tspan x="793" y="209.444">${esStats.images + esStats.count} of ${esStats.total * 2}&#10;</tspan><tspan x="793" y="230.444">${(100 * (esStats.images + esStats.count) / (esStats.total * 2)).toFixed(2)}%</tspan></text>
<text fill="black"><tspan x="922.5" y="209.444">${totalStats.images + totalStats.count} of ${totalStats.total * 2}&#10;</tspan><tspan x="922.5" y="230.444">${(100 * (totalStats.images + totalStats.count) / (totalStats.total * 2)).toFixed(2)}%</tspan></text>
</svg>
`)
res.send(`<svg width="1429" height="726" viewBox="0 0 1429 726" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="1429" height="726" fill="white"/>
<path d="M0 16C0 7.16344 7.16344 0 16 0H1413C1421.84 0 1429 7.16344 1429 16V47C1429 55.8366 1421.84 63 1413 63H16C7.1634 63 0 55.8366 0 47V16Z" fill="#EEEEEE"/>
<text fill="#757575" xml:space="preserve" style="white-space: pre" font-family="Arial" font-size="16" font-weight="600" letter-spacing="0em"><tspan x="136.409" y="37.5">Dutch</tspan></text>
<text fill="#757575" xml:space="preserve" style="white-space: pre" font-family="Arial" font-size="16" font-weight="600" letter-spacing="0em"><tspan x="252.227" y="37.5">English</tspan></text>
<text fill="#757575" xml:space="preserve" style="white-space: pre" font-family="Arial" font-size="16" font-weight="600" letter-spacing="0em"><tspan x="374.545" y="37.5">French</tspan></text>
<text fill="#757575" xml:space="preserve" style="white-space: pre" font-family="Arial" font-size="16" font-weight="600" letter-spacing="0em"><tspan x="490.364" y="37.5">German</tspan></text>
<text fill="#757575" xml:space="preserve" style="white-space: pre" font-family="Arial" font-size="16" font-weight="600" letter-spacing="0em"><tspan x="616.182" y="37.5">Italian</tspan></text>
<text fill="#757575" xml:space="preserve" style="white-space: pre" font-family="Arial" font-size="16" font-weight="600" letter-spacing="0em"><tspan x="740.5" y="37.5">Polish</tspan></text>
<text fill="#757575" xml:space="preserve" style="white-space: pre" font-family="Arial" font-size="16" font-weight="600" letter-spacing="0em"><tspan x="839.763" y="27.5">Portuguese&#10;</tspan><tspan x="856.138" y="47.5">(Brazil)</tspan></text>
<text fill="#757575" xml:space="preserve" style="white-space: pre" font-family="Arial" font-size="16" font-weight="600" letter-spacing="0em"><tspan x="960.582" y="27.5">Portuguese&#10;</tspan><tspan x="965.238" y="47.5">(Portugal)</tspan></text>
<text fill="#757575" xml:space="preserve" style="white-space: pre" font-family="Arial" font-size="16" font-weight="600" letter-spacing="0em"><tspan x="1095.45" y="37.5">Russian</tspan></text>
<text fill="#757575" xml:space="preserve" style="white-space: pre" font-family="Arial" font-size="16" font-weight="600" letter-spacing="0em"><tspan x="1215.77" y="37.5">Spanish</tspan></text>
<text fill="#757575" xml:space="preserve" style="white-space: pre" font-family="Arial" font-size="16" font-weight="600" letter-spacing="0em"><tspan x="1325.59" y="37.5">Total Inter</tspan></text>
<rect width="1429" height="100" transform="translate(0 63)" fill="#FAFAFA"/>
<text fill="#212121" xml:space="preserve" style="white-space: pre" font-family="Arial" font-size="16" font-weight="600" letter-spacing="0em"><tspan x="25.3359" y="109">Cards&#10;</tspan><tspan x="13.7266" y="129">Progress</tspan></text>
<text fill="#212121" xml:space="preserve" style="white-space: pre" font-family="Arial" font-size="16" font-weight="600" letter-spacing="0em"><tspan x="129.616" y="89">${langs['nl'].count}&#10;</tspan><tspan x="149.506" y="109">of&#10;</tspan><tspan x="129.616" y="129">${langs['nl'].total}&#10;</tspan><tspan x="132.858" y="149">(${(100 * langs['nl'].count / langs['nl'].total).toFixed(2)}%)</tspan></text>
<text fill="#212121" xml:space="preserve" style="white-space: pre" font-family="Arial" font-size="16" font-weight="600" letter-spacing="0em"><tspan x="250.707" y="89">${langs['en'].count}&#10;</tspan><tspan x="270.597" y="109">of&#10;</tspan><tspan x="250.707" y="129">${langs['en'].total}&#10;</tspan><tspan x="253.949" y="149">(${(100 * langs['en'].count / langs['en'].total).toFixed(2)}%)</tspan></text>
<text fill="#212121" xml:space="preserve" style="white-space: pre" font-family="Arial" font-size="16" font-weight="600" letter-spacing="0em"><tspan x="371.798" y="89">${langs['fr'].count}&#10;</tspan><tspan x="391.688" y="109">of&#10;</tspan><tspan x="371.798" y="129">${langs['fr'].total}&#10;</tspan><tspan x="375.04" y="149">(${(100 * langs['fr'].count / langs['fr'].total).toFixed(2)}%)</tspan></text>
<text fill="#212121" xml:space="preserve" style="white-space: pre" font-family="Arial" font-size="16" font-weight="600" letter-spacing="0em"><tspan x="492.888" y="89">${langs['de'].count}&#10;</tspan><tspan x="512.779" y="109">of&#10;</tspan><tspan x="492.888" y="129">${langs['de'].total}&#10;</tspan><tspan x="496.131" y="149">(${(100 * langs['de'].count / langs['de'].total).toFixed(2)}%)</tspan></text>
<text fill="#212121" xml:space="preserve" style="white-space: pre" font-family="Arial" font-size="16" font-weight="600" letter-spacing="0em"><tspan x="613.979" y="89">${langs['it'].count}&#10;</tspan><tspan x="633.87" y="109">of&#10;</tspan><tspan x="613.979" y="129">${langs['it'].total}&#10;</tspan><tspan x="617.222" y="149">(${(100 * langs['it'].count / langs['it'].total).toFixed(2)}%)</tspan></text>
<text fill="#212121" xml:space="preserve" style="white-space: pre" font-family="Arial" font-size="16" font-weight="600" letter-spacing="0em"><tspan x="735.07" y="89">${langs['pl'].count}&#10;</tspan><tspan x="754.961" y="109">of&#10;</tspan><tspan x="735.07" y="129">${langs['pl'].total}&#10;</tspan><tspan x="738.312" y="149">(${(100 * langs['pl'].count / langs['pl'].total).toFixed(2)}%)</tspan></text>
<text fill="#212121" xml:space="preserve" style="white-space: pre" font-family="Arial" font-size="16" font-weight="600" letter-spacing="0em"><tspan x="856.161" y="89">${langs['pt'].count}&#10;</tspan><tspan x="876.052" y="109">of&#10;</tspan><tspan x="856.161" y="129">${langs['pt'].total}&#10;</tspan><tspan x="859.403" y="149">(${(100 * langs['pt'].count / langs['pt'].total).toFixed(2)}%)</tspan></text>
<text fill="#212121" xml:space="preserve" style="white-space: pre" font-family="Arial" font-size="16" font-weight="600" letter-spacing="0em"><tspan x="977.252" y="89">${langs['pt-pt'].count}&#10;</tspan><tspan x="997.143" y="109">of&#10;</tspan><tspan x="977.252" y="129">${langs['pt-pt'].total}&#10;</tspan><tspan x="980.494" y="149">(${(100 * langs['pt-pt'].count / langs['pt-pt'].total).toFixed(2)}%)</tspan></text>
<text fill="#212121" xml:space="preserve" style="white-space: pre" font-family="Arial" font-size="16" font-weight="600" letter-spacing="0em"><tspan x="1098.34" y="89">${langs['ru'].count}&#10;</tspan><tspan x="1118.23" y="109">of&#10;</tspan><tspan x="1098.34" y="129">${langs['ru'].total}&#10;</tspan><tspan x="1101.59" y="149">(${(100 * langs['ru'].count / langs['ru'].total).toFixed(2)}%)</tspan></text>
<text fill="#212121" xml:space="preserve" style="white-space: pre" font-family="Arial" font-size="16" font-weight="600" letter-spacing="0em"><tspan x="1219.43" y="89">${langs['es'].count}&#10;</tspan><tspan x="1239.32" y="109">of&#10;</tspan><tspan x="1219.43" y="129">${langs['es'].total}&#10;</tspan><tspan x="1222.68" y="149">(${(100 * langs['es'].count / langs['es'].total).toFixed(2)}%)</tspan></text>
<text fill="#212121" xml:space="preserve" style="white-space: pre" font-family="Arial" font-size="16" font-weight="600" letter-spacing="0em"><tspan x="1340.52" y="89">${totalInter.count}&#10;</tspan><tspan x="1360.42" y="109">of&#10;</tspan><tspan x="1340.52" y="129">${totalInter.total}&#10;</tspan><tspan x="1343.77" y="149">(${(100 * totalInter.count / totalInter.total).toFixed(2)}%)</tspan></text>
<rect width="1429" height="100" transform="translate(0 163)" fill="#FAFAFA"/>
<text fill="#212121" xml:space="preserve" style="white-space: pre" font-family="Arial" font-size="16" font-weight="600" letter-spacing="0em"><tspan x="17.5391" y="209">Images&#10;</tspan><tspan x="13.7266" y="229">Progress</tspan></text>
<text fill="#212121" xml:space="preserve" style="white-space: pre" font-family="Arial" font-size="16" font-weight="600" letter-spacing="0em"><tspan x="129.616" y="189">${langs['nl'].images}&#10;</tspan><tspan x="149.506" y="209">of&#10;</tspan><tspan x="129.616" y="229">${langs['nl'].total}&#10;</tspan><tspan x="132.858" y="249">(${(100 * langs['nl'].images / langs['nl'].total).toFixed(2)}%)</tspan></text>
<text fill="#212121" xml:space="preserve" style="white-space: pre" font-family="Arial" font-size="16" font-weight="600" letter-spacing="0em"><tspan x="250.707" y="189">${langs['en'].images}&#10;</tspan><tspan x="270.597" y="209">of&#10;</tspan><tspan x="250.707" y="229">${langs['en'].total}&#10;</tspan><tspan x="253.949" y="249">(${(100 * langs['en'].images / langs['en'].total).toFixed(2)}%)</tspan></text>
<text fill="#212121" xml:space="preserve" style="white-space: pre" font-family="Arial" font-size="16" font-weight="600" letter-spacing="0em"><tspan x="371.798" y="189">${langs['fr'].images}&#10;</tspan><tspan x="391.688" y="209">of&#10;</tspan><tspan x="371.798" y="229">${langs['fr'].total}&#10;</tspan><tspan x="375.04" y="249">(${(100 * langs['fr'].images / langs['fr'].total).toFixed(2)}%)</tspan></text>
<text fill="#212121" xml:space="preserve" style="white-space: pre" font-family="Arial" font-size="16" font-weight="600" letter-spacing="0em"><tspan x="492.888" y="189">${langs['de'].images}&#10;</tspan><tspan x="512.779" y="209">of&#10;</tspan><tspan x="492.888" y="229">${langs['de'].total}&#10;</tspan><tspan x="496.131" y="249">(${(100 * langs['de'].images / langs['de'].total).toFixed(2)}%)</tspan></text>
<text fill="#212121" xml:space="preserve" style="white-space: pre" font-family="Arial" font-size="16" font-weight="600" letter-spacing="0em"><tspan x="613.979" y="189">${langs['it'].images}&#10;</tspan><tspan x="633.87" y="209">of&#10;</tspan><tspan x="613.979" y="229">${langs['it'].total}&#10;</tspan><tspan x="617.222" y="249">(${(100 * langs['it'].images / langs['it'].total).toFixed(2)}%)</tspan></text>
<text fill="#212121" xml:space="preserve" style="white-space: pre" font-family="Arial" font-size="16" font-weight="600" letter-spacing="0em"><tspan x="735.07" y="189">${langs['pl'].images}&#10;</tspan><tspan x="754.961" y="209">of&#10;</tspan><tspan x="735.07" y="229">${langs['pl'].total}&#10;</tspan><tspan x="738.312" y="249">(${(100 * langs['pl'].images / langs['pl'].total).toFixed(2)}%)</tspan></text>
<text fill="#212121" xml:space="preserve" style="white-space: pre" font-family="Arial" font-size="16" font-weight="600" letter-spacing="0em"><tspan x="856.161" y="189">${langs['pt'].images}&#10;</tspan><tspan x="876.052" y="209">of&#10;</tspan><tspan x="856.161" y="229">${langs['pt'].total}&#10;</tspan><tspan x="859.403" y="249">(${(100 * langs['pt'].images / langs['pt'].total).toFixed(2)}%)</tspan></text>
<text fill="#212121" xml:space="preserve" style="white-space: pre" font-family="Arial" font-size="16" font-weight="600" letter-spacing="0em"><tspan x="977.252" y="189">${langs['pt-pt'].images}&#10;</tspan><tspan x="997.143" y="209">of&#10;</tspan><tspan x="977.252" y="229">${langs['pt-pt'].total}&#10;</tspan><tspan x="980.494" y="249">(${(100 * langs['pt-pt'].images / langs['pt-pt'].total).toFixed(2)}%)</tspan></text>
<text fill="#212121" xml:space="preserve" style="white-space: pre" font-family="Arial" font-size="16" font-weight="600" letter-spacing="0em"><tspan x="1098.34" y="189">${langs['ru'].images}&#10;</tspan><tspan x="1118.23" y="209">of&#10;</tspan><tspan x="1098.34" y="229">${langs['ru'].total}&#10;</tspan><tspan x="1101.59" y="249">(${(100 * langs['ru'].images / langs['ru'].total).toFixed(2)}%)</tspan></text>
<text fill="#212121" xml:space="preserve" style="white-space: pre" font-family="Arial" font-size="16" font-weight="600" letter-spacing="0em"><tspan x="1219.43" y="189">${langs['es'].images}&#10;</tspan><tspan x="1239.32" y="209">of&#10;</tspan><tspan x="1219.43" y="229">${langs['es'].total}&#10;</tspan><tspan x="1222.68" y="249">(${(100 * langs['es'].images / langs['es'].total).toFixed(2)}%)</tspan></text>
<text fill="#212121" xml:space="preserve" style="white-space: pre" font-family="Arial" font-size="16" font-weight="600" letter-spacing="0em"><tspan x="1340.52" y="189">${totalInter.images}&#10;</tspan><tspan x="1360.42" y="209">of&#10;</tspan><tspan x="1340.52" y="229">${totalInter.total}&#10;</tspan><tspan x="1343.77" y="249">(${(100 * totalInter.images / totalInter.total).toFixed(2)}%)</tspan></text>
<rect width="1429" height="100" transform="translate(0 263)" fill="#FAFAFA"/>
<text fill="#212121" xml:space="preserve" style="white-space: pre" font-family="Arial" font-size="16" font-weight="600" letter-spacing="0em"><tspan x="28.5859" y="309">Total&#10;</tspan><tspan x="13.7266" y="329">Progress</tspan></text>
<text fill="#212121" xml:space="preserve" style="white-space: pre" font-family="Arial" font-size="16" font-weight="600" letter-spacing="0em"><tspan x="129.616" y="289">${langs['nl'].count + langs['nl'].images}&#10;</tspan><tspan x="149.506" y="309">of&#10;</tspan><tspan x="129.616" y="329">${langs['nl'].total * 2}&#10;</tspan><tspan x="132.858" y="349">(${(100 * (langs['nl'].count + langs['nl'].images) / (langs['nl'].total * 2)).toFixed(2)}%)</tspan></text>
<text fill="#212121" xml:space="preserve" style="white-space: pre" font-family="Arial" font-size="16" font-weight="600" letter-spacing="0em"><tspan x="250.707" y="289">${langs['en'].count + langs['en'].images}&#10;</tspan><tspan x="270.597" y="309">of&#10;</tspan><tspan x="250.707" y="329">${langs['en'].total * 2}&#10;</tspan><tspan x="253.949" y="349">(${(100 * (langs['en'].count + langs['en'].images) / (langs['en'].total * 2)).toFixed(2)}%)</tspan></text>
<text fill="#212121" xml:space="preserve" style="white-space: pre" font-family="Arial" font-size="16" font-weight="600" letter-spacing="0em"><tspan x="371.798" y="289">${langs['fr'].count + langs['fr'].images}&#10;</tspan><tspan x="391.688" y="309">of&#10;</tspan><tspan x="371.798" y="329">${langs['fr'].total * 2}&#10;</tspan><tspan x="375.04" y="349">(${(100 * (langs['fr'].count + langs['fr'].images) / (langs['fr'].total * 2)).toFixed(2)}%)</tspan></text>
<text fill="#212121" xml:space="preserve" style="white-space: pre" font-family="Arial" font-size="16" font-weight="600" letter-spacing="0em"><tspan x="492.888" y="289">${langs['de'].count + langs['de'].images}&#10;</tspan><tspan x="512.779" y="309">of&#10;</tspan><tspan x="492.888" y="329">${langs['de'].total * 2}&#10;</tspan><tspan x="496.131" y="349">(${(100 * (langs['de'].count + langs['de'].images) / (langs['de'].total * 2)).toFixed(2)}%)</tspan></text>
<text fill="#212121" xml:space="preserve" style="white-space: pre" font-family="Arial" font-size="16" font-weight="600" letter-spacing="0em"><tspan x="613.979" y="289">${langs['it'].count + langs['it'].images}&#10;</tspan><tspan x="633.87" y="309">of&#10;</tspan><tspan x="613.979" y="329">${langs['it'].total * 2}&#10;</tspan><tspan x="617.222" y="349">(${(100 * (langs['it'].count + langs['it'].images) / (langs['it'].total * 2)).toFixed(2)}%)</tspan></text>
<text fill="#212121" xml:space="preserve" style="white-space: pre" font-family="Arial" font-size="16" font-weight="600" letter-spacing="0em"><tspan x="735.07" y="289">${langs['pl'].count + langs['pl'].images}&#10;</tspan><tspan x="754.961" y="309">of&#10;</tspan><tspan x="735.07" y="329">${langs['pl'].total * 2}&#10;</tspan><tspan x="738.312" y="349">(${(100 * (langs['pl'].count + langs['pl'].images) / (langs['pl'].total * 2)).toFixed(2)}%)</tspan></text>
<text fill="#212121" xml:space="preserve" style="white-space: pre" font-family="Arial" font-size="16" font-weight="600" letter-spacing="0em"><tspan x="856.161" y="289">${langs['pt'].count + langs['pt'].images}&#10;</tspan><tspan x="876.052" y="309">of&#10;</tspan><tspan x="856.161" y="329">${langs['pt'].total * 2}&#10;</tspan><tspan x="859.403" y="349">(${(100 * (langs['pt'].count + langs['pt'].images) / (langs['pt'].total * 2)).toFixed(2)}%)</tspan></text>
<text fill="#212121" xml:space="preserve" style="white-space: pre" font-family="Arial" font-size="16" font-weight="600" letter-spacing="0em"><tspan x="977.252" y="289">${langs['pt-pt'].count + langs['pt-pt'].images}&#10;</tspan><tspan x="997.143" y="309">of&#10;</tspan><tspan x="977.252" y="329">${langs['pt-pt'].total * 2}&#10;</tspan><tspan x="980.494" y="349">(${(100 * (langs['pt-pt'].count + langs['pt-pt'].images) / (langs['pt-pt'].total * 2)).toFixed(2)}%)</tspan></text>
<text fill="#212121" xml:space="preserve" style="white-space: pre" font-family="Arial" font-size="16" font-weight="600" letter-spacing="0em"><tspan x="1098.34" y="289">${langs['ru'].count + langs['ru'].images}&#10;</tspan><tspan x="1118.23" y="309">of&#10;</tspan><tspan x="1098.34" y="329">${langs['ru'].total * 2}&#10;</tspan><tspan x="1101.59" y="349">(${(100 * (langs['ru'].count + langs['ru'].images) / (langs['ru'].total * 2)).toFixed(2)}%)</tspan></text>
<text fill="#212121" xml:space="preserve" style="white-space: pre" font-family="Arial" font-size="16" font-weight="600" letter-spacing="0em"><tspan x="1219.43" y="289">${langs['es'].count + langs['es'].images}&#10;</tspan><tspan x="1239.32" y="309">of&#10;</tspan><tspan x="1219.43" y="329">${langs['es'].total * 2}&#10;</tspan><tspan x="1222.68" y="349">(${(100 * (langs['es'].count + langs['es'].images) / (langs['es'].total * 2)).toFixed(2)}%)</tspan></text>
<text fill="#212121" xml:space="preserve" style="white-space: pre" font-family="Arial" font-size="16" font-weight="600" letter-spacing="0em"><tspan x="1340.52" y="289">${totalInter.images + totalInter.count}&#10;</tspan><tspan x="1360.42" y="309">of&#10;</tspan><tspan x="1340.52" y="329">${totalInter.total * 2}&#10;</tspan><tspan x="1343.77" y="349">(${(100 * (totalInter.count + totalInter.images) / (totalInter.total * 2)).toFixed(2)}%)</tspan></text>
<path d="M0 379C0 370.163 7.16344 363 16 363H1413C1421.84 363 1429 370.163 1429 379V410C1429 418.837 1421.84 426 1413 426H16C7.1634 426 0 418.837 0 410V379Z" fill="#EEEEEE"/>
<text fill="#757575" xml:space="preserve" style="white-space: pre" font-family="Arial" font-size="16" font-weight="600" letter-spacing="0em"><tspan x="163.718" y="390.5">Chinese&#10;</tspan><tspan x="139.975" y="410.5">(Traditionnal)</tspan></text>
<text fill="#757575" xml:space="preserve" style="white-space: pre" font-family="Arial" font-size="16" font-weight="600" letter-spacing="0em"><tspan x="353.575" y="390.5">Chinese&#10;</tspan><tspan x="338.176" y="410.5">(Simplified)</tspan></text>
<text fill="#757575" xml:space="preserve" style="white-space: pre" font-family="Arial" font-size="16" font-weight="600" letter-spacing="0em"><tspan x="528.643" y="400.5">Indonesian</tspan></text>
<text fill="#757575" xml:space="preserve" style="white-space: pre" font-family="Arial" font-size="16" font-weight="600" letter-spacing="0em"><tspan x="725.5" y="400.5">Japanese</tspan></text>
<text fill="#757575" xml:space="preserve" style="white-space: pre" font-family="Arial" font-size="16" font-weight="600" letter-spacing="0em"><tspan x="925.357" y="400.5">Korean</tspan></text>
<text fill="#757575" xml:space="preserve" style="white-space: pre" font-family="Arial" font-size="16" font-weight="600" letter-spacing="0em"><tspan x="1126.21" y="400.5">Thai</tspan></text>
<text fill="#757575" xml:space="preserve" style="white-space: pre" font-family="Arial" font-size="16" font-weight="600" letter-spacing="0em"><tspan x="1294.07" y="400.5">Total Asia</tspan></text>
<rect width="1429" height="100" transform="translate(0 426)" fill="#FAFAFA"/>
<text fill="#212121" xml:space="preserve" style="white-space: pre" font-family="Arial" font-size="16" font-weight="600" letter-spacing="0em"><tspan x="25.3359" y="472">Cards&#10;</tspan><tspan x="13.7266" y="492">Progress</tspan></text>
<text fill="#212121" xml:space="preserve" style="white-space: pre" font-family="Arial" font-size="16" font-weight="600" letter-spacing="0em"><tspan x="164.213" y="452">${langs['zh-tw'].count}&#10;</tspan><tspan x="184.104" y="472">of&#10;</tspan><tspan x="164.213" y="492">${langs['zh-tw'].total}&#10;</tspan><tspan x="167.455" y="512">(${(100 * langs['zh-tw'].count / langs['zh-tw'].total).toFixed(2)}%)</tspan></text>
<text fill="#212121" xml:space="preserve" style="white-space: pre" font-family="Arial" font-size="16" font-weight="600" letter-spacing="0em"><tspan x="354.499" y="452">${langs['zh-cn'].count}&#10;</tspan><tspan x="374.39" y="472">of&#10;</tspan><tspan x="354.499" y="492">${langs['zh-cn'].total}&#10;</tspan><tspan x="357.741" y="512">(${(100 * langs['zh-cn'].count / langs['zh-cn'].total).toFixed(2)}%)</tspan></text>
<text fill="#212121" xml:space="preserve" style="white-space: pre" font-family="Arial" font-size="16" font-weight="600" letter-spacing="0em"><tspan x="544.785" y="452">${langs['id'].count}&#10;</tspan><tspan x="564.675" y="472">of&#10;</tspan><tspan x="544.785" y="492">${langs['id'].total}&#10;</tspan><tspan x="548.027" y="512">(${(100 * langs['id'].count / langs['id'].total).toFixed(2)}%)</tspan></text>
<text fill="#212121" xml:space="preserve" style="white-space: pre" font-family="Arial" font-size="16" font-weight="600" letter-spacing="0em"><tspan x="735.07" y="452">${langs['ja'].count}&#10;</tspan><tspan x="754.961" y="472">of&#10;</tspan><tspan x="735.07" y="492">${langs['ja'].total}&#10;</tspan><tspan x="738.312" y="512">(${(100 * langs['ja'].count / langs['ja'].total).toFixed(2)}%)</tspan></text>
<text fill="#212121" xml:space="preserve" style="white-space: pre" font-family="Arial" font-size="16" font-weight="600" letter-spacing="0em"><tspan x="925.356" y="452">${langs['ko'].count}&#10;</tspan><tspan x="945.247" y="472">of&#10;</tspan><tspan x="925.356" y="492">${langs['ko'].total}&#10;</tspan><tspan x="928.598" y="512">(${(100 * langs['ko'].count / langs['ko'].total).toFixed(2)}%)</tspan></text>
<text fill="#212121" xml:space="preserve" style="white-space: pre" font-family="Arial" font-size="16" font-weight="600" letter-spacing="0em"><tspan x="1115.64" y="452">${langs['th'].count}&#10;</tspan><tspan x="1135.53" y="472">of&#10;</tspan><tspan x="1115.64" y="492">${langs['th'].total}&#10;</tspan><tspan x="1118.88" y="512">(${(100 * langs['th'].count / langs['th'].total).toFixed(2)}%)</tspan></text>
<text fill="#212121" xml:space="preserve" style="white-space: pre" font-family="Arial" font-size="16" font-weight="600" letter-spacing="0em"><tspan x="1305.93" y="452">${totalAsia.count}&#10;</tspan><tspan x="1325.82" y="472">of&#10;</tspan><tspan x="1305.93" y="492">${totalAsia.total}&#10;</tspan><tspan x="1309.17" y="512">(${(100 * totalAsia.count / totalAsia.total).toFixed(2)}%)</tspan></text>
<rect width="1429" height="100" transform="translate(0 526)" fill="#FAFAFA"/>
<text fill="#212121" xml:space="preserve" style="white-space: pre" font-family="Arial" font-size="16" font-weight="600" letter-spacing="0em"><tspan x="17.5391" y="572">Images&#10;</tspan><tspan x="13.7266" y="592">Progress</tspan></text>
<text fill="#212121" xml:space="preserve" style="white-space: pre" font-family="Arial" font-size="16" font-weight="600" letter-spacing="0em"><tspan x="164.213" y="552">${langs['zh-tw'].images}&#10;</tspan><tspan x="184.104" y="572">of&#10;</tspan><tspan x="164.213" y="592">${langs['zh-tw'].total}&#10;</tspan><tspan x="167.455" y="612">(${(100 * langs['zh-tw'].images / langs['zh-tw'].total).toFixed(2)}%)</tspan></text>
<text fill="#212121" xml:space="preserve" style="white-space: pre" font-family="Arial" font-size="16" font-weight="600" letter-spacing="0em"><tspan x="354.499" y="552">${langs['zh-cn'].images}&#10;</tspan><tspan x="374.39" y="572">of&#10;</tspan><tspan x="354.499" y="592">${langs['zh-cn'].total}&#10;</tspan><tspan x="357.741" y="612">(${(100 * langs['zh-cn'].images / langs['zh-cn'].total).toFixed(2)}%)</tspan></text>
<text fill="#212121" xml:space="preserve" style="white-space: pre" font-family="Arial" font-size="16" font-weight="600" letter-spacing="0em"><tspan x="544.785" y="552">${langs['id'].images}&#10;</tspan><tspan x="564.675" y="572">of&#10;</tspan><tspan x="544.785" y="592">${langs['id'].total}&#10;</tspan><tspan x="548.027" y="612">(${(100 * langs['id'].images / langs['id'].total).toFixed(2)}%)</tspan></text>
<text fill="#212121" xml:space="preserve" style="white-space: pre" font-family="Arial" font-size="16" font-weight="600" letter-spacing="0em"><tspan x="735.07" y="552">${langs['ja'].images}&#10;</tspan><tspan x="754.961" y="572">of&#10;</tspan><tspan x="735.07" y="592">${langs['ja'].total}&#10;</tspan><tspan x="738.312" y="612">(${(100 * langs['ja'].images / langs['ja'].total).toFixed(2)}%)</tspan></text>
<text fill="#212121" xml:space="preserve" style="white-space: pre" font-family="Arial" font-size="16" font-weight="600" letter-spacing="0em"><tspan x="925.356" y="552">${langs['ko'].images}&#10;</tspan><tspan x="945.247" y="572">of&#10;</tspan><tspan x="925.356" y="592">${langs['ko'].total}&#10;</tspan><tspan x="928.598" y="612">(${(100 * langs['ko'].images / langs['ko'].total).toFixed(2)}%)</tspan></text>
<text fill="#212121" xml:space="preserve" style="white-space: pre" font-family="Arial" font-size="16" font-weight="600" letter-spacing="0em"><tspan x="1115.64" y="552">${langs['th'].images}&#10;</tspan><tspan x="1135.53" y="572">of&#10;</tspan><tspan x="1115.64" y="592">${langs['th'].total}&#10;</tspan><tspan x="1118.88" y="612">(${(100 * langs['th'].images / langs['th'].total).toFixed(2)}%)</tspan></text>
<text fill="#212121" xml:space="preserve" style="white-space: pre" font-family="Arial" font-size="16" font-weight="600" letter-spacing="0em"><tspan x="1305.93" y="552">${totalAsia.images}&#10;</tspan><tspan x="1325.82" y="572">of&#10;</tspan><tspan x="1305.93" y="592">${totalAsia.total}&#10;</tspan><tspan x="1309.17" y="612">(${(100 * totalAsia.images / totalAsia.total).toFixed(2)}%)</tspan></text>
<rect width="1429" height="100" transform="translate(0 626)" fill="#FAFAFA"/>
<text fill="#212121" xml:space="preserve" style="white-space: pre" font-family="Arial" font-size="16" font-weight="600" letter-spacing="0em"><tspan x="28.5859" y="672">Total&#10;</tspan><tspan x="13.7266" y="692">Progress</tspan></text>
<text fill="#212121" xml:space="preserve" style="white-space: pre" font-family="Arial" font-size="16" font-weight="600" letter-spacing="0em"><tspan x="164.213" y="652">${langs['zh-tw'].count + langs['zh-tw'].images}&#10;</tspan><tspan x="184.104" y="672">of&#10;</tspan><tspan x="164.213" y="692">${langs['zh-tw'].total * 2}&#10;</tspan><tspan x="167.455" y="712">(${(100 * (langs['zh-tw'].count + langs['zh-tw'].images) / (langs['zh-tw'].total * 2)).toFixed(2)}%)</tspan></text>
<text fill="#212121" xml:space="preserve" style="white-space: pre" font-family="Arial" font-size="16" font-weight="600" letter-spacing="0em"><tspan x="354.499" y="652">${langs['zh-cn'].count + langs['zh-cn'].images}&#10;</tspan><tspan x="374.39" y="672">of&#10;</tspan><tspan x="354.499" y="692">${langs['zh-cn'].total * 2}&#10;</tspan><tspan x="357.741" y="712">(${(100 * (langs['zh-cn'].count + langs['zh-cn'].images) / (langs['zh-cn'].total * 2)).toFixed(2)}%)</tspan></text>
<text fill="#212121" xml:space="preserve" style="white-space: pre" font-family="Arial" font-size="16" font-weight="600" letter-spacing="0em"><tspan x="544.785" y="652">${langs['id'].count + langs['id'].images}&#10;</tspan><tspan x="564.675" y="672">of&#10;</tspan><tspan x="544.785" y="692">${langs['id'].total * 2}&#10;</tspan><tspan x="548.027" y="712">(${(100 * (langs['id'].count + langs['id'].images) / (langs['id'].total * 2)).toFixed(2)}%)</tspan></text>
<text fill="#212121" xml:space="preserve" style="white-space: pre" font-family="Arial" font-size="16" font-weight="600" letter-spacing="0em"><tspan x="735.07" y="652">${langs['ja'].count + langs['ja'].images}&#10;</tspan><tspan x="754.961" y="672">of&#10;</tspan><tspan x="735.07" y="692">${langs['ja'].total * 2}&#10;</tspan><tspan x="738.312" y="712">(${(100 * (langs['ja'].count + langs['ja'].images) / (langs['ja'].total * 2)).toFixed(2)}%)</tspan></text>
<text fill="#212121" xml:space="preserve" style="white-space: pre" font-family="Arial" font-size="16" font-weight="600" letter-spacing="0em"><tspan x="925.356" y="652">${langs['ko'].count + langs['ko'].images}&#10;</tspan><tspan x="945.247" y="672">of&#10;</tspan><tspan x="925.356" y="692">${langs['ko'].total * 2}&#10;</tspan><tspan x="928.598" y="712">(${(100 * (langs['ko'].count + langs['ko'].images) / (langs['ko'].total * 2)).toFixed(2)}%)</tspan></text>
<text fill="#212121" xml:space="preserve" style="white-space: pre" font-family="Arial" font-size="16" font-weight="600" letter-spacing="0em"><tspan x="1115.64" y="652">${langs['th'].count + langs['th'].images}&#10;</tspan><tspan x="1135.53" y="672">of&#10;</tspan><tspan x="1115.64" y="692">${langs['th'].total * 2}&#10;</tspan><tspan x="1118.88" y="712">(${(100 * (langs['th'].count + langs['th'].images) / (langs['th'].total * 2)).toFixed(2)}%)</tspan></text>
<text fill="#212121" xml:space="preserve" style="white-space: pre" font-family="Arial" font-size="16" font-weight="600" letter-spacing="0em"><tspan x="1305.93" y="652">${totalAsia.count + totalAsia.images}&#10;</tspan><tspan x="1325.82" y="672">of&#10;</tspan><tspan x="1305.93" y="692">${totalAsia.total * 2}&#10;</tspan><tspan x="1309.17" y="712">(${(100 * (totalAsia.count + totalAsia.images) / (totalAsia.total * 2)).toFixed(2)}%)</tspan></text>
</svg>`)
})
.get('/', (_, res): void => {
res.send(`
<style>
td, th {
border-left: black 1px solid;
border-right: black 1px solid;
border-top: black 1px solid;
text-align: center;
}
tr:last-child td {
border-bottom: black 1px solid;
}
th.notop {
border-top: none;
}
th {
font-size: 1.2em;
}
table {
border-collapse: separate;
border-spacing: 1em 0;
min-width: 100%;
}
.serie td:not(:first-child), .completed {
background: gold;
}
.serie td.na, .na {
background: black;
}
.serie td.missing-cards, .missing-cards {
background: #3f17ff4f;
}
.serie td.missing-img, .missing-img {
background: #67e301;
}
.serie td.empty, .empty {
background: #ff000070;
}
h1, h2 {
margin: 1em 0;
text-align: center;
}
</style>
<!DOCTYPE html>
<html lang="en">
<head>
<title>TCGdex Project status</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
td, th {
border-left: black 1px solid;
border-right: black 1px solid;
border-top: black 1px solid;
text-align: center;
}
tr:last-child td {
border-bottom: black 1px solid;
}
th.notop {
border-top: none;
}
th {
font-size: 1.2em;
}
table {
border-collapse: separate;
border-spacing: 1em 0;
min-width: 100%;
}
.serie td:not(:first-child), .completed {
background: gold;
}
.serie td.na, .na {
background: black;
}
.serie td.missing-cards, .missing-cards {
background: #3f17ff4f;
}
.serie td.missing-img, .missing-img {
background: #67e301;
}
.serie td.empty, .empty {
background: #ff000070;
}
.serie td.too-much, .too-much {
background: white;
}
h1, h2 {
margin: 1em 0;
text-align: center;
}
</style>
</head>
<body>
<h1>TCGdex Progress</h1>
<table>
<thead>
<tr>
<th class="na"></th>
<th>English</th>
<th>French</th>
<th>German</th>
<th>Italian</th>
<th>Portuguese</th>
<th>Spanish</th>
<th>Total</th>
</tr>
</thead>
<tbody>
<tr>
<th colspan="8">Card Progress</th>
</tr>
<tr>
<td>Cards</td>
<td>${enStats.count} of ${enStats.total}</td>
<td>${frStats.count} of ${frStats.total}</td>
<td>${deStats.count} of ${deStats.total}</td>
<td>${itStats.count} of ${itStats.total}</td>
<td>${ptStats.count} of ${ptStats.total}</td>
<td>${esStats.count} of ${esStats.total}</td>
<td>${totalStats.count} of ${totalStats.total}</td>
</tr><tr>
<td>Percentage</td>
<td>${(100 * enStats.count / enStats.total).toFixed(2)}%</td>
<td>${(100 * frStats.count / frStats.total).toFixed(2)}%</td>
<td>${(100 * deStats.count / deStats.total).toFixed(2)}%</td>
<td>${(100 * itStats.count / itStats.total).toFixed(2)}%</td>
<td>${(100 * ptStats.count / ptStats.total).toFixed(2)}%</td>
<td>${(100 * esStats.count / esStats.total).toFixed(2)}%</td>
<td>${(100 * totalStats.count / totalStats.total).toFixed(2)}%</td>
</tr><tr>
<td>Remaining</td>
<td>${enStats.total - enStats.count}</td>
<td>${frStats.total - frStats.count}</td>
<td>${deStats.total - deStats.count}</td>
<td>${itStats.total - itStats.count}</td>
<td>${ptStats.total - ptStats.count}</td>
<td>${esStats.total - esStats.count}</td>
<td>${totalStats.total - totalStats.count}</td>
</tr>
<tr>
<th colspan="8">Images Progress</th>
</tr>
<tr>
<td>Cards</td>
<td>${enStats.images} of ${enStats.total}</td>
<td>${frStats.images} of ${frStats.total}</td>
<td>${deStats.images} of ${deStats.total}</td>
<td>${itStats.images} of ${itStats.total}</td>
<td>${ptStats.images} of ${ptStats.total}</td>
<td>${esStats.images} of ${esStats.total}</td>
<td>${totalStats.images} of ${totalStats.total}</td>
</tr><tr>
<td>Percentage</td>
<td>${(100 * enStats.images / enStats.total).toFixed(2)}%</td>
<td>${(100 * frStats.images / frStats.total).toFixed(2)}%</td>
<td>${(100 * deStats.images / deStats.total).toFixed(2)}%</td>
<td>${(100 * itStats.images / itStats.total).toFixed(2)}%</td>
<td>${(100 * ptStats.images / ptStats.total).toFixed(2)}%</td>
<td>${(100 * esStats.images / esStats.total).toFixed(2)}%</td>
<td>${(100 * totalStats.images / totalStats.total).toFixed(2)}%</td>
</tr><tr>
<td>Remaining</td>
<td>${enStats.total - enStats.images}</td>
<td>${frStats.total - frStats.images}</td>
<td>${deStats.total - deStats.images}</td>
<td>${itStats.total - itStats.images}</td>
<td>${ptStats.total - ptStats.images}</td>
<td>${esStats.total - esStats.images}</td>
<td>${totalStats.total - totalStats.images}</td>
</tr>
<tr>
<th colspan="8">Total Progress</th>
</tr>
<tr>
<td>Cards</td>
<td>${enStats.images + enStats.count} of ${enStats.total * 2}</td>
<td>${frStats.images + frStats.count} of ${frStats.total * 2}</td>
<td>${deStats.images + deStats.count} of ${deStats.total * 2}</td>
<td>${itStats.images + itStats.count} of ${itStats.total * 2}</td>
<td>${ptStats.images + ptStats.count} of ${ptStats.total * 2}</td>
<td>${esStats.images + esStats.count} of ${esStats.total * 2}</td>
<td>${totalStats.images + totalStats.count} of ${totalStats.total * 2}</td>
</tr><tr>
<td>Percentage</td>
<td>${(100 * (enStats.images + enStats.count) / (enStats.total * 2)).toFixed(2)}%</td>
<td>${(100 * (frStats.images + frStats.count) / (frStats.total * 2)).toFixed(2)}%</td>
<td>${(100 * (deStats.images + deStats.count) / (deStats.total * 2)).toFixed(2)}%</td>
<td>${(100 * (itStats.images + itStats.count) / (itStats.total * 2)).toFixed(2)}%</td>
<td>${(100 * (ptStats.images + ptStats.count) / (ptStats.total * 2)).toFixed(2)}%</td>
<td>${(100 * (esStats.images + esStats.count) / (esStats.total * 2)).toFixed(2)}%</td>
<td>${(100 * (totalStats.images + totalStats.count) / (totalStats.total * 2)).toFixed(2)}%</td>
</tr><tr>
<td>Remaining</td>
<td>${enStats.total * 2 - (enStats.images + enStats.count)}</td>
<td>${frStats.total * 2 - (frStats.images + frStats.count)}</td>
<td>${deStats.total * 2 - (deStats.images + deStats.count)}</td>
<td>${itStats.total * 2 - (itStats.images + itStats.count)}</td>
<td>${ptStats.total * 2 - (ptStats.images + ptStats.count)}</td>
<td>${esStats.total * 2 - (esStats.images + esStats.count)}</td>
<td>${totalStats.total * 2 - (totalStats.images + totalStats.count)}</td>
</tr>
</tbody>
</table>
<h2>Status</h2>
<ul>
<li class="completed">Completed</li>
<li class="missing-cards">Missing some cards informations</li>
<li class="missing-img">Missing some cards images</li>
<li class="empty">No Data</li>
<li class="na" style="color: white">Not Available</li>
</ul>
<table class="serie">
${objectMap(setsData, (serie, serieId) => {
// Loop through every series and name them
const name = Serie.findOne('en', { filters: { id: serieId }})?.name
return `
<table>
<thead>
<tr><th class="notop" colspan="13"><h2>${name} (${serieId})</h2></th></tr>
<tr>
<th rowspan="2">Set Name</th>
<th colspan="2">English</th>
<th colspan="2">French</th>
<th colspan="2">German</th>
<th colspan="2">Italian</th>
<th colspan="2">Portuguese</th>
<th colspan="2">Spanish</th>
</tr>
<tr>
<th>Cards</th>
<th>Images</th>
<th>Cards</th>
<th>Images</th>
<th>Cards</th>
<th>Images</th>
<th>Cards</th>
<th>Images</th>
<th>Cards</th>
<th>Images</th>
<th>Cards</th>
<th>Images</th>
<th class="na"></th>
${objectMap(langsToName, (name) => `<td>${name}</td>`).join('')}
<th>Total</th>
</tr>
</thead>
<tbody>
${objectMap(serie, (data, setId) => {
// loop through every sets
<tr>
<th colspan="19">Card Progress</th>
</tr>
<tr>
<td>Cards</td>
${objectMap(langs, (it) => `<td>${it.count} of ${it.total}</td>`).join('')}
<td>${totalStats.count} of ${totalStats.total}</td>
</tr>
<tr>
<td>Percentage</td>
${objectMap(langs, (it) => `<td>${(100 * it.count / it.total).toFixed(2)}%</td>`).join('')}
<td>${(100 * totalStats.count / totalStats.total).toFixed(2)}%</td>
</tr><tr>
<td>Remaining</td>
${objectMap(langs, (it) => `<td>${it.total - it.count}</td>`).join('')}
<td>${totalStats.total - totalStats.count}</td>
</tr>
// find the set in the first available language (Should be English globally)
const setTotal = Set.findOne(data[0] as 'en', { filters: { id: setId }})
let str = '<tr>' + `<td>${setTotal?.name} (${setId}) <br />${setTotal?.cardCount.total ?? 1} cards</td>`
<tr>
<th colspan="19">Images Progress</th>
</tr>
<tr>
<td>Cards</td>
${objectMap(langs, (it) => `<td>${it.images} of ${it.total}</td>`).join('')}
<td>${totalStats.images} of ${totalStats.total}</td>
</tr><tr>
<td>Percentage</td>
${objectMap(langs, (it) => `<td>${(100 * it.images / it.total).toFixed(2)}%</td>`).join('')}
<td>${(100 * totalStats.images / totalStats.total).toFixed(2)}%</td>
</tr><tr>
<td>Remaining</td>
${objectMap(langs, (it) => `<td>${it.total - it.images}</td>`).join('')}
<td>${totalStats.total - totalStats.images}</td>
</tr>
// Loop through every languages
const l = ['en', 'fr', 'de', 'it', 'pt', 'es']
l.map((it) => {
// Change the stats file depending on the language
let stats: any = enStats
switch (it) {
case 'fr': stats = frStats; break
case 'de': stats = deStats; break
case 'it': stats = itStats; break
case 'pt': stats = ptStats; break
case 'es': stats = esStats; break
}
// Get the stats we want
const item = stats.sets[serieId]?.[setId] as {count: number, images: number} | undefined
// if item dont exist for the language skip it
if (!item) {
str += `
<td class="na"></td>
<td class="na"></td>`
return
}
// Calculate percentages and status
const percent = 100 * item.count / (setTotal?.cardCount.total ?? 1)
const imgPercent = 100 * item.images / (setTotal?.cardCount.total ?? 1)
// append to string :D
str +=`<td class="${percent === 100 ? '' : percent === 0 ? 'empty' : 'missing-cards'}">${percent.toFixed(2)}% <br />(${item.count})</td>
<td class="${imgPercent === 100 ? '' : imgPercent === 0 ? 'empty' : 'missing-img'}">${imgPercent.toFixed(2)}% <br />(${item.images})</td>`
})
// finish Row
return str + '</tr>'
}).join('')}
<tr>
<th colspan="19">Total Progress</th>
</tr>
<tr>
<td>Cards</td>
${objectMap(langs, (it) => `<td>${it.images + it.count} of ${it.total * 2}</td>`).join('')}
<td>${totalStats.images + totalStats.count} of ${totalStats.total * 2}</td>
</tr><tr>
<td>Percentage</td>
${objectMap(langs, (it) => `<td>${(100 * (it.images + it.count) / (it.total * 2)).toFixed(2)}%</td>`).join('')}
<td>${(100 * (totalStats.images + totalStats.count) / (totalStats.total * 2)).toFixed(2)}%</td>
</tr><tr>
<td>Remaining</td>
${objectMap(langs, (it) => `<td>${it.total * 2 - (it.images + it.count)}</td>`).join('')}
<td>${totalStats.total * 2 - (totalStats.images + totalStats.count)}</td>
</tr>
</tbody>
`}).join('')}
</table>`)
</table>
<h2>Status</h2>
<ul>
<li class="completed">Completed</li>
<li class="missing-cards">Missing some cards informations</li>
<li class="missing-img">Missing some cards images</li>
<li class="empty">No Data</li>
<li class="na" style="color: white">Not Available</li>
</ul>
<table class="serie">
${objectMap(setsData, (serie, serieId) => {
// Loop through every series and name them
const name = Serie.findOne('en', { filters: { id: serieId }})?.name ?? Serie.findOne('ja' as any, { filters: { id: serieId }})?.name
return `
<thead>
<tr><th class="notop" colspan="35"><h2>${name} (${serieId})</h2></th></tr>
<tr>
<th rowspan="2">Set Name</th>
${objectMap(langsToName, (name) => `<th colspan="2">${name}</th>`).join('')}
</tr>
<tr>
${objectMap(langsToName, () => `<th>Cards</th><th>Images</th>`).join('')}
</tr>
</thead>
<tbody>
${objectMap(serie, (data, setId) => {
// loop through every sets
// find the set in the first available language (Should be English globally)
const setTotal = Set.findOne(data[0] as 'en', { filters: { id: setId }})
let str = '<tr>' + `<td>${setTotal?.name} (${setId}) <br />${setTotal?.cardCount.total ?? 1} cards</td>`
// let str = '<tr>' + `<td>${setId})</td>`
// Loop through every languages
const l = objectKeys(langs)
l.map((it) => {
// Change the stats file depending on the language
let stats: any = langs[it]
// Get the stats we want
const item = stats.sets[serieId]?.[setId] as {count: number, images: number} | undefined
// if item dont exist for the language skip it
if (!item) {
str += `<td class="na" /><td class="na" />`
return
}
// Calculate percentages and status
const percent = 100 * item.count / (setTotal?.cardCount.total ?? 1)
const imgPercent = 100 * item.images / (setTotal?.cardCount.total ?? 1)
// const percent = 100 //100 * item.count / (setTotal?.cardCount.total ?? 1)
// const imgPercent = 100 //100 * item.images / (setTotal?.cardCount.total ?? 1)
// append to string :D
str +=`<td class="${percent > 100 ? 'too-much' : percent === 100 ? '' : percent === 0 ? 'empty' : 'missing-cards'}">${percent.toFixed(2)}% <br />(${item.count})</td>
<td class="${imgPercent > 100 ? 'too-much' : imgPercent === 100 ? '' : imgPercent === 0 ? 'empty' : 'missing-img'}">${imgPercent.toFixed(2)}% <br />(${item.images})</td>`
})
// finish Row
return str + '</tr>'
}).join('')}
</tbody>
`}).join('')}
</table>
</body>
</html>
`)
})

View File

@ -4,7 +4,25 @@ import { Response } from 'express'
import { Query } from './interfaces'
export function checkLanguage(str: string): str is SupportedLanguages {
return ['en', 'fr', 'es', 'it', 'pt', 'de'].includes(str)
return [
'en',
'fr',
'es',
'it',
'pt',
'pt-br',
'pt-pt',
'de',
'nl',
'pl',
'ru',
'ja',
'ko',
'zh-tw',
'id',
'th',
'zh-cn'
].includes(str)
}
export function unique(arr: Array<string>): Array<string> {