Created
May 1, 2023 10:35
-
-
Save BOLL7708/69c5618afd6c34259eebabd8c676b3ae to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html lang="en"> | |
<head> | |
<title>Scale Reward Source</title> | |
<style> | |
body { font-family: sans-serif; } | |
span[contenteditable] { | |
padding: 0.25em; | |
border: 2px solid #00000080; | |
border-radius: 0.5em; | |
background-color: #80808040; | |
line-height: 170%; | |
} | |
.password { color: transparent; } | |
.password:focus { color: black; } | |
em { background-color: lightyellow; } | |
li { padding: 0.25em; } | |
button { | |
font-size: 100%; | |
padding: 0.25em 1em; | |
border-radius: 0.25em; | |
} | |
button:hover { background-color: lightgreen; } | |
button:active { background-color: limegreen; } | |
#link { | |
background-color: lightgreen; | |
} | |
</style> | |
</head> | |
<body> | |
<h1>Scale Reward Source</h1> | |
<h2>Description</h2> | |
<p>This source will, from fragment input in the URL, set the world scale for the running application in SteamVR, and then reset it after a delay.</p> | |
<p>It is meant to be used as a browser source in <a href="https://obsproject.com/" target="_blank">OBS</a>, but you can use the link building form just opening this file in a browser.</p> | |
<h2>Instructions</h2> | |
<ol> | |
<li>This requires <a href="https://github.com/BOLL7708/OpenVR2WS/releases" target="_blank">OpenVR2WS</a> to run, launch it with SteamVR.</li> | |
<li>In the application, check <em>Enable remote setting updates</em> and click <em>Set password</em> to set a password.</li> | |
<li>Use the form below with said password and other information to build a link.</li> | |
<li>Load this link in an OBS Browser Source, set it to <em>Shutdown source when not visible</em> so you can trigger this when toggling the source off and on again.</li> | |
<li>To make this a dynamic reward, you would need to use a solution that can change the link in the browser source.</li> | |
<li>To make this invisible in OBS, just set the source size to be 1x1 or body opacity to 0 in the custom CSS field.</li> | |
</ol> | |
<h2>Build Link</h2> | |
<p title="Port set in OpenVR2WS.">OpenVR2WS port: <span contenteditable="true" id="port">7708</span></p> | |
<p title="Password set in OpenVR2WS.">OpenVR2WS password: <span contenteditable="true" id="password" class="password"></span></p> | |
<p title="The World Scale value to set in percent.">World scale: <span contenteditable="true" id="scale">50</span>%</p> | |
<p title="Duration of the scale change in seconds.">Scale duration: <span contenteditable="true" id="seconds">5</span> second(s), will not reset if set to 0.</p> | |
<p title="The minimum value to clamp the scale change to.">Minimum scale: <span contenteditable="true" id="min">10</span>%</p> | |
<p title="The maximum value to clamp the scale change to.">Maximum scale: <span contenteditable="true" id="max">1000</span>%</p> | |
<p><button id="button" onclick="handleLink()">Copy link</button> Link: <span id="link" contenteditable="true"></span></p> | |
<h2>Status Output</h2> | |
<ol id="status"></ol> | |
</body> | |
<script> | |
const | |
portSpan = document.querySelector('#port'), | |
passwordSpan = document.querySelector('#password'), | |
scaleSpan = document.querySelector('#scale'), | |
secondsSpan = document.querySelector('#seconds'), | |
minSpan = document.querySelector('#min'), | |
maxSpan = document.querySelector('#max'), | |
button = document.querySelector('#button'), | |
link = document.querySelector('#link') | |
const canCopy = (typeof navigator.clipboard.write == 'function') | |
if(!canCopy) button.innerHTML = 'Build Link' | |
function handleLink() { | |
const | |
port = portSpan.innerHTML, | |
password = passwordSpan.innerHTML, | |
scale = scaleSpan.innerHTML, | |
seconds = secondsSpan.innerHTML, | |
min = minSpan.innerHTML, | |
max = maxSpan.innerHTML, | |
documentURL = window.location.href, | |
fragmentVars = new URLSearchParams({port,password,scale,seconds,min,max}), | |
triggerURL = `${documentURL}#${fragmentVars}` | |
link.innerHTML = triggerURL | |
if(canCopy) setClipboard(triggerURL) | |
} | |
(async()=>{ | |
const parsedHash = new URLSearchParams( | |
window.location.hash.substring(1) // any_hash_key=any_value | |
); | |
const | |
inScale = parsedHash.get('scale') ?? 50, | |
port = parsedHash.get('port') ?? 7708, | |
password = parsedHash.get('password') ?? '', | |
duration = parseInt(parsedHash.get('seconds') ?? '5'), | |
minScale = parseInt(parsedHash.get('min') ?? '10'), | |
maxScale = parseInt(parsedHash.get('max') ?? '1000'), | |
scale = Math.max(minScale, Math.min(maxScale, inScale)), | |
hash = await hashPassword(password) | |
let appId = '' | |
portSpan.innerHTML = port | |
passwordSpan.innerHTML = password | |
secondsSpan.innerHTML = duration.toString() | |
minSpan.innerHTML = minScale.toString() | |
maxSpan.innerHTML = maxScale.toString() | |
scaleSpan.innerHTML = scale.toString() | |
const status = document.querySelector('#status') | |
function printStatus(message) { | |
status.innerHTML += `<li>${message}</li>` | |
} | |
printStatus(`Will set scale to ${scale} for ${duration} second(s).`) | |
const socket = new WebSocket(`ws://localhost:${port}`) | |
socket.onopen = ()=>{ | |
printStatus('Websocket connected.\n') | |
} | |
socket.onmessage = (event)=>{ | |
try { | |
const response = JSON.parse(event.data) | |
switch(response?.key) { | |
case 'RemoteSetting': | |
printStatus(`Response: ${response?.data?.message}`) | |
break | |
case 'ApplicationInfo': | |
appId = response.data.id | |
printStatus(`Got running app ID: ${appId}`) | |
if(password.length > 0) run() | |
else printStatus('Problem: Password is not set.') | |
break | |
} | |
} catch(e) { | |
console.error(e.message) | |
} | |
} | |
socket.onclose = ()=>{ | |
printStatus('Websocket closed.') | |
} | |
socket.onerror = (e)=>{ | |
printStatus('Websocket error: '+e.message) | |
} | |
function run() { | |
const payload = { | |
key:'RemoteSetting', | |
value: hash, | |
value2: appId, | |
value3:"worldScale", | |
value4:scale/100 | |
} | |
const payloadStr = JSON.stringify(payload) | |
printStatus('Sending first payload.') | |
socket.send(payloadStr) | |
printStatus('Waiting to send second payload.') | |
if(duration) setTimeout(()=>{ | |
printStatus('Sending second payload.') | |
payload.value4 = 1 // Reset world scale | |
const payloadStr2 = JSON.stringify(payload) | |
socket.send(payloadStr2) | |
setTimeout(()=>{ // Terminate connection | |
socket.close() | |
}, 1000) | |
}, duration * 1000) | |
} | |
})() | |
async function hashPassword(password) { | |
const | |
encoder = new TextEncoder(), | |
data = encoder.encode(password), | |
hashBuffer = await crypto.subtle.digest('SHA-256', data) | |
return btoa(String.fromCharCode(...new Uint8Array(hashBuffer)), 'utf8') | |
} | |
function setClipboard(text) { | |
navigator.clipboard.writeText(text).then() | |
} | |
</script> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment