Merge remote-tracking branch 'upstream/develop' into mckael-experiments_2
Conflicts:
cmd/gondole-cli/main.go
--- a/app.go Wed Apr 12 18:07:56 2017 +0200
+++ b/app.go Wed Apr 12 22:26:52 2017 +0200
@@ -2,30 +2,39 @@
import (
"encoding/json"
+ "log"
+ "net/url"
+ "strings"
+
"github.com/sendgrid/rest"
- "log"
- "strings"
)
-var ()
-
type registerApp struct {
- ID string `json:"id"`
+ ID int `json:"id"`
ClientID string `json:"client_id"`
ClientSecret string `json:"client_secret"`
}
// NewApp registers a new instance
-func NewApp(name string, scopes []string, redirectURI, baseURL string) (g *Client, err error) {
- var endpoint string
+func NewApp(name string, scopes []string, redirectURI, instanceURL string) (g *Client, err error) {
+ if instanceURL == "" {
+ instanceURL = defaultInstanceURL
+ }
- if baseURL != "" {
- endpoint = baseURL
+ if !strings.Contains(instanceURL, "://") {
+ instanceURL = "https://" + instanceURL
+ }
+
+ apiPath := instanceURL + defaultAPIPath
+
+ if _, err := url.ParseRequestURI(apiPath); err != nil {
+ return nil, err
}
g = &Client{
- Name: name,
- APIBase: endpoint,
+ Name: name,
+ APIBase: apiPath,
+ InstanceURL: instanceURL,
}
req := g.prepareRequest("apps")
@@ -50,16 +59,8 @@
log.Fatalf("error can not register app: %v", err)
}
- if err != nil {
- log.Fatalf("error: can not write token for %s", name)
- }
-
- g = &Client{
- Name: name,
- ID: resp.ClientID,
- Secret: resp.ClientSecret,
- APIBase: endpoint,
- }
+ g.ID = resp.ClientID
+ g.Secret = resp.ClientSecret
return
}
--- a/cmd/gondole-cli/main.go Wed Apr 12 18:07:56 2017 +0200
+++ b/cmd/gondole-cli/main.go Wed Apr 12 22:26:52 2017 +0200
@@ -1,29 +1,25 @@
package main
import (
- "github.com/keltia/gondole"
- "github.com/urfave/cli"
"log"
"os"
"strings"
+
+ "github.com/urfave/cli"
+
+ "github.com/McKael/gondole"
)
var (
- fVerbose bool
- fAuthMethod string
- fInstance string
- fScopes string
+ fVerbose bool
+ fInstance string
+ fAuthMethod string
+ fUsername, fPassword string
+ fScopes string
instance *gondole.Client
cnf *Server
- // For bootstrapping, override the API endpoint w/o any possible /api/vN, that is
- // supplied by the library
- APIEndpoint string
-
- // Deduced though the full instance URL when registering
- InstanceName string
-
// Default scopes
ourScopes = []string{
"read",
@@ -31,18 +27,21 @@
"follow",
}
+ defaultInstanceURL = "https://mastodon.social"
+
authMethods = map[string]bool{
"basic": true,
"oauth2": true,
}
)
-// Config holds our parameters
+// Server holds our application details
type Server struct {
ID string `json:"id"`
Name string `json:"name"`
BearerToken string `json:"bearer_token"`
- BaseURL string `json:"base_url"` // Allow for overriding APIEndpoint on registration
+ APIBase string `json:"base_url"`
+ InstanceURL string `json:"base_url"`
}
type Config struct {
@@ -60,29 +59,37 @@
var config Config
var scopes []string
+ instanceURL := defaultInstanceURL
if fInstance != "" {
- InstanceName = basename(fInstance)
- APIEndpoint = filterURL(fInstance)
+ if strings.Contains(fInstance, "://") {
+ instanceURL = fInstance
+ } else {
+ instanceURL = "https://" + fInstance
+ }
}
+ instanceName := basename(instanceURL)
+
if fAuthMethod != "" && authMethods[fAuthMethod] {
}
// Load configuration, will register if none is found
- cnf, err = LoadConfig(InstanceName)
+ cnf, err = LoadConfig(instanceName)
if err != nil {
// Nothing exist yet
- config := Config{
- Default: InstanceName,
- Auth: "basic",
- User: "",
- Password: "",
- }
+ /*
+ defName := Config{
+ Default: instanceName,
+ Auth: "basic",
+ User: "",
+ Password: "",
+ }
+ */
err = config.Write()
if err != nil {
- log.Fatalf("error: can not write config for %s", InstanceName)
+ log.Fatalf("error: can not write config for %s", instanceName)
}
// Now register this through OAuth
@@ -92,15 +99,16 @@
scopes = ourScopes
}
- instance, err = gondole.NewApp("gondole-cli", scopes, gondole.NoRedirect, fInstance)
+ instance, err = gondole.NewApp("gondole-cli", scopes, gondole.NoRedirect, instanceURL)
server := &Server{
ID: instance.ID,
Name: instance.Name,
BearerToken: instance.Secret,
- BaseURL: instance.APIBase,
+ APIBase: instance.APIBase,
+ InstanceURL: instance.InstanceURL,
}
- err = server.WriteToken(InstanceName)
+ err = server.WriteToken(instanceName)
if err != nil {
log.Fatalf("error: can not write token for %s", instance.Name)
}
@@ -115,8 +123,11 @@
}
}
+
// Log in to the instance
- err = instance.Login()
+ if fAuthMethod == "basic" {
+ err = instance.LoginBasic(fUsername, fPassword)
+ }
return err
}
@@ -125,7 +136,7 @@
cli.VersionFlag = cli.BoolFlag{Name: "version, V"}
cli.VersionPrinter = func(c *cli.Context) {
- log.Printf("API wrapper: %s Mastodon CLI: %s\n", c.App.Version, gondole.APIVersion)
+ log.Printf("API wrapper: %s Mastodon CLI: %s\n", c.App.Version, gondole.GondoleVersion)
}
}
@@ -135,7 +146,7 @@
app.Name = "gondole"
app.Usage = "Mastodon CLI interface"
app.Author = "Ollivier Robert <roberto@keltia.net>"
- app.Version = gondole.APIVersion
+ app.Version = gondole.GondoleVersion
//app.HideVersion = true
app.Before = setupEnvironment
@@ -156,6 +167,16 @@
Usage: "use these scopes",
Destination: &fScopes,
},
+ cli.StringFlag{
+ Name: "username,login",
+ Usage: "user name",
+ Destination: &fUsername,
+ },
+ cli.StringFlag{
+ Name: "password",
+ Usage: "user password",
+ Destination: &fPassword,
+ },
cli.BoolFlag{
Name: "verbose,v",
Usage: "verbose mode",
--- a/cmd/gondole-cli/utils.go Wed Apr 12 18:07:56 2017 +0200
+++ b/cmd/gondole-cli/utils.go Wed Apr 12 22:26:52 2017 +0200
@@ -1,9 +1,9 @@
package main
import (
- "github.com/urfave/cli"
- "net/url"
- "strings"
+ "github.com/urfave/cli"
+ "net/url"
+ "strings"
)
// ByAlphabet is for sorting
@@ -14,23 +14,23 @@
func (a ByAlphabet) Less(i, j int) bool { return a[i].Name < a[j].Name }
func filterURL(in string) (out string) {
- uri, err := url.Parse(in)
- if err != nil {
- out = ""
- } else {
- uri := url.URL{Scheme: uri.Scheme, Host: uri.Host}
- out = uri.String()
- }
- return
+ uri, err := url.Parse(in)
+ if err != nil {
+ out = ""
+ } else {
+ uri := url.URL{Scheme: uri.Scheme, Host: uri.Host}
+ out = uri.String()
+ }
+ return
}
func basename(in string) (out string) {
- uri, err := url.Parse(in)
- if err != nil {
- out = ""
- } else {
- // Remove the :NN part of present
- out = strings.Split(uri.Host, ":")[0]
- }
- return
+ uri, err := url.Parse(in)
+ if err != nil {
+ out = ""
+ } else {
+ // Remove the :NN part of present
+ out = strings.Split(uri.Host, ":")[0]
+ }
+ return
}
--- a/gondole.go Wed Apr 12 18:07:56 2017 +0200
+++ b/gondole.go Wed Apr 12 22:26:52 2017 +0200
@@ -1,57 +1,47 @@
package gondole
import (
- "github.com/sendgrid/rest"
+ "errors"
"fmt"
- "errors"
+
+ "github.com/sendgrid/rest"
)
const (
- APIVersion = "0.0"
+ // GondoleVersion contains the version of the Gondole implementation
+ GondoleVersion = "0.0"
- // That is not overridable
- apiURL = "/api/v1"
+ defaultInstanceURL = "https://mastodon.social"
+ apiVersion = "v1" // That is not overridable
+ defaultAPIPath = "/api/" + apiVersion
- // Fallback instance
- FallBackURL = "https://mastodon.social"
-
+ // NoRedirect is the URI for no redirection in the App registration
NoRedirect = "urn:ietf:wg:oauth:2.0:oob"
)
var (
- APIEndpoint string
ErrAlreadyRegistered = errors.New("App already registered")
)
// prepareRequest insert all pre-defined stuff
func (g *Client) prepareRequest(what string) (req rest.Request) {
- var APIBase string
+ var endPoint string
- // Allow for overriding for registration
- if g.APIBase == "" {
- APIBase = FallBackURL
- } else {
- APIBase = g.APIBase
- }
-
- APIEndpoint = fmt.Sprintf("%s%s/%", APIBase, apiURL, what)
-
+ endPoint = g.APIBase + "/" + what
// Add at least one option, the APIkey if present
hdrs := make(map[string]string)
opts := make(map[string]string)
// Insert our sig
- hdrs["User-Agent"] = fmt.Sprintf("Client/%s", APIVersion)
- hdrs["Authorization"] = fmt.Sprintf("Bearer %s", g.Secret)
+ hdrs["User-Agent"] = fmt.Sprintf("Gondole/%s", GondoleVersion)
+ if g.userToken != nil {
+ hdrs["Authorization"] = fmt.Sprintf("Bearer %s", g.userToken.Access_token)
+ }
req = rest.Request{
- BaseURL: APIEndpoint,
+ BaseURL: endPoint,
Headers: hdrs,
QueryParams: opts,
}
return
}
-
-func (g *Client) Login() (err error) {
- return
-}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/login.go Wed Apr 12 22:26:52 2017 +0200
@@ -0,0 +1,59 @@
+package gondole
+
+import (
+ "encoding/json"
+ "fmt"
+
+ "github.com/sendgrid/rest"
+)
+
+type UserToken struct {
+ Access_token string `json:"access_token"`
+ CreatedAt int `json:"created_at"`
+ Scope string `json:"scope"`
+ TokenType string `json:"token_type"`
+}
+
+func (g *Client) LoginBasic(username, password string) error {
+ if username == "" {
+ return fmt.Errorf("missing username")
+ }
+ if password == "" {
+ return fmt.Errorf("missing password")
+ }
+
+ hdrs := make(map[string]string)
+ opts := make(map[string]string)
+
+ hdrs["User-Agent"] = "Gondole/" + GondoleVersion
+ hdrs["Authorization"] = "Bearer %s" + g.Secret
+
+ opts["grant_type"] = "password"
+ opts["client_id"] = g.ID
+ opts["client_secret"] = g.Secret
+ opts["username"] = username
+ opts["password"] = password
+
+ req := rest.Request{
+ BaseURL: g.InstanceURL + "/oauth/token",
+ Headers: hdrs,
+ QueryParams: opts,
+ Method: rest.Post,
+ }
+
+ r, err := rest.API(req)
+ if err != nil {
+ return err
+ }
+
+ var resp UserToken
+
+ println(r.Body)
+ err = json.Unmarshal([]byte(r.Body), &resp)
+ if err != nil {
+ return err
+ }
+
+ g.userToken = &resp
+ return nil
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/timelines.go Wed Apr 12 22:26:52 2017 +0200
@@ -0,0 +1,37 @@
+package gondole
+
+import (
+ "encoding/json"
+ "fmt"
+ "strings"
+
+ "github.com/sendgrid/rest"
+)
+
+// GetTimelines returns a timeline (a list of statuses
+// timeline can be "home", "public", or a hashtag (":hashtag")
+func (g *Client) GetTimelines(timeline string) ([]Status, error) {
+ var endPoint string
+ var tl []Status
+
+ if timeline == "home" || timeline == "public" {
+ endPoint = "timelines/" + timeline
+ } else if strings.HasPrefix(timeline, ":") {
+ endPoint = "timelines/tag/" + timeline
+ } else {
+ return tl, fmt.Errorf("GetTimelines: bad timelines argument")
+ }
+
+ req := g.prepareRequest(endPoint)
+ r, err := rest.API(req)
+ if err != nil {
+ return tl, fmt.Errorf("timelines API query: %s", err.Error())
+ }
+
+ err = json.Unmarshal([]byte(r.Body), &tl)
+ if err != nil {
+ return tl, fmt.Errorf("timelines API: %s", err.Error())
+ }
+
+ return tl, nil
+}
--- a/types.go Wed Apr 12 18:07:56 2017 +0200
+++ b/types.go Wed Apr 12 22:26:52 2017 +0200
@@ -4,11 +4,15 @@
"time"
)
+// Client contains data for a gondole client application
type Client struct {
- Name string
- ID string
- Secret string
- APIBase string
+ Name string
+ ID string
+ Secret string
+ APIBase string
+ InstanceURL string
+
+ userToken *UserToken
}
/*