Add club-security; rebuild kitchen-sink with the correct join event type

This commit is contained in:
Claude committed 2026-09-26 21:53:08 +02:00
1 parent 8ac2b8542b
commit 2303897787
5 files changed
+42 -3

No files matched your search

+4
View File
@@ -0,0 +1,4 @@
function l(n){return typeof n=="object"&&n!==null?n:void 0}function d(n){return typeof n=="string"?n:""}function G(n){return Array.isArray(n)?n.filter(e=>typeof e=="string"):[]}function J(n){return/~group\((grp_[0-9a-f-]+)\)/i.exec(n)?.[1]??""}function q(n){let e=l(n);if(e===void 0)return;let t=d(e.id),r=d(e.displayName);if(!(t===""&&r===""))return{id:t,displayName:r,ageVerified:e.ageVerified===!0,ageVerificationStatus:d(e.ageVerificationStatus),platform:d(e.platform),tags:G(e.tags),lastLogin:d(e.lastLogin),avatarId:d(e.avatarId),avatarName:d(e.avatarName)}}function N(n){let e=l(n);if(e===void 0)return;let t=d(e.location);if(t==="")return;let r=Array.isArray(e.users)?e.users:[];return{location:t,worldId:d(e.worldId),worldName:d(e.worldName),instanceType:d(e.instanceType),groupId:J(t),users:r.flatMap(i=>{let o=q(i);return o===void 0?[]:[o]})}}function A(n){return n.ageVerificationStatus!==""||n.tags.length>0||n.lastLogin!==""}function L(n){let e=l(n);if(e===void 0)return;let t=d(e.userId);if(t==="")return;let r=Array.isArray(e.groups)?e.groups:[];return{userId:t,groupIds:r.flatMap(i=>{let o=d(l(i)?.id);return o===""?[]:[o]})}}function $(n){let e=l(n);if(e===void 0)return;let t=d(e.userId);if(t!=="")return{userId:t,avatarId:d(e.avatarId),avatarName:d(e.avatarName)}}function _(n){let e=l(n);if(e===void 0)return;let t=d(e.id);if(t!=="")return{avatarId:t,name:d(e.name),pc:d(e.pcPerf),quest:d(e.questPerf)}}function F(n){return d(l(n)?.id)}function y(n,e,t,r){return new Promise(i=>{let o=!1,a=f=>{o||(o=!0,clearTimeout(u),c(),r.signal.removeEventListener("abort",s),i(f))},s=()=>{a(void 0)},u=setTimeout(()=>{a(void 0)},r.ms),c=n.on(e,f=>{let p=t(f);p!==void 0&&a(p)});r.signal.addEventListener("abort",s,{once:!0})})}function C(n,e){return n.users.find(t=>e.userId!==""?t.id===e.userId:t.displayName===e.name)}function W(){let n=document.getElementById("modalAvatarDetail");return n!==null&&n.offsetParent!==null}var h=class{#e;#n;#t=new Map;constructor(e){this.#e=e,e.events.on("vrcCurrentInstance",t=>{let r=N(t);r!==void 0&&(this.#n=r)}),e.events.on("vrcAvatarDetail",t=>{let r=_(t);r!==void 0&&this.#t.set(r.avatarId,{name:r.name,pc:r.pc,quest:r.quest})})}get instance(){return this.#n}async collect(e,t,r){let i=this.#e.signal,[o,a,s]=await Promise.all([this.#r(e,r,i),t===""?Promise.resolve(void 0):this.#i(e,t,r,i),this.#o(e,r,i)]);return{ageVerified:o===void 0?void 0:o.ageVerified,ageStatus:o?.ageVerificationStatus??"",platform:o?.platform??"",avatar:s,inGroup:a}}async#r(e,t,r){let i=this.#n===void 0?void 0:C(this.#n,e);return i!==void 0&&A(i)?i:await y(this.#e.events,"vrcCurrentInstance",a=>{let s=N(a),u=s===void 0?void 0:C(s,e);return u!==void 0&&A(u)?u:void 0},{ms:t,signal:r})??i}async#i(e,t,r,i){if(e.userId==="")return;let o=t.toLowerCase(),a=y(this.#e.events,"vrcGroupsForNetwork",u=>{let c=L(u);return c?.userId===e.userId?c.groupIds:void 0},{ms:r,signal:i});this.#e.bridge.send("vrcGetGroupsForNetwork",{userId:e.userId});let s=await a;return s===void 0?void 0:s.some(u=>u.toLowerCase()===o)}async#o(e,t,r){if(e.userId==="")return;let i=Date.now(),o=()=>Math.max(1e3,t-(Date.now()-i)),a=this.#n===void 0?"":C(this.#n,e)?.avatarId??"",s="";if(a===""){let p=y(this.#e.events,"vrcInstanceAvatarFound",O=>{let E=$(O);return E?.userId===e.userId?E:void 0},{ms:o(),signal:r});this.#e.bridge.send("vrcGetInstanceAvatars",{userIds:[e.userId]});let g=await p;if(g===void 0)return;a=g.avatarId,s=g.avatarName}if(a==="")return s===""?void 0:{name:s,pc:"",quest:""};let u=this.#t.get(a);if(u!==void 0)return u;if(W())return this.#e.logger.debug("Avatar modal is open; not fetching performance ranks behind it."),{name:s,pc:"",quest:""};let c=y(this.#e.events,"vrcAvatarDetail",p=>{let g=_(p);return g?.avatarId===a?g:void 0},{ms:o(),signal:r});this.#e.bridge.send("vrcGetAvatarDetail",{avatarId:a});let f=await c;return f===void 0?{name:s,pc:"",quest:""}:{name:f.name===""?s:f.name,pc:f.pc,quest:f.quest}}};function R(n){return n.split(/[,\s]+/).map(e=>e.trim().toLowerCase()).filter(e=>e.length>0)}function m(n){return{instanceTypes:R(n.instanceTypes),groupId:n.groupId.trim().toLowerCase(),worldIds:R(n.worldIds)}}function K(n,e){if(n.length===0)return!0;let t=e.toLowerCase();return n.some(r=>r===t||r==="group"&&t.startsWith("group"))}function I(n,e){if(!K(n.instanceTypes,e.instanceType))return!1;let t=n.groupId.toLowerCase();if(t!==""&&e.groupId.toLowerCase()!==t)return!1;let r=n.worldIds.map(i=>i.toLowerCase());return!(r.length>0&&!r.includes(e.worldId.toLowerCase()))}function V(n){let e=[];return n.instanceTypes.length>0&&e.push(`types: ${n.instanceTypes.join(", ")}`),n.groupId!==""&&e.push(`group: ${n.groupId}`),n.worldIds.length>0&&e.push(`worlds: ${String(n.worldIds.length)}`),e.length===0?"every instance":e.join(" \xB7 ")}function Y(n){try{let e=JSON.parse(n);if(typeof e!="object"||e===null||Array.isArray(e))return{};let t={};for(let[r,i]of Object.entries(e)){if(typeof i!="object"||i===null)continue;let o=i;t[r]={name:typeof o.name=="string"?o.Line truncated
`),timeoutSecs:8,icon:"security-high",sinks:s,urgency:v(t)==="warn"?"critical":"normal"});for(let c of u.failed)n.logger.warn(`Native target ${c.sink} failed: ${c.error}`);return}}if(n.notifications.desktopAvailable){n.notifications.desktop({title:o,subtitle:a.join(" \xB7 "),accent:v(t),...t.joiner.userId===""?{}:{friendId:t.joiner.userId}});return}n.logger.info("No desktop or VR channel is reachable: the native companion is not running and this platform has no tray toast.")}async function ee(n,e,t){if(!e.notifyDiscord)return;let r=e.discordWebhookUrl.trim();if(!X.test(r)){n.logger.warn("Discord webhook is enabled but the URL is not a discord.com webhook URL.");return}let i=k(t),o={ok:3908957,warn:15548997,info:5793266}[v(t)],a={username:"Club Security",embeds:[{title:i[0],description:i.slice(1).join(`
`),color:o,footer:{text:`${t.instance.worldName} \xB7 ${t.instance.instanceType}`},timestamp:new Date(t.at).toISOString()}],allowed_mentions:{parse:[]}},s=await fetch(r,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(a),signal:n.signal,credentials:"omit",referrerPolicy:"no-referrer"});s.ok||n.logger.warn(`Discord webhook answered ${String(s.status)}.`)}async function P(n,e){let t=n.settings.values;t.notifyToast&&n.notifications.toast({message:B(e),ok:v(e)!=="warn"});let r=await Promise.allSettled([Z(n,t,e),ee(n,t,e)]);for(let i of r)i.status==="rejected"&&n.logger.warn(`Notification channel failed: ${String(i.reason)}`)}var j=25,x=class{#e;#n;#t=[];#r;#i;constructor(e,t){this.#e=e,this.#n=t}install(){this.#e.ui.addNavTab({label:"Club Security",icon:"security",render:e=>{this.#o(e)}}),this.#e.settings.onChange(()=>{this.refresh()})}push(e){this.#t.unshift(e),this.#t.length>j&&(this.#t.length=j),this.refresh()}refresh(){let e=this.#e.ui.kit;this.#r!==void 0&&e.setChildren(this.#r,this.#s()),this.#i!==void 0&&e.setChildren(this.#i,this.#a())}#o(e){let t=this.#e.ui.kit;this.#r=t.card({title:"Status",icon:"shield"}),this.#i=t.card({title:"Recent reports",icon:"history"});let r=t.card({title:"Actions",icon:"build",children:[t.description("A test report uses the current instance and your own name, and goes to every enabled channel."),t.buttonRow(t.button({label:"Send test notification",icon:"send",onClick:()=>{this.#n.sendTest()}}),t.button({label:"Forget who joined before",icon:"delete_sweep",onClick:()=>{this.#d()}}))]});e.append(t.layout(t.pair(this.#r,r),this.#i)),this.refresh()}#s(){let e=this.#e.ui.kit,t=this.#e.settings.values,r=m(t),i=this.#n.currentInstance(),o=i!==void 0&&I(r,i),a=[t.notifyToast&&"toast",t.notifyDesktop&&"desktop",t.notifyVr&&"VR",t.notifyDiscord&&"Discord"].filter(s=>typeof s=="string");return[e.row({label:"Reporting",value:t.enabled?e.badge("ok","On"):e.badge("neutral","Off")}),e.row({label:"Watching",detail:V(r)}),e.row({label:"Current instance",detail:i===void 0?"Not in an instance":`${i.worldName} \xB7 ${i.instanceType}`,value:i===void 0?void 0:e.badge(o?"ok":"neutral",o?"Matches":"Filtered out")}),e.row({label:"Channels",detail:a.length===0?"none enabled":a.join(", ")}),e.row({label:"Players remembered",value:e.valueText(String(this.#n.memory.size))})]}#a(){let e=this.#e.ui.kit;return this.#t.length===0?[e.emptyState("No joins reported yet.")]:this.#t.map(t=>{let r=k(t),i=new Date(t.at).toLocaleTimeString();return e.row({label:`${i} \xB7 ${t.joiner.name}`,detail:r.slice(1).join(" \xB7 "),value:t.previous===void 0?e.badge("accent","New"):e.badge("warn","Rejoin")})})}async#d(){await this.#e.notifications.confirm({title:"Forget join history",message:"Every player will count as a first-time joiner again. Continue?",confirmLabel:"Forget",icon:"delete_sweep"})&&(await this.#n.memory.clear(),this.#e.notifications.toast({message:"Join history cleared."}),this.refresh())}};var M={enabled:{kind:"boolean",label:"Report joins",description:"Master switch. Filters below decide which instances count.",default:!0},instanceTypes:{kind:"string",label:"Only in these instance types",description:'Comma-separated. Leave empty for every instance. Types: public, friends+, friends, hidden, private, invite_plus, group-public, group-plus, group-members, or just "group" for all group instances.',default:"",placeholder:"group-public, group-plus"},groupId:{kind:"string",label:"Only in instances of this group",description:"A grp_\u2026 id. Leave empty for any group. When set, the report also says whether the joiner is a member.",default:"",placeholder:"grp_00000000-0000-0000-0000-000000000000"},worldIds:{kind:"string",label:"Only in these worlds",description:"Comma-separated wrld_\u2026 ids. Leave empty for any world.",default:"",placeholder:"wrld_\u2026, wrld_\u2026"},collectTimeoutSecs:{kind:"number",label:"Seconds to wait for details",description:"How long to wait for VRCNext to load the profile and avatar before reporting what is known.",default:25,min:5,max:120,step:5},notifyToast:{kind:"boolean",label:"In-app toast",default:!0},notifyDesktop:{kind:"boolean",label:"Desktop notification",description:"Through the native companion on any platform, or VRCNext\u2019s tray toast on Windows.",default:!0},notifyVr:{kind:"boolean",label:"VR overlay notification",description:"Through the native companion\u2019s VR target on any platform, or the SteamVR wrist overlay on Windows.",default:!0},notifyDiscord:{kind:"boolean",label:"Discord webhook",default:!1},discordWebhookUrl:{kind:"string",label:"Discord webhook URL",description:"Server Settings \u2192 Integrations \u2192 Webhooks. Sent straight from this page; the URL is stored locally only.",default:"",placeholder:"https://discord.com/api/webhooks/\u2026"},memory:{kind:"string",label:"Join memory",description:"Internal: who has joined a matching instance before.",default:"{}",hidden:!0}};var ne="gl_player_join";function te(){let n=glob Line truncated
//# sourceMappingURL=club-security.js.map
+7
View File
@@ -0,0 +1,7 @@
{
"version": 3,
"sources": ["../src/vrcnext-data.ts", "../src/collector.ts", "../src/filters.ts", "../src/memory.ts", "../src/notify.ts", "../src/panel.ts", "../src/settings.ts", "../src/index.ts"],
"sourcesContent": ["/**\n * Narrowers for the VRCNext events this plugin consumes.\n *\n * None of these events are in the API's verified map, so every payload arrives as `unknown`.\n * Shapes were read out of VRCNext 2026.61.2: `InstanceController.PushCurrentInstanceFromCache`,\n * `GroupsController` (`vrcGetGroupsForNetwork`), `FriendsController` (`vrcGetInstanceAvatars`)\n * and `MessageRouter` (`vrcGetAvatarDetail`).\n */\n\nexport interface InstanceUser {\n readonly id: string;\n readonly displayName: string;\n readonly ageVerified: boolean;\n /** VRChat's own status string, e.g. `18+`, `verified`, `hidden`. Empty until the profile loaded. */\n readonly ageVerificationStatus: string;\n readonly platform: string;\n /** Present once VRCNext fetched the profile; an empty list means it has not yet. */\n readonly tags: readonly string[];\n readonly lastLogin: string;\n readonly avatarId: string;\n readonly avatarName: string;\n}\n\nexport interface CurrentInstance {\n readonly location: string;\n readonly worldId: string;\n readonly worldName: string;\n readonly instanceType: string;\n /** Parsed from `~group(grp_\u2026)` in the location; empty for non-group instances. */\n readonly groupId: string;\n readonly users: readonly InstanceUser[];\n}\n\nfunction rec(value: unknown): Record<string, unknown> | undefined {\n return typeof value === 'object' && value !== null ? (value as Record<string, unknown>) : undefined;\n}\n\nfunction str(value: unknown): string {\n return typeof value === 'string' ? value : '';\n}\n\nfunction strings(value: unknown): readonly string[] {\n return Array.isArray(value) ? value.filter((v): v is string => typeof v === 'string') : [];\n}\n\nexport function groupIdOf(location: string): string {\n const match = /~group\\((grp_[0-9a-f-]+)\\)/i.exec(location);\n return match?.[1] ?? '';\n}\n\nfunction toInstanceUser(value: unknown): InstanceUser | undefined {\n const r = rec(value);\n if (r === undefined) return undefined;\n const id = str(r['id']);\n const displayName = str(r['displayName']);\n if (id === '' && displayName === '') return undefined;\n return {\n id,\n displayName,\n ageVerified: r['ageVerified'] === true,\n ageVerificationStatus: str(r['ageVerificationStatus']),\n platform: str(r['platform']),\n tags: strings(r['tags']),\n lastLogin: str(r['lastLogin']),\n avatarId: str(r['avatarId']),\n avatarName: str(r['avatarName']),\n };\n}\n\n/** `undefined` for the `{ empty: true }` and `{ error }` variants. */\nexport function toCurrentInstance(payload: unknown): CurrentInstance | undefined {\n const r = rec(payload);\n if (r === undefined) return undefined;\n const location = str(r['location']);\n if (location === '') return undefined;\n const rawUsers = Array.isArray(r['users']) ? r['users'] : [];\n return {\n location,\n worldId: str(r['worldId']),\n worldName: str(r['worldName']),\n instanceType: str(r['instanceType']),\n groupId: groupIdOf(location),\n users: rawUsers.flatMap((u) => {\n const user = toInstanceUser(u);\n return user === undefined ? [] : [user];\n }),\n };\n}\n\n/** VRCNext only fills these once it has fetched the profile from the API. */\nexport function profileLoaded(user: InstanceUser): boolean {\n return user.ageVerificationStatus !== '' || user.tags.length > 0 || user.lastLogin !== '';\n}\n\nexport interface UserGroups {\n readonly userId: string;\n readonly groupIds: readonly string[];\n}\n\nexport function toUserGroups(payload: unknown): UserGroups | undefined {\n const r = rec(payload);\n if (r === undefined) return undefined;\n const userId = str(r['userId']);\n if (userId === '') return undefined;\n const groups = Array.isArray(r['groups']) ? r['groups'] : [];\n return {\n userId,\n groupIds: groups.flatMap((g) => {\n const id = str(rec(g)?.['id']);\n return id === '' ? [] : [id];\n }),\n };\n}\n\nexport interface InstanceAvatar {\n readonly userId: string;\n readonly avatarId: string;\n readonly avatarName: string;\n}\n\nexport function toInstanceAvatar(payload: unknown): InstanceAvatar | undefined {\n const r = rec(payload);\n if (r === undefined) return undefined;\n const userId = str(r['userId']);\n if (userId === '') return undefined;\n return { userId, avatarId: str(r['avatarId']), avatarName: str(r['avatarName']) };\n}\n\nexport interface AvatarPerformance {\n readonly avatarId: string;\n readonly name: string;\n readonly pc: string;\n readonly quest: string;\n}\n\nexport function toAvatarPerformance(payload: unknown): AvatarPerformance | undefined {\n const r = rec(payload);\n if (r === undefined) return undefined;\n const avatarId = str(r['id']);\n if (avatarId === '') return undefined;\n return {\n avatarId,\n name: str(r['name']),\n pc: str(r['pcPerf']),\n quest: str(r['questPerf']),\n };\n}\n\n/** The signed-in account, from the `vrcUser` event VRCNext sends after login. */\nexport fuLine truncated
"mappings": "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 truncated
"names": ["rec", "value", "str", "strings", "v", "groupIdOf", "location", "toInstanceUser", "r", "id", "displayName", "toCurrentInstance", "payload", "rawUsers", "u", "user", "profileLoaded", "toUserGroups", "userId", "groups", "g", "toInstanceAvatar", "toAvatarPerformance", "avatarId", "toSelfId", "waitFor", "events", "type", "pick", "options", "resolve", "done", "finish", "value", "timer", "unsubscribe", "onAbort", "payload", "findUser", "instance", "joiner", "u", "avatarModalOpen", "modal", "FactCollector", "#ctx", "#latest", "#perfCache", "ctx", "toCurrentInstance", "perf", "toAvatarPerformance", "groupId", "deadlineMs", "signal", "profile", "inGroup", "avatar", "#profile", "#membership", "#avatar", "ms", "current", "profileLoaded", "user", "wanted", "pending", "groups", "toUserGroups", "groupIds", "id", "started", "remaining", "avatarId", "avatarName", "found", "toInstanceAvatar", "cached", "splitList", "value", "s", "filterFrom", "values", "typeMatches", "wanted", "actual", "type", "w", "instanceMatches", "filter", "instance", "groupId", "worldIds", "describeFilter", "parts", "parse", "raw", "value", "out", "key", "entry", "e", "trimmed", "map", "entries", "a", "b", "JoinMemory", "#store", "#map", "store", "name", "location", "before", "now", "DISCORD_WEBHOOK", "yesNo", "value", "unknown", "rank", "rejoinText", "previous", "when", "reportLines", "report", "facts", "age", "lines", "reportSummary", "bits", "accentFor", "nativeSinks", "ctx", "wantDesktop", "wantVr", "t", "sendDesktopAndVr", "values", "title", "sinks", "result", "failure", "sendDiscord", "url", "colour", "body", "response", "notifyAll", "results", "MAX_SHOWN", "ReportPanel", "#ctx", "#deps", "#reports", "#status", "#list", "ctx", "deps", "tab", "#render", "report", "k", "#statusRows", "#reportRows", "actions", "#clearMemory", "values", "filter", "filterFrom", "instance", "matches", "instanceMatches", "channels", "c", "describeFilter", "lines", "reportLines", "time", "settings", "JOIN_EVENT", "selfIdFromPage", "user", "id", "ClubSecurity", "#ctx", "#collector", "#memory", "#panel", "#selfId", "#selfName", "#inFlight", "ctx", "FactCollector", "JoinMemory", "ReportPanel", "#sendTest", "payload", "toSelfId", "name", "entry", "#onJoin", "joiner", "values", "#isSelf", "instance", "filter", "filterFrom", "instanceMatches", "key", "previous", "facts", "report", "notifyAll", "error", "me", "index_default", "settings"]
}
+1 -1
View File
@@ -37,5 +37,5 @@ var a=`
.ks-log-line { white-space: pre-wrap; word-break: break-word; } .ks-log-line { white-space: pre-wrap; word-break: break-word; }
.ks-log-line + .ks-log-line { margin-top: 3px; } .ks-log-line + .ks-log-line { margin-top: 3px; }
`;function d(e){let t=document.createElement("div");return t.className="ks-log",e.appendChild(t),{append(n){let i=document.createElement("div");i.className="ks-log-line";let o=new Date().toLocaleTimeString();for(i.textContent=`${o} ${n}`,t.appendChild(i);t.childElementCount>200;)t.firstElementChild?.remove();t.scrollHeight-t.scrollTop-t.clientHeight<40&&(t.scrollTop=t.scrollHeight)},clear(){t.replaceChildren()}}}var u={watchGameLog:{kind:"boolean",label:"Follow the VRChat game log",description:"Streams player joins and world changes into the panel below.",default:!0},oscParameter:{kind:"string",label:"OSC parameter to pulse",description:"Sent to /avatar/parameters/<name> by the dashboard button.",default:"VRCEmote",placeholder:"VRCEmote"},oscValue:{kind:"number",label:"OSC value",default:1,min:0,max:255,step:1},verbosity:{kind:"select",label:"Log verbosity",default:"normal",options:[{value:"quiet",label:"Quiet"},{value:"normal",label:"Normal"},{value:"loud",label:"Everything"}]}},pe={id:"kitchen-sink",settings:u,activate(e){let t={gameLogCount:0,oscParamCount:0,panel:void 0,log:n=>{e.settings.get("verbosity")!=="quiet"&&(e.logger.info(n),t.panel?.append(n))}};e.ui.injectCss(a),p(e),h(e,t),v(e,t),b(e,t),y(e,t),S(e,t),C(e,t),w(e,t),k(e,t),N(e,t),e.logger.info(`Kitchen Sink v${e.version} ready.`)},deactivate(){}};function p(e){e.disposables.add(e.settings.onChange(t=>{e.logger.debug(`Settings changed; verbosity is now ${t.verbosity}.`)}))}function h(e,t){e.events.on("friendTimelineEvent",n=>{t.log(`[friend] ${n.friendName} \u2192 ${n.type}`)}),e.events.on("vrcCurrentInstance",n=>{if(typeof n!="object"||n===null)return;let i=n.worldName;typeof i=="string"&&t.log(`[instance] now in ${i}`)}),e.events.onAny(n=>{e.settings.get("verbosity")==="loud"&&e.logger.debug(`event ${n.type}`)})}function v(e,t){e.gameLog.on(n=>{e.settings.get("watchGameLog")&&(t.gameLogCount+=1,t.log(`[gamelog] ${n.type}: ${n.message}`))}),e.gameLog.onType("OnPlayerJoined",n=>{t.log(`[join] ${n.detail||n.message}`)}),e.gameLog.history(e.signal).then(n=>{e.logger.info(`Game log backlog: ${String(n.length)} entries.`)}).catch(n=>{e.signal.aborted||e.logger.warn(`Could not read the game log: ${String(n)}`)})}function b(e,t){if(!e.osc.available){t.log("[osc] OSC is Windows-only in VRCNext; skipping.");return}e.osc.connect(),e.osc.onParam(n=>{t.oscParamCount+=1,e.settings.get("verbosity")==="loud"&&t.log(`[osc] ${n.name} = ${String(n.value)}`)}),e.osc.onAvatarChange(n=>{t.log(`[osc] avatar ${n.avatarId} with ${String(n.parameters.length)} parameters`)})}function c(e){if(!e.osc.available)return"OSC unavailable (Windows only)";let t=e.settings.get("oscParameter"),n=e.settings.get("oscValue");return e.osc.send(t,"int",n),`${t} = ${String(n)}`}function y(e,t){e.deepLinks.onPrefix("wrld",n=>{t.log(`[link] world ${n.id}`)}),e.deepLinks.on(n=>{t.log(`[link] ${n.prefix}:${n.id}${n.action?` (${n.action})`:""}`)})}function S(e,t){e.router.get("stats",()=>Response.json({gameLogEvents:t.gameLogCount,oscParams:t.oscParamCount,verbosity:e.settings.get("verbosity")})),e.router.get("greet/:name",n=>Response.json({hello:n.params.name??"world"})),e.router.post("osc/pulse",()=>Response.json({sent:c(e)})),e.logger.info(`Routes mounted at ${e.router.base.pathname}`)}function C(e,t){e.contextMenu.contribute(n=>{let i=[{kind:"divider"},{kind:"item",icon:"auto_awesome",label:"Kitchen Sink: log this element",onSelect:()=>{t.log(`[ctx] ${n.element.tagName.toLowerCase()}`)}},{kind:"submenu",icon:"tune",label:"Kitchen Sink: verbosity",items:()=>u.verbosity.options.map(o=>({kind:"item",icon:"adjust",label:o.label,checked:e.settings.get("verbosity")===o.value,onSelect:()=>{e.settings.set("verbosity",o.value)}}))}];if(n.entity!==void 0){let o=n.entity;i.push({kind:"item",icon:"content_copy",label:`Copy ${o.type} id`,onSelect:()=>{navigator.clipboard.writeText(o.id)}})}return i})}function w(e,t){e.gameLog.onType("OnPlayerJoined",n=>{let i=n.detail||n.message;e.notifications.toast({message:`${i} joined.`}),e.notifications.desktopAvailable?e.notifications.desktop({title:"Player joined",subtitle:i,accent:"info"}):t.log("[notify] desktop/VR notifications are Windows-only.")}),e.events.on("friendTimelineEvent",n=>{n.type==="online"&&e.notifications.notifToast({kind:"notification",sender:n.friendName,message:"came online"})})}async function k(e,t){if(!await e.native.ready){t.log(`[native] no companion at ${e.native.endpoint}; VR targets unavailable.`);return}let n=await e.native.targets();t.log(`[native] targets: ${n.map(o=>o.name).join(", ")||"none"}`),e.gameLog.onType("OnPlayerJoined",o=>{let s=o.detail||o.message;e.native.notify({title:"Player joined",content:s,timeoutSecs:4,icon:"user-available",overrides:{wayvr:{content:`${s} joined the instance`,height:200,opacity:.85,alwaysShow:!0}}})}),e.events.on("friendTimelineEvent",o=>{o.type==="online"&&e.native.notify({title:o.friendName,content:"came online",sinks:["wayvr"],timeoutSecs:3,opacity:.7})});let i=await e.native.describe(); Line truncated `;function l(e){let n=document.createElement("div");return n.className="ks-log",e.appendChild(n),{append(t){let i=document.createElement("div");i.className="ks-log-line";let o=new Date().toLocaleTimeString();for(i.textContent=`${o} ${t}`,n.appendChild(i);n.childElementCount>200;)n.firstElementChild?.remove();n.scrollHeight-n.scrollTop-n.clientHeight<40&&(n.scrollTop=n.scrollHeight)},clear(){n.replaceChildren()}}}var u={watchGameLog:{kind:"boolean",label:"Follow the VRChat game log",description:"Streams player joins and world changes into the panel below.",default:!0},oscParameter:{kind:"string",label:"OSC parameter to pulse",description:"Sent to /avatar/parameters/<name> by the dashboard button.",default:"VRCEmote",placeholder:"VRCEmote"},oscValue:{kind:"number",label:"OSC value",default:1,min:0,max:255,step:1},verbosity:{kind:"select",label:"Log verbosity",default:"normal",options:[{value:"quiet",label:"Quiet"},{value:"normal",label:"Normal"},{value:"loud",label:"Everything"}]}},be={id:"kitchen-sink",settings:u,activate(e){let n={gameLogCount:0,oscParamCount:0,panel:void 0,log:t=>{e.settings.get("verbosity")!=="quiet"&&(e.logger.info(t),n.panel?.append(t))}};e.ui.injectCss(a),m(e),h(e,n),v(e,n),b(e,n),y(e,n),S(e,n),C(e,n),_(e,n),w(e,n),k(e,n),e.logger.info(`Kitchen Sink v${e.version} ready.`)},deactivate(){}};function m(e){e.disposables.add(e.settings.onChange(n=>{e.logger.debug(`Settings changed; verbosity is now ${n.verbosity}.`)}))}function h(e,n){e.events.on("friendTimelineEvent",t=>{n.log(`[friend] ${t.friendName} \u2192 ${t.type}`)}),e.events.on("vrcCurrentInstance",t=>{if(typeof t!="object"||t===null)return;let i=t.worldName;typeof i=="string"&&n.log(`[instance] now in ${i}`)}),e.events.onAny(t=>{e.settings.get("verbosity")==="loud"&&e.logger.debug(`event ${t.type}`)})}function v(e,n){e.gameLog.on(t=>{e.settings.get("watchGameLog")&&(n.gameLogCount+=1,n.log(`[gamelog] ${t.type}: ${t.message}`))}),e.gameLog.onType("gl_player_join",t=>{n.log(`[join] ${t.message}`)}),e.gameLog.history(e.signal).then(t=>{e.logger.info(`Game log backlog: ${String(t.length)} entries.`)}).catch(t=>{e.signal.aborted||e.logger.warn(`Could not read the game log: ${String(t)}`)})}function b(e,n){if(!e.osc.available){n.log("[osc] OSC is Windows-only in VRCNext; skipping.");return}e.osc.connect(),e.osc.onParam(t=>{n.oscParamCount+=1,e.settings.get("verbosity")==="loud"&&n.log(`[osc] ${t.name} = ${String(t.value)}`)}),e.osc.onAvatarChange(t=>{n.log(`[osc] avatar ${t.avatarId} with ${String(t.parameters.length)} parameters`)})}function c(e){if(!e.osc.available)return"OSC unavailable (Windows only)";let n=e.settings.get("oscParameter"),t=e.settings.get("oscValue");return e.osc.send(n,"int",t),`${n} = ${String(t)}`}function y(e,n){e.deepLinks.onPrefix("wrld",t=>{n.log(`[link] world ${t.id}`)}),e.deepLinks.on(t=>{n.log(`[link] ${t.prefix}:${t.id}${t.action?` (${t.action})`:""}`)})}function S(e,n){e.router.get("stats",()=>Response.json({gameLogEvents:n.gameLogCount,oscParams:n.oscParamCount,verbosity:e.settings.get("verbosity")})),e.router.get("greet/:name",t=>Response.json({hello:t.params.name??"world"})),e.router.post("osc/pulse",()=>Response.json({sent:c(e)})),e.logger.info(`Routes mounted at ${e.router.base.pathname}`)}function C(e,n){e.contextMenu.contribute(t=>{let i=[{kind:"divider"},{kind:"item",icon:"auto_awesome",label:"Kitchen Sink: log this element",onSelect:()=>{n.log(`[ctx] ${t.element.tagName.toLowerCase()}`)}},{kind:"submenu",icon:"tune",label:"Kitchen Sink: verbosity",items:()=>u.verbosity.options.map(o=>({kind:"item",icon:"adjust",label:o.label,checked:e.settings.get("verbosity")===o.value,onSelect:()=>{e.settings.set("verbosity",o.value)}}))}];if(t.entity!==void 0){let o=t.entity;i.push({kind:"item",icon:"content_copy",label:`Copy ${o.type} id`,onSelect:()=>{navigator.clipboard.writeText(o.id)}})}return i})}function _(e,n){e.gameLog.onType("gl_player_join",t=>{let i=t.message;e.notifications.toast({message:`${i} joined.`}),e.notifications.desktopAvailable?e.notifications.desktop({title:"Player joined",subtitle:i,accent:"info"}):n.log("[notify] desktop/VR notifications are Windows-only.")}),e.events.on("friendTimelineEvent",t=>{t.type==="online"&&e.notifications.notifToast({kind:"notification",sender:t.friendName,message:"came online"})})}async function w(e,n){if(!await e.native.ready){n.log(`[native] no companion at ${e.native.endpoint}; VR targets unavailable.`);return}let t=await e.native.targets();n.log(`[native] targets: ${t.map(o=>o.name).join(", ")||"none"}`),e.gameLog.onType("gl_player_join",o=>{let s=o.message;e.native.notify({title:"Player joined",content:s,timeoutSecs:4,icon:"user-available",overrides:{wayvr:{content:`${s} joined the instance`,height:200,opacity:.85,alwaysShow:!0}}})}),e.events.on("friendTimelineEvent",o=>{o.type==="online"&&e.native.notify({title:o.friendName,content:"came online",sinks:["wayvr"],timeoutSecs:3,opacity:.7})});let i=await e.native.describe();i!==void 0&&n.log(`[native] coLine truncated
//# sourceMappingURL=kitchen-sink.js.map //# sourceMappingURL=kitchen-sink.js.map
+2 -2
View File
@@ -1,7 +1,7 @@
{ {
"version": 3, "version": 3,
"sources": ["../src/styles.ts", "../src/log-panel.ts", "../src/index.ts"], "sources": ["../src/styles.ts", "../src/log-panel.ts", "../src/index.ts"],
"sourcesContent": ["/**\n * Stylesheet injected by the plugin.\n *\n * Colours come from VRCNext's CSS custom properties rather than literals, so the plugin follows\n * the user's theme \u2014 including custom themes \u2014 instead of fighting it.\n */\n\nexport const PLUGIN_CSS = `\n.ks-grid {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));\n gap: 10px;\n padding: 10px 0;\n}\n\n.ks-stat {\n background: var(--bg-input);\n border-radius: 10px;\n padding: 12px 14px;\n}\n\n.ks-stat-label {\n font-size: calc(11px + var(--fs-off, 0px));\n color: var(--tx3);\n}\n\n.ks-stat-value {\n font-size: calc(20px + var(--fs-off, 0px));\n font-weight: 600;\n color: var(--tx0);\n font-variant-numeric: tabular-nums;\n}\n\n.ks-log {\n max-height: 220px;\n overflow-y: auto;\n font-family: ui-monospace, monospace;\n font-size: calc(11px + var(--fs-off, 0px));\n color: var(--tx2);\n background: var(--bg-input);\n border-radius: 8px;\n padding: 8px 10px;\n}\n\n.ks-log-line { white-space: pre-wrap; word-break: break-word; }\n.ks-log-line + .ks-log-line { margin-top: 3px; }\n`;\n", "/**\n * Small scrolling log view used by the Kitchen Sink tab.\n *\n * Kept in its own module to show the normal pattern: UI helpers separate from the plugin's\n * lifecycle wiring, so `index.ts` stays readable.\n */\n\nconst MAX_LINES = 200;\n\nexport interface LogPanel {\n append(message: string): void;\n clear(): void;\n}\n\nexport function createLogPanel(parent: HTMLElement): LogPanel {\n const list = document.createElement('div');\n list.className = 'ks-log';\n parent.appendChild(list);\n\n return {\n append(message: string): void {\n const line = document.createElement('div');\n line.className = 'ks-log-line';\n const time = new Date().toLocaleTimeString();\n line.textContent = `${time} ${message}`;\n list.appendChild(line);\n\n while (list.childElementCount > MAX_LINES) list.firstElementChild?.remove();\n\n // Only chase the tail when the user has not scrolled up to read history.\n const atBottom = list.scrollHeight - list.scrollTop - list.clientHeight < 40;\n if (atBottom) list.scrollTop = list.scrollHeight;\n },\n\n clear(): void {\n list.replaceChildren();\n },\n };\n}\n", "/**\n * Kitchen Sink \u2014 the reference VRCNext plugin.\n *\n * Exercises every capability the host provides, in the order a plugin author is likely to need\n * them. Each section is small and commented so it can be copied out on its own.\n */\n\nimport {\n definePlugin,\n type ContextMenuEntry,\n type PluginContext,\n type PluginId,\n type SettingsSchema,\n} from '@vrcnext/plugin-api';\n\nimport { PLUGIN_CSS } from './styles.js';\nimport { createLogPanel, type LogPanel } from './log-panel.js';\n\nconst settings = {\n watchGameLog: {\n kind: 'boolean',\n label: 'Follow the VRChat game log',\n description: 'Streams player joins and world changes into the panel below.',\n default: true,\n },\n oscParameter: {\n kind: 'string',\n label: 'OSC parameter to pulse',\n description: 'Sent to /avatar/parameters/<name> by the dashboard button.',\n default: 'VRCEmote',\n placeholder: 'VRCEmote',\n },\n oscValue: {\n kind: 'number',\n label: 'OSC value',\n default: 1,\n min: 0,\n max: 255,\n step: 1,\n },\n verbosity: {\n kind: 'select',\n label: 'Log verbosity',\n default: 'normal',\n options: [\n { value: 'quiet', label: 'Quiet' },\n { value: 'normal', label: 'Normal' },\n { value: 'loud', label: 'Everything' },\n ],\n },\n} as const satisfies SettingsSchema;\n\ntype Ctx = PluginContext<typeof settings>;\n\n/** Shared mutable state for the session. Reset each activation. */\ninterface State {\n gameLogCount: number;\n oscParamCount: number;\n panel: LogPanel | undefined;\n /** Writes to both the plugin logger and the on-screen panel, honouring `verbosity`. */\n log(message: string): void;\n}\n\nexport default definePlugin({\n id: 'kitchen-sink' as PluginId,\n settings,\n\n activate(ctx) {\n const state: State = {\n gameLogCount: 0,\n oscParamCount: 0,\n panel: undefined,\n log: (message) => {\n if (ctx.settings.get('verbosity') === 'quiet') return;\n ctx.logger.info(message);\n state.panel?.append(message);\n },\n };\n\n // 1. Custom CSS, removed automatically on deactivate.\n ctx.ui.injectCss(PLUGIN_CSS);\n\n installSettingsWatch(ctx);\n installEvents(ctx, state);\n installGameLog(ctx, state);\n installOsc(ctx, state);\n installDeepLinks(ctx, state);\n installRoutes(ctx, state);\n installContextMenu(ctx, state);\n installNotifications(ctx, state);\n // Fire-and-forget: the companion probe must not hold up the rest of activation.\n void installNative(ctx, state);\n installUi(ctx, state);\n\n ctx.logger.info(`Kitchen Sink v${ctx.version} ready.`);\n },\n\n deactivate() {\nLine truncated "sourcesContent": ["/**\n * Stylesheet injected by the plugin.\n *\n * Colours come from VRCNext's CSS custom properties rather than literals, so the plugin follows\n * the user's theme \u2014 including custom themes \u2014 instead of fighting it.\n */\n\nexport const PLUGIN_CSS = `\n.ks-grid {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));\n gap: 10px;\n padding: 10px 0;\n}\n\n.ks-stat {\n background: var(--bg-input);\n border-radius: 10px;\n padding: 12px 14px;\n}\n\n.ks-stat-label {\n font-size: calc(11px + var(--fs-off, 0px));\n color: var(--tx3);\n}\n\n.ks-stat-value {\n font-size: calc(20px + var(--fs-off, 0px));\n font-weight: 600;\n color: var(--tx0);\n font-variant-numeric: tabular-nums;\n}\n\n.ks-log {\n max-height: 220px;\n overflow-y: auto;\n font-family: ui-monospace, monospace;\n font-size: calc(11px + var(--fs-off, 0px));\n color: var(--tx2);\n background: var(--bg-input);\n border-radius: 8px;\n padding: 8px 10px;\n}\n\n.ks-log-line { white-space: pre-wrap; word-break: break-word; }\n.ks-log-line + .ks-log-line { margin-top: 3px; }\n`;\n", "/**\n * Small scrolling log view used by the Kitchen Sink tab.\n *\n * Kept in its own module to show the normal pattern: UI helpers separate from the plugin's\n * lifecycle wiring, so `index.ts` stays readable.\n */\n\nconst MAX_LINES = 200;\n\nexport interface LogPanel {\n append(message: string): void;\n clear(): void;\n}\n\nexport function createLogPanel(parent: HTMLElement): LogPanel {\n const list = document.createElement('div');\n list.className = 'ks-log';\n parent.appendChild(list);\n\n return {\n append(message: string): void {\n const line = document.createElement('div');\n line.className = 'ks-log-line';\n const time = new Date().toLocaleTimeString();\n line.textContent = `${time} ${message}`;\n list.appendChild(line);\n\n while (list.childElementCount > MAX_LINES) list.firstElementChild?.remove();\n\n // Only chase the tail when the user has not scrolled up to read history.\n const atBottom = list.scrollHeight - list.scrollTop - list.clientHeight < 40;\n if (atBottom) list.scrollTop = list.scrollHeight;\n },\n\n clear(): void {\n list.replaceChildren();\n },\n };\n}\n", "/**\n * Kitchen Sink \u2014 the reference VRCNext plugin.\n *\n * Exercises every capability the host provides, in the order a plugin author is likely to need\n * them. Each section is small and commented so it can be copied out on its own.\n */\n\nimport {\n definePlugin,\n type ContextMenuEntry,\n type PluginContext,\n type PluginId,\n type SettingsSchema,\n} from '@vrcnext/plugin-api';\n\nimport { PLUGIN_CSS } from './styles.js';\nimport { createLogPanel, type LogPanel } from './log-panel.js';\n\nconst settings = {\n watchGameLog: {\n kind: 'boolean',\n label: 'Follow the VRChat game log',\n description: 'Streams player joins and world changes into the panel below.',\n default: true,\n },\n oscParameter: {\n kind: 'string',\n label: 'OSC parameter to pulse',\n description: 'Sent to /avatar/parameters/<name> by the dashboard button.',\n default: 'VRCEmote',\n placeholder: 'VRCEmote',\n },\n oscValue: {\n kind: 'number',\n label: 'OSC value',\n default: 1,\n min: 0,\n max: 255,\n step: 1,\n },\n verbosity: {\n kind: 'select',\n label: 'Log verbosity',\n default: 'normal',\n options: [\n { value: 'quiet', label: 'Quiet' },\n { value: 'normal', label: 'Normal' },\n { value: 'loud', label: 'Everything' },\n ],\n },\n} as const satisfies SettingsSchema;\n\ntype Ctx = PluginContext<typeof settings>;\n\n/** Shared mutable state for the session. Reset each activation. */\ninterface State {\n gameLogCount: number;\n oscParamCount: number;\n panel: LogPanel | undefined;\n /** Writes to both the plugin logger and the on-screen panel, honouring `verbosity`. */\n log(message: string): void;\n}\n\nexport default definePlugin({\n id: 'kitchen-sink' as PluginId,\n settings,\n\n activate(ctx) {\n const state: State = {\n gameLogCount: 0,\n oscParamCount: 0,\n panel: undefined,\n log: (message) => {\n if (ctx.settings.get('verbosity') === 'quiet') return;\n ctx.logger.info(message);\n state.panel?.append(message);\n },\n };\n\n // 1. Custom CSS, removed automatically on deactivate.\n ctx.ui.injectCss(PLUGIN_CSS);\n\n installSettingsWatch(ctx);\n installEvents(ctx, state);\n installGameLog(ctx, state);\n installOsc(ctx, state);\n installDeepLinks(ctx, state);\n installRoutes(ctx, state);\n installContextMenu(ctx, state);\n installNotifications(ctx, state);\n // Fire-and-forget: the companion probe must not hold up the rest of activation.\n void installNative(ctx, state);\n installUi(ctx, state);\n\n ctx.logger.info(`Kitchen Sink v${ctx.version} ready.`);\n },\n\n deactivate() {\nLine truncated
"mappings": "AAOO,IAAMA,EAAa;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;ECOnB,SAASC,EAAeC,EAA+B,CAC5D,IAAMC,EAAO,SAAS,cAAc,KAAK,EACzC,OAAAA,EAAK,UAAY,SACjBD,EAAO,YAAYC,CAAI,EAEhB,CACL,OAAOC,EAAuB,CAC5B,IAAMC,EAAO,SAAS,cAAc,KAAK,EACzCA,EAAK,UAAY,cACjB,IAAMC,EAAO,IAAI,KAAK,EAAE,mBAAmB,EAI3C,IAHAD,EAAK,YAAc,GAAGC,CAAI,KAAKF,CAAO,GACtCD,EAAK,YAAYE,CAAI,EAEdF,EAAK,kBAAoB,KAAWA,EAAK,mBAAmB,OAAO,EAGzDA,EAAK,aAAeA,EAAK,UAAYA,EAAK,aAAe,KAC5DA,EAAK,UAAYA,EAAK,aACtC,EAEA,OAAc,CACZA,EAAK,gBAAgB,CACvB,CACF,CACF,CCpBA,IAAMI,EAAW,CACf,aAAc,CACZ,KAAM,UACN,MAAO,6BACP,YAAa,+DACb,QAAS,EACX,EACA,aAAc,CACZ,KAAM,SACN,MAAO,yBACP,YAAa,6DACb,QAAS,WACT,YAAa,UACf,EACA,SAAU,CACR,KAAM,SACN,MAAO,YACP,QAAS,EACT,IAAK,EACL,IAAK,IACL,KAAM,CACR,EACA,UAAW,CACT,KAAM,SACN,MAAO,gBACP,QAAS,SACT,QAAS,CACP,CAAE,MAAO,QAAS,MAAO,OAAQ,EACjC,CAAE,MAAO,SAAU,MAAO,QAAS,EACnC,CAAE,MAAO,OAAQ,MAAO,YAAa,CACvC,CACF,CACF,EAaOC,GAAqB,CAC1B,GAAI,eACJ,SAAAD,EAEA,SAASE,EAAK,CACZ,IAAMC,EAAe,CACnB,aAAc,EACd,cAAe,EACf,MAAO,OACP,IAAMC,GAAY,CACZF,EAAI,SAAS,IAAI,WAAW,IAAM,UACtCA,EAAI,OAAO,KAAKE,CAAO,EACvBD,EAAM,OAAO,OAAOC,CAAO,EAC7B,CACF,EAGAF,EAAI,GAAG,UAAUG,CAAU,EAE3BC,EAAqBJ,CAAG,EACxBK,EAAcL,EAAKC,CAAK,EACxBK,EAAeN,EAAKC,CAAK,EACzBM,EAAWP,EAAKC,CAAK,EACrBO,EAAiBR,EAAKC,CAAK,EAC3BQ,EAAcT,EAAKC,CAAK,EACxBS,EAAmBV,EAAKC,CAAK,EAC7BU,EAAqBX,EAAKC,CAAK,EAE1BW,EAAcZ,EAAKC,CAAK,EAC7BY,EAAUb,EAAKC,CAAK,EAEpBD,EAAI,OAAO,KAAK,iBAAiBA,EAAI,OAAO,SAAS,CACvD,EAEA,YAAa,CAGb,CACF,EAGA,SAASI,EAAqBJ,EAAgB,CAC5CA,EAAI,YAAY,IACdA,EAAI,SAAS,SAAUc,GAAW,CAChCd,EAAI,OAAO,MAAM,sCAAsCc,EAAO,SAAS,GAAG,CAC5E,CAAC,CACH,CACF,CAGA,SAAST,EAAcL,EAAUC,EAAoB,CAEnDD,EAAI,OAAO,GAAG,sBAAwBe,GAAY,CAChDd,EAAM,IAAI,YAAYc,EAAQ,UAAU,WAAMA,EAAQ,IAAI,EAAE,CAC9D,CAAC,EAGDf,EAAI,OAAO,GAAG,qBAAuBe,GAAY,CAC/C,GAAI,OAAOA,GAAY,UAAYA,IAAY,KAAM,OACrD,IAAMC,EAAaD,EAAoC,UACnD,OAAOC,GAAc,UAAUf,EAAM,IAAI,qBAAqBe,CAAS,EAAE,CAC/E,CAAC,EAGDhB,EAAI,OAAO,MAAOiB,GAAa,CACzBjB,EAAI,SAAS,IAAI,WAAW,IAAM,QACpCA,EAAI,OAAO,MAAM,SAASiB,EAAS,IAAI,EAAE,CAE7C,CAAC,CACH,CAGA,SAASX,EAAeN,EAAUC,EAAoB,CACpDD,EAAI,QAAQ,GAAIkB,GAAU,CACnBlB,EAAI,SAAS,IAAI,cAAc,IACpCC,EAAM,cAAgB,EACtBA,EAAM,IAAI,aAAaiB,EAAM,IAAI,KAAKA,EAAM,OAAO,EAAE,EACvD,CAAC,EAEDlB,EAAI,QAAQ,OAAO,iBAAmBkB,GAAU,CAC9CjB,EAAM,IAAI,UAAUiB,EAAM,QAAUA,EAAM,OAAO,EAAE,CACrD,CAAC,EAGIlB,EAAI,QACN,QAAQA,EAAI,MAAM,EAClB,KAAMmB,GAAY,CAAEnB,EAAI,OAAO,KAAK,qBAAqB,OAAOmB,EAAQ,MAAM,CAAC,WAAW,CAAG,CAAC,EAC9F,MAAOC,GAAmB,CACrBpB,EAAI,OAAO,SACfA,EAAI,OAAO,KAAK,gCAAgC,OAAOoB,CAAK,CAAC,EAAE,CACjE,CAAC,CACL,CAGA,SAASb,EAAWP,EAAUC,EAAoB,CAChD,GAAI,CAACD,EAAI,IAAI,UAAW,CACtBC,EAAM,IAAI,iDAAiD,EAC3D,MACF,CACAD,EAAI,IAAI,QAAQ,EAEhBA,EAAI,IAAI,QAASqB,GAAU,CACzBpB,EAAM,eAAiB,EACnBD,EAAI,SAAS,IAAI,WAAW,IAAM,QACpCC,EAAM,IAAI,SAASoB,EAAM,IAAI,MAAM,OAAOA,EAAM,KAAK,CAAC,EAAE,CAE5D,CAAC,EAEDrB,EAAI,IAAI,eAAgBqB,GAAU,CAChCpB,EAAM,IAAI,gBAAgBoB,EAAM,QAAQ,SAAS,OAAOA,EAAM,WAAW,MAAM,CAAC,aAAa,CAC/F,CAAC,CACH,CAGA,SAASC,EAAStB,EAAkB,CAClC,GAAI,CAACA,EAAI,IAAI,UAAW,MAAO,iCAC/B,IAAMuB,EAAOvB,EAAI,SAAS,IAAI,cAAc,EACtCwB,EAAQxB,EAAI,SAAS,IAAI,UAAU,EACzC,OAAAA,EAAI,IAAI,KAAKuB,EAAM,MAAOC,CAAK,EACxB,GAAGD,CAAI,MAAM,OAAOC,CAAK,CAAC,EACnC,CAGA,SAAShB,EAAiBR,EAAUC,EAAoB,CACtDD,EAAI,UAAU,SAAS,OAASqB,GAAU,CACxCpB,EAAM,IAAI,gBAAgBoB,EAAM,EAAE,EAAE,CAEtC,CAAC,EAEDrB,EAAI,UAAU,GAAIqB,GAAU,CAC1BpB,EAAM,IAAI,UAAUoB,EAAM,MAAM,IAAIA,EAAM,EAAE,GAAGA,EAAM,OAAS,KAAKA,EAAM,MAAM,IAAM,EAAE,EAAE,CAE3F,CAAC,CACH,CAGA,SAASZ,EAAcT,EAAUC,EAAoB,CACnDD,EAAI,OAAO,IAAI,QAAS,IACtB,SAAS,KAAK,CACZ,cAAeC,EAAM,aACrB,UAAWA,EAAM,cACjB,UAAWD,EAAI,SAAS,IAAI,WAAW,CACzC,CAAC,CACH,EAEAA,EAAI,OAAO,IAAI,cAAgByB,GAC7B,SAAS,KAAK,CAAE,MAAOA,EAAQ,OAAO,MAAW,OAAQ,CAAC,CAC5D,EAEAzB,EAAI,OAAO,KAAK,YAAa,IAAM,SAAS,KAAK,CAAE,KAAMsB,EAAStB,CAAG,CAAE,CAAC,CAAC,EAEzEA,EAAI,OAAO,KAAK,qBAAqBA,EAAI,OAAO,KAAK,QAAQ,EAAE,CACjE,CAGA,SAASU,EAAmBV,EAAUC,EAAoB,CACxDD,EAAI,YAAY,WAAY0B,GAAW,CACrC,IAAMP,EAA8B,CAClC,CAAE,KAAM,SAAU,EAClB,CACE,KAAM,OACN,KAAM,eACN,MAAO,iCACP,SAAU,IAAM,CAAElB,EAAM,IAAI,SAASyB,EAAO,QAAQ,QAAQ,YAAY,CAAC,EAAE,CAAG,CAChF,EACA,CACE,KAAM,UACN,KAAM,OACN,MAAO,0BACP,MAAO,IACL5B,EAAS,UAAU,QAAQ,IACxB6B,IAA8B,CAC7B,KAAM,OACN,KAAM,SACN,MAAOA,EAAO,MACd,QAAS3B,EAAI,SAAS,IAAI,WAAW,IAAM2B,EAAO,MAClD,SAAU,IAAM,CAAO3B,EAAI,SAAS,IAAI,YAAa2B,EAAO,KAAK,CAAG,CACtE,EACF,CACJ,CACF,EAEA,GAAID,EAAO,SAAW,OAAW,CAC/B,IAAME,EAASF,EAAO,OACtBP,EAAQ,KAAK,CACX,KAAM,OACN,KAAM,eACN,MAAO,QAAQS,EAAO,IAAI,MAC1B,SAAU,IAAM,CAAO,UAAU,UAAU,UAAUA,EAAO,EAAE,CAAG,CACnE,CAAC,CACH,CACA,OAAOT,CACT,CAAC,CACH,CAGA,SAASR,EAAqBX,EAAUC,EAAoB,CAC1DD,EAAI,QAAQ,OAAO,iBAAmBkB,GAAU,CAC9C,IAAMW,EAAMX,EAAM,QAAUA,EAAM,QAGlClB,EAAI,cAAc,MAAM,CAAE,QAAS,GAAG6B,CAAG,UAAW,CAAC,EAGjD7B,EAAI,cAAc,iBACpBA,EAAI,cAAc,QAAQ,CACxB,MAAO,gBACP,SAAU6B,EACV,OAAQ,MACV,CAAC,EAED5B,EAAM,IAAI,qDAAqD,CAEnE,CAAC,EAGDD,EAAI,OAAO,GAAG,sBAAwBe,GAAY,CAC5CA,EAAQ,OAAS,UACrBf,EAAI,cAAc,WAAW,CAC3B,KAAM,eACN,OAAQe,EAAQ,WAChB,QAAS,aACX,CAAC,CACH,CAAC,CACH,CASA,eAAeH,EAAcZ,EAAUC,EAA6B,CAIlE,GAAI,CAAE,MAAMD,EAAI,OLine truncated "mappings": "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 truncated
"names": ["PLUGIN_CSS", "createLogPanel", "parent", "list", "message", "line", "time", "settings", "index_default", "ctx", "state", "message", "PLUGIN_CSS", "installSettingsWatch", "installEvents", "installGameLog", "installOsc", "installDeepLinks", "installRoutes", "installContextMenu", "installNotifications", "installNative", "installUi", "values", "payload", "worldName", "envelope", "entry", "entries", "error", "event", "pulseOsc", "name", "value", "request", "target", "option", "entity", "who", "targets", "t", "description", "card", "grid", "statTile", "pulse", "installNavTab", "checked", "reset", "confirmed", "tab", "k", "activity", "createLogPanel", "output", "next", "response", "label", "read", "tile", "labelEl", "valueEl"] "names": ["PLUGIN_CSS", "createLogPanel", "parent", "list", "message", "line", "time", "settings", "index_default", "ctx", "state", "message", "PLUGIN_CSS", "installSettingsWatch", "installEvents", "installGameLog", "installOsc", "installDeepLinks", "installRoutes", "installContextMenu", "installNotifications", "installNative", "installUi", "values", "payload", "worldName", "envelope", "entry", "entries", "error", "event", "pulseOsc", "name", "value", "request", "target", "option", "entity", "who", "targets", "t", "description", "card", "grid", "statTile", "pulse", "installNavTab", "checked", "reset", "confirmed", "tab", "k", "activity", "createLogPanel", "output", "next", "response", "label", "read", "tile", "labelEl", "valueEl"]
} }
+28
View File
@@ -21,6 +21,34 @@
"apiVersion": "^0.1.0", "apiVersion": "^0.1.0",
"author": "vrcnext-plugins", "author": "vrcnext-plugins",
"homepage": "https://github.com/vrcnext-plugins/vrcnext-plugin-system" "homepage": "https://github.com/vrcnext-plugins/vrcnext-plugin-system"
},
{
"id": "club-security",
"name": "Club Security",
"version": "1.0.0",
"description": "When someone joins your instance, gathers age verification, avatar performance, group membership and rejoin history, then notifies you in-app, on the desktop, in VR or on Discord.",
"entry": "dist/club-security.js",
"apiVersion": "^0.1.0",
"author": "vrcnext-plugins",
"homepage": "https://github.com/vrcnext-plugins/vrcnext-plugin-system",
"icon": "security",
"tags": [
"Notifications",
"Privacy",
"Activity"
],
"searchTerms": [
"security",
"door",
"bouncer",
"join",
"age verified",
"performance rank",
"group",
"rejoin",
"discord",
"webhook"
]
} }
] ]
} }