mirror of
https://github.com/MrUnknownDE/VRCX.git
synced 2026-04-24 09:13:50 +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 Logger logger = LogManager.GetCurrentClassLogger();
|
||||||
private static readonly MD5 _hasher = MD5.Create();
|
private static readonly MD5 _hasher = MD5.Create();
|
||||||
private static bool dialogOpen;
|
|
||||||
|
|
||||||
static AppApi()
|
static AppApi()
|
||||||
{
|
{
|
||||||
@@ -575,5 +574,16 @@ namespace VRCX
|
|||||||
|
|
||||||
return null;
|
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
|
public partial class AppApi
|
||||||
{
|
{
|
||||||
|
private static bool dialogOpen;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Adds metadata to a PNG screenshot file and optionally renames the file to include the specified world ID.
|
/// Adds metadata to a PNG screenshot file and optionally renames the file to include the specified world ID.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -6,89 +6,119 @@ using System.Linq;
|
|||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Threading.Tasks;
|
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;
|
cacheLocation = Path.Combine(Program.AppDataDirectory, "ImageCache");
|
||||||
private static readonly HttpClient httpClient;
|
var httpClientHandler = new HttpClientHandler();
|
||||||
private static readonly List<string> _imageHosts =
|
if (WebApi.ProxySet)
|
||||||
[
|
httpClientHandler.Proxy = WebApi.Proxy;
|
||||||
"api.vrchat.cloud",
|
|
||||||
"files.vrchat.cloud",
|
|
||||||
"d348imysud55la.cloudfront.net",
|
|
||||||
"assets.vrchat.com"
|
|
||||||
];
|
|
||||||
|
|
||||||
static ImageCache()
|
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))
|
||||||
{
|
{
|
||||||
cacheLocation = Path.Combine(Program.AppDataDirectory, "ImageCache");
|
Directory.SetLastWriteTimeUtc(directoryLocation, DateTime.UtcNow);
|
||||||
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();
|
|
||||||
|
|
||||||
return fileLocation;
|
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);
|
CookieCollection cookies = WebApi.Instance._cookieContainer.GetCookies(new Uri("https://api.vrchat.cloud"));
|
||||||
var folders = dirInfo.GetDirectories().OrderByDescending(p => p.LastWriteTime).Skip(1000);
|
foreach (Cookie cookie in cookies)
|
||||||
foreach (var folder in folders)
|
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) => {
|
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 });
|
API.getUser({ userId: ref1.userId });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -8149,6 +8153,10 @@ speechSynthesis.getVoices();
|
|||||||
'VRCX_StartAtWindowsStartup',
|
'VRCX_StartAtWindowsStartup',
|
||||||
this.isStartAtWindowsStartup
|
this.isStartAtWindowsStartup
|
||||||
);
|
);
|
||||||
|
await configRepository.setBool(
|
||||||
|
'VRCX_saveInstancePrints',
|
||||||
|
this.saveInstancePrints
|
||||||
|
);
|
||||||
VRCXStorage.Set(
|
VRCXStorage.Set(
|
||||||
'VRCX_StartAsMinimizedState',
|
'VRCX_StartAsMinimizedState',
|
||||||
this.isStartAsMinimizedState.toString()
|
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
|
// #endregion
|
||||||
// #region | Emoji
|
// #region | Emoji
|
||||||
|
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ export default class extends baseClass {
|
|||||||
if (this.gameLogDisabled) {
|
if (this.gameLogDisabled) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var userId = gameLog.userId;
|
var userId = String(gameLog.userId || '');
|
||||||
if (!userId && gameLog.displayName) {
|
if (!userId && gameLog.displayName) {
|
||||||
for (var ref of API.cachedUsers.values()) {
|
for (var ref of API.cachedUsers.values()) {
|
||||||
if (ref.displayName === gameLog.displayName) {
|
if (ref.displayName === gameLog.displayName) {
|
||||||
@@ -257,6 +257,25 @@ export default class extends baseClass {
|
|||||||
// if (!userId) {
|
// if (!userId) {
|
||||||
// break;
|
// 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;
|
break;
|
||||||
case 'avatar-change':
|
case 'avatar-change':
|
||||||
var ref = this.lastLocation.playerList.get(userId);
|
var ref = this.lastLocation.playerList.get(userId);
|
||||||
|
|||||||
@@ -598,6 +598,9 @@ export default class extends baseClass {
|
|||||||
},
|
},
|
||||||
key() {
|
key() {
|
||||||
this.parse();
|
this.parse();
|
||||||
|
},
|
||||||
|
userid() {
|
||||||
|
this.parse();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
|
|||||||
@@ -514,19 +514,34 @@ export default class extends baseClass {
|
|||||||
var contentType = content.contentType;
|
var contentType = content.contentType;
|
||||||
console.log('content-refresh', content);
|
console.log('content-refresh', content);
|
||||||
if (contentType === 'icon') {
|
if (contentType === 'icon') {
|
||||||
if ($app.galleryDialogVisible) {
|
if (
|
||||||
|
$app.galleryDialogVisible &&
|
||||||
|
!$app.galleryDialogIconsLoading
|
||||||
|
) {
|
||||||
$app.refreshVRCPlusIconsTable();
|
$app.refreshVRCPlusIconsTable();
|
||||||
}
|
}
|
||||||
} else if (contentType === 'gallery') {
|
} else if (contentType === 'gallery') {
|
||||||
if ($app.galleryDialogVisible) {
|
if (
|
||||||
|
$app.galleryDialogVisible &&
|
||||||
|
!$app.galleryDialogGalleryLoading
|
||||||
|
) {
|
||||||
$app.refreshGalleryTable();
|
$app.refreshGalleryTable();
|
||||||
}
|
}
|
||||||
} else if (contentType === 'emoji') {
|
} else if (contentType === 'emoji') {
|
||||||
if ($app.galleryDialogVisible) {
|
if (
|
||||||
|
$app.galleryDialogVisible &&
|
||||||
|
!$app.galleryDialogEmojisLoading
|
||||||
|
) {
|
||||||
$app.refreshEmojiTable();
|
$app.refreshEmojiTable();
|
||||||
}
|
}
|
||||||
} else if (contentType === 'print') {
|
} else if (
|
||||||
if ($app.galleryDialogVisible) {
|
contentType === 'print' ||
|
||||||
|
contentType === 'prints'
|
||||||
|
) {
|
||||||
|
if (
|
||||||
|
$app.galleryDialogVisible &&
|
||||||
|
!$app.galleryDialogPrintsLoading
|
||||||
|
) {
|
||||||
$app.refreshPrintTable();
|
$app.refreshPrintTable();
|
||||||
}
|
}
|
||||||
} else if (contentType === 'avatar') {
|
} else if (contentType === 'avatar') {
|
||||||
|
|||||||
@@ -433,6 +433,10 @@
|
|||||||
"header": "Local World Persistence",
|
"header": "Local World Persistence",
|
||||||
"description": "Localhost webserver (requires restart)"
|
"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": {
|
"remote_database": {
|
||||||
"header": "Remote Avatar Database",
|
"header": "Remote Avatar Database",
|
||||||
"enable": "Enable",
|
"enable": "Enable",
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ mixin screenshotMetadata()
|
|||||||
br
|
br
|
||||||
location(v-if="screenshotMetadataDialog.metadata.world" :location="screenshotMetadataDialog.metadata.world.instanceId" :hint="screenshotMetadataDialog.metadata.world.name")
|
location(v-if="screenshotMetadataDialog.metadata.world" :location="screenshotMetadataDialog.metadata.world.instanceId" :hint="screenshotMetadataDialog.metadata.world.name")
|
||||||
br
|
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
|
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(ref="screenshotMetadataCarousel" :interval="0" initial-index="1" indicator-position="none" arrow="always" height="600px" style="margin-top:10px" @change="screenshotMetadataCarouselChange")
|
||||||
el-carousel-item
|
el-carousel-item
|
||||||
|
|||||||
@@ -475,6 +475,10 @@ mixin settingsTab()
|
|||||||
div.options-container-item
|
div.options-container-item
|
||||||
span.name(style="min-width:300px") {{ $t('view.settings.advanced.advanced.local_world_persistence.description') }}
|
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")
|
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
|
//- Advanced | Remote Avatar Database
|
||||||
div.options-container
|
div.options-container
|
||||||
span.header {{ $t('view.settings.advanced.advanced.remote_database.header') }}
|
span.header {{ $t('view.settings.advanced.advanced.remote_database.header') }}
|
||||||
|
|||||||
Reference in New Issue
Block a user