Match Patterns
NOTE
It's recommended that you go through the 5 Minute Quick Start first to understand how to create and install your plugin.
Compact Form
Instead of writing out potentially 100s of forms for a command's match property, you can use compact form syntax.
eg. '[delete/remove] [/the ]previous [word/# words]'
expands to:
- delete previous word
- remove previous word
- delete the previous word
- remove the previous word
- delete previous # words
- remove previous # words
- delete the previous # words
- remove the previous # words
TIPS
- You cannot nest brackets.
- Priority is respected by the order in the brackets.
Wildcard Matching
What if we want a plugin that accepts an arbitrary argument after some key words (AKA a slot)?
Let's make a plugin that shows the weather for any city when user says: weather for [city name] (eg. weather for Chiang Mai)
Easy peasy.
Use *
in your match
string to greedily match any words.
/// <reference types="@lipsurf/types/extension"/>
declare const PluginBase: IPluginBase;
export default <IPluginBase & IPlugin>{
...PluginBase,
...{
niceName: "Weather",
match: /.*\.accuweather\.com/,
version: "1.0.0",
apiVersion: 2,
commands: [
{
name: "Check the Weather",
description: "Check the weather for a given city.",
// say it on any page (not just accuweather domain)
global: true,
match: "[weather/forecast] [for/in] *",
pageFn: async (transcript, { preTs, normTs }: TsData) => {
// https://api.accuweather.com/locations/v1/cities/autocomplete?q=chiang%20mai&apikey=d41dfd5e8a1748d0970cba6637647d96&language=en-us&get_param=value
// ex resp: [{"Version":1,"Key":"317505","Type":"City","Rank":41,"LocalizedName":"Chiang Mai","Country":{"ID":"TH","LocalizedName":"Thailand"},"AdministrativeArea":{"ID":"50","LocalizedName":"Chiang Mai"}}]
// https://www.accuweather.com/en/th/chiang-mai/317505/weather-forecast/317505
const resp = await (
await window.fetch(
`https://api.accuweather.com/locations/v1/cities/autocomplete?q=${preTs}&apikey=d41dfd5e8a1748d0970cba6637647d96&language=en-us&get_param=value`
)
).json();
let cityId = resp[0].Key;
let countryCode = resp[0].Country.ID.toLowerCase();
let cityName = resp[0].LocalizedName.replace(" ", "-");
window.location.href = `https://www.accuweather.com/en/${countryCode}/${cityName}/${cityId}/weather-forecast/${cityId}`;
},
},
],
},
};
Numeral Matching
Ain't nothin' to it.
Use #
in your match
string to match numerals or ordinals including ones that are spelled-out (ie. four-thousand)
Let's write a plugin that opens a tab with URL x for y minutes so that we can limit it's time wasting-ness. This might be useful if we need to check Facebook but don't want to get sucked into the feed for too long.
/// <reference types="@lipsurf/types/extension"/>
declare const PluginBase: IPluginBase;
export default <IPluginBase & IPlugin>{
...PluginBase,
...{
niceName: "Anti-procrastination",
description: "Helpers for overcoming procrastination.",
match: /.*/,
version: "1.0.0",
apiVersion: 2,
commands: [
{
name: "Self Destructing Tab",
description:
"Open a new tab with x website for y minutes. Useful for limiting the time-sucking power of sites like facebook, reddit, twitter etc.",
global: true,
match: "open * for # minutes",
pageFn: (transcript, siteStr: TsData, minutes: number) => {
console.log(`site: ${siteStr}, minutes: ${minutes}`);
},
},
],
},
};
Match Function
For the most advanced cases, you can write a function that takes in transcripts as they come down the wire and return undefined
when there's no match, or an array of arguments to pass to the pageFn
when there is a match.
Use cases include:
- Match based on something on the page
- Match based on some internal plugin state
- Regex matching
Cake walk.
We need to make match
an object of type DynamicMatch
How about a plugin for Gmail that moves the currently selected messages to a folder that the user commands to?
TIP
We could use the wildcard matching for this (eg. "move to *") but then we cant limit the user's choices to valid folders.
/// <reference types="@lipsurf/types/extension"/>
declare const PluginBase: IPluginBase;
export default <IPluginBase & IPlugin>{
...PluginBase,
...{
niceName: "Gmail",
match: /^https:\/\/mail\.google\.com/,
version: "1.0.0",
apiVersion: 2,
commands: [
{
name: "Move to Folder",
description: "Move already selected emails to a spoken folder",
match: {
description: 'Say "move to [folder name]"',
fn: (transcript) => {
// exercise left to the reader...
},
},
pageFn: () => {
// exercise left to the reader...
},
},
],
},
};
NOTE
The match function needs to check the whole transcript for matches and then return indexes for which parts it used. This way the transcript can have the proper section success highlighted (in green) that matches a command, and command chaining will work.