diff --git a/src/components/app-body/nostr/feeds/feed-card.js b/src/components/app-body/nostr/feeds/feed-card.js index b2560fd..defcffa 100644 --- a/src/components/app-body/nostr/feeds/feed-card.js +++ b/src/components/app-body/nostr/feeds/feed-card.js @@ -5,22 +5,87 @@ import React, { useCallback, useEffect, useState } from 'react' import { Card, Spinner } from 'react-bootstrap' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' -import { faUser } from '@fortawesome/free-solid-svg-icons' +import { faUser, faHeart as faHeartSolid } from '@fortawesome/free-solid-svg-icons' +import { faHeart } from '@fortawesome/free-regular-svg-icons' import * as nip19 from 'nostr-tools/nip19' import NostrFormat from '../nostr-format' +import { finalizeEvent } from 'nostr-tools/pure' +import { Relay } from 'nostr-tools/relay' +import { hexToBytes } from '@noble/hashes/utils' // already an installed dependency +import { RelayPool } from 'nostr' function FeedCard (props) { const { post, appData, profiles } = props + const { nostrKeyPair } = appData.bchWalletState + const [profile, setProfile] = useState(profiles[post.pubkey]) const [npub, setNpub] = useState('') const [isClicked, setIsClicked] = useState(false) + const [isLiked, setIsLiked] = useState(false) + const [likesCount, setLikesCount] = useState(null) + const [likesFetched, setLikesFetched] = useState(false) + // function to fetch a post likes reaction + const handleLikes = useCallback(async (post, userPubKey) => { + try { + const psf = 'wss://nostr-relay.psfoundation.info' + const res = await new Promise((resolve) => { + let likesCnt = 0 + let userLikedCnt = 0 + const pool = RelayPool([psf]) + pool.on('open', relay => { + relay.subscribe('subid', { kinds: [7], '#e': [post.id] }) + }) + + pool.on('eose', relay => { + relay.close() + resolve({ + count: likesCnt, + userLiked: userLikedCnt > 0 + }) + }) + + pool.on('event', (relay, subId, ev) => { + try { + // Count likes + if (ev.content === '+') { + likesCnt++ + // Count user likes + if (ev.pubkey === userPubKey) { + userLikedCnt++ + } + } + // Count dislikes + if (ev.content === '-') { + likesCnt-- + // Count user dislikes. + if (ev.pubkey === userPubKey) { + userLikedCnt-- + } + } + } catch (error) { + // skip error + } + }) + }) + setLikesCount(res.count) + setIsLiked(res.userLiked) + setLikesFetched(true) + } catch (error) { + console.warn(error) + } + }, []) // Get npub from pubkey useEffect(() => { const npub = nip19.npubEncode(post.pubkey) setNpub(npub) - }, [post]) + + // Handle post likes + if (!likesFetched && nostrKeyPair.pubHex) { + handleLikes(post, nostrKeyPair.pubHex) + } + }, [post, nostrKeyPair, handleLikes, likesFetched]) // Verify if pubkey profile exists into the profiles state useEffect(() => { @@ -41,6 +106,58 @@ function FeedCard (props) { return npub.slice(0, 8) + '...' + npub.slice(-5) }, []) + const submitLike = async (e) => { + // Post on nostr network + e.preventDefault() + try { + const { nostrKeyPair } = appData.bchWalletState + + setLikesFetched(false) + // Convert private key to binary + const privateKeyBin = hexToBytes(nostrKeyPair.privHex) + + // Relay list + const psf = 'wss://nostr-relay.psfoundation.info' + + // Define if like or dislike + let content = '+' + if (isLiked) { content = '-' } + + // Generate a post. + const eventTemplate = { + kind: 7, + created_at: Math.floor(Date.now() / 1000), + tags: [ + ['e', post.id, psf], + ['p', psf, nostrKeyPair.pubHex] + ], + content + } + console.log(`eventTemplate: ${JSON.stringify(eventTemplate, null, 2)}`) + + // Sign the post + const signedEvent = finalizeEvent(eventTemplate, privateKeyBin) + console.log('signedEvent: ', signedEvent) + + // Connect to a relay. + const relay = await Relay.connect(psf) + console.log(`connected to ${relay.url}`) + + // Publish the message to the relay. + const result = await relay.publish(signedEvent) + console.log('result: ', result) + + // Close the connection to the relay. + relay.close() + + await handleLikes(post, nostrKeyPair.pubHex) + setLikesFetched(true) + } catch (error) { + console.warn(error) + setLikesFetched(true) + } + } + return ( @@ -91,6 +208,50 @@ function FeedCard (props) { +
+ {likesFetched && ( + + )} + {!likesFetched && ( + + )} +