Implement NIP-85 fallback WOT ranking
This commit is contained in:
@@ -0,0 +1,96 @@
|
||||
import {request} from "@welshman/net"
|
||||
import {clamp} from "@welshman/lib"
|
||||
import type {TrustedEvent} from "@welshman/util"
|
||||
|
||||
const DEFAULT_ASSERTION_RELAYS =
|
||||
"wss://nip85.brainstorm.world,wss://nip85.nosfabrica.com,wss://nip85.uid.ovh"
|
||||
|
||||
const toAssertionRelayUrl = (url: string) => {
|
||||
const trimmed = url.trim()
|
||||
|
||||
if (!trimmed) return ""
|
||||
if (trimmed.startsWith("http://")) return trimmed.replace(/^http:/, "ws:")
|
||||
if (trimmed.startsWith("https://")) return trimmed.replace(/^https:/, "wss:")
|
||||
if (!/^[a-z]+:\/\//i.test(trimmed)) return `wss://${trimmed}`
|
||||
|
||||
return trimmed
|
||||
}
|
||||
|
||||
const ASSERTION_RELAYS = String(import.meta.env.VITE_ASSERTION_RELAYS || DEFAULT_ASSERTION_RELAYS)
|
||||
.split(",")
|
||||
.map(toAssertionRelayUrl)
|
||||
.filter(Boolean)
|
||||
|
||||
const rankCache = new Map<string, number | null>()
|
||||
const pendingRankRequests = new Map<string, Promise<number | null>>()
|
||||
|
||||
const getMedian = (values: number[]) => {
|
||||
const sorted = [...values].sort((a, b) => a - b)
|
||||
const middle = Math.floor(sorted.length / 2)
|
||||
|
||||
if (sorted.length % 2 === 1) {
|
||||
return sorted[middle]
|
||||
}
|
||||
|
||||
return (sorted[middle - 1] + sorted[middle]) / 2
|
||||
}
|
||||
|
||||
const getRankFromEvent = (event: TrustedEvent) => {
|
||||
const rankTag = event.tags?.find((tag: string[]) => tag[0] === "rank")
|
||||
const rankValue = rankTag ? Number(rankTag[1]) : null
|
||||
|
||||
if (rankValue === null || !Number.isFinite(rankValue)) {
|
||||
return null
|
||||
}
|
||||
|
||||
return clamp([0, 100], rankValue)
|
||||
}
|
||||
|
||||
const fetchPubkeyRank = async (pubkey: string): Promise<number | null> => {
|
||||
if (ASSERTION_RELAYS.length === 0) {
|
||||
return 50
|
||||
}
|
||||
|
||||
try {
|
||||
const events = await request({
|
||||
relays: ASSERTION_RELAYS,
|
||||
autoClose: true,
|
||||
filters: [{kinds: [30382], "#d": [pubkey], limit: 8}],
|
||||
})
|
||||
|
||||
const ranks = events.map(getRankFromEvent).filter((rank): rank is number => rank !== null)
|
||||
|
||||
if (ranks.length === 0) {
|
||||
return 50
|
||||
}
|
||||
|
||||
const median = getMedian(ranks)
|
||||
|
||||
return median
|
||||
} catch {
|
||||
return 50
|
||||
}
|
||||
}
|
||||
|
||||
export const getPubkeyRank = (pubkey: string): Promise<number | null> => {
|
||||
if (rankCache.has(pubkey)) {
|
||||
return Promise.resolve(rankCache.get(pubkey) ?? 50)
|
||||
}
|
||||
|
||||
const pending = pendingRankRequests.get(pubkey)
|
||||
|
||||
if (pending) {
|
||||
return pending
|
||||
}
|
||||
|
||||
const requestPromise = fetchPubkeyRank(pubkey).then(rank => {
|
||||
rankCache.set(pubkey, rank ?? 50)
|
||||
pendingRankRequests.delete(pubkey)
|
||||
|
||||
return rank ?? 50
|
||||
})
|
||||
|
||||
pendingRankRequests.set(pubkey, requestPromise)
|
||||
|
||||
return requestPromise
|
||||
}
|
||||
Reference in New Issue
Block a user