
Improved recognization of replying to thread and being able to open it. Rewrote profile storing on the model. Additionally fixed issues where the profile was not getting loaded for referenced pubkeys on an event.
332 lines
7 KiB
JavaScript
332 lines
7 KiB
JavaScript
const KIND_METADATA = 0;
|
|
const KIND_NOTE = 1;
|
|
const KIND_RELAY = 2;
|
|
const KIND_CONTACT = 3;
|
|
const KIND_DM = 4;
|
|
const KIND_DELETE = 5;
|
|
const KIND_SHARE = 6;
|
|
const KIND_REACTION = 7;
|
|
const KIND_CHATROOM = 42;
|
|
|
|
const TAG_P = "#p";
|
|
const TAG_E = "#e";
|
|
|
|
const R_HEART = "❤️";
|
|
|
|
const STANDARD_KINDS = [
|
|
KIND_NOTE,
|
|
KIND_DELETE,
|
|
KIND_REACTION,
|
|
KIND_SHARE,
|
|
];
|
|
|
|
function get_local_state(key) {
|
|
if (DAMUS[key] != null)
|
|
return DAMUS[key]
|
|
return localStorage.getItem(key)
|
|
}
|
|
|
|
function set_local_state(key, val) {
|
|
DAMUS[key] = val
|
|
localStorage.setItem(key, val)
|
|
}
|
|
|
|
async function sign_id(privkey, id) {
|
|
//const digest = nostrjs.hex_decode(id)
|
|
const sig = await nobleSecp256k1.schnorr.sign(id, privkey)
|
|
return nostrjs.hex_encode(sig)
|
|
}
|
|
|
|
async function broadcast_related_events(ev) {
|
|
ev.tags.reduce((evs, tag) => {
|
|
// cap it at something sane
|
|
if (evs.length >= 5)
|
|
return evs
|
|
const ev = get_tag_event(tag)
|
|
if (!ev)
|
|
return evs
|
|
return evs
|
|
}, [])
|
|
.forEach((ev, i) => {
|
|
// so we don't get rate limited
|
|
setTimeout(() => {
|
|
log_debug("broadcasting related event", ev)
|
|
broadcast_event(ev)
|
|
}, (i+1)*1200)
|
|
});
|
|
}
|
|
|
|
function broadcast_event(ev) {
|
|
DAMUS.pool.send(["EVENT", ev])
|
|
}
|
|
|
|
async function share(evid) {
|
|
const model = DAMUS;
|
|
const e = model.all_events[evid];
|
|
if (!e)
|
|
return;
|
|
let ev = {
|
|
kind: KIND_SHARE,
|
|
created_at: new_creation_time(),
|
|
pubkey: model.pubkey,
|
|
content: JSON.stringify(e),
|
|
tags: [["e", e.id], ["p", e.pubkey]],
|
|
}
|
|
ev.id = await nostrjs.calculate_id(ev);
|
|
ev = await sign_event(ev);
|
|
broadcast_event(ev);
|
|
return ev;
|
|
}
|
|
|
|
async function update_profile(profile={}) {
|
|
let ev = {
|
|
kind: KIND_METADATA,
|
|
created_at: new_creation_time(),
|
|
pubkey: DAMUS.pubkey,
|
|
content: JSON.stringify(profile),
|
|
tags: [],
|
|
};
|
|
ev.id = await nostrjs.calculate_id(ev);
|
|
ev = await sign_event(ev);
|
|
broadcast_event(ev);
|
|
return ev;
|
|
}
|
|
|
|
async function update_contacts() {
|
|
const model = DAMUS;
|
|
const contacts = Array.from(model.contacts.friends);
|
|
const tags = contacts.map((pubkey) => {
|
|
return ["p", pubkey]
|
|
});
|
|
let ev = {
|
|
kind: KIND_CONTACT,
|
|
created_at: new_creation_time(),
|
|
pubkey: model.pubkey,
|
|
content: "",
|
|
tags: tags,
|
|
}
|
|
ev.id = await nostrjs.calculate_id(ev);
|
|
ev = await sign_event(ev);
|
|
broadcast_event(ev);
|
|
return ev;
|
|
}
|
|
|
|
async function sign_event(ev) {
|
|
if (window.nostr && window.nostr.signEvent) {
|
|
const signed = await window.nostr.signEvent(ev)
|
|
if (typeof signed === 'string') {
|
|
ev.sig = signed
|
|
return ev
|
|
}
|
|
return signed
|
|
}
|
|
|
|
const privkey = get_privkey()
|
|
ev.sig = await sign_id(privkey, ev.id)
|
|
return ev
|
|
}
|
|
|
|
async function send_post() {
|
|
const input_el = document.querySelector("#post-input")
|
|
const cw_el = document.querySelector("#content-warning-input")
|
|
const cw = cw_el.value
|
|
const content = input_el.value
|
|
const created_at = new_creation_time()
|
|
const kind = 1
|
|
const tags = cw ? [["content-warning", cw]] : []
|
|
const pubkey = await get_pubkey()
|
|
|
|
let post = { pubkey, tags, content, created_at, kind }
|
|
post.id = await nostrjs.calculate_id(post)
|
|
post = await sign_event(post)
|
|
broadcast_event(post)
|
|
|
|
input_el.value = ""
|
|
cw_el.value = ""
|
|
post_input_changed(input_el)
|
|
}
|
|
|
|
function new_reply_tags(ev) {
|
|
return [
|
|
["e", ev.id, "", "reply"],
|
|
["p", ev.pubkey],
|
|
];
|
|
}
|
|
|
|
async function create_reply(pubkey, content, ev, all=true) {
|
|
const tags = all ? gather_reply_tags(pubkey, ev) : new_reply_tags(ev);
|
|
const created_at = new_creation_time();
|
|
let kind = ev.kind;
|
|
|
|
// convert emoji replies into reactions
|
|
if (is_valid_reaction_content(content))
|
|
kind = 7;
|
|
let reply = {
|
|
pubkey,
|
|
tags,
|
|
content,
|
|
created_at,
|
|
kind
|
|
}
|
|
reply.id = await nostrjs.calculate_id(reply)
|
|
reply = await sign_event(reply)
|
|
return reply
|
|
}
|
|
|
|
async function send_reply(content, replying_to, all=true) {
|
|
const ev = DAMUS.all_events[replying_to]
|
|
if (!ev)
|
|
return;
|
|
|
|
const pubkey = await get_pubkey()
|
|
let reply = await create_reply(pubkey, content, ev, all)
|
|
|
|
broadcast_event(reply)
|
|
broadcast_related_events(reply)
|
|
}
|
|
|
|
async function create_deletion_event(pubkey, target, content="") {
|
|
const created_at = Math.floor(new Date().getTime() / 1000)
|
|
let kind = 5
|
|
|
|
const tags = [["e", target]]
|
|
let del = { pubkey, tags, content, created_at, kind }
|
|
|
|
del.id = await nostrjs.calculate_id(del)
|
|
del = await sign_event(del)
|
|
return del
|
|
}
|
|
|
|
async function delete_post(id, reason) {
|
|
const ev = DAMUS.all_events[id]
|
|
if (!ev)
|
|
return
|
|
|
|
const pubkey = await get_pubkey()
|
|
let del = await create_deletion_event(pubkey, id, reason)
|
|
broadcast_event(del)
|
|
}
|
|
|
|
function model_get_reacts_to(model, pubkey, evid, emoji) {
|
|
const r = model.reactions_to[evid];
|
|
if (!r)
|
|
return;
|
|
for (const id of r.keys()) {
|
|
if (model_is_event_deleted(model, id))
|
|
continue;
|
|
const reaction = model.all_events[id];
|
|
if (!reaction || reaction.pubkey != pubkey)
|
|
continue;
|
|
if (emoji == get_reaction_emoji(reaction))
|
|
return reaction;
|
|
}
|
|
return;
|
|
}
|
|
|
|
function get_reactions(model, evid) {
|
|
const reactions_set = model.reactions_to[evid]
|
|
if (!reactions_set)
|
|
return ""
|
|
|
|
let reactions = []
|
|
for (const id of reactions_set.keys()) {
|
|
if (model_is_event_deleted(model, id))
|
|
continue
|
|
const reaction = model.all_events[id]
|
|
if (!reaction)
|
|
continue
|
|
reactions.push(reaction)
|
|
}
|
|
|
|
const groups = reactions.reduce((grp, r) => {
|
|
const e = get_reaction_emoji(r)
|
|
grp[e] = grp[e] || {}
|
|
grp[e][r.pubkey] = r
|
|
return grp
|
|
}, {})
|
|
|
|
return groups
|
|
}
|
|
|
|
function gather_reply_tags(pubkey, from) {
|
|
let tags = []
|
|
let ids = new Set()
|
|
|
|
if (from.refs && from.refs.root) {
|
|
tags.push(["e", from.refs.root, "", "root"])
|
|
ids.add(from.refs.root)
|
|
}
|
|
|
|
tags.push(["e", from.id, "", "reply"])
|
|
ids.add(from.id)
|
|
|
|
for (const tag of from.tags) {
|
|
if (tag.length >= 2) {
|
|
if (tag[0] === "p" && tag[1] !== pubkey) {
|
|
if (!ids.has(tag[1])) {
|
|
tags.push(["p", tag[1]])
|
|
ids.add(tag[1])
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (from.pubkey !== pubkey && !ids.has(from.pubkey)) {
|
|
tags.push(["p", from.pubkey])
|
|
}
|
|
return tags
|
|
}
|
|
|
|
function get_tag_event(tag) {
|
|
const model = DAMUS;
|
|
if (tag.length < 2)
|
|
return null
|
|
if (tag[0] === "e")
|
|
return model.all_events[tag[1]]
|
|
if (tag[0] === "p") {
|
|
let profile = model_get_profile(model, tag[1]);
|
|
if (profile.evid)
|
|
return model.all_events[profile.evid];
|
|
}
|
|
return null
|
|
}
|
|
|
|
function* yield_etags(tags) {
|
|
for (const tag of tags) {
|
|
if (tag.length >= 2 && tag[0] === "e")
|
|
yield tag
|
|
}
|
|
}
|
|
|
|
function get_content_warning(tags) {
|
|
for (const tag of tags) {
|
|
if (tag.length >= 1 && tag[0] === "content-warning")
|
|
return tag[1] || ""
|
|
}
|
|
return null
|
|
}
|
|
|
|
async function get_nip05_pubkey(email) {
|
|
const [user, host] = email.split("@")
|
|
const url = `https://${host}/.well-known/nostr.json?name=${user}`
|
|
|
|
try {
|
|
const res = await fetch(url)
|
|
const json = await res.json()
|
|
log_debug("nip05 data", json)
|
|
return json.names[user]
|
|
} catch (e) {
|
|
log_error("fetching nip05 entry for %s", email, e)
|
|
throw e
|
|
}
|
|
}
|
|
|
|
// TODO rename handle_pubkey to fetch_pubkey
|
|
async function handle_pubkey(pubkey) {
|
|
if (pubkey[0] === "n")
|
|
pubkey = bech32_decode(pubkey)
|
|
if (pubkey.includes("@"))
|
|
pubkey = await get_nip05_pubkey(pubkey)
|
|
set_local_state('pubkey', pubkey)
|
|
return pubkey
|
|
}
|
|
|