Skip to content

Instantly share code, notes, and snippets.

@Bluscream
Last active April 5, 2025 10:07
Show Gist options
  • Save Bluscream/7842ad23efb6cbb73f6a1bb17008deed to your computer and use it in GitHub Desktop.
Save Bluscream/7842ad23efb6cbb73f6a1bb17008deed to your computer and use it in GitHub Desktop.
VRCX custom files
console.log("custom.js START")
const customjs = {
author: "Bluscream",
date: "2025-04-02 23:17:32 GMT+1",
url: "https://gist.github.com/Bluscream/7842ad23efb6cbb73f6a1bb17008deed"
}
const steam = {
id: "", // TODO: Remove
key: ""
}
// region COMMON
let bak = {
updateCurrentUserLocation: $app.updateCurrentUserLocation,
setCurrentUserLocation: $app.setCurrentUserLocation,
applyWorldDialogInstances: $app.applyWorldDialogInstances,
applyGroupDialogInstances: $app.applyGroupDialogInstances,
playNoty: $app.playNoty,
getInstance: API.getInstance,
};
const isEmpty = function(v) {
return v === null || v === undefined || v === ""
}
const getTimestamp = function(now = null) {
now = now ?? new Date();
const timestamp = now.toLocaleString('en-US', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
hour12: false
});
return timestamp;
}
const formatDateTime = function(now = null) {
now = now ?? new Date();
const year = now.getFullYear();
const month = String(now.getMonth() + 1).padStart(2, '0');
const day = String(now.getDate()).padStart(2, '0');
const hours = String(now.getHours()).padStart(2, '0');
const minutes = String(now.getMinutes()).padStart(2, '0');
const seconds = String(now.getSeconds()).padStart(2, '0');
// const timezone = -now.getTimezoneOffset() / 60;
// const timezoneStr = (timezone >= 0 ? '+' : '') + timezone;
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds} GMT+1`;
}
const log = function(msg, _alert=true, _notify=false, _noty=false, _noty_type='alert') {
console.log(msg);
$app.eventVrcxMessage({'MsgType': 'Noty', 'Data': msg });
$app.eventVrcxMessage({'MsgType': 'External', 'UserId': API.currentUser.id, 'Data': msg });
if (_notify) notify("VRCX Addon", msg);
// if (_alert) alert(msg);
if (_noty) {
setTimeout(async () => { await AppApi.DesktopNotification("VRCX Addon", msg) }, 0);
// new Noty({type: _noty_type, text: msg}).show();
} // TODO: Fix
}
const notify = function(title, msg) {
async () => {
await AppApi.DesktopNotification(title, msg);
await AppApi.XSNotification(title, msg, 5000);
await AppApi.OVRTNotification(true, true, title, msg, 5000);
}
}
const getLocationObject = async function (loc) {
if (typeof loc === 'string') {
if (loc.endsWith(')')) loc = $app.parseLocation(loc);
else if (loc.startsWith('wrld')) loc = { worldId: loc, world: { id: loc } }
else loc = { instanceId: loc, instance: { id: loc } }
} else if (isEmpty(loc) || loc === 'traveling:traveling') {
return;
}
if (isEmpty(loc) && !isEmpty($app.lastLocation)) getLocationObject($app.lastLocation);
if (isEmpty(loc) && !isEmpty($app.lastLocationDestination)) getLocationObject($app.lastLocationDestination);
loc.worldName = await $app.getWorldName(loc);
console.log(loc);
return loc;
}
/**
* @param {{ notificationId: string }} params
* @return { Promise<{json: any, params}> }
*/
const seeNotification = function(params, emit = true) {
return API.call(
`auth/user/notifications/${params.notificationId}/see`,
{
method: 'PUT'
}
).then((json) => {
const args = {
json,
params
};
if (emit) API.$emit('NOTIFICATION:SEE', args);
return args;
});
}
/**
* @param {{ notificationId: string }} params
* @return { Promise<{json: any, params}> }
*/
const hideNotification = function(params, emit = true) {
return window.API.call(
`auth/user/notifications/${params.notificationId}/hide`,
{
method: 'PUT'
}
).then((json) => {
const args = {
json,
params
};
if (emit) window.API.$emit('NOTIFICATION:HIDE', args);
return args;
});
}
// endregion COMMON
setTimeout(async () => { await AppApi.FocusWindow(); await AppApi.ShowDevTools(); }, 0);
// region AUTO_DISABLE_UNTRUSTED_URLS
const REGISTRY_KEY_VRC_ALLOW_UNTRUSTED_URL = "VRC_ALLOW_UNTRUSTED_URL";
const resetPublicUrls = function() {
async () => {
const oldVal = await AppApi.GetVRChatRegistryKey(REGISTRY_KEY_VRC_ALLOW_UNTRUSTED_URL);
// if (oldVal)
console.log(`${REGISTRY_KEY_VRC_ALLOW_UNTRUSTED_URL} was ${oldVal}`)
await AppApi.SetVRChatRegistryKey(REGISTRY_KEY_VRC_ALLOW_UNTRUSTED_URL, 0, 3);
}
}
resetPublicUrls();
// let lastRunningState = $app.isGameRunning;
setInterval(() => {
// if (lastRunningState != $app.isGameRunning && !$app.isGameRunning) {
resetPublicUrls();
// }
// lastRunningState = $app.isGameRunning;
}, 2500);
// endregion AUTO_DISABLE_UNTRUSTED_URLS
// region IPC_DEBUG
// let oldIpcEvent = $app.ipcEvent;
// $app.ipcEvent = function (packet) {
// console.log(packet);
// oldIpcEvent(packet);
// }
// endregion IPC_DEBUG
// region AUTO_INVITE_USER_BUTTON
let autoInviteUser, lastInvitedTo, lastJoined; // = null;
// bak.setCurrentUserLocation = $app.setCurrentUserLocation;
$app.setCurrentUserLocation = function (loc) {
// console.log("Before lastLocationDestination ", $app.lastLocationDestination);
// console.log("Before lastLocation ", $app.lastLocation);
bak.setCurrentUserLocation();
// console.log("After lastLocationDestination ", $app.lastLocationDestination);
// console.log("After lastLocation ", $app.lastLocation);
// console.log("autoInviteUser ", autoInviteUser);
// console.log("lastInvitedTo ", lastInvitedTo);
// console.log("lastJoined ", lastJoined);
setTimeout(async () => { await onCurrentUserLocationChanged(loc) }, 1000);
}
// let oldupdateCurrentUserLocation = $app.updateCurrentUserLocation;
// $app.updateCurrentUserLocation = function () {
// onCurrentUserLocationChanged(API.currentUser.$locationTag)
// oldupdateCurrentUserLocation();
// }
const onCurrentUserLocationChanged = async function(loc) {
console.log(`User Location changed to: ${loc}`)
if (loc === 'traveling:traveling') {
if (!isEmpty(autoInviteUser) && lastInvitedTo !== loc ) {
const userName = `\"${autoInviteUser?.displayName ?? autoInviteUser}\"`;
let n;
let l = $app.lastLocationDestination;
if (isEmpty(l)) {
// log(`Cannot invite ${userName}, lastLocationDestination is empty :(`)
// return;
l = $app.lastLocation.location;
n = $app.lastLocation.name;
}
if (isEmpty(n)) n = await $app.getWorldName(l);
// const p = $app.parseLocation(l);
log(`Inviting user ${userName} to \"${n}\"`, false)
API.sendInvite({ instanceId: l, worldId: l, worldName: n }, autoInviteUser.id);
lastInvitedTo = l;
}
} else {
lastJoined = loc;
}
}
// const onCurrentUserLocationChanged = async function(location) {
// console.log(`User Location changed to: ${location}`);
// const locationObject = await getLocationObject(location);
// if (isEmpty(locationObject)) return;
// if (location === 'traveling:traveling') {
// if (!isEmpty(autoInviteUser)) {
// if (lastInvitedTo === location) return;
// log(`Inviting user \"${autoInviteUser?.displayName ?? autoInviteUser}\" to \"${locationObject.worldName}\"`, false);
// API.sendInvite(locationObject, autoInviteUser.id);
// lastInvitedTo = location;
// }
// } else {
// lastJoined = locationObject;
// console.log("lastJoined updated to:", lastJoined);
// }
// }
const toggleAutoInvite = function(user) {
if (isEmpty(user) || (!isEmpty(autoInviteUser) && user.id === autoInviteUser?.id)) {
log(`Disabled Auto Invite for user ${autoInviteUser.displayName}`);
autoInviteUser = null;
} else {
autoInviteUser = user;
log(`Enabled Auto Invite for user ${autoInviteUser.displayName}`);
}
}
const addUserButtons = function(parentNode) {
const menuItem = document.createElement('li');
menuItem.className = 'el-dropdown-menu__item el-dropdown-menu__item--divided';
menuItem.tabIndex = '-1';
const icon = document.createElement('i');
icon.className = 'el-icon-message';
menuItem.appendChild(icon);
menuItem.onclick = () => {
toggleAutoInvite($app.userDialog.ref);
};
menuItem.appendChild(document.createTextNode('Auto Invite'));
parentNode.appendChild(menuItem);
console.log("Added user buttons");
}
let buttonsAdded = false;
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if (!buttonsAdded && mutation.addedNodes.length) {
// console.log(`mutations: ${mutation.addedNodes.length}`);
mutation.addedNodes.forEach((node) => {
// console.log(node.classList);
if (node.classList && node.classList.contains('el-dropdown-menu__item')) {
console.log("Found user context menu item");
addUserButtons(node.parentElement);
buttonsAdded = true;
return;
}
});
}
});
});
observer.observe(document.body, {
childList: true,
subtree: true
});
// setTimeout(() => { addUserButtons(document.querySelector('.el-dropdown-menu.el-popper.el-dropdown-menu--small')); }, 2500);
// endregion AUTO_INVITE_USER_BUTTON
/*
`
-
Friends: {friends} | Blocked: {blocked} | Muted: {muted}
VRCX time played: {playtime_total}
Steam time played: {playtime_steam}
Date joined: {date_joined}
Last activity: {last_activity} ago
Last updated: {now} (every 2h)`
*/
// region BIO_TIMER
const bioTemplate = `
-
Relationship: {partners} <3
Auto Accept: {autojoin}
Auto Invite: {autoinvite}
Real Rank: {rank}
Friends: {friends} | Blocked: {blocked} | Muted: {muted}
Time played: {playtime_steam} ({playtime_steam_hours})
Date joined: {date_joined}
Last updated: {now} (every 2h)
User ID: {userId}
Steam ID: {steamId}
Oculus ID: {oculusId}`;
const getSteamPlaytime = async function(steamId, api_key) {
try {
if (!steamId) {
console.log("No Steam ID found");
return null;
}
const response = await fetch(`https://api.steampowered.com/IPlayerService/GetOwnedGames/v1/?key=${api_key}&steamid=${steamId}&appids_filter[0]=438100`);
const data = await response.json();
if (!data?.response?.games?.[0]) {
console.log("No VRChat playtime data found");
return null;
}
const playtimeMinutes = data.response.games[0].playtime_forever;
console.log(`Got Steam playtime for vrchat: ${playtimeMinutes} minutes`)
// const playtimeSeconds = playtimeMinutes * 60;
return playtimeMinutes;
} catch (error) {
console.error("Error getting Steam playtime:", error);
return null;
}
}
const changeBio = function(newBio) {
$app.bioDialog.bio = newBio;
$app.saveBio();
}
const updateBio = async function() {
const now = Date.now();
const stats = await database.getUserStats({'id': API.currentUser.id});
const oldBio = API.currentUser.bio.split("\n-\n")[0];
const steamPlayTime = await getSteamPlaytime(steam.id, steam.key);
const steamHours = `${Math.floor(steamPlayTime / 60).toString().padStart(2, '0')}h`;
const moderations = Array.from(API.cachedPlayerModerations.values());
// const last_login = API.currentUser.$last_login;
const last_activity = new Date(API.currentUser.last_activity);
const favs = Array.from($app.favoriteFriends.values())
const joiners = favs.filter(friend => friend.groupKey === "friend:group_2");
const partners = favs.filter(friend => friend.groupKey === "friend:group_1");
// const onlineText = API.currentUser.$online_for != '' ? `Playing since ${$app.timeToText(now-API.currentUser.$online_for)}`: `Offline since ${$app.timeToText(now-API.currentUser.$offline_for)}`;
const newBio = bioTemplate
// .replace('{online_offline_since}', onlineText ?? "")
.replace('{last_activity}', $app.timeToText(now-last_activity) ?? "")
// .replace('{last_login}', $app.timeToText(now-last_login) ?? "")
.replace('{playtime_total}', $app.timeToText(stats.timeSpent) ?? "Unknown")
.replace('{playtime_steam}', $app.timeToText(steamPlayTime * 60 * 1000) ?? "Unknown")
.replace('{playtime_steam_hours}', steamHours ?? "Unknown")
.replace('{date_joined}', API.currentUser.date_joined ?? "Unknown")
.replace('{friends}', API.currentUser.friends.length ?? "?")
.replace('{blocked}', moderations.filter(item => item.type === "block").length ?? "?")
.replace('{muted}', moderations.filter(item => item.type === "mute").length ?? "?")
.replace('{now}', formatDateTime())
.replace('{autojoin}', joiners.map(f => f.name).join(", "))
.replace('{partners}', partners.map(f => f.name).join(", "))
.replace('{autoinvite}', autoInviteUser?.displayName ?? '')
.replace('{userId}', API.currentUser.id)
.replace('{steamId}', API.currentUser.steamId)
.replace('{oculusId}', API.currentUser.oculusId)
.replace('{picoId}', API.currentUser.picoId)
.replace('{viveId}', API.currentUser.viveId)
.replace('{rank}', API.currentUser.$trustLevel)
const bio = oldBio + newBio;
console.log(`Updating bio to ${bio}`)
changeBio(bio);
}
setInterval(async() => { await updateBio(); }, 7200000); // 2hr
setTimeout(async () => { updateBio(); }, 20000);
// endregion BIO_TIMER
// region OPENVR
// (async () => {
// await CefSharp.BindObjectAsync('System');
// await CefSharp.BindObjectAsync('VRCXVR');
// await CefSharp.BindObjectAsync('OpenVR');
// OpenVR.System.GetStringTrackedDeviceProperty(0, ETrackedDeviceProperty.Prop_RenderModelName_String, System.Text.StringBuilder(), 5000, null);
// //VRCXVR.Instance.StartGameFromPath('C:/Windows/System32/calc.exe', '');
// })();
// endregion OPENVR
// region CLIENTUSER_NOTIFICATIONS
let lastInvisiblePlayers = 0;
let comparePlayerCounts = function() {
console.log('Fetching player counts...');
$app.updateCurrentUserLocation();
$app.updateCurrentInstanceWorld();
$app.updateVRLastLocation();
$app.getCurrentInstanceUserList();
setTimeout(() => {
console.log('Comparing player counts...');
// const instanceQueue = $app.currentInstanceWorld.instance.queueSize ?? 0;
let playerCounts = {};
playerCounts.visiblePlayers = $app.lastLocation?.playerList?.size ?? 0; // blocked players also missing here
playerCounts.instanceUserCount = $app.currentInstanceWorld.instance.userCount ?? 0;
playerCounts.instanceUsers = $app.currentInstanceWorld.instance.users?.length ?? 0;
playerCounts.instancePlatformUsers = ($app.currentInstanceWorld.instance.platforms?.android ?? 0)+($app.currentInstanceWorld.instance.platforms?.ios ?? 0)+($app.currentInstanceWorld.instance.platforms?.standalonewindows ?? 0);
playerCounts.n_users = $app.currentInstanceWorld.instance.n_users ?? 0;
console.log("Player Count Comparison:");
console.log(`- Visible Players: ${playerCounts.visiblePlayers}`);
console.log(`- Instance User Count: ${playerCounts.instanceUserCount}`);
console.log(`- Instance Users Length: ${playerCounts.instanceUsers}`);
console.log(`- Platform Users Total: ${playerCounts.instancePlatformUsers}`);
console.log(`- n_users: ${playerCounts.n_users}`);
const invisiblePlayers = playerCounts.n_users - playerCounts.visiblePlayers;
console.log(`- lastInvisiblePlayers: ${lastInvisiblePlayers}`);
console.log(`- invisiblePlayers: ${invisiblePlayers}`);
if (invisiblePlayers > 0) {
if (lastInvisiblePlayers === invisiblePlayers) return;
const diff = invisiblePlayers - lastInvisiblePlayers;
console.log(`- diff: ${diff}`);
if (diff === 0) return;
// const txt = `${Math.abs(diff)} invisible user${Math.abs(diff) === 1 ? '' : 's'} ${diff > 0 ? 'joined' : 'left'}`
const txt = `Invisible user count changed to: ${invisiblePlayers} (${Math.abs(diff)})`;
log(txt, false, true);
}
lastInvisiblePlayers = invisiblePlayers;
}, 2000);
}
// setInterval(() => { comparePlayerCounts(); }, 60000); // 1m
// endregion CLIENTUSER_NOTIFICATIONS
// region INSTANCE_CLIENTUSER_BADGE
API.$on('SHOW_WORLD_DIALOG', (tag) => {
console.log(tag);
});
// bak.applyWorldDialogInstances = $app.applyWorldDialogInstances;
// $app.applyWorldDialogInstances = function (location) {
// bak.applyWorldDialogInstances();
// setTimeout(async () => { console.log("applyWorldDialogInstances"); }, 1000);
// }
// bak.applyGroupDialogInstances = $app.applyGroupDialogInstances;
// $app.applyGroupDialogInstances = function (location) {
// bak.applyGroupDialogInstances();
// setTimeout(async () => { console.log("applyGroupDialogInstances"); }, 1000);
// }
// var targetProxy = new Proxy(API.cachedInstances, {
// set: function (target, key, value) {
// console.log(`${key} set to ${value}`);
// target[key] = value;
// return true;
// }
// });
// bak.getInstance = API.getInstance;
// API.getInstance = async function(params) {
// let instance = bak.getInstance(params);
// setTimeout(async () => { console.log("getInstance"); }, 1000);
// console.log(instance);
// return instance;
// }
API.getInstance = function (params) {
return API.call(`instances/${params.worldId}:${params.instanceId}`, {
method: 'GET'
}).then((json) => {
var args = {
json,
params
};
const users = args.json.userCount;
const realUsers = args.json.n_users - args.json.queueSize;
args.json.invisiblePlayers = realUsers - users;
if (args.json.invisiblePlayers > 0) {
// args.json.name = `${args.json.name} (${args.json.invisiblePlayers} invisible)`
args.json.displayName = `${args.json.displayName??args.json.name} (${args.json.invisiblePlayers} invisible)`
setTimeout(async () => { log(`Found ${args.json.invisiblePlayers} potentially invisible players in instance "${args.json.name}" in world "${args.json.world.name}"`, false, true, true); }, 1000);
}
API.$emit('INSTANCE', args);
return args;
});
};
// API.$on('INSTANCE', function (args) {
// });
// endregion INSTANCE_CLIENTUSER_BADGE
// region BLOCKED_USER_LEAVE
// bak.playNoty = $app.methods.playNoty;
$app.playNoty = function (json) {
setTimeout(() => { bak.playNoty(json); }, 0);
let noty = json;
let message, image;
if (typeof json === 'string') noty, message, image = JSON.parse(json);
if (isEmpty(noty)) return;
const now = new Date().getTime();
const time = new Date(noty.created_at).getTime();
const diff = now - time;
// console.log({ noty: noty, time: time, now: now, diff: diff });
if (diff > 10000) {
return;
}
// noty = $utils.escapeTagRecursive(noty);
// message = $utils.escapeTag(message) || '';
switch (noty.type) {
case 'BlockedOnPlayerJoined':
// console.log(noty);
// setTimeout(async () => { await AppApi.QuitGame() }, 0);
console.log(noty.type, lastJoined);
if (isEmpty(lastJoined)) return;
const p = $app.parseLocation(lastJoined);
$app.newInstanceSelfInvite(p.worldId);
break;
case 'invite':
console.log(noty);
break;
}
}
// endregion BLOCKED_USER_LEAVE
// region CUSTOM_TAGS
setTimeout(async () => {
$app.addCustomTag({'UserId': API.currentUser.id, 'Tag': 'Sexy Mofo', 'TagColour': '#FF00C6'});
$app.addCustomTag({'UserId': 'usr_7e74337e-36c0-48f8-95d5-60e9f7b1d89d', 'Tag': 'Gaylord', 'TagColour': '#FF00C6'});
const msg = `VRCX-Utils started at\n ${getTimestamp()}`;
// $app.eventVrcxMessage({'MsgType': 'Noty', 'Data': msg });
log(msg, true, true, true);
// $app.eventVrcxMessage({'MsgType': 'External', 'UserId': API.currentUser.id, 'Data': 'External: VRCX-Utils started' });
}, 2500);
// endregion CUSTOM_TAGS
// region DISMISS_GROUP_NOTIFICATIONS
API.$on('NOTIFICATION', function (args) {
console.log("NOTIFICATION", args);
switch (args.json.type) {
case 'group.announcement':
hideNotification(args.params, false); // API.
seeNotification(args.params, false);
// $app.$emit('NOTIFICATION:EXPIRE', args);
// $app.$emit('NOTIFICATION:HIDE', args);
// $app.$emit('NOTIFICATION:SEE', args);
console.log(`Automatically dismissed group notification '${args.json.id}' from "${args.json.data.groupName}"`);
break;
}
});
// endregion DISMISS_GROUP_NOTIFICATIONS
// API.$on('NOTIFICATION:V2', function (args) {
// console.log('NOTIFICATION:V2');
// console.log(args);
// });
console.log("custom.js END")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment