@effect-ak/tg-bot-client 0.6.4 → 1.0.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/dist/index.cjs +230 -0
- package/dist/index.d.cts +95 -0
- package/dist/index.d.ts +58 -26
- package/dist/index.js +180 -1
- package/package.json +18 -52
- package/src/client-file.ts +70 -0
- package/src/client.ts +46 -0
- package/src/config.ts +13 -0
- package/src/const.ts +20 -0
- package/src/errors.ts +14 -0
- package/src/execute.ts +84 -0
- package/src/guards.ts +27 -0
- package/src/index.ts +7 -0
- package/test/execute.test.ts +102 -0
- package/test/file.test.ts +23 -0
- package/test/fixture.ts +35 -0
- package/tsconfig.json +12 -0
- package/tsup.config.json +10 -0
- package/dist/bot-bundle.js +0 -22
- package/dist/bot-bundle.mjs +0 -22
- package/dist/bot.d.ts +0 -111
- package/dist/bot.js +0 -1
- package/dist/bot.mjs +0 -1
- package/dist/config-3uU0YevV.d.ts +0 -3250
- package/dist/index.mjs +0 -1
- package/dist/webapp.d.ts +0 -392
- package/dist/webapp.js +0 -1
- package/dist/webapp.mjs +0 -0
- package/readme.md +0 -236
package/package.json
CHANGED
|
@@ -1,75 +1,41 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@effect-ak/tg-bot-client",
|
|
3
|
-
"
|
|
4
|
-
"
|
|
3
|
+
"type": "module",
|
|
4
|
+
"description": "Type-safe HTTP client for Telegram Bot API",
|
|
5
|
+
"version": "1.0.0",
|
|
6
|
+
"license": "MIT",
|
|
5
7
|
"author": {
|
|
6
8
|
"name": "Aleksandr Kondaurov",
|
|
7
9
|
"email": "kondaurov.dev@gmail.com"
|
|
8
10
|
},
|
|
9
|
-
"publishConfig": {
|
|
10
|
-
"access": "public"
|
|
11
|
-
},
|
|
12
|
-
"description": "TypeScript types for Telegram Bot Api and Telegram.Webapp",
|
|
13
|
-
"files": [
|
|
14
|
-
"dist/*.js",
|
|
15
|
-
"dist/*.mjs",
|
|
16
|
-
"dist/*.d.ts",
|
|
17
|
-
"!dist/bot-bundle.d.ts",
|
|
18
|
-
"!dist/bot-bundle.d.mts"
|
|
19
|
-
],
|
|
20
|
-
"keywords": [
|
|
21
|
-
"telegram typescript types",
|
|
22
|
-
"telegram bot api client",
|
|
23
|
-
"telegram webapp"
|
|
24
|
-
],
|
|
25
11
|
"repository": {
|
|
26
12
|
"type": "git",
|
|
27
|
-
"url": "https://github.com/effect-ak/tg-bot-client"
|
|
13
|
+
"url": "https://github.com/effect-ak/tg-bot-client",
|
|
14
|
+
"directory": "packages/client"
|
|
28
15
|
},
|
|
29
16
|
"bugs": {
|
|
30
17
|
"url": "https://github.com/effect-ak/tg-bot-client/issues"
|
|
31
18
|
},
|
|
32
|
-
"
|
|
19
|
+
"publishConfig": {
|
|
20
|
+
"access": "public"
|
|
21
|
+
},
|
|
33
22
|
"types": "./dist/index.d.ts",
|
|
34
|
-
"main": "./dist/index.
|
|
35
|
-
"module": "./dist/index.
|
|
23
|
+
"main": "./dist/index.cjs",
|
|
24
|
+
"module": "./dist/index.js",
|
|
36
25
|
"exports": {
|
|
37
26
|
".": {
|
|
38
27
|
"types": "./dist/index.d.ts",
|
|
39
|
-
"
|
|
40
|
-
"
|
|
41
|
-
},
|
|
42
|
-
"./bot": {
|
|
43
|
-
"types": "./dist/bot.d.ts",
|
|
44
|
-
"require": "./dist/bot.js",
|
|
45
|
-
"import": "./dist/bot.mjs"
|
|
46
|
-
},
|
|
47
|
-
"./bot-bundle": {
|
|
48
|
-
"types": "./dist/bot.d.ts",
|
|
49
|
-
"require": "./dist/bot-bundle.js",
|
|
50
|
-
"import": "./dist/bot-bundle.mjs"
|
|
51
|
-
},
|
|
52
|
-
"./webapp": {
|
|
53
|
-
"types": "./dist/webapp.d.ts",
|
|
54
|
-
"require": "./dist/webapp.js",
|
|
55
|
-
"import": "./dist/webapp.mjs"
|
|
28
|
+
"import": "./dist/index.js",
|
|
29
|
+
"require": "./dist/index.cjs"
|
|
56
30
|
}
|
|
57
31
|
},
|
|
58
|
-
"
|
|
59
|
-
"@
|
|
60
|
-
"@types/js-yaml": "^4.0.9",
|
|
61
|
-
"js-yaml": "^4.1.0",
|
|
62
|
-
"node-html-parser": "^6.1.13",
|
|
63
|
-
"openapi-types": "^12.1.3",
|
|
64
|
-
"ts-morph": "^24.0.0",
|
|
65
|
-
"tsx": "^4.19.3",
|
|
66
|
-
"typescript": "^5.8.3",
|
|
67
|
-
"vite-tsconfig-paths": "^5.1.4",
|
|
68
|
-
"vitest": "^3.1.1",
|
|
69
|
-
"effect": "3.12.0",
|
|
70
|
-
"tsup": "^8.5.0"
|
|
32
|
+
"dependencies": {
|
|
33
|
+
"@effect-ak/tg-bot-api": "0.9.2"
|
|
71
34
|
},
|
|
72
35
|
"peerDependencies": {
|
|
73
36
|
"effect": "^3.12.0"
|
|
37
|
+
},
|
|
38
|
+
"scripts": {
|
|
39
|
+
"build": "tsup"
|
|
74
40
|
}
|
|
75
41
|
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import * as Micro from "effect/Micro"
|
|
2
|
+
import * as Context from "effect/Context"
|
|
3
|
+
|
|
4
|
+
import { TgBotClientError } from "./errors"
|
|
5
|
+
import { TgBotApiBaseUrl, TgBotApiToken } from "./config"
|
|
6
|
+
import { executeTgBotMethod } from "./execute"
|
|
7
|
+
|
|
8
|
+
export class ClientFileService extends Context.Tag("ClientFileService")<
|
|
9
|
+
ClientFileService,
|
|
10
|
+
{
|
|
11
|
+
getFile: (input: GetFile) => ReturnType<typeof getFile>
|
|
12
|
+
}
|
|
13
|
+
>() {
|
|
14
|
+
static live = () => {
|
|
15
|
+
return ClientFileService.context({
|
|
16
|
+
getFile
|
|
17
|
+
})
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface GetFile {
|
|
22
|
+
fileId: string
|
|
23
|
+
type?: string
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const getFile = ({ fileId, type }: GetFile) =>
|
|
27
|
+
getFileBytes(fileId).pipe(
|
|
28
|
+
Micro.andThen(
|
|
29
|
+
({ content, file_name }) =>
|
|
30
|
+
new File([content], file_name, {
|
|
31
|
+
...(type ? { type } : undefined)
|
|
32
|
+
})
|
|
33
|
+
)
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
const getFileBytes = (fileId: string) =>
|
|
37
|
+
Micro.gen(function* () {
|
|
38
|
+
const response = yield* executeTgBotMethod("get_file", { file_id: fileId })
|
|
39
|
+
const file_path = response.file_path
|
|
40
|
+
|
|
41
|
+
if (!file_path || file_path.length == 0) {
|
|
42
|
+
return yield* Micro.fail(
|
|
43
|
+
new TgBotClientError({
|
|
44
|
+
cause: {
|
|
45
|
+
_tag: "UnableToGetFile",
|
|
46
|
+
cause: "File path not defined"
|
|
47
|
+
}
|
|
48
|
+
})
|
|
49
|
+
)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const file_name = file_path.replaceAll("/", "-")
|
|
53
|
+
const baseUrl = yield* Micro.service(TgBotApiBaseUrl)
|
|
54
|
+
const botToken = yield* Micro.service(TgBotApiToken)
|
|
55
|
+
|
|
56
|
+
const url = `${baseUrl}/file/bot${botToken}/${file_path}`
|
|
57
|
+
|
|
58
|
+
const content = yield* Micro.tryPromise({
|
|
59
|
+
try: () => fetch(url).then((_) => _.arrayBuffer()),
|
|
60
|
+
catch: (cause) =>
|
|
61
|
+
new TgBotClientError({
|
|
62
|
+
cause: { _tag: "UnableToGetFile", cause }
|
|
63
|
+
})
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
return {
|
|
67
|
+
content,
|
|
68
|
+
file_name
|
|
69
|
+
}
|
|
70
|
+
})
|
package/src/client.ts
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import * as Micro from "effect/Micro"
|
|
2
|
+
import * as Context from "effect/Context"
|
|
3
|
+
|
|
4
|
+
import type { Api } from "@effect-ak/tg-bot-api"
|
|
5
|
+
import { executeTgBotMethod } from "./execute"
|
|
6
|
+
import { TgBotApiToken } from "./config"
|
|
7
|
+
import { GetFile, ClientFileService } from "./client-file"
|
|
8
|
+
|
|
9
|
+
export interface TgBotClient {
|
|
10
|
+
readonly execute: <M extends keyof Api>(
|
|
11
|
+
method: M,
|
|
12
|
+
input: Parameters<Api[M]>[0]
|
|
13
|
+
) => Promise<ReturnType<Api[M]>>
|
|
14
|
+
readonly getFile: (input: GetFile) => Promise<File>
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
interface MakeTgClient {
|
|
18
|
+
bot_token: string
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function makeTgBotClient(config: MakeTgClient): TgBotClient {
|
|
22
|
+
return createEffect(config).pipe(Micro.runSync)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const createEffect = ({ bot_token }: MakeTgClient) =>
|
|
26
|
+
Micro.gen(function* () {
|
|
27
|
+
const file = yield* Micro.service(ClientFileService)
|
|
28
|
+
const context = Context.make(TgBotApiToken, bot_token)
|
|
29
|
+
|
|
30
|
+
const execute = <M extends keyof Api>(
|
|
31
|
+
method: M,
|
|
32
|
+
input: Parameters<Api[M]>[0]
|
|
33
|
+
) =>
|
|
34
|
+
executeTgBotMethod(method, input).pipe(
|
|
35
|
+
Micro.provideContext(context),
|
|
36
|
+
Micro.runPromise
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
const getFile = (input: GetFile) =>
|
|
40
|
+
file.getFile(input).pipe(Micro.provideContext(context), Micro.runPromise)
|
|
41
|
+
|
|
42
|
+
return {
|
|
43
|
+
execute,
|
|
44
|
+
getFile
|
|
45
|
+
}
|
|
46
|
+
}).pipe(Micro.provideContext(ClientFileService.live()))
|
package/src/config.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import * as Context from "effect/Context"
|
|
2
|
+
|
|
3
|
+
import { TG_BOT_API_URL } from "./const"
|
|
4
|
+
|
|
5
|
+
export class TgBotApiBaseUrl extends Context.Reference<TgBotApiBaseUrl>()(
|
|
6
|
+
"TgBotApiBaseUrl",
|
|
7
|
+
{ defaultValue: () => TG_BOT_API_URL }
|
|
8
|
+
) {}
|
|
9
|
+
|
|
10
|
+
export class TgBotApiToken extends Context.Tag("TgBotApiToken")<
|
|
11
|
+
TgBotApiToken,
|
|
12
|
+
string
|
|
13
|
+
>() {}
|
package/src/const.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export const TG_BOT_API_URL = "https://api.telegram.org"
|
|
2
|
+
|
|
3
|
+
export const MESSAGE_EFFECTS = {
|
|
4
|
+
"🔥": "5104841245755180586",
|
|
5
|
+
"👍": "5107584321108051014",
|
|
6
|
+
"👎": "5104858069142078462",
|
|
7
|
+
"❤️": "5159385139981059251",
|
|
8
|
+
"🎉": "5046509860389126442",
|
|
9
|
+
"💩": "5046589136895476101"
|
|
10
|
+
} as const
|
|
11
|
+
|
|
12
|
+
export type MessageEffect = keyof typeof MESSAGE_EFFECTS
|
|
13
|
+
|
|
14
|
+
export const messageEffectIdCodes = Object.keys(
|
|
15
|
+
MESSAGE_EFFECTS
|
|
16
|
+
) as MessageEffect[]
|
|
17
|
+
|
|
18
|
+
export const isMessageEffect = (input: unknown): input is MessageEffect => {
|
|
19
|
+
return typeof input === "string" && input in MESSAGE_EFFECTS
|
|
20
|
+
}
|
package/src/errors.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import * as Data from "effect/Data"
|
|
2
|
+
|
|
3
|
+
type ErrorReason = Data.TaggedEnum<{
|
|
4
|
+
NotOkResponse: { errorCode?: number; details?: string }
|
|
5
|
+
UnexpectedResponse: { response: unknown }
|
|
6
|
+
ClientInternalError: { cause: unknown }
|
|
7
|
+
UnableToGetFile: { cause: unknown }
|
|
8
|
+
BotHandlerError: { cause: unknown }
|
|
9
|
+
NotJsonResponse: { response: unknown }
|
|
10
|
+
}>
|
|
11
|
+
|
|
12
|
+
export class TgBotClientError extends Data.TaggedError("TgBotClientError")<{
|
|
13
|
+
cause: ErrorReason
|
|
14
|
+
}> {}
|
package/src/execute.ts
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import * as String from "effect/String"
|
|
2
|
+
import * as Micro from "effect/Micro"
|
|
3
|
+
import type { Api } from "@effect-ak/tg-bot-api"
|
|
4
|
+
|
|
5
|
+
import { TgBotClientError } from "./errors"
|
|
6
|
+
import { isFileContent, isTgBotApiResponse } from "./guards"
|
|
7
|
+
import { TgBotApiBaseUrl, TgBotApiToken } from "./config"
|
|
8
|
+
|
|
9
|
+
export const executeTgBotMethod = <M extends keyof Api>(
|
|
10
|
+
method: M,
|
|
11
|
+
input: Parameters<Api[M]>[0]
|
|
12
|
+
): Micro.Micro<ReturnType<Api[M]>, TgBotClientError, TgBotApiToken> =>
|
|
13
|
+
Micro.gen(function* () {
|
|
14
|
+
const botToken = yield* Micro.service(TgBotApiToken)
|
|
15
|
+
const baseUrl = yield* Micro.service(TgBotApiBaseUrl)
|
|
16
|
+
|
|
17
|
+
const httpResponse = yield* Micro.tryPromise({
|
|
18
|
+
try: () =>
|
|
19
|
+
fetch(`${baseUrl}/bot${botToken}/${String.snakeToCamel(method)}`, {
|
|
20
|
+
body: makePayload(input) ?? null,
|
|
21
|
+
method: "POST"
|
|
22
|
+
}),
|
|
23
|
+
catch: (cause) =>
|
|
24
|
+
new TgBotClientError({
|
|
25
|
+
cause: { _tag: "ClientInternalError", cause }
|
|
26
|
+
})
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
const response = yield* Micro.tryPromise({
|
|
30
|
+
try: () => httpResponse.json(),
|
|
31
|
+
catch: () =>
|
|
32
|
+
new TgBotClientError({
|
|
33
|
+
cause: { _tag: "NotJsonResponse", response: httpResponse }
|
|
34
|
+
})
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
if (!isTgBotApiResponse(response)) {
|
|
38
|
+
return yield* Micro.fail(
|
|
39
|
+
new TgBotClientError({
|
|
40
|
+
cause: { _tag: "UnexpectedResponse", response }
|
|
41
|
+
})
|
|
42
|
+
)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (!httpResponse.ok) {
|
|
46
|
+
return yield* Micro.fail(
|
|
47
|
+
new TgBotClientError({
|
|
48
|
+
cause: {
|
|
49
|
+
_tag: "NotOkResponse",
|
|
50
|
+
...(response.error_code
|
|
51
|
+
? { errorCode: response.error_code }
|
|
52
|
+
: undefined),
|
|
53
|
+
...(response.description
|
|
54
|
+
? { details: response.description }
|
|
55
|
+
: undefined)
|
|
56
|
+
}
|
|
57
|
+
})
|
|
58
|
+
)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return response.result as ReturnType<Api[M]>
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
export const makePayload = (body: object): FormData | undefined => {
|
|
65
|
+
const entries = Object.entries(body)
|
|
66
|
+
|
|
67
|
+
if (entries.length == 0) return undefined
|
|
68
|
+
|
|
69
|
+
const result = new FormData()
|
|
70
|
+
|
|
71
|
+
for (const [key, value] of entries) {
|
|
72
|
+
if (!value) continue
|
|
73
|
+
|
|
74
|
+
if (typeof value != "object") {
|
|
75
|
+
result.append(key, `${value}`)
|
|
76
|
+
} else if (isFileContent(value)) {
|
|
77
|
+
result.append(key, new Blob([value.file_content]), value.file_name)
|
|
78
|
+
} else {
|
|
79
|
+
result.append(key, JSON.stringify(value))
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return result
|
|
84
|
+
}
|
package/src/guards.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export interface FileContent {
|
|
2
|
+
file_content: Uint8Array<ArrayBuffer>
|
|
3
|
+
file_name: string
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export const isFileContent = (input: unknown): input is FileContent =>
|
|
7
|
+
typeof input == "object" &&
|
|
8
|
+
input != null &&
|
|
9
|
+
"file_content" in input &&
|
|
10
|
+
input.file_content instanceof Uint8Array &&
|
|
11
|
+
"file_name" in input &&
|
|
12
|
+
typeof input.file_name == "string"
|
|
13
|
+
|
|
14
|
+
export interface TgBotApiResponseSchema {
|
|
15
|
+
ok: boolean
|
|
16
|
+
error_code?: number
|
|
17
|
+
description?: string
|
|
18
|
+
result?: unknown
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export const isTgBotApiResponse = (
|
|
22
|
+
input: unknown
|
|
23
|
+
): input is TgBotApiResponseSchema =>
|
|
24
|
+
typeof input == "object" &&
|
|
25
|
+
input != null &&
|
|
26
|
+
"ok" in input &&
|
|
27
|
+
typeof input.ok == "boolean"
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { describe, expect, assert, vi } from "vitest"
|
|
2
|
+
|
|
3
|
+
import { fixture } from "./fixture"
|
|
4
|
+
import { MESSAGE_EFFECTS } from "../src/const"
|
|
5
|
+
import { Micro } from "effect"
|
|
6
|
+
import { executeTgBotMethod } from "../src/execute"
|
|
7
|
+
|
|
8
|
+
const fetchSpy = vi.spyOn(global, "fetch")
|
|
9
|
+
|
|
10
|
+
describe("telegram bot client, execute method", () => {
|
|
11
|
+
fixture("send dice", async ({ chat_id, context }) => {
|
|
12
|
+
// skip();
|
|
13
|
+
|
|
14
|
+
const response = await executeTgBotMethod("send_dice", {
|
|
15
|
+
chat_id,
|
|
16
|
+
emoji: "🎲",
|
|
17
|
+
message_effect_id: MESSAGE_EFFECTS["🔥"]
|
|
18
|
+
}).pipe(Micro.provideContext(context), Micro.runPromiseExit)
|
|
19
|
+
|
|
20
|
+
assert(response._tag == "Success")
|
|
21
|
+
|
|
22
|
+
const url = fetchSpy.mock.calls[0][0] as string
|
|
23
|
+
const lastPath = url.split("/").pop()
|
|
24
|
+
|
|
25
|
+
expect(lastPath).toEqual("sendDice")
|
|
26
|
+
|
|
27
|
+
assert(response != null)
|
|
28
|
+
|
|
29
|
+
expect(response.value.chat.id).toBeDefined()
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
fixture("send message", async ({ chat_id, client, skip }) => {
|
|
33
|
+
skip()
|
|
34
|
+
|
|
35
|
+
const response = await client.execute("send_message", {
|
|
36
|
+
chat_id,
|
|
37
|
+
text: "hey again",
|
|
38
|
+
message_effect_id: MESSAGE_EFFECTS["🔥"]
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
expect(response.chat.id).toBeDefined()
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
fixture("send message with keyboard", async ({ chat_id, client, skip }) => {
|
|
45
|
+
skip()
|
|
46
|
+
|
|
47
|
+
const response = await client.execute("send_message", {
|
|
48
|
+
chat_id,
|
|
49
|
+
text: "hey again!",
|
|
50
|
+
message_effect_id: MESSAGE_EFFECTS["🎉"],
|
|
51
|
+
reply_markup: {
|
|
52
|
+
inline_keyboard: [
|
|
53
|
+
[
|
|
54
|
+
{
|
|
55
|
+
text: "api documentation",
|
|
56
|
+
web_app: {
|
|
57
|
+
url: "https://core.telegram.org/api"
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
]
|
|
61
|
+
]
|
|
62
|
+
}
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
expect(response.chat.id).toBeDefined()
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
fixture("send document", async ({ chat_id, client, skip }) => {
|
|
69
|
+
skip()
|
|
70
|
+
|
|
71
|
+
const response = await client.execute("send_document", {
|
|
72
|
+
chat_id,
|
|
73
|
+
message_effect_id: MESSAGE_EFFECTS["🎉"],
|
|
74
|
+
document: {
|
|
75
|
+
file_content: Buffer.from("Hello!"),
|
|
76
|
+
file_name: "hello.txt"
|
|
77
|
+
},
|
|
78
|
+
caption: "simple text file"
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
expect(response.document?.file_id).toBeDefined()
|
|
82
|
+
|
|
83
|
+
expect(response.chat.id).toBeDefined()
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
fixture("send message with action", async ({ chat_id, client }) => {
|
|
87
|
+
await client.execute("send_chat_action", {
|
|
88
|
+
chat_id,
|
|
89
|
+
action: "upload_voice"
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
await new Promise((res) => setTimeout(res, 5000))
|
|
93
|
+
|
|
94
|
+
const response = await client.execute("send_message", {
|
|
95
|
+
chat_id,
|
|
96
|
+
text: "hey again with typings",
|
|
97
|
+
message_effect_id: MESSAGE_EFFECTS["🔥"]
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
expect(response.chat.id).toBeDefined()
|
|
101
|
+
})
|
|
102
|
+
})
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { assert, describe, expect } from "vitest"
|
|
2
|
+
|
|
3
|
+
import { fixture } from "./fixture"
|
|
4
|
+
|
|
5
|
+
describe("telegram bot client, download file", () => {
|
|
6
|
+
fixture("get file content", async ({ client, chat_id }) => {
|
|
7
|
+
const document = await client.execute("send_document", {
|
|
8
|
+
chat_id,
|
|
9
|
+
document: {
|
|
10
|
+
file_content: Buffer.from("Hello!"),
|
|
11
|
+
file_name: "hello.txt"
|
|
12
|
+
}
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
const fileId = document.document?.file_id
|
|
16
|
+
|
|
17
|
+
assert(fileId, "file id is null")
|
|
18
|
+
|
|
19
|
+
const response = await client.getFile({ fileId })
|
|
20
|
+
|
|
21
|
+
expect(response).toBeDefined()
|
|
22
|
+
})
|
|
23
|
+
})
|
package/test/fixture.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { test } from "vitest"
|
|
2
|
+
import { Context } from "effect"
|
|
3
|
+
|
|
4
|
+
import { makeTgBotClient, TgBotClient } from "../src/client"
|
|
5
|
+
import { TgBotApiToken } from "../src/config"
|
|
6
|
+
|
|
7
|
+
interface Fixture {
|
|
8
|
+
readonly token: string
|
|
9
|
+
readonly client: TgBotClient
|
|
10
|
+
readonly chat_id: string
|
|
11
|
+
readonly context: Context.Context<TgBotApiToken>
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const fixture = test.extend<Fixture>({
|
|
15
|
+
token: async (_, use) => {
|
|
16
|
+
const token = process.env["bot_token"]
|
|
17
|
+
if (!token) throw Error("bot_token not defined in config.json")
|
|
18
|
+
use(token)
|
|
19
|
+
},
|
|
20
|
+
|
|
21
|
+
client: async ({ token }, use) => {
|
|
22
|
+
const client = makeTgBotClient({
|
|
23
|
+
bot_token: token
|
|
24
|
+
})
|
|
25
|
+
use(client)
|
|
26
|
+
},
|
|
27
|
+
chat_id: async (_, use) => {
|
|
28
|
+
const chatId = process.env["chat_id"]
|
|
29
|
+
if (!chatId) throw Error("chat_id not defined in config.json")
|
|
30
|
+
use(chatId)
|
|
31
|
+
},
|
|
32
|
+
context: async ({ token }, use) => {
|
|
33
|
+
use(Context.make(TgBotApiToken, token))
|
|
34
|
+
}
|
|
35
|
+
})
|
package/tsconfig.json
ADDED