diff --git a/packages/dispatcher/scripts/generate.js b/packages/dispatcher/scripts/generate.js new file mode 100644 index 00000000..3145febd --- /dev/null +++ b/packages/dispatcher/scripts/generate.js @@ -0,0 +1,272 @@ +const fs = require('fs') +const path = require('path') +const prettier = require('prettier') +const { + snakeToCamel, + camelToPascal, + camelToSnake, +} = require('../../tl/scripts/common') + +function parseUpdateTypes() { + const lines = fs + .readFileSync(path.join(__dirname, 'update-types.txt'), 'utf-8') + .split('\n') + .map((it) => it.trim()) + .filter((it) => it && it[0] !== '#') + + const ret = [] + + for (const line of lines) { + const m = line.match(/^([a-z_]+)(?:: ([a-zA-Z]+))? = ([a-zA-Z]+)$/) + if (!m) throw new Error(`invalid syntax: ${line}`) + ret.push({ + typeName: m[1], + handlerTypeName: m[2] || camelToPascal(snakeToCamel(m[1])), + updateType: m[3], + funcName: m[2] + ? m[2][0].toLowerCase() + m[2].substr(1) + : snakeToCamel(m[1]), + }) + } + + return ret +} + +function replaceSections(filename, sections) { + let lines = fs + .readFileSync(path.join(__dirname, '../src', filename), 'utf-8') + .split('\n') + + const findMarker = (marker) => { + const idx = lines.findIndex((line) => line.trim() === `// ${marker}`) + if (idx === -1) throw new Error(marker + ' not found') + return idx + } + + for (const [name, content] of Object.entries(sections)) { + const start = findMarker(`begin-${name}`) + const end = findMarker(`end-${name}`) + + if (start > end) throw new Error('begin is after end') + + lines.splice(start + 1, end - start - 1, content) + } + + fs.writeFileSync(path.join(__dirname, '../src', filename), lines.join('\n')) +} + +const types = parseUpdateTypes() +console.log(types) + +async function formatFile(filename) { + const targetFile = path.join(__dirname, '../src/', filename) + const prettierConfig = await prettier.resolveConfig(targetFile) + let fullSource = await fs.promises.readFile(targetFile, 'utf-8') + fullSource = await prettier.format(fullSource, { + ...(prettierConfig || {}), + filepath: targetFile, + }) + await fs.promises.writeFile(targetFile, fullSource) +} + +function toSentence(type, stype = 'inline') { + const name = camelToSnake(type.handlerTypeName) + .toLowerCase() + .replace(/_/g, ' ') + + if (stype === 'inline') { + return `${name[0].match(/[aeiouy]/i) ? 'an' : 'a'} ${name} handler` + } else { + return `${name[0].toUpperCase()}${name.substr(1)} handler` + } +} + +function generateBuilders() { + const lines = [] + const imports = ['UpdateHandler'] + + types.forEach((type) => { + imports.push(`${type.handlerTypeName}Handler`) + + if (type.updateType === 'IGNORE') { + lines.push(` + /** + * Create ${toSentence(type)} + * + * @param handler ${toSentence(type, 'full')} + */ + export function ${type.funcName}( + handler: ${type.handlerTypeName}Handler['callback'] + ): ${type.handlerTypeName}Handler + + /** + * Create ${toSentence(type)} with a filter + * + * @param filter Predicate to check the update against + * @param handler ${toSentence(type, 'full')} + */ + export function ${type.funcName}( + filter: ${type.handlerTypeName}Handler['check'], + handler: ${type.handlerTypeName}Handler['callback'] + ): ${type.handlerTypeName}Handler + + /** @internal */ + export function ${type.funcName}(filter: any, handler?: any): ${ + type.handlerTypeName + }Handler { + return _create('${type.typeName}', filter, handler) + } +`) + } else { + lines.push(` + /** + * Create ${toSentence(type)} + * + * @param handler ${toSentence(type, 'full')} + */ + export function ${type.funcName}( + handler: ${type.handlerTypeName}Handler['callback'] + ): ${type.handlerTypeName}Handler + + /** + * Create ${toSentence(type)} with a filter + * + * @param filter Update filter + * @param handler ${toSentence(type, 'full')} + */ + export function ${type.funcName}( + filter: UpdateFilter<${type.updateType}, Mod>, + handler: ${type.handlerTypeName}Handler< + filters.Modify<${type.updateType}, Mod> + >['callback'] + ): ${type.handlerTypeName}Handler + + export function ${type.funcName}( + filter: any, + handler?: any + ): ${type.handlerTypeName}Handler { + return _create('${type.typeName}', filter, handler) + } +`) + } + }) + + replaceSections('builders.ts', { + codegen: lines.join('\n'), + 'codegen-imports': + 'import {\n' + + imports.map((i) => ` ${i},\n`).join('') + + "} from './handler'", + }) +} + +function generateHandler() { + const lines = [] + const names = ['RawUpdateHandler'] + + // imports must be added manually because yeah + + types.forEach((type) => { + if (type.updateType === 'IGNORE') return + + lines.push( + `export type ${type.handlerTypeName}Handler = ParsedUpdateHandler<'${type.typeName}', T>` + ) + names.push(`${type.handlerTypeName}Handler`) + }) + + replaceSections('handler.ts', { + codegen: + lines.join('\n') + + '\n\nexport type UpdateHandler = \n' + + names.map((i) => ` | ${i}\n`).join(''), + }) +} + +function generateDispatcher() { + const lines = [] + const imports = ['UpdateHandler'] + + types.forEach((type) => { + imports.push(`${type.handlerTypeName}Handler`) + + if (type.updateType === 'IGNORE') { + lines.push(` + /** + * Register ${toSentence(type)} without any filters + * + * @param handler ${toSentence(type, 'full')} + * @param group Handler group index + */ + on${type.handlerTypeName}(handler: ${type.handlerTypeName}Handler['callback'], group?: number): void + + /** + * Register ${toSentence(type)} with a filter + * + * @param filter Update filter function + * @param handler ${toSentence(type, 'full')} + * @param group Handler group index + */ + on${type.handlerTypeName}( + filter: ${type.handlerTypeName}Handler['check'], + handler: ${type.handlerTypeName}Handler['callback'], + group?: number + ): void + + /** @internal */ + on${type.handlerTypeName}(filter: any, handler?: any, group?: number): void { + this._addKnownHandler('${type.funcName}', filter, handler, group) + } +`) + } else { + lines.push(` + /** + * Register ${toSentence(type)} without any filters + * + * @param handler ${toSentence(type, 'full')} + * @param group Handler group index + * @internal + */ + on${type.handlerTypeName}(handler: ${type.handlerTypeName}Handler['callback'], group?: number): void + + /** + * Register ${toSentence(type)} with a filter + * + * @param filter Update filter + * @param handler ${toSentence(type, 'full')} + * @param group Handler group index + */ + on${type.handlerTypeName}( + filter: UpdateFilter<${type.updateType}, Mod>, + handler: ${type.handlerTypeName}Handler>['callback'], + group?: number + ): void + + /** @internal */ + on${type.handlerTypeName}(filter: any, handler?: any, group?: number): void { + this._addKnownHandler('${type.funcName}', filter, handler, group) + } +`) + } + }) + + replaceSections('dispatcher.ts', { + codegen: lines.join('\n'), + 'codegen-imports': + 'import {\n' + + imports.map((i) => ` ${i},\n`).join('') + + "} from './handler'", + }) +} + +async function main() { + generateBuilders() + generateHandler() + generateDispatcher() + + await formatFile('builders.ts') + await formatFile('handler.ts') + await formatFile('dispatcher.ts') +} + +main().catch(console.error) diff --git a/packages/dispatcher/scripts/update-types.txt b/packages/dispatcher/scripts/update-types.txt new file mode 100644 index 00000000..bd0fd529 --- /dev/null +++ b/packages/dispatcher/scripts/update-types.txt @@ -0,0 +1,8 @@ +# format: type_name[: handler_type_name] = update_type +# IGNORE as update_type disables filters modification +raw: RawUpdate = IGNORE +new_message = Message +edit_message = Message +chat_member: ChatMemberUpdate = ChatMemberUpdate +inline_query = InlineQuery +chosen_inline_result = ChosenInlineResult diff --git a/packages/dispatcher/src/builders.ts b/packages/dispatcher/src/builders.ts index 9b9d410f..9f9100e6 100644 --- a/packages/dispatcher/src/builders.ts +++ b/packages/dispatcher/src/builders.ts @@ -1,11 +1,14 @@ +// begin-codegen-imports import { - ChatMemberUpdateHandler, - ChosenInlineResultHandler, - InlineQueryHandler, - NewMessageHandler, - RawUpdateHandler, UpdateHandler, + RawUpdateHandler, + NewMessageHandler, + EditMessageHandler, + ChatMemberUpdateHandler, + InlineQueryHandler, + ChosenInlineResultHandler, } from './handler' +// end-codegen-imports import { filters, UpdateFilter } from './filters' import { InlineQuery, Message } from '@mtcute/client' import { ChatMemberUpdate } from './updates' @@ -31,44 +34,47 @@ function _create( } export namespace handlers { + // begin-codegen + /** - * Create a {@link RawUpdateHandler} + * Create a raw update handler * - * @param handler Update handler + * @param handler Raw update handler */ export function rawUpdate( handler: RawUpdateHandler['callback'] ): RawUpdateHandler /** - * Create a {@link RawUpdateHandler} with a predicate + * Create a raw update handler with a filter * * @param filter Predicate to check the update against - * @param handler Update handler + * @param handler Raw update handler */ export function rawUpdate( filter: RawUpdateHandler['check'], handler: RawUpdateHandler['callback'] ): RawUpdateHandler + /** @internal */ export function rawUpdate(filter: any, handler?: any): RawUpdateHandler { return _create('raw', filter, handler) } /** - * Create a {@link NewMessageHandler} + * Create a new message handler * - * @param handler Message handler + * @param handler New message handler */ export function newMessage( handler: NewMessageHandler['callback'] ): NewMessageHandler /** - * Create a {@link NewMessageHandler} with a filter + * Create a new message handler with a filter * - * @param filter Message update filter - * @param handler Message handler + * @param filter Update filter + * @param handler New message handler */ export function newMessage( filter: UpdateFilter, @@ -80,7 +86,34 @@ export namespace handlers { } /** - * Create a {@link ChatMemberUpdateHandler} + * Create an edit message handler + * + * @param handler Edit message handler + */ + export function editMessage( + handler: EditMessageHandler['callback'] + ): EditMessageHandler + + /** + * Create an edit message handler with a filter + * + * @param filter Update filter + * @param handler Edit message handler + */ + export function editMessage( + filter: UpdateFilter, + handler: EditMessageHandler>['callback'] + ): EditMessageHandler + + export function editMessage( + filter: any, + handler?: any + ): EditMessageHandler { + return _create('edit_message', filter, handler) + } + + /** + * Create a chat member update handler * * @param handler Chat member update handler */ @@ -89,9 +122,9 @@ export namespace handlers { ): ChatMemberUpdateHandler /** - * Create a {@link ChatMemberUpdateHandler} with a filter + * Create a chat member update handler with a filter * - * @param filter Chat member update filter + * @param filter Update filter * @param handler Chat member update handler */ export function chatMemberUpdate( @@ -118,9 +151,9 @@ export namespace handlers { ): InlineQueryHandler /** - * Create an inline query with a filter + * Create an inline query handler with a filter * - * @param filter Inline query update filter + * @param filter Update filter * @param handler Inline query handler */ export function inlineQuery( @@ -149,7 +182,7 @@ export namespace handlers { /** * Create a chosen inline result handler with a filter * - * @param filter Chosen inline result filter + * @param filter Update filter * @param handler Chosen inline result handler */ export function chosenInlineResult( @@ -165,4 +198,6 @@ export namespace handlers { ): ChosenInlineResultHandler { return _create('chosen_inline_result', filter, handler) } + + // end-codegen } diff --git a/packages/dispatcher/src/dispatcher.ts b/packages/dispatcher/src/dispatcher.ts index 7a1a4efe..89d48ac9 100644 --- a/packages/dispatcher/src/dispatcher.ts +++ b/packages/dispatcher/src/dispatcher.ts @@ -11,14 +11,17 @@ import { StopChildrenPropagation, StopPropagation, } from './propagation' +// begin-codegen-imports import { - ChatMemberUpdateHandler, - ChosenInlineResultHandler, - InlineQueryHandler, - NewMessageHandler, - RawUpdateHandler, UpdateHandler, + RawUpdateHandler, + NewMessageHandler, + EditMessageHandler, + ChatMemberUpdateHandler, + InlineQueryHandler, + ChosenInlineResultHandler, } from './handler' +// end-codegen-imports import { filters, UpdateFilter } from './filters' import { handlers } from './builders' import { ChatMemberUpdate } from './updates' @@ -381,19 +384,21 @@ export class Dispatcher { } } + // begin-codegen + /** - * Register a raw update handler + * Register a raw update handler without any filters * - * @param handler Handler function + * @param handler Raw update handler * @param group Handler group index */ onRawUpdate(handler: RawUpdateHandler['callback'], group?: number): void /** - * Register a filtered raw update handler + * Register a raw update handler with a filter * * @param filter Update filter function - * @param handler Handler function + * @param handler Raw update handler * @param group Handler group index */ onRawUpdate( @@ -408,19 +413,19 @@ export class Dispatcher { } /** - * Register a message handler without any filters. + * Register a new message handler without any filters. * - * @param handler Message handler + * @param handler New message handler * @param group Handler group index * @internal */ onNewMessage(handler: NewMessageHandler['callback'], group?: number): void /** - * Register a message handler with a given filter + * Register a new message handler with a filter * * @param filter Update filter - * @param handler Message handler + * @param handler New message handler * @param group Handler group index */ onNewMessage( @@ -434,10 +439,37 @@ export class Dispatcher { this._addKnownHandler('newMessage', filter, handler, group) } + /** + * Register an edit message handler without any filters. + * + * @param handler Edit message handler + * @param group Handler group index + * @internal + */ + onEditMessage(handler: EditMessageHandler['callback'], group?: number): void + + /** + * Register an edit message handler with a filter + * + * @param filter Update filter + * @param handler Edit message handler + * @param group Handler group index + */ + onEditMessage( + filter: UpdateFilter, + handler: EditMessageHandler>['callback'], + group?: number + ): void + + /** @internal */ + onEditMessage(filter: any, handler?: any, group?: number): void { + this._addKnownHandler('editMessage', filter, handler, group) + } + /** * Register a chat member update handler without any filters. * - * @param handler Update handler + * @param handler Chat member update handler * @param group Handler group index * @internal */ @@ -447,10 +479,10 @@ export class Dispatcher { ): void /** - * Register a message handler with a given filter + * Register a chat member update handler with a filter * * @param filter Update filter - * @param handler Update handler + * @param handler Chat member update handler * @param group Handler group index */ onChatMemberUpdate( @@ -469,17 +501,17 @@ export class Dispatcher { /** * Register an inline query handler without any filters. * - * @param handler Update handler + * @param handler Inline query handler * @param group Handler group index * @internal */ onInlineQuery(handler: InlineQueryHandler['callback'], group?: number): void /** - * Register an inline query handler with a given filter + * Register an inline query handler with a filter * * @param filter Update filter - * @param handler Update handler + * @param handler Inline query handler * @param group Handler group index */ onInlineQuery( @@ -498,7 +530,7 @@ export class Dispatcher { /** * Register a chosen inline result handler without any filters. * - * @param handler Update handler + * @param handler Chosen inline result handler * @param group Handler group index * @internal */ @@ -508,10 +540,10 @@ export class Dispatcher { ): void /** - * Register an inline query handler with a given filter + * Register a chosen inline result handler with a filter * * @param filter Update filter - * @param handler Update handler + * @param handler Chosen inline result handler * @param group Handler group index */ onChosenInlineResult( @@ -526,4 +558,6 @@ export class Dispatcher { onChosenInlineResult(filter: any, handler?: any, group?: number): void { this._addKnownHandler('chosenInlineResult', filter, handler, group) } + + // end-codegen } diff --git a/packages/dispatcher/src/handler.ts b/packages/dispatcher/src/handler.ts index bca38eba..b7e23b1e 100644 --- a/packages/dispatcher/src/handler.ts +++ b/packages/dispatcher/src/handler.ts @@ -41,6 +41,7 @@ export type RawUpdateHandler = BaseUpdateHandler< ) => MaybeAsync > +// begin-codegen export type NewMessageHandler = ParsedUpdateHandler< 'new_message', T @@ -68,3 +69,5 @@ export type UpdateHandler = | ChatMemberUpdateHandler | InlineQueryHandler | ChosenInlineResultHandler + +// end-codegen