Dan Anglin
bc18c00c69
Add the ability to create, update, list and delete lists. Note, adding accounts or removing them from lists is not scoped in this PR.
213 lines
5.5 KiB
Go
213 lines
5.5 KiB
Go
package client
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"net/url"
|
|
"time"
|
|
|
|
"codeflow.dananglin.me.uk/apollo/enbas/internal"
|
|
"codeflow.dananglin.me.uk/apollo/enbas/internal/config"
|
|
"codeflow.dananglin.me.uk/apollo/enbas/internal/model"
|
|
)
|
|
|
|
type Client struct {
|
|
Authentication config.Credentials
|
|
HTTPClient http.Client
|
|
UserAgent string
|
|
Timeout time.Duration
|
|
}
|
|
|
|
func NewClientFromConfig() (*Client, error) {
|
|
config, err := config.NewAuthenticationConfigFromFile()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("unable to get the authentication configuration; %w", err)
|
|
}
|
|
|
|
currentAuthentication := config.Credentials[config.CurrentAccount]
|
|
|
|
return NewClient(currentAuthentication), nil
|
|
}
|
|
|
|
func NewClient(authentication config.Credentials) *Client {
|
|
httpClient := http.Client{}
|
|
|
|
gtsClient := Client{
|
|
Authentication: authentication,
|
|
HTTPClient: httpClient,
|
|
UserAgent: internal.UserAgent,
|
|
Timeout: 5 * time.Second,
|
|
}
|
|
|
|
return >sClient
|
|
}
|
|
|
|
func (g *Client) AuthCodeURL() string {
|
|
format := "%s/oauth/authorize?client_id=%s&redirect_uri=%s&response_type=code"
|
|
escapedRedirectURI := url.QueryEscape(internal.RedirectUri)
|
|
|
|
return fmt.Sprintf(
|
|
format,
|
|
g.Authentication.Instance,
|
|
g.Authentication.ClientID,
|
|
escapedRedirectURI,
|
|
)
|
|
}
|
|
|
|
func (g *Client) VerifyCredentials() (model.Account, error) {
|
|
path := "/api/v1/accounts/verify_credentials"
|
|
url := g.Authentication.Instance + path
|
|
|
|
var account model.Account
|
|
|
|
if err := g.sendRequest(http.MethodGet, url, nil, &account); err != nil {
|
|
return model.Account{}, fmt.Errorf("received an error after sending the request to verify the credentials; %w", err)
|
|
}
|
|
|
|
return account, nil
|
|
}
|
|
|
|
func (g *Client) GetInstance() (model.InstanceV2, error) {
|
|
path := "/api/v2/instance"
|
|
url := g.Authentication.Instance + path
|
|
|
|
var instance model.InstanceV2
|
|
|
|
if err := g.sendRequest(http.MethodGet, url, nil, &instance); err != nil {
|
|
return model.InstanceV2{}, fmt.Errorf("received an error after sending the request to get the instance details; %w", err)
|
|
}
|
|
|
|
return instance, nil
|
|
}
|
|
|
|
func (g *Client) GetAccount(accountURI string) (model.Account, error) {
|
|
path := "/api/v1/accounts/lookup?acct=" + accountURI
|
|
url := g.Authentication.Instance + path
|
|
|
|
var account model.Account
|
|
|
|
if err := g.sendRequest(http.MethodGet, url, nil, &account); err != nil {
|
|
return model.Account{}, fmt.Errorf("received an error after sending the request to get the account information; %w", err)
|
|
}
|
|
|
|
return account, nil
|
|
}
|
|
|
|
func (g *Client) GetStatus(statusID string) (model.Status, error) {
|
|
path := "/api/v1/statuses/" + statusID
|
|
url := g.Authentication.Instance + path
|
|
|
|
var status model.Status
|
|
|
|
if err := g.sendRequest(http.MethodGet, url, nil, &status); err != nil {
|
|
return model.Status{}, fmt.Errorf("received an error after sending the request to get the status information; %w", err)
|
|
}
|
|
|
|
return status, nil
|
|
}
|
|
|
|
func (g *Client) GetHomeTimeline(limit int) (model.Timeline, error) {
|
|
path := fmt.Sprintf("/api/v1/timelines/home?limit=%d", limit)
|
|
|
|
timeline := model.Timeline{
|
|
Name: "HOME TIMELINE",
|
|
Statuses: nil,
|
|
}
|
|
|
|
return g.getTimeline(path, timeline)
|
|
}
|
|
|
|
func (g *Client) GetPublicTimeline(limit int) (model.Timeline, error) {
|
|
path := fmt.Sprintf("/api/v1/timelines/public?limit=%d", limit)
|
|
|
|
timeline := model.Timeline{
|
|
Name: "PUBLIC TIMELINE",
|
|
Statuses: nil,
|
|
}
|
|
|
|
return g.getTimeline(path, timeline)
|
|
}
|
|
|
|
func (g *Client) GetListTimeline(listID string, limit int) (model.Timeline, error) {
|
|
path := fmt.Sprintf("/api/v1/timelines/list/%s?limit=%d", listID, limit)
|
|
|
|
timeline := model.Timeline{
|
|
Name: "LIST: " + listID,
|
|
Statuses: nil,
|
|
}
|
|
|
|
return g.getTimeline(path, timeline)
|
|
}
|
|
|
|
func (g *Client) GetTagTimeline(tag string, limit int) (model.Timeline, error) {
|
|
path := fmt.Sprintf("/api/v1/timelines/tag/%s?limit=%d", tag, limit)
|
|
|
|
timeline := model.Timeline{
|
|
Name: "TAG: " + tag,
|
|
Statuses: nil,
|
|
}
|
|
|
|
return g.getTimeline(path, timeline)
|
|
}
|
|
|
|
func (g *Client) getTimeline(path string, timeline model.Timeline) (model.Timeline, error) {
|
|
url := g.Authentication.Instance + path
|
|
|
|
var statuses []model.Status
|
|
|
|
if err := g.sendRequest(http.MethodGet, url, nil, &statuses); err != nil {
|
|
return timeline, fmt.Errorf("received an error after sending the request to get the timeline; %w", err)
|
|
}
|
|
|
|
timeline.Statuses = statuses
|
|
|
|
return timeline, nil
|
|
}
|
|
|
|
func (g *Client) sendRequest(method string, url string, requestBody io.Reader, object any) error {
|
|
ctx, cancel := context.WithTimeout(context.Background(), g.Timeout)
|
|
defer cancel()
|
|
|
|
request, err := http.NewRequestWithContext(ctx, method, url, requestBody)
|
|
if err != nil {
|
|
return fmt.Errorf("unable to create the HTTP request, %w", err)
|
|
}
|
|
|
|
request.Header.Set("Content-Type", "application/json; charset=utf-8")
|
|
request.Header.Set("Accept", "application/json; charset=utf-8")
|
|
request.Header.Set("User-Agent", g.UserAgent)
|
|
|
|
if len(g.Authentication.AccessToken) > 0 {
|
|
request.Header.Set("Authorization", fmt.Sprintf("Bearer %s", g.Authentication.AccessToken))
|
|
}
|
|
|
|
response, err := g.HTTPClient.Do(request)
|
|
if err != nil {
|
|
return fmt.Errorf("received an error after sending the request; %w", err)
|
|
}
|
|
|
|
defer response.Body.Close()
|
|
|
|
if response.StatusCode < http.StatusOK || response.StatusCode >= http.StatusBadRequest {
|
|
return fmt.Errorf(
|
|
"did not receive an OK response from the GoToSocial server; got %d",
|
|
response.StatusCode,
|
|
)
|
|
}
|
|
|
|
if object == nil {
|
|
return nil
|
|
}
|
|
|
|
if err := json.NewDecoder(response.Body).Decode(object); err != nil {
|
|
return fmt.Errorf(
|
|
"unable to decode the response from the GoToSocial server; %w",
|
|
err,
|
|
)
|
|
}
|
|
|
|
return nil
|
|
}
|