First commit for PR

This commit is contained in:
drori200 2021-10-30 09:03:58 +03:00
parent a2168da999
commit e6faf6f8bd
4 changed files with 193 additions and 184 deletions

View File

@ -2,24 +2,24 @@
* Script for landing.ejs * Script for landing.ejs
*/ */
// Requirements // Requirements
const cp = require('child_process') const cp = require('child_process')
const crypto = require('crypto') const crypto = require('crypto')
const {URL} = require('url') const { URL } = require('url')
// Internal Requirements // Internal Requirements
const DiscordWrapper = require('./assets/js/discordwrapper') const DiscordWrapper = require('./assets/js/discordwrapper')
const Mojang = require('./assets/js/mojang') const Mojang = require('./assets/js/mojang')
const ProcessBuilder = require('./assets/js/processbuilder') const ProcessBuilder = require('./assets/js/processbuilder')
const ServerStatus = require('./assets/js/serverstatus') const ServerStatus = require('./assets/js/serverstatus')
// Launch Elements // Launch Elements
const launch_content = document.getElementById('launch_content') const launch_content = document.getElementById('launch_content')
const launch_details = document.getElementById('launch_details') const launch_details = document.getElementById('launch_details')
const launch_progress = document.getElementById('launch_progress') const launch_progress = document.getElementById('launch_progress')
const launch_progress_label = document.getElementById('launch_progress_label') const launch_progress_label = document.getElementById('launch_progress_label')
const launch_details_text = document.getElementById('launch_details_text') const launch_details_text = document.getElementById('launch_details_text')
const server_selection_button = document.getElementById('server_selection_button') const server_selection_button = document.getElementById('server_selection_button')
const user_text = document.getElementById('user_text') const user_text = document.getElementById('user_text')
const loggerLanding = LoggerUtil('%c[Landing]', 'color: #000668; font-weight: bold') const loggerLanding = LoggerUtil('%c[Landing]', 'color: #000668; font-weight: bold')
@ -30,8 +30,8 @@ const loggerLanding = LoggerUtil('%c[Landing]', 'color: #000668; font-weight: bo
* *
* @param {boolean} loading True if the loading area should be shown, otherwise false. * @param {boolean} loading True if the loading area should be shown, otherwise false.
*/ */
function toggleLaunchArea(loading){ function toggleLaunchArea(loading) {
if(loading){ if (loading) {
launch_details.style.display = 'flex' launch_details.style.display = 'flex'
launch_content.style.display = 'none' launch_content.style.display = 'none'
} else { } else {
@ -45,7 +45,7 @@ function toggleLaunchArea(loading){
* *
* @param {string} details The new text for the loading details. * @param {string} details The new text for the loading details.
*/ */
function setLaunchDetails(details){ function setLaunchDetails(details) {
launch_details_text.innerHTML = details launch_details_text.innerHTML = details
} }
@ -56,7 +56,7 @@ function setLaunchDetails(details){
* @param {number} max The total size. * @param {number} max The total size.
* @param {number|string} percent Optional. The percentage to display on the progress label. * @param {number|string} percent Optional. The percentage to display on the progress label.
*/ */
function setLaunchPercentage(value, max, percent = ((value/max)*100)){ function setLaunchPercentage(value, max, percent = ((value / max) * 100)) {
launch_progress.setAttribute('max', max) launch_progress.setAttribute('max', max)
launch_progress.setAttribute('value', value) launch_progress.setAttribute('value', value)
launch_progress_label.innerHTML = percent + '%' launch_progress_label.innerHTML = percent + '%'
@ -69,8 +69,8 @@ function setLaunchPercentage(value, max, percent = ((value/max)*100)){
* @param {number} max The total download size. * @param {number} max The total download size.
* @param {number|string} percent Optional. The percentage to display on the progress label. * @param {number|string} percent Optional. The percentage to display on the progress label.
*/ */
function setDownloadPercentage(value, max, percent = ((value/max)*100)){ function setDownloadPercentage(value, max, percent = ((value / max) * 100)) {
remote.getCurrentWindow().setProgressBar(value/max) remote.getCurrentWindow().setProgressBar(value / max)
setLaunchPercentage(value, max, percent) setLaunchPercentage(value, max, percent)
} }
@ -79,16 +79,16 @@ function setDownloadPercentage(value, max, percent = ((value/max)*100)){
* *
* @param {boolean} val True to enable, false to disable. * @param {boolean} val True to enable, false to disable.
*/ */
function setLaunchEnabled(val){ function setLaunchEnabled(val) {
document.getElementById('launch_button').disabled = !val document.getElementById('launch_button').disabled = !val
} }
// Bind launch button // Bind launch button
document.getElementById('launch_button').addEventListener('click', function(e){ document.getElementById('launch_button').addEventListener('click', function (e) {
loggerLanding.log('Launching game..') loggerLanding.log('Launching game..')
const mcVersion = DistroManager.getDistribution().getServer(ConfigManager.getSelectedServer()).getMinecraftVersion() const mcVersion = DistroManager.getDistribution().getServer(ConfigManager.getSelectedServer()).getMinecraftVersion()
const jExe = ConfigManager.getJavaExecutable() const jExe = ConfigManager.getJavaExecutable()
if(jExe == null){ if (jExe == null) {
asyncSystemScan(mcVersion) asyncSystemScan(mcVersion)
} else { } else {
@ -99,7 +99,7 @@ document.getElementById('launch_button').addEventListener('click', function(e){
const jg = new JavaGuard(mcVersion) const jg = new JavaGuard(mcVersion)
jg._validateJavaBinary(jExe).then((v) => { jg._validateJavaBinary(jExe).then((v) => {
loggerLanding.log('Java version meta', v) loggerLanding.log('Java version meta', v)
if(v.valid){ if (v.valid) {
dlAsync() dlAsync()
} else { } else {
asyncSystemScan(mcVersion) asyncSystemScan(mcVersion)
@ -123,13 +123,13 @@ document.getElementById('avatarOverlay').onclick = (e) => {
} }
// Bind selected account // Bind selected account
function updateSelectedAccount(authUser){ function updateSelectedAccount(authUser) {
let username = 'No Account Selected' let username = 'No Account Selected'
if(authUser != null){ if (authUser != null) {
if(authUser.displayName != null){ if (authUser.displayName != null) {
username = authUser.displayName username = authUser.displayName
} }
if(authUser.uuid != null){ if (authUser.uuid != null) {
document.getElementById('avatarContainer').style.backgroundImage = `url('https://mc-heads.net/body/${authUser.uuid}/right')` document.getElementById('avatarContainer').style.backgroundImage = `url('https://mc-heads.net/body/${authUser.uuid}/right')`
} }
} }
@ -138,14 +138,14 @@ function updateSelectedAccount(authUser){
updateSelectedAccount(ConfigManager.getSelectedAccount()) updateSelectedAccount(ConfigManager.getSelectedAccount())
// Bind selected server // Bind selected server
function updateSelectedServer(serv){ function updateSelectedServer(serv) {
if(getCurrentView() === VIEWS.settings){ if (getCurrentView() === VIEWS.settings) {
saveAllModConfigurations() saveAllModConfigurations()
} }
ConfigManager.setSelectedServer(serv != null ? serv.getID() : null) ConfigManager.setSelectedServer(serv != null ? serv.getID() : null)
ConfigManager.save() ConfigManager.save()
server_selection_button.innerHTML = '\u2022 ' + (serv != null ? serv.getName() : 'No Server Selected') server_selection_button.innerHTML = '\u2022 ' + (serv != null ? serv.getName() : 'No Server Selected')
if(getCurrentView() === VIEWS.settings){ if (getCurrentView() === VIEWS.settings) {
animateModsTabRefresh() animateModsTabRefresh()
} }
setLaunchEnabled(serv != null) setLaunchEnabled(serv != null)
@ -158,7 +158,7 @@ server_selection_button.onclick = (e) => {
} }
// Update Mojang Status Color // Update Mojang Status Color
const refreshMojangStatuses = async function(){ const refreshMojangStatuses = async function () {
loggerLanding.log('Refreshing Mojang Statuses..') loggerLanding.log('Refreshing Mojang Statuses..')
let status = 'grey' let status = 'grey'
@ -170,10 +170,10 @@ const refreshMojangStatuses = async function(){
greenCount = 0 greenCount = 0
greyCount = 0 greyCount = 0
for(let i=0; i<statuses.length; i++){ for (let i = 0; i < statuses.length; i++) {
const service = statuses[i] const service = statuses[i]
if(service.essential){ if (service.essential) {
tooltipEssentialHTML += `<div class="mojangStatusContainer"> tooltipEssentialHTML += `<div class="mojangStatusContainer">
<span class="mojangStatusIcon" style="color: ${Mojang.statusToHex(service.status)};">&#8226;</span> <span class="mojangStatusIcon" style="color: ${Mojang.statusToHex(service.status)};">&#8226;</span>
<span class="mojangStatusName">${service.name}</span> <span class="mojangStatusName">${service.name}</span>
@ -185,12 +185,12 @@ const refreshMojangStatuses = async function(){
</div>` </div>`
} }
if(service.status === 'yellow' && status !== 'red'){ if (service.status === 'yellow' && status !== 'red') {
status = 'yellow' status = 'yellow'
} else if(service.status === 'red'){ } else if (service.status === 'red') {
status = 'red' status = 'red'
} else { } else {
if(service.status === 'grey'){ if (service.status === 'grey') {
++greyCount ++greyCount
} }
++greenCount ++greenCount
@ -198,8 +198,8 @@ const refreshMojangStatuses = async function(){
} }
if(greenCount === statuses.length){ if (greenCount === statuses.length) {
if(greyCount === statuses.length){ if (greyCount === statuses.length) {
status = 'grey' status = 'grey'
} else { } else {
status = 'green' status = 'green'
@ -216,7 +216,7 @@ const refreshMojangStatuses = async function(){
document.getElementById('mojang_status_icon').style.color = Mojang.statusToHex(status) document.getElementById('mojang_status_icon').style.color = Mojang.statusToHex(status)
} }
const refreshServerStatus = async function(fade = false){ const refreshServerStatus = async function (fade = false) {
loggerLanding.log('Refreshing Server Status') loggerLanding.log('Refreshing Server Status')
const serv = DistroManager.getDistribution().getServer(ConfigManager.getSelectedServer()) const serv = DistroManager.getDistribution().getServer(ConfigManager.getSelectedServer())
@ -224,18 +224,17 @@ const refreshServerStatus = async function(fade = false){
let pVal = 'OFFLINE' let pVal = 'OFFLINE'
try { try {
const serverURL = new URL('my://' + serv.getAddress()) const serverAddress = serv.getAddress()
const servStat = await ServerStatus.getStatus(serverURL.hostname, serverURL.port) const servStat = await ServerStatus.getStatus(serverAddress)
if(servStat.online){ if (servStat.online) {
pLabel = 'PLAYERS' pLabel = 'PLAYERS'
pVal = servStat.onlinePlayers + '/' + servStat.maxPlayers pVal = servStat.onlinePlayers + '/' + servStat.maxPlayers
} }
} catch (err) { } catch (err) {
loggerLanding.warn('Unable to refresh server status, assuming offline.') loggerLanding.warn('Unable to refresh server status, assuming offline.')
loggerLanding.debug(err)
} }
if(fade){ if (fade) {
$('#server_status_wrapper').fadeOut(250, () => { $('#server_status_wrapper').fadeOut(250, () => {
document.getElementById('landingPlayerLabel').innerHTML = pLabel document.getElementById('landingPlayerLabel').innerHTML = pLabel
document.getElementById('player_count').innerHTML = pVal document.getElementById('player_count').innerHTML = pVal
@ -261,7 +260,7 @@ let serverStatusListener = setInterval(() => refreshServerStatus(true), 300000)
* @param {string} title The overlay title. * @param {string} title The overlay title.
* @param {string} desc The overlay description. * @param {string} desc The overlay description.
*/ */
function showLaunchFailure(title, desc){ function showLaunchFailure(title, desc) {
setOverlayContent( setOverlayContent(
title, title,
desc, desc,
@ -285,7 +284,7 @@ let extractListener
* @param {string} mcVersion The Minecraft version we are scanning for. * @param {string} mcVersion The Minecraft version we are scanning for.
* @param {boolean} launchAfter Whether we should begin to launch after scanning. * @param {boolean} launchAfter Whether we should begin to launch after scanning.
*/ */
function asyncSystemScan(mcVersion, launchAfter = true){ function asyncSystemScan(mcVersion, launchAfter = true) {
setLaunchDetails('Please wait..') setLaunchDetails('Please wait..')
toggleLaunchArea(true) toggleLaunchArea(true)
@ -317,8 +316,8 @@ function asyncSystemScan(mcVersion, launchAfter = true){
sysAEx.on('message', (m) => { sysAEx.on('message', (m) => {
if(m.context === 'validateJava'){ if (m.context === 'validateJava') {
if(m.result == null){ if (m.result == null) {
// If the result is null, no valid Java installation was found. // If the result is null, no valid Java installation was found.
// Show this information to the user. // Show this information to the user.
setOverlayContent( setOverlayContent(
@ -329,8 +328,8 @@ function asyncSystemScan(mcVersion, launchAfter = true){
) )
setOverlayHandler(() => { setOverlayHandler(() => {
setLaunchDetails('Preparing Java Download..') setLaunchDetails('Preparing Java Download..')
sysAEx.send({task: 'changeContext', class: 'AssetGuard', args: [ConfigManager.getCommonDirectory(),ConfigManager.getJavaExecutable()]}) sysAEx.send({ task: 'changeContext', class: 'AssetGuard', args: [ConfigManager.getCommonDirectory(), ConfigManager.getJavaExecutable()] })
sysAEx.send({task: 'execute', function: '_enqueueOpenJDK', argsArr: [ConfigManager.getDataDirectory()]}) sysAEx.send({ task: 'execute', function: '_enqueueOpenJDK', argsArr: [ConfigManager.getDataDirectory()] })
toggleOverlay(false) toggleOverlay(false)
}) })
setDismissHandler(() => { setDismissHandler(() => {
@ -365,18 +364,18 @@ function asyncSystemScan(mcVersion, launchAfter = true){
settingsJavaExecVal.value = m.result settingsJavaExecVal.value = m.result
populateJavaExecDetails(settingsJavaExecVal.value) populateJavaExecDetails(settingsJavaExecVal.value)
if(launchAfter){ if (launchAfter) {
dlAsync() dlAsync()
} }
sysAEx.disconnect() sysAEx.disconnect()
} }
} else if(m.context === '_enqueueOpenJDK'){ } else if (m.context === '_enqueueOpenJDK') {
if(m.result === true){ if (m.result === true) {
// Oracle JRE enqueued successfully, begin download. // Oracle JRE enqueued successfully, begin download.
setLaunchDetails('Downloading Java..') setLaunchDetails('Downloading Java..')
sysAEx.send({task: 'execute', function: 'processDlQueues', argsArr: [[{id:'java', limit:1}]]}) sysAEx.send({ task: 'execute', function: 'processDlQueues', argsArr: [[{ id: 'java', limit: 1 }]] })
} else { } else {
@ -396,18 +395,18 @@ function asyncSystemScan(mcVersion, launchAfter = true){
} }
} else if(m.context === 'progress'){ } else if (m.context === 'progress') {
switch(m.data){ switch (m.data) {
case 'download': case 'download':
// Downloading.. // Downloading..
setDownloadPercentage(m.value, m.total, m.percent) setDownloadPercentage(m.value, m.total, m.percent)
break break
} }
} else if(m.context === 'complete'){ } else if (m.context === 'complete') {
switch(m.data){ switch (m.data) {
case 'download': { case 'download': {
// Show installing progress bar. // Show installing progress bar.
remote.getCurrentWindow().setProgressBar(2) remote.getCurrentWindow().setProgressBar(2)
@ -417,7 +416,7 @@ function asyncSystemScan(mcVersion, launchAfter = true){
let dotStr = '' let dotStr = ''
setLaunchDetails(eLStr) setLaunchDetails(eLStr)
extractListener = setInterval(() => { extractListener = setInterval(() => {
if(dotStr.length >= 3){ if (dotStr.length >= 3) {
dotStr = '' dotStr = ''
} else { } else {
dotStr += '.' dotStr += '.'
@ -427,21 +426,21 @@ function asyncSystemScan(mcVersion, launchAfter = true){
break break
} }
case 'java': case 'java':
// Download & extraction complete, remove the loading from the OS progress bar. // Download & extraction complete, remove the loading from the OS progress bar.
remote.getCurrentWindow().setProgressBar(-1) remote.getCurrentWindow().setProgressBar(-1)
// Extraction completed successfully. // Extraction completed successfully.
ConfigManager.setJavaExecutable(m.args[0]) ConfigManager.setJavaExecutable(m.args[0])
ConfigManager.save() ConfigManager.save()
if(extractListener != null){ if (extractListener != null) {
clearInterval(extractListener) clearInterval(extractListener)
extractListener = null extractListener = null
} }
setLaunchDetails('Java Installed!') setLaunchDetails('Java Installed!')
if(launchAfter){ if (launchAfter) {
dlAsync() dlAsync()
} }
@ -449,14 +448,14 @@ function asyncSystemScan(mcVersion, launchAfter = true){
break break
} }
} else if(m.context === 'error'){ } else if (m.context === 'error') {
console.log(m.error) console.log(m.error)
} }
}) })
// Begin system Java scan. // Begin system Java scan.
setLaunchDetails('Checking system info..') setLaunchDetails('Checking system info..')
sysAEx.send({task: 'execute', function: 'validateJava', argsArr: [ConfigManager.getDataDirectory()]}) sysAEx.send({ task: 'execute', function: 'validateJava', argsArr: [ConfigManager.getDataDirectory()] })
} }
@ -477,13 +476,13 @@ let forgeData
let progressListener let progressListener
function dlAsync(login = true){ function dlAsync(login = true) {
// Login parameter is temporary for debug purposes. Allows testing the validation/downloads without // Login parameter is temporary for debug purposes. Allows testing the validation/downloads without
// launching the game. // launching the game.
if(login) { if (login) {
if(ConfigManager.getSelectedAccount() == null){ if (ConfigManager.getSelectedAccount() == null) {
loggerLanding.error('You must be logged into an account.') loggerLanding.error('You must be logged into an account.')
return return
} }
@ -523,7 +522,7 @@ function dlAsync(login = true){
showLaunchFailure('Error During Launch', err.message || 'See console (CTRL + Shift + i) for more details.') showLaunchFailure('Error During Launch', err.message || 'See console (CTRL + Shift + i) for more details.')
}) })
aEx.on('close', (code, signal) => { aEx.on('close', (code, signal) => {
if(code !== 0){ if (code !== 0) {
loggerLaunchSuite.error(`AssetExec exited with code ${code}, assuming error.`) loggerLaunchSuite.error(`AssetExec exited with code ${code}, assuming error.`)
showLaunchFailure('Error During Launch', 'See console (CTRL + Shift + i) for more details.') showLaunchFailure('Error During Launch', 'See console (CTRL + Shift + i) for more details.')
} }
@ -532,8 +531,8 @@ function dlAsync(login = true){
// Establish communications between the AssetExec and current process. // Establish communications between the AssetExec and current process.
aEx.on('message', (m) => { aEx.on('message', (m) => {
if(m.context === 'validate'){ if (m.context === 'validate') {
switch(m.data){ switch (m.data) {
case 'distribution': case 'distribution':
setLaunchPercentage(20, 100) setLaunchPercentage(20, 100)
loggerLaunchSuite.log('Validated distibution index.') loggerLaunchSuite.log('Validated distibution index.')
@ -560,11 +559,11 @@ function dlAsync(login = true){
setLaunchDetails('Downloading files..') setLaunchDetails('Downloading files..')
break break
} }
} else if(m.context === 'progress'){ } else if (m.context === 'progress') {
switch(m.data){ switch (m.data) {
case 'assets': { case 'assets': {
const perc = (m.value/m.total)*20 const perc = (m.value / m.total) * 20
setLaunchPercentage(40+perc, 100, parseInt(40+perc)) setLaunchPercentage(40 + perc, 100, parseInt(40 + perc))
break break
} }
case 'download': case 'download':
@ -579,7 +578,7 @@ function dlAsync(login = true){
let dotStr = '' let dotStr = ''
setLaunchDetails(eLStr) setLaunchDetails(eLStr)
progressListener = setInterval(() => { progressListener = setInterval(() => {
if(dotStr.length >= 3){ if (dotStr.length >= 3) {
dotStr = '' dotStr = ''
} else { } else {
dotStr += '.' dotStr += '.'
@ -589,12 +588,12 @@ function dlAsync(login = true){
break break
} }
} }
} else if(m.context === 'complete'){ } else if (m.context === 'complete') {
switch(m.data){ switch (m.data) {
case 'download': case 'download':
// Download and extraction complete, remove the loading from the OS progress bar. // Download and extraction complete, remove the loading from the OS progress bar.
remote.getCurrentWindow().setProgressBar(-1) remote.getCurrentWindow().setProgressBar(-1)
if(progressListener != null){ if (progressListener != null) {
clearInterval(progressListener) clearInterval(progressListener)
progressListener = null progressListener = null
} }
@ -602,12 +601,12 @@ function dlAsync(login = true){
setLaunchDetails('Preparing to launch..') setLaunchDetails('Preparing to launch..')
break break
} }
} else if(m.context === 'error'){ } else if (m.context === 'error') {
switch(m.data){ switch (m.data) {
case 'download': case 'download':
loggerLaunchSuite.error('Error while downloading:', m.error) loggerLaunchSuite.error('Error while downloading:', m.error)
if(m.error.code === 'ENOENT'){ if (m.error.code === 'ENOENT') {
showLaunchFailure( showLaunchFailure(
'Download Error', 'Download Error',
'Could not connect to the file server. Ensure that you are connected to the internet and try again.' 'Could not connect to the file server. Ensure that you are connected to the internet and try again.'
@ -625,12 +624,12 @@ function dlAsync(login = true){
aEx.disconnect() aEx.disconnect()
break break
} }
} else if(m.context === 'validateEverything'){ } else if (m.context === 'validateEverything') {
let allGood = true let allGood = true
// If these properties are not defined it's likely an error. // If these properties are not defined it's likely an error.
if(m.result.forgeData == null || m.result.versionData == null){ if (m.result.forgeData == null || m.result.versionData == null) {
loggerLaunchSuite.error('Error during validation:', m.result) loggerLaunchSuite.error('Error during validation:', m.result)
loggerLaunchSuite.error('Error during launch', m.result.error) loggerLaunchSuite.error('Error during launch', m.result.error)
@ -642,7 +641,7 @@ function dlAsync(login = true){
forgeData = m.result.forgeData forgeData = m.result.forgeData
versionData = m.result.versionData versionData = m.result.versionData
if(login && allGood) { if (login && allGood) {
const authUser = ConfigManager.getSelectedAccount() const authUser = ConfigManager.getSelectedAccount()
loggerLaunchSuite.log(`Sending selected account (${authUser.displayName}) to ProcessBuilder.`) loggerLaunchSuite.log(`Sending selected account (${authUser.displayName}) to ProcessBuilder.`)
let pb = new ProcessBuilder(serv, versionData, forgeData, authUser, remote.app.getVersion()) let pb = new ProcessBuilder(serv, versionData, forgeData, authUser, remote.app.getVersion())
@ -653,7 +652,7 @@ function dlAsync(login = true){
const onLoadComplete = () => { const onLoadComplete = () => {
toggleLaunchArea(false) toggleLaunchArea(false)
if(hasRPC){ if (hasRPC) {
DiscordWrapper.updateDetails('Loading game..') DiscordWrapper.updateDetails('Loading game..')
} }
proc.stdout.on('data', gameStateChange) proc.stdout.on('data', gameStateChange)
@ -666,11 +665,11 @@ function dlAsync(login = true){
// Will wait for a certain bit of text meaning that // Will wait for a certain bit of text meaning that
// the client application has started, and we can hide // the client application has started, and we can hide
// the progress bar stuff. // the progress bar stuff.
const tempListener = function(data){ const tempListener = function (data) {
if(GAME_LAUNCH_REGEX.test(data.trim())){ if (GAME_LAUNCH_REGEX.test(data.trim())) {
const diff = Date.now()-start const diff = Date.now() - start
if(diff < MIN_LINGER) { if (diff < MIN_LINGER) {
setTimeout(onLoadComplete, MIN_LINGER-diff) setTimeout(onLoadComplete, MIN_LINGER - diff)
} else { } else {
onLoadComplete() onLoadComplete()
} }
@ -678,18 +677,18 @@ function dlAsync(login = true){
} }
// Listener for Discord RPC. // Listener for Discord RPC.
const gameStateChange = function(data){ const gameStateChange = function (data) {
data = data.trim() data = data.trim()
if(SERVER_JOINED_REGEX.test(data)){ if (SERVER_JOINED_REGEX.test(data)) {
DiscordWrapper.updateDetails('Exploring the Realm!') DiscordWrapper.updateDetails('Exploring the Realm!')
} else if(GAME_JOINED_REGEX.test(data)){ } else if (GAME_JOINED_REGEX.test(data)) {
DiscordWrapper.updateDetails('Sailing to Westeros!') DiscordWrapper.updateDetails('Sailing to Westeros!')
} }
} }
const gameErrorListener = function(data){ const gameErrorListener = function (data) {
data = data.trim() data = data.trim()
if(data.indexOf('Could not find or load main class net.minecraft.launchwrapper.Launch') > -1){ if (data.indexOf('Could not find or load main class net.minecraft.launchwrapper.Launch') > -1) {
loggerLaunchSuite.error('Game launch failed, LaunchWrapper was not downloaded properly.') loggerLaunchSuite.error('Game launch failed, LaunchWrapper was not downloaded properly.')
showLaunchFailure('Error During Launch', 'The main file, LaunchWrapper, failed to download properly. As a result, the game cannot launch.<br><br>To fix this issue, temporarily turn off your antivirus software and launch the game again.<br><br>If you have time, please <a href="https://github.com/dscalzi/HeliosLauncher/issues">submit an issue</a> and let us know what antivirus software you use. We\'ll contact them and try to straighten things out.') showLaunchFailure('Error During Launch', 'The main file, LaunchWrapper, failed to download properly. As a result, the game cannot launch.<br><br>To fix this issue, temporarily turn off your antivirus software and launch the game again.<br><br>If you have time, please <a href="https://github.com/dscalzi/HeliosLauncher/issues">submit an issue</a> and let us know what antivirus software you use. We\'ll contact them and try to straighten things out.')
} }
@ -707,7 +706,7 @@ function dlAsync(login = true){
// Init Discord Hook // Init Discord Hook
const distro = DistroManager.getDistribution() const distro = DistroManager.getDistribution()
if(distro.discord != null && serv.discord != null){ if (distro.discord != null && serv.discord != null) {
DiscordWrapper.initRPC(distro.discord, serv.discord) DiscordWrapper.initRPC(distro.discord, serv.discord)
hasRPC = true hasRPC = true
proc.on('close', (code, signal) => { proc.on('close', (code, signal) => {
@ -718,7 +717,7 @@ function dlAsync(login = true){
}) })
} }
} catch(err) { } catch (err) {
loggerLaunchSuite.error('Error during launch', err) loggerLaunchSuite.error('Error during launch', err)
showLaunchFailure('Error During Launch', 'Please check the console (CTRL + Shift + i) for more details.') showLaunchFailure('Error During Launch', 'Please check the console (CTRL + Shift + i) for more details.')
@ -740,23 +739,23 @@ function dlAsync(login = true){
refreshDistributionIndex(true, (data) => { refreshDistributionIndex(true, (data) => {
onDistroRefresh(data) onDistroRefresh(data)
serv = data.getServer(ConfigManager.getSelectedServer()) serv = data.getServer(ConfigManager.getSelectedServer())
aEx.send({task: 'execute', function: 'validateEverything', argsArr: [ConfigManager.getSelectedServer(), DistroManager.isDevMode()]}) aEx.send({ task: 'execute', function: 'validateEverything', argsArr: [ConfigManager.getSelectedServer(), DistroManager.isDevMode()] })
}, (err) => { }, (err) => {
loggerLaunchSuite.log('Error while fetching a fresh copy of the distribution index.', err) loggerLaunchSuite.log('Error while fetching a fresh copy of the distribution index.', err)
refreshDistributionIndex(false, (data) => { refreshDistributionIndex(false, (data) => {
onDistroRefresh(data) onDistroRefresh(data)
serv = data.getServer(ConfigManager.getSelectedServer()) serv = data.getServer(ConfigManager.getSelectedServer())
aEx.send({task: 'execute', function: 'validateEverything', argsArr: [ConfigManager.getSelectedServer(), DistroManager.isDevMode()]}) aEx.send({ task: 'execute', function: 'validateEverything', argsArr: [ConfigManager.getSelectedServer(), DistroManager.isDevMode()] })
}, (err) => { }, (err) => {
loggerLaunchSuite.error('Unable to refresh distribution index.', err) loggerLaunchSuite.error('Unable to refresh distribution index.', err)
if(DistroManager.getDistribution() == null){ if (DistroManager.getDistribution() == null) {
showLaunchFailure('Fatal Error', 'Could not load a copy of the distribution index. See the console (CTRL + Shift + i) for more details.') showLaunchFailure('Fatal Error', 'Could not load a copy of the distribution index. See the console (CTRL + Shift + i) for more details.')
// Disconnect from AssetExec // Disconnect from AssetExec
aEx.disconnect() aEx.disconnect()
} else { } else {
serv = data.getServer(ConfigManager.getSelectedServer()) serv = data.getServer(ConfigManager.getSelectedServer())
aEx.send({task: 'execute', function: 'validateEverything', argsArr: [ConfigManager.getSelectedServer(), DistroManager.isDevMode()]}) aEx.send({ task: 'execute', function: 'validateEverything', argsArr: [ConfigManager.getSelectedServer(), DistroManager.isDevMode()] })
} }
}) })
}) })
@ -767,14 +766,14 @@ function dlAsync(login = true){
*/ */
// DOM Cache // DOM Cache
const newsContent = document.getElementById('newsContent') const newsContent = document.getElementById('newsContent')
const newsArticleTitle = document.getElementById('newsArticleTitle') const newsArticleTitle = document.getElementById('newsArticleTitle')
const newsArticleDate = document.getElementById('newsArticleDate') const newsArticleDate = document.getElementById('newsArticleDate')
const newsArticleAuthor = document.getElementById('newsArticleAuthor') const newsArticleAuthor = document.getElementById('newsArticleAuthor')
const newsArticleComments = document.getElementById('newsArticleComments') const newsArticleComments = document.getElementById('newsArticleComments')
const newsNavigationStatus = document.getElementById('newsNavigationStatus') const newsNavigationStatus = document.getElementById('newsNavigationStatus')
const newsArticleContentScrollable = document.getElementById('newsArticleContentScrollable') const newsArticleContentScrollable = document.getElementById('newsArticleContentScrollable')
const nELoadSpan = document.getElementById('nELoadSpan') const nELoadSpan = document.getElementById('nELoadSpan')
// News slide caches. // News slide caches.
let newsActive = false let newsActive = false
@ -785,7 +784,7 @@ let newsGlideCount = 0
* *
* @param {boolean} up True to slide up, otherwise false. * @param {boolean} up True to slide up, otherwise false.
*/ */
function slide_(up){ function slide_(up) {
const lCUpper = document.querySelector('#landingContainer > #upper') const lCUpper = document.querySelector('#landingContainer > #upper')
const lCLLeft = document.querySelector('#landingContainer > #lower > #left') const lCLLeft = document.querySelector('#landingContainer > #lower > #left')
const lCLCenter = document.querySelector('#landingContainer > #lower > #center') const lCLCenter = document.querySelector('#landingContainer > #lower > #center')
@ -796,7 +795,7 @@ function slide_(up){
newsGlideCount++ newsGlideCount++
if(up){ if (up) {
lCUpper.style.top = '-200vh' lCUpper.style.top = '-200vh'
lCLLeft.style.top = '-200vh' lCLLeft.style.top = '-200vh'
lCLCenter.style.top = '-200vh' lCLCenter.style.top = '-200vh'
@ -807,7 +806,7 @@ function slide_(up){
//landingContainer.style.background = 'rgba(29, 29, 29, 0.55)' //landingContainer.style.background = 'rgba(29, 29, 29, 0.55)'
landingContainer.style.background = 'rgba(0, 0, 0, 0.50)' landingContainer.style.background = 'rgba(0, 0, 0, 0.50)'
setTimeout(() => { setTimeout(() => {
if(newsGlideCount === 1){ if (newsGlideCount === 1) {
lCLCenter.style.transition = 'none' lCLCenter.style.transition = 'none'
newsBtn.style.transition = 'none' newsBtn.style.transition = 'none'
} }
@ -832,13 +831,13 @@ function slide_(up){
// Bind news button. // Bind news button.
document.getElementById('newsButton').onclick = () => { document.getElementById('newsButton').onclick = () => {
// Toggle tabbing. // Toggle tabbing.
if(newsActive){ if (newsActive) {
$('#landingContainer *').removeAttr('tabindex') $('#landingContainer *').removeAttr('tabindex')
$('#newsContainer *').attr('tabindex', '-1') $('#newsContainer *').attr('tabindex', '-1')
} else { } else {
$('#landingContainer *').attr('tabindex', '-1') $('#landingContainer *').attr('tabindex', '-1')
$('#newsContainer, #newsContainer *, #lower, #lower #center *').removeAttr('tabindex') $('#newsContainer, #newsContainer *, #lower, #lower #center *').removeAttr('tabindex')
if(newsAlertShown){ if (newsAlertShown) {
$('#newsButtonAlert').fadeOut(2000) $('#newsButtonAlert').fadeOut(2000)
newsAlertShown = false newsAlertShown = false
ConfigManager.setNewsCacheDismissed(true) ConfigManager.setNewsCacheDismissed(true)
@ -860,13 +859,13 @@ let newsLoadingListener = null
* *
* @param {boolean} val True to set loading animation, otherwise false. * @param {boolean} val True to set loading animation, otherwise false.
*/ */
function setNewsLoading(val){ function setNewsLoading(val) {
if(val){ if (val) {
const nLStr = 'Checking for News' const nLStr = 'Checking for News'
let dotStr = '..' let dotStr = '..'
nELoadSpan.innerHTML = nLStr + dotStr nELoadSpan.innerHTML = nLStr + dotStr
newsLoadingListener = setInterval(() => { newsLoadingListener = setInterval(() => {
if(dotStr.length >= 3){ if (dotStr.length >= 3) {
dotStr = '' dotStr = ''
} else { } else {
dotStr += '.' dotStr += '.'
@ -874,7 +873,7 @@ function setNewsLoading(val){
nELoadSpan.innerHTML = nLStr + dotStr nELoadSpan.innerHTML = nLStr + dotStr
}, 750) }, 750)
} else { } else {
if(newsLoadingListener != null){ if (newsLoadingListener != null) {
clearInterval(newsLoadingListener) clearInterval(newsLoadingListener)
newsLoadingListener = null newsLoadingListener = null
} }
@ -890,7 +889,7 @@ newsErrorRetry.onclick = () => {
} }
newsArticleContentScrollable.onscroll = (e) => { newsArticleContentScrollable.onscroll = (e) => {
if(e.target.scrollTop > Number.parseFloat($('.newsArticleSpacerTop').css('height'))){ if (e.target.scrollTop > Number.parseFloat($('.newsArticleSpacerTop').css('height'))) {
newsContent.setAttribute('scrolled', '') newsContent.setAttribute('scrolled', '')
} else { } else {
newsContent.removeAttribute('scrolled') newsContent.removeAttribute('scrolled')
@ -903,7 +902,7 @@ newsArticleContentScrollable.onscroll = (e) => {
* @returns {Promise.<void>} A promise which resolves when the news * @returns {Promise.<void>} A promise which resolves when the news
* content has finished loading and transitioning. * content has finished loading and transitioning.
*/ */
function reloadNews(){ function reloadNews() {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
$('#newsContent').fadeOut(250, () => { $('#newsContent').fadeOut(250, () => {
$('#newsErrorLoading').fadeIn(250) $('#newsErrorLoading').fadeIn(250)
@ -919,7 +918,7 @@ let newsAlertShown = false
/** /**
* Show the news alert indicating there is new news. * Show the news alert indicating there is new news.
*/ */
function showNewsAlert(){ function showNewsAlert() {
newsAlertShown = true newsAlertShown = true
$(newsButtonAlert).fadeIn(250) $(newsButtonAlert).fadeIn(250)
} }
@ -931,7 +930,7 @@ function showNewsAlert(){
* @returns {Promise.<void>} A promise which resolves when the news * @returns {Promise.<void>} A promise which resolves when the news
* content has finished loading and transitioning. * content has finished loading and transitioning.
*/ */
function initNews(){ function initNews() {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
setNewsLoading(true) setNewsLoading(true)
@ -941,7 +940,7 @@ function initNews(){
newsArr = news.articles || null newsArr = news.articles || null
if(newsArr == null){ if (newsArr == null) {
// News Loading Failed // News Loading Failed
setNewsLoading(false) setNewsLoading(false)
@ -950,7 +949,7 @@ function initNews(){
resolve() resolve()
}) })
}) })
} else if(newsArr.length === 0) { } else if (newsArr.length === 0) {
// No News Articles // No News Articles
setNewsLoading(false) setNewsLoading(false)
@ -976,16 +975,16 @@ function initNews(){
let newDate = new Date(lN.date) let newDate = new Date(lN.date)
let isNew = false let isNew = false
if(cached.date != null && cached.content != null){ if (cached.date != null && cached.content != null) {
if(new Date(cached.date) >= newDate){ if (new Date(cached.date) >= newDate) {
// Compare Content // Compare Content
if(cached.content !== newHash){ if (cached.content !== newHash) {
isNew = true isNew = true
showNewsAlert() showNewsAlert()
} else { } else {
if(!cached.dismissed){ if (!cached.dismissed) {
isNew = true isNew = true
showNewsAlert() showNewsAlert()
} }
@ -1001,7 +1000,7 @@ function initNews(){
showNewsAlert() showNewsAlert()
} }
if(isNew){ if (isNew) {
ConfigManager.setNewsCache({ ConfigManager.setNewsCache({
date: newDate.getTime(), date: newDate.getTime(),
content: newHash, content: newHash,
@ -1012,9 +1011,9 @@ function initNews(){
const switchHandler = (forward) => { const switchHandler = (forward) => {
let cArt = parseInt(newsContent.getAttribute('article')) let cArt = parseInt(newsContent.getAttribute('article'))
let nxtArt = forward ? (cArt >= newsArr.length-1 ? 0 : cArt + 1) : (cArt <= 0 ? newsArr.length-1 : cArt - 1) let nxtArt = forward ? (cArt >= newsArr.length - 1 ? 0 : cArt + 1) : (cArt <= 0 ? newsArr.length - 1 : cArt - 1)
displayArticle(newsArr[nxtArt], nxtArt+1) displayArticle(newsArr[nxtArt], nxtArt + 1)
} }
document.getElementById('newsNavigateRight').onclick = () => { switchHandler(true) } document.getElementById('newsNavigateRight').onclick = () => { switchHandler(true) }
@ -1039,8 +1038,8 @@ function initNews(){
* open the news UI. * open the news UI.
*/ */
document.addEventListener('keydown', (e) => { document.addEventListener('keydown', (e) => {
if(newsActive){ if (newsActive) {
if(e.key === 'ArrowRight' || e.key === 'ArrowLeft'){ if (e.key === 'ArrowRight' || e.key === 'ArrowLeft') {
document.getElementById(e.key === 'ArrowRight' ? 'newsNavigateRight' : 'newsNavigateLeft').click() document.getElementById(e.key === 'ArrowRight' ? 'newsNavigateRight' : 'newsNavigateLeft').click()
} }
// Interferes with scrolling an article using the down arrow. // Interferes with scrolling an article using the down arrow.
@ -1049,8 +1048,8 @@ document.addEventListener('keydown', (e) => {
// document.getElementById('newsButton').click() // document.getElementById('newsButton').click()
// } // }
} else { } else {
if(getCurrentView() === VIEWS.landing){ if (getCurrentView() === VIEWS.landing) {
if(e.key === 'ArrowUp'){ if (e.key === 'ArrowUp') {
document.getElementById('newsButton').click() document.getElementById('newsButton').click()
} }
} }
@ -1063,7 +1062,7 @@ document.addEventListener('keydown', (e) => {
* @param {Object} articleObject The article meta object. * @param {Object} articleObject The article meta object.
* @param {number} index The article index. * @param {number} index The article index.
*/ */
function displayArticle(articleObject, index){ function displayArticle(articleObject, index) {
newsArticleTitle.innerHTML = articleObject.title newsArticleTitle.innerHTML = articleObject.title
newsArticleTitle.href = articleObject.link newsArticleTitle.href = articleObject.link
newsArticleAuthor.innerHTML = 'by ' + articleObject.author newsArticleAuthor.innerHTML = 'by ' + articleObject.author
@ -1078,14 +1077,14 @@ function displayArticle(articleObject, index){
} }
}) })
newsNavigationStatus.innerHTML = index + ' of ' + newsArr.length newsNavigationStatus.innerHTML = index + ' of ' + newsArr.length
newsContent.setAttribute('article', index-1) newsContent.setAttribute('article', index - 1)
} }
/** /**
* Load news information from the RSS feed specified in the * Load news information from the RSS feed specified in the
* distribution index. * distribution index.
*/ */
function loadNews(){ function loadNews() {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const distroData = DistroManager.getDistribution() const distroData = DistroManager.getDistribution()
const newsFeed = distroData.getRSS() const newsFeed = distroData.getRSS()
@ -1096,12 +1095,12 @@ function loadNews(){
const items = $(data).find('item') const items = $(data).find('item')
const articles = [] const articles = []
for(let i=0; i<items.length; i++){ for (let i = 0; i < items.length; i++) {
// JQuery Element // JQuery Element
const el = $(items[i]) const el = $(items[i])
// Resolve date. // Resolve date.
const date = new Date(el.find('pubDate').text()).toLocaleDateString('en-US', {month: 'short', day: 'numeric', year: 'numeric', hour: 'numeric', minute: 'numeric'}) const date = new Date(el.find('pubDate').text()).toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric', hour: 'numeric', minute: 'numeric' })
// Resolve comments. // Resolve comments.
let comments = el.find('slash\\:comments').text() || '0' let comments = el.find('slash\\:comments').text() || '0'
@ -1111,12 +1110,12 @@ function loadNews(){
let content = el.find('content\\:encoded').text() let content = el.find('content\\:encoded').text()
let regex = /src="(?!http:\/\/|https:\/\/)(.+?)"/g let regex = /src="(?!http:\/\/|https:\/\/)(.+?)"/g
let matches let matches
while((matches = regex.exec(content))){ while ((matches = regex.exec(content))) {
content = content.replace(`"${matches[1]}"`, `"${newsHost + matches[1]}"`) content = content.replace(`"${matches[1]}"`, `"${newsHost + matches[1]}"`)
} }
let link = el.find('link').text() let link = el.find('link').text()
let title = el.find('title').text() let title = el.find('title').text()
let author = el.find('dc\\:creator').text() let author = el.find('dc\\:creator').text()
// Generate article. // Generate article.

View File

@ -1,4 +1,6 @@
const net = require('net') //const net = require('net)
const fetch = require('node-fetch')
const loggerLanding = LoggerUtil('%c[Landing]', 'color: #000668; font-weight: bold')
/** /**
* Retrieves the status of a minecraft server. * Retrieves the status of a minecraft server.
@ -8,16 +10,44 @@ const net = require('net')
* @returns {Promise.<Object>} A promise which resolves to an object containing * @returns {Promise.<Object>} A promise which resolves to an object containing
* status information. * status information.
*/ */
exports.getStatus = function(address, port = 25565){ exports.getStatus = async function (address, port = 25565) {
if(port == null || port == ''){ if (port == null || port == '') {
port = 25565 port = 25565
} }
if(typeof port === 'string'){ if (typeof port === 'string') {
port = parseInt(port) port = parseInt(port)
} }
let online,
version,
motd,
onlinePlayers,
maxPlayers;
let data;
return new Promise((resolve, reject) => { try {
var response = await fetch(`https://api.mcsrvstat.us/2/${address}`);
data = await response.json();
online = data.online;
version = data.version
motd = data.motd.clean
onlinePlayers = data.players.online;
maxPlayers = data.players.max
} catch (err) {
loggerLanding.warn('Unable to refresh server status, assuming offline.')
loggerLanding.debug(err)
}
return {
online,
version,
motd,
onlinePlayers,
maxPlayers
};
/*return await new Promise((resolve, reject) => {
Commented out for documentation
const socket = net.connect(port, address, () => { const socket = net.connect(port, address, () => {
let buff = Buffer.from([0xFE, 0x01]) let buff = Buffer.from([0xFE, 0x01])
socket.write(buff) socket.write(buff)
@ -33,17 +63,18 @@ exports.getStatus = function(address, port = 25565){
}) })
}) })
Commented out for documentation
socket.on('data', (data) => { socket.on('data', (data) => {
if(data != null && data != ''){ if (data != null && data != '') {
let server_info = data.toString().split('\x00\x00\x00') let server_info = data.toString().split('\x00\x00\x00')
const NUM_FIELDS = 6 const NUM_FIELDS = 6
if(server_info != null && server_info.length >= NUM_FIELDS){ if (server_info != null && server_info.length >= NUM_FIELDS) {
resolve({ resolve({
online: true, online: true,
version: server_info[2].replace(/\u0000/g, ''), version: server_info[2].replace(/\u0000/g, ''),
motd: server_info[3].replace(/\u0000/g, ''), motd: server_info[3].replace(/\u0000/g, ''),
onlinePlayers: server_info[4].replace(/\u0000/g, ''), onlinePlayers: server_info[4].replace(/\u0000/g, ''),
maxPlayers: server_info[5].replace(/\u0000/g,'') maxPlayers: server_info[5].replace(/\u0000/g, '')
}) })
} else { } else {
resolve({ resolve({
@ -60,6 +91,6 @@ exports.getStatus = function(address, port = 25565){
// ENOTFOUND = Unable to resolve. // ENOTFOUND = Unable to resolve.
// ECONNREFUSED = Unable to connect to port. // ECONNREFUSED = Unable to connect to port.
}) })
}) })*/
} }

28
package-lock.json generated
View File

@ -2522,12 +2522,9 @@
"dev": true "dev": true
}, },
"node-fetch": { "node-fetch": {
"version": "2.6.5", "version": "2.6.2",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.5.tgz", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.2.tgz",
"integrity": "sha512-mmlIVHJEu5rnIxgEgez6b9GgWXbkZj5YZ7fx+2r94a2E+Uirsp6HsPTPlomfdHtpt/B0cdKviwkoaM6pyvUOpQ==", "integrity": "sha512-aLoxToI6RfZ+0NOjmWAgn9+LEd30YCkJKFSyWacNZdEKTit/ZMcKjGkTRo8uWEsnIb/hfKecNPEbln02PdWbcA=="
"requires": {
"whatwg-url": "^5.0.0"
}
}, },
"node-stream-zip": { "node-stream-zip": {
"version": "1.15.0", "version": "1.15.0",
@ -3117,11 +3114,6 @@
"punycode": "^2.1.1" "punycode": "^2.1.1"
} }
}, },
"tr46": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
"integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o="
},
"truncate-utf8-bytes": { "truncate-utf8-bytes": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz", "resolved": "https://registry.npmjs.org/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz",
@ -3333,20 +3325,6 @@
"extsprintf": "^1.2.0" "extsprintf": "^1.2.0"
} }
}, },
"webidl-conversions": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
"integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE="
},
"whatwg-url": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
"integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=",
"requires": {
"tr46": "~0.0.3",
"webidl-conversions": "^3.0.0"
}
},
"which": { "which": {
"version": "2.0.2", "version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",

View File

@ -33,6 +33,7 @@
"fs-extra": "^10.0.0", "fs-extra": "^10.0.0",
"github-syntax-dark": "^0.5.0", "github-syntax-dark": "^0.5.0",
"jquery": "^3.6.0", "jquery": "^3.6.0",
"node-fetch": "^2.6.2",
"node-stream-zip": "^1.15.0", "node-stream-zip": "^1.15.0",
"request": "^2.88.2", "request": "^2.88.2",
"semver": "^7.3.5", "semver": "^7.3.5",