require("dotenv").config(); var Discord = require("discord.js"); var vrchat = require("vrchat"); var exithook = require("async-exit-hook"); var client = new Discord.Client({intents: 32767}); var vrc = { configuration: { username: process.env.VRC_USERNAME, password: process.env.VRC_PASSWORD } }; vrc.authenticationApi = new vrchat.AuthenticationApi(vrc.configuration); vrc.usersApi = new vrchat.UsersApi(vrc.configuration); vrc.friendsApi = new vrchat.FriendsApi(vrc.configuration); vrc.worldsApi = new vrchat.WorldsApi(vrc.configuration); var VRCAT = "959236139913445376"; var SUBCH_PFX = '┗'; var status2icon = { "join me": '🔵', "active": '🟢', "ask me": '🟠', "busy": '🔴', "offline": '⚫' } var selfUserId; client.login(process.env.TOKEN).then(() => { console.log("discord login", client.user.tag); }).catch(error => { console.log("discord login fail", error.stack) }); client.once("ready", async () => { var vrcat = client.channels.resolve(VRCAT); if (!vrcat) return console.log("category not found"); for (let child of vrcat.children.values()) await child.delete(); exithook(async cb => { console.log("discord cleanup"); for (let child of vrcat.children.values()) await child.delete(); client.destroy(); console.log("discord destroyed"); cb(); }); try { var logindata = await vrc.authenticationApi.getCurrentUser().then(res => res.data); } catch (error) { console.log("vrc login fail:", JSON.stringify(error.response.data)); vrcat.createChannel("login failure", {type: "GUILD_VOICE"}).catch(()=>{}); } if (!logindata) return; console.log("vrc login", logindata.username); selfUserId = logindata.id; exithook(cb => { console.log("vrc logout"); vrc.authenticationApi.logout().then(res => { console.log("vrc", res.data); cb(); }); }); do { try { await syncStat(); } catch (error) { console.log("syncStat", error.message); if (error.response?.status == 401 /* && error.response.data.message == "\"Missing Credentials\"" /* todo? */) { console.log(error.response.data); try { var logindata = await vrc.authenticationApi.getCurrentUser().then(res => res.data); console.log("vrc relogin", logindata.username); selfUserId = logindata.id; } catch (error) { console.log("relogin fail", error.message, logindata); vrcat.createChannel("login failure", {type: "GUILD_VOICE"}).catch(()=>{}); await new Promise(r => setTimeout(r, 1000*60*60)); } } else { vrcat.createChannel(error.response?.status?.toString() || error.message, {type: "GUILD_VOICE"}).catch(()=>{}); } } await new Promise(r => setTimeout(r, 1000*60*5)); } while (true); }); async function syncStat() { var friendsOnline = await vrc.friendsApi.getFriends(0, 50, false).then(x => x.data); friendsOnline = friendsOnline.filter(x => x.location); var selfUser = await vrc.usersApi.getUser(selfUserId).then(res => res.data); if (selfUser.state == "online") friendsOnline.unshift(selfUser); //console.debug(friendsOnline.map(m => m.displayName)); for (let fren of friendsOnline) { try { let vcnam = `${status2icon[fren.status]} ${fren.displayName}`.substring(0,100); let subvcnam = `${SUBCH_PFX} ${await getWorldNameForId(fren.location.split(':')[0])}`.substring(0,100); let vc = client.channels.resolve(VRCAT).children.find(vc => vc.vrcuid == fren.id); if (vc) { if (vc.name != vcnam) await vc.setName(vcnam); if (vc.subvc && vc.subvc.name != subvcnam) await vc.subvc.setName(subvcnam); } else { vc = await client.channels.resolve(VRCAT).createChannel(vcnam, {type: "GUILD_VOICE"}); vc.subvc = await client.channels.resolve(VRCAT).createChannel(subvcnam, {type: "GUILD_VOICE"}); vc.vrcuid = fren.id; } } catch(error) { console.log(`fren ${fren.username} brok:`, error); } } for (let vc of client.channels.resolve(VRCAT).children.values()) { if (vc.vrcuid && !friendsOnline.some(fren => fren.id == vc.vrcuid)) { await vc.delete(); await vc.subvc?.delete(); } } } var worldnamecache = {}; async function getWorldNameForId(worldId) { if (!worldId || worldId == "offline") return; if (worldId == "private") return "Private World"; return worldnamecache[worldId] = worldnamecache[worldId] || (await vrc.worldsApi.getWorld(worldId)).data.name; } global.client = client; global.vrc = vrc; global.syncStat = syncStat;