underbbs/adapter/anonAp.go

332 lines
6.8 KiB
Go
Raw Normal View History

package adapter
import (
"encoding/json"
"fmt"
"io"
"net/http"
"os"
2024-11-29 19:00:54 +00:00
"strings"
"time"
"forge.lightcrystal.systems/lightcrystal/underbbs/models"
)
type anonAPAdapter struct {
data *chan models.SocketData
server string
client http.Client
2024-11-29 19:00:54 +00:00
protocol string
nickname string
}
type apLink struct {
Href string
Rel string
Type string
}
2024-11-29 19:00:54 +00:00
type apAttachment struct {
MediaType string
Type string
Name string
Summary string
Url string
2024-11-29 19:00:54 +00:00
}
type apTag struct {
Href string
Name string
Type string
2024-11-29 19:00:54 +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
}
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
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{}
case "Announce":
2024-12-01 23:26:12 +00:00
x = &apAnnounceActivity{}
}
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 {
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 23:26:12 +00:00
type apAnnounceActivity struct {
Object string
2024-12-01 23:26:12 +00:00
apActivity
}
2024-12-01 23:26:12 +00:00
type apObject struct {
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
}
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 {
self.data = data
self.server = server
2024-11-29 19:00:54 +00:00
self.nickname = nickname
self.protocol = protocol
return nil
}
func getBodyJson(res *http.Response) []byte {
l := res.ContentLength
// 4k is a reasonable max size if we get unknown length right?
if l < 0 {
l = 4096
}
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)
if err != nil {
t = time.Now()
}
2024-12-01 23:26:12 +00:00
vis := strings.Split(object.To, "#")
if len(vis) > 1 {
2024-12-01 23:26:12 +00:00
object.To = vis[1]
}
m := &models.Message{
Datagram: models.Datagram{
2024-12-01 23:26:12 +00:00
Id: object.Id,
Uri: object.Url,
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 23:26:12 +00:00
for _, a := range object.Attachment {
m.Attachments = append(m.Attachments, models.Attachment{
Src: a.Url,
Desc: a.Summary,
})
}
2024-11-29 19:00:54 +00:00
return m
}
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 {
curtime := time.Now().UnixMilli()
2024-11-29 19:00:54 +00:00
a := &models.Author{
Datagram: models.Datagram{
Id: actor.Id,
Uri: actor.Url,
Type: "author",
Created: curtime,
Updated: &curtime,
2024-11-29 19:00:54 +00:00
Protocol: self.protocol,
Adapter: self.nickname,
},
Name: actor.PreferredUsername,
ProfileData: actor.Summary,
ProfilePic: actor.Icon.Url,
}
2024-11-29 19:00:54 +00:00
return a
}
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)
if author != nil {
self.send(author)
}
case "byAuthor":
// 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:
msg := self.toMsg(a.Object)
if msg != nil {
self.send(msg)
}
2024-12-01 23:26:12 +00:00
case *apAnnounceActivity:
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 != "" {
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)
}
}
}
// 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
case "message":
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)
if message != nil {
self.send(message)
}
case "children":
case "convoy":
default:
break
}
}
return nil
}