mirror of
https://github.com/Gabi-Zar/Silk-Fly-Launcher.git
synced 2026-04-17 05:26:04 +02:00
Add support for searching and downloading mods from Thunderstore
This commit is contained in:
90
main.js
90
main.js
@@ -39,6 +39,8 @@ let installedCachedModList;
|
|||||||
let installedTotalModsCount;
|
let installedTotalModsCount;
|
||||||
let onlineCachedModList;
|
let onlineCachedModList;
|
||||||
let onlineTotalModsCount;
|
let onlineTotalModsCount;
|
||||||
|
let thunderstoreCachedModList;
|
||||||
|
let thunderstoreTotalModsCount;
|
||||||
|
|
||||||
const bepinexFiles = [".doorstop_version", "changelog.txt", "doorstop_config.ini", "winhttp.dll"];
|
const bepinexFiles = [".doorstop_version", "changelog.txt", "doorstop_config.ini", "winhttp.dll"];
|
||||||
let bepinexVersion = bepinexStore.get("bepinex-version");
|
let bepinexVersion = bepinexStore.get("bepinex-version");
|
||||||
@@ -264,7 +266,11 @@ async function saveModInfo(modId, suppr = false, optionalModInfo = {}) {
|
|||||||
if (onlineCachedModList) {
|
if (onlineCachedModList) {
|
||||||
modInfo = onlineCachedModList.find((mod) => mod.modId == modId);
|
modInfo = onlineCachedModList.find((mod) => mod.modId == modId);
|
||||||
}
|
}
|
||||||
|
if (!modInfo) {
|
||||||
|
if (thunderstoreCachedModList) {
|
||||||
|
modInfo = thunderstoreCachedModList.find((mod) => mod.modId == modId);
|
||||||
|
}
|
||||||
|
}
|
||||||
if (!modInfo) {
|
if (!modInfo) {
|
||||||
modInfo = optionalModInfo;
|
modInfo = optionalModInfo;
|
||||||
}
|
}
|
||||||
@@ -514,6 +520,11 @@ ipcMain.handle("get-mods", async (event, type) => {
|
|||||||
await searchNexusMods("");
|
await searchNexusMods("");
|
||||||
}
|
}
|
||||||
return { onlineModsInfo: onlineCachedModList, onlineTotalCount: onlineTotalModsCount };
|
return { onlineModsInfo: onlineCachedModList, onlineTotalCount: onlineTotalModsCount };
|
||||||
|
} else if (type == "mods-thunderstore") {
|
||||||
|
if (!thunderstoreCachedModList) {
|
||||||
|
await searchThunderstoreMods("");
|
||||||
|
}
|
||||||
|
return { thunderstoreModsInfo: thunderstoreCachedModList, thunderstoreTotalCount: thunderstoreTotalModsCount };
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -646,12 +657,84 @@ async function searchNexusMods(keywords, offset = 0, count = 10, sortFilter = "d
|
|||||||
}
|
}
|
||||||
|
|
||||||
ipcMain.handle("search-thunderstore-mods", async (event, keywords, offset, count, sortFilter, sortOrder) => {
|
ipcMain.handle("search-thunderstore-mods", async (event, keywords, offset, count, sortFilter, sortOrder) => {
|
||||||
searchThunderstoreMods(keywords, offset, count, sortFilter, sortOrder);
|
await searchThunderstoreMods(keywords, offset, count, sortFilter, sortOrder);
|
||||||
});
|
});
|
||||||
|
|
||||||
async function searchThunderstoreMods(keywords, offset = 0, count = 10, sortFilter = "downloads", sortOrder = "DESC") {
|
async function searchThunderstoreMods(keywords, offset = 0, count = 10, sortFilter = "downloads", sortOrder = "DESC") {
|
||||||
console.log("WIP");
|
const res = await fetch("https://thunderstore.io/c/hollow-knight-silksong/api/v1/package/", {
|
||||||
|
headers: {
|
||||||
|
"User-Agent": userAgent,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
let modsInfo = await res.json();
|
||||||
|
|
||||||
|
const modsToRemove = [
|
||||||
|
"f21c391c-0bc5-431d-a233-95323b95e01b",
|
||||||
|
"58c9e43a-549d-4d49-a576-4eed36775a84",
|
||||||
|
"0fc63a3b-3c69-4be5-85f8-bb127eec81b3",
|
||||||
|
"d5419c5d-c22a-4a47-b73d-ba4101f28635",
|
||||||
|
"d5b65a03-1217-4496-8af6-8dda4d763676",
|
||||||
|
"42f76853-d2a4-4520-949b-13a02fdbbbcb",
|
||||||
|
"34eac80c-5497-470e-b98c-f53421b828c0",
|
||||||
|
];
|
||||||
|
let reMappedModsInfo = [];
|
||||||
|
for (let i = 0; i < modsInfo.length; i++) {
|
||||||
|
modsInfo[i].source = "thunderstore";
|
||||||
|
if (modsToRemove.includes(modsInfo[i].uuid4)) {
|
||||||
|
modsInfo.splice(i, 1);
|
||||||
|
i--;
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
reMappedModsInfo.push(reMapThunderstoreModsInfo(modsInfo[i]));
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = sortAndFilterModsList(reMappedModsInfo, keywords, offset, count, sortFilter, sortOrder);
|
||||||
|
thunderstoreCachedModList = result.list;
|
||||||
|
thunderstoreTotalModsCount = result.totalCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
function reMapThunderstoreModsInfo(modInfo) {
|
||||||
|
let totalDownloads = 0;
|
||||||
|
for (const version of modInfo.versions) {
|
||||||
|
totalDownloads += version.downloads;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
author: modInfo.owner,
|
||||||
|
endorsements: modInfo.rating_score,
|
||||||
|
modId: modInfo.uuid4,
|
||||||
|
name: modInfo.name,
|
||||||
|
pictureUrl: modInfo.versions[0].icon,
|
||||||
|
summary: modInfo.versions[0].description,
|
||||||
|
updatedAt: modInfo.date_updated,
|
||||||
|
createdAt: modInfo.date_created,
|
||||||
|
version: modInfo.versions[0].version_number,
|
||||||
|
downloads: totalDownloads,
|
||||||
|
fileSize: modInfo.versions[0].file_size,
|
||||||
|
source: modInfo.source,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
ipcMain.handle("download-thunderstore-mods", async (event, url, modId) => {
|
||||||
|
const bepinexFolderPath = path.join(loadSilksongPath(), "BepInEx");
|
||||||
|
if (!(await fileExists(loadSilksongPath()))) {
|
||||||
|
mainWindow.webContents.send("showToast", "Path to the game invalid", "warning");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(await fileExists(modSavePath))) {
|
||||||
|
await fs.mkdir(modSavePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
await downloadAndUnzip(url, path.join(modSavePath, modId));
|
||||||
|
if (await fileExists(bepinexFolderPath)) {
|
||||||
|
await fs.cp(path.join(modSavePath, modId), path.join(bepinexFolderPath, "plugins", modId), { recursive: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
saveModInfo(modId);
|
||||||
|
mainWindow.webContents.send("showToast", "Mod downloaded successfully.");
|
||||||
|
installedCachedModList = undefined;
|
||||||
|
});
|
||||||
|
|
||||||
//////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////
|
||||||
//////////////////////// MODS ////////////////////////
|
//////////////////////// MODS ////////////////////////
|
||||||
@@ -863,6 +946,7 @@ ipcMain.handle("open-window", async (event, file) => {
|
|||||||
|
|
||||||
ipcMain.handle("launch-game", async (event, mode) => {
|
ipcMain.handle("launch-game", async (event, mode) => {
|
||||||
const silksongExecutablePath = path.join(loadSilksongPath(), "Hollow Knight Silksong.exe");
|
const silksongExecutablePath = path.join(loadSilksongPath(), "Hollow Knight Silksong.exe");
|
||||||
|
const bepinexFolderPath = path.join(loadSilksongPath(), "BepInEx");
|
||||||
if (!fileExists(silksongExecutablePath)) {
|
if (!fileExists(silksongExecutablePath)) {
|
||||||
mainWindow.webContents.send("showToast", "Path to the game invalid", "warning");
|
mainWindow.webContents.send("showToast", "Path to the game invalid", "warning");
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -65,4 +65,5 @@ contextBridge.exposeInMainWorld("mods", {
|
|||||||
|
|
||||||
contextBridge.exposeInMainWorld("thunderstore", {
|
contextBridge.exposeInMainWorld("thunderstore", {
|
||||||
search: (keywords, offset, count, sortFilter, sortOrder) => ipcRenderer.invoke("search-thunderstore-mods", keywords, offset, count, sortFilter, sortOrder),
|
search: (keywords, offset, count, sortFilter, sortOrder) => ipcRenderer.invoke("search-thunderstore-mods", keywords, offset, count, sortFilter, sortOrder),
|
||||||
|
download: (url, modId) => ipcRenderer.invoke("download-thunderstore-mods", url, modId),
|
||||||
});
|
});
|
||||||
|
|||||||
26
renderer/assets/icons/plus.svg
Normal file
26
renderer/assets/icons/plus.svg
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
width="244"
|
||||||
|
height="244"
|
||||||
|
viewBox="0 0 64.558333 64.558333"
|
||||||
|
version="1.1"
|
||||||
|
id="svg1"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg">
|
||||||
|
<defs
|
||||||
|
id="defs1" />
|
||||||
|
<g
|
||||||
|
id="layer1"
|
||||||
|
transform="translate(-1.5875,-1.5875005)">
|
||||||
|
<path
|
||||||
|
style="fill:none;stroke:#ffffff;stroke-width:6.35;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
d="M 62.970833,33.866667 H 4.7625"
|
||||||
|
id="path1" />
|
||||||
|
<path
|
||||||
|
style="fill:none;stroke:#ffffff;stroke-width:6.35;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
d="M 33.866666,62.970833 V 4.7625005"
|
||||||
|
id="path2" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 838 B |
@@ -3,7 +3,7 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<title>Silk Fly Launcher</title>
|
<title>Silk Fly Launcher</title>
|
||||||
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self' 'unsafe-inline'; img-src 'self' https://*.nexusmods.com" />
|
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self' 'unsafe-inline'; img-src 'self' https://*.nexusmods.com https://*.thunderstore.io" />
|
||||||
<link rel="stylesheet" href="style.css" />
|
<link rel="stylesheet" href="style.css" />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
@@ -180,6 +180,7 @@
|
|||||||
<li id="endorsements" onclick="changeSort('endorsements')">by rating count</li>
|
<li id="endorsements" onclick="changeSort('endorsements')">by rating count</li>
|
||||||
<li id="createdAt" onclick="changeSort('createdAt')">by date of creation</li>
|
<li id="createdAt" onclick="changeSort('createdAt')">by date of creation</li>
|
||||||
<li id="updatedAt" onclick="changeSort('updatedAt')">by date of updating</li>
|
<li id="updatedAt" onclick="changeSort('updatedAt')">by date of updating</li>
|
||||||
|
<li id="size" onclick="changeSort('size')">by size</li>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button class="default-button square-button" onclick="inverseSort()"><img class="icons" id="sort-order-image" src="assets/icons/sort-order-1.svg" /></button>
|
<button class="default-button square-button" onclick="inverseSort()"><img class="icons" id="sort-order-image" src="assets/icons/sort-order-1.svg" /></button>
|
||||||
|
|||||||
@@ -312,6 +312,73 @@ async function navigate(page) {
|
|||||||
view.appendChild(thunderstoreModsTemplateCopy);
|
view.appendChild(thunderstoreModsTemplateCopy);
|
||||||
toggleSelectedListButton("sort-menu", thunderstoreSortFilter);
|
toggleSelectedListButton("sort-menu", thunderstoreSortFilter);
|
||||||
setSortOrderButton();
|
setSortOrderButton();
|
||||||
|
|
||||||
|
const { thunderstoreModsInfo, thunderstoreTotalCount } = await mods.getMods(page);
|
||||||
|
thunderstoreModsTotalCount = thunderstoreTotalCount;
|
||||||
|
if (thunderstoreModsInfo == undefined) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
for (const mod of thunderstoreModsInfo) {
|
||||||
|
if (mod.name == undefined) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const modTemplateCopy = modTemplate.content.cloneNode(true);
|
||||||
|
if (mod.name) {
|
||||||
|
const modTitleText = modTemplateCopy.getElementById("mod-title");
|
||||||
|
modTitleText.innerText = mod.name.replaceAll("_", " ");
|
||||||
|
}
|
||||||
|
if (mod.author) {
|
||||||
|
const modAuthorText = modTemplateCopy.getElementById("mod-author");
|
||||||
|
modAuthorText.innerText = `by ${mod.author}`;
|
||||||
|
}
|
||||||
|
if (mod.endorsements) {
|
||||||
|
const modEndorsementsNumber = modTemplateCopy.getElementById("mod-endorsements-number");
|
||||||
|
if (mod.endorsements > 1) {
|
||||||
|
modEndorsementsNumber.innerText = `${mod.endorsements} likes`;
|
||||||
|
} else {
|
||||||
|
modEndorsementsNumber.innerText = `${mod.endorsements} like`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (mod.downloads) {
|
||||||
|
const modDownloadsNumber = modTemplateCopy.getElementById("mod-downloads-number");
|
||||||
|
if (mod.downloads > 1) {
|
||||||
|
modDownloadsNumber.innerText = `${mod.downloads} downloads`;
|
||||||
|
} else {
|
||||||
|
modDownloadsNumber.innerText = `${mod.downloads} download`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (mod.summary) {
|
||||||
|
const modDescriptionText = modTemplateCopy.getElementById("mod-description");
|
||||||
|
modDescriptionText.innerText = mod.summary;
|
||||||
|
}
|
||||||
|
if (mod.pictureUrl) {
|
||||||
|
const modPicture = modTemplateCopy.getElementById("mod-icon");
|
||||||
|
modPicture.src = mod.pictureUrl;
|
||||||
|
}
|
||||||
|
if (mod.version && mod.updatedAt) {
|
||||||
|
const modVersionText = modTemplateCopy.getElementById("mod-version");
|
||||||
|
modVersionText.innerText = `V${mod.version} last updated on ${mod.updatedAt.slice(0, 10)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const modUrl = `https://new.thunderstore.io/c/hollow-knight-silksong/p/${mod.author}/${mod.name}`;
|
||||||
|
|
||||||
|
const modLinkButton = modTemplateCopy.getElementById("external-link");
|
||||||
|
modLinkButton.href = modUrl;
|
||||||
|
modLinkButton.addEventListener("click", function (event) {
|
||||||
|
event.preventDefault();
|
||||||
|
const modLink = modLinkButton.href;
|
||||||
|
electronAPI.openExternalLink(modLink);
|
||||||
|
});
|
||||||
|
|
||||||
|
modDownloadButton = modTemplateCopy.getElementById("download-mod-button");
|
||||||
|
modDownloadButton.addEventListener("click", function (event) {
|
||||||
|
event.preventDefault();
|
||||||
|
const modDownloadLink = `https://thunderstore.io/package/download/${mod.author}/${mod.name}/${mod.version}`;
|
||||||
|
thunderstore.download(modDownloadLink, mod.modId);
|
||||||
|
});
|
||||||
|
|
||||||
|
thunderstoreModsContainer.appendChild(modTemplateCopy);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case "general-settings":
|
case "general-settings":
|
||||||
title.innerText = "Settings";
|
title.innerText = "Settings";
|
||||||
@@ -815,7 +882,7 @@ function changeModsPage(offsetChange) {
|
|||||||
thunderstoreOffset = Math.floor(thunderstoreOffset / 10) * 10;
|
thunderstoreOffset = Math.floor(thunderstoreOffset / 10) * 10;
|
||||||
if (lastThunderstoreOffset != thunderstoreOffset) {
|
if (lastThunderstoreOffset != thunderstoreOffset) {
|
||||||
lastThunderstoreOffset = thunderstoreOffset;
|
lastThunderstoreOffset = thunderstoreOffset;
|
||||||
searchNexusMods();
|
searchThunderstoreMods();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user