mirror of
https://github.com/Grasscutters/Cultivation.git
synced 2025-12-16 17:14:36 +01:00
Merge branch 'mod_management'
This commit is contained in:
@@ -50,6 +50,13 @@ export interface Configuration {
|
||||
|
||||
// Swag stuff
|
||||
akebi_path?: string
|
||||
migoto_path?: string
|
||||
reshade_path?: string
|
||||
last_extras?: {
|
||||
migoto: boolean
|
||||
akebi: boolean
|
||||
reshade: boolean
|
||||
}
|
||||
}
|
||||
|
||||
export async function setConfigOption<K extends keyof Configuration>(key: K, value: Configuration[K]): Promise<void> {
|
||||
|
||||
205
src/utils/gamebanana.ts
Normal file
205
src/utils/gamebanana.ts
Normal file
@@ -0,0 +1,205 @@
|
||||
import { invoke } from '@tauri-apps/api'
|
||||
import { getConfigOption } from './configuration'
|
||||
|
||||
// Generated with https://transform.tools/json-to-typescript I'm lazy cry about it
|
||||
interface GamebananaResponse {
|
||||
_idRow: number
|
||||
_sModelName: string
|
||||
_sSingularTitle: string
|
||||
_sIconClasses: string
|
||||
_sName: string
|
||||
_sProfileUrl: string
|
||||
_aPreviewMedia: PreviewMedia
|
||||
_tsDateAdded: number
|
||||
_bHasFiles: boolean
|
||||
_aSubmitter: Submitter
|
||||
_aRootCategory: RootCategory
|
||||
_bIsNsfw: boolean
|
||||
_sInitialVisibility: string
|
||||
_nLikeCount?: number
|
||||
_bIsOwnedByAccessor: boolean
|
||||
_nViewCount?: number
|
||||
_nPostCount?: number
|
||||
_tsDateUpdated?: number
|
||||
}
|
||||
|
||||
interface PreviewMedia {
|
||||
_aImages?: Image[]
|
||||
_aMetadata?: Metadata
|
||||
}
|
||||
|
||||
interface Image {
|
||||
_sType: string
|
||||
_sBaseUrl: string
|
||||
_sFile: string
|
||||
_sFile530?: string
|
||||
_sFile100: string
|
||||
_sFile220?: string
|
||||
_sCaption?: string
|
||||
}
|
||||
|
||||
interface Metadata {
|
||||
_sState?: string
|
||||
_sSnippet: string
|
||||
_nPostCount?: number
|
||||
_nBounty?: number
|
||||
}
|
||||
|
||||
interface Submitter {
|
||||
_idRow: number
|
||||
_sName: string
|
||||
_bIsOnline: boolean
|
||||
_bHasRipe: boolean
|
||||
_sProfileUrl: string
|
||||
_sAvatarUrl: string
|
||||
_sUpicUrl?: string
|
||||
_aClearanceLevels?: string[]
|
||||
_sHdAvatarUrl?: string
|
||||
}
|
||||
|
||||
interface RootCategory {
|
||||
_sName: string
|
||||
_sProfileUrl: string
|
||||
_sIconUrl: string
|
||||
}
|
||||
|
||||
export interface ModData {
|
||||
id: number
|
||||
name: string
|
||||
images: string[]
|
||||
dateadded: number
|
||||
submitter: {
|
||||
name: string
|
||||
url: string
|
||||
}
|
||||
nsfw: boolean
|
||||
likes: number
|
||||
views: number
|
||||
type: string
|
||||
}
|
||||
|
||||
export interface PartialModData {
|
||||
name: string
|
||||
images: string[]
|
||||
submitter: {
|
||||
name: string
|
||||
}
|
||||
likes: number
|
||||
views: number
|
||||
}
|
||||
|
||||
interface GamebananaDownloads {
|
||||
_bIsTrashed: boolean
|
||||
_bIsWithheld: boolean
|
||||
_aFiles: File[]
|
||||
_sLicense: string
|
||||
}
|
||||
|
||||
interface File {
|
||||
_idRow: number
|
||||
_sFile: string
|
||||
_nFilesize: number
|
||||
_sDescription: string
|
||||
_tsDateAdded: number
|
||||
_nDownloadCount: number
|
||||
_sAnalysisState: string
|
||||
_sDownloadUrl: string
|
||||
_sMd5Checksum: string
|
||||
_sClamAvResult: string
|
||||
_sAnalysisResult: string
|
||||
_bContainsExe: boolean
|
||||
}
|
||||
|
||||
interface ModDownload {
|
||||
filename: string
|
||||
downloadUrl: string
|
||||
filesize: number
|
||||
containsExe: boolean
|
||||
}
|
||||
|
||||
export async function getMods(mode: string) {
|
||||
const resp = JSON.parse(
|
||||
await invoke('list_submissions', {
|
||||
mode,
|
||||
})
|
||||
)
|
||||
|
||||
return formatGamebananaData(resp)
|
||||
}
|
||||
|
||||
export async function formatGamebananaData(obj: GamebananaResponse[]) {
|
||||
if (!obj) return []
|
||||
|
||||
return obj
|
||||
.map((itm) => {
|
||||
const img = itm?._aPreviewMedia?._aImages
|
||||
|
||||
return {
|
||||
id: itm._idRow,
|
||||
name: itm._sName,
|
||||
images: img
|
||||
? img.map((i) => {
|
||||
return i._sBaseUrl + '/' + i._sFile220
|
||||
})
|
||||
: [],
|
||||
dateadded: itm._tsDateAdded,
|
||||
submitter: {
|
||||
name: itm._aSubmitter._sName,
|
||||
url: itm._aSubmitter._sProfileUrl,
|
||||
},
|
||||
nsfw: itm._bIsNsfw,
|
||||
likes: itm?._nLikeCount || 0,
|
||||
views: itm?._nViewCount || 0,
|
||||
type: itm._sSingularTitle,
|
||||
} as ModData
|
||||
})
|
||||
.filter((itm) => itm.type === 'Mod')
|
||||
}
|
||||
|
||||
export async function getInstalledMods() {
|
||||
const migotoPath = await getConfigOption('migoto_path')
|
||||
|
||||
if (!migotoPath) return []
|
||||
|
||||
const mods = (await invoke('list_mods', {
|
||||
path: migotoPath,
|
||||
})) as Record<string, string>
|
||||
|
||||
// These are returned as JSON strings, so we have to parse them
|
||||
return Object.keys(mods).map((path) => {
|
||||
const info = JSON.parse(mods[path]) as ModData | PartialModData
|
||||
|
||||
const modPathArr = path.replace(/\\/g, '/').split('/')
|
||||
|
||||
// If there is a file in this path, remove it from the path
|
||||
if (modPathArr[modPathArr.length - 1].includes('.')) modPathArr.pop()
|
||||
|
||||
return {
|
||||
path: modPathArr.join('/'),
|
||||
info,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export async function getModDownload(modId: string) {
|
||||
const resp = JSON.parse(
|
||||
await invoke('get_download_links', {
|
||||
modId,
|
||||
})
|
||||
) as GamebananaDownloads
|
||||
|
||||
return formatDownloadsData(resp)
|
||||
}
|
||||
|
||||
export async function formatDownloadsData(obj: GamebananaDownloads) {
|
||||
if (!obj) return []
|
||||
|
||||
return obj._aFiles.map((itm) => {
|
||||
return {
|
||||
filename: itm._sFile,
|
||||
downloadUrl: `https://files.gamebanana.com/mods/${itm._sFile}`,
|
||||
filesize: itm._nFilesize,
|
||||
containsExe: itm._bContainsExe,
|
||||
} as ModDownload
|
||||
})
|
||||
}
|
||||
71
src/utils/mods.ts
Normal file
71
src/utils/mods.ts
Normal file
@@ -0,0 +1,71 @@
|
||||
import { invoke } from '@tauri-apps/api'
|
||||
import { getConfigOption } from './configuration'
|
||||
|
||||
export async function getModsFolder() {
|
||||
const migotoPath = await getConfigOption('migoto_path')
|
||||
|
||||
if (!migotoPath) return null
|
||||
|
||||
// Remove exe from path
|
||||
const pathArr = migotoPath.replace(/\\/g, '/').split('/')
|
||||
pathArr.pop()
|
||||
|
||||
return pathArr.join('/') + '/Mods/'
|
||||
}
|
||||
|
||||
export async function disableMod(modId: string) {
|
||||
const path = (await getModsFolder()) + modId
|
||||
const pathExists = await invoke('dir_exists', {
|
||||
path,
|
||||
})
|
||||
|
||||
if (!pathExists) return console.log("Path doesn't exist")
|
||||
|
||||
const modName = path.replace(/\\/g, '/').split('/').pop()
|
||||
|
||||
await invoke('rename', {
|
||||
path,
|
||||
newName: `DISABLED_${modName}`,
|
||||
})
|
||||
}
|
||||
|
||||
export async function enableMod(modId: string) {
|
||||
const path = (await getModsFolder()) + `DISABLED_${modId}`
|
||||
const modName = path.replace(/\\/g, '/').split('/').pop()
|
||||
const pathExists = await invoke('dir_exists', {
|
||||
path,
|
||||
})
|
||||
|
||||
if (!pathExists) return console.log("Path doesn't exist")
|
||||
|
||||
if (!modName?.includes('DISABLED_')) return
|
||||
|
||||
const newName = modName.replace('DISABLED_', '')
|
||||
|
||||
await invoke('rename', {
|
||||
path,
|
||||
newName,
|
||||
})
|
||||
}
|
||||
|
||||
export async function getModFolderName(modId: string) {
|
||||
const modsFolder = await getModsFolder()
|
||||
|
||||
if (!modsFolder) return null
|
||||
|
||||
const modEnabled = await invoke('dir_exists', {
|
||||
path: modsFolder + modId,
|
||||
})
|
||||
const modDisabled = await invoke('dir_exists', {
|
||||
path: modsFolder + 'DISABLED_' + modId,
|
||||
})
|
||||
|
||||
if (!modEnabled && !modDisabled) return null
|
||||
|
||||
if (modEnabled) return modId
|
||||
if (modDisabled) return 'DISABLED_' + modId
|
||||
}
|
||||
|
||||
export async function modIsEnabled(modId: string) {
|
||||
return !(await getModFolderName(modId))?.includes('DISABLED_')
|
||||
}
|
||||
@@ -1,15 +1,30 @@
|
||||
import { invoke } from '@tauri-apps/api'
|
||||
import { listen } from '@tauri-apps/api/event'
|
||||
|
||||
export function unzip(file: string, dest: string, onFinish?: () => void) {
|
||||
invoke('unzip', {
|
||||
zipfile: file,
|
||||
destpath: dest,
|
||||
})
|
||||
interface UnzipPayload {
|
||||
file: string
|
||||
new_folder: string
|
||||
}
|
||||
|
||||
listen('extract_end', ({ payload }) => {
|
||||
if (payload === file && onFinish) {
|
||||
onFinish()
|
||||
}
|
||||
export function unzip(
|
||||
file: string,
|
||||
dest: string,
|
||||
topLevelStrip?: boolean,
|
||||
folderIfLoose?: boolean
|
||||
): Promise<UnzipPayload> {
|
||||
return new Promise((resolve) => {
|
||||
invoke('unzip', {
|
||||
zipfile: file,
|
||||
destpath: dest,
|
||||
topLevelStrip,
|
||||
folderIfLoose,
|
||||
})
|
||||
|
||||
listen('extract_end', ({ payload }) => {
|
||||
// @ts-expect-error Payload is an object
|
||||
if (payload?.file === file) {
|
||||
resolve(payload as UnzipPayload)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user