John Lindquist

John Lindquist

// Menu: Word Game
// Description: Guess letters to win!
// Author: John Lindquist
// Twitter: @johnlindquist
let playAgain = true
while (playAgain) {
let {
data: [word],
} = await get(
`https://random-word-api.herokuapp.com/word`
)
let correct = false
let guesses = []
while (!correct) {
let [...letters] = await arg(
{ placeholder: "Guess a letter/s:", hint: word }, //remove hint to make it more challenging 😉
word
.split("")
.map(char => (guesses.includes(char) ? char : "*"))
.join("")
)
guesses = guesses.concat(...letters)
correct = word
.split("")
.every(char => guesses.includes(char))
}
playAgain = await arg(`🏆 "${word}"! Play Again?`, [
{ name: "Yes", value: true },
{ name: "No", value: false },
])
}
// Menu: Open Graph Image Grabber
// Description: Attempts to scrape the Open Graph image from the focused chrome tab
// Author: John Lindquist
// Twitter: @johnlindquist
//📣 Note: Playwright takes ~20 seconds or so to install...
let { getActiveTab } = await kit("chrome")
let { attribute } = await kit("playwright")
let { copyPathAsPicture } = await kit("file")
let download = await npm("image-downloader")
let sharp = await npm("sharp")
let tab = await getActiveTab()
console.log({ tab })
setPlaceholder(`og:image of ${tab}`)
let url = ""
try {
url = await attribute(
tab,
'head meta[property="og:image"]',
"content"
)
} catch (error) {
setPlaceholder(
`og:image not found. Checking twitter:image`
)
try {
url = await attribute(
tab,
'head meta[name="twitter:image"]',
"content"
)
} catch (error) {
console.log(error)
setPlaceholder(`Sorry, giving up`)
await wait(1000)
exit()
}
}
console.log({ url })
setPlaceholder(`Found ${url}`)
console.log({ url })
let checkRedirects = await get(url)
url = checkRedirects.request.res.responseUrl
console.log({ redirectedUrl: url })
let imageName = tab.split("/").pop()
if (!imageName)
imageName = tab.split("//").pop().replace("/", "")
if (!imageName.endsWith(".png"))
imageName = `${imageName}.png`
console.log({ imageName })
let dest = kenvPath("tmp", imageName)
let { filename: image } = await download
.image({
url,
dest,
})
.catch(error => {
console.log(error)
})
console.log({ image })
let width = parseInt(await arg("Enter width:"), 10)
setPlaceholder(`Resizing to ${width}`)
let metadata = await sharp(image).metadata()
let newHeight = Math.floor(
metadata.height * (width / metadata.width)
)
let lastDot = /.(?!.*\.)/
let resizedImageName = image.replace(lastDot, `-${width}.`)
await sharp(image)
.resize(width, newHeight)
.toFile(resizedImageName)
console.log({ resizedImageName })
await copyPathAsPicture(resizedImageName)
setPlaceholder(`Copied to clipboard`)
await wait(500)
// Menu: Update Twitter Name
// Description: Change your name on twitter
let Twitter = await npm("twitter-lite")
let envOptions = {
hint: md(
`You need to [create an app](https://developer.twitter.com/en/apps) to get these keys/tokens`
),
ignoreBlur: true,
secret: true,
}
let client = new Twitter({
consumer_key: await env(
"TWITTER_CONSUMER_KEY",
envOptions
),
consumer_secret: await env(
"TWITTER_CONSUMER_SECRET",
envOptions
),
access_token_key: await env(
"TWITTER_ACCESS_TOKEN_KEY",
envOptions
),
access_token_secret: await env(
"TWITTER_ACCESS_TOKEN_SECRET",
envOptions
),
})
let name = await arg("Enter new twitter name:")
let response = await client
.post("account/update_profile", {
name,
})
.catch(error => console.log(error))
//Shortcut: opt r
/**
* @typedef {typeof import("reddit")} Reddit
*/
/*
First, create a Reddit App.
Click "Create app"
For simple scripts, you can select a type of "script".
You can enter anything in the "about URL" and "redirect URL" fields.
Note your app ID (appears below the app name) and your app secret.
*/
/** @type Reddit */
let Reddit = await npm("reddit")
let envOptions = {
ignoreBlur: true,
hint: md(
`[Create a reddit app](https://www.reddit.com/prefs/apps)`
),
secret: true,
}
let reddit = new Reddit({
username: await env("REDDIT_USERNAME"),
password: await env("REDDIT_PASSWORD"),
appId: await env("REDDIT_APP_ID", envOptions),
appSecret: await env("REDDIT_APP_SECRET", envOptions),
userAgent: `ScriptKit/1.0.0 (https://scriptkit.com)`,
})
let subreddits = [
"funny",
"aww",
"dataisbeautiful",
"mildlyinteresting",
"RocketLeague",
]
subreddits.forEach(sub => {
onTab(sub, async () => {
await arg("Select post to open:", async () => {
let best = await reddit.get(`/r/${sub}/hot`)
return best.data.children.map(({ data }) => {
let {
title,
thumbnail,
url,
subreddit_name_prefixed,
preview,
} = data
let resolutions = preview?.images?.[0]?.resolutions
let previewImage =
resolutions?.[resolutions?.length - 1]?.url
return {
name: title,
description: subreddit_name_prefixed,
value: url,
img: thumbnail,
...(previewImage && {
preview: md(`
![${title}](${previewImage})
### ${title}
`),
}),
}
})
})
})
})
await run("speak-text", "I like tacos", "--voice", 5)
// Menu: Speak Text
// Description: Speaks Text Using Google's Text-to-Speech
// Author: John Lindquist
// Twitter: @johnlindquist
// Requires a Google Cloud account and configuration:
// https://cloud.google.com/text-to-speech
let { playAudioFile } = await kit("audio")
let { format } = await npm("date-fns")
/** @type typeof import("@google-cloud/text-to-speech") */
let textToSpeech = await npm("@google-cloud/text-to-speech")
let client = new textToSpeech.TextToSpeechClient()
let text = await arg("What should I say?")
let voicesDB = db("voices", { voices: [] })
let voices = voicesDB.get("voices").value()
//cache voices
if (voices.length === 0) {
let [{ voices: englishVoices }] = await client.listVoices(
{
languageCode: "en",
}
)
let voiceChoices = englishVoices.map(voice => {
return {
name: `${voice.ssmlGender} - ${voice.name}`,
value: {
...voice,
languageCode: voice.name.slice(0, 4),
},
}
})
voicesDB.set("voices", voiceChoices).write()
voices = voicesDB.get("voices").value()
}
// From the terminal or run
// speak-text "I like tacos" --voice 5
// await run("speak-text", "I like tacos", "--voice", "5")
let voice =
typeof arg?.voice === "number"
? voices[arg?.voice].value
: await arg("Select voice", voices)
let effectsProfileId = ["headphone-class-device"]
let createRequest = (voice, text) => {
let speakingRate = 1
return {
input: { text },
voice,
audioConfig: {
audioEncoding: "MP3",
effectsProfileId,
speakingRate,
},
}
}
let request = createRequest(voice, text)
let safeFileName = text
.slice(0, 10)
.replace(/[^a-z0-9]/gi, "-")
.toLowerCase()
let date = format(new Date(), "yyyy-MM-dd-hh-mm-ss")
let fileName = `${date}-${safeFileName}.mp3`
// Performs the text-to-speech request
let [response] = await client.synthesizeSpeech(request)
// Write the .mp3 locally
let textAudioPath = tmp(fileName)
await writeFile(
textAudioPath,
response.audioContent,
"binary"
)
playAudioFile(textAudioPath)
// Menu: Google Image Grid
// Description: Create a Grid of Images
// Author: John Lindquist
// Twitter: @johnlindquist
let gis = await npm("g-i-s")
await arg("Search for images:", async input => {
if (input.length < 3) return ``
let searchResults = await new Promise(res => {
gis(input, (_, results) => {
res(results)
})
})
return `<div class="flex flex-wrap">${searchResults
.map(({ url }) => `<img class="h-32" src="${url}" />`)
.join("")}</div>`
})

