1.10.0 Update

This commit is contained in:
Tea 2026-05-12 16:28:45 -04:00
parent 4879d84188
commit e51eb40aaa
7 changed files with 501 additions and 348 deletions

View file

@ -1,4 +1,4 @@
# Multimuse Obsidian # Multimuse Tracker
For use with the BRAT Plugin for the best experiance. 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. 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 ### 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 2. Paste your API key in the "API Key" field
3. Your Discord user ID will be automatically detected from the API key 3. Your Discord user ID will be automatically detected from the API key
4. Configure other settings: 4. Configure other settings:
@ -209,4 +209,4 @@ MIT
## Credits ## Credits
- **Author**: BackstagePass Group - **Author**: BackstagePass Group
- **Plugin**: Multimuse Obsidian - **Plugin**: Multimuse Tracker

View file

@ -1,6 +1,6 @@
import esbuild from "esbuild"; import esbuild from "esbuild";
import process from "process"; import process from "process";
import builtins from "builtin-modules"; import { builtinModules } from "module";
const isProduction = process.argv.includes("production"); const isProduction = process.argv.includes("production");
@ -21,7 +21,7 @@ const ctx = await esbuild.context({
"@lezer/common", "@lezer/common",
"@lezer/highlight", "@lezer/highlight",
"@lezer/lr", "@lezer/lr",
...builtins], ...builtinModules],
format: "cjs", format: "cjs",
target: "es2018", target: "es2018",
logLevel: "info", logLevel: "info",

438
main.ts
View file

