diff --git a/AppApi.cs b/AppApi.cs index 04400f27..9ae7ebb1 100644 --- a/AppApi.cs +++ b/AppApi.cs @@ -355,6 +355,32 @@ namespace VRCX MainForm.Instance.Activate(); } + public string FollowUrl(string url) + { + bool redirecting = true; + + while (redirecting) + { + Console.WriteLine(url); + HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url); + request.AllowAutoRedirect = false; + request.UserAgent = "VRCX"; + HttpWebResponse response = (HttpWebResponse)request.GetResponse(); + if ((int)response.StatusCode == 301 || (int)response.StatusCode == 302) + { + url = response.Headers["Location"]; + if (url.Substring(0, 1) == "/") + return url; + } + else + { + redirecting = false; + } + } + + return ""; + } + public void SetStartup(bool enabled) { try diff --git a/html/src/app.js b/html/src/app.js index 0b04a6ad..2729fda2 100644 --- a/html/src/app.js +++ b/html/src/app.js @@ -448,6 +448,9 @@ speechSynthesis.getVoices(); } return response; } catch (e) {} + if (endpoint.substring(endpoint.length - 10) === '/shortName') { + return response; + } if (response.status === 200) { this.$throw(0, 'Invalid JSON response'); } @@ -1879,6 +1882,25 @@ speechSynthesis.getVoices(); }); }; + /* + params: { + worldId: string, + instanceId: string + } + */ + API.getInstanceShortName = function (params) { + return this.call(`instances/${params.worldId}:${params.instanceId}/shortName`, { + method: 'GET' + }).then((json) => { + var args = { + json, + params + }; + this.$emit('INSTANCE:SHORTNAME', args); + return args; + }); + }; + /* params: { worldId: string, @@ -10165,7 +10187,27 @@ speechSynthesis.getVoices(); if (action === 'confirm' && instance.inputValue) { var input = instance.inputValue; var testUrl = input.substring(0, 15); - if (testUrl === 'https://vrchat.') { + if (testUrl === 'https://vrch.at') { + AppApi.FollowUrl(input).then((url) => { + // /home/launch?worldId=wrld_f20326da-f1ac-45fc-a062-609723b097b1&instanceId=33570~region(jp)&shortName=cough-stockinglinz-ddd26 + if (url.substring(0, 13) === '/home/launch?') { + var urlParams = new URLSearchParams(url.substring(13)); + var worldId = urlParams.get('worldId'); + var instanceId = urlParams.get('instanceId'); + if (instanceId) { + var location = `${worldId}:${instanceId}`; + this.showWorldDialog(location); + } else if (worldId) { + this.showWorldDialog(worldId); + } + } else { + this.$message({ + message: 'Invalid URL', + type: 'error' + }); + } + }); + } else if (testUrl === 'https://vrchat.') { var url = new URL(input); var urlPath = url.pathname; if (urlPath.substring(5, 11) === '/user/') { @@ -12659,6 +12701,11 @@ speechSynthesis.getVoices(); $app.launchDialog.visible = false; }); + API.$on('INSTANCE:SHORTNAME', function (args) { + var url = `https://vrch.at/${args.json}`; + $app.launchDialog.url = url; + }); + $app.methods.showLaunchDialog = function (tag) { this.$nextTick(() => adjustDialogZ(this.$refs.launchDialog.$el)); var L = API.parseLocation(tag); @@ -12673,6 +12720,7 @@ speechSynthesis.getVoices(); } D.url = getLaunchURL(L.worldId, L.instanceId); D.visible = true; + API.getInstanceShortName({worldId: L.worldId, instanceId: L.instanceId}); }; $app.methods.locationToLaunchArg = function (location) { @@ -12762,6 +12810,14 @@ speechSynthesis.getVoices(); this.copyToClipboard(`https://vrchat.com/home/user/${userId}`); }; + $app.methods.copyInstanceUrl = function (url) { + this.$message({ + message: 'Instance URL copied to clipboard', + type: 'success' + }); + this.copyToClipboard(url); + }; + // App: VRCPlus Icons API.$on('LOGIN', function () { diff --git a/html/src/index.pug b/html/src/index.pug index 8f3ee03f..8e8ef4ff 100644 --- a/html/src/index.pug +++ b/html/src/index.pug @@ -1899,6 +1899,8 @@ html //- dialog: launch el-dialog.x-dialog(ref="launchDialog" :visible.sync="launchDialog.visible" title="Launch" width="400px") div #[span(v-text="launchDialog.url" style="word-break:break-all;font-size:12px")] + el-tooltip(placement="top" content="Copy to clipboard" :disabled="hideTooltips") + el-button(@click="copyInstanceUrl(launchDialog.url)" size="mini" icon="el-icon-s-order" style="margin-left:5px" circle) template(#footer) el-checkbox(v-model="launchDialog.desktop" style="float:left;margin-top:5px") Start as Desktop (No VR) el-button(size="small" @click="showInviteDialog(launchDialog.location)" :disabled="checkCanInvite(launchDialog.location)") Invite