feat(dispatcher): text-related filters (equals, contains, starts/ends with)

This commit is contained in:
teidesu 2021-07-09 01:50:39 +03:00
parent c33646943c
commit be8ffe5b5b

View file

@ -21,7 +21,8 @@ import {
Invoice, Invoice,
Game, Game,
WebPage, WebPage,
MessageAction, RawLocation, MessageAction,
RawLocation,
} from '@mtcute/client' } from '@mtcute/client'
import { MaybeArray } from '@mtcute/core' import { MaybeArray } from '@mtcute/core'
import { ChatMemberUpdate } from './updates' import { ChatMemberUpdate } from './updates'
@ -31,6 +32,22 @@ import { UserStatusUpdate } from './updates/user-status-update'
import { PollVoteUpdate } from './updates/poll-vote' import { PollVoteUpdate } from './updates/poll-vote'
import { UserTypingUpdate } from './updates/user-typing-update' import { UserTypingUpdate } from './updates/user-typing-update'
function extractText(
obj: Message | InlineQuery | ChosenInlineResult | CallbackQuery
): string | null {
if (obj.constructor === Message) {
return obj.text
} else if (obj.constructor === InlineQuery) {
return obj.query
} else if (obj.constructor === ChosenInlineResult) {
return obj.id
} else if (obj.constructor === CallbackQuery) {
if (obj.raw.data) return obj.dataStr
}
return null
}
/** /**
* Type describing a primitive filter, which is a function taking some `Base` * Type describing a primitive filter, which is a function taking some `Base`
* and a {@link TelegramClient}, checking it against some condition * and a {@link TelegramClient}, checking it against some condition
@ -318,6 +335,11 @@ export namespace filters {
} }
} }
/**
* Filter that matches any update
*/
export const any: UpdateFilter<any> = () => true
/** /**
* Filter messages generated by yourself (including Saved Messages) * Filter messages generated by yourself (including Saved Messages)
*/ */
@ -718,16 +740,16 @@ export namespace filters {
/** /**
* Filter messages containing any location (live or static). * Filter messages containing any location (live or static).
*/ */
export const anyLocation: UpdateFilter<Message, { media: Location }> = (msg) => export const anyLocation: UpdateFilter<Message, { media: Location }> = (
msg.media instanceof RawLocation msg
) => msg.media instanceof RawLocation
/** /**
* Filter messages containing a static (non-live) location. * Filter messages containing a static (non-live) location.
*/ */
export const location: UpdateFilter< export const location: UpdateFilter<Message, { media: LiveLocation }> = (
Message, msg
{ media: LiveLocation } ) => msg.media?.type === 'location'
> = (msg) => msg.media?.type === 'location'
/** /**
* Filter messages containing a live location. * Filter messages containing a live location.
@ -772,7 +794,7 @@ export namespace filters {
* - for `Message`, `Message.text` is used * - for `Message`, `Message.text` is used
* - for `InlineQuery`, `InlineQuery.query` is used * - for `InlineQuery`, `InlineQuery.query` is used
* - for {@link ChosenInlineResult}, {@link ChosenInlineResult.id} is used * - for {@link ChosenInlineResult}, {@link ChosenInlineResult.id} is used
* - for `CallbackQuery`, `CallbackQuery.dataStr` * - for `CallbackQuery`, `CallbackQuery.dataStr` is used
* *
* When a regex matches, the match array is stored in a * When a regex matches, the match array is stored in a
* type-safe extension field `.match` of the object * type-safe extension field `.match` of the object
@ -785,16 +807,10 @@ export namespace filters {
Message | InlineQuery | ChosenInlineResult | CallbackQuery, Message | InlineQuery | ChosenInlineResult | CallbackQuery,
{ match: RegExpMatchArray } { match: RegExpMatchArray }
> => (obj) => { > => (obj) => {
let m: RegExpMatchArray | null = null const txt = extractText(obj)
if (obj.constructor === Message) { if (!txt) return false
m = obj.text.match(regex)
} else if (obj.constructor === InlineQuery) { const m = txt.match(regex)
m = obj.query.match(regex)
} else if (obj.constructor === ChosenInlineResult) {
m = obj.id.match(regex)
} else if (obj.constructor === CallbackQuery) {
if (obj.raw.data) m = obj.dataStr!.match(regex)
}
if (m) { if (m) {
;(obj as any).match = m ;(obj as any).match = m
@ -803,6 +819,122 @@ export namespace filters {
return false return false
} }
/**
* Filter objects which contain the exact text given
* - for `Message`, `Message.text` is used
* - for `InlineQuery`, `InlineQuery.query` is used
* - for {@link ChosenInlineResult}, {@link ChosenInlineResult.id} is used
* - for `CallbackQuery`, `CallbackQuery.dataStr` is used
*
* @param str String to be matched
* @param ignoreCase Whether string case should be ignored
*/
export const equals = (
str: string,
ignoreCase = false
): UpdateFilter<
Message | InlineQuery | ChosenInlineResult | CallbackQuery
> => {
if (ignoreCase) {
str = str.toLowerCase()
return (obj) => extractText(obj)?.toLowerCase() === str
}
return (obj) => extractText(obj) === str
}
/**
* Filter objects which contain the text given (as a substring)
* - for `Message`, `Message.text` is used
* - for `InlineQuery`, `InlineQuery.query` is used
* - for {@link ChosenInlineResult}, {@link ChosenInlineResult.id} is used
* - for `CallbackQuery`, `CallbackQuery.dataStr` is used
*
* @param str Substring to be matched
* @param ignoreCase Whether string case should be ignored
*/
export const contains = (
str: string,
ignoreCase = false
): UpdateFilter<
Message | InlineQuery | ChosenInlineResult | CallbackQuery
> => {
if (ignoreCase) {
str = str.toLowerCase()
return (obj) => {
const txt = extractText(obj)
return txt != null && txt.toLowerCase().indexOf(str) > -1
}
}
return (obj) => {
const txt = extractText(obj)
return txt != null && txt.indexOf(str) > -1
}
}
/**
* Filter objects which contain the text starting with a given string
* - for `Message`, `Message.text` is used
* - for `InlineQuery`, `InlineQuery.query` is used
* - for {@link ChosenInlineResult}, {@link ChosenInlineResult.id} is used
* - for `CallbackQuery`, `CallbackQuery.dataStr` is used
*
* @param str Substring to be matched
* @param ignoreCase Whether string case should be ignored
*/
export const startsWith = (
str: string,
ignoreCase = false
): UpdateFilter<
Message | InlineQuery | ChosenInlineResult | CallbackQuery
> => {
if (ignoreCase) {
str = str.toLowerCase()
return (obj) => {
const txt = extractText(obj)
return txt != null && txt.toLowerCase().substring(0, str.length) === str
}
}
return (obj) => {
const txt = extractText(obj)
return txt != null && txt.substring(0, str.length) === str
}
}
/**
* Filter objects which contain the text ending with a given string
* - for `Message`, `Message.text` is used
* - for `InlineQuery`, `InlineQuery.query` is used
* - for {@link ChosenInlineResult}, {@link ChosenInlineResult.id} is used
* - for `CallbackQuery`, `CallbackQuery.dataStr` is used
*
* @param str Substring to be matched
* @param ignoreCase Whether string case should be ignored
*/
export const endsWith = (
str: string,
ignoreCase = false
): UpdateFilter<
Message | InlineQuery | ChosenInlineResult | CallbackQuery
> => {
if (ignoreCase) {
str = str.toLowerCase()
return (obj) => {
const txt = extractText(obj)
return txt != null && txt.toLowerCase().substring(0, str.length) === str
}
}
return (obj) => {
const txt = extractText(obj)
return txt != null && txt.substring(0, str.length) === str
}
}
/** /**
* Filter messages that call the given command(s).. * Filter messages that call the given command(s)..
* *
@ -859,7 +991,8 @@ export namespace filters {
const lastGroup = m[m.length - 1] const lastGroup = m[m.length - 1]
if (lastGroup && msg.client['_isBot']) { if (lastGroup && msg.client['_isBot']) {
// check bot username // check bot username
if (lastGroup !== msg.client['_selfUsername']) return false if (lastGroup !== msg.client['_selfUsername'])
return false
} }
const match = m.slice(1, -1) const match = m.slice(1, -1)
@ -889,10 +1022,7 @@ export namespace filters {
* Shorthand filter that matches /start commands sent to bot's * Shorthand filter that matches /start commands sent to bot's
* private messages. * private messages.
*/ */
export const start = and( export const start = and(chat('private'), command('start'))
chat('private'),
command('start')
)
/** /**
* Filter for deep links (i.e. `/start <deeplink_parameter>`). * Filter for deep links (i.e. `/start <deeplink_parameter>`).
@ -900,44 +1030,40 @@ export namespace filters {
* If the parameter is a regex, groups are added to `msg.command`, * If the parameter is a regex, groups are added to `msg.command`,
* meaning that the first group is available in `msg.command[2]`. * meaning that the first group is available in `msg.command[2]`.
*/ */
export const deeplink = (params: MaybeArray<string | RegExp>): UpdateFilter<Message, { command: string[] }> => { export const deeplink = (
params: MaybeArray<string | RegExp>
): UpdateFilter<Message, { command: string[] }> => {
if (!Array.isArray(params)) { if (!Array.isArray(params)) {
return and( return and(start, (msg: Message & { command: string[] }) => {
start,
(msg: Message & { command: string[] }) => {
if (msg.command.length !== 2) return false
const p = msg.command[1]
if (typeof params === 'string' && p === params) return true
const m = p.match(params)
if (!m) return false
msg.command.push(...m.slice(1))
return true
}
)
}
return and(
start,
(msg: Message & { command: string[] }) => {
if (msg.command.length !== 2) return false if (msg.command.length !== 2) return false
const p = msg.command[1] const p = msg.command[1]
for (const param of params) { if (typeof params === 'string' && p === params) return true
if (typeof param === 'string' && p === param) return true
const m = p.match(param) const m = p.match(params)
if (!m) continue if (!m) return false
msg.command.push(...m.slice(1)) msg.command.push(...m.slice(1))
return true return true
} })
}
return false return and(start, (msg: Message & { command: string[] }) => {
if (msg.command.length !== 2) return false
const p = msg.command[1]
for (const param of params) {
if (typeof param === 'string' && p === param) return true
const m = p.match(param)
if (!m) continue
msg.command.push(...m.slice(1))
return true
} }
)
return false
})
} }
/** /**