Skip to content

Commit

Permalink
feat(): library init
Browse files Browse the repository at this point in the history
  • Loading branch information
Max Markin committed Apr 22, 2024
1 parent 736e064 commit 27a25cc
Show file tree
Hide file tree
Showing 14 changed files with 776 additions and 99 deletions.
2 changes: 2 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[*]
tab_width = 2
59 changes: 31 additions & 28 deletions .eslintrc.js
Original file line number Diff line number Diff line change
@@ -1,30 +1,33 @@
module.exports = {
parser: '@typescript-eslint/parser',
parserOptions: {
project: 'tsconfig.json',
sourceType: 'module',
},
plugins: ['@typescript-eslint/eslint-plugin'],
extends: [
'plugin:@typescript-eslint/eslint-recommended',
'plugin:@typescript-eslint/recommended',
'prettier',
parser: '@typescript-eslint/parser',
parserOptions: {
project: 'tsconfig.json',
sourceType: 'module',
},
plugins: ['@typescript-eslint/eslint-plugin'],
extends: [
'plugin:@typescript-eslint/eslint-recommended',
'plugin:@typescript-eslint/recommended',
'prettier',
],
root: true,
env: {
node: true,
jest: true,
},
rules: {
'@typescript-eslint/interface-name-prefix': 'off',
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-use-before-define': 'off',
'@typescript-eslint/no-non-null-assertion': 'off',
'@typescript-eslint/ban-types': [
'error',
{
types: {
Function: false,
},
},
],
root: true,
env: {
node: true,
jest: true,
},
rules: {
'@typescript-eslint/interface-name-prefix': 'off',
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-use-before-define': 'off',
'@typescript-eslint/no-non-null-assertion': 'off',
'@typescript-eslint/ban-types': ['error', {
'types': {
'Function': false,
}
}],
},
};
},
};
3 changes: 2 additions & 1 deletion .prettierrc
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"trailingComma": "all",
"arrowParens": "always",
"singleQuote": true
"singleQuote": true,
"tabWidth": 2
}
Binary file modified .yarn/install-state.gz
Binary file not shown.
2 changes: 2 additions & 0 deletions lib/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './telegram-tl-client.module';
export * from './telegram-tl-client.decorator';
110 changes: 110 additions & 0 deletions lib/telegram-tl-client-events.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import { InjectTelegramClient, TelegramEvent } from './telegram-tl-client.decorator';
import { TELEGRAM_TL_CLIENT_EVENT_LISTENER_METADATA } from './telegram-tl-client.constants';
import { Module, OnModuleInit } from '@nestjs/common';
import { Api, TelegramClient } from 'telegram';
import { Module as ModuleType } from '@nestjs/core/injector/module';
import {
ContextIdFactory,
DiscoveryService,
MetadataScanner,
ModuleRef,
Reflector,
} from '@nestjs/core';
import { Injector } from '@nestjs/core/injector/injector';
import { NewMessage, NewMessageEvent } from 'telegram/events';
import { EventCommon } from 'telegram/events/common';

