gramio 0.0.10 → 0.0.12

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 CHANGED
@@ -1,7 +1,7 @@
1
- # GramIO
2
-
3
- Work in progress
4
-
5
- Currently support Bot API 7.1
6
-
7
- See [Documentation](https://gramio.netlify.app/)
1
+ # GramIO
2
+
3
+ Work in progress
4
+
5
+ Currently support Bot API 7.1
6
+
7
+ See [Documentation](https://gramio.netlify.app/)
package/dist/bot.d.ts CHANGED
@@ -1,11 +1,65 @@
1
+ import { Context } from "@gramio/contexts";
1
2
  import type { APIMethods } from "@gramio/types";
2
3
  import "reflect-metadata";
3
- import { BotOptions } from "./types";
4
+ import { Plugin } from "#plugin";
5
+ import { BotOptions, ErrorDefinitions, Handler, Hooks } from "./types";
4
6
  import { Updates } from "./updates";
5
- export declare class Bot {
7
+ export declare class Bot<Errors extends ErrorDefinitions = {}, Derives = {}> {
6
8
  readonly options: BotOptions;
7
9
  readonly api: APIMethods;
10
+ private errorsDefinitions;
11
+ private errorHandler;
8
12
  updates: Updates;
13
+ private hooks;
9
14
  constructor(token: string, options?: Omit<BotOptions, "token">);
15
+ private runHooks;
16
+ private runImmutableHooks;
10
17
  private _callApi;
18
+ /**
19
+ * Register custom class-error for type-safe catch in `onError` hook
20
+ *
21
+ * @example
22
+ * ```ts
23
+ * export class NoRights extends Error {
24
+ * needRole: "admin" | "moderator";
25
+ *
26
+ * constructor(role: "admin" | "moderator") {
27
+ * super();
28
+ * this.needRole = role;
29
+ * }
30
+ * }
31
+ *
32
+ * const bot = new Bot(process.env.TOKEN!)
33
+ * .error("NO_RIGHTS", NoRights)
34
+ * .onError(({ context, kind, error }) => {
35
+ * if (context.is("message") && kind === "NO_RIGHTS")
36
+ * return context.send(
37
+ * format`You don't have enough rights! You need to have an «${bold(
38
+ * error.needRole
39
+ * )}» role.`
40
+ * );
41
+ * });
42
+ *
43
+ * bot.updates.on("message", (context) => {
44
+ * if (context.text === "bun") throw new NoRights("admin");
45
+ * });
46
+ * ```
47
+ */
48
+ error<Name extends string, NewError extends {
49
+ new (...args: any): any;
50
+ prototype: Error;
51
+ }>(kind: Name, error: NewError): Bot<Errors & { [name in Name]: InstanceType<NewError>; }, {}>;
52
+ /**
53
+ * Set error handler.
54
+ * @example
55
+ * ```ts
56
+ * bot.updates.onError(({ context, kind, error }) => {
57
+ * if(context.is("message")) return context.send(`${kind}: ${error.message}`);
58
+ * })
59
+ * ```
60
+ */
61
+ onError(handler: Hooks.OnError<Errors>): this;
62
+ derive<Handler extends (context: Context) => object>(handler: Handler): Bot<Errors, Derives & ReturnType<Handler>>;
63
+ use(handler: Handler<Context & Derives>): this;
64
+ extend<NewPlugin extends Plugin>(plugin: NewPlugin): Bot<Errors & NewPlugin["Errors"], Derives & NewPlugin["Derives"]>;
11
65
  }
package/dist/bot.js CHANGED
@@ -13,27 +13,69 @@ const form_data_encoder_1 = require("form-data-encoder");
13
13
  const inspectable_1 = require("inspectable");
14
14
  require("reflect-metadata");
15
15
  const undici_1 = require("undici");
16
- const TelegramError_1 = require("./TelegramError");
16
+ const errors_1 = require("./errors");
17
17
  const updates_1 = require("./updates");
18
18
  let Bot = class Bot {
19
19
  options = {};
20
20
  api = new Proxy({}, {
21
21
  get: (_target, method) => (args) => this._callApi(method, args),
22
22
  });
23
- updates = new updates_1.Updates(this);
23
+ errorsDefinitions = {
24
+ TELEGRAM: errors_1.TelegramError,
25
+ };
26
+ errorHandler(context, error) {
27
+ return this.runImmutableHooks("onError", {
28
+ context,
29
+ //@ts-expect-error ErrorKind exists if user register error-class with .error("kind", SomeError);
30
+ kind: error.constructor[errors_1.ErrorKind] ?? "UNKNOWN",
31
+ error: error,
32
+ });
33
+ }
34
+ updates = new updates_1.Updates(this, this.errorHandler.bind(this));
35
+ hooks = {
36
+ preRequest: [
37
+ (ctx) => {
38
+ if (!ctx.params)
39
+ return ctx;
40
+ const formattable = format_1.FormattableMap[ctx.method];
41
+ // @ts-ignore add AnyTelegramMethod to @gramio/format
42
+ if (formattable)
43
+ ctx.params = formattable(ctx.params);
44
+ return ctx;
45
+ },
46
+ ],
47
+ onError: [],
48
+ };
24
49
  constructor(token, options) {
25
50
  this.options = { ...options, token };
26
51
  }
52
+ async runHooks(type, context) {
53
+ let data = context;
54
+ for await (const hook of this.hooks[type]) {
55
+ data = await hook(data);
56
+ }
57
+ return data;
58
+ }
59
+ async runImmutableHooks(type, context) {
60
+ for await (const hook of this.hooks[type]) {
61
+ await hook(context);
62
+ }
63
+ }
27
64
  async _callApi(method, params = {}) {
28
65
  const url = `https://api.telegram.org/bot${this.options.token}/${method}`;
29
66
  const reqOptions = {
30
67
  method: "POST",
31
68
  duplex: "half",
32
69
  };
33
- const formattable = format_1.FormattableMap[method];
34
- // biome-ignore lint/style/noParameterAssign: mutate formattable
35
- if (formattable && params)
36
- params = formattable(params);
70
+ const context = await this.runHooks("preRequest",
71
+ // TODO: fix type error
72
+ // @ts-expect-error
73
+ {
74
+ method,
75
+ params,
76
+ });
77
+ // biome-ignore lint/style/noParameterAssign: mutate params
78
+ params = context.params;
37
79
  if (params && (0, files_1.isMediaUpload)(method, params)) {
38
80
  const formData = await (0, files_1.convertJsonToFormData)(method, params);
39
81
  const encoder = new form_data_encoder_1.FormDataEncoder(formData);
@@ -49,9 +91,81 @@ let Bot = class Bot {
49
91
  const response = await (0, undici_1.fetch)(url, reqOptions);
50
92
  const data = (await response.json());
51
93
  if (!data.ok)
52
- throw new TelegramError_1.TelegramError(data, method, params);
94
+ throw new errors_1.TelegramError(data, method, params);
53
95
  return data.result;
54
96
  }
97
+ /**
98
+ * Register custom class-error for type-safe catch in `onError` hook
99
+ *
100
+ * @example
101
+ * ```ts
102
+ * export class NoRights extends Error {
103
+ * needRole: "admin" | "moderator";
104
+ *
105
+ * constructor(role: "admin" | "moderator") {
106
+ * super();
107
+ * this.needRole = role;
108
+ * }
109
+ * }
110
+ *
111
+ * const bot = new Bot(process.env.TOKEN!)
112
+ * .error("NO_RIGHTS", NoRights)
113
+ * .onError(({ context, kind, error }) => {
114
+ * if (context.is("message") && kind === "NO_RIGHTS")
115
+ * return context.send(
116
+ * format`You don't have enough rights! You need to have an «${bold(
117
+ * error.needRole
118
+ * )}» role.`
119
+ * );
120
+ * });
121
+ *
122
+ * bot.updates.on("message", (context) => {
123
+ * if (context.text === "bun") throw new NoRights("admin");
124
+ * });
125
+ * ```
126
+ */
127
+ error(kind, error) {
128
+ //@ts-expect-error Set ErrorKind
129
+ error[errors_1.ErrorKind] = kind;
130
+ this.errorsDefinitions[kind] = error;
131
+ return this;
132
+ }
133
+ /**
134
+ * Set error handler.
135
+ * @example
136
+ * ```ts
137
+ * bot.updates.onError(({ context, kind, error }) => {
138
+ * if(context.is("message")) return context.send(`${kind}: ${error.message}`);
139
+ * })
140
+ * ```
141
+ */
142
+ onError(handler) {
143
+ this.hooks.onError.push(handler);
144
+ return this;
145
+ }
146
+ derive(handler) {
147
+ this.updates.use((context, next) => {
148
+ for (const [key, value] of Object.entries(handler(context))) {
149
+ context[key] = value;
150
+ }
151
+ next();
152
+ });
153
+ return this;
154
+ }
155
+ use(handler) {
156
+ this.updates.use(handler);
157
+ return this;
158
+ }
159
+ extend(plugin) {
160
+ for (const [key, value] of Object.entries(plugin.errorsDefinitions)) {
161
+ if (this.errorsDefinitions[key])
162
+ this.errorsDefinitions[key] = value;
163
+ }
164
+ for (const derive of plugin.derives) {
165
+ this.derive(derive);
166
+ }
167
+ return this;
168
+ }
55
169
  };
56
170
  exports.Bot = Bot;
57
171
  exports.Bot = Bot = __decorate([
@@ -1,4 +1,5 @@
1
1
  import { APIMethodParams, APIMethods, TelegramAPIResponseError, TelegramResponseParameters } from "@gramio/types";
2
+ export declare const ErrorKind: unique symbol;
2
3
  export declare class TelegramError<T extends keyof APIMethods> extends Error {
3
4
  method: T;
4
5
  params: APIMethodParams<T>;
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.TelegramError = void 0;
3
+ exports.TelegramError = exports.ErrorKind = void 0;
4
+ exports.ErrorKind = Symbol("ErrorKind");
4
5
  class TelegramError extends Error {
5
6
  method;
6
7
  params;
@@ -17,3 +18,5 @@ class TelegramError extends Error {
17
18
  }
18
19
  }
19
20
  exports.TelegramError = TelegramError;
21
+ //@ts-expect-error
22
+ TelegramError.constructor[exports.ErrorKind] = "TELEGRAM";
package/dist/index.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  export * from "./bot";
2
- export * from "./TelegramError";
2
+ export * from "./errors";
3
3
  export * from "./types";
4
+ export * from "./plugin";
4
5
  export * from "@gramio/contexts";
5
6
  export * from "@gramio/files";
6
7
  export * from "@gramio/keyboards";
package/dist/index.js CHANGED
@@ -15,8 +15,9 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
17
  __exportStar(require("./bot"), exports);
18
- __exportStar(require("./TelegramError"), exports);
18
+ __exportStar(require("./errors"), exports);
19
19
  __exportStar(require("./types"), exports);
20
+ __exportStar(require("./plugin"), exports);
20
21
  __exportStar(require("@gramio/contexts"), exports);
21
22
  __exportStar(require("@gramio/files"), exports);
22
23
  __exportStar(require("@gramio/keyboards"), exports);
@@ -0,0 +1,21 @@
1
+ import { Context } from "@gramio/contexts";
2
+ import { ErrorDefinitions } from "types";
3
+ export declare class Plugin<Errors extends ErrorDefinitions = {}, Derives = {}> {
4
+ Errors: Errors;
5
+ Derives: Derives;
6
+ derives: ((context: Context) => object)[];
7
+ name: string;
8
+ errorsDefinitions: Record<string, {
9
+ new (...args: any): any;
10
+ prototype: Error;
11
+ }>;
12
+ constructor(name: string);
13
+ /**
14
+ * Register custom class-error in plugin
15
+ **/
16
+ error<Name extends string, NewError extends {
17
+ new (...args: any): any;
18
+ prototype: Error;
19
+ }>(kind: Name, error: NewError): Plugin<Errors & { [name in Name]: InstanceType<NewError>; }, {}>;
20
+ derive<Handler extends (context: Context) => object>(handler: Handler): Plugin<Errors, Derives & ReturnType<Handler>>;
21
+ }
package/dist/plugin.js ADDED
@@ -0,0 +1,29 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Plugin = void 0;
4
+ const _errors_1 = require("#errors");
5
+ class Plugin {
6
+ // TODO: fix that dump error. https://www.typescriptlang.org/play?#code/KYDwDg9gTgLgBDAnmYcCiUrQCLAGYCWAdgTAREQM5wC8cASsAMbQAmAPAFACQlMUxAOYAaHhixROAPgDcnTqEiw4TADYBDStQAKqgK6Di7cdGqgYwIq2omouQiTIVqdAN4BfKXFc8i6gLbAAFxwfAJEgnI8wJim9sSk5FQhjCxQHDy8-EKi3NyucETAAO5wABQAdFXqUIKUIepEiACUDU0ycGBYMBBIKCG2cO48Xm7uUdwsVPx6TD1QZX6BIWFCzd6ZMAAWBJQVS6h0B3LcwzwA9ABUlzxwlwzAhnwxKnp8EP4qGloAtDEScGInX0hiIt2u52isSgXDyADkAqhzJZrKFshFctw4SVBsirNQCkVSpVqrV6nBGi02ogOl1er1kMF0NChrkpGUANbEVghBGBYRwf7QXk46HrHx5c7nAACMEof3AzBgfxZAGVgPBbABpbmZIVQADa2u5AF1aHAuVYTtxNjs9vrKPFHElKAbLawzXR9RNuFANXooEQEHaKdQ9EQOUQIMUg5o4LoDEZMtxbNQAGTeOAGg6AoN84AmkIASWmjSYwAAKoz2NjirYvMM8rIeMMzgpwNB4GpNNQAEK9YzQswgCz45kSJ2JZzmjxeHzybh4ji1hOgwUjlE6EFGSlSdmZMDbogi4qr4i5VpwFdH9ej1FnojsYh4F4P1NeAD8cH7MEHEnT8ZHu+cAhNsuwbHkfowAGQZgdQcaUicrbyO2Shdt81BwhA9AEIIWxyrem7jtAEFFMArD0BAqhMgAROorD+MQNFwAAPnANH+BArAxOo8w0RMUxhLM8xlFg1EhHRDFMax7GcdxUC8dANHipklB6CgCzNNacH7MA5GUdR5picASGcGcgnwBYfDmkSgGJkQZQ0TAykVPqjlwgA8gA+vQRYAOIABIVqqNEClhOF4XKWnyBZcAAEa9DZJTfr0ZTNDwrkblYZRWTAzRAA
7
+ Errors;
8
+ Derives;
9
+ derives = [];
10
+ name;
11
+ errorsDefinitions = {};
12
+ constructor(name) {
13
+ this.name = name;
14
+ }
15
+ /**
16
+ * Register custom class-error in plugin
17
+ **/
18
+ error(kind, error) {
19
+ //@ts-expect-error Set ErrorKind
20
+ error[_errors_1.ErrorKind] = kind;
21
+ this.errorsDefinitions[kind] = error;
22
+ return this;
23
+ }
24
+ derive(handler) {
25
+ this.derives.push(handler);
26
+ return this;
27
+ }
28
+ }
29
+ exports.Plugin = Plugin;
package/dist/types.d.ts CHANGED
@@ -1,12 +1,12 @@
1
1
  import { Context } from "@gramio/contexts";
2
- import { APIMethods } from "@gramio/types";
2
+ import { APIMethodParams, APIMethods } from "@gramio/types";
3
3
  import { NextMiddleware } from "middleware-io";
4
- import { TelegramError } from "./TelegramError";
4
+ import { TelegramError } from "./errors";
5
5
  export interface BotOptions {
6
6
  token?: string;
7
7
  }
8
8
  export type Handler<T> = (context: T, next: NextMiddleware) => unknown;
9
- interface ErrorHandlerParams<Kind extends string, Err extends Error> {
9
+ interface ErrorHandlerParams<Kind extends string, Err> {
10
10
  context: Context;
11
11
  kind: Kind;
12
12
  error: Err;
@@ -14,5 +14,24 @@ interface ErrorHandlerParams<Kind extends string, Err extends Error> {
14
14
  type AnyTelegramError = {
15
15
  [APIMethod in keyof APIMethods]: TelegramError<APIMethod>;
16
16
  }[keyof APIMethods];
17
- export type ErrorHandler = (options: ErrorHandlerParams<"TELEGRAM", AnyTelegramError> | ErrorHandlerParams<"UNKNOWN", Error>) => unknown;
17
+ type AnyTelegramMethod = {
18
+ [APIMethod in keyof APIMethods]: {
19
+ method: APIMethod;
20
+ params: APIMethodParams<APIMethod>;
21
+ };
22
+ }[keyof APIMethods];
23
+ type MaybePromise<T> = T | Promise<T>;
24
+ export declare namespace Hooks {
25
+ type PreRequestContext = AnyTelegramMethod;
26
+ type PreRequest = (ctx: PreRequestContext) => MaybePromise<PreRequestContext>;
27
+ type OnErrorContext<T extends ErrorDefinitions> = ErrorHandlerParams<"TELEGRAM", AnyTelegramError> | ErrorHandlerParams<"UNKNOWN", Error> | {
28
+ [K in keyof T]: ErrorHandlerParams<K & string, T[K & string]>;
29
+ }[keyof T];
30
+ type OnError<T extends ErrorDefinitions> = (options: OnErrorContext<T>) => unknown;
31
+ interface Store<T extends ErrorDefinitions> {
32
+ preRequest: PreRequest[];
33
+ onError: OnError<T>[];
34
+ }
35
+ }
36
+ export type ErrorDefinitions = Record<string, Error>;
18
37
  export {};
package/dist/types.js CHANGED
File without changes
package/dist/updates.d.ts CHANGED
@@ -1,19 +1,19 @@
1
1
  import { Context, UpdateName, contextsMappings } from "@gramio/contexts";
2
2
  import type { TelegramUpdate } from "@gramio/types";
3
+ import { CaughtMiddlewareHandler } from "middleware-io";
3
4
  import type { Bot } from "./bot";
4
- import { ErrorHandler, Handler } from "./types";
5
+ import { Handler } from "./types";
5
6
  export declare class Updates {
6
7
  private readonly bot;
7
8
  private isStarted;
8
9
  private offset;
9
10
  private composer;
10
- private errorHandler;
11
- constructor(bot: Bot);
11
+ private onError;
12
+ constructor(bot: Bot<any, any>, onError: CaughtMiddlewareHandler<Context>);
12
13
  on<T extends UpdateName>(updateName: T, handler: Handler<InstanceType<(typeof contextsMappings)[T]>>): this;
13
- use(handler: Handler<Context>): this;
14
+ use(handler: Handler<Context & any>): this;
14
15
  handleUpdate(data: TelegramUpdate): Promise<void>;
15
16
  startPolling(): Promise<void>;
16
17
  startFetchLoop(): Promise<void>;
17
18
  stopPolling(): void;
18
- onError(handler: ErrorHandler): void;
19
19
  }
package/dist/updates.js CHANGED
@@ -3,15 +3,15 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.Updates = void 0;
4
4
  const contexts_1 = require("@gramio/contexts");
5
5
  const middleware_io_1 = require("middleware-io");
6
- const _TelegramError_1 = require("#TelegramError");
7
6
  class Updates {
8
7
  bot;
9
8
  isStarted = false;
10
9
  offset = 0;
11
10
  composer = middleware_io_1.Composer.builder();
12
- errorHandler;
13
- constructor(bot) {
11
+ onError;
12
+ constructor(bot, onError) {
14
13
  this.bot = bot;
14
+ this.onError = onError;
15
15
  }
16
16
  on(updateName, handler) {
17
17
  return this.use(async (context, next) => {
@@ -23,21 +23,7 @@ class Updates {
23
23
  });
24
24
  }
25
25
  use(handler) {
26
- this.composer
27
- .caught((ctx, error) => {
28
- if (error instanceof _TelegramError_1.TelegramError)
29
- return this.errorHandler?.({
30
- context: ctx,
31
- kind: "TELEGRAM",
32
- error: error,
33
- });
34
- this.errorHandler?.({
35
- context: ctx,
36
- kind: "UNKNOWN",
37
- error: error,
38
- });
39
- })
40
- .use(handler);
26
+ this.composer.caught(this.onError).use(handler);
41
27
  return this;
42
28
  }
43
29
  async handleUpdate(data) {
@@ -99,8 +85,5 @@ class Updates {
99
85
  stopPolling() {
100
86
  this.isStarted = false;
101
87
  }
102
- onError(handler) {
103
- this.errorHandler = handler;
104
- }
105
88
  }
106
89
  exports.Updates = Updates;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gramio",
3
- "version": "0.0.10",
3
+ "version": "0.0.12",
4
4
  "description": "WIP",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -14,13 +14,14 @@
14
14
  "license": "ISC",
15
15
  "devDependencies": {
16
16
  "@biomejs/biome": "1.5.3",
17
- "@gramio/types": "^7.1.1",
18
- "@types/node": "^20.11.19"
17
+ "@gramio/types": "^7.1.2",
18
+ "@types/node": "^20.11.19",
19
+ "typescript": "^5.3.3"
19
20
  },
20
21
  "dependencies": {
21
22
  "@gramio/contexts": "^0.0.2",
22
23
  "@gramio/files": "^0.0.3",
23
- "@gramio/format": "^0.0.7",
24
+ "@gramio/format": "^0.0.8",
24
25
  "@gramio/keyboards": "^0.1.6",
25
26
  "form-data-encoder": "^4.0.2",
26
27
  "inspectable": "^2.1.0",