Skip to content

Instantly share code, notes, and snippets.

@kasuganosoras
Last active December 4, 2025 03:50
Show Gist options
  • Select an option

  • Save kasuganosoras/9a5bfe88f84aba5135ac308a260fe0f2 to your computer and use it in GitHub Desktop.

Select an option

Save kasuganosoras/9a5bfe88f84aba5135ac308a260fe0f2 to your computer and use it in GitHub Desktop.
GTA5-Mods convert to FiveM resource
// ==UserScript==
// @name Gta5Mods to FiveM resource tool
// @namespace https://gta5mods.hk416.org/
// @version 2.1
// @description A tool that can convert gta5-mods.com mods to FiveM resources.
// @author Akkariin
// @match *://*.gta5-mods.com/*
// @require https://cdn.jsdelivr.net/npm/sweetalert2@11.22.0/dist/sweetalert2.all.min.js
// @grant GM_xmlhttpRequest
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_openInTab
// ==/UserScript==
(function() {
'use strict';
// --- Update Configuration ---
const UPDATE_CONFIG = {
checkUrl: "https://cdn.worldofcraft.cn/fivem-converter-update.json",
downloadUrl: "https://gist.github.com/kasuganosoras/9a5bfe88f84aba5135ac308a260fe0f2",
ignoreVersionKey: "g2f_ignored_version"
};
// --- Configuration ---
const CONFIG = {
apiBaseUrl: "https://convert.cfx.rs",
fileContainerSelector: "#file-container",
downloadButtonHookSelector: ".btn-download",
fivemButtonClass: "downloadFiveMBtn",
localStorageKey: "convertUid_g2f",
pollIntervalMs: 1500,
successMessageResetDelayMs: 3000,
currentVersion: "2.1"
};
const SCRIPT_TEXTS = {
en: {
buttonText: "<i class='fa fa-download'></i>&nbsp;&nbsp;Convert to FiveM resource",
buttonStatusSubmitting: '<i class="fa fa-spinner fa-spin"></i>&nbsp;Submitting...',
buttonStatusSubmitted: "<i class='fa fa-check'></i>&nbsp;Task submitted, ID: ",
buttonStatusConverting: '<i class="fa fa-circle-o-notch fa-spin"></i>&nbsp;',
buttonStatusSuccess: "<i class='fa fa-check'></i>&nbsp;Convert finished: ",
buttonStatusRestoring: '<i class="fa fa-spinner fa-spin"></i>&nbsp;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.",
// Update UI
updateTitle: "New Version Available",
updateMsg: "New version <b>{v}</b> is available. Update now?",
updateBtnYes: "Update Now",
updateBtnNo: "Cancel",
updateBtnIgnore: "Don't Remind"
},
zh: {
buttonText: "<i class='fa fa-download'></i>&nbsp;&nbsp;转换为 FiveM 资源",
buttonStatusSubmitting: '<i class="fa fa-spinner fa-spin"></i>&nbsp;提交中...',
buttonStatusSubmitted: "<i class='fa fa-check'></i>&nbsp;任务已提交,ID: ",
buttonStatusConverting: '<i class="fa fa-circle-o-notch fa-spin"></i>&nbsp;',
buttonStatusSuccess: "<i class='fa fa-check'></i>&nbsp;转换完成: ",
buttonStatusRestoring: '<i class="fa fa-spinner fa-spin"></i>&nbsp;恢复状态中...',
popupErrorTitle: "错误",
popupSubmitError: "提交任务到服务器失败。",
popupSubmitErrorWithDetails: "提交任务失败: ",
popupStatusFetchError: "从服务器获取任务状态失败。",
popupConvertError: "转换失败: ",
popupParseError: "处理服务器响应时发生错误,请重试。",
// Update UI
updateTitle: "发现新版本",
updateMsg: "检测到新版本 <b>{v}</b>,是否立即更新?",
updateBtnYes: "立即更新",
updateBtnNo: "取消",
updateBtnIgnore: "不再提醒此版本"
}
};
// --- 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';
if (window.location.hostname === "zh.gta5-mods.com") {
currentLang = 'zh';
}
const TEXTS = SCRIPT_TEXTS[currentLang];
const API_LANG_PARAM = currentLang === 'zh' ? 'zh-CN' : 'en';
const SUBMIT_ENDPOINT = CONFIG.apiBaseUrl + "/api/convert";
const QUERY_ENDPOINT = CONFIG.apiBaseUrl + "/api/query";
let $fivemButton;
// --- Version Compare Helper ---
function compareVersions(v1, v2) {
const v1parts = v1.split('.').map(Number);
const v2parts = v2.split('.').map(Number);
for (let i = 0; i < Math.max(v1parts.length, v2parts.length); ++i) {
const val1 = v1parts[i] || 0;
const val2 = v2parts[i] || 0;
if (val1 < val2) return -1;
if (val1 > val2) return 1;
}
return 0;
}
// --- Update Check Logic ---
function checkForUpdate() {
if (UPDATE_CONFIG.checkUrl == "") return;
GM_xmlhttpRequest({
method: 'GET',
url: UPDATE_CONFIG.checkUrl + "?t=" + new Date().getTime(), // prevent cache
onload: function(response) {
if (response.status >= 200 && response.status < 300) {
try {
const json = JSON.parse(response.responseText);
const latestVer = json.version;
if (latestVer && compareVersions(latestVer, CONFIG.currentVersion) > 0) {
// Check if ignored
const ignoredVer = localStorage.getItem(UPDATE_CONFIG.ignoreVersionKey);
if (ignoredVer === latestVer) {
console.log("Gta5Mods to FiveM tool: User ignored update to " + latestVer);
return;
}
// Show Update Popup
Swal.fire({
title: TEXTS.updateTitle,
html: TEXTS.updateMsg.replace('{v}', latestVer),
icon: 'info',
showCancelButton: true,
showDenyButton: true,
confirmButtonText: TEXTS.updateBtnYes,
denyButtonText: TEXTS.updateBtnIgnore,
cancelButtonText: TEXTS.updateBtnNo,
reverseButtons: true
}).then((result) => {
if (result.isConfirmed) {
// Open download link
window.open(UPDATE_CONFIG.downloadUrl, '_blank');
} else if (result.isDenied) {
// Ignore this version
localStorage.setItem(UPDATE_CONFIG.ignoreVersionKey, latestVer);
}
});
}
} catch (e) {
console.warn("Gta5Mods to FiveM tool: Failed to parse update info.", e);
}
}
}
});
}
// --- Helper Functions ---
function resetButtonToDefaultState() {
updateButtonUI(TEXTS.buttonText, false);
if ($fivemButton) {
$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) {
window.location.href = url;
}
// --- Core Logic ---
function pollConversionStatus(uuid) {
GM_xmlhttpRequest({
method: 'POST',
url: QUERY_ENDPOINT,
data: `uuid=${encodeURIComponent(uuid)}&lang=${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) {
updateButtonUI(TEXTS.buttonStatusSuccess + json.name, true);
localStorage.removeItem(CONFIG.localStorageKey);
const downloadUrl = CONFIG.apiBaseUrl + "/" + json.file;
downloadFile(downloadUrl);
setTimeout(() => {
resetButtonToDefaultState();
}, CONFIG.successMessageResetDelayMs);
} else if (json.status === 101) {
updateButtonUI(TEXTS.buttonStatusConverting + json.message, true);
setTimeout(() => pollConversionStatus(uuid), CONFIG.pollIntervalMs);
} else {
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);
if (response.status === 404) {
showErrorAlert("Task not found or expired.");
} else {
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.split('#')[0];
if (pageUrl === "") return;
updateButtonUI(TEXTS.buttonStatusSubmitting, true);
GM_xmlhttpRequest({
method: 'POST',
url: SUBMIT_ENDPOINT,
data: `url=${encodeURIComponent(pageUrl)}&lang=${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) {
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 */ }
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);
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();
}
// Check for updates after UI is ready
setTimeout(checkForUpdate, 1000);
}
if (typeof $ === 'function') {
$(document).ready(initialize);
} else {
window.addEventListener('DOMContentLoaded', initialize);
}
})();
@TurboScripts
Copy link

.

@Jannik-Moeller
Copy link

Wie installiert man dieses script? Frage an alle da draußen!

@LibertyCitys
Copy link

how to install

@AsianTheProtogen
Copy link

Sick! Will really come in useful.

@max21l
Copy link

max21l commented Jan 3, 2025

Wie installiert man dieses script? Frage an alle da draußen!
Du installierst im browser die erweiterung Tampermonkey und drückst dort auf Neues Userscript erstellen dort fügst du den Code ein und speicherst es.

@kasuganosoras
Copy link
Author

Refactored the script to adapt to the new converter website API and support multiple languages.

@MrWhitee4
Copy link

Refactored the script to adapt to the new converter website API and support multiple languages.

An automatic push on Tempermonkey would have been great :D I was wondering why it was broken until I checked on this GIT :D

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment