summaryrefslogtreecommitdiff
path: root/gui/src/logic/nostrill.ts
diff options
context:
space:
mode:
Diffstat (limited to 'gui/src/logic/nostrill.ts')
-rw-r--r--gui/src/logic/nostrill.ts155
1 files changed, 136 insertions, 19 deletions
diff --git a/gui/src/logic/nostrill.ts b/gui/src/logic/nostrill.ts
index 97d2156..f976c95 100644
--- a/gui/src/logic/nostrill.ts
+++ b/gui/src/logic/nostrill.ts
@@ -1,9 +1,12 @@
import type { Event } from "@/types/nostr";
-import type { Content, FC, Poast } from "@/types/trill";
+import type { Content, Cursor, FC, FlatFeed, Poast } from "@/types/trill";
import { engagementBunt, openLock } from "./bunts";
import type { UserType } from "@/types/nostrill";
import type { Result } from "@/types/ui";
import { isValidPatp } from "urbit-ob";
+import { IMAGE_SUBREGEX, URL_REGEX, VIDEO_SUBREGEX } from "./constants";
+import { decodeNostrKey } from "./nostr";
+
export function eventsToFc(postEvents: Event[]): FC {
const fc = postEvents.reduce(
(acc: FC, event: Event) => {
@@ -18,9 +21,49 @@ export function eventsToFc(postEvents: Event[]): FC {
);
return fc;
}
+export function addEventToFc(event: Event, fc: FC): FC {
+ const p = eventToPoast(event);
+ if (!p) return fc;
+ fc.feed[p.id] = p;
+ if (!fc.start || event.created_at < Number(fc.start)) fc.start = p.id;
+ if (!fc.end || event.created_at > Number(fc.end)) fc.end = p.id;
+ return fc;
+}
+export function extractURLs(text: string): {
+ text: Array<{ text: string } | { link: { href: string; show: string } }>;
+ pics: string[];
+ vids: string[];
+} {
+ const pics: string[] = [];
+ const vids: string[] = [];
+ const tokens: Array<
+ { text: string } | { link: { href: string; show: string } }
+ > = [];
+ const sections = text.split(URL_REGEX);
+ for (const sec of sections) {
+ if (!sec) continue;
+ const s = sec.trim();
+ if (!s) continue;
+ if (URL_REGEX.test(s)) {
+ if (IMAGE_SUBREGEX.test(s)) {
+ pics.push(s);
+ } else if (VIDEO_SUBREGEX.test(s)) {
+ vids.push(s);
+ } else tokens.push({ link: { href: s, show: s } });
+ } else tokens.push({ text: s });
+ }
+
+ return { text: tokens, pics, vids };
+}
+
export function eventToPoast(event: Event): Poast | null {
if (event.kind !== 1) return null;
- const contents: Content = [{ paragraph: [{ text: event.content }] }];
+ const inl = extractURLs(event.content || "");
+ const contents: Content = [
+ { paragraph: inl.text },
+ { media: { images: inl.pics } },
+ ];
+ if (inl.vids.length > 0) contents.push({ media: { video: inl.vids[0] } });
const ts = event.created_at * 1000;
const id = `${ts}`;
const poast: Poast = {
@@ -28,11 +71,12 @@ export function eventToPoast(event: Event): Poast | null {
host: event.pubkey,
author: event.pubkey,
contents,
- thread: id,
+ thread: null,
parent: null,
read: openLock,
write: openLock,
tags: [],
+ hash: event.id,
time: ts,
engagement: engagementBunt,
children: [],
@@ -48,17 +92,21 @@ export function eventToPoast(event: Event): Poast | null {
// TODO
if (marker === "root") poast.thread = eventId;
else if (marker === "reply") poast.parent = eventId;
+ // TODO this are kinda useful too as quotes or whatever
+ // else if (marker === "mention") poast.parent = eventId;
}
//
- if (ff === "r")
+ else if (ff === "r")
contents.push({
paragraph: [{ link: { show: tag[1]!, href: tag[1]! } }],
});
- if (ff === "p")
- contents.push({
- paragraph: [{ ship: tag[1]! }],
- });
- if (ff === "q")
+ else if (ff === "p") {
+ //
+ }
+ // contents.push({
+ // paragraph: [{ ship: tag[1]! }],
+ // });
+ else if (ff === "q")
contents.push({
ref: {
type: "nostr",
@@ -66,10 +114,23 @@ export function eventToPoast(event: Event): Poast | null {
path: tag[2] || "" + `/${tag[3] || ""}`,
},
});
+ // else console.log("odd tag", tag);
}
+ if (!poast.parent && !poast.thread) {
+ const tags = event.tags.filter((t) => t[0] !== "p");
+ console.log("no parent", { event, poast, tags });
+ }
+ if (!poast.parent && poast.thread) poast.parent = poast.thread;
return poast;
}
+export function stringToUser(s: string): Result<UserType> {
+ const p = isValidPatp(s);
+ if (p) return { ok: { urbit: s } };
+ const dec = decodeNostrKey(s);
+ if (dec) return { ok: { nostr: s } };
+ else return { error: "invalid user" };
+}
export function userToString(user: UserType): Result<string> {
if ("urbit" in user) {
const isValid = isValidPatp(user.urbit);
@@ -78,16 +139,6 @@ export function userToString(user: UserType): Result<string> {
} else if ("nostr" in user) return { ok: user.nostr };
else return { error: "unknown user" };
}
-export function isValidNostrPubkey(pubkey: string): boolean {
- // TODO
- if (pubkey.length !== 64) return false;
- try {
- BigInt("0x" + pubkey);
- return true;
- } catch (_e) {
- return false;
- }
-}
// NOTE common tags:
// imeta
// client
@@ -138,3 +189,69 @@ export function isValidNostrPubkey(pubkey: string): boolean {
// }
// return effects;
// }
+//
+
+function findId(feed: FlatFeed, id: string): Result<string> {
+ const has = feed[id];
+ if (!has) return { ok: id };
+ else {
+ try {
+ const bigint = BigInt(id);
+ const n = bigint + 1n;
+ return findId(feed, n.toString());
+ } catch (e) {
+ return { error: "not a number" };
+ }
+ }
+}
+function updateCursor(cursor: Cursor, ncursor: Cursor, earlier: boolean) {
+ if (!cursor) return ncursor;
+ if (!ncursor) return cursor;
+ const or = BigInt(cursor);
+ const nw = BigInt(ncursor);
+ const shouldChange = earlier ? nw < or : nw > or;
+ return shouldChange ? ncursor : cursor;
+}
+export function consolidateFeeds(fols: Map<string, FC>): FC {
+ const f: FlatFeed = {};
+ let start: Cursor = null;
+ let end: Cursor = null;
+ const feeds = fols.entries();
+ for (const [_userString, fc] of feeds) {
+ start = updateCursor(start, fc.start, true);
+ end = updateCursor(end, fc.end, false);
+
+ const poasts = Object.values(fc.feed);
+ for (const p of poasts) {
+ const nid = findId(f, p.id);
+ if ("error" in nid) continue;
+ f[nid.ok] = p;
+ }
+ }
+ return { start, end, feed: f };
+}
+export function disaggregate(
+ fols: Map<string, FC>,
+ choice: "urbit" | "nostr",
+): FC {
+ const f: FlatFeed = {};
+ let start: Cursor = null;
+ let end: Cursor = null;
+ const feeds = fols.entries();
+ for (const [userString, fc] of feeds) {
+ const want =
+ choice === "urbit"
+ ? isValidPatp(userString)
+ : !!decodeNostrKey(userString);
+ if (!want) continue;
+ start = updateCursor(start, fc.start, true);
+ end = updateCursor(end, fc.end, false);
+ const poasts = Object.values(fc.feed);
+ for (const p of poasts) {
+ const nid = findId(f, p.id);
+ if ("error" in nid) continue;
+ f[nid.ok] = p;
+ }
+ }
+ return { start, end, feed: f };
+}