@grom.js/effect-tg 0.3.1 → 0.5.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.
Files changed (119) hide show
  1. package/dist/Bot.d.ts +13 -0
  2. package/dist/Bot.d.ts.map +1 -0
  3. package/dist/Bot.js +5 -0
  4. package/dist/Bot.js.map +1 -0
  5. package/dist/BotApi.d.ts +12 -3
  6. package/dist/BotApi.d.ts.map +1 -1
  7. package/dist/BotApi.js +7 -2
  8. package/dist/BotApi.js.map +1 -0
  9. package/dist/BotApiTransport.d.ts +11 -10
  10. package/dist/BotApiTransport.d.ts.map +1 -1
  11. package/dist/BotApiTransport.js +6 -8
  12. package/dist/BotApiTransport.js.map +1 -0
  13. package/dist/BotApiUrl.d.ts +14 -0
  14. package/dist/BotApiUrl.d.ts.map +1 -0
  15. package/dist/BotApiUrl.js +13 -0
  16. package/dist/BotApiUrl.js.map +1 -0
  17. package/dist/Content.d.ts +288 -16
  18. package/dist/Content.d.ts.map +1 -1
  19. package/dist/Content.js +189 -28
  20. package/dist/Content.js.map +1 -0
  21. package/dist/Dialog.d.ts +61 -0
  22. package/dist/Dialog.d.ts.map +1 -0
  23. package/dist/Dialog.js +35 -0
  24. package/dist/Dialog.js.map +1 -0
  25. package/dist/File.d.ts +23 -0
  26. package/dist/File.d.ts.map +1 -0
  27. package/dist/File.js +24 -0
  28. package/dist/File.js.map +1 -0
  29. package/dist/LinkPreview.d.ts +2 -2
  30. package/dist/LinkPreview.d.ts.map +1 -1
  31. package/dist/LinkPreview.js +17 -16
  32. package/dist/LinkPreview.js.map +1 -0
  33. package/dist/Markup.d.ts +41 -0
  34. package/dist/Markup.d.ts.map +1 -0
  35. package/dist/Markup.js +10 -0
  36. package/dist/Markup.js.map +1 -0
  37. package/dist/Runner.d.ts +18 -0
  38. package/dist/Runner.d.ts.map +1 -0
  39. package/dist/Runner.js +7 -0
  40. package/dist/Runner.js.map +1 -0
  41. package/dist/Send.d.ts +177 -13
  42. package/dist/Send.d.ts.map +1 -1
  43. package/dist/Send.js +187 -18
  44. package/dist/Send.js.map +1 -0
  45. package/dist/Text.d.ts +26 -10
  46. package/dist/Text.d.ts.map +1 -1
  47. package/dist/Text.js +11 -12
  48. package/dist/Text.js.map +1 -0
  49. package/dist/index.d.ts +6 -3
  50. package/dist/index.d.ts.map +1 -1
  51. package/dist/index.js +7 -3
  52. package/dist/index.js.map +1 -0
  53. package/dist/internal/botApi.d.ts +3 -4
  54. package/dist/internal/botApi.d.ts.map +1 -1
  55. package/dist/internal/botApi.js +18 -22
  56. package/dist/internal/botApi.js.map +1 -0
  57. package/dist/internal/botApiMethods.gen.d.ts +19 -19
  58. package/dist/internal/botApiMethods.gen.d.ts.map +1 -1
  59. package/dist/internal/botApiMethods.gen.js +1 -0
  60. package/dist/internal/botApiMethods.gen.js.map +1 -0
  61. package/dist/internal/botApiShape.gen.d.ts +159 -159
  62. package/dist/internal/botApiShape.gen.d.ts.map +1 -1
  63. package/dist/internal/botApiShape.gen.js +1 -0
  64. package/dist/internal/botApiShape.gen.js.map +1 -0
  65. package/dist/internal/botApiTransport.d.ts +4 -6
  66. package/dist/internal/botApiTransport.d.ts.map +1 -1
  67. package/dist/internal/botApiTransport.js +89 -17
  68. package/dist/internal/botApiTransport.js.map +1 -0
  69. package/dist/internal/botApiTypes.gen.js +1 -0
  70. package/dist/internal/botApiTypes.gen.js.map +1 -0
  71. package/dist/internal/dialog.d.ts +12 -0
  72. package/dist/internal/dialog.d.ts.map +1 -0
  73. package/dist/internal/dialog.js +19 -0
  74. package/dist/internal/dialog.js.map +1 -0
  75. package/dist/internal/runner.d.ts +8 -0
  76. package/dist/internal/runner.d.ts.map +1 -0
  77. package/dist/internal/runner.js +38 -0
  78. package/dist/internal/runner.js.map +1 -0
  79. package/dist/internal/send.d.ts +13 -0
  80. package/dist/internal/send.d.ts.map +1 -0
  81. package/dist/internal/send.js +220 -0
  82. package/dist/internal/send.js.map +1 -0
  83. package/package.json +14 -10
  84. package/src/Bot.ts +16 -0
  85. package/src/BotApi.ts +68 -0
  86. package/src/BotApiTransport.ts +55 -0
  87. package/src/BotApiUrl.ts +28 -0
  88. package/src/Content.ts +410 -0
  89. package/src/Dialog.ts +54 -0
  90. package/src/File.ts +45 -0
  91. package/src/LinkPreview.ts +26 -0
  92. package/src/Markup.ts +33 -0
  93. package/src/Runner.ts +22 -0
  94. package/src/Send.ts +330 -0
  95. package/src/Text.ts +42 -0
  96. package/src/index.ts +12 -0
  97. package/src/internal/botApi.ts +31 -0
  98. package/src/internal/botApiMethods.gen.ts +2027 -0
  99. package/src/internal/botApiShape.gen.ts +398 -0
  100. package/src/internal/botApiTransport.ts +118 -0
  101. package/src/internal/botApiTypes.gen.ts +4178 -0
  102. package/src/internal/dialog.ts +33 -0
  103. package/src/internal/runner.ts +57 -0
  104. package/src/internal/send.ts +318 -0
  105. package/dist/BotApiWebhook.d.ts +0 -41
  106. package/dist/BotApiWebhook.d.ts.map +0 -1
  107. package/dist/BotApiWebhook.js +0 -43
  108. package/dist/Chat.d.ts +0 -96
  109. package/dist/Chat.d.ts.map +0 -1
  110. package/dist/Chat.js +0 -48
  111. package/dist/InputFile.d.ts +0 -4
  112. package/dist/InputFile.d.ts.map +0 -1
  113. package/dist/InputFile.js +0 -3
  114. package/dist/internal/botApiMethod.d.ts +0 -8
  115. package/dist/internal/botApiMethod.d.ts.map +0 -1
  116. package/dist/internal/botApiMethod.js +0 -1
  117. package/dist/internal/chat.d.ts +0 -14
  118. package/dist/internal/chat.d.ts.map +0 -1
  119. package/dist/internal/chat.js +0 -20
