From e51eb40aaaccd3fdcec580d9daed35be8e09cdd9 Mon Sep 17 00:00:00 2001 From: Tea Date: Tue, 12 May 2026 16:28:45 -0400 Subject: [PATCH] 1.10.0 Update --- README.md | 6 +- esbuild.config.mjs | 4 +- main.ts | 438 ++++++++++++++++++++++++++------------------- manifest.json | 10 +- package-lock.json | 354 +++++++++++++++++++++--------------- package.json | 10 +- styles.css | 27 +++ 7 files changed, 501 insertions(+), 348 deletions(-) diff --git a/README.md b/README.md index b8b2dd8..2efd51f 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Multimuse Obsidian +# Multimuse Tracker For use with the BRAT Plugin for the best experiance. An Obsidian plugin for seamless integration with the MultiMuse Discord bot. Track Discord roleplay threads, send messages as muses directly from Obsidian, and automatically sync scene states. @@ -30,7 +30,7 @@ Install via [BRAT](https://obsidian.md/plugins?id=obsidian42-brat) with https:// ### 2. Configure Plugin -1. Open Obsidian Settings → Multimuse Obsidian +1. Open Obsidian Settings → Multimuse Tracker 2. Paste your API key in the "API Key" field 3. Your Discord user ID will be automatically detected from the API key 4. Configure other settings: @@ -209,4 +209,4 @@ MIT ## Credits - **Author**: BackstagePass Group -- **Plugin**: Multimuse Obsidian +- **Plugin**: Multimuse Tracker diff --git a/esbuild.config.mjs b/esbuild.config.mjs index a801f5a..f2b48a1 100644 --- a/esbuild.config.mjs +++ b/esbuild.config.mjs @@ -1,6 +1,6 @@ import esbuild from "esbuild"; import process from "process"; -import builtins from "builtin-modules"; +import { builtinModules } from "module"; const isProduction = process.argv.includes("production"); @@ -21,7 +21,7 @@ const ctx = await esbuild.context({ "@lezer/common", "@lezer/highlight", "@lezer/lr", - ...builtins], + ...builtinModules], format: "cjs", target: "es2018", logLevel: "info", diff --git a/main.ts b/main.ts index c0ff4f8..454df47 100644 --- a/main.ts +++ b/main.ts @@ -1,4 +1,4 @@ -import { Plugin, PluginSettingTab, Setting, Notice, TFile, MetadataCache, App, requestUrl, Modal, Editor, MarkdownView } from 'obsidian'; +import { Plugin, PluginSettingTab, Setting, Notice, TFile, TFolder, TAbstractFile, App, requestUrl, Modal, Editor, MarkdownView, CachedMetadata, RequestUrlResponse } from 'obsidian'; interface MultimuseObsidianSettings { botApiUrl: string; // Bot HTTP API URL (hidden from user UI for security) @@ -40,6 +40,80 @@ interface MuseInfo { muse_id?: string | null; // Optional: for API calls (alias-safe); display always uses name } +type FrontmatterData = Record; +type FrontmatterValue = string | number | boolean | string[]; + +interface AuthMeResponse { + user_id?: string | number; +} + +interface MusesListResponse { + muses?: MuseInfo[]; +} + +interface SceneState { + replied?: boolean | string | null; + is_from_character?: boolean | string | null; + timestamp?: string | null; + your_last_post?: string | null; + posted_since_count?: number | null; +} + +interface SceneQueryResponse { + tracked?: boolean; + state?: SceneState | null; +} + +interface TrackedThread { + thread_id: string | number; + muse_name?: string; + muse_names?: string[]; + participants?: number | string; + scene_path?: string; + scene_paths?: string[]; + guild_id?: string | number | null; + thread_name?: string; +} + +interface TrackedThreadsResponse { + threads?: TrackedThread[]; +} + +interface GuildMember { + id: string; + username: string; + display_name: string; +} + +interface GuildMembersResponse { + members?: GuildMember[]; +} + +interface ApiErrorBody { + message?: string; +} + +function parseJson(text: string): T { + return JSON.parse(text) as T; +} + +function getErrorMessage(error: unknown): string { + if (error && typeof error === 'object') { + const details = error as { message?: unknown; text?: unknown }; + if (typeof details.message === 'string') return details.message; + if (typeof details.text === 'string') return details.text; + } + return 'Unknown error'; +} + +function getErrorStatus(error: unknown): number | undefined { + if (error && typeof error === 'object') { + const details = error as { status?: unknown }; + if (typeof details.status === 'number') return details.status; + } + return undefined; +} + export default class MultimuseObsidian extends Plugin { settings: MultimuseObsidianSettings; pollIntervalId: number | null = null; @@ -71,7 +145,7 @@ export default class MultimuseObsidian extends Plugin { id: 'check-discord-threads', name: 'Check Discord Threads Now', callback: () => { - this.checkAllThreads(); + void this.checkAllThreads(); } }); @@ -81,7 +155,7 @@ export default class MultimuseObsidian extends Plugin { name: 'Toggle Discord Polling', callback: () => { this.settings.enabled = !this.settings.enabled; - this.saveSettings(); + void this.saveSettings(); if (this.settings.enabled) { this.startPolling(); new Notice('Discord polling enabled'); @@ -97,7 +171,7 @@ export default class MultimuseObsidian extends Plugin { id: 'create-scene', name: 'Create New Scene', callback: () => { - this.createNewScene(); + void this.createNewScene(); } }); @@ -106,7 +180,7 @@ export default class MultimuseObsidian extends Plugin { id: 'sync-from-tracker', name: 'Sync from Tracker', callback: () => { - this.syncFromTracker(); + void this.syncFromTracker(); } }); @@ -208,11 +282,11 @@ export default class MultimuseObsidian extends Plugin { const intervalMs = this.settings.pollInterval * 60 * 1000; this.pollIntervalId = window.setInterval(() => { - this.checkAllThreads(); + void this.checkAllThreads(); }, intervalMs); // Do an initial check - this.checkAllThreads(); + void this.checkAllThreads(); } stopPolling() { @@ -250,13 +324,47 @@ export default class MultimuseObsidian extends Plugin { return headers; } + async trackThread(params: { + threadId: string; + userId: string; + museName: string; + participants: number; + scenePath?: string; + guildId?: string | null; + characters?: string[]; + }): Promise { + const body: Record = { + thread_id: params.threadId, + user_id: params.userId, + muse_name: params.museName, + participants: params.participants + }; + if (params.scenePath) body.scene_path = params.scenePath; + if (params.guildId) body.guild_id = params.guildId; + if (params.characters && params.characters.length > 0) body.characters = params.characters; + + return await requestUrl({ + url: `${this.getBotApiUrl()}/api/v1/threads/track`, + method: 'POST', + headers: this.getApiHeaders(), + body: JSON.stringify(body) + }); + } + + getFrontmatter(cache: CachedMetadata | null): FrontmatterData | null { + if (!cache?.frontmatter) { + return null; + } + return cache.frontmatter as FrontmatterData; + } + /** * Handle API response errors, especially authentication errors. * @param response The response object from requestUrl * @param context Context string for logging * @returns true if error was handled, false otherwise */ - handleApiError(response: any, context: string): boolean { + handleApiError(response: RequestUrlResponse, context: string): boolean { if (response.status === 401) { const errorMsg = 'API authentication failed. Please check your API key in settings.'; console.error(`[MultimuseObsidian] ${context}: ${errorMsg}`); @@ -291,7 +399,7 @@ export default class MultimuseObsidian extends Plugin { }); if (response.status === 200) { - const data = JSON.parse(response.text); + const data = parseJson(response.text); const userId = data.user_id; if (userId) { // Cache the user ID @@ -369,7 +477,7 @@ export default class MultimuseObsidian extends Plugin { }); if (response.status === 200) { - const data = JSON.parse(response.text); + const data = parseJson(response.text); const muses: MuseInfo[] = data.muses || []; // Cache all muses for each user ID (API already returns all accessible muses for all provided user IDs) @@ -400,7 +508,7 @@ export default class MultimuseObsidian extends Plugin { } async checkAllThreadsViaBotApi(): Promise { - /**Check all scene files by querying the tracker API for linked scenes in batch.*/ + /**Check all scene files against current thread_tracking state.*/ if (!this.settings.apiKey) { return; } @@ -416,34 +524,37 @@ export default class MultimuseObsidian extends Plugin { return; } - // Get all linked scenes from the API - const linkedUrl = `${this.getBotApiUrl()}/api/v1/scenes/linked?user_id=${primaryUserId}`; - const linkedResponse = await requestUrl({ - url: linkedUrl, + // Get all Discord-side tracked threads from the current API. + const trackedUrl = `${this.getBotApiUrl()}/api/v1/threads/tracked?user_id=${primaryUserId}`; + const trackedResponse = await requestUrl({ + url: trackedUrl, method: 'GET', headers: this.getApiHeaders() }); - if (linkedResponse.status !== 200) { - if (!this.handleApiError(linkedResponse, 'checkAllThreadsViaBotApi')) { - console.error(`[MultimuseObsidian] Failed to fetch linked scenes: ${linkedResponse.status} - ${linkedResponse.text}`); + if (trackedResponse.status !== 200) { + if (!this.handleApiError(trackedResponse, 'checkAllThreadsViaBotApi')) { + console.error(`[MultimuseObsidian] Failed to fetch tracked threads: ${trackedResponse.status} - ${trackedResponse.text}`); } return; } - const linkedData = JSON.parse(linkedResponse.text); - const linkedThreads = linkedData.linked_threads || []; + const trackedData = parseJson(trackedResponse.text); + const trackedThreads = trackedData.threads || []; - if (linkedThreads.length === 0) { + if (trackedThreads.length === 0) { return; } - // Create a map of scene_path -> thread info for quick lookup - const scenePathMap = new Map(); - for (const thread of linkedThreads) { + // Create a map of scene_path -> thread info for quick lookup. + const scenePathMap = new Map(); + for (const thread of trackedThreads) { if (thread.scene_path) { scenePathMap.set(thread.scene_path, thread); } + for (const scenePath of thread.scene_paths || []) { + scenePathMap.set(scenePath, thread); + } } // Get all scene files @@ -460,12 +571,13 @@ export default class MultimuseObsidian extends Plugin { } const cache = this.app.metadataCache.getFileCache(file); - if (!cache || !cache.frontmatter) { + const frontmatter = this.getFrontmatter(cache); + if (!frontmatter) { continue; } // Skip if scene is marked as inactive - const isActive = cache.frontmatter['Is Active?']; + const isActive = frontmatter['Is Active?']; if (isActive === false || isActive === 'false') { continue; } @@ -473,11 +585,11 @@ export default class MultimuseObsidian extends Plugin { // Check if this scene is in the linked threads const threadInfo = scenePathMap.get(file.path); if (!threadInfo) { - // Scene not linked, try querying by thread_id and characters - const link = cache.frontmatter['Link']; - if (!link) continue; + // Scene not linked in thread_tracking, try querying by thread_id and characters. + const link = frontmatter['Link']; + if (typeof link !== 'string') continue; - const characters = this.getCharacterNames(cache.frontmatter); + const characters = this.getCharacterNames(frontmatter); if (characters.length === 0) continue; const threadId = this.extractThreadIdFromUrl(link); @@ -490,10 +602,10 @@ export default class MultimuseObsidian extends Plugin { } } else { // Scene is linked - query its state - const characters = threadInfo.characters || []; + const characters = this.getCharacterNames(frontmatter); if (characters.length === 0) continue; - const charactersParam = Array.isArray(characters) ? characters.join(',') : characters; + const charactersParam = characters.join(','); // Use primary user ID for query const primaryUserId = await this.getPrimaryUserId(); if (!primaryUserId) { @@ -508,7 +620,7 @@ export default class MultimuseObsidian extends Plugin { }); if (queryResponse.status === 200) { - const queryData = JSON.parse(queryResponse.text); + const queryData = parseJson(queryResponse.text); if (queryData.tracked && queryData.state) { const state = queryData.state; @@ -546,22 +658,23 @@ export default class MultimuseObsidian extends Plugin { } const cache = this.app.metadataCache.getFileCache(file); - if (!cache || !cache.frontmatter) { + const frontmatter = this.getFrontmatter(cache); + if (!cache || !frontmatter) { return false; } // Skip if scene is marked as inactive - const isActive = cache.frontmatter['Is Active?']; + const isActive = frontmatter['Is Active?']; if (isActive === false || isActive === 'false') { return false; // Skip inactive scenes } - const link = cache.frontmatter['Link']; - if (!link) { + const link = frontmatter['Link']; + if (typeof link !== 'string') { return false; } - const characters = this.getCharacterNames(cache.frontmatter); + const characters = this.getCharacterNames(frontmatter); if (characters.length === 0) { return false; } @@ -594,7 +707,7 @@ export default class MultimuseObsidian extends Plugin { return false; } - const data = JSON.parse(response.text); + const data = parseJson(response.text); // If scene is not tracked, don't update anything if (!data.tracked || !data.state) { @@ -621,9 +734,13 @@ export default class MultimuseObsidian extends Plugin { } } - async updateSceneFromState(file: TFile, cache: any, state: any): Promise { + async updateSceneFromState(file: TFile, cache: CachedMetadata, state: SceneState): Promise { /**Update scene frontmatter from API state data.*/ let updated = false; + const frontmatter = this.getFrontmatter(cache); + if (!frontmatter) { + return false; + } // Use replied or is_from_character (API may send either) const repliedRaw = state.replied ?? state.is_from_character; @@ -641,7 +758,7 @@ export default class MultimuseObsidian extends Plugin { } // Update Replied? field - normalize boolean values for comparison - const currentRepliedRaw = cache.frontmatter['Replied?']; + const currentRepliedRaw = frontmatter['Replied?']; // Handle both boolean and string values const currentReplied = currentRepliedRaw === true || currentRepliedRaw === 'true' || currentRepliedRaw === 'True'; // true = you've replied (no need to reply), false = need to reply @@ -685,10 +802,10 @@ export default class MultimuseObsidian extends Plugin { } // Small delay to avoid checking during file creation - await new Promise(resolve => setTimeout(resolve, 1000)); + await new Promise(resolve => window.setTimeout(resolve, 1000)); const cache = this.app.metadataCache.getFileCache(file); - if (cache?.frontmatter) { + if (this.getFrontmatter(cache)) { // Sync Is Active? to the tracker so unchecking removes the scene from MultiMuse await this.syncSceneActiveStatusToApi(file, cache); // Sync Participants to the tracker so "your turn" / Replied? use the correct count @@ -721,7 +838,9 @@ export default class MultimuseObsidian extends Plugin { const primaryUserId = await this.getPrimaryUserId(); if (!primaryUserId) return; - const threadId = this.extractThreadIdFromUrl(String(link)); + if (typeof link !== 'string') return; + + const threadId = this.extractThreadIdFromUrl(link); const raw = cache.frontmatter?.['Is Active?']; const isActive = raw !== false && raw !== 'false'; @@ -776,9 +895,15 @@ export default class MultimuseObsidian extends Plugin { const primaryUserId = await this.getPrimaryUserId(); if (!primaryUserId) return; - const threadId = this.extractThreadIdFromUrl(String(link)); + if (typeof link !== 'string') return; + + const threadId = this.extractThreadIdFromUrl(link); const raw = cache.frontmatter?.['Participants']; - const participants = typeof raw === 'number' && raw >= 1 ? raw : parseInt(String(raw || '2'), 10); + const participants = typeof raw === 'number' && raw >= 1 + ? raw + : typeof raw === 'string' + ? parseInt(raw, 10) + : 2; if (isNaN(participants) || participants < 1) return; try { @@ -827,10 +952,10 @@ export default class MultimuseObsidian extends Plugin { return map; } - collectMarkdownFiles(fileOrFolder: any, files: TFile[]): void { + collectMarkdownFiles(fileOrFolder: TAbstractFile, files: TFile[]): void { if (fileOrFolder instanceof TFile && fileOrFolder.extension === 'md') { files.push(fileOrFolder); - } else if ('children' in fileOrFolder) { + } else if (fileOrFolder instanceof TFolder) { for (const child of fileOrFolder.children) { this.collectMarkdownFiles(child, files); } @@ -838,7 +963,7 @@ export default class MultimuseObsidian extends Plugin { } - getCharacterNames(frontmatter: any): string[] { + getCharacterNames(frontmatter: FrontmatterData): string[] { const characters = frontmatter['Characters']; if (!characters) { return []; @@ -879,7 +1004,7 @@ export default class MultimuseObsidian extends Plugin { } - async updateFrontmatter(file: TFile, key: string, value: any): Promise { + async updateFrontmatter(file: TFile, key: string, value: FrontmatterValue): Promise { const content = await this.app.vault.read(file); // Parse frontmatter @@ -961,7 +1086,7 @@ export default class MultimuseObsidian extends Plugin { }); if (response.status === 200) { - const data = JSON.parse(response.text); + const data = parseJson(response.text); muses = data.muses || []; console.log(`[MultimuseObsidian] Found ${muses.length} muse(s) from ${userIds.length} user(s)`); if (muses.length > 0) { @@ -996,7 +1121,7 @@ export default class MultimuseObsidian extends Plugin { const selectedMuse = muses[selectedMuseIndex]; // Small delay to ensure previous modal is fully closed - await new Promise(resolve => setTimeout(resolve, 100)); + await new Promise(resolve => window.setTimeout(resolve, 100)); // 3) Get Discord thread/channel link const threadUrl = await this.showInputPrompt('Enter Discord thread/channel URL'); @@ -1009,7 +1134,7 @@ export default class MultimuseObsidian extends Plugin { } // Small delay before opening location modal - await new Promise(resolve => setTimeout(resolve, 100)); + await new Promise(resolve => window.setTimeout(resolve, 100)); // 4) Get location (RP folder) - pass muse name for context console.log(`[MultimuseObsidian] createNewScene: About to select location for muse "${selectedMuse.name}"`); @@ -1027,7 +1152,7 @@ export default class MultimuseObsidian extends Plugin { // 7) Create scene file const filePath = `${location}/${sceneName}.md`; - const frontmatter: Record = { + const frontmatter: Record = { 'Link': threadUrl, 'Characters': [selectedMuse.name], 'Participants': participants, @@ -1089,80 +1214,37 @@ export default class MultimuseObsidian extends Plugin { // Mark this file as recently created to skip immediate checking this.recentlyCreatedFiles.add(filePath); // Remove from the set after 60 seconds (enough time for the scene to be registered and settled with the API) - setTimeout(() => { + window.setTimeout(() => { this.recentlyCreatedFiles.delete(filePath); console.log(`[MultimuseObsidian] Removed ${filePath} from recently created files - will now be checked by polling`); }, 60000); - // 8) Register with bot API and optionally create thread tracking + // 8) Link the vault scene to the current Discord-side thread tracker. try { - // Register scene - // Log the extracted thread info for debugging - console.debug(`Registering scene: threadId=${threadInfo.threadId}, guildId=${threadInfo.guildId}, channelId=${threadInfo.channelId}, url=${threadUrl}`); + console.debug(`Tracking scene: threadId=${threadInfo.threadId}, guildId=${threadInfo.guildId}, channelId=${threadInfo.channelId}, url=${threadUrl}`); // Convert IDs to strings to avoid JavaScript number precision loss // Discord IDs are larger than Number.MAX_SAFE_INTEGER, so we send them as strings - // Use primary user ID for scene registration + // Use primary user ID for thread tracking const primaryUserId = await this.getPrimaryUserId(); if (!primaryUserId) { new Notice('Failed to get user ID from API key. Please check your API key in settings.'); return; } - const registerResponse = await requestUrl({ - url: `${this.getBotApiUrl()}/api/v1/scenes/create`, - method: 'POST', - headers: this.getApiHeaders(), - body: JSON.stringify({ - thread_id: threadInfo.threadId, // Send as string, let Python convert - user_id: primaryUserId, // Send as string - scene_path: createdFile.path, - characters: [selectedMuse.name], - participants: participants, - is_active: true, - guild_id: threadInfo.guildId || null // Send as string or null - }) + const registerResponse = await this.trackThread({ + threadId: threadInfo.threadId, + userId: primaryUserId, + museName: selectedMuse.name, + participants: participants, + scenePath: createdFile.path, + guildId: threadInfo.guildId || null, + characters: [selectedMuse.name] }); - console.debug(`Scene registration response: ${registerResponse.status} - ${registerResponse.text}`); + console.debug(`Thread tracking response: ${registerResponse.status} - ${registerResponse.text}`); if (registerResponse.status === 200) { - // Scene registered successfully - proceed with optional operations - - // Also create thread tracking (optional - don't fail if this errors) - // This may fail if bot can't access the thread, which is okay - try { - const trackBody: Record = { - thread_id: threadInfo.threadId, - user_id: primaryUserId, - muse_name: selectedMuse.name, - participants: participants, - scene_path: createdFile.path, - guild_id: threadInfo.guildId || null - }; - if (selectedMuse.muse_id) { - trackBody.muse_id = selectedMuse.muse_id; - } - const trackResponse = await requestUrl({ - url: `${this.getBotApiUrl()}/api/v1/threads/track`, - method: 'POST', - headers: this.getApiHeaders(), - body: JSON.stringify(trackBody) - }); - - if (trackResponse.status === 200) { - console.debug('Thread tracking created successfully'); - } else { - console.error(`Thread tracking failed: ${trackResponse.status} - ${trackResponse.text}`); - } - } catch (trackError: any) { - // Thread tracking is optional - scene is already registered - // Only log if it's not a 400 (which means bot can't access thread - expected) - if (trackError?.status !== 400) { - console.error('Thread tracking error (non-fatal):', trackError); - } - } - // Add to Base if configured try { if (this.settings.basePath) { @@ -1176,29 +1258,20 @@ export default class MultimuseObsidian extends Plugin { await this.app.workspace.getLeaf(true).openFile(createdFile); } else if (registerResponse.status === 401) { // Authentication error - show helpful message - this.handleApiError(registerResponse, 'createNewScene - register scene'); - new Notice('Scene created but failed to register with bot: Authentication failed. Check your API key.'); + this.handleApiError(registerResponse, 'createNewScene - track thread'); + new Notice('Scene created but failed to track with bot: Authentication failed. Check your API key.'); } else { const errorText = registerResponse.text || 'Unknown error'; - console.error(`Failed to register scene: ${registerResponse.status} - ${errorText}`); - new Notice(`Scene created but failed to register with bot: ${registerResponse.status}`); + console.error(`Failed to track thread: ${registerResponse.status} - ${errorText}`); + new Notice(`Scene created but failed to track with bot: ${registerResponse.status}`); } - } catch (error: any) { + } catch (error) { // Log the full error for debugging - console.error('Error registering scene:', error); - const errorMessage = error?.message || error?.text || 'Unknown error'; + console.error('Error tracking thread:', error); + const errorMessage = getErrorMessage(error); console.error('Error details:', errorMessage); - // Only show error if scene registration itself failed - // Thread tracking errors are handled above and shouldn't reach here - if (error?.status === 400 && error?.message?.includes('threads/track')) { - // This is a thread tracking error that slipped through - ignore it - // Scene was already registered successfully - new Notice(`Scene created: ${sceneName}`); - await this.app.workspace.getLeaf(true).openFile(createdFile); - } else { - new Notice(`Scene created but failed to register with bot: ${errorMessage}`); - } + new Notice(`Scene created but failed to track with bot: ${errorMessage}`); } } @@ -1232,7 +1305,7 @@ export default class MultimuseObsidian extends Plugin { return; } - const data = JSON.parse(response.text); + const data = parseJson(response.text); const threads = data.threads || []; if (threads.length === 0) { @@ -1248,7 +1321,7 @@ export default class MultimuseObsidian extends Plugin { for (const thread of threads) { const threadId = String(thread.thread_id ?? ''); - const museName = thread.muse_name; + const museName = thread.muse_name || 'Muse'; const participants = thread.participants || 2; const existingScenePath = thread.scene_path; @@ -1298,7 +1371,7 @@ export default class MultimuseObsidian extends Plugin { const sceneName = thread.thread_name || `${museName} - Thread ${threadId}`; const filePath = `${location}/${sceneName}.md`; - const frontmatter: Record = { + const frontmatter: Record = { 'Link': threadUrl, 'Characters': [museName], 'Participants': participants, @@ -1360,30 +1433,25 @@ export default class MultimuseObsidian extends Plugin { // Mark this file as recently created to skip immediate checking this.recentlyCreatedFiles.add(filePath); // Remove from the set after 30 seconds (enough time for the scene to be registered with the API) - setTimeout(() => { + window.setTimeout(() => { this.recentlyCreatedFiles.delete(filePath); }, 30000); - // Register with bot - // Use primary user ID for scene registration + // Link the created note back to the current Discord-side thread tracker. const primaryUserId = await this.getPrimaryUserId(); if (!primaryUserId) { - console.error('Failed to get user ID from API key, skipping scene registration'); + console.error('Failed to get user ID from API key, skipping thread tracking update'); continue; } - await requestUrl({ - url: `${this.getBotApiUrl()}/api/v1/scenes/create`, - method: 'POST', - headers: this.getApiHeaders(), - body: JSON.stringify({ - thread_id: threadId, // Send as string to avoid precision loss - user_id: primaryUserId, // Send as string - scene_path: filePath, - characters: [museName], - participants: participants, - guild_id: guildId || null, // Include guild_id if available - }) + await this.trackThread({ + threadId: threadId, + userId: primaryUserId, + museName: museName, + participants: Number(participants) || 2, + scenePath: filePath, + guildId: guildId ? String(guildId) : null, + characters: [museName] }); // Add to Base if configured @@ -1403,7 +1471,7 @@ export default class MultimuseObsidian extends Plugin { // ========= BASE INTEGRATION ========= - async addSceneToBase(file: TFile, frontmatter: any): Promise { + async addSceneToBase(file: TFile, frontmatter: FrontmatterData): Promise { /**Add scene to Obsidian Base with characters as variables.*/ if (!this.settings.basePath) return; @@ -1457,7 +1525,7 @@ export default class MultimuseObsidian extends Plugin { } } - async updateBaseRecord(file: TFile, thread: any): Promise { + async updateBaseRecord(file: TFile, thread: TrackedThread): Promise { /**Update existing Base record for a scene.*/ if (!this.settings.basePath) return; @@ -1482,7 +1550,7 @@ export default class MultimuseObsidian extends Plugin { const lines = baseContent.split('\n'); const updatedLines = lines.map(line => { if (line.includes(`| ${sceneName} |`)) { - const characters = [thread.muse_name]; + const characters = [thread.muse_name || 'Muse']; const link = thread.guild_id && thread.guild_id !== '0' ? `https://discord.com/channels/${thread.guild_id}/${thread.thread_id}` : `https://discord.com/channels/0/${thread.thread_id}`; @@ -1597,19 +1665,17 @@ export default class MultimuseObsidian extends Plugin { return `${RP_ROOT}/${relPath}`; } - showSuggester(items: string[], values: any[], title?: string): Promise { + showSuggester(items: string[], _values: T[], title?: string): Promise { return new Promise((resolve) => { // Create a modal with buttons const modal = new (class extends Modal { selectedIndex: number | null = null; items: string[]; - values: any[]; titleText: string; - constructor(app: App, items: string[], values: any[], titleText?: string) { + constructor(app: App, items: string[], titleText?: string) { super(app); this.items = items; - this.values = values; this.titleText = titleText || 'Select an option'; // Set title in constructor to ensure it's set before modal opens this.titleEl.textContent = this.titleText; @@ -1629,26 +1695,23 @@ export default class MultimuseObsidian extends Plugin { if (match) { const contextInfo = match[1]; const infoEl = contentEl.createEl('div', { - attr: { - style: 'margin-bottom: 20px; padding: 10px; background: var(--background-modifier-border); border-radius: 4px;' - } + cls: 'multimuse-scene-context' }); infoEl.createEl('strong', { text: `Creating scene file for: ${contextInfo}`, - attr: { style: 'color: var(--text-normal); font-size: 1.1em;' } + cls: 'multimuse-scene-context-title' }); } - const descEl = contentEl.createEl('p', { + contentEl.createEl('p', { text: 'Choose where to create the scene file:', - attr: { style: 'margin-top: 10px; margin-bottom: 15px; color: var(--text-muted);' } + cls: 'multimuse-scene-location-desc' }); } this.items.forEach((item, index) => { const button = contentEl.createEl('button', { text: item, - cls: 'mod-cta', - attr: { style: 'width: 100%; margin: 5px 0;' } + cls: ['mod-cta', 'multimuse-suggester-button'] }); button.onclick = () => { this.selectedIndex = index; @@ -1660,11 +1723,11 @@ export default class MultimuseObsidian extends Plugin { onClose() { resolve(this.selectedIndex); } - })(this.app, items, values, title); + })(this.app, items, title); console.log(`[MultimuseObsidian] showSuggester: Opening modal with ${items.length} items, title: ${title || 'Select an option'}`); // Use requestAnimationFrame to ensure modal opens after any previous modals are fully closed - requestAnimationFrame(() => { + window.requestAnimationFrame(() => { modal.open(); }); }); @@ -1682,9 +1745,9 @@ export default class MultimuseObsidian extends Plugin { this.titleEl.textContent = prompt; this.inputEl = this.contentEl.createEl('input', { type: 'text', - value: defaultValue || '' + value: defaultValue || '', + cls: 'multimuse-input' }); - this.inputEl.style.width = '100%'; this.inputEl.onkeydown = (e) => { if (e.key === 'Enter') { this.value = this.inputEl.value; @@ -1722,8 +1785,9 @@ export default class MultimuseObsidian extends Plugin { new Notice('No frontmatter. Add a Link property (Discord thread URL) to this note.'); return; } - const link = cache.frontmatter['Link']; - if (!link) { + const frontmatter = this.getFrontmatter(cache); + const link = frontmatter?.['Link']; + if (typeof link !== 'string') { new Notice('No Link property. Add the Discord thread URL to frontmatter to use @ mentions.'); return; } @@ -1736,7 +1800,7 @@ export default class MultimuseObsidian extends Plugin { new Notice('API key required in plugin settings to fetch server members.'); return; } - let members: { id: string; username: string; display_name: string }[]; + let members: GuildMember[]; try { const url = `${this.getBotApiUrl()}/api/v1/guilds/${threadInfo.guildId}/members`; const response = await requestUrl({ @@ -1750,7 +1814,7 @@ export default class MultimuseObsidian extends Plugin { } return; } - const data = JSON.parse(response.text); + const data = parseJson(response.text); members = data.members || []; } catch (e) { console.error('[MultimuseObsidian] insertMention fetch error:', e); @@ -1797,19 +1861,20 @@ export default class MultimuseObsidian extends Plugin { // Get frontmatter const cache = this.app.metadataCache.getFileCache(file); - if (!cache || !cache.frontmatter) { + const frontmatter = this.getFrontmatter(cache); + if (!frontmatter) { new Notice('File does not have frontmatter. Please add Link and Characters properties.'); return; } // Extract link and characters - const link = cache.frontmatter['Link']; - if (!link) { + const link = frontmatter?.['Link']; + if (typeof link !== 'string') { new Notice('No Link property found in frontmatter. Please add a Discord thread URL.'); return; } - const characters = this.getCharacterNames(cache.frontmatter); + const characters = this.getCharacterNames(frontmatter); if (characters.length === 0) { new Notice('No Characters property found in frontmatter. Please add at least one character name.'); return; @@ -1861,7 +1926,7 @@ export default class MultimuseObsidian extends Plugin { }); if (response.status === 200) { - const data = JSON.parse(response.text); + const data = parseJson(response.text); muses = data.muses || []; } else { if (!this.handleApiError(response, 'sendSelectionAsMuse - fetch muses')) { @@ -1909,17 +1974,16 @@ export default class MultimuseObsidian extends Plugin { }); if (response.status === 200) { - const data = JSON.parse(response.text); new Notice(`Message sent as ${selectedMuse}!`); } else { if (!this.handleApiError(response, 'sendSelectionAsMuse - post message')) { - const errorData = JSON.parse(response.text); + const errorData = parseJson(response.text); new Notice(`Failed to send message: ${errorData.message || response.status}`); } } - } catch (error: any) { + } catch (error) { console.error('Error sending message:', error); - const errorMessage = error?.message || error?.text || 'Unknown error'; + const errorMessage = getErrorMessage(error); new Notice(`Failed to send message: ${errorMessage}`); } } @@ -1938,7 +2002,9 @@ class MultimuseObsidianSettingTab extends PluginSettingTab { containerEl.empty(); - containerEl.createEl('h2', { text: 'Multimuse Obsidian Settings' }); + new Setting(containerEl) + .setName('Multimuse Settings') + .setHeading(); // Enable/Disable toggle new Setting(containerEl) @@ -1997,7 +2063,9 @@ class MultimuseObsidianSettingTab extends PluginSettingTab { })); // Scene Properties Tracking - containerEl.createEl('h3', { text: 'Scene Properties' }); + new Setting(containerEl) + .setName('Scene Properties') + .setHeading(); new Setting(containerEl) .setName('Track Roleplay Property') @@ -2059,8 +2127,8 @@ class MultimuseObsidianSettingTab extends PluginSettingTab { .setName('Detected User ID') .setDesc(`Your Discord user ID (automatically detected from API key): ${this.plugin.settings.cachedUserId}`) .addText(text => { - text.setValue(this.plugin.settings.cachedUserId) - .setDisabled(true); + text.setValue(this.plugin.settings.cachedUserId); + text.inputEl.disabled = true; }); } @@ -2084,14 +2152,16 @@ class MultimuseObsidianSettingTab extends PluginSettingTab { .setButtonText('Check Now') .setCta() .onClick(() => { - this.plugin.checkAllThreads(); + void this.plugin.checkAllThreads(); new Notice('Checking Discord threads...'); })); // Info section containerEl.createEl('hr'); const infoEl = containerEl.createEl('div'); - infoEl.createEl('h3', { text: 'How It Works' }); + new Setting(infoEl) + .setName('How It Works') + .setHeading(); infoEl.createEl('p', { text: 'This plugin queries the Multimuse API to check if your scene files match tracked threads and updates the "Replied?" and "Participants" fields.' }); infoEl.createEl('p', { text: '• Scenes are matched by Link (thread_id) and Characters properties' }); infoEl.createEl('p', { text: '• If scene matches a tracked thread: Updates Replied? only (true = you replied, false = need to reply). Participants is always editable in frontmatter.' }); diff --git a/manifest.json b/manifest.json index 73724c1..d07d0f1 100644 --- a/manifest.json +++ b/manifest.json @@ -1,9 +1,9 @@ { - "id": "multimuse-obsidian", - "name": "Multimuse Obsidian", - "version": "1.6.1", - "minAppVersion": "1.0.0", - "description": "Roleplay sync tool for use with the MultiMuse bot in discord", + "id": "multimuse-tracker", + "name": "Multimuse Tracker", + "version": "1.10.0", + "minAppVersion": "1.4.0", + "description": "Roleplay sync tool for use with the MultiMuse bot in discord.", "author": "BackstagePass Group", "authorUrl": "", "fundingUrl": "", diff --git a/package-lock.json b/package-lock.json index 5e66522..185620d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,19 +1,18 @@ { - "name": "discord-scene-tracker", - "version": "1.0.0", + "name": "multimuse-tracker", + "version": "1.10.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "discord-scene-tracker", - "version": "1.0.0", + "name": "multimuse-tracker", + "version": "1.10.0", "license": "MIT", "devDependencies": { "@types/node": "^16.11.6", "@typescript-eslint/eslint-plugin": "5.29.0", "@typescript-eslint/parser": "5.29.0", - "builtin-modules": "3.3.0", - "esbuild": "0.17.3", + "esbuild": "^0.28.0", "obsidian": "latest", "tslib": "2.4.0", "typescript": "4.7.4" @@ -44,10 +43,27 @@ "w3c-keyname": "^2.2.4" } }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.0.tgz", + "integrity": "sha512-lhRUCeuOyJQURhTxl4WkpFTjIsbDayJHih5kZC1giwE+MhIzAb7mEsQMqMf18rHLsrb5qI1tafG20mLxEWcWlA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/@esbuild/android-arm": { - "version": "0.17.3", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.17.3.tgz", - "integrity": "sha512-1Mlz934GvbgdDmt26rTLmf03cAgLg5HyOgJN+ZGCeP3Q9ynYTNMn2/LQxIl7Uy+o4K6Rfi2OuLsr12JQQR8gNg==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.0.tgz", + "integrity": "sha512-wqh0ByljabXLKHeWXYLqoJ5jKC4XBaw6Hk08OfMrCRd2nP2ZQ5eleDZC41XHyCNgktBGYMbqnrJKq/K/lzPMSQ==", "cpu": [ "arm" ], @@ -58,13 +74,13 @@ "android" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/android-arm64": { - "version": "0.17.3", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.17.3.tgz", - "integrity": "sha512-XvJsYo3dO3Pi4kpalkyMvfQsjxPWHYjoX4MDiB/FUM4YMfWcXa5l4VCwFWVYI1+92yxqjuqrhNg0CZg3gSouyQ==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.0.tgz", + "integrity": "sha512-+WzIXQOSaGs33tLEgYPYe/yQHf0WTU0X42Jca3y8NWMbUVhp7rUnw+vAsRC/QiDrdD31IszMrZy+qwPOPjd+rw==", "cpu": [ "arm64" ], @@ -75,13 +91,13 @@ "android" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/android-x64": { - "version": "0.17.3", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.17.3.tgz", - "integrity": "sha512-nuV2CmLS07Gqh5/GrZLuqkU9Bm6H6vcCspM+zjp9TdQlxJtIe+qqEXQChmfc7nWdyr/yz3h45Utk1tUn8Cz5+A==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.0.tgz", + "integrity": "sha512-+VJggoaKhk2VNNqVL7f6S189UzShHC/mR9EE8rDdSkdpN0KflSwWY/gWjDrNxxisg8Fp1ZCD9jLMo4m0OUfeUA==", "cpu": [ "x64" ], @@ -92,13 +108,13 @@ "android" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.17.3", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.17.3.tgz", - "integrity": "sha512-01Hxaaat6m0Xp9AXGM8mjFtqqwDjzlMP0eQq9zll9U85ttVALGCGDuEvra5Feu/NbP5AEP1MaopPwzsTcUq1cw==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.0.tgz", + "integrity": "sha512-0T+A9WZm+bZ84nZBtk1ckYsOvyA3x7e2Acj1KdVfV4/2tdG4fzUp91YHx+GArWLtwqp77pBXVCPn2We7Letr0Q==", "cpu": [ "arm64" ], @@ -109,13 +125,13 @@ "darwin" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.17.3", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.17.3.tgz", - "integrity": "sha512-Eo2gq0Q/er2muf8Z83X21UFoB7EU6/m3GNKvrhACJkjVThd0uA+8RfKpfNhuMCl1bKRfBzKOk6xaYKQZ4lZqvA==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.0.tgz", + "integrity": "sha512-fyzLm/DLDl/84OCfp2f/XQ4flmORsjU7VKt8HLjvIXChJoFFOIL6pLJPH4Yhd1n1gGFF9mPwtlN5Wf82DZs+LQ==", "cpu": [ "x64" ], @@ -126,13 +142,13 @@ "darwin" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.17.3", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.3.tgz", - "integrity": "sha512-CN62ESxaquP61n1ZjQP/jZte8CE09M6kNn3baos2SeUfdVBkWN5n6vGp2iKyb/bm/x4JQzEvJgRHLGd5F5b81w==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.0.tgz", + "integrity": "sha512-l9GeW5UZBT9k9brBYI+0WDffcRxgHQD8ShN2Ur4xWq/NFzUKm3k5lsH4PdaRgb2w7mI9u61nr2gI2mLI27Nh3Q==", "cpu": [ "arm64" ], @@ -143,13 +159,13 @@ "freebsd" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.17.3", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.17.3.tgz", - "integrity": "sha512-feq+K8TxIznZE+zhdVurF3WNJ/Sa35dQNYbaqM/wsCbWdzXr5lyq+AaTUSER2cUR+SXPnd/EY75EPRjf4s1SLg==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.0.tgz", + "integrity": "sha512-BXoQai/A0wPO6Es3yFJ7APCiKGc1tdAEOgeTNy3SsB491S3aHn4S4r3e976eUnPdU+NbdtmBuLncYir2tMU9Nw==", "cpu": [ "x64" ], @@ -160,13 +176,13 @@ "freebsd" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-arm": { - "version": "0.17.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.17.3.tgz", - "integrity": "sha512-CLP3EgyNuPcg2cshbwkqYy5bbAgK+VhyfMU7oIYyn+x4Y67xb5C5ylxsNUjRmr8BX+MW3YhVNm6Lq6FKtRTWHQ==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.0.tgz", + "integrity": "sha512-CjaaREJagqJp7iTaNQjjidaNbCKYcd4IDkzbwwxtSvjI7NZm79qiHc8HqciMddQ6CKvJT6aBd8lO9kN/ZudLlw==", "cpu": [ "arm" ], @@ -177,13 +193,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.17.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.17.3.tgz", - "integrity": "sha512-JHeZXD4auLYBnrKn6JYJ0o5nWJI9PhChA/Nt0G4MvLaMrvXuWnY93R3a7PiXeJQphpL1nYsaMcoV2QtuvRnF/g==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.0.tgz", + "integrity": "sha512-RVyzfb3FWsGA55n6WY0MEIEPURL1FcbhFE6BffZEMEekfCzCIMtB5yyDcFnVbTnwk+CLAgTujmV/Lgvih56W+A==", "cpu": [ "arm64" ], @@ -194,13 +210,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.17.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.17.3.tgz", - "integrity": "sha512-FyXlD2ZjZqTFh0sOQxFDiWG1uQUEOLbEh9gKN/7pFxck5Vw0qjWSDqbn6C10GAa1rXJpwsntHcmLqydY9ST9ZA==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.0.tgz", + "integrity": "sha512-KBnSTt1kxl9x70q+ydterVdl+Cn0H18ngRMRCEQfrbqdUuntQQ0LoMZv47uB97NljZFzY6HcfqEZ2SAyIUTQBQ==", "cpu": [ "ia32" ], @@ -211,13 +227,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.17.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.17.3.tgz", - "integrity": "sha512-OrDGMvDBI2g7s04J8dh8/I7eSO+/E7nMDT2Z5IruBfUO/RiigF1OF6xoH33Dn4W/OwAWSUf1s2nXamb28ZklTA==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.0.tgz", + "integrity": "sha512-zpSlUce1mnxzgBADvxKXX5sl8aYQHo2ezvMNI8I0lbblJtp8V4odlm3Yzlj7gPyt3T8ReksE6bK+pT3WD+aJRg==", "cpu": [ "loong64" ], @@ -228,13 +244,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.17.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.17.3.tgz", - "integrity": "sha512-DcnUpXnVCJvmv0TzuLwKBC2nsQHle8EIiAJiJ+PipEVC16wHXaPEKP0EqN8WnBe0TPvMITOUlP2aiL5YMld+CQ==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.0.tgz", + "integrity": "sha512-2jIfP6mmjkdmeTlsX/9vmdmhBmKADrWqN7zcdtHIeNSCH1SqIoNI63cYsjQR8J+wGa4Y5izRcSHSm8K3QWmk3w==", "cpu": [ "mips64el" ], @@ -245,13 +261,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.17.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.17.3.tgz", - "integrity": "sha512-BDYf/l1WVhWE+FHAW3FzZPtVlk9QsrwsxGzABmN4g8bTjmhazsId3h127pliDRRu5674k1Y2RWejbpN46N9ZhQ==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.0.tgz", + "integrity": "sha512-bc0FE9wWeC0WBm49IQMPSPILRocGTQt3j5KPCA8os6VprfuJ7KD+5PzESSrJ6GmPIPJK965ZJHTUlSA6GNYEhg==", "cpu": [ "ppc64" ], @@ -262,13 +278,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.17.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.17.3.tgz", - "integrity": "sha512-WViAxWYMRIi+prTJTyV1wnqd2mS2cPqJlN85oscVhXdb/ZTFJdrpaqm/uDsZPGKHtbg5TuRX/ymKdOSk41YZow==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.0.tgz", + "integrity": "sha512-SQPZOwoTTT/HXFXQJG/vBX8sOFagGqvZyXcgLA3NhIqcBv1BJU1d46c0rGcrij2B56Z2rNiSLaZOYW5cUk7yLQ==", "cpu": [ "riscv64" ], @@ -279,13 +295,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.17.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.17.3.tgz", - "integrity": "sha512-Iw8lkNHUC4oGP1O/KhumcVy77u2s6+KUjieUqzEU3XuWJqZ+AY7uVMrrCbAiwWTkpQHkr00BuXH5RpC6Sb/7Ug==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.0.tgz", + "integrity": "sha512-SCfR0HN8CEEjnYnySJTd2cw0k9OHB/YFzt5zgJEwa+wL/T/raGWYMBqwDNAC6dqFKmJYZoQBRfHjgwLHGSrn3Q==", "cpu": [ "s390x" ], @@ -296,13 +312,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-x64": { - "version": "0.17.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.17.3.tgz", - "integrity": "sha512-0AGkWQMzeoeAtXQRNB3s4J1/T2XbigM2/Mn2yU1tQSmQRmHIZdkGbVq2A3aDdNslPyhb9/lH0S5GMTZ4xsjBqg==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.0.tgz", + "integrity": "sha512-us0dSb9iFxIi8srnpl931Nvs65it/Jd2a2K3qs7fz2WfGPHqzfzZTfec7oxZJRNPXPnNYZtanmRc4AL/JwVzHQ==", "cpu": [ "x64" ], @@ -313,13 +329,30 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.0.tgz", + "integrity": "sha512-CR/RYotgtCKwtftMwJlUU7xCVNg3lMYZ0RzTmAHSfLCXw3NtZtNpswLEj/Kkf6kEL3Gw+BpOekRX0BYCtklhUw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.17.3", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.17.3.tgz", - "integrity": "sha512-4+rR/WHOxIVh53UIQIICryjdoKdHsFZFD4zLSonJ9RRw7bhKzVyXbnRPsWSfwybYqw9sB7ots/SYyufL1mBpEg==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.0.tgz", + "integrity": "sha512-nU1yhmYutL+fQ71Kxnhg8uEOdC0pwEW9entHykTgEbna2pw2dkbFSMeqjjyHZoCmt8SBkOSvV+yNmm94aUrrqw==", "cpu": [ "x64" ], @@ -330,13 +363,30 @@ "netbsd" ], "engines": { - "node": ">=12" + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.0.tgz", + "integrity": "sha512-cXb5vApOsRsxsEl4mcZ1XY3D4DzcoMxR/nnc4IyqYs0rTI8ZKmW6kyyg+11Z8yvgMfAEldKzP7AdP64HnSC/6g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.17.3", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.17.3.tgz", - "integrity": "sha512-cVpWnkx9IYg99EjGxa5Gc0XmqumtAwK3aoz7O4Dii2vko+qXbkHoujWA68cqXjhh6TsLaQelfDO4MVnyr+ODeA==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.0.tgz", + "integrity": "sha512-8wZM2qqtv9UP3mzy7HiGYNH/zjTA355mpeuA+859TyR+e+Tc08IHYpLJuMsfpDJwoLo1ikIJI8jC3GFjnRClzA==", "cpu": [ "x64" ], @@ -347,13 +397,30 @@ "openbsd" ], "engines": { - "node": ">=12" + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.0.tgz", + "integrity": "sha512-FLGfyizszcef5C3YtoyQDACyg95+dndv79i2EekILBofh5wpCa1KuBqOWKrEHZg3zrL3t5ouE5jgr94vA+Wb2w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.17.3", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.17.3.tgz", - "integrity": "sha512-RxmhKLbTCDAY2xOfrww6ieIZkZF+KBqG7S2Ako2SljKXRFi+0863PspK74QQ7JpmWwncChY25JTJSbVBYGQk2Q==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.0.tgz", + "integrity": "sha512-1ZgjUoEdHZZl/YlV76TSCz9Hqj9h9YmMGAgAPYd+q4SicWNX3G5GCyx9uhQWSLcbvPW8Ni7lj4gDa1T40akdlw==", "cpu": [ "x64" ], @@ -364,13 +431,13 @@ "sunos" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.17.3", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.17.3.tgz", - "integrity": "sha512-0r36VeEJ4efwmofxVJRXDjVRP2jTmv877zc+i+Pc7MNsIr38NfsjkQj23AfF7l0WbB+RQ7VUb+LDiqC/KY/M/A==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.0.tgz", + "integrity": "sha512-Q9StnDmQ/enxnpxCCLSg0oo4+34B9TdXpuyPeTedN/6+iXBJ4J+zwfQI28u/Jl40nOYAxGoNi7mFP40RUtkmUA==", "cpu": [ "arm64" ], @@ -381,13 +448,13 @@ "win32" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.17.3", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.17.3.tgz", - "integrity": "sha512-wgO6rc7uGStH22nur4aLFcq7Wh86bE9cOFmfTr/yxN3BXvDEdCSXyKkO+U5JIt53eTOgC47v9k/C1bITWL/Teg==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.0.tgz", + "integrity": "sha512-zF3ag/gfiCe6U2iczcRzSYJKH1DCI+ByzSENHlM2FcDbEeo5Zd2C86Aq0tKUYAJJ1obRP84ymxIAksZUcdztHA==", "cpu": [ "ia32" ], @@ -398,13 +465,13 @@ "win32" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/win32-x64": { - "version": "0.17.3", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.17.3.tgz", - "integrity": "sha512-FdVl64OIuiKjgXBjwZaJLKp0eaEckifbhn10dXWhysMJkWblg3OEEGKSIyhiD5RSgAya8WzP3DNkngtIg3Nt7g==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.0.tgz", + "integrity": "sha512-pEl1bO9mfAmIC+tW5btTmrKaujg3zGtUmWNdCw/xs70FBjwAL3o9OEKNHvNmnyylD6ubxUERiEhdsL0xBQ9efw==", "cpu": [ "x64" ], @@ -415,7 +482,7 @@ "win32" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@eslint-community/eslint-utils": { @@ -830,9 +897,9 @@ } }, "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", "dev": true, "license": "MIT", "dependencies": { @@ -897,9 +964,9 @@ "license": "MIT" }, "node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", "dev": true, "license": "MIT", "dependencies": { @@ -920,19 +987,6 @@ "node": ">=8" } }, - "node_modules/builtin-modules": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz", - "integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -1061,9 +1115,9 @@ } }, "node_modules/esbuild": { - "version": "0.17.3", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.17.3.tgz", - "integrity": "sha512-9n3AsBRe6sIyOc6kmoXg2ypCLgf3eZSraWFRpnkto+svt8cZNuKTkb1bhQcitBcvIqjNiK7K0J3KPmwGSfkA8g==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.0.tgz", + "integrity": "sha512-sNR9MHpXSUV/XB4zmsFKN+QgVG82Cc7+/aaxJ8Adi8hyOac+EXptIp45QBPaVyX3N70664wRbTcLTOemCAnyqw==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -1071,31 +1125,35 @@ "esbuild": "bin/esbuild" }, "engines": { - "node": ">=12" + "node": ">=18" }, "optionalDependencies": { - "@esbuild/android-arm": "0.17.3", - "@esbuild/android-arm64": "0.17.3", - "@esbuild/android-x64": "0.17.3", - "@esbuild/darwin-arm64": "0.17.3", - "@esbuild/darwin-x64": "0.17.3", - "@esbuild/freebsd-arm64": "0.17.3", - "@esbuild/freebsd-x64": "0.17.3", - "@esbuild/linux-arm": "0.17.3", - "@esbuild/linux-arm64": "0.17.3", - "@esbuild/linux-ia32": "0.17.3", - "@esbuild/linux-loong64": "0.17.3", - "@esbuild/linux-mips64el": "0.17.3", - "@esbuild/linux-ppc64": "0.17.3", - "@esbuild/linux-riscv64": "0.17.3", - "@esbuild/linux-s390x": "0.17.3", - "@esbuild/linux-x64": "0.17.3", - "@esbuild/netbsd-x64": "0.17.3", - "@esbuild/openbsd-x64": "0.17.3", - "@esbuild/sunos-x64": "0.17.3", - "@esbuild/win32-arm64": "0.17.3", - "@esbuild/win32-ia32": "0.17.3", - "@esbuild/win32-x64": "0.17.3" + "@esbuild/aix-ppc64": "0.28.0", + "@esbuild/android-arm": "0.28.0", + "@esbuild/android-arm64": "0.28.0", + "@esbuild/android-x64": "0.28.0", + "@esbuild/darwin-arm64": "0.28.0", + "@esbuild/darwin-x64": "0.28.0", + "@esbuild/freebsd-arm64": "0.28.0", + "@esbuild/freebsd-x64": "0.28.0", + "@esbuild/linux-arm": "0.28.0", + "@esbuild/linux-arm64": "0.28.0", + "@esbuild/linux-ia32": "0.28.0", + "@esbuild/linux-loong64": "0.28.0", + "@esbuild/linux-mips64el": "0.28.0", + "@esbuild/linux-ppc64": "0.28.0", + "@esbuild/linux-riscv64": "0.28.0", + "@esbuild/linux-s390x": "0.28.0", + "@esbuild/linux-x64": "0.28.0", + "@esbuild/netbsd-arm64": "0.28.0", + "@esbuild/netbsd-x64": "0.28.0", + "@esbuild/openbsd-arm64": "0.28.0", + "@esbuild/openbsd-x64": "0.28.0", + "@esbuild/openharmony-arm64": "0.28.0", + "@esbuild/sunos-x64": "0.28.0", + "@esbuild/win32-arm64": "0.28.0", + "@esbuild/win32-ia32": "0.28.0", + "@esbuild/win32-x64": "0.28.0" } }, "node_modules/escape-string-regexp": { @@ -1456,9 +1514,9 @@ } }, "node_modules/flatted": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", - "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", "dev": true, "license": "ISC" }, @@ -1777,9 +1835,9 @@ } }, "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "license": "ISC", "dependencies": { @@ -1942,9 +2000,9 @@ } }, "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", "dev": true, "license": "MIT", "engines": { diff --git a/package.json b/package.json index c3efcce..321b6e4 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { - "name": "multimuse-obsidian", - "version": "1.3.0", - "description": "Obsidian plugin for MultiMuse bot integration - track Discord threads and send messages as muses", + "name": "multimuse-tracker", + "version": "1.10.0", + "description": "MultiMuse bot integration for tracking Discord threads and sending messages as muses", "main": "main.js", "scripts": { "dev": "node esbuild.config.mjs", @@ -15,11 +15,9 @@ "@types/node": "^16.11.6", "@typescript-eslint/eslint-plugin": "5.29.0", "@typescript-eslint/parser": "5.29.0", - "builtin-modules": "3.3.0", - "esbuild": "0.17.3", + "esbuild": "^0.28.0", "obsidian": "latest", "tslib": "2.4.0", "typescript": "4.7.4" } } - diff --git a/styles.css b/styles.css index f6c57e3..0f211fd 100644 --- a/styles.css +++ b/styles.css @@ -17,3 +17,30 @@ border-top: none; } +.multimuse-scene-context { + margin-bottom: 20px; + padding: 10px; + background: var(--background-modifier-border); + border-radius: 4px; +} + +.multimuse-scene-context-title { + color: var(--text-normal); + font-size: 1.1em; +} + +.multimuse-scene-location-desc { + margin-top: 10px; + margin-bottom: 15px; + color: var(--text-muted); +} + +.multimuse-suggester-button, +.multimuse-input { + width: 100%; +} + +.multimuse-suggester-button { + margin: 5px 0; +} +