Skip to content

Рецепти ​

Готові скрипти під часті задачі баєра. Кожен зібраний з методів, описаних на сусідніх сторінках, - скопіюйте, впишіть свої значення на початку скрипта й запускайте.

У всіх рецептах замініть ВАШ_КЛЮЧ і panel.example.com на свої. Як отримати ключ, див. розділ «Вступ до API».

Дати - за UTC

Статистика й конверсії рахуються за днями UTC. «Вчора» в скриптах нижче - це вчора за UTC, а не за вашим часовим поясом.

Статистика за вчора в Telegram ​

Щоранку надсилає в Telegram підсумки за вчора: візити, встановлення, реєстрації, депозити, виплати й конверсію.

Що знадобиться:

  1. Свій бот: надішліть @BotFather команду /newbot і скопіюйте токен.
  2. ID чату, куди надсилати: напишіть боту будь-яке повідомлення, відкрийте https://api.telegram.org/bot<ТОКЕН>/getUpdates і візьміть chat.id з відповіді. Для групи - додайте бота в групу, напишіть у ній повідомлення й візьміть chat.id групи (він від'ємний).
python
# pip install requests
import datetime
import requests

API = "https://panel.example.com/api"
HEADERS = {"Authorization": "Bearer ВАШ_КЛЮЧ", "Accept": "application/json"}
BOT_TOKEN = "ТОКЕН_БОТА"
CHAT_ID = "ID_ЧАТУ"

day = (datetime.datetime.now(datetime.timezone.utc).date() - datetime.timedelta(days=1)).isoformat()

response = requests.get(f"{API}/stats/dashboard", headers=HEADERS, params={
    "date_from": day,
    "date_to": day,
}, timeout=30)
response.raise_for_status()
metrics = response.json()["data"]["metrics"]

payouts = ", ".join(f'{a["amount"]} {a["currency"]}' for a in metrics["total_deposits"]["amounts"]) or "0"
text = "\n".join([
    f"PWA Hub за {day}",
    f"Візити: {metrics['total_visits']['value']} ({metrics['total_visits']['trend']}%)",
    f"Встановлення: {metrics['total_installs']['value']} ({metrics['total_installs']['trend']}%)",
    f"Реєстрації: {metrics['total_registrations']['value']}",
    f"Депозити: {metrics['total_deposits']['value']} - {payouts}",
    f"CR: {metrics['avg_cr']['value']}%",
])

requests.post(f"https://api.telegram.org/bot{BOT_TOKEN}/sendMessage", json={
    "chat_id": CHAT_ID,
    "text": text,
}, timeout=30).raise_for_status()
print("Надіслано:\n" + text)
js
// Node.js 18+, без залежностей; збережіть як stats-telegram.mjs
const API = 'https://panel.example.com/api';
const headers = { Authorization: 'Bearer ВАШ_КЛЮЧ', Accept: 'application/json' };
const BOT_TOKEN = 'ТОКЕН_БОТА';
const CHAT_ID = 'ID_ЧАТУ';

const yesterday = new Date(Date.now() - 24 * 60 * 60 * 1000);
const day = yesterday.toISOString().slice(0, 10);

const response = await fetch(`${API}/stats/dashboard?${new URLSearchParams({ date_from: day, date_to: day })}`, {
  headers,
  signal: AbortSignal.timeout(30000),
});
if (!response.ok) throw new Error(`PWA Hub відповів ${response.status}`);
const { metrics } = (await response.json()).data;

const payouts = metrics.total_deposits.amounts.map((a) => `${a.amount} ${a.currency}`).join(', ') || '0';
const text = [
  `PWA Hub за ${day}`,
  `Візити: ${metrics.total_visits.value} (${metrics.total_visits.trend}%)`,
  `Встановлення: ${metrics.total_installs.value} (${metrics.total_installs.trend}%)`,
  `Реєстрації: ${metrics.total_registrations.value}`,
  `Депозити: ${metrics.total_deposits.value} - ${payouts}`,
  `CR: ${metrics.avg_cr.value}%`,
].join('\n');

const sent = await fetch(`https://api.telegram.org/bot${BOT_TOKEN}/sendMessage`, {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ chat_id: CHAT_ID, text }),
  signal: AbortSignal.timeout(30000),
});
if (!sent.ok) throw new Error(`Telegram відповів ${sent.status}`);
console.log(`Надіслано:\n${text}`);
php
<?php
// composer require guzzlehttp/guzzle
require 'vendor/autoload.php';

