@grom.js/effect-tg 0.13.0 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -12
- package/dist/Bot.d.ts +2 -3
- package/dist/Bot.d.ts.map +1 -1
- package/dist/Bot.js +1 -2
- package/dist/Bot.js.map +1 -1
- package/dist/BotApi.d.ts +6 -6
- package/dist/BotApi.d.ts.map +1 -1
- package/dist/BotApi.js +1 -2
- package/dist/BotApi.js.map +1 -1
- package/dist/BotApiError.d.ts +15 -19
- package/dist/BotApiError.d.ts.map +1 -1
- package/dist/BotApiError.js +10 -12
- package/dist/BotApiError.js.map +1 -1
- package/dist/BotApiTransport.d.ts +5 -8
- package/dist/BotApiTransport.d.ts.map +1 -1
- package/dist/BotApiTransport.js +1 -2
- package/dist/BotApiTransport.js.map +1 -1
- package/dist/BotApiUrl.d.ts +6 -9
- package/dist/BotApiUrl.d.ts.map +1 -1
- package/dist/BotApiUrl.js +1 -2
- package/dist/BotApiUrl.js.map +1 -1
- package/dist/Content.d.ts +89 -155
- package/dist/Content.d.ts.map +1 -1
- package/dist/Content.js +26 -119
- package/dist/Content.js.map +1 -1
- package/dist/Dialog.d.ts +28 -58
- package/dist/Dialog.d.ts.map +1 -1
- package/dist/Dialog.js +29 -48
- package/dist/Dialog.js.map +1 -1
- package/dist/File.d.ts +11 -6
- package/dist/File.d.ts.map +1 -1
- package/dist/File.js +19 -3
- package/dist/File.js.map +1 -1
- package/dist/Markup.d.ts +8 -21
- package/dist/Markup.d.ts.map +1 -1
- package/dist/Markup.js +12 -13
- package/dist/Markup.js.map +1 -1
- package/dist/Runner.d.ts +3 -1
- package/dist/Runner.d.ts.map +1 -1
- package/dist/Runner.js.map +1 -1
- package/dist/Send.d.ts +14 -14
- package/dist/Send.d.ts.map +1 -1
- package/dist/Send.js +6 -13
- package/dist/Send.js.map +1 -1
- package/dist/Text.d.ts +13 -26
- package/dist/Text.d.ts.map +1 -1
- package/dist/Text.js +4 -13
- package/dist/Text.js.map +1 -1
- package/dist/internal/botApi.d.ts +3 -3
- package/dist/internal/botApi.d.ts.map +1 -1
- package/dist/internal/botApi.js +2 -1
- package/dist/internal/botApi.js.map +1 -1
- package/dist/internal/botApiTransport.d.ts +2 -2
- package/dist/internal/botApiTransport.d.ts.map +1 -1
- package/dist/internal/botApiTransport.js +3 -4
- package/dist/internal/botApiTransport.js.map +1 -1
- package/dist/internal/dialog.d.ts.map +1 -1
- package/dist/internal/dialog.js +8 -16
- package/dist/internal/dialog.js.map +1 -1
- package/dist/internal/file.d.ts +5 -2
- package/dist/internal/file.d.ts.map +1 -1
- package/dist/internal/file.js +3 -0
- package/dist/internal/file.js.map +1 -1
- package/dist/internal/send.d.ts +6 -4
- package/dist/internal/send.d.ts.map +1 -1
- package/dist/internal/send.js +18 -17
- package/dist/internal/send.js.map +1 -1
- package/package.json +1 -1
- package/src/Bot.ts +3 -4
- package/src/BotApi.ts +7 -8
- package/src/BotApiError.ts +19 -25
- package/src/BotApiTransport.ts +6 -9
- package/src/BotApiUrl.ts +7 -10
- package/src/Content.ts +128 -130
- package/src/Dialog.ts +79 -73
- package/src/File.ts +36 -5
- package/src/Markup.ts +37 -27
- package/src/Runner.ts +5 -1
- package/src/Send.ts +27 -26
- package/src/Text.ts +21 -18
- package/src/internal/botApi.ts +5 -6
- package/src/internal/botApiTransport.ts +5 -6
- package/src/internal/dialog.ts +8 -16
- package/src/internal/file.ts +5 -2
- package/src/internal/send.ts +23 -23
package/src/Dialog.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type * as BotApi from './BotApi.ts'
|
|
2
2
|
import * as Brand from 'effect/Brand'
|
|
3
|
-
import * as
|
|
3
|
+
import * as Match from 'effect/Match'
|
|
4
4
|
import * as Option from 'effect/Option'
|
|
5
5
|
import * as internal from './internal/dialog.ts'
|
|
6
6
|
|
|
@@ -14,20 +14,23 @@ export type Dialog =
|
|
|
14
14
|
| ForumTopic
|
|
15
15
|
| ChannelDm
|
|
16
16
|
|
|
17
|
-
export
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
17
|
+
export interface PrivateTopic {
|
|
18
|
+
readonly _tag: 'PrivateTopic'
|
|
19
|
+
readonly user: User
|
|
20
|
+
readonly topicId: number
|
|
21
|
+
}
|
|
21
22
|
|
|
22
|
-
export
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
23
|
+
export interface ForumTopic {
|
|
24
|
+
readonly _tag: 'ForumTopic'
|
|
25
|
+
readonly supergroup: Supergroup
|
|
26
|
+
readonly topicId: number
|
|
27
|
+
}
|
|
26
28
|
|
|
27
|
-
export
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
29
|
+
export interface ChannelDm {
|
|
30
|
+
readonly _tag: 'ChannelDm'
|
|
31
|
+
readonly channel: Channel
|
|
32
|
+
readonly topicId: number
|
|
33
|
+
}
|
|
31
34
|
|
|
32
35
|
// =============================================================================
|
|
33
36
|
// Peer
|
|
@@ -39,49 +42,38 @@ export type Peer =
|
|
|
39
42
|
| Channel
|
|
40
43
|
| Supergroup
|
|
41
44
|
|
|
42
|
-
export
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
public dialogId(): DialogId {
|
|
46
|
-
return Option.getOrThrow(internal.encodePeerId('user', this.id))
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
public topic(topicId: number): PrivateTopic {
|
|
50
|
-
return new PrivateTopic({ user: this, topicId })
|
|
51
|
-
}
|
|
45
|
+
export interface User {
|
|
46
|
+
readonly _tag: 'User'
|
|
47
|
+
readonly id: UserId
|
|
52
48
|
}
|
|
53
49
|
|
|
54
|
-
export
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
public dialogId(): DialogId {
|
|
58
|
-
return Option.getOrThrow(internal.encodePeerId('group', this.id))
|
|
59
|
-
}
|
|
50
|
+
export interface Group {
|
|
51
|
+
readonly _tag: 'Group'
|
|
52
|
+
readonly id: GroupId
|
|
60
53
|
}
|
|
61
54
|
|
|
62
|
-
export
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
return Option.getOrThrow(internal.encodePeerId('channel', this.id))
|
|
67
|
-
}
|
|
55
|
+
export interface Channel {
|
|
56
|
+
readonly _tag: 'Channel'
|
|
57
|
+
readonly id: ChannelId
|
|
58
|
+
}
|
|
68
59
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
60
|
+
export interface Supergroup {
|
|
61
|
+
readonly _tag: 'Supergroup'
|
|
62
|
+
readonly id: SupergroupId
|
|
72
63
|
}
|
|
73
64
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
public dialogId(): DialogId {
|
|
78
|
-
return Option.getOrThrow(internal.encodePeerId('channel', this.id))
|
|
79
|
-
}
|
|
65
|
+
// =============================================================================
|
|
66
|
+
// Peer Functions
|
|
67
|
+
// =============================================================================
|
|
80
68
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
69
|
+
export const dialogId: (peer: Peer) => DialogId = Match.type<Peer>().pipe(
|
|
70
|
+
Match.tagsExhaustive({
|
|
71
|
+
User: u => Option.getOrThrow(internal.encodePeerId('user', u.id)),
|
|
72
|
+
Group: g => Option.getOrThrow(internal.encodePeerId('group', g.id)),
|
|
73
|
+
Channel: c => Option.getOrThrow(internal.encodePeerId('channel', c.id)),
|
|
74
|
+
Supergroup: s => Option.getOrThrow(internal.encodePeerId('channel', s.id)),
|
|
75
|
+
}),
|
|
76
|
+
)
|
|
85
77
|
|
|
86
78
|
// =============================================================================
|
|
87
79
|
// Brands
|
|
@@ -93,30 +85,25 @@ export class Supergroup extends Data.TaggedClass('Supergroup')<{
|
|
|
93
85
|
* @see {@link https://core.telegram.org/api/bots/ids Telegram API • Bot API dialog IDs}
|
|
94
86
|
*/
|
|
95
87
|
export type DialogId = number & Brand.Brand<'@grom.js/effect-tg/DialogId'>
|
|
96
|
-
export const DialogId = Brand.refined<DialogId>(
|
|
88
|
+
export const DialogId: Brand.Brand.Constructor<DialogId> = Brand.refined<DialogId>(
|
|
97
89
|
n => Option.isSome(internal.decodeDialogId(n)),
|
|
98
90
|
n => Brand.error(`Invalid dialog ID: ${n}`),
|
|
99
91
|
)
|
|
100
92
|
|
|
101
93
|
export type UserId = number & Brand.Brand<'@grom.js/effect-tg/UserId'>
|
|
102
|
-
export const UserId = Brand.refined<UserId>(
|
|
94
|
+
export const UserId: Brand.Brand.Constructor<UserId> = Brand.refined<UserId>(
|
|
103
95
|
n => Option.isSome(internal.encodePeerId('user', n)),
|
|
104
96
|
n => Brand.error(`Invalid user ID: ${n}`),
|
|
105
97
|
)
|
|
106
98
|
|
|
107
99
|
export type GroupId = number & Brand.Brand<'@grom.js/effect-tg/GroupId'>
|
|
108
|
-
export const GroupId = Brand.refined<GroupId>(
|
|
100
|
+
export const GroupId: Brand.Brand.Constructor<GroupId> = Brand.refined<GroupId>(
|
|
109
101
|
n => Option.isSome(internal.encodePeerId('group', n)),
|
|
110
102
|
n => Brand.error(`Invalid group ID: ${n}`),
|
|
111
103
|
)
|
|
112
104
|
|
|
113
|
-
/**
|
|
114
|
-
* ID for channels (including supergroups).
|
|
115
|
-
*
|
|
116
|
-
* @see {@link https://core.telegram.org/api/bots/ids Telegram API • Bot API dialog IDs}
|
|
117
|
-
*/
|
|
118
105
|
export type ChannelId = number & Brand.Brand<'@grom.js/effect-tg/ChannelId'>
|
|
119
|
-
export const ChannelId = Brand.refined<ChannelId>(
|
|
106
|
+
export const ChannelId: Brand.Brand.Constructor<ChannelId> = Brand.refined<ChannelId>(
|
|
120
107
|
n => Option.isSome(internal.encodePeerId('channel', n)),
|
|
121
108
|
n => Brand.error(`Invalid channel or supergroup ID: ${n}`),
|
|
122
109
|
)
|
|
@@ -125,7 +112,7 @@ export const ChannelId = Brand.refined<ChannelId>(
|
|
|
125
112
|
export type SupergroupId = ChannelId
|
|
126
113
|
|
|
127
114
|
/** @alias ChannelId */
|
|
128
|
-
export const SupergroupId = ChannelId
|
|
115
|
+
export const SupergroupId: Brand.Brand.Constructor<ChannelId> = ChannelId
|
|
129
116
|
|
|
130
117
|
// =============================================================================
|
|
131
118
|
// Dialog ID <-> Peer ID
|
|
@@ -156,21 +143,40 @@ export const encodePeerId: (
|
|
|
156
143
|
// Constructors
|
|
157
144
|
// =============================================================================
|
|
158
145
|
|
|
159
|
-
export const user: (id: number) => User =
|
|
160
|
-
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
export const
|
|
164
|
-
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
export const
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
146
|
+
export const user: (id: number) => User = id => ({ _tag: 'User', id: UserId(id) })
|
|
147
|
+
|
|
148
|
+
export const group: (id: number) => Group = id => ({ _tag: 'Group', id: GroupId(id) })
|
|
149
|
+
|
|
150
|
+
export const channel: (id: number) => Channel = id => ({ _tag: 'Channel', id: ChannelId(id) })
|
|
151
|
+
|
|
152
|
+
export const supergroup: (id: number) => Supergroup = id => ({ _tag: 'Supergroup', id: ChannelId(id) })
|
|
153
|
+
|
|
154
|
+
export const privateTopic: (
|
|
155
|
+
user: User,
|
|
156
|
+
topicId: number,
|
|
157
|
+
) => PrivateTopic = (user, topicId) => ({
|
|
158
|
+
_tag: 'PrivateTopic',
|
|
159
|
+
user,
|
|
160
|
+
topicId,
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
export const forumTopic: (
|
|
164
|
+
supergroup: Supergroup,
|
|
165
|
+
topicId: number,
|
|
166
|
+
) => ForumTopic = (supergroup, topicId) => ({
|
|
167
|
+
_tag: 'ForumTopic',
|
|
168
|
+
supergroup,
|
|
169
|
+
topicId,
|
|
170
|
+
})
|
|
171
|
+
|
|
172
|
+
export const channelDm: (
|
|
173
|
+
channel: Channel,
|
|
174
|
+
topicId: number,
|
|
175
|
+
) => ChannelDm = (channel, topicId) => ({
|
|
176
|
+
_tag: 'ChannelDm',
|
|
177
|
+
channel,
|
|
178
|
+
topicId,
|
|
179
|
+
})
|
|
174
180
|
|
|
175
181
|
export const ofMessage: (
|
|
176
182
|
message: BotApi.Types.Message,
|
package/src/File.ts
CHANGED
|
@@ -7,20 +7,51 @@ import type * as BotApi from './BotApi.ts'
|
|
|
7
7
|
import type * as BotApiError from './BotApiError.ts'
|
|
8
8
|
import type * as BotApiUrl from './BotApiUrl.ts'
|
|
9
9
|
import * as Brand from 'effect/Brand'
|
|
10
|
-
import * as
|
|
10
|
+
import * as Predicate from 'effect/Predicate'
|
|
11
11
|
import * as internal from './internal/file.ts'
|
|
12
12
|
|
|
13
13
|
export type FileId = string & Brand.Brand<'FileId'>
|
|
14
|
-
export const FileId = Brand.nominal<FileId>()
|
|
14
|
+
export const FileId: Brand.Brand.Constructor<FileId> = Brand.nominal<FileId>()
|
|
15
15
|
|
|
16
16
|
export type External = URL & Brand.Brand<'External'>
|
|
17
|
-
export const External = Brand.nominal<External>()
|
|
17
|
+
export const External: Brand.Brand.Constructor<External> = Brand.nominal<External>()
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
// =============================================================================
|
|
20
|
+
// InputFile
|
|
21
|
+
// =============================================================================
|
|
22
|
+
|
|
23
|
+
const InputFileTypeId: unique symbol = Symbol.for('effect-tg/InputFile')
|
|
24
|
+
|
|
25
|
+
export type InputFileTypeId = typeof InputFileTypeId
|
|
26
|
+
|
|
27
|
+
export interface InputFile {
|
|
28
|
+
readonly [InputFileTypeId]: InputFileTypeId
|
|
29
|
+
readonly stream: Stream.Stream<Uint8Array>
|
|
30
|
+
readonly filename: string
|
|
31
|
+
readonly mimeType?: string
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const InputFileProto = {
|
|
35
|
+
[InputFileTypeId]: InputFileTypeId,
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export const make: (args: {
|
|
20
39
|
stream: Stream.Stream<Uint8Array>
|
|
21
40
|
filename: string
|
|
22
41
|
mimeType?: string
|
|
23
|
-
}
|
|
42
|
+
}) => InputFile = ({ stream, filename, mimeType }) => {
|
|
43
|
+
const file = Object.create(InputFileProto)
|
|
44
|
+
file.stream = stream
|
|
45
|
+
file.filename = filename
|
|
46
|
+
file.mimeType = mimeType
|
|
47
|
+
return file
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export const isInputFile = (u: unknown): u is InputFile => Predicate.hasProperty(u, InputFileTypeId)
|
|
51
|
+
|
|
52
|
+
// =============================================================================
|
|
53
|
+
// Utilities
|
|
54
|
+
// =============================================================================
|
|
24
55
|
|
|
25
56
|
/**
|
|
26
57
|
* Downloads a file from the Bot API server.
|
package/src/Markup.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import * as Data from 'effect/Data'
|
|
2
1
|
import * as Option from 'effect/Option'
|
|
3
2
|
|
|
4
3
|
// =============================================================================
|
|
@@ -14,27 +13,31 @@ export type Markup =
|
|
|
14
13
|
| ReplyKeyboardRemove
|
|
15
14
|
| ForceReply
|
|
16
15
|
|
|
17
|
-
export
|
|
16
|
+
export interface InlineKeyboard {
|
|
17
|
+
readonly _tag: 'InlineKeyboard'
|
|
18
18
|
readonly rows: ReadonlyArray<ReadonlyArray<InlineButton>>
|
|
19
|
-
}
|
|
19
|
+
}
|
|
20
20
|
|
|
21
|
-
export
|
|
21
|
+
export interface ReplyKeyboard {
|
|
22
|
+
readonly _tag: 'ReplyKeyboard'
|
|
22
23
|
readonly rows: ReadonlyArray<ReadonlyArray<ReplyButton>>
|
|
23
24
|
readonly persistent: boolean
|
|
24
25
|
readonly resizable: boolean
|
|
25
26
|
readonly oneTime: boolean
|
|
26
27
|
readonly selective: boolean
|
|
27
28
|
readonly inputPlaceholder: Option.Option<string>
|
|
28
|
-
}
|
|
29
|
+
}
|
|
29
30
|
|
|
30
|
-
export
|
|
31
|
+
export interface ReplyKeyboardRemove {
|
|
32
|
+
readonly _tag: 'ReplyKeyboardRemove'
|
|
31
33
|
readonly selective: boolean
|
|
32
|
-
}
|
|
34
|
+
}
|
|
33
35
|
|
|
34
|
-
export
|
|
36
|
+
export interface ForceReply {
|
|
37
|
+
readonly _tag: 'ForceReply'
|
|
35
38
|
readonly selective: boolean
|
|
36
39
|
readonly inputPlaceholder: Option.Option<string>
|
|
37
|
-
}
|
|
40
|
+
}
|
|
38
41
|
|
|
39
42
|
// =============================================================================
|
|
40
43
|
// Constructors
|
|
@@ -42,7 +45,10 @@ export class ForceReply extends Data.TaggedClass('ForceReply')<{
|
|
|
42
45
|
|
|
43
46
|
export const inlineKeyboard = (
|
|
44
47
|
rows: ReadonlyArray<ReadonlyArray<InlineButton>>,
|
|
45
|
-
): InlineKeyboard =>
|
|
48
|
+
): InlineKeyboard => ({
|
|
49
|
+
_tag: 'InlineKeyboard',
|
|
50
|
+
rows,
|
|
51
|
+
})
|
|
46
52
|
|
|
47
53
|
export const replyKeyboard = (
|
|
48
54
|
rows: ReadonlyArray<ReadonlyArray<ReplyButton>>,
|
|
@@ -53,27 +59,31 @@ export const replyKeyboard = (
|
|
|
53
59
|
readonly selective?: boolean
|
|
54
60
|
readonly inputPlaceholder?: string
|
|
55
61
|
},
|
|
56
|
-
): ReplyKeyboard =>
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
export const replyKeyboardRemove = (options?: {
|
|
67
|
-
|
|
62
|
+
): ReplyKeyboard => ({
|
|
63
|
+
_tag: 'ReplyKeyboard',
|
|
64
|
+
rows,
|
|
65
|
+
persistent: options?.persistent ?? false,
|
|
66
|
+
resizable: options?.resizable ?? false,
|
|
67
|
+
oneTime: options?.oneTime ?? false,
|
|
68
|
+
selective: options?.selective ?? false,
|
|
69
|
+
inputPlaceholder: Option.fromNullable(options?.inputPlaceholder),
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
export const replyKeyboardRemove = (options?: {
|
|
73
|
+
readonly selective?: boolean
|
|
74
|
+
}): ReplyKeyboardRemove => ({
|
|
75
|
+
_tag: 'ReplyKeyboardRemove',
|
|
76
|
+
selective: options?.selective ?? false,
|
|
77
|
+
})
|
|
68
78
|
|
|
69
79
|
export const forceReply = (options?: {
|
|
70
80
|
readonly selective?: boolean
|
|
71
81
|
readonly inputPlaceholder?: string
|
|
72
|
-
}): ForceReply =>
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
82
|
+
}): ForceReply => ({
|
|
83
|
+
_tag: 'ForceReply',
|
|
84
|
+
selective: options?.selective ?? false,
|
|
85
|
+
inputPlaceholder: Option.fromNullable(options?.inputPlaceholder),
|
|
86
|
+
})
|
|
77
87
|
|
|
78
88
|
// =============================================================================
|
|
79
89
|
// Inline button
|
package/src/Runner.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type * as Effect from 'effect/Effect'
|
|
2
2
|
import type * as Bot from './Bot.ts'
|
|
3
|
+
import type * as BotApi from './BotApi.ts'
|
|
4
|
+
import type * as BotApiError from './BotApiError.ts'
|
|
3
5
|
import * as internal from './internal/runner.ts'
|
|
4
6
|
|
|
5
7
|
/**
|
|
@@ -19,4 +21,6 @@ export interface Runner<E = never, R = never> {
|
|
|
19
21
|
* Creates a simple runner that fetches updates by calling `BotApi.getUpdates`
|
|
20
22
|
* method and handles them one by one.
|
|
21
23
|
*/
|
|
22
|
-
export const makeSimple
|
|
24
|
+
export const makeSimple: (options?: {
|
|
25
|
+
allowedUpdates?: string[]
|
|
26
|
+
}) => Runner<BotApiError.BotApiError, BotApi.BotApi> = internal.makeSimple
|
package/src/Send.ts
CHANGED
|
@@ -36,6 +36,32 @@ export const sendMessage: (params: {
|
|
|
36
36
|
BotApi.BotApi
|
|
37
37
|
> = internal.sendMessage
|
|
38
38
|
|
|
39
|
+
// =============================================================================
|
|
40
|
+
// TargetDialog
|
|
41
|
+
// =============================================================================
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Target dialog for sending messages.
|
|
45
|
+
*/
|
|
46
|
+
export interface TargetDialog {
|
|
47
|
+
readonly dialog: Dialog.Dialog | Dialog.DialogId
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export const TargetDialog: Context.Tag<TargetDialog, TargetDialog> = Context.GenericTag<TargetDialog>('@grom.js/effect-tg/Send/TargetDialog')
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Provides the target dialog for sending messages.
|
|
54
|
+
*/
|
|
55
|
+
export const to: {
|
|
56
|
+
(dialog: Dialog.Dialog | Dialog.DialogId): <A, E, R>(effect: Effect.Effect<A, E, R>) => Effect.Effect<A, E, Exclude<R, TargetDialog>>
|
|
57
|
+
<A, E, R>(effect: Effect.Effect<A, E, R>, dialog: Dialog.Dialog | Dialog.DialogId): Effect.Effect<A, E, Exclude<R, TargetDialog>>
|
|
58
|
+
} = Function.dual(2, <A, E, R>(
|
|
59
|
+
effect: Effect.Effect<A, E, R>,
|
|
60
|
+
dialog: Dialog.Dialog | Dialog.DialogId,
|
|
61
|
+
): Effect.Effect<A, E, Exclude<R, TargetDialog>> => (
|
|
62
|
+
Effect.provideService(effect, TargetDialog, { dialog })
|
|
63
|
+
))
|
|
64
|
+
|
|
39
65
|
// =============================================================================
|
|
40
66
|
// MessageToSend
|
|
41
67
|
// =============================================================================
|
|
@@ -71,7 +97,7 @@ const MessageToSendProto = {
|
|
|
71
97
|
commit(this: MessageToSend) {
|
|
72
98
|
return Effect.flatMap(
|
|
73
99
|
TargetDialog,
|
|
74
|
-
dialog => sendMessage({
|
|
100
|
+
({ dialog }) => sendMessage({
|
|
75
101
|
dialog,
|
|
76
102
|
content: this.content,
|
|
77
103
|
markup: this.markup,
|
|
@@ -109,31 +135,6 @@ export const message = (content: Content.Content, params?: {
|
|
|
109
135
|
return self
|
|
110
136
|
}
|
|
111
137
|
|
|
112
|
-
// =============================================================================
|
|
113
|
-
// TargetDialog
|
|
114
|
-
// =============================================================================
|
|
115
|
-
|
|
116
|
-
/**
|
|
117
|
-
* Target dialog for sending messages.
|
|
118
|
-
*/
|
|
119
|
-
export class TargetDialog extends Context.Tag('@grom.js/effect-tg/Send/TargetDialog')<
|
|
120
|
-
TargetDialog,
|
|
121
|
-
Dialog.Dialog | Dialog.DialogId
|
|
122
|
-
>() {}
|
|
123
|
-
|
|
124
|
-
/**
|
|
125
|
-
* Provides the target dialog for sending messages.
|
|
126
|
-
*/
|
|
127
|
-
export const to: {
|
|
128
|
-
(dialog: Dialog.Dialog): <A, E, R>(effect: Effect.Effect<A, E, R>) => Effect.Effect<A, E, Exclude<R, TargetDialog>>
|
|
129
|
-
<A, E, R>(effect: Effect.Effect<A, E, R>, dialog: Dialog.Dialog): Effect.Effect<A, E, Exclude<R, TargetDialog>>
|
|
130
|
-
} = Function.dual(2, <A, E, R>(
|
|
131
|
-
effect: Effect.Effect<A, E, R>,
|
|
132
|
-
dialog: Dialog.Dialog,
|
|
133
|
-
): Effect.Effect<A, E, Exclude<R, TargetDialog>> => (
|
|
134
|
-
Effect.provideService(effect, TargetDialog, dialog)
|
|
135
|
-
))
|
|
136
|
-
|
|
137
138
|
// =============================================================================
|
|
138
139
|
// Reply Markup
|
|
139
140
|
// =============================================================================
|
package/src/Text.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import type { TgxElement } from '@grom.js/tgx'
|
|
2
2
|
import type { Types } from './BotApi.ts'
|
|
3
|
-
import * as Data from 'effect/Data'
|
|
4
3
|
|
|
5
4
|
/**
|
|
6
5
|
* Formatted text.
|
|
@@ -11,32 +10,36 @@ export type Text =
|
|
|
11
10
|
| Markdown
|
|
12
11
|
| Tgx
|
|
13
12
|
|
|
14
|
-
export
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
13
|
+
export interface Plain {
|
|
14
|
+
readonly _tag: 'Plain'
|
|
15
|
+
readonly text: string
|
|
16
|
+
readonly entities?: Array<Types.MessageEntity>
|
|
17
|
+
}
|
|
18
18
|
|
|
19
|
-
export
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
export interface Html {
|
|
20
|
+
readonly _tag: 'Html'
|
|
21
|
+
readonly html: string
|
|
22
|
+
}
|
|
22
23
|
|
|
23
|
-
export
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
export interface Markdown {
|
|
25
|
+
readonly _tag: 'Markdown'
|
|
26
|
+
readonly markdown: string
|
|
27
|
+
}
|
|
26
28
|
|
|
27
|
-
export
|
|
28
|
-
|
|
29
|
-
|
|
29
|
+
export interface Tgx {
|
|
30
|
+
readonly _tag: 'Tgx'
|
|
31
|
+
readonly tgx: TgxElement
|
|
32
|
+
}
|
|
30
33
|
|
|
31
34
|
// ———— Constructors ———————————————————————————————————————————————————————————
|
|
32
35
|
|
|
33
36
|
export const plain = (
|
|
34
37
|
text: string,
|
|
35
38
|
entities?: Array<Types.MessageEntity>,
|
|
36
|
-
): Plain =>
|
|
39
|
+
): Plain => ({ _tag: 'Plain', text, entities })
|
|
37
40
|
|
|
38
|
-
export const html = (html: string): Html =>
|
|
41
|
+
export const html = (html: string): Html => ({ _tag: 'Html', html })
|
|
39
42
|
|
|
40
|
-
export const markdown = (markdown: string): Markdown =>
|
|
43
|
+
export const markdown = (markdown: string): Markdown => ({ _tag: 'Markdown', markdown })
|
|
41
44
|
|
|
42
|
-
export const tgx = (tgx: TgxElement): Tgx =>
|
|
45
|
+
export const tgx = (tgx: TgxElement): Tgx => ({ _tag: 'Tgx', tgx })
|
package/src/internal/botApi.ts
CHANGED
|
@@ -3,17 +3,16 @@ import type * as BotApiTransport from '../BotApiTransport.ts'
|
|
|
3
3
|
import * as Effect from 'effect/Effect'
|
|
4
4
|
import * as BotApiError from '../BotApiError.ts'
|
|
5
5
|
|
|
6
|
-
export const make = ({
|
|
7
|
-
transport
|
|
8
|
-
}:
|
|
9
|
-
transport: BotApiTransport.Service
|
|
10
|
-
}): BotApi.Service => (
|
|
6
|
+
export const make = ({ transport }: {
|
|
7
|
+
transport: BotApiTransport.BotApiTransport
|
|
8
|
+
}): BotApi.BotApi => (
|
|
11
9
|
new Proxy({}, {
|
|
12
10
|
get: (_target, prop) => {
|
|
13
11
|
if (typeof prop !== 'string') {
|
|
14
12
|
return
|
|
15
13
|
}
|
|
16
14
|
const method = prop
|
|
15
|
+
// TODO: Shouldn't we cache effects not to create them on each call?
|
|
17
16
|
return Effect.fnUntraced(
|
|
18
17
|
function* (params: void | Record<string, unknown> = {}) {
|
|
19
18
|
const response = yield* transport.sendRequest(method, params)
|
|
@@ -24,5 +23,5 @@ export const make = ({
|
|
|
24
23
|
},
|
|
25
24
|
)
|
|
26
25
|
},
|
|
27
|
-
}) as BotApi.
|
|
26
|
+
}) as BotApi.BotApi
|
|
28
27
|
)
|
|
@@ -18,7 +18,7 @@ interface ExtractedFile {
|
|
|
18
18
|
* {@linkcode File.InputFile InputFile} instances.
|
|
19
19
|
*/
|
|
20
20
|
const hasInputFile = (value: unknown): boolean => {
|
|
21
|
-
if (
|
|
21
|
+
if (File.isInputFile(value)) {
|
|
22
22
|
return true
|
|
23
23
|
}
|
|
24
24
|
if (Array.isArray(value)) {
|
|
@@ -34,7 +34,7 @@ const cloneAndExtract = (
|
|
|
34
34
|
value: unknown,
|
|
35
35
|
files: ExtractedFile[],
|
|
36
36
|
): unknown => {
|
|
37
|
-
if (
|
|
37
|
+
if (File.isInputFile(value)) {
|
|
38
38
|
const attachId = String(files.length + 1)
|
|
39
39
|
files.push({ attachId, file: value })
|
|
40
40
|
return `attach://${attachId}`
|
|
@@ -108,15 +108,14 @@ export const make = ({
|
|
|
108
108
|
botApiUrl,
|
|
109
109
|
}: {
|
|
110
110
|
httpClient: HttpClient.HttpClient
|
|
111
|
-
botApiUrl: BotApiUrl.
|
|
112
|
-
}): BotApiTransport.
|
|
111
|
+
botApiUrl: BotApiUrl.BotApiUrl
|
|
112
|
+
}): BotApiTransport.BotApiTransport => ({
|
|
113
113
|
sendRequest: (method, params) => (
|
|
114
114
|
Effect.gen(function* () {
|
|
115
115
|
const body = yield* makeHttpBody(params)
|
|
116
116
|
const response = yield* httpClient.post(botApiUrl.toMethod(method), { body })
|
|
117
|
-
const responseJson = yield* response.json
|
|
118
117
|
// We trust Bot API and don't want to introduce overhead with validation.
|
|
119
|
-
return
|
|
118
|
+
return (yield* response.json) as BotApiTransport.BotApiResponse
|
|
120
119
|
}).pipe(
|
|
121
120
|
Effect.catchAll(cause => (
|
|
122
121
|
Effect.fail(new BotApiError.TransportError({ cause }))
|
package/src/internal/dialog.ts
CHANGED
|
@@ -15,7 +15,7 @@ export const decodeDialogId = (dialogId: number): Option.Option<
|
|
|
15
15
|
> => {
|
|
16
16
|
if (Number.isSafeInteger(dialogId)) {
|
|
17
17
|
if (1 <= dialogId && dialogId <= 0xFFFFFFFFFF) {
|
|
18
|
-
return Option.some({ peer: 'user', id:
|
|
18
|
+
return Option.some({ peer: 'user', id: dialogId as Dialog.UserId })
|
|
19
19
|
}
|
|
20
20
|
if (-999999999999 <= dialogId && dialogId <= -1) {
|
|
21
21
|
return Option.some({ peer: 'group', id: -dialogId as Dialog.GroupId })
|
|
@@ -80,34 +80,26 @@ export const ofMessage: (
|
|
|
80
80
|
) => Dialog.Dialog = (m) => {
|
|
81
81
|
switch (m.chat.type) {
|
|
82
82
|
case 'private': {
|
|
83
|
-
const user =
|
|
84
|
-
id: Option.getOrThrow(decodePeerId('user', m.chat.id)),
|
|
85
|
-
})
|
|
83
|
+
const user = Dialog.user(Option.getOrThrow(decodePeerId('user', m.chat.id)))
|
|
86
84
|
if (m.message_thread_id != null) {
|
|
87
|
-
return
|
|
85
|
+
return Dialog.privateTopic(user, m.message_thread_id)
|
|
88
86
|
}
|
|
89
87
|
return user
|
|
90
88
|
}
|
|
91
89
|
case 'group': {
|
|
92
|
-
return
|
|
93
|
-
id: Option.getOrThrow(decodePeerId('group', m.chat.id)),
|
|
94
|
-
})
|
|
90
|
+
return Dialog.group(Option.getOrThrow(decodePeerId('group', m.chat.id)))
|
|
95
91
|
}
|
|
96
92
|
case 'channel': {
|
|
97
|
-
const channel =
|
|
98
|
-
id: Option.getOrThrow(decodePeerId('channel', m.chat.id)),
|
|
99
|
-
})
|
|
93
|
+
const channel = Dialog.channel(Option.getOrThrow(decodePeerId('channel', m.chat.id)))
|
|
100
94
|
if (m.direct_messages_topic != null) {
|
|
101
|
-
return
|
|
95
|
+
return Dialog.channelDm(channel, m.direct_messages_topic.topic_id)
|
|
102
96
|
}
|
|
103
97
|
return channel
|
|
104
98
|
}
|
|
105
99
|
case 'supergroup': {
|
|
106
|
-
const supergroup =
|
|
107
|
-
id: Option.getOrThrow(decodePeerId('channel', m.chat.id)),
|
|
108
|
-
})
|
|
100
|
+
const supergroup = Dialog.supergroup(Option.getOrThrow(decodePeerId('channel', m.chat.id)))
|
|
109
101
|
if (m.message_thread_id != null) {
|
|
110
|
-
return
|
|
102
|
+
return Dialog.forumTopic(supergroup, m.message_thread_id)
|
|
111
103
|
}
|
|
112
104
|
return supergroup
|
|
113
105
|
}
|
package/src/internal/file.ts
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import type * as File from '../File.ts'
|
|
2
2
|
import * as HttpClient from '@effect/platform/HttpClient'
|
|
3
3
|
import * as Effect from 'effect/Effect'
|
|
4
4
|
import * as BotApi from '../BotApi.ts'
|
|
5
5
|
import * as BotApiUrl from '../BotApiUrl.ts'
|
|
6
6
|
|
|
7
|
+
/**
|
|
8
|
+
* @internal
|
|
9
|
+
*/
|
|
7
10
|
export const download = Effect.fnUntraced(
|
|
8
|
-
function* (fileId: FileId) {
|
|
11
|
+
function* (fileId: File.FileId) {
|
|
9
12
|
const file = yield* BotApi.callMethod('getFile', { file_id: fileId })
|
|
10
13
|
if (file.file_path == null) {
|
|
11
14
|
return yield* Effect.die(new Error(`Bot API returned no file path for file "${fileId}".`))
|