Merge remote-tracking branch 'origin/main' into push

This commit is contained in:
Alex Gleason
2024-10-11 18:03:02 -05:00
44 changed files with 2288 additions and 1804 deletions

View File

@@ -1,4 +1,4 @@
import { Context } from '@hono/hono';
import { type Context } from '@hono/hono';
import { HTTPException } from '@hono/hono/http-exception';
import { NostrEvent, NostrFilter } from '@nostrify/nostrify';
import Debug from '@soapbox/stickynotes/debug';

View File

@@ -1,18 +0,0 @@
import { assert } from '@std/assert';
import ExpiringCache from './expiring-cache.ts';
Deno.test('ExpiringCache', async () => {
const cache = new ExpiringCache(await caches.open('test'));
await cache.putExpiring('http://mostr.local/1', new Response('hello world'), 300);
await cache.putExpiring('http://mostr.local/2', new Response('hello world'), -1);
// const resp1 = await cache.match('http://mostr.local/1');
const resp2 = await cache.match('http://mostr.local/2');
// assert(resp1!.headers.get('Expires'));
assert(!resp2);
// await resp1!.text();
});

View File

@@ -1,68 +0,0 @@
class ExpiringCache implements Cache {
#cache: Cache;
constructor(cache: Cache) {
this.#cache = cache;
}
add(request: RequestInfo | URL): Promise<void> {
return this.#cache.add(request);
}
addAll(requests: RequestInfo[]): Promise<void> {
return this.#cache.addAll(requests);
}
keys(request?: RequestInfo | URL | undefined, options?: CacheQueryOptions | undefined): Promise<readonly Request[]> {
return this.#cache.keys(request, options);
}
matchAll(
request?: RequestInfo | URL | undefined,
options?: CacheQueryOptions | undefined,
): Promise<readonly Response[]> {
return this.#cache.matchAll(request, options);
}
put(request: RequestInfo | URL, response: Response): Promise<void> {
return this.#cache.put(request, response);
}
putExpiring(request: RequestInfo | URL, response: Response, expiresIn: number): Promise<void> {
const expires = Date.now() + expiresIn;
const clone = new Response(response.body, {
status: response.status,
headers: {
expires: new Date(expires).toUTCString(),
...Object.fromEntries(response.headers.entries()),
},
});
return this.#cache.put(request, clone);
}
async match(request: RequestInfo | URL, options?: CacheQueryOptions | undefined): Promise<Response | undefined> {
const response = await this.#cache.match(request, options);
const expires = response?.headers.get('Expires');
if (response && expires) {
if (new Date(expires).getTime() > Date.now()) {
return response;
} else {
await Promise.all([
this.delete(request),
response.text(), // Prevent memory leaks
]);
}
} else if (response) {
return response;
}
}
delete(request: RequestInfo | URL, options?: CacheQueryOptions | undefined): Promise<boolean> {
return this.#cache.delete(request, options);
}
}
export default ExpiringCache;

View File

@@ -0,0 +1,28 @@
import { detectLanguage } from '@/utils/language.ts';
import { assertEquals } from '@std/assert';
Deno.test('Detect English language', () => {
assertEquals(detectLanguage(``, 0.90), undefined);
assertEquals(detectLanguage(`Good morning my fellow friends`, 0.90), 'en');
assertEquals(
detectLanguage(
`Would you listen to Michael Jackson's songs?\n\nnostr:nevent1qvzqqqqqqypzqprpljlvcnpnw3pejvkkhrc3y6wvmd7vjuad0fg2ud3dky66gaxaqyvhwumn8ghj7cm0vfexzen4d4sjucm0d5hhyetvv9usqg8htx8xcjq7ffrzxu7nrhlr8vljcv6gpmet0auy87mpj6djxk4myqha02kp`,
0.90,
),
'en',
);
assertEquals(
detectLanguage(
`https://youtu.be/FxppefYTA2I?si=grgEpbEhFu_-3V_uhttps://youtu.be/FxppefYTA2I?si=grgEpbEhFu_-3V_uhttps://youtu.be/FxppefYTA2I?si=grgEpbEhFu_-3V_uhttps://youtu.be/FxppefYTA2I?si=grgEpbEhFu_-3V_uWould you listen to Michael Jackson's songs?\n\nnostr:nevent1qvzqqqqqqypzqprpljlvcnpnw3pejvkkhrc3y6wvmd7vjuad0fg2ud3dky66gaxaqyvhwumn8ghj7cm0vfexzen4d4sjucm0d5hhyetvv9usqg8htx8xcjq7ffrzxu7nrhlr8vljcv6gpmet0auy87mpj6djxk4myqha02kp`,
0.90,
),
'en',
);
assertEquals(
detectLanguage(
`https://youtu.be/FxppefYTA2I?si=grgEpbEhFu_-3V_u 😂💯♡⌨︎😂💯♡⌨︎😂💯♡⌨︎😂💯♡⌨︎😂💯♡⌨︎😂💯♡⌨︎😂💯♡⌨︎😂💯♡⌨︎😂💯♡⌨︎😂💯♡⌨︎😂💯♡⌨︎😂💯♡⌨︎😂💯♡⌨︎😂💯♡⌨︎😂💯♡⌨︎😂💯♡⌨︎😂💯♡⌨︎😂💯♡⌨︎😂💯♡⌨︎😂💯♡⌨︎😂💯♡⌨︎😂💯♡⌨︎😂💯♡⌨︎😂💯♡⌨︎😂💯♡⌨︎😂💯♡⌨︎😂💯♡⌨︎😂💯♡⌨︎😂💯♡⌨︎ https://youtu.be/FxppefYTA2I?si=grgEpbEhFu_-3V_uhttps://youtu.be/FxppefYTA2I?si=grgEpbEhFu_-3V_uhttps://youtu.be/FxppefYTA2I?si=grgEpbEhFu_-3V_u Would you listen to Michael Jackson's songs?\n\nnostr:nevent1qvzqqqqqqypzqprpljlvcnpnw3pejvkkhrc3y6wvmd7vjuad0fg2ud3dky66gaxaqyvhwumn8ghj7cm0vfexzen4d4sjucm0d5hhyetvv9usqg8htx8xcjq7ffrzxu7nrhlr8vljcv6gpmet0auy87mpj6djxk4myqha02kp`,
0.90,
),
'en',
);
});

34
src/utils/language.ts Normal file
View File

@@ -0,0 +1,34 @@
import ISO6391, { type LanguageCode } from 'iso-639-1';
import lande from 'lande';
import linkify from 'linkifyjs';
linkify.registerCustomProtocol('nostr', true);
/** Returns the detected language if the confidence is greater or equal than 'minConfidence'
* 'minConfidence' must be a number between 0 and 1, such as 0.95
*/
export function detectLanguage(text: string, minConfidence: number): LanguageCode | undefined {
// It's better to remove the emojis first
const sanitizedText = linkify.tokenize(
text
.replaceAll(/\p{Extended_Pictographic}/gu, '')
.replaceAll(/[\s\uFEFF\u00A0\u200B-\u200D\u{0FE0E}]+/gu, ' '),
).reduce((acc, { t, v }) => t === 'text' ? acc + v : acc, '').trim();
if (sanitizedText.length < 10) { // heuristics
return;
}
const [topResult] = lande(
sanitizedText,
);
if (topResult) {
const [iso6393, confidence] = topResult;
const locale = new Intl.Locale(iso6393);
if (confidence >= minConfidence && ISO6391.validate(locale.language)) {
return locale.language as LanguageCode;
}
}
return;
}