@grammyjs/commands 0.3.1 → 0.5.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/README.md +25 -19
- package/out/command.d.ts +33 -9
- package/out/command.js +96 -15
- package/out/{plugin.d.ts → commands.d.ts} +4 -3
- package/out/{plugin.js → commands.js} +11 -9
- package/out/context.d.ts +17 -4
- package/out/context.js +16 -3
- package/out/deps.node.d.ts +0 -1
- package/out/deps.node.js +1 -5
- package/out/errors.d.ts +4 -0
- package/out/errors.js +10 -0
- package/out/jaro-winkler.d.ts +9 -0
- package/out/jaro-winkler.js +109 -0
- package/out/mod.d.ts +1 -1
- package/out/mod.js +1 -1
- package/package.json +26 -25
package/README.md
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
## grammY Commands Plugin
|
|
2
2
|
|
|
3
|
-
This plugin provides a convenient way to define and manage commands for your grammY bot. It simplifies the process of
|
|
3
|
+
This plugin provides a convenient way to define and manage commands for your grammY bot. It simplifies the process of
|
|
4
|
+
setting up commands with scopes and localization.
|
|
4
5
|
|
|
5
6
|
## Installation
|
|
6
7
|
|
|
7
|
-
|
|
8
8
|
```sh
|
|
9
9
|
npm i @grammyjs/commands
|
|
10
10
|
```
|
|
11
11
|
|
|
12
12
|
## Usage
|
|
13
13
|
|
|
14
|
-
The main functionality of this plugin is to define your commands, localize them, and give them handlers for each
|
|
15
|
-
|
|
14
|
+
The main functionality of this plugin is to define your commands, localize them, and give them handlers for each
|
|
15
|
+
[scope](https://core.telegram.org/bots/api#botcommandscope), like so:
|
|
16
16
|
|
|
17
17
|
```typescript
|
|
18
18
|
import { Bot } from "grammy";
|
|
@@ -23,9 +23,15 @@ const bot = new Bot("<telegram token>");
|
|
|
23
23
|
const myCommands = new Commands();
|
|
24
24
|
|
|
25
25
|
myCommands.command("start", "Initializes bot configuration")
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
26
|
+
.localize("pt", "start", "Inicializa as configurações do bot")
|
|
27
|
+
.addToScope(
|
|
28
|
+
{ type: "all_private_chats" },
|
|
29
|
+
(ctx) => ctx.reply(`Hello, ${ctx.chat.first_name}!`),
|
|
30
|
+
)
|
|
31
|
+
.addToScope(
|
|
32
|
+
{ type: "all_group_chats" },
|
|
33
|
+
(ctx) => ctx.reply(`Hello, members of ${ctx.chat.title}!`),
|
|
34
|
+
);
|
|
29
35
|
|
|
30
36
|
// Calls `setMyCommands`
|
|
31
37
|
await myCommands.setCommands(bot);
|
|
@@ -36,17 +42,17 @@ bot.use(myCommands);
|
|
|
36
42
|
bot.start();
|
|
37
43
|
```
|
|
38
44
|
|
|
39
|
-
It is very important that you call `bot.use` with your instance of the `Commands` class.
|
|
40
|
-
|
|
45
|
+
It is very important that you call `bot.use` with your instance of the `Commands` class. Otherwise, the command handlers
|
|
46
|
+
will not be registered, and your bot will not respond to those commands.
|
|
41
47
|
|
|
42
48
|
### Context shortcuts
|
|
43
49
|
|
|
44
|
-
This plugin provides a shortcut for setting the commands for the current chat.
|
|
45
|
-
|
|
50
|
+
This plugin provides a shortcut for setting the commands for the current chat. To use it, you need to install the
|
|
51
|
+
commands flavor and the plugin itself, like so:
|
|
46
52
|
|
|
47
53
|
```typescript
|
|
48
54
|
import { Bot, Context } from "grammy";
|
|
49
|
-
import { Commands,
|
|
55
|
+
import { Commands, commands, CommandsFlavor } from "@grammyjs/commands";
|
|
50
56
|
|
|
51
57
|
type BotContext = CommandsFlavor;
|
|
52
58
|
|
|
@@ -54,15 +60,15 @@ const bot = new Bot<BotContext>("<telegram_token>");
|
|
|
54
60
|
bot.use(commands());
|
|
55
61
|
|
|
56
62
|
bot.on("message", async (ctx) => {
|
|
57
|
-
|
|
63
|
+
const cmds = new Commands();
|
|
58
64
|
|
|
59
|
-
|
|
60
|
-
|
|
65
|
+
cmds.command("start", "Initializes bot configuration")
|
|
66
|
+
.localize("pt", "start", "Inicializa as configurações do bot");
|
|
61
67
|
|
|
62
|
-
|
|
68
|
+
await ctx.setMyCommands(cmds);
|
|
63
69
|
|
|
64
|
-
|
|
70
|
+
return ctx.reply("Commands set!");
|
|
65
71
|
});
|
|
66
72
|
|
|
67
|
-
bot.start()
|
|
68
|
-
```
|
|
73
|
+
bot.start();
|
|
74
|
+
```
|
package/out/command.d.ts
CHANGED
|
@@ -1,24 +1,48 @@
|
|
|
1
1
|
import { type BotCommand, type BotCommandScope, type BotCommandScopeAllChatAdministrators, type BotCommandScopeAllGroupChats, type BotCommandScopeAllPrivateChats, type ChatTypeMiddleware, type Context, type Middleware, type MiddlewareObj } from "./deps.node.js";
|
|
2
2
|
export type MaybeArray<T> = T | T[];
|
|
3
|
+
export type CommandOptions = {
|
|
4
|
+
/**
|
|
5
|
+
* The prefix used to identify a command.
|
|
6
|
+
* Defaults to `/`.
|
|
7
|
+
*/
|
|
8
|
+
prefix: string;
|
|
9
|
+
/**
|
|
10
|
+
* Whether the command should only be matched at the start of the message.
|
|
11
|
+
* Defaults to `true`.
|
|
12
|
+
*/
|
|
13
|
+
matchOnlyAtStart: boolean;
|
|
14
|
+
/**
|
|
15
|
+
* Whether to ignore or only care about commands ending with the bot's username.
|
|
16
|
+
* Defaults to `"optional"`.
|
|
17
|
+
*
|
|
18
|
+
* - `"ignored"`: only non-targeted commands are matched
|
|
19
|
+
* - `"optional"`: both targeted and non-targeted commands are matched
|
|
20
|
+
* - `"ignored"`: only targeted commands are matched
|
|
21
|
+
*/
|
|
22
|
+
targetedCommands: "ignored" | "optional" | "required";
|
|
23
|
+
};
|
|
3
24
|
type BotCommandGroupsScope = BotCommandScopeAllGroupChats | BotCommandScopeAllChatAdministrators;
|
|
25
|
+
export declare const matchesPattern: (value: string, pattern: string | RegExp) => boolean;
|
|
4
26
|
export declare class Command<C extends Context = Context> implements MiddlewareObj<C> {
|
|
5
27
|
private _scopes;
|
|
6
28
|
private _languages;
|
|
7
29
|
private _composer;
|
|
8
|
-
|
|
30
|
+
private _options;
|
|
31
|
+
constructor(name: string | RegExp, description: string, options?: Partial<CommandOptions>);
|
|
9
32
|
get scopes(): BotCommandScope[];
|
|
10
33
|
get languages(): Map<string, {
|
|
11
|
-
name: string;
|
|
34
|
+
name: string | RegExp;
|
|
12
35
|
description: string;
|
|
13
36
|
}>;
|
|
14
|
-
get names(): string[];
|
|
15
|
-
get name(): string;
|
|
37
|
+
get names(): (string | RegExp)[];
|
|
38
|
+
get name(): string | RegExp;
|
|
16
39
|
get description(): string;
|
|
17
|
-
addToScope(scope: BotCommandGroupsScope,
|
|
18
|
-
addToScope(scope: BotCommandScopeAllPrivateChats,
|
|
19
|
-
addToScope(scope: BotCommandScope,
|
|
20
|
-
|
|
21
|
-
|
|
40
|
+
addToScope(scope: BotCommandGroupsScope, middleware: MaybeArray<ChatTypeMiddleware<C, "group" | "supergroup">>, options?: Partial<CommandOptions>): this;
|
|
41
|
+
addToScope(scope: BotCommandScopeAllPrivateChats, middleware: MaybeArray<ChatTypeMiddleware<C, "private">>, options?: Partial<CommandOptions>): this;
|
|
42
|
+
addToScope(scope: BotCommandScope, middleware: MaybeArray<Middleware<C>>, options?: Partial<CommandOptions>): this;
|
|
43
|
+
static hasCommand(command: MaybeArray<string | RegExp>, options: CommandOptions): (ctx: Context) => boolean;
|
|
44
|
+
localize(languageCode: string, name: string | RegExp, description: string): this;
|
|
45
|
+
getLocalizedName(languageCode: string): string | RegExp;
|
|
22
46
|
getLocalizedDescription(languageCode: string): string;
|
|
23
47
|
toObject(languageCode?: string): BotCommand;
|
|
24
48
|
middleware(): import("grammy").MiddlewareFn<C>;
|
package/out/command.js
CHANGED
|
@@ -1,14 +1,26 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.Command = void 0;
|
|
3
|
+
exports.Command = exports.matchesPattern = void 0;
|
|
4
4
|
const deps_node_js_1 = require("./deps.node.js");
|
|
5
|
+
const errors_js_1 = require("./errors.js");
|
|
6
|
+
const ensureArray = (value) => Array.isArray(value) ? value : [value];
|
|
5
7
|
const isAdmin = (ctx) => ctx.getAuthor().then((author) => ["administrator", "creator"].includes(author.status));
|
|
8
|
+
const matchesPattern = (value, pattern) => typeof pattern === "string" ? value === pattern : pattern.test(value);
|
|
9
|
+
exports.matchesPattern = matchesPattern;
|
|
6
10
|
class Command {
|
|
7
|
-
constructor(name, description) {
|
|
11
|
+
constructor(name, description, options = {}) {
|
|
8
12
|
this._scopes = [];
|
|
9
13
|
this._languages = new Map();
|
|
10
14
|
this._composer = new deps_node_js_1.Composer();
|
|
11
|
-
this.
|
|
15
|
+
this._options = {
|
|
16
|
+
prefix: "/",
|
|
17
|
+
matchOnlyAtStart: true,
|
|
18
|
+
targetedCommands: "optional",
|
|
19
|
+
};
|
|
20
|
+
this._options = { ...this._options, ...options };
|
|
21
|
+
if (this._options.prefix === "")
|
|
22
|
+
this._options.prefix = "/";
|
|
23
|
+
this._languages.set("default", { name: name, description });
|
|
12
24
|
}
|
|
13
25
|
get scopes() {
|
|
14
26
|
return this._scopes;
|
|
@@ -25,21 +37,89 @@ class Command {
|
|
|
25
37
|
get description() {
|
|
26
38
|
return this._languages.get("default").description;
|
|
27
39
|
}
|
|
28
|
-
addToScope(scope,
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
40
|
+
addToScope(scope, middleware, options = this._options) {
|
|
41
|
+
const middlewareArray = ensureArray(middleware);
|
|
42
|
+
const optionsObject = { ...this._options, ...options };
|
|
43
|
+
switch (scope.type) {
|
|
44
|
+
case "default":
|
|
45
|
+
this._composer
|
|
46
|
+
.filter(Command.hasCommand(this.names, optionsObject))
|
|
47
|
+
.use(...middlewareArray);
|
|
48
|
+
break;
|
|
49
|
+
case "all_chat_administrators":
|
|
50
|
+
this._composer
|
|
51
|
+
.filter(Command.hasCommand(this.names, optionsObject))
|
|
52
|
+
.filter(isAdmin)
|
|
53
|
+
.use(...middlewareArray);
|
|
54
|
+
break;
|
|
55
|
+
case "all_private_chats":
|
|
56
|
+
this._composer
|
|
57
|
+
.filter(Command.hasCommand(this.names, optionsObject))
|
|
58
|
+
.chatType("private")
|
|
59
|
+
.use(...middlewareArray);
|
|
60
|
+
break;
|
|
61
|
+
case "all_group_chats":
|
|
62
|
+
this._composer
|
|
63
|
+
.filter(Command.hasCommand(this.names, optionsObject))
|
|
64
|
+
.chatType(["group", "supergroup"])
|
|
65
|
+
.use(...middlewareArray);
|
|
66
|
+
break;
|
|
67
|
+
case "chat":
|
|
68
|
+
case "chat_administrators":
|
|
69
|
+
if (scope.chat_id) {
|
|
70
|
+
this._composer
|
|
71
|
+
.filter(Command.hasCommand(this.names, optionsObject))
|
|
72
|
+
.filter((ctx) => { var _a; return ((_a = ctx.chat) === null || _a === void 0 ? void 0 : _a.id) === scope.chat_id; })
|
|
73
|
+
.filter(isAdmin)
|
|
74
|
+
.use(...middlewareArray);
|
|
75
|
+
}
|
|
76
|
+
break;
|
|
77
|
+
case "chat_member":
|
|
78
|
+
if (scope.chat_id && scope.user_id) {
|
|
79
|
+
this._composer
|
|
80
|
+
.filter(Command.hasCommand(this.names, optionsObject))
|
|
81
|
+
.filter((ctx) => { var _a; return ((_a = ctx.chat) === null || _a === void 0 ? void 0 : _a.id) === scope.chat_id; })
|
|
82
|
+
.filter((ctx) => { var _a; return ((_a = ctx.from) === null || _a === void 0 ? void 0 : _a.id) === scope.user_id; })
|
|
83
|
+
.use(...middlewareArray);
|
|
84
|
+
}
|
|
85
|
+
break;
|
|
86
|
+
default:
|
|
87
|
+
throw new errors_js_1.InvalidScopeError(scope);
|
|
88
|
+
}
|
|
38
89
|
this._scopes.push(scope);
|
|
39
90
|
return this;
|
|
40
91
|
}
|
|
92
|
+
static hasCommand(command, options) {
|
|
93
|
+
const { matchOnlyAtStart, prefix, targetedCommands } = options;
|
|
94
|
+
return (ctx) => {
|
|
95
|
+
if (!ctx.has(":text"))
|
|
96
|
+
return false;
|
|
97
|
+
if (matchOnlyAtStart && !ctx.msg.text.startsWith(prefix)) {
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
100
|
+
const commandNames = ensureArray(command);
|
|
101
|
+
const commands = prefix === "/"
|
|
102
|
+
? ctx.entities("bot_command")
|
|
103
|
+
: ctx.msg.text.split(prefix).map((text) => ({ text }));
|
|
104
|
+
for (const { text } of commands) {
|
|
105
|
+
const [command, username] = text.split("@");
|
|
106
|
+
if (targetedCommands === "ignored" && username)
|
|
107
|
+
continue;
|
|
108
|
+
if (targetedCommands === "required" && !username)
|
|
109
|
+
continue;
|
|
110
|
+
if (username && username !== ctx.me.username)
|
|
111
|
+
continue;
|
|
112
|
+
if (commandNames.some((name) => (0, exports.matchesPattern)(command.replace(prefix, ""), name)))
|
|
113
|
+
return true;
|
|
114
|
+
}
|
|
115
|
+
return false;
|
|
116
|
+
};
|
|
117
|
+
}
|
|
41
118
|
localize(languageCode, name, description) {
|
|
42
|
-
this._languages.set(languageCode, {
|
|
119
|
+
this._languages.set(languageCode, {
|
|
120
|
+
name: new RegExp(name),
|
|
121
|
+
description,
|
|
122
|
+
});
|
|
43
123
|
return this;
|
|
44
124
|
}
|
|
45
125
|
getLocalizedName(languageCode) {
|
|
@@ -51,8 +131,9 @@ class Command {
|
|
|
51
131
|
return (_b = (_a = this._languages.get(languageCode)) === null || _a === void 0 ? void 0 : _a.description) !== null && _b !== void 0 ? _b : this.description;
|
|
52
132
|
}
|
|
53
133
|
toObject(languageCode = "default") {
|
|
134
|
+
const localizedName = this.getLocalizedName(languageCode);
|
|
54
135
|
return {
|
|
55
|
-
command:
|
|
136
|
+
command: localizedName instanceof RegExp ? "" : localizedName,
|
|
56
137
|
description: this.getLocalizedDescription(languageCode),
|
|
57
138
|
};
|
|
58
139
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Command } from "./command.js";
|
|
1
|
+
import { Command, CommandOptions } from "./command.js";
|
|
2
2
|
import { Api, BotCommand, BotCommandScope, Context } from "./deps.node.js";
|
|
3
3
|
type SetMyCommandsParams = {
|
|
4
4
|
scope?: BotCommandScope;
|
|
@@ -10,11 +10,12 @@ export declare class Commands<C extends Context> {
|
|
|
10
10
|
private _scopes;
|
|
11
11
|
private _commands;
|
|
12
12
|
private _composer;
|
|
13
|
-
|
|
13
|
+
private _commandOptions;
|
|
14
|
+
constructor(options?: Partial<CommandOptions>);
|
|
14
15
|
private _addCommandToScope;
|
|
15
16
|
private _populateComposer;
|
|
16
17
|
private _populateMetadata;
|
|
17
|
-
command(name: string, description: string): Command<C>;
|
|
18
|
+
command(name: string | RegExp, description: string, options?: Partial<CommandOptions>): Command<C>;
|
|
18
19
|
toArgs(): SetMyCommandsParams[];
|
|
19
20
|
toSingleScopeArgs(scope: BotCommandScope): SetMyCommandsParams[];
|
|
20
21
|
setCommands({ api }: {
|
|
@@ -4,12 +4,13 @@ exports.Commands = void 0;
|
|
|
4
4
|
const command_js_1 = require("./command.js");
|
|
5
5
|
const deps_node_js_1 = require("./deps.node.js");
|
|
6
6
|
class Commands {
|
|
7
|
-
constructor(
|
|
7
|
+
constructor(options = {}) {
|
|
8
8
|
this._languages = new Set();
|
|
9
9
|
this._scopes = new Map();
|
|
10
10
|
this._commands = [];
|
|
11
11
|
this._composer = new deps_node_js_1.Composer();
|
|
12
|
-
|
|
12
|
+
this._commandOptions = {};
|
|
13
|
+
this._commandOptions = options;
|
|
13
14
|
}
|
|
14
15
|
_addCommandToScope(scope, command) {
|
|
15
16
|
var _a;
|
|
@@ -18,9 +19,7 @@ class Commands {
|
|
|
18
19
|
}
|
|
19
20
|
_populateComposer() {
|
|
20
21
|
for (const command of this._commands) {
|
|
21
|
-
|
|
22
|
-
this._composer.command(args.name, command.middleware());
|
|
23
|
-
}
|
|
22
|
+
this._composer.use(command.middleware());
|
|
24
23
|
}
|
|
25
24
|
}
|
|
26
25
|
_populateMetadata() {
|
|
@@ -35,8 +34,8 @@ class Commands {
|
|
|
35
34
|
}
|
|
36
35
|
});
|
|
37
36
|
}
|
|
38
|
-
command(name, description) {
|
|
39
|
-
const command = new command_js_1.Command(name, description);
|
|
37
|
+
command(name, description, options = this._commandOptions) {
|
|
38
|
+
const command = new command_js_1.Command(name, description, options);
|
|
40
39
|
this._commands.push(command);
|
|
41
40
|
return command;
|
|
42
41
|
}
|
|
@@ -47,8 +46,11 @@ class Commands {
|
|
|
47
46
|
for (const language of this._languages) {
|
|
48
47
|
params.push({
|
|
49
48
|
scope: JSON.parse(scope),
|
|
50
|
-
language_code: language === "default"
|
|
51
|
-
|
|
49
|
+
language_code: language === "default"
|
|
50
|
+
? undefined
|
|
51
|
+
: language,
|
|
52
|
+
commands: commands.map((command) => command.toObject(language))
|
|
53
|
+
.filter((args) => args.command.length > 0),
|
|
52
54
|
});
|
|
53
55
|
}
|
|
54
56
|
}
|
package/out/context.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import { Commands } from "./commands.js";
|
|
1
2
|
import { Context, NextFunction } from "./deps.node.js";
|
|
2
|
-
import {
|
|
3
|
-
export
|
|
3
|
+
import { JaroWinklerOptions } from "./jaro-winkler.js";
|
|
4
|
+
export interface CommandsFlavor<C extends Context = Context> extends Context {
|
|
4
5
|
/**
|
|
5
6
|
* Sets the provided commands for the current chat.
|
|
6
7
|
* Cannot be called on updates that don't have a `chat` property.
|
|
@@ -8,6 +9,18 @@ export type CommandsFlavor<C extends Context = Context> = C & {
|
|
|
8
9
|
* @param commands List of available commands
|
|
9
10
|
* @returns Promise with the result of the operations
|
|
10
11
|
*/
|
|
11
|
-
setMyCommands: (commands: Commands<C>) => Promise<
|
|
12
|
-
|
|
12
|
+
setMyCommands: (commands: Commands<C>) => Promise<void>;
|
|
13
|
+
/**
|
|
14
|
+
* Returns the nearest command to the user input.
|
|
15
|
+
* If no command is found, returns `null`.
|
|
16
|
+
*
|
|
17
|
+
* @param commands List of available commands
|
|
18
|
+
* @param options Options for the Jaro-Winkler algorithm
|
|
19
|
+
* @returns The nearest command or `null`
|
|
20
|
+
*/
|
|
21
|
+
getNearestCommand: (commands: Commands<C>, options?: Partial<JaroWinklerOptions>) => string | null;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Installs the commands flavor into the context.
|
|
25
|
+
*/
|
|
13
26
|
export declare function commands<C extends Context>(): (ctx: CommandsFlavor<C>, next: NextFunction) => Promise<void>;
|
package/out/context.js
CHANGED
|
@@ -1,15 +1,28 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.commands = void 0;
|
|
4
|
+
const jaro_winkler_js_1 = require("./jaro-winkler.js");
|
|
5
|
+
/**
|
|
6
|
+
* Installs the commands flavor into the context.
|
|
7
|
+
*/
|
|
4
8
|
function commands() {
|
|
5
9
|
return (ctx, next) => {
|
|
6
|
-
ctx.setMyCommands = (commands) => {
|
|
7
|
-
if (!ctx.chat)
|
|
10
|
+
ctx.setMyCommands = async (commands) => {
|
|
11
|
+
if (!ctx.chat) {
|
|
8
12
|
throw new Error("cannot call `ctx.setMyCommands` on an update with no `chat` property");
|
|
9
|
-
|
|
13
|
+
}
|
|
14
|
+
await Promise.all(commands
|
|
10
15
|
.toSingleScopeArgs({ type: "chat", chat_id: ctx.chat.id })
|
|
11
16
|
.map((args) => ctx.api.raw.setMyCommands(args)));
|
|
12
17
|
};
|
|
18
|
+
ctx.getNearestCommand = (commands, options) => {
|
|
19
|
+
var _a;
|
|
20
|
+
if ((_a = ctx.msg) === null || _a === void 0 ? void 0 : _a.text) {
|
|
21
|
+
const userInput = ctx.msg.text.substring(1);
|
|
22
|
+
return (0, jaro_winkler_js_1.fuzzyMatch)(userInput, commands, { ...options });
|
|
23
|
+
}
|
|
24
|
+
return null;
|
|
25
|
+
};
|
|
13
26
|
return next();
|
|
14
27
|
};
|
|
15
28
|
}
|
package/out/deps.node.d.ts
CHANGED
|
@@ -1,3 +1,2 @@
|
|
|
1
1
|
export { Api, Bot, type ChatTypeContext, type ChatTypeMiddleware, type CommandMiddleware, Composer, Context, type Middleware, type MiddlewareObj, type NextFunction, } from "grammy";
|
|
2
2
|
export type { BotCommand, BotCommandScope, BotCommandScopeAllChatAdministrators, BotCommandScopeAllGroupChats, BotCommandScopeAllPrivateChats, Chat, } from "grammy/types";
|
|
3
|
-
export { match, P } from "ts-pattern";
|
package/out/deps.node.js
CHANGED
|
@@ -1,12 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
4
|
-
// TODO: Replace with official deno module, once it arrives (https://github.com/gvergnaud/ts-pattern/pull/108)
|
|
3
|
+
exports.Context = exports.Composer = exports.Bot = exports.Api = void 0;
|
|
5
4
|
var grammy_1 = require("grammy");
|
|
6
5
|
Object.defineProperty(exports, "Api", { enumerable: true, get: function () { return grammy_1.Api; } });
|
|
7
6
|
Object.defineProperty(exports, "Bot", { enumerable: true, get: function () { return grammy_1.Bot; } });
|
|
8
7
|
Object.defineProperty(exports, "Composer", { enumerable: true, get: function () { return grammy_1.Composer; } });
|
|
9
8
|
Object.defineProperty(exports, "Context", { enumerable: true, get: function () { return grammy_1.Context; } });
|
|
10
|
-
var ts_pattern_1 = require("ts-pattern");
|
|
11
|
-
Object.defineProperty(exports, "match", { enumerable: true, get: function () { return ts_pattern_1.match; } });
|
|
12
|
-
Object.defineProperty(exports, "P", { enumerable: true, get: function () { return ts_pattern_1.P; } });
|
package/out/errors.d.ts
ADDED
package/out/errors.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.InvalidScopeError = void 0;
|
|
4
|
+
class InvalidScopeError extends Error {
|
|
5
|
+
constructor(scope) {
|
|
6
|
+
super(`Invalid scope: ${scope}`);
|
|
7
|
+
this.name = "InvalidScopeError";
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
exports.InvalidScopeError = InvalidScopeError;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { Commands } from "./commands.js";
|
|
2
|
+
import { Context } from "./deps.node.js";
|
|
3
|
+
export declare function distance(s1: string, s2: string): number;
|
|
4
|
+
export type JaroWinklerOptions = {
|
|
5
|
+
ignoreCase?: boolean;
|
|
6
|
+
similarityThreshold?: number;
|
|
7
|
+
};
|
|
8
|
+
export declare function JaroWinklerDistance(s1: string, s2: string, options: Partial<JaroWinklerOptions>): number;
|
|
9
|
+
export declare function fuzzyMatch<C extends Context>(userInput: string, commands: Commands<C>, options: Partial<JaroWinklerOptions>): string | null;
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.fuzzyMatch = exports.JaroWinklerDistance = exports.distance = void 0;
|
|
4
|
+
function distance(s1, s2) {
|
|
5
|
+
if (s1.length === 0 || s2.length === 0) {
|
|
6
|
+
return 0;
|
|
7
|
+
}
|
|
8
|
+
const matchWindow = Math.floor(Math.max(s1.length, s2.length) / 2.0) - 1;
|
|
9
|
+
const matches1 = new Array(s1.length);
|
|
10
|
+
const matches2 = new Array(s2.length);
|
|
11
|
+
let m = 0; // number of matches
|
|
12
|
+
let t = 0; // number of transpositions
|
|
13
|
+
let i = 0; // index for string 1
|
|
14
|
+
let k = 0; // index for string 2
|
|
15
|
+
for (i = 0; i < s1.length; i++) {
|
|
16
|
+
// loop to find matched characters
|
|
17
|
+
const start = Math.max(0, i - matchWindow); // use the higher of the window diff
|
|
18
|
+
const end = Math.min(i + matchWindow + 1, s2.length); // use the min of the window and string 2 length
|
|
19
|
+
for (k = start; k < end; k++) {
|
|
20
|
+
// iterate second string index
|
|
21
|
+
if (matches2[k]) {
|
|
22
|
+
// if second string character already matched
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
if (s1[i] !== s2[k]) {
|
|
26
|
+
// characters don't match
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
// assume match if the above 2 checks don't continue
|
|
30
|
+
matches1[i] = true;
|
|
31
|
+
matches2[k] = true;
|
|
32
|
+
m++;
|
|
33
|
+
break;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
// nothing matched
|
|
37
|
+
if (m === 0) {
|
|
38
|
+
return 0.0;
|
|
39
|
+
}
|
|
40
|
+
k = 0; // reset string 2 index
|
|
41
|
+
for (i = 0; i < s1.length; i++) {
|
|
42
|
+
// loop to find transpositions
|
|
43
|
+
if (!matches1[i]) {
|
|
44
|
+
// non-matching character
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
while (!matches2[k]) {
|
|
48
|
+
// move k index to the next match
|
|
49
|
+
k++;
|
|
50
|
+
}
|
|
51
|
+
if (s1[i] !== s2[k]) {
|
|
52
|
+
// if the characters don't match, increase transposition
|
|
53
|
+
// HtD: t is always less than the number of matches m, because transpositions are a subset of matches
|
|
54
|
+
t++;
|
|
55
|
+
}
|
|
56
|
+
k++; // iterate k index normally
|
|
57
|
+
}
|
|
58
|
+
// transpositions divided by 2
|
|
59
|
+
t /= 2.0;
|
|
60
|
+
return (m / s1.length + m / s2.length + (m - t) / m) / 3.0; // HtD: therefore, m - t > 0, and m - t < m
|
|
61
|
+
// HtD: => return value is between 0 and 1
|
|
62
|
+
}
|
|
63
|
+
exports.distance = distance;
|
|
64
|
+
// Computes the Winkler distance between two string -- intrepreted from:
|
|
65
|
+
// http://en.wikipedia.org/wiki/Jaro%E2%80%93Winkler_distance
|
|
66
|
+
// s1 is the first string to compare
|
|
67
|
+
// s2 is the second string to compare
|
|
68
|
+
// dj is the Jaro Distance (if you've already computed it), leave blank and the method handles it
|
|
69
|
+
// ignoreCase: if true strings are first converted to lower case before comparison
|
|
70
|
+
function JaroWinklerDistance(s1, s2, options) {
|
|
71
|
+
if (s1 === s2) {
|
|
72
|
+
return 1;
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
if (options.ignoreCase) {
|
|
76
|
+
s1 = s1.toLowerCase();
|
|
77
|
+
s2 = s2.toLowerCase();
|
|
78
|
+
}
|
|
79
|
+
const jaro = distance(s1, s2);
|
|
80
|
+
const p = 0.1; // default scaling factor
|
|
81
|
+
let l = 0; // length of the matching prefix
|
|
82
|
+
while (s1[l] === s2[l] && l < 4) {
|
|
83
|
+
l++;
|
|
84
|
+
}
|
|
85
|
+
// HtD: 1 - jaro >= 0
|
|
86
|
+
return jaro + l * p * (1 - jaro);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
exports.JaroWinklerDistance = JaroWinklerDistance;
|
|
90
|
+
function fuzzyMatch(userInput, commands, options) {
|
|
91
|
+
const defaultSimilarityThreshold = 0.85;
|
|
92
|
+
const similarityThreshold = options.similarityThreshold ||
|
|
93
|
+
defaultSimilarityThreshold;
|
|
94
|
+
const commandsSet = new Set(commands
|
|
95
|
+
.toJSON()
|
|
96
|
+
.flatMap((item) => item.commands.map((command) => command.command)));
|
|
97
|
+
const bestMatch = Array.from(commandsSet).reduce((best, command) => {
|
|
98
|
+
const similarity = JaroWinklerDistance(userInput, command, {
|
|
99
|
+
...options,
|
|
100
|
+
});
|
|
101
|
+
return similarity > best.similarity
|
|
102
|
+
? { command, similarity }
|
|
103
|
+
: best;
|
|
104
|
+
}, { command: null, similarity: 0 });
|
|
105
|
+
return bestMatch.similarity > similarityThreshold
|
|
106
|
+
? bestMatch.command
|
|
107
|
+
: null;
|
|
108
|
+
}
|
|
109
|
+
exports.fuzzyMatch = fuzzyMatch;
|
package/out/mod.d.ts
CHANGED
package/out/mod.js
CHANGED
|
@@ -14,5 +14,5 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
14
14
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
15
|
};
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./commands.js"), exports);
|
|
17
18
|
__exportStar(require("./context.js"), exports);
|
|
18
|
-
__exportStar(require("./plugin.js"), exports);
|
package/package.json
CHANGED
|
@@ -1,27 +1,28 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
"
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
"
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
"
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
"
|
|
26
|
-
|
|
2
|
+
"name": "@grammyjs/commands",
|
|
3
|
+
"version": "0.5.0",
|
|
4
|
+
"description": "grammY Commands Plugin",
|
|
5
|
+
"main": "out/mod.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"backport": "deno task backport",
|
|
8
|
+
"prepare": "deno task backport"
|
|
9
|
+
},
|
|
10
|
+
"keywords": [
|
|
11
|
+
"grammY",
|
|
12
|
+
"telegram",
|
|
13
|
+
"bot",
|
|
14
|
+
"commands"
|
|
15
|
+
],
|
|
16
|
+
"author": "Roz <roz@rjmunhoz.me>",
|
|
17
|
+
"license": "MIT",
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"grammy": "^1.17.1",
|
|
20
|
+
"ts-pattern": "^5.0.1"
|
|
21
|
+
},
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
"typescript": "^5.1.6"
|
|
24
|
+
},
|
|
25
|
+
"files": [
|
|
26
|
+
"out"
|
|
27
|
+
]
|
|
27
28
|
}
|