Перейти к основному содержимому

Facebook теперь программируется. Целиком

Всё, что вы делаете в рекламном кабинете руками, теперь умеет делать скрипт — вашим же профилем, по расписанию, круглые сутки.

Это Facebook Qubix Bridge — прямой мост в Facebook из скриптов Qubix. Скрипт отправляет собственный запрос к Facebook — в Graph API или на любой другой его адрес, — а Qubix проводит его через ваш браузерный профиль: ключ, куки, прокси, подпись браузера. Задумка, которая вчера ждала чужой очереди доработок, сегодня — короткий скрипт, который ассистент напишет за вас.

🎬 GIF: скрипт читает дневной бюджет кампании, поднимает его, изменение видно в рекламном кабинете

Что это открывает

🧠 Ваша воронка решает — ваш профиль исполняет. Скрипт прямым SQL-запросом считает окупаемость по вашей статистике — депозиты, доход, любой срез вплоть до гео и часа — и по ней двигает кабинет. Сам кабинет такого не умеет в принципе: вашей воронки у него нет.

💰 Бюджеты по вашей логике. Скрипт находит кампании с окупаемостью выше порога, читает их живой бюджет из самого Facebook и поднимает на ваш шаг — с потолком и выдержкой, чтобы рост не разгонялся и не сбивал обучение. Готовый образец «повышение бюджета прибыльных кампаний» уже в поставке: скопируйте и подстройте пороги под себя.

🔍 Диагностика словами самого Facebook. «Объявление активно, а расхода нет» — скрипт спрашивает у Facebook напрямую и приносит его собственную формулировку проблемы: не прошла модерация, истекла группа, ограничен кабинет.

💳 Деньги кабинетов — сводкой. Общий расход, суточный предел, порог списания, привязанная карта — по каждому кабинету, по расписанию, прямо в консоль или сообщением вам в мессенджер.

🎯 Настройки, которых нет в отчётах. Таргетинг групп, цели оптимизации, вложенные настройки объявления — читаются одним запросом.

📦 Сотни объектов — одним обменом. Пачка запросов уходит в Facebook разом: бюджеты всех кампаний читаются за один заход, а не по одному.

🖼 Креативы — к себе. Картинки и исходники видео выкачиваются скриптом — вплоть до самих файлов — и остаются у вас.

🧹 Комментарии — по вашим правилам. Автоматическая чистка подсадных ссылок в Qubix уже есть; мост добавляет к ней вашу собственную логику: свои списки слов, свои исключения.

📊 Статистика кабинета любым срезом — когда нужен разрез, которого нет в готовых отчётах, скрипт спрашивает его у Facebook сам.

И это только начало списка: мост принимает любой адрес Facebook — Graph API и не только, вплоть до двоичных выгрузок. Ваша фантазия здесь не ограничена ничем — ограничены только права вашего же профиля в Facebook.

Как это устроено: труба, и только труба

Адрес, способ, доводы и заголовки пишете вы, а ключ, куки, прокси и подпись браузера подставляет Qubix. Готовых функций управления кампаниями в мосте нет намеренно — они вмораживали бы сегодняшнюю форму Facebook в поставку и ломались бы при первой же его правке. Мост о форме запроса не знает ничего — поэтому не устаревает: меняется поле у Facebook — вы правите строку в своём скрипте, не дожидаясь обновления Qubix.

Команда fb доступна в пользовательских скриптах — и по расписанию, и по кнопке ▶ Запустить.

Два способа выбрать профиль-исполнитель

Каждый запрос отправляет какой-то браузерный профиль — исполнитель. Вы либо называете его сами, либо доверяете выбор Qubix.

JavaScript
// а) выбор за Qubix: любой профиль, который видит эту кампанию
const res = fb.campaign(campaignId).request({
method: 'GET',
url: `https://graph.facebook.com/${campaignId}`,
params: { fields: 'name,daily_budget' },
})

