2024-11-02 01:13:21 +00:00
|
|
|
package adapter
|
|
|
|
|
|
|
|
import (
|
2024-12-09 17:35:44 +00:00
|
|
|
"bytes"
|
2025-01-05 03:45:22 +00:00
|
|
|
"crypto"
|
2024-12-05 03:22:38 +00:00
|
|
|
"encoding/json"
|
2024-11-02 01:13:21 +00:00
|
|
|
"errors"
|
|
|
|
"fmt"
|
2024-12-07 02:30:41 +00:00
|
|
|
. "forge.lightcrystal.systems/nilix/underbbs/models"
|
2024-12-09 17:35:44 +00:00
|
|
|
"io"
|
|
|
|
"mime/multipart"
|
2024-11-02 01:13:21 +00:00
|
|
|
"net/http"
|
|
|
|
"net/url"
|
|
|
|
"os"
|
2024-12-05 03:22:38 +00:00
|
|
|
"strconv"
|
2024-11-02 01:13:21 +00:00
|
|
|
"strings"
|
2024-12-05 03:22:38 +00:00
|
|
|
"sync"
|
|
|
|
"time"
|
2024-11-02 01:13:21 +00:00
|
|
|
)
|
|
|
|
|
2024-12-05 03:22:38 +00:00
|
|
|
type donk struct {
|
|
|
|
Desc string
|
|
|
|
URL string
|
|
|
|
}
|
|
|
|
|
|
|
|
type honk struct {
|
|
|
|
ID int
|
|
|
|
Honker string
|
2024-12-06 04:05:01 +00:00
|
|
|
Handles string
|
|
|
|
Oonker string
|
2024-12-05 03:22:38 +00:00
|
|
|
XID string
|
|
|
|
RID *string
|
|
|
|
Noise string
|
|
|
|
Donks []donk
|
|
|
|
Convoy string
|
|
|
|
Public bool
|
|
|
|
Date string
|
|
|
|
}
|
|
|
|
|
2024-12-06 04:05:01 +00:00
|
|
|
type honkResponse struct {
|
|
|
|
Honks []honk `json:"honks"`
|
|
|
|
ChatCount int `json:"chatcount"`
|
|
|
|
MeCount int `json:"mecount"`
|
|
|
|
}
|
|
|
|
|
2024-11-02 01:13:21 +00:00
|
|
|
type HonkAdapter struct {
|
|
|
|
data *chan SocketData
|
|
|
|
nickname string
|
|
|
|
server string
|
|
|
|
username string
|
|
|
|
password string
|
|
|
|
token string
|
2025-01-05 03:45:22 +00:00
|
|
|
apSigner *crypto.PrivateKey
|
|
|
|
apDomain *string
|
2024-12-05 03:22:38 +00:00
|
|
|
|
|
|
|
cache map[string]time.Time
|
|
|
|
maxId int
|
|
|
|
mtx sync.RWMutex
|
|
|
|
|
|
|
|
stop chan bool
|
2024-11-02 01:13:21 +00:00
|
|
|
}
|
|
|
|
|
2024-11-28 23:51:12 +00:00
|
|
|
func (self *HonkAdapter) isAnonymous() bool {
|
|
|
|
return self.token == ""
|
|
|
|
}
|
|
|
|
|
2024-11-02 01:13:21 +00:00
|
|
|
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 {
|
2024-12-01 21:08:02 +00:00
|
|
|
self.data = data
|
2024-11-02 01:13:21 +00:00
|
|
|
// separate name and server in handle
|
|
|
|
parts := strings.Split(*settings.Handle, "@")
|
|
|
|
self.username = parts[1]
|
|
|
|
self.server = "https://" + parts[2]
|
|
|
|
self.nickname = settings.Nickname
|
2025-01-05 03:45:22 +00:00
|
|
|
self.apSigner = settings.ApSigner
|
2024-11-28 23:51:12 +00:00
|
|
|
|
2024-12-01 21:08:02 +00:00
|
|
|
if settings.Password == nil || *settings.Password == "" {
|
2024-11-28 23:51:12 +00:00
|
|
|
// we're anonymous!
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
self.password = *settings.Password
|
2024-12-05 03:22:38 +00:00
|
|
|
|
2024-11-02 01:13:21 +00:00
|
|
|
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)
|
2024-12-07 02:25:58 +00:00
|
|
|
|
|
|
|
self.stop = make(chan bool)
|
2024-11-02 01:13:21 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2024-12-07 02:25:58 +00:00
|
|
|
func (self *HonkAdapter) Stop() {
|
|
|
|
close(self.stop)
|
|
|
|
}
|
|
|
|
|
2024-12-06 04:05:01 +00:00
|
|
|
func (self *HonkAdapter) Subscribe(filter string, target *string) []error {
|
2024-12-09 17:35:44 +00:00
|
|
|
if self.isAnonymous() {
|
|
|
|
return nil
|
|
|
|
}
|
2024-12-05 03:22:38 +00:00
|
|
|
if self.stop != nil {
|
|
|
|
close(self.stop)
|
|
|
|
}
|
|
|
|
self.stop = make(chan bool)
|
|
|
|
|
2024-12-06 04:05:01 +00:00
|
|
|
go self.gethonks(filter, target)
|
2024-12-05 03:22:38 +00:00
|
|
|
|
2024-12-01 21:08:02 +00:00
|
|
|
return nil
|
2024-11-02 01:13:21 +00:00
|
|
|
}
|
|
|
|
|
2024-12-06 04:05:01 +00:00
|
|
|
func (self *HonkAdapter) gethonks(filter string, target *string) {
|
2024-12-07 02:25:58 +00:00
|
|
|
x := self.stop
|
|
|
|
self.maxId = 0
|
2024-12-05 03:22:38 +00:00
|
|
|
for {
|
|
|
|
select {
|
2024-12-07 02:25:58 +00:00
|
|
|
case _, ok := <-x:
|
2024-12-05 03:22:38 +00:00
|
|
|
if !ok {
|
2024-12-07 02:25:58 +00:00
|
|
|
fmt.Println("stopping! filter was " + filter)
|
|
|
|
|
2024-12-05 03:22:38 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
default:
|
2024-12-07 02:25:58 +00:00
|
|
|
fmt.Println("getting honks, filter is " + filter)
|
2024-12-05 03:22:38 +00:00
|
|
|
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 {
|
2024-12-06 04:05:01 +00:00
|
|
|
fmt.Println("fucked up: " + err.Error())
|
|
|
|
self.stop <- true
|
|
|
|
return
|
2024-12-05 03:22:38 +00:00
|
|
|
}
|
2024-12-06 04:05:01 +00:00
|
|
|
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 {
|
2024-12-05 03:22:38 +00:00
|
|
|
if h.ID > self.maxId {
|
|
|
|
self.maxId = h.ID
|
|
|
|
}
|
2024-12-06 04:05:01 +00:00
|
|
|
msg := self.toMsg(h, target)
|
|
|
|
self.send(msg)
|
2024-12-05 03:22:38 +00:00
|
|
|
}
|
|
|
|
time.Sleep(5 * time.Second)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-12-06 04:05:01 +00:00
|
|
|
func (self *HonkAdapter) toMsg(h honk, target *string) Message {
|
2024-12-05 03:22:38 +00:00
|
|
|
t, err := time.Parse(time.RFC3339, h.Date)
|
|
|
|
if err != nil {
|
|
|
|
t = time.Now()
|
|
|
|
}
|
|
|
|
tt := t.UnixMilli()
|
|
|
|
|
|
|
|
a := h.Honker
|
2024-12-06 04:05:01 +00:00
|
|
|
if h.Oonker != "" {
|
|
|
|
a = h.Oonker
|
2024-12-05 03:22:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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"
|
|
|
|
}
|
2024-12-06 04:05:01 +00:00
|
|
|
if h.Oonker != "" {
|
2024-12-05 03:22:38 +00:00
|
|
|
r := fmt.Sprintf("%s/bonk/%d", h.Honker, h.ID)
|
2024-12-09 17:35:44 +00:00
|
|
|
fmt.Println(r)
|
|
|
|
msg.Renoter = &h.Honker
|
2024-12-05 03:22:38 +00:00
|
|
|
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)
|
|
|
|
}
|
2024-12-06 04:05:01 +00:00
|
|
|
if target != nil {
|
|
|
|
msg.Target = target
|
|
|
|
}
|
2024-12-05 03:22:38 +00:00
|
|
|
return msg
|
|
|
|
}
|
|
|
|
|
2024-11-02 01:13:21 +00:00
|
|
|
func (self *HonkAdapter) Fetch(etype string, ids []string) error {
|
2024-12-01 21:08:02 +00:00
|
|
|
// honk API is limited, we fall back to the anonymous adapter for fetch ops
|
2024-12-01 17:52:36 +00:00
|
|
|
aaa := anonAPAdapter{}
|
2025-01-05 03:45:22 +00:00
|
|
|
aaa.Init(self.data, self.server, "honk", self.nickname, self.apSigner, self.apDomain)
|
2024-12-01 17:52:36 +00:00
|
|
|
return aaa.Fetch(etype, ids)
|
2024-11-02 01:13:21 +00:00
|
|
|
}
|
|
|
|
|
2024-12-09 17:35:44 +00:00
|
|
|
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"
|
2024-12-10 04:18:08 +00:00
|
|
|
case "replyto":
|
|
|
|
fieldName = "rid"
|
2024-12-09 17:35:44 +00:00
|
|
|
}
|
|
|
|
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))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-11-02 01:13:21 +00:00
|
|
|
func (self *HonkAdapter) Do(action string, data map[string]string) error {
|
2024-12-09 17:35:44 +00:00
|
|
|
if self.isAnonymous() {
|
|
|
|
return nil
|
|
|
|
}
|
2024-11-02 01:13:21 +00:00
|
|
|
switch action {
|
|
|
|
case "post":
|
2024-12-09 17:35:44 +00:00
|
|
|
honkForm := url.Values{
|
2024-11-02 01:13:21 +00:00
|
|
|
"action": []string{"honk"},
|
|
|
|
"token": []string{self.token},
|
|
|
|
"noise": []string{data["content"]},
|
2024-12-09 17:35:44 +00:00
|
|
|
}
|
|
|
|
_, exists := data["file"]
|
|
|
|
if exists {
|
|
|
|
return self.donk(data)
|
|
|
|
}
|
2024-12-10 04:18:08 +00:00
|
|
|
|
|
|
|
replyto, exists := data["replyto"]
|
|
|
|
if exists {
|
|
|
|
honkForm["rid"] = []string{replyto}
|
|
|
|
}
|
|
|
|
|
2024-12-09 17:35:44 +00:00
|
|
|
res, err := http.PostForm(self.server+"/api", honkForm)
|
2024-11-02 01:13:21 +00:00
|
|
|
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 {
|
2024-12-01 17:52:36 +00:00
|
|
|
return "home"
|
2024-11-02 01:13:21 +00:00
|
|
|
}
|