channel-formatter 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +42 -0
- package/package.json +24 -0
- package/src/index.ts +113 -0
- package/tsconfig.json +15 -0
package/README.md
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# channel-formatter
|
|
2
|
+
|
|
3
|
+
Format markdown-like AI output for messaging channels with different syntax and size limits.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- Telegram MarkdownV2 escaping
|
|
8
|
+
- Slack-friendly markdown conversion
|
|
9
|
+
- Plain-text SMS fallback
|
|
10
|
+
- Message chunking by channel size limits
|
|
11
|
+
|
|
12
|
+
## Install
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npm install channel-formatter
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Usage
|
|
19
|
+
|
|
20
|
+
```ts
|
|
21
|
+
import { formatForChannel } from "channel-formatter";
|
|
22
|
+
|
|
23
|
+
const chunks = formatForChannel(markdown, "telegram");
|
|
24
|
+
|
|
25
|
+
for (const chunk of chunks) {
|
|
26
|
+
await sendMessage(chunk);
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Supported Channels
|
|
31
|
+
|
|
32
|
+
- `telegram`
|
|
33
|
+
- `slack`
|
|
34
|
+
- `teams`
|
|
35
|
+
- `sms`
|
|
36
|
+
|
|
37
|
+
## Exports
|
|
38
|
+
|
|
39
|
+
- `formatForChannel(text, channel)`
|
|
40
|
+
- `splitMessage(text, maxLen)`
|
|
41
|
+
|
|
42
|
+
Best for notification bots, chat assistants, and workflow tools that need channel-specific output formatting.
|
package/package.json
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "channel-formatter",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Format AI response Markdown for Telegram, Slack, Teams, and SMS — zero dependencies",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"import": "./dist/index.js",
|
|
12
|
+
"types": "./dist/index.d.ts"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"scripts": {
|
|
16
|
+
"build": "tsc",
|
|
17
|
+
"dev": "tsc --watch",
|
|
18
|
+
"typecheck": "tsc --noEmit --pretty false"
|
|
19
|
+
},
|
|
20
|
+
"keywords": ["formatter", "telegram", "slack", "teams", "sms", "markdown", "channel", "messaging"],
|
|
21
|
+
"devDependencies": {
|
|
22
|
+
"typescript": "^5"
|
|
23
|
+
}
|
|
24
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* channel-formatter
|
|
3
|
+
*
|
|
4
|
+
* Format AI response Markdown for different messaging channels.
|
|
5
|
+
* Pure functions — zero runtime dependencies.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* import { formatForChannel } from "channel-formatter";
|
|
9
|
+
*
|
|
10
|
+
* const chunks = formatForChannel(markdownText, "telegram");
|
|
11
|
+
* for (const chunk of chunks) await sendMessage(chunk);
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
export type Channel = "telegram" | "slack" | "teams" | "sms";
|
|
15
|
+
|
|
16
|
+
const MAX_TELEGRAM_MSG = 4096;
|
|
17
|
+
const MAX_SLACK_MSG = 3000;
|
|
18
|
+
const MAX_SMS_MSG = 1600; // ~10 concatenated SMS segments
|
|
19
|
+
const MAX_TEAMS_MSG = 4000;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Convert Markdown to Telegram MarkdownV2 format.
|
|
23
|
+
* Telegram MarkdownV2 requires escaping many special characters.
|
|
24
|
+
*/
|
|
25
|
+
function toTelegramMarkdown(text: string): string {
|
|
26
|
+
let out = text;
|
|
27
|
+
|
|
28
|
+
// Headers → bold line
|
|
29
|
+
out = out.replace(/^#{1,3}\s+(.+)$/gm, "*$1*");
|
|
30
|
+
|
|
31
|
+
// **bold** → *bold*
|
|
32
|
+
out = out.replace(/\*\*([^*]+)\*\*/g, "*$1*");
|
|
33
|
+
|
|
34
|
+
// Escape special chars outside code spans
|
|
35
|
+
const parts = out.split(/(```[\s\S]*?```|`[^`]+`)/g);
|
|
36
|
+
out = parts.map((part, i) => {
|
|
37
|
+
if (i % 2 === 1) return part; // code span — leave as-is
|
|
38
|
+
return part.replace(/([_\[\]()~>#+=|{}.!\-])/g, "\\$1");
|
|
39
|
+
}).join("");
|
|
40
|
+
|
|
41
|
+
return out;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Convert Markdown to Slack mrkdwn format.
|
|
46
|
+
*/
|
|
47
|
+
function toSlackMarkdown(text: string): string {
|
|
48
|
+
let out = text;
|
|
49
|
+
// Headers → *bold*
|
|
50
|
+
out = out.replace(/^#{1,3}\s+(.+)$/gm, "*$1*");
|
|
51
|
+
// **bold** → *bold*
|
|
52
|
+
out = out.replace(/\*\*([^*]+)\*\*/g, "*$1*");
|
|
53
|
+
// `inline code` stays same; Slack renders - and * as bullets natively
|
|
54
|
+
return out;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Strip all Markdown for plain-text channels (SMS).
|
|
59
|
+
*/
|
|
60
|
+
function toPlainText(text: string): string {
|
|
61
|
+
let out = text;
|
|
62
|
+
out = out.replace(/^#{1,3}\s+/gm, ""); // remove header markers
|
|
63
|
+
out = out.replace(/\*\*([^*]+)\*\*/g, "$1"); // **bold** → bold
|
|
64
|
+
out = out.replace(/`([^`]+)`/g, "$1"); // `code` → code
|
|
65
|
+
out = out.replace(/```[\s\S]*?```/g, "[code block]"); // code blocks
|
|
66
|
+
out = out.replace(/^[-*]\s/gm, "• "); // bullets
|
|
67
|
+
return out.trim();
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Split a long message into chunks that fit within the channel limit.
|
|
72
|
+
* Tries to split on paragraph boundaries to preserve readability.
|
|
73
|
+
*/
|
|
74
|
+
export function splitMessage(text: string, maxLen: number): string[] {
|
|
75
|
+
if (text.length <= maxLen) return [text];
|
|
76
|
+
const chunks: string[] = [];
|
|
77
|
+
let remaining = text;
|
|
78
|
+
while (remaining.length > 0) {
|
|
79
|
+
if (remaining.length <= maxLen) {
|
|
80
|
+
chunks.push(remaining);
|
|
81
|
+
break;
|
|
82
|
+
}
|
|
83
|
+
let splitAt = remaining.lastIndexOf("\n\n", maxLen);
|
|
84
|
+
if (splitAt < maxLen / 2) splitAt = remaining.lastIndexOf("\n", maxLen);
|
|
85
|
+
if (splitAt < maxLen / 2) splitAt = maxLen;
|
|
86
|
+
chunks.push(remaining.slice(0, splitAt).trim());
|
|
87
|
+
remaining = remaining.slice(splitAt).trim();
|
|
88
|
+
}
|
|
89
|
+
return chunks.filter(Boolean);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Format and split a Markdown message for the given channel.
|
|
94
|
+
* Returns an array of strings to send sequentially.
|
|
95
|
+
*
|
|
96
|
+
* @param text - Markdown text produced by your AI
|
|
97
|
+
* @param channel - Target channel
|
|
98
|
+
* @returns - Array of message chunks, each within the channel's size limit
|
|
99
|
+
*/
|
|
100
|
+
export function formatForChannel(text: string, channel: Channel): string[] {
|
|
101
|
+
switch (channel) {
|
|
102
|
+
case "telegram":
|
|
103
|
+
return splitMessage(toTelegramMarkdown(text), MAX_TELEGRAM_MSG);
|
|
104
|
+
case "slack":
|
|
105
|
+
return splitMessage(toSlackMarkdown(text), MAX_SLACK_MSG);
|
|
106
|
+
case "sms":
|
|
107
|
+
return splitMessage(toPlainText(text), MAX_SMS_MSG);
|
|
108
|
+
case "teams":
|
|
109
|
+
default:
|
|
110
|
+
// Teams supports a Markdown subset similar to standard
|
|
111
|
+
return splitMessage(text, MAX_TEAMS_MSG);
|
|
112
|
+
}
|
|
113
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "NodeNext",
|
|
5
|
+
"moduleResolution": "NodeNext",
|
|
6
|
+
"outDir": "./dist",
|
|
7
|
+
"declaration": true,
|
|
8
|
+
"declarationMap": true,
|
|
9
|
+
"sourceMap": true,
|
|
10
|
+
"strict": true,
|
|
11
|
+
"esModuleInterop": true,
|
|
12
|
+
"skipLibCheck": true
|
|
13
|
+
},
|
|
14
|
+
"include": ["src"]
|
|
15
|
+
}
|