2024-11-28 23:51:12 +00:00
|
|
|
package adapter
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
"net/http"
|
|
|
|
"os"
|
2024-11-29 19:00:54 +00:00
|
|
|
"strings"
|
2024-11-28 23:51:12 +00:00
|
|
|
"time"
|
|
|
|
|
|
|
|
"forge.lightcrystal.systems/lightcrystal/underbbs/models"
|
|
|
|
)
|
|
|
|
|
|
|
|
type anonAPAdapter struct {
|
2024-12-01 16:02:12 +00:00
|
|
|
data *chan models.SocketData
|
|
|
|
server string
|
|
|
|
client http.Client
|
2024-11-29 19:00:54 +00:00
|
|
|
protocol string
|
|
|
|
nickname string
|
2024-11-28 23:51:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type apLink struct {
|
|
|
|
Href string
|
|
|
|
Rel string
|
|
|
|
Type string
|
|
|
|
}
|
|
|
|
|
2024-11-29 19:00:54 +00:00
|
|
|
type apAttachment struct {
|
2024-12-01 16:02:12 +00:00
|
|
|
MediaType string
|
|
|
|
Type string
|
|
|
|
Name string
|
|
|
|
Summary string
|
|
|
|
Url string
|
2024-11-29 19:00:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type apTag struct {
|
2024-12-01 16:02:12 +00:00
|
|
|
Href string
|
|
|
|
Name string
|
|
|
|
Type string
|
2024-11-29 19:00:54 +00:00
|
|
|
}
|
|
|
|
|
2024-11-28 23:51:12 +00:00
|
|
|
type apIcon struct {
|
|
|
|
MediaType string
|
|
|
|
Type string
|
|
|
|
Url string
|
|
|
|
}
|
|
|
|
|
|
|
|
type apActor struct {
|
|
|
|
Icon apIcon
|
|
|
|
Id string
|
|
|
|
Name string
|
|
|
|
PreferredUsername string
|
|
|
|
Summary string
|
|
|
|
Url string
|
|
|
|
}
|
|
|
|
|
2024-12-01 16:02:12 +00:00
|
|
|
type apOutbox struct {
|
|
|
|
OrderedItems []interface{} `json:"-"`
|
|
|
|
RawOrderedItems []json.RawMessage `json:"OrderedItems"`
|
|
|
|
}
|
|
|
|
|
|
|
|
func (self *apOutbox) UnmarshalJSON(b []byte) error {
|
|
|
|
type obInternal apOutbox
|
|
|
|
err := json.Unmarshal(b, (*obInternal)(self))
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, raw := range self.RawOrderedItems {
|
2024-12-01 23:26:12 +00:00
|
|
|
var i apActivity
|
2024-12-01 16:02:12 +00:00
|
|
|
err := json.Unmarshal(raw, &i)
|
|
|
|
|
|
|
|
var x interface{}
|
|
|
|
switch i.Type {
|
|
|
|
case "Create":
|
|
|
|
fallthrough
|
|
|
|
case "Update":
|
2024-12-01 23:26:12 +00:00
|
|
|
x = &apCreateUpdateActivity{}
|
2024-12-01 16:02:12 +00:00
|
|
|
case "Announce":
|
2024-12-01 23:26:12 +00:00
|
|
|
x = &apAnnounceActivity{}
|
2024-12-01 16:02:12 +00:00
|
|
|
}
|
|
|
|
err = json.Unmarshal(raw, &x)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
self.OrderedItems = append(self.OrderedItems, x)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2024-12-01 23:26:12 +00:00
|
|
|
type apActivity struct {
|
2024-12-01 16:02:12 +00:00
|
|
|
Actor string
|
|
|
|
Id string
|
|
|
|
Type string
|
|
|
|
To string
|
|
|
|
Published string
|
|
|
|
}
|
|
|
|
|
2024-12-01 23:26:12 +00:00
|
|
|
type apCreateUpdateActivity struct {
|
|
|
|
Object apObject
|
|
|
|
apActivity
|
2024-12-01 16:02:12 +00:00
|
|
|
}
|
|
|
|
|
2024-12-01 23:26:12 +00:00
|
|
|
type apAnnounceActivity struct {
|
2024-12-01 16:02:12 +00:00
|
|
|
Object string
|
2024-12-01 23:26:12 +00:00
|
|
|
apActivity
|
2024-12-01 16:02:12 +00:00
|
|
|
}
|
|
|
|
|
2024-12-01 23:26:12 +00:00
|
|
|
type apObject struct {
|
2024-12-01 16:02:12 +00:00
|
|
|
Id string
|
|
|
|
Content string
|
|
|
|
AttributedTo string
|
|
|
|
Context string
|
|
|
|
Conversation string
|
|
|
|
Published string
|
|
|
|
Tag []apTag
|
|
|
|
Attachment []apAttachment
|
|
|
|
To string
|
|
|
|
Url string
|
|
|
|
InReplyTo *string
|
2024-11-29 19:00:54 +00:00
|
|
|
}
|
|
|
|
|
2024-11-28 23:51:12 +00:00
|
|
|
type webFinger struct {
|
|
|
|
Links []apLink
|
|
|
|
}
|
|
|
|
|
2024-11-29 19:00:54 +00:00
|
|
|
func (self *anonAPAdapter) Init(data *chan models.SocketData, server, protocol, nickname string) error {
|
2024-11-28 23:51:12 +00:00
|
|
|
self.data = data
|
|
|
|
self.server = server
|
2024-11-29 19:00:54 +00:00
|
|
|
self.nickname = nickname
|
|
|
|
self.protocol = protocol
|
2024-11-28 23:51:12 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func getBodyJson(res *http.Response) []byte {
|
|
|
|
l := res.ContentLength
|
2024-12-01 16:02:12 +00:00
|
|
|
// 4k is a reasonable max size if we get unknown length right?
|
|
|
|
if l < 0 {
|
2024-12-01 21:08:02 +00:00
|
|
|
l = 4096
|
2024-12-01 16:02:12 +00:00
|
|
|
}
|
2024-11-28 23:51:12 +00:00
|
|
|
jsonData := make([]byte, l)
|
|
|
|
res.Body.Read(jsonData)
|
|
|
|
return jsonData
|
|
|
|
}
|
|
|
|
|
|
|
|
func (self *anonAPAdapter) makeApRequest(method, url string, data io.Reader) (*http.Response, error) {
|
|
|
|
req, err := http.NewRequest(method, url, data)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
req.Header.Set("Accept", "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"")
|
|
|
|
return self.client.Do(req)
|
|
|
|
}
|
|
|
|
|
2024-12-01 23:26:12 +00:00
|
|
|
func (self *anonAPAdapter) toMsg(object apObject) *models.Message {
|
|
|
|
t, err := time.Parse(time.RFC3339, object.Published)
|
2024-12-01 16:02:12 +00:00
|
|
|
if err != nil {
|
|
|
|
t = time.Now()
|
|
|
|
}
|
2024-12-01 23:26:12 +00:00
|
|
|
vis := strings.Split(object.To, "#")
|
2024-12-01 16:02:12 +00:00
|
|
|
if len(vis) > 1 {
|
2024-12-01 23:26:12 +00:00
|
|
|
object.To = vis[1]
|
2024-12-01 16:02:12 +00:00
|
|
|
}
|
|
|
|
m := &models.Message{
|
|
|
|
Datagram: models.Datagram{
|
2024-12-01 23:26:12 +00:00
|
|
|
Id: object.Id,
|
|
|
|
Uri: object.Url,
|
2024-12-01 16:02:12 +00:00
|
|
|
Type: "message",
|
|
|
|
Created: t.UnixMilli(),
|
|
|
|
Updated: nil,
|
|
|
|
Protocol: self.protocol,
|
|
|
|
Adapter: self.nickname,
|
|
|
|
},
|
2024-12-01 23:26:12 +00:00
|
|
|
Author: object.AttributedTo,
|
|
|
|
Content: object.Content,
|
|
|
|
ReplyTo: object.InReplyTo,
|
|
|
|
Visibility: object.To,
|
2024-12-01 16:02:12 +00:00
|
|
|
}
|
|
|
|
|
2024-12-01 23:26:12 +00:00
|
|
|
for _, a := range object.Attachment {
|
2024-12-01 16:02:12 +00:00
|
|
|
m.Attachments = append(m.Attachments, models.Attachment{
|
|
|
|
Src: a.Url,
|
|
|
|
Desc: a.Summary,
|
|
|
|
})
|
|
|
|
}
|
2024-11-29 19:00:54 +00:00
|
|
|
|
|
|
|
return m
|
2024-11-28 23:51:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (self *anonAPAdapter) send(data models.SocketData) {
|
|
|
|
if self.data != nil {
|
|
|
|
*self.data <- data
|
|
|
|
} else {
|
|
|
|
fmt.Fprintln(os.Stderr, string(data.ToDatagram()))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-11-29 19:00:54 +00:00
|
|
|
func (self *anonAPAdapter) toAuthor(actor apActor) *models.Author {
|
2024-11-28 23:51:12 +00:00
|
|
|
curtime := time.Now().UnixMilli()
|
2024-11-29 19:00:54 +00:00
|
|
|
a := &models.Author{
|
2024-11-28 23:51:12 +00:00
|
|
|
Datagram: models.Datagram{
|
2024-12-01 16:02:12 +00:00
|
|
|
Id: actor.Id,
|
|
|
|
Uri: actor.Url,
|
|
|
|
Type: "author",
|
|
|
|
Created: curtime,
|
|
|
|
Updated: &curtime,
|
2024-11-29 19:00:54 +00:00
|
|
|
Protocol: self.protocol,
|
2024-12-01 16:02:12 +00:00
|
|
|
Adapter: self.nickname,
|
2024-11-28 23:51:12 +00:00
|
|
|
},
|
|
|
|
Name: actor.PreferredUsername,
|
|
|
|
ProfileData: actor.Summary,
|
|
|
|
ProfilePic: actor.Icon.Url,
|
|
|
|
}
|
2024-11-29 19:00:54 +00:00
|
|
|
return a
|
2024-11-28 23:51:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (self *anonAPAdapter) Fetch(etype string, ids []string) error {
|
|
|
|
for _, id := range ids {
|
|
|
|
switch etype {
|
|
|
|
case "author": // webfinger lookup on id
|
|
|
|
if string([]byte{id[0]}) == "@" {
|
|
|
|
id = id[1:]
|
|
|
|
}
|
|
|
|
res, err := http.Get(self.server + "/.well-known/webfinger?resource=acct:" + id)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
data := getBodyJson(res)
|
|
|
|
wf := webFinger{}
|
|
|
|
json.Unmarshal(data, &wf)
|
|
|
|
var profile string
|
|
|
|
for _, l := range wf.Links {
|
|
|
|
if l.Rel == "self" {
|
|
|
|
profile = l.Href
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
res, err = self.makeApRequest("GET", profile, nil)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
authorData := getBodyJson(res)
|
|
|
|
actor := apActor{}
|
|
|
|
json.Unmarshal(authorData, &actor)
|
2024-11-29 19:00:54 +00:00
|
|
|
author := self.toAuthor(actor)
|
2024-11-28 23:51:12 +00:00
|
|
|
if author != nil {
|
|
|
|
self.send(author)
|
|
|
|
}
|
|
|
|
case "byAuthor":
|
2024-12-01 16:02:12 +00:00
|
|
|
// get outbox
|
|
|
|
if string([]byte{id[0]}) == "@" {
|
|
|
|
id = id[1:]
|
|
|
|
}
|
|
|
|
res, err := http.Get(self.server + "/.well-known/webfinger?resource=acct:" + id)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
data := getBodyJson(res)
|
|
|
|
wf := webFinger{}
|
|
|
|
json.Unmarshal(data, &wf)
|
|
|
|
var profile string
|
|
|
|
for _, l := range wf.Links {
|
|
|
|
if l.Rel == "self" {
|
|
|
|
profile = l.Href
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
res, err = self.makeApRequest("GET", profile+"/outbox", nil)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
obData := getBodyJson(res)
|
|
|
|
ob := apOutbox{}
|
|
|
|
ob.UnmarshalJSON(obData)
|
|
|
|
for _, i := range ob.OrderedItems {
|
|
|
|
switch a := i.(type) {
|
2024-12-01 23:26:12 +00:00
|
|
|
case *apCreateUpdateActivity:
|
2024-12-01 16:02:12 +00:00
|
|
|
msg := self.toMsg(a.Object)
|
|
|
|
if msg != nil {
|
|
|
|
self.send(msg)
|
|
|
|
}
|
2024-12-01 23:26:12 +00:00
|
|
|
case *apAnnounceActivity:
|
2024-12-01 16:02:12 +00:00
|
|
|
res, err = self.makeApRequest("GET", a.Object, nil)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2024-12-01 23:26:12 +00:00
|
|
|
objectData := getBodyJson(res)
|
|
|
|
object := apObject{}
|
|
|
|
json.Unmarshal(objectData, &object)
|
|
|
|
ogMsg := self.toMsg(object)
|
2024-12-04 06:44:11 +00:00
|
|
|
// if we couldn't fetch the original, skip it
|
|
|
|
if ogMsg != nil && ogMsg.Author != "" {
|
|
|
|
|
2024-12-01 16:02:12 +00:00
|
|
|
t, err := time.Parse(time.RFC3339, a.Published)
|
|
|
|
if err != nil {
|
|
|
|
t = time.Now()
|
|
|
|
}
|
2024-12-04 06:44:11 +00:00
|
|
|
rt := t.UnixMilli()
|
|
|
|
ogMsg.RenoteId = &a.Id
|
|
|
|
ogMsg.Renoter = &a.Actor
|
|
|
|
ogMsg.RenoteTime = &rt
|
|
|
|
|
|
|
|
self.send(ogMsg)
|
2024-12-01 16:02:12 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// for each item in outbox, check if it's a Create/Update or an Announce
|
|
|
|
// Create/Update you can directly deserialize the object
|
|
|
|
// if it's an Announce, try to get the object and deserialize it, build a boost out of it
|
2024-11-28 23:51:12 +00:00
|
|
|
case "message":
|
2024-12-01 16:02:12 +00:00
|
|
|
res, err := self.makeApRequest("GET", id, nil)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2024-12-01 23:26:12 +00:00
|
|
|
objectData := getBodyJson(res)
|
|
|
|
object := apObject{}
|
|
|
|
json.Unmarshal(objectData, &object)
|
|
|
|
message := self.toMsg(object)
|
2024-12-01 16:02:12 +00:00
|
|
|
if message != nil {
|
|
|
|
self.send(message)
|
|
|
|
}
|
2024-11-28 23:51:12 +00:00
|
|
|
case "children":
|
|
|
|
case "convoy":
|
|
|
|
default:
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|