Add Discord widget integration with dynamic script loading
All checks were successful
Build / build (push) Successful in 1m24s
All checks were successful
Build / build (push) Successful in 1m24s
- Created TypeScript wrapper for Widgetbot Crate - Dynamically loads Discord widget from CDN at runtime - Removed @widgetbot/crate npm package to avoid React dependency (182 packages removed) - Integrated with VR mode: auto-hides in VR, auto-shows in desktop mode - Connected to Discord server 1112846185913401475, channel 1437561367908581406 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
b31e33350e
commit
1648364540
14
package-lock.json
generated
14
package-lock.json
generated
@ -1513,6 +1513,20 @@
|
|||||||
"tr46": "~0.0.3",
|
"tr46": "~0.0.3",
|
||||||
"webidl-conversions": "^3.0.0"
|
"webidl-conversions": "^3.0.0"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"node_modules/yaml": {
|
||||||
|
"version": "2.8.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.1.tgz",
|
||||||
|
"integrity": "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==",
|
||||||
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
|
"peer": true,
|
||||||
|
"bin": {
|
||||||
|
"yaml": "bin.mjs"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 14.6"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -15,15 +15,15 @@
|
|||||||
"export-blend:batch": "tsx scripts/exportBlend.ts --batch"
|
"export-blend:batch": "tsx scripts/exportBlend.ts --batch"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@auth0/auth0-spa-js": "^2.8.0",
|
||||||
"@babylonjs/core": "8.36.1",
|
"@babylonjs/core": "8.36.1",
|
||||||
"@babylonjs/gui": "^8.36.1",
|
"@babylonjs/gui": "^8.36.1",
|
||||||
"@babylonjs/havok": "1.3.10",
|
"@babylonjs/havok": "1.3.10",
|
||||||
"@babylonjs/inspector": "8.36.1",
|
"@babylonjs/inspector": "8.36.1",
|
||||||
"@babylonjs/loaders": "8.36.1",
|
"@babylonjs/loaders": "8.36.1",
|
||||||
"@babylonjs/materials": "8.36.1",
|
"@babylonjs/materials": "8.36.1",
|
||||||
"@babylonjs/serializers": "8.36.1",
|
|
||||||
"@babylonjs/procedural-textures": "8.36.1",
|
"@babylonjs/procedural-textures": "8.36.1",
|
||||||
"@auth0/auth0-spa-js": "^2.8.0",
|
"@babylonjs/serializers": "8.36.1",
|
||||||
"openai": "4.52.3"
|
"openai": "4.52.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
199
src/discordWidget.ts
Normal file
199
src/discordWidget.ts
Normal file
@ -0,0 +1,199 @@
|
|||||||
|
/**
|
||||||
|
* Discord Widget Integration using Widgetbot Crate
|
||||||
|
* Dynamically loads the widget script to avoid npm bundling issues
|
||||||
|
*/
|
||||||
|
|
||||||
|
export interface DiscordWidgetOptions {
|
||||||
|
server: string;
|
||||||
|
channel: string;
|
||||||
|
location?: string[];
|
||||||
|
color?: string;
|
||||||
|
glyph?: string[];
|
||||||
|
notifications?: boolean;
|
||||||
|
indicator?: boolean;
|
||||||
|
allChannelNotifications?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class DiscordWidget {
|
||||||
|
private crate: any = null;
|
||||||
|
private scriptLoaded = false;
|
||||||
|
private isVisible = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the Discord widget
|
||||||
|
* @param options - Widget configuration
|
||||||
|
*/
|
||||||
|
async initialize(options: DiscordWidgetOptions): Promise<void> {
|
||||||
|
// Load the Crate script if not already loaded
|
||||||
|
if (!this.scriptLoaded) {
|
||||||
|
console.log('[DiscordWidget] Loading Crate script...');
|
||||||
|
await this.loadCrateScript();
|
||||||
|
this.scriptLoaded = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for Crate to be available on window
|
||||||
|
await this.waitForCrate();
|
||||||
|
|
||||||
|
// Initialize the Crate widget
|
||||||
|
const defaultOptions: DiscordWidgetOptions = {
|
||||||
|
location: ['bottom', 'right'],
|
||||||
|
color: '#7289DA',
|
||||||
|
glyph: ['💬', '✖️'],
|
||||||
|
notifications: true,
|
||||||
|
indicator: true,
|
||||||
|
...options
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log('[DiscordWidget] Initializing Crate with options:', defaultOptions);
|
||||||
|
|
||||||
|
// @ts-ignore - Crate is loaded from CDN
|
||||||
|
this.crate = new window.Crate(defaultOptions);
|
||||||
|
|
||||||
|
this.setupEventListeners();
|
||||||
|
console.log('[DiscordWidget] Successfully initialized');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dynamically load the Crate script from CDN
|
||||||
|
*/
|
||||||
|
private loadCrateScript(): Promise<void> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
// Check if script already exists
|
||||||
|
const existingScript = document.querySelector('script[src*="widgetbot"]');
|
||||||
|
if (existingScript) {
|
||||||
|
resolve();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const script = document.createElement('script');
|
||||||
|
script.src = 'https://cdn.jsdelivr.net/npm/@widgetbot/crate@3';
|
||||||
|
script.async = true;
|
||||||
|
script.defer = true;
|
||||||
|
|
||||||
|
script.onload = () => {
|
||||||
|
console.log('[DiscordWidget] Script loaded successfully');
|
||||||
|
resolve();
|
||||||
|
};
|
||||||
|
|
||||||
|
script.onerror = () => {
|
||||||
|
console.error('[DiscordWidget] Failed to load script');
|
||||||
|
reject(new Error('Failed to load Widgetbot Crate script'));
|
||||||
|
};
|
||||||
|
|
||||||
|
document.head.appendChild(script);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wait for Crate constructor to be available on window
|
||||||
|
*/
|
||||||
|
private waitForCrate(): Promise<void> {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
const checkCrate = () => {
|
||||||
|
// @ts-ignore
|
||||||
|
if (window.Crate) {
|
||||||
|
resolve();
|
||||||
|
} else {
|
||||||
|
setTimeout(checkCrate, 50);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
checkCrate();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setup event listeners for widget events
|
||||||
|
*/
|
||||||
|
private setupEventListeners(): void {
|
||||||
|
if (!this.crate) return;
|
||||||
|
|
||||||
|
// Listen for when user signs in
|
||||||
|
this.crate.on('signIn', (user: any) => {
|
||||||
|
console.log('[DiscordWidget] User signed in:', user.username);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Listen for widget visibility changes
|
||||||
|
this.crate.on('toggleChat', (visible: boolean) => {
|
||||||
|
this.isVisible = visible;
|
||||||
|
console.log('[DiscordWidget] Chat visibility:', visible);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toggle the Discord chat widget
|
||||||
|
*/
|
||||||
|
toggle(): void {
|
||||||
|
if (this.crate) {
|
||||||
|
this.crate.toggle();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show a notification on the widget button
|
||||||
|
* @param message - Notification message
|
||||||
|
*/
|
||||||
|
notify(message: string): void {
|
||||||
|
if (this.crate) {
|
||||||
|
this.crate.notify(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show the widget
|
||||||
|
*/
|
||||||
|
show(): void {
|
||||||
|
if (this.crate && !this.isVisible) {
|
||||||
|
this.crate.show();
|
||||||
|
this.isVisible = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hide the widget
|
||||||
|
*/
|
||||||
|
hide(): void {
|
||||||
|
if (this.crate && this.isVisible) {
|
||||||
|
this.crate.hide();
|
||||||
|
this.isVisible = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if widget is currently visible
|
||||||
|
*/
|
||||||
|
getIsVisible(): boolean {
|
||||||
|
return this.isVisible;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emit a custom event to the widget
|
||||||
|
* @param event - Event name
|
||||||
|
* @param data - Event data
|
||||||
|
*/
|
||||||
|
emit(event: string, data?: any): void {
|
||||||
|
if (this.crate) {
|
||||||
|
this.crate.emit(event, data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Listen for widget events
|
||||||
|
* @param event - Event name
|
||||||
|
* @param callback - Event callback
|
||||||
|
*/
|
||||||
|
on(event: string, callback: (data: any) => void): void {
|
||||||
|
if (this.crate) {
|
||||||
|
this.crate.on(event, callback);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send a message to the Discord channel (if user is signed in)
|
||||||
|
* @param message - Message text
|
||||||
|
*/
|
||||||
|
sendMessage(message: string): void {
|
||||||
|
if (this.crate) {
|
||||||
|
this.emit('sendMessage', message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
35
src/main.ts
35
src/main.ts
@ -32,6 +32,7 @@ import {ReplayManager} from "./replay/ReplayManager";
|
|||||||
import {AuthService} from "./authService";
|
import {AuthService} from "./authService";
|
||||||
import {updateUserProfile} from "./loginScreen";
|
import {updateUserProfile} from "./loginScreen";
|
||||||
import {Preloader} from "./preloader";
|
import {Preloader} from "./preloader";
|
||||||
|
import {DiscordWidget} from "./discordWidget";
|
||||||
|
|
||||||
// Set to true to run minimal controller debug test
|
// Set to true to run minimal controller debug test
|
||||||
const DEBUG_CONTROLLERS = false;
|
const DEBUG_CONTROLLERS = false;
|
||||||
@ -473,6 +474,20 @@ export class Main {
|
|||||||
pointerFeature.detach();
|
pointerFeature.detach();
|
||||||
debugLog("Pointer selection feature stored and detached");
|
debugLog("Pointer selection feature stored and detached");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Hide Discord widget when entering VR, show when exiting
|
||||||
|
DefaultScene.XR.baseExperience.onStateChangedObservable.add((state) => {
|
||||||
|
const discord = (window as any).__discordWidget as DiscordWidget;
|
||||||
|
if (discord) {
|
||||||
|
if (state === 2) { // WebXRState.IN_XR
|
||||||
|
debugLog('[Main] Entering VR - hiding Discord widget');
|
||||||
|
discord.hide();
|
||||||
|
} else if (state === 0) { // WebXRState.NOT_IN_XR
|
||||||
|
debugLog('[Main] Exiting VR - showing Discord widget');
|
||||||
|
discord.show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
this.reportProgress(40, 'VR support enabled');
|
this.reportProgress(40, 'VR support enabled');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -610,6 +625,26 @@ router.on('/', async () => {
|
|||||||
// Initialize demo mode without engine (just for UI purposes)
|
// Initialize demo mode without engine (just for UI purposes)
|
||||||
const demo = new Demo(main);
|
const demo = new Demo(main);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Initialize Discord widget (if not already initialized)
|
||||||
|
if (!(window as any).__discordWidget) {
|
||||||
|
debugLog('[Router] Initializing Discord widget');
|
||||||
|
const discord = new DiscordWidget();
|
||||||
|
|
||||||
|
// Initialize with your server and channel IDs
|
||||||
|
discord.initialize({
|
||||||
|
server: '1112846185913401475', // Replace with your Discord server ID
|
||||||
|
channel: '1437561367908581406', // Replace with your Discord channel ID
|
||||||
|
color: '#667eea',
|
||||||
|
glyph: ['💬', '✖️'],
|
||||||
|
notifications: true
|
||||||
|
}).then(() => {
|
||||||
|
debugLog('[Router] Discord widget ready');
|
||||||
|
(window as any).__discordWidget = discord;
|
||||||
|
}).catch(error => {
|
||||||
|
console.error('[Router] Failed to initialize Discord widget:', error);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
debugLog('[Router] Home route handler complete');
|
debugLog('[Router] Home route handler complete');
|
||||||
|
|||||||
5
src/vite-env.d.ts
vendored
5
src/vite-env.d.ts
vendored
@ -1 +1,6 @@
|
|||||||
/// <reference types="vite/client" />
|
/// <reference types="vite/client" />
|
||||||
|
|
||||||
|
// Widgetbot Crate global type
|
||||||
|
interface Window {
|
||||||
|
Crate: new (options: any) => any;
|
||||||
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user