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 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";
@@ -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
+ }