diff --git a/ActivityPub/activity.go b/ActivityPub/activity.go index 37a4ef7..cb599d2 100644 --- a/ActivityPub/activity.go +++ b/ActivityPub/activity.go @@ -15,6 +15,7 @@ import ( "time" "github.com/Songmu/go-httpdate" + "github.com/satori/go.uuid" "github.com/yukimochi/Activity-Relay/KeyLoader" "github.com/yukimochi/httpsig" ) @@ -84,13 +85,32 @@ func RetrieveActor(url string) (*Actor, error) { // DescribeNestedActivity : Descrive Nested Activity Series func DescribeNestedActivity(nestedActivity interface{}) (*Activity, error) { mappedObject := nestedActivity.(map[string]interface{}) - - return &Activity{ - ID: mappedObject["id"].(string), - Type: mappedObject["type"].(string), - Actor: mappedObject["actor"].(string), - Object: mappedObject["object"], - }, nil + if id, ok := mappedObject["id"].(string); ok { + if nestedType, ok := mappedObject["type"].(string); ok { + actor, ok := mappedObject["actor"].(string) + if !ok { + actor = "" + } + switch object := mappedObject["object"].(type) { + case string: + return &Activity{ + ID: id, + Type: nestedType, + Actor: actor, + Object: object, + }, nil + default: + return &Activity{ + ID: id, + Type: nestedType, + Actor: actor, + Object: mappedObject["object"], + }, nil + } + } + return nil, errors.New("Can't assart type") + } + return nil, errors.New("Can't assart id") } // GenerateActor : Generate Actor by hostname and publickey @@ -127,8 +147,8 @@ func GenerateWebfingerResource(hostname *url.URL, actor *Actor) WebfingerResourc // GenerateActivityResponse : Generate Responce Activity to Activity func GenerateActivityResponse(host *url.URL, to *url.URL, responseType string, activity Activity) Activity { return Activity{ - []string{"https://www.w3.org/ns/activitystreams", "https://w3id.org/security/v1"}, - host.String() + "/actor#accepts/follows/" + to.Host, + []string{"https://www.w3.org/ns/activitystreams"}, + host.String() + "/activities/" + uuid.NewV4().String(), host.String() + "/actor", responseType, &activity, @@ -136,3 +156,16 @@ func GenerateActivityResponse(host *url.URL, to *url.URL, responseType string, a nil, } } + +// GenerateActivityAnnounce : Generate Announce Activity to Activity +func GenerateActivityAnnounce(host *url.URL, to *url.URL, actiivtyID string) Activity { + return Activity{ + []string{"https://www.w3.org/ns/activitystreams"}, + host.String() + "/activities/" + uuid.NewV4().String(), + host.String() + "/actor", + "Announce", + actiivtyID, + []string{host.String() + "/actor/followers"}, + nil, + } +} diff --git a/RelayConf/relayconf.go b/RelayConf/relayconf.go index 269187c..bb6c9e1 100644 --- a/RelayConf/relayconf.go +++ b/RelayConf/relayconf.go @@ -4,8 +4,9 @@ import "github.com/go-redis/redis" // RelayConfig : struct for relay configuration type RelayConfig struct { - BlockService bool - ManuallyAccept bool + BlockService bool + ManuallyAccept bool + CreateAsAnnounce bool } // LoadConfig : Loader for relay configuration @@ -20,9 +21,15 @@ func LoadConfig(redClient *redis.Client) RelayConfig { redClient.HSet("relay:config", "manually_accept", 0) manuallyAccept = "0" } + createAsAnnounce, err := redClient.HGet("relay:config", "create_as_announce").Result() + if err != nil { + redClient.HSet("relay:config", "create_as_announce", 0) + createAsAnnounce = "0" + } return RelayConfig{ - BlockService: blockService == "1", - ManuallyAccept: manuallyAccept == "1", + BlockService: blockService == "1", + ManuallyAccept: manuallyAccept == "1", + CreateAsAnnounce: createAsAnnounce == "1", } } diff --git a/cli/cli.go b/cli/cli.go index 50d2190..8d4da1a 100644 --- a/cli/cli.go +++ b/cli/cli.go @@ -131,6 +131,17 @@ func main() { }, Action: manuallyAccept, }, + { + Name: "create-as-announce", + Usage: "Enable Announce activity instead of relay create activity (Not recommended)", + Flags: []cli.Flag{ + cli.BoolFlag{ + Name: "undo, u", + Usage: "Undo block", + }, + }, + Action: createAsAnnounce, + }, }, }, { diff --git a/cli/config.go b/cli/config.go index 3525314..63e0d28 100644 --- a/cli/config.go +++ b/cli/config.go @@ -27,9 +27,20 @@ func manuallyAccept(c *cli.Context) { } } +func createAsAnnounce(c *cli.Context) { + if c.Bool("undo") { + relayconf.SetConfig(redClient, "create_as_announce", false) + fmt.Println("Announce activity instead of relay create activity is Disabled.") + } else { + relayconf.SetConfig(redClient, "create_as_announce", true) + fmt.Println("Announce activity instead of relay create activity is Enabled.") + } +} + func listConfigs(c *cli.Context) { config := relayconf.LoadConfig(redClient) fmt.Println("Blocking for service-type actor : ", config.BlockService) fmt.Println("Manually accept follow-request : ", config.ManuallyAccept) + fmt.Println("Announce activity instead of relay create activity : ", config.CreateAsAnnounce) } diff --git a/handle.go b/handle.go index 1103709..535d08f 100644 --- a/handle.go +++ b/handle.go @@ -230,8 +230,24 @@ func handleInbox(w http.ResponseWriter, r *http.Request, activityDecoder func(*h w.Write([]byte(err.Error())) } else { if suitableRelay(activity, actor) { - go pushRelayJob(domain.Host, body) - fmt.Println("Accept Relay Status : ", activity.Actor) + if relConfig.CreateAsAnnounce && activity.Type == "Create" { + nestedObject, err := activitypub.DescribeNestedActivity(activity.Object) + if err != nil { + fmt.Println("Fail Assert activity : activity.Actor") + } + switch nestedObject.Type { + case "Note": + resp := activitypub.GenerateActivityAnnounce(hostname, domain, nestedObject.ID) + jsonData, _ := json.Marshal(&resp) + go pushRelayJob(domain.Host, jsonData) + fmt.Println("Accept Announce Note : ", activity.Actor) + default: + fmt.Println("Skipping Announce", nestedObject.Type, ": ", activity.Actor) + } + } else { + go pushRelayJob(domain.Host, body) + fmt.Println("Accept Relay Status : ", activity.Actor) + } } else { fmt.Println("Skipping Relay Status : ", activity.Actor) } diff --git a/handle_test.go b/handle_test.go index 408c705..34a7b09 100644 --- a/handle_test.go +++ b/handle_test.go @@ -190,6 +190,11 @@ func mockActivity(req string) activitypub.Activity { var activity activitypub.Activity json.Unmarshal([]byte(body), &activity) return activity + case "Create-Article": + body := "{\"@context\":[\"https://www.w3.org/ns/activitystreams\",\"https://w3id.org/security/v1\",{\"manuallyApprovesFollowers\":\"as:manuallyApprovesFollowers\",\"sensitive\":\"as:sensitive\",\"movedTo\":{\"@id\":\"as:movedTo\",\"@type\":\"@id\"},\"Hashtag\":\"as:Hashtag\",\"ostatus\":\"http://ostatus.org#\",\"atomUri\":\"ostatus:atomUri\",\"inReplyToAtomUri\":\"ostatus:inReplyToAtomUri\",\"conversation\":\"ostatus:conversation\",\"toot\":\"http://joinmastodon.org/ns#\",\"Emoji\":\"toot:Emoji\",\"focalPoint\":{\"@container\":\"@list\",\"@id\":\"toot:focalPoint\"},\"featured\":{\"@id\":\"toot:featured\",\"@type\":\"@id\"},\"schema\":\"http://schema.org#\",\"PropertyValue\":\"schema:PropertyValue\",\"value\":\"schema:value\"}],\"id\":\"https://mastodon.test.yukimochi.io/users/yukimochi/statuses/101075045564444857/activity\",\"type\":\"Create\",\"actor\":\"https://mastodon.test.yukimochi.io/users/yukimochi\",\"published\":\"2018-11-15T11:07:26Z\",\"to\":[\"https://www.w3.org/ns/activitystreams#Public\"],\"cc\":[\"https://mastodon.test.yukimochi.io/users/yukimochi/followers\"],\"object\":{\"id\":\"https://mastodon.test.yukimochi.io/users/yukimochi/statuses/101075045564444857\",\"type\":\"Article\",\"summary\":null,\"inReplyTo\":null,\"published\":\"2018-11-15T11:07:26Z\",\"url\":\"https://mastodon.test.yukimochi.io/@yukimochi/101075045564444857\",\"attributedTo\":\"https://mastodon.test.yukimochi.io/users/yukimochi\",\"to\":[\"https://www.w3.org/ns/activitystreams#Public\"],\"cc\":[\"https://mastodon.test.yukimochi.io/users/yukimochi/followers\"],\"sensitive\":false,\"atomUri\":\"https://mastodon.test.yukimochi.io/users/yukimochi/statuses/101075045564444857\",\"inReplyToAtomUri\":null,\"conversation\":\"tag:mastodon.test.yukimochi.io,2018-11-15:objectId=68:objectType=Conversation\",\"content\":\"

Actvity-Relay

\",\"contentMap\":{\"en\":\"

Actvity-Relay

\"},\"attachment\":[],\"tag\":[]},\"signature\":{\"type\":\"RsaSignature2017\",\"creator\":\"https://mastodon.test.yukimochi.io/users/yukimochi#main-key\",\"created\":\"2018-11-15T11:07:26Z\",\"signatureValue\":\"mMgl2GgVPgb1Kw6a2iDIZc7r0j3ob+Cl9y+QkCxIe6KmnUzb15e60UuhkE5j3rJnoTwRKqOFy1PMkSxlYW6fPG/5DBxW9I4kX+8sw8iH/zpwKKUOnXUJEqfwRrNH2ix33xcs/GkKPdedY6iAPV9vGZ10MSMOdypfYgU9r+UI0sTaaC2iMXH0WPnHQuYAI+Q1JDHIbDX5FH1WlDL6+8fKAicf3spBMxDwPHGPK8W2jmDLWdN2Vz4ffsCtWs5BCuqOKZrtTW0Rdd4HWzo40MnRXvBjv7yNlnnKzokANBqiOLWT7kNfK0+Vtnt6c/bNX64KBro53KR7wL3ZBvPVuv5rdQ==\"}}" + var activity activitypub.Activity + json.Unmarshal([]byte(body), &activity) + return activity case "Announce": body := "{\"@context\":[\"https://www.w3.org/ns/activitystreams\",\"https://w3id.org/security/v1\",{\"manuallyApprovesFollowers\":\"as:manuallyApprovesFollowers\",\"sensitive\":\"as:sensitive\",\"movedTo\":{\"@id\":\"as:movedTo\",\"@type\":\"@id\"},\"Hashtag\":\"as:Hashtag\",\"ostatus\":\"http://ostatus.org#\",\"atomUri\":\"ostatus:atomUri\",\"inReplyToAtomUri\":\"ostatus:inReplyToAtomUri\",\"conversation\":\"ostatus:conversation\",\"toot\":\"http://joinmastodon.org/ns#\",\"Emoji\":\"toot:Emoji\",\"focalPoint\":{\"@container\":\"@list\",\"@id\":\"toot:focalPoint\"},\"featured\":{\"@id\":\"toot:featured\",\"@type\":\"@id\"},\"schema\":\"http://schema.org#\",\"PropertyValue\":\"schema:PropertyValue\",\"value\":\"schema:value\"}],\"id\":\"https://mastodon.test.yukimochi.io/users/yukimochi/statuses/101075096728565994/activity\",\"type\":\"Announce\",\"actor\":\"https://mastodon.test.yukimochi.io/users/yukimochi\",\"published\":\"2018-11-15T11:20:27Z\",\"to\":[\"https://www.w3.org/ns/activitystreams#Public\"],\"cc\":[\"https://mastodon.test.yukimochi.io/users/yukimochi\",\"https://mastodon.test.yukimochi.io/users/yukimochi/followers\"],\"object\":\"https://mastodon.test.yukimochi.io/users/yukimochi/statuses/101075042939498980\",\"atomUri\":\"https://mastodon.test.yukimochi.io/users/yukimochi/statuses/101075096728565994/activity\",\"signature\":{\"type\":\"RsaSignature2017\",\"creator\":\"https://mastodon.test.yukimochi.io/users/yukimochi#main-key\",\"created\":\"2018-11-15T11:20:27Z\",\"signatureValue\":\"HUe7M49uoEE5bsCM7rrG1ruamKVuYclKYst4OHQHBcvGSWkMTYCG5OmNQMihpFAantN1Mhz+PWKubXsWmrEnUGDNtog9XDGo2iVbYDcD1wjrDz6EuJiq3CBjLpzQ+F04EIx8LK8WSq6pec+jaIxBJghBa7BNH5i77nUdD7QLZxglqljMkf/r2s1i1eDtVJVDLzU3PW05Qu6Z+RDGZrG137ZwLZ3a5hnFyUPqw3fSgdA4n+AmxYenIHorgj45bmI4QJB8X1TPuAadB2XDvnSTTSuJQyDPyR3kCafBWmXDrqb0MRREsc99KzS9L00OiOY31v0TXr78vjSDxoGzEE81cw==\"}}" var activity activitypub.Activity @@ -537,6 +542,64 @@ func TestHandleInboxlimitedCreate(t *testing.T) { redClient.Del("relay:config:limitedDomain", domain.Host).Result() } +func TestHandleInboxValidCreateAsAnnounceNote(t *testing.T) { + activity := mockActivity("Create") + actor := mockActor("Person") + domain, _ := url.Parse(activity.Actor) + s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + handleInbox(w, r, mockActivityDecoderProvider(&activity, &actor)) + })) + defer s.Close() + + redClient.HSet("relay:subscription:"+domain.Host, "inbox_url", "https://mastodon.test.yukimochi.io/inbox").Result() + redClient.HSet("relay:subscription:example.org", "inbox_url", "https://example.org/inbox").Result() + redClient.HSet("relay:config", "create_as_announce", "1").Result() + relConfig = relayconf.LoadConfig(redClient) + + req, _ := http.NewRequest("POST", s.URL, nil) + client := new(http.Client) + r, err := client.Do(req) + if err != nil { + t.Fatalf("Failed - " + err.Error()) + } + if r.StatusCode != 202 { + t.Fatalf("Failed - StatusCode is not 202") + } + redClient.Del("relay:subscription:" + domain.Host).Result() + redClient.Del("relay:subscription:example.org").Result() + redClient.HSet("relay:config", "create_as_announce", "0").Result() + relConfig = relayconf.LoadConfig(redClient) +} + +func TestHandleInboxValidCreateAsAnnounceNoNote(t *testing.T) { + activity := mockActivity("Create-Article") + actor := mockActor("Person") + domain, _ := url.Parse(activity.Actor) + s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + handleInbox(w, r, mockActivityDecoderProvider(&activity, &actor)) + })) + defer s.Close() + + redClient.HSet("relay:subscription:"+domain.Host, "inbox_url", "https://mastodon.test.yukimochi.io/inbox").Result() + redClient.HSet("relay:subscription:example.org", "inbox_url", "https://example.org/inbox").Result() + redClient.HSet("relay:config", "create_as_announce", "1").Result() + relConfig = relayconf.LoadConfig(redClient) + + req, _ := http.NewRequest("POST", s.URL, nil) + client := new(http.Client) + r, err := client.Do(req) + if err != nil { + t.Fatalf("Failed - " + err.Error()) + } + if r.StatusCode != 202 { + t.Fatalf("Failed - StatusCode is not 202") + } + redClient.Del("relay:subscription:" + domain.Host).Result() + redClient.Del("relay:subscription:example.org").Result() + redClient.HSet("relay:config", "create_as_announce", "0").Result() + relConfig = relayconf.LoadConfig(redClient) +} + func TestHandleInboxUnsubscriptionCreate(t *testing.T) { activity := mockActivity("Create") actor := mockActor("Person")