Twitter is a so-called microblogging service, which provides a nice RESTful API for programmers. Twitter has a new "lists" feature, and the Twitter Web-interface has a new a GUI element and pages for managing one's lists. Personally, I have found the GUI-based list management feature to be slow to respond and sometimes a bit frustrating to use. As an old "command-line" lover, I have begun writing some simple shell scripts to manage my lists at Twitter while avoiding the GUI approach used in the Twitter Web interface. These scripts are very simple (some might say overly simplified), and they are very much "quick hacks", but they work.
All of these scripts are "early" code, meaning I just hacked them together quickly and they just do what they are supposed to do with no "frills". I have added simple error-handling and made their outputs better-formatted for humans, in most cases. A few of the API calls return false error messages or happily do silly things, so I have added some front-end error checking which has the unfortunate effect of causing some of them to make 2 API calls to complete 1 function, but "so it goes".
"Let the reader (user) beware."
All these scripts are written to load your Twitter username and password from a text file called "userprefs.txt" which sits on your machine and just contains one simple line: the two variables (username and password) separated by a colon. That file should probably be in the same directory in which all the scripts live, and the first (and only) line in that plain-text file should look like this:
yourUserNameGoesHere:yourPasswordGoesHere
Obviously, you should edit it to replace "yourUserNameGoesHere" with your real Twitter username, and to replace "yourPasswordGoesHere" with your real Twitter password (do not forget the colon between them ). I would also suggest making the file readable and writeable only by the owner.
Feel free to use these scripts, to modify them, and to pass them along to others. They are released into the wild under a Creative Commons License (see the bottom of this page). I will be going back and making them better, as I get all the API calls covered. However, for now: YOYO.
Download all the scripts and a sample "userprefs.txt" file in a tar-gzip archive.
modified this script 20 Nov 2009 to deal with description field
#!/bin/sh
# create new list
USERINFOFILE="userprefs.txt"
exec 3<$USERINFOFILE
read -r line1 <&3
USERNAME=`echo $line1 | cut -f 1 -d :`
PASSWORD=`echo $line1 | cut -f 2 -d :`
DATASTRING=" "
if [ $# -lt 2 ]; then
echo "create a new list"
echo "usage: mklist listname description [public|private]"
else
# API will happily ignore that list already exists
# and will just make a new list with the same name plus some appended chars
# so test first to see if it already exists, save ourselves from such silliness
RESULTS=`curl -s -u $USERNAME:$PASSWORD http://twitter.com/$USERNAME/lists.xml | grep "<full_name>" | colrm 1 2 | cut -f 2 -d \@ | cut -f 1 -d \<`
if [[ $RESULTS == *$1* ]]; then
echo "list already exists"
else
if [ $# -eq 2 ]; then
DATASTRING="name=$1&description=$2"
elif [ $# -eq 3 ]; then
DATASTRING="name=$1&description=$2&mode=$3"
else
echo "too many arguments"
echo "create a new list"
echo "usage: mklist listname description [public|private]"
exit 1
fi
NEWLIST=`curl -s -u $USERNAME:$PASSWORD -d "$DATASTRING" http://twitter.com/$USERNAME/lists.xml | grep "<full_name>" | colrm 1 2 | cut -f 2 -d \@ | cut -f 1 -d \<`
echo "created $NEWLIST"
fi
fi
#!/bin/sh
# remove list
USERINFOFILE="userprefs.txt"
exec 3<$USERINFOFILE
read -r line1 <&3
USERNAME=`echo $line1 | cut -f 1 -d :`
PASSWORD=`echo $line1 | cut -f 2 -d :`
if [ $# -ne 1 ]; then
echo "remove an existing list"
echo "usage: rmlist listName"
else
RESULTS=`curl -s -u $USERNAME:$PASSWORD -X DELETE http://twitter.com/$USERNAME/lists/$1.xml | grep "<error>Not found</error>"`
if [[ $RESULTS == *Not* ]]; then
echo "cannot delete List that does not exist"
else
echo "$1 deleted"
fi
fi
#!/bin/sh
# change list visibility
USERINFOFILE="userprefs.txt"
exec 3<$USERINFOFILE
read -r line1 <&3
USERNAME=`echo $line1 | cut -f 1 -d :`
PASSWORD=`echo $line1 | cut -f 2 -d :`
if [ $# -ne 2 ]; then
echo "change list visibility"
echo "usage: chviz listName [public|private]"
else
# for some reason this API call always returns an error page, even when it is successful
# so we need to test for list-exists first
RESULTS=`curl -s -u $USERNAME:$PASSWORD http://twitter.com/$USERNAME/lists.xml | grep "<full_name>" | colrm 1 2 | cut -f 2 -d \@ | cut -f 1 -d \<`
if [[ $RESULTS != *\/$1* ]]; then
echo "cannot change list that does not exist"
else
NEWRESULTS=`curl -s -u $USERNAME:$PASSWORD -d "name=$1&mode=$2" http://twitter.com/$USERNAME/lists/$1.xml`
echo "$1 should now have visibility $2"
fi
fi
#!/bin/sh
# change list name
USERINFOFILE="userprefs.txt"
exec 3<$USERINFOFILE
read -r line1 <&3
USERNAME=`echo $line1 | cut -f 1 -d :`
PASSWORD=`echo $line1 | cut -f 2 -d :`
if [ $# -ne 3 ]; then
echo "change list name"
echo "usage: chname oldName newName [public|private]"
else
# for some reason this API call always returns an error page, even when it is successful
# so we need to test for list-exists first
RESULTS=`curl -s -u $USERNAME:$PASSWORD http://twitter.com/$USERNAME/lists.xml | grep "<full_name>" | colrm 1 2 | cut -f 2 -d \@ | cut -f 1 -d \<`
if [[ $RESULTS != *\/$1* ]]; then
echo "cannot change list that does not exist"
else
NEWRESULTS=`curl -s -u $USERNAME:$PASSWORD -d "name=$2&mode=$3" http://twitter.com/$USERNAME/lists/$1.xml`
echo "$1 renamed $2 with visibility $3"
fi
fi
#!/bin/sh
# get list membership from Twitter in xml form
USERINFOFILE="userprefs.txt"
exec 3<$USERINFOFILE
read -r line1 <&3
USERNAME=`echo $line1 | cut -f 1 -d :`
PASSWORD=`echo $line1 | cut -f 2 -d :`
if [ $# -ne 1 ]; then
echo "get list membership"
echo "usage: getlist listName"
else
NEXTCURSOR="-1"
TESTCURSOR="0"
while [[ $NEXTCURSOR -ne $TESTCURSOR ]]; do
curl -s -u $USERNAME:$PASSWORD http://twitter.com/$USERNAME/$1/members.xml?cursor=$NEXTCURSOR > tmpgetlist.xml
NEXTCURSOR=`cat tmpgetlist.xml | sed -e s/://g | sed -e s/\<next_cursor\>/:/g | cut -f 2 -d : | sed -e s/\</:/g | grep "/next_cursor>" | cut -f 1 -d :`
RESULTS=`cat tmpgetlist.xml | grep "<screen_name>" | colrm 1 2 | cut -f 2 -d \< | cut -f 2 -d \>`
if [[ $RESULTS == "" ]]; then
echo "list is empty or does not exist"
else
for (( i = 1; i <= `echo $RESULTS | wc -w`; i++ ))
do
echo `echo $RESULTS | cut -f $i -d " "`
done
fi
done
rm tmpgetlist.xml
fi
#!/bin/sh
# follow someone's list
USERINFOFILE="userprefs.txt"
exec 3<$USERINFOFILE
read -r line1 <&3
USERNAME=`echo $line1 | cut -f 1 -d :`
PASSWORD=`echo $line1 | cut -f 2 -d :`
if [ $# -ne 2 ]; then
echo "follow a list"
echo "usage: follow curatorName listName"
else
RESULTS=`curl -s -u $USERNAME:$PASSWORD -X POST http://twitter.com/$1/$2/subscribers.xml | grep "<error>" | colrm 1 2 | cut -f 2 -d \> | cut -f 1 -d \<`
if [[ $RESULTS != "" ]]; then
echo "curatorName $1 or listName $2 does not exist"
else
echo "you are now following list $2 curated by $1"
fi
fi
#!/bin/sh
# see if username is following a list of yours
USERINFOFILE="userprefs.txt"
exec 3<$USERINFOFILE
read -r line1 <&3
USERNAME=`echo $line1 | cut -f 1 -d :`
PASSWORD=`echo $line1 | cut -f 2 -d :`
if [ $# -ne 2 ]; then
echo "see if someone is following a list of yours"
echo "usage: issubscriber username listname"
else
USERID=`curl -s -u $USERNAME:$PASSWORD http://twitter.com/users/show/$1.xml | grep \<id\> | sed '2d' | sed -e s/\<id\>//g | cut -d \< -f 1 | colrm 1 2`
if [[ $USERID == "" ]]; then
echo "user $1 does not exist"
else
LISTID=`curl -s -u $USERNAME:$PASSWORD http://twitter.com/$USERNAME/lists/$2.xml | grep '<id>' | colrm 1 2 | cut -f 2 -d \< | cut -f 2 -d \> | head -1`
if [[ $LISTID == "" ]]; then
echo "list $2 does not exist"
else
RESULTS=`curl -s -u $USERNAME:$PASSWORD http://twitter.com/$USERNAME/$LISTID/subscribers/$USERID.xml | grep '<id>' | colrm 1 2 | cut -f 2 -d \< | cut -f 2 -d \> | head -1`
if [[ $USERID == $RESULTS ]]; then
echo "user $1 subscribes to your list $2"
else
echo "user $1 does not subscribe to your list $2"
fi
fi
fi
fi
#!/bin/sh
# stop following someone's list
USERINFOFILE="userprefs.txt"
exec 3<$USERINFOFILE
read -r line1 <&3
USERNAME=`echo $line1 | cut -f 1 -d :`
PASSWORD=`echo $line1 | cut -f 2 -d :`
if [ $# -ne 2 ]; then
echo "unfollow a list"
echo "usage: unfollow curatorName listName"
else
RESULTS=`curl -s -u $USERNAME:$PASSWORD -X DELETE http://twitter.com/$1/$2/subscribers.xml | grep "<error>" | colrm 1 2 | cut -f 2 -d \> | cut -f 1 -d \<`
if [[ $RESULTS != "" ]]; then
echo "curatorName $1 or listName $2 does not exist, or you were not following that list"
else
echo "you are no longer following list $2 curated by $1"
fi
fi
#!/bin/sh
# get someone else's list membership from Twitter in xml form
USERINFOFILE="userprefs.txt"
exec 3<$USERINFOFILE
read -r line1 <&3
USERNAME=`echo $line1 | cut -f 1 -d :`
PASSWORD=`echo $line1 | cut -f 2 -d :`
if [ $# -ne 2 ]; then
echo "get some other user's list membership"
echo "usage: getlistof username listName"
else
NEXTCURSOR="-1"
TESTCURSOR="0"
while [[ $NEXTCURSOR -ne $TESTCURSOR ]]; do
curl -s -u $USERNAME:$PASSWORD http://twitter.com/$1/$2/members.xml?cursor=$NEXTCURSOR > tmpgetlistof.xml
NEXTCURSOR=`cat tmpgetlistof.xml | sed -e s/://g | sed -e s/\<next_cursor\>/:/g | cut -f 2 -d : | sed -e s/\</:/g | grep "/next_cursor>" | cut -f 1 -d :`
RESULTS=`cat tmpgetlistof.xml | grep "<screen_name>" | colrm 1 2 | cut -f 2 -d \< | cut -f 2 -d \>`
if [[ $RESULTS == "" ]]; then
echo "list is empty or does not exist"
else
for (( i = 1; i <= `echo $RESULTS | wc -w`; i++ ))
do
echo `echo $RESULTS | cut -f $i -d " "`
done
fi
done
rm tmpgetlistof.xml
fi
#!/bin/sh
# show who follows a list of yours
USERINFOFILE="userprefs.txt"
exec 3<$USERINFOFILE
read -r line1 <&3
USERNAME=`echo $line1 | cut -f 1 -d :`
PASSWORD=`echo $line1 | cut -f 2 -d :`
if [ $# -ne 1 ]; then
echo "list the subscribers who follow your list"
echo "usage: subscribers listname"
else
NEXTCURSOR="-1"
TESTCURSOR="0"
while [[ $NEXTCURSOR -ne $TESTCURSOR ]]; do
curl -s -u $USERNAME:$PASSWORD http://twitter.com/$USERNAME/$1/subscribers.xml?cursor=$NEXTCURSOR > tmpsubscribers.xml
NEXTCURSOR=`cat tmpsubscribers.xml | sed -e s/://g | sed -e s/\<next_cursor\>/:/g | cut -f 2 -d : | sed -e s/\</:/g | grep "/next_cursor>" | cut -f 1 -d :`
cat tmpsubscribers.xml | grep "<screen_name>" | colrm 1 2 | cut -f 2 -d \> | cut -f 1 -d \<
done
rm tmpsubscribers.xml
fi
#!/bin/sh
# show who follows a list of someone else
USERINFOFILE="userprefs.txt"
exec 3<$USERINFOFILE
read -r line1 <&3
USERNAME=`echo $line1 | cut -f 1 -d :`
PASSWORD=`echo $line1 | cut -f 2 -d :`
if [ $# -ne 2 ]; then
echo "list the subscribers who follow someone else's list"
echo "usage: subscribersof username listname"
else
NEXTCURSOR="-1"
TESTCURSOR="0"
while [[ $NEXTCURSOR -ne $TESTCURSOR ]]; do
curl -s -u $USERNAME:$PASSWORD http://twitter.com/$1/$2/subscribers.xml?cursor=$NEXTCURSOR > tmpsubscribersof.xml
NEXTCURSOR=`cat tmpsubscribersof.xml | sed -e s/://g | sed -e s/\<next_cursor\>/:/g | cut -f 2 -d : | sed -e s/\</:/g | grep "/next_cursor>" | cut -f 1 -d :`
cat tmpsubscribersof.xml | grep "<screen_name>" | colrm 1 2 | cut -f 2 -d \> | cut -f 1 -d \<
done
rm tmpsubscribersof.xml
fi
modified this script 20 Nov 2009 to deal with description field
#!/bin/sh
# show list details
USERINFOFILE="userprefs.txt"
exec 3<$USERINFOFILE
read -r line1 <&3
USERNAME=`echo $line1 | cut -f 1 -d :`
PASSWORD=`echo $line1 | cut -f 2 -d :`
if [ $# -ne 1 ]; then
echo "show list details"
echo "usage: listdetails listName"
else
echo "[Information on your $1 list]"
curl -s -u $USERNAME:$PASSWORD http://twitter.com/$USERNAME/lists/$1.xml > tmplistdetails.xml
echo " ID: `cat tmplistdetails.xml | grep '<id>' | colrm 1 2 | cut -f 2 -d \< | cut -f 2 -d \> | head -1`"
echo " full name: `cat tmplistdetails.xml | grep '<full_name>' | colrm 1 2 | cut -f 2 -d \< | cut -f 2 -d \>`"
echo " slug: `cat tmplistdetails.xml | grep '<slug>' | colrm 1 2 | cut -f 2 -d \< | cut -f 2 -d \>`"
echo " subscriber count: `cat tmplistdetails.xml | grep '<subscriber_count>' | colrm 1 2 | cut -f 2 -d \< | cut -f 2 -d \>`"
echo " member count: `cat tmplistdetails.xml | grep '<member_count>' | colrm 1 2 | cut -f 2 -d \< | cut -f 2 -d \>`"
echo " visibility: `cat tmplistdetails.xml | grep '<mode>' | colrm 1 2 | cut -f 2 -d \< | cut -f 2 -d \>`"
echo " description: `cat tmplistdetails.xml | grep '<description>' | colrm 1 2 | cut -f 2 -d \< | cut -f 2 -d \>`"
rm tmplistdetails.xml
fi
#!/bin/sh
# list your lists
USERINFOFILE="userprefs.txt"
exec 3<$USERINFOFILE
read -r line1 <&3
USERNAME=`echo $line1 | cut -f 1 -d :`
PASSWORD=`echo $line1 | cut -f 2 -d :`
if [ $# -ne 0 ]; then
echo "list your lists"
echo "usage: lists"
else
NEXTCURSOR="-1"
TESTCURSOR="0"
while [[ $NEXTCURSOR -ne $TESTCURSOR ]]; do
curl -s -u $USERNAME:$PASSWORD http://twitter.com/$USERNAME/lists.xml?cursor=$NEXTCURSOR > tmplists.xml
NEXTCURSOR=`cat tmplists.xml | sed -e s/://g | sed -e s/\<next_cursor\>/:/g | cut -f 2 -d : | sed -e s/\</:/g | grep "/next_cursor>" | cut -f 1 -d :`
cat tmplists.xml | grep "<full_name>" | colrm 1 2 | cut -f 2 -d \@ | cut -f 1 -d \<
done
rm tmplists.xml
fi
#!/bin/sh
# list some user's lists
USERINFOFILE="userprefs.txt"
exec 3<$USERINFOFILE
read -r line1 <&3
USERNAME=`echo $line1 | cut -f 1 -d :`
PASSWORD=`echo $line1 | cut -f 2 -d :`
if [ $# -ne 1 ]; then
echo "list some other user's lists"
echo "usage: listsof username"
else
NEXTCURSOR="-1"
TESTCURSOR="0"
while [[ $NEXTCURSOR -ne $TESTCURSOR ]]; do
curl -s -u $USERNAME:$PASSWORD http://twitter.com/$1/lists.xml?cursor=$NEXTCURSOR > tmplistsof.xml
NEXTCURSOR=`cat tmplistsof.xml | sed -e s/://g | sed -e s/\<next_cursor\>/:/g | cut -f 2 -d : | sed -e s/\</:/g | grep "/next_cursor>" | cut -f 1 -d :`
cat tmplistsof.xml | grep "<full_name>" | colrm 1 2 | cut -f 2 -d \@ | cut -f 1 -d \<
done
rm tmplistsof.xml
fi
#!/bin/sh
# list your list memberships
USERINFOFILE="userprefs.txt"
exec 3<$USERINFOFILE
read -r line1 <&3
USERNAME=`echo $line1 | cut -f 1 -d :`
PASSWORD=`echo $line1 | cut -f 2 -d :`
if [ $# -ne 0 ]; then
echo "list your list memberships"
echo "usage: memberof"
else
NEXTCURSOR="-1"
TESTCURSOR="0"
while [[ $NEXTCURSOR -ne $TESTCURSOR ]]; do
curl -s -u $USERNAME:$PASSWORD http://twitter.com/$USERNAME/lists/memberships.xml?cursor=$NEXTCURSOR > tmpmemberof.xml
NEXTCURSOR=`cat tmpmemberof.xml | sed -e s/://g | sed -e s/\<next_cursor\>/:/g | cut -f 2 -d : | sed -e s/\</:/g | grep "/next_cursor>" | cut -f 1 -d :`
cat tmpmemberof.xml | grep "<full_name>" | colrm 1 2 | cut -f 2 -d \@ | cut -f 1 -d \<
done
rm tmpmemberof.xml
fi
#!/bin/sh
# show which lists you follow
USERINFOFILE="userprefs.txt"
exec 3<$USERINFOFILE
read -r line1 <&3
USERNAME=`echo $line1 | cut -f 1 -d :`
PASSWORD=`echo $line1 | cut -f 2 -d :`
if [ $# -ne 0 ]; then
echo "list the lists you follow"
echo "usage: subscriptions"
else
NEXTCURSOR="-1"
TESTCURSOR="0"
while [[ $NEXTCURSOR -ne $TESTCURSOR ]]; do
curl -s -u $USERNAME:$PASSWORD http://twitter.com/$USERNAME/lists/subscriptions.xml?cursor=$NEXTCURSOR > tmpsubscriptions.xml
NEXTCURSOR=`cat tmpsubscriptions.xml | sed -e s/://g | sed -e s/\<next_cursor\>/:/g | cut -f 2 -d : | sed -e s/\</:/g | grep "/next_cursor>" | cut -f 1 -d :`
cat tmpsubscriptions.xml | grep "<full_name>" | colrm 1 2 | cut -f 2 -d \@ | cut -f 1 -d \<
done
rm tmpsubscriptions.xml
fi
#!/bin/sh
# show list timeline
USERINFOFILE="userprefs.txt"
exec 3<$USERINFOFILE
read -r line1 <&3
USERNAME=`echo $line1 | cut -f 1 -d :`
PASSWORD=`echo $line1 | cut -f 2 -d :`
PASSWORD="Cyclop"
if [ $# -ne 1 ]; then
echo "show list timeline (last 200 tweets)"
echo "usage: timeline listName"
else
TEMP00="tmptimeline.xml"
TEMP01="tmp01.txt"
TEMP02="tmp02.txt"
curl -s -u $USERNAME:$PASSWORD http://twitter.com/$USERNAME/lists/$1/statuses.xml?per_page=200 > $TEMP00
cat $TEMP00 | grep "<screen_name>" | sed -e s/\<screen_name\>/:/g | sed -e s/\</:/g | cut -f 2 -d : > $TEMP01
cat $TEMP00 | grep "<text>" | sed -e s/\<text\>//g | cut -d \< -f 1 > $TEMP02
exec 3<$TEMP01
exec 4<$TEMP02
while IFS= read -r line1 <&3
IFS= read -r line2 <&4
do
echo "@$line1: $line2"
done
rm $TEMP00
rm $TEMP01
rm $TEMP02
fi
#!/bin/sh
# add a member to a list
USERINFOFILE="userprefs.txt"
exec 3<$USERINFOFILE
read -r line1 <&3
USERNAME=`echo $line1 | cut -f 1 -d :`
PASSWORD=`echo $line1 | cut -f 2 -d :`
if [ $# -ne 2 ]; then
echo "add a member to a list"
echo "usage: addmember membername listname"
else
USERID=`curl -s -u $USERNAME:$PASSWORD http://twitter.com/users/show/$1.xml | grep \<id\> | sed '2d' | sed -e s/\<id\>//g | cut -d \< -f 1 | colrm 1 2`
RESULTS=`echo $USERID | grep "<error>"`
if [[ $RESULTS != "" ]]; then
echo "no such user or other error"
else
NEWRESULTS=`curl -s -u $USERNAME:$PASSWORD -d "id=$USERID" http://twitter.com/$USERNAME/$2/members.xml | grep "Error"`
if [[ $NEWRESULTS != "" ]]; then
echo "error adding user $1"
else
echo "user $1 now member of list $2"
fi
fi
fi
#!/bin/sh
# remove a member from a list
USERINFOFILE="userprefs.txt"
exec 3<$USERINFOFILE
read -r line1 <&3
USERNAME=`echo $line1 | cut -f 1 -d :`
PASSWORD=`echo $line1 | cut -f 2 -d :`
if [ $# -ne 2 ]; then
echo "remove a member from a list"
echo "usage: rmmember membername listname"
else
USERID=`curl -s -u $USERNAME:$PASSWORD http://twitter.com/users/show/$1.xml | grep \<id\> | sed '2d' | sed -e s/\<id\>//g | cut -d \< -f 1 | colrm 1 2`
curl -s -u $USERNAME:$PASSWORD -X DELETE -d "id=$USERID" http://twitter.com/$USERNAME/$2/members.xml > /dev/null
NEWRESULTS=`curl -s -u $USERNAME:$PASSWORD http://twitter.com/$USERNAME/$2/members.xml | grep "<screen_name>" | colrm 1 2 | cut -f 2 -d \< | cut -f 2 -d \>`
for (( i = 1; i <= `echo $NEWRESULTS | wc -w`; i++ ))
do
echo `echo $NEWRESULTS | cut -f $i -d " "`
done
fi
It must be time for a break. If you find a bug or want to suggest an improvement, feel free to drop me a tweet. Have fun! @jeffsonstein
Content on this site is provided by Prof. Jeffrey Sonstein under a Creative Commons License. Please contact me, if you wish more information about my work.