From 8a795dba47e912be09c25ce43f36dcc6932a7970 Mon Sep 17 00:00:00 2001 From: KnightNiwrem Date: Sat, 5 Aug 2023 13:03:36 +0800 Subject: [PATCH 1/8] Improve fmt experience, remove parse_mode completely --- .vscode/settings.json | 5 ++ README.md | 46 +++++------------- package.json | 8 ++-- src/README.md | 46 +++++------------- src/hydrate.ts | 106 +++++++++++++++++++++++------------------- src/transformer.ts | 79 ++++++++++--------------------- src/utils.ts | 58 +++++++++++++++++++++++ 7 files changed, 171 insertions(+), 177 deletions(-) create mode 100644 .vscode/settings.json create mode 100644 src/utils.ts diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..2c7ddc5 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "deno.enable": true, + "deno.lint": true, + "deno.unstable": true +} \ No newline at end of file diff --git a/README.md b/README.md index cddc430..34dd358 100644 --- a/README.md +++ b/README.md @@ -1,53 +1,29 @@ # Parse Mode plugin for grammY -This plugin provides a transformer for setting default `parse_mode`, and a middleware for hydrating `Context` with familiar `reply` variant methods - i.e. `replyWithHTML`, `replyWithMarkdown`, etc. +This plugin provides transformer that injects `entities` or `caption_entities` if `text` or `caption` are FormattedString. It also provides a middleware that installs this transformer on the `ctx.api` object. ## Usage (Using format) ```ts -import { Bot, Composer, Context } from 'grammy'; +import { Bot, Context } from 'grammy'; import { bold, fmt, hydrateReply, italic } from '@grammyjs/parse-mode'; import type { ParseModeFlavor } from '@grammyjs/parse-mode'; const bot = new Bot>(''); -// Install format reply variant to ctx -bot.use(hydrateReply); +// Install automatic entities inject from FormattedString transformer +bot.use(hydrateReply()); bot.command('demo', async ctx => { - await ctx.replyFmt(fmt`${bold('bold!')} -${bold(italic('bitalic!'))} -${bold(fmt`bold ${link('blink', 'example.com')} bold`)}`); + const boldText = fmt`This is a ${bold('bolded')} string`; + await ctx.reply(boldText); - // fmt can also be called like a regular function - await ctx.replyFmt(fmt(['', ' and ', ' and ', ''], fmt`${bold('bold')}`, fmt`${bold(italic('bitalic'))}`, fmt`${italic('italic')}`)); -}); - -bot.start(); -``` - -## Usage (Using default parse mode and utility reply methods) - -```ts -import { Bot, Composer, Context } from 'grammy'; -import { hydrateReply, parseMode } from '@grammyjs/parse-mode'; + const underlineText = fmt`This is an ${underline('underlined')}`; + await ctx.reply(underlineText); -import type { ParseModeFlavor } from '@grammyjs/parse-mode'; - -const bot = new Bot>(''); - -// Install familiar reply variants to ctx -bot.use(hydrateReply); - -// Sets default parse_mode for ctx.reply -bot.api.config.use(parseMode('MarkdownV2')); - -bot.command('demo', async ctx => { - await ctx.reply('*This* is _the_ default `formatting`'); - await ctx.replyWithHTML('This is withHTML formatting'); - await ctx.replyWithMarkdown('*This* is _withMarkdown_ `formatting`'); - await ctx.replyWithMarkdownV1('*This* is _withMarkdownV1_ `formatting`'); - await ctx.replyWithMarkdownV2('*This* is _withMarkdownV2_ `formatting`'); + // fmt can also be use to concat FormattedStrings + cosnt combinedText = fmt`${boldText}\n${underlineText}` + await ctx.reply(combinedText); }); bot.start(); diff --git a/package.json b/package.json index 450cadd..e3da945 100644 --- a/package.json +++ b/package.json @@ -21,11 +21,11 @@ "build": "deno2node tsconfig.json" }, "devDependencies": { - "@grammyjs/types": "^3.0.3", + "@grammyjs/types": "^3.1.2", "@tsconfig/node16": "^1.0.2", - "@types/node": "^16.6.1", - "deno2node": "^1.8.1", - "grammy": "^1.15.3" + "@types/node": "^20.4.7", + "deno2node": "^1.9.0", + "grammy": "^1.17.2" }, "files": [ "dist/" diff --git a/src/README.md b/src/README.md index cddc430..34dd358 100644 --- a/src/README.md +++ b/src/README.md @@ -1,53 +1,29 @@ # Parse Mode plugin for grammY -This plugin provides a transformer for setting default `parse_mode`, and a middleware for hydrating `Context` with familiar `reply` variant methods - i.e. `replyWithHTML`, `replyWithMarkdown`, etc. +This plugin provides transformer that injects `entities` or `caption_entities` if `text` or `caption` are FormattedString. It also provides a middleware that installs this transformer on the `ctx.api` object. ## Usage (Using format) ```ts -import { Bot, Composer, Context } from 'grammy'; +import { Bot, Context } from 'grammy'; import { bold, fmt, hydrateReply, italic } from '@grammyjs/parse-mode'; import type { ParseModeFlavor } from '@grammyjs/parse-mode'; const bot = new Bot>(''); -// Install format reply variant to ctx -bot.use(hydrateReply); +// Install automatic entities inject from FormattedString transformer +bot.use(hydrateReply()); bot.command('demo', async ctx => { - await ctx.replyFmt(fmt`${bold('bold!')} -${bold(italic('bitalic!'))} -${bold(fmt`bold ${link('blink', 'example.com')} bold`)}`); + const boldText = fmt`This is a ${bold('bolded')} string`; + await ctx.reply(boldText); - // fmt can also be called like a regular function - await ctx.replyFmt(fmt(['', ' and ', ' and ', ''], fmt`${bold('bold')}`, fmt`${bold(italic('bitalic'))}`, fmt`${italic('italic')}`)); -}); - -bot.start(); -``` - -## Usage (Using default parse mode and utility reply methods) - -```ts -import { Bot, Composer, Context } from 'grammy'; -import { hydrateReply, parseMode } from '@grammyjs/parse-mode'; + const underlineText = fmt`This is an ${underline('underlined')}`; + await ctx.reply(underlineText); -import type { ParseModeFlavor } from '@grammyjs/parse-mode'; - -const bot = new Bot>(''); - -// Install familiar reply variants to ctx -bot.use(hydrateReply); - -// Sets default parse_mode for ctx.reply -bot.api.config.use(parseMode('MarkdownV2')); - -bot.command('demo', async ctx => { - await ctx.reply('*This* is _the_ default `formatting`'); - await ctx.replyWithHTML('This is withHTML formatting'); - await ctx.replyWithMarkdown('*This* is _withMarkdown_ `formatting`'); - await ctx.replyWithMarkdownV1('*This* is _withMarkdownV1_ `formatting`'); - await ctx.replyWithMarkdownV2('*This* is _withMarkdownV2_ `formatting`'); + // fmt can also be use to concat FormattedStrings + cosnt combinedText = fmt`${boldText}\n${underlineText}` + await ctx.reply(combinedText); }); bot.start(); diff --git a/src/hydrate.ts b/src/hydrate.ts index 7c34884..38c0011 100644 --- a/src/hydrate.ts +++ b/src/hydrate.ts @@ -1,12 +1,16 @@ import type { Context, - MessageEntity, NextFunction, - ParseMode, } from "./deps.deno.ts"; -import { FormattedString, type Stringable } from "./format.ts"; -type Tail> = T extends [head: infer E1, ...tail: infer E2] +import { FormattedString } from "./format.ts"; +import { parseMode } from './transformer.ts'; + +type Head> = T extends [head: infer E1, ...tail: infer E2] + ? E1 + : never; + +type Tail> = T extends [head: infer E1, ...tail: infer E2] ? E2 : []; @@ -15,61 +19,65 @@ type Tail> = T extends [head: infer E1, ...tail: infer E2] * an additional set of reply methods from `hydrateReply` */ type ParseModeFlavor = C & { - replyFmt: ( - stringLike: Stringable, + editMessageCaption: ( + other?: Head> & { caption?: FormattedString }, + ...args: Tail> + ) => ReturnType; + editMessageMedia: ( + media: { caption?: FormattedString }, + ...args: Tail> + ) => ReturnType; + editMessageText: ( + text: FormattedString, + ...args: Tail> + ) => ReturnType; + reply: ( + text: FormattedString, ...args: Tail> ) => ReturnType; - replyWithHTML: C["reply"]; - replyWithMarkdown: C["reply"]; - replyWithMarkdownV1: C["reply"]; - replyWithMarkdownV2: C["reply"]; -}; - -/** - * @deprecated Use ParseModeFlavor instead of ParseModeContext - */ -type ParseModeContext = ParseModeFlavor; - -const buildReplyWithParseMode = ( - parseMode: ParseMode, - ctx: ParseModeFlavor, -) => { - return (...args: Parameters) => { - const [text, payload, ...rest] = args; - return ctx.reply( - text, - { ...payload, parse_mode: parseMode }, - ...rest as any, - ); - }; + replyWithAnimation: ( + animation: Head>, + other?: Head>> & { caption?: FormattedString }, + ...args: Tail>> + ) => ReturnType; + replyWithAudio: ( + audio: Head>, + other?: Head>> & { caption?: FormattedString }, + ...args: Tail>> + ) => ReturnType; + replyWithDocument: ( + document: Head>, + other?: Head>> & { caption?: FormattedString }, + ...args: Tail>> + ) => ReturnType; + replyWithPhoto: ( + photo: Head>, + other?: Head>> & { caption?: FormattedString }, + ...args: Tail>> + ) => ReturnType; + replyWithVideo: ( + photo: Head>, + other?: Head>> & { caption?: FormattedString }, + ...args: Tail>> + ) => ReturnType; + replyWithVoice: ( + photo: Head>, + other?: Head>> & { caption?: FormattedString }, + ...args: Tail>> + ) => ReturnType; }; /** - * Hydrates a context with an additional set of reply methods + * Hydrates a context with new reply method overloads * @param ctx The context to hydrate * @param next The next middleware function */ -const middleware = async ( +const middleware = () => async ( ctx: ParseModeFlavor, next: NextFunction, ) => { - ctx.replyFmt = (stringLike, ...args) => { - const [payload, ...rest] = args; - const entities = stringLike instanceof FormattedString - ? { entities: stringLike.entities } - : undefined; - return ctx.reply( - stringLike.toString(), - { ...payload, ...entities }, - ...rest as any, - ) as ReturnType; - }; - - ctx.replyWithHTML = buildReplyWithParseMode("HTML", ctx); - ctx.replyWithMarkdown = buildReplyWithParseMode("MarkdownV2", ctx); - ctx.replyWithMarkdownV1 = buildReplyWithParseMode("Markdown", ctx); - ctx.replyWithMarkdownV2 = buildReplyWithParseMode("MarkdownV2", ctx); - return next(); + ctx.api.config.use(parseMode()); + await next(); }; -export { middleware as hydrateReply, type ParseModeFlavor, type ParseModeContext }; +export { middleware as hydrateReply, type ParseModeFlavor }; diff --git a/src/transformer.ts b/src/transformer.ts index c25b203..57a8fe1 100644 --- a/src/transformer.ts +++ b/src/transformer.ts @@ -1,70 +1,41 @@ import type { Transformer } from "./deps.deno.ts"; -const wellKnownParseModesMap = new Map([ - ["html", "HTML"], - ["markdown", "Markdown"], - ["markdownv2", "MarkdownV2"], -]); +import { FormattedString } from './format.ts'; +import { isCaptionEntitiesPayload, isMediaEntitiesPayload, isTextEntitiesPayload } from "./utils.ts"; /** - * Creates a new transformer for the given parse mode. - * @param parseMode {string} The parse mode to use. If the parse mode is not in the well known parse modes map, it will be used as is. - * @see https://core.telegram.org/bots/api#formatting-options for well known parse modes. + * Creates a new transformer that extracts entities from FormattedString text element. + * @see Usage https://grammy.dev/plugins/parse-mode#usage-improving-formatting-experience * @returns {Transformer} The transformer. */ -const buildTransformer = (parseMode: string) => { - const normalisedParseMode = - wellKnownParseModesMap.get(parseMode.toLowerCase()) ?? parseMode; - if (!wellKnownParseModesMap.has(parseMode.toLowerCase())) { - console.warn( - `Could not find parse_mode: ${parseMode}. If this is a valid parse_mode, you should ignore this message.`, - ); - } - +const buildTransformer = () => { const transformer: Transformer = (prev, method, payload, signal) => { if (!payload || "parse_mode" in payload) { return prev(method, payload, signal); } - switch (method) { - case "editMessageMedia": - if ( - "media" in payload && - !("parse_mode" in payload.media) - ) { - // @ts-ignore - payload.media.parse_mode = normalisedParseMode; - } - break; - - case "answerInlineQuery": - if ("results" in payload) { - for (const result of payload.results) { - if ( - "input_message_content" in result && - // @ts-ignore - !("parse_mode" in result.input_message_content) - ) { - // @ts-ignore - result.input_message_content.parse_mode = normalisedParseMode; - } - else if (!("parse_mode" in result)) { - // @ts-ignore - result.parse_mode = normalisedParseMode; - } - } + if (isCaptionEntitiesPayload(method, payload) && payload.caption instanceof FormattedString) { + const caption = payload.caption; + const entities = payload.caption_entities; + payload.caption = caption.toString(); + payload.caption_entities = [...(entities ? entities : []), ...caption.entities] + } else if (isMediaEntitiesPayload(method, payload)) { + const iterableMedia = payload.media instanceof Array ? payload.media : [payload.media]; + for (const media of iterableMedia) { + if (media.caption instanceof FormattedString) { + const caption = media.caption; + const entities = media.caption_entities; + media.caption = caption.toString(); + media.caption_entities = [...(entities ? entities : []), ...caption.entities]; } - break; - - default: - payload = { ...payload, ...{ parse_mode: normalisedParseMode } }; + } + } else if (isTextEntitiesPayload(method, payload) && payload.text instanceof FormattedString) { + const text = payload.text; + const entities = payload.entities; + payload.text = text.toString(); + payload.entities = [...(entities ? entities : []), ...text.entities]; } - - return prev( - method, - payload, - signal, - ); + return prev(method, payload, signal); }; return transformer; }; diff --git a/src/utils.ts b/src/utils.ts new file mode 100644 index 0000000..0281eda --- /dev/null +++ b/src/utils.ts @@ -0,0 +1,58 @@ +import type { MessageEntity } from "./deps.deno.ts"; + +import { FormattedString } from './format.ts'; + +interface CaptionEntitiesPayload { + caption: FormattedString | string | undefined; + caption_entities: MessageEntity[]; +} + +interface MediaEntitiesPayload { + media: CaptionEntitiesPayload | CaptionEntitiesPayload[]; +} + +interface TextEntitiesPayload { + text: FormattedString | string | undefined; + entities: MessageEntity[]; +} + +const captionEntitiesMethod = new Set([ + 'editMessageCaption', + 'sendAnimation', + 'sendAudio', + 'sendDocument', + 'sendPhoto', + 'sendVideo', + 'sendVoice', +]); + +const mediaEntitiesMethod = new Set([ + 'editMessageMedia', + 'sendMediaGroup', +]); + +const textEntitiesMethod = new Set([ + 'editMessageText', + 'sendMessage', +]); + +// deno-lint-ignore no-explicit-any +function isCaptionEntitiesPayload(method: any, _payload: any): _payload is CaptionEntitiesPayload { + return captionEntitiesMethod.has(method); +} + +// deno-lint-ignore no-explicit-any +function isMediaEntitiesPayload(method: any, _payload: any): _payload is MediaEntitiesPayload { + return mediaEntitiesMethod.has(method); +} + +// deno-lint-ignore no-explicit-any +function isTextEntitiesPayload(method: any, _payload: any): _payload is TextEntitiesPayload { + return textEntitiesMethod.has(method); +} + +export { + isCaptionEntitiesPayload, + isMediaEntitiesPayload, + isTextEntitiesPayload, +}; From 1149e3f11aa17078aa3f0f1ed3f583261c1bec3c Mon Sep 17 00:00:00 2001 From: KnightNiwrem Date: Sat, 5 Aug 2023 13:07:40 +0800 Subject: [PATCH 2/8] Deno fmt --- .vscode/settings.json | 8 +++--- README.md | 4 ++- src/README.md | 4 ++- src/format.ts | 35 ++++++++++++++++--------- src/hydrate.ts | 48 +++++++++++++++++++++------------- src/transformer.ts | 32 ++++++++++++++++++----- src/utils.ts | 61 +++++++++++++++++++++++++------------------ 7 files changed, 123 insertions(+), 69 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 2c7ddc5..e40716f 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,5 +1,5 @@ { - "deno.enable": true, - "deno.lint": true, - "deno.unstable": true -} \ No newline at end of file + "deno.enable": true, + "deno.lint": true, + "deno.unstable": true +} diff --git a/README.md b/README.md index 34dd358..fded4ae 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ # Parse Mode plugin for grammY -This plugin provides transformer that injects `entities` or `caption_entities` if `text` or `caption` are FormattedString. It also provides a middleware that installs this transformer on the `ctx.api` object. +This plugin provides transformer that injects `entities` or `caption_entities` +if `text` or `caption` are FormattedString. It also provides a middleware that +installs this transformer on the `ctx.api` object. ## Usage (Using format) diff --git a/src/README.md b/src/README.md index 34dd358..fded4ae 100644 --- a/src/README.md +++ b/src/README.md @@ -1,6 +1,8 @@ # Parse Mode plugin for grammY -This plugin provides transformer that injects `entities` or `caption_entities` if `text` or `caption` are FormattedString. It also provides a middleware that installs this transformer on the `ctx.api` object. +This plugin provides transformer that injects `entities` or `caption_entities` +if `text` or `caption` are FormattedString. It also provides a middleware that +installs this transformer on the `ctx.api` object. ## Usage (Using format) diff --git a/src/format.ts b/src/format.ts index 6110fa8..8488281 100644 --- a/src/format.ts +++ b/src/format.ts @@ -1,7 +1,7 @@ import type { MessageEntity } from "./deps.deno.ts"; /** - * Objects that implement this interface implement a `.toString()` + * Objects that implement this interface implement a `.toString()` * method that returns a `string` value representing the object. */ export interface Stringable { @@ -26,11 +26,11 @@ class FormattedString implements Stringable { entities: MessageEntity[]; /** - * Creates a new `FormattedString`. Useful for constructing a + * Creates a new `FormattedString`. Useful for constructing a * `FormattedString` from user's formatted message * @param text Plain text value * @param entities Format entities - * + * * ```ts * // Constructing a new `FormattedString` from user's message * const userMsg = new FormattedString(ctx.message.text, ctx.entities()); @@ -147,34 +147,45 @@ const customEmoji = (placeholder: Stringable, emoji: number) => { * @param chatId The chat ID to link to. * @param messageId The message ID to link to. */ -const linkMessage = (stringLike: Stringable, chatId: number, messageId: number) => { +const linkMessage = ( + stringLike: Stringable, + chatId: number, + messageId: number, +) => { if (chatId > 0) { - console.warn("linkMessage can only be used for supergroups and channel messages. Refusing to transform into link."); + console.warn( + "linkMessage can only be used for supergroups and channel messages. Refusing to transform into link.", + ); return stringLike; } else if (chatId < -1002147483647 || chatId > -1000000000000) { - console.warn("linkMessage is not able to link messages whose chatIds are greater than -1000000000000 or less than -1002147483647 at this moment. Refusing to transform into link."); + console.warn( + "linkMessage is not able to link messages whose chatIds are greater than -1000000000000 or less than -1002147483647 at this moment. Refusing to transform into link.", + ); return stringLike; } else { - return link(stringLike, `https://t.me/c/${(chatId + 1000000000000) * -1}/${messageId}`); + return link( + stringLike, + `https://t.me/c/${(chatId + 1000000000000) * -1}/${messageId}`, + ); } }; // === Format tagged template function /** - * This is the format tagged template function. It accepts a template literal - * containing any mix of `Stringable` and `string` values, and constructs a + * This is the format tagged template function. It accepts a template literal + * containing any mix of `Stringable` and `string` values, and constructs a * `FormattedString` that represents the combination of all the given values. - * The constructed `FormattedString` also implements Stringable, and can be used + * The constructed `FormattedString` also implements Stringable, and can be used * in further `fmt` tagged templates. * @param rawStringParts An array of `string` parts found in the tagged template * @param stringLikes An array of `Stringable`s found in the tagged template - * + * * ```ts * // Using return values of fmt in fmt * const left = fmt`${bold('>>>')} >>>`; * const right = fmt`<<< ${bold('<<<')}`; - * + * * const final = fmt`${left} ${ctx.msg.text} ${right}`; * await ctx.replyFmt(final); * ``` diff --git a/src/hydrate.ts b/src/hydrate.ts index 38c0011..bfb5f0b 100644 --- a/src/hydrate.ts +++ b/src/hydrate.ts @@ -1,26 +1,25 @@ -import type { - Context, - NextFunction, -} from "./deps.deno.ts"; +import type { Context, NextFunction } from "./deps.deno.ts"; import { FormattedString } from "./format.ts"; -import { parseMode } from './transformer.ts'; +import { parseMode } from "./transformer.ts"; -type Head> = T extends [head: infer E1, ...tail: infer E2] - ? E1 +type Head> = T extends + [head: infer E1, ...tail: infer E2] ? E1 : never; -type Tail> = T extends [head: infer E1, ...tail: infer E2] - ? E2 +type Tail> = T extends + [head: infer E1, ...tail: infer E2] ? E2 : []; /** - * Context flavor for `Context` that will be hydrated with + * Context flavor for `Context` that will be hydrated with * an additional set of reply methods from `hydrateReply` */ type ParseModeFlavor = C & { editMessageCaption: ( - other?: Head> & { caption?: FormattedString }, + other?: Head> & { + caption?: FormattedString; + }, ...args: Tail> ) => ReturnType; editMessageMedia: ( @@ -37,32 +36,44 @@ type ParseModeFlavor = C & { ) => ReturnType; replyWithAnimation: ( animation: Head>, - other?: Head>> & { caption?: FormattedString }, + other?: Head>> & { + caption?: FormattedString; + }, ...args: Tail>> ) => ReturnType; replyWithAudio: ( audio: Head>, - other?: Head>> & { caption?: FormattedString }, + other?: Head>> & { + caption?: FormattedString; + }, ...args: Tail>> ) => ReturnType; replyWithDocument: ( document: Head>, - other?: Head>> & { caption?: FormattedString }, + other?: Head>> & { + caption?: FormattedString; + }, ...args: Tail>> ) => ReturnType; replyWithPhoto: ( photo: Head>, - other?: Head>> & { caption?: FormattedString }, + other?: Head>> & { + caption?: FormattedString; + }, ...args: Tail>> ) => ReturnType; replyWithVideo: ( photo: Head>, - other?: Head>> & { caption?: FormattedString }, + other?: Head>> & { + caption?: FormattedString; + }, ...args: Tail>> ) => ReturnType; replyWithVoice: ( photo: Head>, - other?: Head>> & { caption?: FormattedString }, + other?: Head>> & { + caption?: FormattedString; + }, ...args: Tail>> ) => ReturnType; }; @@ -72,7 +83,8 @@ type ParseModeFlavor = C & { * @param ctx The context to hydrate * @param next The next middleware function */ -const middleware = () => async ( +const middleware = () => +async ( ctx: ParseModeFlavor, next: NextFunction, ) => { diff --git a/src/transformer.ts b/src/transformer.ts index 57a8fe1..b81dc1a 100644 --- a/src/transformer.ts +++ b/src/transformer.ts @@ -1,7 +1,11 @@ import type { Transformer } from "./deps.deno.ts"; -import { FormattedString } from './format.ts'; -import { isCaptionEntitiesPayload, isMediaEntitiesPayload, isTextEntitiesPayload } from "./utils.ts"; +import { FormattedString } from "./format.ts"; +import { + isCaptionEntitiesPayload, + isMediaEntitiesPayload, + isTextEntitiesPayload, +} from "./utils.ts"; /** * Creates a new transformer that extracts entities from FormattedString text element. @@ -14,22 +18,36 @@ const buildTransformer = () => { return prev(method, payload, signal); } - if (isCaptionEntitiesPayload(method, payload) && payload.caption instanceof FormattedString) { + if ( + isCaptionEntitiesPayload(method, payload) && + payload.caption instanceof FormattedString + ) { const caption = payload.caption; const entities = payload.caption_entities; payload.caption = caption.toString(); - payload.caption_entities = [...(entities ? entities : []), ...caption.entities] + payload.caption_entities = [ + ...(entities ? entities : []), + ...caption.entities, + ]; } else if (isMediaEntitiesPayload(method, payload)) { - const iterableMedia = payload.media instanceof Array ? payload.media : [payload.media]; + const iterableMedia = payload.media instanceof Array + ? payload.media + : [payload.media]; for (const media of iterableMedia) { if (media.caption instanceof FormattedString) { const caption = media.caption; const entities = media.caption_entities; media.caption = caption.toString(); - media.caption_entities = [...(entities ? entities : []), ...caption.entities]; + media.caption_entities = [ + ...(entities ? entities : []), + ...caption.entities, + ]; } } - } else if (isTextEntitiesPayload(method, payload) && payload.text instanceof FormattedString) { + } else if ( + isTextEntitiesPayload(method, payload) && + payload.text instanceof FormattedString + ) { const text = payload.text; const entities = payload.entities; payload.text = text.toString(); diff --git a/src/utils.ts b/src/utils.ts index 0281eda..4900348 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,58 +1,67 @@ import type { MessageEntity } from "./deps.deno.ts"; -import { FormattedString } from './format.ts'; +import { FormattedString } from "./format.ts"; interface CaptionEntitiesPayload { - caption: FormattedString | string | undefined; - caption_entities: MessageEntity[]; + caption: FormattedString | string | undefined; + caption_entities: MessageEntity[]; } interface MediaEntitiesPayload { - media: CaptionEntitiesPayload | CaptionEntitiesPayload[]; + media: CaptionEntitiesPayload | CaptionEntitiesPayload[]; } interface TextEntitiesPayload { - text: FormattedString | string | undefined; - entities: MessageEntity[]; + text: FormattedString | string | undefined; + entities: MessageEntity[]; } const captionEntitiesMethod = new Set([ - 'editMessageCaption', - 'sendAnimation', - 'sendAudio', - 'sendDocument', - 'sendPhoto', - 'sendVideo', - 'sendVoice', + "editMessageCaption", + "sendAnimation", + "sendAudio", + "sendDocument", + "sendPhoto", + "sendVideo", + "sendVoice", ]); const mediaEntitiesMethod = new Set([ - 'editMessageMedia', - 'sendMediaGroup', + "editMessageMedia", + "sendMediaGroup", ]); const textEntitiesMethod = new Set([ - 'editMessageText', - 'sendMessage', + "editMessageText", + "sendMessage", ]); // deno-lint-ignore no-explicit-any -function isCaptionEntitiesPayload(method: any, _payload: any): _payload is CaptionEntitiesPayload { - return captionEntitiesMethod.has(method); +function isCaptionEntitiesPayload( + method: any, + _payload: any, +): _payload is CaptionEntitiesPayload { + return captionEntitiesMethod.has(method); } // deno-lint-ignore no-explicit-any -function isMediaEntitiesPayload(method: any, _payload: any): _payload is MediaEntitiesPayload { - return mediaEntitiesMethod.has(method); +function isMediaEntitiesPayload( + method: any, + _payload: any, +): _payload is MediaEntitiesPayload { + return mediaEntitiesMethod.has(method); } // deno-lint-ignore no-explicit-any -function isTextEntitiesPayload(method: any, _payload: any): _payload is TextEntitiesPayload { - return textEntitiesMethod.has(method); +function isTextEntitiesPayload( + method: any, + _payload: any, +): _payload is TextEntitiesPayload { + return textEntitiesMethod.has(method); } export { - isCaptionEntitiesPayload, - isMediaEntitiesPayload, - isTextEntitiesPayload, + isCaptionEntitiesPayload, + isMediaEntitiesPayload, + isTextEntitiesPayload, }; From e99184f32edecc1e602fb6ff71902e8f00770ee2 Mon Sep 17 00:00:00 2001 From: KnightNiwrem Date: Mon, 7 Aug 2023 09:56:44 +0800 Subject: [PATCH 3/8] Update type to remove ts-ignore --- src/utils.ts | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/src/utils.ts b/src/utils.ts index 4900348..091eb5d 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -36,26 +36,23 @@ const textEntitiesMethod = new Set([ "sendMessage", ]); -// deno-lint-ignore no-explicit-any function isCaptionEntitiesPayload( - method: any, - _payload: any, + method: string, + _payload: unknown, ): _payload is CaptionEntitiesPayload { return captionEntitiesMethod.has(method); } -// deno-lint-ignore no-explicit-any function isMediaEntitiesPayload( - method: any, - _payload: any, + method: string, + _payload: unknown, ): _payload is MediaEntitiesPayload { return mediaEntitiesMethod.has(method); } -// deno-lint-ignore no-explicit-any function isTextEntitiesPayload( - method: any, - _payload: any, + method: string, + _payload: unknown, ): _payload is TextEntitiesPayload { return textEntitiesMethod.has(method); } From 32c5642c9ccb57b01784f4f359401aa687888461 Mon Sep 17 00:00:00 2001 From: KnightNiwrem Date: Mon, 7 Aug 2023 10:00:53 +0800 Subject: [PATCH 4/8] Remove @grammyjs/types --- package.json | 1 - src/deps.deno.ts | 5 +---- src/deps.node.ts | 2 +- 3 files changed, 2 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index e3da945..ef615e3 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,6 @@ "build": "deno2node tsconfig.json" }, "devDependencies": { - "@grammyjs/types": "^3.1.2", "@tsconfig/node16": "^1.0.2", "@types/node": "^20.4.7", "deno2node": "^1.9.0", diff --git a/src/deps.deno.ts b/src/deps.deno.ts index cbb9ef8..bdf69a2 100644 --- a/src/deps.deno.ts +++ b/src/deps.deno.ts @@ -3,7 +3,4 @@ export type { NextFunction, Transformer, } from "https://lib.deno.dev/x/grammy@^1.0/mod.ts"; -export type { - MessageEntity, - ParseMode, -} from "https://esm.sh/@grammyjs/types@2"; +export type { MessageEntity } from "https://lib.deno.dev/x/grammy@^1.0/types.ts"; diff --git a/src/deps.node.ts b/src/deps.node.ts index 2489246..e24ea14 100644 --- a/src/deps.node.ts +++ b/src/deps.node.ts @@ -1,2 +1,2 @@ export type { Context, NextFunction, Transformer } from "grammy"; -export type { MessageEntity, ParseMode } from "@grammyjs/types"; +export type { MessageEntity } from "grammy/types"; From cc911ae8a09edeae23db503d1d4526f8aaec01e9 Mon Sep 17 00:00:00 2001 From: KnightNiwrem Date: Mon, 7 Aug 2023 13:07:40 +0800 Subject: [PATCH 5/8] Add API flavor, drop middleware for transformer, update README --- README.md | 27 ++++---- src/README.md | 27 ++++---- src/deps.deno.ts | 1 + src/deps.node.ts | 2 +- src/hydrate.ts | 161 ++++++++++++++++++++++++++++++++++++++++----- src/transformer.ts | 47 ++++++++++++- src/utils.ts | 59 +++++++++++++---- 7 files changed, 269 insertions(+), 55 deletions(-) diff --git a/README.md b/README.md index fded4ae..c85ac43 100644 --- a/README.md +++ b/README.md @@ -1,31 +1,32 @@ # Parse Mode plugin for grammY This plugin provides transformer that injects `entities` or `caption_entities` -if `text` or `caption` are FormattedString. It also provides a middleware that -installs this transformer on the `ctx.api` object. +if `text` or `caption` are FormattedString. ## Usage (Using format) ```ts -import { Bot, Context } from 'grammy'; -import { bold, fmt, hydrateReply, italic } from '@grammyjs/parse-mode'; -import type { ParseModeFlavor } from '@grammyjs/parse-mode'; +import { Api, Bot, Context } from "grammy"; +import { bold, fmt, hydrateFmt, underline } from "@grammyjs/parse-mode"; +import type { ParseModeApiFlavor, ParseModeFlavor } from "@grammyjs/parse-mode"; -const bot = new Bot>(''); +type MyApi = ParseModeApiFlavor; +type MyContext = ParseModeFlavor; +const bot = new Bot(""); // Install automatic entities inject from FormattedString transformer -bot.use(hydrateReply()); +bot.api.config.use(hydrateFmt()); -bot.command('demo', async ctx => { - const boldText = fmt`This is a ${bold('bolded')} string`; +bot.command("demo", async (ctx) => { + const boldText = fmt`This is a ${bold("bolded")} string`; await ctx.reply(boldText); - const underlineText = fmt`This is an ${underline('underlined')}`; - await ctx.reply(underlineText); + const underlineText = fmt`This is an ${underline("underlined")}`; + await ctx.api.sendMessage(ctx.chat.id, underlineText); // fmt can also be use to concat FormattedStrings - cosnt combinedText = fmt`${boldText}\n${underlineText}` - await ctx.reply(combinedText); + const combinedText = fmt`${boldText}\n${underlineText}`; + await bot.api.sendMessage(ctx.chat.id, combinedText); }); bot.start(); diff --git a/src/README.md b/src/README.md index fded4ae..c85ac43 100644 --- a/src/README.md +++ b/src/README.md @@ -1,31 +1,32 @@ # Parse Mode plugin for grammY This plugin provides transformer that injects `entities` or `caption_entities` -if `text` or `caption` are FormattedString. It also provides a middleware that -installs this transformer on the `ctx.api` object. +if `text` or `caption` are FormattedString. ## Usage (Using format) ```ts -import { Bot, Context } from 'grammy'; -import { bold, fmt, hydrateReply, italic } from '@grammyjs/parse-mode'; -import type { ParseModeFlavor } from '@grammyjs/parse-mode'; +import { Api, Bot, Context } from "grammy"; +import { bold, fmt, hydrateFmt, underline } from "@grammyjs/parse-mode"; +import type { ParseModeApiFlavor, ParseModeFlavor } from "@grammyjs/parse-mode"; -const bot = new Bot>(''); +type MyApi = ParseModeApiFlavor; +type MyContext = ParseModeFlavor; +const bot = new Bot(""); // Install automatic entities inject from FormattedString transformer -bot.use(hydrateReply()); +bot.api.config.use(hydrateFmt()); -bot.command('demo', async ctx => { - const boldText = fmt`This is a ${bold('bolded')} string`; +bot.command("demo", async (ctx) => { + const boldText = fmt`This is a ${bold("bolded")} string`; await ctx.reply(boldText); - const underlineText = fmt`This is an ${underline('underlined')}`; - await ctx.reply(underlineText); + const underlineText = fmt`This is an ${underline("underlined")}`; + await ctx.api.sendMessage(ctx.chat.id, underlineText); // fmt can also be use to concat FormattedStrings - cosnt combinedText = fmt`${boldText}\n${underlineText}` - await ctx.reply(combinedText); + const combinedText = fmt`${boldText}\n${underlineText}`; + await bot.api.sendMessage(ctx.chat.id, combinedText); }); bot.start(); diff --git a/src/deps.deno.ts b/src/deps.deno.ts index bdf69a2..752747e 100644 --- a/src/deps.deno.ts +++ b/src/deps.deno.ts @@ -1,4 +1,5 @@ export type { + Api, Context, NextFunction, Transformer, diff --git a/src/deps.node.ts b/src/deps.node.ts index e24ea14..48145b8 100644 --- a/src/deps.node.ts +++ b/src/deps.node.ts @@ -1,2 +1,2 @@ -export type { Context, NextFunction, Transformer } from "grammy"; +export type { Api, Context, NextFunction, Transformer } from "grammy"; export type { MessageEntity } from "grammy/types"; diff --git a/src/hydrate.ts b/src/hydrate.ts index bfb5f0b..39ec0c7 100644 --- a/src/hydrate.ts +++ b/src/hydrate.ts @@ -1,7 +1,7 @@ -import type { Context, NextFunction } from "./deps.deno.ts"; +import type { Api, Context } from "./deps.deno.ts"; import { FormattedString } from "./format.ts"; -import { parseMode } from "./transformer.ts"; +import { buildTransformer } from "./transformer.ts"; type Head> = T extends [head: infer E1, ...tail: infer E2] ? E1 @@ -12,10 +12,24 @@ type Tail> = T extends : []; /** - * Context flavor for `Context` that will be hydrated with - * an additional set of reply methods from `hydrateReply` + * Context flavor that will hydrate methods to accept FormattedString */ type ParseModeFlavor = C & { + /* + * No type support for answerInlineQuery yet + * + answerInlineQuery: ( + results: Head>, + ...args: Tail> + ) => ReturnType; + */ + copyMessage: ( + chat_id: Head>, + other?: Head>> & { + caption?: FormattedString; + }, + ...args: Tail>> + ) => ReturnType; editMessageCaption: ( other?: Head> & { caption?: FormattedString; @@ -23,7 +37,9 @@ type ParseModeFlavor = C & { ...args: Tail> ) => ReturnType; editMessageMedia: ( - media: { caption?: FormattedString }, + media: Head> & { + caption?: FormattedString; + }, ...args: Tail> ) => ReturnType; editMessageText: ( @@ -62,6 +78,14 @@ type ParseModeFlavor = C & { }, ...args: Tail>> ) => ReturnType; + replyWithPoll: ( + question: Head>, + options: Head>>, + other?: Head>>> & { + explanation?: FormattedString; + }, + ...args: Tail>>> + ) => ReturnType; replyWithVideo: ( photo: Head>, other?: Head>> & { @@ -79,17 +103,122 @@ type ParseModeFlavor = C & { }; /** - * Hydrates a context with new reply method overloads - * @param ctx The context to hydrate - * @param next The next middleware function + * Api flavor that will hydrate methods to accept FormattedString */ -const middleware = () => -async ( - ctx: ParseModeFlavor, - next: NextFunction, -) => { - ctx.api.config.use(parseMode()); - await next(); +type ParseModeApiFlavor = A & { + /* + * No type support for answerInlineQuery yet + * + answerInlineQuery: ( + inline_query_id: Head>, + results: Head>>, + ...args: Tail>> + ) => ReturnType; + */ + copyMessage: ( + chat_id: Head>, + from_chat_id: Head>>, + message_id: Head>>>, + other?: Tail>>> & { + caption?: FormattedString; + }, + ...args: Tail>>>> + ) => ReturnType; + editMessageCaption: ( + chat_id: Head>, + message_id: Head>>, + other?: Head>>> & { + caption?: FormattedString; + }, + ...args: Tail>>> + ) => ReturnType; + editMessageCaptionInline: ( + inline_message_id: Head>, + other?: Head>> & { + caption?: FormattedString; + }, + ...args: Tail>> + ) => ReturnType; + editMessageMedia: ( + chat_id: Head>, + message_id: Head>>, + media: Head>>> & { + caption?: FormattedString; + }, + ...args: Tail>>> + ) => ReturnType; + editMessageText: ( + chat_id: Head>, + message_id: Head>>, + text: FormattedString, + ...args: Tail>>> + ) => ReturnType; + sendMessage: ( + chat_id: Head>, + text: FormattedString, + ...args: Tail>> + ) => ReturnType; + sendAnimation: ( + chat_id: Head>, + animation: Head>>, + other?: Head>>> & { + caption?: FormattedString; + }, + ...args: Tail>>> + ) => ReturnType; + sendAudio: ( + chat_id: Head>, + audio: Head>>, + other?: Head>>> & { + caption?: FormattedString; + }, + ...args: Tail>>> + ) => ReturnType; + sendDocument: ( + chat_id: Head>, + document: Head>>, + other?: Head>>> & { + caption?: FormattedString; + }, + ...args: Tail>>> + ) => ReturnType; + sendPhoto: ( + chat_id: Head>, + photo: Head>>, + other?: Head>>> & { + caption?: FormattedString; + }, + ...args: Tail>>> + ) => ReturnType; + sendPoll: ( + chat_id: Head>, + question: Head>>, + options: Head>>>, + other?: Head>>>> & { + caption?: FormattedString; + }, + ...args: Tail>>>> + ) => ReturnType; + sendVideo: ( + chat_id: Head>, + video: Head>>, + other?: Head>>> & { + caption?: FormattedString; + }, + ...args: Tail>>> + ) => ReturnType; + sendVoice: ( + chat_id: Head>, + voice: Head>>, + other?: Head>>> & { + caption?: FormattedString; + }, + ...args: Tail>>> + ) => ReturnType; }; -export { middleware as hydrateReply, type ParseModeFlavor }; +export { + buildTransformer as hydrateFmt, + type ParseModeApiFlavor, + type ParseModeFlavor, +}; diff --git a/src/transformer.ts b/src/transformer.ts index b81dc1a..dfa308b 100644 --- a/src/transformer.ts +++ b/src/transformer.ts @@ -3,7 +3,10 @@ import type { Transformer } from "./deps.deno.ts"; import { FormattedString } from "./format.ts"; import { isCaptionEntitiesPayload, + isCaptionEntitiesResult, + isExplanationEntitiesPayload, isMediaEntitiesPayload, + isMessageTextEntitiesContent, isTextEntitiesPayload, } from "./utils.ts"; @@ -29,6 +32,17 @@ const buildTransformer = () => { ...(entities ? entities : []), ...caption.entities, ]; + } else if ( + isExplanationEntitiesPayload(method, payload) && + payload.explanation instanceof FormattedString + ) { + const explanation = payload.explanation; + const entities = payload.explanation_entities; + payload.explanation = explanation.toString(); + payload.explanation_entities = [ + ...(entities ? entities : []), + ...explanation.entities, + ]; } else if (isMediaEntitiesPayload(method, payload)) { const iterableMedia = payload.media instanceof Array ? payload.media @@ -52,10 +66,41 @@ const buildTransformer = () => { const entities = payload.entities; payload.text = text.toString(); payload.entities = [...(entities ? entities : []), ...text.entities]; + } else if ("results" in payload && payload.results instanceof Array) { + for (const result of payload.results) { + if ( + isCaptionEntitiesResult(result) && + result.caption instanceof FormattedString + ) { + const caption = result.caption; + const entities = result.caption_entities; + result.caption = caption.toString(); + result.caption_entities = [ + ...(entities ? entities : []), + ...caption.entities, + ]; + } + + if ("input_message_content" in result && result.input_message_content) { + const content = result.input_message_content; + if ( + isMessageTextEntitiesContent(content) && + content.message_text instanceof FormattedString + ) { + const text = content.message_text; + const entities = content.entities; + content.message_text = text.toString(); + content.entities = [ + ...(entities ? entities : []), + ...text.entities, + ]; + } + } + } } return prev(method, payload, signal); }; return transformer; }; -export { buildTransformer as parseMode }; +export { buildTransformer }; diff --git a/src/utils.ts b/src/utils.ts index 091eb5d..a9067a0 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -2,21 +2,32 @@ import type { MessageEntity } from "./deps.deno.ts"; import { FormattedString } from "./format.ts"; -interface CaptionEntitiesPayload { - caption: FormattedString | string | undefined; - caption_entities: MessageEntity[]; +interface CaptionEntities { + caption?: FormattedString | string; + caption_entities?: MessageEntity[]; } -interface MediaEntitiesPayload { - media: CaptionEntitiesPayload | CaptionEntitiesPayload[]; +interface ExplanationEntities { + explanation?: FormattedString | string; + explanation_entities?: MessageEntity[]; } -interface TextEntitiesPayload { - text: FormattedString | string | undefined; - entities: MessageEntity[]; +interface MediaEntities { + media: CaptionEntities | CaptionEntities[]; +} + +interface MessageTextEntities { + message_text: FormattedString | string; + entities?: MessageEntity[]; +} + +interface TextEntities { + text: FormattedString | string; + entities?: MessageEntity[]; } const captionEntitiesMethod = new Set([ + "copyMessage", "editMessageCaption", "sendAnimation", "sendAudio", @@ -26,6 +37,10 @@ const captionEntitiesMethod = new Set([ "sendVoice", ]); +const explanationEntitiesMethod = new Set([ + "sendPoll", +]); + const mediaEntitiesMethod = new Set([ "editMessageMedia", "sendMediaGroup", @@ -39,26 +54,48 @@ const textEntitiesMethod = new Set([ function isCaptionEntitiesPayload( method: string, _payload: unknown, -): _payload is CaptionEntitiesPayload { +): _payload is CaptionEntities { return captionEntitiesMethod.has(method); } +function isExplanationEntitiesPayload( + method: string, + _payload: unknown, +): _payload is ExplanationEntities { + return explanationEntitiesMethod.has(method); +} + function isMediaEntitiesPayload( method: string, _payload: unknown, -): _payload is MediaEntitiesPayload { +): _payload is MediaEntities { return mediaEntitiesMethod.has(method); } function isTextEntitiesPayload( method: string, _payload: unknown, -): _payload is TextEntitiesPayload { +): _payload is TextEntities { return textEntitiesMethod.has(method); } +function isCaptionEntitiesResult( + result: {}, +): result is CaptionEntities { + return result instanceof Object && "caption" in result; +} + +function isMessageTextEntitiesContent( + content: {}, +): content is MessageTextEntities { + return content instanceof Object && "message_text" in content; +} + export { isCaptionEntitiesPayload, + isCaptionEntitiesResult, + isExplanationEntitiesPayload, isMediaEntitiesPayload, + isMessageTextEntitiesContent, isTextEntitiesPayload, }; From 9a95995b50d9b4958291334627dcbbc53a299109 Mon Sep 17 00:00:00 2001 From: KnightNiwrem Date: Mon, 7 Aug 2023 16:35:41 +0800 Subject: [PATCH 6/8] Update README.md Co-authored-by: KnorpelSenf --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index c85ac43..1deae26 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # Parse Mode plugin for grammY -This plugin provides transformer that injects `entities` or `caption_entities` -if `text` or `caption` are FormattedString. +This plugin lets you format your messages and captions with **bold**, __italic__, etc in a much more reliable and efficient way than with Markdown or HTML. +It does this by providing a transformer that injects `entities` or `caption_entities` if `text` or `caption` are FormattedString. ## Usage (Using format) From 7f00ad345ecfa26a29c5d7f60260fcfd63808083 Mon Sep 17 00:00:00 2001 From: KnightNiwrem Date: Wed, 16 Aug 2023 21:32:22 +0800 Subject: [PATCH 7/8] Add answerInlineQuery signatures --- src/deps.deno.ts | 5 ++++- src/deps.node.ts | 2 +- src/hydrate.ts | 58 +++++++++++++++++++++++++++++++++++++----------- 3 files changed, 50 insertions(+), 15 deletions(-) diff --git a/src/deps.deno.ts b/src/deps.deno.ts index 752747e..5faaf70 100644 --- a/src/deps.deno.ts +++ b/src/deps.deno.ts @@ -4,4 +4,7 @@ export type { NextFunction, Transformer, } from "https://lib.deno.dev/x/grammy@^1.0/mod.ts"; -export type { MessageEntity } from "https://lib.deno.dev/x/grammy@^1.0/types.ts"; +export type { + InputTextMessageContent, + MessageEntity, +} from "https://lib.deno.dev/x/grammy@^1.0/types.ts"; diff --git a/src/deps.node.ts b/src/deps.node.ts index 48145b8..6cd47ff 100644 --- a/src/deps.node.ts +++ b/src/deps.node.ts @@ -1,2 +1,2 @@ export type { Api, Context, NextFunction, Transformer } from "grammy"; -export type { MessageEntity } from "grammy/types"; +export type { InputTextMessageContent, MessageEntity } from "grammy/types"; diff --git a/src/hydrate.ts b/src/hydrate.ts index 39ec0c7..0342e1c 100644 --- a/src/hydrate.ts +++ b/src/hydrate.ts @@ -1,4 +1,4 @@ -import type { Api, Context } from "./deps.deno.ts"; +import type { Api, Context, InputTextMessageContent } from "./deps.deno.ts"; import { FormattedString } from "./format.ts"; import { buildTransformer } from "./transformer.ts"; @@ -11,18 +11,37 @@ type Tail> = T extends [head: infer E1, ...tail: infer E2] ? E2 : []; +type InputTextMessageContentX = + & Omit + & { + message_text: string | FormattedString; + }; + /** * Context flavor that will hydrate methods to accept FormattedString */ -type ParseModeFlavor = C & { - /* - * No type support for answerInlineQuery yet - * +type ParseModeFlavor = Omit & { answerInlineQuery: ( - results: Head>, + results: Parameters[0] extends readonly (infer Q)[] + ? readonly ( + Q extends { type: "article"; input_message_content: infer R1 } + ? Omit & { + input_message_content: R1 | InputTextMessageContentX; + } + : Q extends { caption?: string; input_message_content?: infer R2 } + ? Omit & { + caption?: string | FormattedString; + input_message_content?: R2 | InputTextMessageContentX; + } + : Q extends { input_message_content?: infer R3 } + ? Omit & { + input_message_content?: R3 | InputTextMessageContentX; + } + : Q + )[] + : never, ...args: Tail> ) => ReturnType; - */ copyMessage: ( chat_id: Head>, other?: Head>> & { @@ -105,16 +124,29 @@ type ParseModeFlavor = C & { /** * Api flavor that will hydrate methods to accept FormattedString */ -type ParseModeApiFlavor = A & { - /* - * No type support for answerInlineQuery yet - * +type ParseModeApiFlavor = Omit & { answerInlineQuery: ( inline_query_id: Head>, - results: Head>>, + results: Tail>[0] extends + readonly (infer Q)[] ? readonly ( + Q extends { type: "article"; input_message_content: infer R1 } + ? Omit & { + input_message_content: R1 | InputTextMessageContentX; + } + : Q extends { caption?: string; input_message_content?: infer R2 } + ? Omit & { + caption?: string | FormattedString; + input_message_content?: R2 | InputTextMessageContentX; + } + : Q extends { input_message_content?: infer R3 } + ? Omit & { + input_message_content?: R3 | InputTextMessageContentX; + } + : Q + )[] + : never, ...args: Tail>> ) => ReturnType; - */ copyMessage: ( chat_id: Head>, from_chat_id: Head>>, From c2f9edd45252d5bd5b04a228a352335e7a94d072 Mon Sep 17 00:00:00 2001 From: KnightNiwrem Date: Fri, 18 Aug 2023 00:25:40 +0800 Subject: [PATCH 8/8] Update vscode settings --- .vscode/settings.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index e40716f..e1533c2 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,5 +1,4 @@ { "deno.enable": true, - "deno.lint": true, - "deno.unstable": true + "deno.lint": true }