Live video on the AT Protocol
1package oproxy
2
3import (
4 "context"
5 "encoding/json"
6 "fmt"
7 "io"
8 "net"
9 "net/http"
10 "strings"
11
12 "github.com/bluesky-social/indigo/atproto/syntax"
13)
14
15// mostly borrowed from github.com/haileyok/atproto-oauth-golang, MIT license
16func ResolveHandle(ctx context.Context, handle string) (string, error) {
17 var did string
18
19 _, err := syntax.ParseHandle(handle)
20 if err != nil {
21 return "", err
22 }
23
24 recs, err := net.LookupTXT(fmt.Sprintf("_atproto.%s", handle))
25 if err == nil {
26 for _, rec := range recs {
27 if strings.HasPrefix(rec, "did=") {
28 did = strings.Split(rec, "did=")[1]
29 break
30 }
31 }
32 }
33
34 if did == "" {
35 req, err := http.NewRequestWithContext(
36 ctx,
37 "GET",
38 fmt.Sprintf("https://%s/.well-known/atproto-did", handle),
39 nil,
40 )
41 if err != nil {
42 return "", err
43 }
44
45 resp, err := http.DefaultClient.Do(req)
46 if err != nil {
47 return "", err
48 }
49 defer resp.Body.Close()
50
51 if resp.StatusCode != http.StatusOK {
52 io.Copy(io.Discard, resp.Body)
53 return "", fmt.Errorf("unable to resolve handle")
54 }
55
56 b, err := io.ReadAll(resp.Body)
57 if err != nil {
58 return "", err
59 }
60
61 maybeDid := string(b)
62
63 if _, err := syntax.ParseDID(maybeDid); err != nil {
64 return "", fmt.Errorf("unable to resolve handle")
65 }
66
67 did = maybeDid
68 }
69
70 return did, nil
71}
72
73func ResolveService(ctx context.Context, did string) (string, error) {
74 type Identity struct {
75 Service []struct {
76 ID string `json:"id"`
77 Type string `json:"type"`
78 ServiceEndpoint string `json:"serviceEndpoint"`
79 } `json:"service"`
80 }
81
82 var ustr string
83 if strings.HasPrefix(did, "did:plc:") {
84 ustr = fmt.Sprintf("https://plc.directory/%s", did)
85 } else if strings.HasPrefix(did, "did:web:") {
86 ustr = fmt.Sprintf("https://%s/.well-known/did.json", strings.TrimPrefix(did, "did:web:"))
87 } else {
88 return "", fmt.Errorf("did was not a supported did type")
89 }
90
91 req, err := http.NewRequestWithContext(ctx, "GET", ustr, nil)
92 if err != nil {
93 return "", err
94 }
95
96 resp, err := http.DefaultClient.Do(req)
97 if err != nil {
98 return "", err
99 }
100 defer resp.Body.Close()
101
102 if resp.StatusCode != 200 {
103 io.Copy(io.Discard, resp.Body)
104 return "", fmt.Errorf("could not find identity in plc registry")
105 }
106
107 var identity Identity
108 if err := json.NewDecoder(resp.Body).Decode(&identity); err != nil {
109 return "", err
110 }
111
112 var service string
113 for _, svc := range identity.Service {
114 if svc.ID == "#atproto_pds" {
115 service = svc.ServiceEndpoint
116 }
117 }
118
119 if service == "" {
120 return "", fmt.Errorf("could not find atproto_pds service in identity services")
121 }
122
123 return service, nil
124}