@Module({
providers: [DiscoveryService, MetadataScanner],
})
export class TelegramClientEventsAccessorModule implements OnModuleInit {
private readonly injector = new Injector();

constructor(
@InjectTelegramClient()
protected readonly client: TelegramClient,
protected readonly reflector: Reflector,
protected readonly discoveryService: DiscoveryService,
protected readonly metadataScanner: MetadataScanner,
protected readonly moduleRef: ModuleRef,
) { }

async onModuleInit() {
const providers = [...this.discoveryService.getProviders()].filter(
(_) => _.instance && !_.isAlias,
);
for (const wrapper of providers) {
const { instance } = wrapper;
const prototype = Object.getPrototypeOf(instance) || {};
const methods = this.metadataScanner.getAllMethodNames(prototype);
const isRequestScoped = !wrapper.isDependencyTreeStatic();
for (const method of methods) {
await this.subscribeIfListener(
instance,
method,
isRequestScoped,
wrapper.host as ModuleType,
);
}
}
}

async subscribeIfListener(
instance: Record<string, any>,
methodKey: string,
isRequestedScope: boolean,
moduleRef: ModuleType,
): Promise<void> {
const metadatas = this.reflector.get<{ event: TelegramEvent }[]>(
TELEGRAM_TL_CLIENT_EVENT_LISTENER_METADATA,
instance[methodKey],
);
if (!metadatas) return;

const obj = metadatas[0];
if (!obj.event) return;

if (isRequestedScope) {
this.registerScoped({
listenerMethodKey: methodKey,
moduleRef,
instance,
listenEvent: obj.event,
});
} else {
const cb = (event: EventCommon) => {
instance[methodKey].call(instance, event);
};
this.client.removeEventHandler(cb, obj.event);
this.client.addEventHandler(cb, obj.event);
if (this.client.connected) {
await this.client.getMe();
}
}
}

private registerScoped(context: {
listenerMethodKey: string;
moduleRef: ModuleType;
instance: Record<string, any>;
listenEvent: any;
}) {
const { listenerMethodKey, moduleRef, instance } = context;
const cb = async (event: NewMessageEvent) => {
const contextId = ContextIdFactory.create();
this.moduleRef.registerRequestByContextId(event, contextId);

const contextInstance = await this.injector.loadPerContext(
instance,
moduleRef,
moduleRef.providers,
contextId,
);

return contextInstance[listenerMethodKey].call(contextInstance, event);
};

this.client.removeEventHandler(cb, context.listenEvent);
this.client.addEventHandler(cb, context.listenEvent);
}
}
7 changes: 7 additions & 0 deletions lib/telegram-tl-client.constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export const TELEGRAM_TL_CLIENT_TOKEN = Symbol('TELEGRAM_TL_CLIENT_TOKEN');
export const TELEGRAM_TL_CLIENT_MODULE = Symbol('TELEGRAM_TL_CLIENT_MODULE');
export const TELEGRAM_TL_CLIENT_MODULE_OPTIONS = Symbol(
'TELEGRAM_TL_CLIENT_MODULE_OPTIONS',
);
export const TELEGRAM_TL_CLIENT_EVENT_LISTENER_METADATA =
'TELEGRAM_TL_CLIENT_EVENT_LISTENER_METADATA';
106 changes: 106 additions & 0 deletions lib/telegram-tl-client.core-module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import { createClient, ModuleOptions } from './telegram-tl-client.core';
import { InjectTelegramClient } from './telegram-tl-client.decorator';
import { createTelegramClientProvider } from './telegram-tl-client.provider';
import {
TELEGRAM_TL_CLIENT_MODULE_OPTIONS,
TELEGRAM_TL_CLIENT_TOKEN,
} from './telegram-tl-client.constants';
import { TelegramClient } from 'telegram';
import {
DynamicModule,
Global,
Module,
ModuleMetadata,
Provider,
Type,
} from '@nestjs/common';

export interface TelegramClientOptionsFactory {
createTelegramClientOptions(): Promise<ModuleOptions> | ModuleOptions;
}

export interface TelegramClientAsyncOptions
extends Pick<ModuleMetadata, 'imports'> {
inject?: any[];
useClass?: Type<TelegramClientOptionsFactory>;
useExisting?: Type<TelegramClientOptionsFactory>;
useFactory?: (...args: any[]) => Promise<ModuleOptions> | ModuleOptions;
}

