gramio 0.0.9 → 0.0.11

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.
@@ -0,0 +1,8 @@
1
+ import { APIMethodParams, APIMethods, TelegramAPIResponseError, TelegramResponseParameters } from "@gramio/types";
2
+ export declare class TelegramError<T extends keyof APIMethods> extends Error {
3
+ method: T;
4
+ params: APIMethodParams<T>;
5
+ code: number;
6
+ payload?: TelegramResponseParameters;
7
+ constructor(error: TelegramAPIResponseError, method: T, params: APIMethodParams<T>);
8
+ }
@@ -1,21 +1,19 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.APIError = void 0;
4
- //TODO: more elegant
5
- class APIError extends Error {
3
+ exports.TelegramError = void 0;
4
+ class TelegramError extends Error {
6
5
  method;
7
6
  params;
8
7
  code;
9
8
  payload;
10
- constructor({ method, params }, error) {
9
+ constructor(error, method, params) {
11
10
  super(error.description);
12
11
  this.name = method;
13
12
  this.method = method;
14
13
  this.params = params;
15
14
  this.code = error.error_code;
16
- //TODO: delete when undefined
17
15
  if (error.parameters)
18
16
  this.payload = error.parameters;
19
17
  }
20
18
  }
21
- exports.APIError = APIError;
19
+ exports.TelegramError = TelegramError;
package/dist/bot.d.ts CHANGED
@@ -1,11 +1,60 @@
1
- import type { ApiMethods } from "@gramio/types";
1
+ import type { APIMethods } from "@gramio/types";
2
2
  import "reflect-metadata";
3
- import { BotOptions } from "./types";
3
+ import { BotOptions, ErrorDefinitions, Hooks } from "./types";
4
4
  import { Updates } from "./updates";
5
- export declare class Bot {
5
+ export declare class Bot<Errors extends ErrorDefinitions = {}> {
6
6
  readonly options: BotOptions;
7
- readonly api: ApiMethods;
7
+ readonly api: APIMethods;
8
+ private errorsDefinitions;
9
+ private errorHandler;
8
10
  updates: Updates;
9
- private _callApi;
11
+ private hooks;
10
12
  constructor(token: string, options?: Omit<BotOptions, "token">);
13
+ private runHooks;
14
+ private runImmutableHooks;
15
+ private _callApi;
16
+ /**
17
+ * Register custom class-error for type-safe catch in `onError` hook
18
+ *
19
+ * @example
20
+ * ```ts
21
+ * export class NoRights extends Error {
22
+ * needRole: "admin" | "moderator";
23
+ *
24
+ * constructor(role: "admin" | "moderator") {
25
+ * super();
26
+ * this.needRole = role;
27
+ * }
28
+ * }
29
+ *
30
+ * const bot = new Bot(process.env.TOKEN!)
31
+ * .error("NO_RIGHTS", NoRights)
32
+ * .onError(({ context, kind, error }) => {
33
+ * if (context.is("message") && kind === "NO_RIGHTS")
34
+ * return context.send(
35
+ * format`You don't have enough rights! You need to have an «${bold(
36
+ * error.needRole
37
+ * )}» role.`
38
+ * );
39
+ * });
40
+ *
41
+ * bot.updates.on("message", (context) => {
42
+ * if (context.text === "bun") throw new NoRights("admin");
43
+ * });
44
+ * ```
45
+ */
46
+ error<Name extends string, NewError extends {
47
+ new (...args: any): any;
48
+ prototype: Error;
49
+ }>(kind: Name, error: NewError): Bot<Errors & { [name in Name]: InstanceType<NewError>; }>;
50
+ /**
51
+ * Set error handler.
52
+ * @example
53
+ * ```ts
54
+ * bot.updates.onError(({ context, kind, error }) => {
55
+ * if(context.is("message")) return context.send(`${kind}: ${error.message}`);
56
+ * })
57
+ * ```
58
+ */
59
+ onError(handler: Hooks.OnError<Errors>): this;
11
60
  }
package/dist/bot.js CHANGED
@@ -13,24 +13,68 @@ 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 apiErrors_1 = require("./apiErrors");
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
+ if (formattable)
42
+ ctx.params = formattable(ctx.params);
43
+ return ctx;
44
+ },
45
+ ],
46
+ onError: [],
47
+ };
48
+ constructor(token, options) {
49
+ this.options = { ...options, token };
50
+ }
51
+ async runHooks(type, context) {
52
+ let data = context;
53
+ for await (const hook of this.hooks[type]) {
54
+ data = await hook(data);
55
+ }
56
+ return data;
57
+ }
58
+ async runImmutableHooks(type, context) {
59
+ for await (const hook of this.hooks[type]) {
60
+ await hook(context);
61
+ }
62
+ }
24
63
  async _callApi(method, params = {}) {
25
64
  const url = `https://api.telegram.org/bot${this.options.token}/${method}`;
26
65
  const reqOptions = {
27
66
  method: "POST",
28
67
  duplex: "half",
29
68
  };
30
- const formattable = format_1.FormattableMap[method];
31
- // biome-ignore lint/style/noParameterAssign: mutate formattable
32
- if (formattable && params)
33
- params = formattable(params);
69
+ const context = await this.runHooks("preRequest",
70
+ // TODO: fix type error
71
+ // @ts-expect-error
72
+ {
73
+ method,
74
+ params,
75
+ });
76
+ // biome-ignore lint/style/noParameterAssign: mutate params
77
+ params = context.params;
34
78
  if (params && (0, files_1.isMediaUpload)(method, params)) {
35
79
  const formData = await (0, files_1.convertJsonToFormData)(method, params);
36
80
  const encoder = new form_data_encoder_1.FormDataEncoder(formData);
@@ -46,11 +90,57 @@ let Bot = class Bot {
46
90
  const response = await (0, undici_1.fetch)(url, reqOptions);
47
91
  const data = (await response.json());
48
92
  if (!data.ok)
49
- throw new apiErrors_1.APIError({ method, params }, data);
93
+ throw new errors_1.TelegramError(data, method, params);
50
94
  return data.result;
51
95
  }
52
- constructor(token, options) {
53
- this.options = { ...options, token };
96
+ /**
97
+ * Register custom class-error for type-safe catch in `onError` hook
98
+ *
99
+ * @example
100
+ * ```ts
101
+ * export class NoRights extends Error {
102
+ * needRole: "admin" | "moderator";
103
+ *
104
+ * constructor(role: "admin" | "moderator") {
105
+ * super();
106
+ * this.needRole = role;
107
+ * }
108
+ * }
109
+ *
110
+ * const bot = new Bot(process.env.TOKEN!)
111
+ * .error("NO_RIGHTS", NoRights)
112
+ * .onError(({ context, kind, error }) => {
113
+ * if (context.is("message") && kind === "NO_RIGHTS")
114
+ * return context.send(
115
+ * format`You don't have enough rights! You need to have an «${bold(
116
+ * error.needRole
117
+ * )}» role.`
118
+ * );
119
+ * });
120
+ *
121
+ * bot.updates.on("message", (context) => {
122
+ * if (context.text === "bun") throw new NoRights("admin");
123
+ * });
124
+ * ```
125
+ */
126
+ error(kind, error) {
127
+ //@ts-expect-error Set ErrorKind
128
+ error[errors_1.ErrorKind] = kind;
129
+ this.errorsDefinitions[kind] = error;
130
+ return this;
131
+ }
132
+ /**
133
+ * Set error handler.
134
+ * @example
135
+ * ```ts
136
+ * bot.updates.onError(({ context, kind, error }) => {
137
+ * if(context.is("message")) return context.send(`${kind}: ${error.message}`);
138
+ * })
139
+ * ```
140
+ */
141
+ onError(handler) {
142
+ this.hooks.onError.push(handler);
143
+ return this;
54
144
  }
55
145
  };
56
146
  exports.Bot = Bot;
@@ -58,4 +148,5 @@ exports.Bot = Bot = __decorate([
58
148
  (0, inspectable_1.Inspectable)({
59
149
  serialize: () => ({}),
60
150
  })
151
+ // biome-ignore lint/complexity/noBannedTypes: <explanation>
61
152
  ], Bot);
@@ -0,0 +1,9 @@
1
+ import { APIMethodParams, APIMethods, TelegramAPIResponseError, TelegramResponseParameters } from "@gramio/types";
2
+ export declare const ErrorKind: unique symbol;
3
+ export declare class TelegramError<T extends keyof APIMethods> extends Error {
4
+ method: T;
5
+ params: APIMethodParams<T>;
6
+ code: number;
7
+ payload?: TelegramResponseParameters;
8
+ constructor(error: TelegramAPIResponseError, method: T, params: APIMethodParams<T>);
9
+ }
package/dist/errors.js ADDED
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TelegramError = exports.ErrorKind = void 0;
4
+ exports.ErrorKind = Symbol("ErrorKind");
5
+ class TelegramError extends Error {
6
+ method;
7
+ params;
8
+ code;
9
+ payload;
10
+ constructor(error, method, params) {
11
+ super(error.description);
12
+ this.name = method;
13
+ this.method = method;
14
+ this.params = params;
15
+ this.code = error.error_code;
16
+ if (error.parameters)
17
+ this.payload = error.parameters;
18
+ }
19
+ }
20
+ exports.TelegramError = TelegramError;
21
+ //@ts-expect-error
22
+ TelegramError.constructor[exports.ErrorKind] = "TELEGRAM";
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  export * from "./bot";
2
- export * from "./apiErrors";
2
+ export * from "./errors";
3
3
  export * from "./types";
4
4
  export * from "@gramio/contexts";
5
5
  export * from "@gramio/files";
package/dist/index.js CHANGED
@@ -15,7 +15,7 @@ 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("./apiErrors"), exports);
18
+ __exportStar(require("./errors"), exports);
19
19
  __exportStar(require("./types"), exports);
20
20
  __exportStar(require("@gramio/contexts"), exports);
21
21
  __exportStar(require("@gramio/files"), exports);
package/dist/types.d.ts CHANGED
@@ -1,7 +1,39 @@
1
- import { contextsMappings } from "@gramio/contexts";
1
+ import { Context } from "@gramio/contexts";
2
+ import { APIMethodParams, APIMethods } from "@gramio/types";
2
3
  import { NextMiddleware } from "middleware-io";
4
+ import { TelegramError } from "./errors";
3
5
  export interface BotOptions {
4
6
  token?: string;
5
7
  }
6
- export type THandler<T> = (context: T, next: NextMiddleware) => unknown;
7
- export type UpdateNames = keyof typeof contextsMappings;
8
+ export type Handler<T> = (context: T, next: NextMiddleware) => unknown;
9
+ interface ErrorHandlerParams<Kind extends string, Err> {
10
+ context: Context;
11
+ kind: Kind;
12
+ error: Err;
13
+ }
14
+ type AnyTelegramError = {
15
+ [APIMethod in keyof APIMethods]: TelegramError<APIMethod>;
16
+ }[keyof APIMethods];
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, {
37
+ prototype: Error;
38
+ }>;
39
+ export {};
package/dist/updates.d.ts CHANGED
@@ -1,15 +1,17 @@
1
- import { Context, contextsMappings } from "@gramio/contexts";
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 { THandler, UpdateNames } 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
- constructor(bot: Bot);
11
- on<T extends UpdateNames>(updateName: T, handler: THandler<InstanceType<(typeof contextsMappings)[T]>>): this;
12
- use(handler: THandler<Context>): this;
11
+ private onError;
12
+ constructor(bot: Bot<any>, onError: CaughtMiddlewareHandler<Context>);
13
+ on<T extends UpdateName>(updateName: T, handler: Handler<InstanceType<(typeof contextsMappings)[T]>>): this;
14
+ use(handler: Handler<Context>): this;
13
15
  handleUpdate(data: TelegramUpdate): Promise<void>;
14
16
  startPolling(): Promise<void>;
15
17
  startFetchLoop(): Promise<void>;
package/dist/updates.js CHANGED
@@ -4,45 +4,58 @@ exports.Updates = void 0;
4
4
  const contexts_1 = require("@gramio/contexts");
5
5
  const middleware_io_1 = require("middleware-io");
6
6
  class Updates {
7
+ // biome-ignore lint/suspicious/noExplicitAny: <explanation>
7
8
  bot;
8
9
  isStarted = false;
9
10
  offset = 0;
10
11
  composer = middleware_io_1.Composer.builder();
11
- constructor(bot) {
12
+ onError;
13
+ // biome-ignore lint/suspicious/noExplicitAny: <explanation>
14
+ constructor(bot, onError) {
12
15
  this.bot = bot;
16
+ this.onError = onError;
13
17
  }
14
18
  on(updateName, handler) {
15
- return this.use((context, next) => {
19
+ return this.use(async (context, next) => {
16
20
  //TODO: fix typings
17
21
  if (context.is(updateName))
18
- handler(context, next);
22
+ await handler(context, next);
19
23
  else
20
- next();
24
+ await next();
21
25
  });
22
26
  }
23
27
  use(handler) {
24
- this.composer.use(handler);
28
+ this.composer.caught(this.onError).use(handler);
25
29
  return this;
26
30
  }
27
31
  async handleUpdate(data) {
28
32
  const updateType = Object.keys(data).at(1);
29
33
  this.offset = data.update_id + 1;
34
+ const UpdateContext = contexts_1.contextsMappings[updateType];
35
+ if (!UpdateContext)
36
+ return;
30
37
  try {
31
- const context = new contexts_1.contextsMappings[updateType]({
32
- // @ts-expect-error
38
+ let context = new UpdateContext({
33
39
  bot: this.bot,
34
40
  update: data,
35
- //TODO: fix
36
- //@ts-ignore
41
+ //@ts-expect-error
37
42
  payload: data[updateType],
38
43
  type: updateType,
39
44
  updateId: data.update_id,
40
- // raw: {
41
- // update: data,
42
- // updateId: data.update_id,
43
- // updateType,
44
- // },
45
45
  });
46
+ if ("isEvent" in context && context.isEvent() && context.eventType) {
47
+ context = new contexts_1.contextsMappings[context.eventType]({
48
+ bot: this.bot,
49
+ update: data,
50
+ //@ts-expect-error
51
+ payload: data.message ??
52
+ data.edited_message ??
53
+ data.channel_post ??
54
+ data.edited_channel_post,
55
+ type: context.eventType,
56
+ updateId: data.update_id,
57
+ });
58
+ }
46
59
  this.composer.compose()(
47
60
  //TODO: fix typings
48
61
  context, middleware_io_1.noopNext);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gramio",
3
- "version": "0.0.9",
3
+ "version": "0.0.11",
4
4
  "description": "WIP",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -14,7 +14,7 @@
14
14
  "license": "ISC",
15
15
  "devDependencies": {
16
16
  "@biomejs/biome": "1.5.3",
17
- "@gramio/types": "^7.1.0",
17
+ "@gramio/types": "^7.1.1",
18
18
  "@types/node": "^20.11.19"
19
19
  },
20
20
  "dependencies": {
@@ -1,12 +0,0 @@
1
- import { TelegramAPIResponseError } from "@gramio/types";
2
- export interface APIErrorDetails {
3
- method: string;
4
- params: Record<string, any>;
5
- }
6
- export declare class APIError extends Error {
7
- method: string;
8
- params: Record<string, any>;
9
- code: number;
10
- payload?: TelegramAPIResponseError["parameters"];
11
- constructor({ method, params }: APIErrorDetails, error: TelegramAPIResponseError);
12
- }