222 lines
5.3 KiB
Go
222 lines
5.3 KiB
Go
package adapter
|
|
|
|
import (
|
|
"fmt"
|
|
. "forge.lightcrystal.systems/lightcrystal/underbbs/models"
|
|
"github.com/yitsushi/go-misskey"
|
|
mkcore "github.com/yitsushi/go-misskey/core"
|
|
mkm "github.com/yitsushi/go-misskey/models"
|
|
n "github.com/yitsushi/go-misskey/services/notes"
|
|
tl "github.com/yitsushi/go-misskey/services/notes/timeline"
|
|
_ "strings"
|
|
"time"
|
|
)
|
|
|
|
type MisskeyAdapter struct {
|
|
data chan SocketData
|
|
nickname string
|
|
server string
|
|
apiKey string
|
|
|
|
mk *misskey.Client
|
|
|
|
// unlike the mastodon client, we have to manage combining resources
|
|
// from different API calls instead of streaming them in a single channel
|
|
|
|
cache map[string]time.Time
|
|
|
|
stop chan bool
|
|
}
|
|
|
|
func (self *MisskeyAdapter) Init(settings Settings, data chan SocketData) error {
|
|
fmt.Println("initializing misskey adapter")
|
|
|
|
self.nickname = settings.Nickname
|
|
self.server = *settings.Server
|
|
self.apiKey = *settings.ApiKey
|
|
self.data = data
|
|
|
|
fmt.Println("getting ready to initialize internal client")
|
|
|
|
client, err := misskey.NewClientWithOptions(
|
|
misskey.WithAPIToken(self.apiKey),
|
|
misskey.WithBaseURL("https", self.server, ""),
|
|
)
|
|
|
|
if err != nil {
|
|
fmt.Println(err.Error())
|
|
return err
|
|
}
|
|
fmt.Println("misskey client initialized")
|
|
self.mk = client
|
|
|
|
self.cache = make(map[string]time.Time)
|
|
|
|
return nil
|
|
}
|
|
|
|
func (self *MisskeyAdapter) Subscribe(filter string) []error {
|
|
// misskey streaming API is undocumented....
|
|
// we could try to reverse engineer it by directly connecting to the websocket???
|
|
// alternatively, we can poll timelines, mentions, etc with a cancellation channel,
|
|
// keep a cache of IDs in memory, and send new objects on the data channel
|
|
|
|
// TODO: decode the filter so we can send data to the mk services
|
|
|
|
// same as in masto, we will want to close and reopen the stop channel
|
|
if self.stop != nil {
|
|
close(self.stop)
|
|
}
|
|
self.stop = make(chan bool)
|
|
|
|
// in the background, continuously read data from these API endpoints
|
|
// if they are newer than the cache, convert them to UnderBBS objects
|
|
// and send them on the data channel
|
|
|
|
go self.poll()
|
|
|
|
return nil
|
|
}
|
|
|
|
func (self *MisskeyAdapter) poll() {
|
|
|
|
var latest *time.Time
|
|
|
|
notesService := self.mk.Notes()
|
|
timelineService := notesService.Timeline()
|
|
|
|
for {
|
|
select {
|
|
case _, ok := <-self.stop:
|
|
if !ok {
|
|
return
|
|
}
|
|
default:
|
|
|
|
// TODO: we have to actually decode and pass our filter criteria
|
|
// probe for new notes
|
|
|
|
probenote, err := timelineService.Get(tl.GetRequest{
|
|
Limit: 1,
|
|
})
|
|
if err == nil && len(probenote) > 0 && self.isNew(probenote[0]) {
|
|
if latest == nil {
|
|
latest = &probenote[0].CreatedAt
|
|
// this is the first fetch of notes, we can just grab them
|
|
notes, err := timelineService.Get(tl.GetRequest{
|
|
Limit: 100,
|
|
})
|
|
// if latest is nil also get mentions history
|
|
mentions, merr := notesService.Mentions(n.MentionsRequest{
|
|
Limit: 100,
|
|
})
|
|
if err != nil {
|
|
fmt.Println(err.Error())
|
|
}
|
|
if merr != nil {
|
|
fmt.Println(merr.Error())
|
|
}
|
|
|
|
// check the cache for everything we just collected
|
|
// 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)
|
|
if msg != nil {
|
|
self.data <- msg
|
|
}
|
|
}
|
|
for _, n := range mentions {
|
|
msg := self.cacheAndConvert(n)
|
|
if msg != nil {
|
|
self.data <- msg
|
|
}
|
|
}
|
|
|
|
} else {
|
|
for {
|
|
// get notes since latest, until the probe
|
|
notes, err := timelineService.Get(tl.GetRequest{
|
|
SinceDate: uint64(latest.Unix()),
|
|
UntilDate: uint64(probenote[0].CreatedAt.Unix()),
|
|
Limit: 100,
|
|
})
|
|
if err != nil {
|
|
fmt.Println(err.Error())
|
|
}
|
|
for _, n := range notes {
|
|
msg := self.cacheAndConvert(n)
|
|
if msg == nil {
|
|
latest = &probenote[0].CreatedAt
|
|
break
|
|
}
|
|
self.data <- msg
|
|
}
|
|
if *latest == probenote[0].CreatedAt {
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
time.Sleep(5 * time.Second)
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
func (self *MisskeyAdapter) isNew(n mkm.Note) bool {
|
|
timestamp, exists := self.cache[n.ID]
|
|
return !exists || timestamp.Before(n.CreatedAt)
|
|
}
|
|
|
|
func (self *MisskeyAdapter) cacheAndConvert(n mkm.Note) *Message {
|
|
timestamp, exists := self.cache[n.ID]
|
|
if !exists || timestamp.Before(n.CreatedAt) {
|
|
self.cache[n.ID] = n.CreatedAt
|
|
msg := Message{
|
|
Datagram: Datagram{
|
|
Id: n.ID,
|
|
Uri: n.URI,
|
|
Protocol: "misskey",
|
|
Adapter: self.nickname,
|
|
Type: "message",
|
|
},
|
|
|
|
Created: n.CreatedAt,
|
|
|
|
Author: fmt.Sprintf("%s@%s", n.User.Username, mkcore.StringValue(n.User.Host)),
|
|
Content: n.Text,
|
|
Attachments: []Attachment{},
|
|
Visibility: n.Visibility,
|
|
ReplyTo: n.ReplyID,
|
|
ReplyCount: int(n.RepliesCount),
|
|
Replies: []string{},
|
|
}
|
|
|
|
for _, f := range n.Files {
|
|
msg.Attachments = append(msg.Attachments, Attachment{
|
|
Src: f.URL,
|
|
ThumbSrc: f.ThumbnailURL,
|
|
Size: f.Size,
|
|
Desc: f.Comment,
|
|
CreatedAt: f.CreatedAt,
|
|
})
|
|
}
|
|
return &msg
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (self *MisskeyAdapter) Fetch(query string) error {
|
|
return nil
|
|
}
|
|
|
|
func (self *MisskeyAdapter) Do(action string) error {
|
|
return nil
|
|
}
|
|
|
|
func (self *MisskeyAdapter) DefaultSubscriptionFilter() string {
|
|
return ""
|
|
}
|