mirror of
https://github.com/MrUnknownDE/VRCX.git
synced 2026-05-07 14:56:06 +02:00
Avatar image upload signature
This commit is contained in:
@@ -16,6 +16,7 @@ using System.Security.Cryptography;
|
|||||||
using System.Net;
|
using System.Net;
|
||||||
using Windows.UI.Notifications;
|
using Windows.UI.Notifications;
|
||||||
using Windows.Data.Xml.Dom;
|
using Windows.Data.Xml.Dom;
|
||||||
|
using librsync.net;
|
||||||
|
|
||||||
namespace VRCX
|
namespace VRCX
|
||||||
{
|
{
|
||||||
@@ -35,6 +36,22 @@ namespace VRCX
|
|||||||
return System.Convert.ToBase64String(md5);
|
return System.Convert.ToBase64String(md5);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public string SignFile(string Blob)
|
||||||
|
{
|
||||||
|
byte[] fileData = Convert.FromBase64CharArray(Blob.ToCharArray(), 0, Blob.Length);
|
||||||
|
Stream sig = Librsync.ComputeSignature(new MemoryStream(fileData));
|
||||||
|
var memoryStream = new MemoryStream();
|
||||||
|
sig.CopyTo(memoryStream);
|
||||||
|
byte[] sigBytes = memoryStream.ToArray();
|
||||||
|
return System.Convert.ToBase64String(sigBytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string FileLength(string Blob)
|
||||||
|
{
|
||||||
|
byte[] fileData = Convert.FromBase64CharArray(Blob.ToCharArray(), 0, Blob.Length);
|
||||||
|
return fileData.Length.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
public void ShowDevTools()
|
public void ShowDevTools()
|
||||||
{
|
{
|
||||||
MainForm.Instance.Browser.ShowDevTools();
|
MainForm.Instance.Browser.ShowDevTools();
|
||||||
|
|||||||
@@ -77,6 +77,10 @@
|
|||||||
<StartupObject />
|
<StartupObject />
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<Reference Include="librsync.net, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||||
|
<SpecificVersion>False</SpecificVersion>
|
||||||
|
<HintPath>librsync.net\librsync.net.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
<Reference Include="System" />
|
<Reference Include="System" />
|
||||||
<Reference Include="System.Core" />
|
<Reference Include="System.Core" />
|
||||||
<Reference Include="System.Management" />
|
<Reference Include="System.Management" />
|
||||||
|
|||||||
@@ -153,11 +153,11 @@ namespace VRCX
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options.TryGetValue("uploadImagePUT", out object uploadImagePUT) == true)
|
if (options.TryGetValue("uploadFilePUT", out object uploadImagePUT) == true)
|
||||||
{
|
{
|
||||||
request.Method = "PUT";
|
request.Method = "PUT";
|
||||||
request.ContentType = "image/png";
|
request.ContentType = options["fileMIME"] as string;
|
||||||
var imageData = options["imageData"] as string;
|
var imageData = options["fileData"] as string;
|
||||||
byte[] sentData = Convert.FromBase64CharArray(imageData.ToCharArray(), 0, imageData.Length);
|
byte[] sentData = Convert.FromBase64CharArray(imageData.ToCharArray(), 0, imageData.Length);
|
||||||
request.ContentLength = sentData.Length;
|
request.ContentLength = sentData.Length;
|
||||||
using (System.IO.Stream sendStream = request.GetRequestStream())
|
using (System.IO.Stream sendStream = request.GetRequestStream())
|
||||||
|
|||||||
+111
-36
@@ -362,7 +362,7 @@ speechSynthesis.getVoices();
|
|||||||
if (typeof req !== 'undefined') {
|
if (typeof req !== 'undefined') {
|
||||||
return req;
|
return req;
|
||||||
}
|
}
|
||||||
} else if (init.uploadImage || init.uploadImagePUT) {
|
} else if (init.uploadImage || init.uploadFilePUT) {
|
||||||
} else {
|
} else {
|
||||||
init.headers = {
|
init.headers = {
|
||||||
'Content-Type': 'application/json;charset=utf-8',
|
'Content-Type': 'application/json;charset=utf-8',
|
||||||
@@ -9965,11 +9965,21 @@ speechSynthesis.getVoices();
|
|||||||
return a[field].toLowerCase().localeCompare(b[field].toLowerCase());
|
return a[field].toLowerCase().localeCompare(b[field].toLowerCase());
|
||||||
};
|
};
|
||||||
|
|
||||||
$app.methods.md5 = async function (file) {
|
$app.methods.genMd5 = async function (file) {
|
||||||
var response = await AppApi.MD5File(file);
|
var response = await AppApi.MD5File(file);
|
||||||
return response;
|
return response;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
$app.methods.genSig = async function (file) {
|
||||||
|
var response = await AppApi.SignFile(file);
|
||||||
|
return response;
|
||||||
|
};
|
||||||
|
|
||||||
|
$app.methods.genLength = async function (file) {
|
||||||
|
var response = await AppApi.FileLength(file);
|
||||||
|
return response;
|
||||||
|
};
|
||||||
|
|
||||||
$app.methods.onFileChangeAvatarImage = function (e) {
|
$app.methods.onFileChangeAvatarImage = function (e) {
|
||||||
var clearFile = function () {
|
var clearFile = function () {
|
||||||
if (document.querySelector('#AvatarImageUploadButton')) {
|
if (document.querySelector('#AvatarImageUploadButton')) {
|
||||||
@@ -9989,9 +9999,9 @@ speechSynthesis.getVoices();
|
|||||||
clearFile();
|
clearFile();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!files[0].type.match(/image.*/)) {
|
if (!files[0].type.match(/image.png/)) {
|
||||||
$app.$message({
|
$app.$message({
|
||||||
message: 'File isn\'t an image',
|
message: 'File isn\'t a png',
|
||||||
type: 'error'
|
type: 'error'
|
||||||
});
|
});
|
||||||
clearFile();
|
clearFile();
|
||||||
@@ -10000,27 +10010,30 @@ speechSynthesis.getVoices();
|
|||||||
this.avatarDialog.loading = true;
|
this.avatarDialog.loading = true;
|
||||||
var r = new FileReader();
|
var r = new FileReader();
|
||||||
r.onload = async function (file) {
|
r.onload = async function (file) {
|
||||||
var base64Body = btoa(r.result);
|
var base64File = btoa(r.result);
|
||||||
var fileSize = file.total;
|
var fileMd5 = await $app.genMd5(base64File);
|
||||||
var md5 = await $app.md5(base64Body);
|
var fileSizeInBytes = file.total;
|
||||||
|
var base64SignatureFile = await $app.genSig(base64File);
|
||||||
|
var signatureMd5 = await $app.genMd5(base64SignatureFile);
|
||||||
|
var signatureSizeInBytes = await $app.genLength(base64SignatureFile);
|
||||||
var avatarId = $app.avatarDialog.id;
|
var avatarId = $app.avatarDialog.id;
|
||||||
var { imageUrl } = $app.avatarDialog.ref;
|
var { imageUrl } = $app.avatarDialog.ref;
|
||||||
var url = new URL(imageUrl);
|
var url = new URL(imageUrl);
|
||||||
var pathArray = url.pathname.split('/');
|
var pathArray = url.pathname.split('/');
|
||||||
var fileId = pathArray[4];
|
var fileId = pathArray[4];
|
||||||
var signatureMd5 = await $app.md5(btoa(Math.random().toString(36).substring(7))); // lol...
|
|
||||||
var signatureSize = Math.floor(Math.random() * (10000 - 500 + 1)) + 500;
|
|
||||||
$app.avatarImage = {
|
$app.avatarImage = {
|
||||||
file: base64Body,
|
base64File,
|
||||||
fileMd5: md5,
|
fileMd5,
|
||||||
fileId: fileId,
|
base64SignatureFile,
|
||||||
avatarId: avatarId
|
signatureMd5,
|
||||||
|
fileId,
|
||||||
|
avatarId
|
||||||
};
|
};
|
||||||
var params = {
|
var params = {
|
||||||
fileMd5: md5,
|
fileMd5,
|
||||||
fileSizeInBytes: fileSize,
|
fileSizeInBytes,
|
||||||
signatureMd5: signatureMd5,
|
signatureMd5,
|
||||||
signatureSizeInBytes: signatureSize
|
signatureSizeInBytes
|
||||||
};
|
};
|
||||||
API.uploadAvatarImage(params, fileId);
|
API.uploadAvatarImage(params, fileId);
|
||||||
};
|
};
|
||||||
@@ -10068,10 +10081,10 @@ speechSynthesis.getVoices();
|
|||||||
});
|
});
|
||||||
var fileId = json.id;
|
var fileId = json.id;
|
||||||
var fileVersion = json.versions[json.versions.length - 1].version;
|
var fileVersion = json.versions[json.versions.length - 1].version;
|
||||||
await this.call(`file/${fileId}/${fileVersion}/signature/finish`, {
|
this.call(`file/${fileId}/${fileVersion}/signature/finish`, {
|
||||||
method: 'PUT'
|
method: 'PUT'
|
||||||
});
|
});
|
||||||
await this.call(`file/${fileId}/${fileVersion}/file/finish`, {
|
this.call(`file/${fileId}/${fileVersion}/file/finish`, {
|
||||||
method: 'PUT'
|
method: 'PUT'
|
||||||
});
|
});
|
||||||
$app.avatarDialog.loading = false;
|
$app.avatarDialog.loading = false;
|
||||||
@@ -10080,11 +10093,11 @@ speechSynthesis.getVoices();
|
|||||||
API.$on('AVATARIMAGE:STAGE1', function (args) {
|
API.$on('AVATARIMAGE:STAGE1', function (args) {
|
||||||
var fileId = args.json.id;
|
var fileId = args.json.id;
|
||||||
var fileVersion = args.json.versions[args.json.versions.length - 1].version;
|
var fileVersion = args.json.versions[args.json.versions.length - 1].version;
|
||||||
var parmas = {
|
var params = {
|
||||||
fileId,
|
fileId,
|
||||||
fileVersion
|
fileVersion
|
||||||
};
|
};
|
||||||
this.uploadAvatarImageStage2(parmas);
|
this.uploadAvatarImageStage2(params);
|
||||||
});
|
});
|
||||||
|
|
||||||
API.uploadAvatarImageStage2 = async function (params) {
|
API.uploadAvatarImageStage2 = async function (params) {
|
||||||
@@ -10108,19 +10121,20 @@ speechSynthesis.getVoices();
|
|||||||
API.$on('AVATARIMAGE:STAGE2', function (args) {
|
API.$on('AVATARIMAGE:STAGE2', function (args) {
|
||||||
var { url } = args.json;
|
var { url } = args.json;
|
||||||
var { fileId, fileVersion } = args.params;
|
var { fileId, fileVersion } = args.params;
|
||||||
var parmas = {
|
var params = {
|
||||||
url,
|
url,
|
||||||
fileId,
|
fileId,
|
||||||
fileVersion
|
fileVersion
|
||||||
};
|
};
|
||||||
this.uploadAvatarImageStage3(parmas);
|
this.uploadAvatarImageStage3(params);
|
||||||
});
|
});
|
||||||
|
|
||||||
API.uploadAvatarImageStage3 = function (params) {
|
API.uploadAvatarImageStage3 = function (params) {
|
||||||
return webApiService.execute({
|
return webApiService.execute({
|
||||||
url: params.url,
|
url: params.url,
|
||||||
uploadImagePUT: true,
|
uploadFilePUT: true,
|
||||||
imageData: $app.avatarImage.file,
|
fileData: $app.avatarImage.base64File,
|
||||||
|
fileMIME: 'image/png',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-MD5': $app.avatarImage.fileMd5
|
'Content-MD5': $app.avatarImage.fileMd5
|
||||||
}
|
}
|
||||||
@@ -10140,11 +10154,11 @@ speechSynthesis.getVoices();
|
|||||||
|
|
||||||
API.$on('AVATARIMAGE:STAGE3', function (args) {
|
API.$on('AVATARIMAGE:STAGE3', function (args) {
|
||||||
var { fileId, fileVersion } = args.params;
|
var { fileId, fileVersion } = args.params;
|
||||||
var parmas = {
|
var params = {
|
||||||
fileId,
|
fileId,
|
||||||
fileVersion
|
fileVersion
|
||||||
};
|
};
|
||||||
this.uploadAvatarImageStage4(parmas);
|
this.uploadAvatarImageStage4(params);
|
||||||
});
|
});
|
||||||
|
|
||||||
API.uploadAvatarImageStage4 = function (params) {
|
API.uploadAvatarImageStage4 = function (params) {
|
||||||
@@ -10166,14 +10180,75 @@ speechSynthesis.getVoices();
|
|||||||
|
|
||||||
API.$on('AVATARIMAGE:STAGE4', function (args) {
|
API.$on('AVATARIMAGE:STAGE4', function (args) {
|
||||||
var { fileId, fileVersion } = args.params;
|
var { fileId, fileVersion } = args.params;
|
||||||
var parmas = {
|
var params = {
|
||||||
fileId,
|
fileId,
|
||||||
fileVersion
|
fileVersion
|
||||||
};
|
};
|
||||||
this.uploadAvatarImageStage5(parmas);
|
this.uploadAvatarImageStage5(params);
|
||||||
});
|
});
|
||||||
|
|
||||||
API.uploadAvatarImageStage5 = function (params) {
|
API.uploadAvatarImageStage5 = async function (params) {
|
||||||
|
try {
|
||||||
|
return await this.call(`file/${params.fileId}/${params.fileVersion}/signature/start`, {
|
||||||
|
method: 'PUT'
|
||||||
|
}).then((json) => {
|
||||||
|
var args = {
|
||||||
|
json,
|
||||||
|
params
|
||||||
|
};
|
||||||
|
this.$emit('AVATARIMAGE:STAGE5', args);
|
||||||
|
return args;
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
this.uploadAvatarFailCleanup(params.fileId);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
API.$on('AVATARIMAGE:STAGE5', function (args) {
|
||||||
|
var { url } = args.json;
|
||||||
|
var { fileId, fileVersion } = args.params;
|
||||||
|
var params = {
|
||||||
|
url,
|
||||||
|
fileId,
|
||||||
|
fileVersion
|
||||||
|
};
|
||||||
|
this.uploadAvatarImageStage6(params);
|
||||||
|
});
|
||||||
|
|
||||||
|
API.uploadAvatarImageStage6 = function (params) {
|
||||||
|
return webApiService.execute({
|
||||||
|
url: params.url,
|
||||||
|
uploadFilePUT: true,
|
||||||
|
fileData: $app.avatarImage.base64SignatureFile,
|
||||||
|
fileMIME: 'application/x-rsync-signature',
|
||||||
|
headers: {
|
||||||
|
'Content-MD5': $app.avatarImage.signatureMd5
|
||||||
|
}
|
||||||
|
}).then((json) => {
|
||||||
|
if (json.status !== 200) {
|
||||||
|
$app.avatarDialog.loading = false;
|
||||||
|
this.$throw('Avatar image upload failed', json);
|
||||||
|
}
|
||||||
|
var args = {
|
||||||
|
json,
|
||||||
|
params
|
||||||
|
};
|
||||||
|
this.$emit('AVATARIMAGE:STAGE6', args);
|
||||||
|
return args;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
API.$on('AVATARIMAGE:STAGE6', function (args) {
|
||||||
|
var { fileId, fileVersion } = args.params;
|
||||||
|
var params = {
|
||||||
|
fileId,
|
||||||
|
fileVersion
|
||||||
|
};
|
||||||
|
this.uploadAvatarImageStage7(params);
|
||||||
|
});
|
||||||
|
|
||||||
|
API.uploadAvatarImageStage7 = function (params) {
|
||||||
return this.call(`file/${params.fileId}/${params.fileVersion}/signature/finish`, {
|
return this.call(`file/${params.fileId}/${params.fileVersion}/signature/finish`, {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
params: {
|
params: {
|
||||||
@@ -10185,21 +10260,21 @@ speechSynthesis.getVoices();
|
|||||||
json,
|
json,
|
||||||
params
|
params
|
||||||
};
|
};
|
||||||
this.$emit('AVATARIMAGE:STAGE5', args);
|
this.$emit('AVATARIMAGE:STAGE7', args);
|
||||||
return args;
|
return args;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
API.$on('AVATARIMAGE:STAGE5', function (args) {
|
API.$on('AVATARIMAGE:STAGE7', function (args) {
|
||||||
var { fileId, fileVersion } = args.params;
|
var { fileId, fileVersion } = args.params;
|
||||||
var parmas = {
|
var parmas = {
|
||||||
id: $app.avatarImage.avatarId,
|
id: $app.avatarImage.avatarId,
|
||||||
imageUrl: `https://api.vrchat.cloud/api/1/file/${fileId}/${fileVersion}/file`
|
imageUrl: `https://api.vrchat.cloud/api/1/file/${fileId}/${fileVersion}/file`
|
||||||
};
|
};
|
||||||
this.uploadAvatarImageStage6(parmas);
|
this.uploadAvatarImageStage8(parmas);
|
||||||
});
|
});
|
||||||
|
|
||||||
API.uploadAvatarImageStage6 = function (params) {
|
API.uploadAvatarImageStage8 = function (params) {
|
||||||
return this.call(`avatars/${params.id}`, {
|
return this.call(`avatars/${params.id}`, {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
params
|
params
|
||||||
@@ -10208,13 +10283,13 @@ speechSynthesis.getVoices();
|
|||||||
json,
|
json,
|
||||||
params
|
params
|
||||||
};
|
};
|
||||||
this.$emit('AVATARIMAGE:STAGE6', args);
|
this.$emit('AVATARIMAGE:STAGE8', args);
|
||||||
this.$emit('AVATAR', args);
|
this.$emit('AVATAR', args);
|
||||||
return args;
|
return args;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
API.$on('AVATARIMAGE:STAGE6', function (args) {
|
API.$on('AVATARIMAGE:STAGE8', function (args) {
|
||||||
$app.avatarDialog.loading = false;
|
$app.avatarDialog.loading = false;
|
||||||
if (args.json.imageUrl === args.params.imageUrl) {
|
if (args.json.imageUrl === args.params.imageUrl) {
|
||||||
$app.$message({
|
$app.$message({
|
||||||
|
|||||||
@@ -1786,6 +1786,30 @@ html
|
|||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
SOFTWARE.
|
SOFTWARE.
|
||||||
div(style="margin-top:15px")
|
div(style="margin-top:15px")
|
||||||
|
p(style="font-weight:bold") librsync.net
|
||||||
|
pre(style="font-size:12px;white-space:pre-line").
|
||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2015 Brad Dodson
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
div(style="margin-top:15px")
|
||||||
p(style="font-weight:bold") Newtonsoft.Json
|
p(style="font-weight:bold") Newtonsoft.Json
|
||||||
pre(style="font-size:12px;white-space:pre-line").
|
pre(style="font-size:12px;white-space:pre-line").
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|||||||
Binary file not shown.
Reference in New Issue
Block a user