From 32ca410f4edbff578d71781d943c41573912f476 Mon Sep 17 00:00:00 2001 From: amrkmn Date: Fri, 26 Dec 2025 22:39:23 +0800 Subject: Initial commit --- .env.example | 42 +++ .gitattributes | 3 + .github/workflows/update.yml | 66 ++++ .gitignore | 56 +++ .npmrc | 1 + .prettierignore | 9 + .prettierrc | 17 + .serena/.gitignore | 1 + .serena/memories/project_overview.md | 39 ++ .serena/project.yml | 84 +++++ AGENTS.md | 184 +++++++++ CLAUDE.md | 287 ++++++++++++++ README.md | 44 +++ bun.lock | 370 ++++++++++++++++++ extensions.json | 30 ++ package.json | 40 ++ scripts/cache.ts | 200 ++++++++++ scripts/cache/files.ts | 138 +++++++ scripts/cache/lock.ts | 220 +++++++++++ scripts/cache/logger.ts | 152 ++++++++ scripts/cache/manifest.ts | 76 ++++ scripts/cache/metadata.ts | 96 +++++ scripts/cache/s3.ts | 117 ++++++ scripts/cache/utils.ts | 85 +++++ scripts/config.ts | 20 + scripts/meilisearch.ts | 158 ++++++++ scripts/types.ts | 31 ++ scripts/update.ts | 183 +++++++++ scripts/worker.ts | 6 + src/app.css | 563 ++++++++++++++++++++++++++++ src/app.d.ts | 13 + src/app.html | 13 + src/lib/components/ExtensionCard.svelte | 47 +++ src/lib/components/ExtensionCategory.svelte | 24 ++ src/lib/components/ExtensionRow.svelte | 47 +++ src/lib/components/Footer.svelte | 16 + src/lib/components/MirrorSelector.svelte | 26 ++ src/lib/search/debounce.ts | 30 ++ src/lib/search/meilisearch.ts | 125 ++++++ src/lib/search/types.ts | 27 ++ src/lib/search/utils.ts | 17 + src/lib/stores/mirror.ts | 3 + src/lib/types.ts | 26 ++ src/routes/+layout.svelte | 28 ++ src/routes/+layout.ts | 10 + src/routes/+page.svelte | 21 ++ src/routes/search/+page.svelte | 322 ++++++++++++++++ static/favicon.ico | Bin 0 -> 308321 bytes static/robots.txt | 3 + svelte.config.js | 15 + tests/cache-files.test.ts | 100 +++++ tests/cache-format.test.ts | 27 ++ tests/cache-lock.test.ts | 141 +++++++ tests/cache-manifest.test.ts | 76 ++++ tests/cache-metadata.test.ts | 21 ++ tests/cache-utils.test.ts | 23 ++ tests/debounce.test.ts | 60 +++ tests/logger.test.ts | 26 ++ tests/search-utils.test.ts | 24 ++ tsconfig.json | 20 + vite.config.ts | 6 + wrangler.toml | 5 + 62 files changed, 4660 insertions(+) create mode 100644 .env.example create mode 100644 .gitattributes create mode 100644 .github/workflows/update.yml create mode 100644 .gitignore create mode 100644 .npmrc create mode 100644 .prettierignore create mode 100644 .prettierrc create mode 100644 .serena/.gitignore create mode 100644 .serena/memories/project_overview.md create mode 100644 .serena/project.yml create mode 100644 AGENTS.md create mode 100644 CLAUDE.md create mode 100644 README.md create mode 100644 bun.lock create mode 100644 extensions.json create mode 100644 package.json create mode 100644 scripts/cache.ts create mode 100644 scripts/cache/files.ts create mode 100644 scripts/cache/lock.ts create mode 100644 scripts/cache/logger.ts create mode 100644 scripts/cache/manifest.ts create mode 100644 scripts/cache/metadata.ts create mode 100644 scripts/cache/s3.ts create mode 100644 scripts/cache/utils.ts create mode 100644 scripts/config.ts create mode 100644 scripts/meilisearch.ts create mode 100644 scripts/types.ts create mode 100644 scripts/update.ts create mode 100644 scripts/worker.ts create mode 100644 src/app.css create mode 100644 src/app.d.ts create mode 100644 src/app.html create mode 100644 src/lib/components/ExtensionCard.svelte create mode 100644 src/lib/components/ExtensionCategory.svelte create mode 100644 src/lib/components/ExtensionRow.svelte create mode 100644 src/lib/components/Footer.svelte create mode 100644 src/lib/components/MirrorSelector.svelte create mode 100644 src/lib/search/debounce.ts create mode 100644 src/lib/search/meilisearch.ts create mode 100644 src/lib/search/types.ts create mode 100644 src/lib/search/utils.ts create mode 100644 src/lib/stores/mirror.ts create mode 100644 src/lib/types.ts create mode 100644 src/routes/+layout.svelte create mode 100644 src/routes/+layout.ts create mode 100644 src/routes/+page.svelte create mode 100644 src/routes/search/+page.svelte create mode 100644 static/favicon.ico create mode 100644 static/robots.txt create mode 100644 svelte.config.js create mode 100644 tests/cache-files.test.ts create mode 100644 tests/cache-format.test.ts create mode 100644 tests/cache-lock.test.ts create mode 100644 tests/cache-manifest.test.ts create mode 100644 tests/cache-metadata.test.ts create mode 100644 tests/cache-utils.test.ts create mode 100644 tests/debounce.test.ts create mode 100644 tests/logger.test.ts create mode 100644 tests/search-utils.test.ts create mode 100644 tsconfig.json create mode 100644 vite.config.ts create mode 100644 wrangler.toml diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..d23ce6b --- /dev/null +++ b/.env.example @@ -0,0 +1,42 @@ +# S3-compatible storage configuration +# The caching system supports any S3-compatible service including: +# - Cloudflare R2 +# - Backblaze B2 +# - AWS S3 +# - MinIO +# - DigitalOcean Spaces +# And more... + +# S3 endpoint URL (required) +# Examples: +# Cloudflare R2: https://.r2.cloudflarestorage.com +# Backblaze B2: https://s3..backblazeb2.com +# AWS S3: https://s3..amazonaws.com +# MinIO: http://localhost:9000 +S3_ENDPOINT= + +# S3 access key ID (required) +S3_ACCESS_KEY_ID= + +# S3 secret access key (required) +S3_SECRET_ACCESS_KEY= + +# S3 bucket name (required) +S3_BUCKET_NAME= + +# S3 region (optional) +# Use "auto" for Cloudflare R2 +# For AWS S3 or Backblaze B2, use the appropriate region (e.g., "us-east-1", "eu-central-003") +S3_REGION= + +# Meilisearch configuration (for indexing) +MEILISEARCH_HOST=http://localhost:7700 +MEILISEARCH_MASTER_KEY=masterKey + +# Meilisearch configuration (for frontend - optional) +# Only set VITE_MEILISEARCH_HOST if you want to use Meilisearch search in the browser +# Leave empty to use client-side Fuse.js search instead +VITE_MEILISEARCH_HOST=https://search.x.noz.one + +# Search-only API key for frontend (read-only access) +VITE_MEILISEARCH_DEFAULT_SEARCH_KEY=3266e98f431f6e28db4c804bd4d6b95fc43c8510a693969ef76d5ec3ec62a204 \ No newline at end of file diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..314766e --- /dev/null +++ b/.gitattributes @@ -0,0 +1,3 @@ +* text=auto eol=lf +*.{cmd,[cC][mM][dD]} text eol=crlf +*.{bat,[bB][aA][tT]} text eol=crlf diff --git a/.github/workflows/update.yml b/.github/workflows/update.yml new file mode 100644 index 0000000..053878e --- /dev/null +++ b/.github/workflows/update.yml @@ -0,0 +1,66 @@ +name: Update Extensions + +on: + schedule: + - cron: '0 */4 * * *' + workflow_dispatch: + push: + branches: + - main + +permissions: + contents: write + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: false + +jobs: + update: + runs-on: ubuntu-latest + if: github.event_name != 'push' || github.event.head_commit.author.name != 'github-actions[bot]' + outputs: + updated: ${{ steps.update.outputs.updated }} + + steps: + - name: Checkout repository + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + + - name: Install bun + uses: oven-sh/setup-bun@735343b667d3e6f658f44d0eca948eb6282f2b76 # v2.0.2 + with: + bun-version: latest + + - name: Install dependencies + run: bun install --frozen-lockfile + + - name: Update extensions (quick) + id: update + run: bun run update --quick + + - name: Commit and push changes + if: steps.update.outputs.updated == 'true' + run: | + git config --local user.name "github-actions[bot]" + git config --local user.email "github-actions[bot]@users.noreply.github.com" + git add extensions.json + git diff-index --quiet HEAD || git commit -m "chore: update extensions.json" + git push + + sync-to-gitlab: + runs-on: ubuntu-latest + needs: update + if: needs.update.outputs.updated == 'true' || github.event_name == 'workflow_dispatch' + + steps: + - name: Checkout repository + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + with: + ref: main + fetch-depth: 0 + + - name: Sync to GitLab + uses: pixta-dev/repository-mirroring-action@674e65a7d483ca28dafaacba0d07351bdcc8bd75 # v1.1.1 + with: + target_repo_url: ${{ secrets.GITLAB_REPO }} + ssh_private_key: ${{ secrets.GITLAB_SSH }} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0f33b73 --- /dev/null +++ b/.gitignore @@ -0,0 +1,56 @@ +node_modules + +# Output +.output +.vercel +.netlify +.wrangler +/.svelte-kit +/build + +# OS +.DS_Store +Thumbs.db + +# Env +.env +.env.* +!.env.example +!.env.test + +# Vite +vite.config.js.timestamp-* +vite.config.ts.timestamp-* + +# output +out +dist +*.tgz + +# code coverage +coverage +*.lcov + +# logs +logs +_.log +report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json + +# caches +.eslintcache +.cache +*.tsbuildinfo + +# IntelliJ based IDEs +.idea + +# Extensions +tmp/ +extensions/ + +# Generated static files +static/extensions.json +static/data.json +static/keiyoushi/ +static/yuzono/ +static/kohi-den/ diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..b6f27f1 --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +engine-strict=true diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..7d74fe2 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,9 @@ +# Package Managers +package-lock.json +pnpm-lock.yaml +yarn.lock +bun.lock +bun.lockb + +# Miscellaneous +/static/ diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..6b0167a --- /dev/null +++ b/.prettierrc @@ -0,0 +1,17 @@ +{ + "useTabs": false, + "tabWidth": 4, + "singleQuote": true, + "trailingComma": "none", + "printWidth": 100, + "endOfLine": "lf", + "plugins": ["prettier-plugin-svelte"], + "overrides": [ + { + "files": "*.svelte", + "options": { + "parser": "svelte" + } + } + ] +} diff --git a/.serena/.gitignore b/.serena/.gitignore new file mode 100644 index 0000000..14d86ad --- /dev/null +++ b/.serena/.gitignore @@ -0,0 +1 @@ +/cache diff --git a/.serena/memories/project_overview.md b/.serena/memories/project_overview.md new file mode 100644 index 0000000..2d94b70 --- /dev/null +++ b/.serena/memories/project_overview.md @@ -0,0 +1,39 @@ +# Project: x (Mihon & Aniyomi Extensions Aggregator) + +## Overview + +A repository aggregator for Mihon and Aniyomi extensions that automatically syncs from multiple upstream sources. + +## Tech Stack + +- **Runtime/Package Manager:** Bun +- **Frontend:** SvelteKit, Vite +- **Language:** TypeScript +- **Formatting:** Prettier + +## Architecture + +- **`src/`:** SvelteKit frontend application. +- **`scripts/`:** Bun scripts for project maintenance. +- **`static/`:** Static assets and generated `data.json`. +- **`extensions.json`:** Configuration file defining extension sources. + +## Scripts & Automation + +- **`scripts/update.ts`:** The core script. + - Updates extensions from upstream Git repositories. + - Generates `static/data.json` for the frontend. + - Supports `--generate-only` flag to generate `data.json` without fetching updates (used for build). +- **Refactoring (Dec 2025):** + - `scripts/index.ts` was merged into `scripts/update.ts` to reduce redundancy. + - `scripts/clean.ts` was removed as it was unused. + - `package.json` build script updated to use `bun run scripts/update.ts --generate-only`. + +## Standards + +- **Line Endings:** LF (Line Feed) is enforced project-wide via `.gitattributes` and `.prettierrc` to ensure cross-platform consistency (Windows/Linux/CI). + +## Deployment + +- **Targets:** GitHub Pages and Cloudflare Workers. +- **CI/CD:** GitHub Actions workflow (`.github/workflows/update.yml`) handles updates every 4 hours and on dispatch. diff --git a/.serena/project.yml b/.serena/project.yml new file mode 100644 index 0000000..4e61aec --- /dev/null +++ b/.serena/project.yml @@ -0,0 +1,84 @@ +# list of languages for which language servers are started; choose from: +# al bash clojure cpp csharp csharp_omnisharp +# dart elixir elm erlang fortran go +# haskell java julia kotlin lua markdown +# nix perl php python python_jedi r +# rego ruby ruby_solargraph rust scala swift +# terraform typescript typescript_vts yaml zig +# Note: +# - For C, use cpp +# - For JavaScript, use typescript +# Special requirements: +# - csharp: Requires the presence of a .sln file in the project folder. +# When using multiple languages, the first language server that supports a given file will be used for that file. +# The first language is the default language and the respective language server will be used as a fallback. +# Note that when using the JetBrains backend, language servers are not used and this list is correspondingly ignored. +languages: + - typescript + +# the encoding used by text files in the project +# For a list of possible encodings, see https://docs.python.org/3.11/library/codecs.html#standard-encodings +encoding: 'utf-8' + +# whether to use the project's gitignore file to ignore files +# Added on 2025-04-07 +ignore_all_files_in_gitignore: true + +# list of additional paths to ignore +# same syntax as gitignore, so you can use * and ** +# Was previously called `ignored_dirs`, please update your config if you are using that. +# Added (renamed) on 2025-04-07 +ignored_paths: [] + +# whether the project is in read-only mode +# If set to true, all editing tools will be disabled and attempts to use them will result in an error +# Added on 2025-04-18 +read_only: false + +# list of tool names to exclude. We recommend not excluding any tools, see the readme for more details. +# Below is the complete list of tools for convenience. +# To make sure you have the latest list of tools, and to view their descriptions, +# execute `uv run scripts/print_tool_overview.py`. +# +# * `activate_project`: Activates a project by name. +# * `check_onboarding_performed`: Checks whether project onboarding was already performed. +# * `create_text_file`: Creates/overwrites a file in the project directory. +# * `delete_lines`: Deletes a range of lines within a file. +# * `delete_memory`: Deletes a memory from Serena's project-specific memory store. +# * `execute_shell_command`: Executes a shell command. +# * `find_referencing_code_snippets`: Finds code snippets in which the symbol at the given location is referenced. +# * `find_referencing_symbols`: Finds symbols that reference the symbol at the given location (optionally filtered by type). +# * `find_symbol`: Performs a global (or local) search for symbols with/containing a given name/substring (optionally filtered by type). +# * `get_current_config`: Prints the current configuration of the agent, including the active and available projects, tools, contexts, and modes. +# * `get_symbols_overview`: Gets an overview of the top-level symbols defined in a given file. +# * `initial_instructions`: Gets the initial instructions for the current project. +# Should only be used in settings where the system prompt cannot be set, +# e.g. in clients you have no control over, like Claude Desktop. +# * `insert_after_symbol`: Inserts content after the end of the definition of a given symbol. +# * `insert_at_line`: Inserts content at a given line in a file. +# * `insert_before_symbol`: Inserts content before the beginning of the definition of a given symbol. +# * `list_dir`: Lists files and directories in the given directory (optionally with recursion). +# * `list_memories`: Lists memories in Serena's project-specific memory store. +# * `onboarding`: Performs onboarding (identifying the project structure and essential tasks, e.g. for testing or building). +# * `prepare_for_new_conversation`: Provides instructions for preparing for a new conversation (in order to continue with the necessary context). +# * `read_file`: Reads a file within the project directory. +# * `read_memory`: Reads the memory with the given name from Serena's project-specific memory store. +# * `remove_project`: Removes a project from the Serena configuration. +# * `replace_lines`: Replaces a range of lines within a file with new content. +# * `replace_symbol_body`: Replaces the full definition of a symbol. +# * `restart_language_server`: Restarts the language server, may be necessary when edits not through Serena happen. +# * `search_for_pattern`: Performs a search for a pattern in the project. +# * `summarize_changes`: Provides instructions for summarizing the changes made to the codebase. +# * `switch_modes`: Activates modes by providing a list of their names +# * `think_about_collected_information`: Thinking tool for pondering the completeness of collected information. +# * `think_about_task_adherence`: Thinking tool for determining whether the agent is still on track with the current task. +# * `think_about_whether_you_are_done`: Thinking tool for determining whether the task is truly completed. +# * `write_memory`: Writes a named memory (for future reference) to Serena's project-specific memory store. +excluded_tools: [] + +# initial prompt for the project. It will always be given to the LLM upon activating the project +# (contrary to the memories, which are loaded on demand). +initial_prompt: '' + +project_name: 'x' +included_optional_tools: [] diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..005c5b0 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,184 @@ +# Agent Instructions for Mihon/Aniyomi Extensions Aggregator + +This repository is a SvelteKit project managed with **Bun**. It aggregates extensions for Mihon and Aniyomi, serving them as a static site with an API-like structure in `static/`. + +## 1. Environment & Toolchain + +- **Runtime**: `bun` (Do not use `npm` or `yarn` or `pnpm`). +- **Framework**: SvelteKit (Svelte 5). +- **Language**: TypeScript (Strict mode). +- **Search**: Meilisearch (client-side integration). + +## 2. Development Commands + +### Build & Run + +- **Install dependencies**: + ```bash + bun install + ``` +- **Start dev server**: + ```bash + bun run dev + ``` +- **Build production output**: + ```bash + bun run build + ``` + _Note: This runs `bun run update --generate-only` first to regenerate `static/data.json` before Vite builds._ + +### Linting & Verification + +- **Type Check**: + ```bash + bun run check + ``` + _Always run this after making TypeScript or Svelte changes. There are no unit tests, so strict type checking is your primary safety net._ +- **Format Code**: + ```bash + bun run format + ``` +- **Verify Formatting**: + ```bash + bun run lint + ``` + +### Data Management + +- **Update Extensions**: + ```bash + bun run update + ``` + _Fetches upstream data. Use `--generate-only` to just rebuild `data.json` locally._ + +### Testing + +- **Run all tests**: + ```bash + bun test + ``` +- **Run a single test file**: + ```bash + bun test tests/debounce.test.ts + ``` +- **Run tests with watch mode**: + ```bash + bun test --watch + ``` + +## 3. Testing Strategy + +**Test Framework**: Uses `bun:test` built-in test runner with TypeScript support. + +**Test Coverage**: + +- Utility functions in `src/lib/search/` (debouncing, source name formatting) +- Logger utilities in `scripts/cache/` (transfer stats formatting) +- Cache manifest operations in `scripts/cache/` (finding caches by key/prefix) +- File operations in `scripts/cache/` (checksums, directory management) +- Cache lock utilities (instance ID generation, staleness detection) +- Cache metadata key generation +- Cache utility functions (key generation, byte formatting) + +**Agent Protocol for Verification**: + +1. **Run Tests**: Execute `bun test` to verify existing functionality. +2. **Write Tests**: When adding utility functions, add corresponding test files in `tests/`. +3. **Type Check**: Always run `bun run check` after making changes. +4. **Build Verification**: Run `bun run build` to ensure static adapter and prerendering complete successfully. + +## 4. Code Style & Conventions + +### Formatting (Enforced by Prettier) + +- **Indentation**: 4 spaces. +- **Quotes**: Single quotes (`'`). +- **Trailing Commas**: `none`. +- **Line Width**: 100 characters. +- **Line Endings**: LF (`\n`). + +### TypeScript + +- **Strict Mode**: Enabled. No `any` types unless absolutely necessary. +- **Imports**: Use standard ESM imports. + - SvelteKit aliases: `$lib/` is mapped to `src/lib/`. +- **Interfaces**: Define specific interfaces for data structures (see `src/lib/types.ts` or `src/lib/search/types.ts`). + +### Svelte Components (Svelte 5) + +- Use ` + +
+
+ + {repo.name} + +
+ {#if repo.commit} + Commit:{' '} + + {repo.commit.substring(0, 7)} + + {:else} + Commit: N/A + {/if} +
+
+ +
diff --git a/src/lib/components/ExtensionCategory.svelte b/src/lib/components/ExtensionCategory.svelte new file mode 100644 index 0000000..0acf92f --- /dev/null +++ b/src/lib/components/ExtensionCategory.svelte @@ -0,0 +1,24 @@ + + +
+

{title} Extensions

+
+ {#each repos as repo} + + {/each} +
+
diff --git a/src/lib/components/ExtensionRow.svelte b/src/lib/components/ExtensionRow.svelte new file mode 100644 index 0000000..d2a4102 --- /dev/null +++ b/src/lib/components/ExtensionRow.svelte @@ -0,0 +1,47 @@ + + + + + {extension.name} + + +
+ {extension.name} + {#if extension.nsfw === 1} + NSFW + {/if} +
+
{extension.pkg}
+ {#if extension.sourceName} +
Source: {extension.sourceName}
+ {/if} + + + v{extension.version} + {extension.lang} + + + Download + + diff --git a/src/lib/components/Footer.svelte b/src/lib/components/Footer.svelte new file mode 100644 index 0000000..3a228ca --- /dev/null +++ b/src/lib/components/Footer.svelte @@ -0,0 +1,16 @@ + + + diff --git a/src/lib/components/MirrorSelector.svelte b/src/lib/components/MirrorSelector.svelte new file mode 100644 index 0000000..7db4885 --- /dev/null +++ b/src/lib/components/MirrorSelector.svelte @@ -0,0 +1,26 @@ + + +
+ + +
diff --git a/src/lib/search/debounce.ts b/src/lib/search/debounce.ts new file mode 100644 index 0000000..4b685e7 --- /dev/null +++ b/src/lib/search/debounce.ts @@ -0,0 +1,30 @@ +/** + * Creates a debounced version of a function that delays execution until after + * the specified wait time has elapsed since the last invocation. + */ +export function debounce any>( + func: T, + wait: number +): (...args: Parameters) => void { + let timeoutId: ReturnType | null = null; + + return function (...args: Parameters) { + if (timeoutId !== null) { + clearTimeout(timeoutId); + } + + timeoutId = setTimeout(() => { + func(...args); + timeoutId = null; + }, wait); + }; +} + +/** + * Clears a timeout if it exists + */ +export function clearDebounce(timeoutId: ReturnType | null) { + if (timeoutId !== null) { + clearTimeout(timeoutId); + } +} diff --git a/src/lib/search/meilisearch.ts b/src/lib/search/meilisearch.ts new file mode 100644 index 0000000..9b4c0d1 --- /dev/null +++ b/src/lib/search/meilisearch.ts @@ -0,0 +1,125 @@ +export interface MeilisearchConfig { + host: string; + apiKey?: string; +} + +export interface SearchFilters { + query?: string; + source?: string; + category?: string; + lang?: string; + nsfw?: boolean; + page?: number; + limit?: number; +} + +interface MeilisearchClient { + host: string; + apiKey: string; +} + +let client: MeilisearchClient | null = null; + +export function initMeilisearch(config: MeilisearchConfig) { + if (!config.host) { + console.warn('Meilisearch not configured'); + return null; + } + client = { host: config.host, apiKey: config.apiKey ?? '' }; + return client; +} + +export function isMeilisearchEnabled(): boolean { + return client !== null; +} + +/** + * Transforms a Meilisearch hit to EnrichedExtension format + */ +export function transformMeilisearchHit(hit: any) { + return { + name: hit.name, + pkg: hit.pkg, + apk: hit.apk, + lang: hit.lang, + code: hit.code, + version: hit.version, + nsfw: hit.nsfw, + repoUrl: hit.repoUrl, + sourceName: hit.sourceName, + formattedSourceName: hit.formattedSourceName + }; +} + +export async function searchExtensions(filters: SearchFilters) { + if (!client) { + throw new Error('Meilisearch client not initialized'); + } + + const filterConditions: string[] = []; + + if (filters.source && filters.source !== 'all') + filterConditions.push(`formattedSourceName = "${filters.source}"`); + if (filters.category && filters.category !== 'all') + filterConditions.push(`category = "${filters.category}"`); + if (filters.lang && filters.lang !== 'all') filterConditions.push(`lang = "${filters.lang}"`); + if (filters.nsfw === false) filterConditions.push('nsfw = 0'); + + const page = filters.page || 1; + const limit = filters.limit || 50; + const offset = (page - 1) * limit; + + const body: Record = { + q: filters.query || '', + limit, + offset + }; + + if (filterConditions.length > 0) body.filter = filterConditions; + + const response = await fetch(`${client.host}/indexes/extensions/search`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${client.apiKey}` + }, + body: JSON.stringify(body) + }); + + if (!response.ok) { + throw new Error(`Meilisearch error: ${response.status} ${response.statusText}`); + } + + return await response.json(); +} + +export async function getFilterOptions() { + if (!client) { + throw new Error('Meilisearch client not initialized'); + } + + const response = await fetch(`${client.host}/indexes/extensions/search`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${client.apiKey}` + }, + body: JSON.stringify({ + q: '', + limit: 0, + facets: ['formattedSourceName', 'category', 'lang'] + }) + }); + + if (!response.ok) { + throw new Error(`Meilisearch error: ${response.status} ${response.statusText}`); + } + + const result = await response.json(); + + return { + sources: Object.keys(result.facetDistribution?.formattedSourceName || {}), + categories: Object.keys(result.facetDistribution?.category || {}), + languages: Object.keys(result.facetDistribution?.lang || {}) + }; +} diff --git a/src/lib/search/types.ts b/src/lib/search/types.ts new file mode 100644 index 0000000..cc30ae5 --- /dev/null +++ b/src/lib/search/types.ts @@ -0,0 +1,27 @@ +import type { Extension } from '$lib/types'; + +/** + * Extension enriched with repository and source information + */ +export interface EnrichedExtension extends Extension { + repoUrl: string; + sourceName: string; + formattedSourceName: string; + category: string; +} + +/** + * Repository information from data.json + */ +export interface RepoInfo { + name: string; + path: string; + commit: string; +} + +/** + * Repository data grouped by category + */ +export interface RepoData { + [category: string]: RepoInfo[]; +} diff --git a/src/lib/search/utils.ts b/src/lib/search/utils.ts new file mode 100644 index 0000000..d6b6aa8 --- /dev/null +++ b/src/lib/search/utils.ts @@ -0,0 +1,17 @@ +/** + * Formats a source name to lowercase with dots instead of spaces + */ +export function formatSourceName(sourceName: string): string { + return sourceName.toLowerCase().replace(/\s+/g, '.'); +} + +/** + * Finds a source by its formatted name from available sources + */ +export function findSourceByFormattedName( + formattedName: string, + availableSources: string[] +): string { + if (formattedName === 'all') return 'all'; + return availableSources.find((source) => formatSourceName(source) === formattedName) ?? 'all'; +} diff --git a/src/lib/stores/mirror.ts b/src/lib/stores/mirror.ts new file mode 100644 index 0000000..d80a105 --- /dev/null +++ b/src/lib/stores/mirror.ts @@ -0,0 +1,3 @@ +import { writable } from 'svelte/store'; + +export const selectedDomain = writable(''); diff --git a/src/lib/types.ts b/src/lib/types.ts new file mode 100644 index 0000000..79783b3 --- /dev/null +++ b/src/lib/types.ts @@ -0,0 +1,26 @@ +export interface Extension { + pkg: string; + name: string; + version: string; + lang: string; + apk: string; + nsfw: number; + sourceName?: string; +} + +export interface ExtensionRepo { + source: string; + name: string; + path: string; + commit: string; +} + +export interface AppData { + extensions: { + [category: string]: ExtensionRepo[]; + }; + domains: string[]; + source: string; + commitLink: string; + latestCommitHash: string; +} diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte new file mode 100644 index 0000000..cf931d8 --- /dev/null +++ b/src/routes/+layout.svelte @@ -0,0 +1,28 @@ + + +{@render children()} + +