@ -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 { interface MultimuseObsidianSettings {
botApiUrl: string; // Bot HTTP API URL (hidden from user UI for security) 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 muse_id?: string | null; // Optional: for API calls (alias-safe); display always uses name
} }
type FrontmatterData = Record<string, unknown>;
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<T>(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 { export default class MultimuseObsidian extends Plugin {
settings: MultimuseObsidianSettings; settings: MultimuseObsidianSettings;
pollIntervalId: number | null = null; pollIntervalId: number | null = null;
@ -71,7 +145,7 @@ export default class MultimuseObsidian extends Plugin {
id: 'check-discord-threads', id: 'check-discord-threads',
name: 'Check Discord Threads Now', name: 'Check Discord Threads Now',
callback: () => { callback: () => {
this.checkAllThreads(); void this.checkAllThreads();
} }
}); });
@ -81,7 +155,7 @@ export default class MultimuseObsidian extends Plugin {
name: 'Toggle Discord Polling', name: 'Toggle Discord Polling',
callback: () => { callback: () => {
this.settings.enabled = !this.settings.enabled; this.settings.enabled = !this.settings.enabled;
this.saveSettings(); void this.saveSettings();
if (this.settings.enabled) { if (this.settings.enabled) {
this.startPolling(); this.startPolling();
new Notice('Discord polling enabled'); new Notice('Discord polling enabled');
@ -97,7 +171,7 @@ export default class MultimuseObsidian extends Plugin {
id: 'create-scene', id: 'create-scene',
name: 'Create New Scene', name: 'Create New Scene',
callback: () => { callback: () => {
this.createNewScene(); void this.createNewScene();
} }
}); });
@ -106,7 +180,7 @@ export default class MultimuseObsidian extends Plugin {
id: 'sync-from-tracker', id: 'sync-from-tracker',
name: 'Sync from Tracker', name: 'Sync from Tracker',
callback: () => { callback: () => {
this.syncFromTracker(); void this.syncFromTracker();
} }
}); });
@ -208,11 +282,11 @@ export default class MultimuseObsidian extends Plugin {
const intervalMs = this.settings.pollInterval * 60 * 1000; const intervalMs = this.settings.pollInterval * 60 * 1000;
this.pollIntervalId = window.setInterval(() => { this.pollIntervalId = window.setInterval(() => {
this.checkAllThreads(); void this.checkAllThreads();
}, intervalMs); }, intervalMs);
// Do an initial check // Do an initial check
this.checkAllThreads(); void this.checkAllThreads();
} }
stopPolling() { stopPolling() {
@ -250,13 +324,47 @@ export default class MultimuseObsidian extends Plugin {
return headers; return headers;
} }
async trackThread(params: {
threadId: string;
userId: string;
museName: string;
participants: number;
scenePath?: string;
guildId?: string | null;
characters?: string[];
}): Promise<RequestUrlResponse> {
const body: Record<string, unknown> = {
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. * Handle API response errors, especially authentication errors.
* @param response The response object from requestUrl * @param response The response object from requestUrl
* @param context Context string for logging * @param context Context string for logging
* @returns true if error was handled, false otherwise * @returns true if error was handled, false otherwise
*/ */
handleApiError(response: any, context: string): boolean { handleApiError(response: RequestUrlResponse, context: string): boolean {
if (response.status === 401) { if (response.status === 401) {
const errorMsg = 'API authentication failed. Please check your API key in settings.'; const errorMsg = 'API authentication failed. Please check your API key in settings.';
console.error(`[MultimuseObsidian] ${context}: ${errorMsg}`); console.error(`[MultimuseObsidian] ${context}: ${errorMsg}`);
@ -291,7 +399,7 @@ export default class MultimuseObsidian extends Plugin {
}); });
if (response.status === 200) { if (response.status === 200) {
const data = JSON.parse(response.text); const data = parseJson<AuthMeResponse>(response.text);
const userId = data.user_id; const userId = data.user_id;
if (userId) { if (userId) {
// Cache the user ID // Cache the user ID
@ -369,7 +477,7 @@ export default class MultimuseObsidian extends Plugin {
}); });
if (response.status === 200) { if (response.status === 200) {
const data = JSON.parse(response.text); const data = parseJson<MusesListResponse>(response.text);
const muses: MuseInfo[] = data.muses || []; const muses: MuseInfo[] = data.muses || [];
// Cache all muses for each user ID (API already returns all accessible muses for all provided user IDs) // 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<void> { async checkAllThreadsViaBotApi(): Promise<void> {
/**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) { if (!this.settings.apiKey) {
return; return;
} }
@ -416,34 +524,37 @@ export default class MultimuseObsidian extends Plugin {
return; return;
} }
// Get all linked scenes from the API // Get all Discord-side tracked threads from the current API.
const linkedUrl = `${this.getBotApiUrl()}/api/v1/scenes/linked?user_id=${primaryUserId}`; const trackedUrl = `${this.getBotApiUrl()}/api/v1/threads/tracked?user_id=${primaryUserId}`;
const linkedResponse = await requestUrl({ const trackedResponse = await requestUrl({
url: linkedUrl, url: trackedUrl,
method: 'GET', method: 'GET',
headers: this.getApiHeaders() headers: this.getApiHeaders()
}); });
if (linkedResponse.status !== 200) { if (trackedResponse.status !== 200) {
if (!this.handleApiError(linkedResponse, 'checkAllThreadsViaBotApi')) { if (!this.handleApiError(trackedResponse, 'checkAllThreadsViaBotApi')) {
console.error(`[MultimuseObsidian] Failed to fetch linked scenes: ${linkedResponse.status} - ${linkedResponse.text}`); console.error(`[MultimuseObsidian] Failed to fetch tracked threads: ${trackedResponse.status} - ${trackedResponse.text}`);
} }
return; return;
} }
const linkedData = JSON.parse(linkedResponse.text); const trackedData = parseJson<TrackedThreadsResponse>(trackedResponse.text);
const linkedThreads = linkedData.linked_threads || []; const trackedThreads = trackedData.threads || [];
if (linkedThreads.length === 0) { if (trackedThreads.length === 0) {
return; return;
} }
// Create a map of scene_path -> thread info for quick lookup // Create a map of scene_path -> thread info for quick lookup.
const scenePathMap = new Map<string, any>(); const scenePathMap = new Map<string, TrackedThread>();
for (const thread of linkedThreads) { for (const thread of trackedThreads) {
if (thread.scene_path) { if (thread.scene_path) {
scenePathMap.set(thread.scene_path, thread); scenePathMap.set(thread.scene_path, thread);
} }
for (const scenePath of thread.scene_paths || []) {
scenePathMap.set(scenePath, thread);
}
} }
// Get all scene files // Get all scene files
@ -460,12 +571,13 @@ export default class MultimuseObsidian extends Plugin {
} }
const cache = this.app.metadataCache.getFileCache(file); const cache = this.app.metadataCache.getFileCache(file);
if (!cache || !cache.frontmatter) { const frontmatter = this.getFrontmatter(cache);
if (!frontmatter) {
continue; continue;
} }
// Skip if scene is marked as inactive // Skip if scene is marked as inactive
const isActive = cache.frontmatter['Is Active?']; const isActive = frontmatter['Is Active?'];
if (isActive === false || isActive === 'false') { if (isActive === false || isActive === 'false') {
continue; continue;
} }
@ -473,11 +585,11 @@ export default class MultimuseObsidian extends Plugin {
// Check if this scene is in the linked threads // Check if this scene is in the linked threads
const threadInfo = scenePathMap.get(file.path); const threadInfo = scenePathMap.get(file.path);
if (!threadInfo) { if (!threadInfo) {
// Scene not linked, try querying by thread_id and characters // Scene not linked in thread_tracking, try querying by thread_id and characters.
const link = cache.frontmatter['Link']; const link = frontmatter['Link'];
if (!link) continue; if (typeof link !== 'string') continue;
const characters = this.getCharacterNames(cache.frontmatter); const characters = this.getCharacterNames(frontmatter);
if (characters.length === 0) continue; if (characters.length === 0) continue;
const threadId = this.extractThreadIdFromUrl(link); const threadId = this.extractThreadIdFromUrl(link);
@ -490,10 +602,10 @@ export default class MultimuseObsidian extends Plugin {
} }
} else { } else {
// Scene is linked - query its state // Scene is linked - query its state
const characters = threadInfo.characters || []; const characters = this.getCharacterNames(frontmatter);
if (characters.length === 0) continue; if (characters.length === 0) continue;
const charactersParam = Array.isArray(characters) ? characters.join(',') : characters; const charactersParam = characters.join(',');
// Use primary user ID for query // Use primary user ID for query
const primaryUserId = await this.getPrimaryUserId(); const primaryUserId = await this.getPrimaryUserId();
if (!primaryUserId) { if (!primaryUserId) {
@ -508,7 +620,7 @@ export default class MultimuseObsidian extends Plugin {
}); });
if (queryResponse.status === 200) { if (queryResponse.status === 200) {
const queryData = JSON.parse(queryResponse.text); const queryData = parseJson<SceneQueryResponse>(queryResponse.text);
if (queryData.tracked && queryData.state) { if (queryData.tracked && queryData.state) {
const state = queryData.state; const state = queryData.state;
@ -546,22 +658,23 @@ export default class MultimuseObsidian extends Plugin {
} }
const cache = this.app.metadataCache.getFileCache(file); const cache = this.app.metadataCache.getFileCache(file);
if (!cache || !cache.frontmatter) { const frontmatter = this.getFrontmatter(cache);
if (!cache || !frontmatter) {
return false; return false;
} }
// Skip if scene is marked as inactive // Skip if scene is marked as inactive
const isActive = cache.frontmatter['Is Active?']; const isActive = frontmatter['Is Active?'];
if (isActive === false || isActive === 'false') { if (isActive === false || isActive === 'false') {
return false; // Skip inactive scenes return false; // Skip inactive scenes
} }
const link = cache.frontmatter['Link']; const link = frontmatter['Link'];
if (!link) { if (typeof link !== 'string') {
return false; return false;
} }
const characters = this.getCharacterNames(cache.frontmatter); const characters = this.getCharacterNames(frontmatter);
if (characters.length === 0) { if (characters.length === 0) {
return false; return false;
} }
@ -594,7 +707,7 @@ export default class MultimuseObsidian extends Plugin {
return false; return false;
} }
const data = JSON.parse(response.text); const data = parseJson<SceneQueryResponse>(response.text);
// If scene is not tracked, don't update anything // If scene is not tracked, don't update anything
if (!data.tracked || !data.state) { if (!data.tracked || !data.state) {
@ -621,9 +734,13 @@ export default class MultimuseObsidian extends Plugin {
} }
} }
async updateSceneFromState(file: TFile, cache: any, state: any): Promise<boolean> { async updateSceneFromState(file: TFile, cache: CachedMetadata, state: SceneState): Promise<boolean> {
/**Update scene frontmatter from API state data.*/ /**Update scene frontmatter from API state data.*/
let updated = false; let updated = false;
const frontmatter = this.getFrontmatter(cache);
if (!frontmatter) {
return false;
}
// Use replied or is_from_character (API may send either) // Use replied or is_from_character (API may send either)
const repliedRaw = state.replied ?? state.is_from_character; 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 // Update Replied? field - normalize boolean values for comparison
const currentRepliedRaw = cache.frontmatter['Replied?']; const currentRepliedRaw = frontmatter['Replied?'];
// Handle both boolean and string values // Handle both boolean and string values
const currentReplied = currentRepliedRaw === true || currentRepliedRaw === 'true' || currentRepliedRaw === 'True'; const currentReplied = currentRepliedRaw === true || currentRepliedRaw === 'true' || currentRepliedRaw === 'True';
// true = you've replied (no need to reply), false = need to reply // 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 // 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); 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 // Sync Is Active? to the tracker so unchecking removes the scene from MultiMuse
await this.syncSceneActiveStatusToApi(file, cache); await this.syncSceneActiveStatusToApi(file, cache);
// Sync Participants to the tracker so "your turn" / Replied? use the correct count // 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(); const primaryUserId = await this.getPrimaryUserId();
if (!primaryUserId) return; 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 raw = cache.frontmatter?.['Is Active?'];
const isActive = raw !== false && raw !== 'false'; const isActive = raw !== false && raw !== 'false';
@ -776,9 +895,15 @@ export default class MultimuseObsidian extends Plugin {
const primaryUserId = await this.getPrimaryUserId(); const primaryUserId = await this.getPrimaryUserId();
if (!primaryUserId) return; 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 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; if (isNaN(participants) || participants < 1) return;
try { try {
@ -827,10 +952,10 @@ export default class MultimuseObsidian extends Plugin {
return map; return map;
} }
collectMarkdownFiles(fileOrFolder: any, files: TFile[]): void { collectMarkdownFiles(fileOrFolder: TAbstractFile, files: TFile[]): void {
if (fileOrFolder instanceof TFile && fileOrFolder.extension === 'md') { if (fileOrFolder instanceof TFile && fileOrFolder.extension === 'md') {
files.push(fileOrFolder); files.push(fileOrFolder);
} else if ('children' in fileOrFolder) { } else if (fileOrFolder instanceof TFolder) {
for (const child of fileOrFolder.children) { for (const child of fileOrFolder.children) {
this.collectMarkdownFiles(child, files); 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']; const characters = frontmatter['Characters'];
if (!characters) { if (!characters) {
return []; return [];
@ -879,7 +1004,7 @@ export default class MultimuseObsidian extends Plugin {
} }
async updateFrontmatter(file: TFile, key: string, value: any): Promise<void> { async updateFrontmatter(file: TFile, key: string, value: FrontmatterValue): Promise<void> {
const content = await this.app.vault.read(file); const content = await this.app.vault.read(file);
// Parse frontmatter // Parse frontmatter
@ -961,7 +1086,7 @@ export default class MultimuseObsidian extends Plugin {
}); });
if (response.status === 200) { if (response.status === 200) {
const data = JSON.parse(response.text); const data = parseJson<MusesListResponse>(response.text);
muses = data.muses || []; muses = data.muses || [];
console.log(`[MultimuseObsidian] Found ${muses.length} muse(s) from ${userIds.length} user(s)`); console.log(`[MultimuseObsidian] Found ${muses.length} muse(s) from ${userIds.length} user(s)`);
if (muses.length > 0) { if (muses.length > 0) {
@ -996,7 +1121,7 @@ export default class MultimuseObsidian extends Plugin {
const selectedMuse = muses[selectedMuseIndex]; const selectedMuse = muses[selectedMuseIndex];
// Small delay to ensure previous modal is fully closed // 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 // 3) Get Discord thread/channel link
const threadUrl = await this.showInputPrompt('Enter Discord thread/channel URL'); 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 // 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 // 4) Get location (RP folder) - pass muse name for context
console.log(`[MultimuseObsidian] createNewScene: About to select location for muse "${selectedMuse.name}"`); 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 // 7) Create scene file
const filePath = `${location}/${sceneName}.md`; const filePath = `${location}/${sceneName}.md`;
const frontmatter: Record<string, any> = { const frontmatter: Record<string, FrontmatterValue> = {
'Link': threadUrl, 'Link': threadUrl,
'Characters': [selectedMuse.name], 'Characters': [selectedMuse.name],
'Participants': participants, 'Participants': participants,
@ -1089,80 +1214,37 @@ export default class MultimuseObsidian extends Plugin {
// Mark this file as recently created to skip immediate checking // Mark this file as recently created to skip immediate checking
this.recentlyCreatedFiles.add(filePath); this.recentlyCreatedFiles.add(filePath);
// Remove from the set after 60 seconds (enough time for the scene to be registered and settled with the API) // 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); this.recentlyCreatedFiles.delete(filePath);
console.log(`[MultimuseObsidian] Removed ${filePath} from recently created files - will now be checked by polling`); console.log(`[MultimuseObsidian] Removed ${filePath} from recently created files - will now be checked by polling`);
}, 60000); }, 60000);
// 8) Register with bot API and optionally create thread tracking // 8) Link the vault scene to the current Discord-side thread tracker.
try { try {
// Register scene console.debug(`Tracking scene: threadId=${threadInfo.threadId}, guildId=${threadInfo.guildId}, channelId=${threadInfo.channelId}, url=${threadUrl}`);
// Log the extracted thread info for debugging
console.debug(`Registering scene: threadId=${threadInfo.threadId}, guildId=${threadInfo.guildId}, channelId=${threadInfo.channelId}, url=${threadUrl}`);
// Convert IDs to strings to avoid JavaScript number precision loss // 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 // 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(); const primaryUserId = await this.getPrimaryUserId();
if (!primaryUserId) { if (!primaryUserId) {
new Notice('Failed to get user ID from API key. Please check your API key in settings.'); new Notice('Failed to get user ID from API key. Please check your API key in settings.');
return; return;
} }
const registerResponse = await requestUrl({ const registerResponse = await this.trackThread({
url: `${this.getBotApiUrl()}/api/v1/scenes/create`, threadId: threadInfo.threadId,
method: 'POST', userId: primaryUserId,
headers: this.getApiHeaders(), museName: selectedMuse.name,
body: JSON.stringify({ participants: participants,
thread_id: threadInfo.threadId, // Send as string, let Python convert scenePath: createdFile.path,
user_id: primaryUserId, // Send as string guildId: threadInfo.guildId || null,
scene_path: createdFile.path, characters: [selectedMuse.name]
characters: [selectedMuse.name],
participants: participants,
is_active: true,
guild_id: threadInfo.guildId || null // Send as string or null
})
}); });
console.debug(`Scene registration response: ${registerResponse.status} - ${registerResponse.text}`); console.debug(`Thread tracking response: ${registerResponse.status} - ${registerResponse.text}`);
if (registerResponse.status === 200) { 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<string, unknown> = {
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 // Add to Base if configured
try { try {
if (this.settings.basePath) { if (this.settings.basePath) {
@ -1176,29 +1258,20 @@ export default class MultimuseObsidian extends Plugin {
await this.app.workspace.getLeaf(true).openFile(createdFile); await this.app.workspace.getLeaf(true).openFile(createdFile);
} else if (registerResponse.status === 401) { } else if (registerResponse.status === 401) {
// Authentication error - show helpful message // Authentication error - show helpful message
this.handleApiError(registerResponse, 'createNewScene - register scene'); this.handleApiError(registerResponse, 'createNewScene - track thread');
new Notice('Scene created but failed to register with bot: Authentication failed. Check your API key.'); new Notice('Scene created but failed to track with bot: Authentication failed. Check your API key.');
} else { } else {
const errorText = registerResponse.text || 'Unknown error'; const errorText = registerResponse.text || 'Unknown error';
console.error(`Failed to register scene: ${registerResponse.status} - ${errorText}`); console.error(`Failed to track thread: ${registerResponse.status} - ${errorText}`);
new Notice(`Scene created but failed to register with bot: ${registerResponse.status}`); new Notice(`Scene created but failed to track with bot: ${registerResponse.status}`);
} }
} catch (error: any) { } catch (error) {
// Log the full error for debugging // Log the full error for debugging
console.error('Error registering scene:', error); console.error('Error tracking thread:', error);
const errorMessage = error?.message || error?.text || 'Unknown error'; const errorMessage = getErrorMessage(error);
console.error('Error details:', errorMessage); console.error('Error details:', errorMessage);
// Only show error if scene registration itself failed new Notice(`Scene created but failed to track with bot: ${errorMessage}`);
// 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}`);
}
} }
} }
@ -1232,7 +1305,7 @@ export default class MultimuseObsidian extends Plugin {
return; return;
} }
const data = JSON.parse(response.text); const data = parseJson<TrackedThreadsResponse>(response.text);
const threads = data.threads || []; const threads = data.threads || [];
if (threads.length === 0) { if (threads.length === 0) {
@ -1248,7 +1321,7 @@ export default class MultimuseObsidian extends Plugin {
for (const thread of threads) { for (const thread of threads) {
const threadId = String(thread.thread_id ?? ''); const threadId = String(thread.thread_id ?? '');
const museName = thread.muse_name; const museName = thread.muse_name || 'Muse';
const participants = thread.participants || 2; const participants = thread.participants || 2;
const existingScenePath = thread.scene_path; const existingScenePath = thread.scene_path;
@ -1298,7 +1371,7 @@ export default class MultimuseObsidian extends Plugin {
const sceneName = thread.thread_name || `${museName} - Thread ${threadId}`; const sceneName = thread.thread_name || `${museName} - Thread ${threadId}`;
const filePath = `${location}/${sceneName}.md`; const filePath = `${location}/${sceneName}.md`;
const frontmatter: Record<string, any> = { const frontmatter: Record<string, FrontmatterValue> = {
'Link': threadUrl, 'Link': threadUrl,
'Characters': [museName], 'Characters': [museName],
'Participants': participants, 'Participants': participants,
@ -1360,30 +1433,25 @@ export default class MultimuseObsidian extends Plugin {
// Mark this file as recently created to skip immediate checking // Mark this file as recently created to skip immediate checking
this.recentlyCreatedFiles.add(filePath); this.recentlyCreatedFiles.add(filePath);
// Remove from the set after 30 seconds (enough time for the scene to be registered with the API) // 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); this.recentlyCreatedFiles.delete(filePath);
}, 30000); }, 30000);
// Register with bot // Link the created note back to the current Discord-side thread tracker.
// Use primary user ID for scene registration
const primaryUserId = await this.getPrimaryUserId(); const primaryUserId = await this.getPrimaryUserId();
if (!primaryUserId) { 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; continue;
} }
await requestUrl({ await this.trackThread({
url: `${this.getBotApiUrl()}/api/v1/scenes/create`, threadId: threadId,
method: 'POST', userId: primaryUserId,
headers: this.getApiHeaders(), museName: museName,
body: JSON.stringify({ participants: Number(participants) || 2,
thread_id: threadId, // Send as string to avoid precision loss scenePath: filePath,
user_id: primaryUserId, // Send as string guildId: guildId ? String(guildId) : null,
scene_path: filePath, characters: [museName]
characters: [museName],
participants: participants,
guild_id: guildId || null, // Include guild_id if available
})
}); });
// Add to Base if configured // Add to Base if configured
@ -1403,7 +1471,7 @@ export default class MultimuseObsidian extends Plugin {
// ========= BASE INTEGRATION ========= // ========= BASE INTEGRATION =========
async addSceneToBase(file: TFile, frontmatter: any): Promise<void> { async addSceneToBase(file: TFile, frontmatter: FrontmatterData): Promise<void> {
/**Add scene to Obsidian Base with characters as variables.*/ /**Add scene to Obsidian Base with characters as variables.*/
if (!this.settings.basePath) return; if (!this.settings.basePath) return;
@ -1457,7 +1525,7 @@ export default class MultimuseObsidian extends Plugin {
} }
} }
async updateBaseRecord(file: TFile, thread: any): Promise<void> { async updateBaseRecord(file: TFile, thread: TrackedThread): Promise<void> {
/**Update existing Base record for a scene.*/ /**Update existing Base record for a scene.*/
if (!this.settings.basePath) return; if (!this.settings.basePath) return;
@ -1482,7 +1550,7 @@ export default class MultimuseObsidian extends Plugin {
const lines = baseContent.split('\n'); const lines = baseContent.split('\n');
const updatedLines = lines.map(line => { const updatedLines = lines.map(line => {
if (line.includes(`| ${sceneName} |`)) { if (line.includes(`| ${sceneName} |`)) {
const characters = [thread.muse_name]; const characters = [thread.muse_name || 'Muse'];
const link = thread.guild_id && thread.guild_id !== '0' const link = thread.guild_id && thread.guild_id !== '0'
? `https://discord.com/channels/${thread.guild_id}/${thread.thread_id}` ? `https://discord.com/channels/${thread.guild_id}/${thread.thread_id}`
: `https://discord.com/channels/0/${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}`; return `${RP_ROOT}/${relPath}`;
} }
showSuggester(items: string[], values: any[], title?: string): Promise<number | null> { showSuggester<T>(items: string[], _values: T[], title?: string): Promise<number | null> {
return new Promise((resolve) => { return new Promise((resolve) => {
// Create a modal with buttons // Create a modal with buttons
const modal = new (class extends Modal { const modal = new (class extends Modal {
selectedIndex: number | null = null; selectedIndex: number | null = null;
items: string[]; items: string[];
values: any[];
titleText: string; titleText: string;
constructor(app: App, items: string[], values: any[], titleText?: string) { constructor(app: App, items: string[], titleText?: string) {
super(app); super(app);
this.items = items; this.items = items;
this.values = values;
this.titleText = titleText || 'Select an option'; this.titleText = titleText || 'Select an option';
// Set title in constructor to ensure it's set before modal opens // Set title in constructor to ensure it's set before modal opens
this.titleEl.textContent = this.titleText; this.titleEl.textContent = this.titleText;
@ -1629,26 +1695,23 @@ export default class MultimuseObsidian extends Plugin {
if (match) { if (match) {
const contextInfo = match[1]; const contextInfo = match[1];
const infoEl = contentEl.createEl('div', { const infoEl = contentEl.createEl('div', {
attr: { cls: 'multimuse-scene-context'
style: 'margin-bottom: 20px; padding: 10px; background: var(--background-modifier-border); border-radius: 4px;'
}
}); });
infoEl.createEl('strong', { infoEl.createEl('strong', {
text: `Creating scene file for: ${contextInfo}`, 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:', 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) => { this.items.forEach((item, index) => {
const button = contentEl.createEl('button', { const button = contentEl.createEl('button', {
text: item, text: item,
cls: 'mod-cta', cls: ['mod-cta', 'multimuse-suggester-button']
attr: { style: 'width: 100%; margin: 5px 0;' }
}); });
button.onclick = () => { button.onclick = () => {
this.selectedIndex = index; this.selectedIndex = index;
@ -1660,11 +1723,11 @@ export default class MultimuseObsidian extends Plugin {
onClose() { onClose() {
resolve(this.selectedIndex); 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'}`); 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 // Use requestAnimationFrame to ensure modal opens after any previous modals are fully closed
requestAnimationFrame(() => { window.requestAnimationFrame(() => {
modal.open(); modal.open();
}); });
}); });
@ -1682,9 +1745,9 @@ export default class MultimuseObsidian extends Plugin {
this.titleEl.textContent = prompt; this.titleEl.textContent = prompt;
this.inputEl = this.contentEl.createEl('input', { this.inputEl = this.contentEl.createEl('input', {
type: 'text', type: 'text',
value: defaultValue || '' value: defaultValue || '',
cls: 'multimuse-input'
}); });
this.inputEl.style.width = '100%';
this.inputEl.onkeydown = (e) => { this.inputEl.onkeydown = (e) => {
if (e.key === 'Enter') { if (e.key === 'Enter') {
this.value = this.inputEl.value; 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.'); new Notice('No frontmatter. Add a Link property (Discord thread URL) to this note.');
return; return;
} }
const link = cache.frontmatter['Link']; const frontmatter = this.getFrontmatter(cache);
if (!link) { const link = frontmatter?.['Link'];
if (typeof link !== 'string') {
new Notice('No Link property. Add the Discord thread URL to frontmatter to use @ mentions.'); new Notice('No Link property. Add the Discord thread URL to frontmatter to use @ mentions.');
return; return;
} }
@ -1736,7 +1800,7 @@ export default class MultimuseObsidian extends Plugin {
new Notice('API key required in plugin settings to fetch server members.'); new Notice('API key required in plugin settings to fetch server members.');
return; return;
} }
let members: { id: string; username: string; display_name: string }[]; let members: GuildMember[];
try { try {
const url = `${this.getBotApiUrl()}/api/v1/guilds/${threadInfo.guildId}/members`; const url = `${this.getBotApiUrl()}/api/v1/guilds/${threadInfo.guildId}/members`;
const response = await requestUrl({ const response = await requestUrl({
@ -1750,7 +1814,7 @@ export default class MultimuseObsidian extends Plugin {
} }
return; return;
} }
const data = JSON.parse(response.text); const data = parseJson<GuildMembersResponse>(response.text);
members = data.members || []; members = data.members || [];
} catch (e) { } catch (e) {
console.error('[MultimuseObsidian] insertMention fetch error:', e); console.error('[MultimuseObsidian] insertMention fetch error:', e);
@ -1797,19 +1861,20 @@ export default class MultimuseObsidian extends Plugin {
// Get frontmatter // Get frontmatter
const cache = this.app.metadataCache.getFileCache(file); 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.'); new Notice('File does not have frontmatter. Please add Link and Characters properties.');
return; return;
} }
// Extract link and characters // Extract link and characters
const link = cache.frontmatter['Link']; const link = frontmatter?.['Link'];
if (!link) { if (typeof link !== 'string') {
new Notice('No Link property found in frontmatter. Please add a Discord thread URL.'); new Notice('No Link property found in frontmatter. Please add a Discord thread URL.');
return; return;
} }
const characters = this.getCharacterNames(cache.frontmatter); const characters = this.getCharacterNames(frontmatter);
if (characters.length === 0) { if (characters.length === 0) {
new Notice('No Characters property found in frontmatter. Please add at least one character name.'); new Notice('No Characters property found in frontmatter. Please add at least one character name.');
return; return;
@ -1861,7 +1926,7 @@ export default class MultimuseObsidian extends Plugin {
}); });
if (response.status === 200) { if (response.status === 200) {
const data = JSON.parse(response.text); const data = parseJson<MusesListResponse>(response.text);
muses = data.muses || []; muses = data.muses || [];
} else { } else {
if (!this.handleApiError(response, 'sendSelectionAsMuse - fetch muses')) { if (!this.handleApiError(response, 'sendSelectionAsMuse - fetch muses')) {
@ -1909,17 +1974,16 @@ export default class MultimuseObsidian extends Plugin {
}); });
if (response.status === 200) { if (response.status === 200) {
const data = JSON.parse(response.text);
new Notice(`Message sent as ${selectedMuse}!`); new Notice(`Message sent as ${selectedMuse}!`);
} else { } else {
if (!this.handleApiError(response, 'sendSelectionAsMuse - post message')) { if (!this.handleApiError(response, 'sendSelectionAsMuse - post message')) {
const errorData = JSON.parse(response.text); const errorData = parseJson<ApiErrorBody>(response.text);
new Notice(`Failed to send message: ${errorData.message || response.status}`); new Notice(`Failed to send message: ${errorData.message || response.status}`);
} }
} }
} catch (error: any) { } catch (error) {
console.error('Error sending message:', 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}`); new Notice(`Failed to send message: ${errorMessage}`);
} }
} }
@ -1938,7 +2002,9 @@ class MultimuseObsidianSettingTab extends PluginSettingTab {
containerEl.empty(); containerEl.empty();
containerEl.createEl('h2', { text: 'Multimuse Obsidian Settings' }); new Setting(containerEl)
.setName('Multimuse Settings')
.setHeading();
// Enable/Disable toggle // Enable/Disable toggle
new Setting(containerEl) new Setting(containerEl)
@ -1997,7 +2063,9 @@ class MultimuseObsidianSettingTab extends PluginSettingTab {
})); }));
// Scene Properties Tracking // Scene Properties Tracking
containerEl.createEl('h3', { text: 'Scene Properties' }); new Setting(containerEl)
.setName('Scene Properties')
.setHeading();
new Setting(containerEl) new Setting(containerEl)
.setName('Track Roleplay Property') .setName('Track Roleplay Property')
@ -2059,8 +2127,8 @@ class MultimuseObsidianSettingTab extends PluginSettingTab {
.setName('Detected User ID') .setName('Detected User ID')
.setDesc(`Your Discord user ID (automatically detected from API key): ${this.plugin.settings.cachedUserId}`) .setDesc(`Your Discord user ID (automatically detected from API key): ${this.plugin.settings.cachedUserId}`)
.addText(text => { .addText(text => {
text.setValue(this.plugin.settings.cachedUserId) text.setValue(this.plugin.settings.cachedUserId);
.setDisabled(true); text.inputEl.disabled = true;
}); });
} }
@ -2084,14 +2152,16 @@ class MultimuseObsidianSettingTab extends PluginSettingTab {
.setButtonText('Check Now') .setButtonText('Check Now')
.setCta() .setCta()
.onClick(() => { .onClick(() => {
this.plugin.checkAllThreads(); void this.plugin.checkAllThreads();
new Notice('Checking Discord threads...'); new Notice('Checking Discord threads...');
})); }));
// Info section // Info section
containerEl.createEl('hr'); containerEl.createEl('hr');
const infoEl = containerEl.createEl('div'); 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: '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: '• 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.' }); 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.' });

View file

@ -1,9 +1,9 @@
{ {
"id": "multimuse-obsidian", "id": "multimuse-tracker",
"name": "Multimuse Obsidian", "name": "Multimuse Tracker",
"version": "1.6.1", "version": "1.10.0",
"minAppVersion": "1.0.0", "minAppVersion": "1.4.0",
"description": "Roleplay sync tool for use with the MultiMuse bot in discord", "description": "Roleplay sync tool for use with the MultiMuse bot in discord.",
"author": "BackstagePass Group", "author": "BackstagePass Group",
"authorUrl": "", "authorUrl": "",
"fundingUrl": "", "fundingUrl": "",

354
package-lock.json generated
View file

@ -1,19 +1,18 @@
{ {
"name": "discord-scene-tracker", "name": "multimuse-tracker",
"version": "1.0.0", "version": "1.10.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "discord-scene-tracker", "name": "multimuse-tracker",
"version": "1.0.0", "version": "1.10.0",
"license": "MIT", "license": "MIT",
"devDependencies": { "devDependencies": {
"@types/node": "^16.11.6", "@types/node": "^16.11.6",
"@typescript-eslint/eslint-plugin": "5.29.0", "@typescript-eslint/eslint-plugin": "5.29.0",
"@typescript-eslint/parser": "5.29.0", "@typescript-eslint/parser": "5.29.0",
"builtin-modules": "3.3.0", "esbuild": "^0.28.0",
"esbuild": "0.17.3",
"obsidian": "latest", "obsidian": "latest",
"tslib": "2.4.0", "tslib": "2.4.0",
"typescript": "4.7.4" "typescript": "4.7.4"
@ -44,10 +43,27 @@
"w3c-keyname": "^2.2.4" "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": { "node_modules/@esbuild/android-arm": {
"version": "0.17.3", "version": "0.28.0",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.17.3.tgz", "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.0.tgz",
"integrity": "sha512-1Mlz934GvbgdDmt26rTLmf03cAgLg5HyOgJN+ZGCeP3Q9ynYTNMn2/LQxIl7Uy+o4K6Rfi2OuLsr12JQQR8gNg==", "integrity": "sha512-wqh0ByljabXLKHeWXYLqoJ5jKC4XBaw6Hk08OfMrCRd2nP2ZQ5eleDZC41XHyCNgktBGYMbqnrJKq/K/lzPMSQ==",
"cpu": [ "cpu": [
"arm" "arm"
], ],
@ -58,13 +74,13 @@
"android" "android"
], ],
"engines": { "engines": {
"node": ">=12" "node": ">=18"
} }
}, },
"node_modules/@esbuild/android-arm64": { "node_modules/@esbuild/android-arm64": {
"version": "0.17.3", "version": "0.28.0",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.17.3.tgz", "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.0.tgz",
"integrity": "sha512-XvJsYo3dO3Pi4kpalkyMvfQsjxPWHYjoX4MDiB/FUM4YMfWcXa5l4VCwFWVYI1+92yxqjuqrhNg0CZg3gSouyQ==", "integrity": "sha512-+WzIXQOSaGs33tLEgYPYe/yQHf0WTU0X42Jca3y8NWMbUVhp7rUnw+vAsRC/QiDrdD31IszMrZy+qwPOPjd+rw==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@ -75,13 +91,13 @@
"android" "android"
], ],
"engines": { "engines": {
"node": ">=12" "node": ">=18"
} }
}, },
"node_modules/@esbuild/android-x64": { "node_modules/@esbuild/android-x64": {
"version": "0.17.3", "version": "0.28.0",
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.17.3.tgz", "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.0.tgz",
"integrity": "sha512-nuV2CmLS07Gqh5/GrZLuqkU9Bm6H6vcCspM+zjp9TdQlxJtIe+qqEXQChmfc7nWdyr/yz3h45Utk1tUn8Cz5+A==", "integrity": "sha512-+VJggoaKhk2VNNqVL7f6S189UzShHC/mR9EE8rDdSkdpN0KflSwWY/gWjDrNxxisg8Fp1ZCD9jLMo4m0OUfeUA==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@ -92,13 +108,13 @@
"android" "android"
], ],
"engines": { "engines": {
"node": ">=12" "node": ">=18"
} }
}, },
"node_modules/@esbuild/darwin-arm64": { "node_modules/@esbuild/darwin-arm64": {
"version": "0.17.3", "version": "0.28.0",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.17.3.tgz", "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.0.tgz",
"integrity": "sha512-01Hxaaat6m0Xp9AXGM8mjFtqqwDjzlMP0eQq9zll9U85ttVALGCGDuEvra5Feu/NbP5AEP1MaopPwzsTcUq1cw==", "integrity": "sha512-0T+A9WZm+bZ84nZBtk1ckYsOvyA3x7e2Acj1KdVfV4/2tdG4fzUp91YHx+GArWLtwqp77pBXVCPn2We7Letr0Q==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@ -109,13 +125,13 @@
"darwin" "darwin"
], ],
"engines": { "engines": {
"node": ">=12" "node": ">=18"
} }
}, },
"node_modules/@esbuild/darwin-x64": { "node_modules/@esbuild/darwin-x64": {
"version": "0.17.3", "version": "0.28.0",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.17.3.tgz", "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.0.tgz",
"integrity": "sha512-Eo2gq0Q/er2muf8Z83X21UFoB7EU6/m3GNKvrhACJkjVThd0uA+8RfKpfNhuMCl1bKRfBzKOk6xaYKQZ4lZqvA==", "integrity": "sha512-fyzLm/DLDl/84OCfp2f/XQ4flmORsjU7VKt8HLjvIXChJoFFOIL6pLJPH4Yhd1n1gGFF9mPwtlN5Wf82DZs+LQ==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@ -126,13 +142,13 @@
"darwin" "darwin"
], ],
"engines": { "engines": {
"node": ">=12" "node": ">=18"
} }
}, },
"node_modules/@esbuild/freebsd-arm64": { "node_modules/@esbuild/freebsd-arm64": {
"version": "0.17.3", "version": "0.28.0",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.3.tgz", "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.0.tgz",
"integrity": "sha512-CN62ESxaquP61n1ZjQP/jZte8CE09M6kNn3baos2SeUfdVBkWN5n6vGp2iKyb/bm/x4JQzEvJgRHLGd5F5b81w==", "integrity": "sha512-l9GeW5UZBT9k9brBYI+0WDffcRxgHQD8ShN2Ur4xWq/NFzUKm3k5lsH4PdaRgb2w7mI9u61nr2gI2mLI27Nh3Q==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@ -143,13 +159,13 @@
"freebsd" "freebsd"
], ],
"engines": { "engines": {
"node": ">=12" "node": ">=18"
} }
}, },
"node_modules/@esbuild/freebsd-x64": { "node_modules/@esbuild/freebsd-x64": {
"version": "0.17.3", "version": "0.28.0",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.17.3.tgz", "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.0.tgz",
"integrity": "sha512-feq+K8TxIznZE+zhdVurF3WNJ/Sa35dQNYbaqM/wsCbWdzXr5lyq+AaTUSER2cUR+SXPnd/EY75EPRjf4s1SLg==", "integrity": "sha512-BXoQai/A0wPO6Es3yFJ7APCiKGc1tdAEOgeTNy3SsB491S3aHn4S4r3e976eUnPdU+NbdtmBuLncYir2tMU9Nw==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@ -160,13 +176,13 @@
"freebsd" "freebsd"
], ],
"engines": { "engines": {
"node": ">=12" "node": ">=18"
} }
}, },
"node_modules/@esbuild/linux-arm": { "node_modules/@esbuild/linux-arm": {
"version": "0.17.3", "version": "0.28.0",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.17.3.tgz", "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.0.tgz",
"integrity": "sha512-CLP3EgyNuPcg2cshbwkqYy5bbAgK+VhyfMU7oIYyn+x4Y67xb5C5ylxsNUjRmr8BX+MW3YhVNm6Lq6FKtRTWHQ==", "integrity": "sha512-CjaaREJagqJp7iTaNQjjidaNbCKYcd4IDkzbwwxtSvjI7NZm79qiHc8HqciMddQ6CKvJT6aBd8lO9kN/ZudLlw==",
"cpu": [ "cpu": [
"arm" "arm"
], ],
@ -177,13 +193,13 @@
"linux" "linux"
], ],
"engines": { "engines": {
"node": ">=12" "node": ">=18"
} }
}, },
"node_modules/@esbuild/linux-arm64": { "node_modules/@esbuild/linux-arm64": {
"version": "0.17.3", "version": "0.28.0",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.17.3.tgz", "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.0.tgz",
"integrity": "sha512-JHeZXD4auLYBnrKn6JYJ0o5nWJI9PhChA/Nt0G4MvLaMrvXuWnY93R3a7PiXeJQphpL1nYsaMcoV2QtuvRnF/g==", "integrity": "sha512-RVyzfb3FWsGA55n6WY0MEIEPURL1FcbhFE6BffZEMEekfCzCIMtB5yyDcFnVbTnwk+CLAgTujmV/Lgvih56W+A==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@ -194,13 +210,13 @@
"linux" "linux"
], ],
"engines": { "engines": {
"node": ">=12" "node": ">=18"
} }
}, },
"node_modules/@esbuild/linux-ia32": { "node_modules/@esbuild/linux-ia32": {
"version": "0.17.3", "version": "0.28.0",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.17.3.tgz", "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.0.tgz",
"integrity": "sha512-FyXlD2ZjZqTFh0sOQxFDiWG1uQUEOLbEh9gKN/7pFxck5Vw0qjWSDqbn6C10GAa1rXJpwsntHcmLqydY9ST9ZA==", "integrity": "sha512-KBnSTt1kxl9x70q+ydterVdl+Cn0H18ngRMRCEQfrbqdUuntQQ0LoMZv47uB97NljZFzY6HcfqEZ2SAyIUTQBQ==",
"cpu": [ "cpu": [
"ia32" "ia32"
], ],
@ -211,13 +227,13 @@
"linux" "linux"
], ],
"engines": { "engines": {
"node": ">=12" "node": ">=18"
} }
}, },
"node_modules/@esbuild/linux-loong64": { "node_modules/@esbuild/linux-loong64": {
"version": "0.17.3", "version": "0.28.0",
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.17.3.tgz", "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.0.tgz",
"integrity": "sha512-OrDGMvDBI2g7s04J8dh8/I7eSO+/E7nMDT2Z5IruBfUO/RiigF1OF6xoH33Dn4W/OwAWSUf1s2nXamb28ZklTA==", "integrity": "sha512-zpSlUce1mnxzgBADvxKXX5sl8aYQHo2ezvMNI8I0lbblJtp8V4odlm3Yzlj7gPyt3T8ReksE6bK+pT3WD+aJRg==",
"cpu": [ "cpu": [
"loong64" "loong64"
], ],
@ -228,13 +244,13 @@
"linux" "linux"
], ],
"engines": { "engines": {
"node": ">=12" "node": ">=18"
} }
}, },
"node_modules/@esbuild/linux-mips64el": { "node_modules/@esbuild/linux-mips64el": {
"version": "0.17.3", "version": "0.28.0",
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.17.3.tgz", "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.0.tgz",
"integrity": "sha512-DcnUpXnVCJvmv0TzuLwKBC2nsQHle8EIiAJiJ+PipEVC16wHXaPEKP0EqN8WnBe0TPvMITOUlP2aiL5YMld+CQ==", "integrity": "sha512-2jIfP6mmjkdmeTlsX/9vmdmhBmKADrWqN7zcdtHIeNSCH1SqIoNI63cYsjQR8J+wGa4Y5izRcSHSm8K3QWmk3w==",
"cpu": [ "cpu": [
"mips64el" "mips64el"
], ],
@ -245,13 +261,13 @@
"linux" "linux"
], ],
"engines": { "engines": {
"node": ">=12" "node": ">=18"
} }
}, },
"node_modules/@esbuild/linux-ppc64": { "node_modules/@esbuild/linux-ppc64": {
"version": "0.17.3", "version": "0.28.0",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.17.3.tgz", "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.0.tgz",
"integrity": "sha512-BDYf/l1WVhWE+FHAW3FzZPtVlk9QsrwsxGzABmN4g8bTjmhazsId3h127pliDRRu5674k1Y2RWejbpN46N9ZhQ==", "integrity": "sha512-bc0FE9wWeC0WBm49IQMPSPILRocGTQt3j5KPCA8os6VprfuJ7KD+5PzESSrJ6GmPIPJK965ZJHTUlSA6GNYEhg==",
"cpu": [ "cpu": [
"ppc64" "ppc64"
], ],
@ -262,13 +278,13 @@
"linux" "linux"
], ],
"engines": { "engines": {
"node": ">=12" "node": ">=18"
} }
}, },
"node_modules/@esbuild/linux-riscv64": { "node_modules/@esbuild/linux-riscv64": {
"version": "0.17.3", "version": "0.28.0",
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.17.3.tgz", "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.0.tgz",
"integrity": "sha512-WViAxWYMRIi+prTJTyV1wnqd2mS2cPqJlN85oscVhXdb/ZTFJdrpaqm/uDsZPGKHtbg5TuRX/ymKdOSk41YZow==", "integrity": "sha512-SQPZOwoTTT/HXFXQJG/vBX8sOFagGqvZyXcgLA3NhIqcBv1BJU1d46c0rGcrij2B56Z2rNiSLaZOYW5cUk7yLQ==",
"cpu": [ "cpu": [
"riscv64" "riscv64"
], ],
@ -279,13 +295,13 @@
"linux" "linux"
], ],
"engines": { "engines": {
"node": ">=12" "node": ">=18"
} }
}, },
"node_modules/@esbuild/linux-s390x": { "node_modules/@esbuild/linux-s390x": {
"version": "0.17.3", "version": "0.28.0",
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.17.3.tgz", "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.0.tgz",
"integrity": "sha512-Iw8lkNHUC4oGP1O/KhumcVy77u2s6+KUjieUqzEU3XuWJqZ+AY7uVMrrCbAiwWTkpQHkr00BuXH5RpC6Sb/7Ug==", "integrity": "sha512-SCfR0HN8CEEjnYnySJTd2cw0k9OHB/YFzt5zgJEwa+wL/T/raGWYMBqwDNAC6dqFKmJYZoQBRfHjgwLHGSrn3Q==",
"cpu": [ "cpu": [
"s390x" "s390x"
], ],
@ -296,13 +312,13 @@
"linux" "linux"
], ],
"engines": { "engines": {
"node": ">=12" "node": ">=18"
} }
}, },
"node_modules/@esbuild/linux-x64": { "node_modules/@esbuild/linux-x64": {
"version": "0.17.3", "version": "0.28.0",
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.17.3.tgz", "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.0.tgz",
"integrity": "sha512-0AGkWQMzeoeAtXQRNB3s4J1/T2XbigM2/Mn2yU1tQSmQRmHIZdkGbVq2A3aDdNslPyhb9/lH0S5GMTZ4xsjBqg==", "integrity": "sha512-us0dSb9iFxIi8srnpl931Nvs65it/Jd2a2K3qs7fz2WfGPHqzfzZTfec7oxZJRNPXPnNYZtanmRc4AL/JwVzHQ==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@ -313,13 +329,30 @@
"linux" "linux"
], ],
"engines": { "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": { "node_modules/@esbuild/netbsd-x64": {
"version": "0.17.3", "version": "0.28.0",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.17.3.tgz", "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.0.tgz",
"integrity": "sha512-4+rR/WHOxIVh53UIQIICryjdoKdHsFZFD4zLSonJ9RRw7bhKzVyXbnRPsWSfwybYqw9sB7ots/SYyufL1mBpEg==", "integrity": "sha512-nU1yhmYutL+fQ71Kxnhg8uEOdC0pwEW9entHykTgEbna2pw2dkbFSMeqjjyHZoCmt8SBkOSvV+yNmm94aUrrqw==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@ -330,13 +363,30 @@
"netbsd" "netbsd"
], ],
"engines": { "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": { "node_modules/@esbuild/openbsd-x64": {
"version": "0.17.3", "version": "0.28.0",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.17.3.tgz", "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.0.tgz",
"integrity": "sha512-cVpWnkx9IYg99EjGxa5Gc0XmqumtAwK3aoz7O4Dii2vko+qXbkHoujWA68cqXjhh6TsLaQelfDO4MVnyr+ODeA==", "integrity": "sha512-8wZM2qqtv9UP3mzy7HiGYNH/zjTA355mpeuA+859TyR+e+Tc08IHYpLJuMsfpDJwoLo1ikIJI8jC3GFjnRClzA==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@ -347,13 +397,30 @@
"openbsd" "openbsd"
], ],
"engines": { "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": { "node_modules/@esbuild/sunos-x64": {
"version": "0.17.3", "version": "0.28.0",
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.17.3.tgz", "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.0.tgz",
"integrity": "sha512-RxmhKLbTCDAY2xOfrww6ieIZkZF+KBqG7S2Ako2SljKXRFi+0863PspK74QQ7JpmWwncChY25JTJSbVBYGQk2Q==", "integrity": "sha512-1ZgjUoEdHZZl/YlV76TSCz9Hqj9h9YmMGAgAPYd+q4SicWNX3G5GCyx9uhQWSLcbvPW8Ni7lj4gDa1T40akdlw==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@ -364,13 +431,13 @@
"sunos" "sunos"
], ],
"engines": { "engines": {
"node": ">=12" "node": ">=18"
} }
}, },
"node_modules/@esbuild/win32-arm64": { "node_modules/@esbuild/win32-arm64": {
"version": "0.17.3", "version": "0.28.0",
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.17.3.tgz", "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.0.tgz",
"integrity": "sha512-0r36VeEJ4efwmofxVJRXDjVRP2jTmv877zc+i+Pc7MNsIr38NfsjkQj23AfF7l0WbB+RQ7VUb+LDiqC/KY/M/A==", "integrity": "sha512-Q9StnDmQ/enxnpxCCLSg0oo4+34B9TdXpuyPeTedN/6+iXBJ4J+zwfQI28u/Jl40nOYAxGoNi7mFP40RUtkmUA==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@ -381,13 +448,13 @@
"win32" "win32"
], ],
"engines": { "engines": {
"node": ">=12" "node": ">=18"
} }
}, },
"node_modules/@esbuild/win32-ia32": { "node_modules/@esbuild/win32-ia32": {
"version": "0.17.3", "version": "0.28.0",
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.17.3.tgz", "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.0.tgz",
"integrity": "sha512-wgO6rc7uGStH22nur4aLFcq7Wh86bE9cOFmfTr/yxN3BXvDEdCSXyKkO+U5JIt53eTOgC47v9k/C1bITWL/Teg==", "integrity": "sha512-zF3ag/gfiCe6U2iczcRzSYJKH1DCI+ByzSENHlM2FcDbEeo5Zd2C86Aq0tKUYAJJ1obRP84ymxIAksZUcdztHA==",
"cpu": [ "cpu": [
"ia32" "ia32"
], ],
@ -398,13 +465,13 @@
"win32" "win32"
], ],
"engines": { "engines": {
"node": ">=12" "node": ">=18"
} }
}, },
"node_modules/@esbuild/win32-x64": { "node_modules/@esbuild/win32-x64": {
"version": "0.17.3", "version": "0.28.0",
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.17.3.tgz", "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.0.tgz",
"integrity": "sha512-FdVl64OIuiKjgXBjwZaJLKp0eaEckifbhn10dXWhysMJkWblg3OEEGKSIyhiD5RSgAya8WzP3DNkngtIg3Nt7g==", "integrity": "sha512-pEl1bO9mfAmIC+tW5btTmrKaujg3zGtUmWNdCw/xs70FBjwAL3o9OEKNHvNmnyylD6ubxUERiEhdsL0xBQ9efw==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@ -415,7 +482,7 @@
"win32" "win32"
], ],
"engines": { "engines": {
"node": ">=12" "node": ">=18"
} }
}, },
"node_modules/@eslint-community/eslint-utils": { "node_modules/@eslint-community/eslint-utils": {
@ -830,9 +897,9 @@
} }
}, },
"node_modules/ajv": { "node_modules/ajv": {
"version": "6.12.6", "version": "6.15.0",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz",
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@ -897,9 +964,9 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/brace-expansion": { "node_modules/brace-expansion": {
"version": "1.1.12", "version": "1.1.14",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz",
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@ -920,19 +987,6 @@
"node": ">=8" "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": { "node_modules/callsites": {
"version": "3.1.0", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
@ -1061,9 +1115,9 @@
} }
}, },
"node_modules/esbuild": { "node_modules/esbuild": {
"version": "0.17.3", "version": "0.28.0",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.17.3.tgz", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.0.tgz",
"integrity": "sha512-9n3AsBRe6sIyOc6kmoXg2ypCLgf3eZSraWFRpnkto+svt8cZNuKTkb1bhQcitBcvIqjNiK7K0J3KPmwGSfkA8g==", "integrity": "sha512-sNR9MHpXSUV/XB4zmsFKN+QgVG82Cc7+/aaxJ8Adi8hyOac+EXptIp45QBPaVyX3N70664wRbTcLTOemCAnyqw==",
"dev": true, "dev": true,
"hasInstallScript": true, "hasInstallScript": true,
"license": "MIT", "license": "MIT",
@ -1071,31 +1125,35 @@
"esbuild": "bin/esbuild" "esbuild": "bin/esbuild"
}, },
"engines": { "engines": {
"node": ">=12" "node": ">=18"
}, },
"optionalDependencies": { "optionalDependencies": {
"@esbuild/android-arm": "0.17.3", "@esbuild/aix-ppc64": "0.28.0",
"@esbuild/android-arm64": "0.17.3", "@esbuild/android-arm": "0.28.0",
"@esbuild/android-x64": "0.17.3", "@esbuild/android-arm64": "0.28.0",
"@esbuild/darwin-arm64": "0.17.3", "@esbuild/android-x64": "0.28.0",
"@esbuild/darwin-x64": "0.17.3", "@esbuild/darwin-arm64": "0.28.0",
"@esbuild/freebsd-arm64": "0.17.3", "@esbuild/darwin-x64": "0.28.0",
"@esbuild/freebsd-x64": "0.17.3", "@esbuild/freebsd-arm64": "0.28.0",
"@esbuild/linux-arm": "0.17.3", "@esbuild/freebsd-x64": "0.28.0",
"@esbuild/linux-arm64": "0.17.3", "@esbuild/linux-arm": "0.28.0",
"@esbuild/linux-ia32": "0.17.3", "@esbuild/linux-arm64": "0.28.0",
"@esbuild/linux-loong64": "0.17.3", "@esbuild/linux-ia32": "0.28.0",
"@esbuild/linux-mips64el": "0.17.3", "@esbuild/linux-loong64": "0.28.0",
"@esbuild/linux-ppc64": "0.17.3", "@esbuild/linux-mips64el": "0.28.0",
"@esbuild/linux-riscv64": "0.17.3", "@esbuild/linux-ppc64": "0.28.0",
"@esbuild/linux-s390x": "0.17.3", "@esbuild/linux-riscv64": "0.28.0",
"@esbuild/linux-x64": "0.17.3", "@esbuild/linux-s390x": "0.28.0",
"@esbuild/netbsd-x64": "0.17.3", "@esbuild/linux-x64": "0.28.0",
"@esbuild/openbsd-x64": "0.17.3", "@esbuild/netbsd-arm64": "0.28.0",
"@esbuild/sunos-x64": "0.17.3", "@esbuild/netbsd-x64": "0.28.0",
"@esbuild/win32-arm64": "0.17.3", "@esbuild/openbsd-arm64": "0.28.0",
"@esbuild/win32-ia32": "0.17.3", "@esbuild/openbsd-x64": "0.28.0",
"@esbuild/win32-x64": "0.17.3" "@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": { "node_modules/escape-string-regexp": {
@ -1456,9 +1514,9 @@
} }
}, },
"node_modules/flatted": { "node_modules/flatted": {
"version": "3.3.3", "version": "3.4.2",
"resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz",
"integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==",
"dev": true, "dev": true,
"license": "ISC" "license": "ISC"
}, },
@ -1777,9 +1835,9 @@
} }
}, },
"node_modules/minimatch": { "node_modules/minimatch": {
"version": "3.1.2", "version": "3.1.5",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
"dev": true, "dev": true,
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
@ -1942,9 +2000,9 @@
} }
}, },
"node_modules/picomatch": { "node_modules/picomatch": {
"version": "2.3.1", "version": "2.3.2",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz",
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {

View file

@ -1,7 +1,7 @@
{ {
"name": "multimuse-obsidian", "name": "multimuse-tracker",
"version": "1.3.0", "version": "1.10.0",
"description": "Obsidian plugin for MultiMuse bot integration - track Discord threads and send messages as muses", "description": "MultiMuse bot integration for tracking Discord threads and sending messages as muses",
"main": "main.js", "main": "main.js",
"scripts": { "scripts": {
"dev": "node esbuild.config.mjs", "dev": "node esbuild.config.mjs",
@ -15,11 +15,9 @@
"@types/node": "^16.11.6", "@types/node": "^16.11.6",
"@typescript-eslint/eslint-plugin": "5.29.0", "@typescript-eslint/eslint-plugin": "5.29.0",
"@typescript-eslint/parser": "5.29.0", "@typescript-eslint/parser": "5.29.0",
"builtin-modules": "3.3.0", "esbuild": "^0.28.0",
"esbuild": "0.17.3",
"obsidian": "latest", "obsidian": "latest",
"tslib": "2.4.0", "tslib": "2.4.0",
"typescript": "4.7.4" "typescript": "4.7.4"
} }
} }

View file

@ -17,3 +17,30 @@
border-top: none; 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;
}