// б) названный профиль: без подбора и без запасных
const res2 = fb.profile(profileId).request({
method: 'GET',
url: 'https://graph.facebook.com/me',
params: { fields: 'id,name' },
})
  • fb.campaign(campaignId) — исполнителя Qubix подбирает среди профилей, которые видят эту кампанию, предпочитая профили владельца объявления. Отпал кандидат на уровне доставки — пробуется следующий, но только для чтения: изменение через другой профиль не повторяется никогда, поэтому не может примениться дважды. Facebook ответил — пусть даже отказом — перебор останавливается.
  • fb.profile(profileId) — запрос идёт ровно этим профилем, без запасных. Перечень профилей, видящих кампанию или объявление, даёт getProfiles() — со всем, что мы о них знаем: имя, группа, владелец, жив ли ключ, не стоит ли профиль на проверке безопасности.

Запрос и ответ

request(options) принимает один объект:

ПараметрЗначение
urlабсолютный адрес запроса: `https://graph.facebook.com/${campaignId}`, https://graph.facebook.com/me/adaccounts. Годится любой хост *.facebook.com — куки и ключ профиля на чужой хост не уходят
methodGET (по умолчанию), POST или DELETE
paramsдоводы запроса объектом; POST несёт их телом, остальные способы — в адресе
headersсвои заголовки; идут поверх наших и вправе затереть любой
timeoutMsсколько ждать ответа; пусто — срок оператора, выше его потолка не подняться

Ответ Facebook приходит как есть, Qubix его не разбирает — разбираете вы: status (поля ok нет намеренно — оно могло быть только истиной и прятало бы отказы), contentType, headers (все, кроме Set-Cookie), text (тело строкой — JSON.parse(res.text)), bytes (оно же двоично — для картинок и выгрузок), profileId (каким профилем ушло).

Отказов два вида: Qubix не смог доставить — бросается ошибкой, ловите try/catch; Facebook ответил отказом — обычный ответ со status >= 400 и причиной в теле. Помощник на три строки, чтобы не повторять разбор:

JavaScript
function graph(res) {
if (res.status >= 400) throw new Error('Facebook отказал: ' + res.status)
return res.text ? JSON.parse(res.text) : {}
}
Не перечитывайте сразу после изменения

Чтение Facebook отдаёт с задержкой: значение, прочитанное сразу после удачного изменения, может оказаться прежним. Проверяйте следующим прогоном либо запоминайте поставленное в ctx.state.

Пачка: много запросов одним обменом

Очередь по браузерному профилю пропускает один запрос в промежуток — сотня одиночных чтений в предел прогона не уложится. Пачка платит очередь один раз:

JavaScript
const rows = fb.batch(fb.profile(profileId), ids.map((id) => ({ method: 'GET', relativeUrl: `${id}?fields=name,daily_budget` })))
for (const r of rows) {
if (r.code >= 400) { console.log('код', r.code); continue }
console.log(r.data.name, r.data.daily_budget)
}

fb.batch режется по 50 подзапросов сама — это предел Facebook; порядок сохраняется, у каждого ряда свой code (отказ — code >= 400), разобранный ответ — в data. Подзапрос — { method, relativeUrl, body }; перечень чтений собирается обычным map. Пачка никогда не переходит к запасному профилю — изменение внутри неё применяется один раз либо не применяется вовсе.

Каталог кирпичиков

Каждый запрос ниже Qubix отправлял в живой работе и получал ответ (сверено 27.08.2026) — на них можно опираться как на базовый набор. Всё за пределами тоже проходит: мост принимает любой адрес *.facebook.com, а дальше решают права вашего профиля.

Кто я под этим профилем

JavaScript
const res = fb.profile(profileId).request({ url: 'https://graph.facebook.com/me', params: { fields: 'id,name,email' } })

Отказ здесь означает, что сессия профиля мертва, — самый дешёвый способ проверять живость сессий по расписанию, раньше, чем это заметит залив.

Рекламные кабинеты — включая деньги