$api = new GuzzleHttp\Client([
    'base_uri' => 'https://panel.example.com/api/',
    'headers' => ['Authorization' => 'Bearer ВАШ_КЛЮЧ', 'Accept' => 'application/json'],
    'timeout' => 30,
]);
$botToken = 'ТОКЕН_БОТА';
$chatId = 'ID_ЧАТУ';

$day = (new DateTimeImmutable('yesterday', new DateTimeZone('UTC')))->format('Y-m-d');

$response = $api->get('stats/dashboard', ['query' => ['date_from' => $day, 'date_to' => $day]]);
$metrics = json_decode($response->getBody(), true)['data']['metrics'];

$payouts = implode(', ', array_map(
    fn($a) => "{$a['amount']} {$a['currency']}",
    $metrics['total_deposits']['amounts']
)) ?: '0';
$text = implode("\n", [
    "PWA Hub за {$day}",
    "Візити: {$metrics['total_visits']['value']} ({$metrics['total_visits']['trend']}%)",
    "Встановлення: {$metrics['total_installs']['value']} ({$metrics['total_installs']['trend']}%)",
    "Реєстрації: {$metrics['total_registrations']['value']}",
    "Депозити: {$metrics['total_deposits']['value']} - {$payouts}",
    "CR: {$metrics['avg_cr']['value']}%",
]);

(new GuzzleHttp\Client(['timeout' => 30]))->post("https://api.telegram.org/bot{$botToken}/sendMessage", [
    'json' => ['chat_id' => $chatId, 'text' => $text],
]);
echo "Надіслано:\n{$text}" . PHP_EOL;

За розкладом. На сервері з Linux додайте рядок у crontab -e - скрипт спрацьовуватиме щодня о 07:00 за часом сервера:

bash
0 7 * * * /usr/bin/python3 /home/user/stats-telegram.py

Для Node.js - /usr/bin/node /home/user/stats-telegram.mjs, для PHP - /usr/bin/php /home/user/stats-telegram.php. Запускайте не раніше 00:10 UTC: свіжі події потрапляють у статистику із затримкою до 5 хвилин.

trend - зміна до попереднього дня у відсотках. Докладно про показники - у розділі «Статистика».

Клонувати PWA, прив’язати домен і ввімкнути ​

Робить нове PWA з готового за один запуск: клонує його, прив’язує до копії домен і вмикає її. Зручно, коли під кожну зв’язку потрібен свій застосунок зі своїм доменом.

Що знадобиться: id PWA-зразка (зі списку PWA або адресного рядка панелі) і домен для копії.

Це реальні дії в Cloudflare

Прив’язка домену створює зону й DNS-записи у вашому акаунті Cloudflare. Піддомен (go.example.com) прив’язується, лише якщо його кореневий домен (example.com) уже доданий у панель - інакше прийде 404. Подробиці - у розділі «Домени».

python
# pip install requests
import requests

API = "https://panel.example.com/api"
HEADERS = {"Authorization": "Bearer ВАШ_КЛЮЧ", "Accept": "application/json"}
SOURCE_ID = "WZAX"                # PWA-зразок
NAME = "Promo EN - зв’язка 12"    # назва копії
DOMAIN = "fit-tracker-12.example"  # домен для копії


def call(method, path, **kwargs):
    response = requests.request(method, f"{API}/{path}", headers=HEADERS, timeout=30, **kwargs)
    if not response.ok:
        raise SystemExit(f"{method} /{path}: {response.status_code} {response.json().get('message')}")
    return response.json()["data"]


# Команда зразка потрібна для PUT (team_lead і admin передають team_id завжди)
team_id = call("GET", f"pwas/{SOURCE_ID}")["pwa"]["team"]["id"]

clone = call("POST", f"pwas/{SOURCE_ID}/clone", json={"names": [NAME]})["clones"][0]
print("Копія:", clone["id"], clone["name"])

domain = call("POST", "domains", json={"domain": DOMAIN, "pwa_id": clone["id"]})["domain"]
print("Домен:", domain["domain"])

