@es-labs/jslib 0.0.1

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.
Files changed (72) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/README.md +42 -0
  3. package/__test__/services.test.js +32 -0
  4. package/auth/index.js +226 -0
  5. package/auth/keyv.js +23 -0
  6. package/auth/knex.js +29 -0
  7. package/auth/redis.js +23 -0
  8. package/comms/email.js +123 -0
  9. package/comms/nexmo.js +44 -0
  10. package/comms/telegram.js +43 -0
  11. package/comms/telegram2/inbound.js +314 -0
  12. package/comms/telegram2/outbound.js +574 -0
  13. package/comms/webpush.js +60 -0
  14. package/config.js +37 -0
  15. package/express/controller/auth/oauth.js +39 -0
  16. package/express/controller/auth/oidc.js +87 -0
  17. package/express/controller/auth/own.js +100 -0
  18. package/express/controller/auth/saml.js +74 -0
  19. package/express/upload.js +48 -0
  20. package/index.js +1 -0
  21. package/iso/README.md +4 -0
  22. package/iso/__tests__/csv-utils.spec.js +128 -0
  23. package/iso/__tests__/datetime.spec.js +101 -0
  24. package/iso/__tests__/fetch.spec.js +270 -0
  25. package/iso/csv-utils.js +206 -0
  26. package/iso/datetime.js +103 -0
  27. package/iso/fetch.js +129 -0
  28. package/iso/fetch2.js +180 -0
  29. package/iso/log-filter.js +17 -0
  30. package/iso/sleep.js +6 -0
  31. package/iso/ws.js +63 -0
  32. package/node/oss-files/oss-uploader-client-fetch.js +258 -0
  33. package/node/oss-files/oss-uploader-client-fetch.md +31 -0
  34. package/node/oss-files/oss-uploader-client.js +219 -0
  35. package/node/oss-files/oss-uploader-server.js +199 -0
  36. package/node/oss-files/oss-uploader-usage.js +121 -0
  37. package/node/oss-files/oss-uploader-usage.md +34 -0
  38. package/node/oss-files/s3-uploader-client.js +217 -0
  39. package/node/oss-files/s3-uploader-server.js +123 -0
  40. package/node/oss-files/s3-uploader-usage.js +77 -0
  41. package/node/oss-files/s3-uploader-usage.md +34 -0
  42. package/package.json +53 -0
  43. package/packageInfo.js +9 -0
  44. package/services/ali.js +279 -0
  45. package/services/aws.js +194 -0
  46. package/services/db/__tests__/keyv.spec.js +31 -0
  47. package/services/db/keyv.js +14 -0
  48. package/services/db/knex.js +67 -0
  49. package/services/db/redis.js +51 -0
  50. package/services/index.js +57 -0
  51. package/services/mq/README.md +8 -0
  52. package/services/websocket.js +139 -0
  53. package/t4t/README.md +1 -0
  54. package/traps.js +20 -0
  55. package/utils/__tests__/aes.spec.js +52 -0
  56. package/utils/aes.js +23 -0
  57. package/web/UI.md +71 -0
  58. package/web/bwc-autocomplete.js +211 -0
  59. package/web/bwc-combobox.js +343 -0
  60. package/web/bwc-fileupload.js +87 -0
  61. package/web/bwc-loading-overlay.js +54 -0
  62. package/web/bwc-t4t-form.js +511 -0
  63. package/web/bwc-table.js +756 -0
  64. package/web/fetch.js +129 -0
  65. package/web/i18n.js +24 -0
  66. package/web/idle.js +49 -0
  67. package/web/parse-jwt.js +15 -0
  68. package/web/pwa.js +84 -0
  69. package/web/sign-pad.js +164 -0
  70. package/web/t4t-fe.js +164 -0
  71. package/web/util.js +126 -0
  72. package/web/web-cam.js +182 -0
