-
-
Save neetsdkasu/4a26ced27af45a71a9c9b7c41bc22b0e to your computer and use it in GitHub Desktop.
Hatena OAuth
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
*.json | |
*.exe | |
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
package main | |
import ( | |
"encoding/json" | |
"io/ioutil" | |
"os" | |
) | |
func load(filename string, data interface{}) (bool, error) { | |
blob, err := ioutil.ReadFile(filename) | |
if err != nil { | |
if os.IsNotExist(err) { | |
return false, nil | |
} | |
return false, err | |
} | |
err = json.Unmarshal(blob, data) | |
if err != nil { | |
return false, err | |
} | |
return true, nil | |
} | |
func loadConsumer() (*HatenaConsumer, error) { | |
cons := new(HatenaConsumer) | |
ok, err := load("consumer.json", cons) | |
if err != nil { | |
return nil, err | |
} | |
if !ok { | |
cons = new(HatenaConsumer) | |
} | |
return cons, nil | |
} | |
func loadRequestToken() (*HatenaRequestToken, error) { | |
reqToken := new(HatenaRequestToken) | |
ok, err := load("requestToken.json", reqToken) | |
if !ok { | |
reqToken = nil | |
} | |
return reqToken, err | |
} | |
func loadAccessToken() (*HatenaAccessToken, error) { | |
accToken := new(HatenaAccessToken) | |
ok, err := load("accessToken.json", accToken) | |
if !ok { | |
accToken = nil | |
} | |
return accToken, err | |
} | |
func loadWsse() (*HatenaWsse, error) { | |
wsse := new(HatenaWsse) | |
ok, err := load("wsse.json", wsse) | |
if !ok { | |
wsse = new(HatenaWsse) | |
} | |
return wsse, err | |
} | |
func loadBlog() (*HatenaBlog, error) { | |
blog := new(HatenaBlog) | |
ok, err := load("hatena_blog.json", blog) | |
if !ok { | |
blog = new(HatenaBlog) | |
} | |
return blog, err | |
} | |
func save(filename string, data interface{}) error { | |
blob, err := json.Marshal(data) | |
if err != nil { | |
return err | |
} | |
return ioutil.WriteFile(filename, blob, 0664) | |
} | |
func saveConsumer(cons *HatenaConsumer) error { | |
return save("consumer.json", cons) | |
} | |
func saveRequestToken(reqToken *HatenaRequestToken) error { | |
return save("requestToken.json", reqToken) | |
} | |
func saveAccessToken(accToken *HatenaAccessToken) error { | |
return save("accessToken.json", accToken) | |
} | |
func saveWsse(wsse *HatenaWsse) error { | |
return save("wsse.json", wsse) | |
} | |
func saveBlog(blog *HatenaBlog) error { | |
return save("hatena_blog.json", blog) | |
} |
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
module hatena-oauth | |
go 1.16 |
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
package main | |
type HatenaApplicationsMy struct { | |
UrlName string `json:"url_name"` | |
DisplayName string `json:"display_name"` | |
ProfileImageUrl string `json:"profile_image_url"` | |
} | |
type HatenaBookmarkRest1My struct { | |
Name string `json:"name"` | |
PlusUser bool `json:"plususer"` | |
Private bool `json:"private"` | |
IsOAuthTwitter bool `json:"is_oauth_twitter"` | |
IsOAuthEvernote bool `json:"is_oauth_evernote"` | |
IsOAuthFacebook bool `json:"is_oauth_facebook"` | |
IsOAuthMixi bool `json:"is_oauth_mixi_check"` | |
} | |
type HatenaBookmarkRest1MyTags struct { | |
Tags []*struct { | |
Count int `json:"count"` | |
Tag string `json:"tag"` | |
} | |
} | |
type HatenaBookmarkMySearchResultBookmark struct { | |
Entry struct { | |
Title string `json:"title"` | |
Count int `json:"count"` | |
Url string `json:"url"` | |
EId string `json:"eid"` | |
Snippet string `json:"snippet"` | |
} `json:"entry"` | |
Timestamp float64 `json:"timestamp"` | |
Comment string `json:"comment"` | |
IsPrivateValue int `json:"is_private"` | |
} | |
type HatenaBookmarkMySearchResult struct { | |
Bookmarks []*HatenaBookmarkMySearchResultBookmark `json:"bookmarks"` | |
Meta struct { | |
Total int `json:"total"` | |
Query struct { | |
Original string `json:"original"` | |
Queries []string `json:"queries"` | |
} `json:"query"` | |
Status int `json:"status"` | |
Elapsed float64 `json:"elapsed"` | |
} `json:"meta"` | |
Offset int `json:"-"` | |
} | |
type HatenaBookmarkRest1MyBookmark struct { | |
EId string `json:"eid"` | |
Comment string `json:"comment"` | |
CommentRaw string `json:"comment_raw"` | |
CreatedDateTime string `json:"created_datetime"` | |
CreatedEpoch float64 `json:"created_epoch"` | |
User string `json:"user"` | |
Permalink string `json:"permalink"` | |
Private bool `json:"private"` | |
Tags []string `json:"tags"` | |
Favorites []*HatenaBookmarkRest1MyBookmark `json:"favorites"` | |
} | |
func (this *HatenaBookmarkMySearchResultBookmark) IsPrivate() bool { | |
return this.IsPrivateValue != 0 | |
} | |
func (this *HatenaBookmarkMySearchResult) HasNext() bool { | |
if this == nil { | |
return false | |
} | |
return this.Offset+len(this.Bookmarks) < this.Meta.Total | |
} |
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
package main | |
import ( | |
"bufio" | |
"log" | |
"os" | |
"strings" | |
) | |
func main() { | |
log.Println("program start") | |
defer log.Println("program has finished") | |
state, err := NewState() | |
if err != nil { | |
log.Println(err) | |
return | |
} | |
scn := bufio.NewScanner(os.Stdin) | |
mainloop: | |
for { | |
state.Prompt() | |
if !scn.Scan() { | |
break | |
} | |
cmds := strings.Fields(scn.Text()) | |
if len(cmds) == 0 { | |
continue | |
} | |
switch strings.ToLower(cmds[0]) { | |
case "quit", "exit", "bye": | |
break mainloop | |
} | |
state, err = state.Do(cmds) | |
if err != nil { | |
log.Println(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
package main | |
import ( | |
"encoding/base64" | |
"math/rand" | |
"strings" | |
"time" | |
"unicode" | |
) | |
func GenerateNonce() string { | |
src := make([]byte, 32) | |
rand.Seed(time.Now().Unix()) | |
rand.Read(src) | |
return strings.Join( | |
strings.FieldsFunc( | |
base64.StdEncoding.EncodeToString(src), | |
func(c rune) bool { | |
return !unicode.IsLetter(c) && !unicode.IsDigit(c) | |
}, | |
), | |
"", | |
) | |
} |
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
package main | |
import ( | |
"bytes" | |
"encoding/json" | |
"fmt" | |
"io/ioutil" | |
"log" | |
"net/http" | |
"net/url" | |
"strings" | |
) | |
type Consumer struct { | |
ConsumerKey string `json:"oauth_consumer_key"` | |
ConsumerSecret string `json:"oauth_consumer_secret"` | |
} | |
type RequestToken struct { | |
Token string `json:"oauth_token"` | |
TokenSecret string `json:"oauth_token_secret"` | |
CallbackConfirmed bool `json:"oauth_callback_confirmed"` | |
} | |
type AccessToken struct { | |
Token string `json:"oauth_token"` | |
TokenSecret string `json:"oauth_token_secret"` | |
} | |
func (this *Consumer) TakeRequestToken(u string, form url.Values) (*RequestToken, interface{}, error) { | |
log.Println("Consumer.TakeRequestToken") | |
defer log.Println("end of Consumer.TakeRequestToken") | |
req, err := http.NewRequest( | |
http.MethodPost, | |
u, | |
strings.NewReader(form.Encode()), | |
) | |
if err != nil { | |
return nil, nil, err | |
} | |
params := NewParamaterList(this.ConsumerKey) | |
params.SetOAuthCallbackOob() | |
err = params.SetSignature(req.Method, u, this.ConsumerSecret, "", form) | |
if err != nil { | |
return nil, nil, err | |
} | |
authorization := params.HeaderValue() | |
req.Header.Set("Authorization", authorization) | |
req.Header.Set("Content-Type", "application/x-www-form-urlencoded") | |
resp, err := http.DefaultClient.Do(req) | |
defer func() { | |
if resp != nil && resp.Body != nil { | |
resp.Body.Close() | |
} | |
}() | |
if err != nil { | |
if resp != nil { | |
log.Println(u) | |
log.Println(form) | |
log.Println(req.Header) | |
log.Println(resp.Status) | |
log.Println(resp.Header) | |
} | |
return nil, nil, err | |
} | |
respBody, err := ioutil.ReadAll(resp.Body) | |
if err != nil { | |
return nil, nil, err | |
} | |
if resp.StatusCode != http.StatusOK { | |
log.Println(u) | |
log.Println(form) | |
log.Println(req.Header) | |
log.Println(resp.Status) | |
log.Println(resp.Header) | |
log.Println(string(respBody)) | |
return nil, respBody, fmt.Errorf( | |
"%s %s", | |
resp.Status, | |
string(respBody), | |
) | |
} | |
contentType := resp.Header.Get("Content-Type") | |
if strings.Contains(contentType, "json") { | |
var res interface{} | |
err := json.Unmarshal(respBody, &res) | |
if err != nil { | |
return nil, nil, err | |
} | |
m, ok := res.(map[string]interface{}) | |
if !ok { | |
return nil, respBody, fmt.Errorf("unknown format json") | |
} | |
reqToken := &RequestToken{} | |
if v, ok := m["oauth_token"]; ok { | |
if s, ok := v.(string); ok { | |
reqToken.Token = s | |
} | |
} | |
if v, ok := m["oauth_token_secret"]; ok { | |
if s, ok := v.(string); ok { | |
reqToken.TokenSecret = s | |
} | |
} | |
if v, ok := m["oauth_callback_confirmed"]; ok { | |
if b, ok := v.(bool); ok { | |
reqToken.CallbackConfirmed = b | |
} else if s, ok := v.(string); ok { | |
reqToken.CallbackConfirmed = s == "true" | |
} | |
} | |
return reqToken, m, nil | |
} | |
values, err := url.ParseQuery(string(respBody)) | |
if err != nil { | |
return nil, respBody, err | |
} | |
return &RequestToken{ | |
Token: values.Get("oauth_token"), | |
TokenSecret: values.Get("oauth_token_secret"), | |
CallbackConfirmed: values.Get("oauth_callback_confirmed") == "true", | |
}, values, nil | |
} | |
func (this *Consumer) TakeAccessToken(u string, reqToken *RequestToken, verifier string) (*AccessToken, interface{}, error) { | |
log.Println("Consumer.TakeAccessToken") | |
defer log.Println("end of Consumer.TakeAccessToken") | |
req, err := http.NewRequest( | |
http.MethodPost, | |
u, | |
nil, | |
) | |
if err != nil { | |
return nil, nil, err | |
} | |
params := NewParamaterList(this.ConsumerKey) | |
params.SetOAuthToken(reqToken.Token) | |
params.SetOAuthVerifier(verifier) | |
err = params.SetSignature(req.Method, u, this.ConsumerSecret, reqToken.TokenSecret, nil) | |
if err != nil { | |
return nil, nil, err | |
} | |
authorization := params.HeaderValue() | |
req.Header.Set("Authorization", authorization) | |
resp, err := http.DefaultClient.Do(req) | |
defer func() { | |
if resp != nil && resp.Body != nil { | |
resp.Body.Close() | |
} | |
}() | |
if err != nil { | |
if resp != nil { | |
log.Println(u) | |
log.Println(req.Header) | |
log.Println(resp.Status) | |
log.Println(resp.Header) | |
} | |
return nil, nil, err | |
} | |
respBody, err := ioutil.ReadAll(resp.Body) | |
if err != nil { | |
return nil, nil, err | |
} | |
if resp.StatusCode != http.StatusOK { | |
log.Println(u) | |
log.Println(req.Header) | |
log.Println(resp.Status) | |
log.Println(resp.Header) | |
log.Println(string(respBody)) | |
return nil, respBody, fmt.Errorf( | |
"%s %s", | |
resp.Status, | |
string(respBody), | |
) | |
} | |
contentType := resp.Header.Get("Content-Type") | |
if strings.Contains(contentType, "json") { | |
var res interface{} | |
err := json.Unmarshal(respBody, &res) | |
if err != nil { | |
return nil, nil, err | |
} | |
m, ok := res.(map[string]interface{}) | |
if !ok { | |
return nil, respBody, fmt.Errorf("unknown format json") | |
} | |
accToken := &AccessToken{} | |
if v, ok := m["oauth_token"]; ok { | |
if s, ok := v.(string); ok { | |
accToken.Token = s | |
} | |
} | |
if v, ok := m["oauth_token_secret"]; ok { | |
if s, ok := v.(string); ok { | |
accToken.TokenSecret = s | |
} | |
} | |
return accToken, m, nil | |
} | |
values, err := url.ParseQuery(string(respBody)) | |
if err != nil { | |
return nil, respBody, err | |
} | |
return &AccessToken{ | |
Token: values.Get("oauth_token"), | |
TokenSecret: values.Get("oauth_token_secret"), | |
}, values, nil | |
} | |
func (this *Consumer) Get(u string, accToken *AccessToken) ([]byte, http.Header, error) { | |
log.Println("Consumer.Get") | |
defer log.Println("end of Consumer.Get") | |
req, err := http.NewRequest( | |
http.MethodGet, | |
u, | |
nil, | |
) | |
if err != nil { | |
return nil, nil, err | |
} | |
params := NewParamaterList(this.ConsumerKey) | |
params.SetOAuthToken(accToken.Token) | |
err = params.SetSignature(req.Method, u, this.ConsumerSecret, accToken.TokenSecret, nil) | |
if err != nil { | |
return nil, nil, err | |
} | |
authorization := params.HeaderValue() | |
req.Header.Set("Authorization", authorization) | |
resp, err := http.DefaultClient.Do(req) | |
defer func() { | |
if resp != nil && resp.Body != nil { | |
resp.Body.Close() | |
} | |
}() | |
if err != nil { | |
var h http.Header | |
if resp != nil { | |
log.Println(u) | |
log.Println(req.Header) | |
log.Println(resp.Status) | |
log.Println(resp.Header) | |
h = resp.Header | |
} | |
return nil, h, err | |
} | |
if resp.Body == nil { | |
return []byte{}, resp.Header, nil | |
} | |
respBody, err := ioutil.ReadAll(resp.Body) | |
if err != nil { | |
return nil, resp.Header, err | |
} | |
if resp.StatusCode != http.StatusOK { | |
log.Println(u) | |
log.Println(req.Header) | |
log.Println(resp.Status) | |
log.Println(resp.Header) | |
log.Println(string(respBody)) | |
return respBody, resp.Header, fmt.Errorf( | |
"%s %s", | |
resp.Status, | |
string(respBody), | |
) | |
} | |
return respBody, resp.Header, nil | |
} | |
func (this *Consumer) Post(u string, accToken *AccessToken, contentType string, body []byte) ([]byte, http.Header, error) { | |
log.Println("Consumer.Post") | |
defer log.Println("end of Consumer.Post") | |
req, err := http.NewRequest( | |
http.MethodPost, | |
u, | |
bytes.NewReader(body), | |
) | |
if err != nil { | |
return nil, nil, err | |
} | |
params := NewParamaterList(this.ConsumerKey) | |
params.SetOAuthToken(accToken.Token) | |
var form url.Values | |
if contentType == "application/x-www-form-urlencoded" { | |
form, err = url.ParseQuery(string(body)) | |
if err != nil { | |
return nil, nil, err | |
} | |
} | |
err = params.SetSignature(req.Method, u, this.ConsumerSecret, accToken.TokenSecret, form) | |
if err != nil { | |
return nil, nil, err | |
} | |
authorization := params.HeaderValue() | |
req.Header.Set("Authorization", authorization) | |
req.Header.Set("Content-Type", contentType) | |
resp, err := http.DefaultClient.Do(req) | |
defer func() { | |
if resp != nil && resp.Body != nil { | |
resp.Body.Close() | |
} | |
}() | |
if err != nil { | |
var h http.Header | |
if resp != nil { | |
log.Println(u) | |
log.Println(req.Header) | |
log.Println(resp.Status) | |
log.Println(resp.Header) | |
h = resp.Header | |
} | |
return nil, h, err | |
} | |
if resp.Body == nil { | |
return []byte{}, resp.Header, nil | |
} | |
respBody, err := ioutil.ReadAll(resp.Body) | |
if err != nil { | |
return nil, resp.Header, err | |
} | |
if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusCreated { | |
log.Println(u) | |
log.Println(req.Header) | |
log.Println(resp.Status) | |
log.Println(resp.Header) | |
log.Println(string(respBody)) | |
return respBody, resp.Header, fmt.Errorf( | |
"%s %s", | |
resp.Status, | |
string(respBody), | |
) | |
} | |
return respBody, resp.Header, nil | |
} | |
func (this *Consumer) Put(u string, accToken *AccessToken, contentType string, body []byte) ([]byte, http.Header, error) { | |
log.Println("Consumer.Put") | |
defer log.Println("end of Consumer.Put") | |
req, err := http.NewRequest( | |
http.MethodPut, | |
u, | |
bytes.NewReader(body), | |
) | |
if err != nil { | |
return nil, nil, err | |
} | |
params := NewParamaterList(this.ConsumerKey) | |
params.SetOAuthToken(accToken.Token) | |
err = params.SetSignature(req.Method, u, this.ConsumerSecret, accToken.TokenSecret, nil) | |
if err != nil { | |
return nil, nil, err | |
} | |
authorization := params.HeaderValue() | |
req.Header.Set("Authorization", authorization) | |
req.Header.Set("Content-Type", contentType) | |
resp, err := http.DefaultClient.Do(req) | |
defer func() { | |
if resp != nil && resp.Body != nil { | |
resp.Body.Close() | |
} | |
}() | |
if err != nil { | |
var h http.Header | |
if resp != nil { | |
log.Println(u) | |
log.Println(req.Header) | |
log.Println(resp.Status) | |
log.Println(resp.Header) | |
h = resp.Header | |
} | |
return nil, h, err | |
} | |
if resp.Body == nil { | |
return []byte{}, resp.Header, nil | |
} | |
respBody, err := ioutil.ReadAll(resp.Body) | |
if err != nil { | |
return nil, resp.Header, err | |
} | |
if resp.StatusCode != http.StatusOK && | |
resp.StatusCode != http.StatusCreated && | |
resp.StatusCode != http.StatusNoContent { | |
log.Println(u) | |
log.Println(req.Header) | |
log.Println(resp.Status) | |
log.Println(resp.Header) | |
log.Println(string(respBody)) | |
return respBody, resp.Header, fmt.Errorf( | |
"%s %s", | |
resp.Status, | |
string(respBody), | |
) | |
} | |
return respBody, resp.Header, nil | |
} | |
func (this *Consumer) Delete(u string, accToken *AccessToken, contentType string, body []byte) ([]byte, http.Header, error) { | |
log.Println("Consumer.Delete") | |
defer log.Println("end of Consumer.Delete") | |
req, err := http.NewRequest( | |
http.MethodDelete, | |
u, | |
bytes.NewReader(body), | |
) | |
if err != nil { | |
return nil, nil, err | |
} | |
params := NewParamaterList(this.ConsumerKey) | |
params.SetOAuthToken(accToken.Token) | |
err = params.SetSignature(req.Method, u, this.ConsumerSecret, accToken.TokenSecret, nil) | |
if err != nil { | |
return nil, nil, err | |
} | |
authorization := params.HeaderValue() | |
req.Header.Set("Authorization", authorization) | |
if contentType != "" { | |
req.Header.Set("Content-Type", contentType) | |
} | |
resp, err := http.DefaultClient.Do(req) | |
defer func() { | |
if resp != nil && resp.Body != nil { | |
resp.Body.Close() | |
} | |
}() | |
if err != nil { | |
var h http.Header | |
if resp != nil { | |
log.Println(u) | |
log.Println(req.Header) | |
log.Println(resp.Status) | |
log.Println(resp.Header) | |
h = resp.Header | |
} | |
return nil, h, err | |
} | |
if resp.Body == nil { | |
return []byte{}, resp.Header, nil | |
} | |
respBody, err := ioutil.ReadAll(resp.Body) | |
if err != nil { | |
return nil, resp.Header, err | |
} | |
if resp.StatusCode != http.StatusOK && | |
resp.StatusCode != http.StatusNoContent && | |
resp.StatusCode != http.StatusAccepted { | |
log.Println(u) | |
log.Println(req.Header) | |
log.Println(resp.Status) | |
log.Println(resp.Header) | |
log.Println(string(respBody)) | |
return respBody, resp.Header, fmt.Errorf( | |
"%s %s", | |
resp.Status, | |
string(respBody), | |
) | |
} | |
return respBody, resp.Header, nil | |
} |
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
package main | |
import ( | |
"encoding/json" | |
"log" | |
"net/url" | |
"strings" | |
) | |
var _ = log.Println | |
type ( | |
HatenaConsumer Consumer | |
HatenaRequestToken RequestToken | |
HatenaAccessToken struct { | |
AccessToken | |
UrlName string `json:"url_name"` | |
DisplayName string `json:"display_name"` | |
} | |
) | |
func (this *HatenaConsumer) RequestToken() (*HatenaRequestToken, error) { | |
const u = "https://www.hatena.com/oauth/initiate" | |
form := url.Values{} | |
form.Add( | |
"scope", | |
strings.Join( | |
[]string{ | |
"read_public", | |
"write_public", | |
"read_private", | |
"write_private", | |
}, | |
",", | |
), | |
) | |
req, _, err := this.GetConsumer().TakeRequestToken(u, form) | |
return (*HatenaRequestToken)(req), err | |
} | |
func (this *HatenaConsumer) AccessToken(reqToken *HatenaRequestToken, verifier string) (*HatenaAccessToken, error) { | |
const u = "https://www.hatena.com/oauth/token" | |
acc, resp, err := this.GetConsumer().TakeAccessToken(u, reqToken.GetRequestToken(), verifier) | |
if err != nil { | |
return nil, err | |
} | |
values := resp.(url.Values) | |
return &HatenaAccessToken{ | |
AccessToken: *acc, | |
UrlName: values.Get("url_name"), | |
DisplayName: values.Get("display_name"), | |
}, nil | |
} | |
func (this *HatenaConsumer) ApplicationsMy(accToken *HatenaAccessToken) (*HatenaApplicationsMy, error) { | |
const u = "https://n.hatena.com/applications/my.json" | |
blob, _, err := this.GetConsumer().Get(u, accToken.GetAccessToken()) | |
if err != nil { | |
return nil, err | |
} | |
my := new(HatenaApplicationsMy) | |
err = json.Unmarshal(blob, my) | |
return my, err | |
} | |
func (this *HatenaConsumer) GetConsumer() *Consumer { | |
return (*Consumer)(this) | |
} | |
func (this *HatenaRequestToken) GetRequestToken() *RequestToken { | |
return (*RequestToken)(this) | |
} | |
func (this *HatenaAccessToken) GetAccessToken() *AccessToken { | |
return &this.AccessToken | |
} | |
func (this *HatenaRequestToken) AuthorizeUrl() string { | |
token := PercentEncode(this.Token) | |
endPoint := "/oauth/authorize?oauth_token=" + token | |
return "https://www.hatena.ne.jp" + endPoint | |
} |
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
package main | |
import ( | |
"bytes" | |
"encoding/xml" | |
"fmt" | |
"io/ioutil" | |
"mime" | |
"path" | |
"strings" | |
"time" | |
) | |
type ( | |
HatenaBlog struct { | |
HatenaId string `json:"hatena_id"` | |
BlogId string `json:"blog_id"` | |
} | |
HatenaBlogEndpoint string | |
) | |
const ( | |
BlogAtom HatenaBlogEndpoint = "/atom" | |
BlogAtomEntry = BlogAtom + "/entry" | |
BlogAtomCategory = BlogAtom + "/category" | |
) | |
const ( | |
BlogTimeFormat = "2006-01-02T15:04:05-07:00" | |
) | |
func (this HatenaBlogEndpoint) Url(blog *HatenaBlog, options ...string) string { | |
return "https://" + path.Join(append([]string{"blog.hatena.ne.jp", blog.HatenaId, blog.BlogId, string(this)}, options...)...) | |
} | |
func (this *HatenaConsumer) BlogAtom(accToken *HatenaAccessToken, blog *HatenaBlog) (interface{}, error) { | |
u := BlogAtom.Url(blog) | |
blob, _, err := this.GetConsumer().Get(u, accToken.GetAccessToken()) | |
if err != nil { | |
return nil, err | |
} | |
err = ioutil.WriteFile("blog_atom.xml", blob, 0664) | |
if err != nil { | |
return nil, err | |
} | |
res := &HatenaBlogAtomResult{} | |
err = xml.Unmarshal(blob, res) | |
if err != nil { | |
return "SAVE TO blog_atom.xml", err | |
} | |
return res, nil | |
} | |
func (this *HatenaConsumer) BlogAtomCategory(accToken *HatenaAccessToken, blog *HatenaBlog) (interface{}, error) { | |
u := BlogAtomCategory.Url(blog) | |
blob, _, err := this.GetConsumer().Get(u, accToken.GetAccessToken()) | |
if err != nil { | |
return nil, err | |
} | |
err = ioutil.WriteFile("blog_atom_category.xml", blob, 0664) | |
if err != nil { | |
return nil, err | |
} | |
res := &HatenaBlogAtomCategoryResult{} | |
err = xml.Unmarshal(blob, res) | |
if err != nil { | |
return "SAVE TO blog_atom_category.xml", err | |
} | |
return res, nil | |
} | |
func (this *HatenaConsumer) BlogAtomEntryGet(accToken *HatenaAccessToken, blog *HatenaBlog, nextPageId string) (interface{}, error) { | |
u := BlogAtomEntry.Url(blog) | |
if nextPageId != "" { | |
u += "?" + nextPageId | |
} | |
blob, _, err := this.GetConsumer().Get(u, accToken.GetAccessToken()) | |
if err != nil { | |
return nil, err | |
} | |
err = ioutil.WriteFile("blog_atom_entry_get.xml", blob, 0664) | |
if err != nil { | |
return nil, err | |
} | |
res := &HatenaBlogAtomEntryGetResult{} | |
err = xml.Unmarshal(blob, res) | |
if err != nil { | |
return "SAVE TO blog_atom_entry_get.xml", err | |
} | |
return res, nil | |
} | |
func (this *HatenaConsumer) BlogAtomEntryPost(accToken *HatenaAccessToken, blog *HatenaBlog, file, title, category, date string) (interface{}, error) { | |
u := BlogAtomEntry.Url(blog) | |
contentType := mime.TypeByExtension(".xml") | |
if contentType == "" { | |
contentType = "application/xml" | |
} | |
if date == "" { | |
date = time.Now().Local().Format(BlogTimeFormat) | |
} else if _, err := time.Parse(BlogTimeFormat, date); err != nil { | |
return nil, err | |
} | |
blogBody, err := ioutil.ReadFile(file) | |
if err != nil { | |
return nil, err | |
} | |
entry := HatenaBlogAtomEntryPostBody{} | |
entry.XmlNS = "http://www.w3.org/2007/app" | |
entry.Author = blog.HatenaId | |
entry.Title = title | |
entry.Updated = date | |
for _, c := range strings.Split(category, ",") { | |
cat := &HatenaBlogCategory{Term: c} | |
entry.Categories = append(entry.Categories, cat) | |
} | |
entry.Content.Type = "text/plain" | |
entry.Content.Value = string(blogBody) | |
entry.Draft = "no" | |
var b bytes.Buffer | |
if _, err = b.WriteString(xml.Header); err != nil { | |
return nil, err | |
} | |
enc := xml.NewEncoder(&b) | |
if err = enc.Encode(&entry); err != nil { | |
return nil, err | |
} | |
if err = enc.Flush(); err != nil { | |
return nil, err | |
} | |
body := b.Bytes() | |
blob, _, err := this.GetConsumer().Post(u, accToken.GetAccessToken(), contentType, body) | |
if err != nil { | |
return nil, err | |
} | |
err = ioutil.WriteFile("blog_atom_entry_post.xml", blob, 0664) | |
if err != nil { | |
return nil, err | |
} | |
res := &HatenaBlogEntryResult{} | |
err = xml.Unmarshal(blob, res) | |
if err != nil { | |
return "SAVE TO blog_atom_entry_post.xml", err | |
} | |
return res, nil | |
} | |
func (this *HatenaConsumer) BlogAtomEntryIdGet(accToken *HatenaAccessToken, blog *HatenaBlog, id string) (interface{}, error) { | |
u := BlogAtomEntry.Url(blog, id) | |
blob, _, err := this.GetConsumer().Get(u, accToken.GetAccessToken()) | |
if err != nil { | |
return nil, err | |
} | |
output := fmt.Sprintf("blog_atom_entry_%s_get.xml", id) | |
err = ioutil.WriteFile(output, blob, 0664) | |
if err != nil { | |
return nil, err | |
} | |
res := &HatenaBlogEntryResult{} | |
err = xml.Unmarshal(blob, res) | |
if err != nil { | |
return "SAVE TO " + output, err | |
} | |
return res, nil | |
} | |
func (this *HatenaConsumer) BlogAtomEntryIdDelete(accToken *HatenaAccessToken, blog *HatenaBlog, id string) (interface{}, error) { | |
u := BlogAtomEntry.Url(blog, id) | |
blob, _, err := this.GetConsumer().Delete(u, accToken.GetAccessToken(), "", nil) | |
if err != nil { | |
return nil, err | |
} | |
if len(blob) == 0 { | |
return "DELETED " + id, nil | |
} | |
output := fmt.Sprintf("blog_atom_entry_%s_delete.xml", id) | |
err = ioutil.WriteFile(output, blob, 0664) | |
if err != nil { | |
return nil, err | |
} | |
return "SAVE TO " + output, nil | |
} | |
func (this *HatenaConsumer) BlogAtomEntryIdPut(accToken *HatenaAccessToken, blog *HatenaBlog, id, file, title, category, date string) (interface{}, error) { | |
u := BlogAtomEntry.Url(blog, id) | |
contentType := mime.TypeByExtension(".xml") | |
if contentType == "" { | |
contentType = "application/xml" | |
} | |
if date == "" { | |
date = time.Now().Local().Format(BlogTimeFormat) | |
} else if _, err := time.Parse(BlogTimeFormat, date); err != nil { | |
return nil, err | |
} | |
blogBody, err := ioutil.ReadFile(file) | |
if err != nil { | |
return nil, err | |
} | |
entry := HatenaBlogAtomEntryPostBody{} | |
entry.XmlNS = "http://www.w3.org/2007/app" | |
entry.Author = blog.HatenaId | |
entry.Title = title | |
entry.Updated = date | |
for _, c := range strings.Split(category, ",") { | |
cat := &HatenaBlogCategory{Term: c} | |
entry.Categories = append(entry.Categories, cat) | |
} | |
entry.Content.Type = "text/plain" | |
entry.Content.Value = string(blogBody) | |
entry.Draft = "no" | |
var b bytes.Buffer | |
if _, err = b.WriteString(xml.Header); err != nil { | |
return nil, err | |
} | |
enc := xml.NewEncoder(&b) | |
if err = enc.Encode(&entry); err != nil { | |
return nil, err | |
} | |
if err = enc.Flush(); err != nil { | |
return nil, err | |
} | |
body := b.Bytes() | |
blob, _, err := this.GetConsumer().Put(u, accToken.GetAccessToken(), contentType, body) | |
if err != nil { | |
return nil, err | |
} | |
output := fmt.Sprintf("blog_atom_entry_%s_put.xml", id) | |
err = ioutil.WriteFile(output, blob, 0664) | |
if err != nil { | |
return nil, err | |
} | |
res := &HatenaBlogEntryResult{} | |
err = xml.Unmarshal(blob, res) | |
if err != nil { | |
return "SAVE TO " + output, err | |
} | |
return res, nil | |
} |
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
package main | |
import ( | |
"encoding/json" | |
"fmt" | |
"io/ioutil" | |
"net/url" | |
) | |
type HatenaBookmarkRest1MyBookmarkPostParam struct { | |
Url string | |
Comment string | |
Tags []string | |
PostTwitter bool | |
PostMixi bool | |
PostEvernote bool | |
Private bool | |
state int | |
} | |
func (this *HatenaConsumer) BookmarkRest1My(accToken *HatenaAccessToken) (interface{}, error) { | |
const u = "https://bookmark.hatenaapis.com/rest/1/my" | |
blob, _, err := this.GetConsumer().Get(u, accToken.GetAccessToken()) | |
if err != nil { | |
return nil, err | |
} | |
err = ioutil.WriteFile("bookmark_rest_1_my.json", blob, 0664) | |
if err != nil { | |
return nil, err | |
} | |
my := new(HatenaBookmarkRest1My) | |
err = json.Unmarshal(blob, my) | |
return my, err | |
} | |
func (this *HatenaConsumer) BookmarkRest1MyTags(accToken *HatenaAccessToken) (interface{}, error) { | |
const u = "https://bookmark.hatenaapis.com/rest/1/my/tags" | |
blob, _, err := this.GetConsumer().Get(u, accToken.GetAccessToken()) | |
if err != nil { | |
return nil, err | |
} | |
err = ioutil.WriteFile("bookmark_rest_1_my_tags.json", blob, 0664) | |
if err != nil { | |
return nil, err | |
} | |
tags := new(HatenaBookmarkRest1MyTags) | |
err = json.Unmarshal(blob, tags) | |
return tags, err | |
} | |
func (this *HatenaConsumer) BookmarkMySearchJson(accToken *HatenaAccessToken, query string, offset, limit int) (interface{}, error) { | |
const baseUrl = "https://b.hatena.ne.jp/my/search/json?" | |
v := url.Values{} | |
v.Set("q", query) | |
if offset > 0 { | |
v.Set("of", fmt.Sprint(offset)) | |
} | |
if limit > 100 { | |
limit = 100 | |
} | |
if limit > 20 { | |
v.Set("limit", fmt.Sprint(limit)) | |
} | |
u := baseUrl + v.Encode() | |
blob, _, err := this.GetConsumer().Get(u, accToken.GetAccessToken()) | |
if err != nil { | |
return nil, err | |
} | |
err = ioutil.WriteFile("bookmark_my_search_json.json", blob, 0664) | |
if err != nil { | |
return nil, err | |
} | |
result := new(HatenaBookmarkMySearchResult) | |
result.Offset = offset | |
err = json.Unmarshal(blob, result) | |
return result, err | |
} | |
func (this *HatenaConsumer) BookmarkRest1MyBookmarkGet(accToken *HatenaAccessToken, targetUrl string) (interface{}, error) { | |
const baseUrl = "https://bookmark.hatenaapis.com/rest/1/my/bookmark?" | |
v := url.Values{} | |
v.Set("url", targetUrl) | |
u := baseUrl + v.Encode() | |
blob, _, err := this.GetConsumer().Get(u, accToken.GetAccessToken()) | |
if err != nil { | |
return nil, err | |
} | |
err = ioutil.WriteFile("bookmark_rest_1_my_bookmark_get.json", blob, 0664) | |
if err != nil { | |
return nil, err | |
} | |
result := new(HatenaBookmarkRest1MyBookmark) | |
err = json.Unmarshal(blob, result) | |
return result, err | |
} | |
func (this *HatenaConsumer) BookmarkRest1MyBookmarkPost(accToken *HatenaAccessToken, param *HatenaBookmarkRest1MyBookmarkPostParam) (interface{}, error) { | |
const u = "https://bookmark.hatenaapis.com/rest/1/my/bookmark" | |
const contentType = "application/x-www-form-urlencoded" | |
v := url.Values{} | |
v.Set("url", param.Url) | |
v.Set("comment", param.Comment) | |
for _, t := range param.Tags { | |
v.Add("tags", t) | |
} | |
if param.PostTwitter { | |
v.Set("post_twitter", "1") | |
} | |
if param.PostMixi { | |
v.Set("post_mixi", "1") | |
} | |
if param.PostEvernote { | |
v.Set("post_evernote", "1") | |
} | |
if param.Private { | |
v.Set("private", "1") | |
} else { | |
v.Set("private", "0") | |
} | |
body := []byte(v.Encode()) | |
blob, _, err := this.GetConsumer().Post(u, accToken.GetAccessToken(), contentType, body) | |
if err != nil { | |
return nil, err | |
} | |
err = ioutil.WriteFile("bookmark_rest_1_my_bookmark_post.json", blob, 0664) | |
if err != nil { | |
return nil, err | |
} | |
result := new(HatenaBookmarkRest1MyBookmark) | |
err = json.Unmarshal(blob, result) | |
return result, err | |
} | |
func (this *HatenaConsumer) BookmarkRest1MyBookmarkDelete(accToken *HatenaAccessToken, targetUrl string) (interface{}, error) { | |
const baseUrl = "https://bookmark.hatenaapis.com/rest/1/my/bookmark?" | |
v := url.Values{} | |
v.Set("url", targetUrl) | |
u := baseUrl + v.Encode() | |
blob, _, err := this.GetConsumer().Delete(u, accToken.GetAccessToken(), "", nil) | |
if err != nil { | |
return blob, err | |
} | |
if len(blob) > 0 { | |
err = ioutil.WriteFile("bookmark_rest_1_my_bookmark_delete.json", blob, 0664) | |
if err != nil { | |
return nil, err | |
} | |
} | |
return blob, 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
package main | |
import ( | |
"bytes" | |
"encoding/base64" | |
"encoding/xml" | |
"fmt" | |
"io/ioutil" | |
"mime" | |
"path/filepath" | |
"strings" | |
) | |
func (this *HatenaConsumer) FotolifeFeed(accToken *HatenaAccessToken) (interface{}, error) { | |
const u = "https://f.hatena.ne.jp/atom/feed" | |
blob, h, err := this.GetConsumer().Get(u, accToken.GetAccessToken()) | |
if err != nil { | |
wa := h.Get("WWW-Authenticate") | |
if strings.Contains(wa, "WSSE") { | |
return "wsse", err | |
} | |
return nil, err | |
} | |
err = ioutil.WriteFile("fotolife_feed.xml", blob, 0664) | |
return "SAVE TO fotolife_feed.xml", err | |
} | |
func (this *HatenaConsumer) FotolifePost(accToken *HatenaAccessToken, file, title, directory string) (interface{}, error) { | |
const u = "https://f.hatena.ne.jp/atom/post" | |
contentType := mime.TypeByExtension(".xml") | |
if contentType == "" { | |
contentType = "application/xml" | |
} | |
ext := filepath.Ext(file) | |
mediaType := mime.TypeByExtension(ext) | |
switch ext { | |
default: | |
return nil, fmt.Errorf("unsupport file type: '%s' (%s)", ext, mediaType) | |
case ".png": | |
if mediaType == "" { | |
mediaType = "image/png" | |
} | |
case ".jpg": | |
if mediaType == "" { | |
mediaType = "image/jpeg" | |
} | |
} | |
img, err := ioutil.ReadFile(file) | |
if err != nil { | |
return nil, err | |
} | |
content := base64.StdEncoding.EncodeToString(img) | |
flp := HatenaFotolifePostBody{} | |
flp.Title = title | |
flp.Content.Mode = "base64" | |
flp.Content.Type = mediaType | |
flp.Content.Value = content | |
flp.Generator = "neetsdkasu::Inwardly" | |
if directory != "" { | |
flp.XmlNS = "http://purl.org/dc/elements/1.1/" | |
flp.Subject = directory | |
} | |
var b bytes.Buffer | |
if _, err = b.WriteString(xml.Header); err != nil { | |
return nil, err | |
} | |
enc := xml.NewEncoder(&b) | |
if err = enc.Encode(&flp); err != nil { | |
return nil, err | |
} | |
if err = enc.Flush(); err != nil { | |
return nil, err | |
} | |
body := b.Bytes() | |
err = ioutil.WriteFile("fotolife_post_body.xml", body, 0664) | |
if err != nil { | |
return nil, err | |
} | |
blob, _, err := this.GetConsumer().Post(u, accToken.GetAccessToken(), contentType, body) | |
if err != nil { | |
return nil, err | |
} | |
err = ioutil.WriteFile("fotolife_post_result.xml", blob, 0664) | |
if err != nil { | |
return nil, err | |
} | |
res := &HatenaFotolifePostResult{} | |
err = xml.Unmarshal(blob, res) | |
if err != nil { | |
return "SAVE TO fotolife_post_result.xml", err | |
} | |
return res, nil | |
} |
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
package main | |
import ( | |
"crypto/hmac" | |
"crypto/sha1" | |
"encoding/base64" | |
"fmt" | |
"net/url" | |
"sort" | |
"strings" | |
"time" | |
) | |
var percentEncodeTable [256][]byte | |
func init() { | |
for i := range percentEncodeTable { | |
switch { | |
default: | |
b := make([]byte, 0, 3) | |
b = append(b, '%') | |
b = append(b, []byte(fmt.Sprintf("%02X", i))...) | |
percentEncodeTable[i] = b | |
case '0' <= i && i <= '9', | |
'A' <= i && i <= 'Z', 'a' <= i && i <= 'z', | |
i == '-', i == '.', i == '_', i == '~': | |
percentEncodeTable[i] = []byte{byte(i)} | |
} | |
} | |
} | |
func PercentEncode(s string) string { | |
bs := []byte(s) | |
ret := make([]byte, 0, len(bs)*3) | |
for _, b := range bs { | |
ret = append(ret, percentEncodeTable[b]...) | |
} | |
return string(ret) | |
} | |
type Paramerter struct { | |
Key, Value string | |
} | |
func NewQuotedParameter(key, value string) *Paramerter { | |
return &Paramerter{ | |
Key: PercentEncode(key), | |
Value: PercentEncode(value), | |
} | |
} | |
func (this *Paramerter) Quoted() *Paramerter { | |
return NewQuotedParameter(this.Key, this.Value) | |
} | |
func (this *Paramerter) Pair() string { | |
return this.Key + "=" + this.Value | |
} | |
func (this *Paramerter) QuotedPair() string { | |
return fmt.Sprintf( | |
`%s="%s"`, | |
PercentEncode(this.Key), | |
PercentEncode(this.Value), | |
) | |
} | |
func (this *Paramerter) Compare(other *Paramerter) int { | |
cmp := strings.Compare(this.Key, other.Key) | |
if cmp != 0 { | |
return cmp | |
} | |
return strings.Compare(this.Value, other.Value) | |
} | |
type ParamerterList []Paramerter | |
func NewParamaterList(consumerKey string) ParamerterList { | |
return ParamerterList{ | |
{"oauth_signature_method", "HMAC-SHA1"}, | |
{"oauth_version", "1.0"}, | |
{"oauth_timestamp", fmt.Sprint(time.Now().UTC().Unix())}, | |
{"oauth_nonce", GenerateNonce()}, | |
{"oauth_consumer_key", consumerKey}, | |
} | |
} | |
func (this *ParamerterList) HeaderValue() string { | |
tmp := *this | |
params := make([]string, 0, len(tmp)) | |
for i := range tmp { | |
params = append(params, tmp[i].QuotedPair()) | |
} | |
return "OAuth " + strings.Join(params, ", ") | |
} | |
func (this *ParamerterList) Set(key, value string) { | |
tmp := *this | |
for i := range tmp { | |
if tmp[i].Key == key { | |
tmp[i].Value = value | |
*this = tmp | |
return | |
} | |
} | |
tmp = append(tmp, Paramerter{ | |
Key: key, | |
Value: value, | |
}) | |
*this = tmp | |
} | |
func (this *ParamerterList) SetOAuthToken(token string) { | |
this.Set("oauth_token", token) | |
} | |
func (this *ParamerterList) SetOAuthVerifier(verifier string) { | |
this.Set("oauth_verifier", verifier) | |
} | |
func (this *ParamerterList) SetOAuthCallback(callback string) { | |
this.Set("oauth_callback", callback) | |
} | |
func (this *ParamerterList) SetOAuthCallbackOob() { | |
this.SetOAuthCallback("oob") | |
} | |
func (this *ParamerterList) SetSignature(method, uri, key1, key2 string, form url.Values) error { | |
u, err := url.Parse(uri) | |
if err != nil { | |
return err | |
} | |
params := []*Paramerter{} | |
for _, p := range *this { | |
params = append(params, p.Quoted()) | |
} | |
queries := u.Query() | |
for k, vs := range queries { | |
if len(vs) == 0 { | |
params = append( | |
params, | |
NewQuotedParameter(k, ""), | |
) | |
} | |
for _, v := range vs { | |
params = append( | |
params, | |
NewQuotedParameter(k, v), | |
) | |
} | |
} | |
if form != nil { | |
for k, vs := range form { | |
if len(vs) == 0 { | |
params = append( | |
params, | |
NewQuotedParameter(k, ""), | |
) | |
} | |
for _, v := range vs { | |
params = append( | |
params, | |
NewQuotedParameter(k, v), | |
) | |
} | |
} | |
} | |
sort.Slice(params, func(i, j int) bool { | |
return params[i].Compare(params[j]) < 0 | |
}) | |
pairs := make([]string, 0, len(params)) | |
for _, p := range params { | |
pairs = append( | |
pairs, | |
p.Pair(), | |
) | |
} | |
u.RawQuery = "" | |
u.RawFragment = "" | |
baseUri := u.String() | |
message := []byte( | |
strings.Join( | |
[]string{ | |
PercentEncode(method), | |
PercentEncode(baseUri), | |
PercentEncode( | |
strings.Join(pairs, "&"), | |
), | |
}, | |
"&", | |
), | |
) | |
key := []byte(PercentEncode(key1) + "&" + PercentEncode(key2)) | |
mac := hmac.New(sha1.New, key) | |
mac.Write(message) | |
signature := base64.StdEncoding.EncodeToString(mac.Sum(nil)) | |
this.Set("oauth_signature", signature) | |
return nil | |
} |
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
package main | |
import ( | |
"log" | |
"net/http" | |
"net/url" | |
"testing" | |
) | |
func TestParam(t *testing.T) { | |
log.Println("TestParam") | |
defer log.Println("end of TestParam") | |
api := "https://api.twitter.com/1/statuses/update.json" | |
u, err := url.Parse(api) | |
if err != nil { | |
t.Fatal(err) | |
} | |
v := url.Values{} | |
v.Set("status", "Hello Ladies + Gentlemen, a signed OAuth request!") | |
v.Set("include_entities", "true") | |
u.RawQuery = v.Encode() | |
c := &Consumer{ | |
ConsumerKey: "xvz1evFS4wEEPTGEFPHBog", | |
ConsumerSecret: "kAcSOqF21Fu85e7zjz7ZN2U4ZRhfV3WpwPAoE3Z7kBw", | |
} | |
params := NewParamaterList(c.ConsumerKey) | |
params.Set("oauth_nonce", "kYjzVBB8Y0ZFabxSWbWovY3uYSQ2pTgmZeNu2VS4cg") | |
params.Set("oauth_timestamp", "1318622958") | |
params.SetOAuthToken("370773112-GmHxMAgYyLbNEtIKZeRNFsMKPR9EyMZeS9weJAEb") | |
tokenSecret := "LswwdoUaIvS8ltyTt5jkRh4J50vUPVVHtR2YPi5kE" | |
err = params.SetSignature(http.MethodPost, u.String(), c.ConsumerSecret, tokenSecret, nil) | |
if err != nil { | |
t.Fatal(err) | |
} | |
authorization := params.HeaderValue() | |
log.Println(authorization) | |
correctSignature := "tnnArxj06cWHq44gCs1OSKk/jLY=" | |
for _, e := range params { | |
if e.Key != "oauth_signature" { | |
continue | |
} | |
log.Println("result", e.Value) | |
log.Println("expect", correctSignature) | |
if e.Value != correctSignature { | |
t.Fatal("not equal signature") | |
} | |
return | |
} | |
t.Fatal("not found signature") | |
} |
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
package main | |
const USE_WSSE = false // WSSE失敗する… | |
type State interface { | |
Prompt() | |
Do(cmds []string) (State, error) | |
} | |
type Container struct { | |
consumer *HatenaConsumer | |
requestToken *HatenaRequestToken | |
accessToken *HatenaAccessToken | |
wsse *HatenaWsse | |
blog *HatenaBlog | |
entries map[string]*HatenaBlogEntryResult | |
nextPageId string | |
bookmarks *HatenaBookmarkMySearchResult | |
bookmarkParam *HatenaBookmarkRest1MyBookmarkPostParam | |
} | |
type ( | |
NoConsumerKey Container | |
NoConsumerSecret Container | |
NoWsseUsername Container | |
NoWssePassword Container | |
HasAccessToken Container | |
HasRequestToken Container | |
NoRequestToken Container | |
NoHatenaId Container | |
NoBlogId Container | |
FotolifePostState Container | |
BlogEntryPostState Container | |
BlogEntryIdGetState Container | |
BlogEntryIdPutState Container | |
BlogEntryIdDeleteState Container | |
BookmarkMySearchJsonState Container | |
BookmarkRest1MyBookmarkGetState Container | |
BookmarkRest1MyBookmarkPostState Container | |
BookmarkRest1MyBookmarkDeleteState Container | |
) | |
func NewState() (State, error) { | |
var err error | |
state := new(Container) | |
state.entries = make(map[string]*HatenaBlogEntryResult) | |
state.consumer, err = loadConsumer() | |
if err != nil { | |
return nil, err | |
} | |
state.requestToken, err = loadRequestToken() | |
if err != nil { | |
return nil, err | |
} | |
state.accessToken, err = loadAccessToken() | |
if err != nil { | |
return nil, err | |
} | |
state.blog, err = loadBlog() | |
if err != nil { | |
return nil, err | |
} | |
if USE_WSSE { | |
state.wsse, err = loadWsse() | |
if err != nil { | |
return nil, err | |
} | |
} | |
if state.consumer.ConsumerKey == "" { | |
return (*NoConsumerKey)(state), nil | |
} | |
if state.consumer.ConsumerSecret == "" { | |
return (*NoConsumerSecret)(state), nil | |
} | |
if state.blog.HatenaId == "" { | |
return (*NoHatenaId)(state), nil | |
} | |
if state.blog.BlogId == "" { | |
return (*NoBlogId)(state), nil | |
} | |
if USE_WSSE { | |
if state.wsse.Username == "" { | |
return (*NoWsseUsername)(state), nil | |
} | |
if state.wsse.Password == "" { | |
return (*NoWssePassword)(state), nil | |
} | |
} | |
if state.accessToken != nil { | |
return (*HasAccessToken)(state), nil | |
} | |
if state.requestToken != nil { | |
return (*HasRequestToken)(state), nil | |
} else { | |
return (*NoRequestToken)(state), nil | |
} | |
} |
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
package main | |
import ( | |
"fmt" | |
"path" | |
) | |
// | |
// | |
// BlogEntryIdGetState | |
// | |
// | |
func (this *BlogEntryIdGetState) Prompt() { | |
fmt.Println("BlogEntryIdGetState") | |
for id, t := range this.entries { | |
fmt.Println(id, t.Updated, t.Title) | |
} | |
fmt.Println("<id> ?") | |
} | |
func (this *BlogEntryIdGetState) Do(cmds []string) (State, error) { | |
id := cmds[0] | |
if _, ok := this.entries[id]; !ok { | |
return (*HasAccessToken)(this), fmt.Errorf("WRONG ID") | |
} | |
s, err := this.consumer.BlogAtomEntryIdGet(this.accessToken, this.blog, id) | |
if s != nil { | |
fmt.Printf("%#v\n", s) | |
if x, ok := s.(*HatenaBlogEntryResult); ok { | |
var id string | |
for _, lk := range x.Links { | |
if lk.Rel == "edit" { | |
id = path.Base(lk.Href) | |
} | |
} | |
this.entries[id] = x | |
} | |
} | |
return (*HasAccessToken)(this), err | |
} | |
// | |
// | |
// BlogEntryIdPutState | |
// | |
// | |
func (this *BlogEntryIdPutState) Prompt() { | |
fmt.Println("BlogEntryIdPutState") | |
for id, t := range this.entries { | |
fmt.Println(id, t.Updated, t.Title) | |
} | |
fmt.Println("<id> <file> [<title> [<category,*> [<date(" + BlogTimeFormat + ")>]]] ?") | |
} | |
func (this *BlogEntryIdPutState) Do(cmds []string) (State, error) { | |
if len(cmds) < 2 { | |
return (*HasAccessToken)(this), fmt.Errorf("invalid input: %#v", cmds) | |
} | |
id := cmds[0] | |
file := cmds[1] | |
e, ok := this.entries[id] | |
if !ok { | |
return (*HasAccessToken)(this), fmt.Errorf("WRONG ID") | |
} | |
title := e.Title | |
category := "" | |
for _, c := range e.Categories { | |
if category != "" { | |
category += "," + c.Term | |
} else { | |
category += c.Term | |
} | |
} | |
date := e.Updated | |
if len(cmds) > 2 { | |
title = cmds[2] | |
if len(cmds) > 3 { | |
category = cmds[3] | |
if len(cmds) > 4 { | |
date = cmds[4] | |
} | |
} | |
} | |
s, err := this.consumer.BlogAtomEntryIdPut(this.accessToken, this.blog, id, file, title, category, date) | |
if s != nil { | |
fmt.Printf("%#v\n", s) | |
if x, ok := s.(*HatenaBlogEntryResult); ok { | |
var id string | |
for _, lk := range x.Links { | |
if lk.Rel == "edit" { | |
id = path.Base(lk.Href) | |
} | |
} | |
this.entries[id] = x | |
} | |
} | |
return (*HasAccessToken)(this), err | |
} | |
// | |
// | |
// BlogEntryIdDeleteState | |
// | |
// | |
func (this *BlogEntryIdDeleteState) Prompt() { | |
fmt.Println("BlogEntryIdDeleteState") | |
for id, t := range this.entries { | |
fmt.Println(id, t.Updated, t.Title) | |
} | |
fmt.Println("<id> ?") | |
} | |
func (this *BlogEntryIdDeleteState) Do(cmds []string) (State, error) { | |
id := cmds[0] | |
if _, ok := this.entries[id]; !ok { | |
return (*HasAccessToken)(this), fmt.Errorf("WRONG ID") | |
} | |
s, err := this.consumer.BlogAtomEntryIdDelete(this.accessToken, this.blog, id) | |
if s != nil { | |
fmt.Printf("%#v\n", s) | |
delete(this.entries, id) | |
} | |
return (*HasAccessToken)(this), err | |
} | |
// | |
// | |
// BlogEntryPostState | |
// | |
// | |
func (this *BlogEntryPostState) Prompt() { | |
fmt.Println("BlogEntryPostState") | |
fmt.Println("<file> <title> <category,*> [<date(" + BlogTimeFormat + ")>] ?") | |
} | |
func (this *BlogEntryPostState) Do(cmds []string) (State, error) { | |
if len(cmds) < 3 { | |
return (*HasAccessToken)(this), fmt.Errorf("invalid input: %#v", cmds) | |
} | |
var file, title, category, date string | |
file = cmds[0] | |
title = cmds[1] | |
category = cmds[2] | |
if len(cmds) > 3 { | |
date = cmds[3] | |
} | |
s, err := this.consumer.BlogAtomEntryPost(this.accessToken, this.blog, file, title, category, date) | |
if s != nil { | |
fmt.Printf("%#v\n", s) | |
if x, ok := s.(*HatenaBlogEntryResult); ok { | |
var id string | |
for _, lk := range x.Links { | |
if lk.Rel == "edit" { | |
id = path.Base(lk.Href) | |
} | |
} | |
this.entries[id] = x | |
} | |
} | |
return (*HasAccessToken)(this), 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
package main | |
import ( | |
"fmt" | |
"log" | |
"strings" | |
) | |
// | |
// | |
// BookmarkMySearchJson | |
// | |
// | |
func (this *BookmarkMySearchJsonState) Prompt() { | |
fmt.Println("Search Keyword?") | |
} | |
func (this *BookmarkMySearchJsonState) Do(cmds []string) (State, error) { | |
query := strings.Join(cmds, " ") | |
offset := 0 | |
limit := 20 | |
if s, err := this.consumer.BookmarkMySearchJson(this.accessToken, query, offset, limit); err == nil { | |
if result, ok := s.(*HatenaBookmarkMySearchResult); !ok { | |
fmt.Printf("%#v\n", s) | |
} else { | |
this.bookmarks = result | |
for i, bookmark := range result.Bookmarks { | |
fmt.Printf("%3d) %s\n", i, bookmark.Entry.Title) | |
if bookmark.IsPrivate() { | |
fmt.Println(" [PRIVATE]") | |
} | |
fmt.Printf(" %s\n", bookmark.Entry.Url) | |
fmt.Printf(" %s\n", bookmark.Comment) | |
} | |
fmt.Printf("Total: %d\n", result.Meta.Total) | |
fmt.Printf("Status: %d\n", result.Meta.Status) | |
} | |
} else { | |
return (*HasAccessToken)(this), err | |
} | |
return (*HasAccessToken)(this), nil | |
} | |
// | |
// | |
// BookmarkRest1MyBookmarkGet | |
// | |
// | |
func (this *BookmarkRest1MyBookmarkGetState) Prompt() { | |
fmt.Println("URL?") | |
} | |
func (this *BookmarkRest1MyBookmarkGetState) Do(cmds []string) (State, error) { | |
if s, err := this.consumer.BookmarkRest1MyBookmarkGet(this.accessToken, cmds[0]); err == nil { | |
if result, ok := s.(*HatenaBookmarkRest1MyBookmark); !ok { | |
fmt.Printf("%#v\n", s) | |
} else { | |
if result.Private { | |
fmt.Println("[PRIVATE]") | |
} | |
fmt.Println("Create:", result.CreatedDateTime) | |
fmt.Println("Link:", result.Permalink) | |
fmt.Println("Comment:", result.Comment) | |
fmt.Println("Tags:", result.Tags) | |
} | |
} else { | |
return (*HasAccessToken)(this), err | |
} | |
return (*HasAccessToken)(this), nil | |
} | |
// | |
// | |
// BookmarkRest1MyBookmarkPost | |
// | |
// | |
func (this *BookmarkRest1MyBookmarkPostState) Prompt() { | |
if this.bookmarkParam == nil { | |
fmt.Println("URL?") | |
} else { | |
switch this.bookmarkParam.state { | |
case 0: | |
fmt.Println("Private? (yes/no)") | |
case 1: | |
fmt.Println("Require Commet? (yes/no)") | |
case 2: | |
fmt.Println("Commet?") | |
case 3: | |
fmt.Println("Url:", this.bookmarkParam.Url) | |
fmt.Println("Private:", this.bookmarkParam.Private) | |
fmt.Println("Commet:", this.bookmarkParam.Comment) | |
fmt.Println("Ok? (yes/no)") | |
} | |
} | |
} | |
func (this *BookmarkRest1MyBookmarkPostState) Do(cmds []string) (State, error) { | |
if this.bookmarkParam == nil { | |
this.bookmarkParam = &HatenaBookmarkRest1MyBookmarkPostParam{} | |
this.bookmarkParam.Url = cmds[0] | |
this.bookmarkParam.state = 0 | |
if _, err := (*BookmarkRest1MyBookmarkGetState)(this).Do(cmds); err != nil { | |
log.Println(err) | |
} | |
return this, nil | |
} | |
if this.bookmarkParam.state == 0 { | |
if cmds[0] == "yes" { | |
this.bookmarkParam.Private = true | |
this.bookmarkParam.state = 1 | |
} else if cmds[0] == "no" { | |
this.bookmarkParam.state = 1 | |
} | |
return this, nil | |
} | |
if this.bookmarkParam.state == 1 { | |
if cmds[0] == "yes" { | |
this.bookmarkParam.state = 2 | |
} else if cmds[0] == "no" { | |
this.bookmarkParam.state = 3 | |
} | |
return this, nil | |
} | |
if this.bookmarkParam.state == 2 { | |
this.bookmarkParam.Comment = strings.Join(cmds, " ") | |
this.bookmarkParam.state = 3 | |
return this, nil | |
} | |
if this.bookmarkParam.state == 3 { | |
if cmds[0] == "no" { | |
fmt.Println("Canceled") | |
return (*HasAccessToken)(this), nil | |
} else if cmds[0] != "yes" { | |
return this, nil | |
} | |
} | |
if s, err := this.consumer.BookmarkRest1MyBookmarkPost(this.accessToken, this.bookmarkParam); err == nil { | |
if result, ok := s.(*HatenaBookmarkRest1MyBookmark); !ok { | |
fmt.Printf("%#v\n", s) | |
} else { | |
if result.Private { | |
fmt.Println("[PRIVATE]") | |
} | |
fmt.Println("Create:", result.CreatedDateTime) | |
fmt.Println("Link:", result.Permalink) | |
fmt.Println("Comment:", result.Comment) | |
fmt.Println("Tags:", result.Tags) | |
} | |
} else { | |
return (*HasAccessToken)(this), err | |
} | |
return (*HasAccessToken)(this), nil | |
} | |
// | |
// | |
// BookmarkRest1MyBookmarkDelete | |
// | |
// | |
func (this *BookmarkRest1MyBookmarkDeleteState) Prompt() { | |
fmt.Println("Delete URL?") | |
} | |
func (this *BookmarkRest1MyBookmarkDeleteState) Do(cmds []string) (State, error) { | |
targetUrl := cmds[0] | |
if s, err := this.consumer.BookmarkRest1MyBookmarkDelete(this.accessToken, targetUrl); err == nil { | |
if ss, ok := s.([]byte); ok { | |
fmt.Println(string(ss)) | |
} | |
fmt.Println("[DELETE]", targetUrl, "(success)") | |
} else { | |
return (*HasAccessToken)(this), err | |
} | |
return (*HasAccessToken)(this), nil | |
} |
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
package main | |
import "fmt" | |
// | |
// | |
// FotolifeFeed | |
// | |
// | |
func (this *Container) FotolifeFeed() (interface{}, error) { | |
s, err := this.consumer.FotolifeFeed(this.accessToken) | |
if err == nil { | |
return s, nil | |
} | |
if s != nil && USE_WSSE && s.(string) == "wsse" { | |
return this.wsse.FotolifeFeed() | |
} | |
return nil, err | |
} | |
// | |
// | |
// FotolifePostState | |
// | |
// | |
func (this *FotolifePostState) Prompt() { | |
fmt.Println("FotolifePostState") | |
fmt.Println("<file> [<title> [<directory>]] ?") | |
} | |
func (this *FotolifePostState) Do(cmds []string) (State, error) { | |
var file, title, directory string | |
file = cmds[0] | |
if len(cmds) > 1 { | |
title = cmds[1] | |
if len(cmds) > 2 { | |
directory = cmds[2] | |
} | |
} | |
s, err := this.consumer.FotolifePost(this.accessToken, file, title, directory) | |
if s != nil { | |
fmt.Printf("%#v\n", s) | |
} | |
return (*HasAccessToken)(this), 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
package main | |
import ( | |
"fmt" | |
"path" | |
"strings" | |
) | |
// | |
// | |
// HasAccessToken | |
// | |
// | |
func (this *HasAccessToken) Prompt() { | |
fmt.Printf("AccessToken: %#v\n", this.accessToken) | |
fmt.Println("--- API LIST ---") | |
fmt.Println(" applications/my.json") | |
fmt.Println(" fotolife/feed") | |
fmt.Println(" fotolife/post") | |
fmt.Println(" blog/atom") | |
fmt.Println(" blog/atom/entry/get") | |
if this.nextPageId != "" { | |
fmt.Println(" blog/atom/entry/next/get") | |
} | |
fmt.Println(" blog/atom/entry/post") | |
fmt.Println(" blog/atom/entry/id/get") | |
fmt.Println(" blog/atom/entry/id/put") | |
fmt.Println(" blog/atom/entry/id/delete") | |
fmt.Println(" blog/atom/category") | |
fmt.Println(" bookmark/rest/1/my") | |
fmt.Println(" bookmark/rest/1/my/tags") | |
fmt.Println(" bookmark/my/search/json") | |
if this.bookmarks.HasNext() { | |
fmt.Println(" bookmark/my/search/json/next") | |
fmt.Printf(" ( %s ) ( %d / %d )\n", | |
this.bookmarks.Meta.Query.Original, | |
this.bookmarks.Offset+20, | |
this.bookmarks.Meta.Total, | |
) | |
} | |
fmt.Println(" bookmark/rest/1/my/bookmark/get") | |
fmt.Println(" bookmark/rest/1/my/bookmark/post") | |
fmt.Println(" bookmark/rest/1/my/bookmark/delete") | |
fmt.Println("----------------") | |
fmt.Println("API? (or quit)") | |
} | |
func (this *HasAccessToken) Do(cmds []string) (State, error) { | |
switch cmds[0] { | |
default: | |
fmt.Println("wrong API name:", cmds[0]) | |
case "applications/my.json": | |
return this.doApplicationsMy_Json() | |
case "fotolife/feed": | |
return this.doFotolifeFeed() | |
case "fotolife/post": | |
return this.doFotolifePost() | |
case "blog/atom": | |
return this.doBlogAtom() | |
case "blog/atom/entry/get": | |
return this.doBlogAtomEntryGet() | |
case "blog/atom/entry/next/get": | |
return this.doBlogAtomEntryNextGet() | |
case "blog/atom/entry/post": | |
return this.doBlogAtomEntryPost() | |
case "blog/atom/entry/id/get": | |
return this.doBlogAtomEntryIdGet() | |
case "blog/atom/entry/id/put": | |
return this.doBlogAtomEntryIdPut() | |
case "blog/atom/entry/id/delete": | |
return this.doBlogAtomEntryIdDelete() | |
case "blog/atom/category": | |
return this.doBlogAtomCategory() | |
case "bookmark/rest/1/my": | |
return this.doBookmarkRest1My() | |
case "bookmark/rest/1/my/tags": | |
return this.doBookmarkRest1MyTags() | |
case "bookmark/my/search/json": | |
return this.doBookmarkMySearchJson() | |
case "bookmark/my/search/json/next": | |
return this.doBookmarkMySearchJsonNext() | |
case "bookmark/rest/1/my/bookmark/get": | |
return this.doBookmarkRest1MyBookmarkGet() | |
case "bookmark/rest/1/my/bookmark/post": | |
return this.doBookmarkRest1MyBookmarkPost() | |
case "bookmark/rest/1/my/bookmark/delete": | |
return this.doBookmarkRest1MyBookmarkDelete() | |
} | |
return this, nil | |
} | |
func (this *HasAccessToken) doApplicationsMy_Json() (State, error) { | |
if my, err := this.consumer.ApplicationsMy(this.accessToken); err != nil { | |
return this, err | |
} else { | |
fmt.Printf("%#v\n", my) | |
return this, nil | |
} | |
} | |
func (this *HasAccessToken) doFotolifeFeed() (State, error) { | |
if s, err := (*Container)(this).FotolifeFeed(); err != nil { | |
return this, err | |
} else { | |
fmt.Printf("%#v\n", s) | |
return this, nil | |
} | |
} | |
func (this *HasAccessToken) doFotolifePost() (State, error) { | |
return (*FotolifePostState)(this), nil | |
} | |
func (this *HasAccessToken) doBlogAtom() (State, error) { | |
if s, err := this.consumer.BlogAtom(this.accessToken, this.blog); err != nil { | |
return this, err | |
} else { | |
fmt.Printf("%#v\n", s) | |
return this, nil | |
} | |
} | |
func (this *HasAccessToken) doBlogAtomEntryGet() (State, error) { | |
if s, err := this.consumer.BlogAtomEntryGet(this.accessToken, this.blog, ""); err != nil { | |
return this, err | |
} else { | |
fmt.Printf("%#v\n", s) | |
if e, ok := s.(*HatenaBlogAtomEntryGetResult); ok { | |
if len(e.Entries) > 0 { | |
fmt.Printf("%#v\n", e.Entries[0]) | |
} | |
for _, x := range e.Entries { | |
var id string | |
for _, lk := range x.Links { | |
if lk.Rel == "edit" { | |
id = path.Base(lk.Href) | |
} | |
} | |
this.entries[id] = x | |
} | |
this.nextPageId = "" | |
for _, lk := range e.Links { | |
if lk.Rel == "next" { | |
u := strings.SplitN(lk.Href, "?", 2) | |
if len(u) == 2 { | |
this.nextPageId = u[1] | |
} | |
} | |
} | |
} | |
return this, nil | |
} | |
} | |
func (this *HasAccessToken) doBlogAtomEntryNextGet() (State, error) { | |
if s, err := this.consumer.BlogAtomEntryGet(this.accessToken, this.blog, this.nextPageId); err != nil { | |
return this, err | |
} else { | |
fmt.Printf("%#v\n", s) | |
if e, ok := s.(*HatenaBlogAtomEntryGetResult); ok { | |
if len(e.Entries) > 0 { | |
fmt.Printf("%#v\n", e.Entries[0]) | |
} | |
for _, x := range e.Entries { | |
var id string | |
for _, lk := range x.Links { | |
if lk.Rel == "edit" { | |
id = path.Base(lk.Href) | |
} | |
} | |
this.entries[id] = x | |
} | |
for _, lk := range e.Links { | |
if lk.Rel == "next" { | |
u := strings.SplitN(lk.Href, "?", 2) | |
if len(u) == 2 { | |
this.nextPageId = u[1] | |
} | |
} | |
} | |
} | |
return this, nil | |
} | |
} | |
func (this *HasAccessToken) doBlogAtomEntryPost() (State, error) { | |
return (*BlogEntryPostState)(this), nil | |
} | |
func (this *HasAccessToken) doBlogAtomEntryIdGet() (State, error) { | |
if len(this.entries) == 0 { | |
return this, fmt.Errorf("NO ENTRIES") | |
} | |
return (*BlogEntryIdGetState)(this), nil | |
} | |
func (this *HasAccessToken) doBlogAtomEntryIdPut() (State, error) { | |
if len(this.entries) == 0 { | |
return this, fmt.Errorf("NO ENTRIES") | |
} | |
return (*BlogEntryIdPutState)(this), nil | |
} | |
func (this *HasAccessToken) doBlogAtomEntryIdDelete() (State, error) { | |
if len(this.entries) == 0 { | |
return this, fmt.Errorf("NO ENTRIES") | |
} | |
return (*BlogEntryIdDeleteState)(this), nil | |
} | |
func (this *HasAccessToken) doBlogAtomCategory() (State, error) { | |
if s, err := this.consumer.BlogAtomCategory(this.accessToken, this.blog); err != nil { | |
return this, err | |
} else { | |
fmt.Printf("%#v\n", s) | |
return this, nil | |
} | |
} | |
func (this *HasAccessToken) doBookmarkRest1My() (State, error) { | |
if s, err := this.consumer.BookmarkRest1My(this.accessToken); err != nil { | |
return this, err | |
} else { | |
fmt.Printf("%#v\n", s) | |
return this, nil | |
} | |
} | |
func (this *HasAccessToken) doBookmarkRest1MyTags() (State, error) { | |
if s, err := this.consumer.BookmarkRest1MyTags(this.accessToken); err != nil { | |
return this, err | |
} else { | |
if tags, ok := s.(*HatenaBookmarkRest1MyTags); !ok { | |
fmt.Printf("%#v\n", s) | |
} else { | |
for i, tag := range tags.Tags { | |
fmt.Printf("%3d) %3d: %s\n", i, tag.Count, tag.Tag) | |
} | |
} | |
return this, nil | |
} | |
} | |
func (this *HasAccessToken) doBookmarkMySearchJson() (State, error) { | |
return (*BookmarkMySearchJsonState)(this), nil | |
} | |
func (this *HasAccessToken) doBookmarkMySearchJsonNext() (State, error) { | |
query := this.bookmarks.Meta.Query.Original | |
offset := this.bookmarks.Offset + 20 | |
limit := 20 | |
if s, err := this.consumer.BookmarkMySearchJson(this.accessToken, query, offset, limit); err == nil { | |
if result, ok := s.(*HatenaBookmarkMySearchResult); !ok { | |
fmt.Printf("%#v\n", s) | |
} else { | |
this.bookmarks = result | |
for i, bookmark := range result.Bookmarks { | |
fmt.Printf("%3d) %s\n", offset+i, bookmark.Entry.Title) | |
fmt.Printf(" %s\n", bookmark.Entry.Url) | |
if bookmark.IsPrivate() { | |
fmt.Println(" [PRIVATE]") | |
} | |
} | |
fmt.Printf("Total: %d\n", result.Meta.Total) | |
fmt.Printf("Status: %d\n", result.Meta.Status) | |
} | |
} else { | |
return this, err | |
} | |
return this, nil | |
} | |
func (this *HasAccessToken) doBookmarkRest1MyBookmarkGet() (State, error) { | |
return (*BookmarkRest1MyBookmarkGetState)(this), nil | |
} | |
func (this *HasAccessToken) doBookmarkRest1MyBookmarkPost() (State, error) { | |
this.bookmarkParam = nil | |
return (*BookmarkRest1MyBookmarkPostState)(this), nil | |
} | |
func (this *HasAccessToken) doBookmarkRest1MyBookmarkDelete() (State, error) { | |
return (*BookmarkRest1MyBookmarkDeleteState)(this), nil | |
} |
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
package main | |
import ( | |
"fmt" | |
"os" | |
) | |
// | |
// | |
// NoConsumerKey | |
// | |
// | |
func (*NoConsumerKey) Prompt() { | |
fmt.Println("ConsumerKey?") | |
} | |
func (this *NoConsumerKey) Do(cmds []string) (State, error) { | |
this.consumer.ConsumerKey = cmds[0] | |
err := saveConsumer(this.consumer) | |
if err != nil { | |
return this, err | |
} | |
return (*NoConsumerSecret)(this), nil | |
} | |
// | |
// | |
// NoConsumerSecret | |
// | |
// | |
func (*NoConsumerSecret) Prompt() { | |
fmt.Println("ConsumerSecret?") | |
} | |
func (this *NoConsumerSecret) Do(cmds []string) (State, error) { | |
this.consumer.ConsumerSecret = cmds[0] | |
err := saveConsumer(this.consumer) | |
if err != nil { | |
return this, err | |
} | |
return (*NoHatenaId)(this), nil | |
} | |
// | |
// | |
// NoHatenaId | |
// | |
// | |
func (*NoHatenaId) Prompt() { | |
fmt.Println("Hatena ID?") | |
} | |
func (this *NoHatenaId) Do(cmds []string) (State, error) { | |
this.blog.HatenaId = cmds[0] | |
err := saveBlog(this.blog) | |
if err != nil { | |
return this, err | |
} | |
return (*NoBlogId)(this), nil | |
} | |
// | |
// | |
// NoBlogId | |
// | |
// | |
func (*NoBlogId) Prompt() { | |
fmt.Println("Blog ID? (blog domain)") | |
} | |
func (this *NoBlogId) Do(cmds []string) (State, error) { | |
this.blog.BlogId = cmds[0] | |
err := saveBlog(this.blog) | |
if err != nil { | |
return this, err | |
} | |
if USE_WSSE { | |
return (*NoWsseUsername)(this), nil | |
} | |
return (*NoRequestToken)(this), nil | |
} | |
// | |
// | |
// NoWsseUsername | |
// | |
// | |
func (*NoWsseUsername) Prompt() { | |
fmt.Println("WSSE Username?") | |
} | |
func (this *NoWsseUsername) Do(cmds []string) (State, error) { | |
this.wsse.Username = cmds[0] | |
err := saveWsse(this.wsse) | |
if err != nil { | |
return this, err | |
} | |
return (*NoWssePassword)(this), nil | |
} | |
// | |
// | |
// NoWssePassword | |
// | |
// | |
func (*NoWssePassword) Prompt() { | |
fmt.Println("WSSE Password? (API Key)") | |
} | |
func (this *NoWssePassword) Do(cmds []string) (State, error) { | |
this.wsse.Password = cmds[0] | |
err := saveWsse(this.wsse) | |
if err != nil { | |
return this, err | |
} | |
return (*NoRequestToken)(this), nil | |
} | |
// | |
// | |
// NoRequestToken | |
// | |
// | |
func (this *NoRequestToken) Prompt() { | |
fmt.Printf("Consumer: %#v\n", this.consumer) | |
fmt.Println("get RequestToken? (yes/no)") | |
} | |
func (this *NoRequestToken) Do(cmds []string) (State, error) { | |
if cmds[0] != "yes" { | |
return this, nil | |
} | |
var err error | |
this.requestToken, err = this.consumer.RequestToken() | |
if err != nil { | |
return this, err | |
} | |
fmt.Printf("RequestToken: %#v\n", this.requestToken) | |
err = saveRequestToken(this.requestToken) | |
if err != nil { | |
return this, err | |
} | |
return (*HasRequestToken)(this), nil | |
} | |
// | |
// | |
// HasRequestToken | |
// | |
// | |
func (this *HasRequestToken) Prompt() { | |
fmt.Printf("RequestToken: %#v\n", this.requestToken) | |
fmt.Println(this.requestToken.AuthorizeUrl()) | |
fmt.Println("verifier?") | |
} | |
func (this *HasRequestToken) Do(cmds []string) (State, error) { | |
var err error | |
verifier := cmds[0] | |
this.accessToken, err = this.consumer.AccessToken(this.requestToken, verifier) | |
if err != nil { | |
return this, err | |
} | |
fmt.Printf("AccessToken: %#v\n", this.accessToken) | |
err = saveAccessToken(this.accessToken) | |
if err != nil { | |
return this, err | |
} | |
err = os.Remove("requestToken.json") | |
this.requestToken = nil | |
return (*HasAccessToken)(this), 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
package main | |
// WSSE接続失敗するのでわからん…WSSEの実装を俺がミスってる…? | |
import ( | |
"crypto/sha1" | |
"encoding/base64" | |
"fmt" | |
"io/ioutil" | |
"log" | |
"net/http" | |
"time" | |
) | |
type Wsse struct { | |
Username string `json:"username"` | |
Password string `json:"password"` | |
} | |
func (this *Wsse) SetAuthorization(header http.Header) { | |
nonce := GenerateNonce() | |
date := time.Now().UTC().Format(time.RFC3339) | |
key := []byte(nonce + date + this.Password) | |
h := sha1.New() | |
h.Write(key) | |
src := h.Sum(nil) | |
passwordDigest := base64.StdEncoding.EncodeToString(src) | |
header.Set("Authorization", `WSSE profile="UsernameToken"`) | |
header.Set("X-WSSE", fmt.Sprintf( | |
`UsernameToken Username="%s", PasswordDigest="%s", Nonce="%s", Created="%s"`, | |
this.Username, | |
passwordDigest, | |
nonce, | |
date, | |
)) | |
} | |
func (this *Wsse) Get(u string) ([]byte, http.Header, error) { | |
log.Println("Wsse.Get") | |
defer log.Println("end of Wsse.Get") | |
req, err := http.NewRequest( | |
http.MethodGet, | |
u, | |
nil, | |
) | |
if err != nil { | |
return nil, nil, err | |
} | |
this.SetAuthorization(req.Header) | |
resp, err := http.DefaultClient.Do(req) | |
defer func() { | |
if resp != nil && resp.Body != nil { | |
resp.Body.Close() | |
} | |
}() | |
if err != nil { | |
if resp != nil { | |
log.Println(u) | |
log.Println(req.Header) | |
log.Println(resp.Status) | |
log.Println(resp.Header) | |
} | |
return nil, resp.Header, err | |
} | |
if resp.Body == nil { | |
return []byte{}, resp.Header, nil | |
} | |
respBody, err := ioutil.ReadAll(resp.Body) | |
if err != nil { | |
return nil, resp.Header, err | |
} | |
if resp.StatusCode != http.StatusOK { | |
log.Println(u) | |
log.Println(req.Header) | |
log.Println(resp.Status) | |
log.Println(resp.Header) | |
log.Println(string(respBody)) | |
return respBody, resp.Header, fmt.Errorf( | |
"%s %s", | |
resp.Status, | |
string(respBody), | |
) | |
} | |
return respBody, resp.Header, nil | |
} |
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
package main | |
// WSSE接続失敗するのでわからん…WSSEの実装を俺がミスってる…? | |
import ( | |
"io/ioutil" | |
) | |
type HatenaWsse Wsse | |
func (this *HatenaWsse) FotolifeFeed() (interface{}, error) { | |
const u = "https://f.hatena.ne.jp/atom/feed" | |
blob, _, err := this.GetWsse().Get(u) | |
if err != nil { | |
return nil, err | |
} | |
err = ioutil.WriteFile("fotolife_feed.xml", blob, 0664) | |
return "save to fotolife_feed.xml", err | |
} | |
func (this *HatenaWsse) GetWsse() *Wsse { | |
return (*Wsse)(this) | |
} |
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
package main | |
/* | |
encoding/xml | |
https://github.com/golang/go/issues?q=is%3Aissue+is%3Aopen+xml | |
挙動が色々怪しい… | |
https://github.com/golang/go/issues/9519 | |
XMLのNameSpaceの動作が…MarshalもUnmarshalも…ダメ | |
MarshalとUnmarshalでNameSpaceの扱いが違って一貫性がない… | |
Marshal時は | |
プリフィクスNameSpaceの追加は`xml:"xmlns:dc"`で出力したり | |
プリフィクス付きの<dc:subject>タグは`xml:"dc:subject"`で出力したり | |
Unmarshal時 | |
<dc:subject>を読み取るなら | |
`xml:"subject"` | |
か | |
`xml:"http://purl.org/dc/elements/1.1/ subject"` | |
のどちらかにしないと読めない | |
`xml:"dc:subject"`にすると読み込めない… | |
`xml:"http://purl.org/dc/elements/1.1/ dc:subject"`でも読み込めない… | |
Marshalのomitemptyはシンプルなstring定義以外はomitしてくんない… | |
*/ | |
import ( | |
"encoding/xml" | |
) | |
// title未指定だとIDがtitleに割り当てられるぽい | |
// titleはフォトライフ上で閲覧するときに表示されるだけ | |
// ディレクトリ(dc:subject)が指定されているとそこに保存される | |
// ディレクトリが指定されておらず、かつジェネレータ名(generator)が指定されてるとジェネレータ名のディレクトリに保存される | |
// 保存先が指定されない場合は公開ディレクトリに保存される | |
// 保存先が指定されtる場合、保存先が存在しない場合は指定した名前の非公開ディレクトリが作成される | |
// 保存先ディレクトリはあくまでフォトライフ内での管理上の分類、直リンURLには影響しない | |
type HatenaFotolifePostBody struct { | |
XMLName xml.Name `xml:"http://purl.org/atom/ns# entry"` | |
XmlNS string `xml:"xmlns:dc,attr,omitempty"` | |
Title string `xml:"title,omitempty"` | |
Content struct { | |
Mode string `xml:"mode,attr"` | |
Type string `xml:"type,attr"` | |
Value string `xml:",chardata"` | |
} `xml:"content"` | |
Subject string `xml:"dc:subject,omitempty"` | |
Generator string `xml:"generator,omitempty"` | |
} | |
// ドキュメントにはない hatena:imageurlmedium タグが取得xmlに存在… | |
// hatena:imageurlmedium は縦か横のどちらかサイズ(おそらく長辺)が120pxとして縮小(正方形の画像でテストしたから分からん…) | |
// hatena:imageurlsmall は 60px に縮小 | |
// なおブログで貼り付けられてる画像では縮小版は使われておらず | |
// ブログの記事一覧ページのアイキャッチ画像にもこの縮小版は使われていない…(アイキャッチ画像は一辺が500px前後に伸縮される) | |
// 縮小版は存在が謎すぎる…容量の無駄では…? | |
// ブログの画像のimgタグのtitle属性についてるの | |
// xmlのsyntaxの末尾の:image部分を:plainに置き換えてる感じぽい | |
// f:id:neetsdkasu:XXXXXXXXXXXXXXp:image | |
// id末尾の記号がファイルタイプを表すぽい | |
// jpegだと f:id:neetsdkasu:XXXXXXXXXXXXXXj:plain | |
// pngだと f:id:neetsdkasu:XXXXXXXXXXXXXXp:plain | |
type HatenaFotolifePostResult struct { | |
XMLName xml.Name `xml:"entry"` | |
Title string `xml:"title"` | |
Links []struct { | |
Rel string `xml:"rel,attr"` | |
Type string `xml:"type,attr"` | |
Href string `xml:"href,attr"` | |
Title string `xml:"title,attr"` | |
} `xml:"link"` | |
Issued string `xml:"issued"` | |
Author string `xml:"author>name"` | |
Generator struct { | |
Url string `xml:"url,attr"` | |
Vesion string `xml:"version,attr"` | |
Value string `xml:",chardata"` | |
} `xml:"generator"` | |
Subject string `xml:"subject"` | |
Id string `xml:"id"` | |
ImageUrl string `xml:"imageurl"` | |
ImageUrlMedium string `xml:"imageurlmedium"` | |
ImageUrlSmall string `xml:"imageurlsmall"` | |
Syntax string `xml:"syntax"` | |
} | |
type HatenaBlogAtomResult struct { | |
XMLName xml.Name `xml:"service"` | |
Title string `xml:"workspace>title"` | |
Collection struct { | |
Href string `xml:"href,attr"` | |
Title string `xml:"title"` | |
Accept string `xml:"accept"` | |
} `xml:"workspace>collection"` | |
} | |
type HatenaBlogCategory struct { | |
XMLName xml.Name `xml:"category"` | |
Term string `xml:"term,attr"` | |
} | |
type HatenaBlogAtomCategoryResult struct { | |
XMLName xml.Name `xml:"categories"` | |
Fixed string `xml:"fixed,attr"` | |
Categories []*HatenaBlogCategory `xml:"category"` | |
} | |
type HatenaBlogEntryResult struct { | |
XMLName xml.Name `xml:"entry"` | |
Id string `xml:"id"` | |
Links []struct { | |
Rel string `xml:"rel,attr"` | |
Href string `xml:"href,attr"` | |
} `xml:"link"` | |
Author string `xml:"author>name"` | |
Title string `xml:"title"` | |
Updated string `xml:"updated"` | |
Published string `xml:"published"` | |
Edited string `xml:"edited"` | |
Summary struct { | |
Type string `xml:"type,attr"` | |
Value string `xml:",chardata"` | |
} `xml:"summary"` | |
Content struct { | |
Type string `xml:"type,attr"` | |
Value string `xml:",chardata"` | |
} `xml:"content"` | |
FormattedContent struct { | |
Type string `xml:"type,attr"` | |
Value string `xml:",chardata"` | |
} `xml:"formatted-content"` | |
Categories []*HatenaBlogCategory `xml:"category"` | |
Draft string `xml:"control>draft"` | |
} | |
type HatenaBlogAtomEntryGetResult struct { | |
XMLName xml.Name `xml:"feed"` | |
Links []struct { | |
Rel string `xml:"rel,attr"` | |
Href string `xml:"href,attr"` | |
} `xml:"link"` | |
Title string `xml:"title"` | |
SubTitle string `xml:"subtitle"` | |
Updated string `xml:"updated"` | |
Author string `xml:"author>name"` | |
Generator struct { | |
Uri string `xml:"uri,attr"` | |
Vesion string `xml:"version,attr"` | |
Value string `xml:",chardata"` | |
} `xml:"generator"` | |
Id string `xml:"id"` | |
Entries []*HatenaBlogEntryResult `xml:"entry"` | |
} | |
// app:draft は yes か no | |
type HatenaBlogAtomEntryPostBody struct { | |
XMLName xml.Name `xml:"http://www.w3.org/2005/Atom entry"` | |
XmlNS string `xml:"xmlns:app,attr"` | |
Title string `xml:"title"` | |
Author string `xml:"author>name"` | |
Content struct { | |
Type string `xml:"type,attr"` | |
Value string `xml:",chardata"` | |
} `xml:"content"` | |
Updated string `xml:"updated,omitempty"` | |
Categories []*HatenaBlogCategory | |
Draft string `xml:"app:control>app:draft"` | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment