### Query Documentation Example Source: https://seanime.gitbook.io/seanime-extensions/plugins/apis/shared.md This example shows how to query the documentation dynamically using an HTTP GET request with the `ask` query parameter. This is useful for retrieving specific information or clarifications not explicitly present on the page. ```http GET https://seanime.gitbook.io/seanime-extensions/plugins/apis/shared.md?ask= ``` -------------------------------- ### Querying Documentation via HTTP GET Source: https://seanime.gitbook.io/seanime-extensions/plugins/ui/other/app-settings.md This example shows how to query the documentation dynamically by appending the 'ask' query parameter to the page URL. Use this for questions not explicitly answered on the page. ```http GET https://seanime.gitbook.io/seanime-extensions/plugins/ui/other/app-settings.md?ask= ``` -------------------------------- ### Register and Start a Cron Job Source: https://seanime.gitbook.io/seanime-extensions/plugins/ui/cron.md This example demonstrates how to register a recurring job to refresh an anime cache every 15 minutes and then start the cron scheduler. Ensure the 'cron' permission is granted. ```typescript $ui.register((ctx) => { ctx.cron.add("refresh-anime-cache", "*/15 * * * *", () => { console.log("refreshing anime cache") }) ctx.cron.start() }) ``` -------------------------------- ### UI Plugin Auth Example Source: https://seanime.gitbook.io/seanime-extensions/plugins/ui/other/auth.md This example demonstrates how to register a UI plugin that uses the ctx.auth API to log in and out of AniList. It includes input fields for tokens and toast notifications for feedback. ```typescript $ui.register((ctx) => { const tray = ctx.newTray({ tooltipText: "AniList auth", withContent: true, }) const tokenRef = ctx.fieldRef("") ctx.registerEventHandler("login-anilist", async () => { try { await ctx.auth.login(tokenRef.current ?? "") tokenRef.setValue("") ctx.toast.success("AniList login updated") } catch (error) { ctx.toast.alert(`Login failed: ${error.message}`) } }) ctx.registerEventHandler("logout-anilist", async () => { try { await ctx.auth.logout() ctx.toast.info("AniList logged out") } catch (error) { ctx.toast.alert(`Logout failed: ${error.message}`) } }) tray.render(() => tray.stack([ tray.input("AniList token", { fieldRef: tokenRef }), tray.button("Log in", { onClick: "login-anilist" }), tray.button("Log out", { onClick: "logout-anilist" }), ])) }) ``` -------------------------------- ### Query Documentation via HTTP GET Source: https://seanime.gitbook.io/seanime-extensions/plugins/ui/downloading/torrent-client.md Perform an HTTP GET request to query the documentation dynamically. Include your question in the 'ask' query parameter. ```http GET https://seanime.gitbook.io/seanime-extensions/plugins/ui/downloading/torrent-client.md?ask= ``` -------------------------------- ### Example: Fetching Data on Navigation Source: https://seanime.gitbook.io/seanime-extensions/plugins/ui/basics.md This example demonstrates fetching data based on navigation events. It uses states to track the current media ID and fetched data, and an effect to trigger the fetch when the ID changes. ```typescript const currentMediaId = ctx.state(null) const fetchedData = ctx.state([]) // When the user navigates to an anime, get the media ID ctx.screen.onNavigate((e) => { if (e.pathname === "/entry" && !!e.searchParams.id) { const id = parseInt(e.searchParams.id); currentMediaId.set(id); } else { currentMediaId.set(null); } }); // Trigger 'ctx.screen.onNavigate' when the plugin loads ctx.screen.loadCurrent() // Fetch data each time the media ID changes. ctx.effect(async () => { if (!currentMediaId.get()) return const res = ctx.fetch(`https://example.com/anilistId?=${currentMediaId.get()}`) // Store the results fetchedData.set(res.json()) }, [currentMediaId]) ``` -------------------------------- ### Query Documentation via HTTP GET Source: https://seanime.gitbook.io/seanime-extensions/plugins Perform an HTTP GET request to query the documentation dynamically. Include your specific question in the 'ask' query parameter. ```http GET https://seanime.gitbook.io/seanime-extensions/plugins.md?ask= ``` -------------------------------- ### Example: Managing Library Auto Scan Settings Source: https://seanime.gitbook.io/seanime-extensions/plugins/ui/other/app-settings.md This TypeScript example demonstrates how to use ctx.appSettings to read, update, and patch the 'library.autoScan' setting. It includes event handlers for refreshing and toggling the setting, and updates the UI accordingly. ```typescript $ui.register((ctx) => { const tray = ctx.newTray({ tooltipText: "Library settings", withContent: true, }) const autoScanState = ctx.state(null) async function loadAutoScan() { const enabled = await ctx.appSettings.get( "library.autoScan", false, ) autoScanState.set(Boolean(enabled)) } ctx.registerEventHandler("refresh-auto-scan", async () => { try { await loadAutoScan() ctx.toast.info("Loaded app settings") } catch (error) { ctx.toast.alert(`Could not read settings: ${error.message}`) } }) ctx.registerEventHandler("toggle-auto-scan", async () => { try { const nextValue = !autoScanState.get() await ctx.appSettings.patch({ library: { autoScan: nextValue, }, }) autoScanState.set(nextValue) ctx.toast.success(`Auto Scan: ${nextValue ? "on" : "off"}`) } catch (error) { ctx.toast.alert(`Could not update settings: ${error.message}`) } }) tray.render(() => tray.stack([ tray.text(`Auto Scan: ${autoScanState.get() === null ? "unknown" : autoScanState.get() ? "enabled" : "disabled"}`), tray.button("Refresh", { onClick: "refresh-auto-scan" }), tray.button("Toggle", { onClick: "toggle-auto-scan" }), ])) loadAutoScan() }) ``` -------------------------------- ### Example Plugin: Storing Scan Durations Source: https://seanime.gitbook.io/seanime-extensions/plugins/apis/storage.md A plugin example that stores scan completion times and manages a history of scan durations using $store and $storage. ```typescript // A simple plugin that stores the history of scan durations function init() { $app.onScanCompleted((e) => { // Store the scanning duration (in ms) $store.set("scan-completed", e.duration) e.next() }) $ui.register((ctx) => { // Callback is triggered when the value is updated $store.watch("scan-completed", (value) => { const date = new Date() const now = date.toISOString().replaceall(".", "_") // Add the value to the history $storage.set("scan-duration-history."+now, { duration: value, durationInSeconds: value/1000, addedAt: date, }) ctx.toast.info(`Scanning took ${value/1000} seconds!`); }) function deleteHistory() { $storage.remove("scan-duration-history") } }) } ``` -------------------------------- ### Start the Cron Scheduler Source: https://seanime.gitbook.io/seanime-extensions/plugins/ui/cron.md Call the `start` method to begin executing registered cron jobs. Calling it again will restart the scheduler. ```javascript ctx.cron.start() ``` -------------------------------- ### Plugin Example: Storing Scan Durations Source: https://seanime.gitbook.io/seanime-extensions/plugins/apis/store.md An example plugin that stores scan completion durations and logs them to storage with toast notifications. ```typescript // A simple plugin that stores the history of scan durations function init() { $app.onScanCompleted((e) => { // Store the scanning duration (in ms) $store.set("scan-completed", e.duration) e.next() }) $ui.register((ctx) => { // Callback is triggered when the value is updated $store.watch("scan-completed", (value) => { const now = new Date().toISOString().replaceall(".", "_") $storage.set("scan-duration-history."+now, value) ctx.toast.info(`Scanning took ${value/1000} seconds!`) }) }) } ``` -------------------------------- ### Ask a Question via HTTP GET Source: https://seanime.gitbook.io/seanime-extensions/llms.txt Perform an HTTP GET request to the documentation index with an 'ask' parameter to get a direct answer to a specific question, along with relevant excerpts and sources. This is the recommended method for finding information when the exact page is unknown. ```http GET https://seanime.gitbook.io/seanime-extensions/plugins/apis/system.md?ask= ``` -------------------------------- ### Example: Managing Extension Enable/Disable State Source: https://seanime.gitbook.io/seanime-extensions/plugins/ui/other/extensions.md This example shows how to register UI elements (a tray with an input and buttons) to enable or disable another extension using its ID. It handles user input for the extension ID and displays toast notifications for success or failure. ```typescript $ui.register((ctx) => { const tray = ctx.newTray({ tooltipText: "Extension manager", withContent: true, }) const extensionIdRef = ctx.fieldRef("my-other-plugin") ctx.registerEventHandler("disable-extension", async () => { try { await ctx.extensions.disable(extensionIdRef.current ?? "") ctx.toast.warning("Extension disabled") } catch (error) { ctx.toast.alert(`Disable failed: ${error.message}`) } }) ctx.registerEventHandler("enable-extension", async () => { try { await ctx.extensions.enable(extensionIdRef.current ?? "") ctx.toast.success("Extension enabled") } catch (error) { ctx.toast.alert(`Enable failed: ${error.message}`) } }) tray.render(() => tray.stack([ tray.input("Extension ID", { fieldRef: extensionIdRef }), tray.button("Disable", { onClick: "disable-extension" }), tray.button("Enable", { onClick: "enable-extension" }), ])) }) ``` -------------------------------- ### Query Documentation with GET Request Source: https://seanime.gitbook.io/seanime-extensions/plugins/ui/helpers.md Perform an HTTP GET request to the current page URL, appending the `ask` query parameter with your natural language question. The response will contain a direct answer and relevant excerpts. ```http GET https://seanime.gitbook.io/seanime-extensions/plugins/ui/helpers.md?ask= ``` -------------------------------- ### startManualTracking Source: https://seanime.gitbook.io/seanime-extensions/plugins/ui/anime-library/playback-external.md Starts manual progress tracking for media not launched through Seanime's integrated playback APIs. ```APIDOC ## startManualTracking ### Description Starts manual progress tracking for media that is not being launched through Seanime's integrated playback APIs. If `clientId` is omitted, Seanime broadcasts it to all clients. ### Method INVOKE ### Endpoint ctx.playback.startManualTracking(opts: PlaybackManualTrackingOptions) ### Parameters #### Request Body - **opts** (PlaybackManualTrackingOptions) - Required - Options for manual tracking. - **mediaId** (Number) - Required - AniList media ID to track. - **episodeNumber** (Number) - Required - Episode number to sync. - **clientId** (String) - Optional - Client ID override. ### Response #### Success Response Manual tracking is started successfully. ### Response Example ```typescript await ctx.playback.startManualTracking({ mediaId: 21, episodeNumber: 1, }) ``` ``` -------------------------------- ### Query Documentation Dynamically Source: https://seanime.gitbook.io/seanime-extensions/frequently-asked/feature-requests.md To get information not directly on a page, send an HTTP GET request to the page URL with an 'ask' query parameter. The question should be specific and in natural language. ```http GET https://seanime.gitbook.io/seanime-extensions/frequently-asked/feature-requests.md?ask= ``` -------------------------------- ### Quick Debugging Example Source: https://seanime.gitbook.io/seanime-extensions/plugins/apis/debug.md Logs scan completion details and UI thread startup. These methods are only active for development plugins. ```typescript $app.onScanCompleted((event) => { $debug.info("scan completed", { duration: event.duration, mediaCount: event.stats?.animeCount, }) event.next() }) $ui.register((ctx) => { $debug.mark("ui thread started") }) ``` -------------------------------- ### Start Manual Tracking Source: https://seanime.gitbook.io/seanime-extensions/plugins/ui/anime-library/playback-external.md Starts manual progress tracking for media not launched through Seanime's integrated APIs. Specify media ID and episode number. ```typescript await ctx.playback.startManualTracking({ mediaId: 21, episodeNumber: 1, }) ``` -------------------------------- ### Query Documentation with 'ask' Parameter Source: https://seanime.gitbook.io/seanime-extensions/plugins/ui/user-interface.md To get specific information not directly on a page, make an HTTP GET request to the page URL with the 'ask' query parameter. The question should be in natural language and specific. ```http GET https://seanime.gitbook.io/seanime-extensions/plugins/ui/user-interface.md?ask= ``` -------------------------------- ### Query Documentation with 'ask' Parameter Source: https://seanime.gitbook.io/seanime-extensions/plugins/ui/anime-library.md To get additional information not directly present on a page, perform an HTTP GET request to the page URL with the 'ask' query parameter. The question should be specific and in natural language. The response will include an answer and relevant excerpts. ```http GET https://seanime.gitbook.io/seanime-extensions/plugins/ui/anime-library.md?ask= ``` -------------------------------- ### $debug.time Source: https://seanime.gitbook.io/seanime-extensions/plugins/apis/debug.md Starts a named timer. If no label is provided, it defaults to 'default'. ```APIDOC ## time `$debug.time(label?)` Starts a named timer. If you omit the label, Seanime uses `"default"`. ``` -------------------------------- ### Get Studio Details Source: https://seanime.gitbook.io/seanime-extensions/plugins/apis/anilist.md Fetches detailed information about a studio using its ID. ```typescript /** * Get studio details */ $anilist.getStudioDetails(studioId: number): $app.AL_StudioDetails ``` -------------------------------- ### Media Fillers - Get All Source: https://seanime.gitbook.io/seanime-extensions/plugins/apis/database.md Retrieves all media filler data from the database. ```APIDOC ## Media Fillers - Get All ### Description Retrieves all media filler data from the database. ### Method GET ### Endpoint $database.mediaFillers.getAll() ### Response #### Success Response (200) - **fillerData** (array) - An array of media filler objects. ``` -------------------------------- ### Auto Downloader Rules - Get All Source: https://seanime.gitbook.io/seanime-extensions/plugins/apis/database.md Retrieves all configured auto-downloader rules. ```APIDOC ## Auto Downloader Rules - Get All ### Description Retrieves all configured auto-downloader rules. ### Method GET ### Endpoint $database.autoDownloaderRules.getAll() ### Response #### Success Response (200) - **rules** (array) - An array of auto-downloader rule objects. ``` -------------------------------- ### Debrid Permissions Configuration Source: https://seanime.gitbook.io/seanime-extensions/plugins/ui/downloading/debrid.md Example JSON configuration for granting 'debrid' scope and 'allow.writePaths' for downloads. ```json { //... "plugin": { "permissions": { "scopes": ["debrid"], "allow": { "writePaths": ["$DOWNLOAD/**/*"] } } } } ``` -------------------------------- ### Query Documentation via HTTP GET Source: https://seanime.gitbook.io/seanime-extensions/plugins/ui.md Use this method to ask specific questions about the documentation when the answer is not directly available on the page. The question should be in natural language. ```http GET https://seanime.gitbook.io/seanime-extensions/plugins/ui.md?ask= ``` -------------------------------- ### Basic Store Operations Source: https://seanime.gitbook.io/seanime-extensions/plugins/apis/store.md Demonstrates fundamental operations like setting, getting, watching, and removing values from the store. ```typescript $store.set("foo", "bar") $store.get("foo") $store.watch("foo", (value) => {}) $store.getAll() // { "foo": "bar" } $store.remove("foo") $store.removeAll() $store.has("foo") // false $store.getOrSet("foo", () => { return "bar" }) $store.values() // ["bar"] ``` -------------------------------- ### Access Additional Directories Source: https://seanime.gitbook.io/seanime-extensions/plugins/apis/system/os.md Get paths for desktop, document, and download directories. Ensure these are in the allow list for security. ```typescript $osExtra.desktopDir() $osExtra.documentDir() $osExtra.downloadDir() ``` -------------------------------- ### Play Local File Source: https://seanime.gitbook.io/seanime-extensions/plugins/ui/anime-library/videocore.md Starts playback of a local file that has been scanned into Seanime's library. Progress tracking is enabled. The promise resolves once playback is initiated. ```typescript await ctx.videoCore.playLocalFile("/anime/One Piece/One Piece - 1015.mkv") ``` -------------------------------- ### Plugin Initialization with Type Definitions Source: https://seanime.gitbook.io/seanime-extensions/plugins/write-test-share.md Shows how to set up a plugin with necessary type references and register a UI context callback for when the page loads. ```typescript /// /// /// /// function init() { // Everything is magically typed! $ui.register((ctx) => { ctx.dom.onReady(() => { console.log("Page loaded!") }) }) } ``` -------------------------------- ### Storage API Usage Source: https://seanime.gitbook.io/seanime-extensions/plugins/apis/storage.md Demonstrates basic usage of the Storage API for setting, checking, and getting values, including nested keys and type casting. ```typescript $storage.set("foo.bar", 1) $storage.set("foo.baz", "2") $storage.has("foo") // true $storage.get("foo.bar") // 1 $storage.get>("foo") // { "bar": 1, "baz": "2" } $storage.set("foo", "bar") $storage.get("foo") // bar $storage.watch("foo", (value) => {}) ``` -------------------------------- ### File System Operations Example Source: https://seanime.gitbook.io/seanime-extensions/plugins/apis/system/os.md Demonstrates reading, writing, creating, listing, renaming, and removing files and directories. Ensure relevant paths are in the allow list. Always call `close()` when done with a file. ```typescript // C:\Users\user\Downloads\test.txt Hello World! // my-plugin.ts // "C:/Users/user/Downloads/**/*" and "$TEMP/**/*" have been added to 'allowReadPaths' and 'allowWritePaths' // Access the temp directory const tempDirPath = $os.tempDir(); console.log("Temp dir:", tempDirPath); // Read files const content = $os.readFile("C:\Users\user\Downloads\test.txt"); console.log("File content:", $toString(content)); // Hello World! // Write/create files $os.writeFile("C:\Users\user\Downloads\test.txt.new", $toBytes("New content"), 0644); const newContent = $os.readFile("C:\Users\user\Downloads\test.txt.new"); console.log("New file content:", $toString(newContent)); // New content // Read directories const entries = $os.readDir("C:\Users\user\Downloads"); for (const entry of entries) { console.log(entry.name()); // test.txt, test.txt.new } // Create directories $os.mkdir("C:\Users\user\Downloads\newdir", 0755); const newEntries = $os.readDir("C:\Users\user\Downloads"); for (const entry of newEntries) { console.log(entry.name()); // test.txt, test.txt.new, newdir } // Rename files $os.rename("C:\Users\user\Downloads\test.txt.new", "C:\Users\user\Downloads\test.txt.renamed"); let renameSuccess = true try { // File exists, no error thrown $os.stat("C:\Users\user\Downloads\test.txt.renamed"); } catch(e) { renameSuccess = false } console.log(renameSuccess); // true // Remove files $os.remove("C:\Users\user\Downloads\test.txt.renamed"); let removeSuccess = true; try { $os.stat("C:\Users\user\Downloads\test.txt.renamed"); removeSuccess = false; } catch (e) { // Error thrown becuase file should not exist removeSuccess = true; } console.log(removeSuccess); // true ``` -------------------------------- ### Get Parent Element Source: https://seanime.gitbook.io/seanime-extensions/plugins/ui/user-interface/dom.md Use getParent to retrieve the direct parent of a given DOM element. This is useful for navigating up the DOM tree, for example, to access properties of a parent list item. ```typescript const listItem = await ctx.dom.queryOne("li.active") const list = await listItem.getParent() console.log(`Parent element tag: ${list.tagName}`) ``` -------------------------------- ### Start Torrent Stream Source: https://seanime.gitbook.io/seanime-extensions/plugins/ui/anime-library/torrentstream.md Initiates a torrent stream with specified options. Requires 'clientId' if using 'nativeplayer' or 'externalPlayerLink' playback types. ```typescript await ctx.torrentstream.startStream({ mediaId: 21, episodeNumber: 1, aniDbEpisode: "1", playbackType: "default", autoSelect: true, }) ``` -------------------------------- ### Initialize Webview with Preact and State Management Source: https://seanime.gitbook.io/seanime-extensions/plugins/ui/user-interface/webview.md This example initializes a webview panel, registers state variables for media and notes, and sets up communication channels between the extension and the webview. It uses Preact for rendering the UI within the webview. ```typescript function init() { $ui.register((ctx) => { // In a real plugin, you'd likely load this from whatever resource const currentMedia = ctx.state({ id: 101, title: "Frieren: Beyond Journey's End", cover: "https://s4.anilist.co/file/anilistcdn/media/anime/cover/large/bx154587-qQTzQnEJJ3oB.jpg" }) const notes = ctx.state>([ { id: "2", text: "Check discussion for latest episode", checked: true }, { id: "1", text: "Watch the next season", checked: false }, ]) // Create the Webview const panel = ctx.newWebview({ slot: "screen", fullWidth: true, autoHeight: true, sidebar: { label: "Notepad", icon: ``, }, }) // Setup Communication // Automatically keep 'notes' and 'currentMedia' variables in sync with the webview panel.channel.sync("notes", notes) panel.channel.sync("media", currentMedia) // Handle events sent from the webview panel.channel.on("add-note", (text) => { console.log("Received note:", text) const newNote = { id: Date.now().toString(), text, checked: false } // Updating this state automatically sends the new value to the webview thanks to .sync() notes.set([...notes.get(), newNote]) ctx.toast.success("Note added") }) panel.channel.on("toggle-note", (id) => { notes.set(prev => prev.map(n => n.id === id ? { ...n, checked: !n.checked } : n)) }) panel.channel.on("delete-note", (id) => { notes.set(prev => prev.filter(n => n.id !== id)) }) // Render the UI panel.setContent(() => `