add --all option, stdin and password option

This commit is contained in:
Niccolo Borgioli 2023-05-25 10:16:44 +02:00
parent 2e89007c83
commit b43b802221
No known key found for this signature in database
GPG Key ID: D93C615F75EE4F0B
3 changed files with 51 additions and 34 deletions

View File

@ -7,7 +7,7 @@ import pretty from 'pretty-bytes'
import { exit } from './utils' import { exit } from './utils'
export async function download(url: URL) { export async function download(url: URL, all: boolean, suggestedPassword?: string) {
setBase(url.origin) setBase(url.origin)
const id = url.pathname.split('/')[2] const id = url.pathname.split('/')[2]
const preview = await info(id).catch(() => exit('Note does not exist or is expired')) const preview = await info(id).catch(() => exit('Note does not exist or is expired'))
@ -16,14 +16,18 @@ export async function download(url: URL) {
let password: string let password: string
const derivation = preview?.meta.derivation const derivation = preview?.meta.derivation
if (derivation) { if (derivation) {
const response = await inquirer.prompt([ if (suggestedPassword) {
{ password = suggestedPassword
type: 'password', } else {
message: 'Note password', const response = await inquirer.prompt([
name: 'password', {
}, type: 'password',
]) message: 'Note password',
password = response.password name: 'password',
},
])
password = response.password
}
} else { } else {
password = url.hash.slice(1) password = url.hash.slice(1)
} }
@ -39,25 +43,29 @@ export async function download(url: URL) {
exit('No files found in note') exit('No files found in note')
return return
} }
const { names } = await inquirer.prompt([
{
type: 'checkbox',
message: 'What files should be saved?',
name: 'names',
choices: files.map((file) => ({
value: file.name,
name: `${file.name} - ${file.type} - ${pretty(file.size, { binary: true })}`,
checked: true,
})),
},
])
const selected = files.filter((file) => names.includes(file.name)) let selected: typeof files
if (all) {
selected = files
} else {
const { names } = await inquirer.prompt([
{
type: 'checkbox',
message: 'What files should be saved?',
name: 'names',
choices: files.map((file) => ({
value: file.name,
name: `${file.name} - ${file.type} - ${pretty(file.size, { binary: true })}`,
checked: true,
})),
},
])
selected = files.filter((file) => names.includes(file.name))
}
if (!selected.length) exit('No files selected') if (!selected.length) exit('No files selected')
await Promise.all( await Promise.all(
files.map(async (file) => { selected.map(async (file) => {
let filename = resolve(file.name) let filename = resolve(file.name)
try { try {
// If exists -> prepend timestamp to not overwrite the current file // If exists -> prepend timestamp to not overwrite the current file
@ -68,6 +76,7 @@ export async function download(url: URL) {
console.log(`Saved: ${basename(filename)}`) console.log(`Saved: ${basename(filename)}`)
}) })
) )
break break
case 'text': case 'text':
const plaintext = await Adapters.Text.decrypt(note.contents, key).catch(couldNotDecrypt) const plaintext = await Adapters.Text.decrypt(note.contents, key).catch(couldNotDecrypt)

View File

@ -15,6 +15,7 @@ const server = new Option('-s --server <url>', 'the cryptgeon server to use').de
const files = new Argument('<file...>', 'Files to be sent').argParser(parseFile) const files = new Argument('<file...>', 'Files to be sent').argParser(parseFile)
const text = new Argument('<text>', 'Text content of the note') const text = new Argument('<text>', 'Text content of the note')
const password = new Option('-p --password <string>', 'manually set a password') const password = new Option('-p --password <string>', 'manually set a password')
const all = new Option('-a --all', 'Save all files without prompt').default(false)
const url = new Argument('<url>', 'The url to open') const url = new Argument('<url>', 'The url to open')
const views = new Option('-v --views <number>', 'Amount of views before getting destroyed').argParser(parseNumber) const views = new Option('-v --views <number>', 'Amount of views before getting destroyed').argParser(parseNumber)
const minutes = new Option('-m --minutes <number>', 'Minutes before the note expires').argParser(parseNumber) const minutes = new Option('-m --minutes <number>', 'Minutes before the note expires').argParser(parseNumber)
@ -86,10 +87,13 @@ send
program program
.command('open') .command('open')
.addArgument(url) .addArgument(url)
.addOption(password)
.addOption(all)
.action(async (note, options) => { .action(async (note, options) => {
try { try {
const url = new URL(note) const url = new URL(note)
await download(url) options.password ||= await getStdin()
await download(url, options.all, options.password)
} catch { } catch {
exit('Invalid URL') exit('Invalid URL')
} }

View File

@ -2,19 +2,23 @@ export function getStdin(timeout: number = 10): Promise<string> {
return new Promise<string>((resolve, reject) => { return new Promise<string>((resolve, reject) => {
// Store the data from stdin in a buffer // Store the data from stdin in a buffer
let buffer = '' let buffer = ''
process.stdin.on('data', (d) => (buffer += d.toString())) let t: NodeJS.Timeout
const dataHandler = (d: Buffer) => (buffer += d.toString())
const endHandler = () => {
clearTimeout(t)
resolve(buffer.trim())
}
// Stop listening for data after the timeout, otherwise hangs indefinitely // Stop listening for data after the timeout, otherwise hangs indefinitely
const t = setTimeout(() => { t = setTimeout(() => {
process.stdin.destroy() process.stdin.removeListener('data', dataHandler)
process.stdin.removeListener('end', endHandler)
process.stdin.pause()
resolve('') resolve('')
}, timeout) }, timeout)
// Listen for end and error events process.stdin.on('data', dataHandler)
process.stdin.on('end', () => { process.stdin.on('end', endHandler)
clearTimeout(t)
resolve(buffer.trim())
})
process.stdin.on('error', reject)
}) })
} }