Weather Script — UK

Hi.

Any curl gurus out there? After fooling around with Dylan’s weather script the other day, I decided to do a version for my own use to work with AccuWeather.com’s UK/Ireland pages. These don’t change as radically throughout the day as the US pages and in fact it’s possible to get all the relevant information from the 5-day forecasts alone.

I’ve been trying to get the script to accept either a postcode or a place name as an input parameter. AccuWeather’s UK/Ireland URLs are based on postcodes. If you enter a place name, say “London”, into the site’s search field in a browser, a query is fired off in the form:

The site then returns a relative URL containing a postcode near the centre of the location and the browser redirects to that.

Using curl in the script, I’ve been able to isolate the returned postcode and redirect to the relevant 5-day forecast. But there’s a problem with non-unique place names. For instance, there’s a York in Yorkshire, which everyone knows, and a York in Lancashire, which is unbelievable, given the history of the two counties. When the query URL is entered into a browser .

. the browser redirects to a page asking you to choose between the two Yorks. The curl command, however, given the same URL, simply hangs and I have to force-quit AppleScript Editor to release it. Can anyone guide me to a solution or to the correct curl syntax? The problem area’s marked near the top of the displayWeather() handler.

property returnTab : return & tab
property twoReturns : return & return

on findNreplace(txt, find, replace)
	set astid to AppleScript's text item delimiters
	
	set AppleScript's text item delimiters to find
	set txt to txt's text items
	set AppleScript's text item delimiters to replace
	set txt to txt as text
	set AppleScript's text item delimiters to astid
	
	return txt
end findNreplace

