diff --git a/packages/app/src/commands.ts b/packages/app/src/commands.ts new file mode 100644 index 0000000..7f93243 --- /dev/null +++ b/packages/app/src/commands.ts @@ -0,0 +1,35 @@ +import {get} from 'svelte/store' +import {ctx} from '@welshman/lib' +import {addToListPublicly, removeFromList, makeList, FOLLOWS, MUTES} from '@welshman/util' +import {userFollows, userMutes} from './user' +import {nip44EncryptToSelf} from './session' +import {publishThunk} from './thunk' + +export const unfollow = async (value: string) => { + const list = get(userFollows) || makeList({kind: FOLLOWS}) + const event = await removeFromList(list, value).reconcile(nip44EncryptToSelf) + + return publishThunk({event, relays: ctx.app.router.WriteRelays().getUrls()}) +} + +export const follow = async (tag: string[]) => { + const list = get(userFollows) || makeList({kind: FOLLOWS}) + const event = await addToListPublicly(list, tag).reconcile(nip44EncryptToSelf) + + return publishThunk({event, relays: ctx.app.router.WriteRelays().getUrls()}) +} + +export const unmute = async (value: string) => { + const list = get(userMutes) || makeList({kind: MUTES}) + const event = await removeFromList(list, value).reconcile(nip44EncryptToSelf) + + return publishThunk({event, relays: ctx.app.router.WriteRelays().getUrls()}) +} + +export const mute = async (tag: string[]) => { + const list = get(userMutes) || makeList({kind: MUTES}) + const event = await addToListPublicly(list, tag).reconcile(nip44EncryptToSelf) + + return publishThunk({event, relays: ctx.app.router.WriteRelays().getUrls()}) +} + diff --git a/packages/app/src/index.ts b/packages/app/src/index.ts index 21fa46b..d08665e 100644 --- a/packages/app/src/index.ts +++ b/packages/app/src/index.ts @@ -1,6 +1,7 @@ export * from './context' export * from './core' export * from './collection' +export * from './commands' export * from './freshness' export * from './follows' export * from './handles' diff --git a/packages/app/src/session.ts b/packages/app/src/session.ts index 38e96de..a0a349f 100644 --- a/packages/app/src/session.ts +++ b/packages/app/src/session.ts @@ -82,3 +82,14 @@ export const onAuth = async (url: string, challenge: string) => { return event } + +export const nip44EncryptToSelf = (payload: string) => { + const $pubkey = pubkey.get() + const $signer = signer.get() + + if (!$signer) { + throw new Error("Unable to encrypt to self without valid signer") + } + + return $signer.nip44.encrypt($pubkey!, payload) +} diff --git a/packages/app/src/thunk.ts b/packages/app/src/thunk.ts index cb8a4e7..079e279 100644 --- a/packages/app/src/thunk.ts +++ b/packages/app/src/thunk.ts @@ -20,12 +20,9 @@ export type PublishStatusDataByUrlById = Record export const publishStatusData = writable({}) -export type Thunk = { +export type ThunkWithResolve = { event: TrustedEvent relays: string[] -} - -export type ThunkWithResolve = Thunk & { resolve: (data: PublishStatusDataByUrl) => void } @@ -103,13 +100,17 @@ export const prepEvent = (event: ThunkEvent) => { return event as TrustedEvent } -export const makeThunk = ({event, relays}: {event: ThunkEvent, relays: string[]}) => - ({event, relays}) +export type ThunkParams = { + event: ThunkEvent + relays: string[] +} -export const publishThunk = ({event, relays}: Thunk) => +export const makeThunk = (params: ThunkParams) => params + +export const publishThunk = (params: ThunkParams) => new Promise(resolve => { - event = prepEvent(event) + const event = prepEvent(params.event) - thunkWorker.push({event, relays, resolve}) + thunkWorker.push({...params, event, resolve}) repository.publish(event) }) diff --git a/packages/util/src/Encryptable.ts b/packages/util/src/Encryptable.ts index 17938a9..a87709c 100644 --- a/packages/util/src/Encryptable.ts +++ b/packages/util/src/Encryptable.ts @@ -1,21 +1,9 @@ -import type {EventContent, TrustedEvent} from './Events' +import type {EventContent, TrustedEvent, EventTemplate} from './Events' export type Encrypt = (x: string) => Promise -export type EncryptableParams = { - kind: number, - tags?: string[][] - content?: string -} - export type EncryptableUpdates = Partial -export type EncryptableResult = { - kind: number, - tags: string[][] - content: string -} - export type DecryptedEvent = TrustedEvent & { plaintext: EncryptableUpdates } @@ -26,7 +14,7 @@ export const asDecryptedEvent = (event: TrustedEvent, plaintext: EncryptableUpda /** * Represents an encryptable event with optional updates. */ -export class Encryptable { +export class Encryptable { /** * Creates an instance of Encryptable. * @param event - An EventTemplate with optional tags and content. @@ -39,14 +27,14 @@ export class Encryptable { * const eventTemplate = await encryptable.reconcile(myEncryptFunction) * ``` */ - constructor(readonly event: EncryptableParams, readonly updates: EncryptableUpdates) {} + constructor(readonly event: Partial, readonly updates: EncryptableUpdates) {} /** * Encrypts plaintext updates and merges them into the event template. * @param encrypt - The encryption function to be used. * @returns A promise that resolves to the reconciled and encrypted event. */ - async reconcile(encrypt: Encrypt): Promise { + async reconcile(encrypt: Encrypt) { const encryptContent = () => { if (!this.updates.content) return null @@ -72,6 +60,6 @@ export class Encryptable { ...this.event, tags: tags || this.event.tags || [], content: content || this.event.content || "", - } + } as T } } diff --git a/packages/util/src/List.ts b/packages/util/src/List.ts index 6ca5af7..f65f512 100644 --- a/packages/util/src/List.ts +++ b/packages/util/src/List.ts @@ -1,4 +1,4 @@ -import {parseJson, append, nthNe, nthEq} from "@welshman/lib" +import {parseJson, append, nthEq} from "@welshman/lib" import {Address} from "./Address" import {uniqTags} from "./Tags" import {isShareableRelayUrl} from "./Relay" @@ -44,22 +44,25 @@ export const readList = (event: DecryptedEvent): PublishedList => { export const getListTags = (list: List | undefined) => [...list?.publicTags || [], ...list?.privateTags || []] -export const removeFromList = (list: List, value: string) => { +export const removeFromListByPredicate = (list: List, pred: (t: string[]) => boolean) => { const plaintext: EncryptableUpdates = {} const template = { kind: list.kind, content: list.event?.content || "", - tags: list.publicTags.filter(nthNe(1, value)), + tags: list.publicTags.filter(t => !pred(t)), } // Avoid redundant encrypt calls if possible - if (list.privateTags.some(nthEq(1, value))) { - plaintext.content = JSON.stringify(list.privateTags.filter(nthNe(1, value))) + if (list.privateTags.some(t => pred(t))) { + plaintext.content = JSON.stringify(list.privateTags.filter(t => !pred(t))) } return new Encryptable(template, plaintext) } +export const removeFromList = (list: List, value: string) => + removeFromListByPredicate(list, nthEq(1, value)) + export const addToListPublicly = (list: List, tag: string[]) => { const template = { kind: list.kind,