|
#!/usr/bin/env bun |
|
import { $ } from "bun"; |
|
import { readdir } from "node:fs/promises"; |
|
import { createHash } from "node:crypto"; |
|
|
|
// Parse arguments |
|
const [sourceUrl, reason, imageUrl] = Bun.argv.slice(2); |
|
|
|
if (!sourceUrl) { |
|
console.error("Usage: dinspo.ts <source_url> [reason] [image_url]"); |
|
process.exit(1); |
|
} |
|
|
|
// Get current date and time |
|
const now = new Date(); |
|
const date = now.toISOString().split('T')[0]; // YYYY-MM-DD |
|
const hours = String(now.getHours()).padStart(2, '0'); |
|
const minutes = String(now.getMinutes()).padStart(2, '0'); |
|
const time = `${hours}${minutes}`; // HHMM |
|
|
|
// Generate filename from reason |
|
function generateFilename(reason: string | undefined, date: string, time: string): string { |
|
if (!reason) { |
|
return `Design inspiration ${date} ${time}`; |
|
} |
|
let filename = reason.substring(0, 50); |
|
// Sentence case (capitalize first letter) |
|
filename = filename.charAt(0).toUpperCase() + filename.slice(1); |
|
// Remove trailing period |
|
filename = filename.replace(/\.$/, ''); |
|
return filename; |
|
} |
|
|
|
const summaryFilename = generateFilename(reason, date, time); |
|
|
|
// Hash a file |
|
async function hashFile(path: string): Promise<string> { |
|
const bytes = await Bun.file(path).bytes(); |
|
return createHash('sha256').update(bytes).digest('hex'); |
|
} |
|
|
|
// Check for duplicates in media directory |
|
async function isDuplicate(filePath: string): Promise<string | null> { |
|
const newHash = await hashFile(filePath); |
|
|
|
try { |
|
const files = await readdir('media'); |
|
|
|
for (const file of files) { |
|
const existingPath = `media/${file}`; |
|
try { |
|
const existingHash = await hashFile(existingPath); |
|
if (existingHash === newHash) { |
|
return file; |
|
} |
|
} catch (err) { |
|
// Skip files that can't be read |
|
continue; |
|
} |
|
} |
|
} catch (err) { |
|
console.error("Error reading media directory:", err); |
|
} |
|
|
|
return null; |
|
} |
|
|
|
// Main logic |
|
async function main() { |
|
let mediaFilename: string; |
|
let mediaExtension: string; |
|
let tmpPath: string; |
|
let finalPath: string; |
|
|
|
try { |
|
if (imageUrl) { |
|
// Download image to /tmp |
|
const tmpImage = `/tmp/dinspo-${Date.now()}.tmp`; |
|
console.log(`π₯ Downloading image from ${imageUrl}...`); |
|
|
|
const curlResult = await $`curl -sL ${imageUrl} -o ${tmpImage}`.nothrow(); |
|
if (curlResult.exitCode !== 0) { |
|
console.error("β Failed to download image"); |
|
process.exit(1); |
|
} |
|
|
|
// Convert to WebP in temp |
|
mediaExtension = 'webp'; |
|
mediaFilename = `${summaryFilename}.${mediaExtension}`; |
|
tmpPath = `/tmp/dinspo-${Date.now()}.webp`; |
|
|
|
console.log(`π Converting to WebP...`); |
|
const cwebpResult = await $`cwebp ${tmpImage} -o ${tmpPath}`.nothrow(); |
|
|
|
if (cwebpResult.exitCode !== 0) { |
|
console.error("β Failed to convert to WebP"); |
|
process.exit(1); |
|
} |
|
|
|
// Clean up temp image |
|
await $`rm ${tmpImage}`.nothrow(); |
|
|
|
} else { |
|
// Download video to temp with yt-dlp |
|
mediaExtension = 'mp4'; |
|
mediaFilename = `${summaryFilename}.${mediaExtension}`; |
|
tmpPath = `/tmp/dinspo-${Date.now()}.mp4`; |
|
|
|
console.log(`π₯ Downloading video from ${sourceUrl}...`); |
|
const ytdlpResult = await $`yt-dlp ${sourceUrl} -o ${tmpPath}`.nothrow(); |
|
|
|
if (ytdlpResult.exitCode !== 0) { |
|
console.error("β Failed to download video"); |
|
process.exit(1); |
|
} |
|
} |
|
|
|
// Check for duplicates before moving to final location |
|
console.log(`π Checking for duplicates...`); |
|
const duplicate = await isDuplicate(tmpPath); |
|
|
|
if (duplicate) { |
|
console.log(`β οΈ Duplicate found: ${duplicate}`); |
|
console.log(`ποΈ Removing downloaded file...`); |
|
await $`rm ${tmpPath}`.nothrow(); |
|
process.exit(1); |
|
} |
|
|
|
// Move to final location |
|
finalPath = `media/${mediaFilename}`; |
|
await $`mv ${tmpPath} ${finalPath}`.nothrow(); |
|
|
|
// Read template |
|
const template = await Bun.file('templates/design inspiration template.md').text(); |
|
|
|
// Create note content |
|
const reasonField = reason ? `reason: ${reason}\n` : ''; |
|
const noteContent = `--- |
|
tags: |
|
- design-inspiration |
|
created: ${date} |
|
time: ${time} |
|
source: ${sourceUrl} |
|
${reasonField}media: "[[${mediaFilename}]]" |
|
--- |
|
|
|
![[${mediaFilename}]] |
|
`; |
|
|
|
// Write note |
|
const notePath = `${summaryFilename}.md`; |
|
await Bun.write(notePath, noteContent); |
|
|
|
console.log(`β
Created note: ${notePath}`); |
|
console.log(`π Saved media: ${finalPath}`); |
|
|
|
} catch (error) { |
|
console.error("β Error:", error); |
|
process.exit(1); |
|
} |
|
} |
|
|
|
main(); |