@adriangalilea/utils 2.28.0 → 2.29.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/bot/groups.d.ts +11 -5
- package/dist/bot/groups.d.ts.map +1 -1
- package/dist/bot/groups.js +34 -28
- package/dist/bot/groups.js.map +1 -1
- package/package.json +1 -1
package/dist/bot/groups.d.ts
CHANGED
|
@@ -25,12 +25,16 @@ export type ChatIdCtx = {
|
|
|
25
25
|
};
|
|
26
26
|
/** Minimum ctx shape `isGroupAdmin` reads. `bot` is the running gramio Bot — typed
|
|
27
27
|
* `unknown` so MenuCtx and every real ctx assign without a cast; its
|
|
28
|
-
* `api.
|
|
28
|
+
* `api.getChatAdministrators` is asserted at call time. `senderChat` is the
|
|
29
|
+
* anonymous-admin identity on message ctxs (callback ctxs have none). */
|
|
29
30
|
export type GroupAdminCtx = ChatIdCtx & {
|
|
30
31
|
bot: unknown;
|
|
31
32
|
from?: {
|
|
32
33
|
id?: number;
|
|
33
34
|
};
|
|
35
|
+
senderChat?: {
|
|
36
|
+
id?: number;
|
|
37
|
+
};
|
|
34
38
|
};
|
|
35
39
|
/** The ctx's chat id, whatever the event spelling. `undefined` only when the event has
|
|
36
40
|
* no chat at all (inline queries, some service events). */
|
|
@@ -40,10 +44,12 @@ export declare const isGroupChat: (ctx: ChatTypeCtx) => boolean;
|
|
|
40
44
|
/** True for a 1:1 private chat. */
|
|
41
45
|
export declare const isPrivateChat: (ctx: ChatTypeCtx) => boolean;
|
|
42
46
|
/**
|
|
43
|
-
* Whether a user is a creator/administrator of a group, per `
|
|
44
|
-
* user default to the ctx's own ({@link chatIdOf}, `ctx.from.id` —
|
|
45
|
-
* parameter-defaulting idiom); pass `chatId` / `userId` for cross-chat
|
|
46
|
-
*
|
|
47
|
+
* Whether a user is a creator/administrator of a group, per `getChatAdministrators`
|
|
48
|
+
* membership. Chat and user default to the ctx's own ({@link chatIdOf}, `ctx.from.id` —
|
|
49
|
+
* gramio's own parameter-defaulting idiom); pass `chatId` / `userId` for cross-chat
|
|
50
|
+
* checks. An anonymous admin's own message (`senderChat` = the chat itself) answers
|
|
51
|
+
* `true` without an API call — unless an explicit `userId` asks about someone else.
|
|
52
|
+
* Missing ids (an actor-less service event) or an API rejection answer `false`.
|
|
47
53
|
*/
|
|
48
54
|
export declare function isGroupAdmin(ctx: GroupAdminCtx, opts?: {
|
|
49
55
|
chatId?: number;
|
package/dist/bot/groups.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"groups.d.ts","sourceRoot":"","sources":["../../src/bot/groups.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"groups.d.ts","sourceRoot":"","sources":["../../src/bot/groups.ts"],"names":[],"mappings":"AAsDA;8DAC8D;AAC9D,MAAM,MAAM,WAAW,GAAG;IACzB,IAAI,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACzB,OAAO,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE;YAAE,IAAI,CAAC,EAAE,MAAM,CAAA;SAAE,CAAA;KAAE,CAAC;CACvC,CAAC;AAEF;sCACsC;AACtC,MAAM,MAAM,SAAS,GAAG;IACvB,IAAI,CAAC,EAAE;QAAE,EAAE,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACvB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE;YAAE,EAAE,CAAC,EAAE,MAAM,CAAA;SAAE,CAAA;KAAE,CAAC;CACrC,CAAC;AAEF;;;0EAG0E;AAC1E,MAAM,MAAM,aAAa,GAAG,SAAS,GAAG;IACvC,GAAG,EAAE,OAAO,CAAC;IACb,IAAI,CAAC,EAAE;QAAE,EAAE,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACvB,UAAU,CAAC,EAAE;QAAE,EAAE,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CAC7B,CAAC;AAUF;4DAC4D;AAC5D,eAAO,MAAM,QAAQ,GAAI,KAAK,SAAS,KAAG,MAAM,GAAG,SACC,CAAC;AAMrD,2CAA2C;AAC3C,eAAO,MAAM,WAAW,GAAI,KAAK,WAAW,KAAG,OAG9C,CAAC;AAEF,mCAAmC;AACnC,eAAO,MAAM,aAAa,GAAI,KAAK,WAAW,KAAG,OACnB,CAAC;AAE/B;;;;;;;GAOG;AACH,wBAAsB,YAAY,CACjC,GAAG,EAAE,aAAa,EAClB,IAAI,GAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAO,GAC7C,OAAO,CAAC,OAAO,CAAC,CAmBlB"}
|
package/dist/bot/groups.js
CHANGED
|
@@ -3,9 +3,12 @@
|
|
|
3
3
|
* check. Worker-safe (no env, no OS).
|
|
4
4
|
*
|
|
5
5
|
* Telegram's Bot API has no "is this user a group admin?" primitive and neither does
|
|
6
|
-
* gramio
|
|
7
|
-
*
|
|
8
|
-
*
|
|
6
|
+
* gramio. The raw material is `getChatAdministrators` — NOT `getChatMember`: in a group
|
|
7
|
+
* whose member list is hidden, `getChatMember` on another user rejects with
|
|
8
|
+
* `CHAT_ADMIN_REQUIRED` for a non-admin bot, so a gate built on it denies EVERY real
|
|
9
|
+
* admin (creator included) while the admin list itself stays readable. Every bot with an
|
|
10
|
+
* admin-gated group setting re-rolls that check; this module rolls it ONCE, on the call
|
|
11
|
+
* that works everywhere the bot is present.
|
|
9
12
|
*
|
|
10
13
|
* ## The two ctx spellings (the trap this module absorbs)
|
|
11
14
|
*
|
|
@@ -21,19 +24,22 @@
|
|
|
21
24
|
* These take minimum structural ctx shapes (the `bot/ctx.ts` philosophy) that real
|
|
22
25
|
* gramio contexts AND `bot/menu`'s `MenuCtx` satisfy by duck typing — a menu action
|
|
23
26
|
* gates with `isGroupAdmin(ctx)`, no cast. `ctx.bot` stays `unknown` for exactly that
|
|
24
|
-
* assignability (MenuCtx's own choice); a ctx whose bot has no
|
|
25
|
-
* PANICS — a miswire screams, it doesn't read as "not
|
|
27
|
+
* assignability (MenuCtx's own choice); a ctx whose bot has no
|
|
28
|
+
* `api.getChatAdministrators` PANICS — a miswire screams, it doesn't read as "not
|
|
29
|
+
* admin".
|
|
26
30
|
*
|
|
27
|
-
* `isGroupAdmin` FAILS CLOSED on the API call itself: a `
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
31
|
+
* `isGroupAdmin` FAILS CLOSED on the API call itself: a `getChatAdministrators`
|
|
32
|
+
* rejection (the bot removed mid-tap, network) answers `false`, never throws — it is a
|
|
33
|
+
* permission gate, and a gate that throws breaks the surface it guards (a menu tap's
|
|
34
|
+
* single answerCallbackQuery, a command reply). That rejection is the messy real world;
|
|
35
|
+
* denial is the only honest answer to "I couldn't verify".
|
|
32
36
|
*
|
|
33
|
-
* Anonymous
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
+
* Anonymous admins: a message posted "as the group" carries the chat itself in
|
|
38
|
+
* `sender_chat`, and Telegram offers that identity only to the chat's own admins — so
|
|
39
|
+
* `sender_chat.id === chat.id` IS the proof, no API call (a linked channel's auto-forward
|
|
40
|
+
* wears the CHANNEL's id, so the equality never false-positives). Callback taps always
|
|
41
|
+
* carry the real user, and anonymous admins appear in the admin list under their real
|
|
42
|
+
* user, so inline-button gates resolve them too.
|
|
37
43
|
*
|
|
38
44
|
* @example
|
|
39
45
|
* // Gate a group-wide toggle: the bot's own admins always may, otherwise the
|
|
@@ -45,9 +51,6 @@
|
|
|
45
51
|
* await isGroupAdmin(ctx, { chatId: -100123, userId: 42 })
|
|
46
52
|
*/
|
|
47
53
|
import { assert } from "../offensive.js";
|
|
48
|
-
// The two chat-member statuses that hold power over a group. Not exported: the
|
|
49
|
-
// contract is the question `isGroupAdmin` answers, never status comparison.
|
|
50
|
-
const GROUP_ADMIN_STATUSES = new Set(["creator", "administrator"]);
|
|
51
54
|
/** The ctx's chat id, whatever the event spelling. `undefined` only when the event has
|
|
52
55
|
* no chat at all (inline queries, some service events). */
|
|
53
56
|
export const chatIdOf = (ctx) => ctx.chat?.id ?? ctx.chatId ?? ctx.message?.chat?.id;
|
|
@@ -61,24 +64,27 @@ export const isGroupChat = (ctx) => {
|
|
|
61
64
|
/** True for a 1:1 private chat. */
|
|
62
65
|
export const isPrivateChat = (ctx) => chatTypeOf(ctx) === "private";
|
|
63
66
|
/**
|
|
64
|
-
* Whether a user is a creator/administrator of a group, per `
|
|
65
|
-
* user default to the ctx's own ({@link chatIdOf}, `ctx.from.id` —
|
|
66
|
-
* parameter-defaulting idiom); pass `chatId` / `userId` for cross-chat
|
|
67
|
-
*
|
|
67
|
+
* Whether a user is a creator/administrator of a group, per `getChatAdministrators`
|
|
68
|
+
* membership. Chat and user default to the ctx's own ({@link chatIdOf}, `ctx.from.id` —
|
|
69
|
+
* gramio's own parameter-defaulting idiom); pass `chatId` / `userId` for cross-chat
|
|
70
|
+
* checks. An anonymous admin's own message (`senderChat` = the chat itself) answers
|
|
71
|
+
* `true` without an API call — unless an explicit `userId` asks about someone else.
|
|
72
|
+
* Missing ids (an actor-less service event) or an API rejection answer `false`.
|
|
68
73
|
*/
|
|
69
74
|
export async function isGroupAdmin(ctx, opts = {}) {
|
|
70
75
|
const chatId = opts.chatId ?? chatIdOf(ctx);
|
|
76
|
+
if (chatId === undefined)
|
|
77
|
+
return false;
|
|
78
|
+
if (opts.userId === undefined && ctx.senderChat?.id === chatId)
|
|
79
|
+
return true;
|
|
71
80
|
const userId = opts.userId ?? ctx.from?.id;
|
|
72
|
-
if (
|
|
81
|
+
if (userId === undefined)
|
|
73
82
|
return false;
|
|
74
83
|
const bot = ctx.bot;
|
|
75
|
-
assert(typeof bot?.api?.
|
|
84
|
+
assert(typeof bot?.api?.getChatAdministrators === "function", "isGroupAdmin: ctx.bot.api.getChatAdministrators missing — not a gramio ctx?");
|
|
76
85
|
try {
|
|
77
|
-
const
|
|
78
|
-
|
|
79
|
-
user_id: userId,
|
|
80
|
-
});
|
|
81
|
-
return GROUP_ADMIN_STATUSES.has(member.status ?? "");
|
|
86
|
+
const admins = await bot.api.getChatAdministrators({ chat_id: chatId });
|
|
87
|
+
return admins.some((m) => m.user?.id === userId);
|
|
82
88
|
}
|
|
83
89
|
catch {
|
|
84
90
|
return false;
|
package/dist/bot/groups.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"groups.js","sourceRoot":"","sources":["../../src/bot/groups.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"groups.js","sourceRoot":"","sources":["../../src/bot/groups.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmDG;AACH,OAAO,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AAmCzC;4DAC4D;AAC5D,MAAM,CAAC,MAAM,QAAQ,GAAG,CAAC,GAAc,EAAsB,EAAE,CAC9D,GAAG,CAAC,IAAI,EAAE,EAAE,IAAI,GAAG,CAAC,MAAM,IAAI,GAAG,CAAC,OAAO,EAAE,IAAI,EAAE,EAAE,CAAC;AAErD,mEAAmE;AACnE,MAAM,UAAU,GAAG,CAAC,GAAgB,EAAsB,EAAE,CAC3D,GAAG,CAAC,IAAI,EAAE,IAAI,IAAI,GAAG,CAAC,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC;AAE3C,2CAA2C;AAC3C,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,GAAgB,EAAW,EAAE;IACxD,MAAM,IAAI,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC;IAC7B,OAAO,IAAI,KAAK,OAAO,IAAI,IAAI,KAAK,YAAY,CAAC;AAClD,CAAC,CAAC;AAEF,mCAAmC;AACnC,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,GAAgB,EAAW,EAAE,CAC1D,UAAU,CAAC,GAAG,CAAC,KAAK,SAAS,CAAC;AAE/B;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CACjC,GAAkB,EAClB,OAA6C,EAAE;IAE/C,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,IAAI,QAAQ,CAAC,GAAG,CAAC,CAAC;IAC5C,IAAI,MAAM,KAAK,SAAS;QAAE,OAAO,KAAK,CAAC;IACvC,IAAI,IAAI,CAAC,MAAM,KAAK,SAAS,IAAI,GAAG,CAAC,UAAU,EAAE,EAAE,KAAK,MAAM;QAAE,OAAO,IAAI,CAAC;IAC5E,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,IAAI,GAAG,CAAC,IAAI,EAAE,EAAE,CAAC;IAC3C,IAAI,MAAM,KAAK,SAAS;QAAE,OAAO,KAAK,CAAC;IACvC,MAAM,GAAG,GAAG,GAAG,CAAC,GAEJ,CAAC;IACb,MAAM,CACL,OAAO,GAAG,EAAE,GAAG,EAAE,qBAAqB,KAAK,UAAU,EACrD,6EAA6E,CAC7E,CAAC;IACF,IAAI,CAAC;QACJ,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,GAAG,CAAC,qBAAqB,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC;QACxE,OAAO,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,EAAE,KAAK,MAAM,CAAC,CAAC;IAClD,CAAC;IAAC,MAAM,CAAC;QACR,OAAO,KAAK,CAAC;IACd,CAAC;AACF,CAAC"}
|
package/package.json
CHANGED