diff --git a/adapter/adapter.go b/adapter/adapter.go index a808970..4b93231 100644 --- a/adapter/adapter.go +++ b/adapter/adapter.go @@ -6,8 +6,9 @@ import ( type Adapter interface { Init(Settings, chan SocketData) error + Name() string Subscribe(string) []error - Fetch(string) error + Fetch(string, string) error Do(string) error DefaultSubscriptionFilter() string } diff --git a/adapter/mastodon.go b/adapter/mastodon.go index 608dcac..1d0d16f 100644 --- a/adapter/mastodon.go +++ b/adapter/mastodon.go @@ -21,6 +21,11 @@ type MastoAdapter struct { var scopes = []string{"read", "write", "follow"} +func (self *MastoAdapter) Name() string { + return self.nickname +} + + func (self *MastoAdapter) Init(settings Settings, data chan SocketData) error { self.nickname = settings.Nickname self.server = *settings.Server @@ -86,7 +91,7 @@ func (self *MastoAdapter) Subscribe(filter string) []error { return nil } -func (self *MastoAdapter) Fetch(query string) error { +func (self *MastoAdapter) Fetch(etype string, id string) error { return nil } diff --git a/adapter/misskey.go b/adapter/misskey.go index 0ff117b..a8c5be7 100644 --- a/adapter/misskey.go +++ b/adapter/misskey.go @@ -7,8 +7,9 @@ import ( mkcore "github.com/yitsushi/go-misskey/core" mkm "github.com/yitsushi/go-misskey/models" n "github.com/yitsushi/go-misskey/services/notes" + users "github.com/yitsushi/go-misskey/services/users" tl "github.com/yitsushi/go-misskey/services/notes/timeline" - _ "strings" + "strings" "time" ) @@ -28,6 +29,10 @@ type MisskeyAdapter struct { stop chan bool } +func (self *MisskeyAdapter) Name() string { + return self.nickname +} + func (self *MisskeyAdapter) Init(settings Settings, data chan SocketData) error { fmt.Println("initializing misskey adapter") @@ -121,13 +126,13 @@ func (self *MisskeyAdapter) poll() { // if anything is newer or as of yet not in the cache, add it // and convert it to a SocketData implementation before sending on data channel for _, n := range notes { - msg := self.cacheAndConvert(n) + msg := self.toMessageIfNew(n) if msg != nil { self.data <- msg } } for _, n := range mentions { - msg := self.cacheAndConvert(n) + msg := self.toMessageIfNew(n) if msg != nil { self.data <- msg } @@ -145,7 +150,7 @@ func (self *MisskeyAdapter) poll() { fmt.Println(err.Error()) } for _, n := range notes { - msg := self.cacheAndConvert(n) + msg := self.toMessageIfNew(n) if msg == nil { latest = &probenote[0].CreatedAt break @@ -171,9 +176,21 @@ func (self *MisskeyAdapter) isNew(n mkm.Note) bool { return !exists || timestamp.Before(n.CreatedAt) } -func (self *MisskeyAdapter) cacheAndConvert(n mkm.Note) *Message { +func (self *MisskeyAdapter) toMessageIfNew(n mkm.Note) *Message { + return self.toMessage(n, false); +} + +func (self *MisskeyAdapter) toMessage(n mkm.Note, bustCache bool) *Message { timestamp, exists := self.cache[n.ID] - if !exists || timestamp.Before(n.CreatedAt) { + if bustCache || !exists || timestamp.Before(n.CreatedAt) { + host := mkcore.StringValue(n.User.Host) + authorId := "" + if host != "" { + authorId = fmt.Sprintf("@%s@%s", n.User.Username, host) + } else { + authorId = fmt.Sprintf("@%s", n.User.Username) + } + self.cache[n.ID] = n.CreatedAt msg := Message{ Datagram: Datagram{ @@ -186,7 +203,7 @@ func (self *MisskeyAdapter) cacheAndConvert(n mkm.Note) *Message { Created: n.CreatedAt, - Author: fmt.Sprintf("%s@%s", n.User.Username, mkcore.StringValue(n.User.Host)), + Author: authorId, Content: n.Text, Attachments: []Attachment{}, Visibility: n.Visibility, @@ -209,7 +226,103 @@ func (self *MisskeyAdapter) cacheAndConvert(n mkm.Note) *Message { return nil } -func (self *MisskeyAdapter) Fetch(query string) error { +func (self *MisskeyAdapter) toAuthor(usr mkm.User) *Author { + fmt.Println("converting author: " + usr.ID) + host := mkcore.StringValue(usr.Host) + authorId := "" + if host != "" { + authorId = fmt.Sprintf("@%s@%s", usr.Username, host) + } else { + authorId = fmt.Sprintf("@%s", usr.Username) + } + + author := Author{ + Datagram: Datagram { + Id: authorId, + Uri: mkcore.StringValue(usr.URL), + Protocol: "misskey", + Adapter: self.nickname, + Type: "author", + }, + Name: usr.Name, + ProfilePic: usr.AvatarURL, + ProfileData: usr.Description, + } + + return &author +} + +func (self *MisskeyAdapter) Fetch(etype, id string) error { + switch (etype) { + case "message": + data, err := self.mk.Notes().Show(id) + if err != nil { + return err + } else { + msg := self.toMessage(data, true) + if msg != nil { + self.data <- msg + } + } + case "children": + data, err := self.mk.Notes().Children(n.ChildrenRequest{ + NoteID: id, + Limit: 100, + }) + if err != nil { + return err + } else { + for _, n := range data { + msg := self.toMessage(n, true) + if msg != nil { + self.data <- msg + } + } + } + case "convoy": + data, err := self.mk.Notes().Conversation(n.ConversationRequest{ + NoteID: id, + Limit: 100, + }) + if err != nil { + return err + } else { + for _, n := range data { + msg := self.toMessage(n, true) + if msg != nil{ + self.data <- msg + } + } + } + case "author": + user := "" + host := "" + idParts := strings.Split(id, "@") + user = idParts[0] + if len(idParts) == 2 { + host = idParts[1] + } + + var hostPtr *string = nil + if len(host) > 0 { + hostPtr = &host + } + + // fmt.Printf("attempting user resolution: @%s@%s\n", user, host) + data, err := self.mk.Users().Show(users.ShowRequest{ + Username: &user, + Host: hostPtr, + }) + if err != nil { + return err + } else { + a := self.toAuthor(data) + if a != nil { + self.data <- a + } + } + + } return nil } diff --git a/adapter/nostr.go b/adapter/nostr.go index 38be7af..9d0e6e8 100644 --- a/adapter/nostr.go +++ b/adapter/nostr.go @@ -17,6 +17,10 @@ type NostrAdapter struct { relays []*nostr.Relay } +func (self *NostrAdapter) Name() string { + return self.nickname +} + func (self *NostrAdapter) Init(settings Settings, data chan SocketData) error { self.nickname = settings.Nickname self.privkey = *settings.PrivKey @@ -75,7 +79,7 @@ func (self *NostrAdapter) Subscribe(filter string) []error { return nil } -func (self *NostrAdapter) Fetch(query string) error { +func (self *NostrAdapter) Fetch(etype, id string) error { return nil } diff --git a/models/msg.go b/models/msg.go index 8872413..74a94e6 100644 --- a/models/msg.go +++ b/models/msg.go @@ -55,3 +55,11 @@ func (self Message) ToDatagram() []byte { } return data } + +func (self Author) ToDatagram() []byte { + data, err := json.Marshal(self) + if err != nil { + panic(err.Error()) + } + return data +} diff --git a/server/api.go b/server/api.go index 7cfb067..a3952c9 100644 --- a/server/api.go +++ b/server/api.go @@ -147,17 +147,44 @@ func ProtectWithSubscriberKey(next http.Handler, subscribers map[*Subscriber][]a authHeader := req.Header.Get("Authorization") if strings.HasPrefix(authHeader, "Bearer ") { subscriberKey := strings.Split(authHeader, "Bearer ")[1] - for s, _ := range subscribers { - if s.key == subscriberKey { - next.ServeHTTP(w, req) - return - } + if getSubscriberByKey(subscriberKey, subscribers) != nil { + next.ServeHTTP(w, req); + return } } w.WriteHeader(http.StatusUnauthorized) }) } +func apiAdapterFetch(next http.Handler, subscribers map[*Subscriber][]adapter.Adapter) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + authHeader := req.Header.Get("Authorization") + if strings.HasPrefix(authHeader, "Bearer ") { + subscriberKey := strings.Split(authHeader, "Bearer ")[1] + s := getSubscriberByKey(subscriberKey, subscribers) + if s != nil { + apiParams := req.Context().Value("params").(map[string]string) + queryParams := req.URL.Query() + for _, a := range subscribers[s] { + if a.Name() == apiParams["adapter_id"] { + err := a.Fetch(queryParams["entity_type"][0], queryParams["entity_id"][0]) + if err != nil { + fmt.Println(err.Error()) + w.WriteHeader(http.StatusInternalServerError) + } else { + w.WriteHeader(http.StatusAccepted) + } + next.ServeHTTP(w, req) + return + } + } + } + } + w.WriteHeader(http.StatusUnauthorized) + }) +} + + func (self *BBSServer) apiMux() http.Handler { errTemplate, err := template.New("err").Parse("{{ $params := (.Context).Value \"params\" }}

ERROR {{ $params.ErrorCode }}

{{ $params.ErrorMessage }}

") if err != nil { @@ -182,6 +209,11 @@ func (self *BBSServer) apiMux() http.Handler { apiAdapterSubscribe(renderer.JSON("data")), self.subscribers, )) + + rtr.Get(`/adapters/(?P\S+)/fetch`, ProtectWithSubscriberKey( + apiAdapterFetch(renderer.JSON("data"), self.subscribers), + self.subscribers, + )) return http.HandlerFunc(rtr.ServeHTTP) } diff --git a/server/server.go b/server/server.go index 299c699..e3721bf 100644 --- a/server/server.go +++ b/server/server.go @@ -48,7 +48,8 @@ func New() *BBSServer { // websocket srvr.serveMux.HandleFunc("/subscribe", srvr.subscribeHandler) - srvr.serveMux.HandleFunc("/publish", srvr.publishHandler) + // publish is unused currently, we just use the API and send data back on the websocket + // srvr.serveMux.HandleFunc("/publish", srvr.publishHandler) return srvr } diff --git a/ts/adapter-element.ts b/ts/adapter-element.ts index 94867d3..0714cc2 100644 --- a/ts/adapter-element.ts +++ b/ts/adapter-element.ts @@ -213,7 +213,14 @@ export class AdapterElement extends HTMLElement { // otherwise we can orphan it and try to fill it in later if (this._orphans.filter(o=>o.id == msg.id).length == 0) { this._orphans.push(msg); - // TODO: request the parent's data + if (msg.replyTo) { + // request the parent's data, which will try to adopt this orphan when it comes in + util.authorizedFetch( + "GET", + `/api/adapters/${this._name}/fetch?entity_type=message&entity_id=${msg.replyTo}`, + null); + } + } return null; } diff --git a/ts/thread-summary-element.ts b/ts/thread-summary-element.ts index 1c97ff2..1eea87b 100644 --- a/ts/thread-summary-element.ts +++ b/ts/thread-summary-element.ts @@ -41,7 +41,10 @@ export class ThreadSummaryElement extends HTMLElement { } let author = datastore.profileCache.get(this._msg.author); if (!author) { - // request it! + util.authorizedFetch( + "GET", + `/api/adapters/${this._adapter}/fetch?entity_type=author&entity_id=${this._msg.author}`, + null); } this._author = author || { id: this._msg.author }; const threadAuthor = this.querySelector(".thread_author");