refactor things to enable CLI mode
PGP Signature
-----BEGIN PGP SIGNATURE----- iHUEABYKAB0WIQT/foVVmI9pK13hPWFohAcXSWbK8wUCZvg/NwAKCRBohAcXSWbK 80k6AQCTuaCGp0CYQNW+teghIEeWRjsMVQuM2rqt2x+nWM7/cQD/eRJyf5/1onkq /bxFysadQaWuPUbqjb6+wmdy/QdWfQk= =l1Cu -----END PGP SIGNATURE-----
@@ -2,4 +2,5 @@ node_modules/
frontend/dist/*.js frontend/.js underbbs +underbbs-cli __debug_*
@@ -1,14 +1,22 @@
# underBBS -underBBS is a platform-agnostic messaging and social media client +underBBS is a protocol-agnostic decentralized social media client and toolkit ## design +`underbbs` can run in two modes depending on its executable name: + +### web client + `underbbs` supports multiple simultaneous account logins, mediating them for each user through a gateway server that handles all protocol-specific logic via `adapter`s and streaming content to the user through a single websocket connection with a singular data interface. each distinct `adapter` connection/configuration is represented in the frontend as a tab, and using the websocket's event-driven javascript interface with web components we can simply either store the data or tell the currently visible adapter that it might need to respond to the new data adapters receive commands via a quartzgun web API and send data back on their shared websocket connection + +### CLI + +`underbbs-cli` pulls adapter credentials from `~/.config/underbbs/cli.conf` and accepts commands on individual adapters, printing data to standard output. ## building and running
@@ -5,7 +5,7 @@ . "forge.lightcrystal.systems/lightcrystal/underbbs/models"
) type Adapter interface { - Init(Settings, chan SocketData) error + Init(Settings, *chan SocketData) error Name() string Subscribe(string) []error Fetch(string, []string) error
@@ -4,10 +4,12 @@ import (
"fmt" . "forge.lightcrystal.systems/lightcrystal/underbbs/models" madon "github.com/McKael/madon" + "log" + "os" ) type MastoAdapter struct { - data chan SocketData + data *chan SocketData nickname string server string apiKey string@@ -21,11 +23,19 @@ }
var scopes = []string{"read", "write", "follow"} +func (self *MastoAdapter) send(data SocketData) { + if self.data != nil { + *self.data <- data + } else { + fmt.Println(os.Stdout, string(data.ToDatagram())) + } +} + func (self *MastoAdapter) Name() string { return self.nickname } -func (self *MastoAdapter) Init(settings Settings, data chan SocketData) error { +func (self *MastoAdapter) Init(settings Settings, data *chan SocketData) error { self.nickname = settings.Nickname self.server = *settings.Server self.apiKey = *settings.ApiKey@@ -62,7 +72,7 @@ return []error{err}
} go func() { for e := range self.events { - fmt.Println("event: %s !!!", e.Event) + log.Printf("event: %s !!!", e.Event) switch e.Event { case "error": case "update":@@ -77,7 +87,7 @@ case madon.Status:
msg = self.mastoUpdateToMessage(v) } if msg != nil { - self.data <- msg + self.send(msg) } case "notification": case "delete":
@@ -9,13 +9,15 @@ mkm "github.com/yitsushi/go-misskey/models"
n "github.com/yitsushi/go-misskey/services/notes" tl "github.com/yitsushi/go-misskey/services/notes/timeline" users "github.com/yitsushi/go-misskey/services/users" + "log" + "os" "strings" "sync" "time" ) type MisskeyAdapter struct { - data chan SocketData + data *chan SocketData nickname string server string apiKey string@@ -31,19 +33,27 @@
stop chan bool } +func (self *MisskeyAdapter) send(data SocketData) { + if self.data != nil { + *self.data <- data + } else { + fmt.Fprintln(os.Stderr, string(data.ToDatagram())) + } +} + func (self *MisskeyAdapter) Name() string { return self.nickname } -func (self *MisskeyAdapter) Init(settings Settings, data chan SocketData) error { - fmt.Println("initializing misskey adapter") +func (self *MisskeyAdapter) Init(settings Settings, data *chan SocketData) error { + log.Print("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") + log.Print("getting ready to initialize internal client") client, err := misskey.NewClientWithOptions( misskey.WithAPIToken(self.apiKey),@@ -51,10 +61,10 @@ misskey.WithBaseURL("https", self.server, ""),
) if err != nil { - fmt.Println(err.Error()) + log.Print(err.Error()) return err } - fmt.Println("misskey client initialized") + log.Print("misskey client initialized") self.mk = client self.cache = make(map[string]time.Time)@@ -119,10 +129,10 @@ mentions, merr := notesService.Mentions(n.MentionsRequest{
Limit: 100, }) if err != nil { - fmt.Println(err.Error()) + log.Print(err.Error()) } if merr != nil { - fmt.Println(merr.Error()) + log.Print(merr.Error()) } // check the cache for everything we just collected@@ -131,13 +141,13 @@ // and convert it to a SocketData implementation before sending on data channel
for _, n := range notes { msg := self.toMessageIfNew(n) if msg != nil { - self.data <- msg + self.send(msg) } } for _, n := range mentions { msg := self.toMessageIfNew(n) if msg != nil { - self.data <- msg + self.send(msg) } }@@ -150,7 +160,7 @@ UntilDate: uint64(probenote[0].CreatedAt.Unix()),
Limit: 100, }) if err != nil { - fmt.Println(err.Error()) + log.Print(err.Error()) } for _, n := range notes { msg := self.toMessageIfNew(n)@@ -158,7 +168,7 @@ if msg == nil {
latest = &probenote[0].CreatedAt break } - self.data <- msg + self.send(msg) } if *latest == probenote[0].CreatedAt { break@@ -254,7 +264,7 @@ updated = &updatedTmp
} if bustCache || !exists || (updated != nil && timestamp.Before(time.UnixMilli(*updated))) || timestamp.Before(*usr.CreatedAt) { - fmt.Println("converting author: " + usr.ID) + log.Print("converting author: " + usr.ID) if usr.UpdatedAt != nil { self.cache[authorId] = *usr.UpdatedAt } else {@@ -291,7 +301,7 @@ return err
} else { msg := self.toMessage(data, true) if msg != nil { - self.data <- msg + self.send(msg) } } case "children":@@ -305,7 +315,7 @@ } else {
for _, n := range data { msg := self.toMessage(n, true) if msg != nil { - self.data <- msg + self.send(msg) } } }@@ -320,7 +330,7 @@ } else {
for _, n := range data { msg := self.toMessage(n, true) if msg != nil { - self.data <- msg + self.send(msg) } } }@@ -338,7 +348,6 @@ 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,@@ -348,7 +357,7 @@ return err
} else { a := self.toAuthor(data, false) if a != nil { - self.data <- a + self.send(a) } }
@@ -7,21 +7,31 @@ "errors"
"fmt" . "forge.lightcrystal.systems/lightcrystal/underbbs/models" nostr "github.com/nbd-wtf/go-nostr" + "log" + "os" "strings" ) type NostrAdapter struct { - data chan SocketData + data *chan SocketData nickname string privkey string relays []*nostr.Relay } +func (self *NostrAdapter) send(data SocketData) { + if self.data != nil { + *self.data <- data + } else { + fmt.Fprintln(os.Stdout, string(data.ToDatagram())) + } +} + func (self *NostrAdapter) Name() string { return self.nickname } -func (self *NostrAdapter) Init(settings Settings, data chan SocketData) error { +func (self *NostrAdapter) Init(settings Settings, data *chan SocketData) error { self.nickname = settings.Nickname self.privkey = *settings.PrivKey self.data = data@@ -47,35 +57,32 @@ }
errs := make([]error, 0) - fmt.Print("unmarshalled filter from json; iterating through relays to subscribe..") + log.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 := self.nostrEventToMsg(ev) if err == nil { - self.data <- m + self.send(m) } } }() } - fmt.Println() } if len(errs) > 0 { - fmt.Println("subscription operation completed with errors") + log.Print("subscription operation completed with errors") return errs } - fmt.Println("subscription operation completed without errors") + log.Print("subscription operation completed without errors") return nil }
@@ -10,6 +10,7 @@ "hacklab.nilfm.cc/quartzgun/renderer"
"hacklab.nilfm.cc/quartzgun/router" "hacklab.nilfm.cc/quartzgun/util" "html/template" + "log" "net/http" "strings" )@@ -33,16 +34,14 @@ }
func setAdaptersForSubscriber(key string, adapters []adapter.Adapter, subscribers map[*Subscriber][]adapter.Adapter) error { var ptr *Subscriber = nil - fmt.Print("looking for subscriber in map..") + log.Print("looking for subscriber in map...") for s, _ := range subscribers { - fmt.Print(".") if s.key == key { ptr = s } } - fmt.Println() if ptr != nil { - fmt.Println("setting adaters for the found subscriber: " + ptr.key) + log.Print("setting adaters for the found subscriber: " + ptr.key) subscribers[ptr] = adapters return nil }@@ -85,7 +84,7 @@ default:
break } - err := a.Init(s, subscriber.data) + err := a.Init(s, &subscriber.data) if err != nil { util.AddContextValue(req, "data", err.Error()) w.WriteHeader(500)@@ -93,13 +92,13 @@ next.ServeHTTP(w, req)
return } - fmt.Println("adapter initialized - subscribing with default filter") + log.Print("adapter initialized - subscribing with default filter") errs := a.Subscribe(a.DefaultSubscriptionFilter()) if errs != nil { errMsg := "" for _, e := range errs { - fmt.Println("processing an error") + log.Print("processing an error") errMsg += fmt.Sprintf("- %s\n", e.Error()) } util.AddContextValue(req, "data", errMsg)@@ -108,10 +107,10 @@ next.ServeHTTP(w, req)
return } - fmt.Println("adapter ready for use; adding to array") + log.Print("adapter ready for use; adding to array") adapters = append(adapters, a) - fmt.Println("adapter added to array") + log.Print("adapter added to array") } // TODO: cancel subscriptions on any existing adapters@@ -169,7 +168,7 @@ for _, a := range subscribers[s] {
if a.Name() == apiParams["adapter_id"] { err := a.Fetch(queryParams["entity_type"][0], queryParams["entity_id"]) if err != nil { - fmt.Println(err.Error()) + log.Print(err.Error()) w.WriteHeader(http.StatusInternalServerError) } else { w.WriteHeader(http.StatusAccepted)
@@ -3,7 +3,6 @@
import ( "context" "errors" - "fmt" "forge.lightcrystal.systems/lightcrystal/underbbs/adapter" "forge.lightcrystal.systems/lightcrystal/underbbs/models" "golang.org/x/time/rate"@@ -90,14 +89,14 @@ defer self.deleteSubscriber(s)
defer c.Close(websocket.StatusInternalError, "") go func() { - fmt.Println("waiting for data on the subscriber's channel") + self.logf("waiting for data on the subscriber's channel") for { select { case msg := <-s.msgs: writeTimeout(ctx, time.Second*5, c, msg) case <-ctx.Done(): - fmt.Println("subscriber has disconnected") + self.logf("subscriber has disconnected") close(s.data) return //ctx.Err() }@@ -110,7 +109,7 @@
// block on the data channel, serializing and passing the data to the subscriber listen([]chan models.SocketData{s.data}, s.msgs) - fmt.Println("data listener is done!") + self.logf("data listener is done!") if errors.Is(err, context.Canceled) { return
@@ -7,6 +7,7 @@ "net"
"net/http" "os" "os/signal" + "path/filepath" "strconv" "time"@@ -14,13 +15,31 @@ "forge.lightcrystal.systems/lightcrystal/underbbs/server"
) func main() { - err := run() + + args := os.Args + + var err error = nil + + switch filepath.Base(args[0]) { + case "underbbs-cli": + err = run_cli(args[1:]...) + break + default: + err = run_srvr() + break + } + if err != nil { log.Fatal(err) } } -func run() error { +func run_cli(args ...string) error { + log.Print("test!!") + return nil +} + +func run_srvr() error { l, err := net.Listen("tcp", ":"+strconv.FormatInt(int64(9090), 10)) if err != nil { return err