139 lines
2.9 KiB
Go
139 lines
2.9 KiB
Go
package cmd
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"io/ioutil"
|
|
"os"
|
|
"strings"
|
|
|
|
"github.com/bogosj/tesla"
|
|
"github.com/evcc-io/evcc/util/request"
|
|
"github.com/manifoldco/promptui"
|
|
"github.com/skratchdot/open-golang/open"
|
|
"golang.org/x/oauth2"
|
|
)
|
|
|
|
// copied from https://github.com/bogosj/tesla
|
|
func getUsernameAndPassword() (string, string, error) {
|
|
user, err := (&promptui.Prompt{
|
|
Label: "Username",
|
|
Pointer: promptui.PipeCursor,
|
|
Validate: func(s string) error {
|
|
if len(s) == 0 {
|
|
return errors.New("len(s) == 0")
|
|
}
|
|
return nil
|
|
},
|
|
}).Run()
|
|
if err != nil {
|
|
return "", "", err
|
|
}
|
|
|
|
password, err := (&promptui.Prompt{
|
|
Label: "Password",
|
|
Mask: '*',
|
|
Pointer: promptui.PipeCursor,
|
|
Validate: func(s string) error {
|
|
if len(s) == 0 {
|
|
return errors.New("len(s) == 0")
|
|
}
|
|
return nil
|
|
},
|
|
}).Run()
|
|
if err != nil {
|
|
return "", "", err
|
|
}
|
|
|
|
return user, password, nil
|
|
}
|
|
|
|
func codePrompt(ctx context.Context, devices []tesla.Device) (tesla.Device, string, error) {
|
|
var i int
|
|
if len(devices) > 1 {
|
|
var err error
|
|
i, _, err = (&promptui.Select{
|
|
Label: "Device",
|
|
Items: devices,
|
|
Pointer: promptui.PipeCursor,
|
|
}).Run()
|
|
if err != nil {
|
|
return tesla.Device{}, "", fmt.Errorf("select device: %w", err)
|
|
}
|
|
}
|
|
|
|
code, err := (&promptui.Prompt{
|
|
Label: "Passcode",
|
|
Pointer: promptui.PipeCursor,
|
|
Validate: func(s string) error {
|
|
if len(s) != 6 {
|
|
return errors.New("len(s) != 6")
|
|
}
|
|
return nil
|
|
},
|
|
}).Run()
|
|
if err != nil {
|
|
return tesla.Device{}, "", err
|
|
}
|
|
|
|
return devices[i], strings.TrimSpace(code), nil
|
|
}
|
|
|
|
func captchaPrompt(ctx context.Context, svg io.Reader) (string, error) {
|
|
tmpFile, err := ioutil.TempFile(os.TempDir(), "evcc-*.svg")
|
|
if err != nil {
|
|
return "", fmt.Errorf("cannot create temp file: %w", err)
|
|
}
|
|
|
|
if _, err := io.Copy(tmpFile, svg); err != nil {
|
|
return "", fmt.Errorf("cannot write temp file: %w", err)
|
|
}
|
|
|
|
_ = tmpFile.Close()
|
|
|
|
if err := open.Run(tmpFile.Name()); err != nil {
|
|
return "", fmt.Errorf("cannot open captcha for display: %w", err)
|
|
}
|
|
|
|
fmt.Println("Captcha is now being opened in default application for svg files.")
|
|
|
|
captcha, err := (&promptui.Prompt{
|
|
Label: "Captcha",
|
|
Pointer: promptui.PipeCursor,
|
|
Validate: func(s string) error {
|
|
if len(s) < 4 {
|
|
return errors.New("len(s) < 4")
|
|
}
|
|
return nil
|
|
},
|
|
}).Run()
|
|
|
|
return strings.TrimSpace(captcha), err
|
|
}
|
|
|
|
func teslaToken() (*oauth2.Token, error) {
|
|
username, password, err := getUsernameAndPassword()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
ctx := context.WithValue(context.Background(), oauth2.HTTPClient, request.NewHelper(log).Client)
|
|
client, err := tesla.NewClient(
|
|
ctx,
|
|
tesla.WithMFAHandler(codePrompt),
|
|
tesla.WithCaptchaHandler(captchaPrompt),
|
|
tesla.WithCredentials(username, password),
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
token, err := client.Token()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return token, nil
|
|
}
|