You've already forked AstralRinth
forked from didirus/AstralRinth
feat: add support for multiple account types in database
This commit is contained in:
@@ -1,17 +1,9 @@
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<div v-if="mode !== 'isolated'" ref="button"
|
||||||
v-if="mode !== 'isolated'"
|
|
||||||
ref="button"
|
|
||||||
class="button-base mt-2 px-3 py-2 bg-button-bg rounded-xl flex items-center gap-2"
|
class="button-base mt-2 px-3 py-2 bg-button-bg rounded-xl flex items-center gap-2"
|
||||||
:class="{ expanded: mode === 'expanded' }"
|
:class="{ expanded: mode === 'expanded' }" @click="toggleMenu">
|
||||||
@click="toggleMenu"
|
<Avatar size="36px" :src="selectedAccount ? avatarUrl : 'https://launcher-files.modrinth.com/assets/steve_head.png'
|
||||||
>
|
" />
|
||||||
<Avatar
|
|
||||||
size="36px"
|
|
||||||
:src="
|
|
||||||
selectedAccount ? avatarUrl : 'https://launcher-files.modrinth.com/assets/steve_head.png'
|
|
||||||
"
|
|
||||||
/>
|
|
||||||
<div class="flex flex-col w-full">
|
<div class="flex flex-col w-full">
|
||||||
<span>
|
<span>
|
||||||
<component :is="getAccountType(selectedAccount)" v-if="selectedAccount" class="vector-icon" />
|
<component :is="getAccountType(selectedAccount)" v-if="selectedAccount" class="vector-icon" />
|
||||||
@@ -32,30 +24,23 @@
|
|||||||
</h4>
|
</h4>
|
||||||
<p>Selected</p>
|
<p>Selected</p>
|
||||||
</div>
|
</div>
|
||||||
<Button
|
<Button v-tooltip="'Log out'" icon-only color="raised" @click="logout(selectedAccount.profile.id)">
|
||||||
v-tooltip="'Log out'"
|
|
||||||
icon-only
|
|
||||||
color="raised"
|
|
||||||
@click="logout(selectedAccount.profile.id)"
|
|
||||||
>
|
|
||||||
<TrashIcon />
|
<TrashIcon />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="login-section account">
|
<div v-else class="login-section account">
|
||||||
<h4>Not signed in</h4>
|
<h4>Not signed in</h4>
|
||||||
<Button
|
<Button v-tooltip="'Log via Microsoft'" :disabled="microsoftLoginDisabled" icon-only @click="login()">
|
||||||
v-tooltip="'Log in'"
|
<MicrosoftIcon v-if="!microsoftLoginDisabled" />
|
||||||
:disabled="loginDisabled"
|
|
||||||
icon-only
|
|
||||||
color="primary"
|
|
||||||
@click="login()"
|
|
||||||
>
|
|
||||||
<MicrosoftIcon v-if="!loginDisabled"/>
|
|
||||||
<SpinnerIcon v-else class="animate-spin" />
|
<SpinnerIcon v-else class="animate-spin" />
|
||||||
</Button>
|
</Button>
|
||||||
<Button v-tooltip="'Add offline'" icon-only @click="tryOfflineLogin()">
|
<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()">
|
||||||
|
<ElyByIcon v-if="!elybyLoginDisabled" />
|
||||||
|
<SpinnerIcon v-else class="animate-spin" />
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="displayAccounts.length > 0" class="account-group">
|
<div v-if="displayAccounts.length > 0" class="account-group">
|
||||||
<div v-for="account in displayAccounts" :key="account.profile.id" class="account-row">
|
<div v-for="account in displayAccounts" :key="account.profile.id" class="account-row">
|
||||||
@@ -72,50 +57,85 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="accounts.length > 0" class="login-section account centered">
|
<div v-if="accounts.length > 0" class="login-section account centered">
|
||||||
<Button v-tooltip="'Log in'" icon-only @click="login()">
|
<Button v-tooltip="'Log via Microsoft'" icon-only @click="login()">
|
||||||
<MicrosoftIcon />
|
<MicrosoftIcon v-if="!microsoftLoginDisabled" />
|
||||||
|
<SpinnerIcon v-else class="animate-spin" />
|
||||||
</Button>
|
</Button>
|
||||||
<Button v-tooltip="'Add offline'" icon-only @click="tryOfflineLogin()">
|
<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()">
|
||||||
|
<ElyByIcon v-if="!elybyLoginDisabled" />
|
||||||
|
<SpinnerIcon v-else class="animate-spin" />
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
</transition>
|
</transition>
|
||||||
<ModalWrapper ref="loginOfflineModal" class="modal" header="Add new offline account">
|
<ModalWrapper ref="addOfflineModal" class="modal" header="Add new offline account">
|
||||||
<div class="modal-body">
|
<div class="flex flex-col gap-4 px-6 py-5">
|
||||||
<div class="label">Enter offline username</div>
|
<label class="label">Enter your player name</label>
|
||||||
<input type="text" v-model="playerName" placeholder="Provide offline player name" />
|
<input
|
||||||
<Button icon-only color="secondary" @click="offlineLoginFinally()">
|
type="text"
|
||||||
Continue
|
v-model="offlinePlayerName"
|
||||||
</Button>
|
placeholder="Your player name here..."
|
||||||
|
class="input"
|
||||||
|
/>
|
||||||
|
<div class="mt-6 ml-auto">
|
||||||
|
<Button
|
||||||
|
icon-only
|
||||||
|
color="primary"
|
||||||
|
@click="addOfflineProfile()"
|
||||||
|
class="continue-button"
|
||||||
|
>
|
||||||
|
Login
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ModalWrapper>
|
</ModalWrapper>
|
||||||
<ModalWrapper ref="loginErrorModal" class="modal" header="Error while proceed">
|
<ModalWrapper ref="inputErrorModal" class="modal" header="Error while proceeding">
|
||||||
<div class="modal-body">
|
<div class="flex flex-col gap-4 px-6 py-5">
|
||||||
<div class="label">Error occurred while adding offline account</div>
|
<label class="text-base font-medium text-red-700">
|
||||||
<Button color="primary" @click="retryOfflineLogin()">
|
An error occurred while adding the offline account. Please follow the instructions below.
|
||||||
Try again
|
</label>
|
||||||
</Button>
|
|
||||||
|
<ul class="list-disc list-inside text-sm space-y-1">
|
||||||
|
<li>Check that you have entered the correct player name.</li>
|
||||||
|
<li>
|
||||||
|
Player name must be at least {{ minOfflinePlayerNameLength }} characters long and no more than
|
||||||
|
{{ maxOfflinePlayerNameLength }} characters.
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<div class="mt-6 ml-auto">
|
||||||
|
<Button
|
||||||
|
color="primary"
|
||||||
|
@click="retryAddOfflineProfile"
|
||||||
|
class="retry-button"
|
||||||
|
>
|
||||||
|
Try again
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ModalWrapper>
|
</ModalWrapper>
|
||||||
<ModalWrapper ref="unexpectedErrorModal" class="modal" header="Ошибка">
|
<ModalWrapper ref="exceptionErrorModal" class="modal" header="Unexpected error occurred">
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<div class="label">Unexcepted error</div>
|
<label class="label">An unexpected error has occurred. Please try again later.</label>
|
||||||
</div>
|
</div>
|
||||||
</ModalWrapper>
|
</ModalWrapper>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import {
|
import {
|
||||||
DropdownIcon,
|
DropdownIcon,
|
||||||
PlusIcon,
|
|
||||||
TrashIcon,
|
TrashIcon,
|
||||||
LogInIcon,
|
|
||||||
PirateIcon as Offline,
|
PirateIcon as Offline,
|
||||||
MicrosoftIcon as License,
|
MicrosoftIcon as License,
|
||||||
|
ElyByIcon as ElyBy,
|
||||||
MicrosoftIcon,
|
MicrosoftIcon,
|
||||||
PirateIcon,
|
PirateIcon,
|
||||||
SpinnerIcon } from '@modrinth/assets'
|
ElyByIcon,
|
||||||
|
SpinnerIcon
|
||||||
|
} from '@modrinth/assets'
|
||||||
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 {
|
||||||
@@ -145,48 +165,80 @@ defineProps({
|
|||||||
const emit = defineEmits(['change'])
|
const emit = defineEmits(['change'])
|
||||||
|
|
||||||
const accounts = ref({})
|
const accounts = ref({})
|
||||||
const loginDisabled = ref(false)
|
const microsoftLoginDisabled = ref(false)
|
||||||
|
const elybyLoginDisabled = ref(false)
|
||||||
const defaultUser = ref()
|
const defaultUser = ref()
|
||||||
const loginOfflineModal = ref(null)
|
|
||||||
const loginErrorModal = ref(null)
|
|
||||||
const unexpectedErrorModal = ref(null)
|
|
||||||
const playerName = ref('')
|
|
||||||
|
|
||||||
async function tryOfflineLogin() { // [AR] Feature
|
// [AR] • Feature
|
||||||
loginOfflineModal.value.show()
|
const addOfflineModal = ref(null)
|
||||||
|
const inputErrorModal = ref(null)
|
||||||
|
const exceptionErrorModal = ref(null)
|
||||||
|
const offlinePlayerName = ref('')
|
||||||
|
const minOfflinePlayerNameLength = 2
|
||||||
|
const maxOfflinePlayerNameLength = 20
|
||||||
|
|
||||||
|
// [AR] • Feature
|
||||||
|
function getAccountType(account) {
|
||||||
|
switch (account.account_type) {
|
||||||
|
case 'microsoft':
|
||||||
|
return License
|
||||||
|
case 'pirate':
|
||||||
|
return Offline
|
||||||
|
case 'elyby':
|
||||||
|
return ElyBy
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function offlineLoginFinally() { // [AR] Feature
|
// [AR] • Feature
|
||||||
const name = playerName.value
|
function showOfflineLoginModal() {
|
||||||
if (name.length > 1 && name.length < 20 && name !== '') {
|
addOfflineModal.value?.show()
|
||||||
const loggedIn = await offline_login(name).catch(handleError)
|
}
|
||||||
loginOfflineModal.value.hide()
|
|
||||||
if (loggedIn) {
|
// [AR] • Feature
|
||||||
await setAccount(loggedIn)
|
function retryAddOfflineProfile() {
|
||||||
|
inputErrorModal.value?.hide()
|
||||||
|
showOfflineLoginModal()
|
||||||
|
}
|
||||||
|
|
||||||
|
// [AR] • Feature
|
||||||
|
async function addOfflineProfile() {
|
||||||
|
const name = offlinePlayerName.value.trim()
|
||||||
|
const isValidName = name.length >= minOfflinePlayerNameLength && name.length <= maxOfflinePlayerNameLength
|
||||||
|
|
||||||
|
if (!isValidName) {
|
||||||
|
addOfflineModal.value?.hide()
|
||||||
|
inputErrorModal.value?.show()
|
||||||
|
offlinePlayerName.value = ''
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await offline_login(name)
|
||||||
|
|
||||||
|
addOfflineModal.value?.hide()
|
||||||
|
|
||||||
|
if (result) {
|
||||||
|
await setAccount(result)
|
||||||
await refreshValues()
|
await refreshValues()
|
||||||
} else {
|
} else {
|
||||||
unexpectedErrorModal.value.show()
|
exceptionErrorModal.value?.show()
|
||||||
}
|
}
|
||||||
playerName.value = ''
|
} catch (error) {
|
||||||
} else {
|
handleError(error)
|
||||||
playerName.value = ''
|
exceptionErrorModal.value?.show()
|
||||||
loginOfflineModal.value.hide()
|
} finally {
|
||||||
loginErrorModal.value.show()
|
offlinePlayerName.value = ''
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function retryOfflineLogin() { // [AR] Feature
|
// [AR] • Feature
|
||||||
loginErrorModal.value.hide()
|
// TODO:
|
||||||
tryOfflineLogin()
|
async function loginViaElyBy() {
|
||||||
|
elybyLoginDisabled.value = true
|
||||||
|
console.log("Login via Ely.by clicked!")
|
||||||
|
elybyLoginDisabled.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
function getAccountType(account) { // [AR] Feature
|
|
||||||
if (account.access_token != "null" && account.access_token != null && account.access_token != "") {
|
|
||||||
return License
|
|
||||||
} else {
|
|
||||||
return Offline
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const equippedSkin = ref(null)
|
const equippedSkin = ref(null)
|
||||||
const headUrlCache = ref(new Map())
|
const headUrlCache = ref(new Map())
|
||||||
|
|
||||||
@@ -212,13 +264,13 @@ async function refreshValues() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function setLoginDisabled(value) {
|
function setLoginDisabled(value) {
|
||||||
loginDisabled.value = value
|
microsoftLoginDisabled.value = value
|
||||||
}
|
}
|
||||||
|
|
||||||
defineExpose({
|
defineExpose({
|
||||||
refreshValues,
|
refreshValues,
|
||||||
setLoginDisabled,
|
setLoginDisabled,
|
||||||
loginDisabled,
|
loginDisabled: microsoftLoginDisabled,
|
||||||
})
|
})
|
||||||
await refreshValues()
|
await refreshValues()
|
||||||
|
|
||||||
@@ -264,7 +316,7 @@ async function setAccount(account) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function login() {
|
async function login() {
|
||||||
loginDisabled.value = true
|
microsoftLoginDisabled.value = true
|
||||||
const loggedIn = await login_flow().catch(handleSevereError)
|
const loggedIn = await login_flow().catch(handleSevereError)
|
||||||
|
|
||||||
if (loggedIn) {
|
if (loggedIn) {
|
||||||
@@ -273,7 +325,7 @@ async function login() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
trackEvent('AccountLogIn')
|
trackEvent('AccountLogIn')
|
||||||
loginDisabled.value = false
|
microsoftLoginDisabled.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
const logout = async (id) => {
|
const logout = async (id) => {
|
||||||
|
|||||||
@@ -41,7 +41,7 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"productName": "AstralRinth App",
|
"productName": "AstralRinth App",
|
||||||
"version": "0.10.304",
|
"version": "0.10.305",
|
||||||
"mainBinaryName": "AstralRinth App",
|
"mainBinaryName": "AstralRinth App",
|
||||||
"identifier": "AstralRinthApp",
|
"identifier": "AstralRinthApp",
|
||||||
"plugins": {
|
"plugins": {
|
||||||
|
|||||||
+8
-2
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"db_name": "SQLite",
|
"db_name": "SQLite",
|
||||||
"query": "\n SELECT\n uuid, active, username, access_token, refresh_token, expires\n FROM minecraft_users\n WHERE active = TRUE\n ",
|
"query": "\n SELECT\n uuid, active, username, access_token, refresh_token, expires, account_type\n FROM minecraft_users\n WHERE active = TRUE\n ",
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [
|
"columns": [
|
||||||
{
|
{
|
||||||
@@ -32,6 +32,11 @@
|
|||||||
"name": "expires",
|
"name": "expires",
|
||||||
"ordinal": 5,
|
"ordinal": 5,
|
||||||
"type_info": "Integer"
|
"type_info": "Integer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "account_type",
|
||||||
|
"ordinal": 6,
|
||||||
|
"type_info": "Text"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"parameters": {
|
"parameters": {
|
||||||
@@ -43,8 +48,9 @@
|
|||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
|
false,
|
||||||
false
|
false
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"hash": "bf7d47350092d87c478009adaab131168e87bb37aa65c2156ad2cb6198426d8c"
|
"hash": "57214178fb3a0ccd8f67457e9732a706cbc4a4f5190c9320d1ad6111b9711d63"
|
||||||
}
|
}
|
||||||
+8
-2
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"db_name": "SQLite",
|
"db_name": "SQLite",
|
||||||
"query": "\n SELECT\n uuid, active, username, access_token, refresh_token, expires\n FROM minecraft_users\n ",
|
"query": "\n SELECT\n uuid, active, username, access_token, refresh_token, expires, account_type\n FROM minecraft_users\n ",
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [
|
"columns": [
|
||||||
{
|
{
|
||||||
@@ -32,6 +32,11 @@
|
|||||||
"name": "expires",
|
"name": "expires",
|
||||||
"ordinal": 5,
|
"ordinal": 5,
|
||||||
"type_info": "Integer"
|
"type_info": "Integer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "account_type",
|
||||||
|
"ordinal": 6,
|
||||||
|
"type_info": "Text"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"parameters": {
|
"parameters": {
|
||||||
@@ -43,8 +48,9 @@
|
|||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
|
false,
|
||||||
false
|
false
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"hash": "727e3e1bc8625bbcb833920059bb8cea926ac6c65d613904eff1d740df30acda"
|
"hash": "5c803f3d90c147210e8e7a7a6d7234d3801bc38c23e1e02fbd8fa08ae51e8f08"
|
||||||
}
|
}
|
||||||
Generated
+12
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"db_name": "SQLite",
|
||||||
|
"query": "\n INSERT INTO minecraft_users (uuid, active, username, access_token, refresh_token, expires, account_type)\n VALUES ($1, $2, $3, $4, $5, $6, $7)\n ON CONFLICT (uuid) DO UPDATE SET\n active = $2,\n username = $3,\n access_token = $4,\n refresh_token = $5,\n expires = $6,\n account_type = $7\n ",
|
||||||
|
"describe": {
|
||||||
|
"columns": [],
|
||||||
|
"parameters": {
|
||||||
|
"Right": 7
|
||||||
|
},
|
||||||
|
"nullable": []
|
||||||
|
},
|
||||||
|
"hash": "8f7d4406ddae4a158eabb20fc6a8ffb21c4e22c7ff33459df5049ee441fa0467"
|
||||||
|
}
|
||||||
Generated
-12
@@ -1,12 +0,0 @@
|
|||||||
{
|
|
||||||
"db_name": "SQLite",
|
|
||||||
"query": "\n INSERT INTO minecraft_users (uuid, active, username, access_token, refresh_token, expires)\n VALUES ($1, $2, $3, $4, $5, $6)\n ON CONFLICT (uuid) DO UPDATE SET\n active = $2,\n username = $3,\n access_token = $4,\n refresh_token = $5,\n expires = $6\n ",
|
|
||||||
"describe": {
|
|
||||||
"columns": [],
|
|
||||||
"parameters": {
|
|
||||||
"Right": 6
|
|
||||||
},
|
|
||||||
"nullable": []
|
|
||||||
},
|
|
||||||
"hash": "d719cf2f6f87c5ea7ea6ace2d6a1828ee58a724f06a91633b8a40b4e04d0b9a0"
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
-- [AR] - SQL Migration
|
||||||
|
ALTER TABLE minecraft_users ADD COLUMN account_type varchar(32) NOT NULL DEFAULT 'unknown';
|
||||||
|
|
||||||
|
UPDATE minecraft_users SET account_type = 'microsoft' WHERE access_token != 'null';
|
||||||
|
UPDATE minecraft_users SET account_type = 'pirate' WHERE access_token == 'null';
|
||||||
@@ -131,6 +131,7 @@ where
|
|||||||
expires: legacy_credentials.expires,
|
expires: legacy_credentials.expires,
|
||||||
active: minecraft_auth.default_user == Some(uuid)
|
active: minecraft_auth.default_user == Some(uuid)
|
||||||
|| minecraft_users_len == 1,
|
|| minecraft_users_len == 1,
|
||||||
|
account_type: legacy_credentials.account_type,
|
||||||
}
|
}
|
||||||
.upsert(exec)
|
.upsert(exec)
|
||||||
.await?;
|
.await?;
|
||||||
@@ -518,6 +519,7 @@ struct LegacyCredentials {
|
|||||||
pub access_token: String,
|
pub access_token: String,
|
||||||
pub refresh_token: String,
|
pub refresh_token: String,
|
||||||
pub expires: DateTime<Utc>,
|
pub expires: DateTime<Utc>,
|
||||||
|
pub account_type: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
#[derive(Deserialize, Debug)]
|
||||||
|
|||||||
@@ -191,6 +191,7 @@ pub async fn login_finish(
|
|||||||
expires: oauth_token.date
|
expires: oauth_token.date
|
||||||
+ Duration::seconds(oauth_token.value.expires_in as i64),
|
+ Duration::seconds(oauth_token.value.expires_in as i64),
|
||||||
active: true,
|
active: true,
|
||||||
|
account_type: AccountType::Microsoft.as_lowercase_str(),
|
||||||
};
|
};
|
||||||
|
|
||||||
// During login, we need to fetch the online profile at least once to get the
|
// During login, we need to fetch the online profile at least once to get the
|
||||||
@@ -229,6 +230,7 @@ pub async fn offline_auth(
|
|||||||
refresh_token: refresh_token,
|
refresh_token: refresh_token,
|
||||||
expires: Utc::now() + Duration::days(365 * 99),
|
expires: Utc::now() + Duration::days(365 * 99),
|
||||||
active: true,
|
active: true,
|
||||||
|
account_type: AccountType::Pirate.as_lowercase_str(),
|
||||||
};
|
};
|
||||||
|
|
||||||
credentials.offline_profile = MinecraftProfile {
|
credentials.offline_profile = MinecraftProfile {
|
||||||
@@ -242,6 +244,30 @@ pub async fn offline_auth(
|
|||||||
Ok(credentials)
|
Ok(credentials)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// [AR] • Feature
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
pub enum AccountType {
|
||||||
|
Unknown,
|
||||||
|
Microsoft,
|
||||||
|
Pirate,
|
||||||
|
ElyBy,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AccountType {
|
||||||
|
fn as_str(&self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
AccountType::Unknown => "Unknown",
|
||||||
|
AccountType::Microsoft => "Microsoft",
|
||||||
|
AccountType::Pirate => "Pirate",
|
||||||
|
AccountType::ElyBy => "ElyBy",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn as_lowercase_str(&self) -> String {
|
||||||
|
self.as_str().to_lowercase()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
#[derive(Deserialize, Debug)]
|
||||||
pub struct Credentials {
|
pub struct Credentials {
|
||||||
/// The offline profile of the user these credentials are for.
|
/// The offline profile of the user these credentials are for.
|
||||||
@@ -255,6 +281,7 @@ pub struct Credentials {
|
|||||||
pub refresh_token: String,
|
pub refresh_token: String,
|
||||||
pub expires: DateTime<Utc>,
|
pub expires: DateTime<Utc>,
|
||||||
pub active: bool,
|
pub active: bool,
|
||||||
|
pub account_type: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An entry in the player profile cache, keyed by player UUID.
|
/// An entry in the player profile cache, keyed by player UUID.
|
||||||
@@ -480,7 +507,7 @@ impl Credentials {
|
|||||||
let res = sqlx::query!(
|
let res = sqlx::query!(
|
||||||
"
|
"
|
||||||
SELECT
|
SELECT
|
||||||
uuid, active, username, access_token, refresh_token, expires
|
uuid, active, username, access_token, refresh_token, expires, account_type
|
||||||
FROM minecraft_users
|
FROM minecraft_users
|
||||||
WHERE active = TRUE
|
WHERE active = TRUE
|
||||||
"
|
"
|
||||||
@@ -503,6 +530,7 @@ impl Credentials {
|
|||||||
.single()
|
.single()
|
||||||
.unwrap_or_else(Utc::now),
|
.unwrap_or_else(Utc::now),
|
||||||
active: x.active == 1,
|
active: x.active == 1,
|
||||||
|
account_type: x.account_type,
|
||||||
};
|
};
|
||||||
credentials.refresh(exec).await.ok();
|
credentials.refresh(exec).await.ok();
|
||||||
Some(credentials)
|
Some(credentials)
|
||||||
@@ -517,7 +545,7 @@ impl Credentials {
|
|||||||
let res = sqlx::query!(
|
let res = sqlx::query!(
|
||||||
"
|
"
|
||||||
SELECT
|
SELECT
|
||||||
uuid, active, username, access_token, refresh_token, expires
|
uuid, active, username, access_token, refresh_token, expires, account_type
|
||||||
FROM minecraft_users
|
FROM minecraft_users
|
||||||
"
|
"
|
||||||
)
|
)
|
||||||
@@ -537,6 +565,7 @@ impl Credentials {
|
|||||||
.single()
|
.single()
|
||||||
.unwrap_or_else(Utc::now),
|
.unwrap_or_else(Utc::now),
|
||||||
active: x.active == 1,
|
active: x.active == 1,
|
||||||
|
account_type: x.account_type,
|
||||||
};
|
};
|
||||||
|
|
||||||
async move {
|
async move {
|
||||||
@@ -572,14 +601,15 @@ impl Credentials {
|
|||||||
|
|
||||||
sqlx::query!(
|
sqlx::query!(
|
||||||
"
|
"
|
||||||
INSERT INTO minecraft_users (uuid, active, username, access_token, refresh_token, expires)
|
INSERT INTO minecraft_users (uuid, active, username, access_token, refresh_token, expires, account_type)
|
||||||
VALUES ($1, $2, $3, $4, $5, $6)
|
VALUES ($1, $2, $3, $4, $5, $6, $7)
|
||||||
ON CONFLICT (uuid) DO UPDATE SET
|
ON CONFLICT (uuid) DO UPDATE SET
|
||||||
active = $2,
|
active = $2,
|
||||||
username = $3,
|
username = $3,
|
||||||
access_token = $4,
|
access_token = $4,
|
||||||
refresh_token = $5,
|
refresh_token = $5,
|
||||||
expires = $6
|
expires = $6,
|
||||||
|
account_type = $7
|
||||||
",
|
",
|
||||||
uuid,
|
uuid,
|
||||||
self.active,
|
self.active,
|
||||||
@@ -587,6 +617,7 @@ impl Credentials {
|
|||||||
self.access_token,
|
self.access_token,
|
||||||
self.refresh_token,
|
self.refresh_token,
|
||||||
expires,
|
expires,
|
||||||
|
self.account_type,
|
||||||
)
|
)
|
||||||
.execute(exec)
|
.execute(exec)
|
||||||
.await?;
|
.await?;
|
||||||
@@ -649,6 +680,7 @@ impl Serialize for Credentials {
|
|||||||
ser.serialize_field("refresh_token", &self.refresh_token)?;
|
ser.serialize_field("refresh_token", &self.refresh_token)?;
|
||||||
ser.serialize_field("expires", &self.expires)?;
|
ser.serialize_field("expires", &self.expires)?;
|
||||||
ser.serialize_field("active", &self.active)?;
|
ser.serialize_field("active", &self.active)?;
|
||||||
|
ser.serialize_field("account_type", &self.account_type)?;
|
||||||
ser.end()
|
ser.end()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,64 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
width="10mm"
|
||||||
|
height="10mm"
|
||||||
|
viewBox="0 0 10 10"
|
||||||
|
version="1.1"
|
||||||
|
id="svg1"
|
||||||
|
inkscape:version="1.4.2 (ebf0e940, 2025-05-08)"
|
||||||
|
sodipodi:docname="elyby-icon.svg"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg">
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="namedview1"
|
||||||
|
pagecolor="#505050"
|
||||||
|
bordercolor="#eeeeee"
|
||||||
|
borderopacity="1"
|
||||||
|
inkscape:showpageshadow="0"
|
||||||
|
inkscape:pageopacity="0"
|
||||||
|
inkscape:pagecheckerboard="0"
|
||||||
|
inkscape:deskcolor="#505050"
|
||||||
|
inkscape:document-units="mm"
|
||||||
|
inkscape:zoom="20.48"
|
||||||
|
inkscape:cx="13.891602"
|
||||||
|
inkscape:cy="18.09082"
|
||||||
|
inkscape:window-width="1776"
|
||||||
|
inkscape:window-height="1236"
|
||||||
|
inkscape:window-x="244"
|
||||||
|
inkscape:window-y="25"
|
||||||
|
inkscape:window-maximized="0"
|
||||||
|
inkscape:current-layer="layer1" />
|
||||||
|
<defs
|
||||||
|
id="defs1" />
|
||||||
|
<g
|
||||||
|
inkscape:label="Layer 1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1">
|
||||||
|
<rect
|
||||||
|
style="fill:#00c600;stroke-width:0.264999;paint-order:markers fill stroke;fill-opacity:1"
|
||||||
|
id="rect1"
|
||||||
|
width="10"
|
||||||
|
height="10"
|
||||||
|
x="0"
|
||||||
|
y="0"
|
||||||
|
rx="1"
|
||||||
|
ry="1" />
|
||||||
|
<text
|
||||||
|
xml:space="preserve"
|
||||||
|
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:9.61119px;font-family:Arial;-inkscape-font-specification:'Arial, Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;text-align:start;writing-mode:lr-tb;direction:ltr;text-anchor:start;fill:#ffffff;fill-opacity:1;stroke-width:0.80219;paint-order:markers fill stroke"
|
||||||
|
x="1.0449646"
|
||||||
|
y="9.1731787"
|
||||||
|
id="text1"
|
||||||
|
inkscape:label="text1"
|
||||||
|
transform="scale(1.1466469,0.87210806)"><tspan
|
||||||
|
sodipodi:role="line"
|
||||||
|
id="tspan1"
|
||||||
|
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:9.61119px;font-family:Arial;-inkscape-font-specification:'Arial, Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#ffffff;fill-opacity:1;stroke-width:0.802193"
|
||||||
|
x="1.0449646"
|
||||||
|
y="9.1731787">E</tspan></text>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.4 KiB |
@@ -89,6 +89,7 @@ import _PirateIcon from './icons/pirate.svg?component'
|
|||||||
import _MicrosoftIcon from './icons/microsoft.svg?component'
|
import _MicrosoftIcon from './icons/microsoft.svg?component'
|
||||||
import _PirateShipIcon from './icons/pirate-ship.svg?component'
|
import _PirateShipIcon from './icons/pirate-ship.svg?component'
|
||||||
import _AstralRinthLogo from './icons/astralrinth-logo.svg?component'
|
import _AstralRinthLogo from './icons/astralrinth-logo.svg?component'
|
||||||
|
import _ElyByIcon from './icons/elyby-icon.svg?component'
|
||||||
|
|
||||||
// [AR] Feature. Exports
|
// [AR] Feature. Exports
|
||||||
|
|
||||||
@@ -96,6 +97,7 @@ export const PirateIcon = _PirateIcon
|
|||||||
export const MicrosoftIcon = _MicrosoftIcon
|
export const MicrosoftIcon = _MicrosoftIcon
|
||||||
export const PirateShipIcon = _PirateShipIcon
|
export const PirateShipIcon = _PirateShipIcon
|
||||||
export const AstralRinthLogo = _AstralRinthLogo
|
export const AstralRinthLogo = _AstralRinthLogo
|
||||||
|
export const ElyByIcon = _ElyByIcon
|
||||||
|
|
||||||
// Skin Models
|
// Skin Models
|
||||||
export { default as ClassicPlayerModel } from './models/classic-player.gltf?url'
|
export { default as ClassicPlayerModel } from './models/classic-player.gltf?url'
|
||||||
|
|||||||
Reference in New Issue
Block a user