on displayWeather(locationInput) -- locationInput is a record: either {postcode:"xxx xxx"} or {location:"Place Name"}
	set {postcode:postcode, location:location} to locationInput & {postcode:"", location:""}
	
	set astid to AppleScript's text item delimiters
	
	if ((count postcode) > 0) then -- Postcode supplied.
		set postcode to findNreplace(postcode, space, "%20")
	else if ((count location) > 0) then -- Place name supplied.
		
		(* Problem here with non-unique place names. 'curl' never returns. *)
		
		set redirectData to (do shell script ("curl " & quoted form of "http://www.accuweather.com/ukie/uk-city-list.asp?partner=accuweather&postalcode=" & findNreplace(location, space, "%20") & "&submit=GO&u=1&partner=accuweather"))
		if (redirectData begins with "<head><title>Object moved</title></head>") then
			set AppleScript's text item delimiters to quote
			set postcode to text item 2 of redirectData
			set AppleScript's text item delimiters to "="
			set postcode to text item -1 of postcode
		else
			set AppleScript's text item delimiters to astid
			-- The server's returned something else.
		end if
	else
		-- Input parameter problem.
	end if
	
	display dialog "Loading Weather Forecast; may be a few seconds!" buttons {"OK"} default button 1 giving up after 2
	
	set fiveDayData to do shell script "curl " & space & quoted form of ("http://www.accuweather.com/ukie/forecast.asp?partner=accuweather&traveler=0&postalcode=" & postcode & "&metric=1")
	
	-- Location.
	set AppleScript's text item delimiters to "<div id=\"fcst_title\">"
	set fiveDayData to text from text item 2 to -1 of fiveDayData
	set AppleScript's text item delimiters to "</div>"
	set city to text item 1 of fiveDayData
	
	-- Local date and time.
	set AppleScript's text item delimiters to "<span class=\"textsmallbold\">"
	set fiveDayData to text from text item 2 to -1 of fiveDayData
	set {m, d, y} to words 2 thru 4 of fiveDayData
	set today to (date "Monday 1 January 1000 00:00:00")
	set today's year to y
	set today's month to (offset of (text 1 thru 3 of m) in "JanFebMarAprMayJunJulAugSepOctNovDec") div 3 + 1
	set today's day to d
	set AppleScript's text item delimiters to ">Currently</a> at "
	set fiveDayData to text from text item 2 to -1 of fiveDayData
	set AppleScript's text item delimiters to "</div>"
	set t to text item 1 of fiveDayData
	tell t to set today's time to ((word 1) mod 12 + ((word 3 is "PM") as integer) * 12) * hours + (word 2) * minutes
	
	-- Data for now, today, and tomorrow.
	set dayAfterTomorrow to today + 2 * days
	set AppleScript's text item delimiters to "class=\"textsmall\">" & (dayAfterTomorrow's weekday) & "</a>, " & text 1 thru 3 of (dayAfterTomorrow's month as text) & " " & (dayAfterTomorrow's day) & "</div>"
	set twoDayData to text item 1 of fiveDayData
	
	-- Current, high, low, and feel temperatures. Only the first "feel" temperature is currently used.
	set AppleScript's text item delimiters to {" °C", " °C", "°C"}
	set {feelNow, temperatureNow, todayHighFeel, todayHigh, todayLowFeel, todayLow, tomorrowHighFeel, tomorrowHigh, tomorrowLowFeel, tomorrowLow} to text items 1 thru 10 of twoDayData
	
	set AppleScript's text item delimiters to {"Temp: ", "Temp: "}
	set temperatureNow to text item 2 of temperatureNow & "ºC"
	
	set AppleScript's text item delimiters to {">Realfeel®:"}
	set feelNow to text item 2 of feelNow & "ºC"
	
	set AppleScript's text item delimiters to {"High: ", "High: "}
	set todayHigh to text item 2 of todayHigh & "ºC"
	set tomorrowHigh to text item 2 of tomorrowHigh & "ºC"
	
	set AppleScript's text item delimiters to {"Low: ", "Low: "}
	set todayLow to text item 2 of todayLow & "ºC"
	set tomorrowLow to text item 2 of tomorrowLow & "ºC"
	
	-- Descriptions.
	set AppleScript's text item delimiters to "<div class=\"fcstTextBox\">"
	set {nowdescription, todayDescription, tonightDescription, tomorrowDescription} to text items 2 thru 5 of twoDayData
	
	set AppleScript's text item delimiters to "</div>"
	set nowdescription to text 6 thru -1 of text item 1 of nowdescription
	set todayDescription to text item 1 of todayDescription
	set tonightDescription to text item 1 of tonightDescription
	set tomorrowDescription to text item 1 of tomorrowDescription
	
	-- Dialog text.
	set nowText to "Currently" & returnTab & nowdescription & returnTab & "Temperature: " & temperatureNow & returnTab & "Feels like: " & feelNow & twoReturns
	set todayText to "Today" & returnTab & todayDescription & returnTab & "High: " & todayHigh & twoReturns
	set tonightText to "Tonight" & returnTab & tonightDescription & returnTab & "Low: " & todayLow & twoReturns
	set tomorrowText to "Tomorrow" & returnTab & tomorrowDescription & returnTab & "High: " & tomorrowHigh & returnTab & "Low: " & tomorrowLow
	
	set dialog to "Weather for " & city & return & today & twoReturns & nowText & todayText & tonightText & tomorrowText
	set AppleScript's text item delimiters to ". "
	set dialog to dialog's text items
	set AppleScript's text item delimiters to returnTab
	set dialog to dialog as text
	
	set AppleScript's text item delimiters to astid
	-----------------------------------------------------------------------------------------
	display dialog dialog buttons {"OK"} default button 1 with title "Weather"
end displayWeather

displayWeather({location:"York"})

You have it wrong. It should be

set redirectData to do shell script "/usr/bin/curl" & space & ¬
	"-A" & space & quoted form of agent & space & ¬
	"-d" & space & quoted form of "partner=accuweather&postalcode=London&submit=GO&u=1&partner=accuweather" & space & quoted form of "http://www.accuweather.com/ukie/uk-city-list.asp?"

Note the “-d” showing the data sent to get the London’s weather. The agent part helps Accuweather think you are a browser and you should not get any 404 errors. Hope this helps cause it works for me.

Opps… Also add

set agent to "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_2; en-us) AppleWebKit/531.21.8 (KHTML, like Gecko) Version/4.0.4 Safari/531.21.10"

before it.

Hi, Dylan.

Thanks for your reply. Unfortunately, in that form, the curl command only returns what appears to be the template for a main page, without any specific location, link, or weather details. It’s the same whatever location name’s entered. I need to be able to gleen a postcode for the 5-day-forecast URL, or have curl go there automatically.

Hi Nigel ,

Hope you do not mind, but I use my code in this example, rather than go through yours.
The are notes in the code.
I hope this helps.

