LoginSignup
0
0

More than 3 years have passed since last update.

Go-twitter で相互フォローを実装する

Last updated at Posted at 2020-04-07

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 という名前にしてあります。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0