-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Largitdata
authored and
Largitdata
committed
Oct 15, 2021
0 parents
commit 14f878a
Showing
5 changed files
with
294 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
module github.com/HoMuChen/linenotify | ||
|
||
go 1.17 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,160 @@ | ||
package linenotify | ||
|
||
import ( | ||
"encoding/json" | ||
"fmt" | ||
"io/ioutil" | ||
"net/http" | ||
"net/url" | ||
"os/exec" | ||
"strings" | ||
"runtime" | ||
) | ||
|
||
const ( | ||
LINE_AUTH_PATH = "https://notify-bot.line.me/oauth/authorize" | ||
LINE_TOKEN_PATH = "https://notify-bot.line.me/oauth/token" | ||
NOTIFY_HOST = "https://notify-api.line.me/api/notify" | ||
) | ||
|
||
type client struct { | ||
id string | ||
secret string | ||
callback *url.URL | ||
token string | ||
done chan error | ||
} | ||
|
||
func New(id, secret, callback string) (*client, error) { | ||
if id == "" || secret == "" { | ||
return nil, fmt.Errorf("client id: %s and secret: %s can not be empty", id, secret) | ||
} | ||
|
||
cb, err := url.Parse(callback) | ||
if err != nil { | ||
return nil, fmt.Errorf("fail to pars callback url with err: %v", err) | ||
} | ||
|
||
c := &client{ | ||
id: id, | ||
secret: secret, | ||
callback: cb, | ||
token: "", | ||
done: make(chan error), | ||
} | ||
|
||
return c, nil | ||
} | ||
|
||
func (c *client) Login() (string, error) { | ||
if err := openbrowser(c.AuthUrl()); err != nil { | ||
return "", fmt.Errorf("fail to open browser, %v", err) | ||
} | ||
|
||
if err := c.waitForCallback(); err != nil { | ||
return "", fmt.Errorf("fail to start server waiting for callback code to exchange access toekn: %v", err) | ||
} | ||
|
||
return c.token, nil | ||
} | ||
|
||
func (c *client) AuthUrl() string { | ||
values := url.Values{} | ||
values.Add("client_id", c.id) | ||
values.Add("response_type", "code") | ||
values.Add("scope", "notify") | ||
values.Add("redirect_uri", c.callback.String()) | ||
values.Add("state", "123") | ||
query := values.Encode() | ||
|
||
url := LINE_AUTH_PATH + "?" + query | ||
|
||
return url | ||
} | ||
|
||
func (c *client) makeTokenBody(code string) string { | ||
values := url.Values{} | ||
values.Add("client_id", c.id) | ||
values.Add("client_secret", c.secret) | ||
values.Add("redirect_uri", c.callback.String()) | ||
values.Add("code", code) | ||
values.Add("grant_type", "authorization_code") | ||
body := values.Encode() | ||
|
||
return body | ||
} | ||
|
||
func (c *client) GetToken(code string) (string, error) { | ||
body := c.makeTokenBody(code) | ||
req, _ := http.NewRequest("POST", LINE_TOKEN_PATH, strings.NewReader(body)) | ||
req.Header.Add("Content-type", "application/x-www-form-urlencoded") | ||
|
||
res, err := http.DefaultClient.Do(req) | ||
if err != nil { | ||
return "", fmt.Errorf("fail to get token with err: %v", err) | ||
} | ||
defer res.Body.Close() | ||
|
||
data, err := ioutil.ReadAll(res.Body) | ||
if err != nil { | ||
return "", fmt.Errorf("fail to read response body with err %v", err) | ||
} | ||
|
||
var buf struct { | ||
Access_token string `json:access_token` | ||
Status int `json:status` | ||
Message string `json:message` | ||
} | ||
if err := json.Unmarshal(data, &buf); err != nil { | ||
return "", fmt.Errorf("fail to json parse body with err %v", err) | ||
} | ||
|
||
if buf.Status >= 400 { | ||
return "", fmt.Errorf("fail to get token with status: %v and message: %s", buf.Status, buf.Message) | ||
} | ||
|
||
return buf.Access_token, nil | ||
} | ||
|
||
func (c *client) handler(w http.ResponseWriter, r *http.Request) { | ||
code := r.URL.Query().Get("code") | ||
token, err := c.GetToken(code) | ||
if err != nil { | ||
c.done <- err | ||
return | ||
} | ||
|
||
w.Write([]byte(token)) | ||
|
||
c.token = token | ||
c.done <- nil | ||
} | ||
|
||
func (c *client) waitForCallback() error { | ||
server := &http.Server{ | ||
Addr: c.callback.Host, | ||
Handler: http.HandlerFunc(c.handler), | ||
} | ||
|
||
go server.ListenAndServe() | ||
defer server.Close() | ||
|
||
return <-c.done | ||
} | ||
|
||
func openbrowser(url string) error { | ||
var err error | ||
|
||
switch runtime.GOOS { | ||
case "linux": | ||
err = exec.Command("xdg-open", url).Start() | ||
case "windows": | ||
err = exec.Command("rundll32", "url.dll,FileProtocolHandler", url).Start() | ||
case "darwin": | ||
err = exec.Command("open", url).Start() | ||
default: | ||
err = fmt.Errorf("unsupported platform") | ||
} | ||
|
||
return err | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
package linenotify | ||
|
||
import ( | ||
"testing" | ||
) | ||
|
||
const ( | ||
ID = "id" | ||
SECRET = "secret" | ||
CB = "http://localhost:5000/callback" | ||
) | ||
|
||
func TestNewWithoutSecret(t *testing.T) { | ||
_, err := New(ID, "", "") | ||
|
||
if err == nil { | ||
t.Errorf("error should not nil") | ||
} | ||
} | ||
|
||
func TestNew(t *testing.T) { | ||
_, err := New(ID, SECRET, CB) | ||
|
||
if err != nil { | ||
t.Errorf("error should be nil, but got: %v", err) | ||
} | ||
} | ||
|
||
func TestAuthUrl(t *testing.T) { | ||
c, _ := New(ID, SECRET, CB) | ||
|
||
url := c.AuthUrl() | ||
expected := "https://notify-bot.line.me/oauth/authorize?client_id=id&redirect_uri=http%3A%2F%2Flocalhost%3A5000%2Fcallback&response_type=code&scope=notify&state=123" | ||
|
||
if url != expected { | ||
t.Errorf("expected: %s, but got: %s", expected, url) | ||
} | ||
} | ||
|
||
func TestMakeTokenBody(t *testing.T) { | ||
c, _ := New(ID, SECRET, CB) | ||
|
||
body := c.makeTokenBody("abctoken") | ||
expected := "client_id=id&client_secret=secret&code=abctoken&grant_type=authorization_code&redirect_uri=http%3A%2F%2Flocalhost%3A5000%2Fcallback" | ||
|
||
if body != expected { | ||
t.Errorf("expected: %s, but got: %s", expected, body) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
package linenotify | ||
|
||
import ( | ||
"encoding/json" | ||
"fmt" | ||
"io/ioutil" | ||
"net/http" | ||
"net/url" | ||
"strings" | ||
) | ||
|
||
|
||
func (c *client) Send(token, message string) error { | ||
req := c.makeNotifyRequest(token, message) | ||
res, err := http.DefaultClient.Do(req) | ||
if err != nil { | ||
return fmt.Errorf("fail to post to notify api with err: %v", err) | ||
} | ||
defer res.Body.Close() | ||
|
||
data, err := ioutil.ReadAll(res.Body) | ||
if err != nil { | ||
return fmt.Errorf("fail to read response body with err %v", err) | ||
} | ||
|
||
var buf struct { | ||
Status int `json:status` | ||
Message string `json:message` | ||
} | ||
if err := json.Unmarshal(data, &buf); err != nil { | ||
return fmt.Errorf("fail to json parse body with err %v", err) | ||
} | ||
if buf.Status >= 400 { | ||
return fmt.Errorf("fail to send message with status: %v and res message: %s", buf.Status, buf.Message) | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func (c *client) makeNotifyRequest(token, message string) *http.Request { | ||
values := url.Values{} | ||
values.Add("message", message) | ||
body := values.Encode() | ||
|
||
req, _ := http.NewRequest("POST", NOTIFY_HOST, strings.NewReader(body)) | ||
req.Header.Add("Content-type", "application/x-www-form-urlencoded") | ||
req.Header.Add("Authorization", "Bearer " + token) | ||
|
||
return req | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
package linenotify | ||
|
||
import ( | ||
"testing" | ||
"net/http" | ||
"strings" | ||
) | ||
|
||
func TestSend(t *testing.T) { | ||
c, _ := New(ID, SECRET, CB) | ||
|
||
message := "this is a message for test" | ||
req := c.makeNotifyRequest("token", message) | ||
|
||
if req.Method != http.MethodPost { | ||
t.Error() | ||
} | ||
if req.Header.Get("Content-Type") != "application/x-www-form-urlencoded" { | ||
t.Error() | ||
} | ||
if !strings.Contains(req.Header.Get("Authorization"), "token") { | ||
t.Error() | ||
} | ||
|
||
err := req.ParseForm() | ||
if err != nil { | ||
t.Error(err) | ||
} | ||
if req.Form.Get("message") != message { | ||
t.Errorf("form data with key message should be %s, but got: %s", message, req.Form.Get("message")) | ||
} | ||
} |