package activitypub import ( "crypto/rsa" "encoding/json" "errors" "io/ioutil" "net/http" "net/url" "time" cache "github.com/patrickmn/go-cache" uuid "github.com/satori/go.uuid" keyloader "github.com/yukimochi/Activity-Relay/KeyLoader" ) // PublicKey : Activity Certificate. type PublicKey struct { ID string `json:"id,omitempty"` Owner string `json:"owner,omitempty"` PublicKeyPem string `json:"publicKeyPem,omitempty"` } //Endpoints : Contains SharedInbox address. type Endpoints struct { SharedInbox string `json:"sharedInbox,omitempty"` } // Image : Image Object. type Image struct { URL string `json:"url,omitempty"` } // Actor : ActivityPub Actor. type Actor struct { Context interface{} `json:"@context,omitempty"` ID string `json:"id,omitempty"` Type string `json:"type,omitempty"` Name string `json:"name,omitempty"` PreferredUsername string `json:"preferredUsername,omitempty"` Summary string `json:"summary,omitempty"` Inbox string `json:"inbox,omitempty"` Endpoints *Endpoints `json:"endpoints,omitempty"` PublicKey PublicKey `json:"publicKey,omitempty"` Icon Image `json:"icon,omitempty"` Image Image `json:"image,omitempty"` } // GenerateSelfKey : Generate relay Actor from Publickey. func (actor *Actor) GenerateSelfKey(hostname *url.URL, publickey *rsa.PublicKey) { actor.Context = []string{"https://www.w3.org/ns/activitystreams", "https://w3id.org/security/v1"} actor.ID = hostname.String() + "/actor" actor.Type = "Service" actor.PreferredUsername = "relay" actor.Inbox = hostname.String() + "/inbox" actor.PublicKey = PublicKey{ hostname.String() + "/actor#main-key", hostname.String() + "/actor", keyloader.GeneratePublicKeyPEMString(publickey), } } // RetrieveRemoteActor : Retrieve Actor from remote instance. func (actor *Actor) RetrieveRemoteActor(url string, uaString string, cache *cache.Cache) error { var err error cacheData, found := cache.Get(url) if found { err = json.Unmarshal(cacheData.([]byte), &actor) if err != nil { cache.Delete(url) } else { return nil } } req, _ := http.NewRequest("GET", url, nil) req.Header.Set("Accept", "application/activity+json") req.Header.Set("User-Agent", uaString) client := new(http.Client) resp, err := client.Do(req) if err != nil { return err } defer resp.Body.Close() if resp.StatusCode != 200 { return errors.New(resp.Status) } data, _ := ioutil.ReadAll(resp.Body) err = json.Unmarshal(data, &actor) if err != nil { return err } cache.Set(url, data, 5*time.Minute) return nil } // Activity : ActivityPub Activity. type Activity struct { Context interface{} `json:"@context,omitempty"` ID string `json:"id,omitempty"` Actor string `json:"actor,omitempty"` Type string `json:"type,omitempty"` Object interface{} `json:"object,omitempty"` To []string `json:"to,omitempty"` Cc []string `json:"cc,omitempty"` } // GenerateResponse : Generate activity response. func (activity *Activity) GenerateResponse(host *url.URL, responseType string) Activity { return Activity{ []string{"https://www.w3.org/ns/activitystreams"}, host.String() + "/activities/" + uuid.NewV4().String(), host.String() + "/actor", responseType, &activity, nil, nil, } } // GenerateAnnounce : Generate Announce of activity. func (activity *Activity) GenerateAnnounce(host *url.URL) Activity { return Activity{ []string{"https://www.w3.org/ns/activitystreams"}, host.String() + "/activities/" + uuid.NewV4().String(), host.String() + "/actor", "Announce", activity.ID, []string{host.String() + "/actor/followers"}, nil, } } // NestedActivity : Unwrap nested activity. func (activity *Activity) NestedActivity() (*Activity, error) { mappedObject := activity.Object.(map[string]interface{}) 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") } // ActivityObject : ActivityPub Activity. type ActivityObject struct { ID string `json:"id,omitempty"` Type string `json:"type,omitempty"` Name string `json:"name,omitempty"` Content string `json:"content,omitempty"` To []string `json:"to,omitempty"` Cc []string `json:"cc,omitempty"` } // Signature : ActivityPub Header Signature. type Signature struct { Type string `json:"type,omitempty"` Creator string `json:"creator,omitempty"` Created string `json:"created,omitempty"` SignatureValue string `json:"signatureValue,omitempty"` } // WebfingerLink : Webfinger Link Resource. type WebfingerLink struct { Rel string `json:"rel,omitempty"` Type string `json:"type,omitempty"` Href string `json:"href,omitempty"` } // WebfingerResource : Webfinger Resource. type WebfingerResource struct { Subject string `json:"subject,omitempty"` Links []WebfingerLink `json:"links,omitempty"` } // GenerateFromActor : Generate Webfinger resource from Actor. func (resource *WebfingerResource) GenerateFromActor(hostname *url.URL, actor *Actor) { resource.Subject = "acct:" + actor.PreferredUsername + "@" + hostname.Host resource.Links = []WebfingerLink{ WebfingerLink{ "self", "application/activity+json", actor.ID, }, } } // NodeinfoResources : Nodeinfo Resources. type NodeinfoResources struct { NodeinfoLinks NodeinfoLinks Nodeinfo Nodeinfo } // NodeinfoLinks : Nodeinfo Link Resource. type NodeinfoLinks struct { Links []NodeinfoLink `json:"links"` } // NodeinfoLink : Nodeinfo Link Resource. type NodeinfoLink struct { Rel string `json:"rel"` Href string `json:"href"` } // Nodeinfo : Nodeinfo Resource. type Nodeinfo struct { Version string `json:"version"` Software NodeinfoSoftware `json:"software"` Protocols []string `json:"protocols"` Services NodeinfoServices `json:"services"` OpenRegistrations bool `json:"openRegistrations"` Usage NodeinfoUsage `json:"usage"` Metadata NodeinfoMetadata `json:"metadata"` } // NodeinfoSoftware : NodeinfoSoftware Resource. type NodeinfoSoftware struct { Name string `json:"name"` Version string `json:"version"` Repository string `json:"repository,omitempty"` } // NodeinfoServices : NodeinfoSoftware Resource. type NodeinfoServices struct { Inbound []string `json:"inbound"` Outbound []string `json:"outbound"` } // NodeinfoUsage : NodeinfoUsage Resource. type NodeinfoUsage struct { Users NodeinfoUsageUsers `json:"users"` } // NodeinfoUsageUsers : NodeinfoUsageUsers Resource. type NodeinfoUsageUsers struct { Total int `json:"total"` ActiveMonth int `json:"activeMonth"` ActiveHalfyear int `json:"activeHalfyear"` } // NodeinfoMetadata : NodeinfoMetadata Resource. type NodeinfoMetadata struct { } // GenerateFromActor : Generate Webfinger resource from Actor. func (resource *NodeinfoResources) GenerateFromActor(hostname *url.URL, actor *Actor, serverVersion string) { resource.NodeinfoLinks.Links = []NodeinfoLink{ NodeinfoLink{ "http://nodeinfo.diaspora.software/ns/schema/2.1", "https://" + hostname.Host + "/nodeinfo/2.1", }, } resource.Nodeinfo = Nodeinfo{ "2.1", NodeinfoSoftware{"activity-relay", serverVersion, "https://github.com/yukimochi/Activity-Relay"}, []string{"activitypub"}, NodeinfoServices{[]string{}, []string{}}, true, NodeinfoUsage{NodeinfoUsageUsers{0, 0, 0}}, NodeinfoMetadata{}, } }