Go-twitter で GET friendships/lookup で作成した lookup API を利用して、go-twitter で相互フォローを実装してみます。
twitterut.go
package twitterut
import (
"bytes"
"context"
"fmt"
"net/http"
"strings"
"cloud.google.com/go/logging"
"github.com/dghubble/go-twitter/twitter"
"github.com/dghubble/oauth1"
"github.com/dghubble/sling"
)
type Client struct {
HTTPClient *http.Client
*twitter.Client
}
func NewClient() *Client {
config := oauth1.NewConfig("FIXME", "FIXME")
token := oauth1.NewToken("FIXME", "FIXME")
httpClient := config.Client(oauth1.NoContext, token)
client := twitter.NewClient(httpClient)
return &Client{
httpClient,
client,
}
}
func Followers(ctx context.Context, client *Client) ([]twitter.User, error) {
var cursor int64 = -1
result := make([]twitter.User, 0, 2000)
for {
followers, _, err := client.Followers.List(&twitter.FollowerListParams{
Cursor: cursor,
Count: 200,
SkipStatus: twitter.Bool(true),
IncludeUserEntities: twitter.Bool(false),
})
if err != nil {
return nil, err
}
result = append(result, followers.Users...)
cursor = followers.NextCursor
if cursor == 0 {
break
}
}
return result, nil
}
func Friends(ctx context.Context, client *Client) ([]twitter.User, error) {
var cursor int64 = -1
result := make([]twitter.User, 0, 2000)
for {
friends, _, err := client.Friends.List(&twitter.FriendListParams{
Cursor: cursor,
Count: 200,
SkipStatus: twitter.Bool(true),
IncludeUserEntities: twitter.Bool(false),
})
if err != nil {
return nil, err
}
result = append(result, friends.Users...)
cursor = friends.NextCursor
if cursor == 0 {
break
}
}
return result, nil
}
type FriendshipLookupStatus struct {
Name string `json:"name"`
ScreenName string `json:"screen_name"`
ID int64 `json:"id"`
IDStr string `json:"id_str"`
Connections []string `json:"connections"`
}
type FriendshipLookupParams struct {
UserID string `url:"user_id,omitempty"`
ScreenName string `url:"screen_name,omitempty"`
}
func relevantError(httpError error, apiError twitter.APIError) error {
if httpError != nil {
return httpError
}
if apiError.Empty() {
return nil
}
return apiError
}
func Lookup(ctx context.Context, client *Client, params *FriendshipLookupParams) ([]FriendshipLookupStatus, *http.Response, error) {
s := sling.New().Client(client.HTTPClient).Base("https://api.twitter.com/1.1/").Path("friendships/")
friendships := new([]FriendshipLookupStatus)
apiError := new(twitter.APIError)
resp, err := s.New().Get("lookup.json").QueryStruct(params).Receive(friendships, apiError)
return *friendships, resp, relevantError(err, *apiError)
}
func Friendships(ctx context.Context, client *Client, users []twitter.User) ([]FriendshipLookupStatus, error) {
result := make([]FriendshipLookupStatus, 0, len(users))
for i := 0; i < len(users); i += 100 { // GET friendships/lookup は一度のリクエストで 100 ユーザまで
var buf bytes.Buffer
unit := users[i:mathut.MinInt(i+100, len(users))]
for _, user := range unit {
stringut.AppendSplit(&buf, user.IDStr, ",")
}
friendships, _, err := Lookup(ctx, client, &FriendshipLookupParams{
UserID: buf.String(),
})
if err != nil {
return nil, err
}
result = append(result, friendships...)
}
return result, nil
}
func CreateFriendship(ctx context.Context, client *Client, targetID int64) (*twitter.User, error) {
user, _, err := client.Friendships.Create(&twitter.FriendshipCreateParams{
UserID: targetID,
})
if err != nil {
return nil, err
}
return user, nil
}
func DestroyFriendship(ctx context.Context, client *Client, targetID int64) (*twitter.User, error) {
user, _, err := client.Friendships.Destroy(&twitter.FriendshipDestroyParams{
UserID: targetID,
})
if err != nil {
return nil, err
}
return user, nil
}
func FollowEachOther(ctx context.Context, client *Client) {
{ // フォロワーの中で未フォローのユーザをフォロー
followers, err := Followers(ctx, client)
if err == nil {
for i := range followers {
if followers[i].Protected { // 鍵がかかっているユーザはフォローしない
} else if !followers[i].Following {
CreateFriendship(ctx, client, followers[i].ID)
}
}
}
}
{ // フォローユーザの中で自分をフォローしていないユーザをリムーブ
friends, err := Friends(ctx, client)
if err == nil {
// 現在フォロー中のユーザ(friends)が 1500 を超える場合、Friendships はエラーを返します
// (GET friendships/lookup が API 1 回の呼び出しで 100 ユーザまでしか取得できない、15 分の間で 15 回までしか API を呼び出せないため)
// 1500 ユーザを超える場合 1500 単位で区切って残りは次回にする等の実装が必要ですが、ここでは単純のためやっていません
friendships, err := Friendships(ctx, client, friends)
if err == nil {
for i := range friendships {
followedBy := false
for _, connection := range friendships[i].Connections {
if strings.EqualFold(connection, "followed_by") {
followedBy = true
break
}
}
if !followedBy {
DestroyFriendship(ctx, client, friendships[i].ID)
}
}
}
}
}
}
相互フォローって英語だとなんていうんだろ・・って調べたのですがよくわからなかったのでとりあえず FollowEachOther という名前にしてあります。