218 lines
5.7 KiB
TypeScript
218 lines
5.7 KiB
TypeScript
import type * as net from "node:net";
|
|
import type { Event } from "@nostr/tools";
|
|
import { exists } from "jsr:@std/fs/exists";
|
|
import type { ImapCommandParameters } from "./imapParsing.ts";
|
|
import type { ImapSession } from "./imapServer.ts";
|
|
|
|
export async function isMessageRead(
|
|
session: ImapSession,
|
|
eventId: string,
|
|
): Promise<boolean> {
|
|
if (!session.npub) {
|
|
return false;
|
|
}
|
|
const readMessages = JSON.parse(
|
|
await getFileOrDefault("readMessages.json", "{}"),
|
|
);
|
|
const readMessagesForUser = readMessages[session.npub];
|
|
if (!readMessagesForUser) {
|
|
return false;
|
|
}
|
|
return readMessagesForUser.includes(eventId);
|
|
}
|
|
|
|
export async function markMessageAsRead(
|
|
session: ImapSession,
|
|
eventId: string,
|
|
positive = true,
|
|
) {
|
|
if (!session.npub) {
|
|
return;
|
|
}
|
|
const readMessages = JSON.parse(
|
|
await getFileOrDefault("readMessages.json", "{}"),
|
|
);
|
|
readMessages[session.npub] = readMessages[session.npub] || [];
|
|
if (positive) {
|
|
readMessages[session.npub].push(eventId);
|
|
} else {
|
|
readMessages[session.npub] = readMessages[session.npub].filter(
|
|
(id: string) => id !== eventId,
|
|
);
|
|
}
|
|
await Deno.writeTextFile(
|
|
"data/readMessages.json",
|
|
JSON.stringify(readMessages, null, 2),
|
|
);
|
|
}
|
|
|
|
export async function isMessageFlagged(
|
|
session: ImapSession,
|
|
eventId: string,
|
|
): Promise<boolean> {
|
|
if (!session.npub) {
|
|
return false;
|
|
}
|
|
const flaggedMessages = JSON.parse(
|
|
await getFileOrDefault("flaggedMessages.json", "{}"),
|
|
);
|
|
const flaggedMessagesForUser = flaggedMessages[session.npub];
|
|
if (!flaggedMessagesForUser) {
|
|
return false;
|
|
}
|
|
return flaggedMessagesForUser.includes(eventId);
|
|
}
|
|
|
|
export async function markMessageAsFlagged(
|
|
session: ImapSession,
|
|
eventId: string,
|
|
positive = true,
|
|
) {
|
|
if (!session.npub) {
|
|
return;
|
|
}
|
|
const flaggedMessages = JSON.parse(
|
|
await getFileOrDefault("flaggedMessages.json", "{}"),
|
|
);
|
|
flaggedMessages[session.npub] = flaggedMessages[session.npub] || [];
|
|
if (positive) {
|
|
flaggedMessages[session.npub].push(eventId);
|
|
} else {
|
|
flaggedMessages[session.npub] = flaggedMessages[session.npub].filter(
|
|
(id: string) => id !== eventId,
|
|
);
|
|
}
|
|
await Deno.writeTextFile(
|
|
"data/flaggedMessages.json",
|
|
JSON.stringify(flaggedMessages, null, 2),
|
|
);
|
|
}
|
|
|
|
export async function getFlags(
|
|
session: ImapSession,
|
|
eventId: string,
|
|
): Promise<string[]> {
|
|
const isRead = await isMessageRead(session, eventId);
|
|
const isFlagged = await isMessageFlagged(session, eventId);
|
|
return [isRead ? "\\Seen" : "", isFlagged ? "\\Flagged" : ""].filter(Boolean);
|
|
}
|
|
|
|
export function parseUid(uid: ImapCommandParameters): [number, number] {
|
|
if (typeof uid !== "string") {
|
|
throw new Error("Invalid UID");
|
|
}
|
|
const [start, end] = uid.split(":");
|
|
if (end === undefined) {
|
|
return [Number.parseInt(start), Number.parseInt(start)];
|
|
}
|
|
if (start === "*") {
|
|
return [Number.NEGATIVE_INFINITY, Number.POSITIVE_INFINITY];
|
|
}
|
|
if (end === "*") {
|
|
return [Number.parseInt(start), Number.POSITIVE_INFINITY];
|
|
}
|
|
return [Number.parseInt(start), Number.parseInt(end)];
|
|
}
|
|
|
|
export function getByteLength(text: string): number {
|
|
return new Blob([text]).size;
|
|
}
|
|
|
|
export function parseBasicNostrMarkdown(text: string): string {
|
|
let generatedHtml = text.replaceAll("<", "<");
|
|
generatedHtml = generatedHtml.replaceAll(">", ">");
|
|
|
|
generatedHtml = generatedHtml.replace(/\*\*(.*?)\*\*/g, "<b>$1</b>");
|
|
|
|
generatedHtml = generatedHtml.replace(/\*(.*?)\*/g, "<i>$1</i>");
|
|
|
|
generatedHtml = generatedHtml.replace(/`(.*?)`/g, "<code>$1</code>");
|
|
|
|
generatedHtml = generatedHtml.replace(/^###\s+(.*?)$/gm, "<h3>$1</h3>");
|
|
generatedHtml = generatedHtml.replace(/^##\s+(.*?)$/gm, "<h2>$1</h2>");
|
|
generatedHtml = generatedHtml.replace(/^#\s+(.*?)$/gm, "<h1>$1</h1>");
|
|
|
|
generatedHtml = generatedHtml.replace(
|
|
/!\[(.*?)\]\((https?:\/\/[^\s)]+\.(jpg|jpeg|png|gif|webp))\)/g,
|
|
'<img src="$2" alt="$1">',
|
|
);
|
|
generatedHtml = generatedHtml.replace(
|
|
/(https?:\/\/[^\s)]+)/g,
|
|
'<img src="$1" alt="$1">',
|
|
);
|
|
|
|
generatedHtml = generatedHtml.replace(
|
|
/nostr:(npub1[a-z0-9]+)/g,
|
|
'<a href="nostr:$1">$1</a>',
|
|
);
|
|
generatedHtml = generatedHtml.replace(
|
|
/nostr:(note1[a-z0-9]+)/g,
|
|
'<a href="nostr:$1">$1</a>',
|
|
);
|
|
generatedHtml = generatedHtml.replace(
|
|
/nostr:(event1[a-z0-9]+)/g,
|
|
'<a href="nostr:$1">$1</a>',
|
|
);
|
|
|
|
generatedHtml = generatedHtml.replace(/\n\n/g, "<br><br>");
|
|
|
|
return generatedHtml;
|
|
}
|
|
|
|
const eventIdToUidMap: string[] = JSON.parse(
|
|
await getFileOrDefault("event-id-to-uid-map.json", "[]"),
|
|
);
|
|
|
|
export function findEventIdForUid(uid: number): string {
|
|
return eventIdToUidMap[uid];
|
|
}
|
|
|
|
export function findUidForEvent(event: Event): number {
|
|
const eventId = event.id;
|
|
const uid = eventIdToUidMap.findIndex((id) => id === eventId);
|
|
if (uid !== -1) {
|
|
return uid;
|
|
}
|
|
eventIdToUidMap.push(eventId);
|
|
Deno.writeFileSync(
|
|
"event-id-to-uid-map.json",
|
|
new TextEncoder().encode(JSON.stringify(eventIdToUidMap)),
|
|
);
|
|
return eventIdToUidMap.length;
|
|
}
|
|
|
|
export function sendSocketMessage(socket: net.Socket, message: string) {
|
|
if (socket.destroyed || socket.closed) {
|
|
console.error("Socket is destroyed or closed");
|
|
return;
|
|
}
|
|
socket.write(message);
|
|
logMessage("out", message);
|
|
}
|
|
|
|
export function logMessage(direction: string, message: string) {
|
|
Deno.writeTextFileSync(
|
|
"data/message-logs.txt",
|
|
`${
|
|
JSON.stringify({
|
|
direction,
|
|
message,
|
|
})
|
|
}\n`,
|
|
{
|
|
append: true,
|
|
},
|
|
);
|
|
}
|
|
|
|
export async function getFileOrDefault(path: string, defaultValue: string) {
|
|
if (!await exists("data")) {
|
|
await Deno.mkdir("data");
|
|
}
|
|
|
|
if (!await exists(`data/${path}`)) {
|
|
await Deno.writeTextFile(`data/${path}`, defaultValue);
|
|
}
|
|
|
|
return await Deno.readTextFile(`data/${path}`);
|
|
}
|