Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- # Plan: Add MAX (max.ru) as a Platform
- ## Context
- The user wants to add MAX (max.ru) — a Russian social messenger — as a new posting platform in Skyfall Autoposter. MAX provides a Bot API at `platform-api.max.ru` analogous to Telegram's Bot API: bots are authorized with a token, can send messages to chats/channels, and support file uploads (images, video, audio, files). The integration should follow the same patterns used for Telegram (the most recently added platform), which will serve as the direct structural template.
- **MAX Bot API key facts:**
- - Base URL: `https://platform-api.max.ru`
- - Auth: `Authorization: <token>` header (no query param)
- - Send message: `POST /messages?chat_id={id}` body: `{ text, attachments, format, notify }`
- - Text limit: 4000 characters
- - Upload files: `POST /uploads?type={image|video|audio|file}` → returns `{ url, token }`
- - Images/files: upload to `url`, reference in message as `{ type: "image"/"file", payload: { token } }`
- - Video/audio: upload to `url`, reference with `token` in payload
- - Supported: JPG/PNG/GIF (image), MP4/MOV/MKV/WEBM (video), MP3/WAV (audio), any (file)
- - Max file size: 4 GB
- - Note: after upload, server processing may take time → `attachment.not.ready` error requires retry
- - Test bot info: `GET /me`
- - Rate limit: 30 req/s
- ---
- ## Files to Create
- ### 1. `src/platforms/config/max.js`
- Axios client factory — mirrors `src/platforms/config/telegram.js`.
- - Credential field: `botToken`
- - `baseURL`: `https://platform-api.max.ru`
- - Auth via `Authorization` header added to every request (via axios `headers.Authorization`)
- - Proxy support via `createProxyAgent()` / `isProxyConfigured()`
- - Timeout: `60000` (uploads can be slow)
- ### 2. `src/platforms/services/maxService.js`
- Platform service — mirrors `src/platforms/services/telegramService.js` structure.
- **Functions:**
- - `testConnection(credentials)` → `GET /me` → returns `{ success, user: { id, username, name } }`
- - `sendPost(options, credentials)` where options = `{ chatId, text, filePaths }`
- - No files → `POST /messages?chat_id={chatId}` with `{ text }`
- - With files → upload each via `POST /uploads?type={type}`, collect tokens, send message with `attachments` array
- - Retry logic for `attachment.not.ready` errors (up to 3 retries with 2s delays)
- - Caption limit: 4000 chars (MAX applies text to message body, not caption)
- **Upload flow per file:**
- 1. `POST /uploads?type=image|video|audio|file` → get `{ url, token }`
- 2. PUT file binary to returned `url`
- 3. Add `{ type: "image"|"video"|"audio"|"file", payload: { token } }` to attachments array
- 4. `POST /messages?chat_id={chatId}` with `{ text, attachments }`
- **Helper:** `parseMaxError(error)` for friendly error messages.
- ### 3. `src/platforms/handlers/MaxHandler.js`
- Extends `PlatformHandler` — mirrors `src/platforms/handlers/TelegramHandler.js`.
- - `getPlatformId()` → `'max'`
- - `getPlatformName()` → `'MAX'`
- - `getCredentialFields()` → `[{ name: 'botToken', label: 'Bot Token', type: 'password', required: true, sensitive: true }]`
- - `getJobFields()` → `[{ name: 'accountId', ... }, { name: 'chatId', ... }]`
- - `validateJob()` — checks accountId, credentials, chatId, and content/media presence; text ≤ 4000
- - `processJob()` — calls `maxService.sendPost()`; returns `createSuccessResult({ messageId, chatId })`
- - `testConnection()` — calls `maxService.testConnection()`
- - `getAccountIdentifier()` — returns bot `name` or `@username`
- Local `categorizeMaxError(msg)` function mapping error strings to `ErrorCategory.*`.
- ### 4. `public/js/platforms/max.js`
- Frontend module — mirrors `public/js/platforms/telegram.js`.
- - Class `MaxPlatform` with `init()`, `destroy()`, `loadInitialData()`, `setupStateSubscriptions()`, `setupEventListeners()`
- - State keys: `platforms.max.accounts`, `platforms.max.selectedAccountId`, `platforms.max.chatId`
- - Actions: `max-test-account`, `max-delete-account` (event delegation)
- - Form submission for `#max-account-form` → `addAccount({ botToken })`
- - Renders account list in `#max-accounts-list`
- - Manages account selector `#max-account` in compose form
- - Shows/hides `#max-account-form-container`
- - Export singleton: `export const maxPlatform = new MaxPlatform()`
- ---
- ## Files to Modify
- ### 5. `public/index.html`
- Four additions:
- **a) Platform toggle checkbox** (after the Telegram toggle, ~line 67):
- ```html
- <label class="platform-toggle">
- <input type="checkbox" name="platform" value="max" id="platform-max">
- <i class="fas fa-comment-dots"></i>
- MAX
- </label>
- ```
- **b) Compose content section** (after `#telegram-content-section`):
- ```html
- <section id="max-content-section" class="platform-content" style="display: none;">
- <header class="platform-content-header">
- <i class="fas fa-comment-dots"></i> MAX
- </header>
- <textarea id="max-content" name="max-content" rows="6"
- placeholder="Message for MAX..." maxlength="4000"></textarea>
- <div class="char-counter" id="max-char-counter">
- <span class="count">0</span> / 4000
- </div>
- </section>
- ```
- **c) Compose options section** (after `#telegram-options`):
- ```html
- <div id="max-options" style="display: none;">
- <div class="form-group">
- <label for="max-account">MAX Bot *</label>
- <select id="max-account" name="maxAccount">
- <option value="">Select bot...</option>
- </select>
- <div class="error-message" id="max-account-error"></div>
- </div>
- <div class="form-group">
- <label for="max-chat-id">Chat ID *</label>
- <input type="text" id="max-chat-id" name="maxChatId"
- placeholder="Numeric chat/channel ID">
- <small class="field-hint">The numeric chat_id of the target chat or channel.</small>
- <div class="error-message" id="max-chat-id-error"></div>
- </div>
- </div>
- ```
- **d) Settings page platform card** (after Telegram card, before Discord card):
- ```html
- <div class="platform-card">
- <header class="platform-header">
- <div class="platform-info">
- <i class="fas fa-comment-dots platform-icon"></i>
- <div>
- <h4>MAX Bots</h4>
- <p class="platform-description">Manage MAX bot accounts for channel/group posting</p>
- </div>
- </div>
- <button type="button" class="btn btn-primary btn-sm" id="add-max-account-btn">
- <i class="fas fa-plus"></i> Add Bot
- </button>
- </header>
- <div class="platform-body">
- <div id="max-accounts-list" class="accounts-list">
- <div class="loading-state"><i class="fas fa-spinner fa-spin"></i> Loading bots...</div>
- </div>
- <div id="max-account-form-container" style="display: none;">
- <h4>Add MAX Bot</h4>
- <form id="max-account-form" class="config-form">
- <div class="form-row">
- <div class="form-group">
- <label for="max-bot-token">Bot Token *</label>
- <input type="password" id="max-bot-token" name="botToken"
- placeholder="Your MAX bot token" required>
- <small class="field-hint">Bot token from the MAX Developer Portal</small>
- </div>
- </div>
- <aside class="config-instructions">
- <h5>Setup Instructions:</h5>
- <ol>
- <li>Go to <strong>business.max.ru</strong> and create a bot</li>
- <li>After moderation approval, get the token from <strong>Чат-боты → Интеграция → Получить токен</strong></li>
- <li>Add the bot to your channel/group as an Administrator</li>
- <li>Get the numeric Chat ID from the channel URL or bot updates</li>
- </ol>
- </aside>
- <div class="config-actions">
- <button type="submit" class="btn btn-primary">
- <i class="fas fa-save"></i> Add Bot
- </button>
- <button type="button" class="btn btn-secondary" id="cancel-max-account-btn">
- <i class="fas fa-times"></i> Cancel
- </button>
- </div>
- <div id="max-account-form-result" class="test-result"></div>
- </form>
- </div>
- </div>
- </div>
- ```
- ### 6. `public/js/main.js`
- - Add import: `import { maxPlatform } from './platforms/max.js';`
- - Add `maxPlatform.init()` in `init()` (after `telegramPlatform.init()`)
- - Add `maxPlatform.destroy?.()` in cleanup
- ### 7. `public/js/modules/compose.js`
- **a) Cache new DOM elements** in `setup()` (`this.elements` object):
- ```javascript
- maxContent: document.getElementById('max-content'),
- maxContentSection: document.getElementById('max-content-section'),
- maxCharCounter: document.getElementById('max-char-counter'),
- maxOptions: document.getElementById('max-options'),
- maxAccount: document.getElementById('max-account'),
- maxChatId: document.getElementById('max-chat-id'),
- ```
- **b) Add event listeners** in `setupEventListeners()`:
- ```javascript
- if (this.elements.maxContent) {
- this.elements.maxContent.addEventListener('input', (e) => {
- state.setState('compose.platformContent.max', e.target.value);
- });
- }
- if (this.elements.maxAccount) {
- this.elements.maxAccount.addEventListener('change', (e) => {
- state.setState('platforms.max.selectedAccountId', e.target.value);
- });
- }
- if (this.elements.maxChatId) {
- this.elements.maxChatId.addEventListener('input', (e) => {
- state.setState('platforms.max.chatId', e.target.value);
- });
- }
- ```
- **c) Show/hide UI** in `updatePlatformUI()` (after Telegram section):
- ```javascript
- if (this.elements.maxContentSection) {
- this.elements.maxContentSection.style.display = platforms.max ? 'block' : 'none';
- }
- if (this.elements.maxOptions) {
- this.elements.maxOptions.style.display = platforms.max ? 'block' : 'none';
- }
- if (platforms.max) {
- const maxAccounts = state.getState('platforms.max.accounts') || [];
- const selectedAccountId = state.getState('platforms.max.selectedAccountId');
- if ((!selectedAccountId || selectedAccountId === '') && maxAccounts.length > 0) {
- state.setState('platforms.max.selectedAccountId', maxAccounts[0].id);
- }
- }
- ```
- **d) Add to `platformOrder`** for copy-to-all:
- ```javascript
- const platformOrder = ['x', 'bluesky', 'discord', 'telegram', 'max', 'e6ai'];
- ```
- **e) Read state in `gatherFormData()`** (after telegram block):
- ```javascript
- const maxAccountId = state.getState('platforms.max.selectedAccountId');
- const maxChatId = state.getState('platforms.max.chatId') || '';
- ```
- **f) Add to `buildPostPayload()`** (after telegram block):
- ```javascript
- if (formData.platforms.max) {
- platformData.max = {
- accountId: formData.maxAccountId,
- chatId: formData.maxChatId,
- content: formData.platformContent.max || ''
- };
- }
- ```
- ### 8. `public/js/utils/errorMessages.js`
- Add `max` to `getPlatformName()` and `getPlatformHelp()`:
- ```javascript
- // getPlatformName
- max: 'MAX',
- // getPlatformHelp
- max: {
- AUTH_ERROR: 'Check your MAX bot token. Ensure the bot is approved and added as Administrator to the channel.',
- NETWORK_ERROR: 'Cannot reach MAX servers. Check internet connection and proxy settings.',
- PLATFORM_ERROR: 'MAX API error. Verify the Chat ID is correct and the bot has posting permissions.',
- VALIDATION_ERROR: 'Check Chat ID (numeric). Text limit: 4000 chars.'
- }
- ```
- ### 9. `public/js/utils/validation.js`
- Add `max` to `PLATFORM_LIMITS`:
- ```javascript
- max: 4000,
- ```
- ---
- ## Key Implementation Notes
- 1. **No custom route file needed** — `createAccountRouter('max')` auto-registers `/api/max/accounts/*` routes via the platform auto-discovery system.
- 2. **File upload flow for MAX** differs from Telegram's multipart approach:
- - Step 1: `POST /uploads?type=image` → `{ url, token }`
- - Step 2: PUT file binary to `url`
- - Step 3: Attach `{ type: "image", payload: { token } }` in message body
- - Implement retry (up to 3×, 2s interval) for `attachment.not.ready` errors
- 3. **Media handling** — MAX supports multiple attachments in a single message (unlike Telegram's media groups), so all files go into a single `POST /messages` with an `attachments` array.
- 4. **Icon** — MAX doesn't have a Font Awesome brand icon; use `fas fa-comment-dots` as a generic messenger icon.
- 5. **chat_id** is numeric (integer) in the MAX API — validate and coerce to number in the handler.
- ---
- ## Verification Steps
- 1. Start the server: `npm start`
- 2. Open Settings → add a MAX bot token → verify "Add Bot" calls `POST /api/max/accounts` and the bot appears in the list with its name
- 3. Click Test (plug icon) → verify `POST /api/max/accounts/{id}/test` returns bot info
- 4. Go to Compose → check MAX toggle → verify content section and options appear
- 5. Post a text-only message to a MAX chat → verify message arrives in MAX chat
- 6. Post with an image file → verify image arrives in MAX chat
- 7. Post with multiple files → verify all files arrive in a single MAX message
- 8. Delete the bot → verify it's removed from the list
- 9. Test error handling: bad token → verify `AUTH_ERROR` message; bad chat_id → verify `PLATFORM_ERROR`
Advertisement
Add Comment
Please, Sign In to add comment