refactor client api

This commit is contained in:
WheatCarrier 2023-08-18 08:20:55 +08:00
parent 0b0a5ad098
commit a654739090
18 changed files with 476 additions and 360 deletions

View File

@ -1,3 +1,4 @@
.vscode/
coverage/
node_modules/
dist/

View File

@ -4,6 +4,7 @@
"trailingComma": "all",
"singleQuote": true,
"semi": true,
"plugins": ["@trivago/prettier-plugin-sort-imports"],
"importOrder": ["^[^(\\.|src/)]", "^src/", "^[\\.]"],
"importOrderSeparation": true,
"importOrderSortSpecifiers": true

View File

@ -75,7 +75,6 @@ Tested WebDAV Clients:
$ tgfs cmd rm -r /some-folder
```
## Step by Step Guide to Set up config
### Automatically:
@ -96,14 +95,15 @@ A config file will be auto-generated when you run the program for the first time
2. There should be a message like "Channel created". Right click the message and copy the post link.
3. The format of the link should be like `https://t.me/c/1234567/1`, where `1234567` is the channel id. Copy the channel id to the config file (`telegram -> private_file_channel`)
## Config fields explanation
- telegram
- session_file: The file path to store the session data. If you want to use multiple accounts, you can set different session files for each account.
- login_timeout: Time to wait before login attempt aborts (in milliseconds).
- tgfs
- download
- porgress: Whether to show a progress bar when downloading files
- chunk_size_kb: The chunk size in KB when downloading files. Bigger chunk size means less requests.

View File

