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