You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

205 lines
4.7 KiB

package altvr
import (
"errors"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"strings"
)
/*
X-CSRF-Token: 14VIIeFLgADJVsdemydLkdyEq61KFedTfpC5AGuunv4Svodeu86AysE+VADloZv6c0l9yqCcXWtZ3hP1p93qrQ==
User-Agent: UnityPlayer/2019.4.2f1 (UnityWebRequest/1.0, libcurl/7.52.0-DEV)
X-AppName: altspace_vr_client
X-AppVersion: 4.0.95.d065d
X-AltspaceVR-Version: AltspaceVR-App 4.0.95.d065d
Authorization: Token <token> // _Positron_session
X-Unity-Version: 2019.4.2f1
// {"reason":"bad_credentials"}
*/
const (
// Host is the API host
host = "account.altvr.com"
)
var (
errorLogin = errors.New("Not logged in")
etags = map[string]etag{}
)
type etag struct {
key string
date string
body []byte
}
// Clear HTTP response cache.
func flushEtags() {
for url := range etags {
delete(etags, url)
}
}
func jarGetToken(jar http.CookieJar, u *url.URL) string {
for _, cookie := range jar.Cookies(u) {
if cookie.Name == "_Positron_session" {
return cookie.Value
}
}
return ""
}
func (avr *AltVR) reqSetAuthToken(r *http.Request, u *url.URL) {
token := jarGetToken(avr.jar, u)
if token != "" {
r.Header.Set("Authorization", "Token "+token)
}
}
func (avr *AltVR) get(path string) ([]byte, error) {
apiURL, err := absolutePath(path)
if err != nil {
return nil, err
}
req, err := avr.buildGetRequest(apiURL)
if err != nil {
return nil, err
}
client := &http.Client{Jar: avr.jar}
resp, err := client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
// Check for no changes
if resp.StatusCode == http.StatusNotModified {
return etags[apiURL.String()].body, nil
}
if resp.StatusCode == 401 {
return nil, errorLogin
}
if resp.StatusCode != 200 {
return nil, fmt.Errorf("Page unavailable (%d)", resp.StatusCode)
}
/*
ctype := resp.Header.Get("content-Type")
if !strings.HasPrefix(ctype, "application/json") {
return "", errors.New("Invalid content type")
}
*/
return avr.updateEtagCache(apiURL.String(), resp)
}
func (avr *AltVR) post(path string, reader *strings.Reader) ([]byte, error) {
apiURL, err := absolutePath(path)
if err != nil {
return nil, err
}
req, err := avr.buildPostRequest(apiURL, reader)
if err != nil {
return nil, err
}
client := &http.Client{Jar: avr.jar}
resp, err := client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode == 401 {
return nil, errorLogin
}
if resp.StatusCode != 200 && resp.StatusCode != 201 {
return nil, fmt.Errorf("Page unavailable (%d)", resp.StatusCode)
}
/*
ctype := resp.Header.Get("content-Type")
if !strings.HasPrefix(ctype, "application/json") {
return "", errors.New("Invalid content type")
}
*/
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
return body, nil
}
func (avr *AltVR) updateEtagCache(apiURL string, resp *http.Response) ([]byte, error) {
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
key := resp.Header.Get("ETag")
date := resp.Header.Get("Date")
if key == "" /*|| date == ""*/ {
return body, err
}
etags[apiURL] = etag{key, date, body}
return body, err
}
func (avr *AltVR) buildRequest(req *http.Request, apiURL *url.URL) (*http.Request, error) {
req.Header.Set("User-Agent", "UnityPlayer/2019.4.2f1 (UnityWebRequest/1.0, libcurl/7.52.0-DEV)")
req.Header.Set("X-AppName", "altspace_vr_client")
req.Header.Set("X-AppVersion", "4.0.95.d065d")
req.Header.Set("X-AltspaceVR-Version", "AltspaceVR-App 4.0.95.d065d")
req.Header.Set("X-Unity-Version", "2019.4.2f1")
req.Header.Set("Accept-Encoding", "identity")
req.Header.Set("X-CSRF-Token", avr.csrfToken)
req.Header.Set("Accept", "*/*")
avr.reqSetAuthToken(req, apiURL)
return req, nil
}
func (avr *AltVR) buildGetRequest(apiURL *url.URL) (*http.Request, error) {
req, err := http.NewRequest("GET", apiURL.String(), nil)
if err != nil {
return nil, err
}
if _, present := etags[apiURL.String()]; present {
req.Header.Add("If-None-Match", etags[apiURL.String()].key)
//req.Header.Add("If-Modified-Since", etags[apiURL.String()].date)
}
return avr.buildRequest(req, apiURL)
}
func (avr *AltVR) buildPostRequest(apiURL *url.URL, reader *strings.Reader) (*http.Request, error) {
req, err := http.NewRequest("POST", apiURL.String(), reader)
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", "application/json; charset=utf8")
return avr.buildRequest(req, apiURL)
}
func absolutePath(path string) (*url.URL, error) {
if !strings.HasPrefix(path, "/") {
path = "/" + path
}
apiURL, err := url.Parse(api() + path)
query := apiURL.Query()
if err != nil {
return nil, err
}
apiURL.RawQuery = query.Encode()
return apiURL, nil
}
func api() string {
return "https://" + host + "/api"
}