Skip to content

Instantly share code, notes, and snippets.

@jdolitsky
Created February 12, 2020 20:56
Show Gist options
  • Save jdolitsky/a0875b9a5e283d9c1b5552979c301905 to your computer and use it in GitHub Desktop.
Save jdolitsky/a0875b9a5e283d9c1b5552979c301905 to your computer and use it in GitHub Desktop.
Script to add everyone you follow on Twitter to a new private list
/*
new_private_list_with_all_followed_users.go
Script to add everyone you follow on Twitter to a new private list
Usage (requires Go 1.13+):
// Name of the Twitter list (should not exist yet, will be created)
export TW_LIST_NAME="<new_list_name>"
// Twitter auth vars, obtain these from https://developer.twitter.com/
export TW_CONSUMER_KEY="<twitter_api_consumer_key>"
export TW_CONSUMER_SECRET="<twitter_api_consumer_secret>"
export TW_ACCESS_TOKEN="<twitter_api_access_token>"
export TW_ACCESS_SECRET="<twitter_api_access_secret>"
// Download Go dependencies
go mod init new_private_list_with_all_followed_users
go mod vendor
// Run it
go run new_private_list_with_all_followed_users.go
*/
package main
import (
"log"
"math"
"net/http"
"os"
"sort"
"strings"
"time"
tw "github.com/dghubble/go-twitter/twitter"
"github.com/dghubble/oauth1"
)
var (
listName = os.Getenv("TW_LIST_NAME")
consumerKey = os.Getenv("TW_CONSUMER_KEY")
consumerSecret = os.Getenv("TW_CONSUMER_SECRET")
accessToken = os.Getenv("TW_ACCESS_TOKEN")
accessSecret = os.Getenv("TW_ACCESS_SECRET")
client *tw.Client
account *tw.User
followedUsersCache = map[string]int64{}
)
func main() {
checkEnv()
createClient()
loadAccount()
loadFollowedUsers()
createNewListWithAllFollowedUsers()
}
func checkEnv() {
if listName == "" || consumerKey == "" || consumerSecret == "" || accessToken == "" || accessSecret == "" {
log.Fatal("Missing one of the required env vars: " +
"TW_LIST_NAME TW_CONSUMER_KEY TW_CONSUMER_SECRET TW_ACCESS_TOKEN TW_ACCESS_SECRET")
}
}
func createClient() {
client = tw.NewClient(oauth1.NewConfig(consumerKey, consumerSecret).Client(oauth1.NoContext,
oauth1.NewToken(accessToken, accessSecret)))
}
func loadAccount() {
var err error
accountVerifyParams := tw.AccountVerifyParams{}
account, _, err = client.Accounts.VerifyCredentials(&accountVerifyParams)
if err != nil {
log.Fatal(err)
}
log.Println("Your username:", account.ScreenName)
}
func loadFollowedUsers() {
var nextCursor int64
for {
friendsListParams := tw.FriendListParams{
UserID: account.ID,
Cursor: nextCursor,
Count: 200, // Max
}
log.Printf("Listing users followed (cursor: %d)\n", nextCursor)
friends, resp, err := client.Friends.List(&friendsListParams)
if err != nil {
log.Fatal(err)
}
log.Printf("HTTP Status: %d\n", resp.StatusCode)
if resp.StatusCode != http.StatusOK {
log.Fatal("Unexpected HTTP status code")
}
log.Printf("Listed %d users\n", len(friends.Users))
s := "---\nid,name\n"
for _, u := range friends.Users {
followedUsersCache[strings.ToLower(u.ScreenName)] = u.ID
s += u.IDStr + "," + u.ScreenName + "\n"
}
log.Println(s + "---")
if nextCursor = friends.NextCursor; nextCursor == 0 {
break
}
log.Println("Sleeping 5 seconds to prevent rate-limiting...")
time.Sleep(5 * time.Second)
}
log.Printf("Found %d followed users total\n", len(followedUsersCache))
}
func createNewListWithAllFollowedUsers() {
listCreateParams := tw.ListsCreateParams{
Mode: "private",
}
log.Println("Creating new private list:", listName)
list, resp, err := client.Lists.Create(listName, &listCreateParams)
if err != nil {
log.Fatal(err)
}
log.Printf("Private list created: %d (%s)", list.ID, listName)
log.Printf("HTTP Status: %d\n", resp.StatusCode)
if resp.StatusCode != http.StatusOK {
log.Fatal("Unexpected HTTP status code")
}
log.Println("Private list created:", listName)
var keys []string
for k := range followedUsersCache {
keys = append(keys, k)
}
sort.Strings(keys)
numKeys := float64(len(keys))
currentPart := 1
numParts := int(math.Max(math.Ceil(numKeys/float64(100)), 1)) // 100 is max per page
for {
partIDs := keys[(currentPart-1)*100 : int(math.Min(float64(currentPart*100), numKeys))]
membersCreateAllParams := tw.ListsMembersCreateAllParams{
ListID: list.ID,
Slug: listName,
OwnerScreenName: account.ScreenName,
UserID: strings.Join(partIDs, ","),
}
log.Printf("Adding users to list (num users: %d)\n", len(partIDs))
resp, err := client.Lists.MembersCreateAll(&membersCreateAllParams)
if err != nil {
log.Fatal(err)
}
log.Printf("HTTP Status: %d\n", resp.StatusCode)
if resp.StatusCode != http.StatusOK {
log.Fatal("Unexpected HTTP status code")
}
if numParts <= currentPart {
break
}
currentPart++
log.Println("Sleeping 5 seconds to prevent rate-limiting...")
time.Sleep(5 * time.Second)
}
log.Printf("Added %d users to list %s\n", int(numKeys), listName)
}
@sweinberg
Copy link

Hey @jdolitsky, thanks for sharing this script! Been looking for a way to do this for awhile. I've never used Go before so pardon the noob question, but how do I modify the auth vars in the script to include my list name and auth keys?

Am I supposed to uncomment lines 12-15 (after replacing the <twitter_api_X_X> parts with my respective auth keys/tokens/secrets)?

@jdolitsky
Copy link
Author

@sweinberg - glad you found it useful. The comment section are the commands you need to run in the terminal (export environment variables, run the program, etc.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment