An Obsidian plugin that syncs your AniList anime & manga lists into your vault as wikilinked markdown notes, so they show up in Obsidian's graph view. Includes an AI-powered chat assistant to query your library using natural language.
- One-click AniList OAuth via a small GitHub Pages callback page.
- Incremental, idempotent syncs β SHA-256-based change detection means a steady-state sync takes ~3 GraphQL calls and 0 writes in under a second.
- Manual + periodic triggers β ribbon icon, command palette (with hotkeys), settings button, or a configurable auto-sync interval.
- Drift-free β entries you remove from AniList are also removed from your vault.
- Read-only with respect to AniList β your AniList list is the source of truth.
- Works on mobile β
isDesktopOnly: false. - AI Chat Assistant β Ask questions about your anime/manga library using natural language, powered by OpenRouter LLMs.
- Hybrid Search Engine β 7-layer search combining BM25, Trigrams, Vector Search (TF-IDF), Synonyms, Metadata Index, Heading Index, and Link Graph.
- Performance Optimized β Incremental indexing, lazy vector build, disk caching for instant reloads.
- Chat History β Persistent chat sessions with dropdown selector and delete functionality.
- Live Typewriter Animation β Responses stream character-by-character with a blinking cursor.
- Smart Context Extraction β Only relevant sections sent to LLM, reducing token usage.
- Numeric Range Queries β Filter by episodes, duration, year, score ranges.
- Graph Colors β Customize node colors for each note type in Obsidian's Graph View.
- Characters & Voice Actors β Characters synced per-anime with inlined VA data and wikilink tags.
| Note type | Folder | Notes per user |
|---|---|---|
| Anime | Ani-sync/Anime/ |
One per anime on the list |
| Manga | Ani-sync/Manga/ |
One per manga on the list |
| Characters | Ani-sync/Characters/ |
One per anime series, with voice actor data inlined |
| Studios | Ani-sync/Studios/ |
Referenced by Anime notes |
| Staff | Ani-sync/Staff/ |
Referenced by Anime notes (with images) |
| Tags / Genres | Ani-sync/Tags/ |
Referenced by Anime & Manga notes |
| Profile | Ani-sync/Profile.md |
One summary note |
| Voice Actors | Ani-sync/Voice-Actors.md |
Map of all voice actors to their characters |
Every Anime/Manga note links out to studios, staff, characters, tags, and relations with [[Wiki Links]], so they all show up as connected nodes in Obsidian's graph view.
- Obsidian 1.4.0 or later
- An AniList account
- A GitHub Pages site hosting this plugin's OAuth callback page (see
docs/)
- Download
Ani-sync.zipfrom the latest release. - Extract the zip β you'll get an
Ani-sync/folder containingmain.js,manifest.json, andstyles.css. - Copy the
Ani-sync/folder into<your-vault>/.obsidian/plugins/. - In Obsidian: Settings β Community plugins β Installed plugins, enable Ani-sync.
npm installto fetch dev dependencies.npm run buildto producemain.js.- Copy
main.js,manifest.json, andstyles.cssfrom this folder into<your-vault>/.obsidian/plugins/ani-sync/. - In Obsidian: Settings β Community plugins β Installed plugins, enable Ani-sync.
- Host the
docs/folder of this repo on GitHub Pages. - In Obsidian: open Settings β Ani-sync:
- Type your AniList username.
- Click Connect to AniList β a browser tab opens β approve on AniList β AniList registers the Ani-sync app under your account β tab auto-closes β status turns to Connected.
| Setting | Default | Notes |
|---|---|---|
| AniList username | (empty) | Auto-detected after OAuth |
| Output folder | Ani-sync |
Created automatically with subfolders |
| Enable auto-sync | true |
Runs while Obsidian is open |
| Poll interval | 30 (seconds, min 30) |
Used when auto-sync is enabled |
| OpenRouter API key | (empty) | Required for AI chat feature |
| OpenRouter model | (empty) | Select from fetched models list |
| Graph Colors | 6 defaults | Per-type colors for Obsidian Graph View |
- Ribbon icons:
- (database) β sync now.
- (message-circle) β open AI chat sidebar.
- Command palette (all with hotkeys):
Ani-sync: Sync nowβCtrl+Shift+SAni-sync: Disconnect AniListβCtrl+Shift+DAni-sync: Clear sync cacheβCtrl+Shift+CAni-sync: Open Ani-sync ChatβCtrl+Shift+O
- Settings tab:
- Sync now / Clear sync cache buttons.
- OpenRouter AI section β configure API key and model.
- Graph Colors section β color pickers for each node type.
A toast notice reports created N, updated M, skipped K, failed F after each sync.
- Summary query β fetches
id + updatedAtfor every entry (2 GraphQL calls, ANIME and MANGA in parallel). - Diff against cache β if nothing changed, exit in ~1 s with 0 detail fetches and 0 writes.
- Full lists + detail batch β only changed entries' full Media details are fetched (AniList's
Page(perPage: 50)query). - Character fetch β per-media, 4 concurrent, paginated (50 per page). Voice actors filtered to Japanese by preference with fallback.
- Build notes β
builder.tsformats each entity with wikilinked frontmatter + body (characters get inline VA data + tags). - SHA-256 hash check β only notes whose hash changed are written; stale file paths are cleaned up on rename.
- Removals β entries removed from AniList are deleted from the vault.
The cache lives in data.json (Obsidian's plugin data file). AniList rate limits are respected (700ms minimum between requests, 3-attempt retry on 429 / 5xx with exponential backoff). Character fetch is rate-limited at 4 concurrent requests.
The plugin includes an AI-powered chat sidebar that lets you query your synced AniList library using natural language.
- Get an API key from OpenRouter.
- Open Settings β Ani-sync β OpenRouter AI.
- Enter your API key and click Fetch models.
- Select a model from the dropdown (free models are tagged).
The chat uses a 7-layer hybrid search that combines multiple algorithms for maximum accuracy:
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β HYBRID SEARCH SYSTEM β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β βββββββββββββββ βββββββββββββββ βββββββββββββββ β
β β BM25 β β Trigrams β β Vector β β
β β (Fast) β β (Fuzzy) β β (Semantic) β β
β βββββββββββββββ βββββββββββββββ βββββββββββββββ β
β β β β β
β ββββββββββββββββββΌβββββββββββββββββ β
β βΌ β
β βββββββββββββββββββ β
β β Score Fusion β β
β β (Weighted) β β
β βββββββββββββββββββ β
β β β
β βββββββββββββββ ββββββββ΄βββββββ βββββββββββββββ β
β β Synonyms β β Metadata β β Heading β β
β β (Smart) β β (Filters) β β (O(1)) β β
β βββββββββββββββ βββββββββββββββ βββββββββββββββ β
β β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
| Layer | Algorithm | Purpose | Speed |
|---|---|---|---|
| BM25 | TF-IDF with field weighting | Statistical relevance ranking | ~5ms |
| Trigrams | 3-char n-gram Jaccard | Typo-tolerant fuzzy matching | ~5ms |
| Vector Search | TF-IDF + cosine similarity | Semantic understanding | ~15ms |
| Synonyms | Domain-specific expansion | "kid" β "son", "who voices" β "voice actor" | ~1ms |
| Metadata Index | Frontmatter field indexing | Filter by genre, status, score, studio | ~1ms |
| Heading Index | ## heading HashMap (O(1)) |
Instant character/section lookup | ~2ms |
| Link Graph | Wikilink traversal | Follows [[links]] to related files |
~2ms |
| Feature | Description |
|---|---|
| Exact Match | Title/ID exact lookup |
| Substring Match | Title contains query |
| Fuzzy Matching | Trigram matching handles typos |
| Semantic Search | Vector search understands meaning |
| Synonym Expansion | Domain-specific term expansion |
| Metadata Filtering | Genre, status, score, studio filters |
| Numeric Ranges | Episode count, duration, year, score ranges |
| Smart Context | Extracts only relevant sections |
The search supports numeric range queries:
| Query Type | Examples |
|---|---|
| Score ranges | "score above 3", "score below 8", "score of 5" |
| Episode ranges | "more than 100 episodes", "less than 12 episodes" |
| Duration ranges | "more than 60 min", "less than 30 min" |
| Year ranges | "in 2024", "from 2020 to 2024", "after 2019" |
| Optimization | Impact | Description |
|---|---|---|
| Incremental Indexing | 90% faster re-open | Only re-index changed files |
| Lazy Vector Build | 1s faster load | Defer TF-IDF computation to first query |
| Disk Caching | Instant reload | Save index to .anisync-search-cache.json |
| Memory Caching | 5-min instant | In-memory cache with TTL |
| Metric | 100 files | 1000 files | 5000 files |
|---|---|---|---|
| First load | ~1s | ~3s | ~10s |
| Second load | ~0.1s | ~0.5s | ~1s |
| After restart | ~0.1s | ~0.5s | ~1s |
| Query time | ~10ms | ~25ms | ~50ms |
| Memory usage | ~5MB | ~30MB | ~150MB |
Chat sessions are automatically saved as JSON in data.json. Features include:
- Dropdown selector β Click the history icon (β±) to see past conversations
- Individual delete β Hover over a session to reveal delete button
- Batch delete β "Delete all history" button at bottom
- Timestamps β Each message shows the time it was sent
- Persistent storage β History survives Obsidian restarts
User query β Quick response? β Static reply (greetings/bye/help)
β No β Preflight check (API key + model)
β Vault index search (hybrid 7-layer)
β Smart context extraction (relevant sections only)
β Token budget check (max 24K chars)
β sendChatStream(OpenRouter)
β Typewriter animation (throttled 200ms)
β Final render (no cursor)
Entity Queries:
- "What is Frieren about?"
- "Who is Hyakkimaru?"
- "Tell me about Dororo"
Rating/Status Queries:
- "Anime with score 5"
- "Best anime I watched"
- "Anime I'm currently watching"
- "Romance anime completed"
Studio/Staff Queries:
- "Anime by Ufotable"
- "Studio Bones anime"
- "Directed by Mamoru Hosoda"
Relationship Queries:
- "Name of Ichigo and Orihime's kid"
- "Renji and Rukia's child"
- "Who voices Tanjirou?"
Numeric Range Queries:
- "Anime with more than 100 episodes"
- "Score above 3"
- "Anime from 2020 to 2024"
- "Movies less than 30 min"
Complex Queries:
- "Fantasy anime with female protagonist"
- "Action anime with demons tag"
- "Manga completed with score 5"
Customize the color of each note type in Obsidian's Graph View via Settings β Ani-sync β Graph Colors.
| Type | Default Color |
|---|---|
| Anime | #02a9ff (blue) |
| Manga | #8b5cf6 (purple) |
| Staff | #4ade80 (green) |
| Studios | #f59e0b (amber) |
| Tags | #f87171 (red) |
| Characters | #fbbf24 (yellow) |
Colors are applied via Obsidian's .obsidian/graph.json color groups, targeting files by path prefix.
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β ANI-SYNC SYSTEM β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β ANILIST API β β
β β OAuth β GraphQL Queries β Rate Limiting β Retry β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β β
β βΌ β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β SYNC ENGINE β β
β β Diff β Fetch β Build β Hash β Write/Delete β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β β
β βΌ β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β VAULT (.md files) β β
β β Frontmatter + Wikilinks + SHA-256 markers β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β β
β βΌ β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β CACHE LAYER β β
β β Memory Cache (5min) β Disk Cache (1hr) β Full Load β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β β
β βΌ β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β HYBRID SEARCH β β
β β BM25 + Trigrams + Vector + Synonyms + Metadata β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β β
β βΌ β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β LLM PROMPT β β
β β Smart Context β Token Budget β OpenRouter API β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β β
β βΌ β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β CHAT UI β β
β β Typewriter Animation β Markdown Render β History β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
AniList API
β SyncEngine (diff + fetch + hash + write, 700ms rate limit, 8 concurrent writes)
β Vault (.md files with frontmatter + wikilinks + SHA-256 markers)
β data.json (summary map, detail cache, note hashes, file paths, chat history)
β ChatView onOpen() β preloadVaultContext()
β VaultContext.load()
β Disk Cache Check β Memory Cache Check β Full Load
β SearchIndex.build() (BM25 + Trigrams + Heading + Metadata)
β VectorSearch.setNodes() (lazy build on first query)
β handleSend() β buildContextForQuery()
β Hybrid Search (7 layers)
β Smart Context Extraction
β sendChatStream(OpenRouter)
β Typewriter animation β rendered markdown
- Sync writes: 8 concurrent
- Sync deletes: 4 concurrent
- Character fetches: 4 concurrent
- Search index: built once, reused across queries (concurrency-safe via shared promise)
- Vector search: lazy-built on first query
- Typewriter render: lock-flagged to prevent overlapping renders
- Your AniList token is stored in Obsidian's
data.json(not synced to git). - The hosted callback page is static; the Client ID is hardcoded.
- The plugin's settings tab verifies
event.origin === 'https://agniveshtm.github.io'before trusting the OAuthpostMessage. - Your OpenRouter API key is stored in
data.jsonand sent only to OpenRouter's API endpoint. - Session IDs use
crypto.randomUUID()(cryptographically secure) instead ofMath.random().
.
βββ manifest.json Obsidian plugin manifest
βββ main.js Built/bundled output
βββ styles.css Custom styles (chat, settings, progress, cursor)
βββ assets/logo.png Plugin logo
βββ package.json devDeps: obsidian, esbuild, typescript, β¦
βββ esbuild.config.mjs bundles src/main.ts β main.js
βββ tsconfig.json strict TS
βββ src/
β βββ main.ts Plugin class, ribbon, commands, sync orchestration, graph colors
β βββ settings.ts AnisyncSettings + DEFAULT_SETTINGS + GraphColors
β βββ settingsTab.ts Settings tab UI (6 sections, safe-rendered)
β βββ types.ts AniList GraphQL response types (including Character, VoiceActor)
β βββ auth/
β β βββ constants.ts OAuth URLs, client ID, origin validation
β β βββ implicit.ts OAuth implicit flow via postMessage
β βββ anilist/
β β βββ client.ts GraphQL client (rate-limiter, retry, character fetch)
β β βββ queries.ts All GraphQL operations (6 queries)
β βββ notes/
β β βββ builder.ts Note artifact builder (7 types, character+VA inlining)
β β βββ slugify.ts Filename sanitization
β βββ sync/
β β βββ engine.ts Sync orchestrator (diff β fetch β build β hash β write/delete)
β β βββ hash.ts SHA-256 via crypto.subtle + marker extract/strip
β β βββ cache.ts Cache schema + diff algorithm
β βββ chat/
β β βββ view.ts Chat UI (typewriter, markdown, history dropdown, timestamps)
β β βββ vaultContext.ts 7-layer hybrid search (BM25, Trigram, Vector, Synonyms, Metadata, Heading, LinkGraph)
β β βββ logo.ts Logo data URL for welcome screen
β βββ openrouter/
β βββ client.ts OpenRouter API (models list + streaming chat completions)
β βββ types.ts OpenRouter API types
βββ docs/ Host on GitHub Pages for OAuth callback
β βββ index.html Callback page with postMessage
β βββ style.css
β βββ script.js
βββ .github/workflows/
βββ test.yml CI: typecheck + build
βββ deploy-docs.yml Deploy docs/ to GitHub Pages
βββ release.yml Build + create release with zip
solution.mdβ Technical notes and improvement log.
MIT