JavaScript
const res = fb.profile(profileId).request({
url: 'https://graph.facebook.com/me/adaccounts',
params: {
limit: 50,
fields: 'name,account_id,account_status,disable_reason,currency,' +
'adspaymentcycle,adtrust_dsl,amount_spent,timezone_name',
},
})

Деньги живут здесь — отдельного запроса «баланс» у Graph API нет: amount_spent — общий расход в минорных единицах валюты кабинета, adtrust_dsl — суточный предел, adspaymentcycle — порог списания (сумма в .data[0].threshold_amount, делится на 100), account_status и disable_reason — состояние и причина отключения числами. Карты оплаты — тем же адресом с полем all_payment_methods{pm_credit_card{display_string}}.

Состояние и бюджет кампании

JavaScript
const res = fb.campaign(campaignId).request({
url: `https://graph.facebook.com/${campaignId}`,
params: { fields: 'id,name,status,effective_status,daily_budget' },
})

daily_budget приходит в минорных единицах, строкой. Бюджет задан на группах — поля просто нет, это обычное состояние, а не ошибка.

Изменение бюджета

JavaScript
const res = fb.campaign(campaignId).request({
method: 'POST',
url: `https://graph.facebook.com/${campaignId}`,
params: { daily_budget: '5000' }, // минорные единицы: 5000 = 50.00 в валюте кабинета
})

На успех Facebook отвечает {"success":true}.

Настройки группы: таргетинг, бюджеты, оптимизация

JavaScript
const res = fb.campaign(campaignId).request({
url: `https://graph.facebook.com/${adsetId}`,
params: { fields: 'name,targeting,daily_budget,lifetime_budget,optimization_goal,billing_event' },
})

Тем же способом у объявления одним запросом достаются вложенные объекты — кампания, группа и креатив сразу: fields: 'name,effective_status,campaign{objective,daily_budget},adset{targeting},creative{id}'.

Пауза и запуск

JavaScript
fb.campaign(campaignId).request({ method: 'POST', url: `https://graph.facebook.com/${someId}`, params: { status: 'PAUSED' } })
fb.campaign(campaignId).request({ method: 'POST', url: `https://graph.facebook.com/${someId}`, params: { status: 'ACTIVE' } })

Одна форма для объявления, группы и кампании. Пауза кампании действует каскадом: у её объявлений effective_status становится CAMPAIGN_PAUSED, а собственный status не меняется — судите по effective_status.

Почему объявление не крутится

JavaScript
const res = fb.campaign(campaignId).request({
url: `https://graph.facebook.com/${adId}`,
params: { fields: 'effective_status,issues_info{error_summary,error_message,level},adset{effective_status,end_time}' },
})

issues_info несёт формулировку проблемы словами самого Facebook; adset.end_time ловит истёкшую группу.

Статистика напрямую из кабинета

JavaScript
const res = fb.campaign(campaignId).request({
url: `https://graph.facebook.com/act_${accountId}/insights`,
params: {
level: 'ad',
fields: 'ad_id,ad_name,spend,impressions,clicks',
time_range: JSON.stringify({ since: '2026-08-20', until: '2026-08-27' }),
time_increment: 1,
limit: 500,
},
})

Два подводных камня: outbound_clicks приходит массивом объектов, а поля landing_page_views на верхнем уровне нет вовсе — ищите его внутри actions.

Креативы, страницы, комментарии

JavaScript
// картинки по хешам
fb.campaign(campaignId).request({ url: `https://graph.facebook.com/act_${accountId}/adimages`, params: { hashes: JSON.stringify([imageHash]), fields: 'url,permalink_url,hash' } })
// исходник видео
fb.campaign(campaignId).request({ url: `https://graph.facebook.com/${videoId}`, params: { fields: 'source' } })
// фан-страницы и бизнес-менеджеры — независимые чтения, один обмен
const rows = fb.batch(fb.profile(profileId), [
{ method: 'GET', relativeUrl: 'me/accounts?limit=25' },
{ method: 'GET', relativeUrl: 'me/businesses' },
])
// посты страницы
const posts = graph(fb.profile(profileId).request({ url: `https://graph.facebook.com/${pageId}/published_posts`, params: { limit: 5 } }))
// комментарии ВСЕХ свежих постов — одной пачкой на весь список
const coms = fb.batch(fb.profile(profileId),
(posts.data || []).map((po) => ({ method: 'GET', relativeUrl: `${po.id}/comments?fields=id,message,from,created_time&limit=100` })))
// удалить комментарий — изменение идёт одиночным запросом
fb.profile(profileId).request({ method: 'DELETE', url: `https://graph.facebook.com/${commentId}` })

