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: add mixin of commands on ctx #15

Merged
merged 4 commits into from
May 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,7 @@ export class Command<C extends Context = Context> implements MiddlewareObj<C> {
* .localize("pt", "iniciar", "Inicia a configuração do bot")
* ```
*
* @param languageCode Language this translation applies to
* @param languageCode Language this translation applies to. @see https://en.wikipedia.org/wiki/List_of_ISO_639_language_codes
* @param name Localized command name
* @param description Localized command description
*/
Expand Down
8 changes: 5 additions & 3 deletions src/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@ import {
} from "./deps.deno.ts";
import { CommandOptions } from "./types.ts";

type SetMyCommandsParams = {
export type SetMyCommandsParams = {
/**
* Scope
* @param language_code two letter abbreviation in ISO_639 standard: https://en.wikipedia.org/wiki/List_of_ISO_639_language_codes
*/
scope?: BotCommandScope;
language_code?: string;
Expand Down Expand Up @@ -149,8 +150,9 @@ export class Commands<C extends Context> {
language_code: language === "default"
? undefined
: language,
commands: commands
.map((command) => command.toObject(language))
commands: commands.map((command) =>
command.toObject(language)
)
.filter((args) => args.command.length > 0),
});
}
Expand Down
54 changes: 50 additions & 4 deletions src/context.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Commands } from "./commands.ts";
import { Context, NextFunction } from "./deps.deno.ts";
import { fuzzyMatch, JaroWinklerOptions } from "./jaro-winkler.ts";
import { SetMyCommandsParams } from "./mod.ts";

export interface CommandsFlavor<C extends Context = Context> extends Context {
/**
Expand All @@ -10,7 +11,10 @@ export interface CommandsFlavor<C extends Context = Context> extends Context {
* @param commands List of available commands
* @returns Promise with the result of the operations
*/
setMyCommands: (commands: Commands<C>) => Promise<void>;
setMyCommands: (
commands: Commands<C>,
...moreCommands: Commands<C>[]
) => Promise<void>;
/**
* Returns the nearest command to the user input.
* If no command is found, returns `null`.
Expand All @@ -30,16 +34,26 @@ export interface CommandsFlavor<C extends Context = Context> extends Context {
*/
export function commands<C extends Context>() {
return (ctx: CommandsFlavor<C>, next: NextFunction) => {
ctx.setMyCommands = async (commands) => {
ctx.setMyCommands = async (
commands,
...moreCommands: Commands<C>[]
) => {
if (!ctx.chat) {
throw new Error(
"cannot call `ctx.setMyCommands` on an update with no `chat` property",
);
}
const commandsParams = [commands].concat(moreCommands).map((
commands,
) => commands.toSingleScopeArgs({
type: "chat",
chat_id: ctx.chat!.id,
}));

const mergedCommands = _mergeMyCommandsParams(commandsParams);

await Promise.all(
commands
.toSingleScopeArgs({ type: "chat", chat_id: ctx.chat.id })
mergedCommands
.map((args) => ctx.api.raw.setMyCommands(args)),
);
};
Expand All @@ -55,3 +69,35 @@ export function commands<C extends Context>() {
return next();
};
}

/**
* Iterates over an array of commands params, merging commands when two commandsParams
* are from the same language.
*
* @param commandParams an array of commands params coming from multiple Commands instances
* @returns an array containing all commands to be set on ctx
*/

export function _mergeMyCommandsParams(
commandParams: SetMyCommandsParams[][],
): SetMyCommandsParams[] {
if (!commandParams.flat().length) return [];
return commandParams
.flat()
.sort((a, b) => {
if (!a.language_code) return -1;
if (!b.language_code) return 1;
return a.language_code.localeCompare(b.language_code);
})
.reduce((result, current, i, arr) => {
if (i === 0 || current.language_code !== arr[i - 1].language_code) {
result.push(current);
return result;
} else {
result[result.length - 1].commands = result[result.length - 1]
.commands
.concat(current.commands);
return result;
}
}, [] as SetMyCommandsParams[]);
}
104 changes: 104 additions & 0 deletions test/commands.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Commands } from "../src/commands.ts";
import { _mergeMyCommandsParams } from "../src/mod.ts";
import { assertEquals, describe, it } from "./deps.test.ts";

describe("Commands", () => {
Expand Down Expand Up @@ -130,5 +131,108 @@ describe("Commands", () => {
]);
});
});
describe("_mergeMyCommandsParams", () => {
it("should merge command's from different Commands instances", () => {
const a = new Commands();
a.command("a", "test a", (_) => _);
const b = new Commands();
b.command("b", "test b", (_) => _);
const c = new Commands();
c.command("c", "test c", (_) => _);

const allParams = [a, b, c].map((
commands,
) => commands.toSingleScopeArgs({
type: "chat",
chat_id: 10,
}));

const mergedCommands = _mergeMyCommandsParams(allParams);

assertEquals(mergedCommands, [
{
scope: { type: "chat", chat_id: 10 },
language_code: undefined,
commands: [
{ command: "c", description: "test c" },
{ command: "b", description: "test b" },
{ command: "a", description: "test a" },
],
},
]);
});
it("should merge for localized scopes", () => {
const a = new Commands();
a.command("a", "test a", (_) => _);
a.command("a1", "test a1", (_) => _).localize(
"es",
"localA1",
"prueba a1 localizada",
);
a.command("a2", "test a2", (_) => _).localize(
"fr",
"localiseA2",
"test a2 localisé",
);

const b = new Commands();
b.command("b", "test b", (_) => _)
.localize("es", "localB", "prueba b localizada")
.localize("fr", "localiseB", "prueba b localisé");

const allParams = [a, b].map((
commands,
) => commands.toSingleScopeArgs({
type: "chat",
chat_id: 10,
}));

const mergedCommands = _mergeMyCommandsParams(allParams);
assertEquals(mergedCommands, [
{
scope: { type: "chat", chat_id: 10 },
language_code: undefined,
commands: [
{ command: "b", description: "test b" },
{ command: "a", description: "test a" },
{ command: "a1", description: "test a1" },
{ command: "a2", description: "test a2" },
],
},
{
scope: { type: "chat", chat_id: 10 },
language_code: "es",
commands: [
{ command: "a", description: "test a" },
{
command: "localA1",
description: "prueba a1 localizada",
},
{ command: "a2", description: "test a2" },
{
command: "localB",
description: "prueba b localizada",
},
],
},
{
scope: { type: "chat", chat_id: 10 },
language_code: "fr",
commands: [
{ command: "a", description: "test a" },
{ command: "a1", description: "test a1" },
{
command: "localiseA2",
description: "test a2 localisé",
},
{
command: "localiseB",
description: "prueba b localisé",
},
],
},
]);
});
});
});
});
Loading