Add support for zaps on profile

This commit is contained in:
SondreB 2023-03-24 11:02:29 +01:00
parent 7451f91051
commit b04e11dbf7
No known key found for this signature in database
GPG Key ID: D6CC44C75005FDBF
7 changed files with 139 additions and 23 deletions

View File

@ -17,6 +17,7 @@ import { NostrService } from './nostr';
import { ArticleService } from './article';
import { LoggerService } from './logger';
import { BadgeService } from './badge';
import { ZapUiService } from './zap-ui';
@Injectable({
providedIn: 'root',
@ -55,7 +56,8 @@ export class RelayService {
private db: StorageService,
private options: OptionsService,
private eventService: EventService,
private appState: ApplicationState
private appState: ApplicationState,
private zapUi: ZapUiService
) {
// Whenever the visibility becomes visible, run connect to ensure we're connected to the relays.
this.appState.visibility$.subscribe((visible) => {
@ -85,6 +87,11 @@ export class RelayService {
// Then create a new subscription:
const kinds = this.options.values.enableReactions ? [Kind.Text, Kind.Reaction, 6] : [Kind.Text, 6];
if (this.options.values.enableZapping) {
kinds.push(Kind.Zap);
}
this.profileEventSubscription = this.subscribe([{ authors: [this.ui.profile!.pubkey], kinds: kinds, until: options.until, limit: 100 }]).id;
} else if (options.type == 'feed') {
// If there are no subscription yet, just skip load more.
@ -108,6 +115,11 @@ export class RelayService {
// Then create a new subscription:
const kinds = this.options.values.enableReactions ? [Kind.Text, Kind.Reaction, 6] : [Kind.Text, 6];
if (this.options.values.enableZapping) {
kinds.push(Kind.Zap);
}
this.circleEventSubscription = this.subscribe([{ authors: pubkeys, kinds: kinds, until: options.until, limit: 100 }], 'feed').id;
}
});
@ -132,6 +144,11 @@ export class RelayService {
}
const kinds = this.options.values.enableReactions ? [Kind.Text, Kind.Reaction, 6] : [Kind.Text, 6];
if (this.options.values.enableZapping) {
kinds.push(Kind.Zap);
}
this.circleEventSubscription = this.subscribe([{ authors: pubkeys, kinds: kinds, limit: 100 }], 'feed').id;
});
@ -153,12 +170,16 @@ export class RelayService {
this.ui.setProfile(profile);
}
this.zapUi.reset();
// else {
// this.enque({ type: 'Profile', identifier: id });
// }
// Subscribe to events for the current user profile.
this.profileEventSubscription = this.subscribe([{ authors: [id], kinds: [Kind.Text, Kind.Reaction, 6], limit: 100 }]).id;
// Subscribe to events and zaps (received) for the current user profile.
this.profileEventSubscription = this.subscribe([
{ ['#p']: [id], kinds: [Kind.Zap] },
{ authors: [id], kinds: [Kind.Text, Kind.Reaction, 6], limit: 100 },
]).id;
});
// Whenever the event ID changes, we'll attempt to load the event.
@ -396,9 +417,12 @@ export class RelayService {
this.logger.debug('SAVE EVENT?:', event);
if (event.kind == Kind.Zap) {
this.zapUi.addZap(event);
}
if (response.subscription) {
const sub = this.subs.get(response.subscription);
if (sub) {
if (sub.type == 'Event') {
const index = sub.events.findIndex((e) => e.id == event.id);

View File

@ -13,7 +13,7 @@ import { ZapService } from './zap.service';
})
/** The orchestrator for UI that holds data to be rendered in different views at any given time. */
export class UIService {
constructor(private eventService: EventService, private options: OptionsService, private zapService: ZapService) { }
constructor(private eventService: EventService, private options: OptionsService, private zapService: ZapService) {}
#lists = {
feedEvents: [] as NostrEventDocument[],
@ -617,9 +617,9 @@ export class UIService {
entry.reactionIds.push(event.id!);
const parsedZap = this.zapService.parseZap(event);
entry.zaps != undefined ? entry.zaps.push(parsedZap) : entry.zaps = [parsedZap];
entry.zaps != undefined ? entry.zaps.push(parsedZap) : (entry.zaps = [parsedZap]);
this.putThreadEntry(entry)
this.putThreadEntry(entry);
}
}
}

View File

@ -0,0 +1,89 @@
import { Injectable } from '@angular/core';
import { NostrEventDocument, Zapper, ParsedZap, NostrEvent } from './interfaces';
import { decode } from 'light-bolt11-decoder';
import { sha256 } from '@noble/hashes/sha256';
import { Utilities } from './utilities';
import { ZapService } from './zap.service';
import { EventService } from './event';
interface ZapStatus {
ids: string[];
amount: number;
}
@Injectable({
providedIn: 'root',
})
export class ZapUiService {
events = new Map<string, ZapStatus>();
profiles = new Map<string, ZapStatus>();
constructor(private zapService: ZapService, private eventService: EventService) {}
reset() {
this.events.clear();
this.profiles.clear();
}
getEventZapAmount(eventId: string) {
const zapStatus = this.events.get(eventId);
if (!zapStatus) {
return null;
}
return zapStatus.amount;
}
getProfileZapAmount(pubkey: string) {
const zapStatus = this.profiles.get(pubkey);
if (!zapStatus) {
return null;
}
return zapStatus.amount;
}
addZap(event: NostrEvent) {
const parsedZap = this.zapService.parseZap(event);
if (parsedZap.e) {
let eventMap = this.events.get(parsedZap.e);
if (!eventMap) {
eventMap = {
ids: [parsedZap.id],
amount: parsedZap.amount,
};
} else {
if (eventMap.ids.includes(parsedZap.id)) {
return;
}
eventMap.ids.push(parsedZap.id);
eventMap.amount += parsedZap.amount;
}
this.events.set(parsedZap.e, eventMap);
}
let profileMap = this.profiles.get(parsedZap.p);
if (!profileMap) {
profileMap = {
ids: [parsedZap.id],
amount: parsedZap.amount,
};
} else {
if (profileMap.ids.includes(parsedZap.id)) {
return;
}
profileMap.ids.push(parsedZap.id);
profileMap.amount += parsedZap.amount;
}
this.profiles.set(parsedZap.p, profileMap);
}
}

View File

@ -13,8 +13,8 @@ export class ZapService {
parseZap(zapEvent: NostrEventDocument): ParsedZap {
const { amount, hash } = this.getInvoice(zapEvent);
const zapper = hash ? this.getZapper(zapEvent, hash) : ({ isValid: false, content: '' } as Zapper);
const e = this.zappedAuthor(zapEvent);
const p = this.zappedPost(zapEvent);
const p = this.zappedAuthor(zapEvent);
const e = this.zappedPost(zapEvent);
return {
id: zapEvent.id,

View File

@ -107,8 +107,9 @@
<p class="wrap linebreaks" *ngIf="profile.about">{{ profile.about }}</p>
</span>
<div class="following-counts" *ngIf="profile.following">
<a class="following-link" [routerLink]="['/following', profile.pubkey]">{{ profile.following.length }} <span class="dimmed">Following</span></a>
<div class="following-counts">
<a class="following-link" *ngIf="profile.following" [routerLink]="['/following', profile.pubkey]">{{ profile.following.length }} <span class="dimmed">Following</span></a>
&nbsp;&nbsp;⚡️{{ zapUi.getProfileZapAmount(profile.pubkey) }} <span class="dimmed">Sats</span>
</div>
<div>

View File

@ -11,6 +11,7 @@ import { UIService } from 'src/app/services/ui';
import { ApplicationState } from 'src/app/services/applicationstate';
import { nip05 } from 'nostr-tools';
import { ZapDialogComponent } from '../zap-dialog/zap-dialog.component';
import { ZapUiService } from 'src/app/services/zap-ui';
@Component({
selector: 'app-profile-header',
@ -29,9 +30,9 @@ export class ProfileHeaderComponent {
qr06?: string;
qr16?: string;
userPubKey: string;
isValidNip05: boolean = false
isValidNip05: boolean = false;
constructor(private appState: ApplicationState, public ui: UIService, public profileService: ProfileService, public dialog: MatDialog, public circleService: CircleService, public utilities: Utilities) {
constructor(public zapUi: ZapUiService, private appState: ApplicationState, public ui: UIService, public profileService: ProfileService, public dialog: MatDialog, public circleService: CircleService, public utilities: Utilities) {
this.userPubKey = this.appState.getPublicKey();
}
@ -115,13 +116,13 @@ export class ProfileHeaderComponent {
scale: 5,
});
}
if(profile.nip05) {
let nip05Data = null
if (profile.nip05) {
let nip05Data = null;
try {
nip05Data = await nip05.queryProfile(profile?.nip05)
this.isValidNip05 = nip05Data?.pubkey == profile?.pubkey
nip05Data = await nip05.queryProfile(profile?.nip05);
this.isValidNip05 = nip05Data?.pubkey == profile?.pubkey;
} catch (_) {
console.log('failed to fetch NIP05 identifier')
console.log('failed to fetch NIP05 identifier');
}
}
})
@ -154,9 +155,8 @@ export class ProfileHeaderComponent {
this.dialog.open(ZapDialogComponent, {
width: '400px',
data: {
profile: profile
profile: profile,
},
});
}
}

View File

@ -641,7 +641,9 @@ export class RelayWorker {
// return;
// }
const sub = this.relay.sub([{ kinds: [1], ids: ids }]) as NostrSub;
const kinds = [Kind.Text];
const sub = this.relay.sub([{ kinds: kinds, ids: ids }]) as NostrSub;
this.eventSub = sub;
sub.on('event', (originalEvent: any) => {