Skip to content

Commit

Permalink
feat: first implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
Eomm committed Oct 15, 2023
1 parent fcefc4f commit fed6d5f
Show file tree
Hide file tree
Showing 7 changed files with 223 additions and 1 deletion.
1 change: 1 addition & 0 deletions .eslintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"extends": "standard"}
20 changes: 20 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
name: ci

on:
push:
paths-ignore:
- 'docs/**'
- '*.md'
pull_request:
paths-ignore:
- 'docs/**'
- '*.md'

jobs:
test:
permissions:
contents: write
pull-requests: write
uses: fastify/workflows/.github/workflows/plugins-ci.yml@v3
with:
lint: true
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -128,3 +128,5 @@ dist
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*
.vscode/
package-lock.json
58 changes: 57 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,58 @@
# telegraf-save-md-reply
# telegraf-safe-md-reply

[![JavaScript Style Guide](https://img.shields.io/badge/code_style-standard-brightgreen.svg)](https://standardjs.com)
[![ci](https://github.com/Eomm/telegraf-safe-md-reply/actions/workflows/ci.yml/badge.svg)](https://github.com/Eomm/telegraf-safe-md-reply/actions/workflows/ci.yml)

Reply safely with markdown!

Are you tired of this error?!

```
"type": "TelegramError",
"message": "400: Bad Request: can't parse entities: Character '-' is reserved and must be escaped with the preceding '\\'",
```

This package is for you!

## Usage

The `telegraf-safe-md-reply` middleware will add new methods to the `ctx` object:

- `replyWithSafeMarkdownV2`: reply with markdown escaping all the reserved characters
- `escapeMarkdown`: escape a string escaping all the reserved markdown characters

```js
const Telegraf = require('telegraf')
const safeReply = require('telegraf-safe-md-reply')

const bot = new Telegraf(process.env.BOT_TOKEN)

bot.use(safeReply())

bot.command('test', async (ctx) => {
// use the new method to reply
ctx.replyWithSafeMarkdownV2('Hello-World(?)')

// or escape manually:
ctx.replyWithMarkdownV2(`*Hello*${ctx.escapeMarkdown('-World(?)')}`)
})
```

## Options

You can pass an object with options to the middleware:

```js
bot.use(safeReply({
methodName: 'safeReply'
}))

bot.command('test', (ctx) => {
ctx.safeReply('Hello-World(?)')
})
```


## License

Copyright [Manuel Spigolon](https://github.com/Eomm), Licensed under [MIT](./LICENSE).
26 changes: 26 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
'use strict'

module.exports = function helpMiddleware ({
methodName = 'replyWithSafeMarkdownV2'
} = {}) {
return function middleware (ctx, next) {
ctx[methodName] = function (text, extra) {
return ctx.replyWithMarkdownV2(escapeMarkdown(text), extra)
}

ctx.escapeMarkdown = escapeMarkdown

return next()
}
}

// https://core.telegram.org/bots/api#markdownv2-style
const SPECIAL_CHARS = [
'\\', '_', '*', '[', ']', '(', ')', '~', '`', '>', '#', '+', '-', '=', '|', '{', '}', '.', '!'
]

const regex = new RegExp(`[${SPECIAL_CHARS.join('\\')}]`, 'ig')

function escapeMarkdown (text) {
return text.replace(regex, '\\$&')
}
37 changes: 37 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{
"name": "telegraf-safe-md-reply",
"version": "0.0.0",
"description": "Reply safely with markdown!",
"main": "index.js",
"scripts": {
"lint": "standard",
"lint:fix": "standard --fix",
"test": "tap test/*.js"
},
"keywords": [
"telegraf",
"telegram",
"bot-middleware",
"middleware",
"markdown",
"reply"
],
"repository": {
"type": "git",
"url": "git+https://github.com/Eomm/telegraf-safe-md-reply.git"
},
"author": "Manuel Spigolon <[email protected]> (https://github.com/Eomm)",
"funding": "https://github.com/Eomm/telegraf-safe-md-reply?sponsor=1",
"license": "MIT",
"bugs": {
"url": "https://github.com/Eomm/telegraf-safe-md-reply/issues"
},
"engines": {
"node": ">=14"
},
"homepage": "https://github.com/Eomm/telegraf-safe-md-reply#readme",
"devDependencies": {
"standard": "^17.1.0",
"tap": "^16.3.9"
}
}
80 changes: 80 additions & 0 deletions test/index.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
'use strict'

const { test } = require('tap')

const helpMiddleware = require('../index')

test('helpMiddleware', async t => {
const ctx = {}
const next = () => Promise.resolve()

const middleware = helpMiddleware()

await middleware(ctx, next)

t.ok(ctx.replyWithSafeMarkdownV2, 'ctx.replyWithSafeMarkdownV2 exists')
t.ok(ctx.escapeMarkdown, 'ctx.escapeMarkdown exists')
})

test('helpMiddleware with custom methodName', async t => {
const ctx = {}
const next = () => Promise.resolve()

const middleware = helpMiddleware({ methodName: 'replyWithMarkdown' })

await middleware(ctx, next)

t.ok(ctx.replyWithMarkdown, 'ctx.replyWithMarkdown exists')
t.ok(ctx.escapeMarkdown, 'ctx.escapeMarkdown exists')
})

test('replyWithSafeMarkdownV2', async t => {
const ctx = {
replyWithMarkdownV2: (text, extra) => {
// echo
return text
}
}
const next = () => Promise.resolve()

const middleware = helpMiddleware()

await middleware(ctx, next)

t.equal(ctx.replyWithSafeMarkdownV2('foo_bar'), 'foo\\_bar')
t.equal(ctx.replyWithSafeMarkdownV2('foo*bar'), 'foo\\*bar')
t.equal(ctx.replyWithSafeMarkdownV2('foo[bar'), 'foo\\[bar')
t.equal(ctx.replyWithSafeMarkdownV2('foo`bar'), 'foo\\`bar')
t.equal(ctx.replyWithSafeMarkdownV2('foo_bar*'), 'foo\\_bar\\*')
t.equal(ctx.replyWithSafeMarkdownV2('foo_bar*'), 'foo\\_bar\\*')
t.equal(ctx.replyWithSafeMarkdownV2('foo_bar*'), 'foo\\_bar\\*')
t.equal(ctx.replyWithSafeMarkdownV2('foo_bar*'), 'foo\\_bar\\*')
t.equal(ctx.replyWithSafeMarkdownV2('foo_bar*'), 'foo\\_bar\\*')
t.equal(ctx.replyWithSafeMarkdownV2('foo_bar*'), 'foo\\_bar\\*')
t.equal(ctx.replyWithSafeMarkdownV2('foo_bar*'), 'foo\\_bar\\*')
t.equal(ctx.replyWithSafeMarkdownV2('foo_bar*'), 'foo\\_bar\\*')
t.equal(ctx.replyWithSafeMarkdownV2('foo_bar*'), 'foo\\_bar\\*')
})

test('escapeMarkdown', async t => {
const ctx = {}
const next = () => Promise.resolve()

const middleware = helpMiddleware()

await middleware(ctx, next)

t.equal(ctx.escapeMarkdown('foo_bar'), 'foo\\_bar')
t.equal(ctx.escapeMarkdown('foo*bar'), 'foo\\*bar')
t.equal(ctx.escapeMarkdown('foo[bar'), 'foo\\[bar')
t.equal(ctx.escapeMarkdown('foo`bar'), 'foo\\`bar')
t.equal(ctx.escapeMarkdown('foo_bar*'), 'foo\\_bar\\*')
t.equal(ctx.escapeMarkdown('foo_bar*'), 'foo\\_bar\\*')
t.equal(ctx.escapeMarkdown('foo_bar*'), 'foo\\_bar\\*')
t.equal(ctx.escapeMarkdown('foo_bar*'), 'foo\\_bar\\*')
t.equal(ctx.escapeMarkdown('foo_bar*'), 'foo\\_bar\\*')
t.equal(ctx.escapeMarkdown('foo_bar*'), 'foo\\_bar\\*')
t.equal(ctx.escapeMarkdown('foo_bar*'), 'foo\\_bar\\*')
t.equal(ctx.escapeMarkdown('foo_bar*'), 'foo\\_bar\\*')
t.equal(ctx.escapeMarkdown('foo_bar*'), 'foo\\_bar\\*')
})

0 comments on commit fed6d5f

Please sign in to comment.