Skip to content

Instantly share code, notes, and snippets.

@neetsdkasu
Last active August 9, 2021 13:37
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save neetsdkasu/4a26ced27af45a71a9c9b7c41bc22b0e to your computer and use it in GitHub Desktop.
Save neetsdkasu/4a26ced27af45a71a9c9b7c41bc22b0e to your computer and use it in GitHub Desktop.
Hatena OAuth
*.json
*.exe
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)
}
module hatena-oauth
go 1.16
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
}
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)
}
}
}
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)
},
),
"",
)
}
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
}
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
}
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
}
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
}
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
}
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
}
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")
}
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
}
}
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
}
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
}
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
}
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
}
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
}
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
}
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)
}
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