Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: ability to add command w/o handler to cmd menu. refactor: CommandElementals type #60

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
Open
14 changes: 8 additions & 6 deletions src/command-group.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
type LanguageCode,
Middleware,
} from "./deps.deno.ts";
import type { CommandElementals, CommandOptions } from "./types.ts";
import type { BotCommandX, CommandOptions } from "./types.ts";
import {
ensureArray,
getCommandsRegex,
Expand All @@ -34,7 +34,7 @@ export interface SetMyCommandsParams {
*/
language_code?: LanguageCode;
/** Commands that can be each one passed to a SetMyCommands Call */
commands: BotCommand[];
commands: (BotCommand & { noHandler?: boolean })[];
}

/**
Expand Down Expand Up @@ -275,20 +275,21 @@ export class CommandGroup<C extends Context> {
}

/**
* Serialize all register commands into it's name, prefix and language
* Serialize all register commands into a more detailed object
* including it's name, prefix and language, and more data
*
* @param filterLanguage if undefined, it returns all names
* else get only the locales for the given filterLanguage
* fallbacks to "default"
*
* @returns an array of {@link CommandElementals}
* @returns an array of {@link BotCommandX}
*
* Note: mainly used to serialize for {@link FuzzyMatch}
*/

public toElementals(
filterLanguage?: LanguageCode | "default",
): CommandElementals[] {
): BotCommandX[] {
this._populateMetadata();

return Array.from(this._scopes.values())
Expand All @@ -300,7 +301,7 @@ export class CommandGroup<C extends Context> {
const [language, local] of command.languages.entries()
) {
elements.push({
name: local.name instanceof RegExp
command: local.name instanceof RegExp
? local.name.source
: local.name,
language,
Expand All @@ -309,6 +310,7 @@ export class CommandGroup<C extends Context> {
description: command.getLocalizedDescription(
language,
),
...(command.noHandler ? { noHandler: true } : {}),
});
}
if (filterLanguage) {
Expand Down
25 changes: 19 additions & 6 deletions src/command.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { CommandsFlavor } from "./context.ts";
import {
type BotCommand,
type BotCommandScope,
type BotCommandScopeAllChatAdministrators,
type BotCommandScopeAllGroupChats,
Expand All @@ -12,8 +11,9 @@ import {
type LanguageCode,
type Middleware,
type MiddlewareObj,
type NextFunction,
} from "./deps.deno.ts";
import type { CommandOptions } from "./types.ts";
import type { BotCommandX, CommandOptions } from "./types.ts";
import { ensureArray, type MaybeArray } from "./utils/array.ts";
import {
isAdmin,
Expand Down Expand Up @@ -66,6 +66,7 @@ export class Command<C extends Context = Context> implements MiddlewareObj<C> {
targetedCommands: "optional",
ignoreCase: false,
};
private _noHandler: boolean;

/**
* Initialize a new command with a default handler.
Expand Down Expand Up @@ -130,14 +131,17 @@ export class Command<C extends Context = Context> implements MiddlewareObj<C> {
| Partial<CommandOptions>,
options?: Partial<CommandOptions>,
) {
const handler = isMiddleware(handlerOrOptions)
? handlerOrOptions
: undefined;
let handler = isMiddleware(handlerOrOptions) ? handlerOrOptions : undefined;

options = !handler && isCommandOptions(handlerOrOptions)
? handlerOrOptions
: options;

if (!handler) {
handler = async (_ctx: Context, next: NextFunction) => await next();
this._noHandler = true;
} else this._noHandler = false;

this._options = { ...this._options, ...options };
if (this._options.prefix?.trim() === "") this._options.prefix = "/";
this._languages.set("default", { name: name, description });
Expand Down Expand Up @@ -248,6 +252,14 @@ export class Command<C extends Context = Context> implements MiddlewareObj<C> {
return this._options.prefix;
}

/**
* Get if this command has a handler
*/

get noHandler(): boolean {
return this._noHandler;
}
Comment on lines +255 to +261
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick: I'd invert this to be hasHandler and return !this._noHandler

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking on this and came to the conclusion that I still prefer this being a "negatively described" property, just for the sake of not cluttering json command representations.

I mean, it would add a hasHandler to almost every command out there, to the test's and so what. Since having a handler it's the most common case I prefer it being non present by default property.

The otherway around it would always be present, rather false or true, but always there. Idk. WDYT?


/**
* Registers the command to a scope to allow it to be handled and used with `setMyCommands`.
* This will automatically apply filtering middlewares for you, so the handler only runs on the specified scope.
Expand Down Expand Up @@ -507,13 +519,14 @@ export class Command<C extends Context = Context> implements MiddlewareObj<C> {
*/
public toObject(
languageCode: LanguageCode | "default" = "default",
): BotCommand {
): Pick<BotCommandX, "command" | "description" | "noHandler"> {
const localizedName = this.getLocalizedName(languageCode);
return {
command: localizedName instanceof RegExp
? localizedName.source
: localizedName,
description: this.getLocalizedDescription(languageCode),
...(this.noHandler ? { noHandler: true } : {}),
};
}

Expand Down
2 changes: 1 addition & 1 deletion src/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ export function commands<C extends Context>() {

if (!result || !result.command) return null;

return result.command.prefix + result.command.name;
return result.command.prefix + result.command.command;
};

ctx.getCommandEntities = (
Expand Down
19 changes: 16 additions & 3 deletions src/types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type {
BotCommand,
BotCommandScope,
LanguageCode,
MessageEntity,
Expand Down Expand Up @@ -34,12 +35,24 @@ export interface CommandOptions {
ignoreCase: boolean;
}

export interface CommandElementals {
name: string;
/**
* BotCommand representation with more information about it.
* Specially in regards to the plugin manipulation of it
*/
export interface BotCommandX extends BotCommand {
prefix: string;
/**
* Language in which this command is localize
*/
language: LanguageCode | "default";
/**
* Scopes in which this command is registered
*/
scopes: BotCommandScope[];
description: string;
/**
* True if this command has no middleware attach to it. False if it has.
*/
noHandler?: boolean;
}

/** represents a bot__command entity inside a text message */
Expand Down
6 changes: 3 additions & 3 deletions src/utils/jaro-winkler.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { CommandGroup } from "../command-group.ts";
import { Context, LanguageCode } from "../deps.deno.ts";
import type { CommandElementals } from "../types.ts";
import type { BotCommandX } from "../types.ts";
import { LanguageCodes } from "../language-codes.ts";

export function distance(s1: string, s2: string) {
Expand Down Expand Up @@ -79,7 +79,7 @@ export type JaroWinklerOptions = {
};

type CommandSimilarity = {
command: CommandElementals | null;
command: BotCommandX | null;
similarity: number;
};

Expand Down Expand Up @@ -142,7 +142,7 @@ export function fuzzyMatch<C extends Context>(

const bestMatch = cmds.reduce(
(best: CommandSimilarity, command) => {
const similarity = JaroWinklerDistance(userInput, command.name, {
const similarity = JaroWinklerDistance(userInput, command.command, {
...options,
});
return similarity > best.similarity ? { command, similarity } : best;
Expand Down
20 changes: 17 additions & 3 deletions test/command-group.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { dummyCtx } from "./context.test.ts";
import {
assert,
assertEquals,
assertObjectMatch,
assertRejects,
assertThrows,
describe,
Expand All @@ -16,7 +17,15 @@ describe("CommandGroup", () => {
const commands = new CommandGroup();
commands.command("test", "no handler");

assertEquals(commands.toArgs().scopes, []);
assertObjectMatch(commands.toArgs().scopes[0], {
commands: [
{
command: "test",
description: "no handler",
noHandler: true,
},
],
});
});

it("should create a command with a default handler", () => {
Expand Down Expand Up @@ -125,10 +134,10 @@ describe("CommandGroup", () => {
},
]);
});
it("should omit commands with no handler", () => {
it("should mark commands with no handler", () => {
const commands = new CommandGroup();
commands.command("test", "handler", (_) => _);
commands.command("omitme", "nohandler");
commands.command("markme", "nohandler");
const params = commands.toSingleScopeArgs({
type: "chat",
chat_id: 10,
Expand All @@ -139,6 +148,11 @@ describe("CommandGroup", () => {
language_code: undefined,
commands: [
{ command: "test", description: "handler" },
{
command: "markme",
description: "nohandler",
noHandler: true,
},
],
},
]);
Expand Down
14 changes: 14 additions & 0 deletions test/integration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,20 @@ describe("Integration", () => {

assertSpyCalls(setMyCommandsSpy, 0);
});
it("should be able to set commands with no handler", async () => {
const myCommands = new CommandGroup();
myCommands.command("command", "super description");

const setMyCommandsSpy = spy(resolvesNext([true] as const));

await myCommands.setCommands({
api: {
raw: { setMyCommands: setMyCommandsSpy },
} as unknown as Api,
});

assertSpyCalls(setMyCommandsSpy, 1);
});
});

describe("ctx.setMyCommands", () => {
Expand Down
Loading