@ -26,7 +26,7 @@
"yargs": "^17.7.2"
},
"devDependencies": {
"@trivago/prettier-plugin-sort-imports": "^4.1.1",
"@trivago/prettier-plugin-sort-imports": "^4.2.0",
"@types/cli-progress": "^3.11.0",
"@types/express": "^4.17.17",
"@types/jest": "^29.5.2",
@ -37,7 +37,7 @@
"@types/yargs": "^17.0.24",
"jest": "^29.6.0",
"module-alias": "^2.2.3",
"prettier": "^2.8.8",
"prettier": "^3.0.2",
"supertest": "^6.3.3",
"ts-jest": "^29.1.1",
"ts-node": "^10.9.1",

View File

@ -1,313 +0,0 @@
import cliProgress from 'cli-progress';
import fs from 'fs';
import { Api, TelegramClient } from 'telegram';
import { IterDownloadFunction } from 'telegram/client/downloads';
import { CustomFile } from 'telegram/client/uploads';
import { FileLike } from 'telegram/define';
import { config } from '../config';
import { TechnicalError } from '../errors/base';
import { DirectoryIsNotEmptyError } from '../errors/path';
import { TGFSDirectory, TGFSFileRef } from '../model/directory';
import { TGFSFile } from '../model/file';
import { TGFSMetadata } from '../model/metadata';
import { validateName } from '../utils/validate-name';
export class Client {
private metadata: TGFSMetadata;
constructor(
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) {
return await this.client.sendMessage(this.privateChannelId, {
message,
});
}
private async getMessagesByIds(messageIds: number[]) {
return await this.client.getMessages(this.privateChannelId, {
ids: messageIds,
});
}
private async getObjectsByMessageIds(messageIds: number[]) {
return (await this.getMessagesByIds(messageIds)).map((message) =>
JSON.parse(message.text),
);
}
public async getFileInfo(fileRef: TGFSFileRef): Promise<TGFSFile> {
const file = await this.getFileFromFileRef(fileRef);
const versions = Object.values(file.versions);
const fileMessages = await this.getMessagesByIds(
versions.map((version) => version.messageId),
);
versions.forEach((version, i) => {
const fileMessage = fileMessages[i];
version.size = Number(fileMessage.document.size);
});
return file;
}
private async downloadMediaByMessageId(
file: { name: string; messageId: number },
withProgressBar?: boolean,
options?: IterDownloadFunction,
) {
const message = (await this.getMessagesByIds([file.messageId]))[0];
const fileSize = Number(message.document.size);
const chunkSize = config.tgfs.download.chunksize * 1024;
let pgBar: cliProgress.SingleBar;
if (withProgressBar) {
pgBar = new cliProgress.SingleBar({
format: `${file.name} [{bar}] {percentage}%`,
});
pgBar.start(fileSize, 0);
}
const buffer = Buffer.alloc(fileSize);
let i = 0;
for await (const chunk of this.client.iterDownload({
file: new Api.InputDocumentFileLocation({
id: message.document.id,
accessHash: message.document.accessHash,
fileReference: message.document.fileReference,
thumbSize: '',
}),
requestSize: chunkSize,
})) {
chunk.copy(buffer, i * chunkSize, 0, Number(chunk.length));
i += 1;
if (withProgressBar) {
pgBar.update(i * chunkSize);
}
}
return buffer;
}
public async downloadFileAtVersion(
fileRef: TGFSFileRef,
outputFile?: string | fs.WriteStream,
versionId?: string,
): Promise<Buffer | null> {
const tgfsFile = await this.getFileFromFileRef(fileRef);
const version = versionId
? tgfsFile.getVersion(versionId)
: tgfsFile.getLatest();
const res = await this.downloadMediaByMessageId(
{ messageId: version.messageId, name: tgfsFile.name },
true,
);
if (res instanceof Buffer) {
if (outputFile) {
if (outputFile instanceof fs.WriteStream) {
outputFile.write(res);
} else {
fs.writeFile(outputFile, res, (err) => {
if (err) {
throw err;
}
});
}
}
return res;
} else {
throw new TechnicalError(
`Downloaded file is not a buffer. ${this.privateChannelId}/${version.messageId}`,
);
}
}
private async getMetadata() {
const pinnedMessage = (
await this.client.getMessages(this.privateChannelId, {
filter: new Api.InputMessagesFilterPinned(),
})
)[0];
if (!pinnedMessage) {
return null;
}
const metadata = TGFSMetadata.fromObject(
JSON.parse(
String(
await this.downloadMediaByMessageId(
{ messageId: pinnedMessage.id, name: 'metadata.json' },
false,
),
),
),
);
metadata.msgId = pinnedMessage.id;
return metadata;
}
private async sendFile(file: FileLike) {
return await this.client.sendFile(this.privateChannelId, {
file,
workers: 16,
});
}
private async syncMetadata() {
this.metadata.syncWith(await this.getMetadata());
await this.updateMetadata();
}
private async updateMetadata() {
const buffer = Buffer.from(JSON.stringify(this.metadata.toObject()));
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 getRootDirectory() {
return this.metadata.dir;
}
public async createEmptyDirectory() {
this.metadata.dir = new TGFSDirectory('root', null);
await this.syncMetadata();
return this.metadata.dir;
}
public async createDirectoryUnder(name: string, where: TGFSDirectory) {
validateName(name);
const newDirectory = where.createChild(name);
await this.syncMetadata();
return newDirectory;
}
public async getFileFromFileRef(fileRef: TGFSFileRef) {
return TGFSFile.fromObject(
(await this.getObjectsByMessageIds([fileRef.getMessageId()]))[0],
);
}
public async newFileUnder(
name: string,
where: TGFSDirectory,
file: FileLike,
) {
validateName(name);
const uploadFileMsg = await this.sendFile(file);
const tgfsFile = new TGFSFile(name);
tgfsFile.addVersionFromFileMessage(uploadFileMsg);
const tgfsFileMsg = await this.send(JSON.stringify(tgfsFile.toObject()));
const tgfsFileRef = where.createFileRef(name, tgfsFileMsg);
await this.syncMetadata();
return tgfsFileRef;
}
public async updateFile(
tgfsFileRef: TGFSFileRef,
file: FileLike,
versionId?: string,
) {
const tgfsFile = await this.getFileFromFileRef(tgfsFileRef);
const uploadFileMsg = await this.sendFile(file);
if (!versionId) {
tgfsFile.addVersionFromFileMessage(uploadFileMsg);
} else {
const tgfsFileVersion = tgfsFile.getVersion(versionId);
tgfsFileVersion.messageId = uploadFileMsg.id;
tgfsFile.updateVersion(tgfsFileVersion);
}
this.client.editMessage(this.privateChannelId, {
message: tgfsFileRef.getMessageId(),
text: JSON.stringify(tgfsFile.toObject()),
});
await this.syncMetadata();
return tgfsFileRef;
}
public async putFileUnder(
name: string,
where: TGFSDirectory,
file: FileLike,
) {
const tgfsFileRef = where.findFiles([name])[0];
if (tgfsFileRef) {
return await this.updateFile(tgfsFileRef, file);
} else {
return await this.newFileUnder(name, where, file);
}
}
public async deleteFileAtVersion(tgfsFileRef: TGFSFileRef, version?: string) {
if (!version) {
tgfsFileRef.delete();
} else {
const tgfsFile = await this.getFileFromFileRef(tgfsFileRef);
tgfsFile.deleteVersion(version);
await this.client.editMessage(this.privateChannelId, {
message: tgfsFileRef.getMessageId(),
text: JSON.stringify(tgfsFile.toObject()),
});
}
await this.syncMetadata();
}
public async deleteEmptyDirectory(directory: TGFSDirectory) {
if (
directory.findChildren().length > 0 ||
directory.findFiles().length > 0
) {
throw new DirectoryIsNotEmptyError();
}
await this.deleteDirectory(directory);
}
public async deleteDirectory(directory: TGFSDirectory) {
directory.delete();
await this.syncMetadata();
}
}

View File

@ -0,0 +1,43 @@
import { TelegramClient } from 'telegram';
import { DirectoryIsNotEmptyError } from '../../errors/path';
import { TGFSDirectory } from '../../model/directory';
import { validateName } from '../../utils/validate-name';
import { MetaDataApi } from './metadata-api';
export class DirectoryApi extends MetaDataApi {
constructor(protected readonly client: TelegramClient) {
super(client);
}
public async createRootDirectory() {
await this.resetMetadata();
await this.syncMetadata();
return this.metadata.dir;
}
public async createDirectoryUnder(name: string, where: TGFSDirectory) {
validateName(name);
const newDirectory = where.createChild(name);
await this.syncMetadata();
return newDirectory;
}
public async deleteEmptyDirectory(directory: TGFSDirectory) {
if (
directory.findChildren().length > 0 ||
directory.findFiles().length > 0
) {
throw new DirectoryIsNotEmptyError();
}
await this.deleteDirectory(directory);
}
public async deleteDirectory(directory: TGFSDirectory) {
directory.delete();
await this.syncMetadata();
}
}

121
src/api/client/file-api.ts Normal file
View File

@ -0,0 +1,121 @@
import cliProgress from 'cli-progress';
import fs from 'fs';
import { Api, TelegramClient } from 'telegram';
import { IterDownloadFunction } from 'telegram/client/downloads';
import { FileLike } from 'telegram/define';
import { config } from '../../config';
import { TechnicalError } from '../../errors/base';
import { TGFSFileRef } from '../../model/directory';
import { TGFSFile } from '../../model/file';
import { MessageApi } from './message-api';
export class FileApi extends MessageApi {
constructor(protected readonly client: TelegramClient) {
super(client);
}
public async getFileInfo(fileRef: TGFSFileRef): Promise<TGFSFile> {
const file = await this.getFileFromFileRef(fileRef);
const versions = Object.values(file.versions);
const fileMessages = await this.getMessagesByIds(
versions.map((version) => version.messageId),
);
versions.forEach((version, i) => {
const fileMessage = fileMessages[i];
version.size = Number(fileMessage.document.size);
});
return file;
}
protected async downloadMediaByMessageId(
file: { name: string; messageId: number },
withProgressBar?: boolean,
options?: IterDownloadFunction,
) {
const message = (await this.getMessagesByIds([file.messageId]))[0];
const fileSize = Number(message.document.size);
const chunkSize = config.tgfs.download.chunksize * 1024;
let pgBar: cliProgress.SingleBar;
if (withProgressBar) {
pgBar = new cliProgress.SingleBar({
format: `${file.name} [{bar}] {percentage}%`,
});
pgBar.start(fileSize, 0);
}
const buffer = Buffer.alloc(fileSize);
let i = 0;
for await (const chunk of this.client.iterDownload({
file: new Api.InputDocumentFileLocation({
id: message.document.id,
accessHash: message.document.accessHash,
fileReference: message.document.fileReference,
thumbSize: '',
}),
requestSize: chunkSize,
})) {
chunk.copy(buffer, i * chunkSize, 0, Number(chunk.length));
i += 1;
if (withProgressBar) {
pgBar.update(i * chunkSize);
}
}
return buffer;
}
public async downloadFileAtVersion(
fileRef: TGFSFileRef,
outputFile?: string | fs.WriteStream,
versionId?: string,
): Promise<Buffer | null> {
const tgfsFile = await this.getFileFromFileRef(fileRef);
const version = versionId
? tgfsFile.getVersion(versionId)
: tgfsFile.getLatest();
const res = await this.downloadMediaByMessageId(
{ messageId: version.messageId, name: tgfsFile.name },
true,
);
if (res instanceof Buffer) {
if (outputFile) {
if (outputFile instanceof fs.WriteStream) {
outputFile.write(res);
} else {
fs.writeFile(outputFile, res, (err) => {
if (err) {
throw err;
}
});
}
}
return res;
} else {
throw new TechnicalError(
`Downloaded file is not a buffer. ${this.privateChannelId}/${version.messageId}`,
);
}
}
protected async sendFile(file: FileLike) {
return await this.client.sendFile(this.privateChannelId, {
file,
workers: 16,
});
}
public async getFileFromFileRef(fileRef: TGFSFileRef) {
const message = (await this.getMessagesByIds([fileRef.getMessageId()]))[0];
return TGFSFile.fromObject(JSON.parse(message.text));
}
}

95
src/api/client/index.ts Normal file
View File

@ -0,0 +1,95 @@
import { TelegramClient } from 'telegram';
import { FileLike } from 'telegram/define';
import { TGFSDirectory, TGFSFileRef } from '../../model/directory';
import { TGFSFile } from '../../model/file';
import { validateName } from '../../utils/validate-name';
import { DirectoryApi } from './directory-api';
export class Client extends DirectoryApi {
constructor(protected readonly client: TelegramClient) {
super(client);
}
public async init() {
await this.initMetadata();
if (!this.getRootDirectory()) {
await this.createRootDirectory();
}
}
public async newFileUnder(
name: string,
where: TGFSDirectory,
file: FileLike,
) {
validateName(name);
const uploadFileMsg = await this.sendFile(file);
const tgfsFile = new TGFSFile(name);
tgfsFile.addVersionFromFileMessage(uploadFileMsg);
const tgfsFileMsg = await this.send(JSON.stringify(tgfsFile.toObject()));
const tgfsFileRef = where.createFileRef(name, tgfsFileMsg);
await this.syncMetadata();
return tgfsFileRef;
}
public async updateFile(
tgfsFileRef: TGFSFileRef,
file: FileLike,
versionId?: string,
) {
const tgfsFile = await this.getFileFromFileRef(tgfsFileRef);
const uploadFileMsg = await this.sendFile(file);
if (!versionId) {
tgfsFile.addVersionFromFileMessage(uploadFileMsg);
} else {
const tgfsFileVersion = tgfsFile.getVersion(versionId);
tgfsFileVersion.messageId = uploadFileMsg.id;
tgfsFile.updateVersion(tgfsFileVersion);
}
this.client.editMessage(this.privateChannelId, {
message: tgfsFileRef.getMessageId(),
text: JSON.stringify(tgfsFile.toObject()),
});
await this.syncMetadata();
return tgfsFileRef;
}
public async putFileUnder(
name: string,
where: TGFSDirectory,
file: FileLike,
) {
const tgfsFileRef = where.findFiles([name])[0];
if (tgfsFileRef) {
return await this.updateFile(tgfsFileRef, file);
} else {
return await this.newFileUnder(name, where, file);
}
}
public async deleteFileAtVersion(tgfsFileRef: TGFSFileRef, version?: string) {
if (!version) {
tgfsFileRef.delete();
} else {
const tgfsFile = await this.getFileFromFileRef(tgfsFileRef);
tgfsFile.deleteVersion(version);
await this.client.editMessage(this.privateChannelId, {
message: tgfsFileRef.getMessageId(),
text: JSON.stringify(tgfsFile.toObject()),
});
}
await this.syncMetadata();
}
}

View File

@ -0,0 +1,21 @@
import { TelegramClient } from 'telegram';
import { config } from 'src/config';
export class MessageApi {
protected readonly privateChannelId = config.telegram.private_file_channel;
constructor(protected readonly client: TelegramClient) {}
protected async send(message: string) {
return await this.client.sendMessage(this.privateChannelId, {
message,
});
}
protected async getMessagesByIds(messageIds: number[]) {
return await this.client.getMessages(this.privateChannelId, {
ids: messageIds,
});
}
}

View File

@ -0,0 +1,73 @@
import { Api } from 'telegram';
import { CustomFile } from 'telegram/client/uploads';
import { TGFSDirectory } from '../../model/directory';
import { TGFSMetadata } from '../../model/metadata';
import { FileApi } from './file-api';
export class MetaDataApi extends FileApi {
protected metadata: TGFSMetadata;
protected async initMetadata() {
this.metadata = await this.getMetadata();
if (!this.metadata) {
this.metadata = new TGFSMetadata();
}
}
protected async resetMetadata() {
this.metadata.dir = new TGFSDirectory('root', null);
}
protected async getMetadata() {
const pinnedMessage = (
await this.client.getMessages(this.privateChannelId, {
filter: new Api.InputMessagesFilterPinned(),
})
)[0];
if (!pinnedMessage) {
return null;
}
const metadata = TGFSMetadata.fromObject(
JSON.parse(
String(
await this.downloadMediaByMessageId(
{ messageId: pinnedMessage.id, name: 'metadata.json' },
false,
),
),
),
);
metadata.msgId = pinnedMessage.id;
return metadata;
}
protected async syncMetadata() {
this.metadata.syncWith(await this.getMetadata());
await this.updateMetadata();
}
protected async updateMetadata() {
const buffer = Buffer.from(JSON.stringify(this.metadata.toObject()));
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 getRootDirectory() {
return this.metadata.dir;
}
}

View File

@ -22,7 +22,7 @@ export const login =
try {
await client.connect();
return new Client(client, config.telegram.private_file_channel);
return new Client(client);
} catch (err) {
Logger.error(err);
}
@ -39,5 +39,5 @@ export const login =
String(client.session.save()),
);
return new Client(client, config.telegram.private_file_channel);
return new Client(client);
};

View File

@ -1,9 +1,8 @@
import fs from 'fs';
import input from 'input';
import yaml from 'js-yaml';
import os from 'os';
import path from 'path';
import input from 'input';
import { loginAsUser } from './auth';
export const config: any = {};
@ -47,7 +46,9 @@ export const loadConfig = (configPath: string) => {
};
export const createConfig = async () => {
const createNow = await input.confirm('The config file is not found. Create a config file now?')
const createNow = await input.confirm(
'The config file is not found. Create a config file now?',
);
if (!createNow) {
process.exit(0);
@ -57,43 +58,59 @@ export const createConfig = async () => {
if (answer.trim().length > 0) {
return true;
} else {
return 'This field is mandatory!'
return 'This field is mandatory!';
}
}
};
const config: any = {};
const configPath = await input.text('Where do you want to save the config file', { default: path.join(process.cwd(), "config.yaml") });
const configPath = await input.text(
'Where do you want to save the config file',
{ default: path.join(process.cwd(), 'config.yaml') },
);
config.telegram = {};
console.log('\nGo to https://my.telegram.org/apps, follow the steps to log in and paste the App api_id and App api_hash here')
config.telegram.api_id = Number(await input.text('App api_id', { validate: validateNotEmpty }));
config.telegram.api_hash = await input.text('App api_hash', { validate: validateNotEmpty });
config.telegram.session_file = await input.text('Where do you want to save the session', { default: '~/.tgfs/account.session' });
console.log(
'\nGo to https://my.telegram.org/apps, follow the steps to log in and paste the App api_id and App api_hash here',
);
config.telegram.api_id = Number(
await input.text('App api_id', { validate: validateNotEmpty }),
);
config.telegram.api_hash = await input.text('App api_hash', {
validate: validateNotEmpty,
});
config.telegram.session_file = await input.text(
'Where do you want to save the session',
{ default: '~/.tgfs/account.session' },
);
console.log('\nCreate a PRIVATE channel and paste the channel id here');
config.telegram.private_file_channel = Number(await input.text('Channel to store the files', { validate: validateNotEmpty }));
config.telegram.private_file_channel = Number(
await input.text('Channel to store the files', {
validate: validateNotEmpty,
}),
);
config.tgfs = {
download: {
progress: 'true',
chunk_size_kb: 1024
}
}
chunk_size_kb: 1024,
},
};
config.webdav = {
host: '0.0.0.0',
port: 1900,
users: {
user: {
password: 'password'
}
}
}
password: 'password',
},
},
};
const yamlString = yaml.dump(config);
fs.writeFileSync(configPath, yamlString);
return configPath;
}
};

