Compare commits
20 Commits
4cebcc8a25
...
edc01e028d
Author | SHA1 | Date | |
---|---|---|---|
edc01e028d | |||
16d39c407d | |||
b10920597f | |||
|
2dd261161e | ||
0e8eeb13d1 | |||
|
4bf18f1584 | ||
988cc03f37 | |||
|
ffc7b60363 | ||
|
545e6e6ec0 | ||
|
b0ebe7154a | ||
|
b69be4d755 | ||
04f32546f2 | |||
|
9aeee018b3 | ||
|
e3b6aecfe8 | ||
|
4214fe127f | ||
|
4bd7c77c05 | ||
|
82b87b3e32 | ||
|
9d50cdfd88 | ||
7f66c17f90 | |||
|
59f4fd6b29 |
@ -5,8 +5,7 @@ import { handleModImageError } from '../utils'
|
|||||||
import { ModDetails } from 'types'
|
import { ModDetails } from 'types'
|
||||||
import { getModPageRoute } from 'routes'
|
import { getModPageRoute } from 'routes'
|
||||||
import { kinds, nip19 } from 'nostr-tools'
|
import { kinds, nip19 } from 'nostr-tools'
|
||||||
import { useDidMount, useReactions } from 'hooks'
|
import { useDidMount, useNDKContext, useReactions } from 'hooks'
|
||||||
import { RelayController } from 'controllers'
|
|
||||||
import { toast } from 'react-toastify'
|
import { toast } from 'react-toastify'
|
||||||
import { useComments } from 'hooks/useComments'
|
import { useComments } from 'hooks/useComments'
|
||||||
|
|
||||||
@ -19,10 +18,10 @@ export const ModCard = React.memo((props: ModDetails) => {
|
|||||||
eTag: props.id,
|
eTag: props.id,
|
||||||
aTag: props.aTag
|
aTag: props.aTag
|
||||||
})
|
})
|
||||||
|
const { getTotalZapAmount } = useNDKContext()
|
||||||
|
|
||||||
useDidMount(() => {
|
useDidMount(() => {
|
||||||
RelayController.getInstance()
|
getTotalZapAmount(props.author, props.id, props.aTag)
|
||||||
.getTotalZapAmount(props.author, props.id, props.aTag)
|
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
setTotalZappedAmount(res.accumulatedZapAmount)
|
setTotalZappedAmount(res.accumulatedZapAmount)
|
||||||
})
|
})
|
||||||
@ -51,7 +50,13 @@ export const ModCard = React.memo((props: ModDetails) => {
|
|||||||
src={props.featuredImageUrl}
|
src={props.featuredImageUrl}
|
||||||
onError={handleModImageError}
|
onError={handleModImageError}
|
||||||
className='cMMPicture'
|
className='cMMPicture'
|
||||||
|
alt={`featured image for mod ${props.title}`}
|
||||||
/>
|
/>
|
||||||
|
{props.nsfw && (
|
||||||
|
<div className='IBMSMSMBSSTagsTag IBMSMSMBSSTagsTagNSFW IBMSMSMBSSTagsTagNSFWCard'>
|
||||||
|
<p>NSFW</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className='cMMBody'>
|
<div className='cMMBody'>
|
||||||
<h3 className='cMMBodyTitle'>{props.title}</h3>
|
<h3 className='cMMBodyTitle'>{props.title}</h3>
|
||||||
|
@ -13,8 +13,7 @@ import { toast } from 'react-toastify'
|
|||||||
import { FixedSizeList as List } from 'react-window'
|
import { FixedSizeList as List } from 'react-window'
|
||||||
import { v4 as uuidv4 } from 'uuid'
|
import { v4 as uuidv4 } from 'uuid'
|
||||||
import { T_TAG_VALUE } from '../constants'
|
import { T_TAG_VALUE } from '../constants'
|
||||||
import { RelayController } from '../controllers'
|
import { useAppSelector, useGames, useNDKContext } from '../hooks'
|
||||||
import { useAppSelector, useGames } from '../hooks'
|
|
||||||
import { appRoutes, getModPageRoute } from '../routes'
|
import { appRoutes, getModPageRoute } from '../routes'
|
||||||
import '../styles/styles.css'
|
import '../styles/styles.css'
|
||||||
import { DownloadUrl, ModDetails, ModFormState } from '../types'
|
import { DownloadUrl, ModDetails, ModFormState } from '../types'
|
||||||
@ -29,6 +28,7 @@ import {
|
|||||||
} from '../utils'
|
} from '../utils'
|
||||||
import { CheckboxField, InputError, InputField } from './Inputs'
|
import { CheckboxField, InputError, InputField } from './Inputs'
|
||||||
import { LoadingSpinner } from './LoadingSpinner'
|
import { LoadingSpinner } from './LoadingSpinner'
|
||||||
|
import { NDKEvent } from '@nostr-dev-kit/ndk'
|
||||||
|
|
||||||
interface FormErrors {
|
interface FormErrors {
|
||||||
game?: string
|
game?: string
|
||||||
@ -54,6 +54,7 @@ type ModFormProps = {
|
|||||||
export const ModForm = ({ existingModData }: ModFormProps) => {
|
export const ModForm = ({ existingModData }: ModFormProps) => {
|
||||||
const location = useLocation()
|
const location = useLocation()
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
|
const { ndk, publish } = useNDKContext()
|
||||||
const games = useGames()
|
const games = useGames()
|
||||||
const userState = useAppSelector((state) => state.user)
|
const userState = useAppSelector((state) => state.user)
|
||||||
|
|
||||||
@ -243,9 +244,8 @@ export const ModForm = ({ existingModData }: ModFormProps) => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const publishedOnRelays = await RelayController.getInstance().publish(
|
const ndkEvent = new NDKEvent(ndk, signedEvent)
|
||||||
signedEvent as Event
|
const publishedOnRelays = await publish(ndkEvent)
|
||||||
)
|
|
||||||
|
|
||||||
// Handle cases where publishing failed or succeeded
|
// Handle cases where publishing failed or succeeded
|
||||||
if (publishedOnRelays.length === 0) {
|
if (publishedOnRelays.length === 0) {
|
||||||
@ -763,8 +763,9 @@ const GameDropdown = ({
|
|||||||
<div className='inputLabelWrapperMain'>
|
<div className='inputLabelWrapperMain'>
|
||||||
<label className='form-label labelMain'>Game</label>
|
<label className='form-label labelMain'>Game</label>
|
||||||
<p className='labelDescriptionMain'>
|
<p className='labelDescriptionMain'>
|
||||||
Can't find the game you're looking for? You can temporarily publish the mod under '(Unlisted Game)' and
|
Can't find the game you're looking for? You can temporarily publish the
|
||||||
later edit it with the proper game name once we add it.
|
mod under '(Unlisted Game)' and later edit it with the proper game name
|
||||||
|
once we add it.
|
||||||
</p>
|
</p>
|
||||||
<div className='dropdown dropdownMain'>
|
<div className='dropdown dropdownMain'>
|
||||||
<div className='inputWrapperMain inputWrapperMainAlt'>
|
<div className='inputWrapperMain inputWrapperMainAlt'>
|
||||||
@ -827,8 +828,10 @@ const GameDropdown = ({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{error && <InputError message={error} />}
|
{error && <InputError message={error} />}
|
||||||
<p className='labelDescriptionMain'>Note: Please mention the game name in the body text of your mod post (e.g., 'This is a mod for Game Name')
|
<p className='labelDescriptionMain'>
|
||||||
so we know what to look for and add.
|
Note: Please mention the game name in the body text of your mod post
|
||||||
|
(e.g., 'This is a mod for Game Name') so we know what to look for and
|
||||||
|
add.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
@ -4,13 +4,17 @@ import { QRCodeSVG } from 'qrcode.react'
|
|||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { Link } from 'react-router-dom'
|
import { Link } from 'react-router-dom'
|
||||||
import { toast } from 'react-toastify'
|
import { toast } from 'react-toastify'
|
||||||
import { RelayController, UserRelaysType } from '../controllers'
|
import {
|
||||||
import { useAppSelector, useDidMount, useNDKContext } from '../hooks'
|
useAppSelector,
|
||||||
|
useBodyScrollDisable,
|
||||||
|
useDidMount,
|
||||||
|
useNDKContext
|
||||||
|
} from '../hooks'
|
||||||
import { appRoutes, getProfilePageRoute } from '../routes'
|
import { appRoutes, getProfilePageRoute } from '../routes'
|
||||||
import '../styles/author.css'
|
import '../styles/author.css'
|
||||||
import '../styles/innerPage.css'
|
import '../styles/innerPage.css'
|
||||||
import '../styles/socialPosts.css'
|
import '../styles/socialPosts.css'
|
||||||
import { UserProfile } from '../types'
|
import { UserProfile, UserRelaysType } from '../types'
|
||||||
import {
|
import {
|
||||||
copyTextToClipboard,
|
copyTextToClipboard,
|
||||||
hexToNpub,
|
hexToNpub,
|
||||||
@ -22,6 +26,7 @@ import {
|
|||||||
import { LoadingSpinner } from './LoadingSpinner'
|
import { LoadingSpinner } from './LoadingSpinner'
|
||||||
import { ZapPopUp } from './Zap'
|
import { ZapPopUp } from './Zap'
|
||||||
import placeholder from '../assets/img/DEGMods Placeholder Img.png'
|
import placeholder from '../assets/img/DEGMods Placeholder Img.png'
|
||||||
|
import { NDKEvent } from '@nostr-dev-kit/ndk'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
pubkey: string
|
pubkey: string
|
||||||
@ -254,6 +259,8 @@ export const ProfileQRButtonWithPopUp = ({
|
|||||||
}: QRButtonWithPopUpProps) => {
|
}: QRButtonWithPopUpProps) => {
|
||||||
const [isOpen, setIsOpen] = useState(false)
|
const [isOpen, setIsOpen] = useState(false)
|
||||||
|
|
||||||
|
useBodyScrollDisable(isOpen)
|
||||||
|
|
||||||
const nprofile = nip19.nprofileEncode({
|
const nprofile = nip19.nprofileEncode({
|
||||||
pubkey
|
pubkey
|
||||||
})
|
})
|
||||||
@ -335,6 +342,8 @@ type ZapButtonWithPopUpProps = {
|
|||||||
const ZapButtonWithPopUp = ({ pubkey }: ZapButtonWithPopUpProps) => {
|
const ZapButtonWithPopUp = ({ pubkey }: ZapButtonWithPopUpProps) => {
|
||||||
const [isOpen, setIsOpen] = useState(false)
|
const [isOpen, setIsOpen] = useState(false)
|
||||||
|
|
||||||
|
useBodyScrollDisable(isOpen)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div
|
<div
|
||||||
@ -368,7 +377,7 @@ type FollowButtonProps = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const FollowButton = ({ pubkey }: FollowButtonProps) => {
|
const FollowButton = ({ pubkey }: FollowButtonProps) => {
|
||||||
const { fetchEventFromUserRelays } = useNDKContext()
|
const { ndk, fetchEventFromUserRelays, publish } = useNDKContext()
|
||||||
const [isFollowing, setIsFollowing] = useState(false)
|
const [isFollowing, setIsFollowing] = useState(false)
|
||||||
|
|
||||||
const [isLoading, setIsLoading] = useState(false)
|
const [isLoading, setIsLoading] = useState(false)
|
||||||
@ -441,9 +450,8 @@ const FollowButton = ({ pubkey }: FollowButtonProps) => {
|
|||||||
|
|
||||||
if (!signedEvent) return false
|
if (!signedEvent) return false
|
||||||
|
|
||||||
const publishedOnRelays = await RelayController.getInstance().publish(
|
const ndkEvent = new NDKEvent(ndk, signedEvent)
|
||||||
signedEvent as Event
|
const publishedOnRelays = await publish(ndkEvent)
|
||||||
)
|
|
||||||
|
|
||||||
if (publishedOnRelays.length === 0) {
|
if (publishedOnRelays.length === 0) {
|
||||||
toast.error('Failed to publish event on any relay')
|
toast.error('Failed to publish event on any relay')
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { getRelayListForUser } from '@nostr-dev-kit/ndk'
|
||||||
import { QRCodeSVG } from 'qrcode.react'
|
import { QRCodeSVG } from 'qrcode.react'
|
||||||
import React, {
|
import React, {
|
||||||
Dispatch,
|
Dispatch,
|
||||||
@ -9,7 +10,7 @@ import React, {
|
|||||||
} from 'react'
|
} from 'react'
|
||||||
import Countdown, { CountdownRenderProps } from 'react-countdown'
|
import Countdown, { CountdownRenderProps } from 'react-countdown'
|
||||||
import { toast } from 'react-toastify'
|
import { toast } from 'react-toastify'
|
||||||
import { MetadataController, ZapController } from '../controllers'
|
import { ZapController } from '../controllers'
|
||||||
import { useAppSelector, useDidMount, useNDKContext } from '../hooks'
|
import { useAppSelector, useDidMount, useNDKContext } from '../hooks'
|
||||||
import '../styles/popup.css'
|
import '../styles/popup.css'
|
||||||
import { PaymentRequest, UserProfile } from '../types'
|
import { PaymentRequest, UserProfile } from '../types'
|
||||||
@ -251,7 +252,7 @@ export const ZapPopUp = ({
|
|||||||
setHasZapped,
|
setHasZapped,
|
||||||
handleClose
|
handleClose
|
||||||
}: ZapPopUpProps) => {
|
}: ZapPopUpProps) => {
|
||||||
const { findMetadata } = useNDKContext()
|
const { ndk, findMetadata } = useNDKContext()
|
||||||
const [isLoading, setIsLoading] = useState(false)
|
const [isLoading, setIsLoading] = useState(false)
|
||||||
const [loadingSpinnerDesc, setLoadingSpinnerDesc] = useState('')
|
const [loadingSpinnerDesc, setLoadingSpinnerDesc] = useState('')
|
||||||
const [amount, setAmount] = useState<number>(0)
|
const [amount, setAmount] = useState<number>(0)
|
||||||
@ -300,6 +301,20 @@ export const ZapPopUp = ({
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Find the receiver's read relays.
|
||||||
|
const receiverRelays = await getRelayListForUser(receiver, ndk)
|
||||||
|
.then((ndkRelayList) => {
|
||||||
|
if (ndkRelayList) return ndkRelayList.readRelayUrls
|
||||||
|
return [] // Return an empty array if ndkRelayList is undefined
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.error(
|
||||||
|
`An error occurred in getting zap receiver's read relays`,
|
||||||
|
err
|
||||||
|
)
|
||||||
|
return [] as string[]
|
||||||
|
})
|
||||||
|
|
||||||
const zapController = ZapController.getInstance()
|
const zapController = ZapController.getInstance()
|
||||||
|
|
||||||
setLoadingSpinnerDesc('Creating zap request')
|
setLoadingSpinnerDesc('Creating zap request')
|
||||||
@ -308,6 +323,7 @@ export const ZapPopUp = ({
|
|||||||
receiverMetadata.lud16,
|
receiverMetadata.lud16,
|
||||||
amount,
|
amount,
|
||||||
receiverMetadata.pubkey as string,
|
receiverMetadata.pubkey as string,
|
||||||
|
receiverRelays,
|
||||||
userHexKey,
|
userHexKey,
|
||||||
message,
|
message,
|
||||||
eventId,
|
eventId,
|
||||||
@ -320,7 +336,7 @@ export const ZapPopUp = ({
|
|||||||
.finally(() => {
|
.finally(() => {
|
||||||
setIsLoading(false)
|
setIsLoading(false)
|
||||||
})
|
})
|
||||||
}, [amount, message, userState, receiver, eventId, aTag])
|
}, [amount, message, userState, receiver, eventId, aTag, ndk, findMetadata])
|
||||||
|
|
||||||
const handleGenerateQRCode = async () => {
|
const handleGenerateQRCode = async () => {
|
||||||
const pr = await generatePaymentRequest()
|
const pr = await generatePaymentRequest()
|
||||||
@ -482,7 +498,7 @@ export const ZapSplit = ({
|
|||||||
setHasZapped,
|
setHasZapped,
|
||||||
handleClose
|
handleClose
|
||||||
}: ZapSplitProps) => {
|
}: ZapSplitProps) => {
|
||||||
const { findMetadata } = useNDKContext()
|
const { ndk, findMetadata } = useNDKContext()
|
||||||
const [isLoading, setIsLoading] = useState(false)
|
const [isLoading, setIsLoading] = useState(false)
|
||||||
const [loadingSpinnerDesc, setLoadingSpinnerDesc] = useState('')
|
const [loadingSpinnerDesc, setLoadingSpinnerDesc] = useState('')
|
||||||
const [amount, setAmount] = useState<number>(0)
|
const [amount, setAmount] = useState<number>(0)
|
||||||
@ -502,8 +518,8 @@ export const ZapSplit = ({
|
|||||||
setAuthor(res)
|
setAuthor(res)
|
||||||
})
|
})
|
||||||
|
|
||||||
const metadataController = await MetadataController.getInstance()
|
const adminNpubs = import.meta.env.VITE_ADMIN_NPUBS.split(',')
|
||||||
findMetadata(metadataController.adminNpubs[0]).then((res) => {
|
findMetadata(adminNpubs[0]).then((res) => {
|
||||||
setAdmin(res)
|
setAdmin(res)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -557,12 +573,30 @@ export const ZapSplit = ({
|
|||||||
const invoices = new Map<string, PaymentRequest>()
|
const invoices = new Map<string, PaymentRequest>()
|
||||||
|
|
||||||
if (authorShare > 0 && author?.pubkey && author?.lud16) {
|
if (authorShare > 0 && author?.pubkey && author?.lud16) {
|
||||||
|
// Find the receiver's read relays.
|
||||||
|
const authorRelays = await getRelayListForUser(
|
||||||
|
author.pubkey as string,
|
||||||
|
ndk
|
||||||
|
)
|
||||||
|
.then((ndkRelayList) => {
|
||||||
|
if (ndkRelayList) return ndkRelayList.readRelayUrls
|
||||||
|
return [] // Return an empty array if ndkRelayList is undefined
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.error(
|
||||||
|
`An error occurred in getting zap receiver's read relays`,
|
||||||
|
err
|
||||||
|
)
|
||||||
|
return [] as string[]
|
||||||
|
})
|
||||||
|
|
||||||
setLoadingSpinnerDesc('Generating invoice for author')
|
setLoadingSpinnerDesc('Generating invoice for author')
|
||||||
const invoice = await zapController
|
const invoice = await zapController
|
||||||
.getLightningPaymentRequest(
|
.getLightningPaymentRequest(
|
||||||
author.lud16,
|
author.lud16,
|
||||||
authorShare,
|
authorShare,
|
||||||
author.pubkey as string,
|
author.pubkey as string,
|
||||||
|
authorRelays,
|
||||||
userHexKey,
|
userHexKey,
|
||||||
message,
|
message,
|
||||||
eventId,
|
eventId,
|
||||||
@ -579,12 +613,27 @@ export const ZapSplit = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (adminShare > 0 && admin?.pubkey && admin?.lud16) {
|
if (adminShare > 0 && admin?.pubkey && admin?.lud16) {
|
||||||
|
// Find the receiver's read relays.
|
||||||
|
const adminRelays = await getRelayListForUser(admin.pubkey as string, ndk)
|
||||||
|
.then((ndkRelayList) => {
|
||||||
|
if (ndkRelayList) return ndkRelayList.readRelayUrls
|
||||||
|
return [] // Return an empty array if ndkRelayList is undefined
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.error(
|
||||||
|
`An error occurred in getting zap receiver's read relays`,
|
||||||
|
err
|
||||||
|
)
|
||||||
|
return [] as string[]
|
||||||
|
})
|
||||||
|
|
||||||
setLoadingSpinnerDesc('Generating invoice for site owner')
|
setLoadingSpinnerDesc('Generating invoice for site owner')
|
||||||
const invoice = await zapController
|
const invoice = await zapController
|
||||||
.getLightningPaymentRequest(
|
.getLightningPaymentRequest(
|
||||||
admin.lud16,
|
admin.lud16,
|
||||||
adminShare,
|
adminShare,
|
||||||
admin.pubkey as string,
|
admin.pubkey as string,
|
||||||
|
adminRelays,
|
||||||
userHexKey,
|
userHexKey,
|
||||||
message,
|
message,
|
||||||
eventId,
|
eventId,
|
||||||
|
@ -3,17 +3,18 @@ import NDK, {
|
|||||||
NDKEvent,
|
NDKEvent,
|
||||||
NDKFilter,
|
NDKFilter,
|
||||||
NDKKind,
|
NDKKind,
|
||||||
|
NDKList,
|
||||||
NDKRelaySet,
|
NDKRelaySet,
|
||||||
NDKSubscriptionCacheUsage,
|
NDKSubscriptionCacheUsage,
|
||||||
NDKUser
|
NDKUser,
|
||||||
|
zapInvoiceFromEvent
|
||||||
} from '@nostr-dev-kit/ndk'
|
} from '@nostr-dev-kit/ndk'
|
||||||
import NDKCacheAdapterDexie from '@nostr-dev-kit/ndk-cache-dexie'
|
import NDKCacheAdapterDexie from '@nostr-dev-kit/ndk-cache-dexie'
|
||||||
import { MOD_FILTER_LIMIT, T_TAG_VALUE } from 'constants.ts'
|
import { MOD_FILTER_LIMIT, T_TAG_VALUE } from 'constants.ts'
|
||||||
import { UserRelaysType } from 'controllers'
|
|
||||||
import { Dexie } from 'dexie'
|
import { Dexie } from 'dexie'
|
||||||
import { createContext, ReactNode, useEffect, useMemo } from 'react'
|
import { createContext, ReactNode, useEffect, useMemo } from 'react'
|
||||||
import { toast } from 'react-toastify'
|
import { toast } from 'react-toastify'
|
||||||
import { ModDetails, UserProfile } from 'types'
|
import { ModDetails, MuteLists, UserProfile, UserRelaysType } from 'types'
|
||||||
import {
|
import {
|
||||||
constructModListFromEvents,
|
constructModListFromEvents,
|
||||||
hexToNpub,
|
hexToNpub,
|
||||||
@ -33,23 +34,34 @@ type FetchModsOptions = {
|
|||||||
interface NDKContextType {
|
interface NDKContextType {
|
||||||
ndk: NDK
|
ndk: NDK
|
||||||
fetchMods: (opts: FetchModsOptions) => Promise<ModDetails[]>
|
fetchMods: (opts: FetchModsOptions) => Promise<ModDetails[]>
|
||||||
fetchEvents: (filter: NDKFilter, relayUrls?: string[]) => Promise<NDKEvent[]>
|
fetchEvents: (filter: NDKFilter) => Promise<NDKEvent[]>
|
||||||
fetchEvent: (
|
fetchEvent: (filter: NDKFilter) => Promise<NDKEvent | null>
|
||||||
filter: NDKFilter,
|
|
||||||
relayUrls?: string[]
|
|
||||||
) => Promise<NDKEvent | null>
|
|
||||||
|
|
||||||
fetchEventsFromUserRelays: (
|
fetchEventsFromUserRelays: (
|
||||||
filter: NDKFilter,
|
filter: NDKFilter | NDKFilter[],
|
||||||
hexKey: string,
|
hexKey: string,
|
||||||
userRelaysType: UserRelaysType
|
userRelaysType: UserRelaysType
|
||||||
) => Promise<NDKEvent[]>
|
) => Promise<NDKEvent[]>
|
||||||
fetchEventFromUserRelays: (
|
fetchEventFromUserRelays: (
|
||||||
filter: NDKFilter,
|
filter: NDKFilter | NDKFilter[],
|
||||||
hexKey: string,
|
hexKey: string,
|
||||||
userRelaysType: UserRelaysType
|
userRelaysType: UserRelaysType
|
||||||
) => Promise<NDKEvent | null>
|
) => Promise<NDKEvent | null>
|
||||||
findMetadata: (pubkey: string) => Promise<UserProfile>
|
findMetadata: (pubkey: string) => Promise<UserProfile>
|
||||||
|
getTotalZapAmount: (
|
||||||
|
user: string,
|
||||||
|
eTag: string,
|
||||||
|
aTag?: string,
|
||||||
|
currentLoggedInUser?: string
|
||||||
|
) => Promise<{
|
||||||
|
accumulatedZapAmount: number
|
||||||
|
hasZapped: boolean
|
||||||
|
}>
|
||||||
|
publish: (event: NDKEvent) => Promise<string[]>
|
||||||
|
getNSFWList: () => Promise<string[]>
|
||||||
|
getMuteLists: (pubkey?: string) => Promise<{
|
||||||
|
admin: MuteLists
|
||||||
|
user: MuteLists
|
||||||
|
}>
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create the context with an initial value of `null`
|
// Create the context with an initial value of `null`
|
||||||
@ -72,6 +84,31 @@ export const NDKContextProvider = ({ children }: { children: ReactNode }) => {
|
|||||||
}
|
}
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
const addAdminRelays = async (ndk: NDK) => {
|
||||||
|
const adminNpubs = import.meta.env.VITE_ADMIN_NPUBS.split(',')
|
||||||
|
adminNpubs.forEach((npub) => {
|
||||||
|
const hexKey = npubToHex(npub)
|
||||||
|
if (hexKey) {
|
||||||
|
getRelayListForUser(hexKey, ndk)
|
||||||
|
.then((ndkRelayList) => {
|
||||||
|
if (ndkRelayList) {
|
||||||
|
ndkRelayList.bothRelayUrls.forEach((url) =>
|
||||||
|
ndk.addExplicitRelay(url)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
log(
|
||||||
|
true,
|
||||||
|
LogType.Error,
|
||||||
|
`❌ Error occurred in getting the instance of NDKRelayList for npub: ${npub}`,
|
||||||
|
err
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
const ndk = useMemo(() => {
|
const ndk = useMemo(() => {
|
||||||
localStorage.setItem('debug', '*')
|
localStorage.setItem('debug', '*')
|
||||||
const dexieAdapter = new NDKCacheAdapterDexie({ dbName: 'degmod-db' })
|
const dexieAdapter = new NDKCacheAdapterDexie({ dbName: 'degmod-db' })
|
||||||
@ -88,6 +125,7 @@ export const NDKContextProvider = ({ children }: { children: ReactNode }) => {
|
|||||||
],
|
],
|
||||||
cacheAdapter: dexieAdapter
|
cacheAdapter: dexieAdapter
|
||||||
})
|
})
|
||||||
|
addAdminRelays(ndk)
|
||||||
|
|
||||||
ndk.connect()
|
ndk.connect()
|
||||||
|
|
||||||
@ -110,33 +148,6 @@ export const NDKContextProvider = ({ children }: { children: ReactNode }) => {
|
|||||||
since,
|
since,
|
||||||
limit
|
limit
|
||||||
}: FetchModsOptions): Promise<ModDetails[]> => {
|
}: FetchModsOptions): Promise<ModDetails[]> => {
|
||||||
const relays = new Set<string>()
|
|
||||||
relays.add(import.meta.env.VITE_APP_RELAY)
|
|
||||||
|
|
||||||
const adminNpubs = import.meta.env.VITE_ADMIN_NPUBS.split(',')
|
|
||||||
|
|
||||||
const promises = adminNpubs.map((npub) => {
|
|
||||||
const hexKey = npubToHex(npub)
|
|
||||||
if (!hexKey) return null
|
|
||||||
|
|
||||||
return getRelayListForUser(hexKey, ndk)
|
|
||||||
.then((ndkRelayList) => {
|
|
||||||
if (ndkRelayList) {
|
|
||||||
ndkRelayList.writeRelayUrls.forEach((url) => relays.add(url))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
log(
|
|
||||||
true,
|
|
||||||
LogType.Error,
|
|
||||||
`❌ Error occurred in getting the instance of NDKRelayList for npub: ${npub}`,
|
|
||||||
err
|
|
||||||
)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
await Promise.allSettled(promises)
|
|
||||||
|
|
||||||
// Define the filter criteria for fetching mods
|
// Define the filter criteria for fetching mods
|
||||||
const filter: NDKFilter = {
|
const filter: NDKFilter = {
|
||||||
kinds: [NDKKind.Classified], // Specify the kind of events to fetch
|
kinds: [NDKKind.Classified], // Specify the kind of events to fetch
|
||||||
@ -152,11 +163,10 @@ export const NDKContextProvider = ({ children }: { children: ReactNode }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return ndk
|
return ndk
|
||||||
.fetchEvents(
|
.fetchEvents(filter, {
|
||||||
filter,
|
closeOnEose: true,
|
||||||
{ closeOnEose: true, cacheUsage: NDKSubscriptionCacheUsage.PARALLEL },
|
cacheUsage: NDKSubscriptionCacheUsage.PARALLEL
|
||||||
NDKRelaySet.fromRelayUrls(Array.from(relays), ndk, true)
|
})
|
||||||
)
|
|
||||||
.then((ndkEventSet) => {
|
.then((ndkEventSet) => {
|
||||||
const ndkEvents = Array.from(ndkEventSet)
|
const ndkEvents = Array.from(ndkEventSet)
|
||||||
orderEventsChronologically(ndkEvents)
|
orderEventsChronologically(ndkEvents)
|
||||||
@ -179,56 +189,17 @@ export const NDKContextProvider = ({ children }: { children: ReactNode }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Asynchronously retrieves multiple event from a set of relays based on a provided filter.
|
* Asynchronously retrieves multiple event based on a provided filter.
|
||||||
* If no relays are specified, it defaults to using connected relays.
|
|
||||||
*
|
*
|
||||||
* @param filter - The filter criteria to find the event.
|
* @param filter - The filter criteria to find the event.
|
||||||
* @param relays - An optional array of relay URLs to search for the event.
|
|
||||||
* @returns Returns a promise that resolves to the found event or null if not found.
|
* @returns Returns a promise that resolves to the found event or null if not found.
|
||||||
*/
|
*/
|
||||||
const fetchEvents = async (
|
const fetchEvents = async (filter: NDKFilter): Promise<NDKEvent[]> => {
|
||||||
filter: NDKFilter,
|
|
||||||
relayUrls: string[] = []
|
|
||||||
): Promise<NDKEvent[]> => {
|
|
||||||
const relays = new Set<string>()
|
|
||||||
|
|
||||||
// add all the relays passed to relay set
|
|
||||||
relayUrls.forEach((relayUrl) => {
|
|
||||||
relays.add(relayUrl)
|
|
||||||
})
|
|
||||||
|
|
||||||
relays.add(import.meta.env.VITE_APP_RELAY)
|
|
||||||
|
|
||||||
const adminNpubs = import.meta.env.VITE_ADMIN_NPUBS.split(',')
|
|
||||||
|
|
||||||
const promises = adminNpubs.map((npub) => {
|
|
||||||
const hexKey = npubToHex(npub)
|
|
||||||
if (!hexKey) return null
|
|
||||||
|
|
||||||
return getRelayListForUser(hexKey, ndk)
|
|
||||||
.then((ndkRelayList) => {
|
|
||||||
if (ndkRelayList) {
|
|
||||||
ndkRelayList.writeRelayUrls.forEach((url) => relays.add(url))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
log(
|
|
||||||
true,
|
|
||||||
LogType.Error,
|
|
||||||
`❌ Error occurred in getting the instance of NDKRelayList for npub: ${npub}`,
|
|
||||||
err
|
|
||||||
)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
await Promise.allSettled(promises)
|
|
||||||
|
|
||||||
return ndk
|
return ndk
|
||||||
.fetchEvents(
|
.fetchEvents(filter, {
|
||||||
filter,
|
closeOnEose: true,
|
||||||
{ closeOnEose: true, cacheUsage: NDKSubscriptionCacheUsage.PARALLEL },
|
cacheUsage: NDKSubscriptionCacheUsage.PARALLEL
|
||||||
NDKRelaySet.fromRelayUrls(Array.from(relays), ndk, true)
|
})
|
||||||
)
|
|
||||||
.then((ndkEventSet) => {
|
.then((ndkEventSet) => {
|
||||||
const ndkEvents = Array.from(ndkEventSet)
|
const ndkEvents = Array.from(ndkEventSet)
|
||||||
return orderEventsChronologically(ndkEvents)
|
return orderEventsChronologically(ndkEvents)
|
||||||
@ -242,15 +213,13 @@ export const NDKContextProvider = ({ children }: { children: ReactNode }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Asynchronously retrieves an event from a set of relays based on a provided filter.
|
* Asynchronously retrieves an event based on a provided filter.
|
||||||
* If no relays are specified, it defaults to using connected relays.
|
|
||||||
*
|
*
|
||||||
* @param filter - The filter criteria to find the event.
|
* @param filter - The filter criteria to find the event.
|
||||||
* @param relaysUrls - An optional array of relay URLs to search for the event.
|
|
||||||
* @returns Returns a promise that resolves to the found event or null if not found.
|
* @returns Returns a promise that resolves to the found event or null if not found.
|
||||||
*/
|
*/
|
||||||
const fetchEvent = async (filter: NDKFilter, relayUrls: string[] = []) => {
|
const fetchEvent = async (filter: NDKFilter) => {
|
||||||
const events = await fetchEvents(filter, relayUrls)
|
const events = await fetchEvents(filter)
|
||||||
if (events.length === 0) return null
|
if (events.length === 0) return null
|
||||||
return events[0]
|
return events[0]
|
||||||
}
|
}
|
||||||
@ -265,10 +234,10 @@ export const NDKContextProvider = ({ children }: { children: ReactNode }) => {
|
|||||||
* @returns A promise that resolves with an array of events.
|
* @returns A promise that resolves with an array of events.
|
||||||
*/
|
*/
|
||||||
const fetchEventsFromUserRelays = async (
|
const fetchEventsFromUserRelays = async (
|
||||||
filter: NDKFilter,
|
filter: NDKFilter | NDKFilter[],
|
||||||
hexKey: string,
|
hexKey: string,
|
||||||
userRelaysType: UserRelaysType
|
userRelaysType: UserRelaysType
|
||||||
) => {
|
): Promise<NDKEvent[]> => {
|
||||||
// Find the user's relays.
|
// Find the user's relays.
|
||||||
const relayUrls = await getRelayListForUser(hexKey, ndk)
|
const relayUrls = await getRelayListForUser(hexKey, ndk)
|
||||||
.then((ndkRelayList) => {
|
.then((ndkRelayList) => {
|
||||||
@ -285,8 +254,22 @@ export const NDKContextProvider = ({ children }: { children: ReactNode }) => {
|
|||||||
return [] as string[]
|
return [] as string[]
|
||||||
})
|
})
|
||||||
|
|
||||||
// Fetch the event from the user's relays using the provided filter and relay URLs
|
return ndk
|
||||||
return fetchEvents(filter, relayUrls)
|
.fetchEvents(
|
||||||
|
filter,
|
||||||
|
{ closeOnEose: true, cacheUsage: NDKSubscriptionCacheUsage.PARALLEL },
|
||||||
|
NDKRelaySet.fromRelayUrls(relayUrls, ndk, true)
|
||||||
|
)
|
||||||
|
.then((ndkEventSet) => {
|
||||||
|
const ndkEvents = Array.from(ndkEventSet)
|
||||||
|
return orderEventsChronologically(ndkEvents)
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
// Log the error and show a notification if fetching fails
|
||||||
|
log(true, LogType.Error, 'An error occurred in fetching events', err)
|
||||||
|
toast.error('An error occurred in fetching events') // Show error notification
|
||||||
|
return [] // Return an empty array in case of an error
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -299,7 +282,7 @@ export const NDKContextProvider = ({ children }: { children: ReactNode }) => {
|
|||||||
* @returns A promise that resolves to the fetched event or null if the operation fails.
|
* @returns A promise that resolves to the fetched event or null if the operation fails.
|
||||||
*/
|
*/
|
||||||
const fetchEventFromUserRelays = async (
|
const fetchEventFromUserRelays = async (
|
||||||
filter: NDKFilter,
|
filter: NDKFilter | NDKFilter[],
|
||||||
hexKey: string,
|
hexKey: string,
|
||||||
userRelaysType: UserRelaysType
|
userRelaysType: UserRelaysType
|
||||||
) => {
|
) => {
|
||||||
@ -329,6 +312,178 @@ export const NDKContextProvider = ({ children }: { children: ReactNode }) => {
|
|||||||
return userProfile
|
return userProfile
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getTotalZapAmount = async (
|
||||||
|
user: string,
|
||||||
|
eTag: string,
|
||||||
|
aTag?: string,
|
||||||
|
currentLoggedInUser?: string
|
||||||
|
) => {
|
||||||
|
const filters: NDKFilter[] = [
|
||||||
|
{
|
||||||
|
kinds: [NDKKind.Zap],
|
||||||
|
'#e': [eTag],
|
||||||
|
'#p': [user]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
if (aTag) {
|
||||||
|
filters.push({
|
||||||
|
kinds: [NDKKind.Zap],
|
||||||
|
'#a': [aTag],
|
||||||
|
'#p': [user]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const zapEvents = await fetchEventsFromUserRelays(
|
||||||
|
filters,
|
||||||
|
user,
|
||||||
|
UserRelaysType.Read
|
||||||
|
)
|
||||||
|
|
||||||
|
let accumulatedZapAmount = 0
|
||||||
|
let hasZapped = false
|
||||||
|
|
||||||
|
zapEvents.forEach((zap) => {
|
||||||
|
const zapInvoice = zapInvoiceFromEvent(zap)
|
||||||
|
if (zapInvoice) {
|
||||||
|
accumulatedZapAmount += Math.round(zapInvoice.amount / 1000)
|
||||||
|
|
||||||
|
if (!hasZapped) hasZapped = zapInvoice.zappee === currentLoggedInUser
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
accumulatedZapAmount,
|
||||||
|
hasZapped
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const publish = async (event: NDKEvent): Promise<string[]> => {
|
||||||
|
if (!event.sig) throw new Error('Before publishing first sign the event!')
|
||||||
|
|
||||||
|
return event
|
||||||
|
.publish(undefined, 30000)
|
||||||
|
.then((res) => {
|
||||||
|
const relaysPublishedOn = Array.from(res)
|
||||||
|
return relaysPublishedOn.map((relay) => relay.url)
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.error(`An error occurred in publishing event`, err)
|
||||||
|
return []
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves a list of NSFW (Not Safe For Work) posts that were not specified as NSFW by post author but marked as NSFW by admin.
|
||||||
|
*
|
||||||
|
* @returns {Promise<string[]>} - A promise that resolves to an array of NSFW post identifiers (e.g., URLs or IDs).
|
||||||
|
*/
|
||||||
|
const getNSFWList = async (): Promise<string[]> => {
|
||||||
|
// Initialize an array to store the NSFW post identifiers
|
||||||
|
const nsfwPosts: string[] = []
|
||||||
|
|
||||||
|
const reportingNpub = import.meta.env.VITE_REPORTING_NPUB
|
||||||
|
|
||||||
|
// Convert the public key (npub) to a hexadecimal format
|
||||||
|
const hexKey = npubToHex(reportingNpub)
|
||||||
|
|
||||||
|
// If the conversion is successful and we have a hexKey
|
||||||
|
if (hexKey) {
|
||||||
|
// Fetch the event that contains the NSFW list
|
||||||
|
const nsfwListEvent = await fetchEvent({
|
||||||
|
kinds: [NDKKind.ArticleCurationSet],
|
||||||
|
authors: [hexKey],
|
||||||
|
'#d': ['nsfw']
|
||||||
|
})
|
||||||
|
|
||||||
|
if (nsfwListEvent) {
|
||||||
|
// Convert the event data to an NDKList, which is a structured list format
|
||||||
|
const list = NDKList.from(nsfwListEvent)
|
||||||
|
|
||||||
|
// Iterate through the items in the list
|
||||||
|
list.items.forEach((item) => {
|
||||||
|
if (item[0] === 'a') {
|
||||||
|
// Add the identifier of the NSFW post to the nsfwPosts array
|
||||||
|
nsfwPosts.push(item[1])
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the array of NSFW post identifiers
|
||||||
|
return nsfwPosts
|
||||||
|
}
|
||||||
|
|
||||||
|
const getMuteLists = async (
|
||||||
|
pubkey?: string
|
||||||
|
): Promise<{
|
||||||
|
admin: MuteLists
|
||||||
|
user: MuteLists
|
||||||
|
}> => {
|
||||||
|
const adminMutedAuthors = new Set<string>()
|
||||||
|
const adminMutedPosts = new Set<string>()
|
||||||
|
|
||||||
|
const reportingNpub = import.meta.env.VITE_REPORTING_NPUB
|
||||||
|
|
||||||
|
const adminHexKey = npubToHex(reportingNpub)
|
||||||
|
|
||||||
|
if (adminHexKey) {
|
||||||
|
const muteListEvent = await fetchEvent({
|
||||||
|
kinds: [NDKKind.MuteList],
|
||||||
|
authors: [adminHexKey]
|
||||||
|
})
|
||||||
|
|
||||||
|
if (muteListEvent) {
|
||||||
|
const list = NDKList.from(muteListEvent)
|
||||||
|
|
||||||
|
list.items.forEach((item) => {
|
||||||
|
if (item[0] === 'p') {
|
||||||
|
adminMutedAuthors.add(item[1])
|
||||||
|
} else if (item[0] === 'a') {
|
||||||
|
adminMutedPosts.add(item[1])
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const userMutedAuthors = new Set<string>()
|
||||||
|
const userMutedPosts = new Set<string>()
|
||||||
|
|
||||||
|
if (pubkey) {
|
||||||
|
const userHexKey = npubToHex(pubkey)
|
||||||
|
|
||||||
|
if (userHexKey) {
|
||||||
|
const muteListEvent = await fetchEvent({
|
||||||
|
kinds: [NDKKind.MuteList],
|
||||||
|
authors: [userHexKey]
|
||||||
|
})
|
||||||
|
|
||||||
|
if (muteListEvent) {
|
||||||
|
const list = NDKList.from(muteListEvent)
|
||||||
|
|
||||||
|
list.items.forEach((item) => {
|
||||||
|
if (item[0] === 'p') {
|
||||||
|
userMutedAuthors.add(item[1])
|
||||||
|
} else if (item[0] === 'a') {
|
||||||
|
userMutedPosts.add(item[1])
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
admin: {
|
||||||
|
authors: Array.from(adminMutedAuthors),
|
||||||
|
replaceableEvents: Array.from(adminMutedPosts)
|
||||||
|
},
|
||||||
|
user: {
|
||||||
|
authors: Array.from(userMutedAuthors),
|
||||||
|
replaceableEvents: Array.from(userMutedPosts)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<NDKContext.Provider
|
<NDKContext.Provider
|
||||||
value={{
|
value={{
|
||||||
@ -338,7 +493,11 @@ export const NDKContextProvider = ({ children }: { children: ReactNode }) => {
|
|||||||
fetchEvent,
|
fetchEvent,
|
||||||
fetchEventsFromUserRelays,
|
fetchEventsFromUserRelays,
|
||||||
fetchEventFromUserRelays,
|
fetchEventFromUserRelays,
|
||||||
findMetadata
|
findMetadata,
|
||||||
|
getTotalZapAmount,
|
||||||
|
publish,
|
||||||
|
getNSFWList,
|
||||||
|
getMuteLists
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
@ -1,3 +1 @@
|
|||||||
export * from './metadata'
|
|
||||||
export * from './relay'
|
|
||||||
export * from './zap'
|
export * from './zap'
|
||||||
|
@ -1,217 +0,0 @@
|
|||||||
import NDK, { getRelayListForUser, NDKList } from '@nostr-dev-kit/ndk'
|
|
||||||
import { kinds } from 'nostr-tools'
|
|
||||||
import { MuteLists } from '../types'
|
|
||||||
import { log, LogType, npubToHex, timeout } from '../utils'
|
|
||||||
|
|
||||||
export enum UserRelaysType {
|
|
||||||
Read = 'readRelayUrls',
|
|
||||||
Write = 'writeRelayUrls',
|
|
||||||
Both = 'bothRelayUrls'
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Singleton class to manage metadata operations using NDK.
|
|
||||||
*/
|
|
||||||
export class MetadataController {
|
|
||||||
private static instance: MetadataController
|
|
||||||
private ndk: NDK
|
|
||||||
public adminNpubs: string[]
|
|
||||||
public adminRelays = new Set<string>()
|
|
||||||
public reportingNpub: string
|
|
||||||
|
|
||||||
private constructor() {
|
|
||||||
this.ndk = new NDK({
|
|
||||||
explicitRelayUrls: [
|
|
||||||
'wss://user.kindpag.es',
|
|
||||||
'wss://purplepag.es',
|
|
||||||
'wss://relay.damus.io/',
|
|
||||||
import.meta.env.VITE_APP_RELAY
|
|
||||||
]
|
|
||||||
})
|
|
||||||
|
|
||||||
this.ndk
|
|
||||||
.connect()
|
|
||||||
.then(() => {
|
|
||||||
console.log('NDK connected')
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
console.log('error in ndk connection', err)
|
|
||||||
})
|
|
||||||
|
|
||||||
this.adminNpubs = import.meta.env.VITE_ADMIN_NPUBS.split(',')
|
|
||||||
this.reportingNpub = import.meta.env.VITE_REPORTING_NPUB
|
|
||||||
}
|
|
||||||
|
|
||||||
private setAdminRelays = async () => {
|
|
||||||
const promises = this.adminNpubs.map((npub) => {
|
|
||||||
const hexKey = npubToHex(npub)
|
|
||||||
if (!hexKey) return null
|
|
||||||
|
|
||||||
return getRelayListForUser(hexKey, this.ndk)
|
|
||||||
.then((ndkRelayList) => {
|
|
||||||
if (ndkRelayList) {
|
|
||||||
ndkRelayList.writeRelayUrls.forEach((url) =>
|
|
||||||
this.adminRelays.add(url)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
log(
|
|
||||||
true,
|
|
||||||
LogType.Error,
|
|
||||||
`❌ Error occurred in getting the instance of NDKRelayList for npub: ${npub}`,
|
|
||||||
err
|
|
||||||
)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
await Promise.allSettled(promises)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Provides the singleton instance of MetadataController.
|
|
||||||
*
|
|
||||||
* @returns The singleton instance of MetadataController.
|
|
||||||
*/
|
|
||||||
public static async getInstance(): Promise<MetadataController> {
|
|
||||||
if (!MetadataController.instance) {
|
|
||||||
MetadataController.instance = new MetadataController()
|
|
||||||
|
|
||||||
await MetadataController.instance.setAdminRelays()
|
|
||||||
}
|
|
||||||
return MetadataController.instance
|
|
||||||
}
|
|
||||||
|
|
||||||
public findUserRelays = async (
|
|
||||||
hexKey: string,
|
|
||||||
userRelaysType: UserRelaysType = UserRelaysType.Both
|
|
||||||
): Promise<string[]> => {
|
|
||||||
log(true, LogType.Info, `ℹ Finding user's relays`, hexKey, userRelaysType)
|
|
||||||
|
|
||||||
const ndkRelayListPromise = getRelayListForUser(hexKey, this.ndk)
|
|
||||||
|
|
||||||
// Use Promise.race to either get the NDKRelayList instance or handle the timeout
|
|
||||||
return await Promise.race([
|
|
||||||
ndkRelayListPromise,
|
|
||||||
timeout() // Custom timeout function that rejects after a specified time
|
|
||||||
])
|
|
||||||
.then((ndkRelayList) => {
|
|
||||||
if (ndkRelayList) return ndkRelayList[userRelaysType]
|
|
||||||
return [] // Return an empty array if ndkRelayList is undefined
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
log(true, LogType.Error, err)
|
|
||||||
return [] // Return an empty array if an error occurs
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
public getNDKRelayList = async (hexKey: string) =>
|
|
||||||
getRelayListForUser(hexKey, this.ndk)
|
|
||||||
|
|
||||||
public getMuteLists = async (
|
|
||||||
pubkey?: string
|
|
||||||
): Promise<{
|
|
||||||
admin: MuteLists
|
|
||||||
user: MuteLists
|
|
||||||
}> => {
|
|
||||||
const adminMutedAuthors = new Set<string>()
|
|
||||||
const adminMutedPosts = new Set<string>()
|
|
||||||
|
|
||||||
const adminHexKey = npubToHex(this.reportingNpub)
|
|
||||||
|
|
||||||
if (adminHexKey) {
|
|
||||||
const muteListEvent = await this.ndk.fetchEvent({
|
|
||||||
kinds: [kinds.Mutelist],
|
|
||||||
authors: [adminHexKey]
|
|
||||||
})
|
|
||||||
|
|
||||||
if (muteListEvent) {
|
|
||||||
const list = NDKList.from(muteListEvent)
|
|
||||||
|
|
||||||
list.items.forEach((item) => {
|
|
||||||
if (item[0] === 'p') {
|
|
||||||
adminMutedAuthors.add(item[1])
|
|
||||||
} else if (item[0] === 'a') {
|
|
||||||
adminMutedPosts.add(item[1])
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const userMutedAuthors = new Set<string>()
|
|
||||||
const userMutedPosts = new Set<string>()
|
|
||||||
|
|
||||||
if (pubkey) {
|
|
||||||
const userHexKey = npubToHex(pubkey)
|
|
||||||
|
|
||||||
if (userHexKey) {
|
|
||||||
const muteListEvent = await this.ndk.fetchEvent({
|
|
||||||
kinds: [kinds.Mutelist],
|
|
||||||
authors: [userHexKey]
|
|
||||||
})
|
|
||||||
|
|
||||||
if (muteListEvent) {
|
|
||||||
const list = NDKList.from(muteListEvent)
|
|
||||||
|
|
||||||
list.items.forEach((item) => {
|
|
||||||
if (item[0] === 'p') {
|
|
||||||
userMutedAuthors.add(item[1])
|
|
||||||
} else if (item[0] === 'a') {
|
|
||||||
userMutedPosts.add(item[1])
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
admin: {
|
|
||||||
authors: Array.from(adminMutedAuthors),
|
|
||||||
replaceableEvents: Array.from(adminMutedPosts)
|
|
||||||
},
|
|
||||||
user: {
|
|
||||||
authors: Array.from(userMutedAuthors),
|
|
||||||
replaceableEvents: Array.from(userMutedPosts)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves a list of NSFW (Not Safe For Work) posts that were not specified as NSFW by post author but marked as NSFW by admin.
|
|
||||||
*
|
|
||||||
* @returns {Promise<string[]>} - A promise that resolves to an array of NSFW post identifiers (e.g., URLs or IDs).
|
|
||||||
*/
|
|
||||||
public getNSFWList = async (): Promise<string[]> => {
|
|
||||||
// Initialize an array to store the NSFW post identifiers
|
|
||||||
const nsfwPosts: string[] = []
|
|
||||||
|
|
||||||
// Convert the public key (npub) to a hexadecimal format
|
|
||||||
const hexKey = npubToHex(this.reportingNpub)
|
|
||||||
|
|
||||||
// If the conversion is successful and we have a hexKey
|
|
||||||
if (hexKey) {
|
|
||||||
// Fetch the event that contains the NSFW list
|
|
||||||
const nsfwListEvent = await this.ndk.fetchEvent({
|
|
||||||
kinds: [kinds.Curationsets],
|
|
||||||
authors: [hexKey],
|
|
||||||
'#d': ['nsfw']
|
|
||||||
})
|
|
||||||
|
|
||||||
if (nsfwListEvent) {
|
|
||||||
// Convert the event data to an NDKList, which is a structured list format
|
|
||||||
const list = NDKList.from(nsfwListEvent)
|
|
||||||
|
|
||||||
// Iterate through the items in the list
|
|
||||||
list.items.forEach((item) => {
|
|
||||||
if (item[0] === 'a') {
|
|
||||||
// Add the identifier of the NSFW post to the nsfwPosts array
|
|
||||||
nsfwPosts.push(item[1])
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return the array of NSFW post identifiers
|
|
||||||
return nsfwPosts
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,561 +0,0 @@
|
|||||||
import { Event, Filter, kinds, nip57, Relay } from 'nostr-tools'
|
|
||||||
import {
|
|
||||||
extractZapAmount,
|
|
||||||
log,
|
|
||||||
LogType,
|
|
||||||
normalizeWebSocketURL,
|
|
||||||
timeout
|
|
||||||
} from '../utils'
|
|
||||||
import { MetadataController, UserRelaysType } from './metadata'
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Singleton class to manage relay operations.
|
|
||||||
*/
|
|
||||||
export class RelayController {
|
|
||||||
private static instance: RelayController
|
|
||||||
private events = new Map<string, Event>()
|
|
||||||
private debug = true
|
|
||||||
public connectedRelays: Relay[] = []
|
|
||||||
|
|
||||||
private constructor() {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Provides the singleton instance of RelayController.
|
|
||||||
*
|
|
||||||
* @returns The singleton instance of RelayController.
|
|
||||||
*/
|
|
||||||
public static getInstance(): RelayController {
|
|
||||||
if (!RelayController.instance) {
|
|
||||||
RelayController.instance = new RelayController()
|
|
||||||
}
|
|
||||||
return RelayController.instance
|
|
||||||
}
|
|
||||||
|
|
||||||
public connectRelay = async (relayUrl: string) => {
|
|
||||||
const relay = this.connectedRelays.find(
|
|
||||||
(relay) =>
|
|
||||||
normalizeWebSocketURL(relay.url) === normalizeWebSocketURL(relayUrl)
|
|
||||||
)
|
|
||||||
if (relay) {
|
|
||||||
// already connected, skip
|
|
||||||
return relay
|
|
||||||
}
|
|
||||||
|
|
||||||
return await Relay.connect(relayUrl)
|
|
||||||
.then((relay) => {
|
|
||||||
log(this.debug, LogType.Info, `✅ nostr (${relayUrl}): Connected!`)
|
|
||||||
this.connectedRelays.push(relay)
|
|
||||||
return relay
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
log(
|
|
||||||
this.debug,
|
|
||||||
LogType.Error,
|
|
||||||
`❌ nostr (${relayUrl}): Connection error!`,
|
|
||||||
err
|
|
||||||
)
|
|
||||||
return null
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Publishes an event to multiple relays.
|
|
||||||
*
|
|
||||||
* This method establishes a connection to the application relay specified by
|
|
||||||
* an environment variable and a set of relays obtained from the
|
|
||||||
* `MetadataController`. It attempts to publish the event to all connected
|
|
||||||
* relays and returns a list of URLs of relays where the event was successfully
|
|
||||||
* published.
|
|
||||||
*
|
|
||||||
* If the process of finding relays or publishing the event takes too long,
|
|
||||||
* it handles the timeout to prevent blocking the operation.
|
|
||||||
*
|
|
||||||
* @param event - The event to be published.
|
|
||||||
* @param userHexKey - The user's hexadecimal public key, used to retrieve their relays.
|
|
||||||
* If not provided, the event's public key will be used.
|
|
||||||
* @param userRelaysType - The type of relays to be retrieved (e.g., write relays).
|
|
||||||
* Defaults to `UserRelaysType.Write`.
|
|
||||||
* @returns A promise that resolves to an array of URLs of relays where the event
|
|
||||||
* was published, or an empty array if no relays were connected or the
|
|
||||||
* event could not be published.
|
|
||||||
*/
|
|
||||||
publish = async (
|
|
||||||
event: Event,
|
|
||||||
userHexKey?: string,
|
|
||||||
userRelaysType?: UserRelaysType
|
|
||||||
): Promise<string[]> => {
|
|
||||||
// Connect to the application relay specified by an environment variable
|
|
||||||
const appRelayPromise = this.connectRelay(import.meta.env.VITE_APP_RELAY)
|
|
||||||
|
|
||||||
// TODO: Implement logic to retrieve relays using `window.nostr.getRelays()` once it becomes available in nostr-login.
|
|
||||||
|
|
||||||
// Retrieve an instance of MetadataController to find user relays
|
|
||||||
const metadataController = await MetadataController.getInstance()
|
|
||||||
|
|
||||||
// Retrieve the list of relays for the specified user's public key
|
|
||||||
const relayUrls = await metadataController.findUserRelays(
|
|
||||||
userHexKey || event.pubkey,
|
|
||||||
userRelaysType || UserRelaysType.Write
|
|
||||||
)
|
|
||||||
|
|
||||||
// Add admin relay URLs from the metadata controller to the list of relay URLs
|
|
||||||
metadataController.adminRelays.forEach((url) => {
|
|
||||||
relayUrls.push(url)
|
|
||||||
})
|
|
||||||
|
|
||||||
// Attempt to connect to all write relays obtained from MetadataController
|
|
||||||
const relayPromises = relayUrls.map((relayUrl) =>
|
|
||||||
this.connectRelay(relayUrl)
|
|
||||||
)
|
|
||||||
|
|
||||||
// Wait for all relay connection attempts to settle (either fulfilled or rejected)
|
|
||||||
const results = await Promise.allSettled([
|
|
||||||
appRelayPromise,
|
|
||||||
...relayPromises
|
|
||||||
])
|
|
||||||
|
|
||||||
// Extract non-null values from fulfilled promises in a single pass
|
|
||||||
const relays = results.reduce<Relay[]>((acc, result) => {
|
|
||||||
if (result.status === 'fulfilled') {
|
|
||||||
const value = result.value
|
|
||||||
if (value) {
|
|
||||||
acc.push(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return acc
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
// If no relays are connected, log an error and return an empty array
|
|
||||||
if (relays.length === 0) {
|
|
||||||
log(this.debug, LogType.Error, 'No relay is connected!')
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
|
|
||||||
const publishedOnRelays: string[] = [] // Track relays where the event was successfully published
|
|
||||||
|
|
||||||
// Create promises to publish the event to each connected relay
|
|
||||||
const publishPromises = relays.map((relay) => {
|
|
||||||
log(
|
|
||||||
this.debug,
|
|
||||||
LogType.Info,
|
|
||||||
`⬆️ nostr (${relay.url}): Sending event:`,
|
|
||||||
event
|
|
||||||
)
|
|
||||||
|
|
||||||
return Promise.race([
|
|
||||||
relay.publish(event), // Publish the event to the relay
|
|
||||||
timeout(30000) // Set a timeout to handle slow publishing operations
|
|
||||||
])
|
|
||||||
.then((res) => {
|
|
||||||
log(
|
|
||||||
this.debug,
|
|
||||||
LogType.Info,
|
|
||||||
`⬆️ nostr (${relay.url}): Publish result:`,
|
|
||||||
res
|
|
||||||
)
|
|
||||||
publishedOnRelays.push(relay.url) // Add successful relay URL to the list
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
log(
|
|
||||||
this.debug,
|
|
||||||
LogType.Error,
|
|
||||||
`❌ nostr (${relay.url}): Publish error!`,
|
|
||||||
err
|
|
||||||
)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
// Wait for all publish operations to complete (either fulfilled or rejected)
|
|
||||||
await Promise.allSettled(publishPromises)
|
|
||||||
|
|
||||||
if (publishedOnRelays.length > 0) {
|
|
||||||
// If the event was successfully published to any relays, check if it contains an `aTag`
|
|
||||||
// If the `aTag` is present, cache the event locally
|
|
||||||
const aTag = event.tags.find((item) => item[0] === 'a')
|
|
||||||
if (aTag && aTag[1]) {
|
|
||||||
this.events.set(aTag[1], event)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return the list of relay URLs where the event was successfully published
|
|
||||||
return publishedOnRelays
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Publishes an encrypted DM to receiver's read relays.
|
|
||||||
*
|
|
||||||
* This method connects to the application relay and a set of receiver's read relays
|
|
||||||
* obtained from the `MetadataController`. It then publishes the event to
|
|
||||||
* all connected relays and returns a list of relays where the event was successfully published.
|
|
||||||
*
|
|
||||||
* @param event - The event to be published.
|
|
||||||
* @returns A promise that resolves to an array of URLs of relays where the event was published,
|
|
||||||
* or an empty array if no relays were connected or the event could not be published.
|
|
||||||
*/
|
|
||||||
publishDM = async (event: Event, receiver: string): Promise<string[]> => {
|
|
||||||
// Connect to the application relay specified by environment variable
|
|
||||||
const appRelayPromise = this.connectRelay(import.meta.env.VITE_APP_RELAY)
|
|
||||||
|
|
||||||
// todo: window.nostr.getRelays() is not implemented yet in nostr-login, implement the logic once its done
|
|
||||||
|
|
||||||
const metadataController = await MetadataController.getInstance()
|
|
||||||
|
|
||||||
// Retrieve the list of read relays for the receiver
|
|
||||||
const readRelayUrls = await metadataController.findUserRelays(
|
|
||||||
receiver,
|
|
||||||
UserRelaysType.Read
|
|
||||||
)
|
|
||||||
|
|
||||||
// push admin relay urls obtained from metadata controller to readRelayUrls list
|
|
||||||
metadataController.adminRelays.forEach((url) => {
|
|
||||||
readRelayUrls.push(url)
|
|
||||||
})
|
|
||||||
|
|
||||||
// Connect to all write relays obtained from MetadataController
|
|
||||||
const relayPromises = readRelayUrls.map((relayUrl) =>
|
|
||||||
this.connectRelay(relayUrl)
|
|
||||||
)
|
|
||||||
|
|
||||||
// Wait for all relay connections to settle (either fulfilled or rejected)
|
|
||||||
await Promise.allSettled([appRelayPromise, ...relayPromises])
|
|
||||||
|
|
||||||
// Check if any relays are connected; if not, log an error and return null
|
|
||||||
if (this.connectedRelays.length === 0) {
|
|
||||||
log(this.debug, LogType.Error, 'No relay is connected!')
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
|
|
||||||
const publishedOnRelays: string[] = [] // List to track which relays successfully published the event
|
|
||||||
|
|
||||||
// Create a promise for publishing the event to each connected relay
|
|
||||||
const publishPromises = this.connectedRelays.map((relay) => {
|
|
||||||
log(
|
|
||||||
this.debug,
|
|
||||||
LogType.Info,
|
|
||||||
`⬆️ nostr (${relay.url}): Sending event:`,
|
|
||||||
event
|
|
||||||
)
|
|
||||||
|
|
||||||
return Promise.race([
|
|
||||||
relay.publish(event), // Publish the event to the relay
|
|
||||||
timeout(30000) // Set a timeout to handle cases where publishing takes too long
|
|
||||||
])
|
|
||||||
.then((res) => {
|
|
||||||
log(
|
|
||||||
this.debug,
|
|
||||||
LogType.Info,
|
|
||||||
`⬆️ nostr (${relay.url}): Publish result:`,
|
|
||||||
res
|
|
||||||
)
|
|
||||||
publishedOnRelays.push(relay.url) // Add the relay URL to the list of successfully published relays
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
log(
|
|
||||||
this.debug,
|
|
||||||
LogType.Error,
|
|
||||||
`❌ nostr (${relay.url}): Publish error!`,
|
|
||||||
err
|
|
||||||
)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
// Wait for all publish operations to complete (either fulfilled or rejected)
|
|
||||||
await Promise.allSettled(publishPromises)
|
|
||||||
|
|
||||||
// Return the list of relay URLs where the event was published
|
|
||||||
return publishedOnRelays
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Publishes an event to multiple relays.
|
|
||||||
*
|
|
||||||
* This method establishes a connection to the application relay specified by
|
|
||||||
* an environment variable and a set of relays provided as argument.
|
|
||||||
* It attempts to publish the event to all connected relays
|
|
||||||
* and returns a list of URLs of relays where the event was successfully published.
|
|
||||||
*
|
|
||||||
* If the process of publishing the event takes too long,
|
|
||||||
* it handles the timeout to prevent blocking the operation.
|
|
||||||
*
|
|
||||||
* @param event - The event to be published.
|
|
||||||
* @param relayUrls - The array of relayUrl where event should be published
|
|
||||||
* @returns A promise that resolves to an array of URLs of relays where the event
|
|
||||||
* was published, or an empty array if no relays were connected or the
|
|
||||||
* event could not be published.
|
|
||||||
*/
|
|
||||||
publishOnRelays = async (
|
|
||||||
event: Event,
|
|
||||||
relayUrls: string[]
|
|
||||||
): Promise<string[]> => {
|
|
||||||
const appRelay = import.meta.env.VITE_APP_RELAY
|
|
||||||
|
|
||||||
if (!relayUrls.includes(appRelay)) {
|
|
||||||
/**
|
|
||||||
* NOTE: To avoid side-effects on external relayUrls array passed as argument
|
|
||||||
* re-assigned relayUrls with added sigit relay instead of just appending to same array
|
|
||||||
*/
|
|
||||||
relayUrls = [...relayUrls, appRelay] // Add app relay to relays array if not exists already
|
|
||||||
}
|
|
||||||
|
|
||||||
// connect to all specified relays
|
|
||||||
const relayPromises = relayUrls.map((relayUrl) =>
|
|
||||||
this.connectRelay(relayUrl)
|
|
||||||
)
|
|
||||||
|
|
||||||
// Use Promise.allSettled to wait for all promises to settle
|
|
||||||
const results = await Promise.allSettled(relayPromises)
|
|
||||||
|
|
||||||
// Extract non-null values from fulfilled promises in a single pass
|
|
||||||
const relays = results.reduce<Relay[]>((acc, result) => {
|
|
||||||
if (result.status === 'fulfilled') {
|
|
||||||
const value = result.value
|
|
||||||
if (value) {
|
|
||||||
acc.push(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return acc
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
// Check if any relays are connected
|
|
||||||
if (relays.length === 0) {
|
|
||||||
log(this.debug, LogType.Error, 'No relay is connected!')
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
|
|
||||||
const publishedOnRelays: string[] = [] // Track relays where the event was successfully published
|
|
||||||
|
|
||||||
// Create promises to publish the event to each connected relay
|
|
||||||
const publishPromises = relays.map((relay) => {
|
|
||||||
log(
|
|
||||||
this.debug,
|
|
||||||
LogType.Info,
|
|
||||||
`⬆️ nostr (${relay.url}): Sending event:`,
|
|
||||||
event
|
|
||||||
)
|
|
||||||
|
|
||||||
return Promise.race([
|
|
||||||
relay.publish(event), // Publish the event to the relay
|
|
||||||
timeout(30000) // Set a timeout to handle slow publishing operations
|
|
||||||
])
|
|
||||||
.then((res) => {
|
|
||||||
log(
|
|
||||||
this.debug,
|
|
||||||
LogType.Info,
|
|
||||||
`⬆️ nostr (${relay.url}): Publish result:`,
|
|
||||||
res
|
|
||||||
)
|
|
||||||
publishedOnRelays.push(relay.url) // Add successful relay URL to the list
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
log(
|
|
||||||
this.debug,
|
|
||||||
LogType.Error,
|
|
||||||
`❌ nostr (${relay.url}): Publish error!`,
|
|
||||||
err
|
|
||||||
)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
// Wait for all publish operations to complete (either fulfilled or rejected)
|
|
||||||
await Promise.allSettled(publishPromises)
|
|
||||||
|
|
||||||
if (publishedOnRelays.length > 0) {
|
|
||||||
// If the event was successfully published to any relays, check if it contains an `aTag`
|
|
||||||
// If the `aTag` is present, cache the event locally
|
|
||||||
const aTag = event.tags.find((item) => item[0] === 'a')
|
|
||||||
if (aTag && aTag[1]) {
|
|
||||||
this.events.set(aTag[1], event)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return the list of relay URLs where the event was successfully published
|
|
||||||
return publishedOnRelays
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Subscribes to events from multiple relays.
|
|
||||||
*
|
|
||||||
* This method connects to the specified relay URLs and subscribes to events
|
|
||||||
* using the provided filter. It handles incoming events through the given
|
|
||||||
* `eventHandler` callback and manages the subscription lifecycle.
|
|
||||||
*
|
|
||||||
* @param filter - The filter criteria to apply when subscribing to events.
|
|
||||||
* @param relayUrls - An optional array of relay URLs to connect to. The default relay URL (`APP_RELAY`) is added automatically.
|
|
||||||
* @param eventHandler - A callback function to handle incoming events. It receives an `Event` object.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
subscribeForEvents = async (
|
|
||||||
filter: Filter,
|
|
||||||
relayUrls: string[] = [],
|
|
||||||
eventHandler: (event: Event) => void
|
|
||||||
) => {
|
|
||||||
const appRelay = import.meta.env.VITE_APP_RELAY
|
|
||||||
if (!relayUrls.includes(appRelay)) {
|
|
||||||
/**
|
|
||||||
* NOTE: To avoid side-effects on external relayUrls array passed as argument
|
|
||||||
* re-assigned relayUrls with added sigit relay instead of just appending to same array
|
|
||||||
*/
|
|
||||||
relayUrls = [...relayUrls, appRelay] // Add app relay to relays array if not exists already
|
|
||||||
}
|
|
||||||
|
|
||||||
// connect to all specified relays
|
|
||||||
const relayPromises = relayUrls.map((relayUrl) =>
|
|
||||||
this.connectRelay(relayUrl)
|
|
||||||
)
|
|
||||||
|
|
||||||
// Use Promise.allSettled to wait for all promises to settle
|
|
||||||
const results = await Promise.allSettled(relayPromises)
|
|
||||||
|
|
||||||
// Extract non-null values from fulfilled promises in a single pass
|
|
||||||
const relays = results.reduce<Relay[]>((acc, result) => {
|
|
||||||
if (result.status === 'fulfilled') {
|
|
||||||
const value = result.value
|
|
||||||
if (value) {
|
|
||||||
acc.push(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return acc
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
// Check if any relays are connected
|
|
||||||
if (relays.length === 0) {
|
|
||||||
throw new Error('No relay is connected to fetch events!')
|
|
||||||
}
|
|
||||||
|
|
||||||
const processedEvents: string[] = [] // To keep track of processed events
|
|
||||||
|
|
||||||
// Create a promise for each relay subscription
|
|
||||||
const subscriptions = relays.map((relay) =>
|
|
||||||
relay.subscribe([filter], {
|
|
||||||
// Handle incoming events
|
|
||||||
onevent: (e) => {
|
|
||||||
// Process event only if it hasn't been processed before
|
|
||||||
if (!processedEvents.includes(e.id)) {
|
|
||||||
processedEvents.push(e.id)
|
|
||||||
eventHandler(e) // Call the event handler with the event
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
return subscriptions
|
|
||||||
}
|
|
||||||
|
|
||||||
getTotalZapAmount = async (
|
|
||||||
user: string,
|
|
||||||
eTag: string,
|
|
||||||
aTag?: string,
|
|
||||||
currentLoggedInUser?: string
|
|
||||||
) => {
|
|
||||||
const metadataController = await MetadataController.getInstance()
|
|
||||||
|
|
||||||
const relayUrls = await metadataController.findUserRelays(
|
|
||||||
user,
|
|
||||||
UserRelaysType.Read
|
|
||||||
)
|
|
||||||
|
|
||||||
const appRelay = import.meta.env.VITE_APP_RELAY
|
|
||||||
if (!relayUrls.includes(appRelay)) {
|
|
||||||
relayUrls.push(appRelay)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Connect to all specified relays
|
|
||||||
const relayPromises = relayUrls.map((relayUrl) =>
|
|
||||||
this.connectRelay(relayUrl)
|
|
||||||
)
|
|
||||||
|
|
||||||
// Use Promise.allSettled to wait for all promises to settle
|
|
||||||
const results = await Promise.allSettled(relayPromises)
|
|
||||||
|
|
||||||
// Extract non-null values from fulfilled promises in a single pass
|
|
||||||
const relays = results.reduce<Relay[]>((acc, result) => {
|
|
||||||
if (result.status === 'fulfilled') {
|
|
||||||
const value = result.value
|
|
||||||
if (value) {
|
|
||||||
acc.push(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return acc
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
let accumulatedZapAmount = 0
|
|
||||||
let hasZapped = false
|
|
||||||
|
|
||||||
const eventIds = new Set<string>() // To keep track of event IDs and avoid duplicates
|
|
||||||
|
|
||||||
const filters: Filter[] = [
|
|
||||||
{
|
|
||||||
kinds: [kinds.Zap],
|
|
||||||
'#e': [eTag]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
if (aTag) {
|
|
||||||
filters.push({
|
|
||||||
kinds: [kinds.Zap],
|
|
||||||
'#a': [aTag]
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a promise for each relay subscription
|
|
||||||
const subPromises = relays.map((relay) => {
|
|
||||||
return new Promise<void>((resolve) => {
|
|
||||||
// Subscribe to the relay with the specified filter
|
|
||||||
const sub = relay.subscribe(filters, {
|
|
||||||
// Handle incoming events
|
|
||||||
onevent: (e) => {
|
|
||||||
// Add the event to the array if it's not a duplicate
|
|
||||||
if (!eventIds.has(e.id)) {
|
|
||||||
eventIds.add(e.id) // Record the event ID
|
|
||||||
|
|
||||||
const zapRequestStr = e.tags.find(
|
|
||||||
(t) => t[0] === 'description'
|
|
||||||
)?.[1]
|
|
||||||
if (!zapRequestStr) return
|
|
||||||
|
|
||||||
const error = nip57.validateZapRequest(zapRequestStr)
|
|
||||||
if (error) return
|
|
||||||
|
|
||||||
let zapRequest: Event | null = null
|
|
||||||
|
|
||||||
try {
|
|
||||||
zapRequest = JSON.parse(zapRequestStr)
|
|
||||||
} catch (error) {
|
|
||||||
log(
|
|
||||||
true,
|
|
||||||
LogType.Error,
|
|
||||||
'Error occurred in parsing zap request',
|
|
||||||
error
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!zapRequest) return
|
|
||||||
|
|
||||||
const amount = extractZapAmount(zapRequest)
|
|
||||||
accumulatedZapAmount += amount
|
|
||||||
|
|
||||||
if (amount > 0) {
|
|
||||||
if (!hasZapped) {
|
|
||||||
hasZapped = zapRequest.pubkey === currentLoggedInUser
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
// Handle the End-Of-Stream (EOSE) message
|
|
||||||
oneose: () => {
|
|
||||||
sub.close() // Close the subscription
|
|
||||||
resolve() // Resolve the promise when EOSE is received
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
// Wait for all subscriptions to complete
|
|
||||||
await Promise.allSettled(subPromises)
|
|
||||||
|
|
||||||
return {
|
|
||||||
accumulatedZapAmount,
|
|
||||||
hasZapped
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -17,7 +17,6 @@ import {
|
|||||||
ZapRequest
|
ZapRequest
|
||||||
} from '../types'
|
} from '../types'
|
||||||
import { log, LogType, npubToHex } from '../utils'
|
import { log, LogType, npubToHex } from '../utils'
|
||||||
import { MetadataController, UserRelaysType } from './metadata'
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Singleton class to manage zap related operations.
|
* Singleton class to manage zap related operations.
|
||||||
@ -48,6 +47,7 @@ export class ZapController {
|
|||||||
* @param lud16 - LUD-16 of the recipient.
|
* @param lud16 - LUD-16 of the recipient.
|
||||||
* @param amount - payment amount (will be multiplied by 1000 to represent sats).
|
* @param amount - payment amount (will be multiplied by 1000 to represent sats).
|
||||||
* @param recipientPubKey - pubKey of the recipient.
|
* @param recipientPubKey - pubKey of the recipient.
|
||||||
|
* @param recipientRelays - relays on which zap receipt will be published.
|
||||||
* @param senderPubkey - pubKey of of the sender.
|
* @param senderPubkey - pubKey of of the sender.
|
||||||
* @param content - optional content (comment).
|
* @param content - optional content (comment).
|
||||||
* @param eventId - event id, if zapping an event.
|
* @param eventId - event id, if zapping an event.
|
||||||
@ -59,6 +59,7 @@ export class ZapController {
|
|||||||
lud16: string,
|
lud16: string,
|
||||||
amount: number,
|
amount: number,
|
||||||
recipientPubKey: string,
|
recipientPubKey: string,
|
||||||
|
recipientRelays: string[],
|
||||||
senderPubkey: string,
|
senderPubkey: string,
|
||||||
content?: string,
|
content?: string,
|
||||||
eventId?: string,
|
eventId?: string,
|
||||||
@ -88,6 +89,7 @@ export class ZapController {
|
|||||||
amount,
|
amount,
|
||||||
content,
|
content,
|
||||||
recipientPubKey,
|
recipientPubKey,
|
||||||
|
recipientRelays,
|
||||||
senderPubkey,
|
senderPubkey,
|
||||||
eventId,
|
eventId,
|
||||||
aTag
|
aTag
|
||||||
@ -273,6 +275,7 @@ export class ZapController {
|
|||||||
* @param amount - request amount (sats).
|
* @param amount - request amount (sats).
|
||||||
* @param content - comment.
|
* @param content - comment.
|
||||||
* @param recipientPubKey - pubKey of the recipient.
|
* @param recipientPubKey - pubKey of the recipient.
|
||||||
|
* @param recipientRelays - relays on which zap receipt will be published.
|
||||||
* @param senderPubkey - pubKey of of the sender.
|
* @param senderPubkey - pubKey of of the sender.
|
||||||
* @param eventId - event id, if zapping an event.
|
* @param eventId - event id, if zapping an event.
|
||||||
* @param aTag - value of `a` tag.
|
* @param aTag - value of `a` tag.
|
||||||
@ -282,6 +285,7 @@ export class ZapController {
|
|||||||
amount: number,
|
amount: number,
|
||||||
content = '',
|
content = '',
|
||||||
recipientPubKey: string,
|
recipientPubKey: string,
|
||||||
|
recipientRelays: string[],
|
||||||
senderPubkey: string,
|
senderPubkey: string,
|
||||||
eventId?: string,
|
eventId?: string,
|
||||||
aTag?: string
|
aTag?: string
|
||||||
@ -290,21 +294,15 @@ export class ZapController {
|
|||||||
|
|
||||||
if (!recipientHexKey) throw 'Invalid recipient pubKey.'
|
if (!recipientHexKey) throw 'Invalid recipient pubKey.'
|
||||||
|
|
||||||
const metadataController = await MetadataController.getInstance()
|
if (!recipientRelays.includes(this.appRelay)) {
|
||||||
const receiverReadRelays = await metadataController.findUserRelays(
|
recipientRelays.push(this.appRelay)
|
||||||
recipientHexKey,
|
|
||||||
UserRelaysType.Read
|
|
||||||
)
|
|
||||||
|
|
||||||
if (!receiverReadRelays.includes(this.appRelay)) {
|
|
||||||
receiverReadRelays.push(this.appRelay)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const zapRequest: ZapRequest = {
|
const zapRequest: ZapRequest = {
|
||||||
kind: kinds.ZapRequest,
|
kind: kinds.ZapRequest,
|
||||||
content,
|
content,
|
||||||
tags: [
|
tags: [
|
||||||
['relays', ...receiverReadRelays],
|
['relays', ...recipientRelays],
|
||||||
['amount', `${amount}`],
|
['amount', `${amount}`],
|
||||||
['p', recipientHexKey]
|
['p', recipientHexKey]
|
||||||
],
|
],
|
||||||
|
@ -6,3 +6,4 @@ export * from './useMuteLists'
|
|||||||
export * from './useNSFWList'
|
export * from './useNSFWList'
|
||||||
export * from './useReactions'
|
export * from './useReactions'
|
||||||
export * from './useNDKContext'
|
export * from './useNDKContext'
|
||||||
|
export * from './useScrollDisable'
|
||||||
|
@ -6,9 +6,8 @@ import {
|
|||||||
NDKSubscription,
|
NDKSubscription,
|
||||||
NDKSubscriptionCacheUsage
|
NDKSubscriptionCacheUsage
|
||||||
} from '@nostr-dev-kit/ndk'
|
} from '@nostr-dev-kit/ndk'
|
||||||
import { UserRelaysType } from 'controllers'
|
|
||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import { CommentEvent, ModDetails } from 'types'
|
import { CommentEvent, ModDetails, UserRelaysType } from 'types'
|
||||||
import { log, LogType } from 'utils'
|
import { log, LogType } from 'utils'
|
||||||
import { useNDKContext } from './useNDKContext'
|
import { useNDKContext } from './useNDKContext'
|
||||||
|
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import { MuteLists } from 'types'
|
import { MuteLists } from 'types'
|
||||||
import { useAppSelector } from './redux'
|
import { useAppSelector } from './redux'
|
||||||
import { MetadataController } from 'controllers'
|
import { useNDKContext } from './useNDKContext'
|
||||||
|
|
||||||
export const useMuteLists = () => {
|
export const useMuteLists = () => {
|
||||||
|
const { getMuteLists } = useNDKContext()
|
||||||
const [muteLists, setMuteLists] = useState<{
|
const [muteLists, setMuteLists] = useState<{
|
||||||
admin: MuteLists
|
admin: MuteLists
|
||||||
user: MuteLists
|
user: MuteLists
|
||||||
@ -21,17 +22,11 @@ export const useMuteLists = () => {
|
|||||||
const userState = useAppSelector((state) => state.user)
|
const userState = useAppSelector((state) => state.user)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const getMuteLists = async () => {
|
|
||||||
const pubkey = userState.user?.pubkey as string | undefined
|
const pubkey = userState.user?.pubkey as string | undefined
|
||||||
|
getMuteLists(pubkey).then((lists) => {
|
||||||
const metadataController = await MetadataController.getInstance()
|
|
||||||
metadataController.getMuteLists(pubkey).then((lists) => {
|
|
||||||
setMuteLists(lists)
|
setMuteLists(lists)
|
||||||
})
|
})
|
||||||
}
|
}, [userState, getMuteLists])
|
||||||
|
|
||||||
getMuteLists()
|
|
||||||
}, [userState])
|
|
||||||
|
|
||||||
return muteLists
|
return muteLists
|
||||||
}
|
}
|
||||||
|
@ -9,23 +9,5 @@ export const useNDKContext = () => {
|
|||||||
'NDKContext should not be used in out component tree hierarchy'
|
'NDKContext should not be used in out component tree hierarchy'
|
||||||
)
|
)
|
||||||
|
|
||||||
const {
|
return { ...ndkContext }
|
||||||
ndk,
|
|
||||||
fetchEvents,
|
|
||||||
fetchEvent,
|
|
||||||
fetchEventsFromUserRelays,
|
|
||||||
fetchEventFromUserRelays,
|
|
||||||
fetchMods,
|
|
||||||
findMetadata
|
|
||||||
} = ndkContext
|
|
||||||
|
|
||||||
return {
|
|
||||||
ndk,
|
|
||||||
fetchEvents,
|
|
||||||
fetchEvent,
|
|
||||||
fetchEventsFromUserRelays,
|
|
||||||
fetchEventFromUserRelays,
|
|
||||||
fetchMods,
|
|
||||||
findMetadata
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,14 +1,13 @@
|
|||||||
import { MetadataController } from 'controllers'
|
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { useDidMount } from './useDidMount'
|
import { useDidMount } from './useDidMount'
|
||||||
|
import { useNDKContext } from './useNDKContext'
|
||||||
|
|
||||||
export const useNSFWList = () => {
|
export const useNSFWList = () => {
|
||||||
|
const { getNSFWList } = useNDKContext()
|
||||||
const [nsfwList, setNSFWList] = useState<string[]>([])
|
const [nsfwList, setNSFWList] = useState<string[]>([])
|
||||||
|
|
||||||
useDidMount(async () => {
|
useDidMount(async () => {
|
||||||
const metadataController = await MetadataController.getInstance()
|
getNSFWList().then((list) => {
|
||||||
|
|
||||||
metadataController.getNSFWList().then((list) => {
|
|
||||||
setNSFWList(list)
|
setNSFWList(list)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import { NDKEvent, NDKFilter } from '@nostr-dev-kit/ndk'
|
import { NDKEvent, NDKFilter } from '@nostr-dev-kit/ndk'
|
||||||
import { REACTIONS } from 'constants.ts'
|
import { REACTIONS } from 'constants.ts'
|
||||||
import { RelayController, UserRelaysType } from 'controllers'
|
|
||||||
import { useAppSelector, useDidMount, useNDKContext } from 'hooks'
|
import { useAppSelector, useDidMount, useNDKContext } from 'hooks'
|
||||||
import { Event, kinds, UnsignedEvent } from 'nostr-tools'
|
import { Event, kinds, UnsignedEvent } from 'nostr-tools'
|
||||||
import { useMemo, useState } from 'react'
|
import { useMemo, useState } from 'react'
|
||||||
import { toast } from 'react-toastify'
|
import { toast } from 'react-toastify'
|
||||||
|
import { UserRelaysType } from 'types'
|
||||||
import { abbreviateNumber, log, LogType, now } from 'utils'
|
import { abbreviateNumber, log, LogType, now } from 'utils'
|
||||||
|
|
||||||
type UseReactionsParams = {
|
type UseReactionsParams = {
|
||||||
@ -14,7 +14,7 @@ type UseReactionsParams = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const useReactions = (params: UseReactionsParams) => {
|
export const useReactions = (params: UseReactionsParams) => {
|
||||||
const { ndk, fetchEventsFromUserRelays } = useNDKContext()
|
const { ndk, fetchEventsFromUserRelays, publish } = useNDKContext()
|
||||||
const [isReactionInProgress, setIsReactionInProgress] = useState(false)
|
const [isReactionInProgress, setIsReactionInProgress] = useState(false)
|
||||||
const [isDataLoaded, setIsDataLoaded] = useState(false)
|
const [isDataLoaded, setIsDataLoaded] = useState(false)
|
||||||
const [reactionEvents, setReactionEvents] = useState<NDKEvent[]>([])
|
const [reactionEvents, setReactionEvents] = useState<NDKEvent[]>([])
|
||||||
@ -119,13 +119,11 @@ export const useReactions = (params: UseReactionsParams) => {
|
|||||||
|
|
||||||
if (!signedEvent) return
|
if (!signedEvent) return
|
||||||
|
|
||||||
setReactionEvents((prev) => [...prev, new NDKEvent(ndk, signedEvent)])
|
const ndkEvent = new NDKEvent(ndk, signedEvent)
|
||||||
|
|
||||||
const publishedOnRelays = await RelayController.getInstance().publish(
|
setReactionEvents((prev) => [...prev, ndkEvent])
|
||||||
signedEvent as Event,
|
|
||||||
params.pubkey,
|
const publishedOnRelays = await publish(ndkEvent)
|
||||||
UserRelaysType.Read
|
|
||||||
)
|
|
||||||
|
|
||||||
if (publishedOnRelays.length === 0) {
|
if (publishedOnRelays.length === 0) {
|
||||||
log(
|
log(
|
||||||
|
11
src/hooks/useScrollDisable.ts
Normal file
11
src/hooks/useScrollDisable.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import { useEffect } from 'react'
|
||||||
|
|
||||||
|
export const useBodyScrollDisable = (disable: boolean) => {
|
||||||
|
useEffect(() => {
|
||||||
|
if (disable) document.body.style.overflow = 'hidden'
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
document.body.style.overflow = ''
|
||||||
|
}
|
||||||
|
}, [disable])
|
||||||
|
}
|
@ -6,10 +6,10 @@ import React, { useEffect, useState } from 'react'
|
|||||||
import { Link } from 'react-router-dom'
|
import { Link } from 'react-router-dom'
|
||||||
import { Banner } from '../components/Banner'
|
import { Banner } from '../components/Banner'
|
||||||
import { ZapPopUp } from '../components/Zap'
|
import { ZapPopUp } from '../components/Zap'
|
||||||
import { MetadataController } from '../controllers'
|
|
||||||
import {
|
import {
|
||||||
useAppDispatch,
|
useAppDispatch,
|
||||||
useAppSelector,
|
useAppSelector,
|
||||||
|
useBodyScrollDisable,
|
||||||
useDidMount,
|
useDidMount,
|
||||||
useNDKContext
|
useNDKContext
|
||||||
} from '../hooks'
|
} from '../hooks'
|
||||||
@ -27,6 +27,18 @@ export const Header = () => {
|
|||||||
const { findMetadata } = useNDKContext()
|
const { findMetadata } = useNDKContext()
|
||||||
const userState = useAppSelector((state) => state.user)
|
const userState = useAppSelector((state) => state.user)
|
||||||
|
|
||||||
|
// Track nostr-login extension modal open state
|
||||||
|
const [isOpen, setIsOpen] = useState(false)
|
||||||
|
const handleOpen = () => setIsOpen(true)
|
||||||
|
const handleClose = () => setIsOpen(false)
|
||||||
|
useEffect(() => {
|
||||||
|
window.addEventListener('nlCloseModal', handleClose)
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener('nlCloseModal', handleClose)
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
useBodyScrollDisable(isOpen)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
initNostrLogin({
|
initNostrLogin({
|
||||||
darkMode: true,
|
darkMode: true,
|
||||||
@ -66,6 +78,7 @@ export const Header = () => {
|
|||||||
}, [dispatch, findMetadata])
|
}, [dispatch, findMetadata])
|
||||||
|
|
||||||
const handleLogin = () => {
|
const handleLogin = () => {
|
||||||
|
handleOpen()
|
||||||
launchNostrLoginDialog()
|
launchNostrLoginDialog()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -260,9 +273,11 @@ const TipButtonWithDialog = React.memo(() => {
|
|||||||
const [adminNpub, setAdminNpub] = useState<string | null>(null)
|
const [adminNpub, setAdminNpub] = useState<string | null>(null)
|
||||||
const [isOpen, setIsOpen] = useState(false)
|
const [isOpen, setIsOpen] = useState(false)
|
||||||
|
|
||||||
|
useBodyScrollDisable(isOpen)
|
||||||
|
|
||||||
useDidMount(async () => {
|
useDidMount(async () => {
|
||||||
const metadataController = await MetadataController.getInstance()
|
const adminNpubs = import.meta.env.VITE_ADMIN_NPUBS.split(',')
|
||||||
setAdminNpub(metadataController.adminNpubs[0])
|
setAdminNpub(adminNpubs[0])
|
||||||
})
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -321,6 +336,8 @@ const TipButtonWithDialog = React.memo(() => {
|
|||||||
const RegisterButtonWithDialog = () => {
|
const RegisterButtonWithDialog = () => {
|
||||||
const [showPopUp, setShowPopUp] = useState(false)
|
const [showPopUp, setShowPopUp] = useState(false)
|
||||||
|
|
||||||
|
useBodyScrollDisable(showPopUp)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<a
|
<a
|
||||||
|
@ -14,7 +14,7 @@ import {
|
|||||||
useNDKContext,
|
useNDKContext,
|
||||||
useNSFWList
|
useNSFWList
|
||||||
} from 'hooks'
|
} from 'hooks'
|
||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useRef, useState } from 'react'
|
||||||
import { useParams } from 'react-router-dom'
|
import { useParams } from 'react-router-dom'
|
||||||
import {
|
import {
|
||||||
FilterOptions,
|
FilterOptions,
|
||||||
@ -23,9 +23,10 @@ import {
|
|||||||
NSFWFilter,
|
NSFWFilter,
|
||||||
SortBy
|
SortBy
|
||||||
} from 'types'
|
} from 'types'
|
||||||
import { extractModData, isModDataComplete } from 'utils'
|
import { extractModData, isModDataComplete, scrollIntoView } from 'utils'
|
||||||
|
|
||||||
export const GamePage = () => {
|
export const GamePage = () => {
|
||||||
|
const scrollTargetRef = useRef<HTMLDivElement>(null)
|
||||||
const params = useParams()
|
const params = useParams()
|
||||||
const { name: gameName } = params
|
const { name: gameName } = params
|
||||||
const { ndk } = useNDKContext()
|
const { ndk } = useNDKContext()
|
||||||
@ -61,6 +62,7 @@ export const GamePage = () => {
|
|||||||
|
|
||||||
const handlePageChange = (page: number) => {
|
const handlePageChange = (page: number) => {
|
||||||
if (page >= 1 && page <= totalPages) {
|
if (page >= 1 && page <= totalPages) {
|
||||||
|
scrollIntoView(scrollTargetRef.current)
|
||||||
setCurrentPage(page)
|
setCurrentPage(page)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -102,7 +104,10 @@ export const GamePage = () => {
|
|||||||
<>
|
<>
|
||||||
<div className='InnerBodyMain'>
|
<div className='InnerBodyMain'>
|
||||||
<div className='ContainerMain'>
|
<div className='ContainerMain'>
|
||||||
<div className='IBMSecMainGroup IBMSecMainGroupAlt'>
|
<div
|
||||||
|
className='IBMSecMainGroup IBMSecMainGroupAlt'
|
||||||
|
ref={scrollTargetRef}
|
||||||
|
>
|
||||||
<div className='IBMSecMain'>
|
<div className='IBMSecMain'>
|
||||||
<div className='SearchMainWrapper'>
|
<div className='SearchMainWrapper'>
|
||||||
<div className='IBMSMTitleMain'>
|
<div className='IBMSMTitleMain'>
|
||||||
|
@ -8,8 +8,10 @@ import '../styles/search.css'
|
|||||||
import '../styles/styles.css'
|
import '../styles/styles.css'
|
||||||
import { createSearchParams, useNavigate } from 'react-router-dom'
|
import { createSearchParams, useNavigate } from 'react-router-dom'
|
||||||
import { appRoutes } from 'routes'
|
import { appRoutes } from 'routes'
|
||||||
|
import { scrollIntoView } from 'utils'
|
||||||
|
|
||||||
export const GamesPage = () => {
|
export const GamesPage = () => {
|
||||||
|
const scrollTargetRef = useRef<HTMLDivElement>(null)
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const { fetchMods } = useNDKContext()
|
const { fetchMods } = useNDKContext()
|
||||||
const searchTermRef = useRef<HTMLInputElement>(null)
|
const searchTermRef = useRef<HTMLInputElement>(null)
|
||||||
@ -63,6 +65,7 @@ export const GamesPage = () => {
|
|||||||
|
|
||||||
const handlePageChange = (page: number) => {
|
const handlePageChange = (page: number) => {
|
||||||
if (page >= 1 && page <= totalPages) {
|
if (page >= 1 && page <= totalPages) {
|
||||||
|
scrollIntoView(scrollTargetRef.current)
|
||||||
setCurrentPage(page)
|
setCurrentPage(page)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -88,7 +91,10 @@ export const GamesPage = () => {
|
|||||||
return (
|
return (
|
||||||
<div className='InnerBodyMain'>
|
<div className='InnerBodyMain'>
|
||||||
<div className='ContainerMain'>
|
<div className='ContainerMain'>
|
||||||
<div className='IBMSecMainGroup IBMSecMainGroupAlt'>
|
<div
|
||||||
|
className='IBMSecMainGroup IBMSecMainGroupAlt'
|
||||||
|
ref={scrollTargetRef}
|
||||||
|
>
|
||||||
<div className='IBMSecMain'>
|
<div className='IBMSecMain'>
|
||||||
<div className='SearchMainWrapper'>
|
<div className='SearchMainWrapper'>
|
||||||
<div className='IBMSMTitleMain'>
|
<div className='IBMSMTitleMain'>
|
||||||
|
@ -152,7 +152,7 @@ const SlideContent = ({ naddr }: SlideContentProps) => {
|
|||||||
|
|
||||||
useDidMount(() => {
|
useDidMount(() => {
|
||||||
const decoded = nip19.decode<'naddr'>(naddr as `naddr1${string}`)
|
const decoded = nip19.decode<'naddr'>(naddr as `naddr1${string}`)
|
||||||
const { identifier, kind, pubkey, relays = [] } = decoded.data
|
const { identifier, kind, pubkey } = decoded.data
|
||||||
|
|
||||||
const ndkFilter: NDKFilter = {
|
const ndkFilter: NDKFilter = {
|
||||||
'#a': [identifier],
|
'#a': [identifier],
|
||||||
@ -160,7 +160,7 @@ const SlideContent = ({ naddr }: SlideContentProps) => {
|
|||||||
kinds: [kind]
|
kinds: [kind]
|
||||||
}
|
}
|
||||||
|
|
||||||
fetchEvent(ndkFilter, relays)
|
fetchEvent(ndkFilter)
|
||||||
.then((ndkEvent) => {
|
.then((ndkEvent) => {
|
||||||
if (ndkEvent) {
|
if (ndkEvent) {
|
||||||
const extracted = extractModData(ndkEvent)
|
const extracted = extractModData(ndkEvent)
|
||||||
@ -225,7 +225,7 @@ const DisplayMod = ({ naddr }: DisplayModProps) => {
|
|||||||
|
|
||||||
useDidMount(() => {
|
useDidMount(() => {
|
||||||
const decoded = nip19.decode<'naddr'>(naddr as `naddr1${string}`)
|
const decoded = nip19.decode<'naddr'>(naddr as `naddr1${string}`)
|
||||||
const { identifier, kind, pubkey, relays = [] } = decoded.data
|
const { identifier, kind, pubkey } = decoded.data
|
||||||
|
|
||||||
const ndkFilter: NDKFilter = {
|
const ndkFilter: NDKFilter = {
|
||||||
'#a': [identifier],
|
'#a': [identifier],
|
||||||
@ -233,7 +233,7 @@ const DisplayMod = ({ naddr }: DisplayModProps) => {
|
|||||||
kinds: [kind]
|
kinds: [kind]
|
||||||
}
|
}
|
||||||
|
|
||||||
fetchEvent(ndkFilter, relays)
|
fetchEvent(ndkFilter)
|
||||||
.then((ndkEvent) => {
|
.then((ndkEvent) => {
|
||||||
if (ndkEvent) {
|
if (ndkEvent) {
|
||||||
const extracted = extractModData(ndkEvent)
|
const extracted = extractModData(ndkEvent)
|
||||||
|
@ -11,8 +11,12 @@ import { toast } from 'react-toastify'
|
|||||||
import { BlogCard } from '../../components/BlogCard'
|
import { BlogCard } from '../../components/BlogCard'
|
||||||
import { LoadingSpinner } from '../../components/LoadingSpinner'
|
import { LoadingSpinner } from '../../components/LoadingSpinner'
|
||||||
import { ProfileSection } from '../../components/ProfileSection'
|
import { ProfileSection } from '../../components/ProfileSection'
|
||||||
import { MetadataController, UserRelaysType } from '../../controllers'
|
import {
|
||||||
import { useAppSelector, useDidMount, useNDKContext } from '../../hooks'
|
useAppSelector,
|
||||||
|
useBodyScrollDisable,
|
||||||
|
useDidMount,
|
||||||
|
useNDKContext
|
||||||
|
} from '../../hooks'
|
||||||
import { getGamePageRoute, getModsEditPageRoute } from '../../routes'
|
import { getGamePageRoute, getModsEditPageRoute } from '../../routes'
|
||||||
import '../../styles/comments.css'
|
import '../../styles/comments.css'
|
||||||
import '../../styles/downloads.css'
|
import '../../styles/downloads.css'
|
||||||
@ -24,7 +28,7 @@ import '../../styles/styles.css'
|
|||||||
import '../../styles/tabs.css'
|
import '../../styles/tabs.css'
|
||||||
import '../../styles/tags.css'
|
import '../../styles/tags.css'
|
||||||
import '../../styles/write.css'
|
import '../../styles/write.css'
|
||||||
import { DownloadUrl, ModDetails } from '../../types'
|
import { DownloadUrl, ModDetails, UserRelaysType } from '../../types'
|
||||||
import {
|
import {
|
||||||
abbreviateNumber,
|
abbreviateNumber,
|
||||||
copyTextToClipboard,
|
copyTextToClipboard,
|
||||||
@ -53,7 +57,7 @@ export const ModPage = () => {
|
|||||||
useDidMount(async () => {
|
useDidMount(async () => {
|
||||||
if (naddr) {
|
if (naddr) {
|
||||||
const decoded = nip19.decode<'naddr'>(naddr as `naddr1${string}`)
|
const decoded = nip19.decode<'naddr'>(naddr as `naddr1${string}`)
|
||||||
const { identifier, kind, pubkey, relays = [] } = decoded.data
|
const { identifier, kind, pubkey } = decoded.data
|
||||||
|
|
||||||
const filter: NDKFilter = {
|
const filter: NDKFilter = {
|
||||||
'#a': [identifier],
|
'#a': [identifier],
|
||||||
@ -61,7 +65,7 @@ export const ModPage = () => {
|
|||||||
kinds: [kind]
|
kinds: [kind]
|
||||||
}
|
}
|
||||||
|
|
||||||
fetchEvent(filter, relays)
|
fetchEvent(filter)
|
||||||
.then((event) => {
|
.then((event) => {
|
||||||
if (event) {
|
if (event) {
|
||||||
const extracted = extractModData(event)
|
const extracted = extractModData(event)
|
||||||
@ -212,7 +216,7 @@ type GameProps = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const Game = ({ naddr, game, author, aTag }: GameProps) => {
|
const Game = ({ naddr, game, author, aTag }: GameProps) => {
|
||||||
const { fetchEventFromUserRelays } = useNDKContext()
|
const { ndk, fetchEventFromUserRelays, publish } = useNDKContext()
|
||||||
|
|
||||||
const userState = useAppSelector((state) => state.user)
|
const userState = useAppSelector((state) => state.user)
|
||||||
const [isLoading, setIsLoading] = useState(false)
|
const [isLoading, setIsLoading] = useState(false)
|
||||||
@ -221,6 +225,8 @@ const Game = ({ naddr, game, author, aTag }: GameProps) => {
|
|||||||
const [isBlocked, setIsBlocked] = useState(false)
|
const [isBlocked, setIsBlocked] = useState(false)
|
||||||
const [isAddedToNSFW, setIsAddedToNSFW] = useState(false)
|
const [isAddedToNSFW, setIsAddedToNSFW] = useState(false)
|
||||||
|
|
||||||
|
useBodyScrollDisable(showReportPopUp)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (userState.auth && userState.user?.pubkey) {
|
if (userState.auth && userState.user?.pubkey) {
|
||||||
const pubkey = userState.user.pubkey as string
|
const pubkey = userState.user.pubkey as string
|
||||||
@ -343,7 +349,7 @@ const Game = ({ naddr, game, author, aTag }: GameProps) => {
|
|||||||
|
|
||||||
setLoadingSpinnerDesc('Updating mute list event')
|
setLoadingSpinnerDesc('Updating mute list event')
|
||||||
|
|
||||||
const isUpdated = await signAndPublish(unsignedEvent)
|
const isUpdated = await signAndPublish(unsignedEvent, ndk, publish)
|
||||||
if (isUpdated) {
|
if (isUpdated) {
|
||||||
setIsBlocked(true)
|
setIsBlocked(true)
|
||||||
}
|
}
|
||||||
@ -384,7 +390,7 @@ const Game = ({ naddr, game, author, aTag }: GameProps) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setLoadingSpinnerDesc('Updating mute list event')
|
setLoadingSpinnerDesc('Updating mute list event')
|
||||||
const isUpdated = await signAndPublish(unsignedEvent)
|
const isUpdated = await signAndPublish(unsignedEvent, ndk, publish)
|
||||||
if (isUpdated) {
|
if (isUpdated) {
|
||||||
setIsBlocked(false)
|
setIsBlocked(false)
|
||||||
}
|
}
|
||||||
@ -450,7 +456,7 @@ const Game = ({ naddr, game, author, aTag }: GameProps) => {
|
|||||||
|
|
||||||
setLoadingSpinnerDesc('Updating nsfw list event')
|
setLoadingSpinnerDesc('Updating nsfw list event')
|
||||||
|
|
||||||
const isUpdated = await signAndPublish(unsignedEvent)
|
const isUpdated = await signAndPublish(unsignedEvent, ndk, publish)
|
||||||
if (isUpdated) {
|
if (isUpdated) {
|
||||||
setIsAddedToNSFW(true)
|
setIsAddedToNSFW(true)
|
||||||
}
|
}
|
||||||
@ -491,7 +497,7 @@ const Game = ({ naddr, game, author, aTag }: GameProps) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setLoadingSpinnerDesc('Updating nsfw list event')
|
setLoadingSpinnerDesc('Updating nsfw list event')
|
||||||
const isUpdated = await signAndPublish(unsignedEvent)
|
const isUpdated = await signAndPublish(unsignedEvent, ndk, publish)
|
||||||
if (isUpdated) {
|
if (isUpdated) {
|
||||||
setIsAddedToNSFW(false)
|
setIsAddedToNSFW(false)
|
||||||
}
|
}
|
||||||
@ -661,7 +667,7 @@ type ReportPopupProps = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const ReportPopup = ({ aTag, handleClose }: ReportPopupProps) => {
|
const ReportPopup = ({ aTag, handleClose }: ReportPopupProps) => {
|
||||||
const { fetchEventFromUserRelays } = useNDKContext()
|
const { ndk, fetchEventFromUserRelays, publish } = useNDKContext()
|
||||||
const userState = useAppSelector((state) => state.user)
|
const userState = useAppSelector((state) => state.user)
|
||||||
const [selectedOptions, setSelectedOptions] = useState({
|
const [selectedOptions, setSelectedOptions] = useState({
|
||||||
actuallyCP: false,
|
actuallyCP: false,
|
||||||
@ -708,8 +714,8 @@ const ReportPopup = ({ aTag, handleClose }: ReportPopupProps) => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const metadataController = await MetadataController.getInstance()
|
const reportingNpub = import.meta.env.VITE_REPORTING_NPUB
|
||||||
const reportingPubkey = npubToHex(metadataController.reportingNpub)
|
const reportingPubkey = npubToHex(reportingNpub)
|
||||||
|
|
||||||
if (reportingPubkey === hexPubkey) {
|
if (reportingPubkey === hexPubkey) {
|
||||||
setLoadingSpinnerDesc(`Finding user's mute list`)
|
setLoadingSpinnerDesc(`Finding user's mute list`)
|
||||||
@ -760,7 +766,7 @@ const ReportPopup = ({ aTag, handleClose }: ReportPopupProps) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setLoadingSpinnerDesc('Updating mute list event')
|
setLoadingSpinnerDesc('Updating mute list event')
|
||||||
const isUpdated = await signAndPublish(unsignedEvent)
|
const isUpdated = await signAndPublish(unsignedEvent, ndk, publish)
|
||||||
if (isUpdated) handleClose()
|
if (isUpdated) handleClose()
|
||||||
} else {
|
} else {
|
||||||
const href = window.location.href
|
const href = window.location.href
|
||||||
@ -773,7 +779,12 @@ const ReportPopup = ({ aTag, handleClose }: ReportPopupProps) => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
setLoadingSpinnerDesc('Sending report')
|
setLoadingSpinnerDesc('Sending report')
|
||||||
const isSent = await sendDMUsingRandomKey(message, reportingPubkey!)
|
const isSent = await sendDMUsingRandomKey(
|
||||||
|
message,
|
||||||
|
reportingPubkey!,
|
||||||
|
ndk,
|
||||||
|
publish
|
||||||
|
)
|
||||||
if (isSent) handleClose()
|
if (isSent) handleClose()
|
||||||
}
|
}
|
||||||
setIsLoading(false)
|
setIsLoading(false)
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
|
import { NDKEvent } from '@nostr-dev-kit/ndk'
|
||||||
import { ZapPopUp } from 'components/Zap'
|
import { ZapPopUp } from 'components/Zap'
|
||||||
import {
|
|
||||||
MetadataController,
|
|
||||||
RelayController,
|
|
||||||
UserRelaysType
|
|
||||||
} from 'controllers'
|
|
||||||
import { formatDate } from 'date-fns'
|
import { formatDate } from 'date-fns'
|
||||||
import { useAppSelector, useDidMount, useNDKContext, useReactions } from 'hooks'
|
import {
|
||||||
|
useAppSelector,
|
||||||
|
useBodyScrollDisable,
|
||||||
|
useDidMount,
|
||||||
|
useNDKContext,
|
||||||
|
useReactions
|
||||||
|
} from 'hooks'
|
||||||
import { useComments } from 'hooks/useComments'
|
import { useComments } from 'hooks/useComments'
|
||||||
import { Event, kinds, nip19, UnsignedEvent } from 'nostr-tools'
|
import { Event, kinds, nip19, UnsignedEvent } from 'nostr-tools'
|
||||||
import React, {
|
import React, {
|
||||||
@ -47,6 +49,7 @@ type Props = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const Comments = ({ modDetails, setCommentCount }: Props) => {
|
export const Comments = ({ modDetails, setCommentCount }: Props) => {
|
||||||
|
const { ndk, publish } = useNDKContext()
|
||||||
const { commentEvents, setCommentEvents } = useComments(modDetails)
|
const { commentEvents, setCommentEvents } = useComments(modDetails)
|
||||||
const [filterOptions, setFilterOptions] = useState<FilterOptions>({
|
const [filterOptions, setFilterOptions] = useState<FilterOptions>({
|
||||||
sort: SortByEnum.Latest,
|
sort: SortByEnum.Latest,
|
||||||
@ -82,7 +85,8 @@ export const Comments = ({ modDetails, setCommentCount }: Props) => {
|
|||||||
created_at: now(),
|
created_at: now(),
|
||||||
tags: [
|
tags: [
|
||||||
['e', modDetails.id],
|
['e', modDetails.id],
|
||||||
['a', modDetails.aTag]
|
['a', modDetails.aTag],
|
||||||
|
['p', modDetails.author]
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -105,27 +109,9 @@ export const Comments = ({ modDetails, setCommentCount }: Props) => {
|
|||||||
...prev
|
...prev
|
||||||
])
|
])
|
||||||
|
|
||||||
const publish = async () => {
|
const ndkEvent = new NDKEvent(ndk, signedEvent)
|
||||||
const metadataController = await MetadataController.getInstance()
|
publish(ndkEvent)
|
||||||
const modAuthorReadRelays = await metadataController.findUserRelays(
|
.then((publishedOnRelays) => {
|
||||||
modDetails.author,
|
|
||||||
UserRelaysType.Read
|
|
||||||
)
|
|
||||||
const commentatorWriteRelays = await metadataController.findUserRelays(
|
|
||||||
pubkey,
|
|
||||||
UserRelaysType.Write
|
|
||||||
)
|
|
||||||
|
|
||||||
const combinedRelays = [
|
|
||||||
...new Set(...modAuthorReadRelays, ...commentatorWriteRelays)
|
|
||||||
]
|
|
||||||
|
|
||||||
const publishedOnRelays =
|
|
||||||
await RelayController.getInstance().publishOnRelays(
|
|
||||||
signedEvent,
|
|
||||||
combinedRelays
|
|
||||||
)
|
|
||||||
|
|
||||||
if (publishedOnRelays.length === 0) {
|
if (publishedOnRelays.length === 0) {
|
||||||
setCommentEvents((prev) =>
|
setCommentEvents((prev) =>
|
||||||
prev.map((event) => {
|
prev.map((event) => {
|
||||||
@ -166,9 +152,22 @@ export const Comments = ({ modDetails, setCommentCount }: Props) => {
|
|||||||
})
|
})
|
||||||
)
|
)
|
||||||
}, 15000)
|
}, 15000)
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.error('An error occurred in publishing comment', err)
|
||||||
|
setCommentEvents((prev) =>
|
||||||
|
prev.map((event) => {
|
||||||
|
if (event.id === signedEvent.id) {
|
||||||
|
return {
|
||||||
|
...event,
|
||||||
|
status: CommentEventStatus.Failed
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
publish()
|
return event
|
||||||
|
})
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@ -496,15 +495,16 @@ const Reactions = (props: Event) => {
|
|||||||
|
|
||||||
const Zap = (props: Event) => {
|
const Zap = (props: Event) => {
|
||||||
const [isOpen, setIsOpen] = useState(false)
|
const [isOpen, setIsOpen] = useState(false)
|
||||||
|
const [totalZappedAmount, setTotalZappedAmount] = useState(0)
|
||||||
const [hasZapped, setHasZapped] = useState(false)
|
const [hasZapped, setHasZapped] = useState(false)
|
||||||
|
|
||||||
const userState = useAppSelector((state) => state.user)
|
const userState = useAppSelector((state) => state.user)
|
||||||
|
const { getTotalZapAmount } = useNDKContext()
|
||||||
|
|
||||||
const [totalZappedAmount, setTotalZappedAmount] = useState(0)
|
useBodyScrollDisable(isOpen)
|
||||||
|
|
||||||
useDidMount(() => {
|
useDidMount(() => {
|
||||||
RelayController.getInstance()
|
getTotalZapAmount(
|
||||||
.getTotalZapAmount(
|
|
||||||
props.pubkey,
|
props.pubkey,
|
||||||
props.id,
|
props.id,
|
||||||
undefined,
|
undefined,
|
||||||
|
@ -1,6 +1,10 @@
|
|||||||
import { ZapSplit } from 'components/Zap'
|
import { ZapSplit } from 'components/Zap'
|
||||||
import { RelayController } from 'controllers'
|
import {
|
||||||
import { useAppSelector, useDidMount } from 'hooks'
|
useAppSelector,
|
||||||
|
useBodyScrollDisable,
|
||||||
|
useDidMount,
|
||||||
|
useNDKContext
|
||||||
|
} from 'hooks'
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { toast } from 'react-toastify'
|
import { toast } from 'react-toastify'
|
||||||
import { ModDetails } from 'types'
|
import { ModDetails } from 'types'
|
||||||
@ -12,15 +16,16 @@ type ZapProps = {
|
|||||||
|
|
||||||
export const Zap = ({ modDetails }: ZapProps) => {
|
export const Zap = ({ modDetails }: ZapProps) => {
|
||||||
const [isOpen, setIsOpen] = useState(false)
|
const [isOpen, setIsOpen] = useState(false)
|
||||||
|
const [totalZappedAmount, setTotalZappedAmount] = useState(0)
|
||||||
const [hasZapped, setHasZapped] = useState(false)
|
const [hasZapped, setHasZapped] = useState(false)
|
||||||
|
|
||||||
const userState = useAppSelector((state) => state.user)
|
const userState = useAppSelector((state) => state.user)
|
||||||
|
const { getTotalZapAmount } = useNDKContext()
|
||||||
|
|
||||||
const [totalZappedAmount, setTotalZappedAmount] = useState(0)
|
useBodyScrollDisable(isOpen)
|
||||||
|
|
||||||
useDidMount(() => {
|
useDidMount(() => {
|
||||||
RelayController.getInstance()
|
getTotalZapAmount(
|
||||||
.getTotalZapAmount(
|
|
||||||
modDetails.author,
|
modDetails.author,
|
||||||
modDetails.id,
|
modDetails.id,
|
||||||
modDetails.aTag,
|
modDetails.aTag,
|
||||||
|
@ -24,8 +24,10 @@ import {
|
|||||||
NSFWFilter,
|
NSFWFilter,
|
||||||
SortBy
|
SortBy
|
||||||
} from '../types'
|
} from '../types'
|
||||||
|
import { scrollIntoView } from 'utils'
|
||||||
|
|
||||||
export const ModsPage = () => {
|
export const ModsPage = () => {
|
||||||
|
const scrollTargetRef = useRef<HTMLDivElement>(null)
|
||||||
const { fetchMods } = useNDKContext()
|
const { fetchMods } = useNDKContext()
|
||||||
const [isFetching, setIsFetching] = useState(false)
|
const [isFetching, setIsFetching] = useState(false)
|
||||||
const [mods, setMods] = useState<ModDetails[]>([])
|
const [mods, setMods] = useState<ModDetails[]>([])
|
||||||
@ -66,6 +68,7 @@ export const ModsPage = () => {
|
|||||||
.then((res) => {
|
.then((res) => {
|
||||||
setMods(res)
|
setMods(res)
|
||||||
setPage((prev) => prev + 1)
|
setPage((prev) => prev + 1)
|
||||||
|
scrollIntoView(scrollTargetRef.current)
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
setIsFetching(false)
|
setIsFetching(false)
|
||||||
@ -84,6 +87,7 @@ export const ModsPage = () => {
|
|||||||
.then((res) => {
|
.then((res) => {
|
||||||
setMods(res)
|
setMods(res)
|
||||||
setPage((prev) => prev - 1)
|
setPage((prev) => prev - 1)
|
||||||
|
scrollIntoView(scrollTargetRef.current)
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
setIsFetching(false)
|
setIsFetching(false)
|
||||||
@ -103,7 +107,10 @@ export const ModsPage = () => {
|
|||||||
{isFetching && <LoadingSpinner desc='Fetching mod details from relays' />}
|
{isFetching && <LoadingSpinner desc='Fetching mod details from relays' />}
|
||||||
<div className='InnerBodyMain'>
|
<div className='InnerBodyMain'>
|
||||||
<div className='ContainerMain'>
|
<div className='ContainerMain'>
|
||||||
<div className='IBMSecMainGroup IBMSecMainGroupAlt'>
|
<div
|
||||||
|
className='IBMSecMainGroup IBMSecMainGroupAlt'
|
||||||
|
ref={scrollTargetRef}
|
||||||
|
>
|
||||||
<PageTitleRow />
|
<PageTitleRow />
|
||||||
<ModFilter
|
<ModFilter
|
||||||
filterOptions={filterOptions}
|
filterOptions={filterOptions}
|
||||||
|
@ -43,7 +43,13 @@ import {
|
|||||||
NSFWFilter,
|
NSFWFilter,
|
||||||
SortBy
|
SortBy
|
||||||
} from 'types'
|
} from 'types'
|
||||||
import { extractModData, isModDataComplete, log, LogType } from 'utils'
|
import {
|
||||||
|
extractModData,
|
||||||
|
isModDataComplete,
|
||||||
|
log,
|
||||||
|
LogType,
|
||||||
|
scrollIntoView
|
||||||
|
} from 'utils'
|
||||||
|
|
||||||
enum SearchKindEnum {
|
enum SearchKindEnum {
|
||||||
Mods = 'Mods',
|
Mods = 'Mods',
|
||||||
@ -52,6 +58,7 @@ enum SearchKindEnum {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const SearchPage = () => {
|
export const SearchPage = () => {
|
||||||
|
const scrollTargetRef = useRef<HTMLDivElement>(null)
|
||||||
const [searchParams] = useSearchParams()
|
const [searchParams] = useSearchParams()
|
||||||
|
|
||||||
const muteLists = useMuteLists()
|
const muteLists = useMuteLists()
|
||||||
@ -88,7 +95,10 @@ export const SearchPage = () => {
|
|||||||
return (
|
return (
|
||||||
<div className='InnerBodyMain'>
|
<div className='InnerBodyMain'>
|
||||||
<div className='ContainerMain'>
|
<div className='ContainerMain'>
|
||||||
<div className='IBMSecMainGroup IBMSecMainGroupAlt'>
|
<div
|
||||||
|
className='IBMSecMainGroup IBMSecMainGroupAlt'
|
||||||
|
ref={scrollTargetRef}
|
||||||
|
>
|
||||||
<div className='IBMSecMain'>
|
<div className='IBMSecMain'>
|
||||||
<div className='SearchMainWrapper'>
|
<div className='SearchMainWrapper'>
|
||||||
<div className='IBMSMTitleMain'>
|
<div className='IBMSMTitleMain'>
|
||||||
@ -141,6 +151,7 @@ export const SearchPage = () => {
|
|||||||
filterOptions={filterOptions}
|
filterOptions={filterOptions}
|
||||||
muteLists={muteLists}
|
muteLists={muteLists}
|
||||||
nsfwList={nsfwList}
|
nsfwList={nsfwList}
|
||||||
|
el={scrollTargetRef.current}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{searchKind === SearchKindEnum.Users && (
|
{searchKind === SearchKindEnum.Users && (
|
||||||
@ -263,13 +274,15 @@ type ModsResultProps = {
|
|||||||
user: MuteLists
|
user: MuteLists
|
||||||
}
|
}
|
||||||
nsfwList: string[]
|
nsfwList: string[]
|
||||||
|
el: HTMLElement | null
|
||||||
}
|
}
|
||||||
|
|
||||||
const ModsResult = ({
|
const ModsResult = ({
|
||||||
filterOptions,
|
filterOptions,
|
||||||
searchTerm,
|
searchTerm,
|
||||||
muteLists,
|
muteLists,
|
||||||
nsfwList
|
nsfwList,
|
||||||
|
el
|
||||||
}: ModsResultProps) => {
|
}: ModsResultProps) => {
|
||||||
const { ndk } = useNDKContext()
|
const { ndk } = useNDKContext()
|
||||||
const [mods, setMods] = useState<ModDetails[]>([])
|
const [mods, setMods] = useState<ModDetails[]>([])
|
||||||
@ -305,7 +318,9 @@ const ModsResult = ({
|
|||||||
}, [ndk])
|
}, [ndk])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
scrollIntoView(el)
|
||||||
setPage(1)
|
setPage(1)
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [searchTerm])
|
}, [searchTerm])
|
||||||
|
|
||||||
const filteredMods = useMemo(() => {
|
const filteredMods = useMemo(() => {
|
||||||
@ -334,10 +349,12 @@ const ModsResult = ({
|
|||||||
)
|
)
|
||||||
|
|
||||||
const handleNext = () => {
|
const handleNext = () => {
|
||||||
|
scrollIntoView(el)
|
||||||
setPage((prev) => prev + 1)
|
setPage((prev) => prev + 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
const handlePrev = () => {
|
const handlePrev = () => {
|
||||||
|
scrollIntoView(el)
|
||||||
setPage((prev) => prev - 1)
|
setPage((prev) => prev - 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -392,7 +409,7 @@ const UsersResult = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
setIsFetching(true)
|
setIsFetching(true)
|
||||||
fetchEvents(filter, ['wss://purplepag.es', 'wss://user.kindpag.es'])
|
fetchEvents(filter)
|
||||||
.then((events) => {
|
.then((events) => {
|
||||||
const results = events.map((event) => {
|
const results = events.map((event) => {
|
||||||
const ndkEvent = new NDKEvent(undefined, event)
|
const ndkEvent = new NDKEvent(undefined, event)
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import { AdminSVG, PreferenceSVG, ProfileSVG, RelaySVG } from 'components/SVGs'
|
import { AdminSVG, PreferenceSVG, ProfileSVG, RelaySVG } from 'components/SVGs'
|
||||||
import { MetadataController } from 'controllers'
|
|
||||||
import { useAppSelector } from 'hooks'
|
import { useAppSelector } from 'hooks'
|
||||||
import { logout } from 'nostr-login'
|
import { logout } from 'nostr-login'
|
||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
@ -57,15 +56,12 @@ const SettingTabs = () => {
|
|||||||
const userState = useAppSelector((state) => state.user)
|
const userState = useAppSelector((state) => state.user)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
MetadataController.getInstance().then((controller) => {
|
const adminNpubs = import.meta.env.VITE_ADMIN_NPUBS.split(',')
|
||||||
if (userState.auth && userState.user?.npub) {
|
if (userState.auth && userState.user?.npub) {
|
||||||
setIsAdmin(
|
setIsAdmin(adminNpubs.includes(userState.user.npub as string))
|
||||||
controller.adminNpubs.includes(userState.user.npub as string)
|
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
setIsAdmin(false)
|
setIsAdmin(false)
|
||||||
}
|
}
|
||||||
})
|
|
||||||
}, [userState])
|
}, [userState])
|
||||||
|
|
||||||
const handleSignOut = () => {
|
const handleSignOut = () => {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { InputField } from 'components/Inputs'
|
import { InputField } from 'components/Inputs'
|
||||||
import { ProfileQRButtonWithPopUp } from 'components/ProfileSection'
|
import { ProfileQRButtonWithPopUp } from 'components/ProfileSection'
|
||||||
import { useAppDispatch, useAppSelector } from 'hooks'
|
import { useAppDispatch, useAppSelector, useNDKContext } from 'hooks'
|
||||||
import { kinds, nip19, UnsignedEvent, Event } from 'nostr-tools'
|
import { kinds, nip19, UnsignedEvent, Event } from 'nostr-tools'
|
||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import { Link } from 'react-router-dom'
|
import { Link } from 'react-router-dom'
|
||||||
@ -14,7 +14,6 @@ import {
|
|||||||
profileFromEvent,
|
profileFromEvent,
|
||||||
serializeProfile
|
serializeProfile
|
||||||
} from '@nostr-dev-kit/ndk'
|
} from '@nostr-dev-kit/ndk'
|
||||||
import { RelayController } from 'controllers'
|
|
||||||
import { LoadingSpinner } from 'components/LoadingSpinner'
|
import { LoadingSpinner } from 'components/LoadingSpinner'
|
||||||
import { setUser } from 'store/reducers/user'
|
import { setUser } from 'store/reducers/user'
|
||||||
import placeholderMod from '../../assets/img/DEGMods Placeholder Img.png'
|
import placeholderMod from '../../assets/img/DEGMods Placeholder Img.png'
|
||||||
@ -43,6 +42,7 @@ const defaultFormState: FormState = {
|
|||||||
export const ProfileSettings = () => {
|
export const ProfileSettings = () => {
|
||||||
const dispatch = useAppDispatch()
|
const dispatch = useAppDispatch()
|
||||||
const userState = useAppSelector((state) => state.user)
|
const userState = useAppSelector((state) => state.user)
|
||||||
|
const { ndk, publish } = useNDKContext()
|
||||||
|
|
||||||
const [isPublishing, setIsPublishing] = useState(false)
|
const [isPublishing, setIsPublishing] = useState(false)
|
||||||
const [formState, setFormState] = useState<FormState>(defaultFormState)
|
const [formState, setFormState] = useState<FormState>(defaultFormState)
|
||||||
@ -163,9 +163,8 @@ export const ProfileSettings = () => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const publishedOnRelays = await RelayController.getInstance().publish(
|
const ndkEvent = new NDKEvent(ndk, signedEvent)
|
||||||
signedEvent as Event
|
const publishedOnRelays = await publish(ndkEvent)
|
||||||
)
|
|
||||||
|
|
||||||
// Handle cases where publishing failed or succeeded
|
// Handle cases where publishing failed or succeeded
|
||||||
if (publishedOnRelays.length === 0) {
|
if (publishedOnRelays.length === 0) {
|
||||||
|
@ -1,21 +1,23 @@
|
|||||||
import { NDKRelayList } from '@nostr-dev-kit/ndk'
|
import {
|
||||||
|
getRelayListForUser,
|
||||||
|
NDKEvent,
|
||||||
|
NDKRelayList,
|
||||||
|
NDKRelayStatus
|
||||||
|
} from '@nostr-dev-kit/ndk'
|
||||||
import { InputField } from 'components/Inputs'
|
import { InputField } from 'components/Inputs'
|
||||||
import { LoadingSpinner } from 'components/LoadingSpinner'
|
import { LoadingSpinner } from 'components/LoadingSpinner'
|
||||||
import {
|
import { useAppSelector, useDidMount, useNDKContext } from 'hooks'
|
||||||
MetadataController,
|
|
||||||
RelayController,
|
|
||||||
UserRelaysType
|
|
||||||
} from 'controllers'
|
|
||||||
import { useAppSelector, useDidMount } from 'hooks'
|
|
||||||
import { Event, kinds, UnsignedEvent } from 'nostr-tools'
|
import { Event, kinds, UnsignedEvent } from 'nostr-tools'
|
||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import { toast } from 'react-toastify'
|
import { toast } from 'react-toastify'
|
||||||
|
import { UserRelaysType } from 'types'
|
||||||
import { log, LogType, normalizeWebSocketURL, now } from 'utils'
|
import { log, LogType, normalizeWebSocketURL, now } from 'utils'
|
||||||
|
|
||||||
const READ_MARKER = 'read'
|
const READ_MARKER = 'read'
|
||||||
const WRITE_MARKER = 'write'
|
const WRITE_MARKER = 'write'
|
||||||
|
|
||||||
export const RelaySettings = () => {
|
export const RelaySettings = () => {
|
||||||
|
const { ndk, publish } = useNDKContext()
|
||||||
const userState = useAppSelector((state) => state.user)
|
const userState = useAppSelector((state) => state.user)
|
||||||
const [ndkRelayList, setNDKRelayList] = useState<NDKRelayList | null>(null)
|
const [ndkRelayList, setNDKRelayList] = useState<NDKRelayList | null>(null)
|
||||||
const [isPublishing, setIsPublishing] = useState(false)
|
const [isPublishing, setIsPublishing] = useState(false)
|
||||||
@ -23,10 +25,8 @@ export const RelaySettings = () => {
|
|||||||
const [inputValue, setInputValue] = useState('')
|
const [inputValue, setInputValue] = useState('')
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchRelayList = async (pubkey: string) => {
|
if (userState.auth && userState.user?.pubkey) {
|
||||||
const metadataController = await MetadataController.getInstance()
|
getRelayListForUser(userState.user.pubkey as string, ndk)
|
||||||
metadataController
|
|
||||||
.getNDKRelayList(pubkey)
|
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
setNDKRelayList(res)
|
setNDKRelayList(res)
|
||||||
})
|
})
|
||||||
@ -38,14 +38,10 @@ export const RelaySettings = () => {
|
|||||||
)
|
)
|
||||||
setNDKRelayList(null)
|
setNDKRelayList(null)
|
||||||
})
|
})
|
||||||
}
|
|
||||||
|
|
||||||
if (userState.auth && userState.user?.pubkey) {
|
|
||||||
fetchRelayList(userState.user.pubkey as string)
|
|
||||||
} else {
|
} else {
|
||||||
setNDKRelayList(null)
|
setNDKRelayList(null)
|
||||||
}
|
}
|
||||||
}, [userState])
|
}, [userState, ndk])
|
||||||
|
|
||||||
const handleAdd = async (relayUrl: string) => {
|
const handleAdd = async (relayUrl: string) => {
|
||||||
if (!ndkRelayList) return
|
if (!ndkRelayList) return
|
||||||
@ -78,11 +74,8 @@ export const RelaySettings = () => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const publishedOnRelays =
|
const ndkEvent = new NDKEvent(ndk, signedEvent)
|
||||||
await RelayController.getInstance().publishOnRelays(
|
const publishedOnRelays = await publish(ndkEvent)
|
||||||
signedEvent,
|
|
||||||
ndkRelayList.writeRelayUrls
|
|
||||||
)
|
|
||||||
|
|
||||||
// Handle cases where publishing failed or succeeded
|
// Handle cases where publishing failed or succeeded
|
||||||
if (publishedOnRelays.length === 0) {
|
if (publishedOnRelays.length === 0) {
|
||||||
@ -140,11 +133,8 @@ export const RelaySettings = () => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const publishedOnRelays =
|
const ndkEvent = new NDKEvent(ndk, signedEvent)
|
||||||
await RelayController.getInstance().publishOnRelays(
|
const publishedOnRelays = await publish(ndkEvent)
|
||||||
signedEvent,
|
|
||||||
ndkRelayList.writeRelayUrls
|
|
||||||
)
|
|
||||||
|
|
||||||
// Handle cases where publishing failed or succeeded
|
// Handle cases where publishing failed or succeeded
|
||||||
if (publishedOnRelays.length === 0) {
|
if (publishedOnRelays.length === 0) {
|
||||||
@ -214,11 +204,8 @@ export const RelaySettings = () => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const publishedOnRelays =
|
const ndkEvent = new NDKEvent(ndk, signedEvent)
|
||||||
await RelayController.getInstance().publishOnRelays(
|
const publishedOnRelays = await publish(ndkEvent)
|
||||||
signedEvent,
|
|
||||||
ndkRelayList.writeRelayUrls
|
|
||||||
)
|
|
||||||
|
|
||||||
// Handle cases where publishing failed or succeeded
|
// Handle cases where publishing failed or succeeded
|
||||||
if (publishedOnRelays.length === 0) {
|
if (publishedOnRelays.length === 0) {
|
||||||
@ -382,18 +369,30 @@ const RelayListItem = ({
|
|||||||
changeRelayType
|
changeRelayType
|
||||||
}: RelayItemProps) => {
|
}: RelayItemProps) => {
|
||||||
const [isConnected, setIsConnected] = useState(false)
|
const [isConnected, setIsConnected] = useState(false)
|
||||||
|
const { ndk } = useNDKContext()
|
||||||
|
|
||||||
useDidMount(() => {
|
useDidMount(() => {
|
||||||
RelayController.getInstance()
|
const ndkPool = ndk.pool
|
||||||
.connectRelay(relayUrl)
|
|
||||||
.then((relay) => {
|
ndkPool.on('relay:connect', (relay) => {
|
||||||
if (relay && relay.connected) {
|
if (relay.url === relayUrl) {
|
||||||
|
setIsConnected(true)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
ndkPool.on('relay:disconnect', (relay) => {
|
||||||
|
if (relay.url === relayUrl) {
|
||||||
|
setIsConnected(false)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const relay = ndkPool.relays.get(relayUrl)
|
||||||
|
if (relay && relay.status >= NDKRelayStatus.CONNECTED) {
|
||||||
setIsConnected(true)
|
setIsConnected(true)
|
||||||
} else {
|
} else {
|
||||||
setIsConnected(false)
|
setIsConnected(false)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='relayListItem'>
|
<div className='relayListItem'>
|
||||||
|
@ -29,7 +29,7 @@ export const SubmitModPage = () => {
|
|||||||
useDidMount(async () => {
|
useDidMount(async () => {
|
||||||
if (naddr) {
|
if (naddr) {
|
||||||
const decoded = nip19.decode<'naddr'>(naddr as `naddr1${string}`)
|
const decoded = nip19.decode<'naddr'>(naddr as `naddr1${string}`)
|
||||||
const { identifier, kind, pubkey, relays = [] } = decoded.data
|
const { identifier, kind, pubkey } = decoded.data
|
||||||
|
|
||||||
const filter: NDKFilter = {
|
const filter: NDKFilter = {
|
||||||
'#a': [identifier],
|
'#a': [identifier],
|
||||||
@ -39,7 +39,7 @@ export const SubmitModPage = () => {
|
|||||||
|
|
||||||
setIsFetching(true)
|
setIsFetching(true)
|
||||||
|
|
||||||
fetchEvent(filter, relays)
|
fetchEvent(filter)
|
||||||
.then((event) => {
|
.then((event) => {
|
||||||
if (event) {
|
if (event) {
|
||||||
const extracted = extractModData(event)
|
const extracted = extractModData(event)
|
||||||
|
@ -96,6 +96,7 @@
|
|||||||
-webkit-box-orient: vertical;
|
-webkit-box-orient: vertical;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
-webkit-line-clamp: 2;
|
-webkit-line-clamp: 2;
|
||||||
|
line-clamp: 2;
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
line-height: 1.25;
|
line-height: 1.25;
|
||||||
color: rgba(255, 255, 255, 0.75);
|
color: rgba(255, 255, 255, 0.75);
|
||||||
@ -107,6 +108,7 @@
|
|||||||
-webkit-box-orient: vertical;
|
-webkit-box-orient: vertical;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
-webkit-line-clamp: 2;
|
-webkit-line-clamp: 2;
|
||||||
|
line-clamp: 2;
|
||||||
color: rgba(255, 255, 255, 0.5);
|
color: rgba(255, 255, 255, 0.5);
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
@ -119,11 +121,12 @@
|
|||||||
justify-content: start;
|
justify-content: start;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
background: rgba(255,255,255,0.05);
|
background: rgba(255, 255, 255, 0.05);
|
||||||
display: -webkit-box;
|
display: -webkit-box;
|
||||||
-webkit-box-orient: vertical;
|
-webkit-box-orient: vertical;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
-webkit-line-clamp: 1;
|
-webkit-line-clamp: 1;
|
||||||
|
line-clamp: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cMMFootReactions {
|
.cMMFootReactions {
|
||||||
@ -143,3 +146,12 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
color: rgba(255, 255, 255, 0.25);
|
color: rgba(255, 255, 255, 0.25);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.IBMSMSMBSSTagsTag.IBMSMSMBSSTagsTagNSFW.IBMSMSMBSSTagsTagNSFWCard {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 10px;
|
||||||
|
right: 10px;
|
||||||
|
-webkit-backdrop-filter: blur(10px);
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
background: rgba(35, 35, 35, 0.85);
|
||||||
|
}
|
||||||
|
@ -1,3 +1,9 @@
|
|||||||
import { NDKUserProfile } from '@nostr-dev-kit/ndk'
|
import { NDKUserProfile } from '@nostr-dev-kit/ndk'
|
||||||
|
|
||||||
export type UserProfile = NDKUserProfile | null
|
export type UserProfile = NDKUserProfile | null
|
||||||
|
|
||||||
|
export enum UserRelaysType {
|
||||||
|
Read = 'readRelayUrls',
|
||||||
|
Write = 'writeRelayUrls',
|
||||||
|
Both = 'bothRelayUrls'
|
||||||
|
}
|
||||||
|
@ -9,9 +9,8 @@ import {
|
|||||||
UnsignedEvent
|
UnsignedEvent
|
||||||
} from 'nostr-tools'
|
} from 'nostr-tools'
|
||||||
import { toast } from 'react-toastify'
|
import { toast } from 'react-toastify'
|
||||||
import { RelayController } from '../controllers'
|
|
||||||
import { log, LogType } from './utils'
|
import { log, LogType } from './utils'
|
||||||
import { NDKEvent } from '@nostr-dev-kit/ndk'
|
import NDK, { NDKEvent } from '@nostr-dev-kit/ndk'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the current time in seconds since the Unix epoch (January 1, 1970).
|
* Get the current time in seconds since the Unix epoch (January 1, 1970).
|
||||||
@ -123,7 +122,11 @@ export const extractZapAmount = (event: Event): number => {
|
|||||||
* @param unsignedEvent - The event object which needs to be signed before publishing.
|
* @param unsignedEvent - The event object which needs to be signed before publishing.
|
||||||
* @returns - A promise that resolves to boolean indicating whether the event was successfully signed and published
|
* @returns - A promise that resolves to boolean indicating whether the event was successfully signed and published
|
||||||
*/
|
*/
|
||||||
export const signAndPublish = async (unsignedEvent: UnsignedEvent) => {
|
export const signAndPublish = async (
|
||||||
|
unsignedEvent: UnsignedEvent,
|
||||||
|
ndk: NDK,
|
||||||
|
publish: (event: NDKEvent) => Promise<string[]>
|
||||||
|
) => {
|
||||||
// Sign the event. This returns a signed event or null if signing fails.
|
// Sign the event. This returns a signed event or null if signing fails.
|
||||||
const signedEvent = await window.nostr
|
const signedEvent = await window.nostr
|
||||||
?.signEvent(unsignedEvent)
|
?.signEvent(unsignedEvent)
|
||||||
@ -138,11 +141,10 @@ export const signAndPublish = async (unsignedEvent: UnsignedEvent) => {
|
|||||||
// If the event couldn't be signed, exit the function and return null.
|
// If the event couldn't be signed, exit the function and return null.
|
||||||
if (!signedEvent) return false
|
if (!signedEvent) return false
|
||||||
|
|
||||||
// Publish the signed event to the relays using the RelayController.
|
// Publish the signed event to the relays.
|
||||||
// This returns an array of relay URLs where the event was successfully published.
|
// This returns an array of relay URLs where the event was successfully published.
|
||||||
const publishedOnRelays = await RelayController.getInstance().publish(
|
const ndkEvent = new NDKEvent(ndk, signedEvent)
|
||||||
signedEvent as Event
|
const publishedOnRelays = await publish(ndkEvent)
|
||||||
)
|
|
||||||
|
|
||||||
// Handle cases where publishing to the relays failed
|
// Handle cases where publishing to the relays failed
|
||||||
if (publishedOnRelays.length === 0) {
|
if (publishedOnRelays.length === 0) {
|
||||||
@ -170,7 +172,9 @@ export const signAndPublish = async (unsignedEvent: UnsignedEvent) => {
|
|||||||
*/
|
*/
|
||||||
export const sendDMUsingRandomKey = async (
|
export const sendDMUsingRandomKey = async (
|
||||||
message: string,
|
message: string,
|
||||||
receiver: string
|
receiver: string,
|
||||||
|
ndk: NDK,
|
||||||
|
publish: (event: NDKEvent) => Promise<string[]>
|
||||||
) => {
|
) => {
|
||||||
// Generate a random secret key for encrypting the message
|
// Generate a random secret key for encrypting the message
|
||||||
const secretKey = generateSecretKey()
|
const secretKey = generateSecretKey()
|
||||||
@ -201,11 +205,8 @@ export const sendDMUsingRandomKey = async (
|
|||||||
// Finalize and sign the event using the generated secret key
|
// Finalize and sign the event using the generated secret key
|
||||||
const signedEvent = finalizeEvent(unsignedEvent, secretKey)
|
const signedEvent = finalizeEvent(unsignedEvent, secretKey)
|
||||||
|
|
||||||
// Publish the signed event (the encrypted DM) to the relays
|
const ndkEvent = new NDKEvent(ndk, signedEvent)
|
||||||
const publishedOnRelays = await RelayController.getInstance().publishDM(
|
const publishedOnRelays = await publish(ndkEvent)
|
||||||
signedEvent,
|
|
||||||
receiver
|
|
||||||
)
|
|
||||||
|
|
||||||
// Handle cases where publishing to the relays failed
|
// Handle cases where publishing to the relays failed
|
||||||
if (publishedOnRelays.length === 0) {
|
if (publishedOnRelays.length === 0) {
|
||||||
|
@ -135,3 +135,14 @@ export const handleModImageError = (
|
|||||||
) => {
|
) => {
|
||||||
e.currentTarget.src = import.meta.env.VITE_FALLBACK_MOD_IMAGE
|
e.currentTarget.src = import.meta.env.VITE_FALLBACK_MOD_IMAGE
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const scrollIntoView = (el: HTMLElement | null) => {
|
||||||
|
if (el) {
|
||||||
|
setTimeout(() => {
|
||||||
|
el.scrollIntoView({
|
||||||
|
behavior: 'smooth',
|
||||||
|
block: 'start'
|
||||||
|
})
|
||||||
|
}, 100)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user