Avatar lookup loading toast

This commit is contained in:
Natsumi
2026-02-03 13:52:55 +13:00
parent bbbb79eaca
commit 5a27e6fb51
7 changed files with 126 additions and 58 deletions
+8 -2
View File
@@ -384,7 +384,7 @@
"fetch_cancelled_graph_not_updated": "Fetch cancelled" "fetch_cancelled_graph_not_updated": "Fetch cancelled"
}, },
"notifications": { "notifications": {
"start_fetching": "Start fetching", "start_fetching": "Started fetching",
"mutual_friend_graph_ready_title": "Mutual Friend Network Graph", "mutual_friend_graph_ready_title": "Mutual Friend Network Graph",
"mutual_friend_graph_ready_message": "Mutual friend network graph is ready", "mutual_friend_graph_ready_message": "Mutual friend network graph is ready",
"friend_list_changed_fetch_again": "Friend list changed. Please fetch the mutual friend network again." "friend_list_changed_fetch_again": "Friend list changed. Please fetch the mutual friend network again."
@@ -2082,6 +2082,12 @@
"image_changed": "Avatar image changed", "image_changed": "Avatar image changed",
"image_invalid": "Current avatar image invalid" "image_invalid": "Current avatar image invalid"
}, },
"avatar_lookup": {
"not_found": "Avatar not found in search providers",
"failed": "Avatar lookup failed",
"private_or_not_found": "Users avatar is private or not found in search providers",
"loading": "Searching for avatar using search providers"
},
"database": { "database": {
"upgrade_complete": "Database upgrade complete" "upgrade_complete": "Database upgrade complete"
}, },
@@ -2091,7 +2097,7 @@
"file": { "file": {
"not_image": "File isn't an image", "not_image": "File isn't an image",
"too_large": "File size too large", "too_large": "File size too large",
"folder_missing": "Folder dosn't exist" "folder_missing": "Folder doesn't exist"
}, },
"group": { "group": {
"load_failed": "Failed to load group" "load_failed": "Failed to load group"
+82 -25
View File
@@ -69,6 +69,7 @@ export const useAvatarStore = defineStore('Avatar', () => {
timeSpent: 0 timeSpent: 0
}); });
const avatarHistory = ref([]); const avatarHistory = ref([]);
const loadingToastId = ref(null);
watch( watch(
() => watchState.isLoggedIn, () => watchState.isLoggedIn,
@@ -463,10 +464,11 @@ export const useAvatarStore = defineStore('Avatar', () => {
const avatars = new Map(); const avatars = new Map();
if (type === 'search') { if (type === 'search') {
try { try {
const response = await webApiService.execute({ const url = `${
url: `${
avatarProviderStore.avatarRemoteDatabaseProvider avatarProviderStore.avatarRemoteDatabaseProvider
}?${type}=${encodeURIComponent(search)}&n=5000`, }?${type}=${encodeURIComponent(search)}&n=5000`;
const response = await webApiService.execute({
url,
method: 'GET', method: 'GET',
headers: { headers: {
Referer: 'https://vrcx.app', Referer: 'https://vrcx.app',
@@ -475,7 +477,7 @@ export const useAvatarStore = defineStore('Avatar', () => {
}); });
const json = JSON.parse(response.data); const json = JSON.parse(response.data);
if (AppDebug.debugWebRequests) { if (AppDebug.debugWebRequests) {
console.log(json, response); console.log(url, json, response);
} }
if (response.status === 200 && typeof json === 'object') { if (response.status === 200 && typeof json === 'object') {
json.forEach((avatar) => { json.forEach((avatar) => {
@@ -522,11 +524,18 @@ export const useAvatarStore = defineStore('Avatar', () => {
} }
async function lookupAvatarByImageFileId(authorId, fileId) { async function lookupAvatarByImageFileId(authorId, fileId) {
const length = for (const providerUrl of avatarProviderStore.avatarRemoteDatabaseProviderList) {
avatarProviderStore.avatarRemoteDatabaseProviderList.length; const avatar = await lookupAvatarByFileId(providerUrl, fileId);
for (let i = 0; i < length; ++i) { if (avatar?.id) {
const url = avatarProviderStore.avatarRemoteDatabaseProviderList[i]; return avatar.id;
const avatarArray = await lookupAvatarsByAuthor(url, authorId); }
}
for (const providerUrl of avatarProviderStore.avatarRemoteDatabaseProviderList) {
const avatarArray = await lookupAvatarsByAuthor(
providerUrl,
authorId
);
for (const avatar of avatarArray) { for (const avatar of avatarArray) {
if (extractFileId(avatar.imageUrl) === fileId) { if (extractFileId(avatar.imageUrl) === fileId) {
return avatar.id; return avatar.id;
@@ -536,14 +545,11 @@ export const useAvatarStore = defineStore('Avatar', () => {
return null; return null;
} }
async function lookupAvatarsByAuthor(url, authorId) { async function lookupAvatarByFileId(providerUrl, fileId) {
const avatars = [];
if (!url) {
return avatars;
}
try { try {
const url = `${providerUrl}?fileId=${encodeURIComponent(fileId)}`;
const response = await webApiService.execute({ const response = await webApiService.execute({
url: `${url}?authorId=${encodeURIComponent(authorId)}`, url,
method: 'GET', method: 'GET',
headers: { headers: {
Referer: 'https://vrcx.app', Referer: 'https://vrcx.app',
@@ -552,7 +558,50 @@ export const useAvatarStore = defineStore('Avatar', () => {
}); });
const json = JSON.parse(response.data); const json = JSON.parse(response.data);
if (AppDebug.debugWebRequests) { if (AppDebug.debugWebRequests) {
console.log(json, response); console.log(url, json, response);
}
if (response.status === 200 && typeof json === 'object') {
const ref = {
authorId: '',
authorName: '',
name: '',
description: '',
id: '',
imageUrl: '',
thumbnailImageUrl: '',
created_at: '0001-01-01T00:00:00.0000000Z',
updated_at: '0001-01-01T00:00:00.0000000Z',
releaseStatus: 'public',
...json
};
return ref;
} else {
return null;
}
} catch (err) {
// ignore errors for now, not all providers support this lookup type
return null;
}
}
async function lookupAvatarsByAuthor(providerUrl, authorId) {
const avatars = [];
if (!providerUrl) {
return avatars;
}
try {
const url = `${providerUrl}?authorId=${encodeURIComponent(authorId)}`;
const response = await webApiService.execute({
url,
method: 'GET',
headers: {
Referer: 'https://vrcx.app',
'VRCX-ID': vrcxUpdaterStore.vrcxId
}
});
const json = JSON.parse(response.data);
if (AppDebug.debugWebRequests) {
console.log(url, json, response);
} }
if (response.status === 200 && typeof json === 'object') { if (response.status === 200 && typeof json === 'object') {
json.forEach((avatar) => { json.forEach((avatar) => {
@@ -621,11 +670,21 @@ export const useAvatarStore = defineStore('Avatar', () => {
async function checkAvatarCacheRemote(fileId, ownerUserId) { async function checkAvatarCacheRemote(fileId, ownerUserId) {
if (advancedSettingsStore.avatarRemoteDatabase) { if (advancedSettingsStore.avatarRemoteDatabase) {
try {
toast.dismiss(loadingToastId.value);
loadingToastId.value = toast.loading(
t('message.avatar_lookup.loading')
);
const avatarId = await lookupAvatarByImageFileId( const avatarId = await lookupAvatarByImageFileId(
ownerUserId, ownerUserId,
fileId fileId
); );
return avatarId; return avatarId;
} catch (err) {
console.error('Failed to lookup avatar by image file id:', err);
} finally {
toast.dismiss(loadingToastId.value);
}
} }
return null; return null;
} }
@@ -637,7 +696,7 @@ export const useAvatarStore = defineStore('Avatar', () => {
) { ) {
const fileId = extractFileId(currentAvatarImageUrl); const fileId = extractFileId(currentAvatarImageUrl);
if (!fileId) { if (!fileId) {
toast.error('Sorry, the author is unknown'); toast.error(t('message.avatar_lookup.failed'));
} else if (refUserId === userStore.currentUser.id) { } else if (refUserId === userStore.currentUser.id) {
showAvatarDialog(userStore.currentUser.currentAvatar); showAvatarDialog(userStore.currentUser.currentAvatar);
} else { } else {
@@ -646,22 +705,20 @@ export const useAvatarStore = defineStore('Avatar', () => {
if (!avatarId) { if (!avatarId) {
avatarInfo = await getAvatarName(currentAvatarImageUrl); avatarInfo = await getAvatarName(currentAvatarImageUrl);
if (avatarInfo.ownerId === userStore.currentUser.id) { if (avatarInfo.ownerId === userStore.currentUser.id) {
userStore.refreshUserDialogAvatars(fileId); await userStore.refreshUserDialogAvatars(fileId);
return;
} }
} }
if (!avatarId) { if (!avatarId) {
avatarId = await checkAvatarCacheRemote( avatarId = await checkAvatarCacheRemote(fileId, ownerUserId);
fileId,
avatarInfo.ownerId
);
} }
if (!avatarId) { if (!avatarId) {
if (avatarInfo.ownerId === refUserId) { if (ownerUserId === refUserId) {
toast.warning( toast.warning(
"It's personal (own) avatar or not found in avatar database" t('message.avatar_lookup.private_or_not_found')
); );
} else { } else {
toast.warning('Avatar not found in avatar database'); toast.warning(t('message.avatar_lookup.not_found'));
userStore.showUserDialog(avatarInfo.ownerId); userStore.showUserDialog(avatarInfo.ownerId);
} }
} }
+15 -10
View File
@@ -15,7 +15,7 @@ export const useAvatarProviderStore = defineStore('AvatarProvider', () => {
const avatarRemoteDatabaseProvider = ref(''); const avatarRemoteDatabaseProvider = ref('');
const avatarRemoteDatabaseProviderList = ref([ const avatarRemoteDatabaseProviderList = ref([
'https://api.avtrdb.com/v2/avatar/search/vrcx' 'https://api.avtrdb.com/v3/avatar/search/vrcx'
]); ]);
watch( watch(
() => watchState.isLoggedIn, () => watchState.isLoggedIn,
@@ -29,21 +29,26 @@ export const useAvatarProviderStore = defineStore('AvatarProvider', () => {
avatarRemoteDatabaseProviderList.value = JSON.parse( avatarRemoteDatabaseProviderList.value = JSON.parse(
await configRepository.getString( await configRepository.getString(
'VRCX_avatarRemoteDatabaseProviderList', 'VRCX_avatarRemoteDatabaseProviderList',
'[ "https://api.avtrdb.com/v2/avatar/search/vrcx" ]' '[ "https://api.avtrdb.com/v3/avatar/search/vrcx" ]'
) )
); );
const deprecated = 'https://avtr.just-h.party/vrcx_search.php';
const v1 = 'https://api.avtrdb.com/v1/avatar/search/vrcx';
const v2 = 'https://api.avtrdb.com/v2/avatar/search/vrcx';
const v3 = 'https://api.avtrdb.com/v3/avatar/search/vrcx';
const newList = avatarRemoteDatabaseProviderList.value
.filter((u) => u !== deprecated)
.map((u) => (u === v1 || u === v2 ? v3 : u));
if ( if (
avatarRemoteDatabaseProviderList.value.includes( JSON.stringify(newList) !==
'https://avtr.just-h.party/vrcx_search.php' JSON.stringify(avatarRemoteDatabaseProviderList.value)
)
) { ) {
removeFromArray( avatarRemoteDatabaseProviderList.value = newList;
avatarRemoteDatabaseProviderList.value,
'https://avtr.just-h.party/vrcx_search.php'
);
await configRepository.setString( await configRepository.setString(
'VRCX_avatarRemoteDatabaseProviderList', 'VRCX_avatarRemoteDatabaseProviderList',
JSON.stringify(avatarRemoteDatabaseProviderList.value) JSON.stringify(newList)
); );
} }
+10 -8
View File
@@ -458,7 +458,7 @@ export const useAdvancedSettingsStore = defineStore('AdvancedSettings', () => {
const data = JSON.parse(response.data); const data = JSON.parse(response.data);
if (AppDebug.debugWebRequests) { if (AppDebug.debugWebRequests) {
console.log('Models API response:', data); console.log(modelsURL, data, response);
} }
if (data.data && Array.isArray(data.data)) { if (data.data && Array.isArray(data.data)) {
@@ -685,10 +685,11 @@ export const useAdvancedSettingsStore = defineStore('AdvancedSettings', () => {
apiKey = youTubeApiKey.value; apiKey = youTubeApiKey.value;
} }
try { try {
const response = await webApiService.execute({ const url = `https://www.googleapis.com/youtube/v3/videos?id=${encodeURIComponent(
url: `https://www.googleapis.com/youtube/v3/videos?id=${encodeURIComponent(
videoId videoId
)}&part=snippet,contentDetails&key=${apiKey}`, )}&part=snippet,contentDetails&key=${apiKey}`;
const response = await webApiService.execute({
url,
method: 'GET', method: 'GET',
headers: { headers: {
Referer: 'https://vrcx.app' Referer: 'https://vrcx.app'
@@ -696,7 +697,7 @@ export const useAdvancedSettingsStore = defineStore('AdvancedSettings', () => {
}); });
const json = JSON.parse(response.data); const json = JSON.parse(response.data);
if (AppDebug.debugWebRequests) { if (AppDebug.debugWebRequests) {
console.log(json, response); console.log(url, json, response);
} }
if (response.status === 200) { if (response.status === 200) {
data = json; data = json;
@@ -725,8 +726,9 @@ export const useAdvancedSettingsStore = defineStore('AdvancedSettings', () => {
return null; return null;
} }
try { try {
const url = `https://translation.googleapis.com/language/translate/v2?key=${keyToUse}`;
const response = await webApiService.execute({ const response = await webApiService.execute({
url: `https://translation.googleapis.com/language/translate/v2?key=${keyToUse}`, url,
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
@@ -745,7 +747,7 @@ export const useAdvancedSettingsStore = defineStore('AdvancedSettings', () => {
} }
const data = JSON.parse(response.data); const data = JSON.parse(response.data);
if (AppDebug.debugWebRequests) { if (AppDebug.debugWebRequests) {
console.log(data, response); console.log(url, data, response);
} }
return data.data.translations[0].translatedText; return data.data.translations[0].translatedText;
} catch (err) { } catch (err) {
@@ -809,7 +811,7 @@ export const useAdvancedSettingsStore = defineStore('AdvancedSettings', () => {
const data = JSON.parse(response.data); const data = JSON.parse(response.data);
if (AppDebug.debugWebRequests) { if (AppDebug.debugWebRequests) {
console.log(data, response); console.log(endpoint, data, response);
} }
const translated = data?.choices?.[0]?.message?.content; const translated = data?.choices?.[0]?.message?.content;
+2 -2
View File
@@ -1208,7 +1208,7 @@ export const useUserStore = defineStore('User', () => {
D.avatars = array; D.avatars = array;
} }
function refreshUserDialogAvatars(fileId) { async function refreshUserDialogAvatars(fileId) {
const D = userDialog.value; const D = userDialog.value;
if (D.isAvatarsLoading) { if (D.isAvatarsLoading) {
return; return;
@@ -1233,7 +1233,7 @@ export const useUserStore = defineStore('User', () => {
} }
} }
const map = new Map(); const map = new Map();
processBulk({ await processBulk({
fn: avatarRequest.getAvatars, fn: avatarRequest.getAvatars,
N: -1, N: -1,
params, params,
+2 -2
View File
@@ -221,7 +221,7 @@ export const useVRCXUpdaterStore = defineStore('VRCXUpdater', () => {
} }
pendingVRCXUpdate.value = false; pendingVRCXUpdate.value = false;
if (AppDebug.debugWebRequests) { if (AppDebug.debugWebRequests) {
console.log(json, response); console.log(url, json, response);
} }
if (json === Object(json) && json.name && json.published_at) { if (json === Object(json) && json.name && json.published_at) {
changeLogDialog.value.buildName = json.name; changeLogDialog.value.buildName = json.name;
@@ -308,7 +308,7 @@ export const useVRCXUpdaterStore = defineStore('VRCXUpdater', () => {
return; return;
} }
if (AppDebug.debugWebRequests) { if (AppDebug.debugWebRequests) {
console.log(json, response); console.log(url, json, response);
} }
const releases = []; const releases = [];
if (typeof json !== 'object' || json.message) { if (typeof json !== 'object' || json.message) {
@@ -515,6 +515,7 @@
if (hasFetched.value && !status.needsRefetch) return; if (hasFetched.value && !status.needsRefetch) return;
isLoadingSnapshot.value = true; isLoadingSnapshot.value = true;
toast.dismiss(loadingToastId.value);
loadingToastId.value = toast.loading(t('view.charts.mutual_friend.status.loading_cache')); loadingToastId.value = toast.loading(t('view.charts.mutual_friend.status.loading_cache'));
try { try {
@@ -561,10 +562,7 @@
console.error('[MutualNetworkGraph] Failed to load cached mutual graph', err); console.error('[MutualNetworkGraph] Failed to load cached mutual graph', err);
} finally { } finally {
isLoadingSnapshot.value = false; isLoadingSnapshot.value = false;
if (loadingToastId.value !== null && loadingToastId.value !== undefined) {
toast.dismiss(loadingToastId.value); toast.dismiss(loadingToastId.value);
loadingToastId.value = null;
}
} }
} }