feat: zap (#107)
This commit is contained in:
@@ -1,3 +1,7 @@
|
||||
export function isTouchDevice() {
|
||||
return 'ontouchstart' in window || navigator.maxTouchPoints > 0
|
||||
}
|
||||
|
||||
export function isEmail(email: string) {
|
||||
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)
|
||||
}
|
||||
|
||||
@@ -3,11 +3,13 @@ import client from '@/services/client.service'
|
||||
import { TImageInfo, TRelayList } from '@/types'
|
||||
import { LRUCache } from 'lru-cache'
|
||||
import { Event, kinds, nip19 } from 'nostr-tools'
|
||||
import { getAmountFromInvoice, getLightningAddressFromProfile } from './lightning'
|
||||
import { formatPubkey } from './pubkey'
|
||||
import { extractImageInfoFromTag, isReplyETag, isRootETag, tagNameEquals } from './tag'
|
||||
import { isWebsocketUrl, normalizeHttpUrl, normalizeUrl } from './url'
|
||||
|
||||
const EVENT_EMBEDDED_EVENT_IDS_CACHE = new LRUCache<string, string[]>({ max: 10000 })
|
||||
const EVENT_IS_REPLY_NOTE_CACHE = new LRUCache<string, boolean>({ max: 10000 })
|
||||
|
||||
export function isNsfwEvent(event: Event) {
|
||||
return event.tags.some(
|
||||
@@ -19,15 +21,23 @@ export function isNsfwEvent(event: Event) {
|
||||
export function isReplyNoteEvent(event: Event) {
|
||||
if (event.kind !== kinds.ShortTextNote) return false
|
||||
|
||||
const cache = EVENT_IS_REPLY_NOTE_CACHE.get(event.id)
|
||||
if (cache !== undefined) return cache
|
||||
|
||||
const mentionsEventIds: string[] = []
|
||||
for (const [tagName, eventId, , marker] of event.tags) {
|
||||
if (tagName !== 'e' || !eventId) continue
|
||||
|
||||
mentionsEventIds.push(eventId)
|
||||
if (['root', 'reply'].includes(marker)) return true
|
||||
if (['root', 'reply'].includes(marker)) {
|
||||
EVENT_IS_REPLY_NOTE_CACHE.set(event.id, true)
|
||||
return true
|
||||
}
|
||||
}
|
||||
const embeddedEventIds = extractEmbeddedEventIds(event)
|
||||
return mentionsEventIds.some((id) => !embeddedEventIds.includes(id))
|
||||
const result = mentionsEventIds.some((id) => !embeddedEventIds.includes(id))
|
||||
EVENT_IS_REPLY_NOTE_CACHE.set(event.id, result)
|
||||
return result
|
||||
}
|
||||
|
||||
export function isCommentEvent(event: Event) {
|
||||
@@ -159,6 +169,9 @@ export function getProfileFromProfileEvent(event: Event) {
|
||||
nip05: profileObj.nip05,
|
||||
about: profileObj.about,
|
||||
website: profileObj.website ? normalizeHttpUrl(profileObj.website) : undefined,
|
||||
lud06: profileObj.lud06,
|
||||
lud16: profileObj.lud16,
|
||||
lightningAddress: getLightningAddressFromProfile(profileObj),
|
||||
created_at: event.created_at
|
||||
}
|
||||
} catch (err) {
|
||||
@@ -363,6 +376,68 @@ export function extractEmbeddedNotesFromContent(content: string) {
|
||||
return { embeddedNotes, contentWithoutEmbeddedNotes: c }
|
||||
}
|
||||
|
||||
export function extractZapInfoFromReceipt(receiptEvent: Event) {
|
||||
if (receiptEvent.kind !== kinds.Zap) return null
|
||||
|
||||
let senderPubkey: string | undefined
|
||||
let recipientPubkey: string | undefined
|
||||
let eventId: string | undefined
|
||||
let invoice: string | undefined
|
||||
let amount: number | undefined
|
||||
let comment: string | undefined
|
||||
let description: string | undefined
|
||||
let preimage: string | undefined
|
||||
try {
|
||||
receiptEvent.tags.forEach(([tagName, tagValue]) => {
|
||||
switch (tagName) {
|
||||
case 'P':
|
||||
senderPubkey = tagValue
|
||||
break
|
||||
case 'p':
|
||||
recipientPubkey = tagValue
|
||||
break
|
||||
case 'e':
|
||||
eventId = tagValue
|
||||
break
|
||||
case 'bolt11':
|
||||
invoice = tagValue
|
||||
break
|
||||
case 'description':
|
||||
description = tagValue
|
||||
break
|
||||
case 'preimage':
|
||||
preimage = tagValue
|
||||
break
|
||||
}
|
||||
})
|
||||
if (!recipientPubkey || !invoice) return null
|
||||
amount = invoice ? getAmountFromInvoice(invoice) : 0
|
||||
if (description) {
|
||||
try {
|
||||
const zapRequest = JSON.parse(description)
|
||||
comment = zapRequest.content
|
||||
if (!senderPubkey) {
|
||||
senderPubkey = zapRequest.pubkey
|
||||
}
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
senderPubkey,
|
||||
recipientPubkey,
|
||||
eventId,
|
||||
invoice,
|
||||
amount,
|
||||
comment,
|
||||
preimage
|
||||
}
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
export function extractEmbeddedEventIds(event: Event) {
|
||||
const cache = EVENT_EMBEDDED_EVENT_IDS_CACHE.get(event.id)
|
||||
if (cache) return cache
|
||||
|
||||
32
src/lib/lightning.ts
Normal file
32
src/lib/lightning.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import { TProfile } from '@/types'
|
||||
import { Invoice } from '@getalby/lightning-tools'
|
||||
import { isEmail } from './common'
|
||||
|
||||
export function getAmountFromInvoice(invoice: string): number {
|
||||
const _invoice = new Invoice({ pr: invoice }) // TODO: need to validate
|
||||
return _invoice.satoshi
|
||||
}
|
||||
|
||||
export function formatAmount(amount: number) {
|
||||
if (amount < 1000) return amount
|
||||
if (amount < 1000000) return `${Math.round(amount / 100) / 10}k`
|
||||
return `${Math.round(amount / 100000) / 10}M`
|
||||
}
|
||||
|
||||
export function getLightningAddressFromProfile(profile: TProfile) {
|
||||
// Some clients have incorrectly filled in the positions for lud06 and lud16
|
||||
const { lud16: a, lud06: b } = profile
|
||||
let lud16: string | undefined
|
||||
let lud06: string | undefined
|
||||
if (a && isEmail(a)) {
|
||||
lud16 = a
|
||||
} else if (b && isEmail(b)) {
|
||||
lud16 = b
|
||||
} else if (b && b.startsWith('lnurl')) {
|
||||
lud06 = b
|
||||
} else if (a && a.startsWith('lnurl')) {
|
||||
lud06 = a
|
||||
}
|
||||
|
||||
return lud16 || lud06 || undefined
|
||||
}
|
||||
@@ -39,6 +39,7 @@ export const toRelaySettings = (tag?: 'mailbox' | 'relay-sets') => {
|
||||
return '/relay-settings' + (tag ? '#' + tag : '')
|
||||
}
|
||||
export const toSettings = () => '/settings'
|
||||
export const toWallet = () => '/wallet'
|
||||
export const toProfileEditor = () => '/profile-editor'
|
||||
export const toRelay = (url: string) => `/relays/${encodeURIComponent(url)}`
|
||||
export const toMuteList = () => '/mutes'
|
||||
|
||||
Reference in New Issue
Block a user