package ap import ( "encoding/json" "errors" "io" "net/http" "time" ) // Data returned from a webfinger response (and also sent when asked for an account via webfinger) type WebfingerData struct { // What this webfinger data refers to. Accounts are usually `acct:username@host` Subject string `json:"subject"` // List of links related to the account Links []struct { // What type of link this is // - `self` refers to the activitypub object and has Type and Href set // - `http://webfinger.net/rel/profile-page` refers to the public webpage of the account and has Type and Href set // - `http://ostatus.org/schema/1.0/subscribe` provides a template for subscribing/following the account. Has Template set // Template will contain a `{uri}` part with which to replace idk yet Relation string `json:"rel"` // The content type of the url Type *string `json:"type"` // The url Href *string `json:"href"` // Template to use for something Template *string `json:"template"` } `json:"links"` } var ErrAccountNotFound = errors.New("account not found") var ErrRemoteServerFailed = errors.New("remote server errored out") // Get the webfinger data for a given full account handle (like @bob@example.com or bob@example.com) func GetAccountWebfinger(fullHandle string) (*WebfingerData, error) { // First get the handle and server domain from the full handle (and ensure it's a correctly formatted one) handle, server, err := SplitFullHandle(fullHandle) if err != nil { return nil, err } webfingerRequest, err := http.NewRequest( // Webfinger requests are GET "GET", // The webfinger url is located at /.well-known/webfinger // The url parameter `resource` tells the remote server what data we want, should always be an account // The prefix `acct:` (where full-handle is in the form of bob@example.com) // tells the remote server that we want the account data of the given account handle "https://"+server+"/.well-known/webfinger?resource=acct:"+handle+"@"+server, // No request body since it's a get request and we only need the url parameter nil, ) if err != nil { return nil, err } // Make a http client with a timeout limit of 30 seconds client := http.Client{Timeout: time.Second * 30} // Then send the request result, err := client.Do(webfingerRequest) if err != nil { return nil, err } // Code 404 indicates that the account doesn't exist if result.StatusCode == 404 { return nil, ErrAccountNotFound } else if result.StatusCode != 200 { // And anything other than 200 or 404 is an error for now // TODO: Add information gathering here to figure out what the remote server's problem is return nil, ErrRemoteServerFailed } // Get the body data from the response data, err := io.ReadAll(result.Body) if err != nil { return nil, err } // Then try and parse it into webfinger data webfinger := WebfingerData{} err = json.Unmarshal(data, &webfinger) if err != nil { return nil, err } return &webfinger, nil }