ekster/pkg/client/requests.go

425 lines
10 KiB
Go
Raw Normal View History

/*
* Ekster is a microsub server
* Copyright (c) 2021 The Ekster authors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
2018-05-12 11:08:36 +00:00
package client
import (
"encoding/json"
"fmt"
2018-08-19 19:34:46 +00:00
"io/ioutil"
2018-08-18 09:50:38 +00:00
"log"
2018-05-12 11:08:36 +00:00
"net/http"
2018-08-18 09:50:38 +00:00
"net/http/httputil"
2018-05-12 11:08:36 +00:00
"net/url"
2018-05-16 15:54:34 +00:00
"strings"
2018-05-12 11:08:36 +00:00
"p83.nl/go/ekster/pkg/microsub"
"p83.nl/go/ekster/pkg/sse"
2018-05-12 11:08:36 +00:00
)
2019-03-07 19:55:25 +00:00
// Client is a HTTP client for Microsub
2018-05-12 11:08:36 +00:00
type Client struct {
Me *url.URL
2018-05-12 11:08:36 +00:00
MicrosubEndpoint *url.URL
Token string
2018-08-18 09:50:38 +00:00
2018-08-18 09:58:57 +00:00
Logging bool
2018-05-12 11:08:36 +00:00
}
func (c *Client) microsubGetRequest(action string, args map[string]string) (*http.Response, error) {
client := http.Client{}
u := *c.MicrosubEndpoint
q := u.Query()
q.Add("action", action)
for k, v := range args {
q.Add(k, v)
}
u.RawQuery = q.Encode()
req, err := http.NewRequest(http.MethodGet, u.String(), nil)
if err != nil {
return nil, err
}
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", c.Token))
2018-08-18 09:58:57 +00:00
if c.Logging {
2018-08-18 09:50:38 +00:00
x, _ := httputil.DumpRequestOut(req, true)
log.Printf("REQUEST:\n\n%s\n\n", x)
}
res, err := client.Do(req)
2018-08-18 09:58:57 +00:00
if c.Logging {
2018-08-18 09:50:38 +00:00
x, _ := httputil.DumpResponse(res, true)
log.Printf("RESPONSE:\n\n%s\n\n", x)
}
return res, err
2018-05-12 11:08:36 +00:00
}
func (c *Client) microsubPostRequest(action string, args map[string]string) (*http.Response, error) {
client := http.Client{}
u := *c.MicrosubEndpoint
q := u.Query()
q.Add("action", action)
for k, v := range args {
q.Add(k, v)
}
u.RawQuery = q.Encode()
req, err := http.NewRequest(http.MethodPost, u.String(), nil)
if err != nil {
return nil, err
}
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", c.Token))
2018-08-18 09:58:57 +00:00
if c.Logging {
2018-08-18 09:50:38 +00:00
x, _ := httputil.DumpRequestOut(req, true)
log.Printf("REQUEST:\n\n%s\n\n", x)
}
res, err := client.Do(req)
2018-08-18 09:58:57 +00:00
if c.Logging {
2018-08-18 09:50:38 +00:00
x, _ := httputil.DumpResponse(res, true)
log.Printf("RESPONSE:\n\n%s\n\n", x)
}
2018-08-19 19:34:46 +00:00
if res.StatusCode != 200 {
msg, _ := ioutil.ReadAll(res.Body)
return nil, fmt.Errorf("unsuccessful response: %d: %q", res.StatusCode, strings.TrimSpace(string(msg)))
2018-08-19 19:34:46 +00:00
}
2018-08-18 09:50:38 +00:00
return res, err
2018-05-12 11:08:36 +00:00
}
2018-05-16 15:54:34 +00:00
func (c *Client) microsubPostFormRequest(action string, args map[string]string, data url.Values) (*http.Response, error) {
client := http.Client{}
u := *c.MicrosubEndpoint
q := u.Query()
q.Add("action", action)
for k, v := range args {
q.Add(k, v)
}
u.RawQuery = q.Encode()
req, err := http.NewRequest(http.MethodPost, u.String(), strings.NewReader(data.Encode()))
if err != nil {
return nil, err
}
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", c.Token))
2018-08-19 19:34:46 +00:00
res, err := client.Do(req)
if res.StatusCode != 200 {
msg, _ := ioutil.ReadAll(res.Body)
return nil, fmt.Errorf("unsuccessful response: %d: %q", res.StatusCode, strings.TrimSpace(string(msg)))
2018-08-19 19:34:46 +00:00
}
return res, err
2018-05-16 15:54:34 +00:00
}
2019-03-07 19:55:25 +00:00
// ChannelsGetList gets the channels from a Microsub server
func (c *Client) ChannelsGetList() ([]microsub.Channel, error) {
2018-05-12 11:08:36 +00:00
args := make(map[string]string)
res, err := c.microsubGetRequest("channels", args)
if err != nil {
return []microsub.Channel{}, err
2018-05-12 11:08:36 +00:00
}
defer res.Body.Close()
if res.StatusCode != 200 {
body, err := ioutil.ReadAll(res.Body)
if err != nil {
return []microsub.Channel{}, fmt.Errorf("HTTP Status is not 200, but %d, error while reading body", res.StatusCode)
}
return []microsub.Channel{}, fmt.Errorf("HTTP Status is not 200, but %d: %s", res.StatusCode, body)
}
2018-05-12 11:08:36 +00:00
type channelsResponse struct {
Channels []microsub.Channel `json:"channels"`
}
dec := json.NewDecoder(res.Body)
var channels channelsResponse
err = dec.Decode(&channels)
2018-08-18 09:50:38 +00:00
return channels.Channels, err
2018-05-12 11:08:36 +00:00
}
2019-03-07 19:55:25 +00:00
// TimelineGet gets a timeline from a Microsub server
func (c *Client) TimelineGet(before, after, channel string) (microsub.Timeline, error) {
2018-05-12 11:08:36 +00:00
args := make(map[string]string)
args["after"] = after
args["before"] = before
args["channel"] = channel
res, err := c.microsubGetRequest("timeline", args)
if err != nil {
return microsub.Timeline{}, err
2018-05-12 11:08:36 +00:00
}
defer res.Body.Close()
if res.StatusCode != 200 {
body, err := ioutil.ReadAll(res.Body)
if err != nil {
return microsub.Timeline{}, fmt.Errorf("HTTP Status is not 200, but %d, error while reading body", res.StatusCode)
}
return microsub.Timeline{}, fmt.Errorf("HTTP Status is not 200, but %d: %s", res.StatusCode, body)
}
2018-05-12 11:08:36 +00:00
dec := json.NewDecoder(res.Body)
var timeline microsub.Timeline
err = dec.Decode(&timeline)
if err != nil {
return microsub.Timeline{}, err
2018-05-12 11:08:36 +00:00
}
return timeline, nil
2018-05-12 11:08:36 +00:00
}
2019-03-07 19:55:25 +00:00
// PreviewURL gets a Timeline for a url from a Microsub server
func (c *Client) PreviewURL(url string) (microsub.Timeline, error) {
2018-05-12 11:08:36 +00:00
args := make(map[string]string)
args["url"] = url
res, err := c.microsubPostRequest("preview", args)
2018-05-12 11:08:36 +00:00
if err != nil {
return microsub.Timeline{}, err
2018-05-12 11:08:36 +00:00
}
defer res.Body.Close()
2018-05-12 11:58:01 +00:00
var timeline microsub.Timeline
if res.StatusCode != 200 {
body, err := ioutil.ReadAll(res.Body)
if err != nil {
return timeline, fmt.Errorf("HTTP Status is not 200, but %d, error while reading body", res.StatusCode)
}
return timeline, fmt.Errorf("HTTP Status is not 200, but %d: %s", res.StatusCode, body)
}
dec := json.NewDecoder(res.Body)
err = dec.Decode(&timeline)
if err != nil {
return microsub.Timeline{}, err
}
return timeline, nil
2018-05-12 11:08:36 +00:00
}
// FollowGetList gets the list of followed feeds.
func (c *Client) FollowGetList(channel string) ([]microsub.Feed, error) {
2018-05-12 11:08:36 +00:00
args := make(map[string]string)
args["channel"] = channel
res, err := c.microsubGetRequest("follow", args)
if err != nil {
return []microsub.Feed{}, nil
2018-05-12 11:08:36 +00:00
}
defer res.Body.Close()
if res.StatusCode != 200 {
body, err := ioutil.ReadAll(res.Body)
if err != nil {
return []microsub.Feed{}, fmt.Errorf("HTTP Status is not 200, but %d, error while reading body", res.StatusCode)
}
return []microsub.Feed{}, fmt.Errorf("HTTP Status is not 200, but %d: %s", res.StatusCode, body)
}
2018-05-12 11:08:36 +00:00
dec := json.NewDecoder(res.Body)
type followResponse struct {
Items []microsub.Feed `json:"items"`
}
var response followResponse
err = dec.Decode(&response)
if err != nil {
return []microsub.Feed{}, nil
}
return response.Items, nil
2018-05-12 11:08:36 +00:00
}
// ChannelsCreate creates and new channel on a microsub server.
func (c *Client) ChannelsCreate(name string) (microsub.Channel, error) {
2018-05-12 11:08:36 +00:00
args := make(map[string]string)
args["name"] = name
res, err := c.microsubPostRequest("channels", args)
if err != nil {
return microsub.Channel{}, nil
2018-05-12 11:08:36 +00:00
}
defer res.Body.Close()
var channel microsub.Channel
dec := json.NewDecoder(res.Body)
err = dec.Decode(&channel)
if err != nil {
return microsub.Channel{}, nil
}
return channel, nil
2018-05-12 11:08:36 +00:00
}
// ChannelsUpdate updates a channel.
func (c *Client) ChannelsUpdate(uid, name string) (microsub.Channel, error) {
2018-05-12 11:08:36 +00:00
args := make(map[string]string)
args["name"] = name
args["channel"] = uid
2018-05-12 11:08:36 +00:00
res, err := c.microsubPostRequest("channels", args)
if err != nil {
return microsub.Channel{}, err
2018-05-12 11:08:36 +00:00
}
defer res.Body.Close()
var channel microsub.Channel
dec := json.NewDecoder(res.Body)
err = dec.Decode(&channel)
if err != nil {
return microsub.Channel{}, err
}
return channel, nil
2018-05-12 11:08:36 +00:00
}
// ChannelsDelete deletes a channel.
func (c *Client) ChannelsDelete(uid string) error {
2018-05-12 11:08:36 +00:00
args := make(map[string]string)
args["channel"] = uid
2018-05-12 11:08:36 +00:00
args["method"] = "delete"
res, err := c.microsubPostRequest("channels", args)
if err != nil {
return err
2018-05-12 11:08:36 +00:00
}
res.Body.Close()
return nil
2018-05-12 11:08:36 +00:00
}
// FollowURL follows a url.
func (c *Client) FollowURL(channel, url string) (microsub.Feed, error) {
2018-05-12 11:08:36 +00:00
args := make(map[string]string)
args["channel"] = channel
args["url"] = url
res, err := c.microsubPostRequest("follow", args)
if err != nil {
return microsub.Feed{}, err
2018-05-12 11:08:36 +00:00
}
defer res.Body.Close()
var feed microsub.Feed
dec := json.NewDecoder(res.Body)
err = dec.Decode(&feed)
if err != nil {
return microsub.Feed{}, err
}
return feed, nil
2018-05-12 11:08:36 +00:00
}
// UnfollowURL unfollows a url in a channel.
func (c *Client) UnfollowURL(channel, url string) error {
2018-05-12 11:08:36 +00:00
args := make(map[string]string)
args["channel"] = channel
args["url"] = url
res, err := c.microsubPostRequest("unfollow", args)
if err != nil {
return err
2018-05-12 11:08:36 +00:00
}
res.Body.Close()
return nil
2018-05-12 11:08:36 +00:00
}
// Search asks the server to search for the query.
func (c *Client) Search(query string) ([]microsub.Feed, error) {
2018-05-12 11:08:36 +00:00
args := make(map[string]string)
args["query"] = query
res, err := c.microsubPostRequest("search", args)
if err != nil {
return []microsub.Feed{}, err
2018-05-12 11:08:36 +00:00
}
type searchResponse struct {
Results []microsub.Feed `json:"results"`
}
defer res.Body.Close()
var response searchResponse
dec := json.NewDecoder(res.Body)
err = dec.Decode(&response)
if err != nil {
return []microsub.Feed{}, err
}
return response.Results, nil
2018-05-12 11:08:36 +00:00
}
// ItemSearch send a search request to the server
func (c *Client) ItemSearch(channel, query string) ([]microsub.Item, error) {
args := make(map[string]string)
args["query"] = query
args["channel"] = channel
res, err := c.microsubPostRequest("search", args)
if err != nil {
return []microsub.Item{}, err
}
type searchResponse struct {
Items []microsub.Item `json:"items"`
}
defer res.Body.Close()
var response searchResponse
dec := json.NewDecoder(res.Body)
err = dec.Decode(&response)
if err != nil {
return []microsub.Item{}, err
}
return response.Items, nil
}
// MarkRead marks an item read on the server.
func (c *Client) MarkRead(channel string, uids []string) error {
2018-05-16 15:54:34 +00:00
args := make(map[string]string)
args["channel"] = channel
args["method"] = "mark_read"
2018-05-12 11:08:36 +00:00
data := url.Values{}
for _, uid := range uids {
data.Add("entry[]", uid)
}
res, err := c.microsubPostFormRequest("timeline", args, data)
if err != nil {
return err
2018-05-12 11:08:36 +00:00
}
res.Body.Close()
return nil
2018-05-12 11:08:36 +00:00
}
// Events open an event channel to the server.
func (c *Client) Events() (chan sse.Message, error) {
2019-03-24 15:39:07 +00:00
ch := make(chan sse.Message)
errorCounter := 0
go func() {
for {
res, err := c.microsubGetRequest("events", nil)
if err != nil {
2019-03-24 15:41:13 +00:00
log.Printf("could not request events: %+v", err)
2019-03-24 15:39:07 +00:00
errorCounter++
if errorCounter > 5 {
break
}
continue
}
err = sse.Reader(res.Body, ch)
if err != nil {
2019-03-24 15:41:13 +00:00
log.Printf("could not create reader: %+v", err)
2019-03-24 15:39:07 +00:00
break
}
}
close(ch)
}()
return ch, nil
}