forked from Kieran/snort
set event seen_at times, sort by seen_at in ForYouFeed
This commit is contained in:
parent
5bc3c10d36
commit
7ceab04cbc
@ -1,11 +1,12 @@
|
||||
import { EventKind, NostrLink } from "@snort/system";
|
||||
import classNames from "classnames";
|
||||
import React, { useCallback, useState } from "react";
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import { useInView } from "react-intersection-observer";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { LRUCache } from "typescript-lru-cache";
|
||||
|
||||
import { Relay } from "@/Cache";
|
||||
import NoteHeader from "@/Components/Event/Note/NoteHeader";
|
||||
import { NoteText } from "@/Components/Event/Note/NoteText";
|
||||
import { TranslationInfo } from "@/Components/Event/Note/TranslationInfo";
|
||||
@ -38,6 +39,7 @@ export function Note(props: NoteProps) {
|
||||
const baseClassName = classNames("note min-h-[110px] flex flex-col gap-4 card", className ?? "");
|
||||
const { isEventMuted } = useModeration();
|
||||
const { ref, inView } = useInView({ triggerOnce: true, rootMargin: "2000px" });
|
||||
const { ref: setSeenAtRef, inView: setSeenAtInView } = useInView({ rootMargin: "0px", threshold: 1 });
|
||||
const [showTranslation, setShowTranslation] = useState(true);
|
||||
const [translated, setTranslated] = useState<NoteTranslation>(translationCache.get(ev.id));
|
||||
const cachedSetTranslated = useCallback(
|
||||
@ -48,6 +50,16 @@ export function Note(props: NoteProps) {
|
||||
[ev.id],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
let timeout: ReturnType<typeof setTimeout>;
|
||||
if (setSeenAtInView) {
|
||||
timeout = setTimeout(() => {
|
||||
Relay.setEventMetadata(ev.id, { seen_at: Math.round(Date.now() / 1000) });
|
||||
}, 5000);
|
||||
}
|
||||
return () => clearTimeout(timeout);
|
||||
}, [setSeenAtInView]);
|
||||
|
||||
const optionsMerged = { ...defaultOptions, ...opt };
|
||||
const goToEvent = useGoToEvent(props, optionsMerged);
|
||||
|
||||
@ -71,7 +83,7 @@ export function Note(props: NoteProps) {
|
||||
{translated && <TranslationInfo translated={translated} setShowTranslation={setShowTranslation} />}
|
||||
{ev.kind === EventKind.Polls && <Poll ev={ev} />}
|
||||
{optionsMerged.showFooter && (
|
||||
<div className="mt-4">
|
||||
<div className="mt-4" ref={setSeenAtRef}>
|
||||
<NoteFooter ev={ev} replyCount={props.threadChains?.get(chainKey(ev))?.length} />
|
||||
</div>
|
||||
)}
|
||||
|
@ -1,9 +1,9 @@
|
||||
import {EventKind, NostrEvent} from "@snort/system";
|
||||
import { EventKind, NostrEvent } from "@snort/system";
|
||||
import { memo, useEffect, useMemo, useState } from "react";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
import { Link } from "react-router-dom";
|
||||
|
||||
import {Relay} from "@/Cache";
|
||||
import { Relay } from "@/Cache";
|
||||
import { DisplayAs, DisplayAsSelector } from "@/Components/Feed/DisplayAsSelector";
|
||||
import { TimelineRenderer } from "@/Components/Feed/TimelineRenderer";
|
||||
import { TaskList } from "@/Components/Tasks/TaskList";
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { NostrEvent, parseZap } from "@snort/system";
|
||||
import debug from "debug";
|
||||
|
||||
import {RelayHandler} from "./types";
|
||||
import { RelayHandler } from "./types";
|
||||
|
||||
const log = debug("getForYouFeed");
|
||||
|
||||
@ -37,12 +37,9 @@ export async function getForYouFeed(relay: RelayHandler, pubkey: string): Promis
|
||||
async function getMyReactedAuthors(relay: RelayHandler, myReactedEventIds: Set<string>, myPubkey: string) {
|
||||
const myReactedAuthors = new Map<string, number>();
|
||||
|
||||
const myReactions = relay.req(
|
||||
"getMyReactedAuthors",
|
||||
{
|
||||
"#e": Array.from(myReactedEventIds),
|
||||
},
|
||||
) as NostrEvent[];
|
||||
const myReactions = relay.req("getMyReactedAuthors", {
|
||||
"#e": Array.from(myReactedEventIds),
|
||||
}) as NostrEvent[];
|
||||
|
||||
myReactions.forEach(reaction => {
|
||||
if (reaction.pubkey !== myPubkey) {
|
||||
@ -56,13 +53,10 @@ async function getMyReactedAuthors(relay: RelayHandler, myReactedEventIds: Set<s
|
||||
async function getMyReactedEvents(relay: RelayHandler, pubkey: string) {
|
||||
const myReactedEventIds = new Set<string>();
|
||||
|
||||
const myEvents = relay.req(
|
||||
"getMyReactedEventIds",
|
||||
{
|
||||
authors: [pubkey],
|
||||
kinds: [1, 6, 7, 9735],
|
||||
},
|
||||
) as NostrEvent[];
|
||||
const myEvents = relay.req("getMyReactedEventIds", {
|
||||
authors: [pubkey],
|
||||
kinds: [1, 6, 7, 9735],
|
||||
}) as NostrEvent[];
|
||||
myEvents.forEach(ev => {
|
||||
const targetEventId = ev.kind === 9735 ? parseZap(ev).event?.id : ev.tags.find(tag => tag[0] === "e")?.[1];
|
||||
if (targetEventId) {
|
||||
@ -76,12 +70,9 @@ async function getMyReactedEvents(relay: RelayHandler, pubkey: string) {
|
||||
async function getOthersWhoReacted(relay: RelayHandler, myReactedEventIds: Set<string>, myPubkey: string) {
|
||||
const othersWhoReacted = new Map<string, number>();
|
||||
|
||||
const otherReactions = relay.req(
|
||||
"getOthersWhoReacted",
|
||||
{
|
||||
"#e": Array.from(myReactedEventIds),
|
||||
},
|
||||
) as NostrEvent[];
|
||||
const otherReactions = relay.req("getOthersWhoReacted", {
|
||||
"#e": Array.from(myReactedEventIds),
|
||||
}) as NostrEvent[];
|
||||
|
||||
otherReactions.forEach(reaction => {
|
||||
if (reaction.pubkey !== myPubkey) {
|
||||
@ -100,13 +91,10 @@ async function getEventIdsReactedByOthers(
|
||||
) {
|
||||
const eventIdsReactedByOthers = new Map<string, number>();
|
||||
|
||||
const events = relay.req(
|
||||
"getEventIdsReactedByOthers",
|
||||
{
|
||||
authors: [...othersWhoReacted.keys()],
|
||||
kinds: [1, 6, 7, 9735],
|
||||
},
|
||||
) as NostrEvent[];
|
||||
const events = relay.req("getEventIdsReactedByOthers", {
|
||||
authors: [...othersWhoReacted.keys()],
|
||||
kinds: [1, 6, 7, 9735],
|
||||
}) as NostrEvent[];
|
||||
|
||||
events.forEach(event => {
|
||||
if (event.pubkey === myPub || myReactedEvents.has(event.id)) {
|
||||
@ -124,11 +112,19 @@ async function getEventIdsReactedByOthers(
|
||||
return eventIdsReactedByOthers;
|
||||
}
|
||||
|
||||
async function getFeedEvents(relay: RelayHandler, reactedToIds: Map<string, number>, reactedToAuthors: Map<string, number>) {
|
||||
const events = relay.sql(
|
||||
`select json from events where id in (${Array.from(reactedToIds.keys()).map(() => "?").join(", ")}) and kind = 1 order by seen_at ASC, created DESC limit 1000`,
|
||||
Array.from(reactedToIds.keys()),
|
||||
).map(row => JSON.parse(row[0] as string) as NostrEvent);
|
||||
async function getFeedEvents(
|
||||
relay: RelayHandler,
|
||||
reactedToIds: Map<string, number>,
|
||||
reactedToAuthors: Map<string, number>,
|
||||
) {
|
||||
const events = relay
|
||||
.sql(
|
||||
`select json from events where id in (${Array.from(reactedToIds.keys())
|
||||
.map(() => "?")
|
||||
.join(", ")}) and kind = 1 order by seen_at ASC, created DESC limit 1000`,
|
||||
Array.from(reactedToIds.keys()),
|
||||
)
|
||||
.map(row => JSON.parse(row[0] as string) as NostrEvent);
|
||||
|
||||
const seen = new Set<string>(events.map(ev => ev.id));
|
||||
|
||||
@ -138,11 +134,14 @@ async function getFeedEvents(relay: RelayHandler, reactedToIds: Map<string, numb
|
||||
.sort((a, b) => reactedToAuthors.get(b)! - reactedToAuthors.get(a)!)
|
||||
.slice(20);
|
||||
|
||||
const eventsByFavoriteAuthors = relay.sql(
|
||||
`select json from events where pubkey in (${favoriteAuthors.map(() => "?").join(", ")}) and kind = 1 order by seen_at ASC, created DESC limit 100`,
|
||||
favoriteAuthors,
|
||||
).map(row => JSON.parse(row[0] as string) as NostrEvent);
|
||||
|
||||
const eventsByFavoriteAuthors = relay
|
||||
.sql(
|
||||
`select json from events where pubkey in (${favoriteAuthors
|
||||
.map(() => "?")
|
||||
.join(", ")}) and kind = 1 order by seen_at ASC, created DESC limit 100`,
|
||||
favoriteAuthors,
|
||||
)
|
||||
.map(row => JSON.parse(row[0] as string) as NostrEvent);
|
||||
|
||||
eventsByFavoriteAuthors.forEach(ev => {
|
||||
if (!seen.has(ev.id)) {
|
||||
|
@ -1,4 +1,12 @@
|
||||
import { NostrEvent, OkResponse, ReqCommand, ReqFilter, WorkerMessage, WorkerMessageCommand } from "./types";
|
||||
import {
|
||||
EventMetadata,
|
||||
NostrEvent,
|
||||
OkResponse,
|
||||
ReqCommand,
|
||||
ReqFilter,
|
||||
WorkerMessage,
|
||||
WorkerMessageCommand,
|
||||
} from "./types";
|
||||
import { v4 as uuid } from "uuid";
|
||||
|
||||
export class WorkerRelayInterface {
|
||||
@ -49,6 +57,10 @@ export class WorkerRelayInterface {
|
||||
return await this.#workerRpc<string, Array<NostrEvent>>("forYouFeed", pubkey);
|
||||
}
|
||||
|
||||
setEventMetadata(id: string, meta: EventMetadata) {
|
||||
return this.#workerRpc<[string, EventMetadata], void>("setEventMetadata", [id, meta]);
|
||||
}
|
||||
|
||||
#workerRpc<T, R>(cmd: WorkerMessageCommand, args?: T) {
|
||||
const id = uuid();
|
||||
const msg = {
|
||||
|
@ -1,5 +1,5 @@
|
||||
import EventEmitter from "eventemitter3";
|
||||
import { NostrEvent, RelayHandler, RelayHandlerEvents, ReqFilter, eventMatchesFilter } from "./types";
|
||||
import { NostrEvent, RelayHandler, RelayHandlerEvents, ReqFilter, eventMatchesFilter, EventMetadata } from "./types";
|
||||
import debug from "debug";
|
||||
|
||||
/**
|
||||
@ -79,4 +79,8 @@ export class InMemoryRelay extends EventEmitter<RelayHandlerEvents> implements R
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
setEventMetadata(_id: string, _meta: EventMetadata) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
import {NostrEvent} from "./types";
|
||||
import {SqliteRelay} from "./sqlite-relay";
|
||||
import { NostrEvent } from "./types";
|
||||
import { SqliteRelay } from "./sqlite-relay";
|
||||
import debug from "debug";
|
||||
|
||||
const log = debug('SqliteRelay:migrations');
|
||||
const log = debug("SqliteRelay:migrations");
|
||||
|
||||
/**
|
||||
* Do database migration
|
||||
@ -98,4 +98,4 @@ async function migrate_v4(relay: SqliteRelay) {
|
||||
});
|
||||
}
|
||||
|
||||
export default migrate;
|
||||
export default migrate;
|
||||
|
@ -1,6 +1,6 @@
|
||||
import sqlite3InitModule, { Database, Sqlite3Static } from "@sqlite.org/sqlite-wasm";
|
||||
import { EventEmitter } from "eventemitter3";
|
||||
import { NostrEvent, RelayHandler, RelayHandlerEvents, ReqFilter, unixNowMs } from "./types";
|
||||
import { EventMetadata, NostrEvent, RelayHandler, RelayHandlerEvents, ReqFilter, unixNowMs } from "./types";
|
||||
import debug from "debug";
|
||||
import migrate from "./migrations";
|
||||
|
||||
@ -87,6 +87,15 @@ export class SqliteRelay extends EventEmitter<RelayHandlerEvents> implements Rel
|
||||
return eventsInserted.length > 0;
|
||||
}
|
||||
|
||||
setEventMetadata(id: string, meta: EventMetadata) {
|
||||
if (meta.seen_at) {
|
||||
console.log("update seen_at", id, meta.seen_at);
|
||||
this.db?.exec("update events set seen_at = ? where id = ?", {
|
||||
bind: [meta.seen_at, id],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#deleteById(db: Database, ids: Array<string>) {
|
||||
db.exec(`delete from events where id in (${this.#repeatParams(ids.length)})`, {
|
||||
bind: ids,
|
||||
|
@ -10,7 +10,8 @@ export type WorkerMessageCommand =
|
||||
| "close"
|
||||
| "dumpDb"
|
||||
| "emit-event"
|
||||
| "forYouFeed";
|
||||
| "forYouFeed"
|
||||
| "setEventMetadata";
|
||||
|
||||
export interface WorkerMessage<T> {
|
||||
id: string;
|
||||
@ -29,6 +30,10 @@ export interface NostrEvent {
|
||||
relays?: Array<string>;
|
||||
}
|
||||
|
||||
export interface EventMetadata {
|
||||
seen_at?: number;
|
||||
}
|
||||
|
||||
export type ReqCommand = ["REQ", id: string, ...filters: Array<ReqFilter>];
|
||||
|
||||
export interface ReqFilter {
|
||||
@ -65,6 +70,7 @@ export interface RelayHandler extends EventEmitter<RelayHandlerEvents> {
|
||||
count(req: ReqFilter): number;
|
||||
summary(): Record<string, number>;
|
||||
dump(): Promise<Uint8Array>;
|
||||
setEventMetadata(id: string, meta: EventMetadata): void;
|
||||
}
|
||||
|
||||
export interface RelayHandlerEvents {
|
||||
|
@ -3,8 +3,8 @@
|
||||
import { InMemoryRelay } from "./memory-relay";
|
||||
import { WorkQueueItem, barrierQueue, processWorkQueue } from "./queue";
|
||||
import { SqliteRelay } from "./sqlite-relay";
|
||||
import { NostrEvent, RelayHandler, ReqCommand, ReqFilter, WorkerMessage, unixNowMs } from "./types";
|
||||
import {getForYouFeed} from "./forYouFeed";
|
||||
import { NostrEvent, RelayHandler, ReqCommand, ReqFilter, WorkerMessage, unixNowMs, EventMetadata } from "./types";
|
||||
import { getForYouFeed } from "./forYouFeed";
|
||||
|
||||
let relay: RelayHandler | undefined;
|
||||
|
||||
@ -138,6 +138,13 @@ globalThis.onmessage = async ev => {
|
||||
});
|
||||
break;
|
||||
}
|
||||
case "setEventMetadata": {
|
||||
await barrierQueue(cmdQueue, async () => {
|
||||
const [id, metadata] = msg.args as [string, EventMetadata];
|
||||
relay!.setEventMetadata(id, metadata);
|
||||
});
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
reply(msg.id, { error: "Unknown command" });
|
||||
break;
|
||||
|
Loading…
Reference in New Issue
Block a user