Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

233 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Ani-sync

Ani-sync Logo

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.

Features

  • 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.

What gets synced

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.

Requirements

  • Obsidian 1.4.0 or later
  • An AniList account
  • A GitHub Pages site hosting this plugin's OAuth callback page (see docs/)

Installation (manual)

  1. Download Ani-sync.zip from the latest release.
  2. Extract the zip β€” you'll get an Ani-sync/ folder containing main.js, manifest.json, and styles.css.
  3. Copy the Ani-sync/ folder into <your-vault>/.obsidian/plugins/.
  4. In Obsidian: Settings β†’ Community plugins β†’ Installed plugins, enable Ani-sync.

Installation (developer mode)

  1. npm install to fetch dev dependencies.
  2. npm run build to produce main.js.
  3. Copy main.js, manifest.json, and styles.css from this folder into <your-vault>/.obsidian/plugins/ani-sync/.
  4. In Obsidian: Settings β†’ Community plugins β†’ Installed plugins, enable Ani-sync.

AniList setup (one-time)

  1. Host the docs/ folder of this repo on GitHub Pages.
  2. 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.

Configuration

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

Usage

  • Ribbon icons:
    • (database) β€” sync now.
    • (message-circle) β€” open AI chat sidebar.
  • Command palette (all with hotkeys):
    • Ani-sync: Sync now β€” Ctrl+Shift+S
    • Ani-sync: Disconnect AniList β€” Ctrl+Shift+D
    • Ani-sync: Clear sync cache β€” Ctrl+Shift+C
    • Ani-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.

How sync works

  1. Summary query β€” fetches id + updatedAt for every entry (2 GraphQL calls, ANIME and MANGA in parallel).
  2. Diff against cache β€” if nothing changed, exit in ~1 s with 0 detail fetches and 0 writes.
  3. Full lists + detail batch β€” only changed entries' full Media details are fetched (AniList's Page(perPage: 50) query).
  4. Character fetch β€” per-media, 4 concurrent, paginated (50 per page). Voice actors filtered to Japanese by preference with fallback.
  5. Build notes β€” builder.ts formats each entity with wikilinked frontmatter + body (characters get inline VA data + tags).
  6. SHA-256 hash check β€” only notes whose hash changed are written; stale file paths are cleaned up on rename.
  7. 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.

AI Chat

The plugin includes an AI-powered chat sidebar that lets you query your synced AniList library using natural language.

Setup

  1. Get an API key from OpenRouter.
  2. Open Settings β†’ Ani-sync β†’ OpenRouter AI.
  3. Enter your API key and click Fetch models.
  4. Select a model from the dropdown (free models are tagged).

Hybrid Search Engine

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

Search Features

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

Numeric Query Support

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"

Performance Optimizations

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

Performance Metrics

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 History

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

Response Pipeline

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)

Example Queries

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"

Graph Colors

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.

Architecture

System Overview

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                        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        β”‚   β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β”‚
β”‚                                                                 β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Data Flow

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

Concurrency

  • 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

Security

  • 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 OAuth postMessage.
  • Your OpenRouter API key is stored in data.json and sent only to OpenRouter's API endpoint.
  • Session IDs use crypto.randomUUID() (cryptographically secure) instead of Math.random().

Project layout

.
β”œβ”€β”€ 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

See also

  • solution.md β€” Technical notes and improvement log.

License

MIT

About

Ani-sync is an Obsidian plugin that automatically syncs your AniList anime and manga lists directly into your Obsidian vault as wikilinked Markdown notes.

Topics

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Used by

Contributors

Languages