diff --git a/.github/dependabot.yml b/.github/dependabot.yml deleted file mode 100644 index 7f8c9fa..0000000 --- a/.github/dependabot.yml +++ /dev/null @@ -1,14 +0,0 @@ -# Please see the documentation for all configuration options: -# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates - -version: 2 -updates: - - package-ecosystem: "npm" - directory: "/" - open-pull-requests-limit: 30 - schedule: - interval: "weekly" - day: "saturday" - time: "02:42" # UTC - commit-message: - prefix: "build(npm):" diff --git a/.github/workflows/deno.yml b/.github/workflows/deno.yml new file mode 100644 index 0000000..fc0d102 --- /dev/null +++ b/.github/workflows/deno.yml @@ -0,0 +1,33 @@ +name: Deno + +on: + push: + pull_request: + schedule: + # Check if it works with current deno version + - cron: '42 2 * * 6' # weekly on Saturday 2:42 UTC + +jobs: + denofmt-and-lint: + runs-on: ubuntu-latest + steps: + - uses: denoland/setup-deno@v1 + with: + deno-version: v1.x + - uses: actions/checkout@v4 + + - run: deno lint + - run: deno fmt --check + + test: + runs-on: ubuntu-latest + steps: + - uses: denoland/setup-deno@v1 + with: + deno-version: v1.x + - uses: actions/checkout@v4 + + - run: deno cache source/*.ts + - run: deno check source/*.ts + - run: deno test --coverage=coverage/ + - run: deno coverage coverage/ diff --git a/.gitignore b/.gitignore index 3148a6d..8443fca 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ +.vscode /*-*.*.*.tgz coverage dist +docs node_modules diff --git a/README.md b/README.md index 8a4bcdc..b6855d1 100644 --- a/README.md +++ b/README.md @@ -2,149 +2,70 @@ [![NPM Version](https://img.shields.io/npm/v/telegram-format.svg)](https://www.npmjs.com/package/telegram-format) [![node](https://img.shields.io/node/v/telegram-format.svg)](https://www.npmjs.com/package/telegram-format) +[![deno module](https://shield.deno.dev/x/telegram_format)](https://deno.land/x/telegram_format) > Format Telegram message texts with Markdown or HTML -This library abstracts the [formatting options](https://core.telegram.org/bots/api#formatting-options) for you. +This library abstracts the +[formatting options](https://core.telegram.org/bots/api#formatting-options) for +you. ## Install +Node.js: + ```bash npm install telegram-format ``` +Deno: + +```ts +import {/* ... */} from "https://deno.land/x/telegram_format/mod.ts"; +``` + ## Usage -```js -import {html as format} from 'telegram-format'; -import {markdownv2 as format} from 'telegram-format'; +```ts +import { html as format } from "telegram-format"; +import { markdownv2 as format } from "telegram-format"; -format.bold('hey'); +format.bold("hey"); //=> '*hey*' -format.italic('you'); +format.italic("you"); //=> '_you_' -format.bold(format.italic('they')) +format.bold(format.italic("they")); //=> '*_they_*' -format.url('me', 'https://edjopato.de'); +format.url("me", "https://edjopato.de"); //=> '[me](https://edjopato.de)' // Legacy but still works -import {markdown as format} from 'telegram-format'; +import { markdown as format } from "telegram-format"; ``` -When using `format` as an alias its easy to switch between Markdown and HTML fast. +When using `format` as an alias its easy to switch between Markdown and HTML +fast. ### Escaping Input -When you have something that might be unescaped you need to use `format.escape` before formatting it. +When you have something that might be unescaped you need to use `format.escape` +before formatting it. -```js -const username = 'master_yoda' -format.italic(format.escape(username)) +```ts +const username = "master_yoda"; +format.italic(format.escape(username)); ``` -`format.monospace` and `format.monospaceBlock` will escape on their own as they only need to escape specific characters. -You do not need to escape the input in this cases. - -`MarkdownV2` and `HTML` are fairly similar in escaping inputs but `Markdown` is not. -`Markdown` is still supported by this library and by Telegram for legacy reasons but it behaves a bit differently. -`Markdown` still escapes inputs and does not need `format.escape` before. -Also nested formatting is not supported by telegram itself. -Consider switching to `MarkdownV2` or `HTML` for simplicity reasons. - -## API - -### bold(text) - -#### text - -Type: `string` - -Text to be formatted bold - -### italic(text) - -#### text - -Type: `string` - -Text to be formatted italic - -### strikethrough(text) - -#### text - -Type: `string` - -Text to be striked through - -### underline(text) - -#### text - -Type: `string` - -Text to be underlined - -### monospace(text) - -#### text - -Type: `string` - -Text to be displayed as inline monospace - -### monospaceBlock(text, [programmingLanguage]) - -#### text - -Type: `string` - -Text to be displayed as monospace block - -#### programmingLanguage - -Type: `string` (optional) - -Can be used to specify the programming language of the code - -### url(label, url) - -#### label - -Type: `string` - -Label of the url field - -#### url - -Type: `string` - -Url which is called when the user taps on the label - -### userMention(label, userId) - -#### label - -Type: `string` - -Label of the user mention. Might be used with the first name for example. - -#### userID - -Type: `number` - -User id to which is linked - -### escape(text) - -#### text - -Type: `string` - -Text which should be escaped so it does not interfer with formatting. +`format.monospace` and `format.monospaceBlock` will escape on their own as they +only need to escape specific characters. You do not need to escape the input in +this cases. -User generated Text should always be escapted to prevent errors. +`MarkdownV2` and `HTML` are fairly similar in escaping inputs but `Markdown` is +not. `Markdown` is still supported by this library and by Telegram for legacy +reasons but it behaves a bit differently. `Markdown` still escapes inputs and +does not need `format.escape` before. Also nested formatting is not supported by +telegram itself. Consider switching to `MarkdownV2` or `HTML` for simplicity +reasons. diff --git a/deno.jsonc b/deno.jsonc new file mode 100644 index 0000000..0c9d637 --- /dev/null +++ b/deno.jsonc @@ -0,0 +1,44 @@ +{ + // https://deno.land/manual/getting_started/configuration_file + "lock": false, + // https://deno.land/manual/advanced/typescript/configuration + // https://www.typescriptlang.org/tsconfig/ + "compilerOptions": { + "noFallthroughCasesInSwitch": true, + "noImplicitOverride": true, + "noImplicitReturns": true, + "noPropertyAccessFromIndexSignature": true, + "noUncheckedIndexedAccess": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "useUnknownInCatchVariables": true + }, + "exclude": [ + "./coverage/", + "./dist/", + "./docs/", + "./node_modules/" + ], + "fmt": { + "useTabs": true + }, + "lint": { + "rules": { + // https://lint.deno.land/?all=on + "include": [ + "default-param-last", + "eqeqeq", + "explicit-module-boundary-types", + "no-await-in-loop", + "no-eval", + "no-non-null-asserted-optional-chain", + "no-non-null-assertion", + "no-sparse-arrays" + ] + } + }, + "tasks": { + "coverage": "deno test --coverage=coverage/ && deno coverage coverage/", + "doc": "deno doc --html --name=telegram_format source/mod.ts" + } +} diff --git a/package.json b/package.json index f6b2a54..6dd7999 100644 --- a/package.json +++ b/package.json @@ -10,51 +10,33 @@ "markdownv2" ], "license": "MIT", - "repository": "EdJoPaTo/telegram-format", + "repository": { + "type": "git", + "url": "https://github.com/EdJoPaTo/telegram-format.git" + }, "author": { "name": "EdJoPaTo", - "email": "telegram-format-npm-package@edjopato.de", + "email": "telegram-format-typescript-package@edjopato.de", "url": "https://edjopato.de" }, "scripts": { - "build": "del-cli dist && tsc", - "prepack": "npm run build", - "test": "tsc --sourceMap && xo && c8 --all ava" + "prepare": "deno2node", + "pretest": "deno2node", + "test": "node --test" }, "type": "module", "engines": { "node": ">=14" }, "devDependencies": { - "@sindresorhus/tsconfig": "^5.0.0", - "ava": "^5.0.1", - "c8": "^8.0.1", - "del-cli": "^5.0.0", - "typescript": "^5.0.2", - "xo": "^0.56.0" + "@types/node": "^16.18.64", + "deno2node": "1.10.1" }, "files": [ "dist", - "!*.test.*" + "!*.test.*", + "!test.*" ], - "main": "./dist/index.js", - "types": "./dist/index.d.ts", - "xo": { - "rules": { - "@typescript-eslint/naming-convention": "off", - "@typescript-eslint/prefer-readonly-parameter-types": "error", - "unicorn/prefer-string-replace-all": "off", - "ava/no-ignored-test-files": "off" - }, - "overrides": [ - { - "files": [ - "**/*.test.*" - ], - "rules": { - "@typescript-eslint/prefer-readonly-parameter-types": "off" - } - } - ] - } + "main": "./dist/mod.js", + "types": "./dist/mod.d.ts" } diff --git a/source/README.md b/source/README.md new file mode 100644 index 0000000..5382501 --- /dev/null +++ b/source/README.md @@ -0,0 +1,4 @@ +# telegram_format + +See the [README on GitHub](https://github.com/EdJoPaTo/telegram-format#readme) +or the [documentation on deno.land](https://deno.land/x/telegram-format?doc). diff --git a/source/html.test.ts b/source/html.test.ts index 0cc3301..f910122 100644 --- a/source/html.test.ts +++ b/source/html.test.ts @@ -1,67 +1,74 @@ -import test from 'ava'; -import {html as format} from './html.js'; +import { strictEqual } from "node:assert"; +import { test } from "node:test"; +import * as format from "./html.ts"; +import { Formatter } from "./types.ts"; -test('bold', t => { - t.is(format.bold('bold'), 'bold'); +format satisfies Formatter; + +test("bold", () => { + strictEqual(format.bold("bold"), "bold"); }); -test('italic', t => { - t.is(format.italic('italic'), 'italic'); +test("italic", () => { + strictEqual(format.italic("italic"), "italic"); }); -test('strikethrough', t => { - t.is(format.strikethrough('strikethrough'), 'strikethrough'); +test("strikethrough", () => { + strictEqual(format.strikethrough("strikethrough"), "strikethrough"); }); -test('underline', t => { - t.is(format.underline('underline'), 'underline'); +test("underline", () => { + strictEqual(format.underline("underline"), "underline"); }); -test('spoiler', t => { - t.is(format.spoiler('spoiler'), 'spoiler'); +test("spoiler", () => { + strictEqual( + format.spoiler("spoiler"), + 'spoiler', + ); }); -test('url', t => { - t.is( - format.url('me', 'https://edjopato.de'), +test("url", () => { + strictEqual( + format.url("me", "https://edjopato.de"), 'me', ); }); -test('escape', t => { - t.is(format.escape('hy'), 'h<e>y'); +test("escape", () => { + strictEqual(format.escape("hy"), "h<e>y"); }); -test('bold italic', t => { - t.is(format.bold(format.italic('green')), 'green'); +test("bold italic", () => { + strictEqual(format.bold(format.italic("green")), "green"); }); -test('user mention', t => { - t.is( - format.userMention('inline mention of a user', 123_456_789), +test("user mention", () => { + strictEqual( + format.userMention("inline mention of a user", 123_456_789), 'inline mention of a user', ); }); -test('monospace', t => { - t.is( - format.monospace('inline fixed-width code'), - 'inline fixed-width code', +test("monospace", () => { + strictEqual( + format.monospace("inline fixed-width code"), + "inline fixed-width code", ); }); -test('monospaceBlock w/o language', t => { - t.is( - format.monospaceBlock('pre-formatted fixed-width code block'), - '
pre-formatted fixed-width code block
', +test("monospaceBlock w/o language", () => { + strictEqual( + format.monospaceBlock("pre-formatted fixed-width code block"), + "
pre-formatted fixed-width code block
", ); }); -test('monospaceBlock w/ language', t => { - t.is( +test("monospaceBlock w/ language", () => { + strictEqual( format.monospaceBlock( - 'pre-formatted fixed-width code block written in the Python programming language', - 'python', + "pre-formatted fixed-width code block written in the Python programming language", + "python", ), '
pre-formatted fixed-width code block written in the Python programming language
', ); diff --git a/source/html.ts b/source/html.ts index 174fe64..45e05ad 100644 --- a/source/html.ts +++ b/source/html.ts @@ -1,63 +1,64 @@ -import type {Formatter} from './types.js'; +/** https://core.telegram.org/bots/api#html-style */ +export const parse_mode = "HTML"; -function escape(text: string): string { +/** Escape the input text to be displayed as it is */ +export function escape(text: string): string { return text - .replace(/&/g, '&') - .replace(//g, '>'); + .replace(/&/g, "&") + .replace(//g, ">"); } -function bold(text: string): string { +/** Format the input text bold */ +export function bold(text: string): string { return `${text}`; } -function italic(text: string): string { +/** Format the input text italic */ +export function italic(text: string): string { return `${text}`; } -function strikethrough(text: string): string { +/** Strikethrough the input text */ +export function strikethrough(text: string): string { return `${text}`; } -function underline(text: string): string { +/** Underline the input text */ +export function underline(text: string): string { return `${text}`; } -function spoiler(text: string): string { +/** Format the input text as spoiler */ +export function spoiler(text: string): string { return `${text}`; } -function monospace(text: string): string { +/** Format the input text as monospace */ +export function monospace(text: string): string { return `${escape(text)}`; } -function monospaceBlock(text: string, programmingLanguage?: string): string { +/** Format the input text as a monospace block optionally with a programming language */ +export function monospaceBlock( + text: string, + programmingLanguage?: string, +): string { if (programmingLanguage) { - return `
${escape(text)}
`; + return `
${
+			escape(text)
+		}
`; } return `
${escape(text)}
`; } -function url(label: string, url: string): string { +/** Create an url with a label text */ +export function url(label: string, url: string): string { return `${label}`; } -function userMention(label: string, userId: number): string { +/** Create a user mention with a label text */ +export function userMention(label: string, userId: number): string { return url(label, `tg://user?id=${userId}`); } - -/** https://core.telegram.org/bots/api#html-style */ -export const html = { - parse_mode: 'HTML', - escape, - bold, - italic, - strikethrough, - underline, - spoiler, - monospace, - monospaceBlock, - url, - userMention, -} as const satisfies Formatter; diff --git a/source/index.ts b/source/index.ts deleted file mode 100644 index 41ea91b..0000000 --- a/source/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -export type {Formatter} from './types.js'; - -export {html} from './html.js'; -export {markdown} from './markdown.js'; -export {markdownv2} from './markdownv2.js'; diff --git a/source/markdown.test.ts b/source/markdown.test.ts index 55a5630..408e282 100644 --- a/source/markdown.test.ts +++ b/source/markdown.test.ts @@ -1,75 +1,76 @@ -import test from 'ava'; -import {markdown as format} from './markdown.js'; +import { strictEqual, throws } from "node:assert"; +import { test } from "node:test"; +import * as format from "./markdown.ts"; +import { Formatter } from "./types.ts"; -test('bold', t => { - t.is(format.bold('bold'), '*bold*'); +format satisfies Formatter; + +test("bold", () => { + strictEqual(format.bold("bold"), "*bold*"); }); -test('italic', t => { - t.is(format.italic('italic'), '_italic_'); +test("italic", () => { + strictEqual(format.italic("italic"), "_italic_"); }); -test('url', t => { - t.is(format.url('me', 'https://edjopato.de'), '[me](https://edjopato.de)'); +test("url", () => { + strictEqual( + format.url("me", "https://edjopato.de"), + "[me](https://edjopato.de)", + ); }); -test('escape', t => { - t.is(format.escape('[h_]e(*y)`'), 'hey'); +test("escape", () => { + strictEqual(format.escape("[h_]e(*y)`"), "hey"); }); -test('bold malicious', t => { - t.is(format.bold('bo*ld'), '*bold*'); +test("bold malicious", () => { + strictEqual(format.bold("bo*ld"), "*bold*"); }); -test('italic malicious', t => { - t.is(format.italic('ita_lic'), '_italic_'); +test("italic malicious", () => { + strictEqual(format.italic("ita_lic"), "_italic_"); }); -test('user mention', t => { - t.is( - format.userMention('inline mention of a user', 123_456_789), - '[inline mention of a user](tg://user?id=123456789)', +test("user mention", () => { + strictEqual( + format.userMention("inline mention of a user", 123_456_789), + "[inline mention of a user](tg://user?id=123456789)", ); }); -test('monospace', t => { - t.is( - format.monospace('inline fixed-width code'), - '`inline fixed-width code`', +test("monospace", () => { + strictEqual( + format.monospace("inline fixed-width code"), + "`inline fixed-width code`", ); }); -test('monospaceBlock w/o language', t => { - t.is( - format.monospaceBlock('pre-formatted fixed-width code block'), - '```\npre-formatted fixed-width code block\n```', +test("monospaceBlock w/o language", () => { + strictEqual( + format.monospaceBlock("pre-formatted fixed-width code block"), + "```\npre-formatted fixed-width code block\n```", ); }); -test('monospaceBlock w/ language', t => { - t.is( +test("monospaceBlock w/ language", () => { + strictEqual( format.monospaceBlock( - 'pre-formatted fixed-width code block written in the Python programming language', - 'python', + "pre-formatted fixed-width code block written in the Python programming language", + "python", ), - '```python\npre-formatted fixed-width code block written in the Python programming language\n```', + "```python\npre-formatted fixed-width code block written in the Python programming language\n```", ); }); -test('strikethrough', t => { - t.throws(() => format.strikethrough('1337'), { - message: 'strikethrough is not supported by Markdown. Use MarkdownV2 instead.', - }); +test("strikethrough", () => { + throws(() => format.strikethrough("1337")); }); -test('underline', t => { - t.throws(() => format.underline('1337'), { - message: 'underline is not supported by Markdown. Use MarkdownV2 instead.', - }); +test("underline", () => { + throws(() => format.underline("1337")); }); -test('spoiler', t => { - t.throws(() => format.spoiler('1337'), { - message: 'spoiler is not supported by Markdown. Use MarkdownV2 instead.', - }); +test("spoiler", () => { + throws(() => format.spoiler("1337")); }); diff --git a/source/markdown.ts b/source/markdown.ts index eaccff6..99a710a 100644 --- a/source/markdown.ts +++ b/source/markdown.ts @@ -1,67 +1,72 @@ -import type {Formatter} from './types.js'; +/** https://core.telegram.org/bots/api#markdown-style */ +export const parse_mode = "Markdown"; -function escape(text: string): string { - return text.replace(/[*_`[\]()]/g, ''); +/** Escape the input text to be displayed as it is */ +export function escape(text: string): string { + return text.replace(/[*_`[\]()]/g, ""); } -function bold(text: string): string { - return `*${text.replace(/\*/g, '')}*`; +/** Format the input text bold */ +export function bold(text: string): string { + return `*${text.replace(/\*/g, "")}*`; } -function italic(text: string): string { - return `_${text.replace(/_/g, '')}_`; +/** Format the input text italic */ +export function italic(text: string): string { + return `_${text.replace(/_/g, "")}_`; } -function strikethrough(_text: string): string { - throw new Error('strikethrough is not supported by Markdown. Use MarkdownV2 instead.'); +/** @deprecated unsupported by Telegram. Use MarkdownV2 or HTML instead */ +export function strikethrough(_text: string): string { + throw new Error( + "strikethrough is not supported by Markdown. Use MarkdownV2 instead.", + ); } -function underline(_text: string): string { - throw new Error('underline is not supported by Markdown. Use MarkdownV2 instead.'); +/** @deprecated unsupported by Telegram. Use MarkdownV2 or HTML instead */ +export function underline(_text: string): string { + throw new Error( + "underline is not supported by Markdown. Use MarkdownV2 instead.", + ); } -function spoiler(_text: string): string { - throw new Error('spoiler is not supported by Markdown. Use MarkdownV2 instead.'); +/** @deprecated unsupported by Telegram. Use MarkdownV2 or HTML instead */ +export function spoiler(_text: string): string { + throw new Error( + "spoiler is not supported by Markdown. Use MarkdownV2 instead.", + ); } -function monospace(text: string): string { - return '`' + text.replace(/`/g, '') + '`'; +/** Format the input text as monospace */ +export function monospace(text: string): string { + return "`" + text.replace(/`/g, "") + "`"; } -function monospaceBlock(text: string, programmingLanguage?: string): string { - let result = ''; - result += '```'; +/** Format the input text as a monospace block optionally with a programming language */ +export function monospaceBlock( + text: string, + programmingLanguage?: string, +): string { + let result = ""; + result += "```"; if (programmingLanguage) { result += programmingLanguage; } - result += '\n'; - result += text.replace(/```/g, ''); - result += '\n'; - result += '```'; + result += "\n"; + result += text.replace(/```/g, ""); + result += "\n"; + result += "```"; return result; } -function url(label: string, url: string): string { - return `[${label.replace(/]/, '')}](${url.replace(/\)/, '')})`; +/** Create an url with a label text */ +export function url(label: string, url: string): string { + return `[${label.replace(/]/, "")}](${url.replace(/\)/, "")})`; } -function userMention(label: string, userId: number): string { +/** Create a user mention with a label text */ +export function userMention(label: string, userId: number): string { return url(label, `tg://user?id=${userId}`); } - -/** https://core.telegram.org/bots/api#markdown-style */ -export const markdown = { - parse_mode: 'Markdown', - escape, - bold, - italic, - strikethrough, - underline, - spoiler, - monospace, - monospaceBlock, - url, - userMention, -} as const satisfies Formatter; diff --git a/source/markdownv2.test.ts b/source/markdownv2.test.ts index 02a362a..19264fb 100644 --- a/source/markdownv2.test.ts +++ b/source/markdownv2.test.ts @@ -1,69 +1,76 @@ -import test from 'ava'; -import {markdownv2 as format} from './markdownv2.js'; +import { strictEqual } from "node:assert"; +import { test } from "node:test"; +import * as format from "./markdownv2.ts"; +import { Formatter } from "./types.ts"; -test('bold', t => { - t.is(format.bold('bold'), '*bold*'); +format satisfies Formatter; + +test("bold", () => { + strictEqual(format.bold("bold"), "*bold*"); }); -test('italic', t => { - t.is(format.italic('italic'), '_italic_'); +test("italic", () => { + strictEqual(format.italic("italic"), "_italic_"); }); -test('strikethrough', t => { - t.is(format.strikethrough('strikethrough'), '~strikethrough~'); +test("strikethrough", () => { + strictEqual(format.strikethrough("strikethrough"), "~strikethrough~"); }); -test('underline', t => { - t.is(format.underline('underline'), '__underline__'); +test("underline", () => { + strictEqual(format.underline("underline"), "__underline__"); }); -test('spoiler', t => { - t.is(format.spoiler('spoiler'), '||spoiler||'); +test("spoiler", () => { + strictEqual(format.spoiler("spoiler"), "||spoiler||"); }); -test('url', t => { - t.is(format.url('me', 'https://edjopato.de'), '[me](https://edjopato.de)'); +test("url", () => { + strictEqual( + format.url("me", "https://edjopato.de"), + "[me](https://edjopato.de)", + ); }); -test('escape', t => { - t.is(format.escape('[h_]e(*y\\)`'), '\\[h\\_\\]e\\(\\*y\\\\\\)\\`'); +test("escape", () => { + strictEqual(format.escape("[h_]e(*y\\)`"), "\\[h\\_\\]e\\(\\*y\\\\\\)\\`"); }); -test('escape with number', t => { - t.is(format.escape('h1e2y'), 'h1e2y'); +test("escape with number", () => { + strictEqual(format.escape("h1e2y"), "h1e2y"); }); -test('bold italic', t => { - t.is(format.bold(format.italic('green')), '*_green_*'); +test("bold italic", () => { + strictEqual(format.bold(format.italic("green")), "*_green_*"); }); -test('user mention', t => { - t.is( - format.userMention('inline mention of a user', 123_456_789), - '[inline mention of a user](tg://user?id=123456789)', +test("user mention", () => { + strictEqual( + format.userMention("inline mention of a user", 123_456_789), + "[inline mention of a user](tg://user?id=123456789)", ); }); -test('monospace', t => { - t.is( - format.monospace('inline fixed-width code'), - '`inline fixed-width code`', +test("monospace", () => { + strictEqual( + format.monospace("inline fixed-width code"), + "`inline fixed-width code`", ); }); -test('monospaceBlock w/o language', t => { - t.is( - format.monospaceBlock('pre-formatted fixed-width code block'), - '```\npre-formatted fixed-width code block\n```', +test("monospaceBlock w/o language", () => { + strictEqual( + format.monospaceBlock("pre-formatted fixed-width code block"), + "```\npre-formatted fixed-width code block\n```", ); }); -test('monospaceBlock w/ language', t => { - t.is( +test("monospaceBlock w/ language", () => { + strictEqual( format.monospaceBlock( - 'pre-formatted fixed-width code block written in the Python programming language', - 'python', + "pre-formatted fixed-width code block written in the Python programming language", + "python", ), - '```python\npre-formatted fixed-width code block written in the Python programming language\n```', + "```python\npre-formatted fixed-width code block written in the Python programming language\n```", ); }); diff --git a/source/markdownv2.ts b/source/markdownv2.ts index e8afe72..8a4d34c 100644 --- a/source/markdownv2.ts +++ b/source/markdownv2.ts @@ -1,71 +1,70 @@ -import type {Formatter} from './types.js'; +/** https://core.telegram.org/bots/api#markdownv2-style */ +export const parse_mode = "MarkdownV2"; function escapeInternal(text: string, escapeChars: string): string { - return text.replace(new RegExp(`[${escapeChars}\\\\]`, 'g'), '\\$&'); + return text.replace(new RegExp(`[${escapeChars}\\\\]`, "g"), "\\$&"); } -function escape(text: string): string { - return text.replace(/[_*[\]()~`>#+\-=|{}.!\\]/g, '\\$&'); +/** Escape the input text to be displayed as it is */ +export function escape(text: string): string { + return text.replace(/[_*[\]()~`>#+\-=|{}.!\\]/g, "\\$&"); } -function bold(text: string): string { +/** Format the input text bold */ +export function bold(text: string): string { return `*${text}*`; } -function italic(text: string): string { +/** Format the input text italic */ +export function italic(text: string): string { return `_${text}_`; } -function strikethrough(text: string): string { +/** Strikethrough the input text */ +export function strikethrough(text: string): string { return `~${text}~`; } -function underline(text: string): string { +/** Underline the input text */ +export function underline(text: string): string { return `__${text}__`; } -function spoiler(text: string): string { +/** Format the input text as spoiler */ +export function spoiler(text: string): string { return `||${text}||`; } -function monospace(text: string): string { - return '`' + escapeInternal(text, '`') + '`'; +/** Format the input text as monospace */ +export function monospace(text: string): string { + return "`" + escapeInternal(text, "`") + "`"; } -function monospaceBlock(text: string, programmingLanguage?: string): string { - let result = ''; - result += '```'; +/** Format the input text as a monospace block optionally with a programming language */ +export function monospaceBlock( + text: string, + programmingLanguage?: string, +): string { + let result = ""; + result += "```"; if (programmingLanguage) { result += programmingLanguage; } - result += '\n'; - result += escapeInternal(text, '`'); - result += '\n'; - result += '```'; + result += "\n"; + result += escapeInternal(text, "`"); + result += "\n"; + result += "```"; return result; } -function url(label: string, url: string): string { - return `[${label}](${escapeInternal(url, ')')})`; +/** Create an url with a label text */ +export function url(label: string, url: string): string { + return `[${label}](${escapeInternal(url, ")")})`; } -function userMention(label: string, userId: number): string { +/** Create a user mention with a label text */ +export function userMention(label: string, userId: number): string { return url(label, `tg://user?id=${userId}`); } - -/** https://core.telegram.org/bots/api#markdownv2-style */ -export const markdownv2 = { - parse_mode: 'MarkdownV2', - escape, - bold, - italic, - strikethrough, - underline, - spoiler, - monospace, - monospaceBlock, - url, - userMention, -} as const satisfies Formatter; diff --git a/source/mod.ts b/source/mod.ts new file mode 100644 index 0000000..72774d6 --- /dev/null +++ b/source/mod.ts @@ -0,0 +1,5 @@ +export type { Formatter } from "./types.ts"; + +export * as html from "./html.ts"; +export * as markdown from "./markdown.ts"; +export * as markdownv2 from "./markdownv2.ts"; diff --git a/source/types.ts b/source/types.ts index e14603f..51c97a8 100644 --- a/source/types.ts +++ b/source/types.ts @@ -1,13 +1,25 @@ -export type Formatter = { - parse_mode: 'HTML' | 'Markdown' | 'MarkdownV2'; +/** All exported formatters satisfy this interface */ +export interface Formatter { + /** parse_mode which can be used on sendMessage */ + parse_mode: "HTML" | "Markdown" | "MarkdownV2"; + /** Format the input text bold */ bold: (text: string) => string; + /** Escape the input text to be displayed as it is */ escape: (text: string) => string; + /** Format the input text italic */ italic: (text: string) => string; + /** Format the input text as monospace */ monospace: (text: string) => string; + /** Format the input text as a monospace block optionally with a programming language */ monospaceBlock: (text: string, programmingLanguage?: string) => string; + /** Format the input text as spoiler */ spoiler: (text: string) => string; + /** Strikethrough the input text */ strikethrough: (text: string) => string; + /** Underline the input text */ underline: (text: string) => string; + /** Create an url with a label text */ url: (label: string, url: string) => string; + /** Create a user mention with a label text */ userMention: (label: string, userId: number) => string; -}; +} diff --git a/tsconfig.json b/tsconfig.json index 431fb68..69bb64e 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,11 +1,10 @@ { - "extends": "@sindresorhus/tsconfig/tsconfig.json", "include": [ "source" ], "compilerOptions": { - "target": "ES2020", - "lib": ["ES2020"], + "declaration": true, + "target": "ES2020", // Node.js 14 "outDir": "dist" } }