Skip to content

Instantly share code, notes, and snippets.

@neetsdkasu
Last active January 7, 2021 20:51
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/e422233d4b62242d2c6062ec4c11dd51 to your computer and use it in GitHub Desktop.
Save neetsdkasu/e422233d4b62242d2c6062ec4c11dd51 to your computer and use it in GitHub Desktop.
GoでTweet操作
*.exe
*.sh
*.json
*.cmd
*.txt
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
}
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)
},
),
"",
)
}
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
}
package main
type TwitterBearer struct {
TokenType string `json:"token_type"`
AccessToken string `json:"access_token"`
}
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 (
"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
}
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"`
}
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