205 lines
5.8 KiB
Go
205 lines
5.8 KiB
Go
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"io/ioutil"
|
|
"log"
|
|
"net/http"
|
|
"net/url"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
const TWITTER_API_URL = "https://api.twitter.com/1.1"
|
|
|
|
type TwitterApi struct {
|
|
BaseUrl string
|
|
AccessToken string
|
|
}
|
|
|
|
func NewTwitterApi(baseUrl, accesToken string) *TwitterApi {
|
|
return &TwitterApi{baseUrl, accesToken}
|
|
}
|
|
|
|
func (t *TwitterApi) encodeParams(params map[string]string) string {
|
|
|
|
q := url.Values{}
|
|
for k, v := range params {
|
|
q.Set(k, v)
|
|
}
|
|
return q.Encode()
|
|
|
|
}
|
|
|
|
func (t *TwitterApi) createGetPathAndParams(apiEndpoint string, params map[string]string) string {
|
|
u, err := url.Parse(apiEndpoint)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
q := u.Query()
|
|
for k, v := range params {
|
|
q.Set(k, v)
|
|
}
|
|
u.RawQuery = q.Encode()
|
|
return u.String()
|
|
}
|
|
|
|
func (t *TwitterApi) GetBase64EncodedBearerTokenCredentials(consumerKey, consumerSecret string) string {
|
|
data := []byte(consumerKey + ":" + consumerSecret)
|
|
return base64.URLEncoding.EncodeToString(data)
|
|
}
|
|
|
|
func (t *TwitterApi) Get(path_ string) (resp *http.Response, err error) {
|
|
req, err := http.NewRequest("GET", t.BaseUrl+path_, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
req.Header.Set("Authorization", "Bearer "+t.AccessToken)
|
|
req.Header.Set("content-type", "application/json; charset=utf-8")
|
|
return http.DefaultClient.Do(req)
|
|
}
|
|
|
|
func (t *TwitterApi) Post(path_ string, body string) (resp *http.Response, err error) {
|
|
req, err := http.NewRequest("POST", t.BaseUrl+path_, bytes.NewBufferString(body))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
req.Header.Set("Authorization", "Bearer "+t.AccessToken)
|
|
req.Header.Set("content-type", "application/json; charset=utf-8")
|
|
return http.DefaultClient.Do(req)
|
|
}
|
|
|
|
func (t *TwitterApi) GetAndDeserialize(path string, params map[string]string, v interface{}) (err error) {
|
|
defer func() {
|
|
if err == io.EOF {
|
|
err = nil
|
|
}
|
|
}()
|
|
if r, err := t.Get(t.createGetPathAndParams(path, params)); err != nil {
|
|
return err
|
|
} else if r.StatusCode/100 != 2 {
|
|
if errMsg, err := ioutil.ReadAll(r.Body); err != nil {
|
|
return errors.New("error getting endpoint and cannot deserialize error message")
|
|
} else {
|
|
return NewTwitterErr(string(errMsg), r.StatusCode)
|
|
}
|
|
} else {
|
|
d := json.NewDecoder(r.Body)
|
|
return d.Decode(v)
|
|
}
|
|
}
|
|
|
|
func (t *TwitterApi) PostAndDeserialize(path string, params map[string]string, v interface{}) (err error) {
|
|
defer func() {
|
|
if err == io.EOF {
|
|
err = nil
|
|
}
|
|
}()
|
|
if r, err := t.Post(path, t.encodeParams(params)); err != nil {
|
|
return err
|
|
} else if r.StatusCode/100 != 2 {
|
|
if errMsg, err := ioutil.ReadAll(r.Body); err != nil {
|
|
return errors.New("error getting endpoint and cannot deserialize error message")
|
|
} else {
|
|
return NewTwitterErr(string(errMsg), r.StatusCode)
|
|
}
|
|
} else {
|
|
d := json.NewDecoder(r.Body)
|
|
return d.Decode(v)
|
|
}
|
|
}
|
|
|
|
func (t *TwitterApi) GetTwitterIdByScreenName(sceenName string) (id uint64, err error) {
|
|
ids := make([]*idHolder, 0, 1)
|
|
params := map[string]string{"screen_name": sceenName, "include_entities": "id"}
|
|
apiPath := "/users/lookup.json"
|
|
if err := t.GetAndDeserialize(apiPath, params, &ids); err != nil {
|
|
return 0, err
|
|
} else if len(ids) < 1 {
|
|
return 0, errors.New("cannot find user with screen name " + sceenName)
|
|
}
|
|
return ids[0].Id, nil
|
|
}
|
|
|
|
func (t *TwitterApi) GetFollowerByCursor(screenName, cursor string) <-chan *FollowerList {
|
|
followerListC := make(chan *FollowerList)
|
|
go func() {
|
|
params := map[string]string{"screen_name": screenName, "count": "200", "skip_status": "true", "cursor": cursor}
|
|
apiPath := "/followers/list.json"
|
|
followers := new(FollowerList)
|
|
if err := t.GetAndDeserialize(apiPath, params, followers); err == nil {
|
|
followerListC <- followers
|
|
} else if twitterErr, ok := err.(*TwitterErr); ok && twitterErr.Status == 429 {
|
|
log.Println("api limit reached, need to sleep")
|
|
time.Sleep(5 * 60 * time.Second)
|
|
followerListC <- <-t.GetFollowerByCursor(screenName, cursor)
|
|
} else {
|
|
log.Println(err)
|
|
followerListC <- nil
|
|
}
|
|
}()
|
|
return followerListC
|
|
}
|
|
|
|
func (t *TwitterApi) GetGetFollowerIdsByCursor(screenName, cursor string) <-chan *FollowerIDList {
|
|
followerListC := make(chan *FollowerIDList)
|
|
|
|
go func() {
|
|
params := map[string]string{"screen_name": screenName, "count": "5000", "cursor": cursor}
|
|
apiPath := "/followers/ids.json"
|
|
followers := new(FollowerIDList)
|
|
if err := t.GetAndDeserialize(apiPath, params, followers); err == nil {
|
|
followerListC <- followers
|
|
} else if twitterErr, ok := err.(*TwitterErr); ok && twitterErr.Status == 429 {
|
|
log.Println("api limit reached, need to sleep")
|
|
time.Sleep(5 * 60 * time.Second)
|
|
followerListC <- <-t.GetGetFollowerIdsByCursor(screenName, cursor)
|
|
} else {
|
|
log.Println(err)
|
|
followerListC <- nil
|
|
}
|
|
}()
|
|
|
|
return followerListC
|
|
}
|
|
|
|
func (t *TwitterApi) GetScreenNameOfUsersByIds(ids []uint64) <-chan string {
|
|
if len(ids) >= 100 {
|
|
log.Println("GetScreenNameOfUsersByIds received a list of more that 100 ids. This is not supported by twitter")
|
|
}
|
|
screenNameC := make(chan string)
|
|
go func() {
|
|
path := "/users/lookup.json"
|
|
params := map[string]string{"include_entities": "false", "user_id": t.asCommaSeparatedString(ids)}
|
|
users := make([]*User, 0, len(ids))
|
|
if err := t.PostAndDeserialize(path, params, users); err == nil {
|
|
for _, user := range users {
|
|
screenNameC <- user.ScreenName
|
|
}
|
|
} else if twitterErr, ok := err.(*TwitterErr); ok && twitterErr.Status == 429 {
|
|
log.Println("api limit reached, need to sleep")
|
|
time.Sleep(5 * 60 * time.Second)
|
|
screenNameC <- <-t.GetScreenNameOfUsersByIds(ids)
|
|
} else {
|
|
log.Println(err)
|
|
screenNameC <- ""
|
|
}
|
|
close(screenNameC)
|
|
|
|
}()
|
|
return screenNameC
|
|
}
|
|
|
|
func (t *TwitterApi) asCommaSeparatedString(lst []uint64) string {
|
|
lstAsString := make([]string, len(lst))
|
|
for i, n := range lst {
|
|
lstAsString[i] = fmt.Sprintf("%v", n)
|
|
}
|
|
return strings.Join(lstAsString, ",")
|
|
}
|