refactor(dispatcher): big refactor, moved updates parsing to client, separated raw updates from parsed, moved Conversation to client package
This commit is contained in:
parent
8fb099cfeb
commit
627fdbed2f
27 changed files with 1008 additions and 1343 deletions
|
@ -4,6 +4,7 @@ const fs = require('fs')
|
|||
const prettier = require('prettier')
|
||||
// not the best way but who cares lol
|
||||
const { createWriter } = require('../../tl/scripts/common')
|
||||
const updates = require('./generate-updates')
|
||||
|
||||
const targetDir = path.join(__dirname, '../src')
|
||||
|
||||
|
@ -299,6 +300,32 @@ async function main() {
|
|||
)
|
||||
output.tab()
|
||||
|
||||
output.write(`/**
|
||||
* Register a raw update handler
|
||||
*
|
||||
* @param name Event name
|
||||
* @param handler Raw update handler
|
||||
*/
|
||||
on(name: 'raw_update', handler: ((upd: tl.TypeUpdate | tl.TypeMessage, users: UsersIndex, chats: ChatsIndex) => void)): this
|
||||
/**
|
||||
* Register a parsed update handler
|
||||
*
|
||||
* @param name Event name
|
||||
* @param handler Raw update handler
|
||||
*/
|
||||
on(name: 'update', handler: ((upd: ParsedUpdate) => void)): this`)
|
||||
|
||||
updates.types.forEach((type) => {
|
||||
output.write(`/**
|
||||
* Register ${updates.toSentence(type, 'inline')}
|
||||
*
|
||||
* @param name Event name
|
||||
* @param handler ${updates.toSentence(type, 'full')}
|
||||
*/
|
||||
on(name: '${type.typeName}', handler: ((upd: ${type.updateType}) => void)): this`)
|
||||
})
|
||||
|
||||
|
||||
const printer = ts.createPrinter()
|
||||
|
||||
const classContents = []
|
||||
|
|
326
packages/client/scripts/generate-updates.js
Normal file
326
packages/client/scripts/generate-updates.js
Normal file
|
@ -0,0 +1,326 @@
|
|||
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]+)( \+ State)?$/)
|
||||
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]),
|
||||
state: !!m[4]
|
||||
})
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
function replaceSections(filename, sections, dir = __dirname) {
|
||||
let lines = fs
|
||||
.readFileSync(path.join(dir, '../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(dir, '../src', filename), lines.join('\n'))
|
||||
}
|
||||
|
||||
const types = parseUpdateTypes()
|
||||
|
||||
async function formatFile(filename, dir = __dirname) {
|
||||
const targetFile = path.join(dir, '../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 if (stype === 'plain') {
|
||||
return `${name} handler`
|
||||
} else {
|
||||
return `${name[0].toUpperCase()}${name.substr(1)} 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<T = ${type.updateType}` +
|
||||
// `${type.state ? ', S = never' : ''}> = ParsedUpdateHandler<` +
|
||||
// `'${type.typeName}', T${type.state ? ', S' : ''}>`
|
||||
// )
|
||||
// names.push(`${type.handlerTypeName}Handler`)
|
||||
// })
|
||||
//
|
||||
// replaceSections('handler.ts', {
|
||||
// codegen:
|
||||
// lines.join('\n') +
|
||||
// '\n\nexport type UpdateHandler = \n' +
|
||||
// names.map((i) => ` | ${i}\n`).join(''),
|
||||
// })
|
||||
// }
|
||||
//
|
||||
// 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}<Mod>(
|
||||
// filter: UpdateFilter<${type.updateType}, Mod>,
|
||||
// handler: ${type.handlerTypeName}Handler<
|
||||
// filters.Modify<${type.updateType}, Mod>
|
||||
// >['callback']
|
||||
// ): ${type.handlerTypeName}Handler
|
||||
//
|
||||
// /** @internal */
|
||||
// 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 generateDispatcher() {
|
||||
// const lines = []
|
||||
// const declareLines = []
|
||||
// const imports = ['UpdateHandler']
|
||||
//
|
||||
// types.forEach((type) => {
|
||||
// imports.push(`${type.handlerTypeName}Handler`)
|
||||
//
|
||||
// if (type.updateType === 'IGNORE') {
|
||||
// declareLines.push(`
|
||||
// /**
|
||||
// * Register a plain old ${toSentence(type, 'plain')}
|
||||
// *
|
||||
// * @param name Event name
|
||||
// * @param handler ${toSentence(type, 'full')}
|
||||
// */
|
||||
// on(name: '${type.typeName}', handler: ${type.handlerTypeName}Handler['callback']): this
|
||||
// `)
|
||||
//
|
||||
// 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 {
|
||||
// declareLines.push(`
|
||||
// /**
|
||||
// * Register a plain old ${toSentence(type, 'plain')}
|
||||
// *
|
||||
// * @param name Event name
|
||||
// * @param handler ${toSentence(type, 'full')}
|
||||
// */
|
||||
// on(name: '${type.typeName}', handler: ${type.handlerTypeName}Handler['callback']): this
|
||||
//
|
||||
// `)
|
||||
// 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${type.state ? `<${type.updateType}, State extends never ? never : UpdateState<State, SceneName>>` : ''}['callback'], group?: number): void
|
||||
//
|
||||
// ${type.state ? `
|
||||
// /**
|
||||
// * Register ${toSentence(type)} with a filter
|
||||
// *
|
||||
// * @param filter Update filter
|
||||
// * @param handler ${toSentence(type, 'full')}
|
||||
// * @param group Handler group index
|
||||
// */
|
||||
// on${type.handlerTypeName}<Mod>(
|
||||
// filter: UpdateFilter<${type.updateType}, Mod, State>,
|
||||
// handler: ${type.handlerTypeName}Handler<filters.Modify<${type.updateType}, Mod>, State extends never ? never : UpdateState<State, SceneName>>['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}<Mod>(
|
||||
// filter: UpdateFilter<${type.updateType}, Mod>,
|
||||
// handler: ${type.handlerTypeName}Handler<filters.Modify<${type.updateType}, Mod>${type.state ? ', State extends never ? never : UpdateState<State, SceneName>' : ''}>['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-declare': declareLines.join('\n'),
|
||||
// 'codegen-imports':
|
||||
// 'import {\n' +
|
||||
// imports.map((i) => ` ${i},\n`).join('') +
|
||||
// "} from './handler'",
|
||||
// })
|
||||
// }
|
||||
//
|
||||
|
||||
function generateParsedUpdate() {
|
||||
replaceSections('types/updates/index.ts', {
|
||||
codegen: 'export type ParsedUpdate =\n'
|
||||
+ types.map((typ) => ` | { name: '${typ.typeName}', data: ${typ.updateType} }\n`).join(''),
|
||||
})
|
||||
}
|
||||
|
||||
async function main() {
|
||||
generateParsedUpdate()
|
||||
// generateBuilders()
|
||||
// generateHandler()
|
||||
// generateDispatcher()
|
||||
|
||||
// await formatFile('builders.ts')
|
||||
// await formatFile('handler.ts')
|
||||
// await formatFile('dispatcher.ts')
|
||||
}
|
||||
|
||||
module.exports = { types, toSentence, replaceSections, formatFile }
|
||||
|
||||
if (require.main === module) {
|
||||
main().catch(console.error)
|
||||
}
|
|
@ -1,6 +1,4 @@
|
|||
# format: type_name[: handler_type_name] = update_type[ + State]
|
||||
# IGNORE as update_type disables filters modification
|
||||
raw: RawUpdate = IGNORE
|
||||
new_message = Message + State
|
||||
edit_message = Message + State
|
||||
delete_message = DeleteMessageUpdate
|
|
@ -142,12 +142,12 @@ import { getStickerSet } from './methods/stickers/get-sticker-set'
|
|||
import { moveStickerInSet } from './methods/stickers/move-sticker-in-set'
|
||||
import { setStickerSetThumb } from './methods/stickers/set-sticker-set-thumb'
|
||||
import {
|
||||
_dispatchUpdate,
|
||||
_fetchUpdatesState,
|
||||
_handleUpdate,
|
||||
_loadStorage,
|
||||
_saveStorage,
|
||||
catchUp,
|
||||
dispatchUpdate,
|
||||
} from './methods/updates'
|
||||
import { blockUser } from './methods/users/block-user'
|
||||
import { deleteProfilePhotos } from './methods/users/delete-profile-photos'
|
||||
|
@ -168,17 +168,23 @@ import { Readable } from 'stream'
|
|||
import {
|
||||
ArrayWithTotal,
|
||||
BotCommands,
|
||||
CallbackQuery,
|
||||
Chat,
|
||||
ChatEvent,
|
||||
ChatInviteLink,
|
||||
ChatMember,
|
||||
ChatMemberUpdate,
|
||||
ChatPreview,
|
||||
ChatsIndex,
|
||||
ChosenInlineResult,
|
||||
DeleteMessageUpdate,
|
||||
Dialog,
|
||||
FileDownloadParameters,
|
||||
FormattedString,
|
||||
GameHighScore,
|
||||
HistoryReadUpdate,
|
||||
IMessageEntityParser,
|
||||
InlineQuery,
|
||||
InputFileLike,
|
||||
InputInlineResult,
|
||||
InputMediaLike,
|
||||
|
@ -187,10 +193,13 @@ import {
|
|||
MaybeDynamic,
|
||||
Message,
|
||||
MessageMedia,
|
||||
ParsedUpdate,
|
||||
PartialExcept,
|
||||
PartialOnly,
|
||||
Photo,
|
||||
Poll,
|
||||
PollUpdate,
|
||||
PollVoteUpdate,
|
||||
RawDocument,
|
||||
ReplyMarkup,
|
||||
SentCode,
|
||||
|
@ -201,6 +210,8 @@ import {
|
|||
UploadFileLike,
|
||||
UploadedFile,
|
||||
User,
|
||||
UserStatusUpdate,
|
||||
UserTypingUpdate,
|
||||
UsersIndex,
|
||||
} from './types'
|
||||
import {
|
||||
|
@ -212,6 +223,117 @@ import {
|
|||
import { tdFileId } from '@mtcute/file-id'
|
||||
|
||||
export interface TelegramClient extends BaseTelegramClient {
|
||||
/**
|
||||
* Register a raw update handler
|
||||
*
|
||||
* @param name Event name
|
||||
* @param handler Raw update handler
|
||||
*/
|
||||
on(
|
||||
name: 'raw_update',
|
||||
handler: (
|
||||
upd: tl.TypeUpdate | tl.TypeMessage,
|
||||
users: UsersIndex,
|
||||
chats: ChatsIndex
|
||||
) => void
|
||||
): this
|
||||
/**
|
||||
* Register a parsed update handler
|
||||
*
|
||||
* @param name Event name
|
||||
* @param handler Raw update handler
|
||||
*/
|
||||
on(name: 'update', handler: (upd: ParsedUpdate) => void): this
|
||||
/**
|
||||
* Register a new message handler
|
||||
*
|
||||
* @param name Event name
|
||||
* @param handler New message handler
|
||||
*/
|
||||
on(name: 'new_message', handler: (upd: Message) => void): this
|
||||
/**
|
||||
* Register an edit message handler
|
||||
*
|
||||
* @param name Event name
|
||||
* @param handler Edit message handler
|
||||
*/
|
||||
on(name: 'edit_message', handler: (upd: Message) => void): this
|
||||
/**
|
||||
* Register a delete message handler
|
||||
*
|
||||
* @param name Event name
|
||||
* @param handler Delete message handler
|
||||
*/
|
||||
on(
|
||||
name: 'delete_message',
|
||||
handler: (upd: DeleteMessageUpdate) => void
|
||||
): this
|
||||
/**
|
||||
* Register a chat member update handler
|
||||
*
|
||||
* @param name Event name
|
||||
* @param handler Chat member update handler
|
||||
*/
|
||||
on(name: 'chat_member', handler: (upd: ChatMemberUpdate) => void): this
|
||||
/**
|
||||
* Register an inline query handler
|
||||
*
|
||||
* @param name Event name
|
||||
* @param handler Inline query handler
|
||||
*/
|
||||
on(name: 'inline_query', handler: (upd: InlineQuery) => void): this
|
||||
/**
|
||||
* Register a chosen inline result handler
|
||||
*
|
||||
* @param name Event name
|
||||
* @param handler Chosen inline result handler
|
||||
*/
|
||||
on(
|
||||
name: 'chosen_inline_result',
|
||||
handler: (upd: ChosenInlineResult) => void
|
||||
): this
|
||||
/**
|
||||
* Register a callback query handler
|
||||
*
|
||||
* @param name Event name
|
||||
* @param handler Callback query handler
|
||||
*/
|
||||
on(name: 'callback_query', handler: (upd: CallbackQuery) => void): this
|
||||
/**
|
||||
* Register a poll update handler
|
||||
*
|
||||
* @param name Event name
|
||||
* @param handler Poll update handler
|
||||
*/
|
||||
on(name: 'poll', handler: (upd: PollUpdate) => void): this
|
||||
/**
|
||||
* Register a poll vote handler
|
||||
*
|
||||
* @param name Event name
|
||||
* @param handler Poll vote handler
|
||||
*/
|
||||
on(name: 'poll_vote', handler: (upd: PollVoteUpdate) => void): this
|
||||
/**
|
||||
* Register an user status update handler
|
||||
*
|
||||
* @param name Event name
|
||||
* @param handler User status update handler
|
||||
*/
|
||||
on(name: 'user_status', handler: (upd: UserStatusUpdate) => void): this
|
||||
/**
|
||||
* Register an user typing handler
|
||||
*
|
||||
* @param name Event name
|
||||
* @param handler User typing handler
|
||||
*/
|
||||
on(name: 'user_typing', handler: (upd: UserTypingUpdate) => void): this
|
||||
/**
|
||||
* Register a history read handler
|
||||
*
|
||||
* @param name Event name
|
||||
* @param handler History read handler
|
||||
*/
|
||||
on(name: 'history_read', handler: (upd: HistoryReadUpdate) => void): this
|
||||
/**
|
||||
* Accept the given TOS
|
||||
*
|
||||
|
@ -3110,29 +3232,6 @@ export interface TelegramClient extends BaseTelegramClient {
|
|||
progressCallback?: (uploaded: number, total: number) => void
|
||||
}
|
||||
): Promise<StickerSet>
|
||||
/**
|
||||
* Base function for update handling. Replace or override this function
|
||||
* and implement your own update handler, and call this function
|
||||
* to handle externally obtained or manually crafted updates.
|
||||
*
|
||||
* Note that this function is called every time an `Update` is received,
|
||||
* not `Updates`. Low-level updates containers are parsed by the library,
|
||||
* and you receive ready to use updates and related entities.
|
||||
* Also note that entity maps may contain entities that are not
|
||||
* used in this particular update, so do not rely on its contents.
|
||||
*
|
||||
* `update` might contain a Message object - in this case,
|
||||
* it should be interpreted as some kind of `updateNewMessage`.
|
||||
*
|
||||
* @param update Update that has just happened
|
||||
* @param users Map of users in this update
|
||||
* @param chats Map of chats in this update
|
||||
*/
|
||||
dispatchUpdate(
|
||||
update: tl.TypeUpdate | tl.TypeMessage,
|
||||
users: UsersIndex,
|
||||
chats: ChatsIndex
|
||||
): void
|
||||
_handleUpdate(update: tl.TypeUpdates, noDispatch?: boolean): void
|
||||
/**
|
||||
* Catch up with the server by loading missed updates.
|
||||
|
@ -3524,7 +3623,7 @@ export class TelegramClient extends BaseTelegramClient {
|
|||
protected _fetchUpdatesState = _fetchUpdatesState
|
||||
protected _loadStorage = _loadStorage
|
||||
protected _saveStorage = _saveStorage
|
||||
dispatchUpdate = dispatchUpdate
|
||||
protected _dispatchUpdate = _dispatchUpdate
|
||||
_handleUpdate = _handleUpdate
|
||||
catchUp = catchUp
|
||||
blockUser = blockUser
|
||||
|
|
|
@ -40,7 +40,18 @@ import {
|
|||
MessageMedia,
|
||||
RawDocument,
|
||||
IMessageEntityParser,
|
||||
FormattedString
|
||||
FormattedString,
|
||||
CallbackQuery,
|
||||
ChatMemberUpdate,
|
||||
ChosenInlineResult,
|
||||
DeleteMessageUpdate,
|
||||
HistoryReadUpdate,
|
||||
InlineQuery,
|
||||
ParsedUpdate,
|
||||
PollUpdate,
|
||||
PollVoteUpdate,
|
||||
UserStatusUpdate,
|
||||
UserTypingUpdate,
|
||||
} from '../types'
|
||||
|
||||
// @copy
|
||||
|
|
|
@ -14,6 +14,7 @@ import {
|
|||
} from '@mtcute/core'
|
||||
import { isDummyUpdate, isDummyUpdates } from '../utils/updates-utils'
|
||||
import { ChatsIndex, UsersIndex } from '../types'
|
||||
import { _parseUpdate } from '../utils/parse-update'
|
||||
|
||||
const debug = require('debug')('mtcute:upds')
|
||||
|
||||
|
@ -155,31 +156,21 @@ export async function _saveStorage(
|
|||
}
|
||||
|
||||
/**
|
||||
* Base function for update handling. Replace or override this function
|
||||
* and implement your own update handler, and call this function
|
||||
* to handle externally obtained or manually crafted updates.
|
||||
*
|
||||
* Note that this function is called every time an `Update` is received,
|
||||
* not `Updates`. Low-level updates containers are parsed by the library,
|
||||
* and you receive ready to use updates and related entities.
|
||||
* Also note that entity maps may contain entities that are not
|
||||
* used in this particular update, so do not rely on its contents.
|
||||
*
|
||||
* `update` might contain a Message object - in this case,
|
||||
* it should be interpreted as some kind of `updateNewMessage`.
|
||||
*
|
||||
* @param update Update that has just happened
|
||||
* @param users Map of users in this update
|
||||
* @param chats Map of chats in this update
|
||||
* @internal
|
||||
*/
|
||||
export function dispatchUpdate(
|
||||
export function _dispatchUpdate(
|
||||
this: TelegramClient,
|
||||
update: tl.TypeUpdate | tl.TypeMessage,
|
||||
users: UsersIndex,
|
||||
chats: ChatsIndex
|
||||
): void {
|
||||
// no-op //
|
||||
this.emit('raw_update', update, users, chats)
|
||||
|
||||
const parsed = _parseUpdate(this, update, users, chats)
|
||||
if (parsed) {
|
||||
this.emit('update', parsed)
|
||||
this.emit(parsed.name, parsed.data)
|
||||
}
|
||||
}
|
||||
|
||||
interface NoDispatchIndex {
|
||||
|
@ -441,7 +432,7 @@ async function _loadDifference(
|
|||
if (noDispatch.msg[cid]?.[message.id]) return
|
||||
}
|
||||
|
||||
this.dispatchUpdate(message, users, chats)
|
||||
this._dispatchUpdate(message, users, chats)
|
||||
})
|
||||
|
||||
for (const upd of diff.otherUpdates) {
|
||||
|
@ -495,7 +486,7 @@ async function _loadDifference(
|
|||
if (noDispatch.pts[cid ?? 0]?.[pts]) continue
|
||||
}
|
||||
|
||||
this.dispatchUpdate(upd, users, chats)
|
||||
this._dispatchUpdate(upd, users, chats)
|
||||
}
|
||||
|
||||
this._pts = state.pts
|
||||
|
@ -556,7 +547,7 @@ async function _loadChannelDifference(
|
|||
return
|
||||
if (message._ === 'messageEmpty') return
|
||||
|
||||
this.dispatchUpdate(message, users, chats)
|
||||
this._dispatchUpdate(message, users, chats)
|
||||
})
|
||||
break
|
||||
}
|
||||
|
@ -565,7 +556,7 @@ async function _loadChannelDifference(
|
|||
if (noDispatch && noDispatch.msg[channelId]?.[message.id]) return
|
||||
if (message._ === 'messageEmpty') return
|
||||
|
||||
this.dispatchUpdate(message, users, chats)
|
||||
this._dispatchUpdate(message, users, chats)
|
||||
})
|
||||
|
||||
diff.otherUpdates.forEach((upd) => {
|
||||
|
@ -582,7 +573,7 @@ async function _loadChannelDifference(
|
|||
if (upd._ === 'updateNewChannelMessage' && upd.message._ === 'messageEmpty')
|
||||
return
|
||||
|
||||
this.dispatchUpdate(upd, users, chats)
|
||||
this._dispatchUpdate(upd, users, chats)
|
||||
})
|
||||
|
||||
pts = diff.pts
|
||||
|
@ -728,7 +719,7 @@ export function _handleUpdate(
|
|||
}
|
||||
|
||||
if (!isDummyUpdate(upd) && !noDispatch) {
|
||||
this.dispatchUpdate(upd, users, chats)
|
||||
this._dispatchUpdate(upd, users, chats)
|
||||
}
|
||||
|
||||
if (channelId) {
|
||||
|
@ -738,7 +729,7 @@ export function _handleUpdate(
|
|||
this._pts = pts
|
||||
}
|
||||
} else if (!noDispatch) {
|
||||
this.dispatchUpdate(upd, users, chats)
|
||||
this._dispatchUpdate(upd, users, chats)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -769,7 +760,7 @@ export function _handleUpdate(
|
|||
return await _loadDifference.call(this)
|
||||
}
|
||||
|
||||
this.dispatchUpdate(upd, peers.users, peers.chats)
|
||||
this._dispatchUpdate(upd, peers.users, peers.chats)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -821,7 +812,7 @@ export function _handleUpdate(
|
|||
this._date = update.date
|
||||
this._pts = update.pts
|
||||
|
||||
this.dispatchUpdate(message, peers.users, peers.chats)
|
||||
this._dispatchUpdate(message, peers.users, peers.chats)
|
||||
break
|
||||
}
|
||||
case 'updateShortChatMessage': {
|
||||
|
@ -869,7 +860,7 @@ export function _handleUpdate(
|
|||
this._date = update.date
|
||||
this._pts = update.pts
|
||||
|
||||
this.dispatchUpdate(message, peers.users, peers.chats)
|
||||
this._dispatchUpdate(message, peers.users, peers.chats)
|
||||
break
|
||||
}
|
||||
case 'updateShortSentMessage': {
|
||||
|
|
|
@ -1,104 +1,18 @@
|
|||
import { Dispatcher } from './dispatcher'
|
||||
import {
|
||||
FormattedString,
|
||||
InputMediaLike,
|
||||
InputPeerLike,
|
||||
MaybeAsync,
|
||||
Message,
|
||||
MtCuteArgumentError,
|
||||
TelegramClient,
|
||||
TimeoutError,
|
||||
tl,
|
||||
} from '@mtcute/client'
|
||||
import { AsyncLock, getMarkedPeerId } from '@mtcute/core'
|
||||
import { AsyncLock, getMarkedPeerId, MaybeAsync } from '@mtcute/core'
|
||||
import {
|
||||
ControllablePromise,
|
||||
createControllablePromise,
|
||||
} from '@mtcute/core/src/utils/controllable-promise'
|
||||
import { TelegramClient } from '../client'
|
||||
import { InputMediaLike } from './media'
|
||||
import { MtCuteArgumentError } from './errors'
|
||||
import { InputPeerLike } from './peers'
|
||||
import { HistoryReadUpdate } from './updates'
|
||||
|
||||
interface OneWayLinkedListItem<T> {
|
||||
v: T
|
||||
n?: OneWayLinkedListItem<T>
|
||||
}
|
||||
|
||||
class Queue<T> {
|
||||
first?: OneWayLinkedListItem<T>
|
||||
last?: OneWayLinkedListItem<T>
|
||||
|
||||
length = 0
|
||||
|
||||
constructor (readonly limit = 0) {
|
||||
}
|
||||
|
||||
push(item: T): void {
|
||||
const it: OneWayLinkedListItem<T> = { v: item }
|
||||
if (!this.first) {
|
||||
this.first = this.last = it
|
||||
} else {
|
||||
this.last!.n = it
|
||||
this.last = it
|
||||
}
|
||||
|
||||
this.length += 1
|
||||
|
||||
if (this.limit) {
|
||||
while (this.first && this.length > this.limit) {
|
||||
this.first = this.first.n
|
||||
this.length -= 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
empty(): boolean {
|
||||
return this.first === undefined
|
||||
}
|
||||
|
||||
peek(): T | undefined {
|
||||
return this.first?.v
|
||||
}
|
||||
|
||||
pop(): T | undefined {
|
||||
if (!this.first) return undefined
|
||||
|
||||
const it = this.first
|
||||
this.first = this.first.n
|
||||
if (!this.first) this.last = undefined
|
||||
|
||||
this.length -= 1
|
||||
return it.v
|
||||
}
|
||||
|
||||
removeBy(pred: (it: T) => boolean): void {
|
||||
if (!this.first) return
|
||||
|
||||
let prev: OneWayLinkedListItem<T> | undefined = undefined
|
||||
let it = this.first
|
||||
while (it && !pred(it.v)) {
|
||||
if (!it.n) return
|
||||
|
||||
prev = it
|
||||
it = it.n
|
||||
}
|
||||
|
||||
if (!it) return
|
||||
|
||||
if (prev) {
|
||||
prev.n = it.n
|
||||
} else {
|
||||
this.first = it.n
|
||||
}
|
||||
|
||||
if (!this.first) this.last = undefined
|
||||
|
||||
this.length -= 1
|
||||
}
|
||||
|
||||
clear(): void {
|
||||
this.first = this.last = undefined
|
||||
this.length = 0
|
||||
}
|
||||
}
|
||||
import { FormattedString } from './parser'
|
||||
import { Message } from './messages'
|
||||
import { tl } from '@mtcute/tl'
|
||||
import { TimeoutError } from '@mtcute/tl/errors'
|
||||
import { Queue } from '../utils/queue'
|
||||
|
||||
interface QueuedHandler<T> {
|
||||
promise: ControllablePromise<T>
|
||||
|
@ -120,7 +34,6 @@ interface QueuedHandler<T> {
|
|||
export class Conversation {
|
||||
private _inputPeer: tl.TypeInputPeer
|
||||
private _chatId: number
|
||||
private _client: TelegramClient
|
||||
private _started = false
|
||||
|
||||
private _lastMessage: number
|
||||
|
@ -136,7 +49,7 @@ export class Conversation {
|
|||
private _pendingRead: Record<number, QueuedHandler<void>> = {}
|
||||
|
||||
constructor(
|
||||
readonly dispatcher: Dispatcher<any, any>,
|
||||
readonly client: TelegramClient,
|
||||
readonly chat: InputPeerLike
|
||||
) {
|
||||
this._onNewMessage = this._onNewMessage.bind(this)
|
||||
|
@ -186,24 +99,16 @@ export class Conversation {
|
|||
async start(): Promise<void> {
|
||||
if (this._started) return
|
||||
|
||||
const client = this.dispatcher['_client']
|
||||
if (!client) {
|
||||
throw new MtCuteArgumentError(
|
||||
'Dispatcher is not bound to a client!'
|
||||
)
|
||||
}
|
||||
|
||||
this._client = client
|
||||
this._started = true
|
||||
this._inputPeer = await client.resolvePeer(this.chat)
|
||||
this._inputPeer = await this.client.resolvePeer(this.chat)
|
||||
this._chatId = getMarkedPeerId(this._inputPeer)
|
||||
|
||||
const dialog = await client.getPeerDialogs(this._inputPeer)
|
||||
const dialog = await this.client.getPeerDialogs(this._inputPeer)
|
||||
this._lastMessage = this._lastReceivedMessage = dialog.lastMessage.id
|
||||
|
||||
this.dispatcher.on('new_message', this._onNewMessage)
|
||||
this.dispatcher.on('edit_message', this._onEditMessage)
|
||||
this.dispatcher.on('history_read', this._onHistoryRead)
|
||||
this.client.on('new_message', this._onNewMessage)
|
||||
this.client.on('edit_message', this._onEditMessage)
|
||||
this.client.on('history_read', this._onHistoryRead)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -212,9 +117,9 @@ export class Conversation {
|
|||
stop(): void {
|
||||
if (!this._started) return
|
||||
|
||||
this.dispatcher.off('new_message', this._onNewMessage)
|
||||
this.dispatcher.off('edit_message', this._onEditMessage)
|
||||
this.dispatcher.off('history_read', this._onHistoryRead)
|
||||
this.client.off('new_message', this._onNewMessage)
|
||||
this.client.off('edit_message', this._onEditMessage)
|
||||
this.client.off('history_read', this._onHistoryRead)
|
||||
|
||||
// reset pending status
|
||||
this._queuedNewMessage.clear()
|
||||
|
@ -240,7 +145,7 @@ export class Conversation {
|
|||
throw new MtCuteArgumentError("Conversation hasn't started yet")
|
||||
}
|
||||
|
||||
const res = await this._client.sendText(this._inputPeer, text, params)
|
||||
const res = await this.client.sendText(this._inputPeer, text, params)
|
||||
this._lastMessage = res.id
|
||||
return res
|
||||
}
|
||||
|
@ -259,7 +164,7 @@ export class Conversation {
|
|||
throw new MtCuteArgumentError("Conversation hasn't started yet")
|
||||
}
|
||||
|
||||
const res = await this._client.sendMedia(this._inputPeer, media, params)
|
||||
const res = await this.client.sendMedia(this._inputPeer, media, params)
|
||||
this._lastMessage = res.id
|
||||
return res
|
||||
}
|
||||
|
@ -278,7 +183,7 @@ export class Conversation {
|
|||
throw new MtCuteArgumentError("Conversation hasn't started yet")
|
||||
}
|
||||
|
||||
const res = await this._client.sendMediaGroup(
|
||||
const res = await this.client.sendMediaGroup(
|
||||
this._inputPeer,
|
||||
medias,
|
||||
params
|
||||
|
@ -305,7 +210,7 @@ export class Conversation {
|
|||
message = this._lastMessage ?? 0
|
||||
}
|
||||
|
||||
return this._client.readHistory(this._inputPeer, message, clearMentions)
|
||||
return this.client.readHistory(this._inputPeer, message, clearMentions)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -537,7 +442,7 @@ export class Conversation {
|
|||
)
|
||||
|
||||
// check if the message is already read
|
||||
const dialog = await this._client.getPeerDialogs(this._inputPeer)
|
||||
const dialog = await this.client.getPeerDialogs(this._inputPeer)
|
||||
if (dialog.lastRead >= msgId) return
|
||||
|
||||
const promise = createControllablePromise<void>()
|
||||
|
@ -578,7 +483,7 @@ export class Conversation {
|
|||
this._queuedNewMessage.pop()
|
||||
}
|
||||
} catch (e) {
|
||||
this._client['_emitError'](e)
|
||||
this.client['_emitError'](e)
|
||||
}
|
||||
|
||||
this._lastMessage = this._lastReceivedMessage = msg.id
|
||||
|
@ -603,7 +508,7 @@ export class Conversation {
|
|||
it.promise.resolve(msg)
|
||||
delete this._pendingEditMessage[msg.id]
|
||||
}
|
||||
})().catch((e) => this._client['_emitError'](e))
|
||||
})().catch((e) => this.client['_emitError'](e))
|
||||
}
|
||||
|
||||
private _onHistoryRead(upd: HistoryReadUpdate) {
|
|
@ -5,6 +5,8 @@ export * from './media'
|
|||
export * from './messages'
|
||||
export * from './peers'
|
||||
export * from './misc'
|
||||
export * from './updates'
|
||||
export * from './conversation'
|
||||
|
||||
export * from './errors'
|
||||
export * from './parser'
|
||||
|
|
|
@ -8,7 +8,7 @@ import {
|
|||
User,
|
||||
UsersIndex,
|
||||
} from '@mtcute/client'
|
||||
import { makeInspectable } from '@mtcute/client/src/types/utils'
|
||||
import { makeInspectable } from '../utils'
|
||||
|
||||
export namespace ChatMemberUpdate {
|
||||
/**
|
|
@ -1,4 +1,3 @@
|
|||
import { makeInspectable } from '@mtcute/client/src/types/utils'
|
||||
import { tl } from '@mtcute/tl'
|
||||
import {
|
||||
TelegramClient,
|
||||
|
@ -7,7 +6,8 @@ import {
|
|||
MtCuteArgumentError,
|
||||
UsersIndex,
|
||||
} from '@mtcute/client'
|
||||
import { encodeInlineMessageId } from '@mtcute/client/src/utils/inline-utils'
|
||||
import { encodeInlineMessageId } from '../../utils/inline-utils'
|
||||
import { makeInspectable } from '../utils'
|
||||
|
||||
/**
|
||||
* An inline result was chosen by the user and sent to some chat
|
|
@ -1,7 +1,7 @@
|
|||
import { tl } from '@mtcute/tl'
|
||||
import { makeInspectable } from '@mtcute/client/src/types/utils'
|
||||
import { MAX_CHANNEL_ID } from '@mtcute/core'
|
||||
import { TelegramClient } from '@mtcute/client'
|
||||
import { makeInspectable } from '../utils'
|
||||
|
||||
/**
|
||||
* One or more messages were deleted
|
|
@ -1,7 +1,7 @@
|
|||
import { tl } from '@mtcute/tl'
|
||||
import { TelegramClient } from '@mtcute/client'
|
||||
import { getMarkedPeerId, MAX_CHANNEL_ID } from '@mtcute/core'
|
||||
import { makeInspectable } from '@mtcute/client/src/types/utils'
|
||||
import { makeInspectable } from '../utils'
|
||||
|
||||
export class HistoryReadUpdate {
|
||||
constructor (
|
38
packages/client/src/types/updates/index.ts
Normal file
38
packages/client/src/types/updates/index.ts
Normal file
|
@ -0,0 +1,38 @@
|
|||
import { CallbackQuery, InlineQuery, Message } from '../..'
|
||||
|
||||
import { DeleteMessageUpdate } from './delete-message-update'
|
||||
import { ChatMemberUpdate } from './chat-member-update'
|
||||
import { ChosenInlineResult } from './chosen-inline-result'
|
||||
import { PollUpdate } from './poll-update'
|
||||
import { PollVoteUpdate } from './poll-vote'
|
||||
import { UserStatusUpdate } from './user-status-update'
|
||||
import { UserTypingUpdate } from './user-typing-update'
|
||||
import { HistoryReadUpdate } from './history-read-update'
|
||||
|
||||
export {
|
||||
DeleteMessageUpdate,
|
||||
ChatMemberUpdate,
|
||||
ChosenInlineResult,
|
||||
PollUpdate,
|
||||
PollVoteUpdate,
|
||||
UserStatusUpdate,
|
||||
UserTypingUpdate,
|
||||
HistoryReadUpdate,
|
||||
}
|
||||
|
||||
// begin-codegen
|
||||
export type ParsedUpdate =
|
||||
| { name: 'new_message', data: Message }
|
||||
| { name: 'edit_message', data: Message }
|
||||
| { name: 'delete_message', data: DeleteMessageUpdate }
|
||||
| { name: 'chat_member', data: ChatMemberUpdate }
|
||||
| { name: 'inline_query', data: InlineQuery }
|
||||
| { name: 'chosen_inline_result', data: ChosenInlineResult }
|
||||
| { name: 'callback_query', data: CallbackQuery }
|
||||
| { name: 'poll', data: PollUpdate }
|
||||
| { name: 'poll_vote', data: PollVoteUpdate }
|
||||
| { name: 'user_status', data: UserStatusUpdate }
|
||||
| { name: 'user_typing', data: UserTypingUpdate }
|
||||
| { name: 'history_read', data: HistoryReadUpdate }
|
||||
|
||||
// end-codegen
|
|
@ -1,6 +1,6 @@
|
|||
import { makeInspectable } from '@mtcute/client/src/types/utils'
|
||||
import { TelegramClient, Poll, UsersIndex } from '@mtcute/client'
|
||||
import { tl } from '@mtcute/tl'
|
||||
import { makeInspectable } from '../utils'
|
||||
|
||||
/**
|
||||
* Poll state has changed (stopped, somebody
|
|
@ -5,7 +5,7 @@ import {
|
|||
UsersIndex,
|
||||
} from '@mtcute/client'
|
||||
import { tl } from '@mtcute/tl'
|
||||
import { makeInspectable } from '@mtcute/client/src/types/utils'
|
||||
import { makeInspectable } from '../utils'
|
||||
|
||||
/**
|
||||
* Some user has voted in a public poll.
|
|
@ -1,6 +1,6 @@
|
|||
import { TelegramClient, User } from '@mtcute/client'
|
||||
import { tl } from '@mtcute/tl'
|
||||
import { makeInspectable } from '@mtcute/client/src/types/utils'
|
||||
import { makeInspectable } from '../utils'
|
||||
|
||||
/**
|
||||
* User status has changed
|
|
@ -8,7 +8,7 @@ import {
|
|||
} from '@mtcute/client'
|
||||
import { tl } from '@mtcute/tl'
|
||||
import { getBarePeerId, MAX_CHANNEL_ID } from '@mtcute/core'
|
||||
import { makeInspectable } from '@mtcute/client/src/types/utils'
|
||||
import { makeInspectable } from '../utils'
|
||||
|
||||
/**
|
||||
* User's typing status has changed.
|
131
packages/client/src/utils/parse-update.ts
Normal file
131
packages/client/src/utils/parse-update.ts
Normal file
|
@ -0,0 +1,131 @@
|
|||
import { TelegramClient } from '../client'
|
||||
import { tl } from '@mtcute/tl'
|
||||
import {
|
||||
CallbackQuery,
|
||||
ChatMemberUpdate,
|
||||
ChatsIndex,
|
||||
ChosenInlineResult,
|
||||
DeleteMessageUpdate,
|
||||
HistoryReadUpdate,
|
||||
InlineQuery,
|
||||
Message,
|
||||
ParsedUpdate,
|
||||
PollUpdate,
|
||||
PollVoteUpdate,
|
||||
UsersIndex,
|
||||
UserStatusUpdate,
|
||||
UserTypingUpdate,
|
||||
} from '../types'
|
||||
|
||||
type ParserFunction = (
|
||||
client: TelegramClient,
|
||||
upd: tl.TypeUpdate | tl.TypeMessage,
|
||||
users: UsersIndex,
|
||||
chats: ChatsIndex
|
||||
) => any
|
||||
type UpdateParser = [ParsedUpdate['name'], ParserFunction]
|
||||
|
||||
const baseMessageParser: ParserFunction = (
|
||||
client: TelegramClient,
|
||||
upd,
|
||||
users,
|
||||
chats
|
||||
) =>
|
||||
new Message(
|
||||
client,
|
||||
tl.isAnyMessage(upd) ? upd : (upd as any).message,
|
||||
users,
|
||||
chats,
|
||||
upd._ === 'updateNewScheduledMessage'
|
||||
)
|
||||
|
||||
const newMessageParser: UpdateParser = ['new_message', baseMessageParser]
|
||||
const editMessageParser: UpdateParser = ['edit_message', baseMessageParser]
|
||||
const chatMemberParser: UpdateParser = [
|
||||
'chat_member',
|
||||
(client, upd, users, chats) =>
|
||||
new ChatMemberUpdate(client, upd as any, users, chats),
|
||||
]
|
||||
const callbackQueryParser: UpdateParser = [
|
||||
'callback_query',
|
||||
(client, upd, users) => new CallbackQuery(client, upd as any, users),
|
||||
]
|
||||
const userTypingParser: UpdateParser = [
|
||||
'user_typing',
|
||||
(client, upd) => new UserTypingUpdate(client, upd as any),
|
||||
]
|
||||
const deleteMessageParser: UpdateParser = [
|
||||
'delete_message',
|
||||
(client, upd) => new DeleteMessageUpdate(client, upd as any),
|
||||
]
|
||||
const historyReadParser: UpdateParser = [
|
||||
'history_read',
|
||||
(client, upd) => new HistoryReadUpdate(client, upd as any),
|
||||
]
|
||||
|
||||
const PARSERS: Partial<
|
||||
Record<(tl.TypeUpdate | tl.TypeMessage)['_'], UpdateParser>
|
||||
> = {
|
||||
message: newMessageParser,
|
||||
messageEmpty: newMessageParser,
|
||||
messageService: newMessageParser,
|
||||
updateNewMessage: newMessageParser,
|
||||
updateNewChannelMessage: newMessageParser,
|
||||
updateNewScheduledMessage: newMessageParser,
|
||||
updateEditMessage: editMessageParser,
|
||||
updateEditChannelMessage: editMessageParser,
|
||||
updateChatParticipant: chatMemberParser,
|
||||
updateChannelParticipant: chatMemberParser,
|
||||
updateBotInlineQuery: [
|
||||
'inline_query',
|
||||
(client, upd, users) => new InlineQuery(client, upd as any, users),
|
||||
],
|
||||
updateBotInlineSend: [
|
||||
'chosen_inline_result',
|
||||
(client, upd, users) =>
|
||||
new ChosenInlineResult(client, upd as any, users),
|
||||
],
|
||||
updateBotCallbackQuery: callbackQueryParser,
|
||||
updateInlineBotCallbackQuery: callbackQueryParser,
|
||||
updateMessagePoll: [
|
||||
'poll',
|
||||
(client, upd, users) => new PollUpdate(client, upd as any, users),
|
||||
],
|
||||
updateMessagePollVote: [
|
||||
'poll_vote',
|
||||
(client, upd, users) => new PollVoteUpdate(client, upd as any, users),
|
||||
],
|
||||
updateUserStatus: [
|
||||
'user_status',
|
||||
(client, upd) => new UserStatusUpdate(client, upd as any),
|
||||
],
|
||||
updateChannelUserTyping: userTypingParser,
|
||||
updateChatUserTyping: userTypingParser,
|
||||
updateUserTyping: userTypingParser,
|
||||
updateDeleteChannelMessages: deleteMessageParser,
|
||||
updateDeleteMessages: deleteMessageParser,
|
||||
updateReadHistoryInbox: historyReadParser,
|
||||
updateReadHistoryOutbox: historyReadParser,
|
||||
updateReadChannelInbox: historyReadParser,
|
||||
updateReadChannelOutbox: historyReadParser,
|
||||
updateReadChannelDiscussionInbox: historyReadParser,
|
||||
updateReadChannelDiscussionOutbox: historyReadParser,
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export function _parseUpdate(
|
||||
client: TelegramClient,
|
||||
update: tl.TypeUpdate | tl.TypeMessage,
|
||||
users: UsersIndex,
|
||||
chats: ChatsIndex
|
||||
): ParsedUpdate | null {
|
||||
const pair = PARSERS[update._]
|
||||
if (pair) {
|
||||
return {
|
||||
name: pair[0],
|
||||
data: pair[1](client, update, users, chats),
|
||||
}
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
}
|
81
packages/client/src/utils/queue.ts
Normal file
81
packages/client/src/utils/queue.ts
Normal file
|
@ -0,0 +1,81 @@
|
|||
interface OneWayLinkedListItem<T> {
|
||||
v: T
|
||||
n?: OneWayLinkedListItem<T>
|
||||
}
|
||||
|
||||
export class Queue<T> {
|
||||
first?: OneWayLinkedListItem<T>
|
||||
last?: OneWayLinkedListItem<T>
|
||||
|
||||
length = 0
|
||||
|
||||
constructor(readonly limit = 0) {}
|
||||
|
||||
push(item: T): void {
|
||||
const it: OneWayLinkedListItem<T> = { v: item }
|
||||
if (!this.first) {
|
||||
this.first = this.last = it
|
||||
} else {
|
||||
this.last!.n = it
|
||||
this.last = it
|
||||
}
|
||||
|
||||
this.length += 1
|
||||
|
||||
if (this.limit) {
|
||||
while (this.first && this.length > this.limit) {
|
||||
this.first = this.first.n
|
||||
this.length -= 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
empty(): boolean {
|
||||
return this.first === undefined
|
||||
}
|
||||
|
||||
peek(): T | undefined {
|
||||
return this.first?.v
|
||||
}
|
||||
|
||||
pop(): T | undefined {
|
||||
if (!this.first) return undefined
|
||||
|
||||
const it = this.first
|
||||
this.first = this.first.n
|
||||
if (!this.first) this.last = undefined
|
||||
|
||||
this.length -= 1
|
||||
return it.v
|
||||
}
|
||||
|
||||
removeBy(pred: (it: T) => boolean): void {
|
||||
if (!this.first) return
|
||||
|
||||
let prev: OneWayLinkedListItem<T> | undefined = undefined
|
||||
let it = this.first
|
||||
while (it && !pred(it.v)) {
|
||||
if (!it.n) return
|
||||
|
||||
prev = it
|
||||
it = it.n
|
||||
}
|
||||
|
||||
if (!it) return
|
||||
|
||||
if (prev) {
|
||||
prev.n = it.n
|
||||
} else {
|
||||
this.first = it.n
|
||||
}
|
||||
|
||||
if (!this.first) this.last = undefined
|
||||
|
||||
this.length -= 1
|
||||
}
|
||||
|
||||
clear(): void {
|
||||
this.first = this.last = undefined
|
||||
this.length = 0
|
||||
}
|
||||
}
|
|
@ -35,6 +35,7 @@ import bigInt from 'big-integer'
|
|||
import { BinaryWriter } from './utils/binary/binary-writer'
|
||||
import { encodeUrlSafeBase64, parseUrlSafeBase64 } from './utils/buffer-utils'
|
||||
import { BinaryReader } from './utils/binary/binary-reader'
|
||||
import EventEmitter from 'events'
|
||||
|
||||
const debug = require('debug')('mtcute:base')
|
||||
|
||||
|
@ -148,7 +149,7 @@ export namespace BaseTelegramClient {
|
|||
}
|
||||
}
|
||||
|
||||
export class BaseTelegramClient {
|
||||
export class BaseTelegramClient extends EventEmitter {
|
||||
/**
|
||||
* `initConnection` params taken from {@link BaseTelegramClient.Options.initConnectionOptions}.
|
||||
*/
|
||||
|
@ -242,6 +243,8 @@ export class BaseTelegramClient {
|
|||
protected _handleUpdate(update: tl.TypeUpdates): void {}
|
||||
|
||||
constructor(opts: BaseTelegramClient.Options) {
|
||||
super()
|
||||
|
||||
const apiId =
|
||||
typeof opts.apiId === 'string' ? parseInt(opts.apiId) : opts.apiId
|
||||
if (isNaN(apiId))
|
||||
|
|
|
@ -1,87 +1,4 @@
|
|||
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]+)( \+ State)?$/)
|
||||
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]),
|
||||
state: !!m[4]
|
||||
})
|
||||
}
|
||||
|
||||
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()
|
||||
|
||||
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 if (stype === 'plain') {
|
||||
return `${name} handler`
|
||||
} else {
|
||||
return `${name[0].toUpperCase()}${name.substr(1)} handler`
|
||||
}
|
||||
}
|
||||
const { types, toSentence, replaceSections, formatFile } = require('../../client/scripts/generate-updates')
|
||||
|
||||
function generateHandler() {
|
||||
const lines = []
|
||||
|
@ -90,8 +7,6 @@ function generateHandler() {
|
|||
// imports must be added manually because yeah
|
||||
|
||||
types.forEach((type) => {
|
||||
if (type.updateType === 'IGNORE') return
|
||||
|
||||
lines.push(
|
||||
`export type ${type.handlerTypeName}Handler<T = ${type.updateType}` +
|
||||
`${type.state ? ', S = never' : ''}> = ParsedUpdateHandler<` +
|
||||
|
@ -105,147 +20,17 @@ function generateHandler() {
|
|||
lines.join('\n') +
|
||||
'\n\nexport type UpdateHandler = \n' +
|
||||
names.map((i) => ` | ${i}\n`).join(''),
|
||||
})
|
||||
}
|
||||
|
||||
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}<Mod>(
|
||||
filter: UpdateFilter<${type.updateType}, Mod>,
|
||||
handler: ${type.handlerTypeName}Handler<
|
||||
filters.Modify<${type.updateType}, Mod>
|
||||
>['callback']
|
||||
): ${type.handlerTypeName}Handler
|
||||
|
||||
/** @internal */
|
||||
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'",
|
||||
})
|
||||
}, __dirname)
|
||||
}
|
||||
|
||||
function generateDispatcher() {
|
||||
const lines = []
|
||||
const declareLines = []
|
||||
const imports = ['UpdateHandler']
|
||||
const imports = ['UpdateHandler', 'RawUpdateHandler']
|
||||
|
||||
types.forEach((type) => {
|
||||
imports.push(`${type.handlerTypeName}Handler`)
|
||||
|
||||
if (type.updateType === 'IGNORE') {
|
||||
declareLines.push(`
|
||||
/**
|
||||
* Register a plain old ${toSentence(type, 'plain')}
|
||||
*
|
||||
* @param name Event name
|
||||
* @param handler ${toSentence(type, 'full')}
|
||||
*/
|
||||
on(name: '${type.typeName}', handler: ${type.handlerTypeName}Handler['callback']): this
|
||||
`)
|
||||
|
||||
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 {
|
||||
declareLines.push(`
|
||||
/**
|
||||
* Register a plain old ${toSentence(type, 'plain')}
|
||||
*
|
||||
* @param name Event name
|
||||
* @param handler ${toSentence(type, 'full')}
|
||||
*/
|
||||
on(name: '${type.typeName}', handler: ${type.handlerTypeName}Handler['callback']): this
|
||||
|
||||
`)
|
||||
lines.push(`
|
||||
lines.push(`
|
||||
/**
|
||||
* Register ${toSentence(type)} without any filters
|
||||
*
|
||||
|
@ -284,30 +69,31 @@ ${type.state ? `
|
|||
|
||||
/** @internal */
|
||||
on${type.handlerTypeName}(filter: any, handler?: any, group?: number): void {
|
||||
this._addKnownHandler('${type.funcName}', filter, handler, group)
|
||||
this._addKnownHandler('${type.typeName}', filter, handler, group)
|
||||
}
|
||||
`)
|
||||
}
|
||||
})
|
||||
|
||||
replaceSections('dispatcher.ts', {
|
||||
codegen: lines.join('\n'),
|
||||
'codegen-declare': declareLines.join('\n'),
|
||||
'codegen-imports':
|
||||
'import {\n' +
|
||||
imports.map((i) => ` ${i},\n`).join('') +
|
||||
"} from './handler'",
|
||||
})
|
||||
}, __dirname)
|
||||
}
|
||||
|
||||
|
||||
async function main() {
|
||||
generateBuilders()
|
||||
generateHandler()
|
||||
generateDispatcher()
|
||||
|
||||
await formatFile('builders.ts')
|
||||
await formatFile('handler.ts')
|
||||
await formatFile('dispatcher.ts')
|
||||
await formatFile('handler.ts', __dirname)
|
||||
await formatFile('dispatcher.ts', __dirname)
|
||||
}
|
||||
|
||||
main().catch(console.error)
|
||||
module.exports = { types, toSentence }
|
||||
|
||||
if (require.main === module) {
|
||||
main().catch(console.error)
|
||||
}
|
||||
|
|
|
@ -1,423 +0,0 @@
|
|||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||
// begin-codegen-imports
|
||||
import {
|
||||
UpdateHandler,
|
||||
RawUpdateHandler,
|
||||
NewMessageHandler,
|
||||
EditMessageHandler,
|
||||
DeleteMessageHandler,
|
||||
ChatMemberUpdateHandler,
|
||||
InlineQueryHandler,
|
||||
ChosenInlineResultHandler,
|
||||
CallbackQueryHandler,
|
||||
PollUpdateHandler,
|
||||
PollVoteHandler,
|
||||
UserStatusUpdateHandler,
|
||||
UserTypingHandler,
|
||||
HistoryReadHandler,
|
||||
} from './handler'
|
||||
// end-codegen-imports
|
||||
import { filters, UpdateFilter } from './filters'
|
||||
import { CallbackQuery, InlineQuery, Message } from '@mtcute/client'
|
||||
import {
|
||||
ChatMemberUpdate,
|
||||
ChosenInlineResult,
|
||||
PollUpdate,
|
||||
PollVoteUpdate,
|
||||
UserStatusUpdate,
|
||||
UserTypingUpdate,
|
||||
DeleteMessageUpdate,
|
||||
HistoryReadUpdate,
|
||||
} from './updates'
|
||||
|
||||
function _create<T extends UpdateHandler>(
|
||||
type: T['type'],
|
||||
filter: any,
|
||||
handler?: any
|
||||
): T {
|
||||
if (handler) {
|
||||
return {
|
||||
type,
|
||||
check: filter,
|
||||
callback: handler,
|
||||
} as any
|
||||
}
|
||||
|
||||
return {
|
||||
type,
|
||||
callback: filter,
|
||||
} as any
|
||||
}
|
||||
|
||||
export namespace handlers {
|
||||
// begin-codegen
|
||||
|
||||
/**
|
||||
* Create a raw update handler
|
||||
*
|
||||
* @param handler Raw update handler
|
||||
*/
|
||||
export function rawUpdate(
|
||||
handler: RawUpdateHandler['callback']
|
||||
): RawUpdateHandler
|
||||
|
||||
/**
|
||||
* Create a raw update handler with a filter
|
||||
*
|
||||
* @param filter Predicate to check the update against
|
||||
* @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 new message handler
|
||||
*
|
||||
* @param handler New message handler
|
||||
*/
|
||||
export function newMessage(
|
||||
handler: NewMessageHandler['callback']
|
||||
): NewMessageHandler
|
||||
|
||||
/**
|
||||
* Create a new message handler with a filter
|
||||
*
|
||||
* @param filter Update filter
|
||||
* @param handler New message handler
|
||||
*/
|
||||
export function newMessage<Mod>(
|
||||
filter: UpdateFilter<Message, Mod>,
|
||||
handler: NewMessageHandler<filters.Modify<Message, Mod>>['callback']
|
||||
): NewMessageHandler
|
||||
|
||||
/** @internal */
|
||||
export function newMessage(filter: any, handler?: any): NewMessageHandler {
|
||||
return _create('new_message', filter, handler)
|
||||
}
|
||||
|
||||
/**
|
||||
* 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<Mod>(
|
||||
filter: UpdateFilter<Message, Mod>,
|
||||
handler: EditMessageHandler<filters.Modify<Message, Mod>>['callback']
|
||||
): EditMessageHandler
|
||||
|
||||
/** @internal */
|
||||
export function editMessage(
|
||||
filter: any,
|
||||
handler?: any
|
||||
): EditMessageHandler {
|
||||
return _create('edit_message', filter, handler)
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a delete message handler
|
||||
*
|
||||
* @param handler Delete message handler
|
||||
*/
|
||||
export function deleteMessage(
|
||||
handler: DeleteMessageHandler['callback']
|
||||
): DeleteMessageHandler
|
||||
|
||||
/**
|
||||
* Create a delete message handler with a filter
|
||||
*
|
||||
* @param filter Update filter
|
||||
* @param handler Delete message handler
|
||||
*/
|
||||
export function deleteMessage<Mod>(
|
||||
filter: UpdateFilter<DeleteMessageUpdate, Mod>,
|
||||
handler: DeleteMessageHandler<
|
||||
filters.Modify<DeleteMessageUpdate, Mod>
|
||||
>['callback']
|
||||
): DeleteMessageHandler
|
||||
|
||||
/** @internal */
|
||||
export function deleteMessage(
|
||||
filter: any,
|
||||
handler?: any
|
||||
): DeleteMessageHandler {
|
||||
return _create('delete_message', filter, handler)
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a chat member update handler
|
||||
*
|
||||
* @param handler Chat member update handler
|
||||
*/
|
||||
export function chatMemberUpdate(
|
||||
handler: ChatMemberUpdateHandler['callback']
|
||||
): ChatMemberUpdateHandler
|
||||
|
||||
/**
|
||||
* Create a chat member update handler with a filter
|
||||
*
|
||||
* @param filter Update filter
|
||||
* @param handler Chat member update handler
|
||||
*/
|
||||
export function chatMemberUpdate<Mod>(
|
||||
filter: UpdateFilter<ChatMemberUpdate, Mod>,
|
||||
handler: ChatMemberUpdateHandler<
|
||||
filters.Modify<ChatMemberUpdate, Mod>
|
||||
>['callback']
|
||||
): ChatMemberUpdateHandler
|
||||
|
||||
/** @internal */
|
||||
export function chatMemberUpdate(
|
||||
filter: any,
|
||||
handler?: any
|
||||
): ChatMemberUpdateHandler {
|
||||
return _create('chat_member', filter, handler)
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an inline query handler
|
||||
*
|
||||
* @param handler Inline query handler
|
||||
*/
|
||||
export function inlineQuery(
|
||||
handler: InlineQueryHandler['callback']
|
||||
): InlineQueryHandler
|
||||
|
||||
/**
|
||||
* Create an inline query handler with a filter
|
||||
*
|
||||
* @param filter Update filter
|
||||
* @param handler Inline query handler
|
||||
*/
|
||||
export function inlineQuery<Mod>(
|
||||
filter: UpdateFilter<InlineQuery, Mod>,
|
||||
handler: InlineQueryHandler<
|
||||
filters.Modify<InlineQuery, Mod>
|
||||
>['callback']
|
||||
): InlineQueryHandler
|
||||
|
||||
/** @internal */
|
||||
export function inlineQuery(
|
||||
filter: any,
|
||||
handler?: any
|
||||
): InlineQueryHandler {
|
||||
return _create('inline_query', filter, handler)
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a chosen inline result handler
|
||||
*
|
||||
* @param handler Chosen inline result handler
|
||||
*/
|
||||
export function chosenInlineResult(
|
||||
handler: ChosenInlineResultHandler['callback']
|
||||
): ChosenInlineResultHandler
|
||||
|
||||
/**
|
||||
* Create a chosen inline result handler with a filter
|
||||
*
|
||||
* @param filter Update filter
|
||||
* @param handler Chosen inline result handler
|
||||
*/
|
||||
export function chosenInlineResult<Mod>(
|
||||
filter: UpdateFilter<ChosenInlineResult, Mod>,
|
||||
handler: ChosenInlineResultHandler<
|
||||
filters.Modify<ChosenInlineResult, Mod>
|
||||
>['callback']
|
||||
): ChosenInlineResultHandler
|
||||
|
||||
/** @internal */
|
||||
export function chosenInlineResult(
|
||||
filter: any,
|
||||
handler?: any
|
||||
): ChosenInlineResultHandler {
|
||||
return _create('chosen_inline_result', filter, handler)
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a callback query handler
|
||||
*
|
||||
* @param handler Callback query handler
|
||||
*/
|
||||
export function callbackQuery(
|
||||
handler: CallbackQueryHandler['callback']
|
||||
): CallbackQueryHandler
|
||||
|
||||
/**
|
||||
* Create a callback query handler with a filter
|
||||
*
|
||||
* @param filter Update filter
|
||||
* @param handler Callback query handler
|
||||
*/
|
||||
export function callbackQuery<Mod>(
|
||||
filter: UpdateFilter<CallbackQuery, Mod>,
|
||||
handler: CallbackQueryHandler<
|
||||
filters.Modify<CallbackQuery, Mod>
|
||||
>['callback']
|
||||
): CallbackQueryHandler
|
||||
|
||||
/** @internal */
|
||||
export function callbackQuery(
|
||||
filter: any,
|
||||
handler?: any
|
||||
): CallbackQueryHandler {
|
||||
return _create('callback_query', filter, handler)
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a poll update handler
|
||||
*
|
||||
* @param handler Poll update handler
|
||||
*/
|
||||
export function pollUpdate(
|
||||
handler: PollUpdateHandler['callback']
|
||||
): PollUpdateHandler
|
||||
|
||||
/**
|
||||
* Create a poll update handler with a filter
|
||||
*
|
||||
* @param filter Update filter
|
||||
* @param handler Poll update handler
|
||||
*/
|
||||
export function pollUpdate<Mod>(
|
||||
filter: UpdateFilter<PollUpdate, Mod>,
|
||||
handler: PollUpdateHandler<filters.Modify<PollUpdate, Mod>>['callback']
|
||||
): PollUpdateHandler
|
||||
|
||||
/** @internal */
|
||||
export function pollUpdate(filter: any, handler?: any): PollUpdateHandler {
|
||||
return _create('poll', filter, handler)
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a poll vote handler
|
||||
*
|
||||
* @param handler Poll vote handler
|
||||
*/
|
||||
export function pollVote(
|
||||
handler: PollVoteHandler['callback']
|
||||
): PollVoteHandler
|
||||
|
||||
/**
|
||||
* Create a poll vote handler with a filter
|
||||
*
|
||||
* @param filter Update filter
|
||||
* @param handler Poll vote handler
|
||||
*/
|
||||
export function pollVote<Mod>(
|
||||
filter: UpdateFilter<PollVoteUpdate, Mod>,
|
||||
handler: PollVoteHandler<
|
||||
filters.Modify<PollVoteUpdate, Mod>
|
||||
>['callback']
|
||||
): PollVoteHandler
|
||||
|
||||
/** @internal */
|
||||
export function pollVote(filter: any, handler?: any): PollVoteHandler {
|
||||
return _create('poll_vote', filter, handler)
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an user status update handler
|
||||
*
|
||||
* @param handler User status update handler
|
||||
*/
|
||||
export function userStatusUpdate(
|
||||
handler: UserStatusUpdateHandler['callback']
|
||||
): UserStatusUpdateHandler
|
||||
|
||||
/**
|
||||
* Create an user status update handler with a filter
|
||||
*
|
||||
* @param filter Update filter
|
||||
* @param handler User status update handler
|
||||
*/
|
||||
export function userStatusUpdate<Mod>(
|
||||
filter: UpdateFilter<UserStatusUpdate, Mod>,
|
||||
handler: UserStatusUpdateHandler<
|
||||
filters.Modify<UserStatusUpdate, Mod>
|
||||
>['callback']
|
||||
): UserStatusUpdateHandler
|
||||
|
||||
/** @internal */
|
||||
export function userStatusUpdate(
|
||||
filter: any,
|
||||
handler?: any
|
||||
): UserStatusUpdateHandler {
|
||||
return _create('user_status', filter, handler)
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an user typing handler
|
||||
*
|
||||
* @param handler User typing handler
|
||||
*/
|
||||
export function userTyping(
|
||||
handler: UserTypingHandler['callback']
|
||||
): UserTypingHandler
|
||||
|
||||
/**
|
||||
* Create an user typing handler with a filter
|
||||
*
|
||||
* @param filter Update filter
|
||||
* @param handler User typing handler
|
||||
*/
|
||||
export function userTyping<Mod>(
|
||||
filter: UpdateFilter<UserTypingUpdate, Mod>,
|
||||
handler: UserTypingHandler<
|
||||
filters.Modify<UserTypingUpdate, Mod>
|
||||
>['callback']
|
||||
): UserTypingHandler
|
||||
|
||||
/** @internal */
|
||||
export function userTyping(filter: any, handler?: any): UserTypingHandler {
|
||||
return _create('user_typing', filter, handler)
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a history read handler
|
||||
*
|
||||
* @param handler History read handler
|
||||
*/
|
||||
export function historyRead(
|
||||
handler: HistoryReadHandler['callback']
|
||||
): HistoryReadHandler
|
||||
|
||||
/**
|
||||
* Create a history read handler with a filter
|
||||
*
|
||||
* @param filter Update filter
|
||||
* @param handler History read handler
|
||||
*/
|
||||
export function historyRead<Mod>(
|
||||
filter: UpdateFilter<HistoryReadUpdate, Mod>,
|
||||
handler: HistoryReadHandler<
|
||||
filters.Modify<HistoryReadUpdate, Mod>
|
||||
>['callback']
|
||||
): HistoryReadHandler
|
||||
|
||||
/** @internal */
|
||||
export function historyRead(
|
||||
filter: any,
|
||||
handler?: any
|
||||
): HistoryReadHandler {
|
||||
return _create('history_read', filter, handler)
|
||||
}
|
||||
|
||||
// end-codegen
|
||||
}
|
|
@ -8,6 +8,15 @@ import {
|
|||
MtCuteArgumentError,
|
||||
TelegramClient,
|
||||
UsersIndex,
|
||||
ChatMemberUpdate,
|
||||
ChosenInlineResult,
|
||||
PollUpdate,
|
||||
PollVoteUpdate,
|
||||
UserStatusUpdate,
|
||||
UserTypingUpdate,
|
||||
DeleteMessageUpdate,
|
||||
HistoryReadUpdate,
|
||||
ParsedUpdate,
|
||||
} from '@mtcute/client'
|
||||
import { tl } from '@mtcute/tl'
|
||||
// begin-codegen-imports
|
||||
|
@ -28,262 +37,20 @@ import {
|
|||
HistoryReadHandler,
|
||||
} from './handler'
|
||||
// end-codegen-imports
|
||||
import { ParsedUpdate } from './handler'
|
||||
import { filters, UpdateFilter } from './filters'
|
||||
import { handlers } from './builders'
|
||||
import {
|
||||
ChatMemberUpdate,
|
||||
ChosenInlineResult,
|
||||
PollUpdate,
|
||||
PollVoteUpdate,
|
||||
UserStatusUpdate,
|
||||
UserTypingUpdate,
|
||||
DeleteMessageUpdate,
|
||||
HistoryReadUpdate,
|
||||
} from './updates'
|
||||
import { IStateStorage, UpdateState, StateKeyDelegate } from './state'
|
||||
import { defaultStateKeyDelegate } from './state'
|
||||
import { PropagationAction } from './propagation'
|
||||
import EventEmitter from 'events'
|
||||
|
||||
const noop = () => {}
|
||||
|
||||
type ParserFunction = (
|
||||
client: TelegramClient,
|
||||
upd: tl.TypeUpdate | tl.TypeMessage,
|
||||
users: UsersIndex,
|
||||
chats: ChatsIndex
|
||||
) => any
|
||||
type UpdateParser = [Exclude<UpdateHandler['type'], 'raw'>, ParserFunction]
|
||||
|
||||
const baseMessageParser: ParserFunction = (
|
||||
client: TelegramClient,
|
||||
upd,
|
||||
users,
|
||||
chats
|
||||
) =>
|
||||
new Message(
|
||||
client,
|
||||
tl.isAnyMessage(upd) ? upd : (upd as any).message,
|
||||
users,
|
||||
chats,
|
||||
upd._ === 'updateNewScheduledMessage'
|
||||
)
|
||||
|
||||
const newMessageParser: UpdateParser = ['new_message', baseMessageParser]
|
||||
const editMessageParser: UpdateParser = ['edit_message', baseMessageParser]
|
||||
const chatMemberParser: UpdateParser = [
|
||||
'chat_member',
|
||||
(client, upd, users, chats) =>
|
||||
new ChatMemberUpdate(client, upd as any, users, chats),
|
||||
]
|
||||
const callbackQueryParser: UpdateParser = [
|
||||
'callback_query',
|
||||
(client, upd, users) => new CallbackQuery(client, upd as any, users),
|
||||
]
|
||||
const userTypingParser: UpdateParser = [
|
||||
'user_typing',
|
||||
(client, upd) => new UserTypingUpdate(client, upd as any),
|
||||
]
|
||||
const deleteMessageParser: UpdateParser = [
|
||||
'delete_message',
|
||||
(client, upd) => new DeleteMessageUpdate(client, upd as any),
|
||||
]
|
||||
const historyReadParser: UpdateParser = [
|
||||
'history_read',
|
||||
(client, upd) => new HistoryReadUpdate(client, upd as any),
|
||||
]
|
||||
|
||||
const PARSERS: Partial<
|
||||
Record<(tl.TypeUpdate | tl.TypeMessage)['_'], UpdateParser>
|
||||
> = {
|
||||
message: newMessageParser,
|
||||
messageEmpty: newMessageParser,
|
||||
messageService: newMessageParser,
|
||||
updateNewMessage: newMessageParser,
|
||||
updateNewChannelMessage: newMessageParser,
|
||||
updateNewScheduledMessage: newMessageParser,
|
||||
updateEditMessage: editMessageParser,
|
||||
updateEditChannelMessage: editMessageParser,
|
||||
updateChatParticipant: chatMemberParser,
|
||||
updateChannelParticipant: chatMemberParser,
|
||||
updateBotInlineQuery: [
|
||||
'inline_query',
|
||||
(client, upd, users) => new InlineQuery(client, upd as any, users),
|
||||
],
|
||||
updateBotInlineSend: [
|
||||
'chosen_inline_result',
|
||||
(client, upd, users) =>
|
||||
new ChosenInlineResult(client, upd as any, users),
|
||||
],
|
||||
updateBotCallbackQuery: callbackQueryParser,
|
||||
updateInlineBotCallbackQuery: callbackQueryParser,
|
||||
updateMessagePoll: [
|
||||
'poll',
|
||||
(client, upd, users) => new PollUpdate(client, upd as any, users),
|
||||
],
|
||||
updateMessagePollVote: [
|
||||
'poll_vote',
|
||||
(client, upd, users) => new PollVoteUpdate(client, upd as any, users),
|
||||
],
|
||||
updateUserStatus: [
|
||||
'user_status',
|
||||
(client, upd) => new UserStatusUpdate(client, upd as any),
|
||||
],
|
||||
updateChannelUserTyping: userTypingParser,
|
||||
updateChatUserTyping: userTypingParser,
|
||||
updateUserTyping: userTypingParser,
|
||||
updateDeleteChannelMessages: deleteMessageParser,
|
||||
updateDeleteMessages: deleteMessageParser,
|
||||
updateReadHistoryInbox: historyReadParser,
|
||||
updateReadHistoryOutbox: historyReadParser,
|
||||
updateReadChannelInbox: historyReadParser,
|
||||
updateReadChannelOutbox: historyReadParser,
|
||||
updateReadChannelDiscussionInbox: historyReadParser,
|
||||
updateReadChannelDiscussionOutbox: historyReadParser,
|
||||
}
|
||||
|
||||
const HANDLER_TYPE_TO_UPDATE: Record<string, string[]> = {}
|
||||
Object.keys(PARSERS).forEach((upd: keyof typeof PARSERS) => {
|
||||
const handler = PARSERS[upd]![0]
|
||||
if (!(handler in HANDLER_TYPE_TO_UPDATE))
|
||||
HANDLER_TYPE_TO_UPDATE[handler] = []
|
||||
HANDLER_TYPE_TO_UPDATE[handler].push(upd)
|
||||
})
|
||||
|
||||
export declare interface Dispatcher<
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
State = never,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
SceneName extends string = string
|
||||
> {
|
||||
on<T = {}>(
|
||||
name: 'update',
|
||||
handler: (update: ParsedUpdate & T) => void
|
||||
): this
|
||||
|
||||
// begin-codegen-declare
|
||||
|
||||
/**
|
||||
* Register a plain old raw update handler
|
||||
*
|
||||
* @param name Event name
|
||||
* @param handler Raw update handler
|
||||
*/
|
||||
on(name: 'raw', handler: RawUpdateHandler['callback']): this
|
||||
|
||||
/**
|
||||
* Register a plain old new message handler
|
||||
*
|
||||
* @param name Event name
|
||||
* @param handler New message handler
|
||||
*/
|
||||
on(name: 'new_message', handler: NewMessageHandler['callback']): this
|
||||
|
||||
/**
|
||||
* Register a plain old edit message handler
|
||||
*
|
||||
* @param name Event name
|
||||
* @param handler Edit message handler
|
||||
*/
|
||||
on(name: 'edit_message', handler: EditMessageHandler['callback']): this
|
||||
|
||||
/**
|
||||
* Register a plain old delete message handler
|
||||
*
|
||||
* @param name Event name
|
||||
* @param handler Delete message handler
|
||||
*/
|
||||
on(name: 'delete_message', handler: DeleteMessageHandler['callback']): this
|
||||
|
||||
/**
|
||||
* Register a plain old chat member update handler
|
||||
*
|
||||
* @param name Event name
|
||||
* @param handler Chat member update handler
|
||||
*/
|
||||
on(name: 'chat_member', handler: ChatMemberUpdateHandler['callback']): this
|
||||
|
||||
/**
|
||||
* Register a plain old inline query handler
|
||||
*
|
||||
* @param name Event name
|
||||
* @param handler Inline query handler
|
||||
*/
|
||||
on(name: 'inline_query', handler: InlineQueryHandler['callback']): this
|
||||
|
||||
/**
|
||||
* Register a plain old chosen inline result handler
|
||||
*
|
||||
* @param name Event name
|
||||
* @param handler Chosen inline result handler
|
||||
*/
|
||||
on(
|
||||
name: 'chosen_inline_result',
|
||||
handler: ChosenInlineResultHandler['callback']
|
||||
): this
|
||||
|
||||
/**
|
||||
* Register a plain old callback query handler
|
||||
*
|
||||
* @param name Event name
|
||||
* @param handler Callback query handler
|
||||
*/
|
||||
on(name: 'callback_query', handler: CallbackQueryHandler['callback']): this
|
||||
|
||||
/**
|
||||
* Register a plain old poll update handler
|
||||
*
|
||||
* @param name Event name
|
||||
* @param handler Poll update handler
|
||||
*/
|
||||
on(name: 'poll', handler: PollUpdateHandler['callback']): this
|
||||
|
||||
/**
|
||||
* Register a plain old poll vote handler
|
||||
*
|
||||
* @param name Event name
|
||||
* @param handler Poll vote handler
|
||||
*/
|
||||
on(name: 'poll_vote', handler: PollVoteHandler['callback']): this
|
||||
|
||||
/**
|
||||
* Register a plain old user status update handler
|
||||
*
|
||||
* @param name Event name
|
||||
* @param handler User status update handler
|
||||
*/
|
||||
on(name: 'user_status', handler: UserStatusUpdateHandler['callback']): this
|
||||
|
||||
/**
|
||||
* Register a plain old user typing handler
|
||||
*
|
||||
* @param name Event name
|
||||
* @param handler User typing handler
|
||||
*/
|
||||
on(name: 'user_typing', handler: UserTypingHandler['callback']): this
|
||||
|
||||
/**
|
||||
* Register a plain old history read handler
|
||||
*
|
||||
* @param name Event name
|
||||
* @param handler History read handler
|
||||
*/
|
||||
on(name: 'history_read', handler: HistoryReadHandler['callback']): this
|
||||
|
||||
// end-codegen-declare
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates dispatcher
|
||||
*/
|
||||
export class Dispatcher<
|
||||
State = never,
|
||||
SceneName extends string = string
|
||||
> extends EventEmitter {
|
||||
export class Dispatcher<State = never, SceneName extends string = string> {
|
||||
private _groups: Record<
|
||||
number,
|
||||
Record<UpdateHandler['type'], UpdateHandler[]>
|
||||
Record<UpdateHandler['name'], UpdateHandler[]>
|
||||
> = {}
|
||||
private _groupsOrder: number[] = []
|
||||
|
||||
|
@ -304,8 +71,6 @@ export class Dispatcher<
|
|||
private _customStateKeyDelegate?: StateKeyDelegate
|
||||
private _customStorage?: IStateStorage
|
||||
|
||||
private _handlersCount: Record<string, number> = {}
|
||||
|
||||
private _errorHandler?: <T = {}>(
|
||||
err: Error,
|
||||
update: ParsedUpdate & T,
|
||||
|
@ -346,7 +111,8 @@ export class Dispatcher<
|
|||
storage?: IStateStorage | StateKeyDelegate,
|
||||
key?: StateKeyDelegate
|
||||
) {
|
||||
super()
|
||||
this.dispatchRawUpdate = this.dispatchRawUpdate.bind(this)
|
||||
this.dispatchUpdate = this.dispatchUpdate.bind(this)
|
||||
|
||||
if (client) {
|
||||
if (client instanceof TelegramClient) {
|
||||
|
@ -383,7 +149,9 @@ export class Dispatcher<
|
|||
* Dispatcher also uses bound client to throw errors
|
||||
*/
|
||||
bindToClient(client: TelegramClient): void {
|
||||
client['dispatchUpdate'] = this.dispatchUpdate.bind(this)
|
||||
client.on('update', this.dispatchUpdate)
|
||||
client.on('raw_update', this.dispatchRawUpdate)
|
||||
|
||||
this._client = client
|
||||
}
|
||||
|
||||
|
@ -395,11 +163,103 @@ export class Dispatcher<
|
|||
*/
|
||||
unbind(): void {
|
||||
if (this._client) {
|
||||
this._client['dispatchUpdate'] = noop
|
||||
this._client.off('update', this.dispatchUpdate)
|
||||
this._client.off('raw_update', this.dispatchRawUpdate)
|
||||
|
||||
this._client = undefined
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a raw update with this dispatcher.
|
||||
* Calling this method without bound client will not work.
|
||||
*
|
||||
* Under the hood asynchronously calls {@link dispatchRawUpdateNow}
|
||||
* with error handler set to client's one.
|
||||
*
|
||||
* @param update Update to process
|
||||
* @param users Users map
|
||||
* @param chats Chats map
|
||||
*/
|
||||
dispatchRawUpdate(
|
||||
update: tl.TypeUpdate | tl.TypeMessage,
|
||||
users: UsersIndex,
|
||||
chats: ChatsIndex
|
||||
): void {
|
||||
if (!this._client) return
|
||||
|
||||
// order does not matter in the dispatcher,
|
||||
// so we can handle each update in its own task
|
||||
this.dispatchRawUpdateNow(update, users, chats).catch((err) =>
|
||||
this._client!['_emitError'](err)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a raw update right now in the current stack.
|
||||
*
|
||||
* Unlike {@link dispatchRawUpdate}, this does not schedule
|
||||
* the update to be dispatched, but dispatches it immediately,
|
||||
* and after `await`ing this method you can be certain that the update
|
||||
* was fully processed by all the registered handlers, including children.
|
||||
*
|
||||
* @param update Update to process
|
||||
* @param users Users map
|
||||
* @param chats Chats map
|
||||
* @returns Whether the update was handled
|
||||
*/
|
||||
async dispatchRawUpdateNow(
|
||||
update: tl.TypeUpdate | tl.TypeMessage,
|
||||
users: UsersIndex,
|
||||
chats: ChatsIndex
|
||||
): Promise<boolean> {
|
||||
if (!this._client) return false
|
||||
|
||||
let handled = false
|
||||
|
||||
outer: for (const grp of this._groupsOrder) {
|
||||
const group = this._groups[grp]
|
||||
|
||||
if ('raw' in group) {
|
||||
const handlers = group['raw'] as RawUpdateHandler[]
|
||||
|
||||
for (const h of handlers) {
|
||||
let result: void | PropagationAction
|
||||
|
||||
if (
|
||||
!h.check ||
|
||||
(await h.check(this._client, update, users, chats))
|
||||
) {
|
||||
result = await h.callback(
|
||||
this._client,
|
||||
update,
|
||||
users,
|
||||
chats
|
||||
)
|
||||
handled = true
|
||||
} else continue
|
||||
|
||||
switch (result) {
|
||||
case 'continue':
|
||||
continue
|
||||
case 'stop':
|
||||
break outer
|
||||
case 'stop-children':
|
||||
return handled
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const child of this._children) {
|
||||
handled ||= await child.dispatchRawUpdateNow(update, users, chats)
|
||||
}
|
||||
|
||||
return handled
|
||||
}
|
||||
|
||||
/**
|
||||
* Process an update with this dispatcher.
|
||||
* Calling this method without bound client will not work.
|
||||
|
@ -408,19 +268,13 @@ export class Dispatcher<
|
|||
* with error handler set to client's one.
|
||||
*
|
||||
* @param update Update to process
|
||||
* @param users Map of users
|
||||
* @param chats Map of chats
|
||||
*/
|
||||
dispatchUpdate(
|
||||
update: tl.TypeUpdate | tl.TypeMessage,
|
||||
users: UsersIndex,
|
||||
chats: ChatsIndex
|
||||
): void {
|
||||
dispatchUpdate(update: ParsedUpdate): void {
|
||||
if (!this._client) return
|
||||
|
||||
// order does not matter in the dispatcher,
|
||||
// so we can handle each update in its own task
|
||||
this.dispatchUpdateNow(update, users, chats).catch((err) =>
|
||||
this.dispatchUpdateNow(update).catch((err) =>
|
||||
this._client!['_emitError'](err)
|
||||
)
|
||||
}
|
||||
|
@ -434,58 +288,31 @@ export class Dispatcher<
|
|||
* was fully processed by all the registered handlers, including children.
|
||||
*
|
||||
* @param update Update to process
|
||||
* @param users Map of users
|
||||
* @param chats Map of chats
|
||||
* @returns Whether the update was handled
|
||||
*/
|
||||
async dispatchUpdateNow(
|
||||
update: tl.TypeUpdate | tl.TypeMessage,
|
||||
users: UsersIndex,
|
||||
chats: ChatsIndex
|
||||
): Promise<boolean> {
|
||||
return this._dispatchUpdateNowImpl(update, users, chats)
|
||||
async dispatchUpdateNow(update: ParsedUpdate): Promise<boolean> {
|
||||
return this._dispatchUpdateNowImpl(update)
|
||||
}
|
||||
|
||||
private async _dispatchUpdateNowImpl(
|
||||
update: tl.TypeUpdate | tl.TypeMessage | null,
|
||||
users: UsersIndex | null,
|
||||
chats: ChatsIndex | null,
|
||||
update: ParsedUpdate,
|
||||
// this is getting a bit crazy lol
|
||||
parsed?: any,
|
||||
parsedType?: Exclude<UpdateHandler['type'], 'raw'> | null,
|
||||
parsedState?: UpdateState<State, SceneName> | null,
|
||||
parsedScene?: string | null,
|
||||
forceScene?: true
|
||||
): Promise<boolean> {
|
||||
if (!this._client) return false
|
||||
|
||||
const isRawMessage = update && tl.isAnyMessage(update)
|
||||
|
||||
if (parsed === undefined) {
|
||||
const pair = PARSERS[update!._]
|
||||
if (pair) {
|
||||
if (
|
||||
this._handlersCount[update!._] ||
|
||||
this.listenerCount(pair[0])
|
||||
) {
|
||||
parsed = pair[1](this._client, update!, users!, chats!)
|
||||
parsedType = pair[0]
|
||||
}
|
||||
} else {
|
||||
parsed = parsedType = null
|
||||
}
|
||||
}
|
||||
|
||||
if (parsedScene === undefined) {
|
||||
if (
|
||||
this._storage &&
|
||||
this._scenes &&
|
||||
(parsedType === 'new_message' ||
|
||||
parsedType === 'edit_message' ||
|
||||
parsedType === 'callback_query')
|
||||
(update.name === 'new_message' ||
|
||||
update.name === 'edit_message' ||
|
||||
update.name === 'callback_query')
|
||||
) {
|
||||
// no need to fetch scene if there are no registered scenes
|
||||
const key = await this._stateKeyDelegate!(parsed)
|
||||
const key = await this._stateKeyDelegate!(update.data)
|
||||
if (key) {
|
||||
parsedScene = await this._storage.getCurrentScene(key)
|
||||
} else {
|
||||
|
@ -508,10 +335,6 @@ export class Dispatcher<
|
|||
|
||||
return this._scenes[parsedScene]._dispatchUpdateNowImpl(
|
||||
update,
|
||||
users,
|
||||
chats,
|
||||
parsed,
|
||||
parsedType,
|
||||
parsedState,
|
||||
parsedScene,
|
||||
true
|
||||
|
@ -522,16 +345,18 @@ export class Dispatcher<
|
|||
if (parsedState === undefined) {
|
||||
if (
|
||||
this._storage &&
|
||||
(parsedType === 'new_message' ||
|
||||
parsedType === 'edit_message' ||
|
||||
parsedType === 'callback_query')
|
||||
(update.name === 'new_message' ||
|
||||
update.name === 'edit_message' ||
|
||||
update.name === 'callback_query')
|
||||
) {
|
||||
const key = await this._stateKeyDelegate!(parsed)
|
||||
const key = await this._stateKeyDelegate!(update.data)
|
||||
if (key) {
|
||||
let customKey
|
||||
if (
|
||||
!this._customStateKeyDelegate ||
|
||||
(customKey = await this._customStateKeyDelegate(parsed))
|
||||
(customKey = await this._customStateKeyDelegate(
|
||||
update.data
|
||||
))
|
||||
) {
|
||||
parsedState = new UpdateState(
|
||||
this._storage!,
|
||||
|
@ -552,79 +377,23 @@ export class Dispatcher<
|
|||
|
||||
let shouldDispatch = true
|
||||
let shouldDispatchChildren = true
|
||||
let wasHandled = false
|
||||
let updateInfo: any = null
|
||||
let handled = false
|
||||
|
||||
if (parsed) {
|
||||
updateInfo = { type: parsedType, data: parsed }
|
||||
switch (
|
||||
await this._preUpdateHandler?.(
|
||||
updateInfo as any,
|
||||
parsedState as any
|
||||
)
|
||||
) {
|
||||
case 'stop':
|
||||
shouldDispatch = false
|
||||
break
|
||||
case 'stop-children':
|
||||
return false
|
||||
}
|
||||
switch (await this._preUpdateHandler?.(update, parsedState as any)) {
|
||||
case 'stop':
|
||||
shouldDispatch = false
|
||||
break
|
||||
case 'stop-children':
|
||||
return false
|
||||
}
|
||||
|
||||
if (shouldDispatch) {
|
||||
if (update && !isRawMessage) {
|
||||
this.emit('raw', update, users, chats)
|
||||
}
|
||||
|
||||
if (parsedType) {
|
||||
this.emit('update', updateInfo)
|
||||
this.emit(parsedType, parsed)
|
||||
}
|
||||
|
||||
outer: for (const grp of this._groupsOrder) {
|
||||
const group = this._groups[grp]
|
||||
|
||||
if (update && !isRawMessage && 'raw' in group) {
|
||||
const handlers = group['raw'] as RawUpdateHandler[]
|
||||
|
||||
for (const h of handlers) {
|
||||
let result: void | PropagationAction
|
||||
|
||||
if (
|
||||
!h.check ||
|
||||
(await h.check(
|
||||
this._client,
|
||||
update as any,
|
||||
users!,
|
||||
chats!
|
||||
))
|
||||
) {
|
||||
result = await h.callback(
|
||||
this._client,
|
||||
update as any,
|
||||
users!,
|
||||
chats!
|
||||
)
|
||||
wasHandled = true
|
||||
} else continue
|
||||
|
||||
switch (result) {
|
||||
case 'continue':
|
||||
continue
|
||||
case 'stop':
|
||||
break outer
|
||||
case 'stop-children':
|
||||
shouldDispatchChildren = false
|
||||
break outer
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (parsedType && parsedType in group) {
|
||||
if (update.name in group) {
|
||||
// raw is not handled here, so we can safely assume this
|
||||
const handlers = group[parsedType] as Exclude<
|
||||
const handlers = group[update.name] as Exclude<
|
||||
UpdateHandler,
|
||||
RawUpdateHandler
|
||||
>[]
|
||||
|
@ -635,13 +404,16 @@ export class Dispatcher<
|
|||
|
||||
if (
|
||||
!h.check ||
|
||||
(await h.check(parsed, parsedState as never))
|
||||
(await h.check(
|
||||
update.data as any,
|
||||
parsedState as never
|
||||
))
|
||||
) {
|
||||
result = await h.callback(
|
||||
parsed,
|
||||
update.data as any,
|
||||
parsedState as never
|
||||
)
|
||||
wasHandled = true
|
||||
handled = true
|
||||
} else continue
|
||||
|
||||
switch (result) {
|
||||
|
@ -669,10 +441,6 @@ export class Dispatcher<
|
|||
scene
|
||||
]._dispatchUpdateNowImpl(
|
||||
update,
|
||||
users,
|
||||
chats,
|
||||
parsed,
|
||||
parsedType,
|
||||
undefined,
|
||||
scene,
|
||||
true
|
||||
|
@ -686,7 +454,7 @@ export class Dispatcher<
|
|||
if (this._errorHandler) {
|
||||
const handled = await this._errorHandler(
|
||||
e,
|
||||
updateInfo as any,
|
||||
update,
|
||||
parsedState as never
|
||||
)
|
||||
if (!handled) throw e
|
||||
|
@ -700,25 +468,13 @@ export class Dispatcher<
|
|||
|
||||
if (shouldDispatchChildren) {
|
||||
for (const child of this._children) {
|
||||
wasHandled ||= await child._dispatchUpdateNowImpl(
|
||||
update,
|
||||
users,
|
||||
chats,
|
||||
parsed,
|
||||
parsedType
|
||||
)
|
||||
handled ||= await child._dispatchUpdateNowImpl(update)
|
||||
}
|
||||
}
|
||||
|
||||
if (updateInfo) {
|
||||
this._postUpdateHandler?.(
|
||||
wasHandled,
|
||||
updateInfo,
|
||||
parsedState as any
|
||||
)
|
||||
}
|
||||
this._postUpdateHandler?.(handled, update, parsedState as any)
|
||||
|
||||
return wasHandled
|
||||
return handled
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -734,28 +490,23 @@ export class Dispatcher<
|
|||
this._groupsOrder.sort((a, b) => a - b)
|
||||
}
|
||||
|
||||
if (!(handler.type in this._groups[group])) {
|
||||
this._groups[group][handler.type] = []
|
||||
if (!(handler.name in this._groups[group])) {
|
||||
this._groups[group][handler.name] = []
|
||||
}
|
||||
|
||||
HANDLER_TYPE_TO_UPDATE[handler.type].forEach((upd) => {
|
||||
if (!(upd in this._handlersCount)) this._handlersCount[upd] = 0
|
||||
this._handlersCount[upd] += 1
|
||||
})
|
||||
|
||||
this._groups[group][handler.type].push(handler)
|
||||
this._groups[group][handler.name].push(handler)
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove an update handler (or handlers) from a given
|
||||
* handler group.
|
||||
*
|
||||
* @param handler Update handler to remove, its type or `'all'` to remove all
|
||||
* @param handler Update handler to remove, its name or `'all'` to remove all
|
||||
* @param group Handler group index (-1 to affect all groups)
|
||||
* @internal
|
||||
*/
|
||||
removeUpdateHandler(
|
||||
handler: UpdateHandler | UpdateHandler['type'] | 'all',
|
||||
handler: UpdateHandler | UpdateHandler['name'] | 'all',
|
||||
group = 0
|
||||
): void {
|
||||
if (group !== -1 && !(group in this._groups)) {
|
||||
|
@ -766,38 +517,22 @@ export class Dispatcher<
|
|||
if (handler === 'all') {
|
||||
if (group === -1) {
|
||||
this._groups = {}
|
||||
this._handlersCount = {}
|
||||
} else {
|
||||
const grp = this._groups[group] as any
|
||||
Object.keys(grp).forEach((handler) => {
|
||||
HANDLER_TYPE_TO_UPDATE[handler].forEach((upd) => {
|
||||
this._handlersCount[upd] -= grp[handler].length
|
||||
})
|
||||
})
|
||||
delete this._groups[group]
|
||||
}
|
||||
} else {
|
||||
HANDLER_TYPE_TO_UPDATE[handler].forEach((upd) => {
|
||||
this._handlersCount[upd] -= this._groups[group][
|
||||
handler
|
||||
].length
|
||||
})
|
||||
delete this._groups[group][handler]
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if (!(handler.type in this._groups[group])) {
|
||||
if (!(handler.name in this._groups[group])) {
|
||||
return
|
||||
}
|
||||
|
||||
const idx = this._groups[group][handler.type].indexOf(handler)
|
||||
if (idx > 0) {
|
||||
this._groups[group][handler.type].splice(idx, 1)
|
||||
|
||||
HANDLER_TYPE_TO_UPDATE[handler.type].forEach((upd) => {
|
||||
this._handlersCount[upd] -= 1
|
||||
})
|
||||
const idx = this._groups[group][handler.name].indexOf(handler)
|
||||
if (idx > -1) {
|
||||
this._groups[group][handler.name].splice(idx, 1)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1071,10 +806,6 @@ export class Dispatcher<
|
|||
}
|
||||
})
|
||||
|
||||
Object.keys(other._handlersCount).forEach((typ) => {
|
||||
this._handlersCount[typ] += other._handlersCount[typ]
|
||||
})
|
||||
|
||||
other._children.forEach((it) => {
|
||||
it._unparent()
|
||||
this.addChild(it as any)
|
||||
|
@ -1121,14 +852,13 @@ export class Dispatcher<
|
|||
dp._groups[idx] = {} as any
|
||||
|
||||
Object.keys(this._groups[idx]).forEach(
|
||||
(type: UpdateHandler['type']) => {
|
||||
(type: UpdateHandler['name']) => {
|
||||
dp._groups[idx][type] = [...this._groups[idx][type]]
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
dp._groupsOrder = [...this._groupsOrder]
|
||||
dp._handlersCount = { ...this._handlersCount }
|
||||
dp._errorHandler = this._errorHandler
|
||||
dp._customStateKeyDelegate = this._customStateKeyDelegate
|
||||
dp._customStorage = this._customStorage
|
||||
|
@ -1268,16 +998,23 @@ export class Dispatcher<
|
|||
// addUpdateHandler convenience wrappers //
|
||||
|
||||
private _addKnownHandler(
|
||||
name: keyof typeof handlers,
|
||||
name: UpdateHandler['name'],
|
||||
filter: any,
|
||||
handler?: any,
|
||||
group?: number
|
||||
): void {
|
||||
if (typeof handler === 'number') {
|
||||
this.addUpdateHandler((handlers as any)[name](filter), handler)
|
||||
this.addUpdateHandler({
|
||||
name,
|
||||
callback: filter
|
||||
}, handler)
|
||||
} else {
|
||||
this.addUpdateHandler(
|
||||
(handlers as any)[name](filter, handler),
|
||||
{
|
||||
name,
|
||||
callback: handler,
|
||||
check: filter
|
||||
},
|
||||
group
|
||||
)
|
||||
}
|
||||
|
@ -1285,32 +1022,6 @@ export class Dispatcher<
|
|||
|
||||
// begin-codegen
|
||||
|
||||
/**
|
||||
* Register a raw update handler without any filters
|
||||
*
|
||||
* @param handler Raw update handler
|
||||
* @param group Handler group index
|
||||
*/
|
||||
onRawUpdate(handler: RawUpdateHandler['callback'], group?: number): void
|
||||
|
||||
/**
|
||||
* Register a raw update handler with a filter
|
||||
*
|
||||
* @param filter Update filter function
|
||||
* @param handler Raw update handler
|
||||
* @param group Handler group index
|
||||
*/
|
||||
onRawUpdate(
|
||||
filter: RawUpdateHandler['check'],
|
||||
handler: RawUpdateHandler['callback'],
|
||||
group?: number
|
||||
): void
|
||||
|
||||
/** @internal */
|
||||
onRawUpdate(filter: any, handler?: any, group?: number): void {
|
||||
this._addKnownHandler('rawUpdate', filter, handler, group)
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a new message handler without any filters
|
||||
*
|
||||
|
@ -1359,7 +1070,7 @@ export class Dispatcher<
|
|||
|
||||
/** @internal */
|
||||
onNewMessage(filter: any, handler?: any, group?: number): void {
|
||||
this._addKnownHandler('newMessage', filter, handler, group)
|
||||
this._addKnownHandler('new_message', filter, handler, group)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1410,7 +1121,7 @@ export class Dispatcher<
|
|||
|
||||
/** @internal */
|
||||
onEditMessage(filter: any, handler?: any, group?: number): void {
|
||||
this._addKnownHandler('editMessage', filter, handler, group)
|
||||
this._addKnownHandler('edit_message', filter, handler, group)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1441,7 +1152,7 @@ export class Dispatcher<
|
|||
|
||||
/** @internal */
|
||||
onDeleteMessage(filter: any, handler?: any, group?: number): void {
|
||||
this._addKnownHandler('deleteMessage', filter, handler, group)
|
||||
this._addKnownHandler('delete_message', filter, handler, group)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1472,7 +1183,7 @@ export class Dispatcher<
|
|||
|
||||
/** @internal */
|
||||
onChatMemberUpdate(filter: any, handler?: any, group?: number): void {
|
||||
this._addKnownHandler('chatMemberUpdate', filter, handler, group)
|
||||
this._addKnownHandler('chat_member', filter, handler, group)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1500,7 +1211,7 @@ export class Dispatcher<
|
|||
|
||||
/** @internal */
|
||||
onInlineQuery(filter: any, handler?: any, group?: number): void {
|
||||
this._addKnownHandler('inlineQuery', filter, handler, group)
|
||||
this._addKnownHandler('inline_query', filter, handler, group)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1531,7 +1242,7 @@ export class Dispatcher<
|
|||
|
||||
/** @internal */
|
||||
onChosenInlineResult(filter: any, handler?: any, group?: number): void {
|
||||
this._addKnownHandler('chosenInlineResult', filter, handler, group)
|
||||
this._addKnownHandler('chosen_inline_result', filter, handler, group)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1582,7 +1293,7 @@ export class Dispatcher<
|
|||
|
||||
/** @internal */
|
||||
onCallbackQuery(filter: any, handler?: any, group?: number): void {
|
||||
this._addKnownHandler('callbackQuery', filter, handler, group)
|
||||
this._addKnownHandler('callback_query', filter, handler, group)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1608,7 +1319,7 @@ export class Dispatcher<
|
|||
|
||||
/** @internal */
|
||||
onPollUpdate(filter: any, handler?: any, group?: number): void {
|
||||
this._addKnownHandler('pollUpdate', filter, handler, group)
|
||||
this._addKnownHandler('poll', filter, handler, group)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1636,7 +1347,7 @@ export class Dispatcher<
|
|||
|
||||
/** @internal */
|
||||
onPollVote(filter: any, handler?: any, group?: number): void {
|
||||
this._addKnownHandler('pollVote', filter, handler, group)
|
||||
this._addKnownHandler('poll_vote', filter, handler, group)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1667,7 +1378,7 @@ export class Dispatcher<
|
|||
|
||||
/** @internal */
|
||||
onUserStatusUpdate(filter: any, handler?: any, group?: number): void {
|
||||
this._addKnownHandler('userStatusUpdate', filter, handler, group)
|
||||
this._addKnownHandler('user_status', filter, handler, group)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1695,7 +1406,7 @@ export class Dispatcher<
|
|||
|
||||
/** @internal */
|
||||
onUserTyping(filter: any, handler?: any, group?: number): void {
|
||||
this._addKnownHandler('userTyping', filter, handler, group)
|
||||
this._addKnownHandler('user_typing', filter, handler, group)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1723,7 +1434,7 @@ export class Dispatcher<
|
|||
|
||||
/** @internal */
|
||||
onHistoryRead(filter: any, handler?: any, group?: number): void {
|
||||
this._addKnownHandler('historyRead', filter, handler, group)
|
||||
this._addKnownHandler('history_read', filter, handler, group)
|
||||
}
|
||||
|
||||
// end-codegen
|
||||
|
|
|
@ -23,14 +23,14 @@ import {
|
|||
WebPage,
|
||||
MessageAction,
|
||||
RawLocation,
|
||||
ChatMemberUpdate,
|
||||
ChosenInlineResult,
|
||||
UserStatusUpdate,
|
||||
PollVoteUpdate,
|
||||
UserTypingUpdate,
|
||||
} from '@mtcute/client'
|
||||
import { MaybeArray } from '@mtcute/core'
|
||||
import { ChatMemberUpdate } from './updates'
|
||||
import { ChosenInlineResult } from './updates/chosen-inline-result'
|
||||
import { UpdateState } from './state'
|
||||
import { UserStatusUpdate } from './updates/user-status-update'
|
||||
import { PollVoteUpdate } from './updates/poll-vote'
|
||||
import { UserTypingUpdate } from './updates/user-typing-update'
|
||||
|
||||
function extractText(
|
||||
obj: Message | InlineQuery | ChosenInlineResult | CallbackQuery
|
||||
|
|
|
@ -6,52 +6,42 @@ import {
|
|||
CallbackQuery,
|
||||
UsersIndex,
|
||||
ChatsIndex,
|
||||
ChatMemberUpdate,
|
||||
PollVoteUpdate,
|
||||
UserStatusUpdate,
|
||||
ChosenInlineResult,
|
||||
HistoryReadUpdate,
|
||||
DeleteMessageUpdate,
|
||||
PollUpdate,
|
||||
UserTypingUpdate,
|
||||
} from '@mtcute/client'
|
||||
import { tl } from '@mtcute/tl'
|
||||
import { PropagationAction } from './propagation'
|
||||
import {
|
||||
ChatMemberUpdate,
|
||||
ChosenInlineResult,
|
||||
PollUpdate,
|
||||
PollVoteUpdate,
|
||||
UserStatusUpdate,
|
||||
UserTypingUpdate,
|
||||
DeleteMessageUpdate,
|
||||
HistoryReadUpdate,
|
||||
} from './updates'
|
||||
|
||||
interface BaseUpdateHandler<Type, Handler, Checker> {
|
||||
type: Type
|
||||
interface BaseUpdateHandler<Name, Handler, Checker> {
|
||||
name: Name
|
||||
callback: Handler
|
||||
|
||||
check?: Checker
|
||||
}
|
||||
|
||||
type ParsedUpdateHandler<Type, Update, State = never> = BaseUpdateHandler<
|
||||
Type,
|
||||
type ParsedUpdateHandler<Name, Update, State = never> = BaseUpdateHandler<
|
||||
Name,
|
||||
(update: Update, state: State) => MaybeAsync<void | PropagationAction>,
|
||||
(update: Update, state: State) => MaybeAsync<boolean>
|
||||
>
|
||||
|
||||
type _ParsedUpdate<T> = T extends ParsedUpdateHandler<infer K, infer Q>
|
||||
? {
|
||||
readonly type: K
|
||||
readonly data: Q
|
||||
}
|
||||
: never
|
||||
export type ParsedUpdate = _ParsedUpdate<UpdateHandler>
|
||||
|
||||
export type RawUpdateHandler = BaseUpdateHandler<
|
||||
'raw',
|
||||
(
|
||||
client: TelegramClient,
|
||||
update: tl.TypeUpdate,
|
||||
update: tl.TypeUpdate | tl.TypeMessage,
|
||||
users: UsersIndex,
|
||||
chats: ChatsIndex
|
||||
) => MaybeAsync<void | PropagationAction>,
|
||||
(
|
||||
client: TelegramClient,
|
||||
update: tl.TypeUpdate,
|
||||
update: tl.TypeUpdate | tl.TypeMessage,
|
||||
users: UsersIndex,
|
||||
chats: ChatsIndex
|
||||
) => MaybeAsync<boolean>
|
||||
|
|
|
@ -1,11 +1,8 @@
|
|||
export * from './builders'
|
||||
export * from './dispatcher'
|
||||
export * from './filters'
|
||||
export * from './handler'
|
||||
export * from './propagation'
|
||||
export * from './updates'
|
||||
export * from './wizard'
|
||||
export * from './callback-data-builder'
|
||||
export * from './conversation'
|
||||
|
||||
export { UpdateState, IStateStorage } from './state'
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
export * from './chat-member-update'
|
||||
export * from './chosen-inline-result'
|
||||
export * from './delete-message-update'
|
||||
export * from './poll-update'
|
||||
export * from './poll-vote'
|
||||
export * from './user-status-update'
|
||||
export * from './user-typing-update'
|
||||
export * from './history-read-update'
|
Loading…
Reference in a new issue