mirror of https://gitlab.com/ngerakines/tavern.git
288 lines
7.5 KiB
Go
288 lines
7.5 KiB
Go
package web
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/gofrs/uuid"
|
|
"github.com/microcosm-cc/bluemonday"
|
|
"go.uber.org/zap"
|
|
|
|
"github.com/ngerakines/tavern/storage"
|
|
)
|
|
|
|
type viewFeed struct {
|
|
loggedInActor string
|
|
viewedThread uuid.UUID
|
|
logger *zap.Logger
|
|
feed []map[string]interface{}
|
|
foundActorIDs []string
|
|
foundObjectIDs []string
|
|
mediaURLs []viewFeedMedia
|
|
storage storage.Storage
|
|
refs map[string]storage.Payload
|
|
objectThreads map[string]uuid.UUID
|
|
}
|
|
|
|
type viewFeedMedia struct {
|
|
URL string
|
|
Blurhash string
|
|
}
|
|
|
|
func (v *viewFeed) populateObjectEventPayloads(objectEvents []storage.Payload) error {
|
|
if v.feed == nil {
|
|
v.feed = make([]map[string]interface{}, 0)
|
|
}
|
|
|
|
for _, objectEvent := range objectEvents {
|
|
objectEventType, _ := storage.JSONString(objectEvent, "type")
|
|
switch objectEventType {
|
|
case "Create":
|
|
object, _ := storage.JSONMap(objectEvent, "object")
|
|
name, view := v.createNoteViewFromPayload(object)
|
|
if view == nil {
|
|
v.logger.Warn("Could not create view from object event", zap.Reflect("object_event", objectEvent))
|
|
continue
|
|
}
|
|
v.feed = append(v.feed, map[string]interface{}{
|
|
name: view,
|
|
})
|
|
case "Note":
|
|
name, view := v.createNoteViewFromPayload(objectEvent)
|
|
if view == nil {
|
|
v.logger.Warn("Could not create view from object event", zap.Reflect("object_event", objectEvent))
|
|
continue
|
|
}
|
|
v.feed = append(v.feed, map[string]interface{}{
|
|
name: view,
|
|
})
|
|
case "Announce":
|
|
name, view := v.announceNoteView(objectEvent)
|
|
if view == nil {
|
|
v.logger.Warn("Could not create view from object event", zap.Reflect("object_event", objectEvent))
|
|
continue
|
|
}
|
|
v.feed = append(v.feed, map[string]interface{}{
|
|
name: view,
|
|
})
|
|
|
|
case "Tombstone":
|
|
name, view := v.tombstoneView(objectEvent)
|
|
if view == nil {
|
|
v.logger.Warn("Could not create view from object event", zap.Reflect("object_event", objectEvent))
|
|
continue
|
|
}
|
|
v.feed = append(v.feed, map[string]interface{}{
|
|
name: view,
|
|
})
|
|
|
|
default:
|
|
v.logger.Debug("unexpected view feed object event type", zap.String("type", objectEventType))
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (v *viewFeed) tombstoneView(orig storage.Payload) (string, map[string]interface{}) {
|
|
view := make(map[string]interface{})
|
|
|
|
objectID, _ := storage.JSONString(orig, "id")
|
|
|
|
p, hasP := v.refs[objectID]
|
|
if !hasP {
|
|
v.logger.Warn("refs does not contain object id", zap.String("object_id", objectID))
|
|
return "", nil
|
|
}
|
|
view["object_id"] = objectID
|
|
|
|
view["tombstone"] = true
|
|
|
|
view["former_type"], _ = storage.JSONString(p, "formerType")
|
|
|
|
if deleted, hasDeleted := storage.JSONString(p, "deleted"); hasDeleted {
|
|
var deletedAt time.Time
|
|
deletedAt, err := time.ParseInLocation("2006-01-02T15:04:05Z", deleted, time.UTC)
|
|
if err != nil {
|
|
deletedAt = time.Now()
|
|
}
|
|
view["deleted_at"] = deletedAt
|
|
}
|
|
|
|
return "tombstone", view
|
|
|
|
}
|
|
|
|
func (v *viewFeed) announceNoteView(p storage.Payload) (string, map[string]interface{}) {
|
|
announceNote := make(map[string]interface{})
|
|
|
|
objectID, _ := storage.JSONString(p, "object")
|
|
announceNote["object_id"] = objectID
|
|
v.foundObjectIDs = append(v.foundObjectIDs, objectID)
|
|
|
|
announcer, hasAnnouncer := storage.JSONString(p, "actor")
|
|
if hasAnnouncer {
|
|
announceNote["announcer"] = announcer
|
|
v.foundActorIDs = append(v.foundActorIDs, announcer)
|
|
}
|
|
|
|
object, hasObject := v.refs[objectID]
|
|
if hasObject {
|
|
_, noteView := v.createNoteViewFromPayload(object)
|
|
if noteView != nil {
|
|
announceNote["note"] = noteView
|
|
} else {
|
|
v.logger.Warn("could not create note for announced object", zap.String("object_id", objectID), zap.Reflect("object_event", p))
|
|
}
|
|
} else {
|
|
v.logger.Warn("announced object not found in feed", zap.String("object_id", objectID), zap.Reflect("object_event", p))
|
|
}
|
|
|
|
return "announce_note", announceNote
|
|
}
|
|
|
|
func (v *viewFeed) createNoteViewFromPayload(orig storage.Payload) (string, map[string]interface{}) {
|
|
createNote := make(map[string]interface{})
|
|
|
|
objectID, _ := storage.JSONString(orig, "id")
|
|
|
|
p, hasP := v.refs[objectID]
|
|
if !hasP {
|
|
v.logger.Warn("refs does not contain object id", zap.String("object_id", objectID))
|
|
return "", nil
|
|
}
|
|
createNote["object_id"] = objectID
|
|
|
|
objectType, _ := storage.JSONString(p, "type")
|
|
if objectType == "Tombstone" {
|
|
return v.tombstoneView(orig)
|
|
}
|
|
|
|
createNote["object_id"] = objectID
|
|
v.foundObjectIDs = append(v.foundObjectIDs, objectID)
|
|
|
|
author, hasAuthor := storage.JSONString(p, "attributedTo")
|
|
content, hasContent := storage.JSONString(p, "content")
|
|
|
|
if !hasContent || !hasAuthor {
|
|
return "", nil
|
|
}
|
|
|
|
if author == v.loggedInActor {
|
|
createNote["can_delete"] = true
|
|
}
|
|
|
|
v.foundActorIDs = append(v.foundActorIDs, author)
|
|
|
|
policy := bluemonday.UGCPolicy()
|
|
|
|
createNote["author"] = author
|
|
createNote["content"] = policy.Sanitize(content)
|
|
|
|
createNote["link_reply"] = true
|
|
|
|
if inReplyTo, hasInReplyTo := storage.JSONString(p, "inReplyTo"); hasInReplyTo {
|
|
createNote["in_reply_to"] = inReplyTo
|
|
|
|
replyObj, hasReplyObj := v.refs[inReplyTo]
|
|
if hasReplyObj {
|
|
inReplyToAuthor, hasInReplyToAuthor := storage.JSONString(replyObj, "attributedTo")
|
|
if hasInReplyToAuthor {
|
|
createNote["in_reply_to_author"] = inReplyToAuthor
|
|
}
|
|
}
|
|
}
|
|
//
|
|
// if threadID, hasThreadID := v.objectThreads[objectID]; hasThreadID {
|
|
// createNote["thread_id"] = threadID
|
|
// if v.viewedThread != threadID {
|
|
// createNote["link_thread"] = true
|
|
// }
|
|
// }
|
|
|
|
if published, hasPublished := storage.JSONString(p, "published"); hasPublished {
|
|
var publishedAt time.Time
|
|
publishedAt, err := time.ParseInLocation("2006-01-02T15:04:05Z", published, time.UTC)
|
|
if err != nil {
|
|
publishedAt = time.Now()
|
|
}
|
|
createNote["published_at"] = publishedAt
|
|
}
|
|
|
|
destinations := storage.ActivityDestinations(p)
|
|
for _, destination := range destinations {
|
|
if destination == "https://www.w3.org/ns/activitystreams#Public" {
|
|
createNote["is_public"] = true
|
|
break
|
|
}
|
|
}
|
|
|
|
tags := make(map[string]string)
|
|
mentions := make(map[string]string)
|
|
objectTag, ok := storage.JSONMapList(p, "tag")
|
|
if ok {
|
|
for _, tag := range objectTag {
|
|
tagType, hasType := storage.JSONString(tag, "type")
|
|
href, hasHref := storage.JSONString(tag, "href")
|
|
name, hasName := storage.JSONString(tag, "name")
|
|
// Friendica doesn't include "name" attribute in user mentions.
|
|
if !hasType || !hasHref {
|
|
continue
|
|
}
|
|
switch tagType {
|
|
case "Hashtag":
|
|
if hasName {
|
|
tags[name] = href
|
|
}
|
|
case "Mention":
|
|
if hasName {
|
|
mentions[name] = href
|
|
}
|
|
v.foundActorIDs = append(v.foundActorIDs, href)
|
|
}
|
|
}
|
|
}
|
|
|
|
var media []string
|
|
|
|
attachments, ok := storage.JSONMapList(p, "attachment")
|
|
if ok {
|
|
for _, attachment := range attachments {
|
|
url, hasUrl := storage.JSONString(attachment, "url")
|
|
if hasUrl && strings.HasPrefix(url, "https://") {
|
|
blurHash, _ := storage.JSONString(attachment, "blurhash")
|
|
mediaType, _ := storage.JSONString(attachment, "mediaType")
|
|
switch mediaType {
|
|
case "image/png":
|
|
v.mediaURLs = append(v.mediaURLs, viewFeedMedia{
|
|
URL: url,
|
|
Blurhash: blurHash,
|
|
})
|
|
media = append(media, url)
|
|
case "image/jpeg":
|
|
v.mediaURLs = append(v.mediaURLs, viewFeedMedia{
|
|
URL: url,
|
|
Blurhash: blurHash,
|
|
})
|
|
media = append(media, url)
|
|
default:
|
|
fmt.Println("unsupported attachment", mediaType)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if len(tags) > 0 {
|
|
createNote["tags"] = tags
|
|
}
|
|
if len(mentions) > 0 {
|
|
createNote["mentions"] = mentions
|
|
}
|
|
if len(media) > 0 {
|
|
createNote["media"] = media
|
|
}
|
|
|
|
return "create_note", createNote
|
|
}
|