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 @@
{ {
"name": "imwald", "name": "imwald",
"version": "21.4.0", "version": "22.0.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "imwald", "name": "imwald",
"version": "21.4.0", "version": "22.0.0",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@asciidoctor/core": "^3.0.4", "@asciidoctor/core": "^3.0.4",

2
package.json

@ -1,6 +1,6 @@
{ {
"name": "imwald", "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", "description": "Imwald — a user-friendly Nostr client focused on relay feed browsing, publications, and relay discovery",
"private": true, "private": true,
"type": "module", "type": "module",

10
src/components/NoteList/index.tsx

@ -2990,9 +2990,15 @@ const NoteList = forwardRef(
for (const event of clientFilteredEvents) { for (const event of clientFilteredEvents) {
const labels: string[] = [] const labels: string[] = []
for (const req of reqs) { for (const req of reqs) {
if (eventMatchesSubRequestFilter(event, req.filter as Filter)) { if (!eventMatchesSubRequestFilter(event, req.filter as Filter)) continue
labels.push(req.reasonLabel as string) 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) { if (labels.length) {
map.set(event.id, Array.from(new Set(labels)).join(' · ')) map.set(event.id, Array.from(new Set(labels)).join(' · '))

1
src/i18n/locales/de.ts

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

1
src/i18n/locales/en.ts

@ -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':
'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...', 'Loading trending notes from your relays...': 'Loading trending notes from your relays...',
'Trending on Nostr': 'Trending on Nostr',
Sort: 'Sort', Sort: 'Sort',
newest: 'newest', newest: 'newest',
oldest: 'oldest', oldest: 'oldest',

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

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

5
src/types/index.d.ts vendored

@ -8,6 +8,11 @@ export type TFeedSubRequest = {
filter: Omit<Filter, 'since' | 'until'> filter: Omit<Filter, 'since' | 'until'>
/** Optional UI hint used by feed UIs (e.g. Favorites) to explain why an event was included. */ /** Optional UI hint used by feed UIs (e.g. Favorites) to explain why an event was included. */
reasonLabel?: string 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 = { export type TProfile = {

Loading…
Cancel
Save