Files
g3n/bin/not3.ts
bipproduction a8a700f09a tambahannya
2025-09-24 12:23:17 +08:00

276 lines
7.4 KiB
TypeScript
Executable File

#!/usr/bin/env bun
import * as fs from 'fs';
import * as path from 'path';
import * as os from 'os';
import { execSync } from 'child_process';
const CONFIG_FILE = path.join(os.homedir(), '.note.conf');
interface Config {
TOKEN?: string;
REPO?: string;
URL?: string;
}
// --- Check dependencies ---
function checkDependency(cmd: string): boolean {
try {
execSync(`command -v ${cmd}`, { stdio: 'ignore' });
return true;
} catch {
return false;
}
}
function checkDependencies(): void {
const deps = ['curl', 'jq'];
for (const cmd of deps) {
if (!checkDependency(cmd)) {
console.error(`❌ Missing dependency: ${cmd} (please install it)`);
process.exit(1);
}
}
}
// --- Config management ---
function createDefaultConfig(): void {
const defaultConfig = `TOKEN=
REPO=
URL=https://cld-dkr-makuro-seafile.wibudev.com/api2
`;
fs.writeFileSync(CONFIG_FILE, defaultConfig);
}
function editConfig(): void {
if (!fs.existsSync(CONFIG_FILE)) {
createDefaultConfig();
}
const editor = process.env.EDITOR || 'vim';
try {
execSync(`${editor} "${CONFIG_FILE}"`, { stdio: 'inherit' });
} catch (error) {
console.error('❌ Failed to open editor');
process.exit(1);
}
}
function loadConfig(): Config {
if (!fs.existsSync(CONFIG_FILE)) {
console.error(`⚠️ Config file not found at ${CONFIG_FILE}`);
console.error('Run: node note.ts config to create/edit it.');
process.exit(1);
}
const configContent = fs.readFileSync(CONFIG_FILE, 'utf8');
const config: Config = {};
for (const line of configContent.split('\n')) {
const trimmed = line.trim();
if (trimmed && !trimmed.startsWith('#')) {
const [key, ...valueParts] = trimmed.split('=');
if (key && valueParts.length > 0) {
const value = valueParts.join('=');
config[key as keyof Config] = value;
}
}
}
if (!config.TOKEN || !config.REPO) {
console.error(`❌ Config invalid. Please set TOKEN=... and REPO=... inside ${CONFIG_FILE}`);
process.exit(1);
}
// Set default URL if not provided
if (!config.URL) {
console.error(`❌ Config invalid. Please set URL=... inside ${CONFIG_FILE}`);
process.exit(1);
}
return config;
}
// --- HTTP helpers ---
function curlCommand(config: Config, options: string): string {
try {
return execSync(`curl -s -H "Authorization: Token ${config.TOKEN}" ${options}`,
{ encoding: 'utf8' });
} catch (error) {
console.error('❌ Request failed');
process.exit(1);
}
}
function curlCommandSilent(config: Config, options: string): void {
try {
execSync(`curl -s -H "Authorization: Token ${config.TOKEN}" ${options} >/dev/null`,
{ stdio: 'ignore' });
} catch (error) {
console.error('❌ Request failed');
process.exit(1);
}
}
// --- Commands ---
function listFiles(config: Config): void {
const response = curlCommand(config, `"${config.URL}/${config.REPO}/dir/?p=/"`);
try {
const files = JSON.parse(response);
for (const file of files) {
console.log(file.name);
}
} catch (error) {
console.error('❌ Failed to parse response');
process.exit(1);
}
}
function catFile(config: Config, fileName: string): void {
const downloadUrlResponse = curlCommand(config, `"${config.URL}/${config.REPO}/file/?p=/${fileName}"`);
const downloadUrl = downloadUrlResponse.replace(/"/g, '');
const content = curlCommand(config, `"${downloadUrl}"`);
console.log(content);
}
function uploadFile(config: Config, localFile: string, remoteFile?: string): void {
if (!fs.existsSync(localFile)) {
console.error(`❌ File not found: ${localFile}`);
process.exit(1);
}
const remoteName = remoteFile || path.basename(localFile);
const uploadUrlResponse = curlCommand(config, `"${config.URL}/${config.REPO}/upload-link/?p=/"`);
const uploadUrl = uploadUrlResponse.replace(/"/g, '');
try {
execSync(`curl -s -H "Authorization: Token ${config.TOKEN}" \
-F "file=@${localFile}" \
-F "filename=${remoteName}" \
-F "parent_dir=/" \
"${uploadUrl}" >/dev/null`, { stdio: 'ignore' });
console.log(`✅ Uploaded ${localFile}${remoteName}`);
} catch (error) {
console.error('❌ Upload failed');
process.exit(1);
}
}
function removeFile(config: Config, fileName: string): void {
curlCommandSilent(config, `-X DELETE "${config.URL}/${config.REPO}/file/?p=/${fileName}"`);
console.log(`🗑️ Removed ${fileName}`);
}
function moveFile(config: Config, oldName: string, newName: string): void {
try {
execSync(`curl -s -X POST -H "Authorization: Token ${config.TOKEN}" \
-d "operation=rename" \
-d "newname=${newName}" \
"${config.URL}/${config.REPO}/file/?p=/${oldName}" >/dev/null`, { stdio: 'ignore' });
console.log(`✏️ Renamed ${oldName}${newName}`);
} catch (error) {
console.error('❌ Rename failed');
process.exit(1);
}
}
function downloadFile(config: Config, remoteFile: string, localFile?: string): void {
const localName = localFile || remoteFile;
const downloadUrlResponse = curlCommand(config, `"${config.URL}/${config.REPO}/file/?p=/${remoteFile}"`);
const downloadUrl = downloadUrlResponse.replace(/"/g, '');
try {
execSync(`curl -s -H "Authorization: Token ${config.TOKEN}" "${downloadUrl}" -o "${localName}"`,
{ stdio: 'ignore' });
console.log(`⬇️ Downloaded ${remoteFile}${localName}`);
} catch (error) {
console.error('❌ Download failed');
process.exit(1);
}
}
function showHelp(): void {
console.log(`note - simple CLI for Seafile notes
Usage:
node note.ts ls List files
node note.ts cat <file> Show file content
node note.ts cp <local> [remote] Upload file
node note.ts rm <remote> Remove file
node note.ts mv <old> <new> Rename/move file
node note.ts get <remote> [local] Download file
node note.ts config Edit config (~/.note.conf)
Config (~/.note.conf):
TOKEN=your_seafile_token
REPO=repos/<repo-id>
URL=your_seafile_url/api2`);
}
// --- Main ---
function not3(): void {
const args = process.argv.slice(2);
const cmd = args[0] || 'help';
// Handle config command without dependency check
if (cmd === 'config') {
editConfig();
return;
}
// Check dependencies for other commands
checkDependencies();
// Load config for other commands
const config = loadConfig();
switch (cmd) {
case 'ls':
listFiles(config);
break;
case 'cat':
if (!args[1]) {
console.error('Usage: node note.ts cat <file>');
process.exit(1);
}
catFile(config, args[1]);
break;
case 'cp':
if (!args[1]) {
console.error('Usage: node note.ts cp <local_file> [remote_file]');
process.exit(1);
}
uploadFile(config, args[1], args[2]);
break;
case 'rm':
if (!args[1]) {
console.error('Usage: node note.ts rm <remote_file>');
process.exit(1);
}
removeFile(config, args[1]);
break;
case 'mv':
if (!args[1] || !args[2]) {
console.error('Usage: node note.ts mv <old_name> <new_name>');
process.exit(1);
}
moveFile(config, args[1], args[2]);
break;
case 'get':
if (!args[1]) {
console.error('Usage: node note.ts get <remote_file> [local_file]');
process.exit(1);
}
downloadFile(config, args[1], args[2]);
break;
case 'help':
default:
showHelp();
break;
}
}