From d9218dea59f6ccaa447d2f72ba2450be0576d00e Mon Sep 17 00:00:00 2001 From: quexeky <6-quexeky@users.noreply.lab.deepcore.dev> Date: Wed, 8 Jan 2025 22:43:30 +0000 Subject: [PATCH 01/13] fix: Update README.md with discord link --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4916f96..33d1533 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ [![GitHub License](https://img.shields.io/github/license/Drop-OSS/drop-app)](LICENSE) [![Gitlab Pipeline Status](https://img.shields.io/gitlab/pipeline-status/drop-oss%2Fdrop?gitlab_url=https%3A%2F%2Flab.deepcore.dev)](https://lab.deepcore.dev/drop-oss/drop/-/pipelines) -[![Discord](https://img.shields.io/discord/1291622805124812871?label=discord)](https://discord.gg/ZVGggfXN) +[![Discord](https://img.shields.io/discord/1291622805124812871?label=discord)](https://discord.gg/ACq4qZp4a9) [![Conventional Commits](https://img.shields.io/badge/Conventional%20Commits-1.0.0-%23FE5196?logo=conventionalcommits&logoColor=white)](https://conventionalcommits.org) # Drop From 793b57a163844532fc8d3f3aa3f6bf17f4079e7a Mon Sep 17 00:00:00 2001 From: quexeky <116044207+quexeky@users.noreply.github.com> Date: Sat, 25 Jan 2025 22:49:06 +1100 Subject: [PATCH 02/13] chore: Update changelog.md --- changelog.md | 192 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 192 insertions(+) diff --git a/changelog.md b/changelog.md index 73ad7c9..c362858 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,197 @@ +## Release 0.2.0-beta + +### Fixes +- fix recursive dirs util #02d6346 +- Fix username length requirement #0a5a649 +- remove dynamic imports #0f10626 +- fix for missing developers or publishers #25fc957 +- split prisma schemas #2859005 +- results are returned alphabetically #33d3770 +- update prisma schemas #36776cc +- removed global flag #43e32b4 +- properly disconnect websockets from task handler #5358f1f +- follow best practices #54c5d55 +- future lenience #5c78b20 +- fix width of token breaking things #61d88c3 +- fixed websocket authentication #62ea9a1 +- fix delta manifest generation #6df560c +- admin invitation w/ system user #8463e35 +- properly import icons #8945196 +- prisma create footprint #952ece8 +- game panel now always shows 3 lines exactly #9c2249e +- remove unnecessary import #a361c38 +- fix disconnect code #a8f2106 +- fix types #b511b40 +- add drop-base as git submodule #b75ebd1 +- Update README.md with discord link #c6bb21d +- fix expires requirement in the admin endpoint #c7b675f +- fix always being created as admin #c7eb11a +- moved icons and created PlatformClient so we can use the enum on the frontend #cada630 +- recurse submodules #db103de +- fix FATAL: "root"... message #dbb315a +- only show versions that are directories #ef8f3ae + + +### Features +- update prisma & delete games #089c3e0 +- manual handshake #12e3125 +- fetch game endpoint #1f4d075 +- under the hood organisation and consolidation #26a31f6 +- 'no images' slide on image carousel #28baabc +- improve feedback when metadata fails #2c19e13 +- introduction of 'system user' #2c21a23 +- change name, description and icon #2cfe75a +- 'manual' metadata provider #2f52a16 +- add disabled state #38fc6b8 +- overhauled version importing #39d7ce7 +- automatically create library folder if it doesn't exist #39fe9d5 +- smoother bar in admin task ui #4488ae2 +- add noWrapper option #4f9b949 +- add version metadata route #5393db3 +- completed admin UI, with minor changes to backend #599da0e +- adjust gradient #5a1f841 +- keep track of last connected #69e4c25 +- added notification system w/ interwoven refactoring #6e6f09d +- content length header for chunk downloads #76bceb1 +- add title to tab #7b0756c +- add button to open in admin panel #7b3b919 +- client capability framework + peer API configuration #7d72a86 +- customisable image carousel and new layout #937954f +- support more types #9b12d45 +- generate a server certificate for mtls APIs #9c4b6f3 +- new endpoints, ui and beginnings of main store page #9cbdcbc +- backend #a309651 +- more subtle design improvements #a815542 +- add aden's carousel pagination design #a86045c +- add header #a8a152e +- client side search #b50e27f +- new ws handler #bc0c47c +- user widget now redirects to actual page #bfafe02 +- require lowercase usernames #d7160ab +- more ui improvements #e408ac5 +- add modifying game descriptions #e505e58 +- mobile nav #e5cf13f +- slightly improved game page #e796b46 +- game carousel #ecc819e +- add enum dictionary type #f2e0182 +- improved ux #f3ed0f6 +- cleanup and raw accessors #f7d767d +- add support for overriding UMU id #fd4a7d1 +- add .sh for linux #fe9373a + + +### Other Changes +- quexeky +- fixed manifest generation #03a37f7 +- manual ci/cd #03b0b0c +- ability to fetch client certs for p2p #0a715fe +- disable tls in build #0f80fcd +- Updated README.md #17971e0 +- Merge pull request #18 from Drop-OSS/develop +- initial work on metadata system #196f87c +- more ui #1bd19ad +- remove log statements #1d5e1bd +- small fixes & SSR disabled #1f575b2 +- update information and setup guide #2236622 +- metadata engine #22ac7f6 +- Update CONTRIBUTING.md #2309407 +- slight bug fixes and clean up #24a0d11 +- almst complete admin ui and initial store designs #27070b6 +- handshakes #2b4382d +- user mobile header #2e44ef3 +- more consistent naming for globals #305de9f +- replaced markdown-it with micromark #31e8359 +- fixes to store page for mobile clients #328b9ba +- game version re-ordering #329c74d +- verbose yarn install #36568c3 +- patch for no version check in manifest generation #395219d +- migrate bcrypt to bcryptjs #3a51c9c +- added download chunk endpoint #3dd6062 +- Update README.md #425934d +- build only ci #4273a20 +- object storage + full permission system + testing #435551c +- rename admin socket session map #44c6028 +- bump droplet and add vue carousel #46551f9 +- version importing #46c8f0c +- back to yarn, with nuxt telemetry force disabled #46d35ad +- finished object endpoints #486bce8 +- update dependencies and add note about optional dependencies #4fa771a +- use configuration from docs for ci/cd #52315d0 +- slight fixes to register logic #583301f +- removed yarn.lock #584bcf1 +- Version bump #5f29c28 +- immutable application settings framework #5fe2036 +- fixed docker daemon location #62a111b +- copy autodevops configuration #6328c24 +- Delete .gitlab-ci.yml #69f341b +- admin ui shell #6b5e48d +- bump @drop/droplet version for windows developers #6ba5cdd +- Add LICENSE #6e2dc89 +- custom dind #716eac7 +- task API #718f5ba +- use gitlab ci variable declaration #7194d35 +- move icons into dedicated folder #74fa671 +- another stage of client authentication #7523e53 +- refactoring #7869043 +- moved windows logo into logos dir #789d3ba +- updated text colours across app #7a88f4c +- starting docs infra #7d2a1c6 +- more cleaning #7e17626 +- slight patch to rename query to be more consistent #7f4db0c +- move to raw docker #803752e +- server side and user client side completed for registration #848a611 +- beginnings of download implementation #8674ac7 +- more consistent naming for object handler #87230fb +- use autodevops build stage #886beb6 +- Updated tailwind config #88c95d6 +- change name of store file #8999303 +- split prisma schemas #9011cf5 +- client initiate #909432a +- more client routes to support Drop app update #91b7e10 +- additional polish and QoL features #93bc143 +- upload images to games #9b7ee4e +- migrate to pnpm due to ci/cd issues with yarn #9cb2d6d +- run yarn install in CI/CD non interactively #a208fbe +- completed game importing; partial work on version importing #a7c33e7 +- remove canvas from dependencies #a8f58eb +- fix registry authentication #ad25d3e +- consolidate type utils #adb4b73 +- Updated README.md #b0ef675 +- add proper carousel to store page #b2ab827 +- move to yarn v2 #b744671 +- remove client API deadweight #b9ae26c +- add expires field #be6c30d +- ca groundwork #bfafd2a +- cleanup & polish #c355f6f +- remove bcrypt (debug) #c3914cc +- non rounded bottom #c4391d3 +- failed gracefully on invalid chunk index #c4a3e4e +- update deploy template #c4a419f +- migrate to new droplet ca system #c4d8113 +- docker based deployment #c5d00b4 +- updated CONTRIBUTING.md #cd0d2bf +- update prisma version #ce0a9ab +- README update #ceacd84 +- patch metadata handler #cf578bd +- Added SECURITY.md #d3d93b0 +- finalised client APIs and authentication method #d4e2dc8 +- Update README.md #db916bf +- object storage interface + utility functions #de388a9 +- initial commit #e1a789f +- fixed task system #e1c1d7e +- Update file chunk.get.ts #e4339c3 +- ui groundwork #e52f072 +- Update changelog #eadcaa1 +- check for no version in manifest generation #eb3f9f9 +- break into single column store on lg devices #ecb381e +- better server side signin redirects #ef13b68 +- patch signin #f3672f8 + + +_changelog generated by_ [go-conventional-commits](https://github.com/joselitofilho/go-conventional-commits) + ## Release 0.1.0-beta ### Fixes From d2b485456ae58b48de8192f3b70489bece32fd1c Mon Sep 17 00:00:00 2001 From: Huskydog9988 <39809509+Huskydog9988@users.noreply.github.com> Date: Sun, 9 Mar 2025 21:24:26 -0400 Subject: [PATCH 03/13] gracefully disable metadata provider when api key is missing --- drop-base | 2 +- server/internal/metadata/giantbomb.ts | 4 ++-- server/internal/metadata/index.ts | 29 +++++++++++++++++++++------ 3 files changed, 26 insertions(+), 9 deletions(-) diff --git a/drop-base b/drop-base index ce59647..de0d1b4 160000 --- a/drop-base +++ b/drop-base @@ -1 +1 @@ -Subproject commit ce59647cdaf460fdb609ddcf1875b1be876fd73e +Subproject commit de0d1b46603a4140ad0ff2c149e4fd91277c2fff diff --git a/server/internal/metadata/giantbomb.ts b/server/internal/metadata/giantbomb.ts index 7fdcf95..dfcd2ad 100644 --- a/server/internal/metadata/giantbomb.ts +++ b/server/internal/metadata/giantbomb.ts @@ -1,5 +1,5 @@ import { Developer, MetadataSource, Publisher } from "@prisma/client"; -import { MetadataProvider } from "."; +import { MetadataProvider, MissingMetadataProviderApiKey } from "."; import { GameMetadataSearchResult, _FetchGameMetadataParams, @@ -80,7 +80,7 @@ export class GiantBombProvider implements MetadataProvider { constructor() { const apikey = process.env.GIANT_BOMB_API_KEY; - if (!apikey) throw new Error("No GIANT_BOMB_API_KEY in environment"); + if (!apikey) throw new MissingMetadataProviderApiKey("GiantBomb"); this.apikey = apikey; diff --git a/server/internal/metadata/index.ts b/server/internal/metadata/index.ts index 5fcdada..f97d83f 100644 --- a/server/internal/metadata/index.ts +++ b/server/internal/metadata/index.ts @@ -20,6 +20,19 @@ import { PriorityList, PriorityListIndexed } from "../utils/prioritylist"; import { GiantBombProvider } from "./giantbomb"; import { ManualMetadataProvider } from "./manual"; +export class MissingMetadataProviderApiKey extends Error { + private providerName: string; + + constructor(providerName: string) { + super(`Missing ${providerName} api key`); + this.providerName = providerName; + } + + getProviderName() { + return this.providerName; + } +} + export abstract class MetadataProvider { abstract id(): string; abstract name(): string; @@ -240,13 +253,17 @@ export default metadataHandler; export const enabledMedadataProviders: string[] = []; const metadataProviders = [GiantBombProvider, ManualMetadataProvider]; -for(const provider of metadataProviders){ +for (const provider of metadataProviders) { try { - const prov = new provider; + const prov = new provider(); metadataHandler.addProvider(prov); enabledMedadataProviders.push(prov.id()); - console.log(`enabled metadata provider: ${prov.name()}`) - }catch(e){ - console.error(`skipping metadata provider setup: ${e}`); + console.log(`enabled metadata provider: ${prov.name()}`); + } catch (e) { + if (e instanceof MissingMetadataProviderApiKey) { + console.warn(`Disabling ${e.getProviderName()} metadata provider`); + } else { + console.error(`skipping metadata provider setup: ${e}`); + } } -} \ No newline at end of file +} From 4b4e067fac67a3627e88ef33acfe81661385cf7b Mon Sep 17 00:00:00 2001 From: Huskydog9988 <39809509+Huskydog9988@users.noreply.github.com> Date: Mon, 10 Mar 2025 17:47:47 -0400 Subject: [PATCH 04/13] cleanup giantbomb provider --- server/internal/metadata/giantbomb.ts | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/server/internal/metadata/giantbomb.ts b/server/internal/metadata/giantbomb.ts index dfcd2ad..02de9d0 100644 --- a/server/internal/metadata/giantbomb.ts +++ b/server/internal/metadata/giantbomb.ts @@ -74,13 +74,14 @@ interface CompanySearchResult { }; } +// Api Docs: https://www.giantbomb.com/api/ export class GiantBombProvider implements MetadataProvider { private apikey: string; private turndown: TurndownService; constructor() { const apikey = process.env.GIANT_BOMB_API_KEY; - if (!apikey) throw new MissingMetadataProviderApiKey("GiantBomb"); + if (!apikey) throw new MissingMetadataProviderApiKey(this.name()); this.apikey = apikey; @@ -96,18 +97,14 @@ export class GiantBombProvider implements MetadataProvider { private async request( resource: string, url: string, - query: { [key: string]: string | Array }, + query: { [key: string]: string }, options?: AxiosRequestConfig ) { - const queryOptions = { ...query, api_key: this.apikey, format: "json" }; - const queryString = Object.entries(queryOptions) - .map(([key, value]) => { - if (Array.isArray(value)) { - return `${key}=${value.map(encodeURIComponent).join(",")}`; - } - return `${key}=${encodeURIComponent(value)}`; - }) - .join("&"); + const queryString = new URLSearchParams({ + ...query, + api_key: this.apikey, + format: "json", + }).toString(); const finalURL = `https://www.giantbomb.com/api/${resource}/${url}?${queryString}`; @@ -134,7 +131,7 @@ export class GiantBombProvider implements MetadataProvider { async search(query: string): Promise { const results = await this.request>("search", "", { query: query, - resources: ["game"], + resources: ["game"].join(","), }); const mapped = results.data.results.map((result) => { const date = From 2ca96c34f7aa925722fbaec53cead645b084bcc3 Mon Sep 17 00:00:00 2001 From: Huskydog9988 <39809509+Huskydog9988@users.noreply.github.com> Date: Tue, 11 Mar 2025 18:50:12 -0400 Subject: [PATCH 05/13] note that you need to init submodules --- README.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 33d1533..d23a0e2 100644 --- a/README.md +++ b/README.md @@ -64,11 +64,12 @@ Drop uses a utility package called droplet that's written in Rust. It has builts Steps: +1. Run `git submodule update --init --recursive` to setup submodules 1. Copy the `.env.example` to `.env` and add your GiantBomb metadata key (more metadata providers coming) -2. Create the `.data` directory with `mkdir .data` -3. Ensure that your user owns the `.data` directory with `sudo chown -R $(id -u $(whoami))` -4. Open up a terminal and navigate to `dev-tools`, and run `docker compose up` -5. Open up another terminal in the root directory of the project and run `yarn` and then `yarn dev` to start the dev server +1. Create the `.data` directory with `mkdir .data` +1. Ensure that your user owns the `.data` directory with `sudo chown -R $(id -u $(whoami))` +1. Open up a terminal and navigate to `dev-tools`, and run `docker compose up` +1. Open up another terminal in the root directory of the project and run `yarn` and then `yarn dev` to start the dev server As part of the first-time bootstrap, Drop creates an invitation with the fixed id of 'admin'. So, to create an admin account, go to: From 08164cae68010a9548a0e2aca642feb865295b7d Mon Sep 17 00:00:00 2001 From: Huskydog9988 <39809509+Huskydog9988@users.noreply.github.com> Date: Tue, 11 Mar 2025 19:39:12 -0400 Subject: [PATCH 06/13] add pcgamingwiki as metadata source --- prisma/schema/content.prisma | 2 + server/internal/metadata/index.ts | 7 +- server/internal/metadata/pcgamingwiki.ts | 272 +++++++++++++++++++++++ 3 files changed, 280 insertions(+), 1 deletion(-) create mode 100644 server/internal/metadata/pcgamingwiki.ts diff --git a/prisma/schema/content.prisma b/prisma/schema/content.prisma index f64fdac..386a3b0 100644 --- a/prisma/schema/content.prisma +++ b/prisma/schema/content.prisma @@ -1,6 +1,8 @@ enum MetadataSource { Manual GiantBomb + PCGamingWiki + IGDB } model Game { diff --git a/server/internal/metadata/index.ts b/server/internal/metadata/index.ts index f97d83f..4c18a27 100644 --- a/server/internal/metadata/index.ts +++ b/server/internal/metadata/index.ts @@ -19,6 +19,7 @@ import { ObjectTransactionalHandler } from "../objects/transactional"; import { PriorityList, PriorityListIndexed } from "../utils/prioritylist"; import { GiantBombProvider } from "./giantbomb"; import { ManualMetadataProvider } from "./manual"; +import { PCGamingWikiProvider } from "./pcgamingwiki"; export class MissingMetadataProviderApiKey extends Error { private providerName: string; @@ -251,7 +252,11 @@ export const metadataHandler = new MetadataHandler(); export default metadataHandler; export const enabledMedadataProviders: string[] = []; -const metadataProviders = [GiantBombProvider, ManualMetadataProvider]; +const metadataProviders = [ + GiantBombProvider, + ManualMetadataProvider, + PCGamingWikiProvider, +]; for (const provider of metadataProviders) { try { diff --git a/server/internal/metadata/pcgamingwiki.ts b/server/internal/metadata/pcgamingwiki.ts new file mode 100644 index 0000000..be92c03 --- /dev/null +++ b/server/internal/metadata/pcgamingwiki.ts @@ -0,0 +1,272 @@ +import { Developer, MetadataSource, Publisher } from "@prisma/client"; +import { MetadataProvider, MissingMetadataProviderApiKey } from "."; +import { + GameMetadataSearchResult, + _FetchGameMetadataParams, + GameMetadata, + _FetchPublisherMetadataParams, + PublisherMetadata, + _FetchDeveloperMetadataParams, + DeveloperMetadata, +} from "./types"; +import axios, { AxiosRequestConfig } from "axios"; +import moment from "moment"; +import * as jdenticon from "jdenticon"; + +interface PCGamingWikiPage { + PageID: string; + PageName: string; +} + +interface PCGamingWikiSearchStub extends PCGamingWikiPage { + "Cover URL"?: string; + Released?: string; + Released__precision?: string; +} + +interface PCGamingWikiGame extends PCGamingWikiSearchStub { + Developers?: string; + Genres?: string; + Publishers?: string; + Themes?: string; + Series?: string; + Modes?: string; +} + +interface PCGamingWikiCompany extends PCGamingWikiPage { + Parent?: string; + Founded?: string; + Website?: string; + Founded__precision?: string; + Defunct__precision?: string; +} + +interface PCGamingWikiCargoResult { + cargoquery: [ + { + title: T; + } + ]; + error?: { + code?: string; + info?: string; + errorclass?: string; + "*"?: string; + }; +} + +// Api Docs: https://www.pcgamingwiki.com/wiki/PCGamingWiki:API +// Good tool for helping build cargo queries: https://www.pcgamingwiki.com/wiki/Special:CargoQuery +export class PCGamingWikiProvider implements MetadataProvider { + constructor() {} + + id() { + return "pcgamingwiki"; + } + name() { + return "PCGamingWiki"; + } + source() { + return MetadataSource.PCGamingWiki; + } + + private async request( + query: URLSearchParams, + options?: AxiosRequestConfig + ) { + const finalURL = `https://www.pcgamingwiki.com/w/api.php?${query.toString()}`; + + const overlay: AxiosRequestConfig = { + url: finalURL, + baseURL: "", + }; + const response = await axios.request>( + Object.assign({}, options, overlay) + ); + + if (response.status !== 200) + throw new Error( + `Error in pcgamingwiki \nStatus Code: ${response.status}` + ); + else if (response.data.error !== undefined) + throw new Error(`Error in pcgamingwiki, malformed query`); + + return response; + } + + private markNullUndefined>(obj: T): T { + // TODO: fix types above and make everything nullable instead of possibly undefined, better then this, maybe? + + const result = structuredClone(obj); + for (const [key, value] of Object.entries(result)) { + if (value === null) delete result[key]; + } + return result; + } + + async search(query: string) { + const searchParams = new URLSearchParams({ + action: "cargoquery", + tables: "Infobox_game", + fields: + "Infobox_game._pageID=PageID,Infobox_game._pageName=PageName,Infobox_game.Cover_URL,Infobox_game.Released", + where: `Infobox_game._pageName="${query}"`, + format: "json", + }); + + const res = await this.request(searchParams); + + const mapped = res.data.cargoquery.map((result) => { + const game = this.markNullUndefined(result.title); + + const metadata: GameMetadataSearchResult = { + id: game.PageID, + name: game.PageName, + icon: game["Cover URL"] ?? "", + description: "", // TODO: need to render the `Introduction` template somehow (or we could just hardcode it) + year: + game.Released !== undefined && game.Released.length > 0 + ? moment(game.Released).year() + : 0, + }; + return metadata; + }); + + return mapped; + } + + /** + * Parses the specific format that the wiki returns when specifying a company + * @param companyStr + * @returns + */ + private parseCompanyStr(companyStr: string): string[] { + const results: string[] = []; + // provides the string as a list of companies + // ie: "Company:Digerati Distribution,Company:Greylock Studio" + const items = companyStr.split(","); + + items.forEach((item) => { + // remove the `Company:` and trim and whitespace + results.push(item.replace("Company:", "").trim()); + }); + + return results; + } + + async fetchGame({ + id, + name, + publisher, + developer, + createObject, + }: _FetchGameMetadataParams): Promise { + const searchParams = new URLSearchParams({ + action: "cargoquery", + tables: "Infobox_game", + fields: + "Infobox_game._pageID=PageID,Infobox_game._pageName=PageName,Infobox_game.Cover_URL,Infobox_game.Developers,Infobox_game.Released,Infobox_game.Genres,Infobox_game.Publishers,Infobox_game.Themes,Infobox_game.Series,Infobox_game.Modes", + where: `Infobox_game._pageID="${id}"`, + format: "json", + }); + + const res = await this.request(searchParams); + if (res.data.cargoquery.length < 1) + throw new Error("Error in pcgamingwiki, no game"); + + const game = res.data.cargoquery[0].title; + + const publishers: Publisher[] = []; + if (game.Publishers !== undefined) { + const pubListClean = this.parseCompanyStr(game.Publishers); + for (const pub of pubListClean) { + console.log("Found publisher: ", pub); + publishers.push(await publisher(pub)); + } + } + + const developers: Developer[] = []; + if (game.Developers !== undefined) { + const devListClean = this.parseCompanyStr(game.Developers); + for (const dev of devListClean) { + console.log("Found dev: ", dev); + developers.push(await developer(dev.replace("Company:", ""))); + } + } + + const icon = + game["Cover URL"] !== undefined + ? createObject(game["Cover URL"]) + : createObject(jdenticon.toPng(name, 512)); + + const metadata: GameMetadata = { + id: game.PageID, + name: game.PageName, + shortDescription: "", // TODO: (again) need to render the `Introduction` template somehow (or we could just hardcode it) + description: "", + released: + game.Released !== undefined + ? moment(game.Released).toDate() + : new Date(), + + reviewCount: 0, + reviewRating: 0, + + publishers, + developers, + + icon: icon, + bannerId: icon, + coverId: icon, + images: [icon], + }; + + return metadata; + } + + async fetchPublisher({ + query, + createObject, + }: _FetchPublisherMetadataParams): Promise { + const searchParams = new URLSearchParams({ + action: "cargoquery", + tables: "Company", + fields: + "Company.Parent,Company.Founded,Company.Defunct,Company.Website,Company._pageName=PageName,Company._pageID=pageID", + where: `Company._pageName="Company:${query}"`, + format: "json", + }); + + console.log("Searching for: " + query); + const res = await this.request(searchParams); + + // TODO: replace + const icon = createObject(jdenticon.toPng(query, 512)); + + console.log("Found: ", res.data.cargoquery); + + for (let i = 0; i < res.data.cargoquery.length; i++) { + const company = this.markNullUndefined(res.data.cargoquery[i].title); + + const metadata: PublisherMetadata = { + id: company.PageID, + name: company.PageName, + shortDescription: "", + description: "", + website: company?.Website ?? "", + + logo: icon, + banner: icon, + }; + return metadata; + } + + throw new Error("Error in pcgamingwiki, no publisher"); + } + + async fetchDeveloper( + params: _FetchDeveloperMetadataParams + ): Promise { + return await this.fetchPublisher(params); + } +} From 2b7bc6965d2f2f641c01401a328ef851d156e691 Mon Sep 17 00:00:00 2001 From: Huskydog9988 <39809509+Huskydog9988@users.noreply.github.com> Date: Tue, 11 Mar 2025 20:03:28 -0400 Subject: [PATCH 07/13] add content security policy to allow showing remote images for game importing --- nuxt.config.ts | 15 +- package.json | 1 + yarn.lock | 479 ++++++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 485 insertions(+), 10 deletions(-) diff --git a/nuxt.config.ts b/nuxt.config.ts index 26547a8..c9f3eeb 100644 --- a/nuxt.config.ts +++ b/nuxt.config.ts @@ -35,7 +35,7 @@ export default defineNuxtConfig({ extends: ["./drop-base"], // Module config from here down - modules: ["@nuxt/content", "vue3-carousel-nuxt"], + modules: ["@nuxt/content", "vue3-carousel-nuxt", "nuxt-security"], carousel: { prefix: "Vue", @@ -56,4 +56,17 @@ export default defineNuxtConfig({ }, }, }, + + security: { + headers: { + contentSecurityPolicy: { + "img-src": [ + "'self'", + "data:", + "https://www.giantbomb.com", + "https://images.pcgamingwiki.com", + ], + }, + }, + }, }); diff --git a/package.json b/package.json index 958cbcc..a82e499 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "micromark": "^4.0.1", "moment": "^2.30.1", "nuxt": "^3.13.2", + "nuxt-security": "2.2.0", "prisma": "^6.1.0", "sanitize-filename": "^1.6.3", "stream": "^0.0.3", diff --git a/yarn.lock b/yarn.lock index c5ac808..5309bbd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -20,7 +20,7 @@ resolved "https://registry.yarnpkg.com/@antfu/utils/-/utils-0.7.10.tgz#ae829f170158e297a9b6a28f161a8e487d00814d" integrity sha512-+562v9k4aI80m1+VuMHehNJWLOFjBnXn3tdOitzD0il5b7smkSBal4+a3oKiQTbrwMmN/TBUMDvbdoWDehgOww== -"@babel/code-frame@^7.12.13", "@babel/code-frame@^7.22.13", "@babel/code-frame@^7.24.7", "@babel/code-frame@^7.25.9", "@babel/code-frame@^7.26.0": +"@babel/code-frame@^7.12.13", "@babel/code-frame@^7.22.13", "@babel/code-frame@^7.24.7", "@babel/code-frame@^7.25.9", "@babel/code-frame@^7.26.0", "@babel/code-frame@^7.26.2": version "7.26.2" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.26.2.tgz#4b5fab97d33338eff916235055f0ebc21e573a85" integrity sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ== @@ -34,6 +34,11 @@ resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.26.2.tgz#278b6b13664557de95b8f35b90d96785850bb56e" integrity sha512-Z0WgzSEa+aUcdiJuCIqgujCshpMWgUpgOxXotrYPSA53hA3qopNaqcJpyr0hVb1FeWdnqFA35/fUtXgBK8srQg== +"@babel/compat-data@^7.26.5": + version "7.26.8" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.26.8.tgz#821c1d35641c355284d4a870b8a4a7b0c141e367" + integrity sha512-oH5UPLMWR3L2wEFLnFJ1TZXqHufiTKAiLfqw5zkhS4dKXLJ10yVztfil/twG8EDTA4F/tvVNw9nOl4ZMslB8rQ== + "@babel/core@^7.23.0", "@babel/core@^7.24.7", "@babel/core@^7.25.7": version "7.26.0" resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.26.0.tgz#d78b6023cc8f3114ccf049eb219613f74a747b40" @@ -55,6 +60,38 @@ json5 "^2.2.3" semver "^6.3.1" +"@babel/core@^7.25.2": + version "7.26.10" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.26.10.tgz#5c876f83c8c4dcb233ee4b670c0606f2ac3000f9" + integrity sha512-vMqyb7XCDMPvJFFOaT9kxtiRh42GwlZEg1/uIgtZshS5a/8OaduUfCi7kynKgc3Tw/6Uo2D+db9qBttghhmxwQ== + dependencies: + "@ampproject/remapping" "^2.2.0" + "@babel/code-frame" "^7.26.2" + "@babel/generator" "^7.26.10" + "@babel/helper-compilation-targets" "^7.26.5" + "@babel/helper-module-transforms" "^7.26.0" + "@babel/helpers" "^7.26.10" + "@babel/parser" "^7.26.10" + "@babel/template" "^7.26.9" + "@babel/traverse" "^7.26.10" + "@babel/types" "^7.26.10" + convert-source-map "^2.0.0" + debug "^4.1.0" + gensync "^1.0.0-beta.2" + json5 "^2.2.3" + semver "^6.3.1" + +"@babel/generator@^7.25.0", "@babel/generator@^7.26.10": + version "7.26.10" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.26.10.tgz#a60d9de49caca16744e6340c3658dfef6138c3f7" + integrity sha512-rRHT8siFIXQrAYOYqZQVsAr8vJ+cBNqcVAY6m5V8/4QqzaPl+zDBe6cLEPRDuNOUf3ww8RfJVlOyQMoSI+5Ang== + dependencies: + "@babel/parser" "^7.26.10" + "@babel/types" "^7.26.10" + "@jridgewell/gen-mapping" "^0.3.5" + "@jridgewell/trace-mapping" "^0.3.25" + jsesc "^3.0.2" + "@babel/generator@^7.25.9", "@babel/generator@^7.26.0": version "7.26.2" resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.26.2.tgz#87b75813bec87916210e5e01939a4c823d6bb74f" @@ -84,6 +121,17 @@ lru-cache "^5.1.1" semver "^6.3.1" +"@babel/helper-compilation-targets@^7.26.5": + version "7.26.5" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.26.5.tgz#75d92bb8d8d51301c0d49e52a65c9a7fe94514d8" + integrity sha512-IXuyn5EkouFJscIDuFF5EsiSolseme1s0CZB+QxVugqJLYmKdxI1VfIBOst0SUu4rnk2Z7kqTwmoO1lp3HIfnA== + dependencies: + "@babel/compat-data" "^7.26.5" + "@babel/helper-validator-option" "^7.25.9" + browserslist "^4.24.0" + lru-cache "^5.1.1" + semver "^6.3.1" + "@babel/helper-create-class-features-plugin@^7.25.9": version "7.25.9" resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.25.9.tgz#7644147706bb90ff613297d49ed5266bde729f83" @@ -174,6 +222,14 @@ "@babel/template" "^7.25.9" "@babel/types" "^7.26.0" +"@babel/helpers@^7.26.10": + version "7.26.10" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.26.10.tgz#6baea3cd62ec2d0c1068778d63cb1314f6637384" + integrity sha512-UPYc3SauzZ3JGgj87GgZ89JVdC5dj0AoetR5Bw6wj4niittNyFh6+eOGonYvJ1ao6B8lEa3Q3klS7ADZ53bc5g== + dependencies: + "@babel/template" "^7.26.9" + "@babel/types" "^7.26.10" + "@babel/parser@^7.25.3", "@babel/parser@^7.25.4", "@babel/parser@^7.25.6", "@babel/parser@^7.25.8", "@babel/parser@^7.25.9", "@babel/parser@^7.26.0", "@babel/parser@^7.26.2": version "7.26.2" resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.26.2.tgz#fd7b6f487cfea09889557ef5d4eeb9ff9a5abd11" @@ -181,6 +237,13 @@ dependencies: "@babel/types" "^7.26.0" +"@babel/parser@^7.26.10", "@babel/parser@^7.26.9": + version "7.26.10" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.26.10.tgz#e9bdb82f14b97df6569b0b038edd436839c57749" + integrity sha512-6aQR2zGE/QFi8JpDLjUZEPYOs7+mhKXm86VaKFiLP35JQwQb6bwUE+XbvkH0EptsYhbNBSUGaUBLKqxH1xSgsA== + dependencies: + "@babel/types" "^7.26.10" + "@babel/plugin-proposal-decorators@^7.23.0": version "7.25.9" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.25.9.tgz#8680707f943d1a3da2cd66b948179920f097e254" @@ -250,6 +313,28 @@ "@babel/parser" "^7.25.9" "@babel/types" "^7.25.9" +"@babel/template@^7.26.9": + version "7.26.9" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.26.9.tgz#4577ad3ddf43d194528cff4e1fa6b232fa609bb2" + integrity sha512-qyRplbeIpNZhmzOysF/wFMuP9sctmh2cFzRAZOn1YapxBsE1i9bJIY586R/WBLfLcmcBlM8ROBiQURnnNy+zfA== + dependencies: + "@babel/code-frame" "^7.26.2" + "@babel/parser" "^7.26.9" + "@babel/types" "^7.26.9" + +"@babel/traverse@^7.25.3", "@babel/traverse@^7.26.10": + version "7.26.10" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.26.10.tgz#43cca33d76005dbaa93024fae536cc1946a4c380" + integrity sha512-k8NuDrxr0WrPH5Aupqb2LCVURP/S0vBEn5mK6iH+GIYob66U5EtoZvcdudR2jQ4cmTwhEwW1DLB+Yyas9zjF6A== + dependencies: + "@babel/code-frame" "^7.26.2" + "@babel/generator" "^7.26.10" + "@babel/parser" "^7.26.10" + "@babel/template" "^7.26.9" + "@babel/types" "^7.26.10" + debug "^4.3.1" + globals "^11.1.0" + "@babel/traverse@^7.25.6", "@babel/traverse@^7.25.9": version "7.25.9" resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.25.9.tgz#a50f8fe49e7f69f53de5bea7e413cd35c5e13c84" @@ -271,6 +356,14 @@ "@babel/helper-string-parser" "^7.25.9" "@babel/helper-validator-identifier" "^7.25.9" +"@babel/types@^7.26.10", "@babel/types@^7.26.9": + version "7.26.10" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.26.10.tgz#396382f6335bd4feb65741eacfc808218f859259" + integrity sha512-emqcG3vHrpxUKTrxcblR36dcrcoRDvKmnL/dCL6ZsHaShW80qxCAcNhzQZrpeM765VzEos+xOi4s+r4IXzTwdQ== + dependencies: + "@babel/helper-string-parser" "^7.25.9" + "@babel/helper-validator-identifier" "^7.25.9" + "@cloudflare/kv-asset-handler@^0.3.4": version "0.3.4" resolved "https://registry.yarnpkg.com/@cloudflare/kv-asset-handler/-/kv-asset-handler-0.3.4.tgz#5cc152847c8ae4d280ec5d7f4f6ba8c976b585c3" @@ -836,6 +929,34 @@ unimport "^3.13.1" untyped "^1.5.1" +"@nuxt/kit@^3.11.2": + version "3.16.0" + resolved "https://registry.yarnpkg.com/@nuxt/kit/-/kit-3.16.0.tgz#ce78cfd7aac9d43596d36a078e52b4ba610a7c0a" + integrity sha512-yPfhk58BG6wJhELkGOTCOlkMDbZkizk3IaINcyTKm+hBKiK3SheLt7S9HStNL+qZSfH2Cf7A8sYp6M72lOIEtA== + dependencies: + c12 "^3.0.2" + consola "^3.4.0" + defu "^6.1.4" + destr "^2.0.3" + errx "^0.1.0" + exsolve "^1.0.2" + globby "^14.1.0" + ignore "^7.0.3" + jiti "^2.4.2" + klona "^2.0.6" + knitwork "^1.2.0" + mlly "^1.7.4" + ohash "^2.0.11" + pathe "^2.0.3" + pkg-types "^2.1.0" + scule "^1.3.0" + semver "^7.7.1" + std-env "^3.8.1" + ufo "^1.5.4" + unctx "^2.4.1" + unimport "^4.1.2" + untyped "^2.0.0" + "@nuxt/schema@3.14.0", "@nuxt/schema@^3.13.2": version "3.14.0" resolved "https://registry.yarnpkg.com/@nuxt/schema/-/schema-3.14.0.tgz#b98223ecf5d5469b126b056842b317ef6105a5c1" @@ -2128,6 +2249,13 @@ base64-js@^1.3.1: resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== +basic-auth@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/basic-auth/-/basic-auth-2.0.1.tgz#b998279bf47ce38344b4f3cf916d4679bbf51e3a" + integrity sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg== + dependencies: + safe-buffer "5.1.2" + bcryptjs@^2.4.3: version "2.4.3" resolved "https://registry.yarnpkg.com/bcryptjs/-/bcryptjs-2.4.3.tgz#9ab5627b93e60621ff7cdac5da9733027df1d0cb" @@ -2230,6 +2358,24 @@ c12@2.0.1, c12@^2.0.1: pkg-types "^1.2.0" rc9 "^2.1.2" +c12@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/c12/-/c12-3.0.2.tgz#5ceba55cf081ff4cf95b22c593c80b34cf1f3d2e" + integrity sha512-6Tzk1/TNeI3WBPpK0j/Ss4+gPj3PUJYbWl/MWDJBThFvwNGNkXtd7Cz8BJtD4aRwoGHtzQD0SnxamgUiBH0/Nw== + dependencies: + chokidar "^4.0.3" + confbox "^0.1.8" + defu "^6.1.4" + dotenv "^16.4.7" + exsolve "^1.0.0" + giget "^2.0.0" + jiti "^2.4.2" + ohash "^2.0.5" + pathe "^2.0.3" + perfect-debounce "^1.0.0" + pkg-types "^2.0.0" + rc9 "^2.1.2" + cac@^6.7.14: version "6.7.14" resolved "https://registry.yarnpkg.com/cac/-/cac-6.7.14.tgz#804e1e6f506ee363cb0e3ccbb09cad5dd9870959" @@ -2335,6 +2481,13 @@ chokidar@^4.0.0, chokidar@^4.0.1: dependencies: readdirp "^4.0.1" +chokidar@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-4.0.3.tgz#7be37a4c03c9aee1ecfe862a4a23b2c70c205d30" + integrity sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA== + dependencies: + readdirp "^4.0.1" + chownr@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece" @@ -2424,7 +2577,7 @@ comma-separated-tokens@^2.0.0: resolved "https://registry.yarnpkg.com/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz#4e89c9458acb61bc8fef19f4529973b2392839ee" integrity sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg== -commander@^2.20.0: +commander@^2.20.0, commander@^2.20.3: version "2.20.3" resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== @@ -2485,11 +2638,21 @@ confbox@^0.1.7, confbox@^0.1.8: resolved "https://registry.yarnpkg.com/confbox/-/confbox-0.1.8.tgz#820d73d3b3c82d9bd910652c5d4d599ef8ff8b06" integrity sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w== +confbox@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/confbox/-/confbox-0.2.1.tgz#ae39f2c99699afa451d00206479f15f9a1208a8b" + integrity sha512-hkT3yDPFbs95mNCy1+7qNKC6Pro+/ibzYxtM2iqEigpf0sVw+bg4Zh9/snjsBcf990vfIsg5+1U7VyiyBb3etg== + consola@^3.2.3: version "3.2.3" resolved "https://registry.yarnpkg.com/consola/-/consola-3.2.3.tgz#0741857aa88cfa0d6fd53f1cff0375136e98502f" integrity sha512-I5qxpzLv+sJhTVEoLYNcTW+bThDCPsit0vLNKShZx6rLtpilNpmmeTPaeqJb9ZE9dV3DGaeby6Vuhrw38WjeyQ== +consola@^3.4.0: + version "3.4.0" + resolved "https://registry.yarnpkg.com/consola/-/consola-3.4.0.tgz#4cfc9348fd85ed16a17940b3032765e31061ab88" + integrity sha512-EiPU8G6dQG0GFHNR8ljnZFki/8a+cQwEQ+7wpxdChl02Q8HXlwEZWD5lqAF8vC2sEC3Tehr8hy7vErz88LHyUA== + console-control-strings@^1.0.0, console-control-strings@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" @@ -2623,6 +2786,11 @@ cssesc@^3.0.0: resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee" integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg== +cssfilter@0.0.10: + version "0.0.10" + resolved "https://registry.yarnpkg.com/cssfilter/-/cssfilter-0.0.10.tgz#c6d2672632a2e5c83e013e6864a42ce8defd20ae" + integrity sha512-FAaLDaplstoRsDR8XGYH51znUN0UY7nMc6Z9/fvE8EXGwvJE9hu7W2vHwx1+bd6gCYnln9nLbzxFTrcO9YQDZw== + cssnano-preset-default@^7.0.6: version "7.0.6" resolved "https://registry.yarnpkg.com/cssnano-preset-default/-/cssnano-preset-default-7.0.6.tgz#0220fa7507478369aa2a226bac03e1204cd024c1" @@ -2757,7 +2925,7 @@ define-lazy-prop@^3.0.0: resolved "https://registry.yarnpkg.com/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz#dbb19adfb746d7fc6d734a06b72f4a00d021255f" integrity sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg== -defu@^6.1.4: +defu@^6.1.1, defu@^6.1.4: version "6.1.4" resolved "https://registry.yarnpkg.com/defu/-/defu-6.1.4.tgz#4e0c9cf9ff68fe5f3d7f2765cc1a012dfdcb0479" integrity sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg== @@ -2886,6 +3054,11 @@ dotenv@^16.4.5: resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.4.5.tgz#cdd3b3b604cb327e286b4762e13502f717cb099f" integrity sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg== +dotenv@^16.4.7: + version "16.4.7" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.4.7.tgz#0e20c5b82950140aa99be360a8a5f52335f53c26" + integrity sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ== + duplexer@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.2.tgz#3abe43aef3835f8ae077d136ddce0f276b0400e6" @@ -3114,6 +3287,11 @@ execa@^8.0.1: signal-exit "^4.1.0" strip-final-newline "^3.0.0" +exsolve@^1.0.0, exsolve@^1.0.1, exsolve@^1.0.2: + version "1.0.4" + resolved "https://registry.yarnpkg.com/exsolve/-/exsolve-1.0.4.tgz#7de5c75af82ecd15998328fbf5f2295883be3a39" + integrity sha512-xsZH6PXaER4XoV+NiT7JHp1bJodJVT+cxeSH1G0f0tlT0lJqYuHUP3bUx2HtfTDvOagMINYp8rsqusxud3RXhw== + extend@^3.0.0: version "3.0.2" resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" @@ -3157,6 +3335,17 @@ fast-glob@^3.2.7, fast-glob@^3.3.2: merge2 "^1.3.0" micromatch "^4.0.4" +fast-glob@^3.3.3: + version "3.3.3" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.3.tgz#d06d585ce8dba90a16b0505c543c3ccfb3aeb818" + integrity sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg== + dependencies: + "@nodelib/fs.stat" "^2.0.2" + "@nodelib/fs.walk" "^1.2.3" + glob-parent "^5.1.2" + merge2 "^1.3.0" + micromatch "^4.0.8" + fast-npm-meta@^0.2.2: version "0.2.2" resolved "https://registry.yarnpkg.com/fast-npm-meta/-/fast-npm-meta-0.2.2.tgz#619e4ab6b71f4ce19d9fad48bba6ffa8164b7361" @@ -3174,6 +3363,11 @@ fdir@^6.2.0, fdir@^6.4.2: resolved "https://registry.yarnpkg.com/fdir/-/fdir-6.4.2.tgz#ddaa7ce1831b161bc3657bb99cb36e1622702689" integrity sha512-KnhMXsKSPZlAhp7+IjUkRZKPb4fUyccpDrdFXbi4QL1qkmFh9kVY09Yox+n4MaOb3lHZ1Tv829C3oaaXoMYPDQ== +fdir@^6.4.3: + version "6.4.3" + resolved "https://registry.yarnpkg.com/fdir/-/fdir-6.4.3.tgz#011cdacf837eca9b811c89dbb902df714273db72" + integrity sha512-PMXmW2y1hDDfTSRc9gaXIuCCRpuoz3Kaz8cUelp3smouvfT632ozg2vrT6lJsHKKOF59YLbOGfAWGUcKEfRMQw== + file-type-mime@^0.4.3: version "0.4.5" resolved "https://registry.yarnpkg.com/file-type-mime/-/file-type-mime-0.4.5.tgz#acfbcfe088ed07eb8b6b3eefc16a419a80d57547" @@ -3337,6 +3531,18 @@ giget@^1.2.3: pathe "^1.1.2" tar "^6.2.0" +giget@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/giget/-/giget-2.0.0.tgz#395fc934a43f9a7a29a29d55b99f23e30c14f195" + integrity sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA== + dependencies: + citty "^0.1.6" + consola "^3.4.0" + defu "^6.1.4" + node-fetch-native "^1.6.6" + nypm "^0.6.0" + pathe "^2.0.3" + git-config-path@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/git-config-path/-/git-config-path-2.0.0.tgz#62633d61af63af4405a5024efd325762f58a181b" @@ -3424,6 +3630,18 @@ globby@^14.0.2: slash "^5.1.0" unicorn-magic "^0.1.0" +globby@^14.1.0: + version "14.1.0" + resolved "https://registry.yarnpkg.com/globby/-/globby-14.1.0.tgz#138b78e77cf5a8d794e327b15dce80bf1fb0a73e" + integrity sha512-0Ia46fDOaT7k4og1PDW4YbodWWr3scS2vAr2lTbsplOt2WkKp0vQbkI9wKis/T5LV/dqPjO3bpS/z6GTJB82LA== + dependencies: + "@sindresorhus/merge-streams" "^2.1.0" + fast-glob "^3.3.3" + ignore "^7.0.3" + path-type "^6.0.0" + slash "^5.1.0" + unicorn-magic "^0.3.0" + graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.4, graceful-fs@^4.2.9: version "4.2.11" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" @@ -3709,6 +3927,11 @@ ignore@^6.0.2: resolved "https://registry.yarnpkg.com/ignore/-/ignore-6.0.2.tgz#77cccb72a55796af1b6d2f9eb14fa326d24f4283" integrity sha512-InwqeHHN2XpumIkMvpl/DCJVrAHgCsG5+cn1XlnLWGwtZBm8QJfSusItfrwx81CTp5agNZqpKU2J/ccC5nGT4A== +ignore@^7.0.3: + version "7.0.3" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-7.0.3.tgz#397ef9315dfe0595671eefe8b633fec6943ab733" + integrity sha512-bAH5jbK/F3T3Jls4I0SO1hmPR0dKU0a7+SY6n1yzRtG54FLO8d6w/nxLFX2Nb7dBu6cCWXPaAME6cYqFUMmuCA== + image-meta@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/image-meta/-/image-meta-0.2.1.tgz#3a9eb9f0bfd2f767ca2b0720623c2e03742aa29f" @@ -4005,6 +4228,11 @@ js-tokens@^9.0.0: resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-9.0.0.tgz#0f893996d6f3ed46df7f0a3b12a03f5fd84223c1" integrity sha512-WriZw1luRMlmV3LGJaR6QOJjWwgLUTf89OwT2lUOyjX2dJGBwgmIkbcz+7WFZjrZM635JOIR517++e/67CP9dQ== +js-tokens@^9.0.1: + version "9.0.1" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-9.0.1.tgz#2ec43964658435296f6761b34e10671c2d9527f4" + integrity sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ== + js-yaml@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" @@ -4058,6 +4286,11 @@ knitwork@^1.0.0, knitwork@^1.1.0: resolved "https://registry.yarnpkg.com/knitwork/-/knitwork-1.1.0.tgz#d8c9feafadd7ee744ff64340b216a52c7199c417" integrity sha512-oHnmiBUVHz1V+URE77PNot2lv3QiYU2zQf1JjOVkMt3YDKGbu8NAFr+c4mcNOhdsGrB/VpVbRwPwhiXrPhxQbw== +knitwork@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/knitwork/-/knitwork-1.2.0.tgz#3cc92e76249aeb35449cfbed3f31c6df8444db3f" + integrity sha512-xYSH7AvuQ6nXkq42x0v5S8/Iry+cfulBz/DJQzhIyESdLD7425jXsPy4vn5cCXU+HhRN2kVw51Vd1K6/By4BQg== + koa-compose@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/koa-compose/-/koa-compose-4.1.0.tgz#507306b9371901db41121c812e923d0d67d3e877" @@ -4252,6 +4485,15 @@ local-pkg@^0.5.0: mlly "^1.4.2" pkg-types "^1.0.3" +local-pkg@^1.0.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/local-pkg/-/local-pkg-1.1.1.tgz#f5fe74a97a3bd3c165788ee08ca9fbe998dc58dd" + integrity sha512-WunYko2W1NcdfAFpuLUoucsgULmgDBRkdxHxWQ7mK0cQqwPiy8E1enjuRBrhLtZkB5iScJ1XIPdhVEFK8aOLSg== + dependencies: + mlly "^1.7.4" + pkg-types "^2.0.1" + quansync "^0.2.8" + lodash.castarray@^4.4.0: version "4.4.0" resolved "https://registry.yarnpkg.com/lodash.castarray/-/lodash.castarray-4.4.0.tgz#c02513515e309daddd4c24c60cfddcf5976d9115" @@ -4328,6 +4570,13 @@ magic-string@^0.30.0, magic-string@^0.30.10, magic-string@^0.30.11, magic-string dependencies: "@jridgewell/sourcemap-codec" "^1.5.0" +magic-string@^0.30.17: + version "0.30.17" + resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.17.tgz#450a449673d2460e5bbcfba9a61916a1714c7453" + integrity sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA== + dependencies: + "@jridgewell/sourcemap-codec" "^1.5.0" + magicast@^0.3.5: version "0.3.5" resolved "https://registry.yarnpkg.com/magicast/-/magicast-0.3.5.tgz#8301c3c7d66704a0771eb1bad74274f0ec036739" @@ -4946,6 +5195,16 @@ mlly@^1.3.0, mlly@^1.4.2, mlly@^1.6.1, mlly@^1.7.1, mlly@^1.7.2: pkg-types "^1.2.0" ufo "^1.5.4" +mlly@^1.7.4: + version "1.7.4" + resolved "https://registry.yarnpkg.com/mlly/-/mlly-1.7.4.tgz#3d7295ea2358ec7a271eaa5d000a0f84febe100f" + integrity sha512-qmdSIPC4bDJXgZTCR7XosJiNKySV7O215tsPtDN9iEO/7q/76b/ijtgRu/+epFXSJhijtTCCGp3DWS549P3xKw== + dependencies: + acorn "^8.14.0" + pathe "^2.0.1" + pkg-types "^1.3.0" + ufo "^1.5.4" + moment@^2.30.1: version "2.30.1" resolved "https://registry.yarnpkg.com/moment/-/moment-2.30.1.tgz#f8c91c07b7a786e30c59926df530b4eac96974ae" @@ -5099,6 +5358,11 @@ node-fetch-native@^1.6.3, node-fetch-native@^1.6.4: resolved "https://registry.yarnpkg.com/node-fetch-native/-/node-fetch-native-1.6.4.tgz#679fc8fd8111266d47d7e72c379f1bed9acff06e" integrity sha512-IhOigYzAKHd244OC0JIMIUrjzctirCmPkaIfhDeGcEETWof5zKYUW7e7MYvChGWh/4CJeXEgsRyGzuF334rOOQ== +node-fetch-native@^1.6.6: + version "1.6.6" + resolved "https://registry.yarnpkg.com/node-fetch-native/-/node-fetch-native-1.6.6.tgz#ae1d0e537af35c2c0b0de81cbff37eedd410aa37" + integrity sha512-8Mc2HhqPdlIfedsuZoc3yioPuzp6b+L5jRCRY1QzuWZh2EGJVQrGppC6V6cF0bLdbW0+O2YpqCA25aF/1lvipQ== + node-fetch@^2.6.1, node-fetch@^2.6.7: version "2.7.0" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d" @@ -5174,6 +5438,28 @@ nuxi@^3.15.0: resolved "https://registry.yarnpkg.com/nuxi/-/nuxi-3.15.0.tgz#ed54923ca46727c6e7df10495143db340d9791c9" integrity sha512-ZVu45nuDrdb7nzKW2kLGY/N1vvFYLLbUVX6gUYw4BApKGGu4+GktTR5o48dGVgMYX9A8chaugl7TL9ZYmwC9Mg== +nuxt-csurf@^1.6.5: + version "1.6.5" + resolved "https://registry.yarnpkg.com/nuxt-csurf/-/nuxt-csurf-1.6.5.tgz#c900f8817f565d91fe35c712dde7f2b0b46b8c20" + integrity sha512-/DMNTON8LIVhntamKbBmAuM879B0QnuSJa7ZAkmkZe+21m+1QGcjVUxtSkizaM48NUvkuAGYOG0ncn+kqEgrzw== + dependencies: + "@nuxt/kit" "^3.13.2" + defu "^6.1.4" + uncsrf "^1.2.0" + +nuxt-security@2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/nuxt-security/-/nuxt-security-2.2.0.tgz#c9c17f9023b289c97c0f85a1a2f6c13f18d07325" + integrity sha512-bTdgAAAdnvM1R1wAX3zQBbYJh6YNFyvsKJwbT9oVv+0U9J/9+k+mufQlJMFO8AdTefi/EDFHG75in9RTnCpngQ== + dependencies: + "@nuxt/kit" "^3.11.2" + basic-auth "^2.0.1" + defu "^6.1.1" + nuxt-csurf "^1.6.5" + pathe "^1.0.0" + unplugin-remove "^1.0.3" + xss "^1.0.14" + nuxt@^3.13.2: version "3.14.0" resolved "https://registry.yarnpkg.com/nuxt/-/nuxt-3.14.0.tgz#a3e5c39f6eced7336384b92579a5dbf35f63ba00" @@ -5256,6 +5542,17 @@ nypm@^0.3.11, nypm@^0.3.12, nypm@^0.3.8: pkg-types "^1.2.0" ufo "^1.5.4" +nypm@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/nypm/-/nypm-0.6.0.tgz#3a04623d1c358a93fc4b3cb9cfb6a11af080feca" + integrity sha512-mn8wBFV9G9+UFHIrq+pZ2r2zL4aPau/by3kJb3cM7+5tQHMt6HGQB8FDIeKFYp8o0D2pnH6nVsO88N4AmUxIWg== + dependencies: + citty "^0.1.6" + consola "^3.4.0" + pathe "^2.0.3" + pkg-types "^2.0.0" + tinyexec "^0.3.2" + object-assign@^4.0.1, object-assign@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" @@ -5280,6 +5577,11 @@ ohash@^1.1.3, ohash@^1.1.4: resolved "https://registry.yarnpkg.com/ohash/-/ohash-1.1.4.tgz#ae8d83014ab81157d2c285abf7792e2995fadd72" integrity sha512-FlDryZAahJmEF3VR3w1KogSEdWX3WhA5GPakFx4J81kEAiHyLMpdLLElS8n8dfNadMgAne/MywcvmogzscVt4g== +ohash@^2.0.11, ohash@^2.0.5: + version "2.0.11" + resolved "https://registry.yarnpkg.com/ohash/-/ohash-2.0.11.tgz#60b11e8cff62ca9dee88d13747a5baa145f5900b" + integrity sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ== + on-finished@2.4.1, on-finished@^2.3.0: version "2.4.1" resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f" @@ -5462,11 +5764,21 @@ path-type@^5.0.0: resolved "https://registry.yarnpkg.com/path-type/-/path-type-5.0.0.tgz#14b01ed7aea7ddf9c7c3f46181d4d04f9c785bb8" integrity sha512-5HviZNaZcfqP95rwpv+1HDgUamezbqdSYTyzjTvwtJSnIH+3vnbmWsItli8OFEndS984VT55M3jduxZbX351gg== -pathe@^1.1.1, pathe@^1.1.2: +path-type@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-6.0.0.tgz#2f1bb6791a91ce99194caede5d6c5920ed81eb51" + integrity sha512-Vj7sf++t5pBD637NSfkxpHSMfWaeig5+DKWLhcqIYx6mWQz5hdJTGDVMQiJcw1ZYkhs7AazKDGpRVji1LJCZUQ== + +pathe@^1.0.0, pathe@^1.1.1, pathe@^1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/pathe/-/pathe-1.1.2.tgz#6c4cb47a945692e48a1ddd6e4094d170516437ec" integrity sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ== +pathe@^2.0.1, pathe@^2.0.2, pathe@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/pathe/-/pathe-2.0.3.tgz#3ecbec55421685b70a9da872b2cff3e1cbed1716" + integrity sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w== + peek-readable@^5.1.3: version "5.3.1" resolved "https://registry.yarnpkg.com/peek-readable/-/peek-readable-5.3.1.tgz#9cc2c275cceda9f3d07a988f4f664c2080387dff" @@ -5511,6 +5823,24 @@ pkg-types@^1.0.3, pkg-types@^1.2.0, pkg-types@^1.2.1: mlly "^1.7.2" pathe "^1.1.2" +pkg-types@^1.3.0, pkg-types@^1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/pkg-types/-/pkg-types-1.3.1.tgz#bd7cc70881192777eef5326c19deb46e890917df" + integrity sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ== + dependencies: + confbox "^0.1.8" + mlly "^1.7.4" + pathe "^2.0.1" + +pkg-types@^2.0.0, pkg-types@^2.0.1, pkg-types@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/pkg-types/-/pkg-types-2.1.0.tgz#70c9e1b9c74b63fdde749876ee0aa007ea9edead" + integrity sha512-wmJwA+8ihJixSoHKxZJRBQG1oY8Yr9pGLzRmSsNms0iNWyHHAlZCa7mmKiFR10YPZuz/2k169JiS/inOjBCZ2A== + dependencies: + confbox "^0.2.1" + exsolve "^1.0.1" + pathe "^2.0.3" + pluralize@^8.0.0: version "8.0.0" resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-8.0.0.tgz#1a6fa16a38d12a1901e0320fa017051c539ce3b1" @@ -5849,6 +6179,11 @@ proxy-from-env@^1.1.0: resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== +quansync@^0.2.8: + version "0.2.8" + resolved "https://registry.yarnpkg.com/quansync/-/quansync-0.2.8.tgz#2e893d17bb754ba0988ea399ff0bc5f2a8467793" + integrity sha512-4+saucphJMazjt7iOM27mbFCk+D9dd/zmgMDCzRZ8MEoBfYp7lAvoN38et/phRQF6wOPMy/OROBGgoWeSKyluA== + queue-microtask@^1.2.2: version "1.2.3" resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" @@ -6200,16 +6535,16 @@ run-parallel@^1.1.9: dependencies: queue-microtask "^1.2.2" +safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: + version "5.1.2" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" + integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== + safe-buffer@5.2.1, safe-buffer@^5.1.0, safe-buffer@~5.2.0: version "5.2.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== -safe-buffer@~5.1.0, safe-buffer@~5.1.1: - version "5.1.2" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" - integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== - sanitize-filename@^1.6.3: version "1.6.3" resolved "https://registry.yarnpkg.com/sanitize-filename/-/sanitize-filename-1.6.3.tgz#755ebd752045931977e30b2025d340d7c9090378" @@ -6243,6 +6578,11 @@ semver@^7.3.4, semver@^7.3.5, semver@^7.6.3: resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.3.tgz#980f7b5550bc175fb4dc09403085627f9eb33143" integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A== +semver@^7.7.1: + version "7.7.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.1.tgz#abd5098d82b18c6c81f6074ff2647fd3e7220c9f" + integrity sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA== + send@0.19.0: version "0.19.0" resolved "https://registry.yarnpkg.com/send/-/send-0.19.0.tgz#bbc5a388c8ea6c048967049dbeac0e4a3f09d7f8" @@ -6461,6 +6801,11 @@ std-env@^3.7.0: resolved "https://registry.yarnpkg.com/std-env/-/std-env-3.7.0.tgz#c9f7386ced6ecf13360b6c6c55b8aaa4ef7481d2" integrity sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg== +std-env@^3.8.1: + version "3.8.1" + resolved "https://registry.yarnpkg.com/std-env/-/std-env-3.8.1.tgz#2b81c631c62e3d0b964b87f099b8dcab6c9a5346" + integrity sha512-vj5lIj3Mwf9D79hBkltk5qmkFI+biIKWS2IBxEyEU3AX1tUf7AoL8nSazCOiiqQsGKIq01SClsKEzweu34uwvA== + stream-head@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/stream-head/-/stream-head-3.0.0.tgz#cf69c14f3f6d8c63b1475a0e3ccc0ee58ddd2c1c" @@ -6577,6 +6922,13 @@ strip-literal@^2.1.0: dependencies: js-tokens "^9.0.0" +strip-literal@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/strip-literal/-/strip-literal-3.0.0.tgz#ce9c452a91a0af2876ed1ae4e583539a353df3fc" + integrity sha512-TcccoMhJOM3OebGhSBEmp3UZ2SfDMZUEBdRA/9ynfLi8yYajyWX3JiXArcJt4Umh4vISpspkQIY8ZZoCqjbviA== + dependencies: + js-tokens "^9.0.1" + strtok3@^7.0.0: version "7.1.1" resolved "https://registry.yarnpkg.com/strtok3/-/strtok3-7.1.1.tgz#f548fd9dc59d0a76d5567ff8c16be31221f29dfc" @@ -6777,6 +7129,11 @@ tiny-invariant@^1.1.0: resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.3.3.tgz#46680b7a873a0d5d10005995eb90a70d74d60127" integrity sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg== +tinyexec@^0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/tinyexec/-/tinyexec-0.3.2.tgz#941794e657a85e496577995c6eef66f53f42b3d2" + integrity sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA== + tinyglobby@0.2.10, tinyglobby@^0.2.6: version "0.2.10" resolved "https://registry.yarnpkg.com/tinyglobby/-/tinyglobby-0.2.10.tgz#e712cf2dc9b95a1f5c5bbd159720e15833977a0f" @@ -6785,6 +7142,14 @@ tinyglobby@0.2.10, tinyglobby@^0.2.6: fdir "^6.4.2" picomatch "^4.0.2" +tinyglobby@^0.2.11: + version "0.2.12" + resolved "https://registry.yarnpkg.com/tinyglobby/-/tinyglobby-0.2.12.tgz#ac941a42e0c5773bd0b5d08f32de82e74a1a61b5" + integrity sha512-qkf4trmKSIiMTs/E63cxH+ojC2unam7rJ0WrauAzpT3ECNTxGRMlaXxVbfxMUC/w0LaYk6jQ4y/nGR9uBO3tww== + dependencies: + fdir "^6.4.3" + picomatch "^4.0.2" + to-regex-range@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" @@ -6882,6 +7247,11 @@ uncrypto@^0.1.3: resolved "https://registry.yarnpkg.com/uncrypto/-/uncrypto-0.1.3.tgz#e1288d609226f2d02d8d69ee861fa20d8348ef2b" integrity sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q== +uncsrf@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/uncsrf/-/uncsrf-1.2.0.tgz#ed6ee2609726848869983cc639b907abed9dff21" + integrity sha512-EyeG1tIx1zisLuqokSXZ5LhndzaUd2WBMS+18IlBUYobJsKSUQMpLIEm6QUfY/Azmhnnz0v2QbkrT6/u2K/Y1g== + unctx@^2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/unctx/-/unctx-2.3.1.tgz#5eb4aa9f96fb5fdac18b88fe5ba8e122fe671a62" @@ -6892,6 +7262,16 @@ unctx@^2.3.1: magic-string "^0.30.0" unplugin "^1.3.1" +unctx@^2.4.1: + version "2.4.1" + resolved "https://registry.yarnpkg.com/unctx/-/unctx-2.4.1.tgz#93346a98d4a38c64cc5861f6098f4ce7c6f8164a" + integrity sha512-AbaYw0Nm4mK4qjhns67C+kgxR2YWiwlDBPzxrN8h8C6VtAdCgditAY5Dezu3IJy4XVqAnbrXt9oQJvsn3fyozg== + dependencies: + acorn "^8.14.0" + estree-walker "^3.0.3" + magic-string "^0.30.17" + unplugin "^2.1.0" + undici-types@~6.19.8: version "6.19.8" resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.19.8.tgz#35111c9d1437ab83a7cdc0abae2f26d88eda0a02" @@ -6936,6 +7316,11 @@ unicorn-magic@^0.1.0: resolved "https://registry.yarnpkg.com/unicorn-magic/-/unicorn-magic-0.1.0.tgz#1bb9a51c823aaf9d73a8bfcd3d1a23dde94b0ce4" integrity sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ== +unicorn-magic@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/unicorn-magic/-/unicorn-magic-0.3.0.tgz#4efd45c85a69e0dd576d25532fbfa22aa5c8a104" + integrity sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA== + unified@^11.0.0, unified@^11.0.4, unified@^11.0.5: version "11.0.5" resolved "https://registry.yarnpkg.com/unified/-/unified-11.0.5.tgz#f66677610a5c0a9ee90cab2b8d4d66037026d9e1" @@ -6968,6 +7353,26 @@ unimport@^3.12.0, unimport@^3.13.1: strip-literal "^2.1.0" unplugin "^1.14.1" +unimport@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/unimport/-/unimport-4.1.2.tgz#10ba452519ec23113c1e68b8e9ab26c307a6eebd" + integrity sha512-oVUL7PSlyVV3QRhsdcyYEMaDX8HJyS/CnUonEJTYA3//bWO+o/4gG8F7auGWWWkrrxBQBYOO8DKe+C53ktpRXw== + dependencies: + acorn "^8.14.0" + escape-string-regexp "^5.0.0" + estree-walker "^3.0.3" + local-pkg "^1.0.0" + magic-string "^0.30.17" + mlly "^1.7.4" + pathe "^2.0.3" + picomatch "^4.0.2" + pkg-types "^1.3.1" + scule "^1.3.0" + strip-literal "^3.0.0" + tinyglobby "^0.2.11" + unplugin "^2.2.0" + unplugin-utils "^0.2.4" + unist-builder@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/unist-builder/-/unist-builder-4.0.0.tgz#817b326c015a6f9f5e92bb55b8e8bc5e578fe243" @@ -7018,6 +7423,27 @@ universalify@^2.0.0: resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.1.tgz#168efc2180964e6386d061e094df61afe239b18d" integrity sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw== +unplugin-remove@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/unplugin-remove/-/unplugin-remove-1.0.3.tgz#daf24110c2a0f5f679bb72aed9418078b1008043" + integrity sha512-BZMt9v8Y/Z27cY7YQv+DpcW928znjP1cqplBXOirbANiFQtM2YCdiyNAJhHCvjppT0lScNn1aDrQnXqnRp32pQ== + dependencies: + "@babel/core" "^7.25.2" + "@babel/generator" "^7.25.0" + "@babel/parser" "^7.25.3" + "@babel/traverse" "^7.25.3" + "@rollup/pluginutils" "^5.1.0" + magic-string "^0.30.11" + unplugin "^1.12.0" + +unplugin-utils@^0.2.4: + version "0.2.4" + resolved "https://registry.yarnpkg.com/unplugin-utils/-/unplugin-utils-0.2.4.tgz#56e4029a6906645a10644f8befc404b06d5d24d0" + integrity sha512-8U/MtpkPkkk3Atewj1+RcKIjb5WBimZ/WSLhhR3w6SsIj8XJuKTacSP8g+2JhfSGw0Cb125Y+2zA/IzJZDVbhA== + dependencies: + pathe "^2.0.2" + picomatch "^4.0.2" + unplugin-vue-router@^0.10.8: version "0.10.8" resolved "https://registry.yarnpkg.com/unplugin-vue-router/-/unplugin-vue-router-0.10.8.tgz#a868cb64e3c27aba98b312aa757e8cb48830b891" @@ -7046,6 +7472,22 @@ unplugin@^1.10.0, unplugin@^1.12.2, unplugin@^1.14.1, unplugin@^1.15.0, unplugin acorn "^8.14.0" webpack-virtual-modules "^0.6.2" +unplugin@^1.12.0: + version "1.16.1" + resolved "https://registry.yarnpkg.com/unplugin/-/unplugin-1.16.1.tgz#a844d2e3c3b14a4ac2945c42be80409321b61199" + integrity sha512-4/u/j4FrCKdi17jaxuJA0jClGxB1AvU2hw/IuayPc4ay1XGaJs/rbb4v5WKwAjNifjmXK9PIFyuPiaK8azyR9w== + dependencies: + acorn "^8.14.0" + webpack-virtual-modules "^0.6.2" + +unplugin@^2.1.0, unplugin@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/unplugin/-/unplugin-2.2.0.tgz#2659dee5c6b3de9b7ea671228c18263886ae58b6" + integrity sha512-m1ekpSwuOT5hxkJeZGRxO7gXbXT3gF26NjQ7GdVHoLoF8/nopLcd/QfPigpCy7i51oFHiRJg/CyHhj4vs2+KGw== + dependencies: + acorn "^8.14.0" + webpack-virtual-modules "^0.6.2" + unstorage@^1.12.0, unstorage@^1.13.1: version "1.13.1" resolved "https://registry.yarnpkg.com/unstorage/-/unstorage-1.13.1.tgz#090b30de978ee8755b3ad7bbc00acfade124ac13" @@ -7084,6 +7526,17 @@ untyped@^1.5.1: mri "^1.2.0" scule "^1.3.0" +untyped@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/untyped/-/untyped-2.0.0.tgz#86bc205a4ec4b0137282285866b8278557aeee97" + integrity sha512-nwNCjxJTjNuLCgFr42fEak5OcLuB3ecca+9ksPFNvtfYSLpjf+iJqSIaSnIile6ZPbKYxI5k2AfXqeopGudK/g== + dependencies: + citty "^0.1.6" + defu "^6.1.4" + jiti "^2.4.2" + knitwork "^1.2.0" + scule "^1.3.0" + unwasm@^0.3.9: version "0.3.9" resolved "https://registry.yarnpkg.com/unwasm/-/unwasm-0.3.9.tgz#01eca80a1cf2133743bc1bf5cfa749cc145beea0" @@ -7429,6 +7882,14 @@ xmlhttprequest-ssl@~2.1.1: resolved "https://registry.yarnpkg.com/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.1.2.tgz#e9e8023b3f29ef34b97a859f584c5e6c61418e23" integrity sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ== +xss@^1.0.14: + version "1.0.15" + resolved "https://registry.yarnpkg.com/xss/-/xss-1.0.15.tgz#96a0e13886f0661063028b410ed1b18670f4e59a" + integrity sha512-FVdlVVC67WOIPvfOwhoMETV72f6GbW7aOabBC3WxN/oUdoEMDyLz4OgRv5/gck2ZeNqEQu+Tb0kloovXOfpYVg== + dependencies: + commander "^2.20.3" + cssfilter "0.0.10" + y18n@^5.0.5: version "5.0.8" resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" From 2755aa472bee4946a6d611602ab4acfa64a29209 Mon Sep 17 00:00:00 2001 From: Huskydog9988 <39809509+Huskydog9988@users.noreply.github.com> Date: Wed, 12 Mar 2025 14:19:33 -0400 Subject: [PATCH 08/13] in progress igdb --- nuxt.config.ts | 1 + server/internal/metadata/giantbomb.ts | 8 +- server/internal/metadata/igdb.ts | 399 +++++++++++++++++++++++ server/internal/metadata/index.ts | 15 +- server/internal/metadata/pcgamingwiki.ts | 9 +- 5 files changed, 419 insertions(+), 13 deletions(-) create mode 100644 server/internal/metadata/igdb.ts diff --git a/nuxt.config.ts b/nuxt.config.ts index c9f3eeb..acda755 100644 --- a/nuxt.config.ts +++ b/nuxt.config.ts @@ -65,6 +65,7 @@ export default defineNuxtConfig({ "data:", "https://www.giantbomb.com", "https://images.pcgamingwiki.com", + "https://images.igdb.com", ], }, }, diff --git a/server/internal/metadata/giantbomb.ts b/server/internal/metadata/giantbomb.ts index 02de9d0..e9435e4 100644 --- a/server/internal/metadata/giantbomb.ts +++ b/server/internal/metadata/giantbomb.ts @@ -1,5 +1,5 @@ import { Developer, MetadataSource, Publisher } from "@prisma/client"; -import { MetadataProvider, MissingMetadataProviderApiKey } from "."; +import { MetadataProvider, MissingMetadataProviderConfig } from "."; import { GameMetadataSearchResult, _FetchGameMetadataParams, @@ -81,7 +81,11 @@ export class GiantBombProvider implements MetadataProvider { constructor() { const apikey = process.env.GIANT_BOMB_API_KEY; - if (!apikey) throw new MissingMetadataProviderApiKey(this.name()); + if (!apikey) + throw new MissingMetadataProviderConfig( + "GIANT_BOMB_API_KEY", + this.name() + ); this.apikey = apikey; diff --git a/server/internal/metadata/igdb.ts b/server/internal/metadata/igdb.ts new file mode 100644 index 0000000..c653cb7 --- /dev/null +++ b/server/internal/metadata/igdb.ts @@ -0,0 +1,399 @@ +import { Developer, MetadataSource, Publisher } from "@prisma/client"; +import { MetadataProvider, MissingMetadataProviderConfig } from "."; +import { + GameMetadataSearchResult, + _FetchGameMetadataParams, + GameMetadata, + _FetchPublisherMetadataParams, + PublisherMetadata, + _FetchDeveloperMetadataParams, + DeveloperMetadata, +} from "./types"; +import axios, { AxiosRequestConfig } from "axios"; +import { inspect } from "util"; +import moment from "moment"; + +type IGDBID = number; + +interface TwitchAuthResponse { + access_token: string; + expires_in: number; + token_type: string; // likely 'bearer' +} + +interface IGDBErrorResponse { + title: string; + status: number; + cause: string; +} + +interface IGDBItem { + id: IGDBID; +} + +// denotes role a company had in a game +interface IGDBInvolvedCompany extends IGDBItem { + company: IGDBID; + game: IGDBID; + + developer: boolean; + porting: boolean; + publisher: boolean; + supporting: boolean; + + created_at: number; + updated_at: number; +} + +interface IGDBCompany extends IGDBItem { + name: string; + country: number; // ISO 3166-1 country code + description: string; + logo: IGDBID; + parent: IGDBID; + slug: string; + start_date: number; + status: IGDBID; + websites: IGDBID[]; +} + +interface IGDBCompanyWebsite extends IGDBItem { + trusted: boolean; + url: string; +} + +interface IGDBCover extends IGDBItem { + url: string; +} + +interface IGDBSearchStub extends IGDBItem { + name: string; + cover: IGDBID; + first_release_date: number; // unix timestamp + summary: string; +} + +// https://api-docs.igdb.com/?shell#game +interface IGDBGameFull extends IGDBSearchStub { + age_ratings?: IGDBID[]; + aggregated_rating?: number; + aggregated_rating_count?: number; + alternative_names?: IGDBID[]; + artworks?: IGDBID[]; + bundles?: IGDBID[]; + checksum?: string; + collections?: IGDBID[]; + created_at: number; // unix timestamp + dlcs?: IGDBID[]; + expanded_games?: IGDBID[]; + expansions?: IGDBID[]; + external_games?: IGDBID[]; + forks?: IGDBID[]; + franchise?: IGDBID; + franchises?: IGDBID[]; + game_engines?: IGDBID[]; + game_localizations?: IGDBID[]; + game_modes?: IGDBID[]; + game_status?: IGDBID; + game_type?: IGDBID; + genres?: IGDBID[]; + hypes?: number; + involved_companies?: IGDBID[]; + keywords?: IGDBID[]; + language_supports?: IGDBID[]; + multiplayer_modes?: IGDBID[]; + platforms?: IGDBID[]; + player_perspectives?: IGDBID[]; + ports?: IGDBID[]; + rating?: number; + rating_count?: number; + release_dates?: IGDBID[]; + remakes?: IGDBID[]; + remasters?: IGDBID[]; + screenshots?: IGDBID[]; + similar_games?: IGDBID[]; + slug: string; + standalone_expansions?: IGDBID[]; + storyline?: string; + tags?: IGDBID[]; + themes?: IGDBID[]; + total_rating?: number; + total_rating_count?: number; + updated_at: number; + url: string; + version_parent?: IGDBID; + version_title?: string; + videos?: IGDBID[]; + websites?: IGDBID[]; +} + +// Api Docs: https://api-docs.igdb.com/ +export class IGDBProvider implements MetadataProvider { + private client_id: string; + private client_secret: string; + private access_token: string; + + constructor() { + const client_id = process.env.IGDB_CLIENT_ID; + if (!client_id) + throw new MissingMetadataProviderConfig("IGDB_CLIENT_ID", this.name()); + const client_secret = process.env.IGDB_CLIENT_SECRET; + if (!client_secret) + throw new MissingMetadataProviderConfig( + "IGDB_CLIENT_SECRET", + this.name() + ); + + this.client_id = client_id; + this.client_secret = client_secret; + + this.access_token = "6lkqltu4m70i46jhcdrz8qt8tb7rdh"; + // this.authWithTwitch(); + } + + private async authWithTwitch() { + const params = new URLSearchParams({ + client_id: this.client_id, + client_secret: this.client_secret, + grant_type: "client_credentials", + }); + + const response = await axios.request({ + url: `https://id.twitch.tv/oauth2/token?${params.toString()}`, + baseURL: "", + method: "POST", + }); + + console.log(inspect(response.data)); + + this.access_token = response.data.access_token; + // TODO: handle token expiration, time in seconds is provided, on a long running server + // this WILL be an issue. Can use node timers, or maybe nuxt tasks? problem is + // that idk if tasks can be variable like twitch wants, expires_in is variable + } + + private async request( + resource: string, + body: string, + options?: AxiosRequestConfig + ) { + // prevent calling api before auth is complete + if (this.access_token.length <= 0) + throw new Error( + "IGDB either failed to authenticate, or has not done so yet" + ); + + const finalURL = `https://api.igdb.com/v4/${resource}`; + + const overlay: AxiosRequestConfig = { + url: finalURL, + baseURL: "", + method: "POST", + data: body, + headers: { + Accept: "application/json", + "Client-ID": this.client_id, + Authorization: `Bearer ${this.access_token}`, + "content-type": "text/plain", + }, + }; + const response = await axios.request( + Object.assign({}, options, overlay) + ); + + if (response.status !== 200) { + let cause = ""; + + response.data.forEach((item) => { + if ("cause" in item) cause = item.cause; + }); + + throw new Error( + `Error in igdb \nStatus Code: ${response.status} \nCause: ${cause}` + ); + } + + // should not have an error object if the status code is 200 + return response.data; + } + + private async _getMediaInternal(mediaID: IGDBID, type: string) { + const body = `where id = ${mediaID}; fields url;`; + const response = await this.request(type, body); + + let result = ""; + + response.forEach((cover) => { + if (cover.url.startsWith("https:")) { + result = cover.url; + } else { + // twitch *sometimes* provides it in the format "//images.igdb.com" + result = `https:${cover.url}`; + } + }); + return result; + } + + private async getCoverURl(id: IGDBID) { + return await this._getMediaInternal(id, "covers"); + } + + private async getArtworkURl(id: IGDBID) { + return await this._getMediaInternal(id, "artworks"); + } + + private async getCompanyLogoURl(id: IGDBID) { + return await this._getMediaInternal(id, "company_logos"); + } + + private trimMessage(msg: string, len: number) { + return msg.length > len ? msg.substring(0, 280) + "..." : msg; + } + + id() { + return "igdb"; + } + name() { + return "IGDB"; + } + source() { + return MetadataSource.IGDB; + } + + async search(query: string): Promise { + // throw new Error("Not implemented"); + + const body = `search "${query}"; fields name,cover,first_release_date,summary; limit 3;`; + const response = await this.request("games", body); + + const results: GameMetadataSearchResult[] = []; + for (let i = 0; i < response.length; i++) { + results.push({ + id: "" + response[i].id, + name: response[i].name, + icon: await this.getCoverURl(response[i].cover), + description: response[i].summary, + year: moment.unix(response[i].first_release_date).year(), + }); + } + + return results; + } + async fetchGame({ + id, + publisher, + developer, + createObject, + }: _FetchGameMetadataParams): Promise { + const body = `where id = ${id}; fields *;`; + const response = await this.request("games", body); + + for (let i = 0; i < response.length; i++) { + const icon = createObject(await this.getCoverURl(response[i].cover)); + let banner = ""; + + const images = [icon]; + for (const art of response[i]?.artworks ?? []) { + // if banner not set + if (banner.length <= 0) { + banner = createObject(await this.getArtworkURl(art)); + images.push(banner); + } else images.push(createObject(await this.getArtworkURl(art))); + } + + const publishers: Publisher[] = []; + const developers: Developer[] = []; + for (const involved_company of response[i]?.involved_companies ?? []) { + // get details about the involved company + const involved_company_response = + await this.request( + "involved_companies", + `where id = ${involved_company}; fields *;` + ); + for (const found_involed of involved_company_response) { + // now we need to get the actual company so we can get the name + const find_company_response = await this.request< + { name: string } & IGDBItem + >("companies", `where id = ${found_involed.company}; fields name;`); + + for (const company of find_company_response) { + // if company was a dev or publisher + // CANNOT use else since a company can be both + + // TODO: why did this call manual metadata??? + + if (found_involed.developer) + developers.push(await developer(company.name)); + if (found_involed.publisher) + publishers.push(await publisher(company.name)); + } + } + } + + return { + id: "" + response[i].id, + name: response[i].name, + shortDescription: this.trimMessage(response[i].summary, 280), + description: response[i].summary, + released: moment.unix(response[i].first_release_date).toDate(), + + reviewCount: response[i]?.total_rating_count ?? 0, + reviewRating: response[i]?.total_rating ?? 0, + + publishers: [], + developers: [], + + icon, + bannerId: banner, + coverId: icon, + images, + }; + } + + throw new Error("No game found on igdb with that id"); + } + async fetchPublisher({ + query, + createObject, + }: _FetchPublisherMetadataParams): Promise { + const response = await this.request( + "companies", + `search "${query}"; fields *;` + ); + + for (const company of response) { + const logo = createObject(await this.getCompanyLogoURl(company.logo)); + + let company_url = ""; + for (const company_site of company.websites) { + const company_site_res = await this.request( + "company_websites", + `where id = ${company_site}; fields *;` + ); + + for (const site of company_site_res) { + if (company_url.length <= 0) company_url = site.url; + } + } + const metadata: PublisherMetadata = { + id: "" + company.id, + name: company.name, + shortDescription: this.trimMessage(company.description, 280), + description: company.description, + website: company_url, + + logo: logo, + banner: logo, + }; + + return metadata; + } + + throw new Error("No results found"); + } + async fetchDeveloper( + params: _FetchDeveloperMetadataParams + ): Promise { + return await this.fetchPublisher(params); + } +} diff --git a/server/internal/metadata/index.ts b/server/internal/metadata/index.ts index 4c18a27..5d82751 100644 --- a/server/internal/metadata/index.ts +++ b/server/internal/metadata/index.ts @@ -20,12 +20,13 @@ import { PriorityList, PriorityListIndexed } from "../utils/prioritylist"; import { GiantBombProvider } from "./giantbomb"; import { ManualMetadataProvider } from "./manual"; import { PCGamingWikiProvider } from "./pcgamingwiki"; +import { IGDBProvider } from "./igdb"; -export class MissingMetadataProviderApiKey extends Error { +export class MissingMetadataProviderConfig extends Error { private providerName: string; - constructor(providerName: string) { - super(`Missing ${providerName} api key`); + constructor(configKey: string, providerName: string) { + super(`Missing config item ${configKey} for ${providerName}`); this.providerName = providerName; } @@ -34,6 +35,9 @@ export class MissingMetadataProviderApiKey extends Error { } } +// TODO: add useragent to all outbound api calls (best practice) +export const DropUserAgent = "Drop/0.2"; + export abstract class MetadataProvider { abstract id(): string; abstract name(): string; @@ -208,6 +212,8 @@ export class MetadataHandler { if (existing) return existing; for (const provider of this.providers.values() as any) { + // TODO: why did this call manual metadata??? + const [createObject, pullObjects, dumpObjects] = this.objectHandler.new( {}, ["internal:read"] @@ -256,6 +262,7 @@ const metadataProviders = [ GiantBombProvider, ManualMetadataProvider, PCGamingWikiProvider, + IGDBProvider, ]; for (const provider of metadataProviders) { @@ -265,7 +272,7 @@ for (const provider of metadataProviders) { enabledMedadataProviders.push(prov.id()); console.log(`enabled metadata provider: ${prov.name()}`); } catch (e) { - if (e instanceof MissingMetadataProviderApiKey) { + if (e instanceof MissingMetadataProviderConfig) { console.warn(`Disabling ${e.getProviderName()} metadata provider`); } else { console.error(`skipping metadata provider setup: ${e}`); diff --git a/server/internal/metadata/pcgamingwiki.ts b/server/internal/metadata/pcgamingwiki.ts index be92c03..61ca7ff 100644 --- a/server/internal/metadata/pcgamingwiki.ts +++ b/server/internal/metadata/pcgamingwiki.ts @@ -1,5 +1,5 @@ import { Developer, MetadataSource, Publisher } from "@prisma/client"; -import { MetadataProvider, MissingMetadataProviderApiKey } from "."; +import { MetadataProvider, MissingMetadataProviderConfig } from "."; import { GameMetadataSearchResult, _FetchGameMetadataParams, @@ -180,7 +180,6 @@ export class PCGamingWikiProvider implements MetadataProvider { if (game.Publishers !== undefined) { const pubListClean = this.parseCompanyStr(game.Publishers); for (const pub of pubListClean) { - console.log("Found publisher: ", pub); publishers.push(await publisher(pub)); } } @@ -189,8 +188,7 @@ export class PCGamingWikiProvider implements MetadataProvider { if (game.Developers !== undefined) { const devListClean = this.parseCompanyStr(game.Developers); for (const dev of devListClean) { - console.log("Found dev: ", dev); - developers.push(await developer(dev.replace("Company:", ""))); + developers.push(await developer(dev)); } } @@ -237,14 +235,11 @@ export class PCGamingWikiProvider implements MetadataProvider { format: "json", }); - console.log("Searching for: " + query); const res = await this.request(searchParams); // TODO: replace const icon = createObject(jdenticon.toPng(query, 512)); - console.log("Found: ", res.data.cargoquery); - for (let i = 0; i < res.data.cargoquery.length; i++) { const company = this.markNullUndefined(res.data.cargoquery[i].title); From 77d06df7d3db65e1a5807a6925cd9c36d1f1614d Mon Sep 17 00:00:00 2001 From: Huskydog9988 <39809509+Huskydog9988@users.noreply.github.com> Date: Wed, 12 Mar 2025 16:08:24 -0400 Subject: [PATCH 09/13] fix manual metadata fetching publishers +types --- server/internal/metadata/index.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/server/internal/metadata/index.ts b/server/internal/metadata/index.ts index 5d82751..f986d8f 100644 --- a/server/internal/metadata/index.ts +++ b/server/internal/metadata/index.ts @@ -201,8 +201,8 @@ export class MetadataHandler { // Type-checking this thing is impossible private async fetchDeveloperPublisher( query: string, - functionName: any, - databaseName: any + functionName: "fetchDeveloper" | "fetchPublisher", + databaseName: "developer" | "publisher" ) { const existing = await (prisma as any)[databaseName].findFirst({ where: { @@ -211,14 +211,15 @@ export class MetadataHandler { }); if (existing) return existing; - for (const provider of this.providers.values() as any) { - // TODO: why did this call manual metadata??? + for (const provider of this.providers.values()) { + // don't allow manual provider to "fetch" metadata + if (provider.source() === MetadataSource.Manual) continue; const [createObject, pullObjects, dumpObjects] = this.objectHandler.new( {}, ["internal:read"] ); - let result; + let result: PublisherMetadata; try { result = await provider[functionName]({ query, createObject }); } catch (e) { From bf7eb5b98681140a1622b1392357c45ca2da6fe2 Mon Sep 17 00:00:00 2001 From: Huskydog9988 <39809509+Huskydog9988@users.noreply.github.com> Date: Wed, 12 Mar 2025 16:08:58 -0400 Subject: [PATCH 10/13] fix issue in igdb where company isn't found --- .gitattributes | 1 + server/internal/metadata/igdb.ts | 5 +---- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/.gitattributes b/.gitattributes index af3ad12..e3f08fc 100644 --- a/.gitattributes +++ b/.gitattributes @@ -2,3 +2,4 @@ /.yarn/releases/* binary /.yarn/plugins/**/* binary /.pnp.* binary linguist-generated +* text=auto eol=lf diff --git a/server/internal/metadata/igdb.ts b/server/internal/metadata/igdb.ts index c653cb7..66fdf9d 100644 --- a/server/internal/metadata/igdb.ts +++ b/server/internal/metadata/igdb.ts @@ -319,9 +319,6 @@ export class IGDBProvider implements MetadataProvider { for (const company of find_company_response) { // if company was a dev or publisher // CANNOT use else since a company can be both - - // TODO: why did this call manual metadata??? - if (found_involed.developer) developers.push(await developer(company.name)); if (found_involed.publisher) @@ -358,7 +355,7 @@ export class IGDBProvider implements MetadataProvider { }: _FetchPublisherMetadataParams): Promise { const response = await this.request( "companies", - `search "${query}"; fields *;` + `where name = "${query}"; fields *; limit 1;` ); for (const company of response) { From 4e8cffd778b18202aaed808b786c9a78fc8f5f42 Mon Sep 17 00:00:00 2001 From: Huskydog9988 <39809509+Huskydog9988@users.noreply.github.com> Date: Wed, 12 Mar 2025 16:12:16 -0400 Subject: [PATCH 11/13] make pcgamig wiki types match api return --- server/internal/metadata/pcgamingwiki.ts | 50 ++++++++++-------------- 1 file changed, 20 insertions(+), 30 deletions(-) diff --git a/server/internal/metadata/pcgamingwiki.ts b/server/internal/metadata/pcgamingwiki.ts index 61ca7ff..fb94f45 100644 --- a/server/internal/metadata/pcgamingwiki.ts +++ b/server/internal/metadata/pcgamingwiki.ts @@ -19,26 +19,26 @@ interface PCGamingWikiPage { } interface PCGamingWikiSearchStub extends PCGamingWikiPage { - "Cover URL"?: string; - Released?: string; - Released__precision?: string; + "Cover URL": string | null; + Released: string | null; + Released__precision: string | null; } interface PCGamingWikiGame extends PCGamingWikiSearchStub { - Developers?: string; - Genres?: string; - Publishers?: string; - Themes?: string; - Series?: string; - Modes?: string; + Developers: string | null; + Genres: string | null; + Publishers: string | null; + Themes: string | null; + Series: string | null; + Modes: string | null; } interface PCGamingWikiCompany extends PCGamingWikiPage { - Parent?: string; - Founded?: string; - Website?: string; - Founded__precision?: string; - Defunct__precision?: string; + Parent: string | null; + Founded: string | null; + Website: string | null; + Founded__precision: string | null; + Defunct__precision: string | null; } interface PCGamingWikiCargoResult { @@ -94,16 +94,6 @@ export class PCGamingWikiProvider implements MetadataProvider { return response; } - private markNullUndefined>(obj: T): T { - // TODO: fix types above and make everything nullable instead of possibly undefined, better then this, maybe? - - const result = structuredClone(obj); - for (const [key, value] of Object.entries(result)) { - if (value === null) delete result[key]; - } - return result; - } - async search(query: string) { const searchParams = new URLSearchParams({ action: "cargoquery", @@ -117,7 +107,7 @@ export class PCGamingWikiProvider implements MetadataProvider { const res = await this.request(searchParams); const mapped = res.data.cargoquery.map((result) => { - const game = this.markNullUndefined(result.title); + const game = result.title; const metadata: GameMetadataSearchResult = { id: game.PageID, @@ -125,7 +115,7 @@ export class PCGamingWikiProvider implements MetadataProvider { icon: game["Cover URL"] ?? "", description: "", // TODO: need to render the `Introduction` template somehow (or we could just hardcode it) year: - game.Released !== undefined && game.Released.length > 0 + game.Released !== null && game.Released.length > 0 ? moment(game.Released).year() : 0, }; @@ -177,7 +167,7 @@ export class PCGamingWikiProvider implements MetadataProvider { const game = res.data.cargoquery[0].title; const publishers: Publisher[] = []; - if (game.Publishers !== undefined) { + if (game.Publishers !== null) { const pubListClean = this.parseCompanyStr(game.Publishers); for (const pub of pubListClean) { publishers.push(await publisher(pub)); @@ -185,7 +175,7 @@ export class PCGamingWikiProvider implements MetadataProvider { } const developers: Developer[] = []; - if (game.Developers !== undefined) { + if (game.Developers !== null) { const devListClean = this.parseCompanyStr(game.Developers); for (const dev of devListClean) { developers.push(await developer(dev)); @@ -193,7 +183,7 @@ export class PCGamingWikiProvider implements MetadataProvider { } const icon = - game["Cover URL"] !== undefined + game["Cover URL"] !== null ? createObject(game["Cover URL"]) : createObject(jdenticon.toPng(name, 512)); @@ -241,7 +231,7 @@ export class PCGamingWikiProvider implements MetadataProvider { const icon = createObject(jdenticon.toPng(query, 512)); for (let i = 0; i < res.data.cargoquery.length; i++) { - const company = this.markNullUndefined(res.data.cargoquery[i].title); + const company = res.data.cargoquery[i].title; const metadata: PublisherMetadata = { id: company.PageID, From 7a3b30b01234c639b85da62762356e0cc08068c3 Mon Sep 17 00:00:00 2001 From: Huskydog9988 <39809509+Huskydog9988@users.noreply.github.com> Date: Wed, 12 Mar 2025 16:20:14 -0400 Subject: [PATCH 12/13] accidently commited secret from testing lol (revoked) --- server/internal/metadata/igdb.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/internal/metadata/igdb.ts b/server/internal/metadata/igdb.ts index 66fdf9d..67823b2 100644 --- a/server/internal/metadata/igdb.ts +++ b/server/internal/metadata/igdb.ts @@ -147,8 +147,8 @@ export class IGDBProvider implements MetadataProvider { this.client_id = client_id; this.client_secret = client_secret; - this.access_token = "6lkqltu4m70i46jhcdrz8qt8tb7rdh"; - // this.authWithTwitch(); + this.access_token = ""; + this.authWithTwitch(); } private async authWithTwitch() { From f8ae5b70c0bdb39e6a64e94f01758ed6daea24fb Mon Sep 17 00:00:00 2001 From: Huskydog9988 <39809509+Huskydog9988@users.noreply.github.com> Date: Wed, 12 Mar 2025 18:34:36 -0400 Subject: [PATCH 13/13] automate twitch credential refresh --- nuxt.config.ts | 2 ++ server/internal/metadata/igdb.ts | 16 +++++++++++++--- server/internal/metadata/index.ts | 13 +++++++++++++ server/tasks/metadata/refreshCredentials.ts | 12 ++++++++++++ 4 files changed, 40 insertions(+), 3 deletions(-) create mode 100644 server/tasks/metadata/refreshCredentials.ts diff --git a/nuxt.config.ts b/nuxt.config.ts index acda755..8e32a02 100644 --- a/nuxt.config.ts +++ b/nuxt.config.ts @@ -29,6 +29,8 @@ export default defineNuxtConfig({ scheduledTasks: { "0 * * * *": ["cleanup:invitations"], + // every two hours, check if creds need updating + "0 */2 * * *": ["metadata:refreshCredentials"], }, }, diff --git a/server/internal/metadata/igdb.ts b/server/internal/metadata/igdb.ts index 67823b2..f91930f 100644 --- a/server/internal/metadata/igdb.ts +++ b/server/internal/metadata/igdb.ts @@ -132,6 +132,7 @@ export class IGDBProvider implements MetadataProvider { private client_id: string; private client_secret: string; private access_token: string; + private access_token_expire: moment.Moment; constructor() { const client_id = process.env.IGDB_CLIENT_ID; @@ -148,6 +149,7 @@ export class IGDBProvider implements MetadataProvider { this.client_secret = client_secret; this.access_token = ""; + this.access_token_expire = moment(); this.authWithTwitch(); } @@ -167,9 +169,17 @@ export class IGDBProvider implements MetadataProvider { console.log(inspect(response.data)); this.access_token = response.data.access_token; - // TODO: handle token expiration, time in seconds is provided, on a long running server - // this WILL be an issue. Can use node timers, or maybe nuxt tasks? problem is - // that idk if tasks can be variable like twitch wants, expires_in is variable + this.access_token_expire = moment().add( + response.data.expires_in, + "seconds" + ); + } + + public async refreshCredentials() { + const futureTime = moment().add(1, "day"); + + // if the token expires before this future time (aka soon), refresh + if (this.access_token_expire.isBefore(futureTime)) this.authWithTwitch(); } private async request( diff --git a/server/internal/metadata/index.ts b/server/internal/metadata/index.ts index f986d8f..0b33fc3 100644 --- a/server/internal/metadata/index.ts +++ b/server/internal/metadata/index.ts @@ -51,6 +51,8 @@ export abstract class MetadataProvider { abstract fetchDeveloper( params: _FetchDeveloperMetadataParams ): Promise; + + abstract refreshCredentials?: () => void; } export class MetadataHandler { @@ -253,6 +255,17 @@ export class MetadataHandler { `No metadata provider found a ${databaseName} for "${query}"` ); } + + /** + * Refresh creds for all providers that need it + */ + public refreshCredentials() { + for (const provider of this.providers.values()) { + if (provider.refreshCredentials) { + provider.refreshCredentials(); + } + } + } } export const metadataHandler = new MetadataHandler(); diff --git a/server/tasks/metadata/refreshCredentials.ts b/server/tasks/metadata/refreshCredentials.ts new file mode 100644 index 0000000..fa654f5 --- /dev/null +++ b/server/tasks/metadata/refreshCredentials.ts @@ -0,0 +1,12 @@ +import { metadataHandler } from "~/server/plugins/metadata"; + +export default defineTask({ + meta: { + name: "metadata:refreshCredentials", + description: "Refresh credentials for metadata providers", + }, + run({ payload, context }) { + metadataHandler.refreshCredentials(); + return { result: "Success" }; + }, +});