ekster/pkg/client/requests.go
Peter Stuifzand f14e6d8249
Some checks failed
continuous-integration/drone/push Build is failing
Add full text search to server
Adds Blevesearch to the server. Every item that is processed by the
server is added to the index and can be returned from the ItemSearch
request.
2021-05-30 22:01:34 +02:00

407 lines
9.8 KiB
Go

package client
import (
"encoding/json"
"fmt"
"io/ioutil"
"log"
"net/http"
"net/http/httputil"
"net/url"
"strings"
"p83.nl/go/ekster/pkg/microsub"
"p83.nl/go/ekster/pkg/sse"
)
// Client is a HTTP client for Microsub
type Client struct {
Me *url.URL
MicrosubEndpoint *url.URL
Token string
Logging bool
}
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))
if c.Logging {
x, _ := httputil.DumpRequestOut(req, true)
log.Printf("REQUEST:\n\n%s\n\n", x)
}
res, err := client.Do(req)
if c.Logging {
x, _ := httputil.DumpResponse(res, true)
log.Printf("RESPONSE:\n\n%s\n\n", x)
}
return res, err
}
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))
if c.Logging {
x, _ := httputil.DumpRequestOut(req, true)
log.Printf("REQUEST:\n\n%s\n\n", x)
}
res, err := client.Do(req)
if c.Logging {
x, _ := httputil.DumpResponse(res, true)
log.Printf("RESPONSE:\n\n%s\n\n", x)
}
if res.StatusCode != 200 {
msg, _ := ioutil.ReadAll(res.Body)
return nil, fmt.Errorf("unsuccessful response: %d: %q", res.StatusCode, string(msg))
}
return res, err
}
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))
res, err := client.Do(req)
if res.StatusCode != 200 {
msg, _ := ioutil.ReadAll(res.Body)
return nil, fmt.Errorf("unsuccessful response: %d: %q", res.StatusCode, string(msg))
}
return res, err
}
// ChannelsGetList gets the channels from a Microsub server
func (c *Client) ChannelsGetList() ([]microsub.Channel, error) {
args := make(map[string]string)
res, err := c.microsubGetRequest("channels", args)
if err != nil {
return []microsub.Channel{}, err
}
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)
}
type channelsResponse struct {
Channels []microsub.Channel `json:"channels"`
}
dec := json.NewDecoder(res.Body)
var channels channelsResponse
err = dec.Decode(&channels)
return channels.Channels, err
}
// TimelineGet gets a timeline from a Microsub server
func (c *Client) TimelineGet(before, after, channel string) (microsub.Timeline, error) {
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
}
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)
}
dec := json.NewDecoder(res.Body)
var timeline microsub.Timeline
err = dec.Decode(&timeline)
if err != nil {
return microsub.Timeline{}, err
}
return timeline, nil
}
// PreviewURL gets a Timeline for a url from a Microsub server
func (c *Client) PreviewURL(url string) (microsub.Timeline, error) {
args := make(map[string]string)
args["url"] = url
res, err := c.microsubGetRequest("preview", args)
if err != nil {
return microsub.Timeline{}, err
}
defer res.Body.Close()
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
}
// FollowGetList gets the list of followed feeds.
func (c *Client) FollowGetList(channel string) ([]microsub.Feed, error) {
args := make(map[string]string)
args["channel"] = channel
res, err := c.microsubGetRequest("follow", args)
if err != nil {
return []microsub.Feed{}, nil
}
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)
}
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
}
// ChannelsCreate creates and new channel on a microsub server.
func (c *Client) ChannelsCreate(name string) (microsub.Channel, error) {
args := make(map[string]string)
args["name"] = name
res, err := c.microsubPostRequest("channels", args)
if err != nil {
return microsub.Channel{}, nil
}
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
}
// ChannelsUpdate updates a channel.
func (c *Client) ChannelsUpdate(uid, name string) (microsub.Channel, error) {
args := make(map[string]string)
args["name"] = name
args["channel"] = uid
res, err := c.microsubPostRequest("channels", args)
if err != nil {
return microsub.Channel{}, err
}
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
}
// ChannelsDelete deletes a channel.
func (c *Client) ChannelsDelete(uid string) error {
args := make(map[string]string)
args["channel"] = uid
args["method"] = "delete"
res, err := c.microsubPostRequest("channels", args)
if err != nil {
return err
}
res.Body.Close()
return nil
}
// FollowURL follows a url.
func (c *Client) FollowURL(channel, url string) (microsub.Feed, error) {
args := make(map[string]string)
args["channel"] = channel
args["url"] = url
res, err := c.microsubPostRequest("follow", args)
if err != nil {
return microsub.Feed{}, err
}
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
}
// UnfollowURL unfollows a url in a channel.
func (c *Client) UnfollowURL(channel, url string) error {
args := make(map[string]string)
args["channel"] = channel
args["url"] = url
res, err := c.microsubPostRequest("unfollow", args)
if err != nil {
return err
}
res.Body.Close()
return nil
}
// Search asks the server to search for the query.
func (c *Client) Search(query string) ([]microsub.Feed, error) {
args := make(map[string]string)
args["query"] = query
res, err := c.microsubPostRequest("search", args)
if err != nil {
return []microsub.Feed{}, err
}
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
}
// 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 {
args := make(map[string]string)
args["channel"] = channel
args["method"] = "mark_read"
data := url.Values{}
for _, uid := range uids {
data.Add("entry[]", uid)
}
res, err := c.microsubPostFormRequest("timeline", args, data)
if err != nil {
return err
}
res.Body.Close()
return nil
}
// Events open an event channel to the server.
func (c *Client) Events() (chan sse.Message, error) {
ch := make(chan sse.Message)
errorCounter := 0
go func() {
for {
res, err := c.microsubGetRequest("events", nil)
if err != nil {
log.Printf("could not request events: %+v", err)
errorCounter++
if errorCounter > 5 {
break
}
continue
}
err = sse.Reader(res.Body, ch)
if err != nil {
log.Printf("could not create reader: %+v", err)
break
}
}
close(ch)
}()
return ch, nil
}