Skip to content

Commit

Permalink
feat: add transform handler
Browse files Browse the repository at this point in the history
  • Loading branch information
kravetsone committed May 30, 2024
1 parent 8237ccf commit 36d144c
Show file tree
Hide file tree
Showing 4 changed files with 148 additions and 61 deletions.
31 changes: 30 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
[![JSR](https://jsr.io/badges/@gramio/prompt)](https://jsr.io/@gramio/prompt)
[![JSR Score](https://jsr.io/badges/@gramio/prompt/score)](https://jsr.io/@gramio/prompt)

A plugin for [GramIO](https://gramio.dev/) that provides [Prompt](#prompt) and wait methods
A plugin for [GramIO](https://gramio.dev/) that provides [Prompt](#prompt) and [Wait](#wait) methods

## Usage

Expand Down Expand Up @@ -72,6 +72,20 @@ const answer = await context.prompt(
);
```

### Transform

```ts
const name = await context.prompt(
"message",
format`What's your ${bold`name`}?`,
{
transform: (context) => context.text || context.caption || "",
}
);
```

name is `string`

## Wait

### Wait for the next event from the user
Expand Down Expand Up @@ -102,3 +116,18 @@ const answer = await context.wait("message", (context) =>
/[а-яА-Я]/.test(context.text)
);
```

### Wait for the next event from the user ignoring non validated answers with transformer

You can define a handler in params to **transform** the user's answer.

```ts
const answer = await context.wait((context) => /[а-яА-Я]/.test(context.text));
// or combine with event
const answer = await context.wait("message", {
validate: (context) => /[а-яА-Я]/.test(context.text),
transform: (context) => c.text || "",
});
```

answer is `string`
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@gramio/prompt",
"version": "0.0.5",
"version": "0.0.6",
"description": "Prompt plugin for GramIO",
"main": "dist/index.js",
"type": "commonjs",
Expand Down
5 changes: 4 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -294,7 +294,10 @@ export function prompt(): Plugin<
return;
}

prompt.resolve(context);
prompt.resolve(
// @ts-expect-error
prompt.transform ? prompt.transform(context) : context,
);
return prompts.delete(id);
}

Expand Down
171 changes: 113 additions & 58 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@ import type {
Stringable,
} from "gramio";

export type PromptsType = Map<number, PromptData<EventsUnion>>;
export type PromptsType<Data = never> = Map<
number,
PromptData<EventsUnion, Data>
>;

export const events = [
"message",
Expand All @@ -19,35 +22,64 @@ export const events = [

type EventsUnion = (typeof events)[number];

type PromptAnswer<Event extends EventsUnion> = ContextType<BotLike, Event> & {
/**
* @example
* ```ts
* import { Bot, format, bold } from "gramio";
* import { prompt } from "@gramio/prompt";
*
* const bot = new Bot(process.env.token!)
* .extend(prompt())
* .command("start", async (context) => {
* const answer = await context.prompt(
* "message",
* format`What's your ${bold`name`}?`
* );
*
* return context.send(`✨ Your name is ${answer.text}`);
* })
* .onStart(console.log);
*
* bot.start();
* ```
*/
prompt: PromptFunction;
};

interface PromptData<Event extends EventsUnion> {
resolve: (context: PromptAnswer<Event>) => void;
type IsNever<T> = [T] extends [never] ? true : false;

type PromptAnswer<
Event extends EventsUnion,
Data = never,
> = IsNever<Data> extends true
? ContextType<BotLike, Event> & {
/**
* @example
* ```ts
* import { Bot, format, bold } from "gramio";
* import { prompt } from "@gramio/prompt";
*
* const bot = new Bot(process.env.token!)
* .extend(prompt())
* .command("start", async (context) => {
* const answer = await context.prompt(
* "message",
* format`What's your ${bold`name`}?`
* );
*
* return context.send(`✨ Your name is ${answer.text}`);
* })
* .onStart(console.log);
*
* bot.start();
* ```
*/
prompt: PromptFunction;
/**
* Wait for the next event from the user
*
* @example
* ```ts
* .command("start", async (context) => {
* const answer = await context.wait();
*
* return context.send(`✨ Next message after /start command is ${answer.text}`);
* })
* ```
* */
wait: import("./utils.ts").WaitFunction;
}
: Data;

export type ValidateFunction<Event extends EventsUnion, Data> = (
context: PromptAnswer<Event, Data>,
) => MaybePromise<boolean>;

export type TransformFunction<Event extends EventsUnion, Data> = (
context: PromptAnswer<Event, never>,
) => MaybePromise<Data>;

interface PromptData<Event extends EventsUnion, Data = never> {
resolve: (context: PromptAnswer<Event, Data>) => void;
event?: Event;
validate?: (context: PromptAnswer<Event>) => MaybePromise<boolean>;
validate?: ValidateFunction<Event, Data>;
transform?: TransformFunction<Event, Data>;
sendParams?: Optional<SendMessageParams, "chat_id" | "text">;
text?: string;
}
Expand All @@ -58,54 +90,63 @@ function isEvent(
return events.includes(maybeEvent.toString() as EventsUnion);
}

export type ValidateFunction<Event extends EventsUnion> = (
context: PromptAnswer<Event>,
) => MaybePromise<boolean>;

export interface PromptFunctionParams<Event extends EventsUnion>
export interface PromptFunctionParams<Event extends EventsUnion, Data>
extends Optional<SendMessageParams, "chat_id" | "text"> {
validate?: ValidateFunction<Event>;
validate?: ValidateFunction<Event, Data>;
transform?: TransformFunction<Event, Data>;
}

export interface PromptFunction {
/** Send message and wait answer */
(
<Data = never>(
text: Stringable,
params?: PromptFunctionParams<EventsUnion>,
): Promise<PromptAnswer<EventsUnion>>;
params?: PromptFunctionParams<EventsUnion, Data>,
): Promise<PromptAnswer<EventsUnion, Data>>;
/** Send message and wait answer ignoring events not listed */
<Event extends EventsUnion>(
<Event extends EventsUnion, Data = never>(
event: Event,
text: Stringable,
params?: PromptFunctionParams<Event>,
): Promise<PromptAnswer<Event>>;
params?: PromptFunctionParams<Event, Data>,
): Promise<PromptAnswer<Event, Data>>;
}

export interface WaitFunction {
/** Wait for the next event from the user */
(): Promise<PromptAnswer<EventsUnion>>;
<Data = never>(): Promise<PromptAnswer<EventsUnion, Data>>;
/** Wait for the next event from the user ignoring events not listed */
<Event extends EventsUnion>(event: Event): Promise<PromptAnswer<Event>>;
<Event extends EventsUnion, Data = never>(
event: Event,
): Promise<PromptAnswer<Event, Data>>;
/** Wait for the next event from the user ignoring non validated answers */
(validate: ValidateFunction<EventsUnion>): Promise<PromptAnswer<EventsUnion>>;
<Data = never>(
validate: ValidateFunction<EventsUnion, Data>,
): Promise<PromptAnswer<EventsUnion, Data>>;
/** Wait for the next event from the user ignoring non validated answers and not listed events with transformer */
<Event extends EventsUnion, Data = never>(
event: Event,
options: {
validate?: ValidateFunction<Event, Data>;
transform?: TransformFunction<Event, Data>;
},
): Promise<PromptAnswer<Event, Data>>;
/** Wait for the next event from the user ignoring non validated answers and not listed events */
<Event extends EventsUnion>(
<Event extends EventsUnion, Data = never>(
event: Event,
validate: ValidateFunction<Event>,
): Promise<PromptAnswer<Event>>;
validate: ValidateFunction<Event, Data>,
): Promise<PromptAnswer<Event, Data>>;
}

export function getPrompt(
prompts: PromptsType,
id: number,
context: PromptAnswer<EventsUnion>,
): PromptFunction {
async function prompt<Event extends EventsUnion>(
async function prompt<Event extends EventsUnion, Data>(
eventOrText: Event | Stringable,
textOrParams?: Stringable | PromptFunctionParams<Event>,
params?: PromptFunctionParams<Event>,
textOrParams?: Stringable | PromptFunctionParams<Event, Data>,
params?: PromptFunctionParams<Event, Data>,
) {
const { validate, ...sendParams } =
const { validate, transform, ...sendParams } =
params ||
(typeof textOrParams === "object" && !("toString" in textOrParams)
? textOrParams
Expand All @@ -122,13 +163,14 @@ export function getPrompt(

await context.send(text, sendParams);

return new Promise<PromptAnswer<Event>>((resolve) => {
return new Promise<PromptAnswer<Event, Data>>((resolve) => {
prompts.set(id, {
// @ts-expect-error
resolve: resolve,
event: isEvent(eventOrText) ? eventOrText : undefined,
// @ts-expect-error
validate,
// @ts-expect-error
transform,
sendParams,
text: text.toString(),
});
Expand All @@ -139,9 +181,14 @@ export function getPrompt(
}

export function getWait(prompts: PromptsType, id: number): WaitFunction {
async function wait<Event extends EventsUnion>(
eventOrValidate?: Event | ValidateFunction<Event>,
validate?: ValidateFunction<Event>,
async function wait<Event extends EventsUnion, Data>(
eventOrValidate?: Event | ValidateFunction<Event, Data>,
validateOrOptions?:
| ValidateFunction<Event, Data>
| {
validate?: ValidateFunction<Event, Data>;
transform?: TransformFunction<Event, Data>;
},
) {
return new Promise<PromptAnswer<Event>>((resolve) => {
prompts.set(id, {
Expand All @@ -151,9 +198,17 @@ export function getWait(prompts: PromptsType, id: number): WaitFunction {
eventOrValidate && isEvent(eventOrValidate)
? eventOrValidate
: undefined,
// @ts-expect-error
validate:
typeof eventOrValidate === "function" ? eventOrValidate : validate,
typeof eventOrValidate === "function"
? eventOrValidate
: typeof validateOrOptions === "object"
? validateOrOptions.validate
: undefined,
// @ts-expect-error
transform:
typeof validateOrOptions === "object"
? validateOrOptions.transform
: undefined,
});
});
}
Expand Down

0 comments on commit 36d144c

Please sign in to comment.