An AppleScript to Update Twitter

These scripts allow you to compose Tweets for the popular microblogging service Twitter and will keeping trying to send your Tweet out (every 5 minutes for up to 2 hours) if the Twitter site is down.

What they do:

– prompt you to enter your Tweet
– store your username and password in a preference file
– use URL encoding to allow all standard characters to be used in your Tweets, including quotes and ampersands
– warn you if you exceed the 140 character Twitter limit
– store your Tweets as individual text files when Twitter goes down and then send them out in the order they were written as soon as Twitter is back online
– allow you to choose where you want to keep your Pending Tweets
– work unobstrusively in the background with negligible CPU usage
*** include Growl and Launchbar support, although neither are needed ***

(The Growl support is especially groovy – all notifications that do not require direct user intervention are processed through Growl and the Stay Open app is set up so you can tailor each of its notifcations. Want a special color or background for shell errors? Want Twitter errors to beep but successful updates to silently fade up and down? Or how about vice versa? All the notifications of this app [and many others!] can be easily customized with Growl. It’s totally cool – and it’s totally free. Read about it here:

http://growl.info/about.php

To use these scripts with LaunchBar, just call the Update Twitter script in LB, hit the space bar and type in your Tweet. Done. [Oh, and the LB support is the reason I used two scripts and not just one…])

This app has two parts – a kickoff script that gets the text of your Tweet (which I named “Update Twitter”) – and a Stay Open Script Application which sends your Tweets on to Twitter using cURL. The Stay Open Script Application should be called “Update Twitter Sending Application” and, of course, be saved as a Stay Open Application (without a startup screen.)

I’ve also created an icon for the Stay Open App. :slight_smile: You can find it here: http://tinyurl.com/6pj9he/Update_Twitter_Icon. (To install, save the Stay Open App as an Application Bundle [with Stay Open still checked!] and click the now active Bundle Contents button in the upper right toolbar of the Script Editor window. A drawer will open on the side of the window. Drag in the icon file you just downloaded, delete the applet.icns file that you find in the drawer and rename the the icon you dragged in with the name “applet.icns” [thus, replacing the old one.] If you use Growl, change icon of application “Script Editor” to icon of application “Update Twitter Sending Application” in the registering Growl section in the beginning of the Update Twitter Sending Application script.)

Huge thanks goes to StefanK, Martin Michel, Adam Bell, John M and Bruce Phillips for their help in creating these scripts. I’m sure there is still room for improvement, so if you see anything, please let me know. And if you like anything about them, please let me know that too – they were hard work and I learned a lot – encouragement is greatly appreciated. :slight_smile:

v 1.0 (7-27-08): Original posting of the scripts
v 1.1 (7-28-08): Adding error handling for mistyped username and password.
Full file paths for shell scripts.
URL encoding for the Tweets.
Ability to reset preferences on Quit.
Ability to input Tweet multiple times if the Tweet entered was
over the 140 character limit.
Various improvements in the language of the notifications.

Update Twitter (save as regular AppleScript)


property LastTweet : ""
property AlreadyCounted : false

global s
global OverTheLimit


-- I originally wrote this script to work with LaunchBar but it can also be run directly.  It is meant to work in conjunction with the Update Twitter Sending Application, a separate Stay Open Script Application that the user should have downloaded with this script.  When used together, this script and the Update Twitter Sending Application are coded to play well with LaunchBar and Growl, but neither are needed for them to work.  To use this with LaunchBar, use LB to call up the Update Twitter applescript, hit the space bar, type in your Tweet and hit enter.   No action is needed to get the Growl notifications -- if you have Growl it just works.  

-- This script stores its preferences in the user's ~/Library/Preferences folder under the filename com.UpdateTwitterAppleScript.myPrefs

-- This script was created by Alex Woolfson to teach himself a little AppleScript.  It borrowed from excellent scripts by George the Flea and Nik at the LaunchBar forums and was completed with tons of help from the bbs.macscripter.net community, with "above and beyond the call of duty" help from StefanK, Martin Michel and Adam Bell.  If you find this application useful, you should buy them all dinner.  Expensive dinner.

