mirror of
https://github.com/Drop-OSS/drop.git
synced 2025-11-09 20:12:10 +10:00
Compare commits
12 Commits
2087531ace
...
ef244edd1a
| Author | SHA1 | Date | |
|---|---|---|---|
| ef244edd1a | |||
| b083c1e00a | |||
| 5f2cb75666 | |||
| 2a23f4d14c | |||
| b20d355527 | |||
| fa9620eac1 | |||
| a201b62c04 | |||
| 9bf164ab77 | |||
| 97c6f3490c | |||
| f5cb856d3d | |||
| f331661c14 | |||
| 67de1f6c02 |
@ -4,9 +4,10 @@
|
||||
v-for="(_, i) in amount"
|
||||
:key="i"
|
||||
:class="[
|
||||
carousel.currentSlide == i ? 'bg-blue-600 w-6' : 'bg-zinc-700 w-3',
|
||||
carousel.currentSlide === i ? 'bg-blue-600 w-6' : 'bg-zinc-700 w-3',
|
||||
'transition-all cursor-pointer h-2 rounded-full',
|
||||
]"
|
||||
@click="slideTo(i)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
@ -18,8 +19,8 @@ const carousel = inject(injectCarousel)!;
|
||||
|
||||
const amount = carousel.maxSlide - carousel.minSlide + 1;
|
||||
|
||||
// function slideTo(index: number) {
|
||||
// const offsetIndex = index + carousel.minSlide;
|
||||
// carousel.nav.slideTo(offsetIndex);
|
||||
// }
|
||||
function slideTo(index: number) {
|
||||
const offsetIndex = index + carousel.minSlide;
|
||||
carousel.nav.slideTo(offsetIndex);
|
||||
}
|
||||
</script>
|
||||
|
||||
@ -1,3 +1,12 @@
|
||||
<i18n>
|
||||
{
|
||||
"en": {
|
||||
"↓": "↓",
|
||||
"↑": "↑"
|
||||
}
|
||||
}
|
||||
</i18n>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div>
|
||||
@ -176,9 +185,12 @@
|
||||
active ? 'bg-zinc-900 outline-hidden' : '',
|
||||
'w-full text-left block px-4 py-2 text-sm',
|
||||
]"
|
||||
@click="() => (currentSort = option.param)"
|
||||
@click.prevent="handleSortClick(option, $event)"
|
||||
>
|
||||
{{ option.name }}
|
||||
<span v-if="currentSort === option.param">
|
||||
{{ sortOrder === "asc" ? $t("↑") : $t("↓") }}
|
||||
</span>
|
||||
</button>
|
||||
</MenuItem>
|
||||
</div>
|
||||
@ -298,7 +310,7 @@
|
||||
<div
|
||||
v-if="games?.length ?? 0 > 0"
|
||||
ref="product-grid"
|
||||
class="relative lg:col-span-4 grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-4 xl:grid-cols-6 2xl:grid-cols-7 gap-4"
|
||||
class="relative lg:col-span-4 grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-4 xl:grid-cols-5 2xl:grid-cols-6 gap-4"
|
||||
>
|
||||
<!-- Your content -->
|
||||
<GamePanel
|
||||
@ -397,8 +409,13 @@ const sorts: Array<StoreSortOption> = [
|
||||
name: "Recently Added",
|
||||
param: "recent",
|
||||
},
|
||||
{
|
||||
name: "Name",
|
||||
param: "name",
|
||||
},
|
||||
];
|
||||
const currentSort = ref(sorts[0].param);
|
||||
const sortOrder = ref<"asc" | "desc">("desc");
|
||||
|
||||
const options: Array<StoreFilterOption> = [
|
||||
...(tags.length > 0
|
||||
@ -474,7 +491,7 @@ async function updateGames(query: string, resetGames: boolean) {
|
||||
results: Array<SerializeObject<GameModel>>;
|
||||
count: number;
|
||||
}>(
|
||||
`/api/v1/store?take=50&skip=${resetGames ? 0 : games.value?.length || 0}&sort=${currentSort.value}${query ? "&" + query : ""}`,
|
||||
`/api/v1/store?take=50&skip=${resetGames ? 0 : games.value?.length || 0}&sort=${currentSort.value}&order=${sortOrder.value}${query ? "&" + query : ""}`,
|
||||
);
|
||||
if (resetGames) {
|
||||
games.value = newValues.results;
|
||||
@ -491,6 +508,19 @@ watch(filterQuery, (newUrl) => {
|
||||
watch(currentSort, (_) => {
|
||||
updateGames(filterQuery.value, true);
|
||||
});
|
||||
watch(sortOrder, (_) => {
|
||||
updateGames(filterQuery.value, true);
|
||||
});
|
||||
|
||||
await updateGames(filterQuery.value, true);
|
||||
|
||||
function handleSortClick(option: StoreSortOption, event: MouseEvent) {
|
||||
event.stopPropagation();
|
||||
if (currentSort.value === option.param) {
|
||||
sortOrder.value = sortOrder.value === "asc" ? "desc" : "asc";
|
||||
} else {
|
||||
currentSort.value = option.param;
|
||||
sortOrder.value = option.param === "name" ? "asc" : "desc";
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@ -134,34 +134,41 @@
|
||||
</div>
|
||||
</div>
|
||||
<!-- setup mode -->
|
||||
<SwitchGroup as="div" class="max-w-lg flex items-center justify-between">
|
||||
<span class="flex flex-grow flex-col">
|
||||
<SwitchLabel
|
||||
as="span"
|
||||
class="text-sm font-medium leading-6 text-zinc-100"
|
||||
passive
|
||||
>{{ $t("library.admin.import.version.setupMode") }}</SwitchLabel
|
||||
<fieldset class="max-w-lg">
|
||||
<legend class="text-sm/6 font-semibold text-white">
|
||||
Select an import mode
|
||||
</legend>
|
||||
<div class="mt-2 grid grid-cols-1 gap-y-6 sm:grid-cols-2 sm:gap-x-4">
|
||||
<label
|
||||
v-for="mode in setupModes"
|
||||
:key="mode.id"
|
||||
:aria-label="mode.title"
|
||||
:aria-description="mode.description"
|
||||
class="cursor-pointer group relative flex rounded-lg border border-white/10 bg-zinc-800/50 p-4 has-checked:bg-blue-500/10 has-checked:outline-2 has-checked:-outline-offset-2 has-checked:outline-blue-500 has-focus-visible:outline-3 has-focus-visible:-outline-offset-1 has-disabled:bg-gray-800 has-disabled:opacity-25"
|
||||
>
|
||||
<SwitchDescription as="span" class="text-sm text-zinc-400">{{
|
||||
$t("library.admin.import.version.setupModeDesc")
|
||||
}}</SwitchDescription>
|
||||
</span>
|
||||
<Switch
|
||||
v-model="versionSettings.onlySetup"
|
||||
:class="[
|
||||
versionSettings.onlySetup ? 'bg-blue-600' : 'bg-zinc-800',
|
||||
'relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-blue-600 focus:ring-offset-2',
|
||||
]"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
:class="[
|
||||
versionSettings.onlySetup ? 'translate-x-5' : 'translate-x-0',
|
||||
'pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out',
|
||||
]"
|
||||
<input
|
||||
type="radio"
|
||||
name="mode"
|
||||
:value="mode.id"
|
||||
:checked="versionSettings.onlySetup === mode.value"
|
||||
class="absolute inset-0 appearance-none opacity-0 focus:outline-none"
|
||||
@click="versionSettings.onlySetup = mode.value"
|
||||
/>
|
||||
</Switch>
|
||||
</SwitchGroup>
|
||||
<div class="flex-1">
|
||||
<span class="block text-sm font-medium text-white">{{
|
||||
mode.title
|
||||
}}</span>
|
||||
<span class="mt-1 block text-xs text-zinc-400">{{
|
||||
mode.description
|
||||
}}</span>
|
||||
</div>
|
||||
<CheckCircleIcon
|
||||
class="invisible size-5 text-blue-500 group-has-checked:visible"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
</fieldset>
|
||||
<!-- launch commands -->
|
||||
<div class="relative max-w-3xl">
|
||||
<label
|
||||
@ -462,10 +469,14 @@ import {
|
||||
} from "@headlessui/vue";
|
||||
import { XCircleIcon } from "@heroicons/vue/16/solid";
|
||||
import { CheckIcon, ChevronUpDownIcon } from "@heroicons/vue/20/solid";
|
||||
import { PlusIcon, TrashIcon } from "@heroicons/vue/24/outline";
|
||||
import {
|
||||
CheckCircleIcon,
|
||||
PlusIcon,
|
||||
TrashIcon,
|
||||
} from "@heroicons/vue/24/outline";
|
||||
import { ChevronDownIcon, ChevronUpIcon } from "@heroicons/vue/24/solid";
|
||||
import type { SerializeObject } from "nitropack";
|
||||
import type { ImportVersion } from "~~/server/api/v1/admin/import/version/index.post";
|
||||
import type { ImportGameVersion } from "~~/server/api/v1/admin/import/version/index.post";
|
||||
|
||||
definePageMeta({
|
||||
layout: "admin",
|
||||
@ -481,9 +492,10 @@ const versions = await $dropFetch(
|
||||
const userPlatforms = await useAdminPlatforms();
|
||||
const allPlatforms = renderPlatforms(userPlatforms);
|
||||
const currentlySelectedVersion = ref(-1);
|
||||
const versionSettings = ref<Partial<typeof ImportVersion.infer>>({
|
||||
id: gameId,
|
||||
|
||||
const versionSettings = ref<Partial<ImportGameVersion>>({
|
||||
launches: [],
|
||||
onlySetup: false,
|
||||
});
|
||||
|
||||
const versionGuesses =
|
||||
@ -540,7 +552,7 @@ async function updateCurrentlySelectedVersion(value: number) {
|
||||
const options = await $dropFetch(
|
||||
`/api/v1/admin/import/version/preload?id=${encodeURIComponent(
|
||||
gameId,
|
||||
)}&version=${encodeURIComponent(version)}`,
|
||||
)}&version=${encodeURIComponent(version)}&mode=game`,
|
||||
);
|
||||
versionGuesses.value = options.map((e) => ({
|
||||
...e,
|
||||
@ -556,6 +568,7 @@ async function startImport() {
|
||||
body: {
|
||||
id: gameId,
|
||||
version: versions[currentlySelectedVersion.value],
|
||||
mode: "game",
|
||||
...versionSettings.value,
|
||||
},
|
||||
});
|
||||
@ -572,4 +585,26 @@ function startImport_wrapper() {
|
||||
importLoading.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
const setupModes: Array<{
|
||||
id: string;
|
||||
value: boolean;
|
||||
title: string;
|
||||
description: string;
|
||||
}> = [
|
||||
{
|
||||
id: "portable",
|
||||
value: false,
|
||||
title: "Portable",
|
||||
description:
|
||||
"This mode is for games that are designed to be launched directly from the install directory. Drop works best with these.",
|
||||
},
|
||||
{
|
||||
id: "setup",
|
||||
value: true,
|
||||
title: "Installer",
|
||||
description:
|
||||
"Also known as 'setup-only', this mode is for installers that modify the system directly, and install to directories like Program Files.",
|
||||
},
|
||||
];
|
||||
</script>
|
||||
|
||||
@ -1 +1 @@
|
||||
<template></template>
|
||||
<template><div /></template>
|
||||
|
||||
@ -72,7 +72,7 @@
|
||||
{{ $t("store.images") }}
|
||||
</h2>
|
||||
<div class="relative">
|
||||
<VueCarousel :items-to-show="1">
|
||||
<VueCarousel :items-to-show="1" :wrap-around="true">
|
||||
<VueSlide
|
||||
v-for="image in game.mImageCarouselObjectIds"
|
||||
:key="image"
|
||||
|
||||
@ -190,7 +190,7 @@
|
||||
{{ game.mShortDescription }}
|
||||
</p>
|
||||
<div class="mt-6 py-4 rounded">
|
||||
<VueCarousel :items-to-show="1">
|
||||
<VueCarousel :items-to-show="1" :wrap-around="true">
|
||||
<VueSlide
|
||||
v-for="image in game.mImageCarouselObjectIds"
|
||||
:key="image"
|
||||
|
||||
@ -19,7 +19,7 @@ export default withNuxt([
|
||||
},
|
||||
],
|
||||
"@intlify/vue-i18n/no-missing-keys": "error",
|
||||
"vue/multi-word-component-names": "ignore",
|
||||
"vue/multi-word-component-names": "off",
|
||||
},
|
||||
settings: {
|
||||
"vue-i18n": {
|
||||
|
||||
@ -263,6 +263,7 @@ export default defineNuxtConfig({
|
||||
"https://www.giantbomb.com",
|
||||
"https://images.pcgamingwiki.com",
|
||||
"https://images.igdb.com",
|
||||
"https://*.steamstatic.com",
|
||||
],
|
||||
},
|
||||
strictTransportSecurity: false,
|
||||
|
||||
@ -21,7 +21,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@discordapp/twemoji": "^16.0.1",
|
||||
"@drop-oss/droplet": "3.0.1",
|
||||
"@drop-oss/droplet": "3.2.0",
|
||||
"@headlessui/vue": "^1.7.23",
|
||||
"@heroicons/vue": "^2.1.5",
|
||||
"@nuxt/fonts": "^0.11.0",
|
||||
@ -32,7 +32,7 @@
|
||||
"@vueuse/nuxt": "13.6.0",
|
||||
"argon2": "^0.43.0",
|
||||
"arktype": "^2.1.10",
|
||||
"axios": "^1.7.7",
|
||||
"axios": "^1.12.0",
|
||||
"bcryptjs": "^3.0.2",
|
||||
"cheerio": "^1.0.0",
|
||||
"cookie-es": "^2.0.0",
|
||||
|
||||
159
pnpm-lock.yaml
generated
159
pnpm-lock.yaml
generated
@ -12,8 +12,8 @@ importers:
|
||||
specifier: ^16.0.1
|
||||
version: 16.0.1
|
||||
'@drop-oss/droplet':
|
||||
specifier: 3.0.1
|
||||
version: 3.0.1
|
||||
specifier: 3.2.0
|
||||
version: 3.2.0
|
||||
'@headlessui/vue':
|
||||
specifier: ^1.7.23
|
||||
version: 1.7.23(vue@3.5.21(typescript@5.9.2))
|
||||
@ -45,7 +45,7 @@ importers:
|
||||
specifier: ^2.1.10
|
||||
version: 2.1.22
|
||||
axios:
|
||||
specifier: ^1.7.7
|
||||
specifier: ^1.12.0
|
||||
version: 1.12.2
|
||||
bcryptjs:
|
||||
specifier: ^3.0.2
|
||||
@ -387,74 +387,83 @@ packages:
|
||||
'@discordapp/twemoji@16.0.1':
|
||||
resolution: {integrity: sha512-figLiBWzjS5cyrAjLaGjM8AAaowO3qvK8rg5bA2dElB4qsaPMvBVlFDMO2d3x+nC1igt7kgWH4dvNmvvUHUF8w==}
|
||||
|
||||
'@drop-oss/droplet-darwin-arm64@3.0.1':
|
||||
resolution: {integrity: sha512-LXe8vsXUBL96boI78H6oXpSaPVwF4cCwJ5l/QVtsOWMebNo6gk9wICDZ+5IoR/Ol32t1a1lk+DjbD1zeGenPxg==}
|
||||
'@drop-oss/droplet-darwin-arm64@3.2.0':
|
||||
resolution: {integrity: sha512-dH/vRFxuLjOzYBBvDG140wKcx4LmFxBJ5iTjZrWzV641wiRjx8B38niWXuqZ2ZADkCL4muOvgRGFJ4W1N/j6jQ==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
|
||||
'@drop-oss/droplet-darwin-universal@3.0.1':
|
||||
resolution: {integrity: sha512-Mf2gjC24u6s8djV/3slZvwdr4+h0qBu2OYXBUSDfR4H/VJwV5TstnWVKF+U8d1hjmHE9eLO8elbGNnpQmSoTOQ==}
|
||||
'@drop-oss/droplet-darwin-universal@3.2.0':
|
||||
resolution: {integrity: sha512-k7Xhzs2mXrQcm3SLhLNDBkUaCWqtbQ6dyme1ubsG9PZEcvv25T//8CNVFEsHVZTKqj5nF41iSh4Wz1Qn6VxkVw==}
|
||||
engines: {node: '>= 10'}
|
||||
os: [darwin]
|
||||
|
||||
'@drop-oss/droplet-darwin-x64@3.0.1':
|
||||
resolution: {integrity: sha512-4IIDl/E+hzZ2Vt9m4FMPlZEXwj1EwE6qXyUidACK6TTFqpjLpsEHKuhv1FOxGyJ8qkvagtyPCc+cs1TxoZD6FA==}
|
||||
'@drop-oss/droplet-darwin-x64@3.2.0':
|
||||
resolution: {integrity: sha512-GvRwQrtcC1Dq6YyXxBGSFj+WasnIa1dk9t2lCaR9OQdh3qp2did21o2poo1Sgdjg+mI2lUdgZ6w0yXJlL1vl+A==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
|
||||
'@drop-oss/droplet-linux-arm64-gnu@3.0.1':
|
||||
resolution: {integrity: sha512-klGvlLf1xSMT3iYsIAaBbmbir1ZJWtcVyOMUlsfc1lkJ8mgyB+PrW4BsnYj7Pp4G34n7WsOChjC8TdJDBBuBWg==}
|
||||
'@drop-oss/droplet-linux-arm64-gnu@3.2.0':
|
||||
resolution: {integrity: sha512-ZqH0xTEeSeJF77vy8rZDxHEV8JMaN0khdg6ptpnbBfc56J5jt6wS3NlHK8M0ZVlDqqZnXMS1vUO0b6rfmQodKw==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
'@drop-oss/droplet-linux-arm64-musl@3.0.1':
|
||||
resolution: {integrity: sha512-oOjvGETlrJGC1RlNhUoVS9N89Rn/0DqBauVz3BBFjJTKSd5jU3/gLzwgmfkKDGVEU5lyGPAn2WQroiESEG9wdA==}
|
||||
'@drop-oss/droplet-linux-arm64-musl@3.2.0':
|
||||
resolution: {integrity: sha512-TTw44PggYfp3RJkvNhXH89duuuvONEA8c8oRBCzCczRf3hDnbzCQLaB1UlnIlESsJZXXiFSDIBV2/0kkpB+Ukg==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
'@drop-oss/droplet-linux-riscv64-gnu@3.0.1':
|
||||
resolution: {integrity: sha512-Zf3gUsWq9Hqb275MOi7PJDhmJz7Qa/Y1XMen880bxPaOeDFqFOoKUxUr2/qv1MYp6tT3zO27NprGsHirYWqsyA==}
|
||||
'@drop-oss/droplet-linux-riscv64-gnu@3.2.0':
|
||||
resolution: {integrity: sha512-Ee/PfkoG8pm/9C3LFXJleIi5N8V5cK+44p+iDaneAo6gj5k67zYzuga3mJVswTgd3fncG1cw+xPqBl4PUWc1pg==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
|
||||
'@drop-oss/droplet-linux-x64-gnu@3.0.1':
|
||||
resolution: {integrity: sha512-sskblycJdtNJVnRHjPHhwHkQUfQNaDIWDzXOzEaBPOcDKqYA7od7VMDAseqBkrKDn7l8bBUtRXFAipdsO8hffw==}
|
||||
'@drop-oss/droplet-linux-x64-gnu@3.2.0':
|
||||
resolution: {integrity: sha512-L2M/MEoe5Y74MTtzpEWHIvdyRSPLgM1WLzpb/xRNCWe8d6FcUFDgdMlbd6rDj5t4Q6JEzyMIHUciVRaYIv+ShA==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
'@drop-oss/droplet-linux-x64-musl@3.0.1':
|
||||
resolution: {integrity: sha512-lh+1M6UAf5+ET1/ZEFRsB3shFHjkT/9Ql9akr/vyUue91TWPmP71meqVkCugWDhP6lxBt56jg2VVrJfmPAsK6w==}
|
||||
'@drop-oss/droplet-linux-x64-musl@3.2.0':
|
||||
resolution: {integrity: sha512-F/uQUAHWbhiiAtoyKHQHPgjG7jJd8pQX6sCgdf5ufCdwFLvHEdu9pO0qN+xpzaACceIKX4Vip0vUwQwEzYhAKA==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
'@drop-oss/droplet-win32-arm64-msvc@3.0.1':
|
||||
resolution: {integrity: sha512-caQDPoDNJyyJXUEijw+hGTy0wmCrW5efTqBwnvMcQ282EOilg1d5WeJ31pfEcuLYF4MK1t9uaLcG6jZ9YLtzEQ==}
|
||||
'@drop-oss/droplet-win32-arm64-msvc@3.2.0':
|
||||
resolution: {integrity: sha512-x7i1KKL8vQGcXbKIyH56LCEdQxLKNEk/KFjuD/YGrbBJ/+Q+fh46hLK+Sx4I/HzPHecd5g3xc2kVgO7+DgjhYA==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
|
||||
'@drop-oss/droplet-win32-x64-msvc@3.0.1':
|
||||
resolution: {integrity: sha512-bp8KwewF/T3JkVeJWkg86U3b0cGQD9i8k92x6HYPtnF5nLPAb2UIUEJgmYYFNPFe36RECBV7PIIG0ujdT1ELQw==}
|
||||
'@drop-oss/droplet-win32-x64-msvc@3.2.0':
|
||||
resolution: {integrity: sha512-lC8a456IQ0ArzX40IlStolV4GIdl26xF9PikcuQ9r+n4VDqWSHb8A0Wwj87leU3QdoMu+Y2nlA1QHKgpVSEuoQ==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
'@drop-oss/droplet@3.0.1':
|
||||
resolution: {integrity: sha512-YhtgpwNqEHO8R03yf9Xb5LXuaLWkQvY+2lxOD1PwzpGI5V9PKlDE+x1IJBmdBF5bDPDGk9MxQidGtnYQuAEBEA==}
|
||||
'@drop-oss/droplet@3.2.0':
|
||||
resolution: {integrity: sha512-+3zw3MPriMrj8HlKAq2VTlXEPOXN0homusjmQcBRzVx7GjtGvb5Y9YIHs16qfn8zdTEDi5twrtsUBQYkVjU2bQ==}
|
||||
engines: {node: '>= 10'}
|
||||
|
||||
'@emnapi/core@1.5.0':
|
||||
resolution: {integrity: sha512-sbP8GzB1WDzacS8fgNPpHlp6C9VZe+SJP3F90W9rLemaQj2PzIuTEl1qDOYQf58YIpyjViI24y9aPWCjEzY2cg==}
|
||||
'@emnapi/core@1.4.5':
|
||||
resolution: {integrity: sha512-XsLw1dEOpkSX/WucdqUhPWP7hDxSvZiY+fsUC14h+FtQ2Ifni4znbBt8punRX+Uj2JG/uDb8nEHVKvrVlvdZ5Q==}
|
||||
|
||||
'@emnapi/runtime@1.5.0':
|
||||
resolution: {integrity: sha512-97/BJ3iXHww3djw6hYIfErCZFee7qCtrneuLa20UXFCOTCfBM2cvQHjWJ2EG0s0MtdNwInarqCTz35i4wWXHsQ==}
|
||||
'@emnapi/core@1.6.0':
|
||||
resolution: {integrity: sha512-zq/ay+9fNIJJtJiZxdTnXS20PllcYMX3OE23ESc4HK/bdYu3cOWYVhsOhVnXALfU/uqJIxn5NBPd9z4v+SfoSg==}
|
||||
|
||||
'@emnapi/runtime@1.4.5':
|
||||
resolution: {integrity: sha512-++LApOtY0pEEz1zrd9vy1/zXVaVJJ/EbAF3u0fXIzPJEDtnITsBGbbK0EkM72amhl/R5b+5xx0Y/QhcVOpuulg==}
|
||||
|
||||
'@emnapi/runtime@1.6.0':
|
||||
resolution: {integrity: sha512-obtUmAHTMjll499P+D9A3axeJFlhdjOWdKUNs/U6QIGT7V5RjcUW1xToAzjvmgTSQhDbYn/NwfTRoJcQ2rNBxA==}
|
||||
|
||||
'@emnapi/wasi-threads@1.0.4':
|
||||
resolution: {integrity: sha512-PJR+bOmMOPH8AtcTGAyYNiuJ3/Fcoj2XN/gBEWzDIKh254XO+mM9XoXHk5GNEhodxeMznbg7BlRojVbKN+gC6g==}
|
||||
|
||||
'@emnapi/wasi-threads@1.1.0':
|
||||
resolution: {integrity: sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==}
|
||||
@ -851,8 +860,8 @@ packages:
|
||||
'@napi-rs/wasm-runtime@0.2.12':
|
||||
resolution: {integrity: sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==}
|
||||
|
||||
'@napi-rs/wasm-runtime@1.0.5':
|
||||
resolution: {integrity: sha512-TBr9Cf9onSAS2LQ2+QHx6XcC6h9+RIzJgbqG3++9TUZSH204AwEy5jg3BTQ0VATsyoGj4ee49tN/y6rvaOOtcg==}
|
||||
'@napi-rs/wasm-runtime@1.0.7':
|
||||
resolution: {integrity: sha512-SeDnOO0Tk7Okiq6DbXmmBODgOAb9dp9gjlphokTUxmt8U3liIP1ZsozBahH69j/RJv+Rfs6IwUKHTgQYJ/HBAw==}
|
||||
|
||||
'@nodelib/fs.scandir@2.1.5':
|
||||
resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
|
||||
@ -1804,6 +1813,9 @@ packages:
|
||||
'@twemoji/parser@16.0.0':
|
||||
resolution: {integrity: sha512-jmuIjkp3OIaEemwMy3sArBwZSuZkRqmueGwRe2Zk4cFzbUJISFBJSZLDUUBNIgq3c+nY49ideYN2OiII6JUqwA==}
|
||||
|
||||
'@tybys/wasm-util@0.10.0':
|
||||
resolution: {integrity: sha512-VyyPYFlOMNylG45GoAe0xDoLwWuowvf92F9kySqzYh8vmYm7D2u4iUJKa1tOUpS70Ku13ASrOkS4ScXFsTaCNQ==}
|
||||
|
||||
'@tybys/wasm-util@0.10.1':
|
||||
resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==}
|
||||
|
||||
@ -5802,56 +5814,72 @@ snapshots:
|
||||
jsonfile: 5.0.0
|
||||
universalify: 0.1.2
|
||||
|
||||
'@drop-oss/droplet-darwin-arm64@3.0.1':
|
||||
'@drop-oss/droplet-darwin-arm64@3.2.0':
|
||||
optional: true
|
||||
|
||||
'@drop-oss/droplet-darwin-universal@3.0.1':
|
||||
'@drop-oss/droplet-darwin-universal@3.2.0':
|
||||
optional: true
|
||||
|
||||
'@drop-oss/droplet-darwin-x64@3.0.1':
|
||||
'@drop-oss/droplet-darwin-x64@3.2.0':
|
||||
optional: true
|
||||
|
||||
'@drop-oss/droplet-linux-arm64-gnu@3.0.1':
|
||||
'@drop-oss/droplet-linux-arm64-gnu@3.2.0':
|
||||
optional: true
|
||||
|
||||
'@drop-oss/droplet-linux-arm64-musl@3.0.1':
|
||||
'@drop-oss/droplet-linux-arm64-musl@3.2.0':
|
||||
optional: true
|
||||
|
||||
'@drop-oss/droplet-linux-riscv64-gnu@3.0.1':
|
||||
'@drop-oss/droplet-linux-riscv64-gnu@3.2.0':
|
||||
optional: true
|
||||
|
||||
'@drop-oss/droplet-linux-x64-gnu@3.0.1':
|
||||
'@drop-oss/droplet-linux-x64-gnu@3.2.0':
|
||||
optional: true
|
||||
|
||||
'@drop-oss/droplet-linux-x64-musl@3.0.1':
|
||||
'@drop-oss/droplet-linux-x64-musl@3.2.0':
|
||||
optional: true
|
||||
|
||||
'@drop-oss/droplet-win32-arm64-msvc@3.0.1':
|
||||
'@drop-oss/droplet-win32-arm64-msvc@3.2.0':
|
||||
optional: true
|
||||
|
||||
'@drop-oss/droplet-win32-x64-msvc@3.0.1':
|
||||
'@drop-oss/droplet-win32-x64-msvc@3.2.0':
|
||||
optional: true
|
||||
|
||||
'@drop-oss/droplet@3.0.1':
|
||||
'@drop-oss/droplet@3.2.0':
|
||||
optionalDependencies:
|
||||
'@drop-oss/droplet-darwin-arm64': 3.0.1
|
||||
'@drop-oss/droplet-darwin-universal': 3.0.1
|
||||
'@drop-oss/droplet-darwin-x64': 3.0.1
|
||||
'@drop-oss/droplet-linux-arm64-gnu': 3.0.1
|
||||
'@drop-oss/droplet-linux-arm64-musl': 3.0.1
|
||||
'@drop-oss/droplet-linux-riscv64-gnu': 3.0.1
|
||||
'@drop-oss/droplet-linux-x64-gnu': 3.0.1
|
||||
'@drop-oss/droplet-linux-x64-musl': 3.0.1
|
||||
'@drop-oss/droplet-win32-arm64-msvc': 3.0.1
|
||||
'@drop-oss/droplet-win32-x64-msvc': 3.0.1
|
||||
'@drop-oss/droplet-darwin-arm64': 3.2.0
|
||||
'@drop-oss/droplet-darwin-universal': 3.2.0
|
||||
'@drop-oss/droplet-darwin-x64': 3.2.0
|
||||
'@drop-oss/droplet-linux-arm64-gnu': 3.2.0
|
||||
'@drop-oss/droplet-linux-arm64-musl': 3.2.0
|
||||
'@drop-oss/droplet-linux-riscv64-gnu': 3.2.0
|
||||
'@drop-oss/droplet-linux-x64-gnu': 3.2.0
|
||||
'@drop-oss/droplet-linux-x64-musl': 3.2.0
|
||||
'@drop-oss/droplet-win32-arm64-msvc': 3.2.0
|
||||
'@drop-oss/droplet-win32-x64-msvc': 3.2.0
|
||||
|
||||
'@emnapi/core@1.5.0':
|
||||
'@emnapi/core@1.4.5':
|
||||
dependencies:
|
||||
'@emnapi/wasi-threads': 1.0.4
|
||||
tslib: 2.8.1
|
||||
optional: true
|
||||
|
||||
'@emnapi/core@1.6.0':
|
||||
dependencies:
|
||||
'@emnapi/wasi-threads': 1.1.0
|
||||
tslib: 2.8.1
|
||||
optional: true
|
||||
|
||||
'@emnapi/runtime@1.5.0':
|
||||
'@emnapi/runtime@1.4.5':
|
||||
dependencies:
|
||||
tslib: 2.8.1
|
||||
optional: true
|
||||
|
||||
'@emnapi/runtime@1.6.0':
|
||||
dependencies:
|
||||
tslib: 2.8.1
|
||||
optional: true
|
||||
|
||||
'@emnapi/wasi-threads@1.0.4':
|
||||
dependencies:
|
||||
tslib: 2.8.1
|
||||
optional: true
|
||||
@ -6228,15 +6256,15 @@ snapshots:
|
||||
|
||||
'@napi-rs/wasm-runtime@0.2.12':
|
||||
dependencies:
|
||||
'@emnapi/core': 1.5.0
|
||||
'@emnapi/runtime': 1.5.0
|
||||
'@tybys/wasm-util': 0.10.1
|
||||
'@emnapi/core': 1.4.5
|
||||
'@emnapi/runtime': 1.4.5
|
||||
'@tybys/wasm-util': 0.10.0
|
||||
optional: true
|
||||
|
||||
'@napi-rs/wasm-runtime@1.0.5':
|
||||
'@napi-rs/wasm-runtime@1.0.7':
|
||||
dependencies:
|
||||
'@emnapi/core': 1.5.0
|
||||
'@emnapi/runtime': 1.5.0
|
||||
'@emnapi/core': 1.6.0
|
||||
'@emnapi/runtime': 1.6.0
|
||||
'@tybys/wasm-util': 0.10.1
|
||||
optional: true
|
||||
|
||||
@ -6722,7 +6750,7 @@ snapshots:
|
||||
|
||||
'@oxc-minify/binding-wasm32-wasi@0.87.0':
|
||||
dependencies:
|
||||
'@napi-rs/wasm-runtime': 1.0.5
|
||||
'@napi-rs/wasm-runtime': 1.0.7
|
||||
optional: true
|
||||
|
||||
'@oxc-minify/binding-win32-arm64-msvc@0.87.0':
|
||||
@ -6807,7 +6835,7 @@ snapshots:
|
||||
|
||||
'@oxc-parser/binding-wasm32-wasi@0.87.0':
|
||||
dependencies:
|
||||
'@napi-rs/wasm-runtime': 1.0.5
|
||||
'@napi-rs/wasm-runtime': 1.0.7
|
||||
optional: true
|
||||
|
||||
'@oxc-parser/binding-win32-arm64-msvc@0.70.0':
|
||||
@ -6870,7 +6898,7 @@ snapshots:
|
||||
|
||||
'@oxc-transform/binding-wasm32-wasi@0.87.0':
|
||||
dependencies:
|
||||
'@napi-rs/wasm-runtime': 1.0.5
|
||||
'@napi-rs/wasm-runtime': 1.0.7
|
||||
optional: true
|
||||
|
||||
'@oxc-transform/binding-win32-arm64-msvc@0.87.0':
|
||||
@ -7258,6 +7286,11 @@ snapshots:
|
||||
|
||||
'@twemoji/parser@16.0.0': {}
|
||||
|
||||
'@tybys/wasm-util@0.10.0':
|
||||
dependencies:
|
||||
tslib: 2.8.1
|
||||
optional: true
|
||||
|
||||
'@tybys/wasm-util@0.10.1':
|
||||
dependencies:
|
||||
tslib: 2.8.1
|
||||
|
||||
@ -1,4 +1,8 @@
|
||||
onlyBuiltDependencies:
|
||||
- '@prisma/client'
|
||||
- '@prisma/engines'
|
||||
- '@tailwindcss/oxide'
|
||||
- esbuild
|
||||
- prisma
|
||||
|
||||
shamefullyHoist: true
|
||||
|
||||
@ -0,0 +1,8 @@
|
||||
-- AlterEnum
|
||||
ALTER TYPE "MetadataSource" ADD VALUE 'Steam';
|
||||
|
||||
-- DropIndex
|
||||
DROP INDEX "GameTag_name_idx";
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "GameTag_name_idx" ON "GameTag" USING GIST ("name" gist_trgm_ops(siglen=32));
|
||||
@ -0,0 +1,41 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- A unique constraint covering the columns `[installRId]` on the table `LaunchOption` will be added. If there are existing duplicate values, this will fail.
|
||||
- A unique constraint covering the columns `[uninstallRId]` on the table `LaunchOption` will be added. If there are existing duplicate values, this will fail.
|
||||
- A unique constraint covering the columns `[installId]` on the table `RedistVersion` will be added. If there are existing duplicate values, this will fail.
|
||||
- A unique constraint covering the columns `[uninstallId]` on the table `RedistVersion` will be added. If there are existing duplicate values, this will fail.
|
||||
|
||||
*/
|
||||
-- DropIndex
|
||||
DROP INDEX "public"."GameTag_name_idx";
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "public"."LaunchOption" ADD COLUMN "installRId" TEXT,
|
||||
ADD COLUMN "uninstallRId" TEXT;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "public"."RedistVersion" ADD COLUMN "installId" TEXT,
|
||||
ADD COLUMN "onlySetup" BOOLEAN NOT NULL DEFAULT false,
|
||||
ADD COLUMN "uninstallId" TEXT;
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "GameTag_name_idx" ON "public"."GameTag" USING GIST ("name" gist_trgm_ops(siglen=32));
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "LaunchOption_installRId_key" ON "public"."LaunchOption"("installRId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "LaunchOption_uninstallRId_key" ON "public"."LaunchOption"("uninstallRId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "RedistVersion_installId_key" ON "public"."RedistVersion"("installId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "RedistVersion_uninstallId_key" ON "public"."RedistVersion"("uninstallId");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "public"."RedistVersion" ADD CONSTRAINT "RedistVersion_installId_fkey" FOREIGN KEY ("installId") REFERENCES "public"."LaunchOption"("launchId") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "public"."RedistVersion" ADD CONSTRAINT "RedistVersion_uninstallId_fkey" FOREIGN KEY ("uninstallId") REFERENCES "public"."LaunchOption"("launchId") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
@ -0,0 +1,15 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- Added the required column `versionIndex` to the `RedistVersion` table without a default value. This is not possible if the table is not empty.
|
||||
|
||||
*/
|
||||
-- DropIndex
|
||||
DROP INDEX "public"."GameTag_name_idx";
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "public"."RedistVersion" ADD COLUMN "delta" BOOLEAN NOT NULL DEFAULT false,
|
||||
ADD COLUMN "versionIndex" INTEGER NOT NULL;
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "GameTag_name_idx" ON "public"."GameTag" USING GIST ("name" gist_trgm_ops(siglen=32));
|
||||
@ -49,6 +49,11 @@ model LaunchOption {
|
||||
uninstallGId String? @unique
|
||||
uninstallGVersion GameVersion? @relation(name: "uninstall")
|
||||
|
||||
installRId String? @unique
|
||||
installRVersion RedistVersion? @relation(name: "install_redist")
|
||||
uninstallRId String? @unique
|
||||
uninstallRVersion RedistVersion? @relation(name: "uninstall_redist")
|
||||
|
||||
name String
|
||||
description String
|
||||
|
||||
@ -125,8 +130,17 @@ model RedistVersion {
|
||||
versionId String @id
|
||||
version Version @relation(fields: [versionId], references: [versionId], onDelete: Cascade, onUpdate: Cascade)
|
||||
|
||||
installId String? @unique
|
||||
install LaunchOption? @relation(name: "install_redist", fields: [installId], references: [launchId])
|
||||
uninstallId String? @unique
|
||||
uninstall LaunchOption? @relation(name: "uninstall_redist", fields: [uninstallId], references: [launchId])
|
||||
onlySetup Boolean @default(false)
|
||||
|
||||
launches LaunchOption[]
|
||||
|
||||
versionIndex Int
|
||||
delta Boolean @default(false)
|
||||
|
||||
gameDependees GameVersion[]
|
||||
dlcDependees DLCVersion[]
|
||||
|
||||
|
||||
@ -5,6 +5,7 @@ enum MetadataSource {
|
||||
IGDB
|
||||
Metacritic
|
||||
OpenCritic
|
||||
Steam
|
||||
}
|
||||
|
||||
model Game {
|
||||
|
||||
@ -1,9 +1,7 @@
|
||||
import { type } from "arktype";
|
||||
import { readDropValidatedBody, throwingArktype } from "~~/server/arktype";
|
||||
import aclManager from "~~/server/internal/acls";
|
||||
import prisma from "~~/server/internal/db/database";
|
||||
import libraryManager from "~~/server/internal/library";
|
||||
import { convertIDToLink } from "~~/server/internal/platform/link";
|
||||
|
||||
export const LaunchCommands = type({
|
||||
name: "string > 0",
|
||||
@ -12,14 +10,18 @@ export const LaunchCommands = type({
|
||||
launchArgs: "string = ''",
|
||||
}).array();
|
||||
|
||||
export const ImportVersion = type({
|
||||
const ImportVersionBase = type({
|
||||
id: "string",
|
||||
version: "string",
|
||||
name: "string?",
|
||||
|
||||
platform: "string",
|
||||
onlySetup: "boolean = false",
|
||||
delta: "boolean = false",
|
||||
});
|
||||
|
||||
const ImportGameVersion = type({
|
||||
mode: "'game'",
|
||||
onlySetup: "boolean = false",
|
||||
umuId: "string = ''",
|
||||
|
||||
install: "string?",
|
||||
@ -27,7 +29,26 @@ export const ImportVersion = type({
|
||||
launches: LaunchCommands,
|
||||
uninstall: "string?",
|
||||
uninstallArgs: "string?",
|
||||
}).configure(throwingArktype);
|
||||
});
|
||||
|
||||
const ImportRedistVersion = type({
|
||||
mode: "'redist'",
|
||||
install: "string?",
|
||||
installArgs: "string?",
|
||||
launches: LaunchCommands,
|
||||
uninstall: "string?",
|
||||
uninstallArgs: "string?",
|
||||
});
|
||||
|
||||
export const ImportVersion = ImportVersionBase.and(
|
||||
ImportGameVersion.or(ImportRedistVersion),
|
||||
).configure(throwingArktype);
|
||||
|
||||
export type ImportGameVersion = typeof ImportVersionBase.infer &
|
||||
typeof ImportGameVersion.infer;
|
||||
|
||||
export type ImportRedistVersion = typeof ImportVersionBase.infer &
|
||||
typeof ImportRedistVersion.infer;
|
||||
|
||||
export default defineEventHandler(async (h3) => {
|
||||
const allowed = await aclManager.allowSystemACL(h3, ["import:version:new"]);
|
||||
@ -35,48 +56,10 @@ export default defineEventHandler(async (h3) => {
|
||||
|
||||
const body = await readDropValidatedBody(h3, ImportVersion);
|
||||
|
||||
const platform = await convertIDToLink(body.platform);
|
||||
if (!platform)
|
||||
throw createError({ statusCode: 400, message: "Invalid platform." });
|
||||
|
||||
if (body.delta) {
|
||||
const validOverlayVersions = await prisma.gameVersion.count({
|
||||
where: {
|
||||
version: {
|
||||
gameId: body.id,
|
||||
},
|
||||
delta: false,
|
||||
platform,
|
||||
},
|
||||
});
|
||||
if (validOverlayVersions == 0)
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
message:
|
||||
"Update mode requires a pre-existing version for this platform.",
|
||||
});
|
||||
}
|
||||
|
||||
if (body.onlySetup) {
|
||||
if (!body.install)
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
message: 'Install required in "setup mode".',
|
||||
});
|
||||
} else {
|
||||
if (!body.delta && body.launches.length == 0)
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
message:
|
||||
"At least one launch command is required for non-delta versions",
|
||||
});
|
||||
}
|
||||
|
||||
// startup & delta require more complex checking logic
|
||||
const taskId = await libraryManager.importVersion(
|
||||
body.id,
|
||||
body.version,
|
||||
"game",
|
||||
body,
|
||||
);
|
||||
if (!taskId)
|
||||
|
||||
@ -1,22 +1,26 @@
|
||||
import { ArkErrors, type } from "arktype";
|
||||
import aclManager from "~~/server/internal/acls";
|
||||
import libraryManager from "~~/server/internal/library";
|
||||
import libraryManager, { VersionImportModes } from "~~/server/internal/library";
|
||||
|
||||
export const PreloadQuery = type({
|
||||
id: "string",
|
||||
version: "string",
|
||||
mode: type.enumerated(...VersionImportModes),
|
||||
});
|
||||
|
||||
export default defineEventHandler(async (h3) => {
|
||||
const allowed = await aclManager.allowSystemACL(h3, ["import:version:read"]);
|
||||
if (!allowed) throw createError({ statusCode: 403 });
|
||||
|
||||
const query = await getQuery(h3);
|
||||
const gameId = query.id?.toString();
|
||||
const versionName = query.version?.toString();
|
||||
if (!gameId || !versionName)
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
message: "Missing id or version in request params",
|
||||
});
|
||||
const rawQuery = await getQuery(h3);
|
||||
const query = PreloadQuery(rawQuery);
|
||||
if (query instanceof ArkErrors)
|
||||
throw createError({ statusCode: 400, message: query.summary });
|
||||
|
||||
const preload = await libraryManager.fetchUnimportedVersionInformation(
|
||||
gameId,
|
||||
versionName,
|
||||
query.id,
|
||||
query.mode,
|
||||
query.version,
|
||||
);
|
||||
if (!preload)
|
||||
throw createError({
|
||||
|
||||
@ -2,7 +2,7 @@ import { type } from "arktype";
|
||||
import { readDropValidatedBody, throwingArktype } from "~~/server/arktype";
|
||||
import aclManager from "~~/server/internal/acls";
|
||||
import taskHandler from "~~/server/internal/tasks";
|
||||
import { TASK_GROUPS, type TaskGroup } from "~~/server/internal/tasks/group";
|
||||
import { TASK_GROUPS } from "~~/server/internal/tasks/group";
|
||||
|
||||
const StartTask = type({
|
||||
taskGroup: type.enumerated(...TASK_GROUPS),
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { type } from "arktype";
|
||||
import { ClientCapabilities } from "~~/prisma/client/enums";
|
||||
import type { ClientCapabilities } from "~~/prisma/client/enums";
|
||||
import { readDropValidatedBody, throwingArktype } from "~~/server/arktype";
|
||||
import type {
|
||||
CapabilityConfiguration,
|
||||
@ -7,7 +7,7 @@ import type {
|
||||
import capabilityManager, {
|
||||
validCapabilities,
|
||||
} from "~~/server/internal/clients/capabilities";
|
||||
import clientHandler, { AuthMode, AuthModes } from "~~/server/internal/clients/handler";
|
||||
import clientHandler, { AuthModes } from "~~/server/internal/clients/handler";
|
||||
import { parsePlatform } from "~~/server/internal/utils/parseplatform";
|
||||
|
||||
const ClientAuthInitiate = type({
|
||||
|
||||
@ -1,9 +1,7 @@
|
||||
import { type } from "arktype";
|
||||
import { ClientCapabilities } from "~~/prisma/client/enums";
|
||||
import { readDropValidatedBody, throwingArktype } from "~~/server/arktype";
|
||||
import capabilityManager, {
|
||||
validCapabilities,
|
||||
} from "~~/server/internal/clients/capabilities";
|
||||
import capabilityManager from "~~/server/internal/clients/capabilities";
|
||||
import { defineClientEventHandler } from "~~/server/internal/clients/event-handler";
|
||||
import notificationSystem from "~~/server/internal/notifications";
|
||||
|
||||
|
||||
@ -17,7 +17,8 @@ const StoreRead = type({
|
||||
company: "string?",
|
||||
companyActions: "string = 'published,developed'",
|
||||
|
||||
sort: "'default' | 'newest' | 'recent' = 'default'",
|
||||
sort: "'default' | 'newest' | 'recent' | 'name' = 'default'",
|
||||
order: "'asc' | 'desc' = 'desc'",
|
||||
});
|
||||
|
||||
export default defineEventHandler(async (h3) => {
|
||||
@ -101,10 +102,13 @@ export default defineEventHandler(async (h3) => {
|
||||
switch (options.sort) {
|
||||
case "default":
|
||||
case "newest":
|
||||
sort.mReleased = "desc";
|
||||
sort.mReleased = options.order;
|
||||
break;
|
||||
case "recent":
|
||||
sort.created = "desc";
|
||||
sort.created = options.order;
|
||||
break;
|
||||
case "name":
|
||||
sort.mName = options.order;
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
@ -18,7 +18,10 @@ import type {
|
||||
GameVersionCreateInput,
|
||||
LaunchOptionCreateManyInput,
|
||||
VersionCreateArgs,
|
||||
VersionWhereInput,
|
||||
} from "~~/prisma/client/models";
|
||||
import type { PlatformLink } from "~~/prisma/client/client";
|
||||
import { convertIDToLink } from "../platform/link";
|
||||
|
||||
export const VersionImportModes = ["game", "redist"] as const;
|
||||
export type VersionImportMode = (typeof VersionImportModes)[number];
|
||||
@ -216,7 +219,17 @@ class LibraryManager {
|
||||
return await this.fetchLibraryObjectWithStatus(redists);
|
||||
}
|
||||
|
||||
private async fetchLibraryPath(id: string, mode: VersionImportMode) {
|
||||
private async fetchLibraryPath(
|
||||
id: string,
|
||||
mode: VersionImportMode,
|
||||
platform?: PlatformLink,
|
||||
): Promise<
|
||||
| [
|
||||
{ mName: string; libraryId: string; libraryPath: string } | null,
|
||||
VersionWhereInput,
|
||||
]
|
||||
| undefined
|
||||
> {
|
||||
switch (mode) {
|
||||
case "game":
|
||||
return [
|
||||
@ -224,8 +237,8 @@ class LibraryManager {
|
||||
where: { id },
|
||||
select: { mName: true, libraryId: true, libraryPath: true },
|
||||
}),
|
||||
{ gameId: id },
|
||||
] as const;
|
||||
{ gameId: id, gameVersions: { some: { platform } } },
|
||||
];
|
||||
case "redist":
|
||||
return [
|
||||
await prisma.redist.findUnique({
|
||||
@ -233,7 +246,7 @@ class LibraryManager {
|
||||
select: { mName: true, libraryId: true, libraryPath: true },
|
||||
}),
|
||||
{ redistId: id },
|
||||
] as const;
|
||||
];
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
@ -241,11 +254,10 @@ class LibraryManager {
|
||||
private createVersionOptions(
|
||||
id: string,
|
||||
currentIndex: number,
|
||||
mode: VersionImportMode,
|
||||
metadata: typeof ImportVersion.infer,
|
||||
): Partial<VersionCreateArgs["data"]> {
|
||||
switch (mode) {
|
||||
case "game":
|
||||
switch (metadata.mode) {
|
||||
case "game": {
|
||||
const installCreator = {
|
||||
install: {
|
||||
create: {
|
||||
@ -303,6 +315,7 @@ class LibraryManager {
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
case "redist":
|
||||
return {};
|
||||
}
|
||||
@ -310,18 +323,20 @@ class LibraryManager {
|
||||
|
||||
/**
|
||||
* Fetches recommendations and extra data about the version. Doesn't actually check if it's been imported.
|
||||
* @param gameId
|
||||
* @param versionName
|
||||
* @param id
|
||||
* @param version
|
||||
* @returns
|
||||
*/
|
||||
async fetchUnimportedVersionInformation(gameId: string, versionName: string) {
|
||||
const game = await prisma.game.findUnique({
|
||||
where: { id: gameId },
|
||||
select: { libraryPath: true, libraryId: true, mName: true },
|
||||
});
|
||||
if (!game || !game.libraryId) return undefined;
|
||||
async fetchUnimportedVersionInformation(
|
||||
id: string,
|
||||
mode: VersionImportMode,
|
||||
version: string,
|
||||
) {
|
||||
const value = await this.fetchLibraryPath(id, mode);
|
||||
if (!value?.[0] || !value[0].libraryId) return undefined;
|
||||
const [libraryDetails] = value;
|
||||
|
||||
const library = this.libraries.get(game.libraryId);
|
||||
const library = this.libraries.get(libraryDetails.libraryId);
|
||||
if (!library) return undefined;
|
||||
|
||||
const userPlatforms = await prisma.userPlatform.findMany({});
|
||||
@ -354,7 +369,10 @@ class LibraryManager {
|
||||
match: number;
|
||||
}> = [];
|
||||
|
||||
const files = await library.versionReaddir(game.libraryPath, versionName);
|
||||
const files = await library.versionReaddir(
|
||||
libraryDetails.libraryPath,
|
||||
version,
|
||||
);
|
||||
for (const filename of files) {
|
||||
const basename = path.basename(filename);
|
||||
const dotLocation = filename.lastIndexOf(".");
|
||||
@ -363,7 +381,7 @@ class LibraryManager {
|
||||
for (const [platform, checkExts] of Object.entries(fileExts)) {
|
||||
for (const checkExt of checkExts) {
|
||||
if (checkExt != ext) continue;
|
||||
const fuzzyValue = fuzzy(basename, game.mName);
|
||||
const fuzzyValue = fuzzy(basename, libraryDetails.mName);
|
||||
options.push({
|
||||
filename,
|
||||
platform,
|
||||
@ -404,17 +422,56 @@ class LibraryManager {
|
||||
async importVersion(
|
||||
id: string,
|
||||
version: string,
|
||||
mode: VersionImportMode,
|
||||
metadata: typeof ImportVersion.infer,
|
||||
) {
|
||||
const taskId = createVersionImportTaskId(id, version);
|
||||
|
||||
const value = await this.fetchLibraryPath(id, mode);
|
||||
if (!value || !value[0]) return undefined;
|
||||
if (metadata.mode === "game") {
|
||||
if (metadata.onlySetup) {
|
||||
if (!metadata.install)
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
message: "An install command is required in only-setup mode.",
|
||||
});
|
||||
} else {
|
||||
if (!metadata.delta && metadata.launches.length == 0)
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
message:
|
||||
"At least one launch command is required in non-delta, non-setup mode.",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const platform = await convertIDToLink(metadata.platform);
|
||||
if (!platform)
|
||||
throw createError({ statusCode: 400, message: "Invalid platform." });
|
||||
|
||||
const value = await this.fetchLibraryPath(id, metadata.mode, platform);
|
||||
if (!value || !value[0])
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
message: `${metadata.mode} not found.`,
|
||||
});
|
||||
const [libraryDetails, idFilter] = value;
|
||||
|
||||
const library = this.libraries.get(libraryDetails.libraryId);
|
||||
if (!library) return undefined;
|
||||
if (!library)
|
||||
throw createError({
|
||||
statusCode: 500,
|
||||
message: "Library not found but exists in database?",
|
||||
});
|
||||
|
||||
const currentIndex = await prisma.version.count({
|
||||
where: { ...idFilter },
|
||||
});
|
||||
|
||||
if (metadata.delta && currentIndex == 0)
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
message:
|
||||
"At least one pre-existing version of the same platform is required for delta mode.",
|
||||
});
|
||||
|
||||
taskHandler.create({
|
||||
id: taskId,
|
||||
@ -439,10 +496,6 @@ class LibraryManager {
|
||||
|
||||
logger.info("Created manifest successfully!");
|
||||
|
||||
const currentIndex = await prisma.version.count({
|
||||
where: { ...idFilter },
|
||||
});
|
||||
|
||||
// Then, create the database object
|
||||
await prisma.version.create({
|
||||
data: {
|
||||
@ -450,7 +503,7 @@ class LibraryManager {
|
||||
versionName: metadata.name ?? version,
|
||||
dropletManifest: manifest,
|
||||
|
||||
...libraryManager.createVersionOptions(id, currentIndex, mode, metadata)
|
||||
...libraryManager.createVersionOptions(id, currentIndex, metadata),
|
||||
},
|
||||
});
|
||||
|
||||
@ -460,7 +513,7 @@ class LibraryManager {
|
||||
nonce: `version-create-${id}-${version}`,
|
||||
title: `'${libraryDetails.mName}' ('${version}') finished importing.`,
|
||||
description: `Drop finished importing version ${version} for ${libraryDetails.mName}.`,
|
||||
actions: [`View|/admin/library/${modeToLink[mode]}/${id}`],
|
||||
actions: [`View|/admin/library/${modeToLink[metadata.mode]}/${id}`],
|
||||
acls: ["system:import:version:read"],
|
||||
});
|
||||
|
||||
|
||||
@ -72,7 +72,7 @@ interface IGDBCompanyWebsite extends IGDBItem {
|
||||
}
|
||||
|
||||
interface IGDBCover extends IGDBItem {
|
||||
url: string;
|
||||
image_id: string;
|
||||
}
|
||||
|
||||
interface IGDBSearchStub extends IGDBItem {
|
||||
@ -179,7 +179,7 @@ export class IGDBProvider implements MetadataProvider {
|
||||
|
||||
if (response.status !== 200)
|
||||
throw new Error(
|
||||
`Error in IDGB \nStatus Code: ${response.status}\n${response.data}`,
|
||||
`Error in IGDB \nStatus Code: ${response.status}\n${response.data}`,
|
||||
);
|
||||
|
||||
this.accessToken = response.data.access_token;
|
||||
@ -187,7 +187,7 @@ export class IGDBProvider implements MetadataProvider {
|
||||
seconds: response.data.expires_in,
|
||||
});
|
||||
|
||||
logger.info("IDGB done authorizing with twitch");
|
||||
logger.info("IGDB done authorizing with twitch");
|
||||
}
|
||||
|
||||
private async refreshCredentials() {
|
||||
@ -246,39 +246,47 @@ export class IGDBProvider implements MetadataProvider {
|
||||
return <T[]>response.data;
|
||||
}
|
||||
|
||||
private async _getMediaInternal(mediaID: IGDBID, type: string) {
|
||||
private async _getMediaInternal(
|
||||
mediaID: IGDBID,
|
||||
type: string,
|
||||
size: string = "t_thumb",
|
||||
) {
|
||||
if (mediaID === undefined)
|
||||
throw new Error(
|
||||
`IGDB mediaID when getting item of type ${type} was undefined`,
|
||||
);
|
||||
|
||||
const body = `where id = ${mediaID}; fields url;`;
|
||||
const body = `where id = ${mediaID}; fields image_id;`;
|
||||
const response = await this.request<IGDBCover>(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}`;
|
||||
if (!response.length || !response[0].image_id) {
|
||||
throw new Error(`No image_id found for ${type} with id ${mediaID}`);
|
||||
}
|
||||
});
|
||||
|
||||
const imageId = response[0].image_id;
|
||||
const result = `https://images.igdb.com/igdb/image/upload/${size}/${imageId}.jpg`;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private async getCoverURL(id: IGDBID) {
|
||||
return await this._getMediaInternal(id, "covers");
|
||||
return await this._getMediaInternal(id, "covers", "t_cover_big");
|
||||
}
|
||||
|
||||
private async getArtworkURL(id: IGDBID) {
|
||||
return await this._getMediaInternal(id, "artworks");
|
||||
return await this._getMediaInternal(id, "artworks", "t_1080p");
|
||||
}
|
||||
|
||||
private async getScreenshotURL(id: IGDBID) {
|
||||
return await this._getMediaInternal(id, "screenshots", "t_1080p");
|
||||
}
|
||||
|
||||
private async getIconURL(id: IGDBID) {
|
||||
return await this._getMediaInternal(id, "covers", "t_thumb");
|
||||
}
|
||||
|
||||
private async getCompanyLogoURl(id: IGDBID) {
|
||||
return await this._getMediaInternal(id, "company_logos");
|
||||
return await this._getMediaInternal(id, "company_logos", "t_original");
|
||||
}
|
||||
|
||||
private trimMessage(msg: string, len: number) {
|
||||
@ -327,7 +335,7 @@ export class IGDBProvider implements MetadataProvider {
|
||||
let icon = "";
|
||||
const cover = response[i].cover;
|
||||
if (cover !== undefined) {
|
||||
icon = await this.getCoverURL(cover);
|
||||
icon = await this.getIconURL(cover);
|
||||
} else {
|
||||
icon = "";
|
||||
}
|
||||
@ -355,23 +363,26 @@ export class IGDBProvider implements MetadataProvider {
|
||||
const currentGame = (await this.request<IGDBGameFull>("games", body)).at(0);
|
||||
if (!currentGame) throw new Error("No game found on IGDB with that id");
|
||||
|
||||
context?.logger.info("Using IDGB provider.");
|
||||
context?.logger.info("Using IGDB provider.");
|
||||
|
||||
let iconRaw;
|
||||
let iconRaw, coverRaw;
|
||||
const cover = currentGame.cover;
|
||||
|
||||
if (cover !== undefined) {
|
||||
context?.logger.info("Found cover URL, using...");
|
||||
iconRaw = await this.getCoverURL(cover);
|
||||
iconRaw = await this.getIconURL(cover);
|
||||
coverRaw = await this.getCoverURL(cover);
|
||||
} else {
|
||||
context?.logger.info("Missing cover URL, using fallback...");
|
||||
iconRaw = jdenticon.toPng(id, 512);
|
||||
coverRaw = iconRaw;
|
||||
}
|
||||
|
||||
const icon = createObject(iconRaw);
|
||||
const coverID = createObject(coverRaw);
|
||||
let banner;
|
||||
|
||||
const images = [icon];
|
||||
const images = [coverID];
|
||||
for (const art of currentGame.artworks ?? []) {
|
||||
const objectId = createObject(await this.getArtworkURL(art));
|
||||
if (!banner) {
|
||||
@ -384,6 +395,11 @@ export class IGDBProvider implements MetadataProvider {
|
||||
banner = createObject(jdenticon.toPng(id, 512));
|
||||
}
|
||||
|
||||
for (const screenshot of currentGame.screenshots ?? []) {
|
||||
const objectId = createObject(await this.getScreenshotURL(screenshot));
|
||||
images.push(objectId);
|
||||
}
|
||||
|
||||
context?.progress(20);
|
||||
|
||||
const publishers: CompanyModel[] = [];
|
||||
@ -452,13 +468,25 @@ export class IGDBProvider implements MetadataProvider {
|
||||
|
||||
const genres = await this.getGenres(currentGame.genres);
|
||||
|
||||
const deck = this.trimMessage(currentGame.summary, 280);
|
||||
let description = "";
|
||||
let shortDescription = "";
|
||||
|
||||
if (currentGame.summary.length > (currentGame.storyline?.length ?? 0)) {
|
||||
description = currentGame.summary;
|
||||
shortDescription = this.trimMessage(
|
||||
currentGame.storyline ?? currentGame.summary,
|
||||
280,
|
||||
);
|
||||
} else {
|
||||
description = currentGame.storyline ?? currentGame.summary;
|
||||
shortDescription = this.trimMessage(currentGame.summary, 280);
|
||||
}
|
||||
|
||||
const metadata = {
|
||||
id: currentGame.id.toString(),
|
||||
name: currentGame.name,
|
||||
shortDescription: deck,
|
||||
description: currentGame.summary,
|
||||
shortDescription,
|
||||
description,
|
||||
released,
|
||||
|
||||
genres,
|
||||
@ -471,7 +499,7 @@ export class IGDBProvider implements MetadataProvider {
|
||||
|
||||
icon,
|
||||
bannerId: banner,
|
||||
coverId: icon,
|
||||
coverId: coverID,
|
||||
images,
|
||||
};
|
||||
|
||||
|
||||
1022
server/internal/metadata/steam.ts
Normal file
1022
server/internal/metadata/steam.ts
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,4 +1,4 @@
|
||||
import { defineDropTask } from "..";
|
||||
// import { defineDropTask } from "..";
|
||||
|
||||
/*
|
||||
export default defineDropTask({
|
||||
|
||||
@ -5,11 +5,13 @@ import { GiantBombProvider } from "../internal/metadata/giantbomb";
|
||||
import { IGDBProvider } from "../internal/metadata/igdb";
|
||||
import { ManualMetadataProvider } from "../internal/metadata/manual";
|
||||
import { PCGamingWikiProvider } from "../internal/metadata/pcgamingwiki";
|
||||
import { logger } from "~~/server/internal/logging";
|
||||
import { logger } from "../internal/logging";
|
||||
import { SteamProvider } from "../internal/metadata/steam";
|
||||
|
||||
export default defineNitroPlugin(async (_nitro) => {
|
||||
const metadataProviders = [
|
||||
GiantBombProvider,
|
||||
SteamProvider,
|
||||
PCGamingWikiProvider,
|
||||
IGDBProvider,
|
||||
];
|
||||
|
||||
Reference in New Issue
Block a user