diff --git a/Dotnet/AppApi/Common/ImageSaving.cs b/Dotnet/AppApi/Common/ImageSaving.cs index 26d54e3b..198277ee 100644 --- a/Dotnet/AppApi/Common/ImageSaving.cs +++ b/Dotnet/AppApi/Common/ImageSaving.cs @@ -257,5 +257,26 @@ namespace VRCX return filePath; } + + public async Task SaveEmojiToFile(string url, string ugcFolderPath, string monthFolder, string fileName) + { + var folder = Path.Join(GetUGCPhotoLocation(ugcFolderPath), "Emoji", MakeValidFileName(monthFolder)); + Directory.CreateDirectory(folder); + var filePath = Path.Join(folder, MakeValidFileName(fileName)); + if (File.Exists(filePath)) + return null; + + try + { + await ImageCache.SaveImageToFile(url, filePath); + } + catch (Exception ex) + { + logger.Error(ex, "Failed to save print to file"); + return null; + } + + return filePath; + } } } \ No newline at end of file diff --git a/src/app.js b/src/app.js index 94b8c7ed..b9a2d876 100644 --- a/src/app.js +++ b/src/app.js @@ -32,6 +32,7 @@ import { groupRequest, imageRequest, instanceRequest, + inventoryRequest, inviteMessagesRequest, miscRequest, notificationRequest, @@ -119,6 +120,7 @@ import { userDialogGroupSortingOptions } from './composables/user/constants/user import { getPrintFileName, getPrintLocalDate, + getEmojiFileName, languageClass } from './composables/user/utils'; import InteropApi from './ipc-electron/interopApi.js'; @@ -6768,6 +6770,9 @@ console.log(`isLinux: ${LINUX}`); case 'VRCX_saveInstanceStickers': this.saveInstanceStickers = !this.saveInstanceStickers; break; + case 'VRCX_saveInstanceEmoji': + this.saveInstanceEmoji = !this.saveInstanceEmoji; + break; case 'VRCX_StartAsMinimizedState': this.isStartAsMinimizedState = !this.isStartAsMinimizedState; break; @@ -6808,6 +6813,11 @@ console.log(`isLinux: ${LINUX}`); this.saveInstanceStickers ); + await configRepository.setBool( + 'VRCX_saveInstanceEmoji', + this.saveInstanceEmoji + ); + VRCXStorage.Set( 'VRCX_StartAsMinimizedState', this.isStartAsMinimizedState.toString() @@ -10954,6 +10964,75 @@ console.log(`isLinux: ${LINUX}`); } }; + // #endregion + // #region | Emoji + + $app.data.instanceInventoryCache = []; + $app.data.instanceInventoryQueue = []; + $app.data.instanceInventoryQueueWorker = null; + + $app.methods.queueCheckInstanceInventory = function (inventoryId) { + if (this.instanceInventoryCache.includes(inventoryId)) { + return; + } + this.instanceInventoryCache.push(inventoryId); + if (this.instanceInventoryCache.length > 100) { + this.instanceInventoryCache.shift(); + } + + this.instanceInventoryQueue.push(inventoryId); + + if (!this.instanceInventoryQueueWorker) { + this.instanceInventoryQueueWorker = workerTimers.setInterval(() => { + let inventoryId = this.instanceInventoryQueue.shift(); + if (inventoryId) { + this.trySaveEmojiToFile(inventoryId); + } + }, 2_500); + } + }; + + $app.methods.trySaveEmojiToFile = async function (inventoryId) { + const args = await inventoryRequest.getInventoryItem({ + inventoryId + }); + + if (args.json.itemType !== 'emoji') { + // Not an emoji, skip + return; + } + + const userArgs = await userRequest.getCachedUser({ + userId: args.json.holderId + }); + const displayName = userArgs.json?.displayName ?? ''; + + let emoji = args.json.metadata; + emoji.name = `${displayName}_${inventoryId}`; + + const emojiFileName = getEmojiFileName(emoji); + const imageUrl = args.json.metadata?.imageUrl ?? args.json.imageUrl; + const createdAt = args.json.created_at; + const monthFolder = createdAt.slice(0, 7); + + const filePath = await AppApi.SaveEmojiToFile( + imageUrl, + this.ugcFolderPath, + monthFolder, + emojiFileName + ); + if (filePath) { + console.log( + `Emoji saved to file: ${monthFolder}\\${emojiFileName}` + ); + } + + if (this.instanceInventoryQueue.length === 0) { + workerTimers.clearInterval(this.instanceInventoryQueueWorker); + this.instanceInventoryQueueWorker = null; + } + }; + // #endregion // #region | Prints $app.methods.cropPrintsChanged = function () { @@ -11038,6 +11117,11 @@ console.log(`isLinux: ${LINUX}`); false ); + $app.data.saveInstanceEmoji = await configRepository.getBool( + 'VRCX_saveInstanceEmoji', + false + ); + $app.data.printCache = []; $app.data.printQueue = []; $app.data.printQueueWorker = null; diff --git a/src/classes/gameLog.js b/src/classes/gameLog.js index c226805f..0de97b17 100644 --- a/src/classes/gameLog.js +++ b/src/classes/gameLog.js @@ -260,6 +260,10 @@ export default class extends baseClass { this.processScreenshot(gameLog.screenshotPath); break; case 'api-request': + if ($app.debugWebRequests) { + console.log('API Request:', gameLog.url); + } + // var userId = ''; // try { // var url = new URL(gameLog.url); @@ -277,23 +281,43 @@ export default class extends baseClass { // break; // } - if (!$app.saveInstancePrints) { - break; + if ($app.saveInstanceEmoji) { + try { + // https://api.vrchat.cloud/api/1/inventory/spawn?id=inv_75781d65-92fe-4a80-a1ff-27ee6e843b08 + const url = new URL(gameLog.url); + if ( + url.pathname.substring(0, 22) === + '/api/1/inventory/spawn' + ) { + const inventoryId = url.searchParams.get('id'); + if (inventoryId && inventoryId.length === 40) { + $app.queueCheckInstanceInventory( + inventoryId + ); + } + } + } catch (err) { + console.error(err); + } } - try { - var printId = ''; - var url = new URL(gameLog.url); - if ( - url.pathname.substring(0, 14) === '/api/1/prints/' - ) { - var pathArray = url.pathname.split('/'); - printId = pathArray[4]; + + if ($app.saveInstancePrints) { + try { + let printId = ''; + const url1 = new URL(gameLog.url); + if ( + url1.pathname.substring(0, 14) === + '/api/1/prints/' + ) { + const pathArray = url1.pathname.split('/'); + printId = pathArray[4]; + } + if (printId && printId.length === 41) { + $app.queueSavePrintToFile(printId); + } + } catch (err) { + console.error(err); } - if (printId && printId.length === 41) { - $app.queueSavePrintToFile(printId); - } - } catch (err) { - console.error(err); } break; case 'avatar-change': diff --git a/src/components/dialogs/GalleryDialog.vue b/src/components/dialogs/GalleryDialog.vue index 6833c8c1..aa3dec7d 100644 --- a/src/components/dialogs/GalleryDialog.vue +++ b/src/components/dialogs/GalleryDialog.vue @@ -162,7 +162,7 @@ {{ t('dialog.gallery_icons.recommended_image_size') }}: 1024x1024px (1:1)

-
+
{{ t('dialog.gallery_icons.refresh') }} @@ -237,7 +237,7 @@ @click=" showFullscreenImageDialog( image.versions[image.versions.length - 1].file.url, - getEmojiFileName(image) + getEmojiName(image) ) ">