@@ -0,0 +1,574 @@
1
+ // telegram-sender.js
2
+ import FormData from "form-data";
3
+ import fetch from "node-fetch";
4
+ import fs from "fs";
5
+ import path from "path";
6
+
7
+ const BASE_URL = (token) => `https://api.telegram.org/bot${token}`;
8
+
9
+ // ─── Core Request Helper ──────────────────────────────────────────────────────
10
+
11
+ async function apiRequest(token, method, params = {}, formData = null) {
12
+ const url = `${BASE_URL(token)}/${method}`;
13
+
14
+ const options = formData
15
+ ? { method: "POST", body: formData }
16
+ : {
17
+ method: "POST",
18
+ headers: { "Content-Type": "application/json" },
19
+ body: JSON.stringify(params),
20
+ };
21
+
22
+ const res = await fetch(url, options);
23
+ const data = await res.json();
24
+
25
+ if (!data.ok) {
26
+ throw new TelegramError(data.description, data.error_code, method);
27
+ }
28
+
29
+ return data.result;
30
+ }
31
+
32
+ class TelegramError extends Error {
33
+ constructor(message, code, method) {
34
+ super(`[${method}] Telegram API error ${code}: ${message}`);
35
+ this.code = code;
36
+ this.method = method;
37
+ }
38
+ }
39
+
40
+ // ─── File Helper ──────────────────────────────────────────────────────────────
41
+
42
+ /**
43
+ * Resolves a file input to the right Telegram format:
44
+ * - "file_id:..." → use existing Telegram file ID
45
+ * - "http(s)://..." → use URL
46
+ * - Everything else → treat as local path, attach as multipart
47
+ */
48
+ function resolveFile(fd, fieldName, input) {
49
+ if (!input) return null;
50
+ if (input.startsWith("file_id:")) return input.replace("file_id:", "");
51
+ if (input.startsWith("http://") || input.startsWith("https://")) return input;
52
+
53
+ // Local file – attach to FormData
54
+ const stream = fs.createReadStream(input);
55
+ fd.append(fieldName, stream, path.basename(input));
56
+ return null; // signal: already added to form
57
+ }
58
+
59
+ // ─── Keyboard Builders ────────────────────────────────────────────────────────
60
+
61
+ /**
62
+ * Inline keyboard attached to the message.
63
+ *
64
+ * @example
65
+ * inlineKeyboard([
66
+ * [{ text: "Visit", url: "https://example.com" }],
67
+ * [{ text: "Callback", callback_data: "btn_1" }, { text: "Pay", pay: true }]
68
+ * ])
69
+ */
70
+ export function inlineKeyboard(rows) {
71
+ return JSON.stringify({ inline_keyboard: rows });
72
+ }
73
+
74
+ /**
75
+ * Custom reply keyboard shown to the user.
76
+ *
77
+ * @example
78
+ * replyKeyboard([["Yes", "No"], ["Cancel"]], { resize_keyboard: true })
79
+ */
80
+ export function replyKeyboard(
81
+ rows,
82
+ { resize_keyboard = true, one_time_keyboard = false, is_persistent = false, selective = false } = {}
83
+ ) {
84
+ return JSON.stringify({
85
+ keyboard: rows.map((row) =>
86
+ row.map((btn) => (typeof btn === "string" ? { text: btn } : btn))
87
+ ),
88
+ resize_keyboard,
89
+ one_time_keyboard,
90
+ is_persistent,
91
+ selective,
92
+ });
93
+ }
94
+
95
+ /** Removes any custom keyboard from the user's view. */
96
+ export function removeKeyboard(selective = false) {
97
+ return JSON.stringify({ remove_keyboard: true, selective });
98
+ }
99
+
100
+ /** Forces a reply prompt on the user's client. */
101
+ export function forceReply(input_field_placeholder = "", selective = false) {
102
+ return JSON.stringify({ force_reply: true, input_field_placeholder, selective });
103
+ }
104
+
105
+ // ─── Shared Message Options ───────────────────────────────────────────────────
106
+
107
+ function commonOpts({
108
+ parse_mode, // "HTML" | "Markdown" | "MarkdownV2"
109
+ caption,
110
+ caption_parse_mode,
111
+ reply_to_message_id,
112
+ allow_sending_without_reply,
113
+ reply_markup, // use inlineKeyboard() / replyKeyboard() helpers
114
+ disable_notification,
115
+ protect_content,
116
+ message_thread_id, // forum thread id
117
+ business_connection_id,
118
+ } = {}) {
119
+ return Object.fromEntries(
120
+ Object.entries({
121
+ parse_mode, caption, caption_parse_mode,
122
+ reply_to_message_id, allow_sending_without_reply,
123
+ reply_markup, disable_notification, protect_content,
124
+ message_thread_id, business_connection_id,
125
+ }).filter(([, v]) => v !== undefined)
126
+ );
127
+ }
128
+
129
+ // ─── Text ─────────────────────────────────────────────────────────────────────
130
+
131
+ /**
132
+ * Send a plain or formatted text message.
133
+ *
134
+ * @param {string} token
135
+ * @param {number|string} chatId
136
+ * @param {string} text
137
+ * @param {object} opts
138
+ * @param {string} [opts.parse_mode] – "HTML" | "Markdown" | "MarkdownV2"
139
+ * @param {Array} [opts.entities] – pre-built MessageEntity array
140
+ * @param {boolean} [opts.disable_web_page_preview]
141
+ * @param {string} [opts.reply_markup] – use inlineKeyboard() etc.
142
+ */
143
+ export async function sendMessage(token, chatId, text, opts = {}) {
144
+ return apiRequest(token, "sendMessage", {
145
+ chat_id: chatId,
146
+ text,
147
+ disable_web_page_preview: opts.disable_web_page_preview,
148
+ entities: opts.entities,
149
+ ...commonOpts(opts),
150
+ });
151
+ }
152
+
153
+ // ─── Photo ────────────────────────────────────────────────────────────────────
154
+
155
+ /**
156
+ * @param {string} photo – local path, HTTPS URL, or "file_id:<id>"
157
+ * @param {boolean} [opts.has_spoiler]
158
+ */
159
+ export async function sendPhoto(token, chatId, photo, opts = {}) {
160
+ const fd = new FormData();
161
+ fd.append("chat_id", String(chatId));
162
+ if (opts.caption) fd.append("caption", opts.caption);
163
+ if (opts.parse_mode) fd.append("parse_mode", opts.parse_mode);
164
+ if (opts.has_spoiler) fd.append("has_spoiler", "true");
165
+ if (opts.reply_markup) fd.append("reply_markup", opts.reply_markup);
166
+ if (opts.reply_to_message_id) fd.append("reply_to_message_id", String(opts.reply_to_message_id));
167
+ if (opts.disable_notification) fd.append("disable_notification", "true");
168
+ if (opts.protect_content) fd.append("protect_content", "true");
169
+
170
+ const resolved = resolveFile(fd, "photo", photo);
171
+ if (resolved) fd.append("photo", resolved);
172
+
173
+ return apiRequest(token, "sendPhoto", {}, fd);
174
+ }
175
+
176
+ // ─── Video ────────────────────────────────────────────────────────────────────
177
+
178
+ /**
179
+ * @param {string} video – local path, URL, or file_id
180
+ * @param {object} opts
181
+ * @param {number} [opts.duration]
182
+ * @param {number} [opts.width]
183
+ * @param {number} [opts.height]
184
+ * @param {string} [opts.thumbnail] – local path, URL, or file_id for cover image
185
+ * @param {boolean} [opts.supports_streaming]
186
+ * @param {boolean} [opts.has_spoiler]
187
+ */
188
+ export async function sendVideo(token, chatId, video, opts = {}) {
189
+ const fd = new FormData();
190
+ fd.append("chat_id", String(chatId));
191
+ if (opts.duration) fd.append("duration", String(opts.duration));
192
+ if (opts.width) fd.append("width", String(opts.width));
193
+ if (opts.height) fd.append("height", String(opts.height));
194
+ if (opts.supports_streaming) fd.append("supports_streaming", "true");
195
+ if (opts.has_spoiler) fd.append("has_spoiler", "true");
196
+ if (opts.caption) fd.append("caption", opts.caption);
197
+ if (opts.parse_mode) fd.append("parse_mode", opts.parse_mode);
198
+ if (opts.reply_markup) fd.append("reply_markup", opts.reply_markup);
199
+
200
+ const resolvedVideo = resolveFile(fd, "video", video);
201
+ if (resolvedVideo) fd.append("video", resolvedVideo);
202
+
203
+ if (opts.thumbnail) {
204
+ const resolvedThumb = resolveFile(fd, "thumbnail", opts.thumbnail);
205
+ if (resolvedThumb) fd.append("thumbnail", resolvedThumb);
206
+ }
207
+
208
+ return apiRequest(token, "sendVideo", {}, fd);
209
+ }
210
+
211
+ // ─── Audio ────────────────────────────────────────────────────────────────────
212
+
213
+ /**
214
+ * @param {string} audio – local path, URL, or file_id
215
+ * @param {object} opts
216
+ * @param {number} [opts.duration]
217
+ * @param {string} [opts.performer]
218
+ * @param {string} [opts.title]
219
+ * @param {string} [opts.thumbnail]
220
+ */
221
+ export async function sendAudio(token, chatId, audio, opts = {}) {
222
+ const fd = new FormData();
223
+ fd.append("chat_id", String(chatId));
224
+ if (opts.duration) fd.append("duration", String(opts.duration));
225
+ if (opts.performer) fd.append("performer", opts.performer);
226
+ if (opts.title) fd.append("title", opts.title);
227
+ if (opts.caption) fd.append("caption", opts.caption);
228
+ if (opts.parse_mode) fd.append("parse_mode", opts.parse_mode);
229
+ if (opts.reply_markup) fd.append("reply_markup", opts.reply_markup);
230
+
231
+ const resolved = resolveFile(fd, "audio", audio);
232
+ if (resolved) fd.append("audio", resolved);
233
+
234
+ if (opts.thumbnail) {
235
+ const resolvedThumb = resolveFile(fd, "thumbnail", opts.thumbnail);
236
+ if (resolvedThumb) fd.append("thumbnail", resolvedThumb);
237
+ }
238
+
239
+ return apiRequest(token, "sendAudio", {}, fd);
240
+ }
241
+
242
+ // ─── Document ─────────────────────────────────────────────────────────────────
243
+
244
+ /**
245
+ * @param {string} document – local path, URL, or file_id
246
+ * @param {boolean} [opts.disable_content_type_detection]
247
+ */
248
+ export async function sendDocument(token, chatId, document, opts = {}) {
249
+ const fd = new FormData();
250
+ fd.append("chat_id", String(chatId));
251
+ if (opts.caption) fd.append("caption", opts.caption);
252
+ if (opts.parse_mode) fd.append("parse_mode", opts.parse_mode);
253
+ if (opts.disable_content_type_detection)
254
+ fd.append("disable_content_type_detection", "true");
255
+ if (opts.reply_markup) fd.append("reply_markup", opts.reply_markup);
256
+
257
+ const resolved = resolveFile(fd, "document", document);
258
+ if (resolved) fd.append("document", resolved);
259
+
260
+ if (opts.thumbnail) {
261
+ const resolvedThumb = resolveFile(fd, "thumbnail", opts.thumbnail);
262
+ if (resolvedThumb) fd.append("thumbnail", resolvedThumb);
263
+ }
264
+
265
+ return apiRequest(token, "sendDocument", {}, fd);
266
+ }
267
+
268
+ // ─── Voice ────────────────────────────────────────────────────────────────────
269
+
270
+ /** OGG/OPUS encoded voice message. */
271
+ export async function sendVoice(token, chatId, voice, opts = {}) {
272
+ const fd = new FormData();
273
+ fd.append("chat_id", String(chatId));
274
+ if (opts.duration) fd.append("duration", String(opts.duration));
275
+ if (opts.caption) fd.append("caption", opts.caption);
276
+ if (opts.parse_mode) fd.append("parse_mode", opts.parse_mode);
277
+
278
+ const resolved = resolveFile(fd, "voice", voice);
279
+ if (resolved) fd.append("voice", resolved);
280
+
281
+ return apiRequest(token, "sendVoice", {}, fd);
282
+ }
283
+
284
+ // ─── Video Note ───────────────────────────────────────────────────────────────
285
+
286
+ /** Round video (1:1 aspect ratio). */
287
+ export async function sendVideoNote(token, chatId, videoNote, opts = {}) {
288
+ const fd = new FormData();
289
+ fd.append("chat_id", String(chatId));
290
+ if (opts.duration) fd.append("duration", String(opts.duration));
291
+ if (opts.length) fd.append("length", String(opts.length));
292
+
293
+ const resolved = resolveFile(fd, "video_note", videoNote);
294
+ if (resolved) fd.append("video_note", resolved);
295
+
296
+ return apiRequest(token, "sendVideoNote", {}, fd);
297
+ }
298
+
299
+ // ─── Sticker ──────────────────────────────────────────────────────────────────
300
+
301
+ /**
302
+ * @param {string} sticker – local .webp/.tgs/.webm, URL, or file_id
303
+ * @param {string} [opts.emoji] – emoji associated with the sticker
304
+ */
305
+ export async function sendSticker(token, chatId, sticker, opts = {}) {
306
+ const fd = new FormData();
307
+ fd.append("chat_id", String(chatId));
308
+ if (opts.emoji) fd.append("emoji", opts.emoji);
309
+ if (opts.reply_markup) fd.append("reply_markup", opts.reply_markup);
310
+
311
+ const resolved = resolveFile(fd, "sticker", sticker);
312
+ if (resolved) fd.append("sticker", resolved);
313
+
314
+ return apiRequest(token, "sendSticker", {}, fd);
315
+ }
316
+
317
+ // ─── Animation (GIF) ──────────────────────────────────────────────────────────
318
+
319
+ export async function sendAnimation(token, chatId, animation, opts = {}) {
320
+ const fd = new FormData();
321
+ fd.append("chat_id", String(chatId));
322
+ if (opts.duration) fd.append("duration", String(opts.duration));
323
+ if (opts.width) fd.append("width", String(opts.width));
324
+ if (opts.height) fd.append("height", String(opts.height));
325
+ if (opts.caption) fd.append("caption", opts.caption);
326
+ if (opts.has_spoiler) fd.append("has_spoiler", "true");
327
+
328
+ const resolved = resolveFile(fd, "animation", animation);
329
+ if (resolved) fd.append("animation", resolved);
330
+
331
+ return apiRequest(token, "sendAnimation", {}, fd);
332
+ }
333
+
334
+ // ─── Media Group (Album) ──────────────────────────────────────────────────────
335
+
336
+ /**
337
+ * Send 2–10 photos/videos as an album.
338
+ *
339
+ * @param {Array} media Array of { type, file, caption?, parse_mode? }
340
+ * type: "photo" | "video" | "audio" | "document"
341
+ *
342
+ * @example
343
+ * sendMediaGroup(token, chatId, [
344
+ * { type: "photo", file: "./img1.jpg", caption: "First" },
345
+ * { type: "photo", file: "./img2.jpg" },
346
+ * { type: "video", file: "./clip.mp4" },
347
+ * ]);
348
+ */
349
+ export async function sendMediaGroup(token, chatId, media, opts = {}) {
350
+ const fd = new FormData();
351
+ fd.append("chat_id", String(chatId));
352
+ if (opts.reply_to_message_id)
353
+ fd.append("reply_to_message_id", String(opts.reply_to_message_id));
354
+ if (opts.disable_notification) fd.append("disable_notification", "true");
355
+ if (opts.protect_content) fd.append("protect_content", "true");
356
+
357
+ const mediaArray = media.map((item, i) => {
358
+ const fieldName = `file_${i}`;
359
+ const resolved = resolveFile(fd, fieldName, item.file);
360
+ return {
361
+ type: item.type,
362
+ media: resolved ?? `attach://${fieldName}`,
363
+ ...(item.caption ? { caption: item.caption } : {}),
364
+ ...(item.parse_mode ? { parse_mode: item.parse_mode } : {}),
365
+ ...(item.has_spoiler ? { has_spoiler: true } : {}),
366
+ };
367
+ });
368
+
369
+ fd.append("media", JSON.stringify(mediaArray));
370
+ return apiRequest(token, "sendMediaGroup", {}, fd);
371
+ }
372
+
373
+ // ─── Location ─────────────────────────────────────────────────────────────────
374
+
375
+ /**
376
+ * @param {object} opts
377
+ * @param {number} [opts.horizontal_accuracy] – accuracy radius in metres (0–1500)
378
+ * @param {number} [opts.live_period] – seconds to broadcast live (60–86400)
379
+ * @param {number} [opts.heading] – 1–360 degrees
380
+ * @param {number} [opts.proximity_alert_radius]
381
+ */
382
+ export async function sendLocation(token, chatId, latitude, longitude, opts = {}) {
383
+ return apiRequest(token, "sendLocation", {
384
+ chat_id: chatId,
385
+ latitude,
386
+ longitude,
387
+ horizontal_accuracy: opts.horizontal_accuracy,
388
+ live_period: opts.live_period,
389
+ heading: opts.heading,
390
+ proximity_alert_radius: opts.proximity_alert_radius,
391
+ ...commonOpts(opts),
392
+ });
393
+ }
394
+
395
+ /** Edit a live location message while it's still broadcasting. */
396
+ export async function editLiveLocation(token, chatId, messageId, latitude, longitude, opts = {}) {
397
+ return apiRequest(token, "editMessageLiveLocation", {
398
+ chat_id: chatId,
399
+ message_id: messageId,
400
+ latitude,
401
+ longitude,
402
+ horizontal_accuracy: opts.horizontal_accuracy,
403
+ heading: opts.heading,
404
+ proximity_alert_radius: opts.proximity_alert_radius,
405
+ reply_markup: opts.reply_markup,
406
+ });
407
+ }
408
+
409
+ // ─── Venue ────────────────────────────────────────────────────────────────────
410
+
411
+ export async function sendVenue(token, chatId, { latitude, longitude, title, address, foursquare_id, foursquare_type, google_place_id, google_place_type }, opts = {}) {
412
+ return apiRequest(token, "sendVenue", {
413
+ chat_id: chatId,
414
+ latitude,
415
+ longitude,
416
+ title,
417
+ address,
418
+ foursquare_id,
419
+ foursquare_type,
420
+ google_place_id,
421
+ google_place_type,
422
+ ...commonOpts(opts),
423
+ });
424
+ }
425
+
426
+ // ─── Contact ──────────────────────────────────────────────────────────────────
427
+
428
+ export async function sendContact(token, chatId, { phone_number, first_name, last_name, vcard }, opts = {}) {
429
+ return apiRequest(token, "sendContact", {
430
+ chat_id: chatId,
431
+ phone_number,
432
+ first_name,
433
+ last_name,
434
+ vcard,
435
+ ...commonOpts(opts),
436
+ });
437
+ }
438
+
439
+ // ─── Poll ─────────────────────────────────────────────────────────────────────
440
+
441
+ /**
442
+ * @param {string} question
443
+ * @param {string[]} options – 2–10 answer choices
444
+ * @param {object} opts
445
+ * @param {string} [opts.type] – "regular" | "quiz"
446
+ * @param {boolean} [opts.is_anonymous]
447
+ * @param {boolean} [opts.allows_multiple_answers]
448
+ * @param {number} [opts.correct_option_id] – required for quiz type
449
+ * @param {string} [opts.explanation]
450
+ * @param {number} [opts.open_period] – seconds (5–600)
451
+ * @param {number} [opts.close_date] – unix timestamp
452
+ * @param {boolean} [opts.is_closed]
453
+ */
454
+ export async function sendPoll(token, chatId, question, options, opts = {}) {
455
+ return apiRequest(token, "sendPoll", {
456
+ chat_id: chatId,
457
+ question,
458
+ options,
459
+ type: opts.type ?? "regular",
460
+ is_anonymous: opts.is_anonymous ?? true,
461
+ allows_multiple_answers: opts.allows_multiple_answers,
462
+ correct_option_id: opts.correct_option_id,
463
+ explanation: opts.explanation,
464
+ explanation_parse_mode: opts.explanation_parse_mode,
465
+ open_period: opts.open_period,
466
+ close_date: opts.close_date,
467
+ is_closed: opts.is_closed,
468
+ ...commonOpts(opts),
469
+ });
470
+ }
471
+
472
+ // ─── Dice ─────────────────────────────────────────────────────────────────────
473
+
474
+ /** @param {string} [emoji] – "🎲" | "🎯" | "🏀" | "⚽" | "🎳" | "🎰" (default 🎲) */
475
+ export async function sendDice(token, chatId, emoji = "🎲", opts = {}) {
476
+ return apiRequest(token, "sendDice", {
477
+ chat_id: chatId,
478
+ emoji,
479
+ ...commonOpts(opts),
480
+ });
481
+ }
482
+
483
+ // ─── Chat Action (typing indicator) ──────────────────────────────────────────
484
+
485
+ /**
486
+ * @param {string} action – "typing" | "upload_photo" | "record_video" |
487
+ * "upload_video" | "record_voice" | "upload_voice" |
488
+ * "upload_document" | "choose_sticker" |
489
+ * "find_location" | "record_video_note" |
490
+ * "upload_video_note"
491
+ */
492
+ export async function sendChatAction(token, chatId, action, opts = {}) {
493
+ return apiRequest(token, "sendChatAction", {
494
+ chat_id: chatId,
495
+ action,
496
+ message_thread_id: opts.message_thread_id,
497
+ });
498
+ }
499
+
500
+ // ─── Forward & Copy ───────────────────────────────────────────────────────────
501
+
502
+ export async function forwardMessage(token, chatId, fromChatId, messageId, opts = {}) {
503
+ return apiRequest(token, "forwardMessage", {
504
+ chat_id: chatId,
505
+ from_chat_id: fromChatId,
506
+ message_id: messageId,
507
+ disable_notification: opts.disable_notification,
508
+ protect_content: opts.protect_content,
509
+ message_thread_id: opts.message_thread_id,
510
+ });
511
+ }
512
+
513
+ /** Copy without the forward header. */
514
+ export async function copyMessage(token, chatId, fromChatId, messageId, opts = {}) {
515
+ return apiRequest(token, "copyMessage", {
516
+ chat_id: chatId,
517
+ from_chat_id: fromChatId,
518
+ message_id: messageId,
519
+ caption: opts.caption,
520
+ parse_mode: opts.parse_mode,
521
+ reply_markup: opts.reply_markup,
522
+ disable_notification: opts.disable_notification,
523
+ protect_content: opts.protect_content,
524
+ reply_to_message_id: opts.reply_to_message_id,
525
+ });
526
+ }
527
+
528
+ // ─── Edit & Delete ────────────────────────────────────────────────────────────
529
+
530
+ export async function editMessageText(token, chatId, messageId, text, opts = {}) {
531
+ return apiRequest(token, "editMessageText", {
532
+ chat_id: chatId,
533
+ message_id: messageId,
534
+ text,
535
+ parse_mode: opts.parse_mode,
536
+ entities: opts.entities,
537
+ disable_web_page_preview: opts.disable_web_page_preview,
538
+ reply_markup: opts.reply_markup,
539
+ });
540
+ }
541
+
542
+ export async function editMessageCaption(token, chatId, messageId, caption, opts = {}) {
543
+ return apiRequest(token, "editMessageCaption", {
544
+ chat_id: chatId,
545
+ message_id: messageId,
546
+ caption,
547
+ parse_mode: opts.parse_mode,
548
+ reply_markup: opts.reply_markup,
549
+ });
550
+ }
551
+
552
+ export async function editMessageReplyMarkup(token, chatId, messageId, replyMarkup) {
553
+ return apiRequest(token, "editMessageReplyMarkup", {
554
+ chat_id: chatId,
555
+ message_id: messageId,
556
+ reply_markup: replyMarkup,
557
+ });
558
+ }
559
+
560
+ export async function deleteMessage(token, chatId, messageId) {
561
+ return apiRequest(token, "deleteMessage", { chat_id: chatId, message_id: messageId });
562
+ }
563
+
564
+ export async function pinMessage(token, chatId, messageId, opts = {}) {
565
+ return apiRequest(token, "pinChatMessage", {
566
+ chat_id: chatId,
567
+ message_id: messageId,
568
+ disable_notification: opts.disable_notification,
569
+ });
570
+ }
571
+
572
+ export async function unpinMessage(token, chatId, messageId) {
573
+ return apiRequest(token, "unpinChatMessage", { chat_id: chatId, message_id: messageId });
574
+ }
@@ -0,0 +1,60 @@
1
+ import webPush from 'web-push'
2
+
3
+ // npx web-push generate-vapid-keys
4
+ const vapidKeys = webPush.generateVAPIDKeys() // We use webpush to generate our public and private keys
5
+ const { publicKey, privateKey } = vapidKeys
6
+ const { WEBPUSH_VAPID_SUBJ } = process.env
7
+
8
+ try {
9
+ if (WEBPUSH_VAPID_SUBJ) {
10
+ console.log('webpush setup')
11
+ webPush.setVapidDetails(WEBPUSH_VAPID_SUBJ, publicKey, privateKey) // We are giving webpush the required information to encrypt our data
12
+ console.log('webpush setup done')
13
+ } else {
14
+ console.log('no webpush setup')
15
+ }
16
+ } catch (e) {
17
+ console.error('[webpush error]', e.toString())
18
+ }
19
+
20
+ // This function takes a subscription object and a payload as an argument. It will try to encrypt the payload
21
+ // then attempt to send a notification via the subscription's endpoint
22
+ // will throw exception if error
23
+ const send = async (subscription, payload, options = { TTL: 60 }) => {
24
+ // This means we won't resend a notification if the client is offline
25
+ // what if TTL = 0 ?
26
+ // web-push's sendNotification function does all the work for us
27
+ if (!subscription.keys) { payload = payload || null }
28
+ return await webPush.sendNotification(subscription, payload, options) // will throw if error
29
+ }
30
+
31
+ const getPubKey = () => vapidKeys.publicKey
32
+
33
+ export {
34
+ send,
35
+ getPubKey
36
+ }
37
+
38
+ // // sw.js
39
+ // self.addEventListener('push', (event) => {
40
+ // const data = event.data.json();
41
+ // self.registration.showNotification(data.title, {
42
+ // body: data.body,
43
+ // icon: '/icon.png',
44
+ // });
45
+ // });
46
+
47
+ // // pn.js
48
+ // const reg = await navigator.serviceWorker.register('/sw.js');
49
+
50
+ // const subscription = await reg.pushManager.subscribe({
51
+ // userVisibleOnly: true,
52
+ // applicationServerKey: urlBase64ToUint8Array(VAPID_PUBLIC_KEY), // your public VAPID key
53
+ // });
54
+
55
+ // // Save subscription to your server
56
+ // await fetch('/api/subscribe', {
57
+ // method: 'POST',
58
+ // body: JSON.stringify(subscription),
59
+ // headers: { 'Content-Type': 'application/json' },
60
+ // });
package/config.js ADDED
@@ -0,0 +1,37 @@
1
+ import path from 'path'
2
+ import { readFileSync } from 'fs'
3
+ import { fileURLToPath } from 'url'
4
+
5
+ const __dirname = path.dirname(fileURLToPath(import.meta.url))
6
+
7
+ export default async function(app_path) {
8
+ process.env.NODE_ENV = process.env.NODE_ENV || '' // development, dev, prd... (development is on local machine)
9
+ const { NODE_ENV, VAULT } = process.env
10
+ if (!NODE_ENV) {
11
+ console.log('Exiting No Environment Specified')
12
+ process.exit(1)
13
+ }
14
+
15
+ const packageJsonPath = path.join(app_path, 'package.json')
16
+ const packageJsonContent = JSON.parse(readFileSync(packageJsonPath, 'utf8'))
17
+ const { version, name } = packageJsonContent
18
+ process.env.APP_VERSION = version
19
+ process.env.APP_NAME = name
20
+
21
+ if (NODE_ENV) {
22
+ if (VAULT && VAULT !== 'unused') {
23
+ try {
24
+ const vaultRes = await fetch(VAULT) // a GET with query parameters (protected)
25
+ const vaultConfig = await vaultRes.json()
26
+ process.env = { ...process.env, ...vaultConfig }
27
+ } catch (e) {
28
+ console.log('vault error', e.toString(), VAULT)
29
+ }
30
+ }
31
+ const sleep = (milliseconds) => new Promise(resolve => setTimeout(resolve, milliseconds))
32
+ await sleep(2000)
33
+ console.log('CONFIG DONE!')
34
+ } else {
35
+ console.log('NODE_ENV and APP_PATH needs to be defined')
36
+ }
37
+ }