@grom.js/effect-tg 0.8.0 → 0.10.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 (99) hide show
  1. package/README.md +169 -5
  2. package/dist/BotApi.d.ts +42 -12
  3. package/dist/BotApi.d.ts.map +1 -1
  4. package/dist/BotApi.js +18 -2
  5. package/dist/BotApi.js.map +1 -1
  6. package/dist/BotApiError.d.ts +46 -71
  7. package/dist/BotApiError.d.ts.map +1 -1
  8. package/dist/BotApiError.js +59 -41
  9. package/dist/BotApiError.js.map +1 -1
  10. package/dist/BotApiTransport.d.ts +10 -17
  11. package/dist/BotApiTransport.d.ts.map +1 -1
  12. package/dist/BotApiTransport.js +2 -7
  13. package/dist/BotApiTransport.js.map +1 -1
  14. package/dist/BotApiUrl.d.ts +6 -8
  15. package/dist/BotApiUrl.d.ts.map +1 -1
  16. package/dist/BotApiUrl.js.map +1 -1
  17. package/dist/Content.d.ts.map +1 -1
  18. package/dist/Dialog.d.ts +96 -41
  19. package/dist/Dialog.d.ts.map +1 -1
  20. package/dist/Dialog.js +60 -25
  21. package/dist/Dialog.js.map +1 -1
  22. package/dist/File.d.ts +1 -2
  23. package/dist/File.d.ts.map +1 -1
  24. package/dist/File.js +1 -1
  25. package/dist/File.js.map +1 -1
  26. package/dist/Markup.d.ts.map +1 -1
  27. package/dist/Runner.d.ts +1 -1
  28. package/dist/Runner.d.ts.map +1 -1
  29. package/dist/Send.d.ts +57 -130
  30. package/dist/Send.d.ts.map +1 -1
  31. package/dist/Send.js +65 -128
  32. package/dist/Send.js.map +1 -1
  33. package/dist/Text.d.ts.map +1 -1
  34. package/dist/internal/botApi.d.ts +3 -1
  35. package/dist/internal/botApi.d.ts.map +1 -1
  36. package/dist/internal/botApi.gen.d.ts +6501 -0
  37. package/dist/internal/botApi.gen.d.ts.map +1 -0
  38. package/dist/internal/botApi.gen.js +2 -0
  39. package/dist/internal/botApi.gen.js.map +1 -0
  40. package/dist/internal/botApi.js +2 -6
  41. package/dist/internal/botApi.js.map +1 -1
  42. package/dist/internal/botApiError.d.ts +5 -2
  43. package/dist/internal/botApiError.d.ts.map +1 -1
  44. package/dist/internal/botApiError.js +8 -18
  45. package/dist/internal/botApiError.js.map +1 -1
  46. package/dist/internal/botApiTransport.d.ts +5 -2
  47. package/dist/internal/botApiTransport.d.ts.map +1 -1
  48. package/dist/internal/botApiTransport.js +14 -10
  49. package/dist/internal/botApiTransport.js.map +1 -1
  50. package/dist/internal/dialog.d.ts +27 -10
  51. package/dist/internal/dialog.d.ts.map +1 -1
  52. package/dist/internal/dialog.js +88 -14
  53. package/dist/internal/dialog.js.map +1 -1
  54. package/dist/internal/file.d.ts +1 -1
  55. package/dist/internal/file.d.ts.map +1 -1
  56. package/dist/internal/file.js +1 -1
  57. package/dist/internal/file.js.map +1 -1
  58. package/dist/internal/runner.d.ts +4 -5
  59. package/dist/internal/runner.d.ts.map +1 -1
  60. package/dist/internal/runner.js +14 -19
  61. package/dist/internal/runner.js.map +1 -1
  62. package/dist/internal/send.d.ts +4 -4
  63. package/dist/internal/send.d.ts.map +1 -1
  64. package/dist/internal/send.js +62 -51
  65. package/dist/internal/send.js.map +1 -1
  66. package/package.json +11 -9
  67. package/src/BotApi.ts +100 -31
  68. package/src/BotApiError.ts +109 -63
  69. package/src/BotApiTransport.ts +18 -20
  70. package/src/BotApiUrl.ts +6 -8
  71. package/src/Content.ts +14 -14
  72. package/src/Dialog.ts +164 -42
  73. package/src/File.ts +3 -4
  74. package/src/Markup.ts +5 -5
  75. package/src/Send.ts +114 -202
  76. package/src/Text.ts +5 -5
  77. package/src/internal/botApi.gen.ts +6783 -0
  78. package/src/internal/botApi.ts +7 -11
  79. package/src/internal/botApiError.ts +15 -20
  80. package/src/internal/botApiTransport.ts +25 -17
  81. package/src/internal/dialog.ts +109 -26
  82. package/src/internal/file.ts +1 -1
  83. package/src/internal/runner.ts +34 -38
  84. package/src/internal/send.ts +161 -132
  85. package/dist/internal/botApiMethods.gen.d.ts +0 -2110
  86. package/dist/internal/botApiMethods.gen.d.ts.map +0 -1
  87. package/dist/internal/botApiMethods.gen.js +0 -2
  88. package/dist/internal/botApiMethods.gen.js.map +0 -1
  89. package/dist/internal/botApiShape.gen.d.ts +0 -406
  90. package/dist/internal/botApiShape.gen.d.ts.map +0 -1
  91. package/dist/internal/botApiShape.gen.js +0 -2
  92. package/dist/internal/botApiShape.gen.js.map +0 -1
  93. package/dist/internal/botApiTypes.gen.d.ts +0 -3986
  94. package/dist/internal/botApiTypes.gen.d.ts.map +0 -1
  95. package/dist/internal/botApiTypes.gen.js +0 -2
  96. package/dist/internal/botApiTypes.gen.js.map +0 -1
  97. package/src/internal/botApiMethods.gen.ts +0 -2111
  98. package/src/internal/botApiShape.gen.ts +0 -406
  99. package/src/internal/botApiTypes.gen.ts +0 -4264
