Last active
April 3, 2025 17:25
-
-
Save Gesugao-san/464ff0864c2d49e4cc9c7aebeb1ac795 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env node | |
/** | |
* @file This script extracts a list of mods from a Steam Workshop collection and generates an XML document | |
* in a format compatible with the Barotrauma Modlist. | |
* | |
* @see {@link http://s.team/a/602960} for more information about Barotrauma. | |
* | |
* @example | |
* // How to run: | |
* // 1. Open the Steam Workshop collection page (e.g., for Barotrauma). | |
* // 2. Open the browser's Developer Tools (usually by pressing F12). | |
* // 3. Navigate to the "Console" tab. | |
* // 4. Paste this script and press Enter. | |
* // 5. The generated XML code will be printed in the console, ready to be copied and saved in Modlists folder: | |
* // "C:\Program Files (x86)\Steam\steamapps\common\Barotrauma\ModLists" | |
* | |
* @description | |
* This script performs the following steps: | |
* 1. Waits for the page to fully load and checks for the presence of the mod collection title. | |
* 2. Retrieves the collection title. | |
* 3. Iterates through all mods in the list, extracts their names and IDs. | |
* 4. Structures the mods into an XML format. | |
* 5. Adds a comment with the generation date and version. | |
* 6. Prints the generated XML code to the console. | |
* | |
* @returns {void} This script does not return a value but prints the XML code to the console. | |
* @version 1.2 | |
* @author Gesugao-san | |
* @license MIT | |
*/ | |
// Constants | |
const VERSION = 1.2; // Script version | |
const PINNED_IDS = new Set([ | |
2795927223, // C# | |
2559634234, // Lua | |
3088784724, // LuaC# Enforced | |
2701251094, // Performance Fix | |
3329396988, // Network Tweaks | |
3231293294, // Better Health UI | |
2986079116 // TRT | |
]); | |
const MOD_URL = "https://steamcommunity.com/sharedfiles/filedetails/?id="; | |
const MODS_FOLDER_PATH = "C:\\Program Files (x86)\\Steam\\steamapps\\common\\Barotrauma\\ModLists"; | |
const BBCODE_ENABLED = false; // Set to true if you want to generate BBCode for mod IDs | |
// Helper functions | |
/** | |
* Determines the user's language and returns messages in Russian or English. | |
* @returns {Object} An object containing language-specific messages. | |
*/ | |
function getMessages() { | |
const userLanguage = navigator.language; | |
if (userLanguage.startsWith("ru")) { | |
return { | |
waitingForPage: "Ожидание загрузки страницы...", | |
pageLoaded: "Страница успешно загружена.", | |
errorOccurred: "Произошла ошибка:", | |
skippingMod: "Пропуск мода без ссылки.", | |
skippingInvalidId: 'Пропуск мода "${title}" из-за неверного ID.', | |
pageLoadTimeout: "Страница не загрузилась в ожидаемое время.", | |
titleNotFound: "Не удалось найти заголовок коллекции модов.", | |
}; | |
} else { | |
return { | |
waitingForPage: "Waiting for the page to load...", | |
pageLoaded: "Page loaded successfully.", | |
errorOccurred: "An error occurred:", | |
skippingMod: "Skipping a mod element without a link.", | |
skippingInvalidId: 'Skipping mod "${title}" due to invalid ID.', | |
pageLoadTimeout: "Page did not load within the expected time.", | |
titleNotFound: "Could not find the mod collection title.", | |
}; | |
} | |
} | |
/** | |
* Waits for the page to fully load by checking for the presence of the mod collection title. | |
* @returns {Promise<void>} A promise that resolves when the title element is found. | |
* @throws {Error} If the title element is not found within the specified time. | |
*/ | |
function waitForPageLoad() { | |
return new Promise((resolve, reject) => { | |
const maxAttempts = 30; // Maximum number of attempts | |
const interval = 500; // Check every 500ms | |
let attempts = 0; | |
const checkInterval = setInterval(() => { | |
const titleElement = document.querySelector(".workshopItemTitle"); | |
if (titleElement) { | |
clearInterval(checkInterval); | |
resolve(); | |
} else if (attempts >= maxAttempts) { | |
clearInterval(checkInterval); | |
reject(new Error(messages.pageLoadTimeout)); | |
} | |
attempts++; | |
}, interval); | |
}); | |
} | |
/** | |
* Retrieves the title of the mod collection from the Steam Workshop page. | |
* @returns {string} The title of the mod collection. | |
* @throws {Error} If the title element is not found. | |
*/ | |
function getModpackTitle() { | |
const modpackTitleElement = document.querySelector(".workshopItemTitle"); | |
if (!modpackTitleElement) { | |
throw new Error(messages.titleNotFound); | |
} | |
return modpackTitleElement.textContent; | |
} | |
/** | |
* Extracts mod information (title and ID) from the HTML page. | |
* @returns {Array<{title: string, id: number}>} An array of objects containing mod titles and IDs. | |
*/ | |
function extractModsFromPage() { | |
const modElements = document.querySelectorAll(".collectionItemDetails"); | |
const mods = []; | |
modElements.forEach(modElement => { | |
const modLink = modElement.querySelector("a"); | |
if (!modLink) { | |
console.warn(messages.skippingMod); | |
return; | |
} | |
const title = modLink.innerText; // Extract the mod name | |
const id = parseInt(modLink.href.split('?id=')[1]); // Extract the mod ID from the URL | |
if (isNaN(id)) { | |
console.warn(messages.skippingInvalidId.replace("${title}", title)); | |
return; | |
} | |
mods.push({ title, id }); | |
}); | |
return mods; | |
} | |
/** | |
* Generates the XML structure for the mod list. | |
* @param {string} modpackTitle - The title of the mod collection. | |
* @param {Array<{title: string, id: number}>} mods - An array of mods with titles and IDs. | |
* @returns {string} The generated XML code as a string. | |
*/ | |
function generateXml(modpackTitle, mods) { | |
let output = `<?xml version="1.0" encoding="utf-8"?> | |
<mods name="${modpackTitle}"> | |
<Vanilla /> | |
`; | |
// Add mods to the XML structure | |
mods.forEach(mod => { | |
output += ` <Workshop id="${mod.id}" name="${mod.title}" />\n`; | |
}); | |
// Add a comment with the generation date and version | |
const currentUTCDate = new Date().toISOString().split('T')[0]; // Extract only the date part | |
output += ` <!-- Generated with https://gist.github.com/Gesugao-san/464ff0864c2d49e4cc9c7aebeb1ac795/ (v${VERSION.toString()}) on ${currentUTCDate} UTC -->\n`; | |
// Close the XML structure | |
output += "</mods>\n"; | |
return output; | |
} | |
// Main script | |
(async () => { | |
// Get language-specific messages | |
const messages = getMessages(); | |
try { | |
// Step 0: Wait for the page to fully load | |
console.log(messages.waitingForPage); | |
await waitForPageLoad(); | |
console.log(messages.pageLoaded); | |
// Step 1: Retrieve the mod collection title | |
const modpackTitle = getModpackTitle(); | |
// Step 2: Extract mod information from the page | |
const mods = extractModsFromPage(); | |
// Step 3: Generate the XML structure | |
const xmlOutput = generateXml(modpackTitle, mods); | |
// Step 4: Print the generated XML output to the console | |
console.log(xmlOutput); | |
} catch (error) { | |
console.error(messages.errorOccurred, error); | |
} | |
})(); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env node | |
/** | |
* @file This script converts a Steam Workshop Collection into an XML document | |
* compatible with the Barotrauma Modlist format. It can also be adapted for other games | |
* or purposes requiring similar mod list conversions. | |
* | |
* @see {@link http://s.team/a/602960} for more information about Barotrauma. | |
* | |
* @example | |
* // How to run: | |
* // 1. Open the Steam Workshop Collection page (e.g., for Barotrauma). | |
* // 2. Open the browser's Developer Tools (usually by pressing F12). | |
* // 3. Navigate to the "Console" tab. | |
* // 4. Paste this script and press Enter. | |
* // 5. The generated XML code will be saved as a file in UTF-8 with BOM encoding, | |
* // ready to be copied into the Modlists folder: | |
* // "C:\Program Files (x86)\Steam\steamapps\common\Barotrauma\ModLists" | |
* | |
* @description | |
* This script performs the following steps: | |
* 1. Waits for the page to fully load and checks for the presence of the mod collection title. | |
* 2. Retrieves the mod collection title. | |
* 3. Extracts mod information (title and ID) from the HTML page. | |
* 4. Sorts mods by their IDs (creation date). | |
* 5. Adds "pinned" mods in a separate section. | |
* 6. Writes all mods to an XML document, creating <Workshop> elements with `id` and `name` attributes. | |
* 7. Serializes the XML document into a string and formats it for readability. | |
* 8. Creates an XML file in UTF-8 with BOM encoding and offers it for download to the user. | |
* | |
* @returns {void} This script does not return a value but initiates the download of an XML file. | |
* @version 1.2 | |
* @author Gesugao-san | |
* @license MIT | |
*/ | |
// Constants | |
const VERSION = 1.2; // Script version | |
const PINNED_IDS = new Set([ | |
2795927223, // C# | |
2559634234, // Lua | |
3088784724, // LuaC# Enforced | |
2701251094, // Performance Fix | |
3329396988, // Network Tweaks | |
3231293294, // Better Health UI | |
2986079116 // TRT | |
]); | |
const MOD_URL = "https://steamcommunity.com/sharedfiles/filedetails/?id="; | |
const MODS_FOLDER_PATH = "C:\\Program Files (x86)\\Steam\\steamapps\\common\\Barotrauma\\ModLists"; | |
const BBCODE_ENABLED = false; // Set to true if you want to generate BBCode for mod IDs | |
let messages; | |
// Helper functions | |
/** | |
* Determines the user's language and returns messages in Russian or English. | |
* @returns {Object} An object containing language-specific messages. | |
*/ | |
function getMessages() { | |
const userLanguage = navigator.language; | |
if (userLanguage.startsWith("ru")) { | |
return { | |
waitingForPage: "Ожидание загрузки страницы...", | |
pageLoaded: "Страница успешно загружена.", | |
errorOccurred: "Произошла ошибка:", | |
skippingMod: "Пропуск мода без ссылки.", | |
skippingInvalidId: 'Пропуск мода "${title}" из-за неверного ID.', | |
pageLoadTimeout: "Страница не загрузилась в ожидаемое время.", | |
titleNotFound: "Не удалось найти заголовок коллекции модов.", | |
xmlGenerated: "XML-файл успешно создан и скачан. Поместите его сюда:", | |
}; | |
} else { | |
return { | |
waitingForPage: "Waiting for the page to load...", | |
pageLoaded: "Page loaded successfully.", | |
errorOccurred: "An error occurred:", | |
skippingMod: "Skipping a mod element without a link.", | |
skippingInvalidId: 'Skipping mod "${title}" due to invalid ID.', | |
pageLoadTimeout: "Page did not load within the expected time.", | |
titleNotFound: "Could not find the mod collection title.", | |
xmlGenerated: "XML file has been generated and downloaded. Place it here:", | |
sortedModsComment: " Sorted by ID ", | |
}; | |
} | |
} | |
/** | |
* Waits for the page to fully load by checking for the presence of the mod collection title. | |
* @returns {Promise<void>} A promise that resolves when the title element is found. | |
* @throws {Error} If the title element is not found within the specified time. | |
*/ | |
function waitForPageLoad() { | |
return new Promise((resolve, reject) => { | |
const maxAttempts = 30; // Maximum number of attempts | |
const interval = 500; // Check every 500ms | |
let attempts = 0; | |
const checkInterval = setInterval(() => { | |
const titleElement = document.querySelector(".workshopItemTitle"); | |
if (titleElement) { | |
clearInterval(checkInterval); | |
resolve(); | |
} else if (attempts >= maxAttempts) { | |
clearInterval(checkInterval); | |
reject(new Error(messages.pageLoadTimeout)); | |
} | |
attempts++; | |
}, interval); | |
}); | |
} | |
/** | |
* Retrieves the title of the mod collection from the Steam Workshop page. | |
* @returns {string} The title of the mod collection. | |
* @throws {Error} If the title element is not found. | |
*/ | |
function getModpackTitle() { | |
const titleElement = document.querySelector(".workshopItemTitle"); | |
if (!titleElement) { | |
throw new Error(messages.titleNotFound); | |
} | |
return titleElement.textContent; | |
} | |
/** | |
* Extracts mod information (title and ID) from the HTML page. | |
* @returns {Object} An object where keys are mod IDs and values are mod titles. | |
*/ | |
function extractModsFromPage() { | |
const modElements = document.querySelectorAll(".collectionItemDetails"); | |
const mods = {}; | |
modElements.forEach((item) => { | |
const modLink = item.querySelector("a"); | |
if (!modLink) { | |
console.warn(messages.skippingMod); | |
return; | |
} | |
const title = modLink.innerText; | |
const id = parseInt(modLink.href.split('?id=')[1]); | |
if (isNaN(id)) { | |
console.warn(messages.skippingInvalidId.replace("${title}", title)); | |
return; | |
} | |
mods[id] = title; | |
}); | |
return mods; | |
} | |
/** | |
* Creates an XML document with the root element <mods> and a <Vanilla> child. | |
* @param {string} modpackTitle - The title of the mod collection. | |
* @returns {Document} The created XML document. | |
*/ | |
function createXmlDocument(modpackTitle) { | |
const xmlDoc = document.implementation.createDocument(null, "mods", null); | |
const rootElement = xmlDoc.documentElement; | |
rootElement.setAttribute("name", modpackTitle); | |
// Add Vanilla element | |
const vanillaElement = xmlDoc.createElement("Vanilla"); | |
rootElement.appendChild(vanillaElement); | |
return xmlDoc; | |
} | |
/** | |
* Adds pinned mods to the XML document in a separate section. | |
* @param {Document} xmlDoc - The XML document to which mods will be added. | |
* @param {Object} mods - An object containing mod IDs and titles. | |
*/ | |
function addPinnedMods(xmlDoc, mods) { | |
let pinnedPresence = false; | |
PINNED_IDS.forEach((id) => { | |
if (!mods[id]) return; | |
if (!pinnedPresence) { | |
const pinnedHeader = xmlDoc.createComment(" Pinned mods, own sort order "); | |
xmlDoc.documentElement.appendChild(pinnedHeader); | |
pinnedPresence = true; | |
} | |
const title = mods[id]; | |
const workshopElement = xmlDoc.createElement("Workshop"); | |
workshopElement.setAttribute("id", BBCODE_ENABLED ? `[url=${MOD_URL}${id}]${id}[/url]` : id); | |
workshopElement.setAttribute("name", title); | |
xmlDoc.documentElement.appendChild(workshopElement); | |
}); | |
} | |
/** | |
* Adds sorted mods to the XML document, excluding pinned mods. | |
* @param {Document} xmlDoc - The XML document to which mods will be added. | |
* @param {Object} mods - An object containing mod IDs and titles. | |
*/ | |
function addSortedMods(xmlDoc, mods) { | |
const sortedHeader = xmlDoc.createComment(" Sorted by ID "); | |
xmlDoc.documentElement.appendChild(sortedHeader); | |
Object.keys(mods) | |
.sort((a, b) => parseInt(a) - parseInt(b)) | |
.forEach((idStr) => { | |
const id = parseInt(idStr); | |
if (PINNED_IDS.has(id)) return; | |
const title = mods[id]; | |
const workshopElement = xmlDoc.createElement("Workshop"); | |
workshopElement.setAttribute("id", id); | |
workshopElement.setAttribute("name", title); | |
xmlDoc.documentElement.appendChild(workshopElement); | |
}); | |
} | |
/** | |
* Serializes the XML document to a string, formats it for readability, and initiates a download. | |
* The resulting file is saved in UTF-8 with BOM encoding to ensure compatibility with Barotrauma. | |
* @param {Document} xmlDoc - The XML document to serialize. | |
* @param {string} modpackTitle - The title of the mod collection, used as the filename. | |
*/ | |
function serializeAndDownloadXml(xmlDoc, modpackTitle) { | |
const serializer = new XMLSerializer(); | |
let xmlString = serializer.serializeToString(xmlDoc); | |
// Format XML for readability | |
xmlString = xmlString.replace(/><(\/?)/g, '>\n<$1'); | |
xmlString = `<?xml version="1.0" encoding="utf-8"?>\n${xmlString}`; | |
xmlString = xmlString.replace(/(<[V!WL].+)/gi, " $1"); | |
// Create and download the file with UTF-8 with BOM encoding | |
const blob = new Blob(['\ufeff' + xmlString], { encoding: "UTF-8", type: "application/xml; charset=UTF-8" }); | |
const url = URL.createObjectURL(blob); | |
const a = document.createElement('a'); | |
a.href = url; | |
a.download = `${modpackTitle}.xml`; | |
a.click(); | |
URL.revokeObjectURL(url); | |
console.log(messages.xmlGenerated); | |
console.log(MODS_FOLDER_PATH); | |
} | |
// Main script | |
(async () => { | |
// Get language-specific messages | |
messages = getMessages(); | |
try { | |
// Step 0: Wait for the page to fully load | |
console.log(messages.waitingForPage); | |
await waitForPageLoad(); | |
console.log(messages.pageLoaded); | |
// Step 1: Retrieve the mod collection title | |
const modpackTitle = getModpackTitle(); | |
// Step 2: Extract mod information from the page | |
const mods = extractModsFromPage(); | |
// Step 3: Create the XML document | |
const xmlDoc = createXmlDocument(modpackTitle); | |
// Step 4: Add pinned mods to the XML document | |
addPinnedMods(xmlDoc, mods); | |
// Step 5: Add sorted mods to the XML document | |
addSortedMods(xmlDoc, mods); | |
// Step 6: Add a comment with the generation date and version | |
const currentUTCDate = new Date().toISOString().split('T')[0]; | |
const creditsHeader = xmlDoc.createComment(` Generated with https://gist.github.com/Gesugao-san/464ff0864c2d49e4cc9c7aebeb1ac795/ (v${VERSION.toString()}) on ${currentUTCDate} UTC `); | |
xmlDoc.documentElement.appendChild(creditsHeader); | |
// Step 7: Serialize the XML document and initiate the download | |
serializeAndDownloadXml(xmlDoc, modpackTitle); | |
} catch (error) { | |
console.error(messages.errorOccurred, error); | |
} | |
})(); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ONLY FOR PAGE https://steamcommunity.com/sharedfiles/managecollection/?id=__ID__ | |
// abandoned, do not use please :3 | |
if (typeof jQuery !== 'undefined') { | |
console.log("jQuery version:", jQuery.fn.jquery); | |
} else { | |
new Error("jQuery is not loaded on this page."); | |
} | |
function SortByTimeUpdatedWithPriority() { | |
SetLastSort('timeupdated'); | |
SortChildItems(function(a, b) { | |
var a_sort_data = $J(a).data("timeupdated"); | |
var b_sort_data = $J(b).data("timeupdated"); | |
// Проверяем, есть ли у элементов приоритет (например, флаг "isPriority") | |
var a_is_priority = $J(a).data("isPriority") || false; | |
var b_is_priority = $J(b).data("isPriority") || false; | |
// Если оба элемента имеют приоритет или оба не имеют, сортируем по времени | |
if (a_is_priority === b_is_priority) { | |
var sortFactor = gSortAscending ? 1 : -1; | |
return sortFactor * (a_sort_data - b_sort_data); | |
} | |
// Если один из элементов имеет приоритет, он всегда выше | |
return a_is_priority ? -1 : 1; | |
}); | |
} | |
function addPriorityAttribute(elements, priorityIds) { | |
// Проходим по каждому элементу | |
elements.forEach(element => { | |
// Проверяем, есть ли у элемента атрибут "id" | |
const id = parseInt(element.getAttribute('id').split("_")[1]); | |
// Если атрибут "id" есть и его значение содержится в списке priorityIds | |
if (id && priorityIds.includes(id)) { | |
// Проверяем, есть ли уже атрибут data-isPriority | |
if (!element.hasAttribute('data-isPriority')) { | |
// Добавляем атрибут data-isPriority="true" | |
element.setAttribute('data-isPriority', 'true'); | |
console.log(`Added data-isPriority="true" to element with id="${id}"`); | |
} else { | |
console.log(`Element with id="${id}" already has data-isPriority`); | |
} | |
} | |
}); | |
} | |
// Пример использования: | |
// Список элементов (например, полученных через querySelectorAll) | |
const elements = document.querySelectorAll('[id]'); // Ищем все элементы с атрибутом "id" | |
// Список id, которые должны получить атрибут data-isPriority="true" | |
const priorityIds = [ | |
2795927223, // C# | |
3294275235, | |
3424487895 | |
]; | |
// Вызываем функцию | |
addPriorityAttribute(elements, priorityIds); | |
// Находим элемент по id | |
const sortButton = document.getElementById('sort_timeupdated'); | |
// Проверяем, существует ли элемент | |
if (sortButton) { | |
// Создаем новый div | |
const sortButtonPriority = document.createElement('div'); | |
sortButtonPriority.class = 'btnv6_blue_blue_innerfade btn_small_thin ico_hover sort_button'; | |
sortButtonPriority.id = 'sort_timeupdated_priority'; | |
sortButtonPriority.textContent = 'SortByTimeUpdatedWithPriority();'; | |
sortButtonPriority.setAttribute('onclick', 'SortByTimeUpdatedWithPriority();'); | |
// Вставляем новый div после элемента sortTimeUpdatedElement | |
sortButton.insertAdjacentElement('afterend', sortButtonPriority); | |
console.log('New div added after #sort_timeupdated'); | |
} else { | |
console.error('Element with id="sort_timeupdated" not found'); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment