implement MastoAdapter
This commit is contained in:
parent
27c9e9bc59
commit
d552fc53b9
5 changed files with 254 additions and 116 deletions
|
@ -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
109
adapter/mastodon.go
Normal 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
116
adapter/nostr.go
Normal 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
8
go.mod
|
@ -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
26
go.sum
|
@ -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=
|
||||
|
|
Loading…
Reference in a new issue