Created
February 12, 2020 20:56
-
-
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
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 - 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
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)?