package adapter import ( "bytes" "encoding/json" "errors" "fmt" . "forge.lightcrystal.systems/nilix/underbbs/models" "io" "mime/multipart" "net/http" "net/url" "os" "strconv" "strings" "sync" "time" ) type donk struct { Desc string URL string } type honk struct { ID int Honker string Handles string Oonker string XID string RID *string Noise string Donks []donk Convoy string Public bool Date string } type honkResponse struct { Honks []honk `json:"honks"` ChatCount int `json:"chatcount"` MeCount int `json:"mecount"` } type HonkAdapter struct { data *chan SocketData nickname string server string username string password string token string cache map[string]time.Time maxId int mtx sync.RWMutex stop chan bool } func (self *HonkAdapter) isAnonymous() bool { return self.token == "" } func (self *HonkAdapter) send(data SocketData) { if self.data != nil { *self.data <- data } else { fmt.Fprintln(os.Stdout, string(data.ToDatagram())) } } func (self *HonkAdapter) Name() string { return self.nickname } func (self *HonkAdapter) Init(settings Settings, data *chan SocketData) error { self.data = data // separate name and server in handle parts := strings.Split(*settings.Handle, "@") self.username = parts[1] self.server = "https://" + parts[2] self.nickname = settings.Nickname if settings.Password == nil || *settings.Password == "" { // we're anonymous! return nil } self.password = *settings.Password r, err := http.PostForm(self.server+"/dologin", url.Values{ "username": []string{self.username}, "password": []string{self.password}, "gettoken": []string{"1"}, }) if err != nil { return err } var buf [32]byte _, err = r.Body.Read(buf[:]) if err != nil { return err } self.token = string(buf[:]) fmt.Println(self.token) self.stop = make(chan bool) return nil } func (self *HonkAdapter) Stop() { close(self.stop) } func (self *HonkAdapter) Subscribe(filter string, target *string) []error { if self.isAnonymous() { return nil } if self.stop != nil { close(self.stop) } self.stop = make(chan bool) go self.gethonks(filter, target) return nil } func (self *HonkAdapter) gethonks(filter string, target *string) { x := self.stop self.maxId = 0 for { select { case _, ok := <-x: if !ok { fmt.Println("stopping! filter was " + filter) return } default: fmt.Println("getting honks, filter is " + filter) honkForm := url.Values{ "action": []string{"gethonks"}, "token": []string{self.token}, "page": []string{filter}, } if self.maxId != 0 { honkForm["after"] = []string{strconv.FormatInt(int64(self.maxId), 10)} } res, err := http.PostForm(self.server+"/api", honkForm) if err != nil { fmt.Println("fucked up: " + err.Error()) self.stop <- true return } hr := honkResponse{} err = json.NewDecoder(res.Body).Decode(&hr) if err != nil { fmt.Println("malformed honks: " + err.Error()) self.stop <- true return } for _, h := range hr.Honks { if h.ID > self.maxId { self.maxId = h.ID } msg := self.toMsg(h, target) self.send(msg) } time.Sleep(5 * time.Second) } } } func (self *HonkAdapter) toMsg(h honk, target *string) Message { t, err := time.Parse(time.RFC3339, h.Date) if err != nil { t = time.Now() } tt := t.UnixMilli() a := h.Honker if h.Oonker != "" { a = h.Oonker } msg := Message{ Datagram: Datagram{ Id: h.XID, Uri: h.XID, Protocol: "honk", Adapter: self.nickname, Type: "message", Created: tt, }, Author: a, Content: h.Noise, ReplyTo: h.RID, Visibility: "Private", } if h.Public { msg.Visibility = "Public" } if h.Oonker != "" { r := fmt.Sprintf("%s/bonk/%d", h.Honker, h.ID) fmt.Println(r) msg.Renoter = &h.Honker msg.RenoteId = &r msg.RenoteTime = &tt } for _, d := range h.Donks { a := Attachment{ Src: d.URL, Desc: d.Desc, } msg.Attachments = append(msg.Attachments, a) } if target != nil { msg.Target = target } return msg } func (self *HonkAdapter) Fetch(etype string, ids []string) error { // honk API is limited, we fall back to the anonymous adapter for fetch ops aaa := anonAPAdapter{} aaa.Init(self.data, self.server, "honk", self.nickname) return aaa.Fetch(etype, ids) } func (self *HonkAdapter) donk(data map[string]string) error { var b bytes.Buffer w := multipart.NewWriter(&b) fw, err := w.CreateFormField("action") io.Copy(fw, strings.NewReader("honk")) fw, err = w.CreateFormField("token") io.Copy(fw, strings.NewReader(self.token)) for k, v := range data { if k == "file" { if fw, err = w.CreateFormFile("donk", "donk"); err != nil { return err } } else { fieldName := "noise" switch k { case "desc": fieldName = "donkdesc" case "content": fieldName = "noise" case "replyto": fieldName = "rid" } if fw, err = w.CreateFormField(fieldName); err != nil { return err } } if _, err := io.Copy(fw, strings.NewReader(v)); err != nil { return err } } w.Close() req, err := http.NewRequest("POST", self.server+"/api", &b) if err != nil { return err } req.Header.Set("Content-Type", w.FormDataContentType()) c := http.Client{} res, err := c.Do(req) if err != nil { return err } if res.StatusCode < 400 { return nil } else { return errors.New(fmt.Sprintf("status: %d", res.StatusCode)) } } func (self *HonkAdapter) Do(action string, data map[string]string) error { if self.isAnonymous() { return nil } switch action { case "post": honkForm := url.Values{ "action": []string{"honk"}, "token": []string{self.token}, "noise": []string{data["content"]}, } _, exists := data["file"] if exists { return self.donk(data) } replyto, exists := data["replyto"] if exists { honkForm["rid"] = []string{replyto} } res, err := http.PostForm(self.server+"/api", honkForm) if err != nil { return err } var buf [256]byte _, err = res.Body.Read(buf[:]) if err != nil { return err } fmt.Println(string(buf[:])) default: return errors.New("Do: unknown action") } return nil } func (self *HonkAdapter) DefaultSubscriptionFilter() string { return "home" }