grammy-match-query 0.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +105 -0
- package/out/deps.node.d.ts +1 -0
- package/out/deps.node.js +1 -0
- package/out/mod.d.ts +53 -0
- package/out/mod.js +167 -0
- package/package.json +44 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2021-2023 grammyjs
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# Match Query Plugin for grammY
|
|
2
|
+
|
|
3
|
+
A [grammY](https://grammy.dev/) plugin that interprets
|
|
4
|
+
[filter queries](https://grammy.dev/guide/filter-queries) at runtime, with
|
|
5
|
+
support for any property combination — including queries that grammY's static
|
|
6
|
+
type system would reject.
|
|
7
|
+
|
|
8
|
+
## Features
|
|
9
|
+
|
|
10
|
+
- **Full query syntax** — supports L1, L2, and L3 filter queries just like
|
|
11
|
+
grammY's built-in `bot.on()`.
|
|
12
|
+
- **L1 shortcuts** — `""` and `msg` expand to `message` + `channel_post`;
|
|
13
|
+
`edit` expands to `edited_message` + `edited_channel_post`.
|
|
14
|
+
- **L2 shortcuts** — `""` expands to `entities` + `caption_entities`; `media`
|
|
15
|
+
expands to `photo` + `video`; `file` expands to all file types.
|
|
16
|
+
- **Unrestricted queries** — no static validation, so any property path works
|
|
17
|
+
at runtime (e.g. `:media_group_id`).
|
|
18
|
+
- **Multiple queries** — pass an array of queries for OR logic.
|
|
19
|
+
- **Compatible** — returns a predicate for `bot.filter()`.
|
|
20
|
+
|
|
21
|
+
## Installation
|
|
22
|
+
|
|
23
|
+
### Node.js
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
npm install github:PonomareVlad/grammy-match-query
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### Deno
|
|
30
|
+
|
|
31
|
+
```typescript
|
|
32
|
+
import { matchQuery } from "https://raw.githubusercontent.com/PonomareVlad/grammy-match-query/main/src/mod.ts";
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Usage
|
|
36
|
+
|
|
37
|
+
```typescript
|
|
38
|
+
import { Bot } from "grammy";
|
|
39
|
+
import { matchQuery } from "grammy-match-query";
|
|
40
|
+
|
|
41
|
+
const bot = new Bot("<your-bot-token>");
|
|
42
|
+
|
|
43
|
+
// Filter by an unsupported query — messages with media_group_id
|
|
44
|
+
bot.filter(matchQuery(":media_group_id"), (ctx) => {
|
|
45
|
+
console.log("Media group received");
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
// Restrict to photo/video only (L3 checks L1 object for the property)
|
|
49
|
+
bot.filter(matchQuery(":media:media_group_id"), (ctx) => {
|
|
50
|
+
console.log("Photo/video media group");
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
// Standard queries work too
|
|
54
|
+
bot.filter(matchQuery("message:entities:url"), (ctx) => {
|
|
55
|
+
console.log("Message with URL entity");
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
// Multiple queries (OR logic)
|
|
59
|
+
bot.filter(matchQuery([":photo", ":video"]), (ctx) => {
|
|
60
|
+
console.log("Photo or video");
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
// L1 shortcuts
|
|
64
|
+
bot.filter(matchQuery("edit:text"), (ctx) => {
|
|
65
|
+
console.log("Edited text message or channel post");
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
// L2 shortcuts
|
|
69
|
+
bot.filter(matchQuery("message:file"), (ctx) => {
|
|
70
|
+
console.log("Message with any file type");
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
bot.start();
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Query Syntax
|
|
77
|
+
|
|
78
|
+
Queries use the same colon-separated format as grammY's
|
|
79
|
+
[filter queries](https://grammy.dev/guide/filter-queries):
|
|
80
|
+
|
|
81
|
+
| Query | Description |
|
|
82
|
+
| ----------------------- | ----------------------------------- |
|
|
83
|
+
| `message` | Any message update |
|
|
84
|
+
| `message:photo` | Message with a photo |
|
|
85
|
+
| `message:entities:url` | Message with a URL entity |
|
|
86
|
+
| `:photo` | Photo in message or channel post |
|
|
87
|
+
| `msg:photo` | Same as above |
|
|
88
|
+
| `edit:text` | Edited text message or channel post |
|
|
89
|
+
| `message:media` | Message with photo or video |
|
|
90
|
+
| `message:file` | Message with any file type |
|
|
91
|
+
| `message::url` | URL in entities or caption_entities |
|
|
92
|
+
| `:media_group_id` | Any message with media_group_id |
|
|
93
|
+
| `:media:media_group_id` | Photo/video with media_group_id |
|
|
94
|
+
|
|
95
|
+
## How It Works
|
|
96
|
+
|
|
97
|
+
1. **Parse** — the query string is split by `:` into up to three levels.
|
|
98
|
+
2. **Expand** — L1 and L2 shortcuts are expanded into all concrete paths.
|
|
99
|
+
3. **Compile** — each path becomes a predicate checking property existence on
|
|
100
|
+
the update object. L3 checks both property names and `type` fields.
|
|
101
|
+
4. **Compose** — multiple predicates are combined with OR logic.
|
|
102
|
+
|
|
103
|
+
The key difference from grammY's built-in `matchFilter` is that this plugin
|
|
104
|
+
does not validate queries against a static set of known keys, allowing any
|
|
105
|
+
property path to be checked at runtime.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { Api, Composer, Context } from "grammy";
|
package/out/deps.node.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { Api, Composer, Context } from "grammy";
|
package/out/mod.d.ts
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* grammY Match Query Plugin
|
|
3
|
+
*
|
|
4
|
+
* Interprets filter queries like grammY does, but supports any possible
|
|
5
|
+
* combinations via runtime property checking — including queries that
|
|
6
|
+
* grammY's static type system would reject.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```ts
|
|
10
|
+
* import { matchQuery } from 'grammy-match-query'
|
|
11
|
+
* bot.filter(matchQuery(':media_group_id'), ctx => {
|
|
12
|
+
* console.log('Media group')
|
|
13
|
+
* })
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
import { Context } from "./deps.node.js";
|
|
17
|
+
/**
|
|
18
|
+
* Create a runtime filter predicate from a grammY-style filter query string.
|
|
19
|
+
*
|
|
20
|
+
* Unlike grammY's built-in `matchFilter`, this function does not validate
|
|
21
|
+
* queries against a static set of known keys. Instead, it interprets the
|
|
22
|
+
* query at runtime, checking if the specified properties exist on the
|
|
23
|
+
* update object. This allows using any property path, including ones
|
|
24
|
+
* that grammY's type system would reject (e.g. `:media_group_id`).
|
|
25
|
+
*
|
|
26
|
+
* Supports:
|
|
27
|
+
* - L1, L2, and L3 queries (e.g. `message`, `message:photo`, `message:entities:url`)
|
|
28
|
+
* - L1 shortcuts: `""` and `msg` expand to `message` + `channel_post`;
|
|
29
|
+
* `edit` expands to `edited_message` + `edited_channel_post`
|
|
30
|
+
* - L2 shortcuts: `""` expands to `entities` + `caption_entities`;
|
|
31
|
+
* `media` expands to `photo` + `video`;
|
|
32
|
+
* `file` expands to `photo`, `animation`, `audio`, `document`, `video`, `video_note`, `voice`, `sticker`
|
|
33
|
+
* - Multiple queries separated by logical OR (pass array or use multiple calls)
|
|
34
|
+
*
|
|
35
|
+
* @param query A filter query string or array of filter query strings
|
|
36
|
+
* @returns A predicate function compatible with `bot.filter()`
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* ```ts
|
|
40
|
+
* import { matchQuery } from 'grammy-match-query'
|
|
41
|
+
*
|
|
42
|
+
* // Single query — any message/channel_post with media_group_id
|
|
43
|
+
* bot.filter(matchQuery(':media_group_id'), ctx => {
|
|
44
|
+
* console.log('Media group')
|
|
45
|
+
* })
|
|
46
|
+
*
|
|
47
|
+
* // Multiple queries (OR logic)
|
|
48
|
+
* bot.filter(matchQuery([':photo', ':video']), ctx => {
|
|
49
|
+
* console.log('Photo or video')
|
|
50
|
+
* })
|
|
51
|
+
* ```
|
|
52
|
+
*/
|
|
53
|
+
export declare function matchQuery(query: string | string[]): (ctx: Context) => boolean;
|
package/out/mod.js
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* grammY Match Query Plugin
|
|
3
|
+
*
|
|
4
|
+
* Interprets filter queries like grammY does, but supports any possible
|
|
5
|
+
* combinations via runtime property checking — including queries that
|
|
6
|
+
* grammY's static type system would reject.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```ts
|
|
10
|
+
* import { matchQuery } from 'grammy-match-query'
|
|
11
|
+
* bot.filter(matchQuery(':media_group_id'), ctx => {
|
|
12
|
+
* console.log('Media group')
|
|
13
|
+
* })
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
// L1 shortcuts: map shortcut names to L1 update types
|
|
17
|
+
const L1_SHORTCUTS = {
|
|
18
|
+
"": ["message", "channel_post"],
|
|
19
|
+
msg: ["message", "channel_post"],
|
|
20
|
+
edit: ["edited_message", "edited_channel_post"],
|
|
21
|
+
};
|
|
22
|
+
// L2 shortcuts: map shortcut names to L2 message properties
|
|
23
|
+
const L2_SHORTCUTS = {
|
|
24
|
+
"": ["entities", "caption_entities"],
|
|
25
|
+
media: ["photo", "video"],
|
|
26
|
+
file: [
|
|
27
|
+
"photo",
|
|
28
|
+
"animation",
|
|
29
|
+
"audio",
|
|
30
|
+
"document",
|
|
31
|
+
"video",
|
|
32
|
+
"video_note",
|
|
33
|
+
"voice",
|
|
34
|
+
"sticker",
|
|
35
|
+
],
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* Parse a filter query string into its component parts.
|
|
39
|
+
*/
|
|
40
|
+
function parse(query) {
|
|
41
|
+
return query.split(":");
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Expand shortcuts in a parsed query to produce all concrete query paths.
|
|
45
|
+
*/
|
|
46
|
+
function expand(parts) {
|
|
47
|
+
const [l1, l2, l3] = parts;
|
|
48
|
+
// Do not expand if all parts are empty/undefined
|
|
49
|
+
if (!l1 && !l2 && !l3)
|
|
50
|
+
return [parts];
|
|
51
|
+
// Expand L1 shortcuts
|
|
52
|
+
let expanded;
|
|
53
|
+
if (l1 in L1_SHORTCUTS) {
|
|
54
|
+
const targets = L1_SHORTCUTS[l1];
|
|
55
|
+
expanded = targets.map((s) => [s, l2, l3]);
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
expanded = [[l1, l2, l3]];
|
|
59
|
+
}
|
|
60
|
+
// Expand L2 shortcuts
|
|
61
|
+
expanded = expanded.flatMap((q) => {
|
|
62
|
+
const [ql1, ql2, ql3] = q;
|
|
63
|
+
if (ql2 !== undefined && ql2 in L2_SHORTCUTS) {
|
|
64
|
+
const targets = L2_SHORTCUTS[ql2];
|
|
65
|
+
return targets.map((s) => [ql1, s, ql3]);
|
|
66
|
+
}
|
|
67
|
+
return [q];
|
|
68
|
+
});
|
|
69
|
+
return expanded;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Test a value that may be an array or a single item.
|
|
73
|
+
*/
|
|
74
|
+
function testMaybeArray(t, pred) {
|
|
75
|
+
const p = (x) => x != null && pred(x);
|
|
76
|
+
return Array.isArray(t) ? t.some(p) : p(t);
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Build a predicate function for a single expanded query path.
|
|
80
|
+
*/
|
|
81
|
+
function buildPredicate(parts) {
|
|
82
|
+
const [l1, l2, l3] = parts;
|
|
83
|
+
if (l1 === undefined || l1 === "") {
|
|
84
|
+
throw new Error("Empty filter query given");
|
|
85
|
+
}
|
|
86
|
+
return (update) => {
|
|
87
|
+
// Check L1: the update type property must exist
|
|
88
|
+
const l1Value = update[l1];
|
|
89
|
+
if (l1Value == null)
|
|
90
|
+
return false;
|
|
91
|
+
// L1-only query: just check existence
|
|
92
|
+
if (l2 === undefined)
|
|
93
|
+
return true;
|
|
94
|
+
// Check L2: the L2 property must exist on the L1 object
|
|
95
|
+
const l1Obj = l1Value;
|
|
96
|
+
const l2Value = l1Obj[l2];
|
|
97
|
+
if (l2Value == null)
|
|
98
|
+
return false;
|
|
99
|
+
// L2-only query: just check existence
|
|
100
|
+
if (l3 === undefined)
|
|
101
|
+
return true;
|
|
102
|
+
// Check L3: first check within the L2 value (may be array),
|
|
103
|
+
// then fall back to checking the L1 object for the L3 property.
|
|
104
|
+
// Standard queries like message:entities:url check type="url" on
|
|
105
|
+
// each entity object in the entities array. Runtime-only queries
|
|
106
|
+
// like :media:media_group_id expand media to photo/video at L2,
|
|
107
|
+
// but media_group_id exists on the message (L1), not inside the
|
|
108
|
+
// photo/video sub-object — the L1 fallback handles this case.
|
|
109
|
+
const l2Match = testMaybeArray(l2Value, (item) => (item[l3] !== undefined && item[l3] !== null) ||
|
|
110
|
+
item.type === l3);
|
|
111
|
+
if (l2Match)
|
|
112
|
+
return true;
|
|
113
|
+
// Fall back: check L3 property on the L1 object
|
|
114
|
+
const l3Value = l1Obj[l3];
|
|
115
|
+
return l3Value !== undefined && l3Value !== null;
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Compile multiple query paths into a single predicate (OR logic).
|
|
120
|
+
*/
|
|
121
|
+
function compile(paths) {
|
|
122
|
+
const predicates = paths.map(buildPredicate);
|
|
123
|
+
return (update) => predicates.some((pred) => pred(update));
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Create a runtime filter predicate from a grammY-style filter query string.
|
|
127
|
+
*
|
|
128
|
+
* Unlike grammY's built-in `matchFilter`, this function does not validate
|
|
129
|
+
* queries against a static set of known keys. Instead, it interprets the
|
|
130
|
+
* query at runtime, checking if the specified properties exist on the
|
|
131
|
+
* update object. This allows using any property path, including ones
|
|
132
|
+
* that grammY's type system would reject (e.g. `:media_group_id`).
|
|
133
|
+
*
|
|
134
|
+
* Supports:
|
|
135
|
+
* - L1, L2, and L3 queries (e.g. `message`, `message:photo`, `message:entities:url`)
|
|
136
|
+
* - L1 shortcuts: `""` and `msg` expand to `message` + `channel_post`;
|
|
137
|
+
* `edit` expands to `edited_message` + `edited_channel_post`
|
|
138
|
+
* - L2 shortcuts: `""` expands to `entities` + `caption_entities`;
|
|
139
|
+
* `media` expands to `photo` + `video`;
|
|
140
|
+
* `file` expands to `photo`, `animation`, `audio`, `document`, `video`, `video_note`, `voice`, `sticker`
|
|
141
|
+
* - Multiple queries separated by logical OR (pass array or use multiple calls)
|
|
142
|
+
*
|
|
143
|
+
* @param query A filter query string or array of filter query strings
|
|
144
|
+
* @returns A predicate function compatible with `bot.filter()`
|
|
145
|
+
*
|
|
146
|
+
* @example
|
|
147
|
+
* ```ts
|
|
148
|
+
* import { matchQuery } from 'grammy-match-query'
|
|
149
|
+
*
|
|
150
|
+
* // Single query — any message/channel_post with media_group_id
|
|
151
|
+
* bot.filter(matchQuery(':media_group_id'), ctx => {
|
|
152
|
+
* console.log('Media group')
|
|
153
|
+
* })
|
|
154
|
+
*
|
|
155
|
+
* // Multiple queries (OR logic)
|
|
156
|
+
* bot.filter(matchQuery([':photo', ':video']), ctx => {
|
|
157
|
+
* console.log('Photo or video')
|
|
158
|
+
* })
|
|
159
|
+
* ```
|
|
160
|
+
*/
|
|
161
|
+
export function matchQuery(query) {
|
|
162
|
+
const queries = Array.isArray(query) ? query : [query];
|
|
163
|
+
const allPaths = queries.flatMap((q) => expand(parse(q)));
|
|
164
|
+
const predicate = compile(allPaths);
|
|
165
|
+
// deno-lint-ignore no-explicit-any
|
|
166
|
+
return (ctx) => predicate(ctx.update);
|
|
167
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "grammy-match-query",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"description": "Match Query Plugin for grammY",
|
|
5
|
+
"homepage": "https://github.com/PonomareVlad/grammy-match-query",
|
|
6
|
+
"main": "./out/mod.js",
|
|
7
|
+
"types": "./out/mod.d.ts",
|
|
8
|
+
"type": "module",
|
|
9
|
+
"scripts": {
|
|
10
|
+
"prepare": "npm run build",
|
|
11
|
+
"build": "deno2node tsconfig.json"
|
|
12
|
+
},
|
|
13
|
+
"repository": {
|
|
14
|
+
"type": "git",
|
|
15
|
+
"url": "git+https://github.com/PonomareVlad/grammy-match-query.git"
|
|
16
|
+
},
|
|
17
|
+
"exports": {
|
|
18
|
+
".": {
|
|
19
|
+
"types": "./out/mod.d.ts",
|
|
20
|
+
"default": "./out/mod.js"
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"files": [
|
|
24
|
+
"out/"
|
|
25
|
+
],
|
|
26
|
+
"author": "PonomareVlad",
|
|
27
|
+
"license": "MIT",
|
|
28
|
+
"bugs": {
|
|
29
|
+
"url": "https://github.com/PonomareVlad/grammy-match-query/issues"
|
|
30
|
+
},
|
|
31
|
+
"peerDependencies": {
|
|
32
|
+
"grammy": "^1.15.2"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"deno2node": "^1.3.0"
|
|
36
|
+
},
|
|
37
|
+
"keywords": [
|
|
38
|
+
"grammY",
|
|
39
|
+
"Telegram bot framework",
|
|
40
|
+
"plugin",
|
|
41
|
+
"match query",
|
|
42
|
+
"filter query"
|
|
43
|
+
]
|
|
44
|
+
}
|