on run
	
	-- give the User 7 chances to enter the Tweet within the 140 character limit
	
	set RetryChances to 7
	
	--  I chose to include the default answer of the text of the last tweet in the dialog box here so the user wouldn't have to keep retyping in the text if they went over 140 characters when running this script directly.  LaunchBar does this automatically and I found it convenient.
	
	repeat
		set AlreadyCounted to false
		display dialog "Please enter your Tweet:" default answer LastTweet
		set s to text returned of result
		copy s to LastTweet
		CountNumberOfCharacters(s)
		if not OverTheLimit then exit repeat
		set RetryChances to RetryChances - 1
		if RetryChances = 0 then
			display dialog "You have tried to reenter this Tweet 7 times and now I suspect that we might be caught in some vicious circle, so I'm going to Quit."
			copy s to LastTweet
			exit repeat
		end if
	end repeat
	set AlreadyCounted to true
	handle_string(s)
end run

-- LaunchBar uses the handle_string() handler to send along text entered when you key in the script name and hit the space bar.  Thus if you use LaunchBar to enter your text, it will skip the above On Run section and just provide this script the text directly, no dialog box for you to fill in or click through.

on handle_string(s)
	
	-- The first step in handling the Tweet is to check the character limit.  If the tweet is over the limit, the script will give an error with number of characters in the attempted Tweet.
	
	if not AlreadyCounted then CountNumberOfCharacters(s)
	
	-- If the Tweet is within the character limit, then it will save the Tweet to a file so the Update Twitter Sending Application can use it    
	
	if not OverTheLimit then
		SaveTweet(s)
		
		--  And finally, it will launch the Update Twitter Sendng Application, if it's not already active.  If the application is active, then this script will let it know (with the SendNewTweet() handler) that there is a new Tweet for it to send.
		
		tell application "System Events"
			set processnames to name of every process
		end tell
		if "Update Twitter Sending Application.app" is in processnames then
			tell application "Update Twitter Sending Application" to SendNewTweet()
		else
			tell application "Update Twitter Sending Application"
				launch
				run
			end tell
		end if		
	end if
	set AlreadyCounted to false
end handle_string

on SaveTweet(s)
	
	--  This handler saves the Tweet to a file for later use by the Update Twitter Sending Application.  
	
	-- First, it checks for the preference file (and within it, the path of the folder the user chose for storing the pending Tweet text).  If that information doesn't exist, it prompts the user to choose such a folder and creates/updates the preference file with this information.  (And if the user ever wants to change this folder, they are given the option to delete this preference file which is located at ~/Library/Preferences under com.UpdateTwitterAppleScript.myPrefs when they quit the Update Twitter Sending Application.) 
	
	set myPrefDomain to "com.UpdateTwitterAppleScript.myPrefs"
	try
		set ChosenFolder to do shell script "/usr/bin/defaults read " & myPrefDomain & " Folder"
	on error E
		if E contains "does not exist" then
			set folderName to (choose folder with prompt "Please select a folder to hold the Pending Tweets:")
			tell application "Finder" to set ChosenFolder to folderName as Unicode text
			do shell script "/usr/bin/defaults write " & myPrefDomain & " Folder " & quoted form of ChosenFolder
		end if
	end try
	
	--  After it has determined where the user wants the pending Tweet text files stored, it creates a folder for those files (if it has not been created already).
	
	set TweetFolder to ChosenFolder & "Tweets Pending:"
	tell application "Finder"
		if not (exists folder TweetFolder) then make new folder at folder ChosenFolder with properties {name:"Tweets Pending"}
	end tell
	
	-- Next it sets up the name for the file that contains the Tweet
	
	set TimeStamp to do shell script "/bin/date -n +%m-%d-%Y' '\\(%H.%M.%S\\)"
	set TweetDoc to TweetFolder & "Tweet - " & TimeStamp
	try
		
		-- Then finally, it creates a file in Tweets Pending containing the Tweet text
		
		set ff to open for access file TweetDoc with write permission
		write s to ff
		close access ff
		return true
		
	on error
		try
			close access file TweetDoc
		end try
		return false
	end try
	
	-- That accomplished, the script is now ready to let the Update Twitter Sending Application know that it has a new Tweet to send out.
	
end SaveTweet

-- And lastly here is the handler for making sure the user doesn't exceed Twitter's 140 character limit with their Tweets