@@ -0,0 +1,26 @@
1
+ import type * as BotApi from './BotApi.ts'
2
+ import * as Data from 'effect/Data'
3
+ import * as Option from 'effect/Option'
4
+
5
+ export class LinkPreview extends Data.Class<{
6
+ url: Option.Option<string>
7
+ position: 'above' | 'below'
8
+ mediaSize: 'large' | 'small'
9
+ }> {}
10
+
11
+ export const options = (preview: LinkPreview): BotApi.Types.LinkPreviewOptions => {
12
+ const opts: BotApi.Types.LinkPreviewOptions = { is_disabled: false }
13
+ if (Option.isSome(preview.url)) {
14
+ opts.url = preview.url.value
15
+ }
16
+ if (preview.position === 'above') {
17
+ opts.show_above_text = true
18
+ }
19
+ if (preview.mediaSize === 'large') {
20
+ opts.prefer_large_media = true
21
+ }
22
+ else if (preview.mediaSize === 'small') {
23
+ opts.prefer_small_media = true
24
+ }
25
+ return opts
26
+ }
package/src/Markup.ts ADDED
@@ -0,0 +1,33 @@
1
+ import type * as Option from 'effect/Option'
2
+ import * as Data from 'effect/Data'
3
+
4
+ /**
5
+ * Reply markup for the message.
6
+ */
7
+ export type Markup
8
+ = | InlineKeyboard
9
+ | ReplyKeyboard
10
+ | ReplyKeyboardRemove
11
+ | ForceReply
12
+
13
+ export class InlineKeyboard extends Data.TaggedClass('InlineKeyboard')<{
14
+ rows: [] // TODO
15
+ }> {}
16
+
17
+ export class ReplyKeyboard extends Data.TaggedClass('ReplyKeyboard')<{
18
+ rows: [] // TODO
19
+ persistent: boolean
20
+ resizable: boolean
21
+ oneTime: boolean
22
+ selective: boolean
23
+ inputPlaceholder: Option.Option<string>
24
+ }> {}
25
+
26
+ export class ReplyKeyboardRemove extends Data.TaggedClass('ReplyKeyboardRemove')<{
27
+ selective: boolean
28
+ }> {}
29
+
30
+ export class ForceReply extends Data.TaggedClass('ForceReply')<{
31
+ selective: boolean
32
+ inputPlaceholder: Option.Option<string>
33
+ }> {}
package/src/Runner.ts ADDED
@@ -0,0 +1,22 @@
1
+ import type * as Effect from 'effect/Effect'
2
+ import type * as Bot from './Bot.ts'
3
+ import * as internal from './internal/runner.ts'
4
+
5
+ /**
6
+ * Runner runs a bot using long polling.
7
+ */
8
+ export interface Runner<E = never, R = never> {
9
+ readonly run: {
10
+ <E1, R1>(bot: Bot.Bot<E1, R1>): Effect.Effect<
11
+ never,
12
+ E | E1,
13
+ R | Exclude<R1, Bot.Update>
14
+ >
15
+ }
16
+ }
17
+
18
+ /**
19
+ * Creates a simple runner that fetches updates by calling `BotApi.getUpdates`
20
+ * method and handles them one by one.
21
+ */
22
+ export const makeSimple = internal.makeSimple
package/src/Send.ts ADDED
@@ -0,0 +1,330 @@
1
+ /**
2
+ * Utilities for sending messages.
3
+ */
4
+
5
+ import type * as BotApi from './BotApi.ts'
6
+ import type * as BotApiTransport from './BotApiTransport.ts'
7
+ import type * as Content from './Content.ts'
8
+ import type * as Dialog_ from './Dialog.ts'
9
+ import type * as Markup from './Markup.ts'
10
+ import * as Context from 'effect/Context'
11
+ import * as Data from 'effect/Data'
12
+ import * as Effect from 'effect/Effect'
13
+ import * as Effectable from 'effect/Effectable'
14
+ import * as Function from 'effect/Function'
15
+ import * as Inspectable from 'effect/Inspectable'
16
+ import * as Pipeable from 'effect/Pipeable'
17
+ import * as internal from './internal/send.ts'
18
+
19
+ // ─── Type ID ──────────────────────────────────────────────────────
20
+
21
+ const MessageToSendTypeId: unique symbol = Symbol.for('@grom.js/effect-tg/Send/MessageToSend')
22
+
23
+ // ─── Services ─────────────────────────────────────────────────────
24
+
25
+ /**
26
+ * Service providing the target dialog for sending messages.
27
+ */
28
+ export class Dialog extends Context.Tag('@grom.js/effect-tg/Send/Dialog')<
29
+ Dialog,
30
+ Dialog_.Dialog
31
+ >() {}
32
+
33
+ // ─── Options ──────────────────────────────────────────────────────
34
+
35
+ /**
36
+ * Options for sending a message.
37
+ *
38
+ * @see {@link https://core.telegram.org/tdlib/docs/classtd_1_1td__api_1_1message_send_options.html td.td_api.messageSendOptions}
39
+ */
40
+ export class Options extends Data.Class<{
41
+ disableNotification?: boolean
42
+ protectContent?: boolean
43
+ allowPaidBroadcast?: boolean
44
+ }> {}
45
+
46
+ // ─── Direct Send ──────────────────────────────────────────────────
47
+
48
+ /**
49
+ * Sends a message directly with explicit parameters.
50
+ *
51
+ * Prefer {@linkcode message} for more composability.
52
+ *
53
+ * Example:
54
+ * ```ts
55
+ * yield* Send.sendMessage({
56
+ * content: new Content.Text({
57
+ * text,
58
+ * linkPreview: Option.none(),
59
+ * }),
60
+ * dialog: new Dialog.UserId(userId),
61
+ * options: new Send.Options({
62
+ * protectContent: true,
63
+ * }),
64
+ * })
65
+ * ```
66
+ *
67
+ * @see {@link https://core.telegram.org/tdlib/docs/classtd_1_1td__api_1_1send_message.html td.td_api.sendMessage}
68
+ */
69
+ export const sendMessage: (params: {
70
+ content: Content.Content
71
+ dialog: Dialog_.Dialog
72
+ options?: Options
73
+ markup?: Markup.Markup
74
+ }) => Effect.Effect<
75
+ BotApi.Types.Message,
76
+ BotApi.BotApiError | BotApiTransport.BotApiTransportError,
77
+ BotApi.BotApi
78
+ > = internal.sendMessage
79
+
80
+ // ─── MessageToSend ────────────────────────────────────────────────
81
+
82
+ type MessageToSendEffect = Effect.Effect<
83
+ BotApi.Types.Message,
84
+ BotApi.BotApiError | BotApiTransport.BotApiTransportError,
85
+ BotApi.BotApi | Dialog
86
+ >
87
+
88
+ /**
89
+ * A message prepared to be sent.
90
+ */
91
+ export interface MessageToSend extends
92
+ Inspectable.Inspectable,
93
+ MessageToSendEffect
94
+ {
95
+ readonly [MessageToSendTypeId]: typeof MessageToSendTypeId
96
+ readonly content: Content.Content
97
+ readonly options: Options
98
+ readonly markup: Markup.Markup | undefined
99
+ }
100
+
101
+ const Proto = {
102
+ [MessageToSendTypeId]: MessageToSendTypeId,
103
+ ...Effectable.CommitPrototype,
104
+ ...Inspectable.BaseProto,
105
+
106
+ commit(this: MessageToSend): MessageToSendEffect {
107
+ return Effect.flatMap(
108
+ Dialog,
109
+ dialog => sendMessage({
110
+ dialog,
111
+ content: this.content,
112
+ options: this.options,
113
+ markup: this.markup,
114
+ }),
115
+ )
116
+ },
117
+
118
+ toJSON(this: MessageToSend) {
119
+ return {
120
+ _id: this[MessageToSendTypeId].description,
121
+ content: this.content,
122
+ options: this.options,
123
+ markup: this.markup,
124
+ }
125
+ },
126
+
127
+ pipe() {
128
+ // eslint-disable-next-line prefer-rest-params
129
+ return Pipeable.pipeArguments(this, arguments)
130
+ },
131
+ }
132
+
133
+ const make = (args: {
134
+ content: Content.Content
135
+ options?: Options
136
+ markup?: Markup.Markup
137
+ }): MessageToSend => {
138
+ const self = Object.create(Proto)
139
+ self.content = args.content
140
+ self.options = args.options
141
+ self.markup = args.markup
142
+ return self
143
+ }
144
+
145
+ // ─── Constructors ─────────────────────────────────────────────────
146
+
147
+ /**
148
+ * Creates a message to send with the given content.
149
+ *
150
+ * Example:
151
+ * ```ts
152
+ * yield* Send.message(content).pipe(
153
+ * Send.withProtection,
154
+ * Send.to(dialog),
155
+ * )
156
+ * ```
157
+ */
158
+ export const message = (
159
+ content: Content.Content,
160
+ ): MessageToSend => make({ content })
161
+
162
+ // ─── Dialog Provider ──────────────────────────────────────────────
163
+
164
+ export const to: {
165
+ /**
166
+ * Provides the target dialog for sending messages.
167
+ *
168
+ * Example:
169
+ * ```ts
170
+ * // Per-message
171
+ * yield* Send.message(content).pipe(
172
+ * Send.withProtection,
173
+ * Send.to(dialog),
174
+ * )
175
+ *
176
+ * // For entire handler (multiple sends)
177
+ * const handler = Effect.gen(function* () {
178
+ * yield* Send.message(content1)
179
+ * yield* Send.message(content2)
180
+ * }).pipe(Send.to(dialog))
181
+ * ```
182
+ */
183
+ (dialog: Dialog_.Dialog): <A, E, R>(effect: Effect.Effect<A, E, R>) => Effect.Effect<A, E, Exclude<R, Dialog>>
184
+ <A, E, R>(effect: Effect.Effect<A, E, R>, dialog: Dialog_.Dialog): Effect.Effect<A, E, Exclude<R, Dialog>>
185
+ } = Function.dual(2, <A, E, R>(
186
+ effect: Effect.Effect<A, E, R>,
187
+ dialog: Dialog_.Dialog,
188
+ ): Effect.Effect<A, E, Exclude<R, Dialog>> => (
189
+ Effect.provideService(effect, Dialog, dialog)
190
+ ))
191
+
192
+ // ─── Options Combinators ──────────────────────────────────────────
193
+
194
+ /**
195
+ * Modifies the options for sending a message by merging with existing options.
196
+ *
197
+ * Example:
198
+ * ```ts
199
+ * yield* Send.message(content).pipe(
200
+ * Send.withOptions({
201
+ * disableNotification: true,
202
+ * protectContent: true,
203
+ * }),
204
+ * Send.to(dialog),
205
+ * )
206
+ * ```
207
+ *
208
+ * Available shortcuts:
209
+ * - {@linkcode withoutNotification}
210
+ * - {@linkcode withNotification}
211
+ * - {@linkcode withoutContentProtection}
212
+ * - {@linkcode withContentProtection}
213
+ * - {@linkcode withoutPaidBroadcast}
214
+ * - {@linkcode withPaidBroadcast}
215
+ */
216
+ export const withOptions: {
217
+ (options: Options): (self: MessageToSend) => MessageToSend
218
+ (self: MessageToSend, options: Options): MessageToSend
219
+ } = Function.dual(2, (
220
+ self: MessageToSend,
221
+ options: Options,
222
+ ): MessageToSend => (
223
+ make(
224
+ {
225
+ content: self.content,
226
+ options: new Options({
227
+ ...self.options,
228
+ ...options,
229
+ }),
230
+ markup: self.markup,
231
+ },
232
+ )
233
+ ))
234
+
235
+ /**
236
+ * Disables notification for the message.
237
+ *
238
+ * Shortcut for `withOptions({ disableNotification: true })`.
239
+ */
240
+ export const withoutNotification: (
241
+ self: MessageToSend,
242
+ ) => MessageToSend = withOptions({ disableNotification: true })
243
+
244
+ /**
245
+ * Enables notification for the message.
246
+ *
247
+ * Shortcut for `withOptions({ disableNotification: false })`.
248
+ */
249
+ export const withNotification: (
250
+ self: MessageToSend,
251
+ ) => MessageToSend = withOptions({ disableNotification: false })
252
+
253
+ /**
254
+ * Allows message content to be saved and forwarded.
255
+ *
256
+ * Shortcut for `withOptions({ protectContent: false })`.
257
+ */
258
+ export const withoutContentProtection: (
259
+ self: MessageToSend,
260
+ ) => MessageToSend = withOptions({ protectContent: false })
261
+
262
+ /**
263
+ * Protects message content from saving and forwarding.
264
+ *
265
+ * Shortcut for `withOptions({ protectContent: true })`.
266
+ */
267
+ export const withContentProtection: (
268
+ self: MessageToSend,
269
+ ) => MessageToSend = withOptions({ protectContent: true })
270
+
271
+ /**
272
+ * Disallows paid broadcast for the message.
273
+ *
274
+ * Shortcut for `withOptions({ allowPaidBroadcast: false })`.
275
+ */
276
+ export const withoutPaidBroadcast: (
277
+ self: MessageToSend,
278
+ ) => MessageToSend = withOptions({ allowPaidBroadcast: false })
279
+
280
+ /**
281
+ * Allows paid broadcast for the message.
282
+ *
283
+ * Shortcut for `withOptions({ allowPaidBroadcast: true })`.
284
+ */
285
+ export const withPaidBroadcast: (
286
+ self: MessageToSend,
287
+ ) => MessageToSend = withOptions({ allowPaidBroadcast: true })
288
+
289
+ // ─── Markup Combinators ──────────────────────────────────────────
290
+
291
+ /**
292
+ * Sets the reply markup for the message.
293
+ *
294
+ * Example:
295
+ * ```ts
296
+ * yield* Send.message(content).pipe(
297
+ * Send.withMarkup(
298
+ * new Markup.ForceReply({
299
+ * selective: false,
300
+ * inputPlaceholder: Option.some("effect-tg or grammY?"),
301
+ * }),
302
+ * ),
303
+ * Send.to(dialog),
304
+ * )
305
+ * ```
306
+ */
307
+ export const withMarkup: {
308
+ (markup: Markup.Markup): (self: MessageToSend) => MessageToSend
309
+ (self: MessageToSend, markup: Markup.Markup): MessageToSend
310
+ } = Function.dual(2, (
311
+ self: MessageToSend,
312
+ markup: Markup.Markup,
313
+ ): MessageToSend => (
314
+ make({
315
+ content: self.content,
316
+ options: self.options,
317
+ markup,
318
+ })
319
+ ))
320
+
321
+ /**
322
+ * Removes the reply markup from the message.
323
+ */
324
+ export const withoutMarkup: (
325
+ self: MessageToSend,
326
+ ) => MessageToSend = self => make({
327
+ content: self.content,
328
+ options: self.options,
329
+ markup: undefined,
330
+ })
package/src/Text.ts ADDED
@@ -0,0 +1,42 @@
1
+ import type { TgxElement } from '@grom.js/tgx/types'
2
+ import type { Types } from './BotApi.ts'
3
+ import * as Data from 'effect/Data'
4
+
5
+ /**
6
+ * Formatted text.
7
+ */
8
+ export type Text
9
+ = | Plain
10
+ | Html
11
+ | Markdown
12
+ | Tgx
13
+
14
+ export class Plain extends Data.TaggedClass('Plain')<{
15
+ text: string
16
+ entities?: Array<Types.MessageEntity>
17
+ }> {}
18
+
19
+ export class Html extends Data.TaggedClass('Html')<{
20
+ html: string
21
+ }> {}
22
+
23
+ export class Markdown extends Data.TaggedClass('Markdown')<{
24
+ markdown: string
25
+ }> {}
26
+
27
+ export class Tgx extends Data.TaggedClass('Tgx')<{
28
+ tgx: TgxElement
29
+ }> {}
30
+
31
+ // ———— Constructors ———————————————————————————————————————————————————————————
32
+
33
+ export const plain = (
34
+ text: string,
35
+ entities?: Array<Types.MessageEntity>,
36
+ ): Plain => new Plain({ text, entities })
37
+
38
+ export const html = (html: string): Html => new Html({ html })
39
+
40
+ export const markdown = (markdown: string): Markdown => new Markdown({ markdown })
41
+
42
+ export const tgx = (tgx: TgxElement): Tgx => new Tgx({ tgx })
package/src/index.ts ADDED
@@ -0,0 +1,12 @@
1
+ export * as Bot from './Bot.ts'
2
+ export * as BotApi from './BotApi.ts'
3
+ export * as BotApiTransport from './BotApiTransport.ts'
4
+ export * as BotApiUrl from './BotApiUrl.ts'
5
+ export * as Content from './Content.ts'
6
+ export * as Dialog from './Dialog.ts'
7
+ export * as File from './File.ts'
8
+ export * as LinkPreview from './LinkPreview.ts'
9
+ export * as Markup from './Markup.ts'
10
+ export * as Runner from './Runner.ts'
11
+ export * as Send from './Send.ts'
12
+ export * as Text from './Text.ts'
@@ -0,0 +1,31 @@
1
+ import type * as BotApiTransport from '../BotApiTransport.ts'
2
+ import * as Effect from 'effect/Effect'
3
+ import * as BotApi from '../BotApi.ts'
4
+
5
+ export const make = (
6
+ transport: BotApiTransport.BotApiTransport.Service,
7
+ ): BotApi.BotApi.Service => (
8
+ new Proxy({}, {
9
+ get: (_target, prop) => {
10
+ if (typeof prop !== 'string') {
11
+ return
12
+ }
13
+ const method = prop
14
+ return Effect.fnUntraced(
15
+ function* (params: void | Record<string, unknown> = {}) {
16
+ const response = yield* transport.sendRequest(method, params)
17
+ if (response.ok) {
18
+ return response.result
19
+ }
20
+ return yield* Effect.fail(
21
+ new BotApi.BotApiError({
22
+ code: response.error_code,
23
+ description: response.description,
24
+ parameters: response.parameters,
25
+ }),
26
+ )
27
+ },
28
+ )
29
+ },
30
+ }) as BotApi.BotApi.Service
31
+ )