@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
package/src/Bot.ts ADDED
@@ -0,0 +1,16 @@
1
+ import type * as Effect from 'effect/Effect'
2
+ import type * as BotApi from './BotApi.ts'
3
+ import * as Context from 'effect/Context'
4
+
5
+ export type Bot<E = never, R = never> = Effect.Effect<void, E, R | Update>
6
+
7
+ export class Update extends Context.Tag('@grom.js/effect-tg/Bot/Update')<
8
+ Update,
9
+ BotApi.Types.Update
10
+ >() {}
11
+
12
+ export interface Middleware {
13
+ <E, R>(self: Bot<E, R>): Bot<any, any>
14
+ }
15
+
16
+ export const middleware: <M extends Middleware>(middleware: M) => M = mw => mw
package/src/BotApi.ts ADDED
@@ -0,0 +1,68 @@
1
+ import type { MethodParams, MethodResults } from './internal/botApiMethods.gen.ts'
2
+ import type { BotApiShape } from './internal/botApiShape.gen.ts'
3
+ import type * as Types from './internal/botApiTypes.gen.ts'
4
+ import * as Context from 'effect/Context'
5
+ import * as Data from 'effect/Data'
6
+ import * as Effect from 'effect/Effect'
7
+ import * as Layer from 'effect/Layer'
8
+ import * as BotApiTransport from './BotApiTransport.ts'
9
+ import * as internal from './internal/botApi.ts'
10
+
11
+ export type { MethodParams, MethodResults, Types }
12
+
13
+ export class BotApi extends Context.Tag('@grom.js/effect-tg/BotApi')<
14
+ BotApi,
15
+ BotApi.Service
16
+ >() {}
17
+
18
+ export declare namespace BotApi {
19
+ export type Service = BotApiShape
20
+ }
21
+
22
+ export interface Method<
23
+ M extends keyof MethodParams,
24
+ E = BotApiError | BotApiTransport.BotApiTransportError,
25
+ R = never,
26
+ > {
27
+ (params: MethodParams[M]): Effect.Effect<MethodResults[M], E, R>
28
+ }
29
+
30
+ /**
31
+ * Error returned from the Bot API server in case of unsuccessful method call.
32
+ */
33
+ export class BotApiError extends Data.TaggedError('@grom.js/effect-tg/BotApi/BotApiError')<{
34
+ code: number
35
+ description: string
36
+ parameters?: Types.ResponseParameters
37
+ }> {
38
+ override get message() {
39
+ return `(${this.code}) ${this.description}`
40
+ }
41
+ }
42
+
43
+ export const make: (
44
+ transport: BotApiTransport.BotApiTransport.Service,
45
+ ) => BotApiShape = internal.make
46
+
47
+ export const layer: Layer.Layer<
48
+ BotApi,
49
+ never,
50
+ BotApiTransport.BotApiTransport
51
+ > = Layer.effect(
52
+ BotApi,
53
+ Effect.andThen(BotApiTransport.BotApiTransport, internal.make),
54
+ )
55
+
56
+ export const callMethod: <M extends keyof MethodParams>(
57
+ method: M,
58
+ params: MethodParams[M],
59
+ ) => Effect.Effect<
60
+ MethodResults[M],
61
+ BotApiError | BotApiTransport.BotApiTransportError,
62
+ BotApi
63
+ > = (
64
+ method: string,
65
+ params: unknown = undefined,
66
+ ) => BotApi.pipe(
67
+ Effect.flatMap((api: any) => api[method](params)),
68
+ ) as any
@@ -0,0 +1,55 @@
1
+ import type * as Types from './internal/botApiTypes.gen.ts'
2
+ import * as HttpClient from '@effect/platform/HttpClient'
3
+ import * as Context from 'effect/Context'
4
+ import * as Data from 'effect/Data'
5
+ import * as Effect from 'effect/Effect'
6
+ import * as Layer from 'effect/Layer'
7
+ import * as BotApiUrl from './BotApiUrl.ts'
8
+ import * as internal from './internal/botApiTransport.ts'
9
+
10
+ export class BotApiTransport extends Context.Tag('@grom.js/effect-tg/BotApiTransport')<
11
+ BotApiTransport,
12
+ BotApiTransport.Service
13
+ >() {}
14
+
15
+ export declare namespace BotApiTransport {
16
+ export interface Service {
17
+ sendRequest: (
18
+ method: string,
19
+ params: unknown,
20
+ ) => Effect.Effect<BotApiResponse, BotApiTransportError>
21
+ }
22
+ }
23
+
24
+ /**
25
+ * @see https://core.telegram.org/bots/api#making-requests
26
+ */
27
+ export type BotApiResponse
28
+ = {
29
+ ok: true
30
+ result: unknown
31
+ description?: string
32
+ } | {
33
+ ok: false
34
+ error_code: number
35
+ description: string
36
+ parameters?: Types.ResponseParameters
37
+ }
38
+
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
+ }> {}
45
+
46
+ export const layer: Layer.Layer<
47
+ BotApiTransport,
48
+ never,
49
+ HttpClient.HttpClient | BotApiUrl.BotApiUrl
50
+ > = Layer.effect(
51
+ BotApiTransport,
52
+ Effect.all([HttpClient.HttpClient, BotApiUrl.BotApiUrl]).pipe(
53
+ Effect.andThen(reqs => internal.make(...reqs)),
54
+ ),
55
+ )
@@ -0,0 +1,28 @@
1
+ import * as Context from 'effect/Context'
2
+
3
+ export class BotApiUrl extends Context.Tag('@grom.js/effect-tg/BotApiUrl')<
4
+ BotApiUrl,
5
+ BotApiUrl.Service
6
+ >() {}
7
+
8
+ export declare namespace BotApiUrl {
9
+ export interface Service {
10
+ toMethod: (method: string) => URL
11
+ toFile: (filePath: string) => URL
12
+ }
13
+ }
14
+
15
+ export const makeProd = (token: string): BotApiUrl.Service => (
16
+ {
17
+ toMethod: (method: string) => new URL(`https://api.telegram.org/bot${token}/${method}`),
18
+ toFile: (filePath: string) => new URL(`https://api.telegram.org/file/bot${token}/${filePath}`),
19
+ }
20
+ )
21
+
22
+ export const makeTest = (token: string): BotApiUrl.Service => (
23
+ {
24
+ toMethod: (method: string) => new URL(`https://api.telegram.org/bot${token}/test/${method}`),
25
+ // TODO: make sure this works in test environment
26
+ toFile: (filePath: string) => new URL(`https://api.telegram.org/file/bot${token}/${filePath}`),
27
+ }
28
+ )
package/src/Content.ts ADDED
@@ -0,0 +1,410 @@
1
+ import type * as Duration from 'effect/Duration'
2
+ import type * as File from './File.ts'
3
+ import type * as LinkPreview from './LinkPreview.ts'
4
+ import type * as Text_ from './Text.ts'
5
+ import * as Data from 'effect/Data'
6
+ import * as Option from 'effect/Option'
7
+
8
+ /**
9
+ * Content of a message to be sent.
10
+ *
11
+ * @todo Paid media
12
+ * @todo Invoices (fiat & stars)
13
+ * @see {@link https://core.telegram.org/tdlib/docs/classtd_1_1td__api_1_1_input_message_content.html TDLib • td_api.InputMessageContent}
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
29
+
30
+ /**
31
+ * Content of a text message.
32
+ *
33
+ * @see {@link https://core.telegram.org/tdlib/docs/classtd_1_1td__api_1_1input_message_text.html TDLib • td_api.inputMessageText}
34
+ * @see {@link https://core.telegram.org/bots/api#sendmessage Bot API • sendMessage}
35
+ */
36
+ export class Text extends Data.TaggedClass('Text')<{
37
+ text: Text_.Text
38
+ linkPreview: Option.Option<LinkPreview.LinkPreview>
39
+ }> {}
40
+
41
+ /**
42
+ * Content of a photo message.
43
+ *
44
+ * @see {@link https://core.telegram.org/tdlib/docs/classtd_1_1td__api_1_1input_message_photo.html TDLib • td_api.inputMessagePhoto}
45
+ * @see {@link https://core.telegram.org/bots/api#sendphoto Bot API • sendPhoto}
46
+ */
47
+ export class Photo extends Data.TaggedClass('Photo')<{
48
+ file: File.FileId | File.External | File.InputFile
49
+ caption: Option.Option<Text_.Text>
50
+ layout: 'caption-above' | 'caption-below'
51
+ spoiler: boolean
52
+ }> {}
53
+
54
+ /**
55
+ * Content of an audio message.
56
+ *
57
+ * @see {@link https://core.telegram.org/tdlib/docs/classtd_1_1td__api_1_1input_message_audio.html TDLib • td_api.inputMessageAudio}
58
+ * @see {@link https://core.telegram.org/bots/api#sendaudio Bot API • sendAudio}
59
+ */
60
+ export class Audio extends Data.TaggedClass('Audio')<{
61
+ file: File.FileId | File.External | File.InputFile
62
+ caption: Option.Option<Text_.Text>
63
+ duration: Option.Option<Duration.Duration>
64
+ performer: Option.Option<string>
65
+ title: Option.Option<string>
66
+ thumbnail: Option.Option<File.InputFile>
67
+ }> {}
68
+
69
+ /**
70
+ * Content of a document message.
71
+ *
72
+ * @see {@link https://core.telegram.org/tdlib/docs/classtd_1_1td__api_1_1input_message_document.html TDLib • td_api.inputMessageDocument}
73
+ * @see {@link https://core.telegram.org/bots/api#senddocument Bot API • sendDocument}
74
+ */
75
+ export class Document extends Data.TaggedClass('Document')<{
76
+ file: File.FileId | File.External | File.InputFile
77
+ caption: Option.Option<Text_.Text>
78
+ thumbnail: Option.Option<File.InputFile>
79
+ contentTypeDetection: boolean
80
+ }> {}
81
+
82
+ /**
83
+ * Content of a video message.
84
+ *
85
+ * @see {@link https://core.telegram.org/tdlib/docs/classtd_1_1td__api_1_1input_message_video.html TDLib • td_api.inputMessageVideo}
86
+ * @see {@link https://core.telegram.org/bots/api#sendvideo Bot API • sendVideo}
87
+ */
88
+ export class Video extends Data.TaggedClass('Video')<{
89
+ file: File.FileId | File.External | File.InputFile
90
+ caption: Option.Option<Text_.Text>
91
+ layout: 'caption-above' | 'caption-below'
92
+ spoiler: boolean
93
+ duration: Option.Option<Duration.Duration>
94
+ width: Option.Option<number>
95
+ height: Option.Option<number>
96
+ thumbnail: Option.Option<File.InputFile>
97
+ cover: Option.Option<File.FileId | File.External | File.InputFile>
98
+ startAt: Option.Option<Duration.Duration>
99
+ supportsStreaming: boolean
100
+ }> {}
101
+
102
+ /**
103
+ * Content of an animation message (GIF or video without sound).
104
+ *
105
+ * @see {@link https://core.telegram.org/tdlib/docs/classtd_1_1td__api_1_1input_message_animation.html TDLib • td_api.inputMessageAnimation}
106
+ * @see {@link https://core.telegram.org/bots/api#sendanimation Bot API • sendAnimation}
107
+ */
108
+ export class Animation extends Data.TaggedClass('Animation')<{
109
+ file: File.FileId | File.External | File.InputFile
110
+ caption: Option.Option<Text_.Text>
111
+ layout: 'caption-above' | 'caption-below'
112
+ spoiler: boolean
113
+ duration: Option.Option<Duration.Duration>
114
+ width: Option.Option<number>
115
+ height: Option.Option<number>
116
+ thumbnail: Option.Option<File.InputFile>
117
+ }> {}
118
+
119
+ /**
120
+ * Content of a voice note message.
121
+ *
122
+ * @see {@link https://core.telegram.org/tdlib/docs/classtd_1_1td__api_1_1input_message_voice_note.html TDLib • td_api.inputMessageVoiceNote}
123
+ * @see {@link https://core.telegram.org/bots/api#sendvoice Bot API • sendVoice}
124
+ */
125
+ export class Voice extends Data.TaggedClass('Voice')<{
126
+ file: File.FileId | File.External | File.InputFile
127
+ caption: Option.Option<Text_.Text>
128
+ duration: Option.Option<Duration.Duration>
129
+ }> {}
130
+
131
+ /**
132
+ * Content of a video note message (round video).
133
+ *
134
+ * @see {@link https://core.telegram.org/tdlib/docs/classtd_1_1td__api_1_1input_message_video_note.html TDLib • td_api.inputMessageVideoNote}
135
+ * @see {@link https://core.telegram.org/bots/api#sendvideonote Bot API • sendVideoNote}
136
+ */
137
+ export class VideoNote extends Data.TaggedClass('VideoNote')<{
138
+ file: File.FileId | File.InputFile
139
+ duration: Option.Option<Duration.Duration>
140
+ diameter: Option.Option<number>
141
+ thumbnail: Option.Option<File.InputFile>
142
+ }> {}
143
+
144
+ /**
145
+ * Content of a location message.
146
+ *
147
+ * @see {@link https://core.telegram.org/tdlib/docs/classtd_1_1td__api_1_1input_message_location.html TDLib • td_api.inputMessageLocation}
148
+ * @see {@link https://core.telegram.org/bots/api#sendlocation Bot API • sendLocation}
149
+ */
150
+ export class Location extends Data.TaggedClass('Location')<{
151
+ latitude: number
152
+ longitude: number
153
+ uncertaintyRadius: Option.Option<number>
154
+ livePeriod: Option.Option<Duration.Duration>
155
+ heading: Option.Option<number>
156
+ proximityAlertRadius: Option.Option<number>
157
+ }> {}
158
+
159
+ /**
160
+ * Content of a venue message.
161
+ *
162
+ * @see {@link https://core.telegram.org/tdlib/docs/classtd_1_1td__api_1_1input_message_venue.html TDLib • td_api.inputMessageVenue}
163
+ * @see {@link https://core.telegram.org/bots/api#sendvenue Bot API • sendVenue}
164
+ */
165
+ export class Venue extends Data.TaggedClass('Venue')<{
166
+ latitude: number
167
+ longitude: number
168
+ title: string
169
+ address: string
170
+ foursquareId: Option.Option<string>
171
+ foursquareType: Option.Option<string>
172
+ googlePlaceId: Option.Option<string>
173
+ googlePlaceType: Option.Option<string>
174
+ }> {}
175
+
176
+ /**
177
+ * Content of a contact message.
178
+ *
179
+ * @see {@link https://core.telegram.org/tdlib/docs/classtd_1_1td__api_1_1input_message_contact.html TDLib • td_api.inputMessageContact}
180
+ * @see {@link https://core.telegram.org/bots/api#sendcontact Bot API • sendContact}
181
+ */
182
+ export class Contact extends Data.TaggedClass('Contact')<{
183
+ phoneNumber: string
184
+ firstName: string
185
+ lastName: Option.Option<string>
186
+ vcard: Option.Option<string>
187
+ }> {}
188
+
189
+ /**
190
+ * Content of a dice message.
191
+ *
192
+ * @see {@link https://core.telegram.org/tdlib/docs/classtd_1_1td__api_1_1input_message_dice.html TDLib • td_api.inputMessageDice}
193
+ * @see {@link https://core.telegram.org/bots/api#senddice Bot API • sendDice}
194
+ */
195
+ export class Dice extends Data.TaggedClass('Dice')<{
196
+ emoji: '🎲' | '🎯' | '🏀' | '⚽' | '🎳' | '🎰'
197
+ }> {}
198
+
199
+ /**
200
+ * Content of a sticker message.
201
+ *
202
+ * @see {@link https://core.telegram.org/tdlib/docs/classtd_1_1td__api_1_1input_message_sticker.html TDLib • td_api.inputMessageSticker}
203
+ * @see {@link https://core.telegram.org/bots/api#sendsticker Bot API • sendSticker}
204
+ */
205
+ export class Sticker extends Data.TaggedClass('Sticker')<{
206
+ file: File.FileId | File.External | File.InputFile
207
+ emoji: Option.Option<string>
208
+ }> {}
209
+
210
+ // ——— Constructors ——————————————————————————————————————————————————————————
211
+
212
+ export const text = (
213
+ text: Text_.Text,
214
+ options?: {
215
+ linkPreview?: LinkPreview.LinkPreview
216
+ },
217
+ ) => new Text({
218
+ text,
219
+ linkPreview: Option.fromNullable(options?.linkPreview),
220
+ })
221
+
222
+ export const photo = (
223
+ file: File.FileId | File.External | File.InputFile,
224
+ options: {
225
+ caption?: Text_.Text
226
+ layout?: 'caption-above' | 'caption-below'
227
+ spoiler?: boolean
228
+ },
229
+ ): Photo => new Photo({
230
+ file,
231
+ caption: Option.fromNullable(options.caption),
232
+ layout: options.layout ?? 'caption-below',
233
+ spoiler: options.spoiler ?? false,
234
+ })
235
+
236
+ export const audio = (
237
+ file: File.FileId | File.External | File.InputFile,
238
+ options: {
239
+ caption?: Text_.Text
240
+ duration?: Duration.Duration
241
+ performer?: string
242
+ title?: string
243
+ thumbnail?: File.InputFile
244
+ },
245
+ ): Audio => new Audio({
246
+ file,
247
+ caption: Option.fromNullable(options.caption),
248
+ duration: Option.fromNullable(options.duration),
249
+ performer: Option.fromNullable(options.performer),
250
+ title: Option.fromNullable(options.title),
251
+ thumbnail: Option.fromNullable(options.thumbnail),
252
+ })
253
+
254
+ export const document = (
255
+ file: File.FileId | File.External | File.InputFile,
256
+ options: {
257
+ caption?: Text_.Text
258
+ thumbnail?: File.InputFile
259
+ contentTypeDetection?: boolean
260
+ },
261
+ ): Document => new Document({
262
+ file,
263
+ caption: Option.fromNullable(options.caption),
264
+ thumbnail: Option.fromNullable(options.thumbnail),
265
+ contentTypeDetection: options.contentTypeDetection ?? false,
266
+ })
267
+
268
+ export const video = (
269
+ file: File.FileId | File.External | File.InputFile,
270
+ options: {
271
+ caption?: Text_.Text
272
+ layout?: 'caption-above' | 'caption-below'
273
+ spoiler?: boolean
274
+ duration?: Duration.Duration
275
+ width?: number
276
+ height?: number
277
+ thumbnail?: File.InputFile
278
+ cover?: File.FileId | File.External | File.InputFile
279
+ startAt?: Duration.Duration
280
+ supportsStreaming?: boolean
281
+ },
282
+ ): Video => new Video({
283
+ file,
284
+ caption: Option.fromNullable(options.caption),
285
+ layout: options.layout ?? 'caption-below',
286
+ spoiler: options.spoiler ?? false,
287
+ duration: Option.fromNullable(options.duration),
288
+ width: Option.fromNullable(options.width),
289
+ height: Option.fromNullable(options.height),
290
+ thumbnail: Option.fromNullable(options.thumbnail),
291
+ cover: Option.fromNullable(options.cover),
292
+ startAt: Option.fromNullable(options.startAt),
293
+ supportsStreaming: options.supportsStreaming ?? false,
294
+ })
295
+
296
+ export const animation = (
297
+ file: File.FileId | File.External | File.InputFile,
298
+ options: {
299
+ caption?: Text_.Text
300
+ layout?: 'caption-above' | 'caption-below'
301
+ spoiler?: boolean
302
+ duration?: Duration.Duration
303
+ width?: number
304
+ height?: number
305
+ thumbnail?: File.InputFile
306
+ },
307
+ ): Animation => new Animation({
308
+ file,
309
+ caption: Option.fromNullable(options.caption),
310
+ layout: options.layout ?? 'caption-below',
311
+ spoiler: options.spoiler ?? false,
312
+ duration: Option.fromNullable(options.duration),
313
+ width: Option.fromNullable(options.width),
314
+ height: Option.fromNullable(options.height),
315
+ thumbnail: Option.fromNullable(options.thumbnail),
316
+ })
317
+
318
+ export const voice = (
319
+ file: File.FileId | File.External | File.InputFile,
320
+ options: {
321
+ caption?: Text_.Text
322
+ duration?: Duration.Duration
323
+ },
324
+ ): Voice => new Voice({
325
+ file,
326
+ caption: Option.fromNullable(options.caption),
327
+ duration: Option.fromNullable(options.duration),
328
+ })
329
+
330
+ export const videoNote = (
331
+ file: File.FileId | File.InputFile,
332
+ options: {
333
+ duration?: Duration.Duration
334
+ diameter?: number
335
+ thumbnail?: File.InputFile
336
+ },
337
+ ): VideoNote => new VideoNote({
338
+ file,
339
+ duration: Option.fromNullable(options.duration),
340
+ diameter: Option.fromNullable(options.diameter),
341
+ thumbnail: Option.fromNullable(options.thumbnail),
342
+ })
343
+
344
+ export const location = (options: {
345
+ latitude: number
346
+ longitude: number
347
+ uncertaintyRadius?: number
348
+ }): Location => new Location({
349
+ latitude: options.latitude,
350
+ longitude: options.longitude,
351
+ uncertaintyRadius: Option.fromNullable(options.uncertaintyRadius),
352
+ livePeriod: Option.none(),
353
+ heading: Option.none(),
354
+ proximityAlertRadius: Option.none(),
355
+ })
356
+
357
+ export const liveLocation = (options: {
358
+ latitude: number
359
+ longitude: number
360
+ uncertaintyRadius?: number
361
+ livePeriod: Duration.Duration
362
+ heading?: number
363
+ proximityAlertRadius?: number
364
+ }): Location => new Location({
365
+ latitude: options.latitude,
366
+ longitude: options.longitude,
367
+ uncertaintyRadius: Option.fromNullable(options.uncertaintyRadius),
368
+ livePeriod: Option.some(options.livePeriod),
369
+ heading: Option.fromNullable(options.heading),
370
+ proximityAlertRadius: Option.fromNullable(options.proximityAlertRadius),
371
+ })
372
+
373
+ export const venue = (options: {
374
+ latitude: number
375
+ longitude: number
376
+ title: string
377
+ address: string
378
+ foursquareId?: string
379
+ foursquareType?: string
380
+ googlePlaceId?: string
381
+ googlePlaceType?: string
382
+ }): Venue => new Venue({
383
+ latitude: options.latitude,
384
+ longitude: options.longitude,
385
+ title: options.title,
386
+ address: options.address,
387
+ foursquareId: Option.fromNullable(options.foursquareId),
388
+ foursquareType: Option.fromNullable(options.foursquareType),
389
+ googlePlaceId: Option.fromNullable(options.googlePlaceId),
390
+ googlePlaceType: Option.fromNullable(options.googlePlaceType),
391
+ })
392
+
393
+ export const contact = (options: {
394
+ phoneNumber: string
395
+ firstName: string
396
+ lastName?: string
397
+ vcard?: string
398
+ }): Contact => new Contact({
399
+ phoneNumber: options.phoneNumber,
400
+ firstName: options.firstName,
401
+ lastName: Option.fromNullable(options.lastName),
402
+ vcard: Option.fromNullable(options.vcard),
403
+ })
404
+
405
+ export const dice = (emoji: Dice['emoji']): Dice => new Dice({ emoji })
406
+
407
+ export const sticker = (
408
+ file: File.FileId | File.External | File.InputFile,
409
+ emoji?: string,
410
+ ): Sticker => new Sticker({ file, emoji: Option.fromNullable(emoji) })
package/src/Dialog.ts ADDED
@@ -0,0 +1,54 @@
1
+ import * as Data from 'effect/Data'
2
+ import * as internal from './internal/dialog.ts'
3
+
4
+ export type Dialog
5
+ = | UserId
6
+ | GroupId
7
+ | ChannelId
8
+ | SupergroupId
9
+ | PublicChannel
10
+ | PublicSupergroup
11
+ | ForumTopic
12
+ | ChannelDm
13
+
14
+ export class UserId extends internal.PeerId({
15
+ tag: 'UserId',
16
+ isValid: id => (id >= 1 && id <= 0xFFFFFFFFFF),
17
+ toDialogId: id => id,
18
+ }) {}
19
+
20
+ export class GroupId extends internal.PeerId({
21
+ tag: 'GroupId',
22
+ isValid: id => (id >= 1 && id <= 999999999999),
23
+ toDialogId: id => -id,
24
+ }) {}
25
+
26
+ export class ChannelId extends internal.PeerId({
27
+ tag: 'ChannelId',
28
+ isValid: id => (id >= 1 && id <= 997852516352),
29
+ toDialogId: id => -(1000000000000 + id),
30
+ }) {}
31
+
32
+ export class SupergroupId extends internal.PeerId({
33
+ tag: 'SupergroupId',
34
+ isValid: id => (id >= 1 && id <= 997852516352),
35
+ toDialogId: id => -(1000000000000 + id),
36
+ }) {}
37
+
38
+ export class PublicChannel extends Data.TaggedClass('PublicChannel')<{
39
+ username: string
40
+ }> {}
41
+
42
+ export class PublicSupergroup extends Data.TaggedClass('PublicSupergroup')<{
43
+ username: string
44
+ }> {}
45
+
46
+ export class ForumTopic extends Data.TaggedClass('ForumTopic')<{
47
+ forum: SupergroupId | PublicSupergroup
48
+ topicId: number
49
+ }> {}
50
+
51
+ export class ChannelDm extends Data.TaggedClass('ChannelDm')<{
52
+ channel: ChannelId | PublicChannel
53
+ userId: number
54
+ }> {}
package/src/File.ts ADDED
@@ -0,0 +1,45 @@
1
+ import type * as Stream from 'effect/Stream'
2
+ import * as HttpClient from '@effect/platform/HttpClient'
3
+ import * as Brand from 'effect/Brand'
4
+ import * as Data from 'effect/Data'
5
+ import * as Effect from 'effect/Effect'
6
+ import * as BotApi from './BotApi.ts'
7
+ import * as BotApiUrl from './BotApiUrl.ts'
8
+
9
+ export type FileId = string & Brand.Brand<'FileId'>
10
+ export const FileId = Brand.nominal<FileId>()
11
+
12
+ export type External = URL & Brand.Brand<'External'>
13
+ export const External = Brand.nominal<External>()
14
+
15
+ export class InputFile extends Data.TaggedClass('InputFile')<{
16
+ stream: Stream.Stream<Uint8Array>
17
+ filename: string
18
+ mimeType?: string
19
+ }> {}
20
+
21
+ /**
22
+ * @internal
23
+ */
24
+ const downloadRequest = Effect.fnUntraced(
25
+ function* (fileId: FileId) {
26
+ const file = yield* BotApi.callMethod('getFile', { file_id: fileId })
27
+ if (file.file_path == null) {
28
+ return yield* Effect.die(new Error(`Bot API returned no file path for file "${fileId}".`))
29
+ }
30
+ const url = yield* BotApiUrl.BotApiUrl
31
+ return yield* HttpClient.get(url.toFile(file.file_path))
32
+ },
33
+ )
34
+
35
+ export const download = (fileId: FileId) => (
36
+ downloadRequest(fileId).pipe(
37
+ Effect.flatMap(request => request.arrayBuffer),
38
+ )
39
+ )
40
+
41
+ export const downloadStream = (fileId: FileId) => (
42
+ downloadRequest(fileId).pipe(
43
+ Effect.andThen(request => request.stream),
44
+ )
45
+ )