feat(dispatcher): support chat member updates
This commit is contained in:
parent
7e9f255fdc
commit
fa3c719312
7 changed files with 413 additions and 4 deletions
|
@ -1,6 +1,7 @@
|
|||
import { NewMessageHandler, RawUpdateHandler } from './handler'
|
||||
import { ChatMemberUpdateHandler, NewMessageHandler, RawUpdateHandler } from './handler'
|
||||
import { filters, UpdateFilter } from './filters'
|
||||
import { Message } from '@mtcute/client'
|
||||
import { ChatMemberUpdate } from './updates'
|
||||
|
||||
export namespace handlers {
|
||||
/**
|
||||
|
@ -75,4 +76,42 @@ export namespace handlers {
|
|||
callback: filter,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@link ChatMemberUpdateHandler}
|
||||
*
|
||||
* @param handler Chat member update handler
|
||||
*/
|
||||
export function chatMemberUpdate(
|
||||
handler: ChatMemberUpdateHandler['callback']
|
||||
): ChatMemberUpdateHandler
|
||||
|
||||
/**
|
||||
* Create a {@link ChatMemberUpdateHandler} with a filter
|
||||
*
|
||||
* @param filter Chat member update filter
|
||||
* @param handler Chat member update handler
|
||||
*/
|
||||
export function chatMemberUpdate<Mod>(
|
||||
filter: UpdateFilter<ChatMemberUpdate, Mod>,
|
||||
handler: ChatMemberUpdateHandler<filters.Modify<ChatMemberUpdate, Mod>>['callback']
|
||||
): ChatMemberUpdateHandler
|
||||
|
||||
export function chatMemberUpdate(
|
||||
filter: any,
|
||||
handler?: any
|
||||
): ChatMemberUpdateHandler {
|
||||
if (handler) {
|
||||
return {
|
||||
type: 'chat_member',
|
||||
check: filter,
|
||||
callback: handler,
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'chat_member',
|
||||
callback: filter,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,9 +6,15 @@ import {
|
|||
StopChildrenPropagation,
|
||||
StopPropagation,
|
||||
} from './propagation'
|
||||
import { NewMessageHandler, RawUpdateHandler, UpdateHandler } from './handler'
|
||||
import {
|
||||
ChatMemberUpdateHandler,
|
||||
NewMessageHandler,
|
||||
RawUpdateHandler,
|
||||
UpdateHandler,
|
||||
} from './handler'
|
||||
import { filters, UpdateFilter } from './filters'
|
||||
import { handlers } from './builders'
|
||||
import { ChatMemberUpdate } from './updates'
|
||||
|
||||
const noop = () => {}
|
||||
|
||||
|
@ -127,6 +133,19 @@ export class Dispatcher {
|
|||
)
|
||||
}
|
||||
|
||||
let chatMember: ChatMemberUpdate | null = null
|
||||
if (
|
||||
update._ === 'updateChatParticipant' ||
|
||||
update._ === 'updateChannelParticipant'
|
||||
) {
|
||||
chatMember = new ChatMemberUpdate(
|
||||
this._client,
|
||||
update,
|
||||
users,
|
||||
chats
|
||||
)
|
||||
}
|
||||
|
||||
outer: for (const grp of this._groupsOrder) {
|
||||
for (const handler of this._groups[grp]) {
|
||||
let result: void | PropagationSymbol
|
||||
|
@ -155,6 +174,13 @@ export class Dispatcher {
|
|||
(await handler.check(message, this._client)))
|
||||
) {
|
||||
result = await handler.callback(message, this._client)
|
||||
} else if (
|
||||
handler.type === 'chat_member' &&
|
||||
chatMember &&
|
||||
(!handler.check ||
|
||||
(await handler.check(chatMember, this._client)))
|
||||
) {
|
||||
result = await handler.callback(chatMember, this._client)
|
||||
} else continue
|
||||
|
||||
if (result === ContinuePropagation) continue
|
||||
|
@ -379,4 +405,36 @@ export class Dispatcher {
|
|||
onNewMessage(filter: any, handler?: any, group?: number): void {
|
||||
this._addKnownHandler('newMessage', filter, handler, group)
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a chat member update filter without any filters.
|
||||
*
|
||||
* @param handler Update handler
|
||||
* @param group Handler group index
|
||||
* @internal
|
||||
*/
|
||||
onChatMemberUpdate(
|
||||
handler: ChatMemberUpdateHandler['callback'],
|
||||
group?: number
|
||||
): void
|
||||
|
||||
/**
|
||||
* Register a message handler with a given filter
|
||||
*
|
||||
* @param filter Update filter
|
||||
* @param handler Update handler
|
||||
* @param group Handler group index
|
||||
*/
|
||||
onChatMemberUpdate<Mod>(
|
||||
filter: UpdateFilter<ChatMemberUpdate, Mod>,
|
||||
handler: ChatMemberUpdateHandler<
|
||||
filters.Modify<ChatMemberUpdate, Mod>
|
||||
>['callback'],
|
||||
group?: number
|
||||
): void
|
||||
|
||||
/** @internal */
|
||||
onChatMemberUpdate(filter: any, handler?: any, group?: number): void {
|
||||
this._addKnownHandler('chatMemberUpdate', filter, handler, group)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ import {
|
|||
import { Game } from '@mtcute/client/src/types/media/game'
|
||||
import { WebPage } from '@mtcute/client/src/types/media/web-page'
|
||||
import { MaybeArray } from '@mtcute/core'
|
||||
import { ChatMemberUpdate } from './updates'
|
||||
|
||||
/**
|
||||
* Type describing a primitive filter, which is a function taking some `Base`
|
||||
|
@ -561,4 +562,31 @@ export namespace filters {
|
|||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a filter for {@link ChatMemberUpdate} by update type
|
||||
*
|
||||
* @param types Update type(s)
|
||||
*/
|
||||
export const chatMember: {
|
||||
<T extends ChatMemberUpdate.Type>(type: T): UpdateFilter<
|
||||
ChatMemberUpdate,
|
||||
{ type: T }
|
||||
>
|
||||
<T extends ChatMemberUpdate.Type[]>(types: T): UpdateFilter<
|
||||
ChatMemberUpdate,
|
||||
{ type: T[number] }
|
||||
>
|
||||
} = (
|
||||
types: MaybeArray<ChatMemberUpdate.Type>
|
||||
): UpdateFilter<ChatMemberUpdate> => {
|
||||
if (Array.isArray(types)) {
|
||||
const index: Partial<Record<ChatMemberUpdate.Type, true>> = {}
|
||||
types.forEach((typ) => (index[typ] = true))
|
||||
|
||||
return (upd) => upd.type in index
|
||||
}
|
||||
|
||||
return (upd) => upd.type === types
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { MaybeAsync, Message, TelegramClient } from '@mtcute/client'
|
||||
import { tl } from '@mtcute/tl'
|
||||
import { PropagationSymbol } from './propagation'
|
||||
import { ChatMemberUpdate } from './updates'
|
||||
|
||||
interface BaseUpdateHandler<Type, Handler, Checker> {
|
||||
type: Type
|
||||
|
@ -34,6 +35,16 @@ export type RawUpdateHandler = BaseUpdateHandler<
|
|||
) => MaybeAsync<boolean>
|
||||
>
|
||||
|
||||
export type NewMessageHandler<T = Message> = ParsedUpdateHandler<'new_message', T>
|
||||
export type NewMessageHandler<T = Message> = ParsedUpdateHandler<
|
||||
'new_message',
|
||||
T
|
||||
>
|
||||
export type ChatMemberUpdateHandler<T = ChatMemberUpdate> = ParsedUpdateHandler<
|
||||
'chat_member',
|
||||
T
|
||||
>
|
||||
|
||||
export type UpdateHandler = RawUpdateHandler | NewMessageHandler
|
||||
export type UpdateHandler =
|
||||
| RawUpdateHandler
|
||||
| NewMessageHandler
|
||||
| ChatMemberUpdateHandler
|
||||
|
|
|
@ -3,3 +3,4 @@ export * from './dispatcher'
|
|||
export * from './filters'
|
||||
export * from './handler'
|
||||
export * from './propagation'
|
||||
export * from './updates'
|
||||
|
|
271
packages/dispatcher/src/updates/chat-member-update.ts
Normal file
271
packages/dispatcher/src/updates/chat-member-update.ts
Normal file
|
@ -0,0 +1,271 @@
|
|||
import { tl } from '@mtcute/tl'
|
||||
import {
|
||||
Chat,
|
||||
ChatInviteLink,
|
||||
ChatMember,
|
||||
TelegramClient,
|
||||
User,
|
||||
} from '@mtcute/client'
|
||||
import { makeInspectable } from '@mtcute/client/src/types/utils'
|
||||
|
||||
export namespace ChatMemberUpdate {
|
||||
/**
|
||||
* Type of the event. Can be one of:
|
||||
* - `joined`: User `user` joined the chat/channel on their own
|
||||
* - `added`: User `actor` added another user `user` to the chat
|
||||
* - `left`: User `user` left the channel on their own
|
||||
* - `kicked`: User `user` was kicked from the chat by `actor`
|
||||
* - `unkicked`: User `user` was removed from the list of kicked users by `actor` and can join the chat again
|
||||
* - `restricted`: User `user` was restricted by `actor`
|
||||
* - `unrestricted`: User `user` was unrestricted by `actor`
|
||||
* - `promoted`: User `user` was promoted to admin by `actor`
|
||||
* - `demoted`: User `user` was demoted from admin by `actor`
|
||||
* - `old_owner`: User `user` transferred their own chat ownership
|
||||
* - `new_owner`: User `actor` transferred their chat ownership to `user`
|
||||
* - `other`: Some other event (e.g. change in restrictions, change in admin rights, etc.)
|
||||
*/
|
||||
export type Type =
|
||||
| 'joined'
|
||||
| 'added'
|
||||
| 'left'
|
||||
| 'kicked'
|
||||
| 'unkicked'
|
||||
| 'restricted'
|
||||
| 'unrestricted'
|
||||
| 'promoted'
|
||||
| 'demoted'
|
||||
| 'old_owner'
|
||||
| 'new_owner'
|
||||
| 'other'
|
||||
}
|
||||
|
||||
/**
|
||||
* Update representing a change in the status
|
||||
* of a chat/channel member.
|
||||
*/
|
||||
export class ChatMemberUpdate {
|
||||
readonly client: TelegramClient
|
||||
|
||||
readonly raw: tl.RawUpdateChatParticipant | tl.RawUpdateChannelParticipant
|
||||
|
||||
/** Map of users in this message. Mainly for internal use */
|
||||
readonly _users: Record<number, tl.TypeUser>
|
||||
/** Map of chats in this message. Mainly for internal use */
|
||||
readonly _chats: Record<number, tl.TypeChat>
|
||||
|
||||
constructor(
|
||||
client: TelegramClient,
|
||||
raw: tl.RawUpdateChatParticipant | tl.RawUpdateChannelParticipant,
|
||||
users: Record<number, tl.TypeUser>,
|
||||
chats: Record<number, tl.TypeChat>
|
||||
) {
|
||||
this.client = client
|
||||
this.raw = raw
|
||||
this._users = users
|
||||
this._chats = chats
|
||||
}
|
||||
|
||||
/**
|
||||
* Date of the event
|
||||
*/
|
||||
get date(): Date {
|
||||
return new Date(this.raw.date * 1000)
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether this is an update about current user
|
||||
*/
|
||||
get isSelf(): boolean {
|
||||
return this.user.isSelf
|
||||
}
|
||||
|
||||
private _type?: ChatMemberUpdate.Type
|
||||
/**
|
||||
* Type of the update
|
||||
*
|
||||
* @link ChatMemberUpdate.Type
|
||||
*/
|
||||
get type(): ChatMemberUpdate.Type {
|
||||
if (!this._type) {
|
||||
// we do not use `.actor`, `.newMember` and `.oldMember`,
|
||||
// since using them would mean creating objects,
|
||||
// which will probably be useless in case this property
|
||||
// is used inside of a filter
|
||||
// fortunately, all the info is available as-is and does not require
|
||||
// additional parsing
|
||||
|
||||
const old = this.raw.prevParticipant
|
||||
const cur = this.raw.newParticipant
|
||||
|
||||
const oldId =
|
||||
(old && ((old as any).userId || (old as any).peer.userId)) ||
|
||||
null
|
||||
const curId =
|
||||
(cur && ((cur as any).userId || (cur as any).peer.userId)) ||
|
||||
null
|
||||
|
||||
const actorId = this.raw.actorId
|
||||
|
||||
if (!old && cur) {
|
||||
// join or added
|
||||
return (this._type = actorId === curId ? 'joined' : 'added')
|
||||
}
|
||||
|
||||
if (old && !cur) {
|
||||
// left, kicked (for chats) or unkicked
|
||||
if (actorId === oldId) return (this._type = 'left')
|
||||
|
||||
if (old._ === 'channelParticipantBanned') {
|
||||
return (this._type = 'unkicked')
|
||||
}
|
||||
|
||||
return (this._type = 'kicked')
|
||||
}
|
||||
|
||||
// in this case OR is the same as AND, but AND doesn't work well with typescript :shrug:
|
||||
if (!old || !cur) return (this._type = 'other')
|
||||
|
||||
if (old._ === 'chatParticipant' || old._ === 'channelParticipant') {
|
||||
if (
|
||||
cur._ === 'chatParticipantAdmin' ||
|
||||
cur._ === 'channelParticipantAdmin'
|
||||
) {
|
||||
return (this._type = 'promoted')
|
||||
}
|
||||
|
||||
if (cur._ === 'channelParticipantBanned') {
|
||||
// kicked or restricted
|
||||
if (cur.left) return (this._type = 'kicked')
|
||||
|
||||
return (this._type = 'restricted')
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
old._ === 'channelParticipantBanned' &&
|
||||
cur._ === 'channelParticipant'
|
||||
) {
|
||||
return (this._type = 'unrestricted')
|
||||
}
|
||||
|
||||
if (
|
||||
old._ === 'channelParticipantAdmin' &&
|
||||
cur._ === 'channelParticipant'
|
||||
) {
|
||||
return (this._type = 'demoted')
|
||||
}
|
||||
|
||||
if (
|
||||
old._ === 'chatParticipantCreator' ||
|
||||
old._ === 'channelParticipantCreator'
|
||||
) {
|
||||
return (this._type = 'old_owner')
|
||||
}
|
||||
|
||||
if (
|
||||
cur._ === 'chatParticipantCreator' ||
|
||||
cur._ === 'channelParticipantCreator'
|
||||
) {
|
||||
return (this._type = 'new_owner')
|
||||
}
|
||||
|
||||
return (this._type = 'other')
|
||||
}
|
||||
|
||||
return this._type
|
||||
}
|
||||
|
||||
private _chat?: Chat
|
||||
/**
|
||||
* Chat in which this event has occurred
|
||||
*/
|
||||
get chat(): Chat {
|
||||
if (!this._chat) {
|
||||
const id =
|
||||
this.raw._ === 'updateChannelParticipant'
|
||||
? this.raw.channelId
|
||||
: this.raw.chatId
|
||||
this._chat = new Chat(this.client, this._chats[id])
|
||||
}
|
||||
|
||||
return this._chat
|
||||
}
|
||||
|
||||
private _actor?: User
|
||||
/**
|
||||
* Performer of the action which resulted in this update.
|
||||
*
|
||||
* Can be chat/channel administrator or the {@link user} themself.
|
||||
*/
|
||||
get actor(): User {
|
||||
if (!this._actor) {
|
||||
this._actor = new User(this.client, this._users[this.raw.actorId])
|
||||
}
|
||||
|
||||
return this._actor
|
||||
}
|
||||
|
||||
private _user?: User
|
||||
/**
|
||||
* User representing the chat member whose status was changed.
|
||||
*/
|
||||
get user(): User {
|
||||
if (!this._user) {
|
||||
this._user = new User(this.client, this._users[this.raw.userId])
|
||||
}
|
||||
|
||||
return this._user
|
||||
}
|
||||
|
||||
private _oldMember?: ChatMember
|
||||
/**
|
||||
* Previous (old) information about chat member.
|
||||
*/
|
||||
get oldMember(): ChatMember | null {
|
||||
if (!this.raw.prevParticipant) return null
|
||||
|
||||
if (!this._oldMember) {
|
||||
this._oldMember = new ChatMember(
|
||||
this.client,
|
||||
this.raw.prevParticipant,
|
||||
this._users
|
||||
)
|
||||
}
|
||||
|
||||
return this._oldMember
|
||||
}
|
||||
|
||||
private _newMember?: ChatMember
|
||||
/**
|
||||
* Current (new) information about chat member.
|
||||
*/
|
||||
get newMember(): ChatMember | null {
|
||||
if (!this.raw.newParticipant) return null
|
||||
|
||||
if (!this._newMember) {
|
||||
this._newMember = new ChatMember(
|
||||
this.client,
|
||||
this.raw.newParticipant,
|
||||
this._users
|
||||
)
|
||||
}
|
||||
|
||||
return this._newMember
|
||||
}
|
||||
|
||||
private _inviteLink?: ChatInviteLink
|
||||
/**
|
||||
* In case this is a "join" event, invite link that was used to join (if any)
|
||||
*/
|
||||
get inviteLink(): ChatInviteLink | null {
|
||||
if (!this.raw.invite) return null
|
||||
|
||||
if (!this._inviteLink) {
|
||||
this._inviteLink = new ChatInviteLink(this.client, this.raw.invite)
|
||||
}
|
||||
|
||||
return this._inviteLink
|
||||
}
|
||||
}
|
||||
|
||||
makeInspectable(ChatMemberUpdate)
|
1
packages/dispatcher/src/updates/index.ts
Normal file
1
packages/dispatcher/src/updates/index.ts
Normal file
|
@ -0,0 +1 @@
|
|||
export * from './chat-member-update'
|
Loading…
Reference in a new issue