Last active
September 29, 2023 08:02
-
-
Save EllieTheYeen/85776ad20218db29c7d33e12ce58be29 to your computer and use it in GitHub Desktop.
VRChat online announcer userscript for userscript extensions such as Tampermonkey. Announces with speech synthesis who comes online and goes offline. Good for when you are in VR
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
// ==UserScript== | |
// @name VRChat online announcer | |
// @namespace http://tampermonkey.net/ | |
// @version 1 | |
// @description Announces who becomes online and offline in VRChat and when notifications come | |
// @author EllieTheYeen | |
// @match https://vrchat.com/home* | |
// @icon https://www.google.com/s2/favicons?sz=64&domain=vrchat.com | |
// @grant none | |
// ==/UserScript== | |
(function() { | |
'use strict'; | |
function getStatuses() { | |
const friends = document.querySelector('.e1oqhh5q3') | |
const friende = friends.querySelectorAll('.e1oqhh5q0') | |
const statuses = {} | |
var category = '' | |
for (let index = 0; index < friende.length; index++) { | |
const element = friende[index] | |
const e = element.querySelector('.e176ivn28') | |
if (!e) { | |
category = element.textContent | |
statuses[category] = [] | |
//console.log('Category:', element.textContent) | |
} else { | |
statuses[category].push(e.textContent) | |
//console.log(e.textContent) | |
} | |
} | |
return statuses | |
} | |
function consolidate(data) { | |
const out = { online: [], offline: [] } | |
for (const slot of ['Online Friends', 'Friends in Private Worlds']) { | |
const array = data[slot] | |
const dest = out.online | |
if (!array) continue | |
for (let i = 0; i < array.length; i++) { | |
const member = array[i]; | |
dest.push(member) | |
} | |
} | |
for (const slot of ['Friends Active on the Website', 'Offline Friends']) { | |
const array = data[slot] | |
const dest = out.offline | |
if (!array) continue | |
for (let i = 0; i < array.length; i++) { | |
const member = array[i]; | |
dest.push(member) | |
} | |
} | |
return out | |
} | |
function speak(text) { | |
const utter = new SpeechSynthesisUtterance(text); | |
speechSynthesis.speak(utter); | |
return speechSynthesis | |
} | |
function diff(one, two) { | |
return one.filter((d) => two.indexOf(d) === -1) | |
} | |
function speakableList(arr) { | |
return [arr.slice(0, -1).join(', '), arr.slice(-1)[0]].filter(Boolean).join(' and ') | |
} | |
function scrollList(top = false) { | |
const s = document.querySelector('.e1oqhh5q4') | |
if (s) { | |
s.scroll(0, top ? 0 : 10000) | |
} | |
} | |
function doScrolls() { | |
const scrollData = [[1000, 0], [2000, 0], [3000, 0], [4000, 0], [4500, 1]] | |
scrollData.forEach(e => { | |
setTimeout(d => scrollList(e[1]), e[0]) | |
}); | |
} | |
function notifCount() { | |
const q = document.querySelector('.css-1hhuku4') | |
return q ? +q.textContent : 0 | |
} | |
function run() { | |
const currentOnline = consolidate(getStatuses()).online | |
if (online === null) { | |
online = currentOnline | |
console.log('Online announcer: First scan is now complete and any further online status changes will be announced') | |
return | |
} | |
var toSpeak = [] | |
const currentNotifs = notifCount() | |
if (currentNotifs > notifs) { | |
const notifMessage = `${currentNotifs} notification${currentNotifs === 1 ? '' : 's'}` | |
// You can comment the line below away if you do not want notification messages | |
toSpeak.push(notifMessage) | |
console.log(notifMessage) | |
} | |
notifs = currentNotifs | |
const cameOnline = diff(currentOnline, online) | |
const goneOffline = diff(online, currentOnline) | |
online = currentOnline | |
if (goneOffline.length) { | |
const offlineMessage = `${speakableList(goneOffline)} ${goneOffline.length === 1 ? 'is' : 'are'} now offline` | |
// You can comment the line below away if you do not want to announce those going offline | |
toSpeak.push(offlineMessage) | |
console.log(`Online announcer: ${offlineMessage}`) | |
} | |
if (cameOnline.length) { | |
const onlineMessage = `${speakableList(cameOnline)} ${cameOnline.length === 1 ? 'is' : 'are'} now online` | |
toSpeak.push(onlineMessage) | |
console.log(`Online announcer: ${onlineMessage}`) | |
} | |
if (toSpeak.length) { | |
speak(toSpeak.join(' and ')) | |
} | |
} | |
var notifs = 0 | |
var online = null | |
var timer = setInterval(run, 5000) | |
doScrolls() | |
console.log(`Online announcer: Timer id ${timer}`) | |
console.log('Online announcer: Active') | |
speak('Online announcer activated') | |
})(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment