I need help streamlining this.

A very long time ago I started creating a script that will cross reference Safari history items with a wordlist and then notify me when a word from the list was spotted in the history item. I use it for passive surveillance of the machines at work. As I got more and more into it, I kept adding new features. The script works, but, as expected is rather slow. I realise that, to a certain extent, the slowness is down to the fact that Safari takes forever to add history items in to the Finder. I would like to be able to read the plist file at some point, but we’ll talk about that later.

Anyway, here is the script in its current form, it’s called Bootle26, any suggestions at streamlining it would be greatly appreciated:

(*
	
	Bootle26, Version 1.1.3
	"The ability to monitor the machines, without having to actually monitor the machines"

	Dan Barker 2010
	
	Help from MacScripter.net, MacRumors.com (forum), Rob Whitaker & Dave Novis
	Abuse from Rob Whitaker & Matt Clark
	Support from Jenifer Webber, Neil Munro, David Simons & Dave Novis
	
	Last updated: 22/7/10 at 2346
	
*)


(* Block 1 *)
on adding folder items to this_file after receiving added_items
	(* Locates and reads Blacklist *)
	set Blacklist_File to (open for access ("Macintosh HD:Library:Application Support:Bootle26:Wordlists:Blacklist.txt"))
	set txt to (read Blacklist_File for (get eof Blacklist_File))
	set blackWords to every paragraph of txt
	close access Blacklist_File
	(* Locates and reads RedFlag List *)
	set Red_File to (open for access ("Macintosh HD:Library:Application Support:Bootle26:Wordlists:RedFlagList.txt"))
	set txt2 to (read Red_File for (get eof Red_File))
	set RedWords to every paragraph of txt2
	close access Red_File
	(* Cross references words in Blacklist with words in history items *)
	repeat with i from 1 to number of items in added_items
		tell application "Finder" to set this_item to words of (displayed name of (item i of added_items as alias) as string)
		repeat with z from 1 to number of items in this_item
			set this_item_word to item z of this_item
			if this_item_word is in RedWords then
				my redKeyword(this_item_word)
			else
				if this_item_word is in blackWords then
					my blackKeyword(this_item_word)
				end if
			end if
		end repeat
	end repeat
