Merge remote-tracking branch 'upstream/develop' into mckael-experiments_2
authorMikael Berthe <mikael@lilotux.net>
Wed, 12 Apr 2017 22:26:52 +0200
changeset 89 8a7a33bec6e1
parent 88 df00ec8423fe (diff)
parent 82 09f5e04b1b37 (current diff)
child 90 686374268c54
Merge remote-tracking branch 'upstream/develop' into mckael-experiments_2 Conflicts: cmd/gondole-cli/main.go
cmd/gondole-cli/main.go
gondole.go
login.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
 }
 
 /*