@Global()
@Module({})
export class TelegramClientCoreModule {
constructor(
@InjectTelegramClient()
protected readonly telegramClient: TelegramClient,
) {}

public static forRoot(options: ModuleOptions): DynamicModule {
const provider = createTelegramClientProvider(options);

return {
exports: [provider],
module: TelegramClientCoreModule,
providers: [provider],
};
}

public static forRootAsync(
options: TelegramClientAsyncOptions,
): DynamicModule {
const provider: Provider = {
inject: [TELEGRAM_TL_CLIENT_MODULE_OPTIONS],
provide: TELEGRAM_TL_CLIENT_TOKEN,
useFactory: (options: ModuleOptions) => createClient(options),
};

return {
exports: [provider],
imports: options.imports,
module: TelegramClientCoreModule,
providers: [...this.createAsyncProvider(options), provider],
};
}

public static createAsyncProvider(
options: TelegramClientAsyncOptions,
): Provider[] {
if (options.useExisting || options.useFactory) {
return [this.createAsyncOptionsProvider(options)];
}

const useClass = options.useClass as Type<TelegramClientOptionsFactory>;

return [
this.createAsyncOptionsProvider(options),
{
provide: useClass,
useClass,
},
];
}

private static createAsyncOptionsProvider(
options: TelegramClientAsyncOptions,
): Provider {
if (options.useFactory) {
return {
inject: options.inject || [],
provide: TELEGRAM_TL_CLIENT_MODULE_OPTIONS,
useFactory: options.useFactory,
};
}

const inject = [
(options.useClass ||
options.useExisting) as Type<TelegramClientOptionsFactory>,
];

return {
provide: TELEGRAM_TL_CLIENT_MODULE_OPTIONS,
useFactory: async (optionsFactory: TelegramClientOptionsFactory) =>
await optionsFactory.createTelegramClientOptions(),
inject,
};
}
}
30 changes: 30 additions & 0 deletions lib/telegram-tl-client.core.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { TelegramClient } from 'telegram';
import { TelegramClientParams } from 'telegram/client/telegramBaseClient';
import { StringSession } from 'telegram/sessions';

export type ModuleOptions = {
session?: string;
apiId: number;
apiHash: string;
clientParams: TelegramClientParams;
};

const createClient = async (options: ModuleOptions) => {
const client = new TelegramClient(
new StringSession(options.session),
options.apiId,
options.apiHash,
{
...options.clientParams,
autoReconnect: true,
},
);

if (options.session) {
await client.connect();
}

return client;
};

export { createClient };
29 changes: 29 additions & 0 deletions lib/telegram-tl-client.decorator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import {
TELEGRAM_TL_CLIENT_EVENT_LISTENER_METADATA,
TELEGRAM_TL_CLIENT_TOKEN,
} from './telegram-tl-client.constants';
import { Inject } from '@nestjs/common';
import { extendArrayMetadata } from '@nestjs/common/utils/extend-metadata.util';
import { NewMessage } from 'telegram/events';
import { Album } from 'telegram/events/Album';
import { CallbackQuery } from 'telegram/events/CallbackQuery';
import { DeletedMessage } from 'telegram/events/DeletedMessage';

export type TelegramEvent = NewMessage | Album | CallbackQuery | DeletedMessage;

export function InjectTelegramClient() {
return Inject(TELEGRAM_TL_CLIENT_TOKEN);
}

export function OnTelegramClientEvent(event: TelegramEvent): MethodDecorator {
const decoratorFactory = (target: any, key?: any, descriptor?: any) => {
extendArrayMetadata(
TELEGRAM_TL_CLIENT_EVENT_LISTENER_METADATA,
[{ event }],
descriptor.value,
);
return descriptor;
};
decoratorFactory.KEY = TELEGRAM_TL_CLIENT_EVENT_LISTENER_METADATA;
return decoratorFactory;
}
32 changes: 32 additions & 0 deletions lib/telegram-tl-client.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { DynamicModule, Module } from '@nestjs/common';
import { ModuleOptions } from './telegram-tl-client.core';
import {
TelegramClientAsyncOptions,
TelegramClientCoreModule,
} from './telegram-tl-client.core-module';
import { TelegramClientEventsAccessorModule } from './telegram-tl-client-events.module';

@Module({})
export class TelegramClientModule {
public static forRoot(options: ModuleOptions): DynamicModule {
return {
module: TelegramClientModule,
imports: [
TelegramClientCoreModule.forRoot(options),
TelegramClientEventsAccessorModule,
],
};
}

public static forRootAsync(
options: TelegramClientAsyncOptions,
): DynamicModule {
return {
module: TelegramClientModule,
imports: [
TelegramClientCoreModule.forRootAsync(options),
TelegramClientEventsAccessorModule,
],
};
}
}
Loading

0 comments on commit 27a25cc

Please sign in to comment.