call("PUT", f"pwas/{clone['id']}", json={"active": True, "team_id": team_id})
print("PWA увімкнено")
js
// Node.js 18+, без залежностей; збережіть як clone-pwa.mjs
const API = 'https://panel.example.com/api';
const headers = { Authorization: 'Bearer ВАШ_КЛЮЧ', Accept: 'application/json', 'Content-Type': 'application/json' };
const SOURCE_ID = 'WZAX'; // PWA-зразок
const NAME = 'Promo EN - зв’язка 12'; // назва копії
const DOMAIN = 'fit-tracker-12.example'; // домен для копії

async function call(method, path, body) {
  const response = await fetch(`${API}/${path}`, {
    method,
    headers,
    body: body ? JSON.stringify(body) : undefined,
    signal: AbortSignal.timeout(30000),
  });
  const json = await response.json();
  if (!response.ok) throw new Error(`${method} /${path}: ${response.status} ${json.message}`);
  return json.data;
}

// Команда зразка потрібна для PUT (team_lead і admin передають team_id завжди)
const teamId = (await call('GET', `pwas/${SOURCE_ID}`)).pwa.team.id;

const [clone] = (await call('POST', `pwas/${SOURCE_ID}/clone`, { names: [NAME] })).clones;
console.log('Копія:', clone.id, clone.name);

const { domain } = await call('POST', 'domains', { domain: DOMAIN, pwa_id: clone.id });
console.log('Домен:', domain.domain);

await call('PUT', `pwas/${clone.id}`, { active: true, team_id: teamId });
console.log('PWA увімкнено');
php
<?php
// composer require guzzlehttp/guzzle
require 'vendor/autoload.php';

$api = new GuzzleHttp\Client([
    'base_uri' => 'https://panel.example.com/api/',
    'headers' => ['Authorization' => 'Bearer ВАШ_КЛЮЧ', 'Accept' => 'application/json'],
    'timeout' => 30,
    'http_errors' => false,
]);
$sourceId = 'WZAX';               // PWA-зразок
$name = 'Promo EN - зв’язка 12';  // назва копії
$domainName = 'fit-tracker-12.example'; // домен для копії

function call(GuzzleHttp\Client $api, string $method, string $path, ?array $body = null): array
{
    $response = $api->request($method, $path, $body === null ? [] : ['json' => $body]);
    $json = json_decode($response->getBody(), true);
    if ($response->getStatusCode() >= 400) {
        exit("{$method} /{$path}: {$response->getStatusCode()} {$json['message']}" . PHP_EOL);
    }
    return $json['data'];
}

// Команда зразка потрібна для PUT (team_lead і admin передають team_id завжди)
$teamId = call($api, 'GET', "pwas/{$sourceId}")['pwa']['team']['id'];

$clone = call($api, 'POST', "pwas/{$sourceId}/clone", ['names' => [$name]])['clones'][0];
echo "Копія: {$clone['id']} {$clone['name']}" . PHP_EOL;

$domain = call($api, 'POST', 'domains', ['domain' => $domainName, 'pwa_id' => $clone['id']])['domain'];
echo "Домен: {$domain['domain']}" . PHP_EOL;

call($api, 'PUT', "pwas/{$clone['id']}", ['active' => true, 'team_id' => $teamId]);
echo 'PWA увімкнено' . PHP_EOL;

Копія отримує налаштування, картинки й акаунти зразка, але не потік фільтрації - якщо він потрібен, задайте filter_stream_id у тому ж PUT (див. розділ «PWA»). Нове PWA починає отримувати трафік протягом кількох хвилин після ввімкнення.

Зупинити PWA без депозитів ​

Проходить по всіх увімкнених PWA й вимикає ті, у яких за останні 7 днів набралося достатньо встановлень, але немає жодного депозиту.

Що знадобиться: вирішити, скільки встановлень вважати достатнім (MIN_INSTALLS). За замовчуванням скрипт працює в режимі перевірки (DRY_RUN = True) і лише друкує, що вимкнув би. Переконайтеся, що список правильний, і змініть на False.

python
# pip install requests
import datetime
import time
import requests

API = "https://panel.example.com/api"
HEADERS = {"Authorization": "Bearer ВАШ_КЛЮЧ", "Accept": "application/json"}
MIN_INSTALLS = 50  # менше встановлень - рано судити
DRY_RUN = True     # True - лише показати, False - вимикати

today = datetime.datetime.now(datetime.timezone.utc).date()
date_from = (today - datetime.timedelta(days=7)).isoformat()
date_to = (today - datetime.timedelta(days=1)).isoformat()