//Menu: GitHub Advanced Search
//Description: Search GitHub for Code
//Author: John Lindquist
//Twitter: @johnlindquist
let languages = [
"JavaScript",
"TypeScript",
"C",
"C#",
"C++",
"CoffeeScript",
"CSS",
"Dart",
"DM",
"Elixir",
"Go",
"Groovy",
"HTML",
"Java",
"Kotlin",
"Objective-C",
"Perl",
"PHP",
"PowerShell",
"Python",
"Ruby",
"Rust",
"Scala",
"Shell",
"Swift",
]
let query = await arg("Enter search query:")
query = query.replace(" ", "+")
let lang = await arg("Select language:", languages)
let url = `https://github.com/search?l=&q=${query}+language%3A${lang}&type=code`
exec(`open "${url}"`)
// Menu: Rotate Dropped Image
// Description: Drop an image in and rotate it
// Author: John Lindquist
// Twitter: @johnlindquist
let sharp = await npm("sharp")
let [file] = await arg({
placeholder: "Drop an image",
drop: true,
ignoreBlur: true,
})
await arg("Rotate:", async input => {
let rotate = 0
try {
rotate = parseInt(input || 0, 10)
} catch {}
let buffer = await sharp(file.path)
.rotate(rotate)
.toBuffer()
return `
<img style="height:10rem" src="data:image/jpg;base64,${buffer.toString(
"base64"
)}" alt="">
`
})

