matrix-search

A small nodejs script which indexes a whole matrix account into meilisearch and has a pdf rendering UI
git clone git://archive.git.mtrnord.blog/MTRNord/matrix-search.git
Log | Files | Refs | README | LICENSE

commit 684a2f1fa09426273ee3a63d0811240bd740e5b3
parent a7f087ac11ef94b693293dcfa34c7ec8e717ba26
Author: MTRNord <mtrnord1@gmail.com>
Date:   Thu, 20 Jun 2024 11:09:02 +0200

Beautify the output and renderer

Diffstat:
Mpackage.json | 2++
Mpnpm-lock.yaml | 34++++++++++++++++++++++++++++++++++
Msrc/renderer.tsx | 70+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------
Mtsconfig.json | 6+++---
4 files changed, 100 insertions(+), 12 deletions(-)

diff --git a/package.json b/package.json @@ -15,6 +15,7 @@ "@eslint/js": "^9.4.0", "@types/eslint__js": "^8.42.3", "@types/node": "^20.14.2", + "@types/prompts": "^2.4.9", "@types/react": "^18.3.3", "eslint": "^9.4.0", "eslint-plugin-tsdoc": "^0.3.0", @@ -27,6 +28,7 @@ "@sinclair/typebox": "^0.32.31", "got": "^14.4.1", "meilisearch": "^0.40.0", + "prompts": "^2.4.2", "react": "^18.3.1", "yaml": "^2.4.5" } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml @@ -23,6 +23,9 @@ importers: meilisearch: specifier: ^0.40.0 version: 0.40.0 + prompts: + specifier: ^2.4.2 + version: 2.4.2 react: specifier: ^18.3.1 version: 18.3.1 @@ -39,6 +42,9 @@ importers: '@types/node': specifier: ^20.14.2 version: 20.14.2 + '@types/prompts': + specifier: ^2.4.9 + version: 2.4.9 '@types/react': specifier: ^18.3.3 version: 18.3.3 @@ -193,6 +199,9 @@ packages: '@types/node@20.14.2': resolution: {integrity: sha512-xyu6WAMVwv6AKFLB+e/7ySZVr/0zLCzOa7rSpq6jNwpqOrUbcACDWC+53d4n2QHOnDou0fbIsg8wZu/sxrnI4Q==} + '@types/prompts@2.4.9': + resolution: {integrity: sha512-qTxFi6Buiu8+50/+3DGIWLHM6QuWsEKugJnnP6iv2Mc4ncxE4A/OJkjuVOA+5X0X1S/nq5VJRa8Lu+nwcvbrKA==} + '@types/prop-types@15.7.12': resolution: {integrity: sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==} @@ -623,6 +632,10 @@ packages: keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + kleur@3.0.3: + resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} + engines: {node: '>=6'} + levn@0.4.1: resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} engines: {node: '>= 0.8.0'} @@ -757,6 +770,10 @@ packages: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} + prompts@2.4.2: + resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} + engines: {node: '>= 6'} + prop-types@15.8.1: resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} @@ -835,6 +852,9 @@ packages: simple-swizzle@0.2.2: resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==} + sisteransi@1.0.5: + resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} + slash@3.0.0: resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} engines: {node: '>=8'} @@ -1168,6 +1188,11 @@ snapshots: dependencies: undici-types: 5.26.5 + '@types/prompts@2.4.9': + dependencies: + '@types/node': 20.14.2 + kleur: 3.0.3 + '@types/prop-types@15.7.12': {} '@types/react@18.3.3': @@ -1650,6 +1675,8 @@ snapshots: dependencies: json-buffer: 3.0.1 + kleur@3.0.3: {} + levn@0.4.1: dependencies: prelude-ls: 1.2.1 @@ -1755,6 +1782,11 @@ snapshots: prelude-ls@1.2.1: {} + prompts@2.4.2: + dependencies: + kleur: 3.0.3 + sisteransi: 1.0.5 + prop-types@15.8.1: dependencies: loose-envify: 1.4.0 @@ -1822,6 +1854,8 @@ snapshots: dependencies: is-arrayish: 0.3.2 + sisteransi@1.0.5: {} + slash@3.0.0: {} string_decoder@1.3.0: diff --git a/src/renderer.tsx b/src/renderer.tsx @@ -1,7 +1,8 @@ import React, { PropsWithChildren } from 'react'; -import ReactPDF, { Font, Page, Text, View, Document, StyleSheet } from '@react-pdf/renderer'; +import ReactPDF, { Font, Page, Text, View, Document, StyleSheet, Link } from '@react-pdf/renderer'; import Indexer from './indexer.js'; import { Hits } from 'meilisearch'; +import prompts, { PromptObject } from 'prompts'; Font.registerEmojiSource({ format: 'png', @@ -25,7 +26,7 @@ const indexer = new Indexer(); const borderRadius = 5; // A pdf renderer -const MessageDocument = ({ query, queryResults }: PropsWithChildren<{ query: string, queryResults: { hits: Hits<Record<string, any>>; estimatedTotalHits: number; } }>) => { +const MessageDocument = ({ query, room_id, sender, queryResults }: PropsWithChildren<{ query: string, room_id?: string, sender?: string, queryResults: { hits: Hits<Record<string, any>>; estimatedTotalHits: number; } }>) => { return ( <Document title={`Search results for: "${query}"`} @@ -33,17 +34,36 @@ const MessageDocument = ({ query, queryResults }: PropsWithChildren<{ query: str keywords='matrix' > <Page size="A4" style={styles.page}> + {/* Title page which is centered */} + <View style={{ flexDirection: "row", alignItems: "center", justifyContent: "center", height: "100%" }}> + <View style={{ flexDirection: "column", justifyContent: "center", alignItems: "center" }}> + <Text style={{ fontSize: 24, textAlign: 'center' }}>Matrix Search Engine</Text> + <Text style={{ fontSize: 12, textAlign: 'center', marginTop: 5 }}>Search results for: "{query}"</Text> + {/* Optionally display the other values of room_id and sender*/} + {room_id !== undefined && <Text style={{ fontSize: 12, textAlign: 'center' }}>Room ID: {room_id}</Text>} + {sender !== undefined && <Text style={{ fontSize: 12, textAlign: 'center' }}>Sender: {sender}</Text>} + {/* Number of results */} + <Text style={{ fontSize: 10, textAlign: 'center', marginTop: 10 }}>Number of Results: {queryResults.estimatedTotalHits}</Text> + {/* Date of creation */} + <Text style={{ fontSize: 10, textAlign: 'center', marginTop: 10 }}>Created at: {new Date().toLocaleString()}</Text> + </View> + </View> + </Page> + <Page size="A4" style={styles.page}> <View> <View style={{ fontSize: 10 }}> - <Text>Search results for: {query}</Text> - <Text>Results: {queryResults.estimatedTotalHits}</Text> + <Text>Search results for: "{query}"</Text> + <Text>Number of Results: {queryResults.estimatedTotalHits}</Text> </View> <View> {queryResults.hits.map((result, index) => ( - <View key={index} style={{ flexDirection: 'column', margin: 10, padding: 5, backgroundColor: '#e6e6e6', gap: 25, borderTopLeftRadius: borderRadius, borderTopRightRadius: borderRadius, borderBottomLeftRadius: borderRadius, borderBottomRightRadius: borderRadius, borderStyle: 'solid', borderWidth: 1, borderColor: '#00000' }}> + <View wrap={false} key={index} style={{ flexDirection: 'column', margin: 10, padding: 5, backgroundColor: '#e6e6e6', gap: 25, borderTopLeftRadius: borderRadius, borderTopRightRadius: borderRadius, borderBottomLeftRadius: borderRadius, borderBottomRightRadius: borderRadius, borderStyle: 'solid', borderWidth: 1, borderColor: '#00000' }}> <View style={{ flexDirection: 'row', justifyContent: 'space-between', fontSize: 10 }} > <Text>{result.room_name ?? result.room_id}</Text> - <Text>{result.sender}</Text> + <View style={{ flexDirection: 'row' }}> + <Text>{result.sender}</Text> + <Link style={{ marginLeft: 5 }} src={`https://matrix.to/#/${result.room_id}/${result.id}`}>Link</Link> + </View> </View> <Text>{result.content.body}</Text> </View> @@ -64,10 +84,42 @@ const MessageDocument = ({ query, queryResults }: PropsWithChildren<{ query: str } export async function renderPDFToDisk(query: string, room_id?: string, sender?: string) { - const queryResults = await indexer.search(query, room_id, sender); + let cleanedRoomId = room_id?.trim(); + let cleanedSender = sender?.trim(); + if (cleanedRoomId === "") { + cleanedRoomId = undefined; + } + if (cleanedSender === "") { + cleanedSender = undefined; + } + const queryResults = await indexer.search(query, cleanedRoomId, cleanedSender); //console.log(`Search results:`, queryResults); - await ReactPDF.renderToFile(<MessageDocument query={query} queryResults={queryResults} />, `output.pdf`); + await ReactPDF.renderToFile(<MessageDocument query={query} room_id={cleanedRoomId} sender={cleanedSender} queryResults={queryResults} />, `output.pdf`); } -await renderPDFToDisk("woah", undefined, "@thibaultmartin:matrix.org"); + +const questions: PromptObject<string>[] = [ + { + type: 'text', + name: 'query', + initial: "", + message: 'Enter your search query:', + }, + { + type: 'text', + name: 'room_id', + initial: undefined, + message: 'Enter the Room ID:', + }, + { + type: 'text', + name: 'sender', + initial: undefined, + message: 'Enter the sender:', + } +] + +const response = await prompts(questions); + +await renderPDFToDisk(response.query as string, response.room_id as string | undefined, response.sender as string | undefined); console.log("PDF rendered to disk"); \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json @@ -85,11 +85,11 @@ "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ - // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ - // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ + "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ + "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ - // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ + "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */