Ramiro Araujo

Ramiro Araujo

// Name: Merge / Split Alfred clipboard
// Description: Merge or split clipboard content using Alfred app's clipboard
import "@johnlindquist/kit"
const Database = await npm("better-sqlite3");
const databasePath = home('Library/Application Support/Alfred/Databases/clipboard.alfdb')
if (!await pathExists(databasePath)) {
notify("Alfred clipboard database not found" )
exit()
}
const db = new Database(databasePath);
const queryClipboard = async (sql, params) => {
const stmt = db.prepare(sql);
return sql.trim().toUpperCase().startsWith("SELECT") ? stmt.all(params) : stmt.run(params);
};
const getMergedClipboards = async (count, separator) => {
const sql = `SELECT item FROM clipboard WHERE dataType = 0 order by ROWID desc LIMIT ?`;
const clipboards = await queryClipboard(sql, [count]);
return clipboards.map(row => row.item.trim()).join(separator);
};
const writeMergedClipboards = async (mergedText) => {
await clipboard.writeText(mergedText);
};
const getSplitClipboard = async (separator, trim) => {
const currentClipboard = await clipboard.readText();
return currentClipboard.split(separator).map(item => trim ? item.trim() : item);
};
const writeSplitClipboard = async (splitText) => {
const lastTsSql = `SELECT ts FROM clipboard WHERE dataType = 0 ORDER BY ts DESC LIMIT 1`;
const lastTsResult = await queryClipboard(lastTsSql, []);
let lastTs = lastTsResult.length > 0 ? Number(lastTsResult[0].ts) : 0;
const insertSql = `INSERT INTO clipboard (item, ts, dataType, app, appPath) VALUES (?, ?, 0, 'Kit', '/Applications/Kit.app')`;
for (let i = 0; i < splitText.length - 1; i++) {
lastTs += 1;
await queryClipboard(insertSql, [splitText[i], lastTs]);
}
await clipboard.writeText(splitText[splitText.length - 1]);
};
const action = await arg("Choose action", ["Merge", "Split"]);
if (action === "Merge") {
const count = await arg({
placeholder: "Enter the number of clipboard items to merge",
}, async (input) => {
if (isNaN(Number(input)) || input.length === 0)return ''
return md(`<pre>${await getMergedClipboards(input, '\n')}</pre>`)
})
const separator = await arg({
placeholder: "Enter the separator for merging",
}, async (input) => {
if (input === '\\n') input = '\n'
return md(`<pre>${await getMergedClipboards(count, input)}</pre>`)
})
const mergedText = await getMergedClipboards(count, separator);
await writeMergedClipboards(mergedText);
await notify("Merged clipboard items and copied to clipboard");
} else {
// const separator = await arg("Enter the separator for splitting");
const separator = await arg({
placeholder: "Enter the separator for splitting",
}, async (input) => {
if (input === '\\n') input = '\n'
let strings = await getSplitClipboard(input, true);
return md(`<pre>${strings.join('\n')}</pre>`)
})
const trim = await arg("Trim clipboard content?", ["Yes", "No"]);
const splitText = await getSplitClipboard(separator, trim === "Yes");
await writeSplitClipboard(splitText);
await notify("Split clipboard content and stored in Alfred clipboard");
}
db.close();
// Name: Type Clipboard
// Description: Get the content of the clipboard and "keystroke" it without pasting
// Shortcut: ctrl+cmd+alt+shift+v
import "@johnlindquist/kit"
const clipboardText = await clipboard.readText()
if (clipboardText.length > 1000) {
await notify("Clipboard content is too long")
exit()
}
await applescript(String.raw`
set chars to count (get the clipboard)
tell application "System Events"
delay 0.1
keystroke (get the clipboard)
end tell
`)
// Name: Open in WhatsApp
import "@johnlindquist/kit"
//get the text from the clipboard
let text = await clipboard.readText();
//normalize the text
text = text.replace(/[-() ]/g, "");
//validate if valid phone number
if (!text.match(/^(\+\d{12,13})|(\d{10,11})$/)) {
notify("Invalid phone number");
exit()
}
//assume Argentina if no country code since that's where I'm from
if (!text.startsWith("+")) {
text = "+54" + text;
}
//open in WhatsApp
open(`https://wa.me/${text}`);
// Name: convert selected images
import "@johnlindquist/kit";
// Grab selected files
const files = (await getSelectedFile()).split("\n");
// Set up whitelist of formats
const supportedFormats = [".heic", ".png", ".gif", ".webp", ".jpg", ".jpeg"];
// Filter files based on supported formats
const selectedFiles = files.filter(file =>
supportedFormats.some(format => file.toLowerCase().endsWith(format))
);
// Notify if no files are selected
if (!selectedFiles.length) {
await notify("No supported files selected");
exit();
}
const convertHeic = await npm("heic-convert");
const sharp = await npm("sharp");
// Select the output format
const outputFormat = await arg("Choose an output format", [
"jpg",
"png",
"webp",
]);
const getUniquePath = async (outputPath, suffix = "") => {
if (await isFile(outputPath)) {
const name = path.basename(outputPath, path.extname(outputPath));
const newName = `${name}${suffix}-copy${path.extname(outputPath)}`;
const newPath = path.join(path.dirname(outputPath), newName);
return await getUniquePath(newPath, `${suffix}-copy`);
} else {
return outputPath;
}
};
// Convert selected files to the chosen output format using appropriate libraries
for (const file of selectedFiles) {
const content = await readFile(file);
const name = path.basename(file).split(".")[0];
const outputPath = path.join(path.dirname(file), name + `.${outputFormat}`);
const uniqueOutputPath = await getUniquePath(outputPath);
if (file.toLowerCase().endsWith(".heic")) {
const formatMap = {
jpg: "JPEG",
png: "PNG",
}
const outputBuffer = await convertHeic({
buffer: content,
format: formatMap[outputFormat],
quality: 0.5,
});
await writeFile(uniqueOutputPath, outputBuffer);
} else {
const sharpImage = sharp(content);
switch (outputFormat) {
case "jpg":
await sharpImage.jpeg({ quality: 40 }).toFile(uniqueOutputPath);
break;
case "png":
await sharpImage.png().toFile(uniqueOutputPath);
break;
case "webp":
await sharpImage.webp({ quality: 40 }).toFile(uniqueOutputPath);
break;
}
}
}
await notify(`Converted selected files to ${outputFormat.toUpperCase()}`);
// Name: Emoji Search
// Description: Search and copy emoji to clipboard using SQLite database
import "@johnlindquist/kit"
const Database = await npm("better-sqlite3")
const databaseFile = projectPath("db", "emoji-search-emojilib.db")
const emojilibURL = "https://raw.githubusercontent.com/muan/emojilib/main/dist/emoji-en-US.json"
const createDatabase = async () => {
const response = await get(emojilibURL)
const emojiData = response.data as Record<string, string[]>
//create db and table
const db = new Database(databaseFile)
db.exec(`CREATE TABLE IF NOT EXISTS emojis
(emoji TEXT PRIMARY KEY, name TEXT, keywords TEXT, used INTEGER DEFAULT 0)`)
//populate with data from emojilib
for (const [emojiChar, emojiInfo] of Object.entries(emojiData)) {
const description = emojiInfo[0]
const tags = emojiInfo.slice(1).join(', ')
db.prepare("INSERT OR REPLACE INTO emojis VALUES (?, ?, ?, 0)").run(emojiChar, description, tags)
}
db.close()
};
if (!await pathExists(databaseFile)) {
await createDatabase()
}
const db = new Database(databaseFile)
const queryEmojis = async () => {
const sql = "SELECT emoji, name, keywords FROM emojis ORDER BY used DESC"
const stmt = db.prepare(sql)
return stmt.all()
}
const snakeToHuman = (text) => {
return text
.split('_')
.map((word, index) => index === 0 ? word.charAt(0).toUpperCase() + word.slice(1) : word)
.join(' ')
}
const emojis = await queryEmojis()
const selectedEmoji = await arg("Search Emoji", emojis.map(({ emoji, name, keywords }) => ({
name: `${snakeToHuman(name)} ${keywords}`,
html: md(`<div class="flex items-center">
<span class="text-5xl">${emoji}</span>
<div class="flex flex-col ml-2">
<span class="text-2xl" style="color: lightgrey">${snakeToHuman(name)}</span>
<small style="color: darkgrey">${keywords}</small>
</div>
</div>`),
value: emoji,
})))
await clipboard.writeText(selectedEmoji)
// Update the 'used' count
const updateSql = "UPDATE emojis SET used = used + 1 WHERE emoji = ?"
const updateStmt = db.prepare(updateSql)
updateStmt.run(selectedEmoji)
db.close()