package/src/BotApi.ts CHANGED
@@ -1,35 +1,54 @@
1
+ import type * as Config from 'effect/Config'
2
+ import type * as ConfigError from 'effect/ConfigError'
1
3
  import type * as BotApiError from './BotApiError.ts'
2
- import type { MethodParams, MethodResults } from './internal/botApiMethods.gen.ts'
3
- import type { BotApiShape } from './internal/botApiShape.gen.ts'
4
- import type * as Types from './internal/botApiTypes.gen.ts'
4
+ import type {
5
+ MethodParams,
6
+ MethodResults,
7
+ BotApi as Service,
8
+ Types,
9
+ } from './internal/botApi.gen.ts'
10
+ import * as HttpClient from '@effect/platform/HttpClient'
5
11
  import * as Context from 'effect/Context'
6
12
  import * as Effect from 'effect/Effect'
13
+ import * as Function from 'effect/Function'
7
14
  import * as Layer from 'effect/Layer'
15
+ import * as Redacted from 'effect/Redacted'
8
16
  import * as BotApiTransport from './BotApiTransport.ts'
17
+ import * as BotApiUrl from './BotApiUrl.ts'
9
18
  import * as internal from './internal/botApi.ts'
10
19
 
11
- export type { MethodParams, MethodResults, Types }
20
+ export type { MethodParams, MethodResults, Service, Types }
12
21
 
13
22
  export class BotApi extends Context.Tag('@grom.js/effect-tg/BotApi')<
14
23
  BotApi,
15
- BotApi.Service
24
+ Service
16
25
  >() {}
17
26
 
18
- export declare namespace BotApi {
19
- export type Service = BotApiShape
27
+ export interface BotApiMethod<TMethod extends keyof MethodParams> {
28
+ (...args: MethodArgs<TMethod>): Effect.Effect<
29
+ MethodResults[TMethod],
30
+ BotApiError.BotApiError,
31
+ never
32
+ >
20
33
  }
21
34
 
22
- export interface Method<
23
- M extends keyof MethodParams,
24
- E = BotApiError.BotApiError | BotApiTransport.BotApiTransportError,
25
- R = never,
26
- > {
27
- (params: MethodParams[M]): Effect.Effect<MethodResults[M], E, R>
28
- }
35
+ export const callMethod: <TMethod extends keyof MethodParams>(
36
+ method: TMethod,
37
+ ...args: MethodArgs<TMethod>
38
+ ) => Effect.Effect<
39
+ MethodResults[TMethod],
40
+ BotApiError.BotApiError,
41
+ BotApi
42
+ > = (
43
+ method: string,
44
+ params: unknown = undefined,
45
+ ) => BotApi.pipe(
46
+ Effect.flatMap(api => (api as any)[method](params)),
47
+ ) as any
29
48
 
30
- export const make: (
31
- transport: BotApiTransport.BotApiTransport.Service,
32
- ) => BotApiShape = internal.make
49
+ export const make: (args: {
50
+ transport: BotApiTransport.Service
51
+ }) => Service = internal.make
33
52
 
34
53
  export const layer: Layer.Layer<
35
54
  BotApi,
@@ -37,19 +56,69 @@ export const layer: Layer.Layer<
37
56
  BotApiTransport.BotApiTransport
38
57
  > = Layer.effect(
39
58
  BotApi,
40
- Effect.andThen(BotApiTransport.BotApiTransport, internal.make),
59
+ Effect.andThen(
60
+ BotApiTransport.BotApiTransport,
61
+ transport => internal.make({ transport }),
62
+ ),
41
63
  )
42
64
 
43
- export const callMethod: <M extends keyof MethodParams>(
44
- method: M,
45
- params: MethodParams[M],
46
- ) => Effect.Effect<
47
- MethodResults[M],
48
- BotApiError.BotApiError | BotApiTransport.BotApiTransportError,
49
- BotApi
50
- > = (
51
- method: string,
52
- params: unknown = undefined,
53
- ) => BotApi.pipe(
54
- Effect.flatMap((api: any) => api[method](params)),
55
- ) as any
65
+ type MethodArgs<TMethod extends keyof MethodParams> =
66
+ void extends MethodParams[TMethod]
67
+ ? [params?: MethodParams[TMethod]]
68
+ : [params: MethodParams[TMethod]]
69
+
70
+ /**
71
+ * Constructs a `BotApi` layer from the given configuration.
72
+ */
73
+ export const layerConfig = (options: {
74
+ /**
75
+ * Bot API token from [@BotFather](https://t.me/BotFather).
76
+ */
77
+ readonly token: Config.Config<Redacted.Redacted>
78
+
79
+ /**
80
+ * Target Bot API environment:
81
+ *
82
+ * - `prod` — production environment
83
+ * - `test` — [test environment](https://core.telegram.org/bots/features#dedicated-test-environment)
84
+ *
85
+ * @default 'prod'
86
+ */
87
+ readonly environment?: 'prod' | 'test'
88
+
89
+ /**
90
+ * A function to transform the underlying `BotApiTransport` before
91
+ * it's used to call Bot API methods.
92
+ *
93
+ * Useful for:
94
+ * - Adding custom middleware (logging, metrics, caching)
95
+ * - Adding custom retry logic or error handling
96
+ * - Integrating with monitoring or debugging tools
97
+ */
98
+ readonly transformTransport?: (transport: BotApiTransport.Service) => BotApiTransport.Service
99
+ }): Layer.Layer<BotApi, ConfigError.ConfigError, HttpClient.HttpClient> => {
100
+ const {
101
+ token,
102
+ environment = 'prod',
103
+ transformTransport,
104
+ } = options
105
+ return Layer.provide(
106
+ layer,
107
+ Layer.effect(
108
+ BotApiTransport.BotApiTransport,
109
+ Effect.all([
110
+ HttpClient.HttpClient,
111
+ token.pipe(
112
+ Effect.map(token => (
113
+ environment === 'prod'
114
+ ? BotApiUrl.makeProd(Redacted.value(token))
115
+ : BotApiUrl.makeTest(Redacted.value(token))
116
+ )),
117
+ ),
118
+ ]).pipe(
119
+ Effect.map(([httpClient, botApiUrl]) => BotApiTransport.make({ httpClient, botApiUrl })),
120
+ Effect.map(transformTransport ?? Function.identity),
121
+ ),
122
+ ),
123
+ )
124
+ }
@@ -1,81 +1,127 @@
1
- import type * as Duration from 'effect/Duration'
2
- import type * as BotApi from './BotApi.ts'
1
+ import type * as HttpBody from '@effect/platform/HttpBody'
2
+ import type * as HttpClientError from '@effect/platform/HttpClientError'
3
+ import type * as BotApiTransport from './BotApiTransport.ts'
3
4
  import * as Data from 'effect/Data'
5
+ import * as Duration from 'effect/Duration'
6
+ import * as Match from 'effect/Match'
7
+ import * as Option from 'effect/Option'
8
+ import * as Predicate from 'effect/Predicate'
9
+ import * as Dialog from './Dialog.ts'
4
10
  import * as internal from './internal/botApiError.ts'
5
11
 
12
+ export const TypeId = '@grom.js/effect-tg/BotApiError'
13
+
14
+ export type TypeId = typeof TypeId
15
+
16
+ export const isBotApiError = (u: unknown): u is BotApiError => Predicate.hasProperty(u, TypeId)
17
+
18
+ export type BotApiError =
19
+ | TransportError
20
+ | MethodFailed
21
+ | GroupUpgraded
22
+ | RateLimited
23
+ | InternalServerError
24
+
6
25
  /**
7
- * Error returned from the Bot API server in case of unsuccessful method call.
26
+ * Error caused by the transport when accessing Bot API.
8
27
  */
9
- export class BotApiError extends Data.TaggedError('@grom.js/effect-tg/BotApiError')<{
10
- code: number
11
- description: string
12
- parameters?: BotApi.Types.ResponseParameters
28
+ export class TransportError extends Data.TaggedError('TransportError')<{
29
+ cause:
30
+ | HttpClientError.HttpClientError
31
+ | HttpBody.HttpBodyError
13
32
  }> {
33
+ readonly [TypeId]: TypeId = TypeId
34
+
14
35
  override get message() {
15
- return `(${this.code}) ${this.description}`
36
+ return Match.value(this.cause).pipe(
37
+ Match.tagsExhaustive({
38
+ RequestError: e => e.message,
39
+ ResponseError: e => e.message,
40
+ HttpBodyError: e => Match.value(e.reason).pipe(
41
+ Match.tagsExhaustive({
42
+ SchemaError: e => e.error.message,
43
+ JsonError: e => `JsonError: ${e.error}`,
44
+ }),
45
+ ),
46
+ }),
47
+ )
16
48
  }
17
49
  }
18
50
 
19
- /**
20
- * Attempts to narrow a {@link BotApiError} to a more specific {@link KnownError}.
21
- *
22
- * **Warning!** Error types are detected based on the error code and message
23
- * returned from the Bot API, which are not guaranteed to be the same in
24
- * the future. If they change, this function may not determine error type
25
- * correctly.
26
- *
27
- * @see {@link https://github.com/tdlib/telegram-bot-api telegram-bot-api source code}
28
- */
29
- export const narrow: (error: BotApiError) => KnownError | BotApiError = internal.narrow
51
+ export class MethodFailed extends Data.TaggedError('MethodFailed')<{
52
+ response: FailureResponse
53
+ possibleReason: MethodFailureReason
54
+ }> {
55
+ readonly [TypeId]: TypeId = TypeId
56
+
57
+ override get message() {
58
+ return `(${this.response.error_code}) ${this.response.description}`
59
+ }
60
+ }
30
61
 
31
- export type KnownError
32
- = | TooManyRequests
33
- | BotBlockedByUser
34
- | MessageNotModified
35
- | ReplyMarkupTooLong
36
- | QueryIdInvalid
37
- | MediaGroupedInvalid
62
+ export class GroupUpgraded extends Data.TaggedError('GroupUpgraded')<{
63
+ response: FailureResponse
64
+ supergroup: Dialog.Supergroup
65
+ }> {
66
+ readonly [TypeId]: TypeId = TypeId
38
67
 
39
- /**
40
- * Flood limit exceeded. Need to wait `retryAfter` before retrying.
41
- */
42
- export class TooManyRequests extends Data.TaggedError('@grom.js/effect-tg/BotApiError/TooManyRequests')<{
43
- cause: BotApiError
68
+ override get message() {
69
+ return `Group has been upgraded to a supergroup with ID ${this.supergroup.id}.`
70
+ }
71
+ }
72
+
73
+ export class RateLimited extends Data.TaggedError('RateLimited')<{
74
+ response: FailureResponse
44
75
  retryAfter: Duration.Duration
45
- }> {}
76
+ }> {
77
+ readonly [TypeId]: TypeId = TypeId
46
78
 
47
- /**
48
- * Bot was blocked by the user.
49
- */
50
- export class BotBlockedByUser extends Data.TaggedError('@grom.js/effect-tg/BotApiError/BotBlockedByUser')<{
51
- cause: BotApiError
52
- }> {}
79
+ override get message() {
80
+ return `Flood limit exceeded. Should wait for ${Duration.format(this.retryAfter)} before retrying.`
81
+ }
82
+ }
53
83
 
54
- /**
55
- * Message was not modified as its content and reply markup
56
- * are exactly the same as the current one.
57
- */
58
- export class MessageNotModified extends Data.TaggedError('@grom.js/effect-tg/BotApiError/MessageNotModified')<{
59
- cause: BotApiError
60
- }> {}
84
+ export class InternalServerError extends Data.TaggedError('InternalServerError')<{
85
+ response: FailureResponse
86
+ }> {
87
+ readonly [TypeId]: TypeId = TypeId
88
+ }
61
89
 
62
- /**
63
- * Message reply markup is too long.
64
- */
65
- export class ReplyMarkupTooLong extends Data.TaggedError('@grom.js/effect-tg/BotApiError/ReplyMarkupTooLong')<{
66
- cause: BotApiError
67
- }> {}
90
+ export const fromResponse = (response: FailureResponse): BotApiError => {
91
+ if (response.error_code === 429 && response.parameters?.retry_after != null) {
92
+ return new RateLimited({
93
+ response,
94
+ retryAfter: Duration.seconds(response.parameters.retry_after),
95
+ })
96
+ }
97
+ if (response.error_code === 400 && response.parameters?.migrate_to_chat_id != null) {
98
+ return new GroupUpgraded({
99
+ response,
100
+ supergroup: Dialog.supergroup(
101
+ Option.getOrThrow(
102
+ Dialog.decodePeerId('supergroup', response.parameters.migrate_to_chat_id),
103
+ ),
104
+ ),
105
+ })
106
+ }
107
+ if (response.error_code >= 500) {
108
+ return new InternalServerError({ response })
109
+ }
110
+ return new MethodFailed({
111
+ response,
112
+ possibleReason: internal.guessReason({
113
+ code: response.error_code,
114
+ description: response.description,
115
+ }),
116
+ })
117
+ }
68
118
 
69
- /**
70
- * Query has expired, or ID is invalid.
71
- */
72
- export class QueryIdInvalid extends Data.TaggedError('@grom.js/effect-tg/BotApiError/QueryIdInvalid')<{
73
- cause: BotApiError
74
- }> {}
119
+ export type MethodFailureReason =
120
+ | 'Unknown'
121
+ | 'BotBlockedByUser'
122
+ | 'MessageNotModified'
123
+ | 'ReplyMarkupTooLong'
124
+ | 'QueryIdInvalid'
125
+ | 'MediaGroupedInvalid'
75
126
 
76
- /**
77
- * Invalid combination of media types in the media group.
78
- */
79
- export class MediaGroupedInvalid extends Data.TaggedError('@grom.js/effect-tg/BotApiError/MediaGroupedInvalid')<{
80
- cause: BotApiError
81
- }> {}
127
+ type FailureResponse = Extract<BotApiTransport.BotApiResponse, { ok: false }>
@@ -1,7 +1,7 @@
1
- import type * as Types from './internal/botApiTypes.gen.ts'
1
+ import type * as BotApi from './BotApi.ts'
2
+ import type * as BotApiError from './BotApiError.ts'
2
3
  import * as HttpClient from '@effect/platform/HttpClient'
3
4
  import * as Context from 'effect/Context'
4
- import * as Data from 'effect/Data'
5
5
  import * as Effect from 'effect/Effect'
6
6
  import * as Layer from 'effect/Layer'
7
7
  import * as BotApiUrl from './BotApiUrl.ts'
@@ -9,23 +9,21 @@ import * as internal from './internal/botApiTransport.ts'
9
9
 
10
10
  export class BotApiTransport extends Context.Tag('@grom.js/effect-tg/BotApiTransport')<
11
11
  BotApiTransport,
12
- BotApiTransport.Service
12
+ Service
13
13
  >() {}
14
14
 
15
- export declare namespace BotApiTransport {
16
- export interface Service {
17
- sendRequest: (
18
- method: string,
19
- params: unknown,
20
- ) => Effect.Effect<BotApiResponse, BotApiTransportError>
21
- }
15
+ export interface Service {
16
+ sendRequest: (
17
+ method: string,
18
+ params: unknown,
19
+ ) => Effect.Effect<BotApiResponse, BotApiError.TransportError>
22
20
  }
23
21
 
24
22
  /**
25
23
  * @see https://core.telegram.org/bots/api#making-requests
26
24
  */
27
- export type BotApiResponse
28
- = {
25
+ export type BotApiResponse =
26
+ | {
29
27
  ok: true
30
28
  result: unknown
31
29
  description?: string
@@ -33,15 +31,13 @@ export type BotApiResponse
33
31
  ok: false
34
32
  error_code: number
35
33
  description: string
36
- parameters?: Types.ResponseParameters
34
+ parameters?: BotApi.Types.ResponseParameters
37
35
  }
38
36
 
39
- /**
40
- * Error caused by the transport when accessing Bot API.
41
- */
42
- export class BotApiTransportError extends Data.TaggedError('@grom.js/effect-tg/BotApiTransport/BotApiTransportError')<{
43
- cause: unknown
44
- }> {}
37
+ export const make: (options: {
38
+ httpClient: HttpClient.HttpClient
39
+ botApiUrl: BotApiUrl.Service
40
+ }) => Service = internal.make
45
41
 
46
42
  export const layer: Layer.Layer<
47
43
  BotApiTransport,
@@ -50,6 +46,8 @@ export const layer: Layer.Layer<
50
46
  > = Layer.effect(
51
47
  BotApiTransport,
52
48
  Effect.all([HttpClient.HttpClient, BotApiUrl.BotApiUrl]).pipe(
53
- Effect.andThen(reqs => internal.make(...reqs)),
49
+ Effect.andThen(([httpClient, botApiUrl]) => (
50
+ internal.make({ httpClient, botApiUrl })),
51
+ ),
54
52
  ),
55
53
  )
package/src/BotApiUrl.ts CHANGED
@@ -2,24 +2,22 @@ import * as Context from 'effect/Context'
2
2
 
3
3
  export class BotApiUrl extends Context.Tag('@grom.js/effect-tg/BotApiUrl')<
4
4
  BotApiUrl,
5
- BotApiUrl.Service
5
+ Service
6
6
  >() {}
7
7
 
8
- export declare namespace BotApiUrl {
9
- export interface Service {
10
- toMethod: (method: string) => URL
11
- toFile: (filePath: string) => URL
12
- }
8
+ export interface Service {
9
+ toMethod: (method: string) => URL
10
+ toFile: (filePath: string) => URL
13
11
  }
14
12
 
15
- export const makeProd = (token: string): BotApiUrl.Service => (
13
+ export const makeProd = (token: string): Service => (
16
14
  {
17
15
  toMethod: (method: string) => new URL(`https://api.telegram.org/bot${token}/${method}`),
18
16
  toFile: (filePath: string) => new URL(`https://api.telegram.org/file/bot${token}/${filePath}`),
19
17
  }
20
18
  )
21
19
 
22
- export const makeTest = (token: string): BotApiUrl.Service => (
20
+ export const makeTest = (token: string): Service => (
23
21
  {
24
22
  toMethod: (method: string) => new URL(`https://api.telegram.org/bot${token}/test/${method}`),
25
23
  // TODO: make sure this works in test environment
package/src/Content.ts CHANGED
@@ -12,20 +12,20 @@ import * as Option from 'effect/Option'
12
12
  * @todo Invoices (fiat & stars)
13
13
  * @see {@link https://core.telegram.org/tdlib/docs/classtd_1_1td__api_1_1_input_message_content.html TDLib • td_api.InputMessageContent}
14
14
  */
15
- export type Content
16
- = | Text
17
- | Photo
18
- | Audio
19
- | Document
20
- | Video
21
- | Animation
22
- | Voice
23
- | VideoNote
24
- | Location
25
- | Venue
26
- | Contact
27
- | Dice
28
- | Sticker
15
+ export type Content =
16
+ | Text
17
+ | Photo
18
+ | Audio
19
+ | Document
20
+ | Video
21
+ | Animation
22
+ | Voice
23
+ | VideoNote
24
+ | Location
25
+ | Venue
26
+ | Contact
27
+ | Dice
28
+ | Sticker
29
29
 
30
30
  /**
31
31
  * Content of a text message.