on CountNumberOfCharacters(s)
	set theCharacterCount to count (s)
	if theCharacterCount is greater than 140 then
		display dialog "I'm sorry, but Twitter only allows 140 characters total and that Tweet actually contained " & theCharacterCount & " characters.  Please try again."
		set OverTheLimit to true
	else
		set OverTheLimit to false
	end if
end CountNumberOfCharacters

Update Twitter Sending Application (be sure to use this name, save as Stay Open Application and make sure Startup Screen is NOT checked)


global TwitterUser
global TwitterPassword
global TweetFolder
global NumberOfFiles
global MinutesLeft
global processnames
global ChosenFolder
global TwitterResponse
global myPrefDomain

--  The Update Twitter Sending Application works in conjunction with the Update Twitter applescript (which is what gets the Tweet text from the user).  It's not meant to be run without it (and it should be named Update Twitter Sending Application so the other AppleScript can call it).  When used together, that script and this script application are coded to play well with LaunchBar and Growl, but neither are needed for them to work.  To use this with LaunchBar, use LB to call up the Update Twitter applescript, hit the space bar, type in your Tweet and hit enter.   No action is needed to get the Growl notifications -- if you have Growl it just works.

--  This script application was created by Alex Woolfson to teach himself about AppleScript with tons of help from the bbs.macscripter.net community, with "above and beyond the call of duty" help from StefanK, Martin Michel, Adam Bell, John M and Bruce Phillips.  If you find this application useful, you should buy them dinner.

