package human
import (
"context"
"encoding/json"
"errors"
"io"
"net/http"
"net/url"
"strings"
)
var ErrUnsupportedContentType = errors.New("unsupported content type")
// GetHumanFromHTML returns the [Human] contained in the [http.Response].
//
// If Content-Type is unsupported, it returns [ErrUnsupportedContentType].
// Currently, only `text/html` is supported.
//
// [http.Client] is used to retrieve the `human.json` linked in the [http.Response.Body].
func GetHumanFromHTML(ctx context.Context, client *http.Client, resp *http.Response) (*Human, error) {
if !strings.Contains(resp.Header.Get("Content-Type"), "text/html") {
return nil, ErrUnsupportedContentType
}
b, err := io.ReadAll(resp.Body)
resp.Body.Close()
if err != nil {
return nil, err
}
url, err := GetURLFromHTML(b, resp.Request.URL)
if err != nil {
return nil, err
}
if url == nil {
return nil, nil
}
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url.String(), nil)
if err != nil {
return nil, err
}
resp, err = client.Do(req)
if err != nil {
return nil, err
}
b, err = io.ReadAll(resp.Body)
resp.Body.Close()
if err != nil {
return nil, err
}
var h Human
return &h, json.Unmarshal(b, &h)
}
// GetURLFromHTML returns the [url.URL] extracted from the raw HTML.
// Base is the URL containing the HTML.
//
// Returns nil if nothing is found.
func GetURLFromHTML(b []byte, base *url.URL) (*url.URL, error) {
content := string(b)
runed := []rune(content)
i := strings.Index(content, `= 0 && i+6 < len(runed) {
content = string(runed[i+6:])
runed = []rune(content)
args := parseArgs(content)
if args["rel"] == "human-json" {
href, ok := args["href"]
if ok {
u, err := url.Parse(href)
if err != nil {
return nil, err
}
if u.Host != "" {
return u, nil
}
path := u.Path
*u = *base
if strings.HasPrefix(path, "/") {
u.Path = path
} else {
u = u.JoinPath(path)
}
return u, nil
}
}
i = strings.Index(content, `' && (i+1 == len(content) || string(content[i:i+2]) != "/>") {
curr := content[i]
if !sep {
switch curr {
case '=':
sep = true
case ' ':
sep = false
if key.Len() > 0 {
res[key.String()] = ""
}
key.Reset()
default:
key.WriteRune(curr)
}
} else {
if value.Len() == 0 && curr == '"' {
quote = true
} else if (curr == '"' && quote) || curr == ' ' && !quote {
quote = false
sep = false
res[key.String()] = value.String()
key.Reset()
value.Reset()
} else {
value.WriteRune(curr)
}
}
i++
}
if key.Len() > 0 {
res[key.String()] = value.String()
}
return res
}