View File

@ -1,7 +1,10 @@
import { ErrorCodes } from './error-codes';
export class TechnicalError extends Error {
constructor(public readonly message: string, public readonly cause?: any) {
constructor(
public readonly message: string,
public readonly cause?: any,
) {
super(message);
}
}

View File

@ -1,13 +1,19 @@
import { BusinessError } from './base';
export class FileOrDirectoryAlreadyExistsError extends BusinessError {
constructor(public readonly name: string, public readonly cause?: string) {
constructor(
public readonly name: string,
public readonly cause?: string,
) {
super(`${name} already exists`, 'FILE_OR_DIR_ALREADY_EXISTS', cause);
}
}
export class FileOrDirectoryDoesNotExistError extends BusinessError {
constructor(public readonly name: string, public readonly cause?: string) {
constructor(
public readonly name: string,
public readonly cause?: string,
) {
super(
`No such file or directory: ${name}`,
'FILE_OR_DIR_DOES_NOT_EXIST',
@ -17,7 +23,10 @@ export class FileOrDirectoryDoesNotExistError extends BusinessError {
}
export class InvalidNameError extends BusinessError {
constructor(public readonly name: string, public readonly cause?: string) {
constructor(
public readonly name: string,
public readonly cause?: string,
) {
super(
`Invalid name: ${name}. Name cannot begin with -, and cannot contain /`,
'INVALID_NAME',
@ -27,7 +36,10 @@ export class InvalidNameError extends BusinessError {
}
export class RelativePathError extends BusinessError {
constructor(public readonly name: string, public readonly cause?: string) {
constructor(
public readonly name: string,
public readonly cause?: string,
) {
super(
`Relative path: ${name} is not supported. Path must start with /`,
'RELATIVE_PATH',

View File

@ -1,4 +1,5 @@
#!/usr/bin/env node
import fs from 'fs';
import { exit } from 'process';
import yargs from 'yargs';
import { hideBin } from 'yargs/helpers';
@ -13,7 +14,6 @@ import { runWebDAVServer } from './server/webdav';
import { runSync } from './sync';
import { Logger } from './utils/logger';
import { sleep } from './utils/sleep';
import fs from 'fs';
const { argv }: any = yargs(hideBin(process.argv))
.option('config', {

View File

@ -2,8 +2,8 @@ import * as fs from 'fs';
import yargs from 'yargs/yargs';
import { Client } from 'src/api';
import { createDir } from 'src/api/ops/create-dir';
import { uploadBytes } from 'src/api/ops';
import { createDir } from 'src/api/ops/create-dir';
import { list } from 'src/api/ops/list';
import { removeDir } from 'src/api/ops/remove-dir';
import { Executor } from 'src/commands/executor';

View File

@ -11,6 +11,9 @@ import { MockMessages } from './mock-messages';
jest.mock('src/config', () => {
return {
config: {
telegram: {
private_file_channel: 'mock-private-file-channel',
},
tgfs: {
download: {
chunksize: 1024,
@ -115,8 +118,6 @@ jest.mock('telegram', () => {
export const createClient = async () => {
const client = new Client(
new TelegramClient('mock-session', 0, 'mock-api-hash', {}),
'mock-private-channel-id',
'mock-public-channel-id',
);
await client.init();
return client;

View File

@ -10,13 +10,21 @@
"@jridgewell/gen-mapping" "^0.3.0"
"@jridgewell/trace-mapping" "^0.3.9"
"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.16.7", "@babel/code-frame@^7.22.5":
"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.22.5":
version "7.22.5"
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.22.5.tgz#234d98e1551960604f1246e6475891a570ad5658"
integrity sha512-Xmwn266vad+6DAqEB2A6V/CcZVp62BbwVmcOJc2RPuwih1kw02TjQvWVWlcKGbBPd+8/0V5DEkOcizRGYsspYQ==
dependencies:
"@babel/highlight" "^7.22.5"
"@babel/code-frame@^7.16.7":
version "7.22.10"
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.22.10.tgz#1c20e612b768fefa75f6e90d6ecb86329247f0a3"
integrity sha512-/KKIMG4UEL35WmI9OlvMhurwtytjvXoFcGNrOvyG9zIzA8YmPjVtIZUf7b05+TPO7G7/GEmLHDaoCgACHl9hhA==
dependencies:
"@babel/highlight" "^7.22.10"
chalk "^2.4.2"
"@babel/compat-data@^7.22.6":
version "7.22.6"
resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.22.6.tgz#15606a20341de59ba02cd2fcc5086fcbe73bf544"
@ -52,7 +60,17 @@
jsesc "^2.5.1"
source-map "^0.5.0"
"@babel/generator@^7.17.3", "@babel/generator@^7.22.5", "@babel/generator@^7.7.2":
"@babel/generator@^7.17.3":
version "7.22.10"
resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.22.10.tgz#c92254361f398e160645ac58831069707382b722"
integrity sha512-79KIf7YiWjjdZ81JnLujDRApWtl7BxTqWD88+FFdQEIOG8LJ0etDOM7CXuIgGJa55sGOwZVwuEsaLEm0PJ5/+A==
dependencies:
"@babel/types" "^7.22.10"
"@jridgewell/gen-mapping" "^0.3.2"
"@jridgewell/trace-mapping" "^0.3.17"
jsesc "^2.5.1"
"@babel/generator@^7.22.5", "@babel/generator@^7.7.2":
version "7.22.5"
resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.22.5.tgz#1e7bf768688acfb05cf30b2369ef855e82d984f7"
integrity sha512-+lcUbnTRhd0jOewtFSedLyiPsD5tswKkbgcezOqqWFUVNEwoUTlpPOBmvhG7OXWLR4jMdv0czPGH5XbflnD1EA==
@ -157,6 +175,15 @@
"@babel/traverse" "^7.22.6"
"@babel/types" "^7.22.5"
"@babel/highlight@^7.22.10":
version "7.22.10"
resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.22.10.tgz#02a3f6d8c1cb4521b2fd0ab0da8f4739936137d7"
integrity sha512-78aUtVcT7MUscr0K5mIEnkwxPE0MaxkR5RxRwuHaQ+JuU5AmTPhY+do2mdzVTnIJJpyBglql2pehuBIWHug+WQ==
dependencies:
"@babel/helper-validator-identifier" "^7.22.5"
chalk "^2.4.2"
js-tokens "^4.0.0"
"@babel/highlight@^7.22.5":
version "7.22.5"
resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.22.5.tgz#aa6c05c5407a67ebce408162b7ede789b4d22031"
@ -166,11 +193,16 @@
chalk "^2.0.0"
js-tokens "^4.0.0"
"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.17.3", "@babel/parser@^7.20.5", "@babel/parser@^7.20.7", "@babel/parser@^7.22.5", "@babel/parser@^7.22.6":
"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.20.7", "@babel/parser@^7.22.5", "@babel/parser@^7.22.6":
version "7.22.6"
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.22.6.tgz#201f8b47be20c76c7c5743b9c16129760bf9a975"
integrity sha512-EIQu22vNkceq3LbjAq7knDf/UmtI2qbcNI8GRBlijez6TpQLvSodJPYfydQmNA5buwkxxxa/PVI44jjYZ+/cLw==
"@babel/parser@^7.17.3", "@babel/parser@^7.20.5":
version "7.22.10"
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.22.10.tgz#e37634f9a12a1716136c44624ef54283cabd3f55"
integrity sha512-lNbdGsQb9ekfsnjFGhEiF4hfFqGgfOP3H3d27re3n+CGhNuTSUEQdfWk556sTLNTloczcdM5TYF2LhzmDQKyvQ==
"@babel/plugin-syntax-async-generators@^7.8.4":
version "7.8.4"
resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz#a983fb1aeb2ec3f6ed042a210f640e90e786fe0d"
@ -318,7 +350,7 @@
"@babel/helper-validator-identifier" "^7.16.7"
to-fast-properties "^2.0.0"
"@babel/types@^7.0.0", "@babel/types@^7.17.0", "@babel/types@^7.20.7", "@babel/types@^7.22.5", "@babel/types@^7.3.3":
"@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.22.5", "@babel/types@^7.3.3":
version "7.22.5"
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.22.5.tgz#cd93eeaab025880a3a47ec881f4b096a5b786fbe"
integrity sha512-zo3MIHGOkPOfoRXitsgHLjEXmlDaD/5KU1Uzuc9GNiZPhSqVxVRtxuPaSBZDsYZ9qV88AjtMtWW7ww98loJ9KA==
@ -327,6 +359,15 @@
"@babel/helper-validator-identifier" "^7.22.5"
to-fast-properties "^2.0.0"
"@babel/types@^7.17.0", "@babel/types@^7.22.10":
version "7.22.10"
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.22.10.tgz#4a9e76446048f2c66982d1a989dd12b8a2d2dc03"
integrity sha512-obaoigiLrlDZ7TUQln/8m4mSqIW2QFeOrCQc9r+xsaHGNoplVNYlRVpsfE8Vj35GEm2ZH4ZhrNYogs/3fj85kg==
dependencies:
"@babel/helper-string-parser" "^7.22.5"
"@babel/helper-validator-identifier" "^7.22.5"
to-fast-properties "^2.0.0"
"@bcoe/v8-coverage@^0.2.3":
version "0.2.3"
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
@ -626,10 +667,10 @@
dependencies:
"@sinonjs/commons" "^3.0.0"
"@trivago/prettier-plugin-sort-imports@^4.1.1":
version "4.1.1"
resolved "https://registry.yarnpkg.com/@trivago/prettier-plugin-sort-imports/-/prettier-plugin-sort-imports-4.1.1.tgz#71c3c1ae770c3738b6fc85710714844477574ffd"
integrity sha512-dQ2r2uzNr1x6pJsuh/8x0IRA3CBUB+pWEW3J/7N98axqt7SQSm+2fy0FLNXvXGg77xEDC7KHxJlHfLYyi7PDcw==
"@trivago/prettier-plugin-sort-imports@^4.2.0":
version "4.2.0"
resolved "https://registry.yarnpkg.com/@trivago/prettier-plugin-sort-imports/-/prettier-plugin-sort-imports-4.2.0.tgz#b240366f9e2bda8e14edb18b14ea084e0ec25968"
integrity sha512-YBepjbt+ZNBVmN3ev1amQH3lWCmHyt5qTbLCp/syXJRu/Kw2koXh44qayB1gMRxcL/gV8egmjN5xWSrYyfUtyw==
dependencies:
"@babel/generator" "7.17.7"
"@babel/parser" "^7.20.5"
@ -1186,7 +1227,7 @@ chalk@^1.0.0, chalk@^1.1.1:
strip-ansi "^3.0.0"
supports-color "^2.0.0"
chalk@^2.0.0:
chalk@^2.0.0, chalk@^2.4.2:
version "2.4.2"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==
@ -2809,10 +2850,10 @@ pkg-dir@^4.2.0:
dependencies:
find-up "^4.0.0"
prettier@^2.8.8:
version "2.8.8"
resolved "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz"
integrity sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==
prettier@^3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.0.2.tgz#78fcecd6d870551aa5547437cdae39d4701dca5b"
integrity sha512-o2YR9qtniXvwEZlOKbveKfDQVyqxbEIWn48Z8m3ZJjBjcCmUy3xZGIv+7AkaeuaTr6yPXJjwv07ZWlsWbEy1rQ==
pretty-format@^29.0.0, pretty-format@^29.6.0:
version "29.6.0"