mirror of
https://github.com/coracle-social/coracle.git
synced 2024-10-18 01:24:36 +00:00
Use welshman dvms
This commit is contained in:
parent
ab7a28ae34
commit
b318cc6808
12
README.md
12
README.md
@ -19,7 +19,9 @@ If you like Coracle and want to support its development, you can donate sats via
|
||||
- [x] NIP 87 closed groups
|
||||
- [x] NIP 72 communities
|
||||
- [x] NIP 89 client tag support
|
||||
- [x] NIP 89 handler integration
|
||||
- [x] NIP 32 labeling and collections
|
||||
- [x] NIP 17 DMs
|
||||
- [x] Private group calendars and listings
|
||||
- [x] Cross-posting between communities and main feed
|
||||
- [x] Bech32 entity search and scan
|
||||
@ -46,6 +48,12 @@ If you like Coracle and want to support its development, you can donate sats via
|
||||
- [x] Onboarding workflow
|
||||
- [x] Multi-account support
|
||||
- [x] Notifications view
|
||||
- [x] Web of trust scores for less spam and better group/feed suggestions
|
||||
- [x] Customizable and shareable feeds and lists
|
||||
- [x] Customizable invite links
|
||||
- [x] Reporting via tagr-bot
|
||||
- [x] Nostr Wallet Connect support
|
||||
- [x] Date/time localization
|
||||
|
||||
You can find a more complete changelog [here](./CHANGELOG.md).
|
||||
|
||||
@ -53,9 +61,9 @@ You can find a more complete changelog [here](./CHANGELOG.md).
|
||||
|
||||
- Clone the project repository: `git clone https://github.com/coracle-social/coracle.git`
|
||||
- Navigate to the project directory: `cd coracle`
|
||||
- Install dependencies: `yarn`
|
||||
- Install dependencies: `npm i`
|
||||
- Customize configuration in `.env` (optional, see below)
|
||||
- Start the development server: `yarn dev`
|
||||
- Start the development server: `npm run dev`
|
||||
|
||||
# Customization
|
||||
|
||||
|
BIN
package-lock.json
generated
BIN
package-lock.json
generated
Binary file not shown.
@ -9,7 +9,7 @@
|
||||
"check:es": "eslint 'src/**/*.{js,ts,svelte}' --quiet",
|
||||
"check:ts": "svelte-check --tsconfig ./tsconfig.json --threshold error",
|
||||
"check:fmt": "prettier --check $(git diff head --name-only --diff-filter d | grep -E '(js|ts|svelte)$' | xargs)",
|
||||
"check:errors": "run-p check:es check:ts check:cycles",
|
||||
"check:errors": "run-p check:es check:ts",
|
||||
"check": "run-p check:errors check:fmt",
|
||||
"format": "prettier --write $(git diff head --name-only --diff-filter d | grep -E '(js|ts|svelte)$' | xargs)",
|
||||
"watch": "find src -type f | entr -r"
|
||||
@ -57,7 +57,8 @@
|
||||
"@welshman/feeds": "^0.0.12",
|
||||
"@welshman/lib": "^0.0.11",
|
||||
"@welshman/net": "^0.0.14",
|
||||
"@welshman/util": "^0.0.17",
|
||||
"@welshman/util": "^0.0.18",
|
||||
"@welshman/dvm": "^0.0.2",
|
||||
"bowser": "^2.11.0",
|
||||
"classnames": "^2.5.1",
|
||||
"compressorjs": "^1.2.1",
|
||||
|
@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import {append} from "ramda"
|
||||
import {append} from "@welshman/lib"
|
||||
import {updateIn} from "src/util/misc"
|
||||
import {slide} from "src/util/transition"
|
||||
import Card from "src/partials/Card.svelte"
|
||||
@ -9,7 +9,9 @@
|
||||
export let task
|
||||
|
||||
const hideTask = () =>
|
||||
updateCurrentSession(updateIn("onboarding_tasks_completed", append(task)))
|
||||
updateCurrentSession(
|
||||
updateIn("onboarding_tasks_completed", (tasks: string[]) => append(task, tasks)),
|
||||
)
|
||||
</script>
|
||||
|
||||
{#if !$session.onboarding_tasks_completed.includes(task)}
|
||||
@ -19,9 +21,7 @@
|
||||
<FlexColumn>
|
||||
<slot />
|
||||
</FlexColumn>
|
||||
<i
|
||||
class="fa fa-times absolute right-0 top-0 cursor-pointer p-2"
|
||||
on:click={hideTask} />
|
||||
<i class="fa fa-times absolute right-0 top-0 cursor-pointer p-2" on:click={hideTask} />
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,4 +1,5 @@
|
||||
<script lang="ts">
|
||||
import cx from "classnames"
|
||||
import Anchor from "src/partials/Anchor.svelte"
|
||||
import {router} from "src/app/util/router"
|
||||
import {deriveProfileDisplay, loadPubkeys} from "src/engine"
|
||||
@ -11,6 +12,6 @@
|
||||
loadPubkeys([pubkey])
|
||||
</script>
|
||||
|
||||
<Anchor modal stopPropagation class={$$props.class} href={path}>
|
||||
<Anchor modal stopPropagation class={cx("!no-underline", $$props.class)} href={path}>
|
||||
@<span class="underline">{$display}</span>
|
||||
</Anchor>
|
||||
|
@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import {makeRelayFeed} from "@welshman/feeds"
|
||||
import {makeRelayFeed, makeScopeFeed, Scope} from "@welshman/feeds"
|
||||
import Anchor from "src/partials/Anchor.svelte"
|
||||
import Feed from "src/app/shared/Feed.svelte"
|
||||
import {router} from "src/app/util/router"
|
||||
@ -13,6 +13,8 @@
|
||||
|
||||
if (isPlatformFeed) {
|
||||
globalFeed.set(makeFeed({definition: makeRelayFeed(...$env.PLATFORM_RELAYS)}))
|
||||
} else {
|
||||
globalFeed.set(makeFeed({definition: makeScopeFeed(Scope.Follows)}))
|
||||
}
|
||||
|
||||
document.title = "Feeds"
|
||||
|
@ -18,7 +18,7 @@
|
||||
userIsGroupMember,
|
||||
updateCurrentSession,
|
||||
communityListsByAddress,
|
||||
searchGroupMeta,
|
||||
groupMetaSearch,
|
||||
groupMeta,
|
||||
} from "src/engine"
|
||||
|
||||
@ -34,7 +34,7 @@
|
||||
let limit = 20
|
||||
let element = null
|
||||
|
||||
$: otherGroupMeta = reject(userIsMember, $searchGroupMeta(q)).slice(0, limit)
|
||||
$: otherGroupMeta = reject(userIsMember, $groupMetaSearch.searchOptions(q)).slice(0, limit)
|
||||
|
||||
document.title = "Groups"
|
||||
|
||||
|
@ -15,8 +15,8 @@
|
||||
import GroupCircle from "src/app/shared/GroupCircle.svelte"
|
||||
import PersonSelect from "src/app/shared/PersonSelect.svelte"
|
||||
import {router} from "src/app/util/router"
|
||||
import {displayRelayUrl, displayGroupMeta} from "src/domain"
|
||||
import {hints, relaySearch, searchGroupMeta, groupMetaByAddress} from "src/engine"
|
||||
import {displayRelayUrl} from "src/domain"
|
||||
import {hints, relaySearch, groupMetaSearch, displayGroupByAddress} from "src/engine"
|
||||
|
||||
export let initialPubkey = null
|
||||
export let initialGroupAddress = null
|
||||
@ -70,8 +70,6 @@
|
||||
groups = toSpliced(groups, i, 1)
|
||||
}
|
||||
|
||||
const displayGroupFromAddress = a => displayGroupMeta($groupMetaByAddress.get(a))
|
||||
|
||||
let relayInput, groupInput
|
||||
let sections = []
|
||||
let pubkeys = []
|
||||
@ -185,7 +183,7 @@
|
||||
</p>
|
||||
{#each groups as group, i (group.address + i)}
|
||||
<ListItem on:remove={() => removeGroup(i)}>
|
||||
<span slot="label">{displayGroupFromAddress(group.address)}</span>
|
||||
<span slot="label">{displayGroupByAddress(group.address)}</span>
|
||||
<span slot="data">
|
||||
<Input bind:value={group.claim} placeholder="Invite code (optional)" />
|
||||
</span>
|
||||
@ -194,8 +192,8 @@
|
||||
<SearchSelect
|
||||
value={null}
|
||||
bind:this={groupInput}
|
||||
search={$searchGroupMeta}
|
||||
displayItem={displayGroupMeta}
|
||||
search={$groupMetaSearch.searchOptions}
|
||||
displayItem={$groupMetaSearch.displayOption}
|
||||
getKey={groupMeta => getAddress(groupMeta.event)}
|
||||
onChange={groupMeta => groupMeta && addGroup(getAddress(groupMeta.event))}>
|
||||
<i slot="before" class="fa fa-search" />
|
||||
|
@ -17,6 +17,7 @@ import {
|
||||
getIdFilters,
|
||||
isGroupAddress,
|
||||
isSignedEvent,
|
||||
createEvent,
|
||||
WRAP,
|
||||
WRAP_NIP04,
|
||||
EPOCH,
|
||||
@ -28,6 +29,7 @@ import {
|
||||
HANDLER_INFORMATION,
|
||||
HANDLER_RECOMMENDATION,
|
||||
} from "@welshman/util"
|
||||
import {makeDvmRequest} from "@welshman/dvm"
|
||||
import {updateIn, createBatcher} from "src/util/misc"
|
||||
import {giftWrapKinds, noteKinds, reactionKinds, repostKinds} from "src/util/nostr"
|
||||
import {always, partition, pluck, uniq, without} from "ramda"
|
||||
@ -38,7 +40,6 @@ import {
|
||||
getUserCircles,
|
||||
getGroupReqInfo,
|
||||
getCommunityReqInfo,
|
||||
dvmRequest,
|
||||
env,
|
||||
getFollows,
|
||||
getFilterSelections,
|
||||
@ -59,9 +60,9 @@ import {
|
||||
subscribe,
|
||||
subscribePersistent,
|
||||
dufflepud,
|
||||
signer,
|
||||
} from "src/engine/state"
|
||||
import {updateCurrentSession, updateSession} from "src/engine/commands"
|
||||
import {loadPubkeyRelays} from "src/engine/requests/pubkeys"
|
||||
|
||||
export * from "src/engine/requests/pubkeys"
|
||||
|
||||
@ -275,22 +276,22 @@ export const feedLoader = new FeedLoader<TrustedEvent>({
|
||||
}
|
||||
},
|
||||
requestDVM: async ({kind, onEvent, tags = [], ...request}) => {
|
||||
let relays
|
||||
if (request.relays?.length > 0) {
|
||||
relays = hints.fromRelays(request.relays).getUrls()
|
||||
} else {
|
||||
const pubkeys = tags.filter(nthEq(0, "p")).map(nth(1))
|
||||
tags = [...tags, ["expiration", String(now() + 5)]]
|
||||
|
||||
await loadPubkeyRelays(pubkeys)
|
||||
const req = makeDvmRequest({
|
||||
event: await signer.get().signAsUser(createEvent(kind, {tags})),
|
||||
relays:
|
||||
request.relays?.length > 0
|
||||
? hints.fromRelays(request.relays).getUrls()
|
||||
: hints.Messages(tags.filter(nthEq(0, "p")).map(nth(1))).getUrls(),
|
||||
})
|
||||
|
||||
relays = hints.Messages(pubkeys).getUrls()
|
||||
}
|
||||
|
||||
const event = await dvmRequest({kind, tags, relays})
|
||||
|
||||
if (event) {
|
||||
onEvent(event)
|
||||
}
|
||||
await new Promise<void>(resolve => {
|
||||
req.emitter.on("result", (url, event) => {
|
||||
onEvent(event)
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
},
|
||||
getPubkeysForScope: (scope: string) => {
|
||||
const $pubkey = pubkey.get()
|
||||
|
@ -54,7 +54,7 @@ const getFiltersForKey = (key: string, authors: string[]) => {
|
||||
case "pubkey/relays":
|
||||
return [{authors, kinds: [RELAYS, INBOX_RELAYS]}]
|
||||
case "pubkey/profile":
|
||||
return [{authors, kinds: [PROFILE, FOLLOWS, HANDLER_INFORMATION, COMMUNITIES]}]
|
||||
return [{authors, kinds: [PROFILE, FOLLOWS, MUTES, HANDLER_INFORMATION, COMMUNITIES]}]
|
||||
case "pubkey/user":
|
||||
return [
|
||||
{authors, kinds: [PROFILE, RELAYS, MUTES, FOLLOWS, COMMUNITIES, FEEDS]},
|
||||
|
@ -92,7 +92,7 @@ import {
|
||||
publish as basePublish,
|
||||
subscribe as baseSubscribe,
|
||||
} from "@welshman/net"
|
||||
import type {Publish, PublishRequest, SubscribeRequest} from "@welshman/net"
|
||||
import type {PublishRequest, SubscribeRequest} from "@welshman/net"
|
||||
import * as Content from "@welshman/content"
|
||||
import {fuzzy, synced, withGetter, pushToKey, tryJson, fromCsv, SearchHelper} from "src/util/misc"
|
||||
import {
|
||||
@ -108,6 +108,7 @@ import {
|
||||
} from "src/util/nostr"
|
||||
import logger from "src/util/logger"
|
||||
import type {
|
||||
GroupMeta,
|
||||
PublishedFeed,
|
||||
PublishedProfile,
|
||||
PublishedListFeed,
|
||||
@ -142,6 +143,7 @@ import {
|
||||
filterRelaysByNip,
|
||||
displayRelayUrl,
|
||||
readGroupMeta,
|
||||
displayGroupMeta,
|
||||
} from "src/domain"
|
||||
import type {
|
||||
Channel,
|
||||
@ -693,7 +695,32 @@ export const groupMetaByAddress = withGetter(
|
||||
export const deriveGroupMeta = (address: string) =>
|
||||
derived(groupMetaByAddress, $m => $m.get(address))
|
||||
|
||||
export const searchGroupMeta = derived(
|
||||
export const displayGroupByAddress = a => displayGroupMeta(groupMetaByAddress.get().get(a))
|
||||
|
||||
export class GroupSearch extends SearchHelper<GroupMeta & {score: number}, string> {
|
||||
config = {
|
||||
keys: [{name: "identifier", weight: 0.2}, "name", {name: "about", weight: 0.5}],
|
||||
threshold: 0.3,
|
||||
shouldSort: false,
|
||||
includeScore: true,
|
||||
}
|
||||
|
||||
getSearch = () => {
|
||||
const fuse = new Fuse(this.options, this.config)
|
||||
const sortFn = (r: any) => r.score - Math.pow(Math.max(0, r.item.score), 1 / 100)
|
||||
|
||||
return (term: string) =>
|
||||
term
|
||||
? sortBy(sortFn, fuse.search(term)).map((r: any) => r.item)
|
||||
: sortBy(meta => -meta.score, this.options)
|
||||
}
|
||||
|
||||
getValue = (option: GroupMeta) => getAddress(option.event)
|
||||
|
||||
displayValue = displayGroupByAddress
|
||||
}
|
||||
|
||||
export const groupMetaSearch = derived(
|
||||
[groupMeta, communityListsByAddress, userFollows],
|
||||
([$groupMeta, $communityListsByAddress, $userFollows]) => {
|
||||
const options = $groupMeta.map(meta => {
|
||||
@ -704,19 +731,7 @@ export const searchGroupMeta = derived(
|
||||
return {...meta, score: followedMembers.length}
|
||||
})
|
||||
|
||||
const fuse = new Fuse(options, {
|
||||
keys: [{name: "identifier", weight: 0.2}, "name", {name: "about", weight: 0.5}],
|
||||
threshold: 0.3,
|
||||
shouldSort: false,
|
||||
includeScore: true,
|
||||
})
|
||||
|
||||
const sortFn = (r: any) => r.score - Math.pow(Math.max(0, r.item.score), 1 / 100)
|
||||
|
||||
return (term: string) =>
|
||||
term
|
||||
? sortBy(sortFn, fuse.search(term)).map((r: any) => r.item)
|
||||
: sortBy(meta => -meta.score, options)
|
||||
return new GroupSearch(options)
|
||||
},
|
||||
)
|
||||
|
||||
@ -1975,75 +1990,6 @@ export const getClientTags = () => {
|
||||
return [tag]
|
||||
}
|
||||
|
||||
// DVMs
|
||||
|
||||
export type DVMRequestOpts = {
|
||||
kind: number
|
||||
input?: any
|
||||
inputOpts?: string[]
|
||||
tags?: string[][]
|
||||
relays?: string[]
|
||||
timeout?: number
|
||||
onPublish?: (pub: Publish) => void
|
||||
onProgress?: (e: TrustedEvent) => void
|
||||
sk?: string
|
||||
}
|
||||
|
||||
export const dvmRequest = async ({
|
||||
kind,
|
||||
tags = [],
|
||||
timeout = 30_000,
|
||||
relays = [],
|
||||
onPublish = null,
|
||||
onProgress = null,
|
||||
sk = null,
|
||||
}: DVMRequestOpts): Promise<TrustedEvent> => {
|
||||
if (!sk && !signer.get().isEnabled()) {
|
||||
sk = generatePrivateKey()
|
||||
}
|
||||
|
||||
if (relays.length === 0) {
|
||||
relays = hints.merge([hints.WriteRelays(), hints.fromRelays(env.get().DVM_RELAYS)]).getUrls()
|
||||
}
|
||||
|
||||
tags = tags.concat([["expiration", String(now() + seconds(1, "hour"))]])
|
||||
|
||||
const pub = await createAndPublish({kind, relays, sk, tags, forcePlatform: false})
|
||||
|
||||
onPublish?.(pub)
|
||||
|
||||
return new Promise(resolve => {
|
||||
const kinds = [kind + 1000]
|
||||
|
||||
if (onProgress) {
|
||||
kinds.push(7000)
|
||||
}
|
||||
|
||||
const sub = subscribe({
|
||||
relays,
|
||||
timeout,
|
||||
filters: [
|
||||
{
|
||||
kinds,
|
||||
since: now() - seconds(1, "minute"),
|
||||
"#e": [pub.request.event.id],
|
||||
},
|
||||
],
|
||||
onEvent: (e: TrustedEvent) => {
|
||||
if (e.kind === 7000) {
|
||||
onProgress?.(e)
|
||||
} else {
|
||||
resolve(e)
|
||||
sub.close()
|
||||
}
|
||||
},
|
||||
onComplete: () => {
|
||||
resolve(null)
|
||||
},
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// Thread
|
||||
|
||||
const getAncestorIds = e => {
|
||||
@ -2162,19 +2108,19 @@ class IndexedDBAdapter {
|
||||
const removedRecords = prev.filter(r => !currentIds.has(r[key]))
|
||||
|
||||
if (newRecords.length > 0) {
|
||||
console.log("putting", name, newRecords.length, current.length)
|
||||
await storage.bulkPut(name, newRecords)
|
||||
}
|
||||
|
||||
if (removedRecords.length > 0) {
|
||||
console.trace("deleting", name, removedRecords.length, current.length)
|
||||
if (name === "repository") {
|
||||
console.trace("deleting", removedRecords.length, current.length)
|
||||
}
|
||||
await storage.bulkDelete(name, removedRecords.map(prop(key)))
|
||||
}
|
||||
|
||||
// If we have much more than our limit, prune our store. This will get persisted
|
||||
// the next time around.
|
||||
if (current.length > limit * 1.5) {
|
||||
console.log("pruning", name, current.length)
|
||||
set((sort ? sort(current) : current).slice(0, limit))
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user