set aList to {}
set agent to "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_2; en-us) AppleWebKit/531.21.8 (KHTML, like Gecko) Version/4.0.4 Safari/531.21.10"
set the_Location to "york"

(* search with curl, if the page is redirected, then use a curl a redirect (-L) to search again.
We will know the page is redirected if we get a  "Object moved"*)

set redirectData to (do shell script ("curl " & " -A" & space & quoted form of agent & space & quoted form of "http://www.accuweather.com/ukie/uk-city-list.asp?partner=accuweather&postalcode=" & the_Location))

if redirectData contains "Object moved" then
	set redirectData to (do shell script ("curl -L  " & " -A" & space & quoted form of agent & space & quoted form of "http://www.accuweather.com/ukie/uk-city-list.asp?partner=accuweather&postalcode=" & the_Location))
	
end if

(* Check if a list of possible maches is given, and get the html that contains the list.
We will know if there is a list if  we get any "cellLocation"*)
if redirectData contains "cellLocation" then
	(* search between the two words *)
	set oSet1 to "cellLocation"
	set oSet2 to "</div>"
	my strip(oSet1, oSet2, redirectData, aList)
	
	(* Things to use  when parsing the items in the list.
	
	 cellLocation
 cellPostal  
 cellCountry
 
     You could display a list, and then use the post code in the item to do a new curl.
 *)
	
	
	aList
else
	redirectData

	--your other code here if you get the 5day forcast page straght away
end if

on strip(oSet1, oSet2, thedata, aList)
	set textNumber1 to (offset of oSet1 in thedata)
	set theData1 to text -1 thru (textNumber1 + (count of oSet1)) of thedata
	set textNumber2 to (offset of oSet2 in theData1)
	set textString2 to text from word 1 to (textNumber2 - 1) of theData1
	set thedata to theData1
	copy textString2 to end of aList
end strip

Thanks, Mark!

Very kind of you to look into this. Your code returns exactly the data I was hoping for and I should easily be able to work it into the script. :slight_smile:

I’ll also study your and Dylan’s use of curl in conjunction with the “man” file to try and improve my understanding of it.

Hi, I just tried out your script, Nigel and edited it slightly, so that it reads out the weather, but it says “NNW” instead of “North north west” and “KMH” instead of “kilometres per hour”. How do I make it read the weather properly?

Model: MacBook Pro
AppleScript: 2.21
Browser: Google Chrome 5.0.307.9
Operating System: Mac OS X (10.6)

Hi, magikseb.

“NNW” and “km/h” are what the site sends. And having just written a handler to expand them in the way you want, I think they look better in the dialog as they are.

But anyway, here’s my final version of script, with the adaptation you wanted added. (A handler called unabbreviate() and three calls to it.)

-- Based on a script by Dylan Weber. Additional curl help from Mark Hunte.

property returnTab : return & tab
property twoReturns : return & return
property curlStartWithAgent : "curl -A " & quoted form of "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_2; en-us) AppleWebKit/531.21.8 (KHTML, like Gecko) Version/4.0.4 Safari/531.21.10" & " -m 15 "

on displayWeather(locationInput) -- locationInput is a record: either {postcode:"xxx xxx"} or {location:"Place Name"}
	set {postcode:postcode, location:location} to locationInput & {postcode:"", location:""}
	
	set astid to AppleScript's text item delimiters
	
	if ((count postcode) > 0) then -- Postcode supplied.
		-- Postcode takes priority.
	else if ((count location) > 0) then -- Place name supplied.
		try
			set redirectData to (do shell script (curlStartWithAgent & quoted form of ("http://www.accuweather.com/ukie/uk-city-list.asp?partner=accuweather&postalcode=" & findNreplace(location, space, "%20"))))
		on error
			showError("curl error while trying to retrieve postcode from AccuWeather site.")
		end try
		if (redirectData begins with "<head><title>Object moved</title></head>") then
			set AppleScript's text item delimiters to quote
			set postcode to text item 2 of redirectData
			set AppleScript's text item delimiters to "="
			set postcode to text item -1 of postcode
		else if (redirectData contains "\"cellLocation\"") then
			set AppleScript's text item delimiters to {"\"cellLocation\">", "</table>"}
			set redirectData to text items 2 thru -2 of redirectData
			set AppleScript's text item delimiters to {"\">", "</a></td>"}
			repeat with thisPossibility in redirectData
				tell thisPossibility to set contents to my capitaliseThroughout(text item 2) & ", " & my capitaliseThroughout(text item -2) & "  " & text item 5
			end repeat
			set locationChoice to (choose from list redirectData with prompt ("Which "" & location & "" do you mean?"))
			if (locationChoice is false) then
				set AppleScript's text item delimiters to astid
				error number -128
			end if
			set postcode to text from word -2 to -1 of beginning of locationChoice
		else
			set AppleScript's text item delimiters to astid
			showError("The script's not equipped to deal with what's been returned for "" & location & ""!")
		end if
	else
		showError("Bad input parameter!")
	end if
	
	display dialog "Loading Weather Forecast; may be a few seconds!" buttons {"OK"} default button 1 with title "Weather" giving up after 2
	
	try
		set fiveDayData to (do shell script (curlStartWithAgent & quoted form of ("http://www.accuweather.com/ukie/forecast.asp?partner=accuweather&traveler=0&postalcode=" & findNreplace(postcode, space, "%20") & "&metric=1")))
	on error
		showError("curl error while trying to retrieve weather data from AccuWeather site.")
	end try
	
	-- Location.
	set AppleScript's text item delimiters to "<div id=\"fcst_title\">"
	set fiveDayData to text from text item 2 to -1 of fiveDayData
	set AppleScript's text item delimiters to "</div>"
	set city to text item 1 of fiveDayData
	
	-- Local date and time.
	set AppleScript's text item delimiters to "<span class=\"textsmallbold\">"
	set fiveDayData to text from text item 2 to -1 of fiveDayData
	set {m, d, y} to words 2 thru 4 of fiveDayData
	set today to (date "Monday 1 January 1000 00:00:00")
	set today's year to y
	set today's month to (offset of (text 1 thru 3 of m) in "JanFebMarAprMayJunJulAugSepOctNovDec") div 3 + 1
	set today's day to d
	set AppleScript's text item delimiters to ">Currently</a> at "
	set fiveDayData to text from text item 2 to -1 of fiveDayData
	set AppleScript's text item delimiters to "</div>"
	set t to text item 1 of fiveDayData
	tell t to set today's time to ((word 1) mod 12 + ((word 3 is "PM") as integer) * 12) * hours + (word 2) * minutes
	
	-- Data for now, today, and tomorrow.
	set dayAfterTomorrow to today + 2 * days
	set AppleScript's text item delimiters to "class=\"textsmall\">" & (dayAfterTomorrow's weekday) & "</a>, " & text 1 thru 3 of (dayAfterTomorrow's month as text) & " " & (dayAfterTomorrow's day) & "</div>"
	set twoDayData to text item 1 of fiveDayData
	
	-- Current, high, low, and feel temperatures. Only the first "feel" temperature is currently used.
	set AppleScript's text item delimiters to {" °C", " °C", "°C"}
	set {feelNow, temperatureNow, todayHighFeel, todayHigh, todayLowFeel, todayLow, tomorrowHighFeel, tomorrowHigh, tomorrowLowFeel, tomorrowLow} to text items 1 thru 10 of twoDayData
	
	set AppleScript's text item delimiters to {"Temp: ", "Temp: "}
	set temperatureNow to text item 2 of temperatureNow & "ºC"
	
	set AppleScript's text item delimiters to {">Realfeel®:"}
	set feelNow to text item 2 of feelNow & "ºC"
	
	set AppleScript's text item delimiters to {"High: ", "High: "}
	set todayHigh to text item 2 of todayHigh & "ºC"
	set tomorrowHigh to text item 2 of tomorrowHigh & "ºC"
	
	set AppleScript's text item delimiters to {"Low: ", "Low: "}
	set todayLow to text item 2 of todayLow & "ºC"
	set tomorrowLow to text item 2 of tomorrowLow & "ºC"
	
	-- Descriptions.
	set AppleScript's text item delimiters to "<div class=\"fcstTextBox\">"
	set {nowdescription, todayDescription, tonightDescription, tomorrowDescription} to text items 2 thru 5 of twoDayData
	
	set AppleScript's text item delimiters to "</div>"
	set nowdescription to text 6 thru -1 of text item 1 of nowdescription
	set todayDescription to unabbreviate(text item 1 of todayDescription)
	set tonightDescription to unabbreviate(text item 1 of tonightDescription)
	set tomorrowDescription to unabbreviate(text item 1 of tomorrowDescription)
	
	-- Dialog text.
	set nowText to "Currently" & returnTab & nowdescription & returnTab & "Temperature: " & temperatureNow & returnTab & "Feels like: " & feelNow & twoReturns
	set todayText to "Today" & returnTab & capitalise(todayDescription) & returnTab & "High: " & todayHigh & twoReturns
	set tonightText to "Tonight" & returnTab & capitalise(tonightDescription) & returnTab & "Low: " & todayLow & twoReturns
	set tomorrowText to "Tomorrow" & returnTab & capitalise(tomorrowDescription) & returnTab & "High: " & tomorrowHigh & returnTab & "Low: " & tomorrowLow
	
	set dialog to "Weather for " & city & "  " & findNreplace(postcode, "%20", space) & return & today & twoReturns & nowText & todayText & tonightText & tomorrowText
	set AppleScript's text item delimiters to ". "
	set dialog to dialog's text items
	set AppleScript's text item delimiters to returnTab
	set dialog to dialog as text
	
	set AppleScript's text item delimiters to astid
	-----------------------------------------------------------------------------------------
	display dialog dialog buttons {"OK"} default button 1 with title "Weather"
