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

View File

@ -48,16 +48,19 @@ export default function ModerationSettingsPage() {
value={muteWord}
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+" />
</AsyncButton>
</div>
{getMutedWords().map(v => (
<div key={v} className="p br b flex items-center justify-between">
<div>{v}</div>
<button type="button" onClick={() => removeMutedWord(v)}>
<AsyncButton onClick={() => removeMutedWord(v)}>
<FormattedMessage defaultMessage="Delete" id="K3r6DQ" />
</button>
</AsyncButton>
</div>
))}
</div>

View File

@ -12,12 +12,15 @@ import {
} from ".";
import { findTag } from "./utils";
/**
* An object which can be stored in a nostr event as a tag
*/
export interface ToNostrEventTag {
toEventTag(): Array<string> | undefined;
}
export class NostrHashtagLink implements ToNostrEventTag {
constructor(readonly tag: string) {}
constructor(readonly tag: string) { }
toEventTag() {
return ["t", this.tag];
@ -25,7 +28,7 @@ export class NostrHashtagLink implements ToNostrEventTag {
}
export class UnknownTag implements ToNostrEventTag {
constructor(readonly value: Array<string>) {}
constructor(readonly value: Array<string>) { }
toEventTag(): string[] | undefined {
return this.value;
}
@ -191,11 +194,10 @@ export class NostrLink implements ToNostrEventTag {
}
}
static fromTag<T = NostrLink>(
static fromTag(
tag: Array<string>,
author?: string,
kind?: number,
fnOther?: (tag: Array<string>) => T | undefined,
) {
const relays = tag.length > 2 ? [tag[2]] : undefined;
switch (tag[0]) {
@ -209,17 +211,15 @@ export class NostrLink implements ToNostrEventTag {
const [kind, author, dTag] = tag[1].split(":");
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(
tags.map(a => {
try {
return NostrLink.fromTag<T>(a, undefined, undefined, fnOther);
return NostrLink.fromTag(a);
} catch {
// 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) {
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);
const existing = this.filters;
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);
filters.forEach(f => this.emit("request", this.id, f));
} else {
const filters = await this.request.build(this.#system);
const filters = this.request.build(this.#system);
this.#log("Build %s %O", this.id, filters);
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
*/
async buildDiff(system: SystemInterface, prev: Array<ReqFilter>): Promise<Array<BuiltRawReqFilter>> {
buildDiff(system: SystemInterface, prev: Array<ReqFilter>): Array<BuiltRawReqFilter> {
const start = unixNowMs();
let rawFilters = this.buildRaw();

View File

@ -45,7 +45,7 @@ export class DiffSyncTags extends EventEmitter<SafeSyncEvents> {
* Get decrypted content
*/
get encryptedTags() {
if (this.#decryptedContent) {
if (this.#decryptedContent && this.#decryptedContent.startsWith("[") && this.#decryptedContent.endsWith("]")) {
const tags = JSON.parse(this.#decryptedContent) as Array<Array<string>>;
return tags;
}
@ -99,11 +99,7 @@ export class DiffSyncTags extends EventEmitter<SafeSyncEvents> {
async sync(signer: EventSigner, system: SystemInterface) {
await this.#sync.sync(system);
if (
this.#sync.value?.content &&
this.#sync.value?.content.startsWith("[") &&
this.#sync.value?.content.endsWith("]")
) {
if (this.#sync.value?.content) {
const decrypted = await signer.nip4Decrypt(this.#sync.value.content, await signer.getPubKey());
this.#decryptedContent = decrypted;
}
@ -140,10 +136,9 @@ export class DiffSyncTags extends EventEmitter<SafeSyncEvents> {
}
// apply changes onto next
this.#applyChanges(next.tags, this.#changes);
next.tags = this.#applyChanges(next.tags, this.#changes);
if (this.#changesEncrypted.length > 0 && !content) {
const encryptedTags = isNew ? [] : this.encryptedTags;
this.#applyChanges(encryptedTags, this.#changesEncrypted);
const encryptedTags = this.#applyChanges(isNew ? [] : this.encryptedTags, this.#changesEncrypted);
next.content = JSON.stringify(encryptedTags);
} else if (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() {
const list = this.#standardLists.get(EventKind.MuteList);
if (list) {
return NostrLink.fromTags(list.encryptedTags);
return NostrLink.fromAllTags(list.encryptedTags);
}
return [];
}
@ -396,12 +396,9 @@ export class UserState<TAppData> extends EventEmitter<UserStateEvents> {
return false;
}
getList<T extends ToNostrEventTag>(
kind: EventKind,
fnOther?: (tag: Array<string>) => T | undefined,
): Array<ToNostrEventTag> {
getList(kind: EventKind): Array<ToNostrEventTag> {
const list = this.#standardLists.get(kind);
return NostrLink.fromTags<T>(list?.tags ?? [], fnOther);
return NostrLink.fromAllTags(list?.tags ?? []);
}
serialize(): UserStateObject<TAppData> {