Support likes, liked, blocking, auto-accept/reject follows.

このコミットが含まれているのは:
Cory Slep 2018-02-20 00:09:29 +01:00
コミット 07a04262d9
7個のファイルの変更25966行の追加2608行の削除

ファイルの表示

@ -23,32 +23,8 @@ var (
// TODO: Helper http Handler for serving Tombstone objects
// TODO: Helper http Handler for serving deleted objects
// FederateApp is provided by users of this library and designed to handle
// receiving messages from ActivityPub servers through the Federative API.
type FederateApp interface {
}
// Callbacker provides an Application hooks into the lifecycle of the
// ActivityPub processes for both client-to-server and server-to-server
// interactions. These callbacks are called after their spec-compliant actions
// are completed, but before inbox forwarding and before delivery.
//
// Note that modifying the ActivityStream objects in a callback may cause
// unintentionally non-standard behavior if modifying core attributes, but
// otherwise affords clients powerful flexibility. Use responsibly.
type Callbacker interface {
Create(c context.Context, s *streams.Create) error
Update(c context.Context, s *streams.Update) error
Delete(c context.Context, s *streams.Delete) error
Add(c context.Context, s *streams.Add) error
Remove(c context.Context, s *streams.Remove) error
Like(c context.Context, s *streams.Like) error
Block(c context.Context, s *streams.Block) error
Follow(c context.Context, s *streams.Follow) error
Undo(c context.Context, s *streams.Undo) error
Accept(c context.Context, s *streams.Accept) error
Reject(c context.Context, s *streams.Reject) error
}
// TODO: Authorization client-to-server.
// TODO: Authenticate server-to-server deliveries.
type federator struct {
// Clock determines the time of this federator.
@ -131,6 +107,29 @@ func (f *federator) PostInbox(c context.Context, w http.ResponseWriter, r *http.
if err = json.Unmarshal(b, &m); err != nil {
return true, err
}
ao, err := getActorObject(m)
if err != nil {
return true, err
}
var iris []url.URL
for i := 0; i < ao.ActorLen(); i++ {
if ao.IsActorObject(i) {
obj := ao.GetActorObject(i)
if obj.HasId() {
iris = append(iris, obj.GetId())
}
} else if ao.IsActorLink(i) {
l := ao.GetActorLink(i)
if l.HasHref() {
iris = append(iris, l.GetHref())
}
} else if ao.IsActorIRI(i) {
iris = append(iris, ao.GetActorIRI(i))
}
}
if err = f.FederateApp.Unblocked(c, iris); err != nil {
return true, err
}
if err = f.getPostInboxResolver(c).Deserialize(m); err != nil {
return true, err
}
@ -233,24 +232,9 @@ func (f *federator) PostOutbox(c context.Context, w http.ResponseWriter, r *http
if err != nil {
return true, err
}
recipients, err := f.prepare(obj)
if err != nil {
if err := f.deliver(obj); err != nil {
return true, err
}
m, err := obj.Serialize()
if err != nil {
return true, err
}
m["@context"] = "https://www.w3.org/ns/activitystreams"
b, err := json.Marshal(m)
if err != nil {
return true, err
}
for _, to := range recipients {
f.pool.Do(b, to, func(b []byte, u url.URL) error {
return postToOutbox(f.Client, b, u, f.Agent)
})
}
}
w.Header().Set("Location", newId.String())
w.WriteHeader(http.StatusCreated)
@ -285,8 +269,16 @@ func (f *federator) GetOutbox(c context.Context, w http.ResponseWriter, r *http.
}
func (f *federator) addToOutbox(c context.Context, m map[string]interface{}) error {
// TODO
return nil
outbox, err := f.SocialApp.GetOutbox(c)
if err != nil {
return err
}
activity, err := toAnyActivity(m)
if err != nil {
return err
}
outbox.AddOrderedItemsObject(activity)
return f.App.Set(c, outbox)
}
func (f *federator) getPostOutboxResolver(c context.Context, deliverable *bool) *streams.Resolver {
@ -644,14 +636,16 @@ func (f *federator) handleClientRemove(c context.Context, deliverable *bool) fun
}
}
func (f *federator) handleClientLike(c context.Context, deliverable *bool) func(s *streams.Like) error {
func (f *federator) handleClientLike(ctx context.Context, deliverable *bool) func(s *streams.Like) error {
return func(s *streams.Like) error {
*deliverable = true
if s.LenObject() == 0 {
return ErrObjectRequired
}
// TODO: The server SHOULD add the object to the actor's liked Collection.
return f.ClientCallbacker.Like(c, s)
if err := f.addToAllActorLikedCollection(ctx, s.Raw()); err != nil {
return err
}
return f.ClientCallbacker.Like(ctx, s)
}
}
@ -661,7 +655,7 @@ func (f *federator) handleClientUndo(c context.Context, deliverable *bool) func(
if s.LenObject() == 0 {
return ErrObjectRequired
}
// TODO: Support common forms of undo natively.
// TODO: Determine if we can support common forms of undo natively.
return f.ClientCallbacker.Undo(c, s)
}
}
@ -672,7 +666,6 @@ func (f *federator) handleClientBlock(c context.Context, deliverable *bool) func
if s.LenObject() == 0 {
return ErrObjectRequired
}
// TODO: Add to blocked app.
return f.ClientCallbacker.Block(c, s)
}
}
@ -761,11 +754,31 @@ func (f *federator) handleDelete(c context.Context) func(s *streams.Delete) erro
func (f *federator) handleFollow(c context.Context) func(s *streams.Follow) error {
return func(s *streams.Follow) error {
// TODO: Implement.
// Follow means the client application SHOULD reply with an 'Accept' or
// 'Reject' ActivityStream with the 'Follow' as the 'object' and deliver
// it to the 'actor' of the 'Follow'. This can be human-triggered or
// automatically triggered.
// Permit either human-triggered or automatically triggering
// 'Accept'/'Reject'.
todo := f.FederateApp.OnFollow(c, s)
if todo != DoNothing {
var activity vocab.ActivityType
if todo == AutomaticAccept {
activity = &vocab.Accept{}
} else if todo == AutomaticReject {
activity = &vocab.Reject{}
}
raw := s.Raw()
activity.AddObject(raw)
for i := 0; i < raw.ActorLen(); i++ {
if raw.IsActorObject(i) {
activity.AddToObject(raw.GetActorObject(i))
} else if raw.IsActorLink(i) {
activity.AddToLink(raw.GetActorLink(i))
} else if raw.IsActorIRI(i) {
activity.AddToIRI(raw.GetActorIRI(i))
}
}
if err := f.deliver(activity); err != nil {
return err
}
}
return f.ServerCallbacker.Follow(c, s)
}
}
@ -782,7 +795,6 @@ func (f *federator) handleAccept(c context.Context) func(s *streams.Accept) erro
func (f *federator) handleReject(c context.Context) func(s *streams.Reject) error {
return func(s *streams.Reject) error {
// TODO: Implement.
// Reject can be client application specific. However, if this 'Reject'
// is in response to a 'Follow' then the client MUST NOT go forward with
// adding the 'actor' to the original 'actor's 'following' collection
@ -811,15 +823,15 @@ func (f *federator) handleRemove(c context.Context) func(s *streams.Remove) erro
func (f *federator) handleLike(c context.Context) func(s *streams.Like) error {
return func(s *streams.Like) error {
// TODO: Implement.
// Like triggers adding the like to an object's `like` collection.
if err := f.addToAllLikesCollections(c, s.Raw()); err != nil {
return err
}
return f.ServerCallbacker.Like(c, s)
}
}
func (f *federator) handleUndo(c context.Context) func(s *streams.Undo) error {
return func(s *streams.Undo) error {
// TODO: Implement.
// Undo negates a previous action. The 'actor' on the 'Undo' MUST be the
// same as the 'actor' on the Activity being undone, and the client
// application is responsible for enforcing this. Note that 'Undo'-ing

ファイルの表示

@ -3,6 +3,7 @@ package pub
import (
"context"
"github.com/go-fed/activity/vocab"
"github.com/go-fed/activity/streams"
"net/http"
"net/url"
"time"
@ -31,7 +32,7 @@ type Clock interface {
// modification, allowing implementations to pass-through request-scoped data in
// order to properly handle the request.
type Application interface {
// Get fetches the ActivityStream representation of the given id.
// Get fetches the ActivityStream representation of the given id.
Get(c context.Context, id url.URL) (PubObject, error)
// Set should write or overwrite the value of the provided object for
// its 'id'.
@ -67,6 +68,76 @@ type SocialApp interface {
// CanRemove returns true if the provided object is allowed to be
// removed from the given target collection.
CanRemove(c context.Context, o vocab.ObjectType, t vocab.ObjectType) bool
// GetOutbox gets the outbox of an actor.
GetOutbox(c context.Context) (vocab.OrderedCollectionType, error)
}
// FollowResponse instructs how to proceed upon immediately receiving a request
// to follow.
type FollowResponse int
const (
AutomaticAccept FollowResponse = iota
AutomaticReject
DoNothing
)
// Callbacker provides an Application hooks into the lifecycle of the
// ActivityPub processes for both client-to-server and server-to-server
// interactions. These callbacks are called after their spec-compliant actions
// are completed, but before inbox forwarding and before delivery.
//
// Note that modifying the ActivityStream objects in a callback may cause
// unintentionally non-standard behavior if modifying core attributes, but
// otherwise affords clients powerful flexibility. Use responsibly.
type Callbacker interface {
// Create Activity callback.
Create(c context.Context, s *streams.Create) error
// Update Activity callback.
Update(c context.Context, s *streams.Update) error
// Delete Activity callback.
Delete(c context.Context, s *streams.Delete) error
// Add Activity callback.
Add(c context.Context, s *streams.Add) error
// Remove Activity callback.
Remove(c context.Context, s *streams.Remove) error
// Like Activity callback.
Like(c context.Context, s *streams.Like) error
// Block Activity callback. By default, this implmentation does not
// dictate how blocking should be implemented, so it is up to the
// application to enforce this by implementing the FederateApp
// interface.
Block(c context.Context, s *streams.Block) error
// Follow Activity callback. In the special case of server-to-server
// delivery of a Follow activity, this implementation supports the
// option of automatically replying with an 'Accept', 'Reject', or
// waiting for human interaction as provided in the FederateApp
// interface.
Follow(c context.Context, s *streams.Follow) error
// Undo Activity callback. It is up to the client to provide support
// for all 'Undo' operations; this implementation does not attempt to
// provide a generic implementation.
Undo(c context.Context, s *streams.Undo) error
// Accept Activity callback.
Accept(c context.Context, s *streams.Accept) error
// Reject Activity callback.
Reject(c context.Context, s *streams.Reject) error
}
// FederateApp is provided by users of this library and designed to handle
// receiving messages from ActivityPub servers through the Federative API.
type FederateApp interface {
// OnFollow determines whether to take any automatic reactions in
// response to this follow.
OnFollow(c context.Context, s *streams.Follow) FollowResponse
// Unblocked should return an error if the provided actor ids are not
// able to interact with this particular end user due to being blocked
// or other application-specific logic. This error is passed
// transparently back to the request thread via PostInbox.
//
// If nil error is returned, then the received activity is processed as
// a normal unblocked interaction.
Unblocked(c context.Context, actorIRIs []url.URL) error
}
// PubObject is an ActivityPub Object.

ファイルの表示

@ -2,6 +2,7 @@ package pub
import (
"bytes"
"context"
"encoding/json"
"fmt"
"github.com/go-fed/activity/streams"
@ -168,6 +169,30 @@ func (f *federator) wrapInCreate(o vocab.ObjectType, actor url.URL) *vocab.Creat
// TODO: (Section 7) HTTP caching mechanisms [RFC7234] SHOULD be respected when appropriate, both when receiving responses from other servers as well as sending responses to other servers.
// deliver will complete the peer-to-peer sending of a federated message to
// another server.
func (f *federator) deliver(obj vocab.ActivityType) error {
recipients, err := f.prepare(obj)
if err != nil {
return err
}
m, err := obj.Serialize()
if err != nil {
return err
}
m["@context"] = "https://www.w3.org/ns/activitystreams"
b, err := json.Marshal(m)
if err != nil {
return err
}
for _, to := range recipients {
f.pool.Do(b, to, func(b []byte, u url.URL) error {
return postToOutbox(f.Client, b, u, f.Agent)
})
}
return nil
}
// prepare takes a deliverableObject and returns a list of the proper recipient
// target URIs. Additionally, the deliverableObject will have any hidden
// hidden recipients ("bto" and "bcc") stripped from it.
@ -836,6 +861,152 @@ func toTombstone(obj vocab.ObjectType, id url.URL, now time.Time) vocab.Tombston
return tomb
}
func (f *federator) addToAllActorLikedCollection(ctx context.Context, c vocab.LikeType) error {
for i := 0; i < c.ActorLen(); i++ {
var actor vocab.ObjectType
if c.IsActorObject(i) {
actor = c.GetActorObject(i)
} else if c.IsActorLink(i) {
l := c.GetActorLink(i)
if !l.HasHref() {
return fmt.Errorf("actor Link href required")
}
pObj, err := f.App.Get(ctx, l.GetHref())
if err != nil {
return err
}
ok := false
actor, ok = pObj.(vocab.ObjectType)
if !ok {
return fmt.Errorf("actor is not vocab.ObjectType")
}
} else if c.IsActorIRI(i) {
iri := c.GetActorIRI(i)
pObj, err := f.App.Get(ctx, iri)
if err != nil {
return err
}
ok := false
actor, ok = pObj.(vocab.ObjectType)
if !ok {
return fmt.Errorf("actor is not vocab.ObjectType")
}
}
var lc vocab.CollectionType
var loc vocab.OrderedCollectionType
if actor.IsLikedAnyURI() {
pObj, err := f.App.Get(ctx, actor.GetLikedAnyURI())
if err != nil {
return err
}
ok := false
if lc, ok = pObj.(vocab.CollectionType); !ok {
if loc, ok = pObj.(vocab.OrderedCollectionType); !ok {
return fmt.Errorf("actors liked collection not CollectionType nor OrderedCollectionType")
}
}
} else if actor.IsLikedCollection() {
lc = actor.GetLikedCollection()
} else if actor.IsLikedOrderedCollection() {
loc = actor.GetLikedOrderedCollection()
}
for i := 0; i < c.ObjectLen(); i++ {
if c.IsObjectIRI(i) {
if lc != nil {
lc.AddItemsIRI(c.GetObjectIRI(i))
} else if loc != nil {
loc.AddOrderedItemsIRI(c.GetObjectIRI(i))
}
} else if c.IsObject(i) {
if lc != nil {
lc.AddItemsObject(c.GetObject(i))
} else if loc != nil {
loc.AddOrderedItemsObject(c.GetObject(i))
}
}
}
if lc != nil {
if err := f.App.Set(ctx, lc); err != nil {
return err
}
} else if loc != nil {
if err := f.App.Set(ctx, loc); err != nil {
return err
}
}
}
return nil
}
func (f *federator) addToAllLikesCollections(ctx context.Context, c vocab.LikeType) error {
for i := 0; i < c.ObjectLen(); i++ {
var object vocab.ObjectType
if c.IsObject(i) {
object = c.GetObject(i)
} else if c.IsObjectIRI(i) {
iri := c.GetObjectIRI(i)
pObj, err := f.App.Get(ctx, iri)
if err != nil {
return err
}
ok := false
object, ok = pObj.(vocab.ObjectType)
if !ok {
return fmt.Errorf("object is not vocab.ObjectType")
}
}
var lc vocab.CollectionType
var loc vocab.OrderedCollectionType
if object.IsLikesAnyURI() {
pObj, err := f.App.Get(ctx, object.GetLikesAnyURI())
if err != nil {
return err
}
ok := false
if lc, ok = pObj.(vocab.CollectionType); !ok {
if loc, ok = pObj.(vocab.OrderedCollectionType); !ok {
return fmt.Errorf("object likes collection not CollectionType nor OrderedCollectionType")
}
}
} else if object.IsLikesCollection() {
lc = object.GetLikesCollection()
} else if object.IsLikesOrderedCollection() {
loc = object.GetLikesOrderedCollection()
}
for i := 0; i < c.ActorLen(); i++ {
if c.IsActorIRI(i) {
if lc != nil {
lc.AddItemsIRI(c.GetActorIRI(i))
} else if loc != nil {
loc.AddOrderedItemsIRI(c.GetActorIRI(i))
}
} else if c.IsActorObject(i) {
if lc != nil {
lc.AddItemsObject(c.GetActorObject(i))
} else if loc != nil {
loc.AddOrderedItemsObject(c.GetActorObject(i))
}
} else if c.IsActorLink(i) {
if lc != nil {
lc.AddItemsLink(c.GetActorLink(i))
} else if loc != nil {
loc.AddOrderedItemsLink(c.GetActorLink(i))
}
}
}
if lc != nil {
if err := f.App.Set(ctx, lc); err != nil {
return err
}
} else if loc != nil {
if err := f.App.Set(ctx, loc); err != nil {
return err
}
}
}
return nil
}
// TODO: Move this to vocab package.
var activityTypes = []string{"Accept", "Add", "Announce", "Arrive", "Block", "Create", "Delete", "Dislike", "Flag", "Follow", "Ignore", "Invite", "Join", "Leave", "Like", "Listen", "Move", "Offer", "Question", "Reject", "Read", "Remove", "TentativeReject", "TentativeAccept", "Travel", "Undo", "Update", "View"}

ファイルの表示

@ -35,6 +35,12 @@ func ToPubObject(m map[string]interface{}) (t []PubObject, e error) {
return t, e
}
func getActorObject(m map[string]interface{}) (actorObject, error) {
var a actorObject
err := toActorResolver(&a).Deserialize(m)
return a, err
}
func toActorResolver(a *actorObject) *streams.Resolver {
return &streams.Resolver{
AnyObjectCallback: func(i vocab.ObjectType) error {

ファイル差分が大きすぎるため省略します 差分を読み込み

ファイルの表示

@ -1013,7 +1013,15 @@ var (
URI: propertyBaseURI + "liked",
Notes: "A link to an [ActivityStreams] collection of objects this actor has liked",
Domain: []DomainReference{{T: objectType}},
Range: []RangeReference{{V: xsdAnyURIValueType}},
Range: []RangeReference{{T: collectionType}, {T: orderedCollectionType}, {V: xsdAnyURIValueType}},
Functional: true, // Missing from spec!
}
likesPropertyType = &PropertyType{
Name: "likes",
URI: propertyBaseURI + "likes",
Notes: "A link to an [ActivityStreams] collection of objects this actor has liked",
Domain: []DomainReference{{T: objectType}},
Range: []RangeReference{{T: collectionType}, {T: orderedCollectionType}, {V: xsdAnyURIValueType}},
Functional: true, // Missing from spec!
}
streamsPropertyType = &PropertyType{
@ -1164,6 +1172,7 @@ var (
followingPropertyType,
followersPropertyType,
likedPropertyType,
likesPropertyType,
streamsPropertyType,
preferredUsernamePropertyType,
endpointsPropertyType,
@ -1770,6 +1779,7 @@ func init() {
followingPropertyType,
followersPropertyType,
likedPropertyType,
likesPropertyType,
streamsPropertyType,
preferredUsernamePropertyType,
endpointsPropertyType,

ファイル差分が大きすぎるため省略します 差分を読み込み