You've already forked AstralRinth
forked from didirus/AstralRinth
(WIP) feat: ely.by account authentication
This commit is contained in:
@@ -37,7 +37,7 @@
|
|||||||
<Button v-tooltip="'Add offline account'" icon-only @click="showOfflineLoginModal()">
|
<Button v-tooltip="'Add offline account'" icon-only @click="showOfflineLoginModal()">
|
||||||
<PirateIcon />
|
<PirateIcon />
|
||||||
</Button>
|
</Button>
|
||||||
<Button v-tooltip="'Log via Ely.by'" icon-only @click="loginViaElyBy()">
|
<Button v-tooltip="'Log via Ely.by'" icon-only @click="showElybyLoginModal()">
|
||||||
<ElyByIcon v-if="!elybyLoginDisabled" />
|
<ElyByIcon v-if="!elybyLoginDisabled" />
|
||||||
<SpinnerIcon v-else class="animate-spin" />
|
<SpinnerIcon v-else class="animate-spin" />
|
||||||
</Button>
|
</Button>
|
||||||
@@ -64,35 +64,84 @@
|
|||||||
<Button v-tooltip="'Add offline account'" icon-only @click="showOfflineLoginModal()">
|
<Button v-tooltip="'Add offline account'" icon-only @click="showOfflineLoginModal()">
|
||||||
<PirateIcon />
|
<PirateIcon />
|
||||||
</Button>
|
</Button>
|
||||||
<Button v-tooltip="'Log via Ely.by'" icon-only @click="loginViaElyBy()">
|
<Button v-tooltip="'Log via Ely.by'" icon-only @click="showElybyLoginModal()">
|
||||||
<ElyByIcon v-if="!elybyLoginDisabled" />
|
<ElyByIcon v-if="!elybyLoginDisabled" />
|
||||||
<SpinnerIcon v-else class="animate-spin" />
|
<SpinnerIcon v-else class="animate-spin" />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
</transition>
|
</transition>
|
||||||
<ModalWrapper ref="addOfflineModal" class="modal" header="Add new offline account">
|
<ModalWrapper ref="addElybyModal" class="modal" header="Authenticate with Ely.by">
|
||||||
|
<ModalWrapper ref="requestElybyTwoFactorCodeModal" class="modal"
|
||||||
|
header="Ely.by requested 2FA code for authentication">
|
||||||
|
<div class="flex flex-col gap-4 px-6 py-5">
|
||||||
|
<label class="label">Enter your 2FA code</label>
|
||||||
|
<input v-model="elybyTwoFactorCode" type="text" placeholder="Your 2FA code here..." class="input" />
|
||||||
|
<div class="mt-6 ml-auto">
|
||||||
|
<Button icon-only color="primary" class="continue-button" @click="addElybyProfile()">
|
||||||
|
Continue
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ModalWrapper>
|
||||||
<div class="flex flex-col gap-4 px-6 py-5">
|
<div class="flex flex-col gap-4 px-6 py-5">
|
||||||
<label class="label">Enter your player name</label>
|
<label class="label">Enter your player name or email (preferred)</label>
|
||||||
<input
|
<input v-model="elybyLogin" type="text" placeholder="Your player name or email here..." class="input" />
|
||||||
type="text"
|
<label class="label">Enter your password</label>
|
||||||
v-model="offlinePlayerName"
|
<input v-model="elybyPassword" type="password" placeholder="Your password here..." class="input" />
|
||||||
placeholder="Your player name here..."
|
|
||||||
class="input"
|
|
||||||
/>
|
|
||||||
<div class="mt-6 ml-auto">
|
<div class="mt-6 ml-auto">
|
||||||
<Button
|
<Button icon-only color="primary" class="continue-button" @click="addElybyProfile()">
|
||||||
icon-only
|
|
||||||
color="primary"
|
|
||||||
@click="addOfflineProfile()"
|
|
||||||
class="continue-button"
|
|
||||||
>
|
|
||||||
Login
|
Login
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ModalWrapper>
|
</ModalWrapper>
|
||||||
<ModalWrapper ref="inputErrorModal" class="modal" header="Error while proceeding">
|
<ModalWrapper ref="addOfflineModal" class="modal" header="Add new offline account">
|
||||||
|
<div class="flex flex-col gap-4 px-6 py-5">
|
||||||
|
<label class="label">Enter your player name</label>
|
||||||
|
<input v-model="offlinePlayerName" type="text" placeholder="Your player name here..." class="input" />
|
||||||
|
<div class="mt-6 ml-auto">
|
||||||
|
<Button icon-only color="primary" class="continue-button" @click="addOfflineProfile()">
|
||||||
|
Login
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ModalWrapper>
|
||||||
|
<ModalWrapper
|
||||||
|
ref="authenticationElybyErrorModal"
|
||||||
|
class="modal"
|
||||||
|
header="Error while proceeding authentication event with Ely.by">
|
||||||
|
<div class="flex flex-col gap-4 px-6 py-5">
|
||||||
|
<label class="text-base font-medium text-red-700">
|
||||||
|
An error occurred while logging in.
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<div class="mt-6 ml-auto">
|
||||||
|
<Button color="primary" class="retry-button" @click="retryAddElybyProfile">
|
||||||
|
Try again
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ModalWrapper>
|
||||||
|
<ModalWrapper ref="inputElybyErrorModal" class="modal" header="Error while proceeding input event with Ely.by">
|
||||||
|
<div class="flex flex-col gap-4 px-6 py-5">
|
||||||
|
<label class="text-base font-medium text-red-700">
|
||||||
|
An error occurred while adding the Ely.by account. Please follow the instructions below.
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<ul class="list-disc list-inside text-sm space-y-1">
|
||||||
|
<li>Check that you have entered the correct player name or email.</li>
|
||||||
|
<li>Check that you have entered the correct password.</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<div class="mt-6 ml-auto">
|
||||||
|
<Button color="primary" class="retry-button" @click="retryAddElybyProfile">
|
||||||
|
Try again
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ModalWrapper>
|
||||||
|
<ModalWrapper ref="inputErrorModal" class="modal" header="Error while proceeding input event with offline account">
|
||||||
<div class="flex flex-col gap-4 px-6 py-5">
|
<div class="flex flex-col gap-4 px-6 py-5">
|
||||||
<label class="text-base font-medium text-red-700">
|
<label class="text-base font-medium text-red-700">
|
||||||
An error occurred while adding the offline account. Please follow the instructions below.
|
An error occurred while adding the offline account. Please follow the instructions below.
|
||||||
@@ -107,11 +156,7 @@
|
|||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<div class="mt-6 ml-auto">
|
<div class="mt-6 ml-auto">
|
||||||
<Button
|
<Button color="primary" class="retry-button" @click="retryAddOfflineProfile">
|
||||||
color="primary"
|
|
||||||
@click="retryAddOfflineProfile"
|
|
||||||
class="retry-button"
|
|
||||||
>
|
|
||||||
Try again
|
Try again
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
@@ -130,7 +175,7 @@ import {
|
|||||||
TrashIcon,
|
TrashIcon,
|
||||||
PirateIcon as Offline,
|
PirateIcon as Offline,
|
||||||
MicrosoftIcon as License,
|
MicrosoftIcon as License,
|
||||||
ElyByIcon as ElyBy,
|
ElyByIcon as Elyby,
|
||||||
MicrosoftIcon,
|
MicrosoftIcon,
|
||||||
PirateIcon,
|
PirateIcon,
|
||||||
ElyByIcon,
|
ElyByIcon,
|
||||||
@@ -139,6 +184,8 @@ import {
|
|||||||
import { Avatar, Button, Card } from '@modrinth/ui'
|
import { Avatar, Button, Card } from '@modrinth/ui'
|
||||||
import { ref, computed, onMounted, onBeforeUnmount, onUnmounted } from 'vue'
|
import { ref, computed, onMounted, onBeforeUnmount, onUnmounted } from 'vue'
|
||||||
import {
|
import {
|
||||||
|
elyby_auth_authenticate,
|
||||||
|
elyby_login,
|
||||||
offline_login,
|
offline_login,
|
||||||
users,
|
users,
|
||||||
remove_user,
|
remove_user,
|
||||||
@@ -170,10 +217,18 @@ const elybyLoginDisabled = ref(false)
|
|||||||
const defaultUser = ref()
|
const defaultUser = ref()
|
||||||
|
|
||||||
// [AR] • Feature
|
// [AR] • Feature
|
||||||
|
const clientToken = "astralrinth"
|
||||||
const addOfflineModal = ref(null)
|
const addOfflineModal = ref(null)
|
||||||
|
const addElybyModal = ref(null)
|
||||||
|
const requestElybyTwoFactorCodeModal = ref(null)
|
||||||
|
const authenticationElybyErrorModal = ref(null)
|
||||||
|
const inputElybyErrorModal = ref(null)
|
||||||
const inputErrorModal = ref(null)
|
const inputErrorModal = ref(null)
|
||||||
const exceptionErrorModal = ref(null)
|
const exceptionErrorModal = ref(null)
|
||||||
const offlinePlayerName = ref('')
|
const offlinePlayerName = ref('')
|
||||||
|
const elybyLogin = ref('')
|
||||||
|
const elybyPassword = ref('')
|
||||||
|
const elybyTwoFactorCode = ref('')
|
||||||
const minOfflinePlayerNameLength = 2
|
const minOfflinePlayerNameLength = 2
|
||||||
const maxOfflinePlayerNameLength = 20
|
const maxOfflinePlayerNameLength = 20
|
||||||
|
|
||||||
@@ -185,7 +240,7 @@ function getAccountType(account) {
|
|||||||
case 'pirate':
|
case 'pirate':
|
||||||
return Offline
|
return Offline
|
||||||
case 'elyby':
|
case 'elyby':
|
||||||
return ElyBy
|
return Elyby
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -194,12 +249,38 @@ function showOfflineLoginModal() {
|
|||||||
addOfflineModal.value?.show()
|
addOfflineModal.value?.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// [AR] • Feature
|
||||||
|
function showElybyLoginModal() {
|
||||||
|
addElybyModal.value?.show()
|
||||||
|
}
|
||||||
|
|
||||||
// [AR] • Feature
|
// [AR] • Feature
|
||||||
function retryAddOfflineProfile() {
|
function retryAddOfflineProfile() {
|
||||||
inputErrorModal.value?.hide()
|
inputErrorModal.value?.hide()
|
||||||
|
clearOfflineFields()
|
||||||
showOfflineLoginModal()
|
showOfflineLoginModal()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// [AR] • Feature
|
||||||
|
function retryAddElybyProfile() {
|
||||||
|
authenticationElybyErrorModal.value?.hide()
|
||||||
|
inputElybyErrorModal.value?.hide()
|
||||||
|
clearElybyFields()
|
||||||
|
showElybyLoginModal()
|
||||||
|
}
|
||||||
|
|
||||||
|
// [AR] • Feature
|
||||||
|
function clearElybyFields() {
|
||||||
|
elybyLogin.value = ''
|
||||||
|
elybyPassword.value = ''
|
||||||
|
elybyTwoFactorCode.value = ''
|
||||||
|
}
|
||||||
|
|
||||||
|
// [AR] • Feature
|
||||||
|
function clearOfflineFields() {
|
||||||
|
offlinePlayerName.value = ''
|
||||||
|
}
|
||||||
|
|
||||||
// [AR] • Feature
|
// [AR] • Feature
|
||||||
async function addOfflineProfile() {
|
async function addOfflineProfile() {
|
||||||
const name = offlinePlayerName.value.trim()
|
const name = offlinePlayerName.value.trim()
|
||||||
@@ -208,7 +289,7 @@ async function addOfflineProfile() {
|
|||||||
if (!isValidName) {
|
if (!isValidName) {
|
||||||
addOfflineModal.value?.hide()
|
addOfflineModal.value?.hide()
|
||||||
inputErrorModal.value?.show()
|
inputErrorModal.value?.show()
|
||||||
offlinePlayerName.value = ''
|
clearOfflineFields()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -227,16 +308,82 @@ async function addOfflineProfile() {
|
|||||||
handleError(error)
|
handleError(error)
|
||||||
exceptionErrorModal.value?.show()
|
exceptionErrorModal.value?.show()
|
||||||
} finally {
|
} finally {
|
||||||
offlinePlayerName.value = ''
|
clearOfflineFields()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// [AR] • Feature
|
// [AR] • Feature
|
||||||
// TODO:
|
async function addElybyProfile() {
|
||||||
async function loginViaElyBy() {
|
if (!elybyLogin.value || !elybyPassword.value) {
|
||||||
|
addElybyModal.value?.hide()
|
||||||
|
inputElybyErrorModal.value?.show()
|
||||||
|
clearElybyFields()
|
||||||
|
return
|
||||||
|
}
|
||||||
elybyLoginDisabled.value = true
|
elybyLoginDisabled.value = true
|
||||||
console.log("Login via Ely.by clicked!")
|
|
||||||
elybyLoginDisabled.value = false
|
const login = elybyLogin.value.trim()
|
||||||
|
let password = elybyPassword.value.trim()
|
||||||
|
const twoFactorCode = elybyTwoFactorCode.value.trim()
|
||||||
|
if (password && twoFactorCode) {
|
||||||
|
password = `${password}:${twoFactorCode}`
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const raw_result = await elyby_auth_authenticate(
|
||||||
|
login,
|
||||||
|
password,
|
||||||
|
clientToken
|
||||||
|
)
|
||||||
|
|
||||||
|
const json_data = JSON.parse(raw_result)
|
||||||
|
|
||||||
|
console.log(json_data?.error)
|
||||||
|
console.log(json_data?.errorMessage)
|
||||||
|
|
||||||
|
if (!json_data.accessToken) {
|
||||||
|
if (
|
||||||
|
json_data.error === 'ForbiddenOperationException' &&
|
||||||
|
json_data.errorMessage?.includes('two factor')
|
||||||
|
) {
|
||||||
|
requestElybyTwoFactorCodeModal.value?.show()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
addElybyModal.value?.hide()
|
||||||
|
requestElybyTwoFactorCodeModal.value?.hide()
|
||||||
|
authenticationElybyErrorModal.value?.show()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const accessToken = json_data.accessToken
|
||||||
|
const selectedProfileId = convertRawStringToUUIDv4(json_data.selectedProfile.id)
|
||||||
|
const selectedProfileName = json_data.selectedProfile.name
|
||||||
|
|
||||||
|
const result = await elyby_login(selectedProfileId, selectedProfileName, accessToken)
|
||||||
|
|
||||||
|
addElybyModal.value?.hide()
|
||||||
|
requestElybyTwoFactorCodeModal.value?.hide()
|
||||||
|
|
||||||
|
clearElybyFields()
|
||||||
|
|
||||||
|
await setAccount(result)
|
||||||
|
await refreshValues()
|
||||||
|
} catch (err) {
|
||||||
|
handleError(err)
|
||||||
|
exceptionErrorModal.value?.show()
|
||||||
|
} finally {
|
||||||
|
elybyLoginDisabled.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// [AR] • Feature
|
||||||
|
function convertRawStringToUUIDv4(rawId) {
|
||||||
|
if (rawId.length !== 32) {
|
||||||
|
console.warn('Invalid UUID string:', rawId)
|
||||||
|
return rawId
|
||||||
|
}
|
||||||
|
return `${rawId.slice(0, 8)}-${rawId.slice(8, 12)}-${rawId.slice(12, 16)}-${rawId.slice(16, 20)}-${rawId.slice(20)}`
|
||||||
}
|
}
|
||||||
|
|
||||||
const equippedSkin = ref(null)
|
const equippedSkin = ref(null)
|
||||||
|
|||||||
@@ -17,6 +17,24 @@ export async function offline_login(name) {
|
|||||||
return await invoke('plugin:auth|offline_login', { name: name })
|
return await invoke('plugin:auth|offline_login', { name: name })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// [AR] • Feature
|
||||||
|
export async function elyby_login(uuid, login, accessToken) {
|
||||||
|
return await invoke('plugin:auth|elyby_login', {
|
||||||
|
uuid,
|
||||||
|
login,
|
||||||
|
accessToken
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// [AR] • Feature
|
||||||
|
export async function elyby_auth_authenticate(login, password, clientToken) {
|
||||||
|
return await invoke('plugin:auth|elyby_auth_authenticate', {
|
||||||
|
login,
|
||||||
|
password,
|
||||||
|
clientToken,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Authenticate a user with Hydra - part 1.
|
* Authenticate a user with Hydra - part 1.
|
||||||
* This begins the authentication flow quasi-synchronously.
|
* This begins the authentication flow quasi-synchronously.
|
||||||
|
|||||||
@@ -10,20 +10,20 @@ export async function getOS() {
|
|||||||
return await invoke('plugin:utils|get_os')
|
return await invoke('plugin:utils|get_os')
|
||||||
}
|
}
|
||||||
|
|
||||||
// [AR] Feature
|
// [AR] Feature. Updater
|
||||||
export async function initUpdateLauncher(downloadurl, filename, ostype, autoupdatesupported) {
|
export async function initUpdateLauncher(downloadUrl, filename, osType, autoUpdateSupported) {
|
||||||
console.log('Downloading build', downloadurl, filename, ostype, autoupdatesupported)
|
console.log('Downloading build', downloadUrl, filename, osType, autoUpdateSupported)
|
||||||
return await invoke('plugin:utils|init_update_launcher', { downloadurl, filename, ostype, autoupdatesupported })
|
return await invoke('plugin:utils|init_update_launcher', { downloadUrl, filename, osType, autoUpdateSupported })
|
||||||
}
|
}
|
||||||
|
|
||||||
// [AR] Patch fix
|
// [AR] Migration. Patch
|
||||||
export async function applyMigrationFix(eol) {
|
export async function applyMigrationFix(eol) {
|
||||||
return await invoke('plugin:utils|apply_migration_fix', { eol })
|
return await invoke('plugin:utils|apply_migration_fix', { eol })
|
||||||
}
|
}
|
||||||
|
|
||||||
// [AR] Feature
|
// [AR] Feature. Ely.by
|
||||||
export async function initAuthlibPatching(minecraftversion, ismojang) {
|
export async function initAuthlibPatching(minecraftVersion, isMojang) {
|
||||||
return await invoke('plugin:utils|init_authlib_patching', { minecraftversion, ismojang })
|
return await invoke('plugin:utils|init_authlib_patching', { minecraftVersion, isMojang })
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function openPath(path) {
|
export async function openPath(path) {
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ fn main() {
|
|||||||
InlinedPlugin::new()
|
InlinedPlugin::new()
|
||||||
.commands(&[
|
.commands(&[
|
||||||
"offline_login",
|
"offline_login",
|
||||||
|
"elyby_login",
|
||||||
|
"elyby_auth_authenticate",
|
||||||
"login",
|
"login",
|
||||||
"remove_user",
|
"remove_user",
|
||||||
"get_default_user",
|
"get_default_user",
|
||||||
|
|||||||
@@ -2,12 +2,15 @@ use crate::api::Result;
|
|||||||
use chrono::{Duration, Utc};
|
use chrono::{Duration, Utc};
|
||||||
use tauri::plugin::TauriPlugin;
|
use tauri::plugin::TauriPlugin;
|
||||||
use tauri::{Manager, Runtime, UserAttentionType};
|
use tauri::{Manager, Runtime, UserAttentionType};
|
||||||
|
use tauri_plugin_http::reqwest::Client;
|
||||||
use theseus::prelude::*;
|
use theseus::prelude::*;
|
||||||
|
|
||||||
pub fn init<R: Runtime>() -> TauriPlugin<R> {
|
pub fn init<R: Runtime>() -> TauriPlugin<R> {
|
||||||
tauri::plugin::Builder::<R>::new("auth")
|
tauri::plugin::Builder::<R>::new("auth")
|
||||||
.invoke_handler(tauri::generate_handler![
|
.invoke_handler(tauri::generate_handler![
|
||||||
offline_login,
|
offline_login,
|
||||||
|
elyby_login,
|
||||||
|
elyby_auth_authenticate,
|
||||||
login,
|
login,
|
||||||
remove_user,
|
remove_user,
|
||||||
get_default_user,
|
get_default_user,
|
||||||
@@ -17,14 +20,65 @@ pub fn init<R: Runtime>() -> TauriPlugin<R> {
|
|||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// ### AR • Feature
|
||||||
/// Create new offline user
|
/// Create new offline user
|
||||||
/// This is custom function from Astralium Org.
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn offline_login(name: &str) -> Result<Credentials> {
|
pub async fn offline_login(name: &str) -> Result<Credentials> {
|
||||||
let credentials = minecraft_auth::offline_auth(name).await?;
|
let credentials = minecraft_auth::offline_auth(name).await?;
|
||||||
Ok(credentials)
|
Ok(credentials)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// ### AR • Feature
|
||||||
|
/// Create new Ely.by user
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn elyby_login(
|
||||||
|
uuid: uuid::Uuid,
|
||||||
|
login: &str,
|
||||||
|
access_token: &str
|
||||||
|
) -> Result<Credentials> {
|
||||||
|
let credentials = minecraft_auth::elyby_auth(uuid, login, access_token).await?;
|
||||||
|
Ok(credentials)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// ### AR • Feature
|
||||||
|
/// Authenticate Ely.by user
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn elyby_auth_authenticate(
|
||||||
|
login: &str,
|
||||||
|
password: &str,
|
||||||
|
client_token: &str,
|
||||||
|
) -> Result<String> {
|
||||||
|
let client = Client::new();
|
||||||
|
let auth_body = serde_json::json!({
|
||||||
|
"username": login,
|
||||||
|
"password": password,
|
||||||
|
"clientToken": client_token,
|
||||||
|
});
|
||||||
|
|
||||||
|
let response = match client
|
||||||
|
.post("https://authserver.ely.by/auth/authenticate")
|
||||||
|
.header("Content-Type", "application/json")
|
||||||
|
.json(&auth_body)
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(resp) => resp,
|
||||||
|
Err(e) => {
|
||||||
|
tracing::error!("[AR] • Failed to send request: {}", e);
|
||||||
|
return Ok("".to_string());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let text = match response.text().await {
|
||||||
|
Ok(body) => body,
|
||||||
|
Err(e) => {
|
||||||
|
tracing::error!("[AR] • Failed to read response text: {}", e);
|
||||||
|
return Ok("".to_string());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Ok(text)
|
||||||
|
}
|
||||||
|
|
||||||
/// Authenticate a user with Hydra - part 1
|
/// Authenticate a user with Hydra - part 1
|
||||||
/// This begins the authentication flow quasi-synchronously, returning a URL to visit (that the user will sign in at)
|
/// This begins the authentication flow quasi-synchronously, returning a URL to visit (that the user will sign in at)
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
|
|||||||
@@ -30,37 +30,37 @@ pub fn init<R: tauri::Runtime>() -> tauri::plugin::TauriPlugin<R> {
|
|||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// [AR] Feature
|
/// [AR] Feature. Ely.by
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn init_authlib_patching(
|
pub async fn init_authlib_patching(
|
||||||
minecraftversion: &str,
|
minecraft_version: &str,
|
||||||
ismojang: bool,
|
is_mojang: bool,
|
||||||
) -> Result<bool> {
|
) -> Result<bool> {
|
||||||
let result =
|
let result =
|
||||||
utils::init_authlib_patching(minecraftversion, ismojang).await?;
|
utils::init_authlib_patching(minecraft_version, is_mojang).await?;
|
||||||
Ok(result)
|
Ok(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// [AR] Patch fix
|
/// [AR] Migration. Patch
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn apply_migration_fix(eol: &str) -> Result<bool> {
|
pub async fn apply_migration_fix(eol: &str) -> Result<bool> {
|
||||||
let result = utils::apply_migration_fix(eol).await?;
|
let result = utils::apply_migration_fix(eol).await?;
|
||||||
Ok(result)
|
Ok(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// [AR] Feature
|
/// [AR] Feature. Updater
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn init_update_launcher(
|
pub async fn init_update_launcher(
|
||||||
downloadurl: &str,
|
download_url: &str,
|
||||||
filename: &str,
|
filename: &str,
|
||||||
ostype: &str,
|
os_type: &str,
|
||||||
autoupdatesupported: bool,
|
auto_update_supported: bool,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let _ = utils::init_update_launcher(
|
let _ = utils::init_update_launcher(
|
||||||
downloadurl,
|
download_url,
|
||||||
filename,
|
filename,
|
||||||
ostype,
|
os_type,
|
||||||
autoupdatesupported,
|
auto_update_supported,
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
@@ -86,7 +86,7 @@
|
|||||||
"capabilities": ["core", "plugins"],
|
"capabilities": ["core", "plugins"],
|
||||||
"csp": {
|
"csp": {
|
||||||
"default-src": "'self' customprotocol: asset:",
|
"default-src": "'self' customprotocol: asset:",
|
||||||
"connect-src": "ipc: https://git.astralium.su http://ipc.localhost https://modrinth.com https://*.modrinth.com https://*.posthog.com https://*.sentry.io https://api.mclo.gs 'self' data: blob:",
|
"connect-src": "ipc: https://git.astralium.su https://authserver.ely.by http://ipc.localhost https://modrinth.com https://*.modrinth.com https://*.posthog.com https://*.sentry.io https://api.mclo.gs 'self' data: blob:",
|
||||||
"font-src": ["https://cdn-raw.modrinth.com/fonts/"],
|
"font-src": ["https://cdn-raw.modrinth.com/fonts/"],
|
||||||
"img-src": "https: 'unsafe-inline' 'self' asset: http://asset.localhost http://textures.minecraft.net blob: data:",
|
"img-src": "https: 'unsafe-inline' 'self' asset: http://asset.localhost http://textures.minecraft.net blob: data:",
|
||||||
"style-src": "'unsafe-inline' 'self'",
|
"style-src": "'unsafe-inline' 'self'",
|
||||||
|
|||||||
@@ -28,6 +28,16 @@ pub async fn offline_auth(
|
|||||||
crate::state::offline_auth(name, &state.pool).await
|
crate::state::offline_auth(name, &state.pool).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument]
|
||||||
|
pub async fn elyby_auth(
|
||||||
|
uuid: uuid::Uuid,
|
||||||
|
login: &str,
|
||||||
|
access_token: &str
|
||||||
|
) -> crate::Result<Credentials> {
|
||||||
|
let state = State::get().await?;
|
||||||
|
crate::state::elyby_auth(uuid, login, access_token, &state.pool).await
|
||||||
|
}
|
||||||
|
|
||||||
#[tracing::instrument]
|
#[tracing::instrument]
|
||||||
pub async fn get_default_user() -> crate::Result<Option<uuid::Uuid>> {
|
pub async fn get_default_user() -> crate::Result<Option<uuid::Uuid>> {
|
||||||
let state = State::get().await?;
|
let state = State::get().await?;
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ use crate::launcher::download::download_log_config;
|
|||||||
use crate::launcher::io::IOError;
|
use crate::launcher::io::IOError;
|
||||||
use crate::profile::QuickPlayType;
|
use crate::profile::QuickPlayType;
|
||||||
use crate::state::{
|
use crate::state::{
|
||||||
AccountType, Credentials, JavaVersion, ProcessMetadata, ProfileInstallStage
|
AccountType, Credentials, JavaVersion, ProcessMetadata, ProfileInstallStage,
|
||||||
};
|
};
|
||||||
use crate::util::{io, utils};
|
use crate::util::{io, utils};
|
||||||
use crate::{State, get_resource_file, process, state as st};
|
use crate::{State, get_resource_file, process, state as st};
|
||||||
@@ -637,18 +637,27 @@ pub async fn launch_minecraft(
|
|||||||
if credentials.account_type == AccountType::Pirate.as_lowercase_str() {
|
if credentials.account_type == AccountType::Pirate.as_lowercase_str() {
|
||||||
if version_jar == "1.16.4" || version_jar == "1.16.5" {
|
if version_jar == "1.16.4" || version_jar == "1.16.5" {
|
||||||
let invalid_url = "https://invalid.invalid";
|
let invalid_url = "https://invalid.invalid";
|
||||||
tracing::info!("[AR] • The launcher detected the launch of {} on the offline account. Applying multiplayer fixes.", version_jar);
|
tracing::info!(
|
||||||
|
"[AR] • The launcher detected the launch of {} on the offline account. Applying offline multiplayer fixes.",
|
||||||
|
version_jar
|
||||||
|
);
|
||||||
command.arg("-Dminecraft.api.env=custom");
|
command.arg("-Dminecraft.api.env=custom");
|
||||||
command.arg(format!("-Dminecraft.api.auth.host={}", invalid_url));
|
command.arg(format!("-Dminecraft.api.auth.host={}", invalid_url));
|
||||||
command.arg(format!("-Dminecraft.api.account.host={}", invalid_url));
|
command
|
||||||
command.arg(format!("-Dminecraft.api.session.host={}", invalid_url));
|
.arg(format!("-Dminecraft.api.account.host={}", invalid_url));
|
||||||
command.arg(format!("-Dminecraft.api.services.host={}", invalid_url));
|
command
|
||||||
|
.arg(format!("-Dminecraft.api.session.host={}", invalid_url));
|
||||||
|
command
|
||||||
|
.arg(format!("-Dminecraft.api.services.host={}", invalid_url));
|
||||||
}
|
}
|
||||||
} else if credentials.account_type == AccountType::ElyBy.as_lowercase_str() {
|
} else if credentials.account_type == AccountType::ElyBy.as_lowercase_str()
|
||||||
tracing::info!("[AR] • The launcher detected the launch of {} on the ElyBy account. Applying ElyBy Java Injector.", version_jar);
|
{
|
||||||
let path_buf = utils::get_or_download_ely_by_injector().await?;
|
tracing::info!(
|
||||||
|
"[AR] • The launcher detected the launch of {} on the Ely.by account. Applying Ely.by Java Injector.",
|
||||||
|
version_jar
|
||||||
|
);
|
||||||
|
let path_buf = utils::get_or_download_elyby_injector().await?;
|
||||||
let path = path_buf.to_str().unwrap();
|
let path = path_buf.to_str().unwrap();
|
||||||
tracing::info!("[AR] • ElyBy Java Injector path: {}", path);
|
|
||||||
command.arg(format!("-javaagent:{}=ely.by", path));
|
command.arg(format!("-javaagent:{}=ely.by", path));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -751,10 +760,10 @@ pub async fn launch_minecraft(
|
|||||||
|
|
||||||
// [AR] Feature
|
// [AR] Feature
|
||||||
let selected_phrase = ACTIVE_STATE.choose(&mut rand::thread_rng()).unwrap();
|
let selected_phrase = ACTIVE_STATE.choose(&mut rand::thread_rng()).unwrap();
|
||||||
let _ = state
|
let _ = state
|
||||||
.discord_rpc
|
.discord_rpc
|
||||||
.set_activity(&format!("{} {}", selected_phrase, profile.name), true)
|
.set_activity(&format!("{} {}", selected_phrase, profile.name), true)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
let _ = state
|
let _ = state
|
||||||
.friends_socket
|
.friends_socket
|
||||||
|
|||||||
@@ -244,6 +244,34 @@ pub async fn offline_auth(
|
|||||||
Ok(credentials)
|
Ok(credentials)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// [AR] Feature
|
||||||
|
#[tracing::instrument]
|
||||||
|
pub async fn elyby_auth(
|
||||||
|
uuid: Uuid,
|
||||||
|
username: &str,
|
||||||
|
access_token: &str,
|
||||||
|
exec: impl sqlx::Executor<'_, Database = sqlx::Sqlite> + Copy,
|
||||||
|
) -> crate::Result<Credentials> {
|
||||||
|
let mut credentials = Credentials {
|
||||||
|
offline_profile: MinecraftProfile::default(),
|
||||||
|
access_token: access_token.to_string(),
|
||||||
|
refresh_token: "null".to_string(),
|
||||||
|
expires: Utc::now() + Duration::days(365 * 99),
|
||||||
|
active: true,
|
||||||
|
account_type: AccountType::ElyBy.as_lowercase_str(),
|
||||||
|
};
|
||||||
|
|
||||||
|
credentials.offline_profile = MinecraftProfile {
|
||||||
|
id: uuid,
|
||||||
|
name: username.to_string(),
|
||||||
|
..credentials.offline_profile
|
||||||
|
};
|
||||||
|
|
||||||
|
credentials.upsert(exec).await?;
|
||||||
|
|
||||||
|
Ok(credentials)
|
||||||
|
}
|
||||||
|
|
||||||
/// [AR] • Feature
|
/// [AR] • Feature
|
||||||
#[derive(Deserialize, Debug)]
|
#[derive(Deserialize, Debug)]
|
||||||
pub enum AccountType {
|
pub enum AccountType {
|
||||||
|
|||||||
@@ -47,30 +47,38 @@ pub fn read_package_json() -> io::Result<Launcher> {
|
|||||||
Ok(launcher)
|
Ok(launcher)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// ### AR • Ely By Injector
|
/// ### AR • Ely.by Injector
|
||||||
/// Returns the path to the ely by injector
|
/// Returns the path to the Ely.by injector
|
||||||
/// If resource doesn't exist, it will be downloaded.
|
/// If resource doesn't exist or outdated, it will be downloaded from Git Astralium.
|
||||||
pub async fn get_or_download_ely_by_injector() -> Result<PathBuf> {
|
pub async fn get_or_download_elyby_injector() -> Result<PathBuf> {
|
||||||
tracing::info!("[AR] • Attempting to get or download AuthLib Injector");
|
// TODO: Add support for offline mode
|
||||||
let state= State::get().await?;
|
tracing::info!("[AR] • Attempting to get or download latest AuthLib Injector");
|
||||||
|
let state = State::get().await?;
|
||||||
let libraries_dir = state.directories.libraries_dir();
|
let libraries_dir = state.directories.libraries_dir();
|
||||||
|
|
||||||
ensure_astralrinth_library_dir_exists(&libraries_dir).await?;
|
validate_astralrinth_library_dir(&libraries_dir).await?;
|
||||||
|
|
||||||
let (asset_name, download_url) = extract_ely_authlib_metadata("authlib-injector").await?;
|
let (asset_name, download_url) =
|
||||||
let ely_by_injector = state.directories.libraries_dir().join(format!("astralrinth/{}", asset_name));
|
extract_elyby_authlib_metadata("authlib-injector").await?;
|
||||||
|
let elyby_injector = state
|
||||||
|
.directories
|
||||||
|
.libraries_dir()
|
||||||
|
.join(format!("astralrinth/{}", asset_name));
|
||||||
let path_in_libs = format!("astralrinth/{}", asset_name);
|
let path_in_libs = format!("astralrinth/{}", asset_name);
|
||||||
tracing::info!("[AR] • Path in libs: {}", path_in_libs);
|
tracing::info!("[AR] • Path in libs: {}", path_in_libs);
|
||||||
|
|
||||||
if !ely_by_injector.exists() {
|
if !elyby_injector.exists() {
|
||||||
tracing::info!("[AR] • Doesn't exist, attempting to download AuthLib Injector from URL: {}", download_url);
|
tracing::info!(
|
||||||
|
"[AR] • Doesn't exist or outdated, attempting to download latest AuthLib Injector from URL: {}",
|
||||||
|
download_url
|
||||||
|
);
|
||||||
let bytes = fetch_bytes_from_url(download_url.as_str()).await?;
|
let bytes = fetch_bytes_from_url(download_url.as_str()).await?;
|
||||||
write_file_to_libraries(&path_in_libs, &bytes).await?;
|
write_file_to_libraries(&path_in_libs, &bytes).await?;
|
||||||
}
|
}
|
||||||
Ok(ely_by_injector)
|
Ok(elyby_injector)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// ### AR • Migration
|
/// ### AR • Migration. Patch
|
||||||
/// Applying migration fix for SQLite database.
|
/// Applying migration fix for SQLite database.
|
||||||
pub async fn apply_migration_fix(eol: &str) -> Result<bool> {
|
pub async fn apply_migration_fix(eol: &str) -> Result<bool> {
|
||||||
tracing::info!("[AR] • Attempting to apply migration fix");
|
tracing::info!("[AR] • Attempting to apply migration fix");
|
||||||
@@ -83,7 +91,7 @@ pub async fn apply_migration_fix(eol: &str) -> Result<bool> {
|
|||||||
Ok(patched)
|
Ok(patched)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// ### AR • Updater
|
/// ### AR • Feature. Updater
|
||||||
/// Initialize the update launcher.
|
/// Initialize the update launcher.
|
||||||
pub async fn init_update_launcher(
|
pub async fn init_update_launcher(
|
||||||
download_url: &str,
|
download_url: &str,
|
||||||
@@ -115,7 +123,7 @@ pub async fn init_update_launcher(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// ### AR • AuthLib (Ely By)
|
/// ### AR • AuthLib (Ely.by)
|
||||||
/// Initializes the AuthLib patching process.
|
/// Initializes the AuthLib patching process.
|
||||||
///
|
///
|
||||||
/// Returns `true` if the authlib patched successfully.
|
/// Returns `true` if the authlib patched successfully.
|
||||||
@@ -123,13 +131,18 @@ pub async fn init_authlib_patching(
|
|||||||
minecraft_version: &str,
|
minecraft_version: &str,
|
||||||
is_mojang: bool,
|
is_mojang: bool,
|
||||||
) -> Result<bool> {
|
) -> Result<bool> {
|
||||||
let minecraft_library_metadata = get_minecraft_library_metadata(minecraft_version).await?;
|
let minecraft_library_metadata =
|
||||||
|
get_minecraft_library_metadata(minecraft_version).await?;
|
||||||
// Parses the AuthLib version from string
|
// Parses the AuthLib version from string
|
||||||
// Example output: "com.mojang:authlib:6.0.58" -> "6.0.58"
|
// Example output: "com.mojang:authlib:6.0.58" -> "6.0.58"
|
||||||
let authlib_version = minecraft_library_metadata.name.split(':').nth(2).unwrap_or("unknown");
|
let authlib_version = minecraft_library_metadata
|
||||||
|
.name
|
||||||
|
.split(':')
|
||||||
|
.nth(2)
|
||||||
|
.unwrap_or("unknown");
|
||||||
let authlib_fullname_string = format!("authlib-{}.jar", authlib_version);
|
let authlib_fullname_string = format!("authlib-{}.jar", authlib_version);
|
||||||
let authlib_fullname_str = authlib_fullname_string.as_str();
|
let authlib_fullname_str = authlib_fullname_string.as_str();
|
||||||
|
|
||||||
tracing::info!(
|
tracing::info!(
|
||||||
"[AR] • Attempting to download AuthLib -> {}.",
|
"[AR] • Attempting to download AuthLib -> {}.",
|
||||||
authlib_fullname_string
|
authlib_fullname_string
|
||||||
@@ -145,17 +158,32 @@ pub async fn init_authlib_patching(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Ensures the `astralrinth/` directory exists inside the libraries directory.
|
/// Ensures the `astralrinth/` directory exists inside the libraries directory.
|
||||||
async fn ensure_astralrinth_library_dir_exists(libraries_dir: &PathBuf) -> Result<()> {
|
async fn validate_astralrinth_library_dir(
|
||||||
|
libraries_dir: &PathBuf,
|
||||||
|
) -> Result<()> {
|
||||||
let astralrinth_path = libraries_dir.join("astralrinth");
|
let astralrinth_path = libraries_dir.join("astralrinth");
|
||||||
if !astralrinth_path.exists() {
|
if !astralrinth_path.exists() {
|
||||||
tokio::fs::create_dir_all(&astralrinth_path).await.map_err(|e| {
|
tokio::fs::create_dir_all(&astralrinth_path)
|
||||||
tracing::error!("[AR] • Failed to create {} directory: {:?}", astralrinth_path.display(), e);
|
.await
|
||||||
crate::ErrorKind::IOErrorOccurred {
|
.map_err(|e| {
|
||||||
error: format!("Failed to create {} directory: {}", astralrinth_path.display(), e),
|
tracing::error!(
|
||||||
}
|
"[AR] • Failed to create {} directory: {:?}",
|
||||||
.as_error()
|
astralrinth_path.display(),
|
||||||
})?;
|
e
|
||||||
tracing::info!("[AR] • Created missing {} directory", astralrinth_path.display());
|
);
|
||||||
|
crate::ErrorKind::IOErrorOccurred {
|
||||||
|
error: format!(
|
||||||
|
"Failed to create {} directory: {}",
|
||||||
|
astralrinth_path.display(),
|
||||||
|
e
|
||||||
|
),
|
||||||
|
}
|
||||||
|
.as_error()
|
||||||
|
})?;
|
||||||
|
tracing::info!(
|
||||||
|
"[AR] • Created missing {} directory",
|
||||||
|
astralrinth_path.display()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -178,7 +206,7 @@ async fn write_file_to_libraries(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// ### AR • AuthLib (Ely By)
|
/// ### AR • AuthLib (Ely.by)
|
||||||
/// Downloads the AuthLib file from Mojang libraries or Git Astralium services.
|
/// Downloads the AuthLib file from Mojang libraries or Git Astralium services.
|
||||||
async fn download_authlib(
|
async fn download_authlib(
|
||||||
minecraft_library_metadata: &Library,
|
minecraft_library_metadata: &Library,
|
||||||
@@ -187,14 +215,17 @@ async fn download_authlib(
|
|||||||
is_mojang: bool,
|
is_mojang: bool,
|
||||||
) -> Result<bool> {
|
) -> Result<bool> {
|
||||||
let state = State::get().await?;
|
let state = State::get().await?;
|
||||||
let (mut url, path) = extract_minecraft_local_download_info(minecraft_library_metadata, minecraft_version)?;
|
let (mut url, path) = extract_minecraft_local_download_info(
|
||||||
|
minecraft_library_metadata,
|
||||||
|
minecraft_version,
|
||||||
|
)?;
|
||||||
let full_path = state.directories.libraries_dir().join(path);
|
let full_path = state.directories.libraries_dir().join(path);
|
||||||
|
|
||||||
if !is_mojang {
|
if !is_mojang {
|
||||||
tracing::info!(
|
tracing::info!(
|
||||||
"[AR] • Attempting to download AuthLib from Git Astralium"
|
"[AR] • Attempting to download AuthLib from Git Astralium"
|
||||||
);
|
);
|
||||||
(_, url) = extract_ely_authlib_metadata(authlib_fullname).await?;
|
(_, url) = extract_elyby_authlib_metadata(authlib_fullname).await?;
|
||||||
}
|
}
|
||||||
tracing::info!("[AR] • Downloading AuthLib from URL: {}", url);
|
tracing::info!("[AR] • Downloading AuthLib from URL: {}", url);
|
||||||
let bytes = fetch_bytes_from_url(&url).await?;
|
let bytes = fetch_bytes_from_url(&url).await?;
|
||||||
@@ -204,9 +235,11 @@ async fn download_authlib(
|
|||||||
Ok(true)
|
Ok(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// ### AR • AuthLib (Ely By)
|
/// ### AR • AuthLib (Ely.by)
|
||||||
/// Parses the ElyIntegration release JSON and returns the download URL for the given AuthLib version.
|
/// Parses the ElyIntegration release JSON and returns the download URL for the given AuthLib version.
|
||||||
async fn extract_ely_authlib_metadata(authlib_fullname: &str) -> Result<(String, String)> {
|
async fn extract_elyby_authlib_metadata(
|
||||||
|
authlib_fullname: &str,
|
||||||
|
) -> Result<(String, String)> {
|
||||||
const URL: &str = "https://git.astralium.su/api/v1/repos/didirus/ElyIntegration/releases/latest";
|
const URL: &str = "https://git.astralium.su/api/v1/repos/didirus/ElyIntegration/releases/latest";
|
||||||
|
|
||||||
let response = reqwest::get(URL).await.map_err(|e| {
|
let response = reqwest::get(URL).await.map_err(|e| {
|
||||||
@@ -215,7 +248,10 @@ async fn extract_ely_authlib_metadata(authlib_fullname: &str) -> Result<(String,
|
|||||||
e
|
e
|
||||||
);
|
);
|
||||||
crate::ErrorKind::NetworkErrorOccurred {
|
crate::ErrorKind::NetworkErrorOccurred {
|
||||||
error: format!("Failed to fetch ElyIntegration release JSON: {}", e),
|
error: format!(
|
||||||
|
"Failed to fetch ElyIntegration release JSON: {}",
|
||||||
|
e
|
||||||
|
),
|
||||||
}
|
}
|
||||||
.as_error()
|
.as_error()
|
||||||
})?;
|
})?;
|
||||||
@@ -228,15 +264,15 @@ async fn extract_ely_authlib_metadata(authlib_fullname: &str) -> Result<(String,
|
|||||||
.as_error()
|
.as_error()
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let assets = json
|
let assets =
|
||||||
.get("assets")
|
json.get("assets")
|
||||||
.and_then(|v| v.as_array())
|
.and_then(|v| v.as_array())
|
||||||
.ok_or_else(|| {
|
.ok_or_else(|| {
|
||||||
crate::ErrorKind::ParseError {
|
crate::ErrorKind::ParseError {
|
||||||
reason: "Missing 'assets' array".into(),
|
reason: "Missing 'assets' array".into(),
|
||||||
}
|
}
|
||||||
.as_error()
|
.as_error()
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let asset = assets
|
let asset = assets
|
||||||
.iter()
|
.iter()
|
||||||
@@ -281,7 +317,7 @@ async fn extract_ely_authlib_metadata(authlib_fullname: &str) -> Result<(String,
|
|||||||
Ok((asset_name, download_url))
|
Ok((asset_name, download_url))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// ### AR • AuthLib (Ely By)
|
/// ### AR • AuthLib (Ely.by)
|
||||||
/// Extracts the artifact URL and Path from the library structure.
|
/// Extracts the artifact URL and Path from the library structure.
|
||||||
///
|
///
|
||||||
/// Returns a tuple of references to the URL and path strings,
|
/// Returns a tuple of references to the URL and path strings,
|
||||||
@@ -331,9 +367,15 @@ async fn fetch_bytes_from_url(url: &str) -> Result<bytes::Bytes> {
|
|||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.map_err(|_| {
|
.map_err(|_| {
|
||||||
tracing::error!("[AR] • Download timed out after {} seconds", TIMEOUT_SECONDS);
|
tracing::error!(
|
||||||
|
"[AR] • Download timed out after {} seconds",
|
||||||
|
TIMEOUT_SECONDS
|
||||||
|
);
|
||||||
crate::ErrorKind::NetworkErrorOccurred {
|
crate::ErrorKind::NetworkErrorOccurred {
|
||||||
error: format!("Download timed out after {TIMEOUT_SECONDS} seconds").to_string(),
|
error: format!(
|
||||||
|
"Download timed out after {TIMEOUT_SECONDS} seconds"
|
||||||
|
)
|
||||||
|
.to_string(),
|
||||||
}
|
}
|
||||||
.as_error()
|
.as_error()
|
||||||
})?
|
})?
|
||||||
@@ -363,9 +405,11 @@ async fn fetch_bytes_from_url(url: &str) -> Result<bytes::Bytes> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// ### AR • AuthLib (Ely By)
|
/// ### AR • AuthLib (Ely.by)
|
||||||
/// Gets the Minecraft library metadata from the local libraries directory.
|
/// Gets the Minecraft library metadata from the local libraries directory.
|
||||||
async fn get_minecraft_library_metadata(minecraft_version: &str) -> Result<Library> {
|
async fn get_minecraft_library_metadata(
|
||||||
|
minecraft_version: &str,
|
||||||
|
) -> Result<Library> {
|
||||||
let state = State::get().await?;
|
let state = State::get().await?;
|
||||||
|
|
||||||
let path = state
|
let path = state
|
||||||
@@ -416,4 +460,4 @@ async fn get_minecraft_library_metadata(minecraft_version: &str) -> Result<Libra
|
|||||||
minecraft_version: minecraft_version.to_string(),
|
minecraft_version: minecraft_version.to_string(),
|
||||||
}
|
}
|
||||||
.as_error())
|
.as_error())
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user