end adding folder items to
(* EOBlock 1 *)
(* Block 2 *)
(* if any word is in the Blacklist then the following happens *)
on blackKeyword(N)
	(* Script now defines the required variables *)
	set theDate to date string of (current date)
	set theTime to time string of (current date)
	set dateandtime to "At " & theTime & " on " & theDate
	set ScreenCapDT to theTime & " - " & theDate
	set MacName to computer name of (system info)
	set LogFileName to MacName & ".logtxt"
	set ErrorFileName to MacName & ".errtxt"
	set error_file to (("Macintosh HD:Library:Application Support:Bootle26:Error Log:" as text) & ErrorFileName)
	set picPath to "/Library/Application Support/Bootle26/Screenshots/Blacklist/" & ScreenCapDT & ".png" as string
	set ErrorLogText0 to return & return & "Bootle Error Message!" & return & MacName & return & dateandtime & return & "Reports error code #"
	set ErrorLogTextG to "Error Message!" & return & MacName & return & "Reports error code #"
	(* Blacklist Error Texts *)
	set ErrorLogText2_1 to ErrorLogText0 & "2.1" & return & "An issue with Safari"
	set ErrorLogText2_2 to ErrorLogText0 & "2.2" & return & "An issue with the Log File"
	set ErrorLogText2_3 to ErrorLogText0 & "2.3" & return & "An issue with the Log File"
	set ErrorLogText2_4 to ErrorLogText0 & "2.4" & return & "An issue with Growl/Network/Authentication"
	(* Growl Error Texts *)
	set ErrorLogTextG_2_2 to ErrorLogTextG & "2.2" & return & "An issue with the Log File"
	set ErrorLogTextG_2_3 to ErrorLogTextG & "2.3" & return & "An issue with the Log File"
	(* Block 2.1 *)
	(* Safari reports the current URL & webpage title and saves them as text strings *)
	try
		tell application "Safari"
			get URL of front document as string
		end tell
		copy result as text to longURL
		set i to count words of longURL
		set theURL to word 2 of longURL
		tell application "Safari"
			do JavaScript "document.title" in document 0
		end tell
		copy result as list to {SafTitle}
	on error
		set theURL to "Safari exited early"
		set SafTitle to "Safari exited early"
	end try
	(* EOBlock 2.1 *)
	(* Block 2.2 *)
	(* Locates Log files and defines required log text *)
	try
		set the_file to (("Macintosh HD:Library:Application Support:Bootle26:Machine Logs:" as text) & LogFileName)
		set LogFileText to return & return & "Blacklist Warning!" & return & "User of " & MacName & " requests: " & return & theURL & return & "Title: " & SafTitle & return & "Catalyst: " & N & return & dateandtime
	on error
		set datastream to open for access file error_file with write permission
		write ErrorLogText2_2 to datastream starting at eof
		close access datastream
		tell application "GrowlHelperApp" (*of machine "eppc://UserName:Password@Derby-Counter-Left.local"*)
			set appName to "Bootle26"
			set notificationName to "Bootle26"
			set notifs to {notificationName}
			register as application appName all notifications notifs default notifications notifs
			notify with name notificationName title ("Bootle Reports!") description ErrorLogTextG_2_2 & return & "At " & theTime application name appName icon of application "BootleAppIcon" priority 2 with sticky
		end tell
	end try
	(* EOBlock 2.2 *)
	(* Block 2.3 *)
	(* Writes Log file data to the log file for future reference *)
	try
		set datastream to open for access file the_file with write permission
		write LogFileText to datastream starting at eof
		close access datastream
	on error
		set datastream to open for access file error_file with write permission
		write ErrorLogText2_3 to datastream starting at eof
		close access datastream
		tell application "GrowlHelperApp" (*of machine "eppc://UserName:Password@Derby-Counter-Left.local"*)
			set appName to "Bootle26"
			set notificationName to "Bootle26"
			set notifs to {notificationName}
			register as application appName all notifications notifs default notifications notifs
			notify with name notificationName title ("Bootle Reports!") description ErrorLogTextG_2_3 & return & "At " & theTime application name appName icon of application "BootleAppIcon" priority 2 with sticky
		end tell
	end try
	(* EOBlock 2.3 *)
	
	(* Block 2.4 *)
	(* Pushes Growl notification to left hand machine *)
	(*DON'T FORGET TO REMOVE QUOTES AROUND EPPC BIT*)
	try
		do shell script "screencapture -tjpg " & quoted form of picPath
		tell application "GrowlHelperApp" (*of machine "eppc://UserName:Password@Derby-Counter-Left.local"*)
			set appName to "Bootle26"
			set notificationName to "Bootle26"
			set notifs to {notificationName}
			register as application appName all notifications notifs default notifications notifs
			notify with name notificationName title ("Blacklist Warning!" & return & MacName) description "Catalyst: " & N & return & theURL & return & "At " & theTime application name appName icon of application "BootleAppIcon" priority 0 with sticky
		end tell
	on error
		set datastream to open for access file error_file with write permission
		write ErrorLogText2_4 to datastream starting at eof
		close access datastream
	end try
	(* EOBlock 2.4 *)
end blackKeyword
(* EOBlock 2 *)
(* 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 *)
(* Block 3 *)
(* if any word is in the RedFlag List then the following happens *)
on redKeyword(K)
	set theDate to date string of (current date)
	set theTime to time string of (current date)
	set dateandtime to "At " & theTime & " on " & theDate
	set ScreenCapDT to theTime & " - " & theDate
	set MacName to computer name of (system info)
	set LogFileName to MacName & ".logtxt"
	set ErrorFileName to MacName & ".errtxt"
	set error_file to (("Macintosh HD:Library:Application Support:Bootle26:Error Log:" as text) & ErrorFileName)
	set picPath to "/Library/Application Support/Bootle26/Screenshots/RedFlag List/" & ScreenCapDT & ".png" as string
	set ErrorLogText0 to return & return & "Bootle Error Message!" & return & MacName & return & dateandtime & return & "Reports error code #"
	set ErrorLogTextG to "Error Message!" & return & MacName & return & "Reports error code #"
	(* RedFlag Error Texts *)
	set ErrorLogText3_1 to ErrorLogText0 & "3.1" & return & "An issue with Safari"
	set ErrorLogText3_2 to ErrorLogText0 & "3.2" & return & "An issue with the Log File"
	set ErrorLogText3_3 to ErrorLogText0 & "3.3" & return & "An issue with the Log File"
	set ErrorLogText3_4 to ErrorLogText0 & "3.4" & return & "An issue with Growl/Network/Authentication"
	(* Growl Error Texts *)
	set ErrorLogTextG_3_2 to ErrorLogTextG & "3.2" & return & "An issue with the Log File"
	set ErrorLogTextG_3_3 to ErrorLogTextG & "3.3" & return & "An issue with the Log File"
	(* Block 3.1 *)
	(* Safari reports the current URL & window title and saves as text strings *)
	try
		tell application "Safari"
			get URL of front document as string
		end tell
		copy result as text to longURL
		set i to count words of longURL
		set theURL to word 2 of longURL
		tell application "Safari"
			do JavaScript "document.title" in document 0
		end tell
		copy result as list to {SafTitle}
	on error
		set theURL to "Safari exited early"
		set SafTitle to "Safari exited early"
	end try
	(* EOBlock 3.1 *)
	(* Block 3.2 *)
	(* Locates Log files and defines required log text *)
	try
		set the_file to (("Macintosh HD:Library:Application Support:Bootle26:Machine Logs:" as text) & LogFileName)
		set LogFileText to return & return & "RED FLAG!" & return & "User of " & MacName & " requests: " & return & theURL & return & "Title: " & SafTitle & return & "Catalyst: " & K & return & dateandtime
	on error
		set datastream to open for access file error_file with write permission
		write ErrorLogText3_2 to datastream starting at eof
		close access datastream
		tell application "GrowlHelperApp" (*of machine "eppc://UserName:Password@Derby-Counter-Left.local"*)
			set appName to "Bootle26"
			set notificationName to "Bootle26"
			set notifs to {notificationName}
			register as application appName all notifications notifs default notifications notifs
			notify with name notificationName title ("Bootle Reports!") description ErrorLogTextG_3_2 & return & "At " & theTime application name appName icon of application "BootleAppIcon" priority 2 with sticky
		end tell
	end try
	(* EOBlock 3.2 *)
	(* Block 3.3 *)
	(* Writes Log file data to the log file for future reference *)
	try
		
		set datastream to open for access file the_file with write permission
		write LogFileText to datastream starting at eof
		close access datastream
	on error
		set datastream to open for access file error_file with write permission
		write ErrorLogText3_3 to datastream starting at eof
		close access datastream
		tell application "GrowlHelperApp" (*of machine "eppc://UserName:Password@Derby-Counter-Left.local"*)
			set appName to "Bootle26"
			set notificationName to "Bootle26"
			set notifs to {notificationName}
			register as application appName all notifications notifs default notifications notifs
			notify with name notificationName title ("Bootle Reports!") description ErrorLogTextG_3_3 & return & "At " & theTime application name appName icon of application "BootleAppIcon" priority 2 with sticky
		end tell
	end try
	(* EOBlock 3.3 *)
	
	(* Block 3.4 *)
	(* Pushes Growl notification to left hand machine *)
	(*DON'T FORGET TO REMOVE QUOTES AROUND EPPC BIT*)
	try
		do shell script "screencapture -tjpg " & quoted form of picPath
		tell application "GrowlHelperApp" (*of machine "eppc://UserName:Password@Derby-Counter-Left.local"*)
			set appName to "Bootle26"
			set notificationName to "Bootle26"
			set notifs to {notificationName}
			register as application appName all notifications notifs default notifications notifs
			notify with name notificationName title ("RED FLAG!" & return & MacName) description "The User was logged out!" & return & "At " & theTime application name appName icon of application "BootleAppIcon" priority 2 with sticky
		end tell
	on error
		set datastream to open for access file error_file with write permission
		write ErrorLogText3_4 to datastream starting at eof
		close access datastream
	end try
	(* EOBlock 3.4 *)
	(* DON'T FORGET TO REMOVE THE QUOTES ROUND THE BELOW COMMAND!! *)
	(* The next command is what logs the user out of their machine, it is commented so as to allow script development, as constantly being logged out is incredibly annoying *)
	--do shell script "'/System/Library/CoreServices/Menu Extras/User.menu/Contents/Resources/CGSession' -suspend"
end redKeyword
(* EOBlock 3 *)

Half way through creating Bootle26, I started learning Objective-C, but, oddly, found that the more I learned about Obj-C, the easier it became to finish Bootle in AppleScript.

Bootle is my baby, so be nice. (And yes, I do know about Safari’s Private Browsing mode, but that’s not applicable here.)

Dan.

Model: iMac
Browser: Safari 533.16
Operating System: Mac OS X (10.6)

There’s lots of things to be improved. First, you have two handlers (blackKeyword(N) and redKeyword(K)) that do essentially the same thing with only minor variations. It’s really difficult to maintain 2 handlers, so if you can do it in one you should. Here’s an example of how to combine them. In your main code use this first script and make the one handler look like the second script… where whenever needed you just check isBlack and set the variable accordingly.

if this_item_word is in RedWords then
	my keyWords(this_item_word, false)
else if this_item_word is in blackWords then
	my keyWords(this_item_word, true)
end if
on keyWords(N, isBlack) -- make isBlack true or false
	-- set date and time stuff
	if isBlack then
		set picPath to "/Library/Application Support/Bootle26/Screenshots/Blacklist/" & ScreenCapDT & ".png"
	else
		set picPath to "/Library/Application Support/Bootle26/Screenshots/RedFlag List/" & ScreenCapDT & ".png"
	end if
	-- do other stuff
end keyWords

I went through the lines of your code and you have many, many places where you coerce something to string when it’s not needed. I also found a few other optimizations. So this following script is notes about what you can change. I first give comments about the change, then list the current code that the comments apply to, then list what the optimized code should look like. Let me know if you need help understanding it.

-- displayed name is a string, so no reason to coerce it to a string
-- added_items are already an alias, so no reason to coerce it to alias
tell application "Finder" to set this_item to words of (displayed name of (item i of added_items as alias) as string)
tell application "Finder" to set this_item to words of (displayed name of (item i of added_items))

-- you get current date twice
set theDate to date string of (current date)
set theTime to time string of (current date)
set dateandtime to "At " & theTime & " on " & theDate
set ScreenCapDT to theTime & " - " & theDate

set cDate to current date
set theDate to date string of cDate
set theTime to time string of cDate
set dateandtime to "At " & theTime & " on " & theDate
set ScreenCapDT to theTime & " - " & theDate

-- no need to coerce to text, it's already text
set error_file to (("Macintosh HD:Library:Application Support:Bootle26:Error Log:" as text) & ErrorFileName)
set error_file to "Macintosh HD:Library:Application Support:Bootle26:Error Log:" & ErrorFileName

-- no need to coerce to string, it's already string
set picPath to "/Library/Application Support/Bootle26/Screenshots/Blacklist/" & ScreenCapDT & ".png" as string
set picPath to "/Library/Application Support/Bootle26/Screenshots/Blacklist/" & ScreenCapDT & ".png"

-- no need to coerce to string, it's already string... also can set longURL directly without the copy operation
tell application "Safari"
	get URL of front document as string
end tell
copy result as text to longURL

tell application "Safari" to set longURL to URL of front document

-- you get "words" twice... only do it once
set i to count words of longURL
set theURL to word 2 of longURL

set urlWords to words of longURL
set i to count of urlWords
set theURL to item 2 of urlWords

-- you don't need javascript, or the additional copy operation, and why are you setting SafTitle as a list when in the error statement you don't set SafTitle to a list? I think leave it alone... it's a string
tell application "Safari"
	do JavaScript "document.title" in document 0
end tell
copy result as list to {SafTitle}

tell application "Safari"
	tell window 1 to set SafTitle to name
end tell

-- already text
set the_file to (("Macintosh HD:Library:Application Support:Bootle26:Machine Logs:" as text) & LogFileName)
set the_file to "Macintosh HD:Library:Application Support:Bootle26:Machine Logs:" & LogFileName

This is exactly what I was hoping for. Thank you.

I won’t get a chance to look into it in any detail until this evening, as it’s 11Am here, but you can be sure I will.

Regarding the section about the Safari variables:

If I am understanding this correctly, you’re asking why I set the variables to some text if it errors out? If so, the reason for this is just in case the user quits Safari too early. If that happens, then the variables, in the previous script, didn’t get set to anything, when that happens it causes the whole script to crash out.

For example, if someone typed ‘porn’ into google, hit return, loaded the page and then quit Safari immediately. It would be too quick for the script to even start, as Safari takes forever to add history items. If Safari is quit then Bootle can’t set those variables, so every time I call them later on, in the Log file text blocks and growl alerts, all I get is errors.

With the variables set at “Safari exited early”, The rest of the script can still run, and notify me of the keyword even if the user quits early.

If I haven’t understood you correctly then at least one of the bits of original script is explained.

Anyway, I’ll slowly read through this during the day, and then get stuck in this evening.

Model: iMac
Browser: Safari 533.4
Operating System: Mac OS X (10.6)

Good. I hope it helps.

What I meant about that variable… in the part when it doesn’t error you set the variable to be a list… and in the part where it does error the variable is a string. So that doesn’t make sense. It should be one or the other in both cases so I assumed you needed it to be a string in both cases and as such my updated code makes it a string.

So…

Got stuck in pretty much as soon as I got in from work, sad I know, but it seems to have paid off.

I’m self-taught, as I expect most of us are, but I also have to contend with the people at work telling me to ‘give up’ as soon as I hit a bit I don’t know anything about. Anyway, I drilled through it, and now it looks like this:

(*
	Bootle26 v2 BETA
*)


on adding folder items to this_file after receiving added_items
	(* Locates and reads Blacklist *)
	set Blacklist_File to (open for access ("Macintosh HD:Library:Application Support:Bootle26:Wordlists:Blacklist.txt"))
	set txt to (read Blacklist_File for (get eof Blacklist_File))
	set blackWords to every paragraph of txt
	close access Blacklist_File
	(* Locates and reads RedFlag List *)
	set Red_File to (open for access ("Macintosh HD:Library:Application Support:Bootle26:Wordlists:RedFlagList.txt"))
	set txt2 to (read Red_File for (get eof Red_File))
	set RedWords to every paragraph of txt2
	close access Red_File
	(* Cross references words in Blacklist with words in history items *)
	repeat with i from 1 to number of items in added_items
		tell application "Finder" to set this_item to words of (displayed name of (item i of added_items as alias) as string)
		repeat with z from 1 to number of items in this_item
			set this_item_word to item z of this_item
			if this_item_word is in RedWords then
				my keyWords(this_item_word, false)
			else if this_item_word is in blackWords then
				my keyWords(this_item_word, true)
			end if
		end repeat
	end repeat
end adding folder items to

on keyWords(N, isBlack)
	
	set cDate to current date
	set theDate to date string of cDate
	set theTime to time string of cDate
	set dateandtime to "At " & theTime & " on " & theDate
	set ScreenCapDT to theTime & " - " & theDate
	set MacName to computer name of (system info)
	set LogFileName to MacName & ".logtxt"
	set ErrorFileName to MacName & ".errtxt"
	set error_file to "Macintosh HD:Library:Application Support:Bootle26:Error Log:" & ErrorFileName
	if isBlack then
		set picPath to "/Library/Application Support/Bootle26/Screenshots/Blacklist/" & ScreenCapDT & ".png"
	else
		set picPath to "/Library/Application Support/Bootle26/Screenshots/RedFlag List/" & ScreenCapDT & ".png"
	end if
	set ErrorLogText0 to return & return & "Bootle Error Message!" & return & MacName & return & dateandtime & return & "Reports error code #"
	set ErrorLogTextG to "Error Message!" & return & MacName & return & "Reports error code #"
	(* Blacklist Error Texts *)
	set ErrorLogText2_1 to ErrorLogText0 & "2.1" & return & "An issue with Safari"
	set ErrorLogText2_2 to ErrorLogText0 & "2.2" & return & "An issue with the Log File"
	set ErrorLogText2_3 to ErrorLogText0 & "2.3" & return & "An issue with the Log File"
	set ErrorLogText2_4 to ErrorLogText0 & "2.4" & return & "An issue with Growl/Network/Authentication"
	(* Growl Error Texts *)
	set ErrorLogTextG_2_2 to ErrorLogTextG & "2.2" & return & "An issue with the Log File"
	set ErrorLogTextG_2_3 to ErrorLogTextG & "2.3" & return & "An issue with the Log File"
	
	try
		tell application "Safari" to set longURL to URL of front document
		set urlWords to words of longURL
		set i to count of urlWords
		set theURL to item 2 of urlWords
		tell application "Safari"
			tell window 1 to set SafTitle to name
		end tell
	on error
		set theURL to "Safari exited early"
		set SafTitle to theURL
	end try
	
	set the_file to "Macintosh HD:Library:Application Support:Bootle26:Machine Logs:" & LogFileName
	
	if isBlack then
		set LogFileText to return & return & "Blacklist Warning!" & return & "User of " & MacName & " requests: " & return & theURL & return & "Title: " & SafTitle & return & "Catalyst: " & N & return & dateandtime
		
	else
		set LogFileText to return & return & "RED FLAG!" & return & "User of " & MacName & " requests: " & return & theURL & return & "Title: " & SafTitle & return & "Catalyst: " & N & return & dateandtime
	end if
	
	try
		set datastream to open for access file the_file with write permission
		write LogFileText to datastream starting at eof
		close access datastream
	on error
		set datastream to open for access file error_file with write permission
		write ErrorLogText2_3 to datastream starting at eof
		close access datastream
		tell application "GrowlHelperApp" (*of machine "eppc://UserName:Password@Derby-Counter-Left.local"*)
			set appName to "Bootle26"
			set notificationName to "Bootle26"
			set notifs to {notificationName}
			register as application appName all notifications notifs default notifications notifs
			notify with name notificationName title ("Bootle Reports!") description ErrorLogTextG_2_3 & return & "At " & theTime application name appName icon of application "BootleAppIcon" priority 2 with sticky
		end tell
	end try
	
	(* EOBlock 2.3 *)
	
	(* Block 2.4 *)
	(* Pushes Growl notification to left hand machine *)
	(*DON'T FORGET TO REMOVE QUOTES AROUND EPPC BIT*)
	try
		
		
		if isBlack then
			do shell script "screencapture -tjpg " & quoted form of picPath
			tell application "GrowlHelperApp" (*of machine "eppc://UserName:Password@Derby-Counter-Left.local"*)
				set appName to "Bootle26"
				set notificationName to "Bootle26"
				set notifs to {notificationName}
				register as application appName all notifications notifs default notifications notifs
				notify with name notificationName title ("Blacklist Warning!" & return & MacName) description "Catalyst: " & N & return & theURL & return & "At " & theTime application name appName icon of application "BootleAppIcon" priority 0 with sticky
			end tell
		else
			do shell script "screencapture -tjpg " & quoted form of picPath
			tell application "GrowlHelperApp" (*of machine "eppc://UserName:Password@Derby-Counter-Left.local"*)
				set appName to "Bootle26"
				set notificationName to "Bootle26"
				set notifs to {notificationName}
				register as application appName all notifications notifs default notifications notifs
				notify with name notificationName title ("RED FLAG!" & return & MacName) description "The User was logged out!" & return & "At " & theTime application name appName icon of application "BootleAppIcon" priority 2 with sticky
			end tell
			delay 1
			(* DON'T FORGET TO REMOVE THE QUOTES ROUND THE BELOW COMMAND!! *)
			--do shell script "'/System/Library/CoreServices/Menu Extras/User.menu/Contents/Resources/CGSession' -suspend"
		end if
		
	on error
		set datastream to open for access file error_file with write permission
		write ErrorLogText2_4 to datastream starting at eof
		close access datastream
	end try
	(* EOBlock 2.4 *)
	
end keyWords

Which is much more manageable.

The bit you wrote here:

I got stuck on this for ages. Eventually I built in a test log which would enter a new line of custom text for each section of the script that was run. It turned out that that was causing it to stop at the beginning, so the old line is back in, as that seems to work. I’ll clean up those unused error codes later and I could probably streamline the growl stuff too. But that’s a job for after dinner.

All in all, I am particularly pleased that it is now working, and is scripted properly.

Thanks very much for your help.

Any ideas on how to read Safari’s plist history file?

Dan

Model: iMac
Browser: Safari 533.16
Operating System: Mac OS X (10.6)

Very good! It looks much cleaner. I’m not sure why the Finder stuff didn’t work but that’s fine because in the end your code works… so no sense in spending all your time troubleshooting one line of code. Regarding the plist file. You have 2 options and it depends on what you need to do exactly. There’s a unix command “defaults” that you could use. You can check the man page for how to use it. You can also use “System Events” to read it.

So I looked through my history plist. From what I see you doing I think you’d be interested in the title and the url of the websites. So here’s a script to read those using system events. If my example isn’t what you need then at least it will get you started. You’ll see that I used the “|” character in the script. I had to use it because, for example, “title” is one of the keys in the plist file and title is a key word in applescript… so I couldn’t use title by itself in the script to pull out the value (because it’s a key word). Placing “|” around something tells applescript to ignore the word as a key word.

NOTE: I’m using the latest version of Safari and the latest version of MacOSX, so I can’t guarantee this will work on other versions as the plist file might be organized differently.

set plistPath to (path to library folder from user domain as text) & "Safari:History.plist"

tell application "System Events"
	set plist to property list file plistPath
	set webHistory to value of property list item "WebHistoryDates" of plist
	repeat with aHistory in webHistory
		try
			set thisTitle to |title| of aHistory
			set thisURL to || of aHistory
			-- do something with the title and url of this history item
		end try
	end repeat
end tell

That’s brilliant thanks.

What I’m hoping to do is to replace the ‘on adding folder items to’ bit with the plist stuff instead. In theory, the plist would be updated before the history file will, so it could well make the script quicker.

As it currently stands, the script gets it’s keywords from the file name in “~/Users/danbarker/Library/Caches/Metadata/Safari/History/”
where your typical file name is:
“http/%2F%2Fwww.google.co.uk%2Fsearch?hl=en&source=hp&q=bootle&cts=1279912241303&aq=f&aqi=g10&aql=&oq=&gs_rfai=.webhistory” in this case, bootle, buried in the middle, is the test keyword.

So I guess what I want is to read the URL of Item 0 in the plist to scan for the keywords, and then use the plist later to get the Safari variables that the script asks.

Is there a way to make it read only from Item 0, and only when the plist is updated?

Sure. To get the first item just get “item 1” of webHistory instead of using the repeat loop.

I think to be notified when the plist changes you can use launchd. launchd has watch paths, so you can set the history.plist as the watch path and every time history.plist changes launchd will notice the change and run your script. I think that would work well because when you visit a website in Safari, then Sarafi writes it to the history file, thus launchd will detect the change… so that’s a really good idea.

A quick google search turned up this website on how to configure launchd.

I’ve been playing around with launchd for a few days now, and I can’t for the life of me get a grasp of it.

From what I understand, I need to make an XML file? or a plist file? Either way, I couldn’t work it out.

Eventually I found an app called Lingon, which claimed to do all the required coding for you, but all it did was the opposite of what I wanted it to do.

I wanted it to run the script every time the history.plist file was modified, but instead it launched Safari every time I launched the script. So, naturally I thought to swap the two relevant lines of code over, but that didn’t change anything. And, even though I first disabled and then deleted the plisty-XMLy thing that Lingon created, it still hasn’t stopped the script from launching Safari. Which is rather annoying.

From what I have read, launchd seems to be a very good …thing. But I can’t find anything, even using your link, that actually spells it out with enough understandable detail that a baboon could understand. Or find an app that actually works.

I’ll keep trying to understand it and I’ll persevere with Lingon, in the mean time, if you come across anything that either helps create or spells out in very clear terms, exactly what it does, how it does it and how to do it, please pass it on.

Thanks for your help.

Dan.

Model: iMac
Browser: Safari 533.16
Operating System: Mac OS X (10.6)

Hello.

I post a launchAgent.plist file which should have had the name com.McUsr.UnixMailNotifier.plist
fromt the /Users/McUsr/Library/LaunchAgents.

ObserveThe program’s first argument is it self.

type man launchctl in a terminal window, and read it while you are playing with a launch agent.

It is also useful to use launctl list to see if the agent actually is running.

[code]<?xml version="1.0" encoding="UTF-8"?>

Label com.McUsr.UnixMailNotifier Program /Users/McUsr/.UserAgents/UnixMailNotifier.sh ProgramArguments /Users/McUsr/.UserAgents/UnixMailNotifier.sh RunAtLoad KeepAlive SuccessfulExit WatchPaths /var/mail/McUsr [/code]

Lucky for you then that I have it working.:wink: I thought it was a cool idea so I did a test and it works really well. Do this…

  1. copy/paste the following text into TextEdit, change my user name “hmcshane” to your user name (2 places), name it com.hamsoft.SafariHistoryWatcher.plist, and put it in the ~/Library/LaunchAgents/ folder
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>Label</key>
	<string>com.hamsoft.SafariHistoryWatcher</string>
	<key>LowPriorityIO</key>
	<true/>
	<key>ProgramArguments</key>
	<array>
		<string>osascript</string>
		<string>/Users/hmcshane/Desktop/SafariHistoryWatcher.scptd</string>
	</array>
	<key>WatchPaths</key>
	<array>
		<string>/Users/hmcshane/Library/Safari/History.plist</string>
	</array>
</dict>
</plist>
  1. Create this applescript on your desktop called SafariHistoryWatcher.scptd
set plistPath to (path to library folder from user domain as text) & "Safari:History.plist"

tell application "System Events"
	set plist to property list file plistPath
	set webHistory to value of property list item "WebHistoryDates" of plist
	set firstItem to item 1 of webHistory
	set thisTitle to |title| of firstItem
	set thisURL to || of firstItem
	
	activate
	display dialog "Title:" & return & thisTitle & return & return & "Website:" & return & thisURL buttons {"OK"} default button 1
end tell
  1. in the Terminal type this to start the launchd plist (you won’t have to do this in the future because it is auto-loaded on restart as long as the plist is in the Launch Agents folder)
launchctl load ~/Library/LaunchAgents/com.hamsoft.SafariHistoryWatcher.plist

Now whenever your Safari history file changes the applescript will get run. The applescript pulls out the new history item (item 1), extracts the title and url, and displays it to you. So go to a new website in Safari and it should run. It takes a few seconds to display the dialog.

At this point, now that you have it working just adjust the applescript to do what you want.
NOTE: To turn it off change the launchctl command to “unload”
NOTE: you were right, turning on “private browsing” prevents this from working.

Once again this is great input.

I have now managed to successfully create the Bootle plist file and it is working well with the script.

I am sad to say that now, in its current form, there doesn’t appear to be anything else I need to do to, add or change to make Bootle run at its best.

That said, I did just copy and paste and then edit. So, once I am back from my holiday, which starts tomorrow, I shall go through those last two posts and try to actually understand exactly how it all works. After all, I would genuinely like to know how to create these files from scratch.

Regarding the Private Browsing:

I have known about this since day one of Bootle, but my reason for disregarding it is due to where Bootle is to be used.
I work in an APR in the UK and even though the net is filtered for porn and the like, kids still manage to get to it on our demo machines. I disregard private browsing because the average customer, who types porn into Google, is a youth who, in this case, doesn’t have a Mac at home, so they are unlikely to know about private browsing. In fact, they are so Windows based that when they have finished pissing about in the Finder, renaming and deleting things, they click the little clear rounded rectangle in the top right thinking that will close the window. Naturally all it does is remove the Finder-Candy, so they click it again, apparently thinking that this time it WILL close the window, but it doesn’t, so they click it again and again and again.

So basically, that is my reason for disregarding the private browsing button. Customers are idiots who think typing porn into google is funny.

Thank you both for your input, it is much appreciated, especially Reg’s, I’d still be stuck with a 1,501 word script otherwise. Now, excluding all the comments, it’s 779 words, nearly halved.

As soon as I am back from my hols, I’ll start rolling out Bootle across all of our stores and then I’ll start reading up on launchd.

…suppose I best think of a new project now.

Glad to hear you have it working Nedanator. I think using launchd to run your script will work much better than a folder action. Have fun on your holiday. And I agree about Private Browsing. People are “idiots” so this should be a pretty good solution for you.

Hello.
I just wanted to add that if you use OpenDns as your dns provider they will filter away a lot of porn from your machines, your customers will be able to google it, but won’t be able to open it.

Now a days I think you create a free account with OpenDns in order to be able to use them as your dns provider.

We use OpenDNS already, but we have found that kids still manage to find and display porn, or other such inappropriate content, on the machines. Originally, I started Bootle before we used OpenDNS. We use it mainly because, even if it is blocked, if you search porn into google on one of our machines, we chuck you out. End of.

Before I delve into plists, I have had to make a change to Bootle. I have had to make it so that each machine will only notify us once inside 60 seconds.

The other day, a kid searched a Blacklist term into GoogleMaps for a laugh, then he disappeared. Before we could get to the machine a different customer started using it, he had a look though a few pages of GoogleMaps, which is fine, that’s what they’re there for, to play with. But, every time he loaded a new page, the search term was still there and therefore still activating Bootle. Which was very annoying for us as the alerts kept coming up on our screen.

The part in question is:


(*
		00000000000000000000000000000000000
		NEW ADDITION
		00000000000000000000000000000000000
*)

if isBlack then
		
		set TimeFile to "Macintosh HD:Users:danielbarker:Desktop:" & "TimeLogFile.txt"
		
		
		set ReadTime to (open for access ("/Users/danielbarker/Desktop/TimeLogFile.txt"))
		set txt3 to (read ReadTime for (get eof ReadTime))
		set TimePars to every paragraph of txt3
		set TheTimePar to last item of TimePars
		set theHour to word 1 of TheTimePar
		set theMinutes to word 2 of TheTimePar
		close access ReadTime
		
		set TimeText to return & theTime
		set theNewHour to word 1 of TimeText
		set theNewMinutes to word 2 of TimeText
		
		set datastream to open for access file TimeFile with write permission
		write TimeText to datastream starting at eof
		close access datastream
		
		
		if theNewHour is equal to theHour then
			if theNewMinutes is not equal to theMinutes then
				try
					do shell script "screencapture -tjpg " & quoted form of picPath
					tell application "GrowlHelperApp" (*of machine "eppc://User:Pass@Derby-Counter-Left.local"*)
						set appName to "Bootle26"
						set notificationName to "Bootle26"
						set notifs to {notificationName}
						register as application appName all notifications notifs default notifications notifs
						notify with name notificationName title ("Blacklist Warning!" & return & MacName) description "Catalyst: " & N & return & theURL & return & "At " & theTime application name appName icon of application "BootleAppIcon" priority 0 with sticky
					end tell
				on error
					set datastream to open for access file error_file with write permission
					write ErrorLogText2_4 to datastream starting at eof
					close access datastream
				end try
				
				
			else
				
			end if
		else
			try
				do shell script "screencapture -tjpg " & quoted form of picPath
				tell application "GrowlHelperApp" (*of machine "eppc://User:Pass@Derby-Counter-Left.local"*)
					set appName to "Bootle26"
					set notificationName to "Bootle26"
					set notifs to {notificationName}
					register as application appName all notifications notifs default notifications notifs
					notify with name notificationName title ("Blacklist Warning!" & return & MacName) description "Catalyst: " & N & return & theURL & return & "At " & theTime application name appName icon of application "BootleAppIcon" priority 0 with sticky
				end tell
			on error
				set datastream to open for access file error_file with write permission
				write ErrorLogText2_4 to datastream starting at eof
				close access datastream
			end try
			
		end if
		
		
	else
		do shell script "screencapture -tjpg " & quoted form of picPath
		tell application "GrowlHelperApp" (*of machine "eppc://User:Pass@Derby-Counter-Left.local"*)
			set appName to "Bootle26"
			set notificationName to "Bootle26"
			set notifs to {notificationName}
			register as application appName all notifications notifs default notifications notifs
			notify with name notificationName title ("RED FLAG!" & return & MacName) description "The User was logged out!" & return & "At " & theTime application name appName icon of application "BootleAppIcon" priority 2 with sticky
		end tell
		delay 1
		(* DON'T FORGET TO REMOVE THE QUOTES ROUND THE BELOW COMMAND!! *)
		--do shell script "'/System/Library/CoreServices/Menu Extras/User.menu/Contents/Resources/CGSession' -suspend"
		
		
	end if

Previously I did’t have the if isBlack at the top, which meant that anybody who searched a Blacklist term, then within 60 seconds searched a RedFlag list term, Bootle would just ignore it.
Also, I didn’t have the bit about the Hours. While incredibly unlikely, it would still be possible for someone to activate Bootle at 11:46:02 AM. Then be activated again, by someone else at 16:46:45 PM. If that rather unlikely, but still altogether possible event were to occur, Bootle would ignore it again.

I’m thinking that, perhaps instead of this, maybe checking to see if the catalyst is the same as the last one, and if it is, then checking the time. Could that be a slightly better solution?

You want a method such that you won’t get hits within 60 seconds the same word? You wouldn’t need to have a log file to check the times. You can take advantage of “properties”. Properties remember their values between script launches so you could store simple stuff in properties instead of reading/writing to a log file. Here’s what I’m thinking. In your script you have this part which sends a hit to the subroutine…

if this_item_word is in RedWords then
	my keyWords(this_item_word, false)
else if this_item_word is in blackWords then
	my keyWords(this_item_word, true)
end if

So in there is where you detect a hit on a word so in there you could do a quick check. At the top of your script we’ll add these properties to keep track of that last black word and time… same for red.

property lastBlacklistHit : missing value
property lastBlacklistTime : missing value
property lastRedlistHit : missing value
property lastRedlistTime : missing value

Then change that section of code to this and that should fix your problem…

if this_item_word is in RedWords then
	set currentDate to current date
	if this_item_word is lastRedlistHit then
		if currentDate - lastRedlistTime is less than 60 then return
		set lastRedlistTime to currentDate
	else
		set lastRedlistHit to this_item_word
		set lastRedlistTime to currentDate
	end if
	
	my keyWords(this_item_word, false)
else if this_item_word is in blackWords then
	-- do the same for black-list words
	my keyWords(this_item_word, true)
end if

Sorry about the late reply, been busy with the new iMacs, Magic TrackPad etc etc. Sometimes I wish Apple wouldn’t release new products.

So, here are the changes for the current version.

At the top:


set SBootleVersion to "v2.4.1"
set LBootleVersion to "Bootle26 " & SBootleVersion

property lastBlacklistHit : missing value
property lastBlacklistTime : missing value
property lastRedlistHit : missing value
property lastRedlistTime : missing value


Changes here add the Bootle version number. I was having a problem with a few of the machines. I reasoned that it would help if I knew for sure that it was the latest version of B26 that was running, and not old version that I had missed. Now the version number is added to all the log files and the growls.

The property section is there after Reg’s last reply.

Section 2:


set LongWords to thisTitle & " " & thisURL

This is here due to a change in the way, specifically, Google works. They no longer seem to list the full URL in the plist. I will investigate this further at a later date, for the time being, this is the workaround.


tell application "Finder" to set this_item to words of LongWords
repeat with z from 1 to number of items in this_item
	set this_item_word to item z of this_item
	if this_item_word is in RedWords then
		set currentDate to current date
		if this_item_word is lastRedlistHit then
			if currentDate - lastRedlistTime is less than 60 then return
			set lastRedlistTime to currentDate
		else
			set lastRedlistHit to this_item_word
			set lastRedlistTime to currentDate
		end if
		
		my keyWords(this_item_word, false, thisTitle, thisURL, lastVisit, visCount, LBootleVersion, SBootleVersion)
	else if this_item_word is in blackWords then
		set currentDate to current date
		if this_item_word is lastBlacklistHit then
			if currentDate - lastBlacklistTime is less than 60 then return
			set lastBlacklistTime to currentDate
		else
			set lastBlacklistHit to this_item_word
			set lastBlacklistTime to currentDate
		end if
		my keyWords(this_item_word, true, thisTitle, thisURL, lastVisit, visCount, LBootleVersion, SBootleVersion)
	end if
end repeat


Here more of Reg’s last reply is added. Also I have added a whole load of new information into the SubRoutine from the plist. This way makes sure that as much data as possible is logged. Previously, if Safari had changed or quite since the original page, much data was lost.



set LogFileText to return & return & "000000000000000000" & return & "Blacklist Warning!" & return & "User of " & MacName & " requests: " & return & theURL & return & "Title: " & SafTitle & return & "Catalyst: " & N & return & "Last Visited Date: " & laV & return & "Visit Count: " & visC & return & dateandtime & return & LBv & return & "000000000000000000"


I have changed the way the log files look. This way is easier to read when the log files start to get quite long.

I’m starting to get the hang of the launchd stuff now, but it does need more research.

Another reason my reply is so late is because I have been creating a website for it. Once Bootle is complete, I need a way to get the updates out there, so, naturally, a site is the best way. It doesn’t look very nice, but I just wanted to make sure all the required stuff was up there first. And I’ve credited you both, as if it wasn’t for you both, I’d still be struggling slowly along.

Here’s the link:
http://www.bootle26.co.uk/ReplyLink/macscripter.html