package adapter import ( "encoding/json" "fmt" "io" "net/http" "os" "strings" "time" "forge.lightcrystal.systems/lightcrystal/underbbs/models" ) type anonAPAdapter struct { data *chan models.SocketData server string client http.Client protocol string nickname string } type apLink struct { Href string Rel string Type string } type apAttachment struct { MediaType string Type string Name string Summary string Url string } type apTag struct { Href string Name string Type string } type apIcon struct { MediaType string Type string Url string } type apActor struct { Icon apIcon Id string Name string PreferredUsername string Summary string Url string } type apActivity struct { Id string Content string AttributedTo string Context string Conversation string Published string Tag []apTag Attachment []apAttachment To string Url string Actor *string Object *string InReplyTo *string } type webFinger struct { Links []apLink } func (self *anonAPAdapter) Init(data *chan models.SocketData, server, protocol, nickname string) error { self.data = data self.server = server self.nickname = nickname self.protocol = protocol return nil } func getBodyJson(res *http.Response) []byte { l := res.ContentLength jsonData := make([]byte, l) res.Body.Read(jsonData) return jsonData } func (self *anonAPAdapter) makeApRequest(method, url string, data io.Reader) (*http.Response, error) { req, err := http.NewRequest(method, url, data) if err != nil { return nil, err } req.Header.Set("Accept", "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"") return self.client.Do(req) } func (self *anonAPAdapter) toMsg(activity apActivity) *models.Message { t, err := time.Parse(time.RFC3339, activity.Published) if err != nil { t = time.Now() } vis := strings.Split(activity.To, "#") if len(vis) > 1 { activity.To = vis[1] } m := &models.Message{ Datagram: models.Datagram{ Id: activity.Id, Uri: activity.Url, Type: "message", Created: t.UnixMilli(), Updated: nil, Protocol: self.protocol, Adapter: self.nickname, }, Author: activity.AttributedTo, Content: activity.Content, ReplyTo: activity.InReplyTo, Visibility: activity.To, } for _, a := range activity.Attachment { m.Attachments = append(m.Attachments, models.Attachment{ Src: a.Url, Desc: a.Summary, }) } return m } func (self *anonAPAdapter) send(data models.SocketData) { if self.data != nil { *self.data <- data } else { fmt.Fprintln(os.Stderr, string(data.ToDatagram())) } } func (self *anonAPAdapter) toAuthor(actor apActor) *models.Author { curtime := time.Now().UnixMilli() a := &models.Author{ Datagram: models.Datagram{ Id: actor.Id, Uri: actor.Url, Type: "author", Created: curtime, Updated: &curtime, Protocol: self.protocol, Adapter: self.nickname, }, Name: actor.PreferredUsername, ProfileData: actor.Summary, ProfilePic: actor.Icon.Url, } return a } func (self *anonAPAdapter) Fetch(etype string, ids []string) error { for _, id := range ids { switch etype { case "author": // webfinger lookup on id if string([]byte{id[0]}) == "@" { id = id[1:] } res, err := http.Get(self.server + "/.well-known/webfinger?resource=acct:" + id) if err != nil { return err } data := getBodyJson(res) wf := webFinger{} json.Unmarshal(data, &wf) var profile string for _, l := range wf.Links { if l.Rel == "self" { profile = l.Href break } } res, err = self.makeApRequest("GET", profile, nil) if err != nil { return err } authorData := getBodyJson(res) actor := apActor{} json.Unmarshal(authorData, &actor) author := self.toAuthor(actor) if author != nil { self.send(author) } case "byAuthor": // get outbox // for each item in outbox, check if it's a Create/Update or an Announce // Create/Update you can directly deserialize the object // if it's an Announce, try to get the object and deserialize it, build a boost out of it case "message": res, err := self.makeApRequest("GET", id, nil) if err != nil { return err } activityData := getBodyJson(res) activity := apActivity{} json.Unmarshal(activityData, &activity) message := self.toMsg(activity) if message != nil { self.send(message) } case "children": case "convoy": default: break } } return nil }