@effect-ak/tg-bot-client 0.0.6 → 0.1.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.
@@ -1,6 +1,6 @@
1
1
  import { makeExecute } from "./execute-request.js";
2
2
  import { makeDownloadFile } from "./download-file.js";
3
- const defaultBaseUrl = "https://api.telegram.org";
3
+ import { defaultBaseUrl } from "./const.js";
4
4
  export const makeTgBotClient = (inputConfig) => {
5
5
  const config = {
6
6
  ...inputConfig,
@@ -9,7 +9,7 @@ export const makeTgBotClient = (inputConfig) => {
9
9
  const execute = makeExecute(config);
10
10
  const file = makeDownloadFile(config, execute);
11
11
  return {
12
- execute,
12
+ ...execute,
13
13
  ...file
14
14
  };
15
15
  };
@@ -1,3 +1,4 @@
1
+ export const defaultBaseUrl = "https://api.telegram.org";
1
2
  export const MESSAGE_EFFECTS = {
2
3
  "🔥": "5104841245755180586",
3
4
  "👍": "5107584321108051014",
@@ -1,15 +1,34 @@
1
+ import { TgBotClientError } from "./errors.js";
1
2
  export const makeDownloadFile = (config, execute) => {
2
3
  const getFile = async (input) => {
3
- const response = await execute("get_file", input);
4
- response.result?.file_path;
5
- const file_path = response.result?.file_path;
6
- if (!file_path || file_path.length == 0)
7
- throw new Error("NoFilePath", { cause: response });
4
+ const response = await execute.execute("get_file", input);
5
+ if (response.error)
6
+ return { error: response.error };
7
+ const file_path = response.success?.file_path;
8
+ if (!file_path || file_path.length == 0) {
9
+ return {
10
+ error: new TgBotClientError({
11
+ type: "UnableToGetFile",
12
+ cause: "File path not defined"
13
+ })
14
+ };
15
+ }
8
16
  const file_name = file_path.replaceAll("/", "-");
9
17
  const url = `${config.baseUrl}/file/bot${config.token}/${file_path}`;
10
- const fileContent = await fetch(url).then(_ => _.arrayBuffer());
11
- const file = new File([new Uint8Array(fileContent)], file_name);
12
- return file;
18
+ try {
19
+ const fileContent = await fetch(url).then(_ => _.arrayBuffer());
20
+ const file = new File([new Uint8Array(fileContent)], file_name);
21
+ return {
22
+ success: file
23
+ };
24
+ }
25
+ catch (cause) {
26
+ return {
27
+ error: new TgBotClientError({
28
+ type: "UnableToGetFile", cause
29
+ })
30
+ };
31
+ }
13
32
  };
14
33
  return {
15
34
  getFile
@@ -0,0 +1,12 @@
1
+ export class TgBotClientError extends Error {
2
+ reason;
3
+ _tag = "TgBotClientError";
4
+ constructor(reason) {
5
+ super();
6
+ this.reason = reason;
7
+ }
8
+ static missingSuccess = new TgBotClientError({
9
+ type: "ClientInternalError",
10
+ cause: "Expected 'success' to be defined"
11
+ });
12
+ }
@@ -1,21 +1,54 @@
1
1
  import { makePayload } from "./request.js";
2
2
  import { isTgBotApiResponse } from "./response.js";
3
+ import { TgBotClientError } from "./errors.js";
3
4
  export const makeExecute = (config) => {
4
5
  const execute = async (method, input) => {
5
- const httpResponse = await fetch(`${config.baseUrl}/bot${config.token}/${snakeToCamel(method)}`, {
6
- body: makePayload(input) ?? null,
7
- method: "POST"
8
- }).then(_ => _.json());
9
- if (!isTgBotApiResponse(httpResponse))
10
- throw new Error("Not valid response", {
11
- cause: httpResponse
6
+ try {
7
+ const httpResponse = await fetch(`${config.baseUrl}/bot${config.token}/${snakeToCamel(method)}`, {
8
+ body: makePayload(input) ?? null,
9
+ method: "POST",
12
10
  });
13
- if (httpResponse.ok == false) {
14
- console.warn(httpResponse);
11
+ const response = await httpResponse.json();
12
+ if (!isTgBotApiResponse(response)) {
13
+ return {
14
+ error: new TgBotClientError({
15
+ type: "UnexpectedResponse",
16
+ response
17
+ })
18
+ };
19
+ }
20
+ if (!httpResponse.ok) {
21
+ return {
22
+ error: new TgBotClientError({
23
+ type: "NotOkResponse",
24
+ ...(response.error_code ? { errorCode: response.error_code } : undefined),
25
+ ...(response.description ? { details: response.description } : undefined)
26
+ })
27
+ };
28
+ }
29
+ return {
30
+ success: response.result
31
+ };
15
32
  }
16
- return httpResponse;
33
+ catch (cause) {
34
+ return {
35
+ error: new TgBotClientError({
36
+ type: "ClientInternalError", cause
37
+ })
38
+ };
39
+ }
40
+ };
41
+ const unsafeExecute = async (method, input) => {
42
+ const result = await execute(method, input);
43
+ if (result.error)
44
+ throw result.error;
45
+ if (!("success" in result))
46
+ throw TgBotClientError.missingSuccess;
47
+ return result.success;
48
+ };
49
+ return {
50
+ execute, unsafeExecute
17
51
  };
18
- return execute;
19
52
  };
20
53
  const snakeToCamel = (methodName) => methodName
21
54
  .split("_")
@@ -1,6 +1,15 @@
1
1
  export const isTgBotApiResponse = (input) => {
2
- return (("ok" in input && typeof input.ok == "boolean") &&
3
- (typeof input.error_code === 'undefined' || typeof input.error_code === 'number') &&
4
- (typeof input.description === 'undefined' || typeof input.description === 'string') &&
5
- (typeof input.result === 'undefined' || typeof input.result === 'object'));
2
+ if (typeof input !== "object" || input == null) {
3
+ return false;
4
+ }
5
+ if (!("ok" in input && typeof input.ok == "boolean")) {
6
+ return false;
7
+ }
8
+ if ("error_code" in input && typeof input.error_code != "number") {
9
+ return false;
10
+ }
11
+ if ("description" in input && typeof input.description != "string") {
12
+ return false;
13
+ }
14
+ return true;
6
15
  };
package/dist/esm/index.js CHANGED
@@ -1,4 +1,4 @@
1
1
  export * from "./client/const.js";
2
2
  export * from "./client/_client.js";
3
- export * from "./specification/api.js";
4
- export * from "./specification/types.js";
3
+ // export * from "./specification/api.js"
4
+ // export * from "./specification/types.js"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@effect-ak/tg-bot-client",
3
- "version": "0.0.6",
3
+ "version": "0.1.0",
4
4
  "type": "module",
5
5
  "author": {
6
6
  "name": "Aleksandr Kondaurov",
@@ -32,17 +32,20 @@
32
32
  "@types/node": "^22.10.1",
33
33
  "effect": "^3.11.4",
34
34
  "node-html-parser": "^6.1.13",
35
+ "openapi-types": "^12.1.3",
35
36
  "ts-morph": "^24.0.0",
36
37
  "tsc-alias": "^1.8.10",
37
38
  "type-fest": "^4.30.0",
38
39
  "typescript": "^5.7.2",
39
40
  "vite-tsconfig-paths": "^5.1.4",
40
- "vitest": "^2.1.8"
41
+ "vitest": "^2.1.8",
42
+ "@redocly/cli": "^1.26.0"
41
43
  },
42
44
  "scripts": {
43
- "gen": "bun run codegen/main",
45
+ "gen": "bun run ./codegen/main",
44
46
  "build": "pnpm build-esm && pnpm build-cjs",
45
47
  "build-esm": "tsc -p tsconfig.build.json && tsc-alias -p tsconfig.build.json",
46
- "build-cjs": "babel dist/esm --out-dir dist/cjs"
48
+ "build-cjs": "babel dist/esm --out-dir dist/cjs",
49
+ "gen-html": "redocly build-docs --output ../effect-ak.github.io/telegram-bot-api/index.html"
47
50
  }
48
51
  }
package/readme.md CHANGED
@@ -1,5 +1,7 @@
1
1
  ![NPM Version](https://img.shields.io/npm/v/%40effect-ak%2Ftg-bot-client)
2
2
 
3
+ [OpenApi Specification](https://effect-ak.github.io/telegram-bot-api/)
4
+
3
5
  ### What is it?
4
6
 
5
7
  This is a client for interacting with the Telegram Bot API.
@@ -10,8 +12,15 @@ They only provide documentation in the form of a massive HTML page, which is ver
10
12
  ## Features:
11
13
  - **Pure TypeScript Client**: This is a clean client written in TypeScript with no abstractions.
12
14
  - **Complete**: The entire API is generated from the official documentation [https://core.telegram.org/bots/api](https://core.telegram.org/bots/api) using a [code generator](./codegen/main.ts).
13
- - **Inline Documentation**: No need to read lengthy official documentation. All types and comments are available in JS DOC, allowing you to develop your bot without leaving your IDE.
14
- - **Type Mapping**: Types from the documentation are converted to TypeScript types. For example, `Integer` becomes `number`, `True` becomes `boolean`, `String or Number` becomes `string | number`, and so on.
15
+ - ~~**Inline Documentation**: No need to read lengthy official documentation. All types and comments are available in JS DOC, allowing you to develop your bot without leaving your IDE.~~
16
+ - Codegenerator produces TypeScript code and OpenApi specification now! Documentation was removed from TypeScript interfaces in order to keep npm package smaller.
17
+
18
+ - **Type Mapping**: Types from the documentation are converted to TypeScript types:
19
+ - `Integer` becomes `number`
20
+ - `True` becomes `boolean`
21
+ - `String or Number` becomes `string | number`
22
+ - Enumerated types, such as `Type of the chat, can be either “private”, “group”, “supergroup” or “channel”` becomes a standard union of literal types `"private"| "group" | "supergroup" | "channel"`
23
+ - And so on
15
24
  - **Readable Method Names**: Method names, such as `SetChatAdministratorCustomTitleInput`, are converted to snake_case for easier code readability, e.g., `set_chat_administrator_custom_title`.
16
25
 
17
26
  ### Usage example
@@ -23,19 +32,28 @@ They only provide documentation in the form of a massive HTML page, which is ver
23
32
  #### Creating a Client
24
33
 
25
34
  ```typescript
26
- import { makeTgBotClient } from "effect-ak/tg-bot-client"
35
+ import { makeTgBotClient } from "@effect-ak/tg-bot-client"
27
36
 
28
37
  const client = makeTgBotClient({
29
38
  token: "" //your token from bot father
30
39
  });
31
40
  ```
32
41
 
33
- Now, `client` is an object that has an `execute` method. This method takes two arguments: the first is the API method, and the second is an object containing the arguments for that method.
42
+ #### Executing api methods, typesafety
43
+
44
+ `client` has an `execute` method which requires two arguments
45
+
46
+ - the first is the API method, e.g. `send_message`
47
+ - the second is an object containing the arguments for that method, e.g. `text`
48
+
49
+ `execute` never returns failed promise, instead, it gives an object with two fields, `success`, `error`.
50
+
51
+ if you want, you can use unsafe alternative, `unsafeExecute`, which gives you the result of method or throws an Error
34
52
 
35
53
  #### 1. Sending a Message with an Effect
36
54
 
37
55
  ```typescript
38
- import { MESSAGE_EFFECTS } from "effect-ak/tg-bot-client"
56
+ import { MESSAGE_EFFECTS } from "@effect-ak/tg-bot-client"
39
57
 
40
58
  await client.execute("send_message", {
41
59
  chat_id: "???", // replace ??? with the chat number