Claude-skill-registry Download System
Background chapter downloads and offline reading
install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/downloads" ~/.claude/skills/majiayu000-claude-skill-registry-download-system && rm -rf "$T"
manifest:
skills/data/downloads/SKILL.mdsource content
Download System
Download Service
import { downloadService } from '../services/downloadService'; // Download a chapter await downloadService.downloadChapter(manga, chapter, sourceId); // Download multiple chapters await downloadService.downloadChapters(manga, chapters, sourceId); // Cancel download await downloadService.cancelDownload(chapterId); // Get download status const status = await downloadService.getDownloadStatus(chapterId); // Get all downloads const downloads = await downloadService.getAllDownloads(); // Delete downloaded chapter await downloadService.deleteDownload(chapterId);
Download Status
type DownloadStatus = | 'pending' | 'downloading' | 'completed' | 'failed' | 'cancelled'; interface Download { mangaId: string; chapterId: string; status: DownloadStatus; progress: number; // 0-100 totalPages: number; downloadedPages: number; error?: string; }
Background Downloads (Android)
Uses
react-native-background-actions:
import BackgroundService from 'react-native-background-actions'; const options = { taskName: 'Download', taskTitle: 'Downloading chapters', taskDesc: 'Chapter 1 of 10', taskIcon: { name: 'ic_launcher', type: 'mipmap' }, progressBar: { max: 100, value: 0 }, }; await BackgroundService.start(downloadTask, options); await BackgroundService.updateNotification({ taskDesc: 'Chapter 2 of 10' }); await BackgroundService.stop();
Foreground Service (Android 14+)
Required config in
plugins/withBackgroundActionsServiceType.js:
// Adds foregroundServiceType="dataSync" to AndroidManifest.xml module.exports = function withBackgroundActionsServiceType(config) { return withAndroidManifest(config, (config) => { // Modify service declaration return config; }); };
Headless Extension Runtime
When the app goes to background, the
ExtensionRunner WebView may become unavailable.
The headlessExtensionRuntime.ts provides a fallback that runs extensions directly in
the React Native JS engine:
import { headlessRuntime } from '../services/headlessExtensionRuntime'; // Check if headless runtime is available if (headlessRuntime.isAvailable()) { // Run extension method without WebView const result = await headlessRuntime.runExtensionMethod( extensionId, 'getChapterDetails', [mangaId, chapterId] ); }
The headless runtime:
- Executes extension JavaScript using
in a sandboxed environmenteval() - Provides mock
object withApp
,createRequestManager
, etc.createSourceStateManager - Uses native
for HTTP requests (no CORS in React Native)fetch - Falls back automatically when WebView bridge is unavailable
Storage Structure
documentDirectory/ └── downloads/ └── {mangaId}/ └── {chapterId}/ ├── page_001.jpg ├── page_002.jpg └── ...
File Operations
import * as FileSystem from 'expo-file-system'; const downloadDir = `${FileSystem.documentDirectory}downloads/`; // Create directory await FileSystem.makeDirectoryAsync( `${downloadDir}${mangaId}/${chapterId}`, { intermediates: true } ); // Download image await FileSystem.downloadAsync( imageUrl, `${downloadDir}${mangaId}/${chapterId}/page_${index}.jpg` ); // Check if exists const info = await FileSystem.getInfoAsync(path); if (info.exists) { /* ... */ } // Delete await FileSystem.deleteAsync(path, { idempotent: true });
Download Queue
// Queue management const queue: DownloadTask[] = []; let isProcessing = false; async function processQueue() { if (isProcessing || queue.length === 0) return; isProcessing = true; const task = queue.shift(); await downloadChapter(task); isProcessing = false; processQueue(); // Process next } function addToQueue(task: DownloadTask) { queue.push(task); processQueue(); }
Notifications
import * as Notifications from 'expo-notifications'; // Show progress notification await Notifications.scheduleNotificationAsync({ content: { title: 'Downloading', body: `${manga.title} - Chapter ${chapter.chapNum}`, data: { mangaId, chapterId }, }, trigger: null, // Immediate });