-
-
Save neetsdkasu/e422233d4b62242d2c6062ec4c11dd51 to your computer and use it in GitHub Desktop.
GoでTweet操作
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
*.exe | |
*.sh | |
*.json | |
*.cmd | |
*.txt | |
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/binary" | |
"encoding/json" | |
"fmt" | |
"io/ioutil" | |
"log" | |
"os" | |
"os/exec" | |
"unicode/utf16" | |
) | |
const TwitterOAuthFileName = "twitter_oauth.json" | |
type PromptMode int | |
const ( | |
MainPromptMode PromptMode = iota | |
AccountPromptMode | |
) | |
type TwitterOAuth struct { | |
*TwitterConsumer `json:"consumer"` | |
AccessTokens []*TwitterAccessToken `json:"access_tokens"` | |
*TwitterBearer `json:"bearer"` | |
} | |
func main() { | |
if err := run(); err != nil { | |
log.Panic(err) | |
} | |
} | |
func run() error { | |
var twitter TwitterOAuth | |
err := twitter.Load() | |
if err != nil { | |
return err | |
} | |
if !twitter.HasConsumer() { | |
twitter.TwitterConsumer, err = inputConsumer() | |
if err != nil { | |
return err | |
} | |
err = twitter.Save() | |
if err != nil { | |
return err | |
} | |
} | |
var account *TwitterAccessToken | |
mode := MainPromptMode | |
for { | |
switch mode { | |
case MainPromptMode: | |
cmd, err := mainPrompt(&twitter) | |
if err != nil { | |
return err | |
} | |
switch cmd { | |
case 0: | |
return nil | |
case 1: | |
fmt.Println("bearer") | |
fmt.Println(twitter.TwitterBearer) | |
case 2: | |
err = authorize(&twitter) | |
if err != nil { | |
return err | |
} | |
default: | |
mode = AccountPromptMode | |
account = twitter.Account(cmd - 3) | |
} | |
case AccountPromptMode: | |
cmd, err := accountPrompt(account) | |
if err != nil { | |
return err | |
} | |
switch cmd { | |
case 0: | |
return nil | |
case 1: | |
mode = MainPromptMode | |
case 2: | |
err = postStatusesUpdate(&twitter, account) | |
if err != nil { | |
return err | |
} | |
case 3: | |
err = getFriendsList(&twitter, account) | |
if err != nil { | |
return err | |
} | |
case 4: | |
err = getListsList(&twitter, account) | |
if err != nil { | |
return err | |
} | |
case 5: | |
err = getListsMembers(&twitter, account) | |
if err != nil { | |
return err | |
} | |
} | |
} | |
} | |
} | |
func getListsMembers(twitter *TwitterOAuth, account *TwitterAccessToken) error { | |
res, err := twitter.GetListsList(account) | |
if err != nil { | |
return err | |
} | |
for i, ls := range res { | |
fmt.Println("[", i, "]", ls.Full_name) | |
} | |
fmt.Print("List Number?: ") | |
var cmd int | |
if err := input(&cmd); err != nil { | |
return err | |
} | |
if cmd < 0 || cmd >= len(res) { | |
return fmt.Errorf("invalid number", cmd) | |
} | |
detail := res[cmd] | |
obj, err := twitter.GetListsMembers(account, detail) | |
if err != nil { | |
return err | |
} | |
var b bytes.Buffer | |
for _, u := range obj.Users { | |
fmt.Fprintln(&b, u.Screen_name) | |
} | |
fname := fmt.Sprint("GetListsMembers-", detail.Slug, ".txt") | |
err = ioutil.WriteFile(fname, b.Bytes(), 0666) | |
if err != nil { | |
return err | |
} | |
fmt.Println(b.String()) | |
fmt.Println("menber count:", len(obj.Users)) | |
fmt.Println("write the list members to", fname) | |
return nil | |
} | |
func getListsList(twitter *TwitterOAuth, account *TwitterAccessToken) error { | |
res, err := twitter.GetListsList(account) | |
if err != nil { | |
return err | |
} | |
for _, ls := range res { | |
fmt.Println(ls.Slug, ls.Name, ls.Full_name) | |
} | |
fmt.Println("lists count:", len(res)) | |
return nil | |
} | |
func getFriendsList(twitter *TwitterOAuth, account *TwitterAccessToken) error { | |
res, err := twitter.GetFriendsList(account) | |
if err != nil { | |
return err | |
} | |
var b bytes.Buffer | |
for _, u := range res.Users { | |
fmt.Fprintln(&b, u.Screen_name) | |
} | |
const fname = "GetFriendsList.txt" | |
err = ioutil.WriteFile(fname, b.Bytes(), 0666) | |
if err != nil { | |
return err | |
} | |
fmt.Println(b.String()) | |
fmt.Println("member size:", len(res.Users)) | |
fmt.Println("write list to", fname) | |
return nil | |
} | |
func postStatusesUpdate(twitter *TwitterOAuth, account *TwitterAccessToken) error { | |
text, err := inputText() | |
if err != nil { | |
return err | |
} | |
if text == "" { | |
fmt.Println("canceled") | |
} else { | |
fmt.Println(text) | |
ok, err := confirmPrompt("send this message?") | |
if err != nil { | |
return err | |
} else if !ok { | |
fmt.Println("canceled") | |
} else { | |
err = twitter.PostStatusesUpdate(account, text, "") | |
if err != nil { | |
return err | |
} | |
fmt.Println("success") | |
} | |
} | |
return nil | |
} | |
func authorize(twitter *TwitterOAuth) error { | |
reqToken, err := twitter.GetRequestToken() | |
if err != nil { | |
return err | |
} | |
u := reqToken.AuthorizeUrl() | |
fmt.Println("Authorize") | |
fmt.Println(u) | |
fmt.Print("PIN: ") | |
var pin string | |
if err = input(&pin); err != nil { | |
return err | |
} | |
accToken, err := twitter.GetAccessToken(reqToken, pin) | |
if err != nil { | |
return err | |
} | |
twitter.AccessTokens = append( | |
twitter.AccessTokens, | |
accToken, | |
) | |
err = twitter.Save() | |
if err != nil { | |
return err | |
} | |
const text = "usakdsteen gets this account under control." | |
err = twitter.PostStatusesUpdate(accToken, text, "") | |
if err != nil { | |
return err | |
} | |
return nil | |
} | |
func inputText() (string, error) { | |
f, err := ioutil.TempFile("", "tweet_*.txt") | |
if err != nil { | |
return "", err | |
} | |
defer os.Remove(f.Name()) | |
if err = f.Close(); err != nil { | |
return "", err | |
} | |
cmd := exec.Command("notepad.exe", "/W", f.Name()) | |
if err = cmd.Run(); err != nil { | |
return "", err | |
} | |
blob, err := ioutil.ReadFile(f.Name()) | |
if err != nil { | |
return "", err | |
} | |
if len(blob) == 0 { | |
return "", nil | |
} | |
r := bytes.NewReader(blob) | |
data := make([]uint16, len(blob)/2) | |
if err = binary.Read(r, binary.LittleEndian, data); err != nil { | |
return "", err | |
} | |
s := utf16.Decode(data[1:]) | |
return string(s), nil | |
} | |
func input(data interface{}) error { | |
if n, err := fmt.Scanln(data); err != nil { | |
return err | |
} else if n == 0 { | |
return fmt.Errorf("no input") | |
} | |
return nil | |
} | |
func confirmPrompt(message string) (bool, error) { | |
fmt.Println() | |
fmt.Println(message) | |
fmt.Println("0 - Cancel") | |
fmt.Println("1 - Ok") | |
fmt.Print("Command: ") | |
var cmd int | |
if err := input(&cmd); err != nil { | |
return false, err | |
} | |
return cmd == 1, nil | |
} | |
func accountPrompt(account *TwitterAccessToken) (int, error) { | |
fmt.Println() | |
fmt.Println("Current Account:", account.ScreenName) | |
fmt.Println("0 - exit") | |
fmt.Println("1 - change account") | |
fmt.Println("2 - POST statuses/update") | |
fmt.Println("3 - Get friends/list") | |
fmt.Println("4 - Get lists/list") | |
fmt.Println("5 - Get lists/members") | |
fmt.Print("Command: ") | |
var cmd int | |
if err := input(&cmd); err != nil { | |
return 0, err | |
} | |
return cmd, nil | |
} | |
func mainPrompt(twitter *TwitterOAuth) (int, error) { | |
fmt.Println() | |
fmt.Println("0 - exit") | |
fmt.Println("1 - use bearer") | |
fmt.Println("2 - add account") | |
for i, ats := range twitter.AccessTokens { | |
fmt.Println(i+3, "- use account", ats.ScreenName) | |
} | |
fmt.Print("Command: ") | |
var cmd int | |
if err := input(&cmd); err != nil { | |
return 0, err | |
} | |
return cmd, nil | |
} | |
func inputConsumer() (*TwitterConsumer, error) { | |
var key, secret string | |
fmt.Print("ConsumerKey: ") | |
if err := input(&key); err != nil { | |
return nil, err | |
} | |
fmt.Print("ConsumerSecret: ") | |
if err := input(&secret); err != nil { | |
return nil, err | |
} | |
return &TwitterConsumer{ | |
ConsumerKey: key, | |
ConsumerSecret: secret, | |
}, nil | |
} | |
func (this *TwitterOAuth) HasConsumer() bool { | |
return this.TwitterConsumer != nil | |
} | |
func (this *TwitterOAuth) Account(index int) *TwitterAccessToken { | |
return this.AccessTokens[index] | |
} | |
func (this *TwitterOAuth) Save() error { | |
blob, err := json.Marshal(this) | |
if err != nil { | |
return err | |
} | |
err = ioutil.WriteFile(TwitterOAuthFileName, blob, 0666) | |
if err != nil { | |
return err | |
} | |
return nil | |
} | |
func (this *TwitterOAuth) Load() error { | |
blob, err := ioutil.ReadFile(TwitterOAuthFileName) | |
if err != nil { | |
if os.IsNotExist(err) { | |
return nil | |
} | |
return err | |
} | |
if err = json.Unmarshal(blob, this); err != nil { | |
return err | |
} | |
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 ( | |
"encoding/base64" | |
"math/rand" | |
"strings" | |
"sync" | |
"time" | |
"unicode" | |
) | |
var nonceNoise = struct { | |
count int64 | |
sync.Mutex | |
}{ | |
count: 97531, | |
} | |
func getNonceNoise() int64 { | |
nonceNoise.Lock() | |
defer nonceNoise.Unlock() | |
ret := nonceNoise.count | |
nonceNoise.count = (ret + 100007) % 1000000007 | |
return ret | |
} | |
func GenerateNonce() string { | |
src := make([]byte, 32) | |
noise := getNonceNoise() | |
rand.Seed(time.Now().Unix() + noise) | |
for i := 1 + (noise % 5); i > 0; i-- { | |
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" | |
) | |
const DebugModeOAuth = false | |
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"` | |
} | |
type StrValues map[string]interface{} | |
func (this StrValues) Get(key string) string { | |
if v, ok := this[key]; ok { | |
if s, ok := v.(string); ok { | |
return s | |
} | |
} | |
return "" | |
} | |
func (this *Consumer) TakeRequestToken(u, cb string, form url.Values) (*RequestToken, interface{}, error) { | |
if DebugModeOAuth { | |
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) | |
if cb == "" || cb == "oob" { | |
params.SetOAuthCallbackOob() | |
} else { | |
if _, err = url.Parse(cb); err != nil { | |
return nil, nil, err | |
} | |
params.SetOAuthCallback(cb) | |
} | |
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, StrValues(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) { | |
if DebugModeOAuth { | |
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) { | |
if DebugModeOAuth { | |
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) { | |
if DebugModeOAuth { | |
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) | |
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.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) { | |
if DebugModeOAuth { | |
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) { | |
if DebugModeOAuth { | |
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 | |
type TwitterBearer struct { | |
TokenType string `json:"token_type"` | |
AccessToken string `json:"access_token"` | |
} |
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 ( | |
"encoding/json" | |
"log" | |
"net/url" | |
) | |
type TwitterConsumer Consumer | |
type TwitterRequestToken RequestToken | |
type TwitterAccessToken struct { | |
*AccessToken `json:"access_token"` | |
UserId string `json:"user_id"` | |
ScreenName string `json:"screen_name"` | |
} | |
func (this *TwitterRequestToken) AuthorizeUrl() string { | |
const API = "https://api.twitter.com/oauth/authorize?" | |
params := url.Values{} | |
params.Set("oauth_token", this.Token) | |
return API + params.Encode() | |
} | |
func (this *TwitterConsumer) GetRequestToken() (*TwitterRequestToken, error) { | |
const API = "https://api.twitter.com/oauth/request_token" | |
res, _, err := (*Consumer)(this).TakeRequestToken(API, "oob", nil) | |
if err != nil { | |
return nil, err | |
} | |
return (*TwitterRequestToken)(res), nil | |
} | |
func (this *TwitterConsumer) GetAccessToken(twReqToken *TwitterRequestToken, verifier string) (*TwitterAccessToken, error) { | |
const API = "https://api.twitter.com/oauth/access_token?" | |
reqToken := (*RequestToken)(twReqToken) | |
params := url.Values{} | |
params.Set("oauth_token", reqToken.Token) | |
params.Set("oauth_verifier", verifier) | |
u := API + params.Encode() | |
res, v, err := (*Consumer)(this).TakeAccessToken(u, reqToken, verifier) | |
if err != nil { | |
return nil, err | |
} | |
ret := &TwitterAccessToken{ | |
AccessToken: res, | |
} | |
switch values := v.(type) { | |
case url.Values: | |
ret.UserId = values.Get("user_id") | |
ret.ScreenName = values.Get("screen_name") | |
case map[string]interface{}: | |
strValues := StrValues(values) | |
ret.UserId = strValues.Get("user_id") | |
ret.ScreenName = strValues.Get("screen_name") | |
} | |
return ret, nil | |
} | |
func (this *TwitterConsumer) PostStatusesUpdate(twAccToken *TwitterAccessToken, status, replyId string) error { | |
const API = "https://api.twitter.com/1.1/statuses/update.json?" | |
accToken := twAccToken.AccessToken | |
params := url.Values{} | |
params.Set("status", status) | |
if replyId != "" { | |
params.Set("in_reply_to_status_id", replyId) | |
} | |
u := API + params.Encode() | |
res, _, err := (*Consumer)(this).Post(u, accToken, "", nil) | |
if err != nil { | |
return err | |
} | |
log.Println(string(res)) | |
return nil | |
} | |
// https://developer.twitter.com/en/docs/twitter-api/v1/accounts-and-users/follow-search-get-users/api-reference/get-friends-list | |
func (this *TwitterConsumer) GetFriendsList(twAccToken *TwitterAccessToken) (*TwitterGetFriendsListResult, error) { | |
const API = "https://api.twitter.com/1.1/friends/list.json?" | |
// PARAMS | |
// user_id optional | |
// screen_name optional | |
// cursor semi-optional (-1) | |
// count optional (20) (max 200) | |
// skip_status optional (false) | |
// include_user_entities optional (true) | |
accToken := twAccToken.AccessToken | |
params := url.Values{} | |
params.Set("cursor", "-1") | |
params.Set("count", "200") | |
u := API + params.Encode() | |
res, _, err := (*Consumer)(this).Get(u, accToken) | |
if err != nil { | |
return nil, err | |
} | |
obj := &TwitterGetFriendsListResult{} | |
err = json.Unmarshal(res, obj) | |
if err != nil { | |
log.Println(string(res)) | |
return nil, err | |
} | |
return obj, nil | |
} | |
// https://developer.twitter.com/en/docs/twitter-api/v1/accounts-and-users/create-manage-lists/api-reference/get-lists-list | |
func (this *TwitterConsumer) GetListsList(twAccToken *TwitterAccessToken) (TwitterGetListsListResult, error) { | |
const API = "https://api.twitter.com/1.1/lists/list.json" | |
// PARAMS | |
// user_id optional | |
// screen_name optional | |
// reverse optional (false) | |
accToken := twAccToken.AccessToken | |
// params := url.Values{} | |
// u := API + params.Encode() | |
res, _, err := (*Consumer)(this).Get(API, accToken) | |
if err != nil { | |
return nil, err | |
} | |
obj := TwitterGetListsListResult{} | |
err = json.Unmarshal(res, &obj) | |
if err != nil { | |
log.Println(string(res)) | |
return nil, err | |
} | |
return obj, nil | |
} | |
func (this *TwitterConsumer) GetListsMembers(twAccToken *TwitterAccessToken, detail *TwitterListDetail) (*TwitterGetListsMembersResult, error) { | |
const API = "https://api.twitter.com/1.1/lists/members.json?" | |
// PARAMS | |
// list_id required | |
// slug required (need owner_id or owner_screen_name) | |
// owner_screen_name optional | |
// owner_id optional | |
// count optional (20) (max 5000) | |
// cursor optional (-1) | |
// include_entities optional (true) | |
// skip_status optional (fale) | |
accToken := twAccToken.AccessToken | |
params := url.Values{} | |
params.Set("list_id", detail.Id_str) | |
params.Set("count", "200") | |
params.Set("cursor", "-1") | |
u := API + params.Encode() | |
res, _, err := (*Consumer)(this).Get(u, accToken) | |
if err != nil { | |
return nil, err | |
} | |
obj := &TwitterGetListsMembersResult{} | |
err = json.Unmarshal(res, &obj) | |
if err != nil { | |
return nil, err | |
} | |
return obj, 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 | |
// https://developer.twitter.com/en/docs/twitter-api/v1/data-dictionary/object-model/user | |
type TwitterUserObject struct { | |
Created_at string `json:"created_at"` | |
Default_profile bool `json:"default_profile"` | |
Default_profile_image bool `json:"default_profile_image"` | |
Description string `json:"description"` | |
Entities *TwitterEntitiesForUserObject `json:"entities"` | |
Favourites_count int `json:"favourites_count"` | |
Followers_count int `json:"followers_count"` | |
Friends_count int `json:"friends_count"` | |
Id int64 `json:"id"` | |
Id_str string `json:"id_str"` | |
Listed_count int `json:"listed_count"` | |
Location string `json:"location"` | |
Name string `json:"name"` | |
Profile_banner_url string `json:"profile_banner_url"` | |
Profile_image_url_https string `json:"profile_image_url_https"` | |
Protected bool `json:"protected"` | |
Screen_name string `json:"screen_name"` | |
Statuses_count int `json:"statuses_count"` | |
Url string `json:"url"` | |
Verified bool `json:"verified"` | |
/* 常に null (情報取得に代替手段が存在) */ | |
Follow_request_sent interface{} `json:"follow_request_sent"` | |
Following interface{} `json:"following"` | |
Geo_enabled interface{} `json:"geo_enabled"` | |
Lang interface{} `json:"lang"` | |
Time_zone interface{} `json:"time_zone"` | |
Utc_offset interface{} `json:"utc_offset"` | |
/* 常に null (Deprecated) */ | |
Contributors_enabled interface{} `json:"contributors_enabled"` | |
Has_extended_profile interface{} `json:"has_extended_profile"` | |
Is_translation_enabled interface{} `json:"is_translation_enabled"` | |
Is_translator interface{} `json:"is_translator"` | |
Notifications interface{} `json:"notifications"` | |
Profile_background_color interface{} `json:"profile_background_color"` | |
Profile_background_image_url interface{} `json:"profile_background_image_url"` | |
Profile_background_image_url_https interface{} `json:"profile_background_image_url_https"` | |
Profile_background_tile interface{} `json:"profile_background_tile"` | |
Profile_image_url interface{} `json:"profile_image_url"` | |
Profile_link_color interface{} `json:"profile_link_color"` | |
Profile_location interface{} `json:"profile_location"` | |
Profile_sidebar_border_color interface{} `json:"profile_sidebar_border_color"` | |
Profile_sidebar_fill_color interface{} `json:"profile_sidebar_fill_color"` | |
Profile_text_color interface{} `json:"profile_text_color"` | |
Profile_use_background_image interface{} `json:"profile_use_background_image"` | |
Translator_type interface{} `json:"translator_type"` | |
/* 特定のレスポンスに存在…? */ | |
Status *TwitterTweetObject `json:"status"` | |
} | |
// https://developer.twitter.com/en/docs/twitter-api/v1/data-dictionary/object-model/entities#entities-user | |
type TwitterEntitiesForUserObject struct { | |
Description *TwitterURLObjectArray `json:"description"` | |
Url *TwitterURLObjectArray `json:"url"` | |
} | |
type TwitterURLObjectArray struct { | |
Urls []*TwitterURLObject `json:"urls"` | |
} | |
// https://developer.twitter.com/en/docs/twitter-api/v1/data-dictionary/object-model/entities#mentions | |
type TwitterURLObject struct { | |
Display_url string `json:"display_url"` | |
Expanded_url string `json:"expanded_url"` | |
Indices []int `json:"indices"` | |
Url string `json:"url"` | |
Unwound *TwitterURLObjectUnwound `json:"unwound"` | |
} | |
type TwitterURLObjectUnwound struct { | |
Url string `json:"url"` | |
Status int `json:"status"` | |
Title string `json:"title"` | |
Description string `json:"description"` | |
} | |
// https://developer.twitter.com/en/docs/twitter-api/v1/data-dictionary/object-model/tweet | |
type TwitterTweetObject struct { | |
Created_at string `json:"created_at"` | |
Id int64 `json:"id"` | |
Id_str string `json:"id_str"` | |
Text string `json:"text"` | |
Source string `json:"source"` | |
Truncated bool `json:"truncated"` | |
In_reply_to_status_id int64 `json:"in_reply_to_status_id"` | |
In_reply_to_status_id_str string `json:"in_reply_to_status_id_str"` | |
In_reply_to_user_id int64 `json:"in_reply_to_user_id"` | |
In_reply_to_user_id_str string `json:"in_reply_to_user_id_str"` | |
In_reply_to_screen_name string `json:"in_reply_to_screen_name"` | |
User *TwitterUserObject `json:"user"` | |
Coordinates *TwitterCoordinateObject `json:"coordinates"` | |
Place *TwitterPlaceObject `json:"place"` | |
Quoted_status_id int64 `json:"quoted_status_id"` | |
Quoted_status_id_str string `json:"quoted_status_id_str"` | |
Is_quote_status bool `json:"is_quote_status"` | |
Quoted_status *TwitterTweetObject `json:"quoted_status"` | |
Retweeted_status *TwitterTweetObject `json:"retweeted_status"` | |
Quote_count int `json:"quote_count"` /* nullable */ | |
Reply_count int `json:"reply_count"` | |
Retweet_count int `json:"retweet_count"` | |
Favorite_count int `json:"favorite_count"` /* nullable */ | |
Entities *TwitterEntities `json:"entities"` | |
Extended_entities *TwitterExtendedEntities `json:"extended_entities"` | |
Favorited bool `json:"favorited"` | |
Retweeted bool `json:"retweeted"` | |
Possibly_sensitive bool `json:"possibly_sensitive"` | |
Filter_level string `json:"filter_level"` | |
Lang string `json:"lang"` /* nullable */ | |
Matching_rules []*TwitterRuleObject `json:"matching_rules"` | |
/* Additional Tweet attributes */ | |
Current_user_retweet *struct { | |
Id int64 `json:"id"` | |
Id_str string `json:"id_str"` | |
} `json:"current_user_retweet"` | |
Scopes *struct { | |
Followers bool `json:"followers"` | |
} `json:"scopes"` | |
Withheld_copyright bool `json:"withheld_copyright"` | |
Withheld_in_countries []string `json:"withheld_in_countries"` | |
Withheld_scope string `json:"withheld_scope"` | |
/* Deprecated */ | |
Geo interface{} `json:"geo"` | |
/* 特定のレスポンスに存在…? */ | |
Contributors interface{} `json:"contributors"` | |
} | |
// https://developer.twitter.com/en/docs/twitter-api/v1/data-dictionary/object-model/geo#coordinates | |
type TwitterCoordinateObject struct { | |
Coordinates []float64 `json:"coordinates"` | |
Type string `json:"type"` | |
} | |
// https://developer.twitter.com/en/docs/twitter-api/v1/data-dictionary/object-model/geo#place | |
type TwitterPlaceObject struct { | |
Id string `json:"id"` | |
Url string `json:"url"` | |
Place_type string `json:"place_type"` | |
Name string `json:"name"` | |
Full_name string `json:"full_name"` | |
Country_code string `json:"country_code"` | |
Country string `json:"country"` | |
Bounding_box *struct { | |
Coordinates [][][]float64 `json:"coordinates"` | |
Type string `json:"type"` | |
} `json:"bounding_box"` | |
Attributes interface{} `json:"attributes"` | |
} | |
// https://developer.twitter.com/en/docs/twitter-api/v1/data-dictionary/object-model/entities | |
type TwitterEntities struct { | |
Hashtags []*TwitterHashtagObject `json:"hashtags"` | |
Urls []*TwitterURLObject `json:"urls"` | |
User_mentions []*TwitterUserMentionObject `json:"user_mentions"` | |
Symbols []*TwitterSymbolObject `json:"symbols"` | |
Media []*TwitterMediaObject `json:"media"` | |
Polls []*TwitterPollObject `json:"polls"` | |
} | |
// https://developer.twitter.com/en/docs/twitter-api/v1/data-dictionary/object-model/entities#hashtags | |
type TwitterHashtagObject struct { | |
Indices []int `json:"indices"` | |
Text string `json:"text"` | |
} | |
// https://developer.twitter.com/en/docs/twitter-api/v1/data-dictionary/object-model/entities#media | |
type TwitterUserMentionObject struct { | |
Id int64 `json:"id"` | |
Id_str string `json:"id_str"` | |
Indices []int `json:"indices"` | |
Name string `json:"name"` | |
Screen_name string `json:"screen_name"` | |
} | |
// https://developer.twitter.com/en/docs/twitter-api/v1/data-dictionary/object-model/entities#symbols | |
type TwitterSymbolObject struct { | |
Indices []int `json:"indices"` | |
Text string `json:"text"` | |
} | |
// https://developer.twitter.com/en/docs/twitter-api/v1/data-dictionary/object-model/entities#media | |
type TwitterMediaObject struct { | |
Display_url string `json:"display_url"` | |
Expanded_url string `json:"expanded_url"` | |
Id int64 `json:"id"` | |
Id_str string `json:"id_str"` | |
Indices []int `json:"indices"` | |
Media_url string `json:"media_url"` | |
Media_url_https string `json:"media_url_https"` | |
Sizes *TwitterMediaSizeObject `json:"sizes"` | |
Source_status_id int64 `json:"source_status_id"` | |
Source_status_id_str int64 `json:"source_status_id_str"` | |
Type string `json:"type"` | |
Url string `json:"url"` | |
/* Extended Entities */ | |
Video_info *struct { | |
Aspect_ratio []int `json:"aspect_ratio"` | |
Duration_millis int `json:"duration_millis"` | |
Variants []*struct { | |
Bitrate int `json:"bitrate"` | |
Content_type string `json:"content_type"` | |
Url string `json:"url"` | |
} `json:"variants"` | |
} `json:"video_info"` | |
Additional_media_info *struct { | |
Title string `json:"title"` | |
Description string `json:"description"` | |
Embeddable bool `json:"embeddable"` | |
Monetizable bool `json:"monetizable"` | |
} `json:"additional_media_info"` | |
} | |
// https://developer.twitter.com/en/docs/twitter-api/v1/data-dictionary/object-model/entities#media-size | |
type TwitterMediaSizeObject struct { | |
Thumb *TwitterSizeObject `json:"thumb"` | |
Large *TwitterSizeObject `json:"large"` | |
Medium *TwitterSizeObject `json:"medium"` | |
Small *TwitterSizeObject `json:"small"` | |
} | |
type TwitterSizeObject struct { | |
W int `json:"w"` | |
H int `json:"h"` | |
Resize string `json:"resize"` | |
} | |
// https://developer.twitter.com/en/docs/twitter-api/v1/data-dictionary/object-model/entities#polls | |
type TwitterPollObject struct { | |
Options []*struct { | |
Position int64 `json:"position"` | |
Text string `json:"text"` | |
} `json:"options"` | |
End_datetime string `json:"end_datetime"` | |
Duration_minutes string `json:"duration_minutes"` | |
} | |
// https://developer.twitter.com/en/docs/twitter-api/v1/data-dictionary/object-model/extended-entities | |
type TwitterExtendedEntities struct { | |
Media []*TwitterMediaObject `json:"media"` | |
} | |
type TwitterRuleObject struct { | |
Tag string `json:"tag"` | |
Id int64 `json:"id"` | |
Id_str string `json:"id_str"` | |
} |
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 TwitterGetFriendsListResult struct { | |
Next_cursor int64 `json:"next_cursor"` | |
Next_cursor_str string `json:"next_cursor_str"` | |
Previous_cursor int64 `json:"previous_cursor"` | |
Previous_cursor_str string `json:"previous_cursor_str"` | |
Users []*TwitterUserObject `json:"users"` | |
} | |
type TwitterGetListsListResult []*TwitterListDetail | |
type TwitterListDetail struct { | |
Created_at string `json:"created_at"` | |
Description string `json:"description"` | |
Following bool `json:"following"` | |
Full_name string `json:"full_name"` | |
Id int64 `json:"id"` | |
Id_str string `json:"id_str"` | |
Member_count int `json:"member_count"` | |
Mode string `json:"mode"` | |
Name string `json:"name"` | |
Slug string `json:"slug"` | |
Subscriber_count int `json:"subscriber_count"` | |
Uri string `json:"uri"` | |
User *TwitterUserObject `json:"user"` | |
} | |
type TwitterGetListsMembersResult struct { | |
Next_cursor int64 `json:"next_cursor"` | |
Next_cursor_str string `json:"next_cursor_str"` | |
Previous_cursor int64 `json:"previous_cursor"` | |
Previous_cursor_str string `json:"previous_cursor_str"` | |
Users []*TwitterUserObject `json:"users"` | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment