Some checks failed
continuous-integration/drone/push Build is failing
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.
407 lines
9.8 KiB
Go
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
|
|
}
|