You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
nostrweb/src/contacts.ts

97 lines
3.2 KiB
TypeScript

import {Event, nip19} from 'nostr-tools';
import {elem} from './utils/dom';
import {dateTime} from './utils/time';
import {isPTag, sortByCreatedAt} from './events';
import {getViewContent} from './view';
import {getMetadata} from './profiles';
const contactHistoryMap: {
[pubkey: string]: Event[]
} = {};
const updateFollowing = (evt: Event) => {
const following = getViewContent().querySelector(`[data-following="${evt.pubkey}"]`);
if (following) {
const count = evt.tags.filter(isPTag).length;
const anchor = elem('a', {
data: {following: evt.pubkey},
href: `/${evt.id}`,
title: dateTime.format(new Date(evt.created_at * 1000)),
}, `following ${count}`);
following.replaceWith(anchor);
}
};
export const setContactList = (evt: Event) => {
let contactHistory = contactHistoryMap[evt.pubkey];
if (!contactHistory) {
contactHistoryMap[evt.pubkey] = [evt];
updateFollowing(evt);
return;
}
if (contactHistory.find(({id}) => id === evt.id)) {
return;
}
contactHistory.push(evt);
contactHistory.sort(sortByCreatedAt);
updateFollowing(contactHistory[0]);
};
/**
* findChanges
* returns added and removed contacts list of P tags, ignores any tag other than 'p'
*/
const findChanges = (current: Event, previous: Event) => {
const previousContacts = previous.tags.join('\n'); // filter for p tags first?
const currentContacts = current.tags.join('\n');
const addedContacts = current.tags.filter(([tag, pubkey]) => tag === 'p' && !previousContacts.includes(pubkey));
const removedContacts = previous.tags.filter(([tag, pubkey]) => tag === 'p' && !currentContacts.includes(pubkey));
return [addedContacts, removedContacts];
};
export const updateContactList = (evt: Event) => {
const contactHistory = contactHistoryMap[evt.pubkey];
if (contactHistory.length === 1) {
return [contactHistory[0].tags.filter(isPTag)];
}
const pos = contactHistory.findIndex(({id}) => id === evt.id);
if (evt.id === contactHistory.at(-1)?.id) { // oldest known contact-list update
// update existing contact entries
contactHistory
.slice(0, -1)
.forEach((entry, i) => {
const previous = contactHistory[i + 1];
const [added, removed] = findChanges(entry, previous);
const contactNote = getViewContent().querySelector(`[data-contacts="${entry.id}"]`);
const updated = getContactUpdateMessage(added, removed);
contactNote?.replaceChildren(...updated);
});
return [evt.tags.filter(isPTag)];
}
return findChanges(evt, contactHistory[pos + 1]);
};
export const getContactUpdateMessage = (
addedList: string[][],
removedList: string[][],
) => {
const content = [];
// console.log(addedContacts)
if (addedList.length && addedList[0]) {
const pubkey = addedList[0][1];
const {userName} = getMetadata(pubkey);
const npub = nip19.npubEncode(pubkey);
content.push(
'follows ',
elem('a', {href: `/${npub}`, data: {profile: pubkey}}, userName),
);
}
if (addedList.length > 1) {
content.push(` (+ ${addedList.length - 1} others)`);
}
if (removedList?.length > 0) {
content.push(elem('small', {}, ` and unfollowed ${removedList.length}`));
}
return content;
};