URL: Fetch Requests and Responses
Omni Automation supports calls to network resources that require authentication or use a RESTful API (Representational State Transfer).
There are two shared classes for enabling network retrieval and setting of data using the fetch() function of the URL class:
URL.FetchRequest
URL.FetchResponse
URL.FetchRequest represents a request for a URL resource, providing additional controls for the request (such as the HTTP method, headers, and cache controls) and uses a Promise-based API for actually performing the request and receiving a detailed response (which includes the HTTP status code and headers along with the body of the result—see URL.FetchResponse for more detail).
URL.FetchRequest
Constructor
new URL.FetchRequest() → (URL.FetchRequest) • Creates a new instance.
Class Functions
fromString(string: String) → (URL.FetchRequest or null) • Parses the string as a URL if possible and returns a new instance, or null otherwise.
Instance Functions
fetch() → (Promise) • Perform the request, returning a Promise. On success, the promise will resolve to a URL.FetchResponse.
Instance Properties
allowsConstrainedNetworkAccess (Boolean) • Whether connections may use the network when the user has specified Low Data Mode.
allowsExpensiveNetworkAccess (Boolean) • Whether connections may use a network interface that the system considers expensive.
bodyData (Data or null) • The body of the request, typically used in an HTTP POST or PUT request. This API is suitable for uploading binary data, or for text which needs to be encoded in a form other than UTF–8. If UTF–8 text is suitable, use of the bodyString property is likely to be a better choice.
bodyString (String or null) • The body of the request, typically used in an HTTP POST or PUT request. The provided string will be transmitted using the UTF–8 encoding.
cache (String or null) • The cache policy for the request: default, no-store, reload, no-cache, force-cache, or only-if-cached.
headers (Object) • Custom HTTP headers to be sent with this request. Wikipedia: List of HTTP header fields
httpShouldHandleCookies (Boolean) • Whether to automatically handle cookies.
httpShouldUsePipelining (Boolean) • Whether to transmit data before receiving a response from an earlier request.
method (String or null) • The HTTP request method of the request: GET, POST, PUT, DELETE, etc.
url (URL or null) • The URL for this fetch request. Much of the additional functionality provided by the fetch request API will only work with http and https URLs. (For example, the method and cache and headers don’t have any effect in the context of a file or omnifocus URL.)
Request Methods
From the Wikipedia documentation:
GET • The GET method requests a representation of the specified resource. Requests using GET should only retrieve data and should have no other effect.
POST • The POST method requests that the server accept the entity enclosed in the request as a new subordinate of the web resource identified by the URI. The data POSTed might be, for example, an annotation for existing resources; a message for a bulletin board, newsgroup, mailing list, or comment thread; a block of data that is the result of submitting a web form to a data-handling process; or an item to add to a database.
PUT • The PUT method requests that the enclosed entity be stored under the supplied URI. If the URI refers to an already existing resource, it is modified; if the URI does not point to an existing resource, then the server can create the resource with that URI.
DELETE • The DELETE method deletes the specified resource.
URL.FetchResponse
URL.FetchResponse represents the response from fetching a URL resource, providing additional information about the response such as the HTTP status code and headers along with the actual data for that response. This is a read-only object returned by performing a URL.FetchRequest; see that class for more details on actually performing the request.
Instance Properties
bodyData (Data or null r/o) • Returns the raw HTTP body data from this response.
bodyString (String or null r/o) • This is a convenience wrapper which interprets the bodyData of this response as UTF–8 text. (Note: the current implementation assumes the text is encoded using UTF–8, but ideally it would honor the text encoding as reported by textEncodingName.)
headers (Object r/o) • Returns the HTTP header fields for this response. Wikipedia: List of HTTP header fields
mimeType (String or null r/o) • Returns the HTTP MIME type for this response (e.g. text/plain, application/json, etc.).
statusCode (Number r/o) • Returns the HTTP status code for this response (e.g. 200, 404, etc.). Wikipedia: List of HTTP Status Codes
textEncodingName (String or null r/o) • Returns the reported text encoding for this response. This name will be the actual string reported by the origin source, or null if no encoding was specified.
url (URL or null r/o) • Returns the URL for this response.
Example: HTTP GET with Basic Authentication
In the following Omni Automation example, a GET method is used with the fetch() function to retrieve the contents of a password-protected JSON file on a server.
For this example, the targeted file is hosted on this website:
https://omni-automation.com/secure/conference-sessions.json
If the link is activated, a web browser log-in dialog similar to the following will be presented:
The following example script, written as an Omni Automation plug-in, will prompt for required security credentials by displaying three text input fields in the host Omni app for accessing the required input from the user:
- URL of the file to be retrieved
- The User/Account Name
- The User/Account Password
The provided data will be incorporated into a URL.FetchRequest that authenticates the connection and returns a URL.FetchResponse containing the contents of the protected JSON file hosted on the server.
Authentication Protocols
Here are two common security protocols used with HTTP requests:
Authentication via HTTP Basic Auth
HTTP Basic Auth is a simple method that creates a username and password style authentication for HTTP requests. This technique uses a header with a key titled “Authorization” whose corresponding value contains a base64 encoded representation of the username and password.
To perform a standard simple name/password authorization for the protected URL, the request header object will contain the provided authentication credentials as a single colon-character delineated string <name>:<password> that is base64 encoded by the script and preceded with the word: “Basic”
{"Authorization": "Basic b3R0by1hdXRvbWF0b3I6b21uaS1hdXRvbWF0aW9u"}
If the target URL uses the HTTPS protocol then the name/password will be transmitted securely to the server. Contents of a request sent using the unencrypted HTTP protocol is viewable during transmission.
Creating Encoded Credentials String
var data = Data.fromString(accountName + ":" + accountPassword)
var credentials = data.toBase64()
Authentication via OAuth
OAuth is an open standard for access delegation, commonly used as a way for Internet users to grant websites or applications access to their information on other websites but without giving them the passwords.
When incorporating OAuth in your scripts, the value of the “Authorization” HTTP header uses base64-encoded password tokens rather than user/password scheme found in the Basic HTTP authorization:
{"Authorization": "Bearer VGhpcyBpcyB5b3VyIE9BdXRoIG5hbWUgYW5kIHRva2Vu"}
The tutorialspoint.com website offers thorough documentation of this advanced topic.
IMPORTANT: Access to a relevant subset of Apple’s CryptoKit frameworks is provided in Omni Automation through the Crypto class.
Storing Credentials and Tokens
Use the Credentials class to store credentials and tokens securely in the system Keychain. See the Credentials documentation on this website.
The Example Plug-In
The following example plug-in works with all Omni apps, and uses the standard basic HTTP authentication protocol to access the contents of a JSON file hosted on this website.
NOTE: For the purposes of demonstration, valid values will be provided as the default values for the input fields. Change these to access other servers/files to which you have access. TIP: You can replace the default values in the plug-in with null values to display empty fields by default.
NOTE: For the purposes of illustration, this example logs many statements to the console. Feel free to edit or remove such statements.
Fetch GET with Password
/*{
"type": "action",
"targets": ["omnigraffle","omnifocus","omniplan","omnioutliner"],
"author": "Otto Automator",
"identifier": "com.omni-automation.all.fetch-get-with-password",
"version": "1.1",
"description": "This plug-in uses the URL.FetchRequest and URL.FetchResponse classes to access the contents of a password-protected file on a server.",
"label": "Fetch GET with Password",
"shortLabel": "GET with Password",
"paletteLabel": "GET with Password",
"image": "arrow.up.square.fill"
}*/
(() => {
const action = new PlugIn.Action(async function(selection, sender){
URLhtml = "https://omni-automation.com/secure/index.html"
URLjson = "https://omni-automation.com/secure/conference-sessions.json"
// CREATE FORM FOR GATHERING USER INPUT
inputForm = new Form()
// CREATE TEXT FIELD
urlField = new Form.Field.String(
"targetURL",
"URL",
URLjson //replace with null for empty field
)
nameField = new Form.Field.String(
"accountName",
"Name",
"otto-automator" //replace with null for empty field
)
passwordField = new Form.Field.String(
"accountPassword",
"Password",
"omni-automation" //replace with null for empty field
)
// ADD THE FIELDS TO THE FORM
inputForm.addField(urlField)
inputForm.addField(nameField)
inputForm.addField(passwordField)
// VALIDATE THE USER INPUT
inputForm.validate = function(formObject){
targetURL = formObject.values["targetURL"]
schema = ["http://","https://"]
var urlStatus = false
if (targetURL){
var i;
for (i = 0; i < schema.length; i++) {
if (
targetURL.startsWith(schema[i], 0) &&
URL.fromString(targetURL)
){
urlStatus = true
}
}
}
accountName = formObject.values["accountName"]
nameStatus = (accountName && accountName.length > 0) ? true:false
accountPassword = formObject.values["accountPassword"]
passwordStatus = (accountPassword && accountPassword.length > 0) ? true:false
validation = (urlStatus && nameStatus && passwordStatus) ? true:false
return validation
}
// PRESENT THE FORM TO THE USER
formPrompt = "Enter URL, account name, and password:"
formObject = await inputForm.show(formPrompt,"Continue")
// PROCESSING USING THE DATA EXTRACTED FROM THE FORM
console.clear()
// CONSTRUCT CREDENTIALS
accountName = formObject.values["accountName"]
console.log(`DEBUG: accountName: ${accountName}`)
accountPassword = formObject.values["accountPassword"]
console.log(`DEBUG: accountPassword: ${accountPassword}`)
data = Data.fromString(accountName + ":" + accountPassword)
credentials = data.toBase64()
console.log(`DEBUG: credentials: ${credentials}`)
// CONSTRUCT REQUEST
targetURL = formObject.values["targetURL"]
request = URL.FetchRequest.fromString(targetURL)
console.log(`DEBUG: request.url = ${request.url}`)
request.method = 'GET'
request.cache = "no-cache"
console.log(`DEBUG: request.method = ${request.method}`)
request.headers = {"Authorization": "Basic" + " " + credentials}
console.log(`DEBUG: request.headers = ${JSON.stringify(request.headers)}`)
response = await request.fetch()
console.log(`DEBUG: response=${response}`)
console.log(`DEBUG: response.statusCode=${response.statusCode}`)
console.log(`DEBUG: response.url=${response.url}`)
console.log(`DEBUG: response.mimeType=${response.mimeType}`)
console.log(`DEBUG: response.headers=${JSON.stringify(response.headers)}`)
console.log(`DEBUG: response.bodyString=${response.bodyString}`)
});
action.validate = function(selection, sender){
return true
};
return action;
})();
The console window will display the results of the plug-in execution:
Example: HTTP POST without Authentication
In the following Omni Automation example, a POST method is used with the fetch() function to retrieve a chart generated by the QuickChart website (API documentation).
HTTP POST Example
(async () => {
try {
chartData = {
"backgroundColor": "transparent",
"width": 500,
"height": 300,
"format": "png",
"chart": {
"type": "bar",
"data": {
"labels": ["2012","2013","2014","2015","2016"],
"datasets": [
{
"label": "Users",
"data": [120,60,50,180,120]
}
]
}
}
}
targetURLString = "https://quickchart.io/chart"
request = URL.FetchRequest.fromString(targetURLString)
request.headers = {"Content-Type":"application/json"}
request.method = 'POST'
request.bodyString = JSON.stringify(chartData)
response = await request.fetch()
responseCode = response.statusCode
if (responseCode >= 200 && responseCode < 300){
// Place the returned image on the Pasteboard (clipboard)
var item = new Pasteboard.Item()
item.setDataForType(response.bodyData, TypeIdentifier.png)
Pasteboard.general.items = [item]
new Alert(String(responseCode), "The chart image is on the clipboard.").show()
} else {
new Alert(String(responseCode), "An error occurred.").show()
console.error(JSON.stringify(response.headers))
}
}
catch(err){
new Alert(err.name, err.message).show()
}
})();
The resulting chart image is placed on the Pasteboard (clipboard):
Example: HTTP (GET) Jira Query
An example script showing a basic query using Jira Query Language (JQL) for a specified account:
Example Authenticated Jira Query
var jiraBaseURL = "https://my_jira_url"
var jiraParameters = "/rest/api/2/search?jql=assignee=currentuser()…etc"
var jiraURLString = jiraBaseURL + jiraParameters
var accountName = "ACCOUNT-NAME"
var accountPassword = "ACCOUNT-PASSWORD"
var data = Data.fromString(accountName + ":" + accountPassword)
var credentials = data.toBase64()
var request = URL.FetchRequest.fromString(jiraURLString)
request.method = 'GET'
request.cache = "no-cache"
request.headers = {"Authorization": "Basic" + " " + credentials}
var requestPromise = request.fetch()
requestPromise.then(response => {
var responseCode = response.statusCode
if (responseCode >= 200 && responseCode < 300){
if(response.mimeType == "application/json"){
var responseJSON = JSON.parse(response.bodyString)
// processing statements
} else {
console.error("INCORRECT MIMETYPE:", response.mimeType)
}
} else {
new Alert(String(responseCode), "An error occurred.").show()
console.error(JSON.stringify(response.headers))
}
})
Template: fetch() GET with Authentication Interface
A plug-in template for performing a fetch() GET with authentication interface:
The following plug-in template performs an authenticated GET using await statements within an asynchronous wrapper rather than explicit Promise blocks.
Authenticated GET (asynchronous)
/*{
"type": "action",
"targets": ["omnigraffle","omnifocus","omniplan","omnioutliner"],
"author": "Otto Automator",
"identifier": "com.omni-automation.all.fetch-get-with-password",
"version": "1.0",
"description": "This plug-in uses the URL.FetchRequest and URL.FetchResponse classes to access the contents of a password-protected file on a server.",
"label": "Fetch GET with Password",
"shortLabel": "GET with Password"
"paletteLabel": "Authenticated GET",
"image" : "square.and.arrow.down.fill"
}*/
(() => {
const action = new PlugIn.Action(async function(selection, sender){
try {
// CREATE FORM FOR GATHERING USER INPUT
inputForm = new Form()
// CREATE TEXT FIELDS
urlField = new Form.Field.String(
"targetURL",
"URL",
null
)
nameField = new Form.Field.String(
"accountName",
"Name",
null
)
passwordField = new Form.Field.String(
"accountPassword",
"Password",
null
)
// ADD THE FIELDS TO THE FORM
inputForm.addField(urlField)
inputForm.addField(nameField)
inputForm.addField(passwordField)
// VALIDATE THE USER INPUT
inputForm.validate = function(formObject){
targetURL = formObject.values["targetURL"]
schema = ["http://","https://"]
urlStatus = false
if (targetURL){
var i;
for (i = 0; i < schema.length; i++) {
if (
targetURL.startsWith(schema[i], 0) &&
URL.fromString(targetURL)
){
var urlStatus = true
}
}
}
accountName = formObject.values["accountName"]
nameStatus = (accountName && accountName.length > 0) ? true:false
accountPassword = formObject.values["accountPassword"]
passwordStatus = (accountPassword && accountPassword.length > 0) ? true:false
validation = (urlStatus && nameStatus && passwordStatus) ? true:false
return validation
}
// PRESENT THE FORM TO THE USER
formPrompt = "Enter URL, account name, and password:"
formObject = await inputForm.show(formPrompt,"Continue")
// PROCESSING USING THE DATA EXTRACTED FROM THE FORM
// CONSTRUCT CREDENTIALS
accountName = formObject.values["accountName"]
accountPassword = formObject.values["accountPassword"]
data = Data.fromString(accountName + ":" + accountPassword)
credentials = data.toBase64()
// CONSTRUCT REQUEST
targetURL = formObject.values["targetURL"]
request = URL.FetchRequest.fromString(targetURL)
request.method = 'GET'
request.cache = "no-cache"
request.headers = {"Authorization": "Basic" + " " + credentials}
// EXECUTE REQUEST
response = await request.fetch()
// PROCESS REQUEST RESULT
responseCode = response.statusCode
if (responseCode >= 200 && responseCode < 300){
// PROCESSING STATEMENTS GO HERE
} else {
console.error(JSON.stringify(response.headers))
throw {
name: `Error `${responseCode}`,
message: "Error retrieving data from server."
}
}
}
catch(err){
if(!err.causedByUserCancelling){
new Alert(err.name, err.message).show()
}
}
});
action.validate = function(selection, sender){
return true
};
return action;
})();
Template: Authenticated POST (asynchronous)
The following plug-in example, which retrieves the content of a Slack message, performs an authenticated POST using await statements within an asynchronous wrapper rather than explicit Promise blocks:
This example also stores the API key in the plug-in Preferences.
Retrieve Slack Message from Clipboard Link
/*{
"type": "action",
"targets": ["omnifocus", "omnioutliner", "omnigraffle", "omniplan"],
"author": "Otto Automator",
"identifier": "com.omni-automation.all.retrieve-slack-message",
"version": "1.0",
"description": "A plug-in that performs an authenticated POST",
"label": "Retrieve Slack Message",
"shortLabel": "Retrieve Slack Message",
"image": "text.bubble"
}*/
(() => {
var preferences = new Preferences()
const action = new PlugIn.Action(async function(selection, sender){
try {
var SlackAPI
if (app.controlKeyDown){
alertMessage = "Reset the stored value?"
alert = new Alert("Confirmation Required", alertMessage)
alert.addOption("Reset")
alert.addOption("Cancel")
alert.show(buttonIndex => {
if (buttonIndex === 0){
preferences.write("SlackAPI", null)
}
})
}
SlackAPI = preferences.readString("SlackAPI")
console.log("SlackAPI", SlackAPI)
if (SlackAPI === null){
textInputField = new Form.Field.String(
"textInput",
null,
null
)
inputForm = new Form()
inputForm.addField(textInputField)
inputForm.validate = function(formObject){
textValue = formObject.values['textInput']
return (!textValue) ? false:true
}
formPrompt = "Enter the Slack API key:"
buttonTitle = "Continue"
formObject = await inputForm.show(formPrompt, buttonTitle)
textValue = formObject.values['textInput']
preferences.write("SlackAPI", textValue)
SlackAPI = textValue
}
var slackURL = ""
var clipboardStr = ""
var latest = ""
var channel = ""
var comps = null
clipboardStr = Pasteboard.general.string
console.log("CLIP", clipboardStr)
if(!clipboardStr.startsWith("https://")){
throw {
name: "Invalid URL",
message: "The clipboard did not contain a Slack message link."
}
}
if (clipboardStr.includes('thread_ts')){
console.log("THREADED MESSAGE")
comps = URL.Components.fromString(clipboardStr)
try {
qItems = comps.queryItems
}
catch(err){
throw {
name: "Invalid URL",
message: "The clipboard did not contain a Slack message link."
}
}
for (i = 0; i < qItems.length; i++) {
qItem = qItems[i]
if(qItem.name === "thread_ts"){
latest = qItem.value
}
if(qItem.name === "cid"){
channel = qItem.value
}
}
} else {
console.log("NON-THREADED MESSAGE")
comps = URL.Components.fromString(clipboardStr)
pathComps = comps.url.pathComponents
latest = pathComps[3].substring(1)
channel = pathComps[2]
if (latest.indexOf('?') !== -1) latest = latest.substring(0, latest.indexOf('?'))
latest = latest.substring(0, latest.length-6) + '.' + latest.substring(latest.length-6)
}
slackURL = `https://slack.com/api/conversations.history?`
slackURL += `channel=${channel}`
slackURL += `&latest=${latest}`
slackURL += `&inclusive=true`
slackURL += `&limit=1`
console.log(slackURL)
request = URL.FetchRequest.fromString(slackURL)
request.method = 'POST'
request.cache = "no-cache"
authStr = `Bearer ${SlackAPI}`
request.headers = {
"content-type": "application/json",
"Authorization": authStr
}
response = await request.fetch()
responseCode = response.statusCode
if (responseCode >= 200 && responseCode < 300){
responseJSON = JSON.parse(response.bodyString)
retrievedMsgTxt = responseJSON.messages[0].text
console.log("MSG TEXT:", retrievedMsgTxt)
// PROCESSING STATEMENTS GO HERE
} else {
throw {
name: responseCode,
message: "There was an issue retrieving the message."
}
}
}
catch(err){
if(!err.causedByUserCancelling){
new Alert(err.name, err.message).show()
}
}
})
action.validate = function(selection, sender){
return (Pasteboard.general.hasStrings)
};
return action;
})();
Currency Conversion Service (GET with Key Authentication)
A plug-in that integrates with the RapidAPI Currency Converter Service to convert a provided amount from one chosen currency to another.
NOTE: A RapidAPI developer API key is required by the plug-in. Limited-use free keys are available.
NOTE: the resulting formatted value is automatically placed on the clipboard.
To use, copy the plug-in code to a new document to your script editing application, replace the placeholder “API-KEY-STRING-GOES-HERE” with your RapidAPI key, and save to file with one of the following file extensions: omnifocusjs, omnigrafflejs, omnioutlinerjs, omniplanjs, or omnijs. Install the plug-in in the customary manner.
(Codes from Calculator.net)Currency Converter
/*{
"type": "action",
"targets": ["omnifocus","omniplan","omnigraffle","omnioutliner"],
"author": "Otto Automator",
"identifier": "com.omni-automation.all.currency-converter",
"version": "1.1",
"description": "Uses the online RapidAPI service (rapidapi.com) to convert the provided whole amount from the provided currency to the chosen currency. REQUIRES API KEY.",
"label": "Currency Converter",
"shortLabel": "Currency Converter",
"paletteLabel": "Currency Converter",
"image": "coloncurrencysign.arrow.circlepath"
}*/
(() => {
const action = new PlugIn.Action(async function(selection, sender){
try {
currencyCodes = ["AED", "AFN", "ALL", "AMD", "ANG", "AOA", "ARS", "AUD", "AWG", "AZN", "BAM", "BBD", "BDT", "BGN", "BHD", "BIF", "BMD", "BND", "BOB", "BRL", "BSD", "BTC", "BTN", "BWP", "BYN", "BZD", "CAD", "CDF", "CHF", "CLF", "CLP", "CNH", "CNY", "COP", "CRC", "CUC", "CUP", "CVE", "CZK", "DJF", "DKK", "DOP", "DZD", "EGP", "ERN", "ETB", "EUR", "FJD", "FKP", "GBP", "GEL", "GGP", "GHS", "GIP", "GMD", "GNF", "GTQ", "GYD", "HKD", "HNL", "HRK", "HTG", "HUF", "IDR", "ILS", "IMP", "INR", "IQD", "IRR", "ISK", "JEP", "JMD", "JOD", "JPY", "KES", "KGS", "KHR", "KMF", "KPW", "KRW", "KWD", "KYD", "KZT", "LAK", "LBP", "LKR", "LRD", "LSL", "LYD", "MAD", "MDL", "MGA", "MKD", "MMK", "MNT", "MOP", "MRO", "MRU", "MUR", "MVR", "MWK", "MXN", "MYR", "MZN", "NAD", "NGN", "NIO", "NOK", "NPR", "NZD", "OMR", "PAB", "PEN", "PGK", "PHP", "PKR", "PLN", "PYG", "QAR", "RON", "RSD", "RUB", "RWF", "SAR", "SBD", "SCR", "SDG", "SEK", "SGD", "SHP", "SLL", "SOS", "SRD", "SSP", "STD", "STN", "SVC", "SYP", "SZL", "THB", "TJS", "TMT", "TND", "TOP", "TRY", "TTD", "TWD", "TZS", "UAH", "UGX", "USD", "UYU", "UZS", "VEF", "VES", "VND", "VUV", "WST", "XAF", "XAG", "XAU", "XCD", "XDR", "XOF", "XPD", "XPF", "XPT", "YER", "ZAR", "ZMW", "ZWL"]
currencyStrings = ["United Arab Emirates Dirham", "Afghan Afghani", "Albanian Lek", "Armenian Dram", "Netherlands Antillean Guilder", "Angolan Kwanza", "Argentine Peso", "Australian Dollar", "Aruban Florin", "Azerbaijani Manat", "Bosnia-Herzegovina Convertible Mark", "Barbadian Dollar", "Bangladeshi Taka", "Bulgarian Lev", "Bahraini Dinar", "Burundian Franc", "Bermudan Dollar", "Brunei Dollar", "Bolivian Boliviano", "Brazilian Real", "Bahamian Dollar", "Bitcoin", "Bhutanese Ngultrum", "Botswanan Pula", "Belarusian Ruble", "Belize Dollar", "Canadian Dollar", "Congolese Franc", "Swiss Franc", "Chilean Unit of Account (UF)", "Chilean Peso", "Chinese Yuan (Offshore)", "Chinese Yuan", "Colombian Peso", "Costa Rican Colón", "Cuban Convertible Peso", "Cuban Peso", "Cape Verdean Escudo", "Czech Republic Koruna", "Djiboutian Franc", "Danish Krone", "Dominican Peso", "Algerian Dinar", "Egyptian Pound", "Eritrean Nakfa", "Ethiopian Birr", "Euro", "Fijian Dollar", "Falkland Islands Pound", "British Pound Sterling", "Georgian Lari", "Guernsey Pound", "Ghanaian Cedi", "Gibraltar Pound", "Gambian Dalasi", "Guinean Franc", "Guatemalan Quetzal", "Guyanaese Dollar", "Hong Kong Dollar", "Honduran Lempira", "Croatian Kuna", "Haitian Gourde", "Hungarian Forint", "Indonesian Rupiah", "Israeli New Sheqel", "Manx pound", "Indian Rupee", "Iraqi Dinar", "Iranian Rial", "Icelandic Króna", "Jersey Pound", "Jamaican Dollar", "Jordanian Dinar", "Japanese Yen", "Kenyan Shilling", "Kyrgystani Som", "Cambodian Riel", "Comorian Franc", "North Korean Won", "South Korean Won", "Kuwaiti Dinar", "Cayman Islands Dollar", "Kazakhstani Tenge", "Laotian Kip", "Lebanese Pound", "Sri Lankan Rupee", "Liberian Dollar", "Lesotho Loti", "Libyan Dinar", "Moroccan Dirham", "Moldovan Leu", "Malagasy Ariary", "Macedonian Denar", "Myanma Kyat", "Mongolian Tugrik", "Macanese Pataca", "Mauritanian Ouguiya (pre-2018)", "Mauritanian Ouguiya", "Mauritian Rupee", "Maldivian Rufiyaa", "Malawian Kwacha", "Mexican Peso", "Malaysian Ringgit", "Mozambican Metical", "Namibian Dollar", "Nigerian Naira", "Nicaraguan Córdoba", "Norwegian Krone", "Nepalese Rupee", "New Zealand Dollar", "Omani Rial", "Panamanian Balboa", "Peruvian Nuevo Sol", "Papua New Guinean Kina", "Philippine Peso", "Pakistani Rupee", "Polish Zloty", "Paraguayan Guarani", "Qatari Rial", "Romanian Leu", "Serbian Dinar", "Russian Ruble", "Rwandan Franc", "Saudi Riyal", "Solomon Islands Dollar", "Seychellois Rupee", "Sudanese Pound", "Swedish Krona", "Singapore Dollar", "Saint Helena Pound", "Sierra Leonean Leone", "Somali Shilling", "Surinamese Dollar", "South Sudanese Pound", "São Tomé and Príncipe Dobra (pre-2018)", "São Tomé and Príncipe Dobra", "Salvadoran Colón", "Syrian Pound", "Swazi Lilangeni", "Thai Baht", "Tajikistani Somoni", "Turkmenistani Manat", "Tunisian Dinar", "Tongan Pa anga", "Turkish Lira", "Trinidad and Tobago Dollar", "New Taiwan Dollar", "Tanzanian Shilling", "Ukrainian Hryvnia", "Ugandan Shilling", "United States Dollar", "Uruguayan Peso", "Uzbekistan Som", "Venezuelan Bolívar Fuerte (Old)", "Venezuelan Bolívar Soberano", "Vietnamese Dong", "Vanuatu Vatu", "Samoan Tala", "CFA Franc BEAC", "Silver Ounce", "Gold Ounce", "East Caribbean Dollar", "Special Drawing Rights", "CFA Franc BCEAO", "Palladium Ounce", "CFP Franc", "Platinum Ounce", "Yemeni Rial", "South African Rand", "Zambian Kwacha", "Zimbabwean Dollar"]
menuStrings = ["AED · United Arab Emirates Dirham", "AFN · Afghan Afghani", "ALL · Albanian Lek", "AMD · Armenian Dram", "ANG · Netherlands Antillean Guilder", "AOA · Angolan Kwanza", "ARS · Argentine Peso", "AUD · Australian Dollar", "AWG · Aruban Florin", "AZN · Azerbaijani Manat", "BAM · Bosnia-Herzegovina Convertible Mark", "BBD · Barbadian Dollar", "BDT · Bangladeshi Taka", "BGN · Bulgarian Lev", "BHD · Bahraini Dinar", "BIF · Burundian Franc", "BMD · Bermudan Dollar", "BND · Brunei Dollar", "BOB · Bolivian Boliviano", "BRL · Brazilian Real", "BSD · Bahamian Dollar", "BTC · Bitcoin", "BTN · Bhutanese Ngultrum", "BWP · Botswanan Pula", "BYN · Belarusian Ruble", "BZD · Belize Dollar", "CAD · Canadian Dollar", "CDF · Congolese Franc", "CHF · Swiss Franc", "CLF · Chilean Unit of Account (UF)", "CLP · Chilean Peso", "CNH · Chinese Yuan (Offshore)", "CNY · Chinese Yuan", "COP · Colombian Peso", "CRC · Costa Rican Colón", "CUC · Cuban Convertible Peso", "CUP · Cuban Peso", "CVE · Cape Verdean Escudo", "CZK · Czech Republic Koruna", "DJF · Djiboutian Franc", "DKK · Danish Krone", "DOP · Dominican Peso", "DZD · Algerian Dinar", "EGP · Egyptian Pound", "ERN · Eritrean Nakfa", "ETB · Ethiopian Birr", "EUR · Euro", "FJD · Fijian Dollar", "FKP · Falkland Islands Pound", "GBP · British Pound Sterling", "GEL · Georgian Lari", "GGP · Guernsey Pound", "GHS · Ghanaian Cedi", "GIP · Gibraltar Pound", "GMD · Gambian Dalasi", "GNF · Guinean Franc", "GTQ · Guatemalan Quetzal", "GYD · Guyanaese Dollar", "HKD · Hong Kong Dollar", "HNL · Honduran Lempira", "HRK · Croatian Kuna", "HTG · Haitian Gourde", "HUF · Hungarian Forint", "IDR · Indonesian Rupiah", "ILS · Israeli New Sheqel", "IMP · Manx pound", "INR · Indian Rupee", "IQD · Iraqi Dinar", "IRR · Iranian Rial", "ISK · Icelandic Króna", "JEP · Jersey Pound", "JMD · Jamaican Dollar", "JOD · Jordanian Dinar", "JPY · Japanese Yen", "KES · Kenyan Shilling", "KGS · Kyrgystani Som", "KHR · Cambodian Riel", "KMF · Comorian Franc", "KPW · North Korean Won", "KRW · South Korean Won", "KWD · Kuwaiti Dinar", "KYD · Cayman Islands Dollar", "KZT · Kazakhstani Tenge", "LAK · Laotian Kip", "LBP · Lebanese Pound", "LKR · Sri Lankan Rupee", "LRD · Liberian Dollar", "LSL · Lesotho Loti", "LYD · Libyan Dinar", "MAD · Moroccan Dirham", "MDL · Moldovan Leu", "MGA · Malagasy Ariary", "MKD · Macedonian Denar", "MMK · Myanma Kyat", "MNT · Mongolian Tugrik", "MOP · Macanese Pataca", "MRO · Mauritanian Ouguiya (pre-2018)", "MRU · Mauritanian Ouguiya", "MUR · Mauritian Rupee", "MVR · Maldivian Rufiyaa", "MWK · Malawian Kwacha", "MXN · Mexican Peso", "MYR · Malaysian Ringgit", "MZN · Mozambican Metical", "NAD · Namibian Dollar", "NGN · Nigerian Naira", "NIO · Nicaraguan Córdoba", "NOK · Norwegian Krone", "NPR · Nepalese Rupee", "NZD · New Zealand Dollar", "OMR · Omani Rial", "PAB · Panamanian Balboa", "PEN · Peruvian Nuevo Sol", "PGK · Papua New Guinean Kina", "PHP · Philippine Peso", "PKR · Pakistani Rupee", "PLN · Polish Zloty", "PYG · Paraguayan Guarani", "QAR · Qatari Rial", "RON · Romanian Leu", "RSD · Serbian Dinar", "RUB · Russian Ruble", "RWF · Rwandan Franc", "SAR · Saudi Riyal", "SBD · Solomon Islands Dollar", "SCR · Seychellois Rupee", "SDG · Sudanese Pound", "SEK · Swedish Krona", "SGD · Singapore Dollar", "SHP · Saint Helena Pound", "SLL · Sierra Leonean Leone", "SOS · Somali Shilling", "SRD · Surinamese Dollar", "SSP · South Sudanese Pound", "STD · São Tomé and Príncipe Dobra (pre-2018)", "STN · São Tomé and Príncipe Dobra", "SVC · Salvadoran Colón", "SYP · Syrian Pound", "SZL · Swazi Lilangeni", "THB · Thai Baht", "TJS · Tajikistani Somoni", "TMT · Turkmenistani Manat", "TND · Tunisian Dinar", "TOP · Tongan Pa anga", "TRY · Turkish Lira", "TTD · Trinidad and Tobago Dollar", "TWD · New Taiwan Dollar", "TZS · Tanzanian Shilling", "UAH · Ukrainian Hryvnia", "UGX · Ugandan Shilling", "USD · United States Dollar", "UYU · Uruguayan Peso", "UZS · Uzbekistan Som", "VEF · Venezuelan Bolívar Fuerte (Old)", "VES · Venezuelan Bolívar Soberano", "VND · Vietnamese Dong", "VUV · Vanuatu Vatu", "WST · Samoan Tala", "XAF · CFA Franc BEAC", "XAG · Silver Ounce", "XAU · Gold Ounce", "XCD · East Caribbean Dollar", "XDR · Special Drawing Rights", "XOF · CFA Franc BCEAO", "XPD · Palladium Ounce", "XPF · CFP Franc", "XPT · Platinum Ounce", "YER · Yemeni Rial", "ZAR · South African Rand", "ZMW · Zambian Kwacha", "ZWL · Zimbabwean Dollar"]
menuIndexes = currencyCodes.map((code, index) => index)
defaultFromIndex = menuStrings.indexOf("USD · United States Dollar")
defaultToIndex = menuStrings.indexOf("CNY · Chinese Yuan")
fromCurrencyMenu = new Form.Field.Option(
"fromCurrency",
"From",
menuIndexes,
menuStrings,
defaultFromIndex
)
toCurrencyMenu = new Form.Field.Option(
"toCurrency",
"To",
menuIndexes,
menuStrings,
defaultToIndex
)
textInputField = new Form.Field.String(
"conversionAmount",
"Amount",
null
)
inputForm = new Form()
inputForm.addField(fromCurrencyMenu)
inputForm.addField(toCurrencyMenu)
inputForm.addField(textInputField)
inputForm.validate = function(formObject){
conversionAmount = formObject.values['conversionAmount']
if (!conversionAmount){return false}
isnum = /^[0-9]+$/i.test(conversionAmount)
if (isnum){
intValue = parseInt(conversionAmount)
return ((intValue > 0) ? true:false)
}
return false
}
formPrompt = "Convert currency amount:"
buttonTitle = "Continue"
formObject = await inputForm.show(formPrompt,buttonTitle)
// RETRIEVE VALUES
fromIndex = formObject.values['fromCurrency']
toIndex = formObject.values['toCurrency']
fromCode = currencyCodes[fromIndex]
toCode = currencyCodes[toIndex]
conversionAmount = formObject.values['conversionAmount']
fromDescription = currencyStrings[fromIndex]
toDescription = currencyStrings[toIndex]
// CREATE HTTP GET REQUEST (RapidAPI: rapidapi.com)
requestURLstr = "https://currency-exchange.p.rapidapi.com/exchange?"
requestURLstr += `from=${fromCode}`
requestURLstr += `&to=${toCode}`
requestURLstr += `&q=${conversionAmount}`
request = URL.FetchRequest.fromString(requestURLstr)
request.method = 'GET'
request.cache = "no-cache"
request.headers = {
"x-rapidapi-key": "API-KEY-STRING-GOES-HERE",
"x-rapidapi-host": "currency-exchange.p.rapidapi.com"
}
// PERFORM REQUEST
response = await request.fetch()
responseCode = response.statusCode
if (responseCode >= 200 && responseCode < 300){
if(response.mimeType == "text/plain"){
// RESULT IS CONVERSION RATE
console.log(response.bodyString)
// PERFORM CALCULATION
conversionRate = Number(response.bodyString)
convertedAmount = Number(conversionAmount) * conversionRate
// CREATE CURRENCY STRINGS (WITH BITCOIN EXCEPTIONS)
if(fromCode === "BTC"){
var fmtr1 = Formatter.Decimal.thousandsAndDecimal
var dVal1 = Decimal.fromString(conversionAmount)
var fromDisplay = "BTC" + fmtr1.stringFromDecimal(dVal1)
} else {
var fmtr1 = Formatter.Decimal.currency(fromCode)
var dVal1 = Decimal.fromString(conversionAmount)
var fromDisplay = fmtr1.stringFromDecimal(dVal1)
}
if(toCode === "BTC"){
var fmtr1 = Formatter.Decimal.thousandsAndDecimal
convertedAmount = String(convertedAmount)
var dVal1 = Decimal.fromString(convertedAmount)
var toDisplay = "BTC" + fmtr1.stringFromDecimal(dVal1)
} else {
var fmtr2 = Formatter.Decimal.currency(toCode)
convertedAmount = String(convertedAmount)
var dVal2 = Decimal.fromString(convertedAmount)
var toDisplay = fmtr2.stringFromDecimal(dVal2)
}
// PUT FORMATTED RESULT STRING ON CLIPBOARD
Pasteboard.general.string = toDisplay
// DISPLAY RESULTS
displayResult = fromDescription + " to " + toDescription
displayResult += "\n" + fromDisplay + " = " + toDisplay
new Alert("Currency Conversion", displayResult).show()
} else {
console.error("INCORRECT MIMETYPE:", response.mimeType)
}
} else {
if(response.mimeType == "application/json"){
var errMessage = JSON.parse(response.bodyString).message
console.error(responseCode, errMessage)
} else {
var errMessage = "An error occured."
}
throw {
name: `Error ${responseCode}`,
message: errMessage
}
}
}
catch(err){
if(!err.causedByUserCancelling){
new Alert(err.name, err.message).show()
}
}
});
action.validate = function(selection, sender){
return true
};
return action;
})();
Translation Service (POST with Key Authentication)
A plug-in that integrates with the Rapid Translate Translation Service to convert the provided text from one language to another.
NOTE: A RapidAPI developer API key is required by the plug-in. See website for plan details.
Rapid API Language Codes (JSON)
NOTE: the resulting translated text is automatically placed on the clipboard and logged to the console.
To use, copy the plug-in code to a new document to your script editing application, replace the placeholder “API-KEY-STRING-GOES-HERE” with your RapidAPI key, and save to file with one of the following file extensions: omnifocusjs, omnigrafflejs, omnioutlinerjs, omniplanjs, or omnijs. Install the plug-in in the customary manner.
Translate Clipboard Text
/*{
"type": "action",
"targets": ["omnifocus", "omnioutliner", "omnigraffle", "omniplan"],
"author": "Otto Automator",
"identifier": "com.omni-automation.all.translate-clipboard-text",
"version": "1.2",
"description": "Uses the Rapid Translate service from RapidAPI to replace the text on the clipboard with a translation to the chosen language.",
"label": "Translate Clipboard Text",
"shortLabel": "Translate Clipboard"
}*/
(() => {
var action = new PlugIn.Action(function(selection, sender){
// action code
var languageCodes = ["ar", "as", "bg", "bn", "ca", "cs", "da", "de", "el", "en", "es", "fa", "fi", "fil", "fr-fr", "fr-ca", "gu", "he", "hi", "hr", "ht", "hu", "id", "it", "ja", "kk", "kn", "ko", "lt", "mg", "ml", "mr", "ms", "nb", "nl", "or", "pa", "pl", "ps", "pt-br", "pt-pt", "ro", "ru", "sk", "sl", "sv", "sw", "ta", "te", "th", "tr", "uk", "ur", "vi", "zh-Hans", "zh-Hant"]
var languageTitles = ["Arabic", "Assamese", "Bulgarian", "Bangla", "Catalan", "Czech", "Danish", "German", "Greek", "English", "Spanish", "Persian", "Finnish", "Filipino", "French", "French (Canada)", "Gujarati", "Hebrew", "Hindi", "Croatian", "Haitian Creole", "Hungarian", "Indonesian", "Italian", "Japanese", "Kazakh", "Kannada", "Korean", "Lithuanian", "Malagasy", "Malayalam", "Marathi", "Malay", "Norwegian", "Dutch", "Odia", "Punjabi", "Polish", "Pashto", "Portuguese (Brazil)", "Portuguese (Portugal)", "Romanian", "Russian", "Slovak", "Slovenian", "Swedish", "Swahili", "Tamil", "Telugu", "Thai", "Turkish", "Ukrainian", "Urdu", "Vietnamese", "Chinese (Simplified)", "Chinese (Traditional)"]
var menuItemTitles = ["AR · Arabic", "AS · Assamese", "BG · Bulgarian", "BN · Bangla", "CA · Catalan", "CS · Czech", "DA · Danish", "DE · German", "EL · Greek", "EN · English", "ES · Spanish", "FA · Persian", "FI · Finnish", "FIL · Filipino", "FR-FR · French", "FR-CA · French (Canada)", "GU · Gujarati", "HE · Hebrew", "HI · Hindi", "HR · Croatian", "HT · Haitian Creole", "HU · Hungarian", "ID · Indonesian", "IT · Italian", "JA · Japanese", "KK · Kazakh", "KN · Kannada", "KO · Korean", "LT · Lithuanian", "MG · Malagasy", "ML · Malayalam", "MR · Marathi", "MS · Malay", "NB · Norwegian", "NL · Dutch", "OR · Odia", "PA · Punjabi", "PL · Polish", "PS · Pashto", "PT-BR · Portuguese (Brazil)", "PT-PT · Portuguese (Portugal)", "RO · Romanian", "RU · Russian", "SK · Slovak", "SL · Slovenian", "SV · Swedish", "SW · Swahili", "TA · Tamil", "TE · Telugu", "TH · Thai", "TR · Turkish", "UK · Ukrainian", "UR · Urdu", "VI · Vietnamese", "ZH-HANS · Chinese (Simplified)", "ZH-HANT · Chinese (Traditional)"]
var menuIndexes = new Array()
languageCodes.forEach((item, index) => {menuIndexes.push(index)})
var defaultFromIndex = menuItemTitles.indexOf("EN · English")
var defaultToIndex = menuItemTitles.indexOf("ES · Spanish")
var fromMenu = new Form.Field.Option(
"fromLangauge",
"From",
menuIndexes,
menuItemTitles,
defaultFromIndex
)
var toMenu = new Form.Field.Option(
"toLangauge",
"To",
menuIndexes,
menuItemTitles,
defaultToIndex
)
var inputForm = new Form()
inputForm.addField(fromMenu)
inputForm.addField(toMenu)
var formPrompt = "Translate clipboard text:"
var buttonTitle = "Continue"
var formPromise = inputForm.show(formPrompt,buttonTitle)
inputForm.validate = function(formObject){
var fromIndex = formObject.values['fromLangauge']
var toIndex = formObject.values['toLangauge']
return (fromIndex === toIndex) ? false:true
}
formPromise.then(function(formObject){
// RETRIEVE VALUES
var fromIndex = formObject.values['fromLangauge']
var toIndex = formObject.values['toLangauge']
var fromCode = languageCodes[fromIndex]
var toCode = languageCodes[toIndex]
var fromLanguage = languageTitles[fromIndex]
var toLanguage = languageTitles[toIndex]
var sourceText = Pasteboard.general.string
var queryData = {
"from": `${fromCode}`,
"text": `${sourceText}`,
"to": `${toCode}`
}
// Rapid Translate translation service at RapidAPI:
// https://rapidapi.com/petadata/api/rapid-translate
var urlStr = "https://rapid-translate.p.rapidapi.com/TranslateText"
var request = URL.FetchRequest.fromString(urlStr)
request.headers = {
"content-type": "application/json",
"x-rapidapi-key": "API-KEY-STRING-GOES-HERE",
"x-rapidapi-host": "rapid-translate.p.rapidapi.com",
"useQueryString": true
}
request.method = 'POST'
request.bodyString = JSON.stringify(queryData)
var requestPromise = request.fetch()
requestPromise.then(response => {
try {
var responseCode = response.statusCode
console.log(responseCode)
if (responseCode >= 200 && responseCode < 300){
var resultObj = JSON.parse(response.bodyString)
//--> {"result":"Der Regen in Spanien fällt vor allem auf die Ebene.","from":"en","to":"de"}
var translation = resultObj["result"]
// REPLACE CONTENTS OF CLIPBOARD WITH TRANSLATION
Pasteboard.general.string = translation
var alertMessage = `The clipboard has been translated from ${fromLanguage} to ${toLanguage}.`
new Alert("Translation Completed", alertMessage).show()
} else {
if(response.mimeType == "application/json"){
var errMessage = JSON.parse(response.bodyString).message
console.error(errMessage)
} else {
var errorCodesURL = "https://en.wikipedia.org/wiki/List_of_HTTP_status_codes"
var errMessage = "An error occured.\n" + errorCodesURL
console.log(response.bodyString)
}
new Alert(String(responseCode), errMessage).show()
}
}
catch(err){console.error(err)}
})
})
});
action.validate = function(selection, sender){
// validation code
return (Pasteboard.general.hasStrings)
};
return action;
})();
Using URL.FetchRequest to Read File
The following example show shows how to use the fetch() function with the URL.FetchRequest class to asynchronously read the contents of a file, rather than using the standard urlInstancefetch() technique:
Read Contents of Text File
(async () => {
try {
picker = new FilePicker()
picker.folders = false
picker.multiple = false
picker.types = [FileType.plainText]
urls = await picker.show()
fileRequest = new URL.FetchRequest()
fileRequest.url = urls[0]
fileContents = await fileRequest.fetch().then(file => file.bodyString)
Pasteboard.general.string = fileContents
}
catch(err){
if(!err.causedByUserCancelling){
new Alert(err.name, err.message).show()
}
}
})();
Here’s an example using the API to read an image file (PNG) to the Clipboard:
Read PNG File to Clipboard
(async () => {
try {
picker = new FilePicker()
picker.folders = false
picker.multiple = false
picker.types = [TypeIdentifier.png]
urls = await picker.show()
fileRequest = new URL.FetchRequest()
fileRequest.url = urls[0]
fileContents = await fileRequest.fetch().then(file => {
item = new Pasteboard.Item()
item.setDataForType(file.bodyData,TypeIdentifier.png)
Pasteboard.general.clear()
Pasteboard.general.addItems([item])
})
}
catch(err){
if(!err.causedByUserCancelling){
new Alert(err.name, err.message).show()
}
}
})();