add ls command

This commit is contained in:
WheatCarrier 2023-07-08 08:50:06 +08:00
parent c73760f02b
commit 624f10f90b
14 changed files with 184 additions and 40 deletions

View File

@ -9,6 +9,8 @@
"start": "yarn build:dev && node dist/src/index.js",
"start:dev": "yarn build:dev && nodemon --watch 'src/**/*.ts' --exec 'ts-node' src/index.ts",
"start:prod": "yarn build && node dist/src/index.js",
"start:cmd": "yarn build:dev && node dist/src/cmd.js",
"start:cmd:prod": "yarn build && node dist/src/cmd.js",
"test": "yarn jest"
},
"author": "Wheat Carrier",

View File

@ -15,13 +15,17 @@ export class Client {
metadata: TGFSMetadata;
constructor(
public readonly client: TelegramClient,
protected readonly client: TelegramClient,
private readonly privateChannelId: string,
private readonly publicChannelId?: string,
) {}
public async init() {
this.metadata = await this.getMetadata();
if (!this.metadata) {
this.metadata = new TGFSMetadata();
await this.createEmptyDirectory();
}
}
private async send(message: string) {
@ -42,6 +46,11 @@ export class Client {
);
}
public async getFileInfo(fileRef: TGFSFileRef): Promise<TGFSFile> {
const file = await this.getFileFromFileRef(fileRef);
return file;
}
private async downloadMediaById(
messageIds: number,
downloadParams?: DownloadMediaInterface,
@ -88,8 +97,7 @@ export class Client {
)[0];
if (!pinnedMessage) {
await this.createEmptyDirectory();
return this.metadata;
return null;
}
const metadata = TGFSMetadata.fromObject(
@ -114,16 +122,24 @@ export class Client {
private async updateMetadata() {
const buffer = Buffer.from(JSON.stringify(this.metadata.toObject()));
return await this.client.editMessage(this.privateChannelId, {
message: this.metadata.msgId,
file: new CustomFile('metadata.json', buffer.length, '', buffer),
});
const file = new CustomFile('metadata.json', buffer.length, '', buffer);
if (this.metadata.msgId) {
return await this.client.editMessage(this.privateChannelId, {
message: this.metadata.msgId,
file,
});
} else {
const message = await this.client.sendMessage(this.privateChannelId, {
file,
});
this.metadata.msgId = message.id;
await this.client.pinMessage(this.privateChannelId, message.id);
return message;
}
}
public async createEmptyDirectory() {
this.metadata = new TGFSMetadata();
this.metadata.dir = new TGFSDirectory('root', null, []);
await this.syncMetadata();
return this.metadata.dir;

View File

@ -1,6 +1,6 @@
import * as input from 'input';
import { Logger } from 'src/utils/logger';
import { Logger } from '../utils/logger';
import { login } from './login';
export const loginAsUser = login(async (client) => {

2
src/auth/index.ts Normal file
View File

@ -0,0 +1,2 @@
export { loginAsBot } from './as-bot';
export { loginAsUser } from './as-user';

42
src/cmd.ts Normal file
View File

@ -0,0 +1,42 @@
import { hideBin } from 'yargs/helpers';
import yargs from 'yargs/yargs';
import { loginAsBot } from './auth';
import { Executor } from './commands/executor';
const parse = () => {
const argv: any = yargs(hideBin(process.argv))
.command('ls <path>', 'list all files and directories', {
path: {
type: 'string',
description: 'path to list',
},
})
.command('mkdir <path>', 'create a directory', {
path: {
type: 'string',
description: 'path to create',
},
})
.demandCommand(1, 'You need at least one command before moving on')
.help().argv;
return argv;
};
(async () => {
try {
const client = await loginAsBot();
await client.init();
const executor = new Executor(client);
const argv = parse();
await executor.execute(argv);
} catch (err) {
console.log(err.message);
} finally {
process.exit(0);
}
})();

20
src/commands/executor.ts Normal file
View File

@ -0,0 +1,20 @@
import { Client } from '../api';
import { ls } from './ls';
import { mkdir } from './mkdir';
export class Executor {
constructor(private readonly client: Client) {}
async execute(argv: any) {
let rsp: any;
if (argv._[0] === 'ls') {
rsp = await ls(this.client)(argv.path);
} else if (argv._[0] === 'mkdir') {
rsp = await mkdir(this.client)(argv.path);
} else if (argv._[0] === '') {
}
console.log(rsp);
}
}

View File

@ -1,3 +0,0 @@
export function get(messageId: string) {
}

View File

@ -1,21 +0,0 @@
import { hideBin } from 'yargs/helpers';
import yargs from 'yargs/yargs';
import { ls } from './ls';
const argv: any = yargs(hideBin(process.argv))
.command('ls', 'list all files', (yargs) => {
return yargs.option('path', {
describe: 'Path to list files from',
demandOption: true,
type: 'string',
});
})
.demandCommand(1, 'You need at least one command before moving on')
.help().argv;
if (argv._[0] === 'ls') {
ls(argv.path);
} else if (argv._[0] === 'get') {
} else if (argv._[0] === 'send') {
}

View File

@ -1,5 +1,44 @@
import { PathLike } from 'fs';
export const ls = (path: PathLike) => {
console.log(path);
import { Client } from '../api';
import { FileOrDirectoryDoesNotExistError } from '../errors/directory';
import { TGFSFileRef } from '../model/directory';
import { navigateToDir } from './navigate-to-dir';
const fileInfo = async (client: Client, fileRef: TGFSFileRef) => {
const info = await client.getFileInfo(fileRef);
const head = `${info.name}, ${Object.keys(info.versions).length} versions`;
const versions = Object.entries(info.versions).map(([id, version]) => {
return `${id}: updated at ${version.updatedAt}`;
});
return [head, ...versions].join('\n');
};
export const ls = (client: Client) => async (path: PathLike) => {
const parts = path
.toString()
.split('/')
.filter((part) => part !== '');
let dir = await navigateToDir(client)(
parts.slice(0, parts.length - 1).join('/'),
);
let nextDir = dir;
if (parts.length > 0) {
nextDir = dir.children.find((d) => d.name === parts[parts.length - 1]);
}
if (nextDir) {
return nextDir.children
.map((c) => c.name)
.concat(dir.files?.map((f) => f.name))
.join(' ');
} else {
const nextFile = dir.files?.find((f) => f.name === parts[parts.length - 1]);
if (nextFile) {
return fileInfo(client, nextFile);
} else {
throw new FileOrDirectoryDoesNotExistError(path.toString());
}
}
};

13
src/commands/mkdir.ts Normal file
View File

@ -0,0 +1,13 @@
import { Client } from '../api';
import { navigateToDir } from './navigate-to-dir';
export const mkdir = (client: Client) => async (path: string) => {
const parts = path.toString().split('/');
const dir = await navigateToDir(client)(
parts.slice(0, parts.length - 1).join('/'),
);
await client.createDirectoryUnder(parts[parts.length - 1], dir);
return `created ${path}`;
};

View File

@ -0,0 +1,27 @@
import { TGFSDirectory } from 'src/model/directory';
import { Client } from '../api';
import { FileOrDirectoryDoesNotExistError } from '../errors/directory';
export const navigateToDir = (client: Client) => async (path: string) => {
const pathParts = path
.toString()
.split('/')
.filter((part) => part !== '');
let currentDirectory = client.metadata.dir;
for (const pathPart of pathParts) {
const directory = currentDirectory.children?.find(
(d: TGFSDirectory) => d.name === pathPart,
);
if (!directory) {
throw new FileOrDirectoryDoesNotExistError(path);
}
currentDirectory = directory;
}
return currentDirectory;
};

View File

View File

@ -1,10 +1,16 @@
import { BusinessError } from './base';
export class DirectoryAlreadyExistsError extends BusinessError {
constructor(public readonly dirName: string, public readonly cause?: string) {
super(`Directory ${dirName} already exists`, 'DIR_ALREADY_EXISTS', cause);
}
}
export class FileOrDirectoryDoesNotExistError extends BusinessError {
constructor(public readonly dirName: string, public readonly cause?: string) {
super(
`Directory ${dirName} already exists`,
'DIRECTORY_ALREADY_EXISTS',
`No such file or directory: ${dirName}`,
'FILE_OR_DIR_DOES_NOT_EXIST',
cause,
);
}

View File

@ -2,5 +2,6 @@ export type ErrorCodes =
| 'UNKNOWN'
| 'EMPTY_FILE'
| 'FILE_IS_EMPTY'
| 'DIRECTORY_ALREADY_EXISTS'
| 'FILE_ALREADY_EXISTS';
| 'DIR_ALREADY_EXISTS'
| 'FILE_ALREADY_EXISTS'
| 'FILE_OR_DIR_DOES_NOT_EXIST';