on run
	
	--  Register this app with Growl, if Growl is installed.  If not, move on.  (But Growl is freeware and lovely, offering tons of control for how this application [and many others] give you notifications, so you should get it! Read all about it at http://growl.info/about.php)
	
	tell application "System Events"
		set processnames to name of every process
	end tell
	if "GrowlHelperApp" is in processnames then
		tell application "GrowlHelperApp" to register as application "Update Twitter Sending Application" all notifications {"Success", "Twitter Error", "Shell Error", "Time's Up"} default notifications {"Success", "Twitter Error", "Shell Error", "Time's Up"} icon of application "Script Editor"
	end if
	
	
	--  First, setup variable for countdown of remaining minutes before app gives up.
	
	set MinutesLeft to 120
	
	--  Then, check for the preference file and if it exists, get the user chosen folder for storing the pending Tweet text.  If it doesn't exist, alert the user and quit the app.	
	
	set myPrefDomain to "com.UpdateTwitterAppleScript.myPrefs"
	
	try
		set ChosenFolder to do shell script "/usr/bin/defaults read " & myPrefDomain & " Folder"
	on error E
		if E contains "does not exist" then
			display dialog "Huh.  I can't seem to find where you chose to store your Tweets.  The folder location *should* be in my preference file (which should be located in your home folder at ~/Library/Preferences under com.UpdateTwitterAppleScript.myPrefs.)" & return & "Now, I might not be able to get that information because that preference file somehow got deleted or corrupted, but, most likely, it means you didn't run my parent AppleScript 'Update Twitter' to enter your Tweet." & return & "Run that first, enter your Tweet and I'll be automatically commanded to take care of the rest!"
			quit
		end if
	end try
	
	set TweetFolder to ChosenFolder & "Tweets Pending:"
	
	-- check to see if the username and password have already been entered into the preferences (which, as was said in the dialog above, would be located at ~/Library/Preferences under the filename com.UpdateTwitterAppleScript.myPrefs.  If the user wants to reset their preferences, they have the option of doing so when they Quit or they can just delete this file at the Finder level.)
	
	
	
	try
		set TwitterUser to do shell script "/usr/bin/defaults read " & myPrefDomain & " User"
		set TwitterPassword to do shell script "/usr/bin/defaults read " & myPrefDomain & " Password"
		
		-- if the username and password can't be found, get them from the user now.  Insist on getting an answer before continuing.
		
	on error F
		if F contains "does not exist" then
			GetUsernameAndPassword()
		end if
	end try
	
	
end run


on idle
	
	-- This app is supposed to work in the background, so first, make sure it's hidden using a handler (which you can find at the end of the script).
	
	HideMe()
	
	--  Then, before continuing, make sure there are Tweets in the TweetFolder
	
	CountFiles()
	
	-- If there are no Tweets, then just idle and wait for a command from the Update Twitter script
	
	if NumberOfFiles = 0 then
		return 86400
	else
		
		-- But if there is a Tweet, get the Tweet text from the saved file in the TweetFolder, starting with the oldest.
		
		tell application "Finder"
			set everyfile to sort (get every file in folder TweetFolder) by creation date
			set DocName to last item of everyfile as text
		end tell
		
		set Tweet to read file DocName
		
		-- Then format the Tweet so we can send it out to Twitter using cURL.  This includes making the text of the Tweet "URL-encoded" to it can include things like double-quotes and ampersands.
		
		set TwitterUpdate to "/usr/bin/curl" & ¬
			" --user " & quoted form of (TwitterUser & ":" & TwitterPassword) & ¬
			" --data status=" & quoted form of encodeURL(Tweet) & ¬
			" [url=http://twitter.com/statuses/update.xml]http://twitter.com/statuses/update.xml"[/url]
		
		-- Now try sending the Tweet to Twitter
		
		try
			set TwitterResponse to do shell script TwitterUpdate
			
			-- If the shell spits out an error, such as might happen if the user's Internet connection were down, then let the user know what the error is and try again in 5 minutes.
			
		on error G
			set ShellError to "I received this error when I tried to send your Tweet to Twitter:" & return & " \"" & G & "\"" & return & "I will keep trying to send out your Tweet every 5 minutes for the next " & MinutesLeft & " minutes unless you Quit."
			if "GrowlHelperApp" is in processnames then
				tell application "GrowlHelperApp" to notify with name "Shell Error" title "Trouble!" description ShellError application name "Update Twitter Sending Application"
			end if
			set MinutesLeft to MinutesLeft - 5
			return 300
		end try
		
		
		
		-- This section checks to make sure the user's username and password was accepted, which cURL would tell us about with its own "Could not authenticate" error
		
		if TwitterResponse contains "Could not authenticate" then
			tell me to activate
			display dialog "I'm sorry, but Twitter is telling me that it couldn't authenticate your username and password.  Perhaps there was a typo.  Let's try reentering them."
			GetUsernameAndPassword()
			HideMe()
			idle
			
			-- And this one accounts for any errors from Twitter itself, which would usually be caused by their servers being down for maintenance.  It still allows for the word "error" in the Tweet itself, though.
			
		else if (offset of "error" in TwitterResponse) is equal to 0 or (offset of "error" in TwitterResponse) is greater than 100 then
			
			--if there's no error then let us know it worked and delete that Tweet file so the app can move onto the next one
			
			set HappyResult to "Your Tweet: \"" & Tweet & "\" has been sent!"
			tell application "System Events"
				set processnames to name of every process
			end tell
			if "GrowlHelperApp" is in processnames then
				tell application "GrowlHelperApp" to notify with name "Success" title "Success!" description HappyResult application name "Update Twitter Sending Application"
			end if
			
			-- I chose to use a shell script to delete because it's quieter than using the Finder
			
			do shell script "/bin/rm " & quoted form of the POSIX path of DocName
			
			
			-- after this success, check to see if there are any more Tweets to send and, if so, send them right out.  If not, then restart the idle.
			
			CountFiles()
			if NumberOfFiles = 0 then
				run
			else
				idle
			end if
			
			--  If Twitter *did* return an error, check to see if time is up.  If so, notify the user that there will be no further attempts that day without their direct involvement.
			
		else if MinutesLeft is less than or equal to 0 then
			set TimesUp to "Sorry, I've tried for 2 hours but I haven't been able to get your Tweet(s) through.  It's time to stop for the day." & return & return & "Unless you Quit, I'll try again tomorrow -- and you can always try sending out another Tweet to reset the clock!"
			tell application "System Events"
				set processnames to name of every process
			end tell
			if "GrowlHelperApp" is in processnames then
				tell application "GrowlHelperApp" to notify with name "Time's Up" title "It didn't work out..." description TimesUp application name "Update Twitter Sending Application"
			else
				display dialog TimesUp
			end if
			set MinutesLeft to 120
			return 86400
			
			
			-- If Twitter returned an error but time *is* still on the clock then let the user know what the error text is and try again in 5 minutes
			
		else
			set NoJoy to "Twitter sent me this error: " & "
			" & TwitterResponse & "
			" & "I will keep trying to send out your Tweet:" & return & Tweet & return & "every 5 minutes for the next " & MinutesLeft & " minutes unless you Quit."
			tell application "System Events"
				set processnames to name of every process
			end tell
			if "GrowlHelperApp" is in processnames then
				tell application "GrowlHelperApp" to notify with name "Twitter Error" title "Trouble!" description NoJoy application name "Update Twitter Sending Application"
			end if
		end if
	end if
	
	set MinutesLeft to MinutesLeft - 5
	return 300
end idle

on quit --   let the user know how many Tweets remain and give them the option to keep working or to reset their preferences
	
	CountFiles()
	
	set B to button returned of (display dialog "Are you sure you want to quit?" & return & return & "You have " & NumberOfFiles & " Tweets currently waiting to be sent." buttons {"Oops, NO!", "Yes, Please", "Yes AND RESET my preferences!"} default button 2)
	if B contains "Yes AND RESET my preferences!" then
		set C to button returned of (display dialog "Are you sure you want to reset the preferences?" & return & return & "This will DELETE my preference file." & return & return & "(Of course, you will just be prompted to reenter the information the next time you run the Update Twitter AppleScript...)" buttons {"Oops, NO! Just Quit.", "Yes, Please"} default button 1)
		if C contains "Yes" then
			
			set PreferencePath to (path to preferences folder from user domain as Unicode text) & "com.UpdateTwitterAppleScript.myPrefs.plist"
			do shell script "/bin/rm " & quoted form of the POSIX path of PreferencePath
			display dialog "My preferences have been reset." & return & return & "Just run the Update Twitter AppleScript when you're ready to reenter the location for the" & return & "Tweets Pending folder as well as your Twitter username and password."
			continue quit
		else
			continue quit
		end if
		
	else if B contains "Yes, Please" then
		continue quit
	else
		idle
	end if
end quit

-- This handler counts the number of Tweets remaining in the TweetFolder

on CountFiles()
	tell application "Finder"
		set RemainingFiles to every file in folder TweetFolder
		set NumberOfFiles to count of items in RemainingFiles
	end tell
end CountFiles

-- This handler is called from the Update Twitter script if this application is already launched and idling.

on SendNewTweet()
	set MinutesLeft to 120
	idle
end SendNewTweet

-- This handler prompts the user for their Twitter username and password -- it insists upon an answer

on GetUsernameAndPassword()
	repeat
		display dialog "Please enter your Twitter username." & return & return & "(Your username and password are stored in a file named com.UpdateTwitterAppleScript.myPrefs in your Library/Preferences folder, so you should only have to do this once.)" default answer ""
		if (text returned of result) is not "" then
			set TwitterUser to text returned of result
			exit repeat
		else
			display dialog "You didn't enter a username."
		end if
	end repeat
	
	repeat
		display dialog "Please enter your Twitter password" default answer ""
		if (text returned of result) is not "" then
			set TwitterPassword to text returned of result
			exit repeat
		else
			display dialog "You didn't enter a password."
		end if
	end repeat
	
	-- Once the user has inputted the information, write the username and password to the Prefs file
	
	do shell script "/usr/bin/defaults write " & myPrefDomain & " User " & quoted form of TwitterUser
	do shell script "/usr/bin/defaults write " & myPrefDomain & " Password " & quoted form of TwitterPassword
end GetUsernameAndPassword

-- This application is supposed to work in the background.  This handler hides it.

on HideMe()
	tell application "System Events"
		set frontmostapps to every application process whose frontmost is true
		set frontmostappname to name of item 1 of frontmostapps
	end tell
	if frontmostappname = "Update Twitter Sending Application.app" then
		tell application "System Events" to keystroke "h" using command down
	end if
end HideMe

-- And finally, this handler makes text "URL-encoded" so that any text can be used with cURL

on encodeURL(someURL)
	return do shell script "/usr/bin/python -c '" & ¬
		"from sys import argv; " & ¬
		"from urllib import quote; " & ¬
		"print quote(unicode(argv[1], \"utf8\"))' " & quoted form of someURL
end encodeURL