@jhb0430/telegram 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 jhb0430
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,139 @@
1
+ # @jhb0430/telegram
2
+
3
+ 텔레그램 봇으로 **텍스트 한 통**을 보내는 클라이언트입니다.
4
+
5
+ 봇 토큰과 채팅방 ID를 넣고 `sendMessage`를 호출하면 끝입니다. 사진, 답장, 토픽, 로그 릴레이 서버는 없습니다.
6
+
7
+ ## 설치
8
+
9
+ ```bash
10
+ npm install @jhb0430/telegram
11
+ ```
12
+
13
+ NestJS는 선택입니다. Nest를 쓰지 않으면 `@nestjs/common`이 없어도 됩니다.
14
+
15
+ ## 환경 변수
16
+
17
+ 프로세스에 아래 세 값이 있으면 됩니다.
18
+
19
+ | 변수 | 값 |
20
+ |------|-----|
21
+ | `TELEGRAM_ENABLED` | `true` 또는 `1` 이면 전송. `false` / `0` 이면 보내지 않음 |
22
+ | `TELEGRAM_BOT_TOKEN` | BotFather가 준 토큰 |
23
+ | `TELEGRAM_CHAT_ID` | 메시지를 받을 채팅방 ID |
24
+
25
+ 채팅방 ID는 봇에게 `/start`를 보낸 뒤 `https://api.telegram.org/bot<토큰>/getUpdates`의 `message.chat.id`입니다.
26
+
27
+ 프로젝트마다 키 이름이 다르면 `envKeys`로 넘깁니다. 아래 「다른 키 이름」을 보세요.
28
+
29
+ ## 보내기
30
+
31
+ ```typescript
32
+ import { TelegramBotClient } from '@jhb0430/telegram';
33
+
34
+ const telegram = new TelegramBotClient();
35
+
36
+ const result = await telegram.sendMessage('서버가 시작되었습니다.');
37
+ if (result.delivered) {
38
+ // 채팅방에 도착함
39
+ } else {
40
+ // result.reason, result.status 를 로그에 남기면 됩니다.
41
+ }
42
+ ```
43
+
44
+ `sendMessage`는 예외를 던지지 않습니다. 알림 실패가 요청 처리를 막지 않게 하기 위해서입니다.
45
+
46
+ | `result` | 의미 |
47
+ |----------|------|
48
+ | `delivered: true` | Bot API가 성공으로 응답함 |
49
+ | `delivered: false`, `reason: 'not_configured'` | 꺼져 있거나 토큰·채팅방 ID가 없음 |
50
+ | `delivered: false`, `reason: 'http_error'`, `status: 401` | 토큰이 틀리거나 봇이 그 방에 없음 |
51
+ | `delivered: false`, `reason: 'request_failed'` | 타임아웃·네트워크 오류. `status`는 없음 |
52
+
53
+ 토큰 문자열은 결과와 로그에 넣지 않습니다.
54
+
55
+ 설정만 확인할 때는 `telegram.isConfigured()`를 쓰면 됩니다.
56
+
57
+ ## 굵은 글씨
58
+
59
+ 전송은 항상 텔레그램 HTML 모드입니다. 호출자가 넣은 `<b>`, `<i>`, `<code>`, `<pre>`, `<a href="https://...">` 는 태그로 두고, 그 사이 본문의 `<`, `>`, `&` 만 이스케이프합니다.
60
+
61
+ ```typescript
62
+ const text = telegram.formatHtml([
63
+ '🚨 <b>[prod] API 500</b>',
64
+ '<b>GET</b> /health',
65
+ 'detail: a < b & c',
66
+ ]);
67
+ await telegram.sendMessage(text);
68
+ ```
69
+
70
+ 채팅에는 제목과 `GET`이 굵게 나오고, `a < b & c`는 글자로 보입니다. `<script>`처럼 허용되지 않은 태그는 글자 그대로 이스케이프됩니다.
71
+
72
+ 빈 줄과 `null`은 빠집니다.
73
+
74
+ ## 코드로 토큰 넘기기
75
+
76
+ env 대신 파일·설정 객체에 토큰이 있으면 `fileFallback`에 넣습니다. env가 있으면 env가 우선입니다.
77
+
78
+ ```typescript
79
+ const telegram = new TelegramBotClient({
80
+ fileFallback: {
81
+ enabled: true,
82
+ botToken: config.telegram.botToken,
83
+ chatId: config.telegram.chatId,
84
+ },
85
+ });
86
+ ```
87
+
88
+ ## 다른 키 이름
89
+
90
+ 기본 키를 쓰지 않는 프로젝트는 이름을 지정합니다.
91
+
92
+ ```typescript
93
+ const telegram = new TelegramBotClient({
94
+ envKeys: {
95
+ enabled: 'TELEGRAM_DEV_ALERTS_ENABLED',
96
+ botToken: 'TELEGRAM_DEV_BOT_TOKEN',
97
+ chatId: 'TELEGRAM_DEV_CHAT_ID',
98
+ },
99
+ });
100
+ ```
101
+
102
+ ## NestJS
103
+
104
+ ```typescript
105
+ import { Module } from '@nestjs/common';
106
+ import { TelegramModule, TelegramBotService } from '@jhb0430/telegram';
107
+
108
+ @Module({
109
+ imports: [
110
+ TelegramModule.forRoot({
111
+ fileFallback: {
112
+ enabled: true,
113
+ botToken: process.env.TELEGRAM_BOT_TOKEN,
114
+ chatId: process.env.TELEGRAM_CHAT_ID,
115
+ },
116
+ }),
117
+ ],
118
+ })
119
+ export class AppModule {}
120
+
121
+ // 다른 서비스에서 주입
122
+ constructor(private readonly telegram: TelegramBotService) {}
123
+
124
+ await this.telegram.sendMessage('안녕하세요');
125
+ ```
126
+
127
+ `TelegramBotService.sendMessage`의 반환값은 `TelegramBotClient`와 같습니다.
128
+
129
+ ## 부팅 전에 env 파일 읽기
130
+
131
+ `.env` 파일을 프로세스에 올린 뒤 앱을 띄울 때 씁니다. `TELEGRAM_`으로 시작하는 키만 읽습니다.
132
+
133
+ ```bash
134
+ TELEGRAM_ENV_FILE=/path/to/telegram.env node -r @jhb0430/telegram/preload dist/main.js
135
+ ```
136
+
137
+ 이 preload는 `CONFIG_SOURCE` 같은 다른 설정을 바꾸지 않습니다. 파일이 없으면 아무 것도 하지 않습니다.
138
+
139
+ 예시 파일은 패키지 안의 `config/telegram.env.example`입니다.
@@ -0,0 +1,19 @@
1
+ # Telegram Bot API — 공통 env 예시
2
+ # 사용법:
3
+ # cp config/telegram.env.example telegram.dev.env
4
+ # vi telegram.dev.env
5
+ #
6
+ # Nest API 등에서 부트 전 로드:
7
+ # TELEGRAM_ENV_FILE=/path/to/telegram.dev.env node -r @jhb0430/telegram/preload dist/main.js
8
+ #
9
+ # 기본 키는 TELEGRAM_ENABLED / TELEGRAM_BOT_TOKEN / TELEGRAM_CHAT_ID 다.
10
+ # kcpg-wallet 개발팀 알림은 TELEGRAM_DEV_* 를 쓰고, 앱이 envKeys 로 넘긴다.
11
+ #
12
+ # chat_id 확인 (봇에게 /start 보낸 뒤):
13
+ # https://api.telegram.org/bot<TOKEN>/getUpdates → message.chat.id
14
+ #
15
+ # 이 파일은 git에 올리지 마세요. 실제 비밀값은 telegram.dev.env 만 로컬/서버에 둡니다.
16
+
17
+ TELEGRAM_ENABLED=true
18
+ TELEGRAM_BOT_TOKEN=
19
+ TELEGRAM_CHAT_ID=
@@ -0,0 +1,16 @@
1
+ import type { ResolveTelegramConfigOptions, ResolvedTelegramConfig, TelegramLogger, TelegramSendResult } from './types';
2
+ export declare class TelegramBotClient {
3
+ private readonly options;
4
+ private readonly logger;
5
+ constructor(options?: ResolveTelegramConfigOptions, logger?: TelegramLogger);
6
+ getConfig(): ResolvedTelegramConfig;
7
+ isConfigured(): boolean;
8
+ formatHtml(lines: Array<string | undefined | null>): string;
9
+ /**
10
+ * Telegram Bot API `sendMessage`.
11
+ * 예외는 던지지 않고 `delivered`로 알린다. 알림 실패가 요청 처리를 막지 않게 하기 위해서다.
12
+ * 토큰이 틀리면 `delivered: false`와 HTTP status가 돌아간다.
13
+ */
14
+ sendMessage(text: string): Promise<TelegramSendResult>;
15
+ }
16
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,4BAA4B,EAC5B,sBAAsB,EACtB,cAAc,EACd,kBAAkB,EACnB,MAAM,SAAS,CAAC;AASjB,qBAAa,iBAAiB;IAI1B,OAAO,CAAC,QAAQ,CAAC,OAAO;IAH1B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAiB;gBAGrB,OAAO,GAAE,4BAAiC,EAC3D,MAAM,CAAC,EAAE,cAAc;IAKzB,SAAS,IAAI,sBAAsB;IAInC,YAAY,IAAI,OAAO;IAIvB,UAAU,CAAC,KAAK,EAAE,KAAK,CAAC,MAAM,GAAG,SAAS,GAAG,IAAI,CAAC,GAAG,MAAM;IAI3D;;;;OAIG;IACG,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,CAAC;CAsC7D"}
package/dist/client.js ADDED
@@ -0,0 +1,67 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TelegramBotClient = void 0;
4
+ const html_1 = require("./html");
5
+ const config_1 = require("./config");
6
+ const TELEGRAM_MESSAGE_MAX = 4096;
7
+ const TELEGRAM_API_TIMEOUT_MS = 8000;
8
+ const noopLogger = {
9
+ warn: () => undefined,
10
+ };
11
+ class TelegramBotClient {
12
+ constructor(options = {}, logger) {
13
+ this.options = options;
14
+ this.logger = logger ?? noopLogger;
15
+ }
16
+ getConfig() {
17
+ return (0, config_1.resolveTelegramConfig)(this.options);
18
+ }
19
+ isConfigured() {
20
+ return (0, config_1.isTelegramConfigured)(this.getConfig());
21
+ }
22
+ formatHtml(lines) {
23
+ return (0, html_1.formatTelegramHtml)(lines);
24
+ }
25
+ /**
26
+ * Telegram Bot API `sendMessage`.
27
+ * 예외는 던지지 않고 `delivered`로 알린다. 알림 실패가 요청 처리를 막지 않게 하기 위해서다.
28
+ * 토큰이 틀리면 `delivered: false`와 HTTP status가 돌아간다.
29
+ */
30
+ async sendMessage(text) {
31
+ if (!this.isConfigured()) {
32
+ return { delivered: false, reason: 'not_configured' };
33
+ }
34
+ const { botToken, chatId } = this.getConfig();
35
+ const body = (0, html_1.trimTelegramText)(text, TELEGRAM_MESSAGE_MAX);
36
+ const controller = new AbortController();
37
+ const timeout = setTimeout(() => controller.abort(), TELEGRAM_API_TIMEOUT_MS);
38
+ try {
39
+ const res = await fetch(`https://api.telegram.org/bot${botToken}/sendMessage`, {
40
+ method: 'POST',
41
+ headers: { 'Content-Type': 'application/json' },
42
+ body: JSON.stringify({
43
+ chat_id: chatId,
44
+ text: body,
45
+ parse_mode: 'HTML',
46
+ disable_web_page_preview: true,
47
+ }),
48
+ signal: controller.signal,
49
+ });
50
+ if (!res.ok) {
51
+ const raw = await res.text().catch(() => '');
52
+ this.logger.warn(`Telegram sendMessage failed status=${res.status} body=${(0, html_1.trimTelegramText)(raw, 300)}`);
53
+ return { delivered: false, status: res.status, reason: 'http_error' };
54
+ }
55
+ return { delivered: true, status: res.status };
56
+ }
57
+ catch (error) {
58
+ const message = error instanceof Error ? error.message : String(error);
59
+ this.logger.warn(`Telegram sendMessage error: ${message}`);
60
+ return { delivered: false, reason: 'request_failed' };
61
+ }
62
+ finally {
63
+ clearTimeout(timeout);
64
+ }
65
+ }
66
+ }
67
+ exports.TelegramBotClient = TelegramBotClient;
@@ -0,0 +1,7 @@
1
+ import { type ResolveTelegramConfigOptions, type ResolvedTelegramConfig } from './types';
2
+ /**
3
+ * env `TELEGRAM_*` 우선 — `telegram.dev.env` 등으로 켠 경우 YAML `enabled:false` 를 덮는다.
4
+ */
5
+ export declare function resolveTelegramConfig(options?: ResolveTelegramConfigOptions): ResolvedTelegramConfig;
6
+ export declare function isTelegramConfigured(config: ResolvedTelegramConfig): boolean;
7
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,4BAA4B,EACjC,KAAK,sBAAsB,EAC5B,MAAM,SAAS,CAAC;AASjB;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,GAAE,4BAAiC,GAAG,sBAAsB,CAWxG;AAED,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,sBAAsB,GAAG,OAAO,CAG5E"}
package/dist/config.js ADDED
@@ -0,0 +1,30 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.resolveTelegramConfig = resolveTelegramConfig;
4
+ exports.isTelegramConfigured = isTelegramConfigured;
5
+ const types_1 = require("./types");
6
+ function parseEnabledFlag(raw) {
7
+ const envFlag = String(raw ?? '').trim().toLowerCase();
8
+ if (envFlag === 'true' || envFlag === '1')
9
+ return true;
10
+ if (envFlag === 'false' || envFlag === '0')
11
+ return false;
12
+ return undefined;
13
+ }
14
+ /**
15
+ * env `TELEGRAM_*` 우선 — `telegram.dev.env` 등으로 켠 경우 YAML `enabled:false` 를 덮는다.
16
+ */
17
+ function resolveTelegramConfig(options = {}) {
18
+ const keys = options.envKeys ?? types_1.DEFAULT_TELEGRAM_ENV_KEYS;
19
+ const fallback = options.fileFallback ?? {};
20
+ const envEnabled = parseEnabledFlag(process.env[keys.enabled]);
21
+ const enabled = envEnabled ?? fallback.enabled === true;
22
+ const botToken = String(process.env[keys.botToken] ?? fallback.botToken ?? '').trim();
23
+ const chatId = String(process.env[keys.chatId] ?? fallback.chatId ?? '').trim();
24
+ return { enabled, botToken, chatId };
25
+ }
26
+ function isTelegramConfigured(config) {
27
+ if (!config.enabled)
28
+ return false;
29
+ return config.botToken.length > 0 && config.chatId.length > 0;
30
+ }
@@ -0,0 +1,7 @@
1
+ import type { LoadEnvFileOptions } from './types';
2
+ /**
3
+ * `.env` 형식 파일을 process.env 에 올린다.
4
+ * Nest 부트스트랩 전 preload·YAML sync 이후 재적용 모두 동일 함수로 처리한다.
5
+ */
6
+ export declare function loadEnvFileIntoProcess(filePath: string, options?: LoadEnvFileOptions): number;
7
+ //# sourceMappingURL=env-file.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"env-file.d.ts","sourceRoot":"","sources":["../src/env-file.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAalD;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,GAAE,kBAAuB,GAAG,MAAM,CA0BjG"}
@@ -0,0 +1,40 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.loadEnvFileIntoProcess = loadEnvFileIntoProcess;
4
+ const node_fs_1 = require("node:fs");
5
+ function stripQuotes(value) {
6
+ const trimmed = value.trim();
7
+ if ((trimmed.startsWith('"') && trimmed.endsWith('"'))
8
+ || (trimmed.startsWith("'") && trimmed.endsWith("'"))) {
9
+ return trimmed.slice(1, -1);
10
+ }
11
+ return trimmed;
12
+ }
13
+ /**
14
+ * `.env` 형식 파일을 process.env 에 올린다.
15
+ * Nest 부트스트랩 전 preload·YAML sync 이후 재적용 모두 동일 함수로 처리한다.
16
+ */
17
+ function loadEnvFileIntoProcess(filePath, options = {}) {
18
+ const keyPrefix = options.keyPrefix ?? 'TELEGRAM_';
19
+ if (!(0, node_fs_1.existsSync)(filePath))
20
+ return 0;
21
+ const text = (0, node_fs_1.readFileSync)(filePath, 'utf8');
22
+ let loaded = 0;
23
+ for (const rawLine of text.split('\n')) {
24
+ const line = rawLine.trim();
25
+ if (!line || line.startsWith('#'))
26
+ continue;
27
+ const eq = line.indexOf('=');
28
+ if (eq <= 0)
29
+ continue;
30
+ const key = line.slice(0, eq).trim();
31
+ if (!key.startsWith(keyPrefix))
32
+ continue;
33
+ process.env[key] = stripQuotes(line.slice(eq + 1));
34
+ loaded += 1;
35
+ }
36
+ if (loaded > 0 && options.setConfigSourceOnLoad && !process.env.CONFIG_SOURCE) {
37
+ process.env.CONFIG_SOURCE = 'yamlWithEnvOverrides';
38
+ }
39
+ return loaded;
40
+ }
package/dist/html.d.ts ADDED
@@ -0,0 +1,6 @@
1
+ export declare function escapeTelegramHtml(value: string): string;
2
+ export declare function escapeTelegramHtmlPreservingTags(value: string): string;
3
+ export declare function trimTelegramText(value: string, max: number): string;
4
+ /** HTML parse_mode 용 — 빈 줄은 버리고, 허용 태그는 유지한 채 본문만 escape 한다. */
5
+ export declare function formatTelegramHtml(lines: Array<string | undefined | null>): string;
6
+ //# sourceMappingURL=html.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"html.d.ts","sourceRoot":"","sources":["../src/html.ts"],"names":[],"mappings":"AAAA,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAKxD;AASD,wBAAgB,gCAAgC,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAYtE;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAInE;AAED,gEAAgE;AAChE,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,KAAK,CAAC,MAAM,GAAG,SAAS,GAAG,IAAI,CAAC,GAAG,MAAM,CAKlF"}
package/dist/html.js ADDED
@@ -0,0 +1,43 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.escapeTelegramHtml = escapeTelegramHtml;
4
+ exports.escapeTelegramHtmlPreservingTags = escapeTelegramHtmlPreservingTags;
5
+ exports.trimTelegramText = trimTelegramText;
6
+ exports.formatTelegramHtml = formatTelegramHtml;
7
+ function escapeTelegramHtml(value) {
8
+ return value
9
+ .replace(/&/g, '&amp;')
10
+ .replace(/</g, '&lt;')
11
+ .replace(/>/g, '&gt;');
12
+ }
13
+ /**
14
+ * Bot API HTML에서 허용하는 태그만 그대로 두고, 그 사이 본문은 escape 한다.
15
+ * `<b>` 같은 호출자 마크업은 유지하고, `<script>`·본문의 `<`는 이스케이프한다.
16
+ */
17
+ const TELEGRAM_HTML_TAG = /<\/?(?:b|strong|i|em|u|ins|s|strike|del|code|pre|blockquote|tg-spoiler)>|<a\s+href="(?:https?:\/\/|tg:\/\/)[^"]*">|<\/a>|<span\s+class="tg-spoiler">|<\/span>/gi;
18
+ function escapeTelegramHtmlPreservingTags(value) {
19
+ const tags = new RegExp(TELEGRAM_HTML_TAG.source, 'gi');
20
+ let out = '';
21
+ let last = 0;
22
+ for (const match of value.matchAll(tags)) {
23
+ const index = match.index ?? 0;
24
+ out += escapeTelegramHtml(value.slice(last, index));
25
+ out += match[0];
26
+ last = index + match[0].length;
27
+ }
28
+ out += escapeTelegramHtml(value.slice(last));
29
+ return out;
30
+ }
31
+ function trimTelegramText(value, max) {
32
+ const trimmed = value.trim();
33
+ if (trimmed.length <= max)
34
+ return trimmed;
35
+ return `${trimmed.slice(0, Math.max(0, max - 3))}...`;
36
+ }
37
+ /** HTML parse_mode 용 — 빈 줄은 버리고, 허용 태그는 유지한 채 본문만 escape 한다. */
38
+ function formatTelegramHtml(lines) {
39
+ return lines
40
+ .filter((line) => typeof line === 'string' && line.trim().length > 0)
41
+ .map((line) => escapeTelegramHtmlPreservingTags(line))
42
+ .join('\n');
43
+ }
@@ -0,0 +1,7 @@
1
+ export { TelegramBotClient } from './client';
2
+ export { resolveTelegramConfig, isTelegramConfigured } from './config';
3
+ export { loadEnvFileIntoProcess } from './env-file';
4
+ export { escapeTelegramHtml, escapeTelegramHtmlPreservingTags, trimTelegramText, formatTelegramHtml } from './html';
5
+ export { DEFAULT_TELEGRAM_ENV_KEYS, type LoadEnvFileOptions, type ResolveTelegramConfigOptions, type ResolvedTelegramConfig, type TelegramBotFileConfig, type TelegramEnvKeyNames, type TelegramLogger, type TelegramSendResult, } from './types';
6
+ export { TelegramBotService, TelegramModule, TELEGRAM_BOT_OPTIONS, type TelegramBotModuleOptions, } from './nest';
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AAC7C,OAAO,EAAE,qBAAqB,EAAE,oBAAoB,EAAE,MAAM,UAAU,CAAC;AACvE,OAAO,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AACpD,OAAO,EAAE,kBAAkB,EAAE,gCAAgC,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,QAAQ,CAAC;AACpH,OAAO,EACL,yBAAyB,EACzB,KAAK,kBAAkB,EACvB,KAAK,4BAA4B,EACjC,KAAK,sBAAsB,EAC3B,KAAK,qBAAqB,EAC1B,KAAK,mBAAmB,EACxB,KAAK,cAAc,EACnB,KAAK,kBAAkB,GACxB,MAAM,SAAS,CAAC;AACjB,OAAO,EACL,kBAAkB,EAClB,cAAc,EACd,oBAAoB,EACpB,KAAK,wBAAwB,GAC9B,MAAM,QAAQ,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TELEGRAM_BOT_OPTIONS = exports.TelegramModule = exports.TelegramBotService = exports.DEFAULT_TELEGRAM_ENV_KEYS = exports.formatTelegramHtml = exports.trimTelegramText = exports.escapeTelegramHtmlPreservingTags = exports.escapeTelegramHtml = exports.loadEnvFileIntoProcess = exports.isTelegramConfigured = exports.resolveTelegramConfig = exports.TelegramBotClient = void 0;
4
+ var client_1 = require("./client");
5
+ Object.defineProperty(exports, "TelegramBotClient", { enumerable: true, get: function () { return client_1.TelegramBotClient; } });
6
+ var config_1 = require("./config");
7
+ Object.defineProperty(exports, "resolveTelegramConfig", { enumerable: true, get: function () { return config_1.resolveTelegramConfig; } });
8
+ Object.defineProperty(exports, "isTelegramConfigured", { enumerable: true, get: function () { return config_1.isTelegramConfigured; } });
9
+ var env_file_1 = require("./env-file");
10
+ Object.defineProperty(exports, "loadEnvFileIntoProcess", { enumerable: true, get: function () { return env_file_1.loadEnvFileIntoProcess; } });
11
+ var html_1 = require("./html");
12
+ Object.defineProperty(exports, "escapeTelegramHtml", { enumerable: true, get: function () { return html_1.escapeTelegramHtml; } });
13
+ Object.defineProperty(exports, "escapeTelegramHtmlPreservingTags", { enumerable: true, get: function () { return html_1.escapeTelegramHtmlPreservingTags; } });
14
+ Object.defineProperty(exports, "trimTelegramText", { enumerable: true, get: function () { return html_1.trimTelegramText; } });
15
+ Object.defineProperty(exports, "formatTelegramHtml", { enumerable: true, get: function () { return html_1.formatTelegramHtml; } });
16
+ var types_1 = require("./types");
17
+ Object.defineProperty(exports, "DEFAULT_TELEGRAM_ENV_KEYS", { enumerable: true, get: function () { return types_1.DEFAULT_TELEGRAM_ENV_KEYS; } });
18
+ var nest_1 = require("./nest");
19
+ Object.defineProperty(exports, "TelegramBotService", { enumerable: true, get: function () { return nest_1.TelegramBotService; } });
20
+ Object.defineProperty(exports, "TelegramModule", { enumerable: true, get: function () { return nest_1.TelegramModule; } });
21
+ Object.defineProperty(exports, "TELEGRAM_BOT_OPTIONS", { enumerable: true, get: function () { return nest_1.TELEGRAM_BOT_OPTIONS; } });
@@ -0,0 +1,4 @@
1
+ export { TelegramBotService } from './telegram-bot.service';
2
+ export { TelegramModule } from './telegram.module';
3
+ export { TELEGRAM_BOT_OPTIONS, type TelegramBotModuleOptions } from './telegram.tokens';
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/nest/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAC5D,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACnD,OAAO,EAAE,oBAAoB,EAAE,KAAK,wBAAwB,EAAE,MAAM,mBAAmB,CAAC"}
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TELEGRAM_BOT_OPTIONS = exports.TelegramModule = exports.TelegramBotService = void 0;
4
+ var telegram_bot_service_1 = require("./telegram-bot.service");
5
+ Object.defineProperty(exports, "TelegramBotService", { enumerable: true, get: function () { return telegram_bot_service_1.TelegramBotService; } });
6
+ var telegram_module_1 = require("./telegram.module");
7
+ Object.defineProperty(exports, "TelegramModule", { enumerable: true, get: function () { return telegram_module_1.TelegramModule; } });
8
+ var telegram_tokens_1 = require("./telegram.tokens");
9
+ Object.defineProperty(exports, "TELEGRAM_BOT_OPTIONS", { enumerable: true, get: function () { return telegram_tokens_1.TELEGRAM_BOT_OPTIONS; } });
@@ -0,0 +1,10 @@
1
+ import type { ResolveTelegramConfigOptions, TelegramSendResult } from '../types';
2
+ export declare class TelegramBotService {
3
+ private readonly client;
4
+ private readonly logger;
5
+ constructor(options?: ResolveTelegramConfigOptions);
6
+ isConfigured(): boolean;
7
+ formatHtml(lines: Array<string | undefined | null>): string;
8
+ sendMessage(text: string): Promise<TelegramSendResult>;
9
+ }
10
+ //# sourceMappingURL=telegram-bot.service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"telegram-bot.service.d.ts","sourceRoot":"","sources":["../../src/nest/telegram-bot.service.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,4BAA4B,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AAGjF,qBACa,kBAAkB;IAC7B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAoB;IAC3C,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAuC;gBAGlB,OAAO,CAAC,EAAE,4BAA4B;IAKlF,YAAY,IAAI,OAAO;IAIvB,UAAU,CAAC,KAAK,EAAE,KAAK,CAAC,MAAM,GAAG,SAAS,GAAG,IAAI,CAAC,GAAG,MAAM;IAIrD,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,CAAC;CAG7D"}
@@ -0,0 +1,41 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
11
+ var __param = (this && this.__param) || function (paramIndex, decorator) {
12
+ return function (target, key) { decorator(target, key, paramIndex); }
13
+ };
14
+ var TelegramBotService_1;
15
+ Object.defineProperty(exports, "__esModule", { value: true });
16
+ exports.TelegramBotService = void 0;
17
+ const common_1 = require("@nestjs/common");
18
+ const client_1 = require("../client");
19
+ const telegram_tokens_1 = require("./telegram.tokens");
20
+ let TelegramBotService = TelegramBotService_1 = class TelegramBotService {
21
+ constructor(options) {
22
+ this.logger = new common_1.Logger(TelegramBotService_1.name);
23
+ this.client = new client_1.TelegramBotClient(options ?? {}, this.logger);
24
+ }
25
+ isConfigured() {
26
+ return this.client.isConfigured();
27
+ }
28
+ formatHtml(lines) {
29
+ return this.client.formatHtml(lines);
30
+ }
31
+ async sendMessage(text) {
32
+ return this.client.sendMessage(text);
33
+ }
34
+ };
35
+ exports.TelegramBotService = TelegramBotService;
36
+ exports.TelegramBotService = TelegramBotService = TelegramBotService_1 = __decorate([
37
+ (0, common_1.Injectable)(),
38
+ __param(0, (0, common_1.Optional)()),
39
+ __param(0, (0, common_1.Inject)(telegram_tokens_1.TELEGRAM_BOT_OPTIONS)),
40
+ __metadata("design:paramtypes", [Object])
41
+ ], TelegramBotService);
@@ -0,0 +1,6 @@
1
+ import { DynamicModule } from '@nestjs/common';
2
+ import { type TelegramBotModuleOptions } from './telegram.tokens';
3
+ export declare class TelegramModule {
4
+ static forRoot(options?: TelegramBotModuleOptions): DynamicModule;
5
+ }
6
+ //# sourceMappingURL=telegram.module.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"telegram.module.d.ts","sourceRoot":"","sources":["../../src/nest/telegram.module.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAU,MAAM,gBAAgB,CAAC;AAGvD,OAAO,EAAwB,KAAK,wBAAwB,EAAE,MAAM,mBAAmB,CAAC;AAExF,qBACa,cAAc;IACzB,MAAM,CAAC,OAAO,CAAC,OAAO,GAAE,wBAA6B,GAAG,aAAa;CAatE"}
@@ -0,0 +1,32 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var TelegramModule_1;
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.TelegramModule = void 0;
11
+ const common_1 = require("@nestjs/common");
12
+ const telegram_bot_service_1 = require("./telegram-bot.service");
13
+ const telegram_tokens_1 = require("./telegram.tokens");
14
+ let TelegramModule = TelegramModule_1 = class TelegramModule {
15
+ static forRoot(options = {}) {
16
+ return {
17
+ module: TelegramModule_1,
18
+ providers: [
19
+ {
20
+ provide: telegram_tokens_1.TELEGRAM_BOT_OPTIONS,
21
+ useValue: options,
22
+ },
23
+ telegram_bot_service_1.TelegramBotService,
24
+ ],
25
+ exports: [telegram_bot_service_1.TelegramBotService],
26
+ };
27
+ }
28
+ };
29
+ exports.TelegramModule = TelegramModule;
30
+ exports.TelegramModule = TelegramModule = TelegramModule_1 = __decorate([
31
+ (0, common_1.Module)({})
32
+ ], TelegramModule);
@@ -0,0 +1,4 @@
1
+ import type { ResolveTelegramConfigOptions } from '../types';
2
+ export declare const TELEGRAM_BOT_OPTIONS: unique symbol;
3
+ export type TelegramBotModuleOptions = ResolveTelegramConfigOptions;
4
+ //# sourceMappingURL=telegram.tokens.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"telegram.tokens.d.ts","sourceRoot":"","sources":["../../src/nest/telegram.tokens.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,4BAA4B,EAAE,MAAM,UAAU,CAAC;AAE7D,eAAO,MAAM,oBAAoB,eAAiC,CAAC;AAEnE,MAAM,MAAM,wBAAwB,GAAG,4BAA4B,CAAC"}
@@ -0,0 +1,4 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TELEGRAM_BOT_OPTIONS = void 0;
4
+ exports.TELEGRAM_BOT_OPTIONS = Symbol('TELEGRAM_BOT_OPTIONS');
@@ -0,0 +1,43 @@
1
+ /** env·YAML 등에서 읽은 Bot 설정 (프로젝트별 fallback) */
2
+ export type TelegramBotFileConfig = {
3
+ enabled?: boolean;
4
+ botToken?: string;
5
+ chatId?: string;
6
+ };
7
+ export type TelegramEnvKeyNames = {
8
+ enabled: string;
9
+ botToken: string;
10
+ chatId: string;
11
+ };
12
+ /** 패키지 기본 env. 프로젝트별 키는 `envKeys`로 넘긴다. */
13
+ export declare const DEFAULT_TELEGRAM_ENV_KEYS: TelegramEnvKeyNames;
14
+ export type TelegramSendResult = {
15
+ delivered: boolean;
16
+ /** Bot API가 응답한 HTTP status. 네트워크 실패면 없다. */
17
+ status?: number;
18
+ /** `not_configured` | `http_error` | `request_failed`. 토큰은 넣지 않는다. */
19
+ reason?: 'not_configured' | 'http_error' | 'request_failed';
20
+ };
21
+ export type ResolveTelegramConfigOptions = {
22
+ /** process.env 키 이름. 기본은 `TELEGRAM_ENABLED` / `TELEGRAM_BOT_TOKEN` / `TELEGRAM_CHAT_ID`. */
23
+ envKeys?: TelegramEnvKeyNames;
24
+ /** YAML 등 파일 기반 fallback */
25
+ fileFallback?: TelegramBotFileConfig;
26
+ };
27
+ export type ResolvedTelegramConfig = {
28
+ enabled: boolean;
29
+ botToken: string;
30
+ chatId: string;
31
+ };
32
+ export type TelegramLogger = {
33
+ warn(message: string): void;
34
+ log?(message: string): void;
35
+ debug?(message: string): void;
36
+ };
37
+ export type LoadEnvFileOptions = {
38
+ /** 이 prefix 로 시작하는 키만 process.env 에 올린다 */
39
+ keyPrefix?: string;
40
+ /** 로드된 키가 있고 CONFIG_SOURCE 가 비어 있으면 yamlWithEnvOverrides 로 설정 */
41
+ setConfigSourceOnLoad?: boolean;
42
+ };
43
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,8CAA8C;AAC9C,MAAM,MAAM,qBAAqB,GAAG;IAClC,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,2CAA2C;AAC3C,eAAO,MAAM,yBAAyB,EAAE,mBAIvC,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,SAAS,EAAE,OAAO,CAAC;IACnB,6CAA6C;IAC7C,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,sEAAsE;IACtE,MAAM,CAAC,EAAE,gBAAgB,GAAG,YAAY,GAAG,gBAAgB,CAAC;CAC7D,CAAC;AAEF,MAAM,MAAM,4BAA4B,GAAG;IACzC,4FAA4F;IAC5F,OAAO,CAAC,EAAE,mBAAmB,CAAC;IAC9B,4BAA4B;IAC5B,YAAY,CAAC,EAAE,qBAAqB,CAAC;CACtC,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAAG;IACnC,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG;IAC3B,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,GAAG,CAAC,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,KAAK,CAAC,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CAC/B,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,2CAA2C;IAC3C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,iEAAiE;IACjE,qBAAqB,CAAC,EAAE,OAAO,CAAC;CACjC,CAAC"}
package/dist/types.js ADDED
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DEFAULT_TELEGRAM_ENV_KEYS = void 0;
4
+ /** 패키지 기본 env. 프로젝트별 키는 `envKeys`로 넘긴다. */
5
+ exports.DEFAULT_TELEGRAM_ENV_KEYS = {
6
+ enabled: 'TELEGRAM_ENABLED',
7
+ botToken: 'TELEGRAM_BOT_TOKEN',
8
+ chatId: 'TELEGRAM_CHAT_ID',
9
+ };
package/package.json ADDED
@@ -0,0 +1,48 @@
1
+ {
2
+ "name": "@jhb0430/telegram",
3
+ "version": "0.1.0",
4
+ "description": "Telegram Bot API client — reusable across projects",
5
+ "license": "MIT",
6
+ "publishConfig": {
7
+ "access": "public"
8
+ },
9
+ "type": "commonjs",
10
+ "main": "dist/index.js",
11
+ "types": "dist/index.d.ts",
12
+ "files": [
13
+ "dist",
14
+ "scripts",
15
+ "config"
16
+ ],
17
+ "exports": {
18
+ ".": {
19
+ "types": "./dist/index.d.ts",
20
+ "require": "./dist/index.js",
21
+ "default": "./dist/index.js"
22
+ },
23
+ "./nest": {
24
+ "types": "./dist/nest/index.d.ts",
25
+ "require": "./dist/nest/index.js",
26
+ "default": "./dist/nest/index.js"
27
+ },
28
+ "./preload": "./scripts/preload-env.cjs"
29
+ },
30
+ "scripts": {
31
+ "build": "tsc -p tsconfig.build.json",
32
+ "clean": "rm -rf dist",
33
+ "test": "yarn build && node --test test/*.test.cjs"
34
+ },
35
+ "peerDependencies": {
36
+ "@nestjs/common": "^10.0.0"
37
+ },
38
+ "peerDependenciesMeta": {
39
+ "@nestjs/common": {
40
+ "optional": true
41
+ }
42
+ },
43
+ "devDependencies": {
44
+ "@nestjs/common": "^10.0.0",
45
+ "@types/node": "^20.0.0",
46
+ "typescript": "~5.9.2"
47
+ }
48
+ }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * 프로세스 부트스트랩 전에 Telegram env 파일을 process.env 에 올린다.
3
+ *
4
+ * 사용:
5
+ * node -r @jhb0430/telegram/preload dist/main.js
6
+ * TELEGRAM_ENV_FILE=/path/to/telegram.dev.env node -r @jhb0430/telegram/preload dist/main.js
7
+ */
8
+ const path = require('node:path');
9
+
10
+ const { loadEnvFileIntoProcess } = require('../dist/env-file');
11
+
12
+ const envFile =
13
+ process.env.TELEGRAM_ENV_FILE
14
+ || path.resolve(process.cwd(), 'telegram.dev.env');
15
+
16
+ loadEnvFileIntoProcess(envFile, {
17
+ keyPrefix: process.env.TELEGRAM_ENV_KEY_PREFIX || 'TELEGRAM_',
18
+ });