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 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

Class Functions

Instance Functions

Instance Properties

Request Methods

From the Wikipedia documentation:

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

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:

login-dialog-safari

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:

fetch-input

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:

fetch-get-console-window

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).

(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() } })();
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):

post-chart  

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.

(⬇ see below ) The plug-in input interface:

currency-plug-in-01

(⬇ see below ) A successful result window.

NOTE: the resulting formatted value is automatically placed on the clipboard.

currency-plug-in-02

(⬇ see below ) An error alert stating that the plug-in does not contain the appropriate access key:

currency-plug-in-03

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)

(⬇ see below ) The plug-in selection interface:

translate-clipboard-01

(⬇ see below ) A successful result window.

translate-clipboard-02

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:

(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() } } })();
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:

(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() } } })();
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() } } })();