@aspalagin/openclaw-max 0.7.2
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/LICENSE +21 -0
- package/README.md +424 -0
- package/SKILL.md +108 -0
- package/dist/index.js +17 -0
- package/dist/setup-entry.js +8 -0
- package/dist/src/accounts.js +148 -0
- package/dist/src/actions.js +421 -0
- package/dist/src/api.js +553 -0
- package/dist/src/callbacks.js +111 -0
- package/dist/src/channel-agent-prompt.js +38 -0
- package/dist/src/channel-config.js +103 -0
- package/dist/src/channel-directory.js +84 -0
- package/dist/src/channel-lifecycle.js +219 -0
- package/dist/src/channel-outbound.js +133 -0
- package/dist/src/channel-policy.js +130 -0
- package/dist/src/channel.js +76 -0
- package/dist/src/config-schema.js +133 -0
- package/dist/src/deliver.js +126 -0
- package/dist/src/dispatch.js +186 -0
- package/dist/src/format.js +31 -0
- package/dist/src/inbound-attachments.js +125 -0
- package/dist/src/inbound.js +362 -0
- package/dist/src/media-temp.js +92 -0
- package/dist/src/model-buttons.js +96 -0
- package/dist/src/monitor-types.js +27 -0
- package/dist/src/monitor.js +40 -0
- package/dist/src/onboarding.js +180 -0
- package/dist/src/polling.js +98 -0
- package/dist/src/presentation.js +374 -0
- package/dist/src/runtime.js +36 -0
- package/dist/src/russian-trusted-ca.js +84 -0
- package/dist/src/send.js +612 -0
- package/dist/src/state.js +111 -0
- package/dist/src/sticker-cache.js +26 -0
- package/dist/src/stream-draft.js +102 -0
- package/dist/src/types.js +5 -0
- package/dist/src/webhook-queue.js +215 -0
- package/dist/src/webhook-runner.js +233 -0
- package/dist/src/webhook.js +196 -0
- package/openclaw.plugin.json +263 -0
- package/package.json +100 -0
- package/scripts/check-cycles.mjs +38 -0
- package/scripts/test-api.mjs +169 -0
- package/scripts/test-send.mjs +162 -0
- package/scripts/update-schema.mjs +48 -0
- package/src/accounts.ts +193 -0
- package/src/actions.ts +501 -0
- package/src/api.ts +758 -0
- package/src/callbacks.ts +140 -0
- package/src/channel-agent-prompt.ts +47 -0
- package/src/channel-config.ts +138 -0
- package/src/channel-directory.ts +90 -0
- package/src/channel-lifecycle.ts +256 -0
- package/src/channel-outbound.ts +159 -0
- package/src/channel-policy.ts +185 -0
- package/src/channel.ts +99 -0
- package/src/config-schema.ts +154 -0
- package/src/deliver.ts +168 -0
- package/src/dispatch.ts +224 -0
- package/src/format.ts +33 -0
- package/src/inbound-attachments.ts +152 -0
- package/src/inbound.ts +412 -0
- package/src/media-temp.ts +103 -0
- package/src/model-buttons.ts +150 -0
- package/src/monitor-types.ts +61 -0
- package/src/monitor.ts +49 -0
- package/src/onboarding.ts +215 -0
- package/src/polling.ts +125 -0
- package/src/presentation.ts +452 -0
- package/src/runtime.ts +56 -0
- package/src/russian-trusted-ca.ts +86 -0
- package/src/send.ts +774 -0
- package/src/state.ts +149 -0
- package/src/sticker-cache.ts +28 -0
- package/src/stream-draft.ts +125 -0
- package/src/types.ts +263 -0
- package/src/webhook-queue.ts +248 -0
- package/src/webhook-runner.ts +296 -0
- package/src/webhook.ts +268 -0
package/src/state.ts
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Persistent per-account MAX state: long-polling marker, chat registry and the
|
|
3
|
+
* auto-generated webhook secret.
|
|
4
|
+
*
|
|
5
|
+
* The marker survives gateway restarts so updates are neither replayed nor
|
|
6
|
+
* lost past the server-side retention window. The chat registry replaces the
|
|
7
|
+
* deprecated GET /chats (June 2026): chats are collected from bot_added /
|
|
8
|
+
* bot_started / chat_title_changed updates and passively from group messages.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import * as fs from 'node:fs/promises';
|
|
12
|
+
import * as path from 'node:path';
|
|
13
|
+
|
|
14
|
+
import { readJsonFileWithFallback, writeJsonFileAtomically } from 'openclaw/plugin-sdk/json-store';
|
|
15
|
+
import { resolveStateDir } from 'openclaw/plugin-sdk/state-paths';
|
|
16
|
+
|
|
17
|
+
export interface MaxChatRegistryEntry {
|
|
18
|
+
chatId: number;
|
|
19
|
+
/** "chat" | "channel" | "dialog" */
|
|
20
|
+
type?: string;
|
|
21
|
+
title?: string;
|
|
22
|
+
addedAt?: number;
|
|
23
|
+
/** Set when bot_removed / dialog_removed arrives; cleared on re-add */
|
|
24
|
+
removedAt?: number;
|
|
25
|
+
/** bot_stopped in a dialog: user halted the bot */
|
|
26
|
+
stopped?: boolean;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface MaxAccountState {
|
|
30
|
+
marker?: number | null;
|
|
31
|
+
chats?: Record<string, MaxChatRegistryEntry>;
|
|
32
|
+
/** Auto-generated webhook secret, kept so a restart re-subscribes with the same one */
|
|
33
|
+
webhookSecret?: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function resolveMaxStatePath(accountId: string): string {
|
|
37
|
+
const safeId = accountId.replace(/[^a-zA-Z0-9_-]/g, '_');
|
|
38
|
+
return path.join(resolveStateDir(), 'max', `state-${safeId}.json`);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export async function loadMaxAccountState(accountId: string): Promise<MaxAccountState> {
|
|
42
|
+
const { value } = await readJsonFileWithFallback<MaxAccountState>(
|
|
43
|
+
resolveMaxStatePath(accountId),
|
|
44
|
+
{},
|
|
45
|
+
);
|
|
46
|
+
return value && typeof value === 'object' ? value : {};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export async function saveMaxAccountState(
|
|
50
|
+
accountId: string,
|
|
51
|
+
state: MaxAccountState,
|
|
52
|
+
): Promise<void> {
|
|
53
|
+
const filePath = resolveMaxStatePath(accountId);
|
|
54
|
+
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
55
|
+
await writeJsonFileAtomically(filePath, state);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* In-memory handle over the persisted state with coalesced writes.
|
|
60
|
+
* Mutations mark the state dirty; flushes are serialized and best-effort.
|
|
61
|
+
*/
|
|
62
|
+
export class MaxStateStore {
|
|
63
|
+
private state: MaxAccountState = {};
|
|
64
|
+
private dirty = false;
|
|
65
|
+
private flushing: Promise<void> = Promise.resolve();
|
|
66
|
+
private loaded = false;
|
|
67
|
+
|
|
68
|
+
constructor(
|
|
69
|
+
private accountId: string,
|
|
70
|
+
private onError?: (err: unknown) => void,
|
|
71
|
+
) {}
|
|
72
|
+
|
|
73
|
+
async load(): Promise<MaxAccountState> {
|
|
74
|
+
if (!this.loaded) {
|
|
75
|
+
this.state = await loadMaxAccountState(this.accountId);
|
|
76
|
+
this.loaded = true;
|
|
77
|
+
}
|
|
78
|
+
return this.state;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
get marker(): number | null | undefined {
|
|
82
|
+
return this.state.marker;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
setMarker(marker: number | null): void {
|
|
86
|
+
if (this.state.marker === marker) return;
|
|
87
|
+
this.state.marker = marker;
|
|
88
|
+
this.scheduleFlush();
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
get webhookSecret(): string | undefined {
|
|
92
|
+
return this.state.webhookSecret;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
setWebhookSecret(secret: string): void {
|
|
96
|
+
if (this.state.webhookSecret === secret) return;
|
|
97
|
+
this.state.webhookSecret = secret;
|
|
98
|
+
this.scheduleFlush();
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
get chats(): Record<string, MaxChatRegistryEntry> {
|
|
102
|
+
if (!this.state.chats) this.state.chats = {};
|
|
103
|
+
return this.state.chats;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
upsertChat(chatId: number, patch: Partial<MaxChatRegistryEntry>): void {
|
|
107
|
+
const key = String(chatId);
|
|
108
|
+
const existing = this.chats[key];
|
|
109
|
+
const next: MaxChatRegistryEntry = { ...existing, ...patch, chatId };
|
|
110
|
+
// A re-appearing chat is no longer removed/stopped unless the patch says so
|
|
111
|
+
if (
|
|
112
|
+
patch.removedAt === undefined &&
|
|
113
|
+
existing?.removedAt !== undefined &&
|
|
114
|
+
patch.addedAt !== undefined
|
|
115
|
+
) {
|
|
116
|
+
delete next.removedAt;
|
|
117
|
+
}
|
|
118
|
+
this.chats[key] = next;
|
|
119
|
+
this.scheduleFlush();
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
hasChat(chatId: number): boolean {
|
|
123
|
+
return String(chatId) in this.chats;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/** True only if the chat is known AND not marked removed. */
|
|
127
|
+
hasActiveChat(chatId: number): boolean {
|
|
128
|
+
const entry = this.chats[String(chatId)];
|
|
129
|
+
return Boolean(entry) && entry.removedAt == null;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
private scheduleFlush(): void {
|
|
133
|
+
this.dirty = true;
|
|
134
|
+
this.flushing = this.flushing.then(async () => {
|
|
135
|
+
if (!this.dirty) return;
|
|
136
|
+
this.dirty = false;
|
|
137
|
+
try {
|
|
138
|
+
await saveMaxAccountState(this.accountId, this.state);
|
|
139
|
+
} catch (err) {
|
|
140
|
+
this.onError?.(err);
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/** Wait for pending writes (used on shutdown/tests). */
|
|
146
|
+
async flush(): Promise<void> {
|
|
147
|
+
await this.flushing;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Simple in-memory sticker code cache.
|
|
3
|
+
* Stores the last received sticker code per chat, so the agent can
|
|
4
|
+
* send it back without explicitly passing the code.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const lastStickerByChat = new Map<string, string>();
|
|
8
|
+
const lastStickerGlobal: { code: string | null } = { code: null };
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Store a sticker code received from a chat.
|
|
12
|
+
*/
|
|
13
|
+
export function rememberStickerCode(chatId: string | number, code: string): void {
|
|
14
|
+
const key = String(chatId);
|
|
15
|
+
lastStickerByChat.set(key, code);
|
|
16
|
+
lastStickerGlobal.code = code;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Get the last sticker code for a chat, or the last global one.
|
|
21
|
+
*/
|
|
22
|
+
export function getLastStickerCode(chatId?: string | number): string | null {
|
|
23
|
+
if (chatId != null) {
|
|
24
|
+
const code = lastStickerByChat.get(String(chatId));
|
|
25
|
+
if (code) return code;
|
|
26
|
+
}
|
|
27
|
+
return lastStickerGlobal.code;
|
|
28
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Edit streaming (streamMode "partial"): the first partial reply is sent as a
|
|
3
|
+
* message, later partials edit it (throttled), and the final answer replaces
|
|
4
|
+
* the draft together with its inline keyboard.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { ChannelLogSink } from 'openclaw/plugin-sdk/channel-contract';
|
|
8
|
+
|
|
9
|
+
import type { ResolvedMaxAccount } from './accounts.js';
|
|
10
|
+
import { editMaxMessage, type MaxSendButton, sendMaxMessage } from './send.js';
|
|
11
|
+
|
|
12
|
+
const DRAFT_THROTTLE_MS = 1200;
|
|
13
|
+
const DRAFT_MAX_CHARS = 4000;
|
|
14
|
+
const DRAFT_MIN_CHARS = 30; // Don't send until we have enough text
|
|
15
|
+
|
|
16
|
+
export interface MaxDraftStream {
|
|
17
|
+
/** Mid of the draft message once the first partial was sent. */
|
|
18
|
+
readonly messageId: string | null;
|
|
19
|
+
/** Show a partial reply (throttled; stops on errors or past 4000 chars). */
|
|
20
|
+
update(text: string): Promise<void>;
|
|
21
|
+
/** Replace the draft with the final text; buttons go onto the same edit. */
|
|
22
|
+
finalize(text: string, buttons: MaxSendButton[][] | undefined): Promise<void>;
|
|
23
|
+
/** Cancel a pending update and stop streaming. */
|
|
24
|
+
clear(): Promise<void>;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Draft stream for edit-streaming (like Telegram's partial reply approach). */
|
|
28
|
+
export function createMaxDraftStream(params: {
|
|
29
|
+
account: ResolvedMaxAccount;
|
|
30
|
+
chatId: string;
|
|
31
|
+
replyToId?: string;
|
|
32
|
+
log?: ChannelLogSink;
|
|
33
|
+
statusSink?: (patch: { lastOutboundAt?: number }) => void;
|
|
34
|
+
}): MaxDraftStream {
|
|
35
|
+
const { account, chatId, log, statusSink } = params;
|
|
36
|
+
let draftMid: string | null = null;
|
|
37
|
+
let draftLastText = '';
|
|
38
|
+
let draftLastEditAt = 0;
|
|
39
|
+
let draftTimer: ReturnType<typeof setTimeout> | null = null;
|
|
40
|
+
let draftStopped = false;
|
|
41
|
+
|
|
42
|
+
const draftUpdate = async (text: string) => {
|
|
43
|
+
if (draftStopped || !text) return;
|
|
44
|
+
const trimmed = text.trimEnd();
|
|
45
|
+
if (!trimmed || trimmed === draftLastText) return;
|
|
46
|
+
if (trimmed.length > DRAFT_MAX_CHARS) {
|
|
47
|
+
draftStopped = true;
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
if (!draftMid && trimmed.length < DRAFT_MIN_CHARS) return; // wait for more text
|
|
51
|
+
|
|
52
|
+
// Clear pending timer
|
|
53
|
+
if (draftTimer) {
|
|
54
|
+
clearTimeout(draftTimer);
|
|
55
|
+
draftTimer = null;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const now = Date.now();
|
|
59
|
+
const elapsed = now - draftLastEditAt;
|
|
60
|
+
if (elapsed < DRAFT_THROTTLE_MS) {
|
|
61
|
+
// Schedule a deferred update
|
|
62
|
+
draftTimer = setTimeout(() => {
|
|
63
|
+
draftUpdate(text);
|
|
64
|
+
}, DRAFT_THROTTLE_MS - elapsed);
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
draftLastText = trimmed;
|
|
69
|
+
draftLastEditAt = now;
|
|
70
|
+
|
|
71
|
+
try {
|
|
72
|
+
if (!draftMid) {
|
|
73
|
+
// First chunk — send new message
|
|
74
|
+
const res = await sendMaxMessage(chatId, trimmed, {
|
|
75
|
+
token: account.token,
|
|
76
|
+
replyToMessageId: params.replyToId,
|
|
77
|
+
format: 'markdown',
|
|
78
|
+
});
|
|
79
|
+
draftMid = res.messageId || null;
|
|
80
|
+
statusSink?.({ lastOutboundAt: Date.now() });
|
|
81
|
+
} else {
|
|
82
|
+
// Edit existing message
|
|
83
|
+
await editMaxMessage(draftMid, trimmed, {
|
|
84
|
+
token: account.token,
|
|
85
|
+
format: 'markdown',
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
} catch (err) {
|
|
89
|
+
draftStopped = true;
|
|
90
|
+
log?.debug?.(`[${account.accountId}] MAX draft stream failed: ${String(err)}`);
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
return {
|
|
95
|
+
get messageId() {
|
|
96
|
+
return draftMid;
|
|
97
|
+
},
|
|
98
|
+
update: draftUpdate,
|
|
99
|
+
finalize: async (finalText, buttons) => {
|
|
100
|
+
// Final delivery replaces the draft message with final text. The
|
|
101
|
+
// keyboard (presentation buttons) goes onto the same edit, otherwise
|
|
102
|
+
// the final answer would lose its buttons.
|
|
103
|
+
if (draftMid && (finalText !== draftLastText || buttons?.length)) {
|
|
104
|
+
try {
|
|
105
|
+
await editMaxMessage(draftMid, finalText, {
|
|
106
|
+
token: account.token,
|
|
107
|
+
format: 'markdown',
|
|
108
|
+
buttons,
|
|
109
|
+
});
|
|
110
|
+
draftLastText = finalText;
|
|
111
|
+
} catch {
|
|
112
|
+
/* best effort */
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
draftStopped = true;
|
|
116
|
+
},
|
|
117
|
+
clear: async () => {
|
|
118
|
+
if (draftTimer) {
|
|
119
|
+
clearTimeout(draftTimer);
|
|
120
|
+
draftTimer = null;
|
|
121
|
+
}
|
|
122
|
+
draftStopped = true;
|
|
123
|
+
},
|
|
124
|
+
};
|
|
125
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MAX Messenger Bot API — TypeScript type definitions
|
|
3
|
+
* Based on https://dev.max.ru/docs-api
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// ─── MAX API Client Types (для api.ts) ────────────────────────
|
|
7
|
+
|
|
8
|
+
export interface MaxUser {
|
|
9
|
+
user_id: number;
|
|
10
|
+
first_name: string;
|
|
11
|
+
last_name?: string | null;
|
|
12
|
+
username?: string | null;
|
|
13
|
+
is_bot: boolean;
|
|
14
|
+
last_activity_time?: number;
|
|
15
|
+
name?: string | null;
|
|
16
|
+
description?: string | null;
|
|
17
|
+
avatar_url?: string;
|
|
18
|
+
full_avatar_url?: string;
|
|
19
|
+
commands?: MaxBotCommand[] | null;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface MaxChat {
|
|
23
|
+
chat_id: number;
|
|
24
|
+
type: 'dialog' | 'chat' | 'channel';
|
|
25
|
+
status: string;
|
|
26
|
+
title?: string | null;
|
|
27
|
+
icon?: { url?: string } | null;
|
|
28
|
+
last_event_time?: number;
|
|
29
|
+
participants_count?: number;
|
|
30
|
+
owner_id?: number;
|
|
31
|
+
participants?: Record<string, unknown>;
|
|
32
|
+
is_public?: boolean;
|
|
33
|
+
link?: string;
|
|
34
|
+
description?: string | null;
|
|
35
|
+
dialog_with_user?: MaxUser;
|
|
36
|
+
messages_count?: number;
|
|
37
|
+
chat_message_id?: string;
|
|
38
|
+
pinned_message?: MaxMessage | null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface MaxRecipient {
|
|
42
|
+
chat_id?: number;
|
|
43
|
+
chat_type?: string;
|
|
44
|
+
user_id?: number;
|
|
45
|
+
/** Post identifier for comments */
|
|
46
|
+
post_id?: string | null;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface MaxMessageBody {
|
|
50
|
+
mid: string;
|
|
51
|
+
seq?: number;
|
|
52
|
+
text?: string | null;
|
|
53
|
+
attachments?: MaxAttachment[];
|
|
54
|
+
/** Text markup (MarkupElement[]); user mentions arrive as `user_mention` */
|
|
55
|
+
markup?: MaxMarkupElement[] | null;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* MarkupElement (schema.yaml). `from`/`length` index into `text`. A
|
|
60
|
+
* `user_mention` carries `user_link` (`@username`) or, for users without a
|
|
61
|
+
* username, `user_id`; `link` carries `url`.
|
|
62
|
+
*/
|
|
63
|
+
export interface MaxMarkupElement {
|
|
64
|
+
type: string;
|
|
65
|
+
from: number;
|
|
66
|
+
length: number;
|
|
67
|
+
user_link?: string | null;
|
|
68
|
+
user_id?: number | null;
|
|
69
|
+
url?: string;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export interface MaxAttachment {
|
|
73
|
+
type: string;
|
|
74
|
+
payload?: Record<string, unknown>;
|
|
75
|
+
[key: string]: unknown;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export interface MaxLinkedMessage {
|
|
79
|
+
type: 'forward' | 'reply';
|
|
80
|
+
sender?: MaxUser;
|
|
81
|
+
chat_id?: number;
|
|
82
|
+
message?: MaxMessage;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export interface MaxUpdatesResponse {
|
|
86
|
+
updates: MaxUpdate[];
|
|
87
|
+
marker: number | null;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export interface MaxUploadResult {
|
|
91
|
+
[key: string]: unknown;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export type MaxUpdateType =
|
|
95
|
+
| 'message_created'
|
|
96
|
+
| 'message_callback'
|
|
97
|
+
| 'message_edited'
|
|
98
|
+
| 'message_removed'
|
|
99
|
+
| 'comment_created'
|
|
100
|
+
| 'comment_edited'
|
|
101
|
+
| 'comment_removed'
|
|
102
|
+
| 'bot_added'
|
|
103
|
+
| 'bot_removed'
|
|
104
|
+
| 'bot_started'
|
|
105
|
+
| 'bot_stopped'
|
|
106
|
+
| 'dialog_cleared'
|
|
107
|
+
| 'dialog_removed'
|
|
108
|
+
| 'dialog_muted'
|
|
109
|
+
| 'dialog_unmuted'
|
|
110
|
+
| 'user_added'
|
|
111
|
+
| 'user_removed'
|
|
112
|
+
| 'chat_title_changed'
|
|
113
|
+
| 'bot_admin_permissions_changed';
|
|
114
|
+
|
|
115
|
+
export interface MaxUpdate {
|
|
116
|
+
update_type: MaxUpdateType;
|
|
117
|
+
timestamp: number;
|
|
118
|
+
/** message_callback: the keyboard's message, null when it was deleted */
|
|
119
|
+
message?: MaxMessage | null;
|
|
120
|
+
callback?: MaxCallback;
|
|
121
|
+
chat_id?: number;
|
|
122
|
+
user?: MaxUser;
|
|
123
|
+
user_id?: number;
|
|
124
|
+
inviter_id?: number;
|
|
125
|
+
/** bot_started deeplink payload / message_removed message_id etc. */
|
|
126
|
+
payload?: string | null;
|
|
127
|
+
title?: string;
|
|
128
|
+
is_channel?: boolean;
|
|
129
|
+
user_locale?: string | null;
|
|
130
|
+
[key: string]: unknown;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export interface MaxInlineKeyboardButton {
|
|
134
|
+
type:
|
|
135
|
+
| 'callback'
|
|
136
|
+
| 'link'
|
|
137
|
+
| 'request_contact'
|
|
138
|
+
| 'request_geo_location'
|
|
139
|
+
| 'open_app'
|
|
140
|
+
| 'message'
|
|
141
|
+
| 'clipboard';
|
|
142
|
+
text: string;
|
|
143
|
+
payload?: string;
|
|
144
|
+
url?: string;
|
|
145
|
+
/** open_app: public name of the bot wired to the mini app (required for open_app) */
|
|
146
|
+
web_app?: string;
|
|
147
|
+
/** open_app: id of the bot wired to the mini app */
|
|
148
|
+
contact_id?: number | null;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export interface MaxInlineKeyboardAttachment {
|
|
152
|
+
type: 'inline_keyboard';
|
|
153
|
+
payload: {
|
|
154
|
+
buttons: MaxInlineKeyboardButton[][];
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export interface MaxStickerAttachment {
|
|
159
|
+
type: 'sticker';
|
|
160
|
+
payload: {
|
|
161
|
+
code: string;
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export interface MaxNewMessageBody {
|
|
166
|
+
text?: string | null;
|
|
167
|
+
attachments?: (MaxAttachment | MaxInlineKeyboardAttachment | MaxStickerAttachment)[] | null;
|
|
168
|
+
link?: { type: 'forward' | 'reply'; mid: string } | null;
|
|
169
|
+
notify?: boolean;
|
|
170
|
+
format?: 'markdown' | 'html' | null;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export interface MaxBotCommand {
|
|
174
|
+
name: string;
|
|
175
|
+
description?: string;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/** PATCH /me/commands response (BotCommandsInfo). */
|
|
179
|
+
export interface MaxBotCommandsInfo {
|
|
180
|
+
commands?: MaxBotCommand[] | null;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Chat actions (POST /chats/{chatId}/actions).
|
|
185
|
+
* mark_seen disappeared from the current docs but is still accepted; treat as legacy.
|
|
186
|
+
*/
|
|
187
|
+
export type MaxSenderAction =
|
|
188
|
+
'typing_on' | 'sending_photo' | 'sending_video' | 'sending_audio' | 'sending_file' | 'mark_seen';
|
|
189
|
+
|
|
190
|
+
/** GET /chats/{chatId}/members/me */
|
|
191
|
+
export interface MaxChatMember {
|
|
192
|
+
user_id: number;
|
|
193
|
+
first_name?: string;
|
|
194
|
+
last_name?: string | null;
|
|
195
|
+
username?: string | null;
|
|
196
|
+
is_bot?: boolean;
|
|
197
|
+
is_owner?: boolean;
|
|
198
|
+
is_admin?: boolean;
|
|
199
|
+
join_time?: number;
|
|
200
|
+
permissions?: string[] | null;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/** GET /videos/{videoToken} — playback info; urls may be null while processing. */
|
|
204
|
+
export interface MaxVideoInfo {
|
|
205
|
+
token?: string;
|
|
206
|
+
urls?: {
|
|
207
|
+
mp4_1080?: string;
|
|
208
|
+
mp4_720?: string;
|
|
209
|
+
mp4_480?: string;
|
|
210
|
+
mp4_360?: string;
|
|
211
|
+
mp4_240?: string;
|
|
212
|
+
mp4_144?: string;
|
|
213
|
+
hls?: string;
|
|
214
|
+
} | null;
|
|
215
|
+
/** PhotoAttachmentPayload */
|
|
216
|
+
thumbnail?: { url: string; photo_id?: number; token?: string } | null;
|
|
217
|
+
width?: number;
|
|
218
|
+
height?: number;
|
|
219
|
+
duration?: number;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
export interface MaxMessage {
|
|
223
|
+
sender?: MaxUser;
|
|
224
|
+
recipient: MaxRecipient;
|
|
225
|
+
timestamp: number;
|
|
226
|
+
link?: MaxLinkedMessage | null;
|
|
227
|
+
body: MaxMessageBody;
|
|
228
|
+
stat?: { views?: number } | null;
|
|
229
|
+
url?: string | null;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/** Callback object; the keyboard's message arrives next to it in the update, not inside. */
|
|
233
|
+
export interface MaxCallback {
|
|
234
|
+
timestamp: number;
|
|
235
|
+
callback_id: string;
|
|
236
|
+
payload?: string;
|
|
237
|
+
user: MaxUser;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
export interface MaxSendResult {
|
|
241
|
+
message: MaxMessage;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
export interface MaxSimpleResult {
|
|
245
|
+
success: boolean;
|
|
246
|
+
message?: string;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/** GET /subscriptions item. The secret is write-only (POST /subscriptions) and never returned. */
|
|
250
|
+
export interface MaxSubscription {
|
|
251
|
+
url: string;
|
|
252
|
+
time: number;
|
|
253
|
+
update_types?: string[] | null;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
export interface MaxSubscriptionsResponse {
|
|
257
|
+
subscriptions: MaxSubscription[];
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
export interface MaxUploadUrlResponse {
|
|
261
|
+
url: string;
|
|
262
|
+
token?: string;
|
|
263
|
+
}
|