summaryrefslogtreecommitdiffstats
path: root/scripts/meilisearch.ts
diff options
context:
space:
mode:
authorGravatar amrkmn 2025-12-26 22:39:23 +0800
committerGravatar amrkmn 2025-12-26 22:39:23 +0800
commit32ca410f4edbff578d71781d943c41573912f476 (patch)
tree49f7e1e5602657d23945082fe273fc4802959a40 /scripts/meilisearch.ts
Initial commitmain
Diffstat (limited to 'scripts/meilisearch.ts')
-rw-r--r--scripts/meilisearch.ts158
1 files changed, 158 insertions, 0 deletions
diff --git a/scripts/meilisearch.ts b/scripts/meilisearch.ts
new file mode 100644
index 0000000..a900ceb
--- /dev/null
+++ b/scripts/meilisearch.ts
@@ -0,0 +1,158 @@
+import { MeiliSearch } from 'meilisearch';
+import { readdir } from 'fs/promises';
+import { join } from 'path';
+
+interface Extension {
+ name: string;
+ pkg: string;
+ apk: string;
+ lang: string;
+ code: number;
+ version: string;
+ nsfw: number;
+}
+
+interface EnrichedExtension extends Extension {
+ id: string;
+ category: string;
+ sourceName: string;
+ formattedSourceName: string;
+ repoUrl: string;
+}
+
+interface SourceMapping {
+ name: string;
+ repoUrl: string;
+ category: string;
+}
+
+async function buildSourceMapping(path: string): Promise<Map<string, SourceMapping>> {
+ const mapping = new Map<string, SourceMapping>();
+ const data = await Bun.file(path).json();
+
+ for (const category in data.extensions) {
+ for (const repo of data.extensions[category]) {
+ const normalizedPath = repo.path.replace(/^\//, '');
+ mapping.set(normalizedPath, {
+ name: repo.name,
+ repoUrl: repo.path.substring(0, repo.path.lastIndexOf('/')),
+ category
+ });
+ }
+ }
+ return mapping;
+}
+
+async function findExtensionFiles(dir: string): Promise<string[]> {
+ let results: string[] = [];
+ try {
+ const entries = await readdir(dir, { withFileTypes: true });
+ for (const file of entries) {
+ const path = join(dir, file.name);
+ if (file.isDirectory()) results.push(...(await findExtensionFiles(path)));
+ else if (file.name === 'index.min.json') results.push(path);
+ }
+ } catch (e) {
+ console.error(`Error reading ${dir}:`, e);
+ }
+ return results;
+}
+
+export async function updateMeilisearch() {
+ const env = {
+ host: process.env.MEILISEARCH_HOST,
+ apiKey: process.env.MEILISEARCH_MASTER_KEY
+ };
+
+ if (!env.host || !env.apiKey) {
+ console.log('Skipping Meilisearch update (not configured)');
+ return;
+ }
+
+ console.log('Updating Meilisearch index...');
+ const STATIC_DIR = join(process.cwd(), 'static');
+
+ try {
+ const client = new MeiliSearch({ host: env.host, apiKey: env.apiKey });
+ await client.health();
+ const index = client.index('extensions');
+
+ await index.updateSettings({
+ searchableAttributes: ['name', 'pkg', 'lang', 'sourceName'],
+ filterableAttributes: [
+ 'sourceName',
+ 'formattedSourceName',
+ 'category',
+ 'lang',
+ 'nsfw',
+ 'pkg'
+ ],
+ sortableAttributes: ['name', 'lang', 'version'],
+ rankingRules: ['words', 'typo', 'proximity', 'attribute', 'sort', 'exactness'],
+ pagination: { maxTotalHits: 10000 }
+ });
+
+ const sourceMapping = await buildSourceMapping(join(STATIC_DIR, 'data.json'));
+ const files = await findExtensionFiles(STATIC_DIR);
+
+ if (!files.length) {
+ console.warn('No extension files found for Meilisearch');
+ return;
+ }
+
+ const allExtensions: EnrichedExtension[] = [];
+
+ for (const file of files) {
+ try {
+ const extensions: Extension[] = await Bun.file(file).json();
+ const relativePath = file
+ .replace(STATIC_DIR, '')
+ .replace(/\\/g, '/')
+ .replace(/^\//, '');
+ const pathParts = relativePath.split('/').filter(Boolean);
+ const sourceInfo = sourceMapping.get(relativePath);
+
+ const sourceName = sourceInfo?.name || pathParts[0] || 'Unknown';
+ const repoUrl = sourceInfo?.repoUrl || '/' + pathParts.slice(0, -1).join('/');
+ const category =
+ sourceInfo?.category ||
+ (pathParts[0]?.toLowerCase().includes('anime') ? 'aniyomi' : 'mihon');
+ const formattedSourceName = sourceName.toLowerCase().replace(/\s+/g, '.');
+ const idSafeSourceName = formattedSourceName.replace(/\./g, '_');
+
+ allExtensions.push(
+ ...extensions.map((ext) => ({
+ ...ext,
+ id: `${idSafeSourceName}-${ext.pkg.replace(/\./g, '_')}`,
+ category,
+ sourceName,
+ formattedSourceName,
+ repoUrl,
+ nsfw: typeof ext.nsfw === 'number' ? ext.nsfw : ext.nsfw ? 1 : 0
+ }))
+ );
+ } catch (err) {
+ console.error(`Error processing ${file}:`, err);
+ }
+ }
+
+ const task = await index.updateDocuments(allExtensions, { primaryKey: 'id' });
+ const result = await client.tasks.waitForTask(task.taskUid, {
+ timeout: 300000,
+ interval: 1000
+ });
+
+ if (result.status === 'succeeded') {
+ const stats = await index.getStats();
+ console.log(`Meilisearch updated: ${stats.numberOfDocuments} documents indexed`);
+ } else {
+ console.error('Meilisearch indexing failed:', result.error);
+ }
+ } catch (error) {
+ console.error('Meilisearch update error:', error);
+ }
+}
+
+if (import.meta.main) {
+ await updateMeilisearch();
+}