|
|
|
internal/templates/templates.go raw
| 1 | package templates | | 2 | | | 3 | import ( | | 4 | "encoding/json" | | 5 | "fmt" | | 6 | | | 7 | "github.com/FelineStateMachine/tinyrelay/internal/policy" | | 8 | ) | | 9 | | | 10 | // Template describes a built-in relay policy and its recommended connections. | | 11 | type Template struct { | | 12 | Name string | | 13 | Title string | | 14 | About string | | 15 | Source string | | 16 | EveryHours int | | 17 | Policy map[string]any | | 18 | AllowKinds []int | | 19 | BlockKinds []int | | 20 | RetentionDays int | | 21 | Connections []string | | 22 | } | | 23 | | | 24 | // Connection describes one client integration displayed for a relay. | | 25 | type Connection struct { | | 26 | Name string | | 27 | Title string | | 28 | About string | | 29 | App string | | 30 | Where string | | 31 | Icon string | | 32 | Feature string | | 33 | Visibility string | | 34 | QR string | | 35 | Inputs []Input | | 36 | Links []Link | | 37 | } | | 38 | | | 39 | // Input describes a value a connection asks the user to supply. | | 40 | type Input struct{ Name, Label, Placeholder, Default, Pattern string } | | 41 | | | 42 | // Link describes an external URL or copyable value for a connection. | | 43 | type Link struct{ Label, Href, Copy string } | | 44 | | | 45 | var templateCatalog = []Template{ | | 46 | {Name: "default" , Title: "Default" , About: "Anyone writes, anyone reads, every kind, kept forever. Feature settings stay as they are." , Policy: map[string]any{"writes" : "open" , "reads" : "open" , "directoryPublic" : true}}, | | 47 | {Name: "outbox" , Title: "Outbox" , About: "Only you write, anyone reads. Your public notes and articles, kept forever. Private kinds are refused." , Policy: map[string]any{"writes" : "owner" , "reads" : "open" , "directoryPublic" : true, "delivery" : map[string]any{"enabled" : true}}, BlockKinds: []int{4, 14, 1059}, Connections: []string{"notes" , "find-me" }}, | | 48 | {Name: "inbox" , Title: "Inbox" , About: "Anyone writes notes, replies, reactions and zaps meant for you; anyone reads. Everything is kept 90 days." , Policy: map[string]any{"writes" : "open" , "reads" : "open" , "directoryPublic" : true, "inbox" : map[string]any{"targeted" : true}}, AllowKinds: []int{0, 1, 6, 7, 16, 1111, 1984, 9735, 9802}, RetentionDays: 90, Connections: []string{"find-me" , "notes" }}, | | 49 | {Name: "private" , Title: "Private" , About: "Only you write, only members read. Drafts, wallets and app data, every kind, kept forever." , Policy: map[string]any{"writes" : "owner" , "reads" : "members" , "directoryPublic" : false}}, | | 50 | {Name: "chat" , Title: "Chat" , About: "Members write, members read. Private messages and the group's chat, kept forever. The directory is hidden." , Policy: map[string]any{"writes" : "allowlist" , "reads" : "members" , "directoryPublic" : false}, AllowKinds: []int{0, 7, 9, 11, 1059, 1111}, Connections: []string{"group" }}, | | 51 | {Name: "media" , Title: "Media" , About: "A media host. Members upload files; the only events accepted are profiles and Blossom server lists. Anyone reads." , Policy: map[string]any{"writes" : "allowlist" , "reads" : "open" , "directoryPublic" : true}, AllowKinds: []int{0, 10063}, Connections: []string{"files" , "photos" }}, | | 52 | {Name: "search" , Title: "Search replica" , About: "A read-only copy of another relay's notes, threads, comments, highlights, articles and wiki pages, refreshed every six hours. Anyone reads." , Source: "required" , EveryHours: 6, Policy: map[string]any{"writes" : "owner" , "reads" : "open" , "directoryPublic" : true}, AllowKinds: []int{0, 1, 11, 1111, 9802, 30023, 30818}, Connections: []string{"notes" }}, | | 53 | {Name: "articles" , Title: "Articles" , About: "Long-form articles and the profiles behind them, nothing else. Only you write, anyone reads. Give a source relay to mirror its articles daily." , Source: "optional" , EveryHours: 24, Policy: map[string]any{"writes" : "owner" , "reads" : "open" , "directoryPublic" : true}, AllowKinds: []int{0, 30023}, Connections: []string{"notes" }}, | | 54 | {Name: "social" , Title: "Social" , About: "A chronological social feed for members' notes, comments, reactions and long-form articles. Members write; anyone reads." , Policy: map[string]any{"writes" : "allowlist" , "reads" : "open" , "directoryPublic" : true}, AllowKinds: []int{0, 1, 5, 6, 7, 1111, 30023}, Connections: []string{"notes" , "blog" }}, | | 55 | {Name: "dm" , Title: "DM inbox" , About: "An inbox for private messages. Anyone drops gift wraps, only members read them. Nothing else is accepted." , Policy: map[string]any{"writes" : "open" , "reads" : "members" , "directoryPublic" : false}, AllowKinds: []int{0, 1059}, Connections: []string{"dm" }}, | | 56 | {Name: "quiet" , Title: "Quiet" , About: "A small private relay with nothing that costs or announces. Members write and read; optional features are off." , Policy: map[string]any{"writes" : "allowlist" , "reads" : "members" , "directoryPublic" : false, "features" : map[string]any{"search" : "off" , "sync" : false, "count" : false, "discovery" : false, "names" : false, "files" : false, "pages" : false, "signer" : false, "sites" : false, "marmot" : false, "grasp" : false}}, Connections: []string{}}, | | 57 | {Name: "site" , Title: "Sites" , About: "A static website host. Members publish manifests and files; the relay mirrors missing files into its own store." , Policy: map[string]any{"writes" : "allowlist" , "reads" : "open" , "directoryPublic" : true, "features" : map[string]any{"files" : true, "sites" : map[string]any{"enabled" : true, "mirror" : true}}}, Connections: []string{"notes" }}, | | 58 | {Name: "marmot" , Title: "Marmot" , About: "Public Marmot transport. Anyone publishes KeyPackages and opaque group messages; gift wraps keep their recipient checks." , Policy: map[string]any{"writes" : "open" , "reads" : "open" , "directoryPublic" : true, "features" : map[string]any{"marmot" : true}}, AllowKinds: []int{10002, 10050, 1059, 30443, 445}, Connections: []string{"marmot" }}, | | 59 | {Name: "grasp" , Title: "Git repositories" , About: "A GRASP Git host. Admitted members publish repository state and related events." , Policy: map[string]any{"writes" : "allowlist" , "reads" : "open" , "directoryPublic" : true, "features" : map[string]any{"grasp" : true}}, Connections: []string{"repos" , "notes" }}, | | 60 | {Name: "marmot-members" , Title: "Marmot members" , About: "Members publish Marmot KeyPackages and authenticate group message writes. Encrypted transport stays publicly readable." , Policy: map[string]any{"writes" : "allowlist" , "reads" : "open" , "directoryPublic" : true, "features" : map[string]any{"marmot" : true}}, AllowKinds: []int{10002, 10050, 1059, 30443, 445}, Connections: []string{"marmot" }}, | | 61 | {Name: "home" , Title: "Home" , About: "Your relay as your home on nostr. Members write, anyone reads; sites, files and Git hosting on." , Policy: map[string]any{"writes" : "allowlist" , "reads" : "open" , "directoryPublic" : true, "features" : map[string]any{"files" : true, "sites" : map[string]any{"enabled" : true, "mirror" : true}, "grasp" : true}}, Connections: []string{"notes" , "repos" , "photos" , "find-me" , "group" }}, | | 62 | } | | 63 | | | 64 | var connectionCatalog = []Connection{ | | 65 | {Name: "notes" , Title: "Notes" , About: "Everything posted on this relay as one feed." , App: "Jumble" , Where: "web" , Icon: "notes" , Visibility: "public" , Links: []Link{{Label: "Open" , Href: "https://jumble.social/?r={relay:url|enc}" }}}, | | 66 | {Name: "find-me" , Title: "Find me here" , About: "The owner's profile with this relay attached, for feed apps." , App: "Primal" , Where: "web, phone" , Icon: "person" , Visibility: "public" , QR: "nostr:{owner:nprofile}" , Links: []Link{{Label: "Open" , Href: "https://primal.net/p/{owner:nprofile}" }, {Label: "Open in app" , Href: "nostr:{owner:nprofile}" }}}, | | 67 | {Name: "group" , Title: "Group" , About: "The relay as a space, with its group as a room." , App: "Flotilla" , Where: "web, phone" , Icon: "chat" , Visibility: "public" , QR: "nostr:{relay:naddr}" , Links: []Link{{Label: "Open" , Href: "https://app.flotilla.social/spaces/{relay:host|enc}" }, {Label: "Open group in app" , Href: "nostr:{relay:naddr}" }, {Label: "Copy naddr" , Copy: "{relay:naddr}" }}}, | | 68 | {Name: "blog" , Title: "Blog" , About: "The owner's articles, with this relay attached so a reader finds the rest of them." , App: "YakiHonne" , Where: "web, phone" , Icon: "blog" , Visibility: "public" , QR: "nostr:{owner:nprofile}" , Links: []Link{{Label: "Open" , Href: "https://yakihonne.com/profile/{owner:nprofile}" }, {Label: "Open in app" , Href: "nostr:{owner:nprofile}" }}}, | | 69 | {Name: "repos" , Title: "Repos" , About: "This relay's Git repositories, and the clone command for yours." , App: "GitWorkshop" , Where: "web, terminal" , Icon: "git" , Feature: "grasp" , Visibility: "public" , Inputs: []Input{{Name: "repo" , Label: "Repository name" , Placeholder: "my-project" , Default: "<repo>" , Pattern: "^[A-Za-z0-9][A-Za-z0-9._\\-]{0,63}$" }}, Links: []Link{{Label: "Open" , Href: "https://gitworkshop.dev/relay/{relay:host|enc}" }, {Label: "Copy clone command" , Copy: "git clone '{relay:web}/{user:npub}/{input:repo}.git'" }}}, | | 70 | {Name: "sites" , Title: "Sites" , About: "Static sites this relay hosts, and the command that publishes one here." , App: "nsyte" , Where: "web, terminal" , Icon: "site" , Feature: "sites" , Visibility: "public" , Links: []Link{{Label: "Open the owner's site" , Href: "https://{owner:npub}.{relay:domain}" }, {Label: "Open your site" , Href: "https://{user:npub}.{relay:domain}" }, {Label: "Copy publish command" , Copy: "nsyte deploy ./dist --relays {relay:url} --servers {relay:web}" }}}, | | 71 | {Name: "bookmarks" , Title: "Bookmarks" , About: "Your bookmarks and lists in a list manager; add this relay in its settings so they land here." , App: "Listr" , Where: "web" , Icon: "bookmark" , Visibility: "auth" , Links: []Link{{Label: "Open" , Href: "https://listr.lol/{user:npub}" }, {Label: "Open in noStrudel" , Href: "https://nostrudel.ninja/bookmarks/{user:nprofile}" }}}, | | 72 | {Name: "photos" , Title: "Photo library" , About: "The owner's photos on this relay's file store." , App: "bouquet" , Where: "web" , Icon: "photo" , Feature: "files" , Visibility: "owner" , Links: []Link{{Label: "Open" , Href: "https://bouquet.slidestr.net/" }, {Label: "Copy Blossom server URL" , Copy: "{relay:web}" }}}, | | 73 | {Name: "files" , Title: "Files" , About: "This relay's file store, for members, in a media manager." , App: "bouquet" , Where: "web" , Icon: "files" , Feature: "files" , Visibility: "members" , Links: []Link{{Label: "Open" , Href: "https://bouquet.slidestr.net/" }, {Label: "Copy Blossom server URL" , Copy: "{relay:web}" }, {Label: "Copy NIP-96 URL" , Copy: "{relay:web}/.well-known/nostr/nip96.json" }}}, | | 74 | {Name: "dm" , Title: "Messages" , About: "Message the owner privately; put this relay in your DM inbox list." , App: "0xchat" , Where: "phone, desktop" , Icon: "chat" , Visibility: "public" , QR: "nostr:{owner:nprofile}" , Links: []Link{{Label: "Open in app" , Href: "nostr:{owner:nprofile}" }}}, | | 75 | {Name: "marmot" , Title: "Encrypted groups" , About: "End-to-end encrypted group chat over this relay." , App: "White Noise" , Where: "phone" , Icon: "lock" , Feature: "marmot" , Visibility: "public" , Links: []Link{{Label: "Open" , Href: "https://whitenoise.chat/" }, {Label: "Get the app" , Href: "https://whitenoise.chat/download" }}}, | | 76 | {Name: "relay-page" , Title: "Relay page" , About: "This relay's page in Coracle: its feed and its people." , App: "Coracle" , Where: "web" , Icon: "feed" , Visibility: "public" , Links: []Link{{Label: "Open" , Href: "https://coracle.social/relays/{relay:host|enc}" }}}, | | 77 | } | | 78 | | | 79 | func Names() []string { | | 80 | out := make([]string, 0, len(templateCatalog)) | | 81 | for _, t := range templateCatalog { | | 82 | out = append(out, t.Name) | | 83 | } | | 84 | return out | | 85 | } | | 86 | | | 87 | // Connections returns the built-in connection catalog in display order. | | 88 | func Connections() []Connection { | | 89 | out := make([]Connection, len(connectionCatalog)) | | 90 | for i, c := range connectionCatalog { | | 91 | out[i] = cloneConnection(c) | | 92 | } | | 93 | return out | | 94 | } | | 95 | | | 96 | // Find returns a named template and a boolean indicating whether it exists. | | 97 | func Find(name string) (Template, bool) { | | 98 | for _, t := range templateCatalog { | | 99 | if t.Name == name { | | 100 | return cloneTemplate(t), true | | 101 | } | | 102 | } | | 103 | return Template{}, false | | 104 | } | | 105 | | | 106 | func cloneTemplate(t Template) Template { | | 107 | t.Policy = cloneMap(t.Policy) | | 108 | t.AllowKinds = append([]int(nil), t.AllowKinds...) | | 109 | t.BlockKinds = append([]int(nil), t.BlockKinds...) | | 110 | t.Connections = append([]string(nil), t.Connections...) | | 111 | return t | | 112 | } | | 113 | | | 114 | func cloneConnection(c Connection) Connection { | | 115 | c.Inputs = append([]Input(nil), c.Inputs...) | | 116 | c.Links = append([]Link(nil), c.Links...) | | 117 | return c | | 118 | } | | 119 | | | 120 | func cloneMap(in map[string]any) map[string]any { | | 121 | out := make(map[string]any, len(in)) | | 122 | for k, v := range in { | | 123 | if nested, ok := v.(map[string]any); ok { | | 124 | out[k] = cloneMap(nested) | | 125 | } else { | | 126 | out[k] = v | | 127 | } | | 128 | } | | 129 | return out | | 130 | } | | 131 | | | 132 | func ApplyTemplate(name, owner string) (policy.Policy, error) { | | 133 | t, ok := Find(name) | | 134 | if !ok { | | 135 | return policy.Policy{}, fmt.Errorf("template %q not found" , name) | | 136 | } | | 137 | p := policy.Defaults(owner) | | 138 | b, err := jsonMarshal(t.Policy) | | 139 | if err != nil { | | 140 | return policy.Policy{}, err | | 141 | } | | 142 | p, err = policy.Patch(p, b) | | 143 | if err != nil { | | 144 | return policy.Policy{}, fmt.Errorf("template %q policy: %w" , name, err) | | 145 | } | | 146 | if len(t.AllowKinds) > 0 { | | 147 | p.AllowedKinds = append([]int(nil), t.AllowKinds...) | | 148 | } | | 149 | if len(t.BlockKinds) > 0 { | | 150 | p.BlockedKinds = append([]int(nil), t.BlockKinds...) | | 151 | } | | 152 | if t.RetentionDays > 0 { | | 153 | p.Retention = []policy.RetentionRule{{Kind: -1, Days: t.RetentionDays}} | | 154 | } | | 155 | return p, nil | | 156 | } | | 157 | | | 158 | func jsonMarshal(v map[string]any) (map[string]json.RawMessage, error) { | | 159 | result := make(map[string]json.RawMessage, len(v)) | | 160 | for k, value := range v { | | 161 | b, err := json.Marshal(value) | | 162 | if err != nil { | | 163 | return nil, fmt.Errorf("%s: %w" , k, err) | | 164 | } | | 165 | result[k] = b | | 166 | } | | 167 | return result, nil | | 168 | } |
|
|
About| owner | c01503e8035f |
|---|
| visibility | public |
|---|
| head | d730679639bf |
|---|
Maintainers- c01503e8035f owner
- ba1b5beed5b5 agent hermes
Clonehttps://012.run/npub1cq2s86qrtadf36eqq3epm38wmhdynrrcwxf84eldk3f94dkv7ttsrulrz5/tinyrelay.git
|