Count followers using rbr.bio

This commit is contained in:
Jonathan Staab 2023-03-13 14:31:31 -05:00
parent 1939725ac6
commit 755d6cfa7f
5 changed files with 81 additions and 55 deletions

View File

@ -199,8 +199,8 @@ const applyContext = (notes, context) => {
} }
export default { export default {
listen,
load, load,
listen,
loadPeople, loadPeople,
loadParents, loadParents,
streamContext, streamContext,

View File

@ -38,7 +38,7 @@ class Connection {
url: string url: string
promise?: Deferred<void> promise?: Deferred<void>
queue: string[] queue: string[]
error: {code: string; occurredAt: number} error: {code: string; message: string, occurredAt: number}
status: {code: string; message: string} status: {code: string; message: string}
timeout?: number timeout?: number
stats: Record<string, number> stats: Record<string, number>
@ -72,8 +72,8 @@ class Connection {
setStatus(code, message) { setStatus(code, message) {
this.status = {code, message} this.status = {code, message}
} }
setError(code) { setError(code, message) {
this.error = {code, occurredAt: Date.now()} this.error = {code, message, occurredAt: Date.now()}
} }
connect() { connect() {
if (this.ws) { if (this.ws) {
@ -100,11 +100,11 @@ class Connection {
}) })
this.ws.addEventListener("error", e => { this.ws.addEventListener("error", e => {
log(`Error on connection to ${this.url}`, e) log(`Error on connection to ${this.url}`)
this.disconnect() this.disconnect()
this.promise.reject() this.promise.reject()
this.setError(ERROR.CONNECTION) this.setError(ERROR.CONNECTION, "Disconnected")
this.setStatus(STATUS.CLOSED, "Closed") this.setStatus(STATUS.CLOSED, "Closed")
}) })
@ -172,7 +172,7 @@ class Connection {
conn: this, conn: this,
unsub: () => { unsub: () => {
if (this.status.code === STATUS.READY) { if (this.status.code === STATUS.READY) {
this.send("CLOSE", id, ...filters) this.send("CLOSE", id)
} }
this.bus.off("EVENT", eventChannel) this.bus.off("EVENT", eventChannel)
@ -196,12 +196,23 @@ class Connection {
this.send("EVENT", event) this.send("EVENT", event)
} }
count(filter, id, {onCount}) {
const channel = this.bus.on("COUNT", (subid, ...payload) => {
if (subid === id) {
onCount(...payload)
this.bus.off("COUNT", channel)
}
})
this.send("COUNT", id, ...filter)
}
listenForAuth() { listenForAuth() {
// Propagate auth to global handler // Propagate auth to global handler
this.bus.on("AUTH", challenge => { this.bus.on("AUTH", challenge => {
if (!this.error) { if (!this.error) {
this.setStatus(STATUS.ERROR, "Logging in") this.setStatus(STATUS.ERROR, "Logging in")
this.setError(ERROR.UNAUTHORIZED) this.setError(ERROR.UNAUTHORIZED, "Logging in")
eventBus.handle("AUTH", challenge, this) eventBus.handle("AUTH", challenge, this)
} }
@ -214,7 +225,7 @@ class Connection {
this.setStatus(STATUS.READY, "Connected") this.setStatus(STATUS.READY, "Connected")
} else { } else {
this.disconnect() this.disconnect()
this.setError(ERROR.FORBIDDEN) this.setError(ERROR.FORBIDDEN, "Access restricted")
} }
this.bus.off("OK", channel) this.bus.off("OK", channel)
@ -225,8 +236,8 @@ class Connection {
return this.error && Date.now() - 30_000 < this.error.occurredAt return this.error && Date.now() - 30_000 < this.error.occurredAt
} }
getQuality() { getQuality() {
if (this.status.code === STATUS.ERROR) { if (this.error) {
return [0, this.status.message] return [0, this.error.message]
} }
const {timeouts, subsCount, eoseTimer, eoseCount} = this.stats const {timeouts, subsCount, eoseTimer, eoseCount} = this.stats
@ -474,6 +485,22 @@ const subscribe = async ({relays, filter, onEvent, onEose, onError}: SubscribeOp
} }
} }
const count = async filter => {
const conn = await connect("wss://rbr.bio")
if (!conn || conn.status.code !== STATUS.READY) {
return null
}
filter = ensurePlural(filter)
return new Promise(resolve => {
conn.count(filter, createFilterId(filter), {
onCount: res => resolve(res?.count),
})
})
}
// Utils // Utils
const createFilterId = filters => const createFilterId = filters =>
@ -504,4 +531,5 @@ export default {
disconnect, disconnect,
publish, publish,
subscribe, subscribe,
count,
} }

View File

@ -6,6 +6,7 @@
import {navigate} from "svelte-routing" import {navigate} from "svelte-routing"
import {log} from "src/util/logger" import {log} from "src/util/logger"
import {renderContent, parseHex} from "src/util/html" import {renderContent, parseHex} from "src/util/html"
import {numberFmt} from "src/util/misc"
import {displayPerson, Tags, toHex} from "src/util/nostr" import {displayPerson, Tags, toHex} from "src/util/nostr"
import Tabs from "src/partials/Tabs.svelte" import Tabs from "src/partials/Tabs.svelte"
import Content from "src/partials/Content.svelte" import Content from "src/partials/Content.svelte"
@ -19,7 +20,7 @@
import pool from "src/agent/pool" import pool from "src/agent/pool"
import {sampleRelays, getPubkeyWriteRelays} from "src/agent/relays" import {sampleRelays, getPubkeyWriteRelays} from "src/agent/relays"
import network from "src/agent/network" import network from "src/agent/network"
import {getPersonWithFallback, people} from "src/agent/tables" import {getPersonWithFallback} from "src/agent/tables"
import {routes, modal, theme, getThemeColor} from "src/app/ui" import {routes, modal, theme, getThemeColor} from "src/app/ui"
import PersonCircle from "src/partials/PersonCircle.svelte" import PersonCircle from "src/partials/PersonCircle.svelte"
@ -104,27 +105,25 @@
loading = false loading = false
}) })
// Prime our followers count // Get our followers count
people.all().forEach(p => { const count = await pool.count({kinds: [3], "#p": [pubkey]})
if (Tags.wrap(p.petnames).type("p").values().all().includes(pubkey)) {
followers.add(p.pubkey)
followersCount.set(followers.size)
}
})
// Round out our followers count if (count) {
await network.load({ followersCount.set(count)
shouldProcess: false, } else {
relays: getRelays(), await network.load({
filter: [{kinds: [3], "#p": [pubkey]}], shouldProcess: false,
onChunk: events => { relays: getRelays(),
for (const e of events) { filter: [{kinds: [3], "#p": [pubkey]}],
followers.add(e.pubkey) onChunk: events => {
} for (const e of events) {
followers.add(e.pubkey)
}
followersCount.set(followers.size) followersCount.set(followers.size)
}, },
}) })
}
}) })
const toggleActions = () => { const toggleActions = () => {
@ -231,7 +230,7 @@
<strong>{person.petnames.length}</strong> following <strong>{person.petnames.length}</strong> following
</button> </button>
<button on:click={showFollowers}> <button on:click={showFollowers}>
<strong>{$followersCount}</strong> followers <strong>{numberFmt.format($followersCount)}</strong> followers
</button> </button>
</div> </div>
{/if} {/if}

View File

@ -388,13 +388,13 @@ export const hexToBech32 = (prefix, url) =>
export const bech32ToHex = b32 => utf8.encode(bech32.fromWords(bech32.decode(b32, false).words)) export const bech32ToHex = b32 => utf8.encode(bech32.fromWords(bech32.decode(b32, false).words))
export const formatSats = sats => { export const numberFmt = new Intl.NumberFormat
const formatter = new Intl.NumberFormat()
if (sats < 1_000) return formatter.format(sats) export const formatSats = sats => {
if (sats < 1_000_000) return formatter.format(round(1, sats / 1000)) + "K" if (sats < 1_000) return numberFmt.format(sats)
if (sats < 100_000_000) return formatter.format(round(1, sats / 1_000_000)) + "MM" if (sats < 1_000_000) return numberFmt.format(round(1, sats / 1000)) + "K"
return formatter.format(round(2, sats / 100_000_000)) + "BTC" if (sats < 100_000_000) return numberFmt.format(round(1, sats / 1_000_000)) + "MM"
return numberFmt.format(round(2, sats / 100_000_000)) + "BTC"
} }
type EventBusListener = { type EventBusListener = {

View File

@ -1,10 +1,10 @@
import * as path from 'path' import * as path from "path"
import {defineConfig} from 'vite' import {defineConfig} from "vite"
import {VitePWA} from 'vite-plugin-pwa' import {VitePWA} from "vite-plugin-pwa"
import mkcert from 'vite-plugin-mkcert' import mkcert from "vite-plugin-mkcert"
import sveltePreprocess from 'svelte-preprocess' import sveltePreprocess from "svelte-preprocess"
import {svelte} from '@sveltejs/vite-plugin-svelte' import {svelte} from "@sveltejs/vite-plugin-svelte"
import {nodePolyfills} from 'vite-plugin-node-polyfills' import {nodePolyfills} from "vite-plugin-node-polyfills"
export default defineConfig({ export default defineConfig({
server: { server: {
@ -15,8 +15,8 @@ export default defineConfig({
}, },
resolve: { resolve: {
alias: { alias: {
src: path.resolve(__dirname, 'src'), src: path.resolve(__dirname, "src"),
} },
}, },
plugins: [ plugins: [
mkcert(), mkcert(),
@ -24,13 +24,13 @@ export default defineConfig({
protocolImports: true, protocolImports: true,
}), }),
VitePWA({ VitePWA({
registerType: 'autoUpdate', registerType: "autoUpdate",
injectRegister: 'auto', injectRegister: "auto",
manifest: { manifest: {
name: 'Coracle', name: "Coracle",
short_name: 'Coracle', short_name: "Coracle",
description: 'Nostr, your way.', description: "Nostr, your way.",
theme_color: '#EB5E28', theme_color: "#EB5E28",
icons: [ icons: [
{type: "image/png", sizes: "192x192", src: "/images/favicon/android-icon-192x192.png"}, {type: "image/png", sizes: "192x192", src: "/images/favicon/android-icon-192x192.png"},
{type: "image/png", sizes: "512x512", src: "/images/favicon/android-icon-512x512.png"}, {type: "image/png", sizes: "512x512", src: "/images/favicon/android-icon-512x512.png"},
@ -40,8 +40,7 @@ export default defineConfig({
svelte({ svelte({
preprocess: sveltePreprocess(), preprocess: sveltePreprocess(),
onwarn: (warning, handler) => { onwarn: (warning, handler) => {
if (warning.code.startsWith("a11y-")) return
if (warning.code.startsWith('a11y-')) return
if (warning.filename.includes("node_modules")) return if (warning.filename.includes("node_modules")) return
handler(warning) handler(warning)