Lasa is a stateless proxy that generates a RSS or an Atom feed from a Standard.site publication. lasa.anhgelus.world
rss atom atprotocol standard-site atproto
2
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat(cli): display publications

+159 -17
+16
client.go
··· 1 + package lasa 2 + 3 + import ( 4 + "net" 5 + "net/http" 6 + "time" 7 + 8 + "github.com/valkey-io/valkey-go" 9 + "tangled.org/anhgelus.world/xrpc" 10 + "tangled.org/anhgelus.world/xrpc/atproto" 11 + ) 12 + 13 + func NewClient(client *http.Client, resolver *net.Resolver, cache valkey.Client, dur time.Duration) xrpc.Client { 14 + dir := NewDirectory(atproto.NewDirectory(client, resolver), cache, dur) 15 + return xrpc.NewClient(client, dir) 16 + }
+36
cmd/internal/print.go
··· 1 1 package internal 2 2 3 3 import ( 4 + "context" 4 5 "flag" 5 6 "fmt" 6 7 "os" 7 8 "text/tabwriter" 9 + 10 + site "tangled.org/anhgelus.world/goat-site" 11 + "tangled.org/anhgelus.world/xrpc" 12 + "tangled.org/anhgelus.world/xrpc/atproto" 8 13 ) 9 14 10 15 func Usage(syntax, usage string, commands []Command, flags *flag.FlagSet, examples []string) { ··· 35 40 panic(err) 36 41 } 37 42 } 43 + 44 + func DisplayPublication( 45 + ctx context.Context, 46 + client xrpc.Client, 47 + did *atproto.DID, 48 + raw atproto.RawURI, 49 + pub *site.Publication, 50 + ) { 51 + uri, err := raw.URI(context.Background(), client.Directory()) 52 + if err != nil { 53 + panic(err) 54 + } 55 + w := tabwriter.NewWriter(os.Stdout, 0, 2, 1, ' ', 0) 56 + fmt.Fprintln(w, "Name:\t", pub.Name) 57 + fmt.Fprintln(w, "URL:\t", pub.URL.String()) 58 + fmt.Fprintln(w, "AT URL:\t", raw) 59 + fmt.Fprintln(w, "RecordKey:\t", *uri.RecordKey()) 60 + if pub.Description != nil { 61 + fmt.Fprintln(w, "Description:\t", *pub.Description) 62 + } 63 + ok, err := pub.Verify(context.Background(), client.HTTP(), did, *uri.RecordKey()) 64 + if err != nil { 65 + fmt.Fprintln(w, "Verification:\t error:", err) 66 + return 67 + } 68 + fmt.Fprintf(w, "Verification:\t %v\n", ok) 69 + err = w.Flush() 70 + if err != nil { 71 + panic(err) 72 + } 73 + }
+7 -17
cmd/lasa/main.go
··· 2 2 3 3 import ( 4 4 "flag" 5 + "net" 6 + "net/http" 5 7 8 + "tangled.org/anhgelus.world/lasa" 6 9 "tangled.org/anhgelus.world/lasa/cmd/internal" 10 + "tangled.org/anhgelus.world/xrpc" 7 11 ) 8 12 9 13 var ( ··· 17 21 var commands = []internal.Command{ 18 22 {Name: "publication", Usage: "works with publication", Callback: handlePublication}, 19 23 } 24 + 25 + var client xrpc.Client 20 26 21 27 func main() { 22 28 flag.Parse() ··· 25 31 handleHelp() 26 32 return 27 33 } 34 + client = lasa.NewClient(http.DefaultClient, net.DefaultResolver, nil, 0) 28 35 command := args[0] 29 36 var next []string 30 37 if len(args) > 1 { ··· 50 57 }, 51 58 ) 52 59 } 53 - 54 - func handlePublication(args []string) { 55 - if len(args) == 0 { 56 - internal.Usage( 57 - `lasa publication <identifier> [rkey]`, 58 - `List publications of identifier (can be a DID or an Handle) or display a specific publication referenced by its rkey`, 59 - nil, 60 - nil, 61 - []string{ 62 - "lasa publication anhgelus.world\t-\tdisplay publications of anhgelus.world", 63 - "lasa publication did:plc:123\t-\tdisplay publications of did:plc:123", 64 - "lasa publication did:web:example.org fooBar\t-\tdisplay publication of did:web:example.org referenced by fooBar", 65 - }, 66 - ) 67 - return 68 - } 69 - }
+69
cmd/lasa/publication.go
··· 1 + package main 2 + 3 + import ( 4 + "context" 5 + "fmt" 6 + 7 + site "tangled.org/anhgelus.world/goat-site" 8 + "tangled.org/anhgelus.world/lasa" 9 + "tangled.org/anhgelus.world/lasa/cmd/internal" 10 + "tangled.org/anhgelus.world/xrpc" 11 + "tangled.org/anhgelus.world/xrpc/atproto" 12 + ) 13 + 14 + func handlePublicationUsage() { 15 + internal.Usage( 16 + `lasa publication <identifier> [rkey]`, 17 + `List publications of identifier (can be a DID or an Handle) or display a specific publication referenced by its rkey`, 18 + nil, 19 + nil, 20 + []string{ 21 + "lasa publication anhgelus.world\t-\tdisplay publications of anhgelus.world", 22 + "lasa publication did:plc:123\t-\tdisplay publications of did:plc:123", 23 + "lasa publication did:web:example.org fooBar\t-\tdisplay publication of did:web:example.org referenced by fooBar", 24 + }, 25 + ) 26 + } 27 + 28 + func handlePublication(args []string) { 29 + if len(args) == 0 { 30 + handlePublicationUsage() 31 + return 32 + } 33 + did, err := lasa.Resolve(context.Background(), client.Directory(), args[0]) 34 + if err != nil { 35 + panic(err) 36 + } 37 + if len(args) > 1 { 38 + handlePublicationSpecific(did, args[1:]) 39 + return 40 + } 41 + pubs, _, err := xrpc.ListRecords[*site.Publication](context.Background(), client, did, 0, "", false) 42 + if err != nil { 43 + panic(err) 44 + } 45 + if len(pubs) == 0 { 46 + fmt.Println("No publication found for", args[0]) 47 + return 48 + } 49 + for _, pub := range pubs { 50 + internal.DisplayPublication(context.Background(), client, did, pub.URI, pub.Value) 51 + fmt.Println() 52 + } 53 + } 54 + 55 + func handlePublicationSpecific(did *atproto.DID, args []string) { 56 + if len(args) != 1 { 57 + handlePublicationUsage() 58 + return 59 + } 60 + rkey, err := atproto.ParseRecordKey(args[0]) 61 + if err != nil { 62 + return 63 + } 64 + pub, err := xrpc.GetRecord[*site.Publication](context.Background(), client, did, rkey, nil) 65 + if err != nil { 66 + panic(err) 67 + } 68 + internal.DisplayPublication(context.Background(), client, did, pub.URI, pub.Value) 69 + }
+1
go.mod
··· 5 5 require ( 6 6 github.com/BurntSushi/toml v1.6.0 7 7 github.com/valkey-io/valkey-go v1.0.73 8 + tangled.org/anhgelus.world/goat-site v0.1.0 8 9 tangled.org/anhgelus.world/xrpc v0.1.0 9 10 ) 10 11
+2
go.sum
··· 16 16 golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA= 17 17 pgregory.net/rapid v1.2.0 h1:keKAYRcjm+e1F0oAuU5F5+YPAWcyxNNRK2wud503Gnk= 18 18 pgregory.net/rapid v1.2.0/go.mod h1:PY5XlDGj0+V1FCq0o192FdRhpKHGTRIWBgqjDBTrq04= 19 + tangled.org/anhgelus.world/goat-site v0.1.0 h1:8u6ti+f0/h/Z+Qin6qAQ/QvpnbslV0UwP+F9IzGL1wc= 20 + tangled.org/anhgelus.world/goat-site v0.1.0/go.mod h1:/v73uH2lakbKcrnIhZt3/ErStdLt9rXjOTLUvQJp2dc= 19 21 tangled.org/anhgelus.world/xrpc v0.1.0 h1:IFPgCf2c5j6c5IJmUx/Jid0MZdYJRxlxYAoLMF+nMRs= 20 22 tangled.org/anhgelus.world/xrpc v0.1.0/go.mod h1:DW43uo9DKZHVN9fiH6lAYVQ+0cfSLoceo7aE5lE1jjw=
+28
resolver.go
··· 1 + package lasa 2 + 3 + import ( 4 + "context" 5 + "errors" 6 + "strings" 7 + 8 + "tangled.org/anhgelus.world/xrpc/atproto" 9 + ) 10 + 11 + func Resolve(ctx context.Context, dir atproto.Directory, arg string) (did *atproto.DID, err error) { 12 + if strings.HasPrefix(arg, "did:") { 13 + did, err = atproto.ParseDID(arg) 14 + if err == nil { 15 + return 16 + } 17 + } 18 + handle, e := atproto.ParseHandle(arg) 19 + if err == nil { 20 + err = e 21 + } else { 22 + err = errors.Join(err, e) 23 + } 24 + if err != nil { 25 + return 26 + } 27 + return handle.DID(ctx, dir) 28 + }