def get(path, params=None):
    response = requests.get(f"{API}/{path}", headers=HEADERS, params=params, timeout=30)
    if response.status_code == 401:
        raise SystemExit("Ключ не прийнято - перевірте його в профілі")
    response.raise_for_status()
    return response.json()["data"]


pwas, page = [], 1
while True:
    data = get("pwas", {"status": "active", "page": page, "excludes": "stats"})
    pwas += data["pwas"]
    if page >= data["meta"]["last_page"]:
        break
    page += 1

for pwa in pwas:
    totals = get("stats/funnel", {"date_from": date_from, "date_to": date_to, "pwa_id": pwa["id"]})["funnel"]["totals"]
    time.sleep(0.25)  # не більше 300 запитів на хвилину
    if totals["installs"] < MIN_INSTALLS or totals["deps"] > 0:
        continue
    print(f"{pwa['id']} {pwa['name']}: {totals['installs']} встановлень, 0 депозитів")
    if not DRY_RUN:
        requests.put(f"{API}/pwas/{pwa['id']}", headers=HEADERS, json={
            "active": False,
            "team_id": pwa["team"]["id"],
        }, timeout=30).raise_for_status()
        print("  вимкнено")
js
// Node.js 18+, без залежностей; збережіть як stop-pwas.mjs
const API = 'https://panel.example.com/api';
const headers = { Authorization: 'Bearer ВАШ_КЛЮЧ', Accept: 'application/json', 'Content-Type': 'application/json' };
const MIN_INSTALLS = 50; // менше встановлень - рано судити
const DRY_RUN = true; // true - лише показати, false - вимикати

const day = (daysAgo) => new Date(Date.now() - daysAgo * 24 * 60 * 60 * 1000).toISOString().slice(0, 10);
const dateFrom = day(7);
const dateTo = day(1);
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

async function get(path, params = {}) {
  const response = await fetch(`${API}/${path}?${new URLSearchParams(params)}`, {
    headers,
    signal: AbortSignal.timeout(30000),
  });
  if (response.status === 401) throw new Error('Ключ не прийнято - перевірте його в профілі');
  if (!response.ok) throw new Error(`${path}: ${response.status}`);
  return (await response.json()).data;
}

const pwas = [];
for (let page = 1; ; page++) {
  const data = await get('pwas', { status: 'active', page, excludes: 'stats' });
  pwas.push(...data.pwas);
  if (page >= data.meta.last_page) break;
}

for (const pwa of pwas) {
  const { totals } = (await get('stats/funnel', { date_from: dateFrom, date_to: dateTo, pwa_id: pwa.id })).funnel;
  await sleep(250); // не більше 300 запитів на хвилину
  if (totals.installs < MIN_INSTALLS || totals.deps > 0) continue;
  console.log(`${pwa.id} ${pwa.name}: ${totals.installs} встановлень, 0 депозитів`);
  if (!DRY_RUN) {
    const response = await fetch(`${API}/pwas/${pwa.id}`, {
      method: 'PUT',
      headers,
      body: JSON.stringify({ active: false, team_id: pwa.team.id }),
      signal: AbortSignal.timeout(30000),
    });
    if (!response.ok) throw new Error(`PUT /pwas/${pwa.id}: ${response.status}`);
    console.log('  вимкнено');
  }
}
php
<?php
// composer require guzzlehttp/guzzle
require 'vendor/autoload.php';

$api = new GuzzleHttp\Client([
    'base_uri' => 'https://panel.example.com/api/',
    'headers' => ['Authorization' => 'Bearer ВАШ_КЛЮЧ', 'Accept' => 'application/json'],
    'timeout' => 30,
]);
$minInstalls = 50; // менше встановлень - рано судити
$dryRun = true;    // true - лише показати, false - вимикати

$today = new DateTimeImmutable('today', new DateTimeZone('UTC'));
$dateFrom = $today->modify('-7 days')->format('Y-m-d');
$dateTo = $today->modify('-1 day')->format('Y-m-d');

$pwas = [];
for ($page = 1; ; $page++) {
    $response = $api->get('pwas', ['query' => ['status' => 'active', 'page' => $page, 'excludes' => 'stats']]);
    $data = json_decode($response->getBody(), true)['data'];
    $pwas = [...$pwas, ...$data['pwas']];
    if ($page >= $data['meta']['last_page']) {
        break;
    }
}

