Profile / Thread styles

This commit is contained in:
2023-07-24 15:30:21 +01:00
parent d292e658fc
commit 076d5d8cd5
67 changed files with 684 additions and 602 deletions

View File

@ -96,7 +96,7 @@ export class UserProfileCache extends FeedCache<MetadataCache> {
});
}
},
5
5,
);
setTimeout(() => this.#processZapperQueue(), 1_000);
@ -116,7 +116,7 @@ export class UserProfileCache extends FeedCache<MetadataCache> {
});
}
},
5
5,
);
setTimeout(() => this.#processNip5Queue(), 1_000);
@ -135,7 +135,7 @@ export class UserProfileCache extends FeedCache<MetadataCache> {
console.warn("Failed to process item", i);
}
batch.pop(); // pop any
})()
})(),
);
if (batch.length === batchSize) {
await Promise.all(batch);

View File

@ -153,7 +153,7 @@ export class Connection extends ExternalStore<ConnectionStateSnapshot> {
this.#log(
`[${this.Address}] Closed (code=${e.code}), trying again in ${(this.ConnectTimeout / 1000)
.toFixed(0)
.toLocaleString()} sec`
.toLocaleString()} sec`,
);
this.ReconnectTimer = setTimeout(() => {
this.Connect();
@ -425,7 +425,7 @@ export class Connection extends ExternalStore<ConnectionStateSnapshot> {
"%s Inactive connection has %d active requests! %O",
this.Address,
this.ActiveRequests.size,
this.ActiveRequests
this.ActiveRequests,
);
} else {
this.Close();

View File

@ -33,4 +33,4 @@ export const CashuRegex = /(cashuA[A-Za-z0-9_-]{0,10000}={0,3})/i;
/**
* Regex to match any npub/nevent/naddr/nprofile/note
*/
export const MentionNostrEntityRegex = /@n(pub|profile|event|ote|addr|)1[acdefghjklmnpqrstuvwxyz023456789]+/g;
export const MentionNostrEntityRegex = /@n(pub|profile|event|ote|addr|)1[acdefghjklmnpqrstuvwxyz023456789]+/g;

View File

@ -43,9 +43,7 @@ export class EventBuilder {
*/
processContent() {
if (this.#content) {
this.#content = this.#content.replace(MentionNostrEntityRegex, m =>
this.#replaceMention(m)
);
this.#content = this.#content.replace(MentionNostrEntityRegex, m => this.#replaceMention(m));
const hashTags = [...this.#content.matchAll(HashtagRegex)];
hashTags.map(hashTag => {

View File

@ -146,7 +146,7 @@ export class EventPublisher {
relays: Array<string>,
note?: HexKey,
msg?: string,
fnExtra?: EventBuilderHook
fnExtra?: EventBuilderHook,
) {
const eb = this.#eb(EventKind.ZapRequest);
eb.content(msg ?? "");

View File

@ -175,6 +175,6 @@ function pickTopRelays(cache: RelayCache, authors: Array<string>, n: number) {
key: a.key,
relays: [],
};
})
}),
);
}

View File

@ -1,6 +1,4 @@
import { MessageEncryptor, MessageEncryptorPayload, MessageEncryptorVersion } from "index";
import { base64 } from "@scure/base";
import { secp256k1 } from "@noble/curves/secp256k1";
export class Nip4WebCryptoEncryptor implements MessageEncryptor {
@ -20,7 +18,7 @@ export class Nip4WebCryptoEncryptor implements MessageEncryptor {
iv: iv,
},
key,
data
data,
);
return {
ciphertext: new Uint8Array(result),

View File

@ -94,7 +94,7 @@ export class Nip46Signer implements EventSigner {
"#p": [this.#localPubkey],
},
],
() => {}
() => {},
);
if (isBunker) {
@ -181,7 +181,7 @@ export class Nip46Signer implements EventSigner {
result: "ack",
error: "",
},
unwrap(this.#remotePubkey)
unwrap(this.#remotePubkey),
);
id = "connect";
}

View File

@ -37,7 +37,7 @@ export class Nip7Signer implements EventSigner {
throw new Error("Cannot use NIP-07 signer, not found!");
}
return await barrierQueue(Nip7Queue, () =>
unwrap(window.nostr?.nip04?.encrypt).call(window.nostr?.nip04, key, content)
unwrap(window.nostr?.nip04?.encrypt).call(window.nostr?.nip04, key, content),
);
}
@ -46,7 +46,7 @@ export class Nip7Signer implements EventSigner {
throw new Error("Cannot use NIP-07 signer, not found!");
}
return await barrierQueue(Nip7Queue, () =>
unwrap(window.nostr?.nip04?.decrypt).call(window.nostr?.nip04, otherKey, content)
unwrap(window.nostr?.nip04?.decrypt).call(window.nostr?.nip04, otherKey, content),
);
}

View File

@ -128,7 +128,7 @@ export class ProfileLoaderService {
pubkey: a,
loaded: unixNowMs() - ProfileCacheExpire + 30_000, // expire in 30s
created: 69,
} as MetadataCache)
} as MetadataCache),
);
await Promise.all(empty);
}

View File

@ -27,7 +27,7 @@ class QueryTrace {
readonly filters: Array<ReqFilter>,
readonly connId: string,
fnClose: (id: string) => void,
fnProgress: () => void
fnProgress: () => void,
) {
this.id = uuid();
this.start = unixNowMs();
@ -293,7 +293,7 @@ export class Query implements QueryBase {
q.filters,
c.Id,
x => c.CloseReq(x),
() => this.#onProgress()
() => this.#onProgress(),
);
this.#tracing.push(qt);
c.QueueReq(["REQ", qt.id, ...qt.filters], () => qt.sentToRelay());

View File

@ -5,195 +5,198 @@ import { validateNostrLink } from "./nostr-link";
import { splitByUrl } from "./utils";
export interface ParsedFragment {
type: "text" | "link" | "mention" | "invoice" | "media" | "cashu" | "hashtag" | "custom_emoji"
content: string
mimeType?: string
type: "text" | "link" | "mention" | "invoice" | "media" | "cashu" | "hashtag" | "custom_emoji";
content: string;
mimeType?: string;
}
export type Fragment = string | ParsedFragment;
function extractLinks(fragments: Fragment[]) {
return fragments
.map(f => {
if (typeof f === "string") {
return splitByUrl(f).map(a => {
const validateLink = () => {
const normalizedStr = a.toLowerCase();
return fragments
.map(f => {
if (typeof f === "string") {
return splitByUrl(f).map(a => {
const validateLink = () => {
const normalizedStr = a.toLowerCase();
if (normalizedStr.startsWith("web+nostr:") || normalizedStr.startsWith("nostr:")) {
return validateNostrLink(normalizedStr);
}
return (
normalizedStr.startsWith("http:") ||
normalizedStr.startsWith("https:") ||
normalizedStr.startsWith("magnet:")
);
};
if (validateLink()) {
const url = new URL(a);
const extension = url.pathname.match(FileExtensionRegex);
if (extension && extension.length > 1) {
const mediaType = (() => {
switch (extension[1]) {
case "gif":
case "jpg":
case "jpeg":
case "jfif":
case "png":
case "bmp":
case "webp":
return "image";
case "wav":
case "mp3":
case "ogg":
return "audio";
case "mp4":
case "mov":
case "mkv":
case "avi":
case "m4v":
case "webm":
case "m3u8":
return "video";
default:
return "unknown";
}
})();
return {
type: "media",
content: a,
mimeType: `${mediaType}/${extension[1]}`
} as ParsedFragment;
} else {
return {
type: "link",
content: a
} as ParsedFragment;
}
}
return a;
});
if (normalizedStr.startsWith("web+nostr:") || normalizedStr.startsWith("nostr:")) {
return validateNostrLink(normalizedStr);
}
return f;
})
.flat();
return (
normalizedStr.startsWith("http:") ||
normalizedStr.startsWith("https:") ||
normalizedStr.startsWith("magnet:")
);
};
if (validateLink()) {
const url = new URL(a);
const extension = url.pathname.match(FileExtensionRegex);
if (extension && extension.length > 1) {
const mediaType = (() => {
switch (extension[1]) {
case "gif":
case "jpg":
case "jpeg":
case "jfif":
case "png":
case "bmp":
case "webp":
return "image";
case "wav":
case "mp3":
case "ogg":
return "audio";
case "mp4":
case "mov":
case "mkv":
case "avi":
case "m4v":
case "webm":
case "m3u8":
return "video";
default:
return "unknown";
}
})();
return {
type: "media",
content: a,
mimeType: `${mediaType}/${extension[1]}`,
} as ParsedFragment;
} else {
return {
type: "link",
content: a,
} as ParsedFragment;
}
}
return a;
});
}
return f;
})
.flat();
}
function extractMentions(fragments: Fragment[]) {
return fragments
.map(f => {
if (typeof f === "string") {
return f.split(MentionNostrEntityRegex).map(i => {
if (MentionNostrEntityRegex.test(i)) {
return {
type: "mention",
content: i
} as ParsedFragment;
} else {
return i;
}
});
}
return f;
})
.flat();
return fragments
.map(f => {
if (typeof f === "string") {
return f.split(MentionNostrEntityRegex).map(i => {
if (MentionNostrEntityRegex.test(i)) {
return {
type: "mention",
content: i,
} as ParsedFragment;
} else {
return i;
}
});
}
return f;
})
.flat();
}
function extractCashuTokens(fragments: Fragment[]) {
return fragments
.map(f => {
if (typeof f === "string" && f.includes("cashuA")) {
return f.split(CashuRegex).map(a => {
return {
type: "cashu",
content: a
} as ParsedFragment
});
}
return f;
})
.flat();
return fragments
.map(f => {
if (typeof f === "string" && f.includes("cashuA")) {
return f.split(CashuRegex).map(a => {
return {
type: "cashu",
content: a,
} as ParsedFragment;
});
}
return f;
})
.flat();
}
function extractInvoices(fragments: Fragment[]) {
return fragments
.map(f => {
if (typeof f === "string") {
return f.split(InvoiceRegex).map(i => {
if (i.toLowerCase().startsWith("lnbc")) {
return {
type: "invoice",
content: i
} as ParsedFragment
} else {
return i;
}
});
}
return f;
})
.flat();
return fragments
.map(f => {
if (typeof f === "string") {
return f.split(InvoiceRegex).map(i => {
if (i.toLowerCase().startsWith("lnbc")) {
return {
type: "invoice",
content: i,
} as ParsedFragment;
} else {
return i;
}
});
}
return f;
})
.flat();
}
function extractHashtags(fragments: Fragment[]) {
return fragments
.map(f => {
if (typeof f === "string") {
return f.split(HashtagRegex).map(i => {
if (i.toLowerCase().startsWith("#")) {
return {
type: "hashtag",
content: i.substring(1)
} as ParsedFragment;
} else {
return i;
}
});
}
return f;
})
.flat();
return fragments
.map(f => {
if (typeof f === "string") {
return f.split(HashtagRegex).map(i => {
if (i.toLowerCase().startsWith("#")) {
return {
type: "hashtag",
content: i.substring(1),
} as ParsedFragment;
} else {
return i;
}
});
}
return f;
})
.flat();
}
function extractCustomEmoji(fragments: Fragment[], tags: Array<Array<string>>) {
return fragments
.map(f => {
if (typeof f === "string") {
return f.split(/:(\w+):/g).map(i => {
const t = tags.find(a => a[0] === "emoji" && a[1] === i);
if (t) {
return {
type: "custom_emoji",
content: t[2]
} as ParsedFragment
} else {
return i;
}
});
}
return f;
})
.flat();
return fragments
.map(f => {
if (typeof f === "string") {
return f.split(/:(\w+):/g).map(i => {
const t = tags.find(a => a[0] === "emoji" && a[1] === i);
if (t) {
return {
type: "custom_emoji",
content: t[2],
} as ParsedFragment;
} else {
return i;
}
});
}
return f;
})
.flat();
}
export function transformText(body: string, tags: Array<Array<string>>) {
let fragments = extractLinks([body]);
fragments = extractMentions(fragments);
fragments = extractHashtags(fragments);
fragments = extractInvoices(fragments);
fragments = extractCashuTokens(fragments);
fragments = extractCustomEmoji(fragments, tags);
fragments = fragments.map(a => {
if (typeof a === "string") {
if (a.length > 0) {
return { type: "text", content: a } as ParsedFragment;
}
} else {
return a;
let fragments = extractLinks([body]);
fragments = extractMentions(fragments);
fragments = extractHashtags(fragments);
fragments = extractInvoices(fragments);
fragments = extractCashuTokens(fragments);
fragments = extractCustomEmoji(fragments, tags);
fragments = fragments
.map(a => {
if (typeof a === "string") {
if (a.length > 0) {
return { type: "text", content: a } as ParsedFragment;
}
}).filter(a => a).map(a => unwrap(a));
return fragments as Array<ParsedFragment>;
}
} else {
return a;
}
})
.filter(a => a)
.map(a => unwrap(a));
return fragments as Array<ParsedFragment>;
}

View File

@ -27,24 +27,26 @@ export function reqFilterEq(a: FlatReqFilter | ReqFilter, b: FlatReqFilter | Req
}
export function flatFilterEq(a: FlatReqFilter, b: FlatReqFilter): boolean {
return a.keys === b.keys
&& a.since === b.since
&& a.until === b.until
&& a.limit === b.limit
&& a.search === b.search
&& a.ids === b.ids
&& a.kinds === b.kinds
&& a.authors === b.authors
&& a["#e"] === b["#e"]
&& a["#p"] === b["#p"]
&& a["#t"] === b["#t"]
&& a["#d"] === b["#d"]
&& a["#r"] === b["#r"];
return (
a.keys === b.keys &&
a.since === b.since &&
a.until === b.until &&
a.limit === b.limit &&
a.search === b.search &&
a.ids === b.ids &&
a.kinds === b.kinds &&
a.authors === b.authors &&
a["#e"] === b["#e"] &&
a["#p"] === b["#p"] &&
a["#t"] === b["#t"] &&
a["#d"] === b["#d"] &&
a["#r"] === b["#r"]
);
}
export function splitByUrl(str: string) {
const urlRegex =
/((?:http|ftp|https|nostr|web\+nostr|magnet):\/?\/?(?:[\w+?.\w+])+(?:[a-zA-Z0-9~!@#$%^&*()_\-=+\\/?.:;',]*)?(?:[-A-Za-z0-9+&@#/%=~()_|]))/i;
return str.split(urlRegex);
}
const urlRegex =
/((?:http|ftp|https|nostr|web\+nostr|magnet):\/?\/?(?:[\w+?.\w+])+(?:[a-zA-Z0-9~!@#$%^&*()_\-=+\\/?.:;',]*)?(?:[-A-Za-z0-9+&@#/%=~()_|]))/i;
return str.split(urlRegex);
}