# Comic Looms Architecture Reference
This companion document explains the internal architecture of Comic Looms, focusing on the matcher lifecycle, page/image fetching pipeline, and the roles of key modules.
> [!NOTE]
> This document is intended to be supplementing the [CONTRIBUTING](CONTRIBUTING.md) document, please refer to it for guidance on contributing and adding new site support.
> [!TIP]
> For the complete project structure, please see [PROJECT_STRUCTURE.md](.assets/PROJECT_STRUCTURE.md). For a template file for creating new matchers, check out [template-example.ts](src/platform/matchers/template-example.ts).
## Table of Contents
- [Comic Looms Architecture Reference](#comic-looms-architecture-reference)
- [Table of Contents](#table-of-contents)
- [1. Architecture Overview](#1-architecture-overview)
- [2. Matcher Lifecycle](#2-matcher-lifecycle)
- [2.1. Registration](#21-registration)
- [2.2. URL Matching](#22-url-matching)
- [2.3. Matcher Interface and BaseMatcher](#23-matcher-interface-and-basematcher)
- [3. Page and Image Fetching](#3-page-and-image-fetching)
- [3.1. Practical Flow Example](#31-practical-flow-example)
- [3.2. Function explanations (where to look in code)](#32-function-explanations-where-to-look-in-code)
- [3.3. Image Fetcher state (detailed)](#33-image-fetcher-state-detailed)
- [3.4. Chapter Management](#34-chapter-management)
- [3.5. Lazy Page Loading](#35-lazy-page-loading)
- [4. Image Fetching Flow](#4-image-fetching-flow)
- [4.1. IMGFetcher State Machine](#41-imgfetcher-state-machine)
- [4.2. Image URL Resolution](#42-image-url-resolution)
- [4.3. Image Download and Processing](#43-image-download-and-processing)
- [5. Queue and Concurrency](#5-queue-and-concurrency)
- [6. UI Integration](#6-ui-integration)
- [7. Event Bus](#7-event-bus)
- [7.1. Event Bus (EBUS)](#71-event-bus-ebus)
- [8. Download Architecture](#8-download-architecture)
- [9. Common Code Paths](#9-common-code-paths)
- [9.1. Simple page-based site](#91-simple-page-based-site)
- [9.2. API-based site](#92-api-based-site)
- [10. Recommended Files for Reference](#10-recommended-files-for-reference)
## 1. Architecture Overview
The userscript is built around three main layers:
1. Site detection and matcher selection.
2. Page source retrieval and image node parsing.
3. Image download, processing, rendering, and UI presentation.
The most important files are:
| Component | Key files | Representative functions |
| -------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------------------ |
| Adapter / Matcher registry | `src/platform/adapt.ts`, `src/platform/matchers/*` | `Adapter.addSetup()`, `Adapter.handleMatcher()` |
| Matcher base & interfaces | `src/platform/platform.ts` | `BaseMatcher.fetchPagesSource()`, `parseImgNodes()`, `fetchOriginMeta()` |
| Page orchestration | `src/page-fetcher.ts` | `PageFetcher.init()`, `appendNewChapters_()`, `appendNextPage()` |
| Image fetcher | `src/img-fetcher.ts` | `IMGFetcher.start()`, `fetchImage()`, `fetchOriginMeta()` |
| Queue & concurrency | `src/fetcher-queue.ts` | `IMGFetcherQueue.push()`, concurrency control |
| Download & archive | `src/download/downloader.ts`, `src/download/gallery-meta.ts` | ZIP packaging, `zip-stream` usage |
| UI | `src/ui/` | `full-view-grid-manager`, `big-image-frame-manager` |
Additions below include sequence diagrams and function-level descriptions for key flows.
## 2. Matcher Lifecycle
### 2.1. Registration
Each matcher registers itself in `src/platform/matchers/` by calling `ADAPTER.addSetup()`.
A matcher setup contains:
- `name`
- `workURLs`
- optional `match`
- `constructor`
### 2.2. URL Matching
`src/platform/adapt.ts` performs URL matching:
- `Adapter.addSetup()` stores matcher metadata.
- `Adapter.handleMatcher()` checks each setup's `workURLs` against `window.location.href`.
- The first matching setup becomes `ADAPTER.matcher`.
- `ADAPTER.ready` resolves with the selected setup.
This is the first decision point of the script.
```mermaid
sequenceDiagram
participant W as Window
participant A as ADAPTER
participant M as Matcher
participant P as PageFetcher
W->>A: load matchers
A->>A: handleMatcher(workURLs)
A-->>M: select matcher
M->>P: used by PageFetcher
```
### 2.3. Matcher Interface and BaseMatcher
The internal matcher API is defined in `src/platform/platform.ts`.
**`Matcher
` core methods:**
- `fetchPagesSource(chapter: Chapter): AsyncGenerator>`
- `parseImgNodes(pageSource: P, chapterID?: number): Promise`
- `fetchOriginMeta(node: ImageNode, retry: boolean, chapterID?: number): Promise`
- `fetchImageData(imf: IMGFetcher): Promise<[Blob, number]>`
- `galleryMeta(chapter: Chapter): Promise`
- `title(chapters: Chapter[]): string`
- `processData(data: Uint8Array, contentType: string, node: ImageNode): Promise<[Uint8Array, string]>`
- `headers(node: ImageNode): Record`
- `appendNewChapters(url: string, old: Chapter[]): Chapter[]`
These methods use a few shared types:
- `P` is the matcher’s generic page source type. It can be a DOM `Document`, plain text, JSON payload, or another site-specific structure.
- `PagesSource` is the normalized alias passed into `parseImgNodes()` and typically matches `P`.
- `ImageNode` is the normalized image metadata object. It should include properties like `href`, `thumbnailSrc`, `originSrc`, `title`, and any extra values required to resolve the large image.
- `OriginMeta` is the resolved original-image result, usually containing the final image URL and optional headers.
- `Result` is the async success/failure wrapper used by `fetchPagesSource()` so the pipeline can continue if one page fetch fails.
`BaseMatcher` provides defaults for many matcher behaviors. This means a new matcher usually only needs to implement three methods:
- `fetchPagesSource()` to produce page sources or pagination items.
- `parseImgNodes()` to convert a page source into `ImageNode[]`.
- `fetchOriginMeta()` to resolve the actual image URL.
The base class also supplies working defaults for:
- `fetchImageData()` — standard blob download logic.
- `processData()` — pass-through image processing, overridable for decryption or reconstruction.
- `headers()` — request headers support for protected sites.
- `appendNewChapters()` — default chapter list behavior.
- `galleryMeta()` / `title()` — metadata helper behavior.
This design keeps matcher implementations focused on site-specific parsing and URL resolution while reusing shared download and queue behavior.
## 3. Page and Image Fetching
This section covers how page sources, image nodes, and image fetchers move through the runtime.
### 3.1. Practical Flow Example
This conceptual example shows how `PageFetcher` consumes matcher page sources and creates `IMGFetcher` instances. The exact implementation lives in `src/page-fetcher.ts`.
Example flow
```ts
// conceptual example; see src/page-fetcher.ts for the real implementation
async function consumeChapter(chapter, matcher, queue) {
const iter = matcher.fetchPagesSource(chapter);
while (true) {
const res = await iter.next();
if (res.done) break;
if (res.value.error) continue;
const nodes = await matcher.parseImgNodes(res.value.value || res.value.val);
nodes.forEach((n, idx) =>
queue.push(new IMGFetcher(idx, n, matcher, 0, chapter.id)),
);
}
}
```
### 3.2. Function explanations (where to look in code)
- `Adapter.handleMatcher(setup)` — see [`src/platform/adapt.ts`](src/platform/adapt.ts). It applies site overrides (from `getSiteConfig`) and resolves `ADAPTER.ready` when a match is found.
- `PageFetcher.init()` — see [`src/page-fetcher.ts`](src/page-fetcher.ts). It iterates `matcher.fetchChapters()` and sets up `Chapter.sourceIter`.
- `PageFetcher.appendNextPage()` / `appendPages()` — the methods that consume `Chapter.sourceIter.next()` and call `matcher.parseImgNodes()`; used to lazily grow the thumbnail queue.
- `IMGFetcher.fetchOriginMeta()` — thin wrapper that forwards to `matcher.fetchOriginMeta()` and updates `node.originSrc`.
- `IMGFetcher.fetchImageData()` — calls `matcher.fetchImageData(imf)`; the `BaseMatcher` implementation uses `xhrWapper()` to fetch blobs with timeout support.
- `BaseMatcher.processData()` — default pass-through; override to decrypt or reconstruct images (see `pixiv.ts`, `18comic.ts`).
### 3.3. Image Fetcher state (detailed)
```mermaid
stateDiagram-v2
[*] --> URL: start
URL --> DATA: origin meta resolved
DATA --> DONE: downloaded & processed
DONE --> [*]
URL --> FAILED: on error
DATA --> FAILED: on error
```
### 3.4. Chapter Management
`PageFetcher` supports both single-chapter and multi-chapter sites.
- `matcher.fetchChapters()` may return a single default chapter.
- Sites with real chapters override this method.
- Each `Chapter` has a `sourceIter` created from `matcher.fetchPagesSource(chapter)`.
### 3.5. Lazy Page Loading
`Chapter.sourceIter` is consumed lazily by `PageFetcher.appendNextPage()`:
- Each `next()` call yields one page source.
- This reduces initial load and supports long galleries.
- The script continues loading more pages until enough images are queued.
## 4. Image Fetching Flow
### 4.1. IMGFetcher State Machine
`src/img-fetcher.ts` manages each image through stages:
- `FetchState.URL` – resolve the large image URL.
- `FetchState.DATA` – download image bytes.
- `FetchState.DONE` – create blob URL and render.
`IMGFetcher.start()` retries the full process up to three times.
### 4.2. Image URL Resolution
`IMGFetcher.fetchOriginMeta()` calls the matcher method:
- `matcher.fetchOriginMeta(node, retry, chapterID)`
- If `node.originSrc` exists, this call may return it directly.
- Otherwise, the matcher may request a detail page or API endpoint.
### 4.3. Image Download and Processing
`IMGFetcher.fetchImageData()` calls `matcher.fetchImageData(this)`.
`BaseMatcher.fetchImageData()` uses `xhrWapper()` for blob downloads and supports timeouts.
After download, `matcher.processData(data, contentType, node)` is invoked.
- Most matchers use the default pass-through behavior.
- Complex sites override this to decrypt or restore data.
## 5. Queue and Concurrency
`src/fetcher-queue.ts` manages how many images are loaded concurrently.
- The queue tracks active fetchers.
- It ensures image downloads adhere to configured thread counts.
- It also coordinates with the UI and idle loader.
## 6. UI Integration
`src/ui/` contains the presentation layer.
- `html.ts` establishes the Shadow DOM and root container.
- `full-view-grid-manager.ts` renders thumbnails.
- `big-image-frame-manager.ts` manages the large image viewer.
- `config-panel.ts` exposes user settings.
The UI responds to events from `EBUS` and user actions.
## 7. Event Bus
`src/event-bus.ts` is the central pub-sub bus that enables loose coupling between components.
### 7.1. Event Bus (EBUS)
The event system allows modules to publish and subscribe to named events.
**Event Types:**
```ts
interface EventBus {
subscribe(event: string, callback: (data: T) => void): void;
unsubscribe(event: string, callback: Function): void;
publish(event: string, data: T): void;
once(event: string, callback: (data: T) => void): void;
}
```
**Example usage:**
```ts
// Subscribe to image append events
EBUS.subscribe("pf-on-appended", ({ nodes, chapterID }) => {
console.log(`Added ${nodes.length} images from chapter ${chapterID}`);
renderThumbnails(nodes);
});
// Publish download start
EBUS.publish("start-download", {
chapters: selectedChapters,
options: { zip: true, volumeSize: 1500 },
});
// One-time subscription
EBUS.once("download-finished", ({ path }) => {
showNotification(`Download complete: ${path}`);
});
```
**Important events include:**
| Event | Publisher | Subscribers | Data | Description |
| ------------------------ | -------------------- | ------------------- | -------------------------------------------------------------- | ------------------------------------- |
| `pf-init` | PageFetcher | UI managers | `{ chapters: Chapter[] }` | PageFetcher initialized with chapters |
| `pf-change-chapter` | PageFetcher | UI, Queue | `{ chapter: Chapter, index: number }` | Chapter changed |
| `pf-on-appended` | PageFetcher | FullViewGridManager | `{ nodes: ImageNode[], chapterID: number }` | New images appended |
| `pf-start-fetch` | PageFetcher | Queue | `{ chapterID: number }` | Starting to fetch a chapter |
| `pf-fetch-error` | PageFetcher | UI | `{ error: Error, chapterID: number }` | Error fetching pages |
| `ifq-do` | IMGFetcherQueue | IMGFetcher | `{ fetcher: IMGFetcher }` | Process next fetcher |
| `ifq-on-finished-report` | IMGFetcherQueue | UI | `{ finished: number, total: number }` | Queue progress report |
| `ifq-finished` | IMGFetcherQueue | UI | `{ chapterID: number }` | Queue finished for chapter |
| `imf-on-finished` | IMGFetcher | Queue, UI | `{ node: ImageNode, fetcher: IMGFetcher }` | Image finished loading |
| `imf-on-error` | IMGFetcher | UI | `{ node: ImageNode, error: Error }` | Image failed to load |
| `imf-resize` | IMGFetcher | UI | `{ node: ImageNode, width: number, height: number }` | Image dimensions available |
| `bifm-on-show` | BigImageFrameManager | UI | `{ node: ImageNode }` | Big image viewer shown |
| `bifm-on-hidden` | BigImageFrameManager | UI | `{ node: ImageNode }` | Big image viewer hidden |
| `bifm-on-nav` | BigImageFrameManager | UI | `{ node: ImageNode, direction: 'next', 'prev' }` | Viewer navigation |
| `start-download` | UI | Downloader | `{ chapters: Chapter[], options: DownloadOptions }` | Download initiated |
| `download-progress` | Downloader | UI | `{ current: number, total: number, chapter: string }` | Download progress |
| `download-finished` | Downloader | UI | `{ path: string, chapters: Chapter[] }` | Download complete |
| `download-error` | Downloader | UI | `{ error: Error, chapter?: Chapter }` | Download error |
| `toggle-main-view` | UI | PageFetcher | `{ visible: boolean }` | Main view toggled |
| `config-changed` | Config | All | `{ config: Config }` | Configuration changed |
| `keyboard-shortcut` | event.ts | All | `{ key: string, ctrl: boolean, shift: boolean, alt: boolean }` | Keyboard shortcut triggered |
Components subscribe to these events to synchronize loading, rendering, and user interaction.
## 8. Download Architecture
`src/download/downloader.ts` handles archive creation and segmentation.
- `gallery-meta.ts` defines metadata stored with ZIPs.
- `src/utils/zip-stream.ts` implements streaming ZIP creation.
- Large archives are split at 1.5GB boundaries.
## 9. Common Code Paths
### 9.1. Simple page-based site
- `fetchPagesSource(document)`
- `parseImgNodes(Document)`
- `fetchOriginMeta(node)`
### 9.2. API-based site
- `fetchPagesSource()` returns parsed JSON.
- `parseImgNodes(JSON)` converts API records to `ImageNode[]`.
- `fetchOriginMeta(node)` resolves the original URL from detail or media data.
## 10. Recommended Files for Reference
- `src/platform/platform.ts`
- `src/platform/adapt.ts`
- `src/page-fetcher.ts`
- `src/img-fetcher.ts`
- `src/download/downloader.ts`
- `src/utils/zip-stream.ts`
- `src/platform/matchers/pixiv.ts`
- `src/platform/matchers/ehentai.ts`
- `src/platform/matchers/twitter.ts`
- `src/platform/matchers/18comic.ts`