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",
|
||||
"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"
|
||||
},
|
||||
"dependencies": {
|
||||
"@auth0/auth0-spa-js": "^2.8.0",
|
||||
"@babylonjs/core": "8.36.1",
|
||||
"@babylonjs/gui": "^8.36.1",
|
||||
"@babylonjs/havok": "1.3.10",
|
||||
"@babylonjs/inspector": "8.36.1",
|
||||
"@babylonjs/loaders": "8.36.1",
|
||||
"@babylonjs/materials": "8.36.1",
|
||||
"@babylonjs/serializers": "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"
|
||||
},
|
||||
"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 {updateUserProfile} from "./loginScreen";
|
||||
import {Preloader} from "./preloader";
|
||||
import {DiscordWidget} from "./discordWidget";
|
||||
|
||||
// Set to true to run minimal controller debug test
|
||||
const DEBUG_CONTROLLERS = false;
|
||||
@ -473,6 +474,20 @@ export class Main {
|
||||
pointerFeature.detach();
|
||||
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');
|
||||
} catch (error) {
|
||||
@ -610,6 +625,26 @@ router.on('/', async () => {
|
||||
// Initialize demo mode without engine (just for UI purposes)
|
||||
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');
|
||||
|
||||
5
src/vite-env.d.ts
vendored
5
src/vite-env.d.ts
vendored
@ -1 +1,6 @@
|
||||
/// <reference types="vite/client" />
|
||||
|
||||
// Widgetbot Crate global type
|
||||
interface Window {
|
||||
Crate: new (options: any) => any;
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user