Federated via graft
0

Configure Feed

Select the types of activity you want to include in your feed.

1package activitypub 2 3import ( 4 "strconv" 5 "strings" 6 "time" 7 8 "graft/internal/state" 9) 10 11// OrderedCollection wraps a series' outbox: every mirrored event as a 12// Create{Note} activity, newest first. 13type OrderedCollection struct { 14 Context string `json:"@context"` 15 ID string `json:"id"` 16 Type string `json:"type"` 17 TotalItems int `json:"totalItems"` 18 OrderedItems []Create `json:"orderedItems"` 19} 20 21type Create struct { 22 Context string `json:"@context,omitempty"` 23 ID string `json:"id"` 24 Type string `json:"type"` 25 Actor string `json:"actor"` 26 Published string `json:"published"` 27 To []string `json:"to,omitempty"` 28 Object Note `json:"object"` 29} 30 31// Note is one mirrored event, addressable on its own 32// (/actors/{series}/notes/{id}) so a reply's inReplyTo can point back at 33// the specific commit/issue/patch it's about. 34type Note struct { 35 Context string `json:"@context,omitempty"` 36 ID string `json:"id"` 37 Type string `json:"type"` 38 AttributedTo string `json:"attributedTo"` 39 Content string `json:"content"` 40 URL string `json:"url,omitempty"` 41 Published string `json:"published"` 42 To []string `json:"to,omitempty"` 43} 44 45const publicAudience = "https://www.w3.org/ns/activitystreams#Public" 46 47// NoteURI returns the stable object URI for one activity_log entry's Note. 48func NoteURI(host, series string, entryID int64) string { 49 return ActorURI(host, series) + "/notes/" + strconv.FormatInt(entryID, 10) 50} 51 52// ParseNoteURI is NoteURI's inverse: given a URI a reply's inReplyTo 53// claims to point at, extract the series and activity_log id it names — 54// used by the inbox to resolve a reply back to the item it's about. 55func ParseNoteURI(host, uri string) (series string, entryID int64, ok bool) { 56 prefix := "https://" + host + "/actors/" 57 rest := strings.TrimPrefix(uri, prefix) 58 if rest == uri { 59 return "", 0, false 60 } 61 parts := strings.Split(rest, "/") 62 if len(parts) != 3 || parts[1] != "notes" { 63 return "", 0, false 64 } 65 id, err := strconv.ParseInt(parts[2], 10, 64) 66 if err != nil { 67 return "", 0, false 68 } 69 return parts[0], id, true 70} 71 72// BuildNote turns one mirrored event into its Note representation. 73func BuildNote(host, series string, e state.ActivityEntry) Note { 74 actor := ActorURI(host, series) 75 content := kindLabel(e.Kind) + ": " + e.Summary 76 return Note{ 77 ID: NoteURI(host, series, e.ID), 78 Type: "Note", 79 AttributedTo: actor, 80 Content: content, 81 URL: e.URL, 82 Published: e.OccurredAt.Format(time.RFC3339), 83 To: []string{publicAudience}, 84 } 85} 86 87// BuildOutbox wraps a series' recent activity entries (newest first, as 88// returned by state.Store.ActivityForSeries) into an OrderedCollection of 89// Create activities. 90func BuildOutbox(host, series string, entries []state.ActivityEntry) OrderedCollection { 91 base := ActorURI(host, series) 92 actor := base 93 items := make([]Create, 0, len(entries)) 94 for _, e := range entries { 95 note := BuildNote(host, series, e) 96 items = append(items, Create{ 97 ID: note.ID + "/activity", 98 Type: "Create", 99 Actor: actor, 100 Published: note.Published, 101 To: []string{publicAudience}, 102 Object: note, 103 }) 104 } 105 return OrderedCollection{ 106 Context: "https://www.w3.org/ns/activitystreams", 107 ID: base + "/outbox", 108 Type: "OrderedCollection", 109 TotalItems: len(items), 110 OrderedItems: items, 111 } 112} 113 114func kindLabel(kind string) string { 115 switch kind { 116 case "git": 117 return "Commit" 118 case "issue": 119 return "Issue" 120 case "patch": 121 return "Patch" 122 default: 123 return kind 124 } 125}