mirror of
https://github.com/vrcx-team/VRCX.git
synced 2026-04-06 00:32:02 +02:00
Save Instance Prints To File
This commit is contained in:
@@ -30,7 +30,6 @@ namespace VRCX
|
||||
|
||||
private static readonly Logger logger = LogManager.GetCurrentClassLogger();
|
||||
private static readonly MD5 _hasher = MD5.Create();
|
||||
private static bool dialogOpen;
|
||||
|
||||
static AppApi()
|
||||
{
|
||||
@@ -575,5 +574,16 @@ namespace VRCX
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public async Task<bool> SavePrintToFile(string url, string fileName)
|
||||
{
|
||||
var directory = Path.Combine(GetVRChatPhotosLocation(), "Prints");
|
||||
Directory.CreateDirectory(directory);
|
||||
var path = Path.Combine(directory, fileName);
|
||||
if (File.Exists(path))
|
||||
return false;
|
||||
|
||||
return await ImageCache.SaveImageToFile(url, path);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,8 @@ namespace VRCX
|
||||
{
|
||||
public partial class AppApi
|
||||
{
|
||||
private static bool dialogOpen;
|
||||
|
||||
/// <summary>
|
||||
/// Adds metadata to a PNG screenshot file and optionally renames the file to include the specified world ID.
|
||||
/// </summary>
|
||||
|
||||
@@ -6,89 +6,119 @@ using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace VRCX
|
||||
namespace VRCX;
|
||||
|
||||
internal static class ImageCache
|
||||
{
|
||||
internal static class ImageCache
|
||||
private static readonly string cacheLocation;
|
||||
private static readonly HttpClient httpClient;
|
||||
private static readonly List<string> _imageHosts =
|
||||
[
|
||||
"api.vrchat.cloud",
|
||||
"files.vrchat.cloud",
|
||||
"d348imysud55la.cloudfront.net",
|
||||
"assets.vrchat.com"
|
||||
];
|
||||
|
||||
static ImageCache()
|
||||
{
|
||||
private static readonly string cacheLocation;
|
||||
private static readonly HttpClient httpClient;
|
||||
private static readonly List<string> _imageHosts =
|
||||
[
|
||||
"api.vrchat.cloud",
|
||||
"files.vrchat.cloud",
|
||||
"d348imysud55la.cloudfront.net",
|
||||
"assets.vrchat.com"
|
||||
];
|
||||
cacheLocation = Path.Combine(Program.AppDataDirectory, "ImageCache");
|
||||
var httpClientHandler = new HttpClientHandler();
|
||||
if (WebApi.ProxySet)
|
||||
httpClientHandler.Proxy = WebApi.Proxy;
|
||||
|
||||
httpClient = new HttpClient(httpClientHandler);
|
||||
}
|
||||
|
||||
static ImageCache()
|
||||
public static async Task<string> GetImage(string url, string fileId, string version)
|
||||
{
|
||||
var directoryLocation = Path.Combine(cacheLocation, fileId);
|
||||
var fileLocation = Path.Combine(directoryLocation, $"{version}.png");
|
||||
|
||||
if (File.Exists(fileLocation))
|
||||
{
|
||||
cacheLocation = Path.Combine(Program.AppDataDirectory, "ImageCache");
|
||||
var httpClientHandler = new HttpClientHandler();
|
||||
if (WebApi.ProxySet)
|
||||
httpClientHandler.Proxy = WebApi.Proxy;
|
||||
|
||||
httpClient = new HttpClient(httpClientHandler);
|
||||
}
|
||||
|
||||
public static async Task<string> GetImage(string url, string fileId, string version)
|
||||
{
|
||||
var directoryLocation = Path.Combine(cacheLocation, fileId);
|
||||
var fileLocation = Path.Combine(directoryLocation, $"{version}.png");
|
||||
|
||||
if (File.Exists(fileLocation))
|
||||
{
|
||||
Directory.SetLastWriteTimeUtc(directoryLocation, DateTime.UtcNow);
|
||||
return fileLocation;
|
||||
}
|
||||
|
||||
if (Directory.Exists(directoryLocation))
|
||||
Directory.Delete(directoryLocation, true);
|
||||
Directory.CreateDirectory(directoryLocation);
|
||||
|
||||
var uri = new Uri(url);
|
||||
if (!_imageHosts.Contains(uri.Host))
|
||||
throw new ArgumentException("Invalid image host", url);
|
||||
|
||||
var cookieString = string.Empty;
|
||||
if (WebApi.Instance != null && WebApi.Instance._cookieContainer != null)
|
||||
{
|
||||
CookieCollection cookies = WebApi.Instance._cookieContainer.GetCookies(new Uri("https://api.vrchat.cloud"));
|
||||
foreach (Cookie cookie in cookies)
|
||||
cookieString += $"{cookie.Name}={cookie.Value};";
|
||||
}
|
||||
|
||||
var request = new HttpRequestMessage(HttpMethod.Get, url)
|
||||
{
|
||||
Headers =
|
||||
{
|
||||
{ "Cookie", cookieString },
|
||||
{ "User-Agent", Program.Version }
|
||||
}
|
||||
};
|
||||
using (var response = await httpClient.SendAsync(request))
|
||||
{
|
||||
response.EnsureSuccessStatusCode();
|
||||
await using (var fileStream = new FileStream(fileLocation, FileMode.Create, FileAccess.Write, FileShare.None))
|
||||
{
|
||||
await response.Content.CopyToAsync(fileStream);
|
||||
}
|
||||
}
|
||||
|
||||
var cacheSize = Directory.GetDirectories(cacheLocation).Length;
|
||||
if (cacheSize > 1100)
|
||||
CleanImageCache();
|
||||
|
||||
Directory.SetLastWriteTimeUtc(directoryLocation, DateTime.UtcNow);
|
||||
return fileLocation;
|
||||
}
|
||||
|
||||
private static void CleanImageCache()
|
||||
if (Directory.Exists(directoryLocation))
|
||||
Directory.Delete(directoryLocation, true);
|
||||
Directory.CreateDirectory(directoryLocation);
|
||||
|
||||
var uri = new Uri(url);
|
||||
if (!_imageHosts.Contains(uri.Host))
|
||||
throw new ArgumentException("Invalid image host", url);
|
||||
|
||||
var cookieString = string.Empty;
|
||||
if (WebApi.Instance != null && WebApi.Instance._cookieContainer != null)
|
||||
{
|
||||
var dirInfo = new DirectoryInfo(cacheLocation);
|
||||
var folders = dirInfo.GetDirectories().OrderByDescending(p => p.LastWriteTime).Skip(1000);
|
||||
foreach (var folder in folders)
|
||||
CookieCollection cookies = WebApi.Instance._cookieContainer.GetCookies(new Uri("https://api.vrchat.cloud"));
|
||||
foreach (Cookie cookie in cookies)
|
||||
cookieString += $"{cookie.Name}={cookie.Value};";
|
||||
}
|
||||
|
||||
var request = new HttpRequestMessage(HttpMethod.Get, url)
|
||||
{
|
||||
Headers =
|
||||
{
|
||||
folder.Delete(true);
|
||||
{ "Cookie", cookieString },
|
||||
{ "User-Agent", Program.Version }
|
||||
}
|
||||
};
|
||||
using (var response = await httpClient.SendAsync(request))
|
||||
{
|
||||
response.EnsureSuccessStatusCode();
|
||||
await using (var fileStream = new FileStream(fileLocation, FileMode.Create, FileAccess.Write, FileShare.None))
|
||||
{
|
||||
await response.Content.CopyToAsync(fileStream);
|
||||
}
|
||||
}
|
||||
|
||||
var cacheSize = Directory.GetDirectories(cacheLocation).Length;
|
||||
if (cacheSize > 1100)
|
||||
CleanImageCache();
|
||||
|
||||
return fileLocation;
|
||||
}
|
||||
|
||||
private static void CleanImageCache()
|
||||
{
|
||||
var dirInfo = new DirectoryInfo(cacheLocation);
|
||||
var folders = dirInfo.GetDirectories().OrderByDescending(p => p.LastWriteTime).Skip(1000);
|
||||
foreach (var folder in folders)
|
||||
{
|
||||
folder.Delete(true);
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task<bool> SaveImageToFile(string url, string path)
|
||||
{
|
||||
var uri = new Uri(url);
|
||||
if (!_imageHosts.Contains(uri.Host))
|
||||
throw new ArgumentException("Invalid image host", url);
|
||||
|
||||
var cookieString = string.Empty;
|
||||
if (WebApi.Instance != null && WebApi.Instance._cookieContainer != null)
|
||||
{
|
||||
var cookies = WebApi.Instance._cookieContainer.GetCookies(new Uri("https://api.vrchat.cloud"));
|
||||
foreach (Cookie cookie in cookies)
|
||||
cookieString += $"{cookie.Name}={cookie.Value};";
|
||||
}
|
||||
|
||||
var request = new HttpRequestMessage(HttpMethod.Get, url)
|
||||
{
|
||||
Headers =
|
||||
{
|
||||
{ "Cookie", cookieString },
|
||||
{ "User-Agent", Program.Version }
|
||||
}
|
||||
};
|
||||
using var response = await httpClient.SendAsync(request);
|
||||
if (!response.IsSuccessStatusCode)
|
||||
return false;
|
||||
|
||||
await using var fileStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None);
|
||||
await response.Content.CopyToAsync(fileStream);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -5447,7 +5447,11 @@ speechSynthesis.getVoices();
|
||||
}
|
||||
}
|
||||
this.lastLocation.playerList.forEach((ref1) => {
|
||||
if (ref1.userId && !API.cachedUsers.has(ref1.userId)) {
|
||||
if (
|
||||
ref1.userId &&
|
||||
typeof ref1.userId === 'string' &&
|
||||
!API.cachedUsers.has(ref1.userId)
|
||||
) {
|
||||
API.getUser({ userId: ref1.userId });
|
||||
}
|
||||
});
|
||||
@@ -8149,6 +8153,10 @@ speechSynthesis.getVoices();
|
||||
'VRCX_StartAtWindowsStartup',
|
||||
this.isStartAtWindowsStartup
|
||||
);
|
||||
await configRepository.setBool(
|
||||
'VRCX_saveInstancePrints',
|
||||
this.saveInstancePrints
|
||||
);
|
||||
VRCXStorage.Set(
|
||||
'VRCX_StartAsMinimizedState',
|
||||
this.isStartAsMinimizedState.toString()
|
||||
@@ -17522,6 +17530,52 @@ speechSynthesis.getVoices();
|
||||
}
|
||||
});
|
||||
|
||||
API.getPrint = function (params) {
|
||||
return this.call(`prints/${params.printId}`, {
|
||||
method: 'GET'
|
||||
}).then((json) => {
|
||||
var args = {
|
||||
json,
|
||||
params
|
||||
};
|
||||
this.$emit('PRINT', args);
|
||||
return args;
|
||||
});
|
||||
};
|
||||
|
||||
API.editPrint = function (params) {
|
||||
return this.call(`prints/${params.printId}`, {
|
||||
method: 'POST',
|
||||
params
|
||||
}).then((json) => {
|
||||
var args = {
|
||||
json,
|
||||
params
|
||||
};
|
||||
this.$emit('PRINT:EDIT', args);
|
||||
return args;
|
||||
});
|
||||
};
|
||||
|
||||
$app.data.saveInstancePrints = await configRepository.getBool(
|
||||
'VRCX_saveInstancePrints',
|
||||
false
|
||||
);
|
||||
|
||||
$app.methods.trySavePrintToFile = async function (printId) {
|
||||
var print = await API.getPrint({ printId });
|
||||
var imageUrl = print.json?.files?.image;
|
||||
if (!imageUrl) {
|
||||
console.error('Print image URL is missing', print);
|
||||
return;
|
||||
}
|
||||
var fileName = `${printId}.png`;
|
||||
var status = await AppApi.SavePrintToFile(imageUrl, fileName);
|
||||
if (status) {
|
||||
console.log(`Print saved to file: ${fileName}`);
|
||||
}
|
||||
};
|
||||
|
||||
// #endregion
|
||||
// #region | Emoji
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ export default class extends baseClass {
|
||||
if (this.gameLogDisabled) {
|
||||
return;
|
||||
}
|
||||
var userId = gameLog.userId;
|
||||
var userId = String(gameLog.userId || '');
|
||||
if (!userId && gameLog.displayName) {
|
||||
for (var ref of API.cachedUsers.values()) {
|
||||
if (ref.displayName === gameLog.displayName) {
|
||||
@@ -257,6 +257,25 @@ export default class extends baseClass {
|
||||
// if (!userId) {
|
||||
// break;
|
||||
// }
|
||||
|
||||
if (!$app.saveInstancePrints) {
|
||||
break;
|
||||
}
|
||||
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 (printId && printId.length === 41) {
|
||||
$app.trySavePrintToFile(printId);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
break;
|
||||
case 'avatar-change':
|
||||
var ref = this.lastLocation.playerList.get(userId);
|
||||
|
||||
@@ -598,6 +598,9 @@ export default class extends baseClass {
|
||||
},
|
||||
key() {
|
||||
this.parse();
|
||||
},
|
||||
userid() {
|
||||
this.parse();
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
|
||||
@@ -514,19 +514,34 @@ export default class extends baseClass {
|
||||
var contentType = content.contentType;
|
||||
console.log('content-refresh', content);
|
||||
if (contentType === 'icon') {
|
||||
if ($app.galleryDialogVisible) {
|
||||
if (
|
||||
$app.galleryDialogVisible &&
|
||||
!$app.galleryDialogIconsLoading
|
||||
) {
|
||||
$app.refreshVRCPlusIconsTable();
|
||||
}
|
||||
} else if (contentType === 'gallery') {
|
||||
if ($app.galleryDialogVisible) {
|
||||
if (
|
||||
$app.galleryDialogVisible &&
|
||||
!$app.galleryDialogGalleryLoading
|
||||
) {
|
||||
$app.refreshGalleryTable();
|
||||
}
|
||||
} else if (contentType === 'emoji') {
|
||||
if ($app.galleryDialogVisible) {
|
||||
if (
|
||||
$app.galleryDialogVisible &&
|
||||
!$app.galleryDialogEmojisLoading
|
||||
) {
|
||||
$app.refreshEmojiTable();
|
||||
}
|
||||
} else if (contentType === 'print') {
|
||||
if ($app.galleryDialogVisible) {
|
||||
} else if (
|
||||
contentType === 'print' ||
|
||||
contentType === 'prints'
|
||||
) {
|
||||
if (
|
||||
$app.galleryDialogVisible &&
|
||||
!$app.galleryDialogPrintsLoading
|
||||
) {
|
||||
$app.refreshPrintTable();
|
||||
}
|
||||
} else if (contentType === 'avatar') {
|
||||
|
||||
@@ -433,6 +433,10 @@
|
||||
"header": "Local World Persistence",
|
||||
"description": "Localhost webserver (requires restart)"
|
||||
},
|
||||
"save_instance_prints_to_file": {
|
||||
"header": "Save Instance Prints To File",
|
||||
"description": "Save spawned prints to your VRChat Pictures folder"
|
||||
},
|
||||
"remote_database": {
|
||||
"header": "Remote Avatar Database",
|
||||
"enable": "Enable",
|
||||
|
||||
@@ -32,7 +32,7 @@ mixin screenshotMetadata()
|
||||
br
|
||||
location(v-if="screenshotMetadataDialog.metadata.world" :location="screenshotMetadataDialog.metadata.world.instanceId" :hint="screenshotMetadataDialog.metadata.world.name")
|
||||
br
|
||||
display-name(v-if="screenshotMetadataDialog.metadata.author" :userid="screenshotMetadataDialog.metadata.author.id" :hind="screenshotMetadataDialog.metadata.author.displayName" style="color:#909399;font-family:monospace")
|
||||
display-name(v-if="screenshotMetadataDialog.metadata.author" :userid="screenshotMetadataDialog.metadata.author.id" :hint="screenshotMetadataDialog.metadata.author.displayName" style="color:#909399;font-family:monospace")
|
||||
br
|
||||
el-carousel(ref="screenshotMetadataCarousel" :interval="0" initial-index="1" indicator-position="none" arrow="always" height="600px" style="margin-top:10px" @change="screenshotMetadataCarouselChange")
|
||||
el-carousel-item
|
||||
|
||||
@@ -475,6 +475,10 @@ mixin settingsTab()
|
||||
div.options-container-item
|
||||
span.name(style="min-width:300px") {{ $t('view.settings.advanced.advanced.local_world_persistence.description') }}
|
||||
el-switch(v-model="disableWorldDatabase" :active-value="false" :inactive-value="true" @change="saveVRCXWindowOption")
|
||||
span.sub-header {{ $t('view.settings.advanced.advanced.save_instance_prints_to_file.header') }}
|
||||
div.options-container-item
|
||||
span.name(style="min-width:300px") {{ $t('view.settings.advanced.advanced.save_instance_prints_to_file.description') }}
|
||||
el-switch(v-model="saveInstancePrints" @change="saveVRCXWindowOption")
|
||||
//- Advanced | Remote Avatar Database
|
||||
div.options-container
|
||||
span.header {{ $t('view.settings.advanced.advanced.remote_database.header') }}
|
||||
|
||||
Reference in New Issue
Block a user