foreach ($pwas as $pwa) {
    $response = $api->get('stats/funnel', ['query' => [
        'date_from' => $dateFrom,
        'date_to' => $dateTo,
        'pwa_id' => $pwa['id'],
    ]]);
    $totals = json_decode($response->getBody(), true)['data']['funnel']['totals'];
    usleep(250000); // не більше 300 запитів на хвилину
    if ($totals['installs'] < $minInstalls || $totals['deps'] > 0) {
        continue;
    }
    echo "{$pwa['id']} {$pwa['name']}: {$totals['installs']} встановлень, 0 депозитів" . PHP_EOL;
    if (!$dryRun) {
        $api->put("pwas/{$pwa['id']}", ['json' => ['active' => false, 'team_id' => $pwa['team']['id']]]);
        echo '  вимкнено' . PHP_EOL;
    }
}

Скрипт робить по одному запиту статистики на кожне PWA й тримає паузу 0,25 с, щоб не впертися в ліміт 300 запитів на хвилину. Встановлення й депозити тут - за датою події, як у воронці (див. розділ «Статистика»). Учасники команди PWA отримають сповіщення про кожен вимкнений застосунок.

Статистика в Google Таблицях ​

Щоранку дописує в Google Таблицю рядок із підсумками за вчора. За місяць виходить готова таблиця для графіків і зведень - без серверів і cron, усе всередині Google.

Налаштування:

  1. Відкрийте потрібну Google Таблицю й виберіть Extensions → Apps Script.
  2. Вставте скрипт нижче замість вмісту редактора й замініть panel.example.com на адресу своєї панелі.
  3. Ліворуч відкрийте Project Settings (шестірня) → Script Properties → Add script property: ім’я PWA_HUB_KEY, значення - ваш ключ. Так ключ не зберігається в тексті скрипта.
  4. Угорі виберіть функцію appendYesterday і натисніть Run. Google попросить дозволи - підтвердьте. У таблиці з’явиться аркуш PWA Hub з першим рядком.
  5. Ліворуч відкрийте Triggers (будильник) → Add Trigger: функція appendYesterday, джерело Time-driven, Day timer, час - наприклад, 7am to 8am. Збережіть.
js
// Google Таблиця → Extensions → Apps Script
const API = 'https://panel.example.com/api';
const SHEET = 'PWA Hub';

function appendYesterday() {
  const key = PropertiesService.getScriptProperties().getProperty('PWA_HUB_KEY');
  if (!key) throw new Error('Додайте PWA_HUB_KEY у Script Properties');

  const yesterday = new Date(Date.now() - 24 * 60 * 60 * 1000);
  const day = Utilities.formatDate(yesterday, 'UTC', 'yyyy-MM-dd');

  const response = UrlFetchApp.fetch(`${API}/stats/dashboard?date_from=${day}&date_to=${day}`, {
    headers: { Authorization: `Bearer ${key}`, Accept: 'application/json' },
    muteHttpExceptions: true,
  });
  if (response.getResponseCode() !== 200) {
    throw new Error(`PWA Hub відповів ${response.getResponseCode()}: ${response.getContentText()}`);
  }
  const metrics = JSON.parse(response.getContentText()).data.metrics;

  const spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
  let sheet = spreadsheet.getSheetByName(SHEET);
  if (!sheet) {
    sheet = spreadsheet.insertSheet(SHEET);
    sheet.appendRow(['Дата', 'Візити', 'Унікальні', 'Встановлення', 'Реєстрації', 'Депозити', 'CR, %']);
  }
  sheet.appendRow([
    day,
    metrics.total_visits.value,
    metrics.total_uniques.value,
    metrics.total_installs.value,
    metrics.total_registrations.value,
    metrics.total_deposits.value,
    metrics.avg_cr.value,
  ]);
}

Повторний запуск - повторний рядок

Скрипт просто дописує рядок. Запустили його двічі за день - вчорашній день опиниться в таблиці двічі; зайвий рядок видаліть вручну.

Якщо панель недоступна або ключ відкликано, запуск за тригером завершиться помилкою, а Google надішле лист про збій - рядок за цей день не з’явиться. Пропущений день можна дописати, замінивши yesterday на потрібну дату й запустивши функцію вручну.