feat: automate social graph
This commit is contained in:
117
packages/app/src/Cache/UserFollowsWorker.ts
Normal file
117
packages/app/src/Cache/UserFollowsWorker.ts
Normal file
@ -0,0 +1,117 @@
|
||||
import { CachedTable, CacheEvents, removeUndefined, unixNowMs, unwrap } from "@snort/shared";
|
||||
import { EventKind, NostrEvent, UsersFollows } from "@snort/system";
|
||||
import { WorkerRelayInterface } from "@snort/worker-relay";
|
||||
import debug from "debug";
|
||||
import EventEmitter from "eventemitter3";
|
||||
|
||||
export class UserFollowsWorker extends EventEmitter<CacheEvents> implements CachedTable<UsersFollows> {
|
||||
#relay: WorkerRelayInterface;
|
||||
#keys = new Set<string>();
|
||||
#cache = new Map<string, UsersFollows>();
|
||||
#log = debug("UserFollowsWorker");
|
||||
|
||||
constructor(relay: WorkerRelayInterface) {
|
||||
super();
|
||||
this.#relay = relay;
|
||||
}
|
||||
|
||||
async preload() {
|
||||
const start = unixNowMs();
|
||||
const profiles = await this.#relay.query([
|
||||
"REQ",
|
||||
"profiles-preload",
|
||||
{
|
||||
kinds: [3],
|
||||
},
|
||||
]);
|
||||
this.#cache = new Map<string, UsersFollows>(profiles.map(a => [a.pubkey, unwrap(mapEventToUserFollows(a))]));
|
||||
this.#keys = new Set<string>(this.#cache.keys());
|
||||
this.#log(`Loaded %d/%d in %d ms`, this.#cache.size, this.#keys.size, (unixNowMs() - start).toLocaleString());
|
||||
}
|
||||
|
||||
keysOnTable(): string[] {
|
||||
return [...this.#keys];
|
||||
}
|
||||
|
||||
getFromCache(key?: string | undefined): UsersFollows | undefined {
|
||||
if (key) {
|
||||
return this.#cache.get(key);
|
||||
}
|
||||
}
|
||||
|
||||
discover(ev: NostrEvent) {
|
||||
this.#keys.add(ev.pubkey);
|
||||
}
|
||||
|
||||
async get(key?: string | undefined): Promise<UsersFollows | undefined> {
|
||||
if (key) {
|
||||
const res = await this.bulkGet([key]);
|
||||
if (res.length > 0) {
|
||||
return res[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async bulkGet(keys: string[]) {
|
||||
if (keys.length === 0) return [];
|
||||
|
||||
const results = await this.#relay.query([
|
||||
"REQ",
|
||||
"UserFollowsWorker.bulkGet",
|
||||
{
|
||||
authors: keys,
|
||||
kinds: [3],
|
||||
},
|
||||
]);
|
||||
const mapped = removeUndefined(results.map(a => mapEventToUserFollows(a)));
|
||||
for (const pf of mapped) {
|
||||
this.#cache.set(this.key(pf), pf);
|
||||
}
|
||||
this.emit(
|
||||
"change",
|
||||
mapped.map(a => this.key(a)),
|
||||
);
|
||||
return mapped;
|
||||
}
|
||||
|
||||
async set(obj: UsersFollows) {
|
||||
this.#keys.add(this.key(obj));
|
||||
}
|
||||
|
||||
async bulkSet(obj: UsersFollows[] | readonly UsersFollows[]) {
|
||||
const mapped = obj.map(a => this.key(a));
|
||||
mapped.forEach(a => this.#keys.add(a));
|
||||
// todo: store in cache
|
||||
this.emit("change", mapped);
|
||||
}
|
||||
|
||||
async update(): Promise<"new" | "refresh" | "updated" | "no_change"> {
|
||||
// do nothing
|
||||
return "refresh";
|
||||
}
|
||||
|
||||
async buffer(keys: string[]): Promise<string[]> {
|
||||
const missing = keys.filter(a => !this.#keys.has(a));
|
||||
const res = await this.bulkGet(missing);
|
||||
return missing.filter(a => !res.some(b => this.key(b) === a));
|
||||
}
|
||||
|
||||
key(of: UsersFollows): string {
|
||||
return of.pubkey;
|
||||
}
|
||||
|
||||
snapshot(): UsersFollows[] {
|
||||
return [...this.#cache.values()];
|
||||
}
|
||||
}
|
||||
|
||||
export function mapEventToUserFollows(ev: NostrEvent): UsersFollows | undefined {
|
||||
if (ev.kind !== EventKind.ContactList) return;
|
||||
|
||||
return {
|
||||
pubkey: ev.pubkey,
|
||||
loaded: unixNowMs(),
|
||||
created: ev.created_at,
|
||||
follows: ev.tags,
|
||||
};
|
||||
}
|
@ -6,6 +6,7 @@ import WorkerRelayPath from "@snort/worker-relay/dist/worker?worker&url";
|
||||
import { EventCacheWorker } from "./EventCacheWorker";
|
||||
import { GiftWrapCache } from "./GiftWrapCache";
|
||||
import { ProfileCacheRelayWorker } from "./ProfileWorkerCache";
|
||||
import { UserFollowsWorker } from "./UserFollowsWorker";
|
||||
|
||||
export const Relay = new WorkerRelayInterface(WorkerRelayPath);
|
||||
export async function initRelayWorker() {
|
||||
@ -20,6 +21,7 @@ export const SystemDb = new SnortSystemDb();
|
||||
export const UserRelays = new UserRelaysCache(SystemDb.userRelays);
|
||||
export const RelayMetrics = new RelayMetricCache(SystemDb.relayMetrics);
|
||||
|
||||
export const UserFollows = new UserFollowsWorker(Relay);
|
||||
export const UserCache = new ProfileCacheRelayWorker(Relay);
|
||||
export const EventsCache = new EventCacheWorker(Relay);
|
||||
|
||||
@ -32,6 +34,7 @@ export async function preload(follows?: Array<string>) {
|
||||
GiftsCache.preload(),
|
||||
UserRelays.preload(follows),
|
||||
EventsCache.preload(),
|
||||
UserFollows.preload(),
|
||||
];
|
||||
await Promise.all(preloads);
|
||||
}
|
||||
|
Reference in New Issue
Block a user