130 lines
3.8 KiB
JavaScript
130 lines
3.8 KiB
JavaScript
/**
|
|
* Utilities for parsing social activity events into timeline entries.
|
|
*
|
|
* An `ActivityEntry` represents a single social interaction related to the
|
|
* user's content (e.g. a zap received on one of their photos). Entries are
|
|
* ordered newest-first.
|
|
*/
|
|
|
|
import { tracked } from '@glimmer/tracking';
|
|
import {
|
|
getZapSender,
|
|
getZapRecipient,
|
|
getZapPayment,
|
|
getZapEventPointer,
|
|
getZapRequest,
|
|
} from 'applesauce-common/helpers';
|
|
import { parsePhotoFromEvent } from './contributions';
|
|
|
|
/**
|
|
* A single activity timeline entry.
|
|
*
|
|
* `senderName`, `senderAvatar`, and `senderProfileLoading` are tracked so the
|
|
* row re-renders when the sender's profile is resolved asynchronously.
|
|
*/
|
|
export class ActivityEntry {
|
|
type = 'zap';
|
|
photoEventId;
|
|
photo;
|
|
placeIdentifier;
|
|
senderPubkey;
|
|
amountSats;
|
|
message;
|
|
createdAt;
|
|
@tracked senderName = null;
|
|
@tracked senderAvatar = null;
|
|
@tracked senderProfileLoading = true;
|
|
|
|
constructor({
|
|
photoEventId,
|
|
photo,
|
|
placeIdentifier,
|
|
senderPubkey,
|
|
amountSats,
|
|
message,
|
|
createdAt,
|
|
}) {
|
|
this.photoEventId = photoEventId;
|
|
this.photo = photo;
|
|
this.placeIdentifier = placeIdentifier;
|
|
this.senderPubkey = senderPubkey;
|
|
this.amountSats = amountSats;
|
|
this.message = message;
|
|
this.createdAt = createdAt;
|
|
}
|
|
}
|
|
|
|
// TODO: Place-name resolution. Add a shared place-name-resolver util co-used by
|
|
// services/contributions.js and this service. Each ActivityEntry should get
|
|
// @tracked placeName / placeNameLoading, resolved via the existing cascade
|
|
// (bookmarks → localForage cache → osm cache → batch OSM fetch). Extract from
|
|
// contributions.js rather than duplicating.
|
|
|
|
/**
|
|
* Parses a kind 9735 (Zap Receipt) event into an `ActivityEntry`, given the
|
|
* user's pubkey (to confirm they are the recipient).
|
|
*
|
|
* Returns `null` if the receipt is malformed, not directed at the user, or
|
|
* does not reference a zapped event (kind 360 photo).
|
|
*
|
|
* @param {object} receipt A NIP-57 zap receipt event (kind 9735)
|
|
* @param {string} userPubkey The logged-in user's pubkey
|
|
* @returns {ActivityEntry|null}
|
|
*/
|
|
export function parseZapReceipt(receipt, userPubkey) {
|
|
if (!receipt || receipt.kind !== 9735) return null;
|
|
|
|
const recipient = getZapRecipient(receipt);
|
|
if (!recipient || recipient !== userPubkey) return null;
|
|
|
|
const sender = getZapSender(receipt);
|
|
if (!sender) return null;
|
|
|
|
const payment = getZapPayment(receipt);
|
|
if (!payment || !payment.amount) return null;
|
|
const amountSats = Math.round(payment.amount / 1000);
|
|
|
|
const eventPointer = getZapEventPointer(receipt);
|
|
if (!eventPointer || !eventPointer.id) return null;
|
|
|
|
const zapRequest = getZapRequest(receipt);
|
|
const message = zapRequest?.content || null;
|
|
|
|
return new ActivityEntry({
|
|
photoEventId: eventPointer.id,
|
|
photo: null,
|
|
placeIdentifier: null,
|
|
senderPubkey: sender,
|
|
amountSats,
|
|
message,
|
|
createdAt: receipt.created_at,
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Enriches an `ActivityEntry` with photo and place data from the zapped kind
|
|
* 360 event, if it is available in the store.
|
|
*
|
|
* Returns `true` if the entry was enriched (i.e. the zapped event was found
|
|
* and is a kind 360 authored by the user), or `false` if the entry should be
|
|
* discarded (the zapped event is not one of the user's photos).
|
|
*
|
|
* @param {ActivityEntry} entry
|
|
* @param {object} store The applesauce EventStore to look up the zapped event
|
|
* @param {string} userPubkey The logged-in user's pubkey
|
|
* @returns {boolean}
|
|
*/
|
|
export function enrichWithPhoto(entry, store, userPubkey) {
|
|
const event = store.getEvent?.(entry.photoEventId);
|
|
if (!event || event.kind !== 360 || event.pubkey !== userPubkey) {
|
|
return false;
|
|
}
|
|
|
|
const photo = parsePhotoFromEvent(event);
|
|
if (!photo) return false;
|
|
|
|
entry.photo = photo;
|
|
entry.placeIdentifier = photo.placeIdentifier || null;
|
|
return true;
|
|
}
|