Ответ fb умеет быть и двоичным: res.bytes несёт тело как есть — так забирают сами файлы, а не только ссылки на них. Длинные списки Facebook отдаёт курсорами: пока в ответе есть paging.next, берите paging.cursors.after и повторяйте запрос.

Сила — в связках с остальным SDK

Условия отбирают объекты по вашей статистике, sql достаёт любой срез из базы, fb сверяет и меняет кабинет, ctx.state держит память между прогонами, ctx.fetch уводит итог в ваш мессенджер:

JavaScript
function main() {
// 1. Своя статистика — прямой ClickHouse-запрос: окупаемость кампаний по доходу ВОРОНКИ
const rows = sql`
SELECT campaign_id, sum(spend_24h) AS spend, sum(revenue_24h) AS revenue
FROM v_ads_stats
GROUP BY campaign_id
HAVING spend > 0 AND revenue / spend >= 1.5
ORDER BY revenue / spend DESC`

// 2. Живые бюджеты всех кандидатов — одним обменом с Facebook
const ids = rows.map((r) => r.campaign_id)
if (!ids.length) return
const budgets = fb.batch(fb.campaign(ids[0]), ids.map((id) => ({ method: 'GET', relativeUrl: `${id}?fields=name,daily_budget` })))

for (let i = 0; i < ids.length; i++) {
if (budgets[i].code >= 400) continue
console.log(budgets[i].data.name,
'ROAS воронки:', (rows[i].revenue / rows[i].spend).toFixed(2),
'бюджет в кабинете:', budgets[i].data.daily_budget)
}
}

Так собираются сценарии, до которых у поставщика никогда не дошли бы руки: ваша воронка решает, ваш профиль исполняет.

Кода можно не писать вовсе

Опишите задачу словами — «каждое утро поднимай бюджет кампаниям, которые окупаются, но не выше потолка» — и AI-ассистент прямо в редакторе соберёт скрипт, вставит его в код и поможет поставить на расписание. Ассистент знает и мост, и вашу статистику: скрипт может сперва спросить вашу воронку — какие кампании принесли депозиты, — а потом сходить в Facebook и подвинуть их бюджеты.

🎬 GIF: задача словами в ассистенте → готовый скрипт в редакторе → кнопка запуска

Под контролем — каждый шаг

Каждое обращение скрипта в Facebook — включая чтения — записывается в журнал на вашем сервере: какой скрипт, когда, каким профилем, что ответил Facebook. Изменение никогда не повторяется через запасной профиль, поэтому не может примениться дважды. Запросы к одному профилю разводятся по времени со всей остальной автоматикой — Britva, сбор статистики и скрипты делят одну очередь, профиль в безопасности. Время прогона режет вызовы fb наравне с остальным кодом, срок ожидания одного вызова ограничен потолком оператора, а ключи доступа в журнал не попадают вовсе — из сохраняемых текстов они вычищаются.

Настраивать нечего

Скрипт ходит в Facebook вашим же браузерным профилем — тем самым, которым вы работаете в кабинете: его ключ, его куки, его прокси, его подпись браузера. Для платформы это выглядит как работа живого человека, а не автоматизация. Профиль подключён к Qubix — мост уже работает.


Раньше каждое «а можно, чтобы само?» упиралось в чужие руки. Теперь между вашей задумкой и работающей автоматикой — один скрипт, который ассистент напишет за вас.

Обновите Qubix — и отдайте рутину кабинета скриптам. 🚀

Что дальше