fix: moderation updates
Some checks are pending
continuous-integration/drone/push Build is running

This commit is contained in:
kieran 2024-04-22 21:15:43 +01:00
parent bfdcbca08b
commit ee9f941b11
Signed by: Kieran
GPG Key ID: DE71CEB3925BE941
7 changed files with 59 additions and 41 deletions

View File

@ -1,10 +1,10 @@
import { dedupe } from "@snort/shared"; import { dedupe } from "@snort/shared";
import { EventKind, NostrEvent, NostrLink, TaggedNostrEvent, ToNostrEventTag } from "@snort/system"; import { EventKind, NostrEvent, NostrLink, TaggedNostrEvent, ToNostrEventTag, UnknownTag } from "@snort/system";
import useLogin from "@/Hooks/useLogin"; import useLogin from "@/Hooks/useLogin";
export class MutedWordTag implements ToNostrEventTag { export class MutedWordTag implements ToNostrEventTag {
constructor(readonly word: string) {} constructor(readonly word: string) { }
toEventTag(): string[] | undefined { toEventTag(): string[] | undefined {
return ["word", this.word.toLowerCase()]; return ["word", this.word.toLowerCase()];
@ -43,11 +43,11 @@ export default function useModeration() {
} }
async function addMutedWord(word: string) { async function addMutedWord(word: string) {
await state.addToList(EventKind.MuteList, new MutedWordTag(word.toLowerCase())); await state.addToList(EventKind.MuteList, new MutedWordTag(word.toLowerCase()), true);
} }
async function removeMutedWord(word: string) { async function removeMutedWord(word: string) {
await state.removeFromList(EventKind.MuteList, new MutedWordTag(word.toLowerCase())); await state.removeFromList(EventKind.MuteList, new MutedWordTag(word.toLowerCase()), true);
} }
function isEventMuted(ev: TaggedNostrEvent | NostrEvent) { function isEventMuted(ev: TaggedNostrEvent | NostrEvent) {
@ -56,13 +56,9 @@ export default function useModeration() {
function getMutedWords() { function getMutedWords() {
return state return state
.getList(EventKind.MuteList, o => { .getList(EventKind.MuteList)
if (o[0] === "word") { .filter(a => a instanceof UnknownTag && a.value[0] === "word")
return new MutedWordTag(o[1]); .map(a => (a as UnknownTag).value[1]);
}
})
.filter(a => a instanceof MutedWordTag)
.map(a => (a as MutedWordTag).word);
} }
return { return {

View File

@ -48,16 +48,19 @@ export default function ModerationSettingsPage() {
value={muteWord} value={muteWord}
onChange={e => setMuteWord(e.target.value.toLowerCase())} onChange={e => setMuteWord(e.target.value.toLowerCase())}
/> />
<AsyncButton type="button" onClick={() => addMutedWord(muteWord)}> <AsyncButton onClick={async () => {
await addMutedWord(muteWord);
setMuteWord("");
}}>
<FormattedMessage defaultMessage="Add" id="2/2yg+" /> <FormattedMessage defaultMessage="Add" id="2/2yg+" />
</AsyncButton> </AsyncButton>
</div> </div>
{getMutedWords().map(v => ( {getMutedWords().map(v => (
<div key={v} className="p br b flex items-center justify-between"> <div key={v} className="p br b flex items-center justify-between">
<div>{v}</div> <div>{v}</div>
<button type="button" onClick={() => removeMutedWord(v)}> <AsyncButton onClick={() => removeMutedWord(v)}>
<FormattedMessage defaultMessage="Delete" id="K3r6DQ" /> <FormattedMessage defaultMessage="Delete" id="K3r6DQ" />
</button> </AsyncButton>
</div> </div>
))} ))}
</div> </div>

View File

@ -12,12 +12,15 @@ import {
} from "."; } from ".";
import { findTag } from "./utils"; import { findTag } from "./utils";
/**
* An object which can be stored in a nostr event as a tag
*/
export interface ToNostrEventTag { export interface ToNostrEventTag {
toEventTag(): Array<string> | undefined; toEventTag(): Array<string> | undefined;
} }
export class NostrHashtagLink implements ToNostrEventTag { export class NostrHashtagLink implements ToNostrEventTag {
constructor(readonly tag: string) {} constructor(readonly tag: string) { }
toEventTag() { toEventTag() {
return ["t", this.tag]; return ["t", this.tag];
@ -25,7 +28,7 @@ export class NostrHashtagLink implements ToNostrEventTag {
} }
export class UnknownTag implements ToNostrEventTag { export class UnknownTag implements ToNostrEventTag {
constructor(readonly value: Array<string>) {} constructor(readonly value: Array<string>) { }
toEventTag(): string[] | undefined { toEventTag(): string[] | undefined {
return this.value; return this.value;
} }
@ -191,11 +194,10 @@ export class NostrLink implements ToNostrEventTag {
} }
} }
static fromTag<T = NostrLink>( static fromTag(
tag: Array<string>, tag: Array<string>,
author?: string, author?: string,
kind?: number, kind?: number,
fnOther?: (tag: Array<string>) => T | undefined,
) { ) {
const relays = tag.length > 2 ? [tag[2]] : undefined; const relays = tag.length > 2 ? [tag[2]] : undefined;
switch (tag[0]) { switch (tag[0]) {
@ -209,17 +211,15 @@ export class NostrLink implements ToNostrEventTag {
const [kind, author, dTag] = tag[1].split(":"); const [kind, author, dTag] = tag[1].split(":");
return new NostrLink(NostrPrefix.Address, dTag, Number(kind), author, relays, tag[3]); return new NostrLink(NostrPrefix.Address, dTag, Number(kind), author, relays, tag[3]);
} }
default: {
return fnOther?.(tag) ?? new UnknownTag(tag);
}
} }
throw new Error("Unknown tag!");
} }
static fromTags<T = NostrLink>(tags: ReadonlyArray<Array<string>>, fnOther?: (tag: Array<string>) => T | undefined) { static fromTags(tags: ReadonlyArray<Array<string>>) {
return removeUndefined( return removeUndefined(
tags.map(a => { tags.map(a => {
try { try {
return NostrLink.fromTag<T>(a, undefined, undefined, fnOther); return NostrLink.fromTag(a);
} catch { } catch {
// ignored, cant be mapped // ignored, cant be mapped
} }
@ -227,6 +227,21 @@ export class NostrLink implements ToNostrEventTag {
); );
} }
/**
* Parse all tags even if they are unknown
*/
static fromAllTags(tags: ReadonlyArray<Array<string>>): Array<ToNostrEventTag> {
return removeUndefined(
tags.map(a => {
try {
return NostrLink.fromTag(a);
} catch {
return new UnknownTag(a);
}
}),
);
}
static fromEvent(ev: TaggedNostrEvent | NostrEvent) { static fromEvent(ev: TaggedNostrEvent | NostrEvent) {
const relays = "relays" in ev ? ev.relays : undefined; const relays = "relays" in ev ? ev.relays : undefined;

View File

@ -343,11 +343,11 @@ export class Query extends EventEmitter<QueryEvents> {
this.#log("Starting emit of %s", this.id); this.#log("Starting emit of %s", this.id);
const existing = this.filters; const existing = this.filters;
if (!(this.request.options?.skipDiff ?? false) && existing.length > 0) { if (!(this.request.options?.skipDiff ?? false) && existing.length > 0) {
const filters = await this.request.buildDiff(this.#system, existing); const filters = this.request.buildDiff(this.#system, existing);
this.#log("Build %s %O", this.id, filters); this.#log("Build %s %O", this.id, filters);
filters.forEach(f => this.emit("request", this.id, f)); filters.forEach(f => this.emit("request", this.id, f));
} else { } else {
const filters = await this.request.build(this.#system); const filters = this.request.build(this.#system);
this.#log("Build %s %O", this.id, filters); this.#log("Build %s %O", this.id, filters);
filters.forEach(f => this.emit("request", this.id, f)); filters.forEach(f => this.emit("request", this.id, f));
} }

View File

@ -121,7 +121,7 @@ export class RequestBuilder {
/** /**
* Detects a change in request from a previous set of filters * Detects a change in request from a previous set of filters
*/ */
async buildDiff(system: SystemInterface, prev: Array<ReqFilter>): Promise<Array<BuiltRawReqFilter>> { buildDiff(system: SystemInterface, prev: Array<ReqFilter>): Array<BuiltRawReqFilter> {
const start = unixNowMs(); const start = unixNowMs();
let rawFilters = this.buildRaw(); let rawFilters = this.buildRaw();

View File

@ -45,7 +45,7 @@ export class DiffSyncTags extends EventEmitter<SafeSyncEvents> {
* Get decrypted content * Get decrypted content
*/ */
get encryptedTags() { get encryptedTags() {
if (this.#decryptedContent) { if (this.#decryptedContent && this.#decryptedContent.startsWith("[") && this.#decryptedContent.endsWith("]")) {
const tags = JSON.parse(this.#decryptedContent) as Array<Array<string>>; const tags = JSON.parse(this.#decryptedContent) as Array<Array<string>>;
return tags; return tags;
} }
@ -99,11 +99,7 @@ export class DiffSyncTags extends EventEmitter<SafeSyncEvents> {
async sync(signer: EventSigner, system: SystemInterface) { async sync(signer: EventSigner, system: SystemInterface) {
await this.#sync.sync(system); await this.#sync.sync(system);
if ( if (this.#sync.value?.content) {
this.#sync.value?.content &&
this.#sync.value?.content.startsWith("[") &&
this.#sync.value?.content.endsWith("]")
) {
const decrypted = await signer.nip4Decrypt(this.#sync.value.content, await signer.getPubKey()); const decrypted = await signer.nip4Decrypt(this.#sync.value.content, await signer.getPubKey());
this.#decryptedContent = decrypted; this.#decryptedContent = decrypted;
} }
@ -140,10 +136,9 @@ export class DiffSyncTags extends EventEmitter<SafeSyncEvents> {
} }
// apply changes onto next // apply changes onto next
this.#applyChanges(next.tags, this.#changes); next.tags = this.#applyChanges(next.tags, this.#changes);
if (this.#changesEncrypted.length > 0 && !content) { if (this.#changesEncrypted.length > 0 && !content) {
const encryptedTags = isNew ? [] : this.encryptedTags; const encryptedTags = this.#applyChanges(isNew ? [] : this.encryptedTags, this.#changesEncrypted);
this.#applyChanges(encryptedTags, this.#changesEncrypted);
next.content = JSON.stringify(encryptedTags); next.content = JSON.stringify(encryptedTags);
} else if (content) { } else if (content) {
next.content = content; next.content = content;
@ -206,5 +201,17 @@ export class DiffSyncTags extends EventEmitter<SafeSyncEvents> {
} }
} }
} }
// remove duplicates
return tags.filter((v, i, arr) => {
let hasAnother = false;
for (let x = i + 1; x < arr.length; x++) {
if (arr[x][0] === v[0] && arr[x][1] === v[1]) {
hasAnother = true;
break;
}
}
return !hasAnother;
});
} }
} }

View File

@ -188,7 +188,7 @@ export class UserState<TAppData> extends EventEmitter<UserStateEvents> {
get muted() { get muted() {
const list = this.#standardLists.get(EventKind.MuteList); const list = this.#standardLists.get(EventKind.MuteList);
if (list) { if (list) {
return NostrLink.fromTags(list.encryptedTags); return NostrLink.fromAllTags(list.encryptedTags);
} }
return []; return [];
} }
@ -396,12 +396,9 @@ export class UserState<TAppData> extends EventEmitter<UserStateEvents> {
return false; return false;
} }
getList<T extends ToNostrEventTag>( getList(kind: EventKind): Array<ToNostrEventTag> {
kind: EventKind,
fnOther?: (tag: Array<string>) => T | undefined,
): Array<ToNostrEventTag> {
const list = this.#standardLists.get(kind); const list = this.#standardLists.get(kind);
return NostrLink.fromTags<T>(list?.tags ?? [], fnOther); return NostrLink.fromAllTags(list?.tags ?? []);
} }
serialize(): UserStateObject<TAppData> { serialize(): UserStateObject<TAppData> {