From 9b5a105f656857959bf95c291ecf8b859a81f5a7 Mon Sep 17 00:00:00 2001
From: MSG <59928086+MSghais@users.noreply.github.com>
Date: Tue, 28 May 2024 14:04:51 +0200
Subject: [PATCH] Feat comment Nostr (#123)
* add comment send & fetch + fix send note + fix hooks query tanstack with params
* fix eslint and unused var
* run yarn format
* import styles native
* only use pool for multi relays push + delete var
* run yarn lint:fix
---
JoyboyCommunity/README.md | 160 ++++++++------
JoyboyCommunity/src/context/NostrContext.tsx | 23 ++-
JoyboyCommunity/src/hooks/useNostr.tsx | 62 ++++--
.../src/shared/components/Comments.tsx | 195 +++++++++++-------
.../src/shared/components/PostDetails.tsx | 2 +-
.../src/shared/components/styled.ts | 18 ++
JoyboyCommunity/src/types/index.ts | 8 +-
JoyboyCommunity/src/utils/relay.ts | 5 +
JoyboyCommunity/tsconfig.json | 10 +-
README.md | 1 -
10 files changed, 313 insertions(+), 171 deletions(-)
create mode 100644 JoyboyCommunity/src/shared/components/styled.ts
diff --git a/JoyboyCommunity/README.md b/JoyboyCommunity/README.md
index e4f96132..ae48a3d4 100644
--- a/JoyboyCommunity/README.md
+++ b/JoyboyCommunity/README.md
@@ -1,61 +1,99 @@
-## Mobile
-
-Joyboy Mobile folder in React-native with Expo.
-Pick a good first issue with the mobile labels, and let's contribute and keep building cool things in Open-source.
-
-### Test
-
-To test the project, run:
-
-```bash
-cd JoyboyCommunity
-yarn install
-yarn start
-```
-
-Select Expo web, Android or IOS. You can scan it with Expo GO on your phone.
-
-### Screens of the mobile app
-Recent implementation PoC on React Native.
-Here is what we have on the mobile app on the first days, contributions welcome!
-Check the issues with the "mobile" labels.
-
-Done:
-- Create or import Nostr account
-- Feed for notes
-- See user page with notes
-- Create note on Nostr
-
-WIP:
-- Read Nostr tags content
-- View user page details: followers and follows, replies, likes, repost with all details of the users.
-- Note interactions: Replies, Likes, Report, Tips
-- My profile: Update my profile
-- Feed: Search, Trending fees, For you, Discover
-- DM with private message: NIP-17
-- Public chat: NIP-28
-
-[UI/UX proposal for video discussions](https://github.com/keep-starknet-strange/joyboy/discussions/48#discussion-6683225)
-
-[UI video discussions](https://t.me/JoyboyStarknet/206/397)
-
-
-Home page:
-
-
-
-Create Nostr account
-
-
-
-Feed by default:
-
-
-
-User feed with notes:
-
-
-
-My profile page: WIP
-
-
+## Mobile
+
+Joyboy Mobile folder in React-native with Expo.
+Pick a good first issue with the mobile labels, and let's contribute and keep building cool things in Open-source.
+
+### Test
+
+To test the project, run:
+
+```bash
+cd JoyboyCommunity
+yarn install
+yarn start
+```
+
+Select Expo web, Android or IOS. You can scan it with Expo GO on your phone.
+
+### Screens of the mobile app
+Recent implementation PoC on React Native.
+Here is what we have on the mobile app on the first days, contributions welcome!
+Check the issues with the "mobile" labels.
+
+Done:
+- Create or import Nostr account
+- Feed for notes
+- See user page with notes
+- Create note on Nostr
+
+WIP:
+- Read Nostr tags content
+- View user page details: followers and follows, replies, likes, repost with all details of the users.
+- Note interactions: Replies, Likes, Report, Tips
+- My profile: Update my profile
+- Feed: Search, Trending fees, For you, Discover
+- DM with private message: NIP-17
+- Public chat: NIP-28
+
+[UI/UX proposal for video discussions](https://github.com/keep-starknet-strange/joyboy/discussions/48#discussion-6683225)
+
+[UI video discussions](https://t.me/JoyboyStarknet/206/397)
+
+
+Home page:
+
+
+
+Create Nostr account
+
+
+
+Feed by default:
+
+
+
+User feed with notes:
+
+
+
+My profile page: WIP
+
+
+
+
+## How Nostr work
+
+- [Nostr Implementation Possibilities](https://github.com/nostr-protocol/nips)
+- [JoinStr: Decentralized CoinJoin Implementation Using Nostr](https://www.nobsbitcoin.com/joinstr-decentralized-coinjoin-implementation-using-nostr/)
+
+### NIP-1
+Nostr event are described like this on NIP-1:
+
+```ts
+export interface Event {
+ kind: number; // 1 for note, 0 for profile,
+ tags: string[][]; // NIP-10 for comment root and reply
+ //
+ content: string;
+ created_at: number;
+ pubkey: string;
+ id: string;
+ sig: string;
+ [verifiedSymbol]?: boolean;
+}
+```
+
+### NIP-10
+- [NIP-10](https://github.com/nostr-protocol/nips/blob/master/10.md)
+
+```ts
+/** On the Tags string[][] of the note, you can check if the note are:
+ * repost, quote, reply root, reply comment, or a simple note.
+ *
+ * filter for tags :
+ * ["e", "note_pubkey", "relay_url", "marker as reply | root | mention", "pubkey_author"]
+ * ["p", "id_note","p1_note_reply"]
+ *
+ */
+```
+
diff --git a/JoyboyCommunity/src/context/NostrContext.tsx b/JoyboyCommunity/src/context/NostrContext.tsx
index 4a1cb6ca..1e52274b 100644
--- a/JoyboyCommunity/src/context/NostrContext.tsx
+++ b/JoyboyCommunity/src/context/NostrContext.tsx
@@ -1,12 +1,15 @@
import NDK, {NDKNip07Signer} from '@nostr-dev-kit/ndk';
-import {SimplePool} from 'nostr-tools';
+import {Relay, SimplePool} from 'nostr-tools';
import {createContext, useContext, useMemo} from 'react';
-import {RELAYS_PROD} from '../utils/relay';
+import {JOYBOY_RELAYS, RELAYS_PROD} from '../utils/relay';
export type NostrContextType = {
pool: SimplePool;
relays: string[];
+ relayJoyboy?: Relay;
+ othersRelays?: string[];
+ connectRelayJoyboy: () => Promise;
ndk: NDK;
};
@@ -14,14 +17,24 @@ export const NostrContext = createContext(null);
export const NostrProvider: React.FC = ({children}) => {
const pool = useMemo(() => new SimplePool(), []);
- const relays = RELAYS_PROD;
-
+ const relays = JOYBOY_RELAYS;
+ const othersRelays = RELAYS_PROD;
+
+ /** This Websocket connection to the Relay need to be closed after each utilization*/
+ const connectRelayJoyboy = async () => {
+ const relayJoyboy = await Relay.connect(JOYBOY_RELAYS[0]);
+ return relayJoyboy;
+ };
const ndk = useMemo(() => {
const nip07signer = new NDKNip07Signer();
return new NDK({signer: nip07signer});
}, []);
- return {children};
+ return (
+
+ {children}
+
+ );
};
export const useNostrContext = () => {
diff --git a/JoyboyCommunity/src/hooks/useNostr.tsx b/JoyboyCommunity/src/hooks/useNostr.tsx
index 86aedc16..264b2ab9 100644
--- a/JoyboyCommunity/src/hooks/useNostr.tsx
+++ b/JoyboyCommunity/src/hooks/useNostr.tsx
@@ -1,9 +1,10 @@
import {useMutation, useQuery, useQueryClient} from '@tanstack/react-query';
-import {Event as EventNostr} from 'nostr-tools';
+import {Event as EventNostr, SimplePool} from 'nostr-tools';
import {finalizeEvent, NostrEvent, parseReferences, VerifiedEvent, verifyEvent} from 'nostr-tools';
import {useNostrContext} from '../context/NostrContext';
import {IPoolEventsByQuery, IPoolEventsFromPubkey, ISendNotePayload, IUserQuery} from '../types';
+import {JOYBOY_RELAYS} from '../utils/relay';
export const useGetPoolEventById = (id: string) => {
const {pool, relays} = useNostrContext();
@@ -73,16 +74,41 @@ export const useGetPoolUserQuery = ({id = '0', ...query}: IUserQuery) => {
});
};
-export const useGetPoolEventsByQuery = ({ids = ['1', '3'], ...query}: IPoolEventsByQuery) => {
- const {pool, relays} = useNostrContext();
+export const useGetPoolEventsByQuery = ({
+ ids = ['1', '3'],
+ kinds = [1],
+ filter,
+ pool = new SimplePool(),
+ relaysToUsed,
+}: IPoolEventsByQuery) => {
+ return useQuery({
+ queryFn: () =>
+ pool?.querySync(relaysToUsed, {
+ ids,
+ kinds,
+ ...filter,
+ }),
+ queryKey: ['getPoolEventsByQuery', ids, kinds, filter, relaysToUsed, pool],
+ placeholderData: [],
+ });
+};
+export const useGetPoolEventsTagsByQuery = ({
+ ids,
+ kinds = [1],
+ filter,
+ pool = new SimplePool(),
+ relaysToUsed,
+}: IPoolEventsByQuery) => {
return useQuery({
queryFn: () =>
- pool.querySync(query.relaysProps ?? relays, {
+ pool?.querySync(relaysToUsed, {
ids,
- ...query.filter,
+ kinds,
+ ...filter,
}),
- queryKey: ['getPoolEventsByQuery', query.relaysProps, query.filter, ids],
+ enabled: !!filter?.search,
+ queryKey: ['getPoolEventsByQuery', ids, kinds, filter, pool, relaysToUsed],
placeholderData: [],
});
};
@@ -110,22 +136,28 @@ export const useGetUser = (pubkey: string) => {
});
};
+/** Check if a note is valid and publish not on relayer if event valid */
export const useSendNote = () => {
const queryClient = useQueryClient();
+ const {pool, relays, connectRelayJoyboy} = useNostrContext();
return useMutation({
- mutationFn: sendNote,
- onSuccess(data) {
+ mutationFn: isValidSendNote,
+ async onSuccess(data) {
queryClient.invalidateQueries({
queryKey: [''],
});
+ const event = data?.event;
+ /** @TODO add option to send note on different relays and add list of relays */
+ await pool.publish(JOYBOY_RELAYS, event);
+ return data;
},
});
};
// FUNCTIONS
-export const sendNote = async ({
+export const isValidSendNote = async ({
content,
sk,
tags,
@@ -147,17 +179,17 @@ export const sendNote = async ({
const isGood = verifyEvent(event);
- if (isGood) {
- return {
- event,
- isValid: true,
- };
- } else {
+ if (!isGood) {
return {
event,
isValid: false,
};
}
+
+ return {
+ event,
+ isValid: true,
+ };
} catch (e) {
console.log('issue sendNote', e);
return {
diff --git a/JoyboyCommunity/src/shared/components/Comments.tsx b/JoyboyCommunity/src/shared/components/Comments.tsx
index 2e4fdda1..0163d0da 100644
--- a/JoyboyCommunity/src/shared/components/Comments.tsx
+++ b/JoyboyCommunity/src/shared/components/Comments.tsx
@@ -1,91 +1,126 @@
-import React from 'react';
-import {FlatList, View} from 'react-native';
+import {MaterialCommunityIcons} from '@expo/vector-icons';
+import {Event as EventNostr} from 'nostr-tools';
+import React, {useCallback, useEffect, useState} from 'react';
+import {ActivityIndicator, FlatList, View} from 'react-native';
+import {Input} from '../../components';
+import {useNostrContext} from '../../context/NostrContext';
+import {useGetPoolEventsTagsByQuery, useSendNote} from '../../hooks/useNostr';
+import {useAuth} from '../../store/auth';
import PostComment from './PostComment';
+import {SendComment, ViewSendComment} from './styled';
-const mockEvents = [
- {
- post: {
- content: 'mock content',
- author: 'Roronoa zoro',
+interface IComments {
+ event?: EventNostr;
+}
+function Comments({event}: IComments) {
+ const [text, setText] = useState();
+ const {privateKey, publicKey} = useAuth();
+ const {pool, relays} = useNostrContext();
+ const [tags, setTags] = useState([['e', event?.id]]);
- id: '1',
- created_at: '2021-09-01T00:00:00Z',
- pubkey: '1234567890',
- },
- event: {
- content: 'Lets say that One Piece is awesome',
- created_at: 1680638952,
- id: '12e701f23d365c558e7aa1a6f75f0477fb8593d4ad8421c4b326f8193ed42fc7',
- kind: 1,
- pubkey: '49e2566f8b1ef0da8aeb89865a862ead30dd5e1fc1e540edebf980a04fe9e853',
- sig: 'c931395c5db7538d159d6012115b0db9b1d9a5ada52132437cddc1790982727555d0d618f109fce372f0ca0ae33d29a8d3ffae1af879e278fc2ce3c6d123c658',
- tags: [
- ['e', 'a8de2eb8a069fecd33246cd921124541d6242ea76ee85c38bf45ee9b5fb3feb5'],
- ['p', '1989034e56b8f606c724f45a12ce84a11841621aaf7182a1f6564380b9c4276b'],
- ],
- },
- sourceUser: null,
- },
- {
- post: {
- content: 'mock content',
- author: 'Roronoa zoro',
+ const [comments, setComments] = useState([]);
+ const sendNote = useSendNote();
- id: '1',
- created_at: '2021-09-01T00:00:00Z',
- pubkey: '1234567890',
+ const {
+ data: poolEventNotesData,
+ isLoading: poolEventNotesDataLoading,
+ refetch,
+ } = useGetPoolEventsTagsByQuery({
+ // ids: ['1'],
+ kinds: [1],
+ filter: {
+ '#e': tags[0],
},
- event: {
- content: 'What about Naruto?',
- created_at: 1680638952,
- id: '12e701f23d365c558e7aa1a6f75f0477fb8593d4ad8421c4b326f8193ed42fc7',
- kind: 1,
- pubkey: '49e2566f8b1ef0da8aeb89865a862ead30dd5e1fc1e540edebf980a04fe9e853',
- sig: 'c931395c5db7538d159d6012115b0db9b1d9a5ada52132437cddc1790982727555d0d618f109fce372f0ca0ae33d29a8d3ffae1af879e278fc2ce3c6d123c658',
- tags: [
- ['e', 'a8de2eb8a069fecd33246cd921124541d6242ea76ee85c38bf45ee9b5fb3feb5'],
- ['p', '1989034e56b8f606c724f45a12ce84a11841621aaf7182a1f6564380b9c4276b'],
- ],
- },
- sourceUser: null,
- },
- {
- post: {
- content: 'mock content',
- author: 'Roronoa zoro',
+ pool,
+ relaysToUsed: relays,
+ });
- id: '1',
- created_at: '2021-09-01T00:00:00Z',
- pubkey: '1234567890',
- },
- event: {
- content: 'Yeaaaah Naruto is sick',
- created_at: 1680638952,
- id: '12e701f23d365c558e7aa1a6f75f0477fb8593d4ad8421c4b326f8193ed42fc7',
- kind: 1,
- pubkey: '49e2566f8b1ef0da8aeb89865a862ead30dd5e1fc1e540edebf980a04fe9e853',
- sig: 'c931395c5db7538d159d6012115b0db9b1d9a5ada52132437cddc1790982727555d0d618f109fce372f0ca0ae33d29a8d3ffae1af879e278fc2ce3c6d123c658',
- tags: [
- ['e', 'a8de2eb8a069fecd33246cd921124541d6242ea76ee85c38bf45ee9b5fb3feb5'],
- ['p', '1989034e56b8f606c724f45a12ce84a11841621aaf7182a1f6564380b9c4276b'],
- ],
- },
- sourceUser: null,
- },
-];
-function Comments() {
+ useEffect(() => {
+ const fetchReplies = async () => {
+ if (event?.id) {
+ setTags([['e', event?.id]]);
+ setComments(poolEventNotesData);
+ }
+ };
+ fetchReplies();
+ }, [event, poolEventNotesData]);
+ /** @TODO handle send comment */
+
+ const handleSendComment = useCallback(async () => {
+ try {
+ if (!privateKey) {
+ alert('Please login before send a note');
+ return;
+ }
+
+ if (!text || text?.length == 0) {
+ alert('Write your note');
+ return;
+ }
+
+ /** @TODO handle tags NIP-10 */
+ /** tags */
+ // let tags = [['e', event?.id, JOYBOY_RELAYS[0], 'root', publicKey]];
+ const tags = [['e', event?.id, '', 'root', publicKey]];
+ alert('Note sending, please wait.');
+
+ sendNote.mutate(
+ {sk: privateKey, content: text, tags},
+ {
+ async onSuccess(data) {
+ if (data.isValid) {
+ alert('Note sent');
+ }
+ /** Refetch comment */
+ await refetch();
+ },
+ onError(error) {
+ console.log('Error send note', error);
+ },
+ },
+ );
+ } catch (e) {
+ console.log('Error send note', e);
+ }
+ }, [text, privateKey]);
+ const isCreateDisabled = text && text?.length > 0 ? false : true;
return (
- }
- renderItem={({item}) => {
- return ;
- }}
- />
+
+
+
+
+
+
+
+
+ {poolEventNotesDataLoading && }
+
+ }
+ renderItem={({item}) => {
+ return ;
+ // return ;
+ }}
+ />
+
);
}
diff --git a/JoyboyCommunity/src/shared/components/PostDetails.tsx b/JoyboyCommunity/src/shared/components/PostDetails.tsx
index e6debff2..a00f05ba 100644
--- a/JoyboyCommunity/src/shared/components/PostDetails.tsx
+++ b/JoyboyCommunity/src/shared/components/PostDetails.tsx
@@ -128,7 +128,7 @@ function PostDetails({route}) {
-
+
);
diff --git a/JoyboyCommunity/src/shared/components/styled.ts b/JoyboyCommunity/src/shared/components/styled.ts
new file mode 100644
index 00000000..279cc922
--- /dev/null
+++ b/JoyboyCommunity/src/shared/components/styled.ts
@@ -0,0 +1,18 @@
+import {Pressable, View} from 'react-native';
+import styled from 'styled-components/native';
+
+export const ViewSendComment = styled(View)`
+ flex-direction: row;
+ align-items: baseline;
+ border-radius: 8px;
+ padding: 8px 24px;
+ border-color: black;
+ color: white;
+`;
+
+export const SendComment = styled(Pressable)`
+ border-radius: 8px;
+ padding: 8px 24px;
+ border-color: black;
+ color: white;
+`;
diff --git a/JoyboyCommunity/src/types/index.ts b/JoyboyCommunity/src/types/index.ts
index 294ef111..4533f385 100644
--- a/JoyboyCommunity/src/types/index.ts
+++ b/JoyboyCommunity/src/types/index.ts
@@ -1,4 +1,4 @@
-import {Event as EventNostr, Filter} from 'nostr-tools';
+import {Event as EventNostr, Filter, SimplePool} from 'nostr-tools';
export * from './post';
export * from './routes';
@@ -54,9 +54,11 @@ export interface IPoolEventsFromPubkey {
}
export interface IPoolEventsByQuery {
- ids: string[];
+ ids?: string[];
+ kinds?: number[];
filter?: Filter;
- relaysProps?: string[];
+ relaysToUsed?: string[];
+ pool?: SimplePool;
}
export interface IUserQuery {
diff --git a/JoyboyCommunity/src/utils/relay.ts b/JoyboyCommunity/src/utils/relay.ts
index 7f01ad45..9ac9a04e 100644
--- a/JoyboyCommunity/src/utils/relay.ts
+++ b/JoyboyCommunity/src/utils/relay.ts
@@ -1 +1,6 @@
export const RELAYS_PROD = ['wss://relay.n057r.club', 'wss://relay.nostr.net'];
+
+export const JOYBOY_RELAYS = [
+ 'wss://nostr.joyboy.community',
+ 'ws://localhost:3000', // comment if you don't run a relayer in localhost
+];
diff --git a/JoyboyCommunity/tsconfig.json b/JoyboyCommunity/tsconfig.json
index 73507d54..12f15398 100644
--- a/JoyboyCommunity/tsconfig.json
+++ b/JoyboyCommunity/tsconfig.json
@@ -1,5 +1,5 @@
-{
- "extends": "expo/tsconfig.base",
- "compilerOptions": {},
- "include": ["**/*.ts", "**/*.tsx", "**/*.js", "**/*.jsx"]
-}
+{
+ "extends": "expo/tsconfig.base",
+ "compilerOptions": {},
+ "include": ["**/*.ts", "**/*.tsx", "**/*.js", "**/*.jsx"]
+}
diff --git a/README.md b/README.md
index 0dca754f..b0ea2feb 100644
--- a/README.md
+++ b/README.md
@@ -156,7 +156,6 @@ Here are some work already merge and available for test:
1. Home page:
-[Home page onboard](../resources/screens/onboard.png)
2. Create Nostr account