end displayWeather

on findNreplace(txt, find, replace)
	set astid to AppleScript's text item delimiters
	
	set AppleScript's text item delimiters to find
	set txt to txt's text items
	set AppleScript's text item delimiters to replace
	set txt to txt as text
	set AppleScript's text item delimiters to astid
	
	return txt
end findNreplace

on unabbreviate(txt)
	set o to (offset of "Winds from the " in txt)
	if (o > 0) then
		set t2 to text (o + 15) thru -1 of txt
		set windDirection to word 1 of t2
		set astid to AppleScript's text item delimiters
		repeat with quarter in {"north ", "south ", "east ", "west "}
			set AppleScript's text item delimiters to character 1 of quarter
			set windDirection to windDirection's text items
			set AppleScript's text item delimiters to quarter's contents
			set windDirection to windDirection as text
		end repeat
		set AppleScript's text item delimiters to "km/h"
		if (text item 1 of t2 ends with " 1 ") then
			set t2 to findNreplace(t2, "km/h", "kilometre per hour")
		else
			set t2 to findNreplace(t2, "km/h", "kilometres per hour")
		end if
		set AppleScript's text item delimiters to astid
		set txt to text 1 thru (o + 14) of txt & windDirection & text from word 2 to -1 of t2
	end if
	
	return txt
end unabbreviate

on capitalise(txt)
	set chrs to "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
	
	considering case
		set o to ((offset of (character 1 of txt) in chrs) - 1) mod 26 + 1
	end considering
	
	return character o of chrs & text 2 thru -1 of txt
end capitalise

on capitaliseThroughout(txt)
	set astid to AppleScript's text item delimiters
	set AppleScript's text item delimiters to space
	set txt to text items of text from word 1 to -1 of txt
	repeat with thisWord in txt
		set thisWord's contents to capitalise(thisWord)
	end repeat
	set txt to txt as text
	set AppleScript's text item delimiters to astid
	
	return txt
end capitaliseThroughout

on showError(msg)
	tell application (path to frontmost application as text)
		display dialog msg buttons {"OK"} default button 1 cancel button 1 with title "Weather" with icon stop
	end tell
end showError

displayWeather({postcode:"KW14 8BZ"})

If you want to try it without the “unabbreviations”, change these three lines in the “Descriptions” section of the displayWeather() handler .

set todayDescription to unabbreviate(text item 1 of todayDescription)
set tonightDescription to unabbreviate(text item 1 of tonightDescription)
set tomorrowDescription to unabbreviate(text item 1 of tomorrowDescription)

. to this:

set todayDescription to text item 1 of todayDescription
set tonightDescription to text item 1 of tonightDescription
set tomorrowDescription to text item 1 of tomorrowDescription

I wish there was a website that never change its layout. Like that would ever happen. :lol:

You should see the script I use to save plain-text versions of threads on this very site! Over the past 10 years, it’s become a complete hotch-potch of fixes, special cases, and redundant code. :lol:

Thanks! I just wanted to change those parts so that my mac would read the weather out properly.