//Menu: Testing Arg Panel
//Description: Testing the sequence
//Author: johnlindquist
//Twitter: @johnlindquist
let showGif = await arg(
"You can display anything",
`<div class="flex justify-center">
<img src="https://media3.giphy.com/media/efCPDKBWq8qfZuYR34/giphy-downsized-medium.gif?cid=0b9ef2f4xptdlgxr2p76xp8gi9hezyptzhbhgggo76e30zcl&rid=giphy-downsized-medium.gif" alt="">
</div>`
)
let showLinks = await arg(
"Including links",
md(`
[Visit scriptkit.app](http://scriptkit.app)
[Join the discussion](http://github.com/johnlindquist/kit/discussions)
`)
)
let term = await arg(
"This still returns the input",
md(`## What kind of joke would you like to hear?`)
)
let joke = await arg(
`Here's a ${term} joke to get your day started:`,
async () => {
let response = await get(
`https://icanhazdadjoke.com/search?term=${term}`,
{
headers: {
Accept: "text/plain",
},
}
)
return md(
`${response.data
.split("\n")
.filter(string => string?.trim()?.length)
.map(
joke => `* ${joke}
`
)
.join("")}`
)
}
)
let autoUpdate = await arg(
"Auto-update works too!",
input => `Have a ${input} day!`
)
// Menu: App Launcher
// Description: Search for an app then launch it
// Author: John Lindquist
// Twitter: @johnlindquist
let createChoices = async () => {
let apps = await fileSearch("", {
onlyin: "/",
kind: "application",
})
let prefs = await fileSearch("", {
onlyin: "/",
kind: "preferences",
})
let group = path => apps =>
apps
.filter(app => app.match(path))
.sort((a, b) => {
let aName = a.replace(/.*\//, "")
let bName = b.replace(/.*\//, "")
return aName > bName ? 1 : aName < bName ? -1 : 0
})
return [
...group(/^\/Applications\/(?!Utilities)/)(apps),
...group(/\.prefPane$/)(prefs),
...group(/^\/Applications\/Utilities/)(apps),
...group(/System/)(apps),
...group(/Users/)(apps),
].map(value => {
return {
name: value.split("/").pop().replace(".app", ""),
value,
description: value,
}
})
}
let appsDb = await db("apps", async () => ({
choices: await createChoices(),
}))
let app = await arg("Select app:", appsDb.choices)
let command = `open -a "${app}"`
if (app.endsWith(".prefPane")) {
command = `open ${app}`
}
exec(command)
// Menu: Book Search
// Description: Use Open Library API to search for books
// Author: John Lindquist
// Twitter: @johnlindquist
let query = await arg('Search for a book title:')
//This API can be a little slow. Wait a couple seconds
let response = await get(`http://openlibrary.org/search.json?q=${query}`)
let transform = ({title, author_name}) =>
`* "${title}" - ${author_name?.length && author_name[0]}`
let markdown = response.data.docs.map(transform).join('\n')
inspect(markdown, 'md')
// Menu: Chrome Bookmarks
// Description: Select and open a bookmark from Chrome
// Author: John Lindquist
// Twitter: @johnlindquist
let bookmarks = await readFile(
home(
"Library/Application Support/Google/Chrome/Default/Bookmarks"
)
)
bookmarks = JSON.parse(bookmarks)
bookmarks = bookmarks.roots.bookmark_bar.children
let url = await arg(
"Select bookmark",
bookmarks.map(({ name, url }) => {
return {
name,
description: url,
value: url,
}
})
)
exec(`open "${url}"`)
// Menu: Open Chrome Tab
// Description: List all Chrome tabs. Then switch to that tab
// Author: John Lindquist
// Twitter: @johnlindquist
let currentTabs = await getTabs()
let bookmarks = await readFile(
home(
"Library/Application Support/Google/Chrome/Default/Bookmarks"
)
)
bookmarks = JSON.parse(bookmarks)
bookmarks = bookmarks.roots.bookmark_bar.children
let bookmarkChoices = bookmarks.map(({ name, url }) => {
return {
name: url,
description: name,
value: url,
}
})
let currentOpenChoices = currentTabs.map(
({ url, title }) => ({
name: url,
value: url,
description: title,
})
)
let bookmarksAndOpen = [
...bookmarkChoices,
...currentOpenChoices,
]
let choices = _.uniqBy(bookmarksAndOpen, "name")
let url = await arg("Focus Chrome tab:", choices)
focusTab(url)
// Menu: Convert Colors
// Description: Converts colors between rgb, hex, etc
// Author: John Lindquist
// Twitter: @johnlindquist
let convert = await npm("color-convert")
let createChoice = (type, value, input) => {
return {
name: type + ": " + value,
value,
html: `<div class="h-full w-full p-1 text-xs flex justify-center items-center font-bold" style="background-color:${input}">
<span>${value}</span>
</div>`,
}
}
//using a function with "input" allows you to generate values
let conversion = await arg("Enter color:", input => {
if (input.startsWith("#")) {
return ["rgb", "cmyk", "hsl"].map(type => {
let value = convert.hex[type](input).toString()
return createChoice(type, value, input)
})
}
//two or more lowercase
if (input.match(/^[a-z]{2,}/)) {
return ["rgb", "hex", "cmyk", "hsl"]
.map(type => {
try {
let value =
convert.keyword[type](input).toString()
return createChoice(type, value, input)
} catch (error) {
return ""
}
})
.filter(Boolean)
}
return []
})
setSelectedText(conversion)
// Menu: Search for a File
// Description: File Search
// Author: John Lindquist
// Twitter: @johnlindquist
/** Note: This is a very basic search implementation based on "mdfind".
* File search will be a _big_ focus in future versions of Script Kit
*/
let selectedFile = await arg(
"Search a file:",
async input => {
if (input?.length < 4) return []
let files = await fileSearch(input)
return files.map(path => {
return {
name: path.split("/").pop(),
description: path,
value: path,
}
})
}
)
exec(`open ${selectedFile}`)
// Menu: Giphy
// Description: Search giphy. Paste link.
// Author: John Lindquist
// Twitter: @johnlindquist
let download = await npm("image-downloader")
let queryString = await npm("query-string")
let GIPHY_API_KEY = await env("GIPHY_API_KEY", {
hint: md(
`Get a [Giphy API Key](https://developers.giphy.com/dashboard/)`
),
ignoreBlur: true,
secret: true,
})
let search = q =>
`https://api.giphy.com/v1/gifs/search?api_key=${GIPHY_API_KEY}&q=${q}&limit=10&offset=0&rating=g&lang=en`
let { input, url } = await arg(
"Search giphy:",
async input => {
if (!input) return []
let query = search(input)
let { data } = await get(query)
return data.data.map(gif => {
return {
name: gif.title.trim() || gif.slug,
value: {
input,
url: gif.images.original.url,
},
preview: `<img src="${gif.images.downsized.url}" alt="">`,
}
})
}
)
let formattedLink = await arg("Format to paste", [
{
name: "URL Only",
value: url,
},
{
name: "Markdown Image Link",
value: `![${input}](${url})`,
},
{
name: "HTML <img>",
value: `<img src="${url}" alt="${input}">`,
},
])
setSelectedText(formattedLink)
// Menu: Gist from Finder
// Description: Select a file in Finder, then create a Gist
// Author: John Lindquist
// Twitter: @johnlindquist
let filePath = await getSelectedFile()
let file = filePath.split("/").pop()
let isPublic = await arg("Should the gist be public?", [
{ name: "No", value: false },
{ name: "Yes", value: true },
])
const body = {
files: {
[file]: {
content: await readFile(filePath, "utf8"),
},
},
}
if (isPublic) body.public = true
let config = {
headers: {
Authorization:
"Bearer " +
(await env("GITHUB_GIST_TOKEN", {
info: `Create a gist token: <a class="bg-white" href="https://github.com/settings/tokens/new">https://github.com/settings/tokens/new</a>`,
message: `Set .env GITHUB_GIST_TOKEN:`,
})),
},
}
const response = await post(
`https://api.github.com/gists`,
body,
config
)
exec(`open ` + response.data.html_url)
// Menu: Google Image Grid
// Description: Create a Grid of Images
// Author: John Lindquist
// Twitter: @johnlindquist
let gis = await npm("g-i-s")
await arg("Search for images:", async input => {
if (input.length < 3) return ``
let searchResults = await new Promise(res => {
gis(input, (_, results) => {
res(results)
})
})
return `<div class="flex flex-wrap">${searchResults
.map(({ url }) => `<img class="h-32" src="${url}" />`)
.join("")}</div>`
})
// Menu: Detect Image Width and Height
// Description: Show the metadata of an image
// Author: John Lindquist
// Twitter: @johnlindquist
let sharp = await npm("sharp")
let image = await arg("Search an image:", async input => {
if (input.length < 3) return []
let files = await fileSearch(input, { kind: "image" })
return files.map(path => {
return {
name: path.split("/").pop(),
value: path,
description: path,
}
})
})
let { width, height } = await sharp(image).metadata()
console.log({ width, height })
await arg(`Width: ${width} Height: ${height}`)
// Menu: Resize an Image
// Description: Select an image in Finder. Type option + i to resize it.
// Author: John Lindquist
// Twitter: @johnlindquist
// Shortcut: opt i
let sharp = await npm("sharp")
let imagePath = await getSelectedFile()
let width = Number(await arg("Enter width:"))
let metadata = await sharp(imagePath).metadata()
let newHeight = Math.floor(
metadata.height * (width / metadata.width)
)
let lastDot = /.(?!.*\.)/
let resizedImageName = imagePath.replace(
lastDot,
`-${width}.`
)
await sharp(imagePath)
.resize(width, newHeight)
.toFile(resizedImageName)
// Menu: New Journal Entry
// Description: Generate a file using the current date in a specified folder
// Author: John Lindquist
// Twitter: @johnlindquist
let { format } = await npm("date-fns")
let date = format(new Date(), "yyyy-MM-dd")
let journalPath = await env("JOURNAL_PATH")
if (!(await isDir(journalPath))) {
mkdir("-p", journalPath)
}
let journalFile = path.join(journalPath, date + ".md")
if (!(await isFile(journalFile))) {
let journalPrompt = `How are you feeling today?`
await writeFile(journalFile, journalPrompt)
}
edit(journalFile, env?.JOURNAL_PATH)
// Menu: Open Project
// Description: List dev projects
// Author: John Lindquist
// Twitter: @johnlindquist
let { projects, write } = await db("projects", {
projects: [
"~/.kit",
"~/projects/kitapp",
"~/projects/scriptkit.com",
],
})
onTab("Open", async () => {
let project = await arg("Open project:", projects)
edit(project)
})
onTab("Add", async () => {
while (true) {
let project = await arg(
"Add path to project:",
md(projects.map(project => `* ${project}`).join("\n"))
)
projects.push(project)
await write()
}
})
onTab("Remove", async () => {
while (true) {
let project = await arg("Open project:", projects)
let indexOfProject = projects.indexOf(project)
projects.splice(indexOfProject, 1)
await write()
}
})
// Menu: Quick Thoughts
// Description: Add lines to today's journal page
// Author: John Lindquist
// Twitter: @johnlindquist
let { format } = await npm("date-fns")
let date = format(new Date(), "yyyy-MM-dd")
let thoughtsPath = await env("THOUGHTS_PATH")
let thoughtFile = path.join(thoughtsPath, date + ".md")
let firstEntry = true
let addThought = async thought => {
if (firstEntry) {
thought = `
- ${format(new Date(), "hh:mmaa")}
${thought}\n`
firstEntry = false
} else {
thought = ` ${thought}\n`
}
await appendFile(thoughtFile, thought)
}
let openThoughtFile = async () => {
let { stdout } = exec(`wc ${thoughtFile}`, {
silent: true,
})
let lineCount = stdout.trim().split(" ").shift()
edit(thoughtFile, thoughtsPath, lineCount + 1) //open with cursor at end
await wait(500)
exit()
}
if (!(await isFile(thoughtFile)))
await writeFile(thoughtFile, `# ${date}\n`)
while (true) {
let thought = await arg({
placeholder: "Thought:",
hint: `Type "open" to open journal`,
})
if (thought === "open") {
await openThoughtFile()
} else {
await addThought(thought)
}
}
// Menu: Reddit
// Description: Browse Reddit from Script Kit
// Author: John Lindquist
// Twitter: @johnlindquist
let Reddit = await npm("reddit")
let envOptions = {
ignoreBlur: true,
hint: md(
`[Create a reddit app](https://www.reddit.com/prefs/apps)`
),
secret: true,
}
let reddit = new Reddit({
username: await env("REDDIT_USERNAME"),
password: await env("REDDIT_PASSWORD"),
appId: await env("REDDIT_APP_ID", envOptions),
appSecret: await env("REDDIT_APP_SECRET", envOptions),
userAgent: `ScriptKit/1.0.0 (https://scriptkit.com)`,
})
let subreddits = [
"funny",
"aww",
"dataisbeautiful",
"mildlyinteresting",
"RocketLeague",
]
subreddits.forEach(sub => {
onTab(sub, async () => {
let url = await arg(
"Select post to open:",
async () => {
let best = await reddit.get(`/r/${sub}/hot`)
return best.data.children.map(({ data }) => {
let {
title,
thumbnail,
url,
subreddit_name_prefixed,
preview,
} = data
let resolutions =
preview?.images?.[0]?.resolutions
let previewImage =
resolutions?.[resolutions?.length - 1]?.url
return {
name: title,
description: subreddit_name_prefixed,
value: url,
img: thumbnail,
...(previewImage && {
preview: md(`
![${title}](${previewImage})
### ${title}
`),
}),
}
})
}
)
exec(`open "${url}"`)
})
})
// Menu: Share Selected File
// Description: Select a file in Finder. Creates tunnel and copies link to clipboard.
// Author: John Lindquist
// Twitter: @johnlindquistt
// Background: true
let ngrok = await npm("ngrok")
let handler = await npm("serve-handler")
let exitHook = await npm("exit-hook")
let http = await import("http")
let filePath = await getSelectedFile()
let symLinkName = _.last(
filePath.split(path.sep)
).replaceAll(" ", "-")
let symLinkPath = tmp(symLinkName)
console.log(`Creating temporary symlink: ${symLinkPath}`)
ln(filePath, symLinkPath)
let port = 3033
const server = http.createServer(handler)
cd(tmp())
server.listen(port, async () => {
let tunnel = await ngrok.connect(port)
let shareLink = tunnel + "/" + symLinkName
console.log(
chalk`{yellow ${shareLink}} copied to clipboard`
)
copy(shareLink)
})
exitHook(() => {
server.close()
if (test("-f", symLinkPath)) {
console.log(
`Removing temporary symlink: ${symLinkPath}`
)
exec(`rm ${symLinkPath}`)
}
})
// Menu: Speak Script
// Description: Run a Script based on Speech Input
// Author: John Lindquist
// Twitter: @johnlindquist
let { scripts } = await db("scripts")
let escapedScripts = scripts.map(script => ({
name: `"${script.name.replace(/"/g, '\\"')}"`, //escape quotes
value: script.filePath,
}))
let speakableScripts = escapedScripts
.map(({ name }) => name)
.join(",")
let speech = await applescript(String.raw`
tell application "SpeechRecognitionServer"
listen for {${speakableScripts}}
end tell
`)
let script = escapedScripts.find(
script => script.name == `"${speech}"`
)
await run(script.value)
// Menu: Speed Reader
// Description: Display clipboard content at a defined rate
// Author: John Lindquist
// Twitter: @johnlindquist
let wpm = 1000 * (60 / (await arg('Enter words per minute:')))
let text = await paste()
text = text
.trim()
.split(' ')
.filter(Boolean)
.flatMap((sentence) => sentence.trim().split(' '))
let i = 0
let id = setInterval(() => {
setPlaceholder(` ${text[i++]}`)
if (i >= text.length) clearInterval(id)
}, wpm)
// Menu: Synonym
// Description: List synonyms
// Author: John Lindquist
// Twitter: @johnlindquist
let synonym = await arg("Type a word", async input => {
if (!input || input?.length < 3) return []
let url = `https://api.datamuse.com/words?ml=${input}&md=d`
let response = await get(url)
return response.data.map(({ word, defs }) => {
return {
name: `${word}${defs?.[0] && ` - ${defs[0]}`}`,
value: word,
selected: `Paste ${word}`,
}
})
})
setSelectedText(synonym)
// Menu: Update Twitter Name
// Description: Change your name on twitter
// Author: John Lindquist
// Twitter: @johnlindquist
let Twitter = await npm('twitter-lite')
let envOptions = {
hint: md(
`You need to [create an app](https://developer.twitter.com/en/apps) to get these keys/tokens`,
),
ignoreBlur: true,
secret: true,
}
let client = new Twitter({
consumer_key: await env('TWITTER_CONSUMER_KEY', envOptions),
consumer_secret: await env('TWITTER_CONSUMER_SECRET', envOptions),
access_token_key: await env('TWITTER_ACCESS_TOKEN_KEY', envOptions),
access_token_secret: await env('TWITTER_ACCESS_TOKEN_SECRET', envOptions),
})
let name = await arg('Enter new twitter name:')
let response = await client
.post('account/update_profile', {
name,
})
.catch((error) => console.log(error))
// Menu: Vocab Quiz
// Description: Quiz on random vocab words
// Author: John Lindquist
// Twitter: @johnlindquist
await npm("wordnet-db")
let randomWord = await npm("random-word")
let { WordNet } = await npm("natural")
let wordNet = new WordNet()
let words = []
while (true) {
setPlaceholder(`Finding random word and definitions...`)
while (words.length < 4) {
let quizWord = randomWord()
let results = await new Promise(resolve => {
wordNet.lookup(quizWord, resolve)
})
if (results.length) {
let [{ lemma, def }] = results
words.push({ name: def, value: lemma })
}
}
let word = words[0]
let result = await arg(
`What does "${word.value}" mean?`,
_.shuffle(words)
)
let correct = word.value === result
setPlaceholder(
`${correct ? "✅" : "🚫"} ${word.value}: ${word.name}`
)
words = []
await wait(2000)
}
// Menu: Word API
// Description: Queries a word api. Pastes selection.
// Author: John Lindquist
// Twitter: @johnlindquist
let typeMap = {
describe: "rel_jjb",
trigger: "rel_trg",
noun: "rel_jja",
follow: "lc",
rhyme: "rel_rhy",
spell: "sp",
synonym: "ml",
sounds: "rel_nry",
suggest: "suggest",
}
let word = await arg("Type a word and hit Enter:")
let typeArg = await arg(
"What would you like to find?",
Object.keys(typeMap)
)
let type = typeMap[typeArg]
word = word.replace(/ /g, "+")
let url = `https://api.datamuse.com/words?${type}=${word}&md=d`
if (typeArg == "suggest")
url = `https://api.datamuse.com/sug?s=${word}&md=d`
let response = await get(url)
let formattedWords = response.data.map(({ word, defs }) => {
let info = ""
if (defs) {
let [type, meaning] = defs[0].split("\t")
info = `- (${type}): ${meaning}`
}
return {
name: `${word}${info}`,
value: word,
}
})
let pickWord = await arg("Select to paste:", formattedWords)
setSelectedText(pickWord)