mirror of
https://github.com/cupcakearmy/cryptgeon.git
synced 2026-06-10 11:16:48 +00:00
move shared package to cli
This commit is contained in:
@@ -0,0 +1,90 @@
|
||||
import inquirer from 'inquirer'
|
||||
import { access, constants, writeFile } from 'node:fs/promises'
|
||||
import { basename, resolve } from 'node:path'
|
||||
import { AES, Hex } from 'occulto'
|
||||
import pretty from 'pretty-bytes'
|
||||
import { Adapters } from '../shared/adapters.js'
|
||||
import { API } from '../shared/api.js'
|
||||
|
||||
export async function download(url: URL, all: boolean, suggestedPassword?: string) {
|
||||
API.setOptions({ server: url.origin })
|
||||
const id = url.pathname.split('/')[2]
|
||||
const preview = await API.info(id).catch(() => {
|
||||
throw new Error('Note does not exist or is expired')
|
||||
})
|
||||
|
||||
// Password
|
||||
let password: string
|
||||
const derivation = preview?.meta.derivation
|
||||
if (derivation) {
|
||||
if (suggestedPassword) {
|
||||
password = suggestedPassword
|
||||
} else {
|
||||
const response = await inquirer.prompt([
|
||||
{
|
||||
type: 'password',
|
||||
message: 'Note password',
|
||||
name: 'password',
|
||||
},
|
||||
])
|
||||
password = response.password
|
||||
}
|
||||
} else {
|
||||
password = url.hash.slice(1)
|
||||
}
|
||||
|
||||
const key = derivation ? (await AES.derive(password, derivation))[0] : Hex.decode(password)
|
||||
const note = await API.get(id)
|
||||
|
||||
const couldNotDecrypt = new Error('Could not decrypt note. Probably an invalid password')
|
||||
switch (note.meta.type) {
|
||||
case 'file':
|
||||
const files = await Adapters.Files.decrypt(note.contents, key).catch(() => {
|
||||
throw couldNotDecrypt
|
||||
})
|
||||
if (!files) {
|
||||
throw new Error('No files found in note')
|
||||
}
|
||||
|
||||
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) throw new Error('No files selected')
|
||||
await Promise.all(
|
||||
selected.map(async (file) => {
|
||||
let filename = resolve(file.name)
|
||||
try {
|
||||
// If exists -> prepend timestamp to not overwrite the current file
|
||||
await access(filename, constants.R_OK)
|
||||
filename = resolve(`${Date.now()}-${file.name}`)
|
||||
} catch {}
|
||||
await writeFile(filename, file.contents)
|
||||
console.log(`Saved: ${basename(filename)}`)
|
||||
})
|
||||
)
|
||||
|
||||
break
|
||||
case 'text':
|
||||
const plaintext = await Adapters.Text.decrypt(note.contents, key).catch(() => {
|
||||
throw couldNotDecrypt
|
||||
})
|
||||
console.log(plaintext)
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
import { readFile, stat } from 'node:fs/promises'
|
||||
import { basename } from 'node:path'
|
||||
|
||||
import mime from 'mime'
|
||||
import { AES, Hex } from 'occulto'
|
||||
import { Adapters } from '../shared/adapters.js'
|
||||
import { API, FileDTO, Note, NoteMeta } from '../shared/api.js'
|
||||
|
||||
export type UploadOptions = Pick<Note, 'views' | 'expiration'> & { password?: string }
|
||||
|
||||
export async function upload(input: string | string[], options: UploadOptions): Promise<string> {
|
||||
const { password, ...noteOptions } = options
|
||||
const derived = options.password ? await AES.derive(options.password) : undefined
|
||||
const key = derived ? derived[0] : await AES.generateKey()
|
||||
|
||||
let contents: string
|
||||
let type: NoteMeta['type']
|
||||
if (typeof input === 'string') {
|
||||
contents = await Adapters.Text.encrypt(input, key)
|
||||
type = 'text'
|
||||
} else {
|
||||
const files: FileDTO[] = await Promise.all(
|
||||
input.map(async (path) => {
|
||||
const data = new Uint8Array(await readFile(path))
|
||||
const stats = await stat(path)
|
||||
const extension = path.substring(path.indexOf('.') + 1)
|
||||
const type = mime.getType(extension) ?? 'application/octet-stream'
|
||||
return {
|
||||
name: basename(path),
|
||||
size: stats.size,
|
||||
contents: data,
|
||||
type,
|
||||
} satisfies FileDTO
|
||||
})
|
||||
)
|
||||
contents = await Adapters.Files.encrypt(files, key)
|
||||
type = 'file'
|
||||
}
|
||||
|
||||
// Create the actual note and upload it.
|
||||
const note: Note = { ...noteOptions, contents, meta: { type, derivation: derived?.[1] } }
|
||||
const result = await API.create(note)
|
||||
let url = `${API.getOptions().server}/note/${result.id}`
|
||||
if (!derived) url += `#${Hex.encode(key)}`
|
||||
return url
|
||||
}
|
||||
Reference in New Issue
Block a user