feat(dispatcher): text-related filters (equals, contains, starts/ends with)
This commit is contained in:
parent
c33646943c
commit
be8ffe5b5b
1 changed files with 179 additions and 53 deletions
|
@ -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
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
Loading…
Reference in a new issue