// ==UserScript==
// @name Gta5Mods to FiveM resource tool
// @namespace https://gta5mods.hk416.org/
// @version 1.3
// @description A tool that can convert gta5-mods.com mods to FiveM resources.
// @author Akkariin
// @license MIT
// @match *://*.gta5-mods.com/*
// @require https://cdn.jsdelivr.net/npm/[email protected]/dist/sweetalert2.all.min.js
// @grant GM_xmlhttpRequest
// ==/UserScript==
(function() {
'use strict';
// --- Configuration ---
const CONFIG = {
apiBaseUrl: "https://gta5mods.hk416.org",
fileContainerSelector: "#file-container",
downloadButtonHookSelector: ".btn-download",
fivemButtonClass: "downloadFiveMBtn",
localStorageKey: "convertUid_g2f",
pollIntervalMs: 1500,
successMessageResetDelayMs: 3000
};
const SCRIPT_TEXTS = {
en: {
buttonText: "<i class='fa fa-download'></i> Convert to FiveM resource",
buttonStatusSubmitting: '<i class="fa fa-spinner fa-spin"></i> Submitting...',
buttonStatusSubmitted: "<i class='fa fa-check'></i> Task submitted, ID: ",
buttonStatusConverting: '<i class="fa fa-circle-o-notch fa-spin"></i> ',
buttonStatusSuccess: "<i class='fa fa-check'></i> Convert finished: ",
buttonStatusRestoring: '<i class="fa fa-spinner fa-spin"></i> Restoring state...',
popupErrorTitle: "Error",
popupSubmitError: "Failed to submit the task to the server.",
popupSubmitErrorWithDetails: "Failed to submit the task: ",
popupStatusFetchError: "Failed to get task status from the server.",
popupConvertError: "Conversion failed: ",
popupParseError: "An error occurred while processing the server response. Please try again."
},
zh: {
buttonText: "<i class='fa fa-download'></i> 转换为 FiveM 资源",
buttonStatusSubmitting: '<i class="fa fa-spinner fa-spin"></i> 提交中...',
buttonStatusSubmitted: "<i class='fa fa-check'></i> 任务已提交,ID: ",
buttonStatusConverting: '<i class="fa fa-circle-o-notch fa-spin"></i> ',
buttonStatusSuccess: "<i class='fa fa-check'></i> 转换完成: ",
buttonStatusRestoring: '<i class="fa fa-spinner fa-spin"></i> 恢复状态中...',
popupErrorTitle: "错误",
popupSubmitError: "提交任务到服务器失败。",
popupSubmitErrorWithDetails: "提交任务失败: ",
popupStatusFetchError: "从服务器获取任务状态失败。",
popupConvertError: "转换失败: ",
popupParseError: "处理服务器响应时发生错误,请重试。"
}
};
// --- Initialization ---
if (!document.querySelector(CONFIG.fileContainerSelector)) {
console.log("Gta5Mods to FiveM tool: #file-container not found. Script will not run on this page.");
return;
}
let currentLang = 'en';
switch (window.location.hostname) {
case "zh.gta5-mods.com":
currentLang = 'zh';
break;
default:
currentLang = 'en';
}
const TEXTS = SCRIPT_TEXTS[currentLang];
const API_ENDPOINT = CONFIG.apiBaseUrl + (currentLang === 'zh' ? "/" : "/en");
const API_LANG_PARAM = currentLang === 'zh' ? 'zh_CN' : 'en_US';
let $fivemButton;
// --- Helper Functions ---
function resetButtonToDefaultState() {
updateButtonUI(TEXTS.buttonText, false);
if ($fivemButton) { // Ensure $fivemButton is defined
$fivemButton.off('click').on('click', handleSubmitConversion);
}
localStorage.removeItem(CONFIG.localStorageKey);
}
function showErrorAlert(message) {
Swal.fire({
icon: 'error',
title: TEXTS.popupErrorTitle,
text: message,
}).then(() => {
resetButtonToDefaultState();
});
}
function updateButtonUI(htmlContent, disabled) {
if ($fivemButton) {
$fivemButton.html(htmlContent);
if (disabled) {
$fivemButton.attr('disabled', 'disabled');
} else {
$fivemButton.removeAttr('disabled');
}
}
}
function downloadFile(url, filename) {
window.location.href = url;
}
// --- Core Logic ---
function pollConversionStatus(uuid) {
GM_xmlhttpRequest({
method: 'POST',
url: API_ENDPOINT,
data: `uuid=${encodeURIComponent(uuid)}&lang=${encodeURIComponent(API_LANG_PARAM)}`,
headers: {
"Content-Type": "application/x-www-form-urlencoded"
},
onload: function(response) {
if (response.status >= 200 && response.status < 300) {
try {
const json = JSON.parse(response.responseText);
if (json.status == 200) { // Success
updateButtonUI(TEXTS.buttonStatusSuccess + json.message.name, true);
localStorage.removeItem(CONFIG.localStorageKey);
downloadFile(CONFIG.apiBaseUrl + "/" + json.message.file, json.message.name);
setTimeout(() => {
resetButtonToDefaultState();
}, CONFIG.successMessageResetDelayMs);
} else if (json.status == 101) { // Still processing
updateButtonUI(TEXTS.buttonStatusConverting + json.message, true);
setTimeout(() => pollConversionStatus(uuid), CONFIG.pollIntervalMs);
} else { // Other error from API
showErrorAlert(TEXTS.popupConvertError + (json.message || 'Unknown API error'));
}
} catch (e) {
console.error("Error parsing polling response:", e, response.responseText);
showErrorAlert(TEXTS.popupParseError);
}
} else {
console.error("Polling request failed with HTTP status:", response.status, response.responseText);
showErrorAlert(TEXTS.popupStatusFetchError + ` (Status: ${response.status})`);
}
},
onerror: function(response) {
console.error("Polling request network error:", response);
showErrorAlert(TEXTS.popupStatusFetchError);
}
});
}
function handleSubmitConversion() {
const pageUrl = window.location.href.substring(0, window.location.href.length - window.location.hash.length);
if (pageUrl === "") return;
updateButtonUI(TEXTS.buttonStatusSubmitting, true);
GM_xmlhttpRequest({
method: 'POST',
url: API_ENDPOINT,
data: `url=${encodeURIComponent(pageUrl)}`,
headers: {
"Content-Type": "application/x-www-form-urlencoded"
},
onload: function(response) {
if (response.status >= 200 && response.status < 300) {
try {
const json = JSON.parse(response.responseText);
if (json.status == 200) {
updateButtonUI(TEXTS.buttonStatusSubmitted + json.message, true);
localStorage.setItem(CONFIG.localStorageKey, json.message);
pollConversionStatus(json.message);
} else {
showErrorAlert(TEXTS.popupSubmitErrorWithDetails + (json.message || 'No details provided.'));
}
} catch (e) {
console.error("Error parsing submission response:", e, response.responseText);
showErrorAlert(TEXTS.popupParseError);
}
} else {
console.error("Submission request failed with HTTP status:", response.status, response.responseText);
let apiErrorMessage = `Server responded with status ${response.status}.`;
try {
const json = JSON.parse(response.responseText);
if (json && json.message) {
apiErrorMessage = json.message;
}
} catch (e) {
// Ignore parse error if response is not JSON
}
showErrorAlert(TEXTS.popupSubmitErrorWithDetails + apiErrorMessage);
}
},
onerror: function(response) {
console.error("Submission request network error:", response);
showErrorAlert(TEXTS.popupSubmitError);
}
});
}
// --- UI Setup and Initialization ---
function initialize() {
const styleElement = document.createElement('style');
styleElement.textContent = `
.${CONFIG.fivemButtonClass} {
width: 100%;
margin-bottom: 10px;
}
.swal2-popup {
font-size: 1.2em !important;
}
`;
document.head.appendChild(styleElement);
// Ensure jQuery is available before using $
if (typeof $ === 'undefined') {
console.error("Gta5Mods to FiveM tool: jQuery is not available. The script might not work correctly.");
return;
}
$fivemButton = $("<button class='btn btn-default " + CONFIG.fivemButtonClass + "'></button>");
const $downloadButtonHook = $(CONFIG.downloadButtonHookSelector);
if ($downloadButtonHook.length > 0) {
if ($downloadButtonHook.css('display') === 'inline' || $downloadButtonHook.css('display') === 'inline-block') {
$fivemButton.wrap('<p></p>').parent().insertAfter($downloadButtonHook);
} else {
$fivemButton.insertAfter($downloadButtonHook);
}
} else {
console.warn("Gta5Mods to FiveM tool: Download button hook selector not found. Button not added.");
return;
}
const existingUuid = localStorage.getItem(CONFIG.localStorageKey);
if (existingUuid) {
updateButtonUI(TEXTS.buttonStatusRestoring, true);
pollConversionStatus(existingUuid);
} else {
resetButtonToDefaultState();
}
}
if (typeof $ === 'function') {
$(document).ready(initialize);
} else {
window.addEventListener('DOMContentLoaded', initialize);
}
})();