implement MastoAdapter

This commit is contained in:
Iris Lightshard 2024-06-08 11:58:01 -06:00
parent 27c9e9bc59
commit d552fc53b9
Signed by: Iris Lightshard
GPG key ID: 688407174966CAF3
5 changed files with 254 additions and 116 deletions

View file

@ -1,13 +1,7 @@
package adapter
import (
"context"
"encoding/json"
"errors"
"fmt"
. "forge.lightcrystal.systems/lightcrystal/underbbs/models"
nostr "github.com/nbd-wtf/go-nostr"
"strings"
)
type Adapter interface {
@ -20,108 +14,3 @@ type Adapter interface {
UpdateMetadata(interface{}) error
DefaultSubscriptionFilter() string
}
type NostrAdapter struct {
data chan SocketData
nickname string
privkey string
relays []*nostr.Relay
}
func (self *NostrAdapter) Init(settings Settings, data chan SocketData) error {
self.nickname = settings.Nickname
self.privkey = *settings.PrivKey
self.data = data
ctx := context.Background()
for _, r := range settings.Relays {
pr, _ := nostr.RelayConnect(ctx, strings.Trim(r, " "))
if pr == nil {
return errors.New("Relay connection could not be completed")
}
self.relays = append(self.relays, pr)
}
return nil
}
func (self *NostrAdapter) Subscribe(filter string) []error {
var filters nostr.Filters
err := json.Unmarshal([]byte(filter), &filters)
if err != nil {
return []error{err}
}
errs := make([]error, 0)
fmt.Print("unmarshalled filter from json; iterating through relays to subscribe..")
for _, r := range self.relays {
fmt.Print(".")
sub, err := r.Subscribe(context.Background(), filters)
if err != nil {
errs = append(errs, err)
} else {
go func() {
for ev := range sub.Events {
fmt.Print("!")
// try sequentially to encode into an underbbs object
// and send it to the appropriate channel
m, err := nostrEventToMsg(ev)
if err == nil {
self.data <- m
}
}
}()
}
fmt.Println()
}
if len(errs) > 0 {
fmt.Println("subscription operation completed with errors")
return errs
}
fmt.Println("subscription operation completed without errors")
return nil
}
func (self *NostrAdapter) SendMessage(msg Message) error {
return nil
}
func (self *NostrAdapter) Follow(author Author) error {
return nil
}
func (self *NostrAdapter) Unfollow(author Author) error {
return nil
}
func (self *NostrAdapter) GetFollowers() error {
return nil
}
func (self *NostrAdapter) UpdateMetadata(data interface{}) error {
return nil
}
func (self *NostrAdapter) DefaultSubscriptionFilter() string {
return "[{\"kinds\":[1]}]"
}
func nostrEventToMsg(evt *nostr.Event) (Message, error) {
m := Message{
Protocol: "nostr",
}
if evt == nil {
return m, errors.New("no event")
}
switch evt.Kind {
case nostr.KindTextNote:
m.Uri = evt.ID
m.Author = Author{
Id: evt.PubKey,
}
m.Created = evt.CreatedAt.Time()
m.Content = evt.Content
return m, nil
default:
return m, errors.New(fmt.Sprintf("unsupported event kind: %d", evt.Kind))
}
}

109
adapter/mastodon.go Normal file
View file

@ -0,0 +1,109 @@
package adapter
import (
. "forge.lightcrystal.systems/lightcrystal/underbbs/models"
madon "github.com/McKael/madon"
)
type MastoAdapter struct {
data chan SocketData
nickname string
server string
apiKey string
masto *madon.Client
events chan madon.StreamEvent
stop chan bool
done chan bool
}
var scopes = []string{"read", "write", "follow"}
func (self *MastoAdapter) Init(settings Settings, data chan SocketData) error {
self.nickname = settings.Nickname
self.server = *settings.Server
self.apiKey = *settings.ApiKey
self.data = data
masto, err := madon.NewApp("underbbs", "https://lightcrystal.systems", scopes, madon.NoRedirect, self.server)
if err != nil {
return err
}
self.masto = masto
err = self.masto.SetUserToken(self.apiKey, "", "", []string{})
return err
}
func (self *MastoAdapter) Subscribe(filter string) []error {
// TODO: decode separate timelines and hashtags
// for now, the filter is just the timeline
// if any existing events channel, close it and create a new one
if self.events != nil {
close(self.events)
}
self.events = make(chan madon.StreamEvent)
// if any existing stop channel, close it and create a new one
if self.stop != nil {
close(self.stop)
}
self.stop = make(chan bool)
// make a new done channel
self.done = make(chan bool)
// call StreamListener
self.masto.StreamListener(filter, "", self.events, self.stop, self.done)
go func() {
for e := range self.events {
switch e.Event {
case "error":
case "update":
var msg *Message
switch v := e.Data.(type) {
case int64:
s, _ := self.masto.GetStatus(v)
if s != nil {
msg = mastoUpdateToMessage(*s)
}
case madon.Status:
msg = mastoUpdateToMessage(v)
}
if msg != nil {
self.data <- msg
}
case "notification":
case "delete":
}
}
}()
// in the background, read and translate events from the stream
// and check for doneCh closing
// the stopCh will be closed by a subsequent call to subscribe
errs := make([]error, 0)
return errs
}
func (self *MastoAdapter) SendMessage(msg Message) error {
return nil
}
func (self *MastoAdapter) Follow(author Author) error {
return nil
}
func (self *MastoAdapter) Unfollow(author Author) error {
return nil
}
func (self *MastoAdapter) GetFollowers() error {
return nil
}
func (self *MastoAdapter) UpdateMetadata(data interface{}) error {
return nil
}
func (self *MastoAdapter) DefaultSubscriptionFilter() string {
return "home"
}
func mastoUpdateToMessage(status madon.Status) *Message {
// decode that fucker
return nil
}

116
adapter/nostr.go Normal file
View file

@ -0,0 +1,116 @@
package adapter
import (
"context"
"encoding/json"
"errors"
"fmt"
. "forge.lightcrystal.systems/lightcrystal/underbbs/models"
nostr "github.com/nbd-wtf/go-nostr"
"strings"
)
type NostrAdapter struct {
data chan SocketData
nickname string
privkey string
relays []*nostr.Relay
}
func (self *NostrAdapter) Init(settings Settings, data chan SocketData) error {
self.nickname = settings.Nickname
self.privkey = *settings.PrivKey
self.data = data
ctx := context.Background()
for _, r := range settings.Relays {
pr, _ := nostr.RelayConnect(ctx, strings.Trim(r, " "))
if pr == nil {
return errors.New("Relay connection could not be completed")
}
self.relays = append(self.relays, pr)
}
return nil
}
func (self *NostrAdapter) Subscribe(filter string) []error {
var filters nostr.Filters
err := json.Unmarshal([]byte(filter), &filters)
if err != nil {
return []error{err}
}
errs := make([]error, 0)
fmt.Print("unmarshalled filter from json; iterating through relays to subscribe..")
for _, r := range self.relays {
fmt.Print(".")
sub, err := r.Subscribe(context.Background(), filters)
if err != nil {
errs = append(errs, err)
} else {
go func() {
for ev := range sub.Events {
fmt.Print("!")
// try sequentially to encode into an underbbs object
// and send it to the appropriate channel
m, err := nostrEventToMsg(ev)
if err == nil {
self.data <- m
}
}
}()
}
fmt.Println()
}
if len(errs) > 0 {
fmt.Println("subscription operation completed with errors")
return errs
}
fmt.Println("subscription operation completed without errors")
return nil
}
func (self *NostrAdapter) SendMessage(msg Message) error {
return nil
}
func (self *NostrAdapter) Follow(author Author) error {
return nil
}
func (self *NostrAdapter) Unfollow(author Author) error {
return nil
}
func (self *NostrAdapter) GetFollowers() error {
return nil
}
func (self *NostrAdapter) UpdateMetadata(data interface{}) error {
return nil
}
func (self *NostrAdapter) DefaultSubscriptionFilter() string {
return "[{\"kinds\":[1]}]"
}
func nostrEventToMsg(evt *nostr.Event) (Message, error) {
m := Message{
Protocol: "nostr",
}
if evt == nil {
return m, errors.New("no event")
}
switch evt.Kind {
case nostr.KindTextNote:
m.Uri = evt.ID
m.Author = Author{
Id: evt.PubKey,
}
m.Created = evt.CreatedAt.Time()
m.Content = evt.Content
return m, nil
default:
return m, errors.New(fmt.Sprintf("unsupported event kind: %d", evt.Kind))
}
}

8
go.mod
View file

@ -3,6 +3,7 @@ module forge.lightcrystal.systems/lightcrystal/underbbs
go 1.22.0
require (
github.com/McKael/madon v2.3.0+incompatible
github.com/nbd-wtf/go-nostr v0.31.2
golang.org/x/time v0.5.0
hacklab.nilfm.cc/quartzgun v0.3.2
@ -17,12 +18,17 @@ require (
github.com/gobwas/httphead v0.1.0 // indirect
github.com/gobwas/pool v0.2.1 // indirect
github.com/gobwas/ws v1.2.0 // indirect
github.com/gorilla/websocket v1.5.2 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/puzpuzpuz/xsync/v3 v3.0.2 // indirect
github.com/sendgrid/rest v2.6.9+incompatible // indirect
github.com/tidwall/gjson v1.14.4 // indirect
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.0 // indirect
golang.org/x/exp v0.0.0-20230425010034-47ecfdc1ba53 // indirect
golang.org/x/sys v0.8.0 // indirect
golang.org/x/net v0.23.0 // indirect
golang.org/x/oauth2 v0.21.0 // indirect
golang.org/x/sys v0.18.0 // indirect
)

26
go.sum
View file

@ -1,3 +1,5 @@
github.com/McKael/madon v2.3.0+incompatible h1:xMUA+Fy4saDV+8tN3MMnwJUoYWC//5Fy8LeOqJsRNIM=
github.com/McKael/madon v2.3.0+incompatible/go.mod h1:+issnvJjN1rpjAHZwXRB/x30uHh/NoQR7QaojJK/lSI=
github.com/btcsuite/btcd/btcec/v2 v2.3.2 h1:5n0X6hX0Zk+6omWcihdYvdAlGf2DfasC0GMf7DClJ3U=
github.com/btcsuite/btcd/btcec/v2 v2.3.2/go.mod h1:zYzJ8etWJQIv1Ogk7OzpWjowwOdXY1W/17j2MW85J04=
github.com/btcsuite/btcd/chaincfg/chainhash v1.0.2 h1:KdUfX2zKommPRa+PD0sWZUyXe9w277ABlgELO7H04IM=
@ -14,14 +16,26 @@ github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=
github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
github.com/gobwas/ws v1.2.0 h1:u0p9s3xLYpZCA1z5JgCkMeB34CKCMMQbM+G8Ii7YD0I=
github.com/gobwas/ws v1.2.0/go.mod h1:hRKAFb8wOxFROYNsT1bqfWnhX+b5MFeJM9r2ZSwg/KY=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/gorilla/websocket v1.5.2 h1:qoW6V1GT3aZxybsbC6oLnailWnB+qTMVwMreOso9XUw=
github.com/gorilla/websocket v1.5.2/go.mod h1:0n9H61RBAcf5/38py2MCYbxzPIY9rOkpvvMT24Rqs30=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/nbd-wtf/go-nostr v0.31.2 h1:PkHCAsSzG0Ce8tfF7LKyvZOjYtCdC+hPh5KfO/Rl1b4=
github.com/nbd-wtf/go-nostr v0.31.2/go.mod h1:vHKtHyLXDXzYBN0fi/9Y/Q5AD0p+hk8TQVKlldAi0gI=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/puzpuzpuz/xsync/v3 v3.0.2 h1:3yESHrRFYr6xzkz61LLkvNiPFXxJEAABanTQpKbAaew=
github.com/puzpuzpuz/xsync/v3 v3.0.2/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA=
github.com/sendgrid/rest v2.6.9+incompatible h1:1EyIcsNdn9KIisLW50MKwmSRSK+ekueiEMJ7NEoxJo0=
github.com/sendgrid/rest v2.6.9+incompatible/go.mod h1:kXX7q3jZtJXK5c5qK83bSGMdV6tsOE70KbHoqJls4lE=
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/tidwall/gjson v1.14.4 h1:uo0p8EbA09J7RQaflQ1aBRffTR7xedD2bcIVSYxLnkM=
github.com/tidwall/gjson v1.14.4/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
@ -30,13 +44,17 @@ github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
golang.org/x/exp v0.0.0-20230425010034-47ecfdc1ba53 h1:5llv2sWeaMSnA3w2kS57ouQQ4pudlXrR0dCgw51QK9o=
golang.org/x/exp v0.0.0-20230425010034-47ecfdc1ba53/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w=
golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ=
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs=
golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs=
golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
hacklab.nilfm.cc/quartzgun v0.3.2 h1:PmRFZ/IgsXVWyNn1iOsQ/ZeMnOQIQy0PzFakhXBdZoU=
hacklab.nilfm.cc/quartzgun v0.3.2/go.mod h1:P6qK4HB0CD/xfyRq8wdEGevAPFDDmv0KCaESSvv93LU=
nhooyr.io/websocket v1.8.11 h1:f/qXNc2/3DpoSZkHt1DQu6rj4zGC8JmkkLkWss0MgN0=