This script creates Smart Groups for group childrens’ URL (sub)domains.
Usage
Select some groups (in the item list) or a database’s root (in the navigation sidebar).
Note
If property useSubdomain
is set to true
and there’s e.g.
- a record with domain
substack.com
that got no subdomain - a record with subdomain
edwardsnowden.substack.com
then two Smart Groups are created:
-
url:substack.com url:!~edwardsnowden.substack.com
(i.e. excluding the subdomain) url:edwardsnowden.substack.com
This means if you add new records the first Smart Group’s query may match records that you don’t want to be matched (because it doesn’t know about the new subdomain that you want it to ignore).
Delete the Smart Groups and run the script again to make sure everything’s up to date.
Properties
-
excludeSubgroups
: search in subgroups. this is ignored if a database’s root is selected -
useSubdomain
: use subdomain for searching, e.g.discourse.devontechnologies.com
instead ofdevontechnologies.com
-
theIgnoredSubdomains
: list of subdomains that should be ignored. order is important -
useOnlySubdomainInName
: omit domain, e.g.discourse.devontechnologies.com
instead ofdevontechnologies.com | discourse.devontechnologies.com
-
theNamePrefix
: add custom prefix, e.g.Domain:
-
useGroupNameInName
: add suffixin: Group XY
-
excludeFromSearch
: exclude Smart Groups from search
-- Create Smart Groups for URL (sub)domains
use AppleScript version "2.4"
use framework "Foundation"
use scripting additions
property excludeSubgroups : false -- this is ignored if a database's root is selected
property useSubdomain : true -- use subdomain for searching (e.g. "discourse.devontechnologies.com" instead of domain "devontechnologies.com")
property theIgnoredSubdomains : {"www", "de.m", "de", "en.m", "en", "m", "mobile"} -- subdomains that should be ignored. order is important
property useOnlySubdomainInName : false -- omit domain (e.g. ""discourse.devontechnologies.com" instead of "devontechnologies.com | discourse.devontechnologies.com")
property theNamePrefix : "" -- add custom prefix (e.g. "Domain: ")
property useGroupNameInName : false -- add suffix "in: " plus selected group's name
property excludeFromSearch : false -- exclude Smart Groups from search
tell application id "DNtp"
try
set theRecords to selected records whose type = group
set theErrorMessage to "Please select the database's root in the sidebar." & linefeed & "Or select some groups in the item list."
if theRecords ≠ {} then
show progress indicator "Creating Smart Groups ... " steps ((count of theRecords) * 3) with cancel button
repeat with thisRecord in theRecords
step progress indicator "... " & (name of thisRecord)
set theTempSmartGroup to create record with {name:"temp", type:smart group, search predicates:"url!=", search group:thisRecord, exclude subgroups:excludeSubgroups, exclude from search:true} in thisRecord
set theURLs to URL of children of theTempSmartGroup
delete record theTempSmartGroup
set theSmartGroupProperties to my createSmartGroupProperties(theURLs)
step progress indicator "... " & (name of thisRecord)
my createSmartGroup(theSmartGroupProperties, thisRecord, excludeSubgroups)
step progress indicator "... " & (name of thisRecord)
end repeat
else
show progress indicator "Creating Smart Group ... " steps 3 with cancel button
if not (exists think window 1) then error theErrorMessage
set theRecord to root of think window 1
if theRecord ≠ root of current database then error theErrorMessage
set theURLs to URL of children of theRecord whose URL ≠ ""
step progress indicator "Getting properties ... "
set theSmartGroupProperties to my createSmartGroupProperties(theURLs)
step progress indicator "Creating Smart Group ... "
my createSmartGroup(theSmartGroupProperties, theRecord, true)
step progress indicator "Creating Smart Group ... "
end if
hide progress indicator
on error error_message number error_number
hide progress indicator
if the error_number is not -128 then display alert "DEVONthink" message error_message as warning
return
end try
end tell
on createSmartGroupProperties(theURLs)
try
set theDomains to {}
set theDomainsAndSubdomains to {}
repeat with thisURL in theURLs
set thisURL to thisURL as string
if thisURL does not contain "@" then
set {thisDomain, thisDomainAndSubdomain} to my getDomainAndDomainWithSubdomains(thisURL)
if {thisDomain, thisDomainAndSubdomain} ≠ {missing value, missing value} then
if not useSubdomain then
if thisDomain is not in theDomains then set end of theDomains to thisDomain
else
if thisDomainAndSubdomain is not in theDomainsAndSubdomains then
set thisDomainsAndSubdomains_modified to thisDomainAndSubdomain
repeat with thisIgnoredSubdomain in theIgnoredSubdomains
set thisIgnoredSubdomain to thisIgnoredSubdomain as string
if thisDomainsAndSubdomains_modified starts with (thisIgnoredSubdomain & ".") then
set thisDomainsAndSubdomains_modified to (characters ((length of thisIgnoredSubdomain) + 2) thru -1 in thisDomainsAndSubdomains_modified) as string
end if
end repeat
if thisDomainsAndSubdomains_modified is not in theDomainsAndSubdomains then
set end of theDomains to thisDomain
set end of theDomainsAndSubdomains to thisDomainsAndSubdomains_modified
end if
end if
end if
end if
end if
end repeat
set theDomainsArray to current application's NSMutableArray's arrayWithArray:theDomains
set theDomainAndSubdomainsArray to current application's NSMutableArray's arrayWithArray:theDomainsAndSubdomains
set theArray to current application's NSMutableArray's new()
if useSubdomain then
try
repeat with i from 0 to ((theDomainAndSubdomainsArray's |count|()) - 1)
set thisDomainAndSubdomain to (theDomainAndSubdomainsArray's objectAtIndex:i)
set thisDomain to (theDomainsArray's objectAtIndex:i)
set thisPredicate_string to ("self ENDSWITH " & quoted form of (thisDomain as string)) & " AND " & ("self != " & quoted form of (thisDomainAndSubdomain as string))
set thisDomain_Exclusions to (theDomainAndSubdomainsArray's filteredArrayUsingPredicate:(current application's NSPredicate's predicateWithFormat:(thisPredicate_string)))
if (thisDomainAndSubdomain's isEqualTo:thisDomain) then
set thisSmartGroup_Predicate_URLExclusions_list to thisDomain_Exclusions as list
set thisSmartGroup_Name to thisDomainAndSubdomain
else
set thisSmartGroup_Predicate_URLExclusions_list to {}
if useOnlySubdomainInName then
set thisSmartGroup_Name to theNamePrefix & (thisDomainAndSubdomain as string)
else
set thisSmartGroup_Name to theNamePrefix & (thisDomain as string) & " | " & (thisDomainAndSubdomain as string)
end if
end if
set thisSmartGroup_Predicate_URL to thisDomainAndSubdomain
set thisSmartGroup_Predicate to "url:" & (thisSmartGroup_Predicate_URL as string)
if thisSmartGroup_Predicate_URLExclusions_list ≠ {} then
if (count of thisSmartGroup_Predicate_URLExclusions_list) = 1 then
set thisSmartGroup_Predicate to thisSmartGroup_Predicate & space & "url:!~" & item 1 of thisSmartGroup_Predicate_URLExclusions_list
else
set thisSmartGroup_Predicate to thisSmartGroup_Predicate & space & "{url:!~" & my tid(thisSmartGroup_Predicate_URLExclusions_list, " url:!~") & "}"
end if
end if
(theArray's addObject:{|SmartGroup_Name|:thisSmartGroup_Name, |SmartGroup_Predicate|:thisSmartGroup_Predicate, |SmartGroup_Predicate_URL|:thisSmartGroup_Predicate_URL, |SmartGroup_Predicate_URLExclusions|:thisSmartGroup_Predicate_URLExclusions_list})
end repeat
end try
else
repeat with i from 0 to ((theDomainsArray's |count|()) - 1)
set thisDomain to (theDomainsArray's objectAtIndex:i)
set thisSmartGroup_Name to theNamePrefix & (thisDomain as string)
set thisSmartGroup_Predicate_URL to thisDomain
set thisSmartGroup_Predicate to "url:" & (thisSmartGroup_Predicate_URL as string)
(theArray's addObject:{|SmartGroup_Name|:thisSmartGroup_Name, |SmartGroup_Predicate|:thisSmartGroup_Predicate, |SmartGroup_Predicate_URL|:thisSmartGroup_Predicate_URL, |SmartGroup_Predicate_URLExclusions|:{}})
end repeat
end if
return theArray as list
on error error_message number error_number
activate
if the error_number is not -128 then display alert "Error: Handler \"createSmartGroupProperties\"" message error_message as warning
error number -128
end try
end createSmartGroupProperties
on getDomainAndDomainWithSubdomains(theURL_string) -- based on https://forum.latenightsw.com/t/how-to-extract-the-domain-from-an-url-with-applescriptobjc/3285/5
try
set theURL_DomainWithSubdomains to (DNS form of host of (theURL_string as URL))
set d to AppleScript's text item delimiters
set AppleScript's text item delimiters to "."
set theURL_DomainWithSubdomains_TextItems to text items of theURL_DomainWithSubdomains
set theURL_Domain to theURL_DomainWithSubdomains_TextItems
repeat
tell ("mailto:" & (rest of theURL_Domain)) as URL to tell host & {dotted decimal form:false}
if (dotted decimal form ≠ false) then
set theURL_Domain to rest of theURL_Domain
else
exit repeat
end if
end tell
end repeat
set theURL_Domain to theURL_Domain as string
set AppleScript's text item delimiters to d
return {theURL_Domain, theURL_DomainWithSubdomains}
on error error_message number error_number
return {missing value, missing value}
end try
end getDomainAndDomainWithSubdomains
on createSmartGroup(theSmartGroupProperties, theGroup, excludeSubgroups)
try
tell application id "DNtp"
repeat with theseSmartGroupProperties in theSmartGroupProperties
set thisSmartGroup_Predicate to |SmartGroup_Predicate| of theseSmartGroupProperties
set thisSmartGroup_Name to |SmartGroup_Name| of theseSmartGroupProperties
if not (my existsSmartGroup(thisSmartGroup_Predicate, theGroup)) then
if useGroupNameInName then
set thisSmartGroup_Name to thisSmartGroup_Name & space & "in:" & space & name of theGroup
end if
set theSmartGroup to create record with {name:thisSmartGroup_Name, type:smart group, search predicates:thisSmartGroup_Predicate, search group:theGroup, exclude subgroups:excludeSubgroups, exclude from search:excludeFromSearch} in theGroup
end if
end repeat
end tell
on error error_message number error_number
activate
if the error_number is not -128 then display alert "Error: Handler \"createSmartGroup\"" message error_message as warning
error number -128
end try
end createSmartGroup
on existsSmartGroup(theSmartGroup_Predicate, theGroup)
tell application id "DNtp"
try
set theGroup_Database to database of theGroup
if theGroup = root of theGroup_Database then
set theGroup_LocationAndName to "/"
else
set theGroup_Name to name of theGroup
if theGroup_Name does not contain "/" then
set theGroup_LocationAndName to location of theGroup & theGroup_Name & "/"
else
set theGroup_LocationAndName to location of theGroup & my escapeSlash(theGroup_Name) & "/"
end if
end if
set theResults to (smart groups of theGroup_Database whose search predicates = theSmartGroup_Predicate and search group = theGroup and location = theGroup_LocationAndName)
if theResults = {} then
return false
else
return true
end if
on error error_message number error_number
activate
if the error_number is not -128 then display alert "Error: Handler \"existsSmartGroup\"" message error_message as warning
error number -128
end try
end tell
end existsSmartGroup
on escapeSlash(theText)
set d to AppleScript's text item delimiters
set AppleScript's text item delimiters to "/"
set theTextItems to every text item of theText
set AppleScript's text item delimiters to "\\/"
set theText_escaped to theTextItems as string
set AppleScript's text item delimiters to d
return theText_escaped
end escapeSlash
on tid(theInput, theDelimiter)
set d to AppleScript's text item delimiters
set AppleScript's text item delimiters to theDelimiter
if class of theInput = text then
set theOutput to text items of theInput
else if class of theInput = list then
set theOutput to theInput as text
end if
set AppleScript's text item delimiters to d
return theOutput
end tid