diff --git a/skills/dist/vk-bridge.skill b/skills/dist/vk-bridge.skill index 72c04cf..3148af1 100644 Binary files a/skills/dist/vk-bridge.skill and b/skills/dist/vk-bridge.skill differ diff --git a/skills/vk-bridge/SKILL.md b/skills/vk-bridge/SKILL.md index cd78c18..cbfa0b5 100644 --- a/skills/vk-bridge/SKILL.md +++ b/skills/vk-bridge/SKILL.md @@ -45,9 +45,15 @@ Bridge делает POST на `OPENCLAW_BRIDGE_URL` с JSON: - `reply` (string) — текст для отправки в VK. - `silent` (boolean, optional) — если `true`, не отправлять ответ. +## Дополнительные настройки + +- `VK_ALLOWED_EVENTS` — список типов событий через запятую (по умолчанию `message_new`). +- `VK_SYNC_LONGPOLL_SETTINGS=1` — при старте проверяй `groups.getLongPollSettings` и включай нужные события через `groups.setLongPollSettings`. +- `VK_API_VERSION=5.199` — рекомендуемая версия (раннер предупреждает, если другая). + ## Надёжность - При `failed=1` обновляй `ts`. - При `failed=2|3` запрашивай новый `server/key/ts` через `groups.getLongPollServer`. -- Игнорируй пустые/служебные сообщения. +- Игнорируй пустые/служебные сообщения и события вне списка `VK_ALLOWED_EVENTS`. - Не отвечай на исходящие (`out=1`). diff --git a/skills/vk-bridge/references/.env.example b/skills/vk-bridge/references/.env.example index 0e35750..44d7d0f 100644 --- a/skills/vk-bridge/references/.env.example +++ b/skills/vk-bridge/references/.env.example @@ -9,4 +9,8 @@ OPENCLAW_BRIDGE_URL=http://127.0.0.1:8787/vk/inbound OPENCLAW_BRIDGE_TIMEOUT_MS=45000 # Optional +# Comma-separated list of event types to process in bridge loop. +VK_ALLOWED_EVENTS=message_new +# 1 = try to auto-enable Long Poll + selected events via groups.setLongPollSettings +VK_SYNC_LONGPOLL_SETTINGS=1 LOG_LEVEL=info diff --git a/skills/vk-bridge/scripts/vk-longpoll-bridge.mjs b/skills/vk-bridge/scripts/vk-longpoll-bridge.mjs index 3183792..b6b2805 100755 --- a/skills/vk-bridge/scripts/vk-longpoll-bridge.mjs +++ b/skills/vk-bridge/scripts/vk-longpoll-bridge.mjs @@ -25,6 +25,14 @@ function loadEnv(path) { } } +function parseCsv(value, fallback = []) { + if (!value) return fallback; + return value + .split(',') + .map((x) => x.trim()) + .filter(Boolean); +} + const { env } = parseArgs(); loadEnv(env); @@ -35,12 +43,21 @@ const CFG = { wait: Number(process.env.VK_WAIT || 25), bridgeUrl: process.env.OPENCLAW_BRIDGE_URL || '', bridgeTimeout: Number(process.env.OPENCLAW_BRIDGE_TIMEOUT_MS || 45000), + allowedEvents: new Set(parseCsv(process.env.VK_ALLOWED_EVENTS, ['message_new'])), + syncSettings: process.env.VK_SYNC_LONGPOLL_SETTINGS === '1', }; if (!CFG.groupId || !CFG.token || !CFG.bridgeUrl) { console.error('Missing required env: VK_GROUP_ID, VK_TOKEN, OPENCLAW_BRIDGE_URL'); process.exit(1); } +if (CFG.apiVersion !== '5.199') { + console.warn(`[vk-bridge] warning: VK_API_VERSION=${CFG.apiVersion}; recommended 5.199`); +} +if (CFG.wait > 90 || CFG.wait < 1) { + console.error('VK_WAIT must be between 1 and 90'); + process.exit(1); +} const seen = new Set(); @@ -50,7 +67,7 @@ function vkApi(method, params = {}) { access_token: CFG.token, v: CFG.apiVersion, }); - return fetch(`https://api.vk.com/method/${method}?${qs}`).then(r => r.json()); + return fetch(`https://api.vk.com/method/${method}?${qs}`).then((r) => r.json()); } async function getLongPollServer() { @@ -59,6 +76,40 @@ async function getLongPollServer() { return data.response; } +async function getLongPollSettings() { + const data = await vkApi('groups.getLongPollSettings', { group_id: CFG.groupId }); + if (data.error) throw new Error(`groups.getLongPollSettings: ${JSON.stringify(data.error)}`); + return data.response; +} + +async function setLongPollSettings(params) { + const data = await vkApi('groups.setLongPollSettings', { + group_id: CFG.groupId, + ...params, + }); + if (data.error) throw new Error(`groups.setLongPollSettings: ${JSON.stringify(data.error)}`); + return data.response; +} + +async function ensureLongPollSettings() { + const settings = await getLongPollSettings(); + const updates = { enabled: 1, api_version: CFG.apiVersion }; + + for (const eventType of CFG.allowedEvents) { + const current = settings?.events?.[eventType]; + if (current !== 1) updates[eventType] = 1; + } + + const needApply = Object.keys(updates).length > 2 || settings?.is_enabled !== 1; + if (!needApply) { + console.log('[vk-bridge] long poll settings already OK'); + return; + } + + await setLongPollSettings(updates); + console.log('[vk-bridge] long poll settings synced'); +} + async function callBridge(evt) { const msg = evt?.object?.message; if (!msg) return { silent: true }; @@ -66,6 +117,8 @@ async function callBridge(evt) { const body = { source: 'vk', group_id: CFG.groupId, + event_type: evt?.type || null, + event_id: evt?.event_id || null, user_id: msg.from_id, peer_id: msg.peer_id, text: msg.text || '', @@ -102,6 +155,7 @@ async function sendVk(peerId, text) { } function isInboundUserMessage(evt) { + if (!CFG.allowedEvents.has(evt?.type)) return false; if (evt?.type !== 'message_new') return false; const msg = evt?.object?.message; if (!msg) return false; @@ -111,6 +165,14 @@ function isInboundUserMessage(evt) { } async function main() { + if (CFG.syncSettings) { + try { + await ensureLongPollSettings(); + } catch (e) { + console.error('[vk-bridge] failed to sync long poll settings:', e.message); + } + } + let lp = await getLongPollServer(); let ts = lp.ts;