Browse Source

implement trending API from wisp

imwald
Silberengel 4 weeks ago
parent
commit
045261676f
  1. 4
      package-lock.json
  2. 2
      package.json
  3. 10
      src/components/NoteList/index.tsx
  4. 1
      src/i18n/locales/de.ts
  5. 1
      src/i18n/locales/en.ts
  6. 18
      src/lib/wisp-trending-relay.ts
  7. 29
      src/pages/primary/NoteListPage/FollowingFeed.tsx
  8. 5
      src/types/index.d.ts

4
package-lock.json generated

@ -1,12 +1,12 @@ @@ -1,12 +1,12 @@
{
"name": "imwald",
"version": "21.4.0",
"version": "22.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "imwald",
"version": "21.4.0",
"version": "22.0.0",
"license": "MIT",
"dependencies": {
"@asciidoctor/core": "^3.0.4",

2
package.json

@ -1,6 +1,6 @@ @@ -1,6 +1,6 @@
{
"name": "imwald",
"version": "21.4.0",
"version": "22.0.0",
"description": "Imwald — a user-friendly Nostr client focused on relay feed browsing, publications, and relay discovery",
"private": true,
"type": "module",

10
src/components/NoteList/index.tsx

@ -2990,9 +2990,15 @@ const NoteList = forwardRef( @@ -2990,9 +2990,15 @@ const NoteList = forwardRef(
for (const event of clientFilteredEvents) {
const labels: string[] = []
for (const req of reqs) {
if (eventMatchesSubRequestFilter(event, req.filter as Filter)) {
labels.push(req.reasonLabel as string)
if (!eventMatchesSubRequestFilter(event, req.filter as Filter)) continue
if (req.reasonLabelIfSeenOnRelay) {
const target = normalizeUrl(req.reasonLabelIfSeenOnRelay) || req.reasonLabelIfSeenOnRelay
const seenNorm = client
.getSeenEventRelayUrls(event.id)
.map((u) => normalizeUrl(u) || u)
if (!seenNorm.includes(target)) continue
}
labels.push(req.reasonLabel as string)
}
if (labels.length) {
map.set(event.id, Array.from(new Set(labels)).join(' · '))

1
src/i18n/locales/de.ts

@ -1059,6 +1059,7 @@ export default { @@ -1059,6 +1059,7 @@ export default {
'No recent posts from this user in the current fetch':
'Keine aktuellen Beiträge von diesem Nutzer in dieser Abfrage',
'Loading trending notes from your relays...': 'Trendende Notizen werden geladen …',
'Trending on Nostr': 'Trending auf Nostr',
Sort: 'Sortierung',
newest: 'neueste',
oldest: 'älteste',

1
src/i18n/locales/en.ts

@ -1057,6 +1057,7 @@ export default { @@ -1057,6 +1057,7 @@ export default {
'No recent posts from this user in the current fetch':
'No recent posts from this user in the current fetch',
'Loading trending notes from your relays...': 'Loading trending notes from your relays...',
'Trending on Nostr': 'Trending on Nostr',
Sort: 'Sort',
newest: 'newest',
oldest: 'oldest',

18
src/lib/wisp-trending-relay.ts

@ -0,0 +1,18 @@ @@ -0,0 +1,18 @@
/**
* Trending notes stream from nostrarchives, consumed by
* {@link https://github.com/barrydeen/wisp | Wisp} (Android). Same URL shape as Wisp’s
* `buildTrendingRelayUrl` / `FEED_KINDS` REQ.
*/
export type WispTrendingMetric = 'reactions' | 'replies' | 'reposts' | 'zaps'
export type WispTrendingTimeframe = 'today' | '7d' | '30d' | '1y' | 'all'
export function buildWispTrendingNotesRelayUrl(
metric: WispTrendingMetric = 'reactions',
timeframe: WispTrendingTimeframe = 'today'
): string {
return `wss://feeds.nostrarchives.com/notes/trending/${metric}/${timeframe}`
}
/** Wisp `FeedSubscriptionManager` FEED_KINDS when subscribing to trending notes. */
export const WISP_TRENDING_FEED_KINDS: readonly number[] = [1, 6, 1068, 6969, 30023, 20, 21, 22]

29
src/pages/primary/NoteListPage/FollowingFeed.tsx

@ -11,10 +11,15 @@ import logger from '@/lib/logger' @@ -11,10 +11,15 @@ import logger from '@/lib/logger'
import { useFeed } from '@/providers/FeedProvider'
import { useFavoriteRelays } from '@/providers/FavoriteRelaysProvider'
import { useNostr } from '@/providers/NostrProvider'
import {
buildWispTrendingNotesRelayUrl,
WISP_TRENDING_FEED_KINDS
} from '@/lib/wisp-trending-relay'
import client from '@/services/client.service'
import { TFeedSubRequest } from '@/types'
import type { ReactNode } from 'react'
import { forwardRef, useEffect, useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next'
const FollowingFeed = forwardRef<
TNoteListRef,
@ -23,6 +28,7 @@ const FollowingFeed = forwardRef< @@ -23,6 +28,7 @@ const FollowingFeed = forwardRef<
onSubHeaderRefresh?: () => void
}
>(function FollowingFeed({ setSubHeader, onSubHeaderRefresh }, ref) {
const { t, i18n } = useTranslation()
const { pubkey, relayList, followListEvent } = useNostr()
const { favoriteRelays, blockedRelays } = useFavoriteRelays()
const { feedInfo } = useFeed()
@ -91,6 +97,15 @@ const FollowingFeed = forwardRef< @@ -91,6 +97,15 @@ const FollowingFeed = forwardRef<
{ userWriteRelays: relayList?.write ?? [] }
)
const trendingRelayUrl = buildWispTrendingNotesRelayUrl()
const wispTrendingShard: TFeedSubRequest = {
urls: [trendingRelayUrl],
filter: { kinds: [...WISP_TRENDING_FEED_KINDS], limit: 100 },
reasonLabel: t('Trending on Nostr'),
reasonLabelIfSeenOnRelay: trendingRelayUrl
}
const appendTrending = (batch: TFeedSubRequest[]) => [...batch, wispTrendingShard]
const fromTags = followListEvent ? getPubkeysFromPTags(followListEvent.tags) : []
const provisionalAuthors = [...new Set([pubkey, ...fromTags])]
const provisionalAuthorLower = provisionalAuthors.map((p) => p.toLowerCase())
@ -101,7 +116,8 @@ const FollowingFeed = forwardRef< @@ -101,7 +116,8 @@ const FollowingFeed = forwardRef<
} catch (error) {
logger.warn('[FollowingFeed] provisional generateSubRequestsForPubkeys failed', { error })
}
const provAug = augment(rawProv)
const provAugCore = augment(rawProv)
const provAug = appendTrending(provAugCore)
if (!cancelled) setSubRequests(provAug)
let followings: string[] = fromTags
@ -120,15 +136,15 @@ const FollowingFeed = forwardRef< @@ -120,15 +136,15 @@ const FollowingFeed = forwardRef<
try {
const rawFull = await client.generateSubRequestsForPubkeys(fullAuthors, pubkey)
if (cancelled) return
const fullAug = augment(rawFull)
const delta = buildFollowingFeedDeltaSubRequests(fullAug, provAug, provisionalAuthorLower)
const fullAugCore = augment(rawFull)
const delta = buildFollowingFeedDeltaSubRequests(fullAugCore, provAugCore, provisionalAuthorLower)
if (!cancelled) {
setDeltaSubRequests(delta)
if (delta.length > 0) {
logger.info('[FollowingFeed] delta wave subRequests', {
deltaShardCount: delta.length,
provisionalShardCount: provAug.length,
fullShardCount: fullAug.length
provisionalShardCount: provAugCore.length,
fullShardCount: fullAugCore.length
})
}
}
@ -149,7 +165,8 @@ const FollowingFeed = forwardRef< @@ -149,7 +165,8 @@ const FollowingFeed = forwardRef<
favoriteRelaysKey,
blockedRelaysKey,
relayReadKey,
relayWriteKey
relayWriteKey,
i18n.language
])
return (

5
src/types/index.d.ts vendored

@ -8,6 +8,11 @@ export type TFeedSubRequest = { @@ -8,6 +8,11 @@ export type TFeedSubRequest = {
filter: Omit<Filter, 'since' | 'until'>
/** Optional UI hint used by feed UIs (e.g. Favorites) to explain why an event was included. */
reasonLabel?: string
/**
* When set with {@link reasonLabel}, the label is shown only if the event was received from this relay
* (normalized like other relay URLs), so broad filters (e.g. kinds-only) do not mis-tag other shards events.
*/
reasonLabelIfSeenOnRelay?: string
}
export type TProfile = {

Loading…
Cancel
Save