Подключаем API SMM-панели к шаблону ZennoPoster: заказ, статус, отмена

MARKET SMM

Пользователь
Регистрация
03.07.2023
Сообщения
37
Реакции
9
Баллы
8
Многие шаблоны рано или поздно упираются в одно и то же: аккаунты зарегистрированы и прогреты, контент залит — а дальше каналу или посту нужны первые просмотры, подписчики или реакции. Делать это фермой долго и дорого по прокси, поэтому часть задач проще отдать SMM-панели по API прямо из шаблона.

Ниже — рабочая схема на C#: каталог услуг → заказ → статус → отмена или рефилл → баланс. Плюс что делать с ошибками, где хранить ключ и на чём легко потерять деньги.

Что проверено: класс SmmApi из раздела 3 скомпилирован и запущен в .NET 29.10.2026 — запросы к API, ответ на неверный ключ и разбор всех примеров ответов из документации. Обвязку под ZennoPoster (переменные проекта, кубики) в самом ZennoPoster не запускал. Если что-то не заведётся в вашей версии — напишите в теме, поправлю.

Дисклеймер: я из команды MARKET SMM, примеры написаны на API нашей панели. Протокол простой — один адрес, POST, JSON, — так что разбор пригодится и тем, кто подключает что-то своё.

1. Как устроен API​


  • Адрес: https://market-smm.pro/api/v2
  • Метод: POST, тело — обычная форма (application/x-www-form-urlencoded)
  • Ответ: JSON
  • В каждом запросе два обязательных поля: key (ваш ключ API) и action (что сделать)

actionЧто делаетПараметрыЧто вернёт
servicesКаталог услуг—массив: service, name, type, category, rate, min, max, refill, cancel
addНовый заказservice, link, quantity (+ параметры типа услуги)order — ID заказа
statusСтатус заказаorder, или orders — до 100 ID через запятуюcharge, start_count, status, remains, currency
cancelОтменаorders — до 100 IDcancel по каждому заказу
refillРефилл (докрутка по гарантии)order или ordersrefill — ID рефилла
refill_statusСтатус рефиллаrefill или refillsstatus
balanceБаланс—balance, currency

Полная документация с примерами ответов: market-smm.pro/api

Три вещи, которые стоит знать до первой строки кода:
  1. Числа приходят строками. "rate": "0.90", "remains": "157". И с точкой в качестве разделителя: на русской Windows decimal.Parse("0.90") без CultureInfo.InvariantCulture упадёт или посчитает не то.
  2. Ошибка — это поле error в JSON, например {"error": "Invalid API key"} или {"error": "Incorrect order ID"}. Проверяйте его в каждом ответе. На неверный ключ сервер отвечает HTTP 401 с этим же JSON в теле, а WebClient на 401 бросает исключение — поэтому тело ответа в коде ниже достаём из WebException.
  3. Вебхуков нет. Статусы опрашиваете сами — удобнее пачкой, до 100 ID за запрос.

2. Где хранить ключ​


Ключ берётся в личном кабинете на странице «API» — там же его можно перевыпустить. Ключ даёт доступ к балансу: кто его знает, тот может заказывать за ваш счёт.

Поэтому:
  • Не вписывайте ключ в шаблон. Шаблон .xmlz рано или поздно уходит заказчику, на фриланс или в архив на форуме — вместе с ключом.
  • Держите ключ вне проекта: в переменной окружения Windows SMM_API_KEY или в файле в папке пользователя. ZennoPoster видит новую переменную окружения только после перезапуска.
  • Не пишите в лог тело запроса — в нём ключ. Логируйте только ответ.
  • Если ключ всё-таки утёк — перевыпустите его в кабинете: старый сразу перестанет работать, новый пропишите в переменной или файле.

3. Общий код: один метод на все запросы​


Добавьте класс в «Общий код» проекта (внутрь namespace, который ZennoPoster создаёт сам). Внешние библиотеки не нужны — только стандартный .NET.

C#:
Развернуть Свернуть Копировать
public static class SmmApi
{
    public const string Url = "https://market-smm.pro/api/v2";

    // Ключ: переменная окружения SMM_API_KEY, иначе файл smm_api_key.txt в папке пользователя
    public static string Key()
    {
        string k = System.Environment.GetEnvironmentVariable("SMM_API_KEY");
        if (string.IsNullOrWhiteSpace(k))
        {
            string path = System.IO.Path.Combine(
                System.Environment.GetFolderPath(System.Environment.SpecialFolder.UserProfile),
                "smm_api_key.txt");
            if (System.IO.File.Exists(path)) k = System.IO.File.ReadAllText(path);
        }
        if (string.IsNullOrWhiteSpace(k))
            throw new System.Exception("Ключ API не найден: задайте SMM_API_KEY или создайте smm_api_key.txt");
        return k.Trim();
    }

    // POST key + action + параметры. Возвращает тело ответа строкой.
    public static string Call(string action, System.Collections.Generic.Dictionary<string, string> args = null)
    {
        System.Net.ServicePointManager.SecurityProtocol |= System.Net.SecurityProtocolType.Tls12;

        var form = new System.Collections.Specialized.NameValueCollection();
        form["key"] = Key();
        form["action"] = action;
        if (args != null)
            foreach (var p in args) form[p.Key] = p.Value;

        using (var wc = new System.Net.WebClient())
        {
            wc.Encoding = System.Text.Encoding.UTF8;
            try
            {
                byte[] raw = wc.UploadValues(Url, "POST", form);
                return System.Text.Encoding.UTF8.GetString(raw);
            }
            catch (System.Net.WebException ex)
            {
                // Сервер ответил не 200: в теле всё равно может быть {"error": "..."}
                var resp = ex.Response as System.Net.HttpWebResponse;
                if (resp == null) throw;   // ответа нет вовсе: таймаут, DNS, обрыв
                using (var r = new System.IO.StreamReader(resp.GetResponseStream()))
                    return r.ReadToEnd();
            }
        }
    }

    // Значение плоского поля: "name": "строка" или "name": 123 / true / null
    public static string Field(string json, string name)
    {
        var m = System.Text.RegularExpressions.Regex.Match(json,
            "\"" + name + "\"\\s*:\\s*(?:\"((?:[^\"\\\\]|\\\\.)*)\"|(-?[\\d.]+|true|false|null))");
        if (!m.Success) return null;
        return m.Groups[1].Success
            ? System.Text.RegularExpressions.Regex.Unescape(m.Groups[1].Value)
            : m.Groups[2].Value;
    }

    // Текст ошибки или null
    public static string Error(string json)
    {
        return Field(json, "error");
    }

    // Число из строки ответа ("0.90") независимо от языка Windows
    public static decimal Num(string s)
    {
        return decimal.Parse(s, System.Globalization.CultureInfo.InvariantCulture);
    }
}

Почему регулярки, а не полноценный JSON-парсер: ответы API плоские (кроме пачек, о них ниже), а так код не зависит от того, какая JSON-библиотека подключена у вас в ZennoPoster. Если у вас уже есть привычный парсер — используйте его, логика не меняется.

4. Каталог услуг → CSV​


Каталог нужен, чтобы знать ID услуги, минимум и максимум заказа и, главное, флаги cancel и refill: отменить или докрутить можно не каждый заказ. Кубик «C# код»:

C#:
Развернуть Свернуть Копировать
string json = SmmApi.Call("services");
string err = SmmApi.Error(json);
if (err != null) throw new Exception("services: " + err);

string[] cols = { "service", "name", "type", "category", "rate", "min", "max", "refill", "cancel" };
var lines = new System.Collections.Generic.List<string>();
lines.Add(string.Join(";", cols));

// Каждая услуга — плоский объект {...}
foreach (System.Text.RegularExpressions.Match m in
         System.Text.RegularExpressions.Regex.Matches(json, "\\{[^{}]*\\}"))
{
    var row = new string[cols.Length];
    for (int i = 0; i < cols.Length; i++)
        row[i] = (SmmApi.Field(m.Value, cols[i]) ?? "").Replace(";", ",");
    lines.Add(string.Join(";", row));
}

string file = System.IO.Path.Combine(project.Directory, "services.csv");
System.IO.File.WriteAllLines(file, lines, System.Text.Encoding.UTF8);
return (lines.Count - 1).ToString();   // сколько услуг сохранили

Каталог меняется: услуги добавляются, цены и лимиты обновляются. Обновляйте CSV раз в сутки или перед запуском большой партии, а не один раз навсегда. Поле type подсказывает, какие параметры нужны в add: у обычной услуги (Default) — ссылка и количество, у комментариев — список comments, у опроса — answer_number, у подписок на новые посты (Subscriptions) — username и диапазон min/max. Подробно — в документации.

5. Заказ​


Перед кубиком заведите переменные проекта service_id, target_link, quantity, order_id, api_response.

C#:
Развернуть Свернуть Копировать
string json = SmmApi.Call("add", new System.Collections.Generic.Dictionary<string, string> {
    { "service",  project.Variables["service_id"].Value },
    { "link",     project.Variables["target_link"].Value },
    { "quantity", project.Variables["quantity"].Value }
});
project.Variables["api_response"].Value = json;

string err = SmmApi.Error(json);
if (err != null) throw new Exception("add: " + err);   // кубик уйдёт в красную ветку

string order = SmmApi.Field(json, "order");   // успешный ответ: {"order": 23501}
if (string.IsNullOrEmpty(order)) throw new Exception("add: неожиданный ответ " + json);

project.Variables["order_id"].Value = order;
return order;

Сразу запишите order_id вместе со ссылкой в таблицу или файл: без ID вы не узнаете статус и не отмените заказ.

Главная ловушка — повтор add по таймауту. Если запрос ушёл, а ответа нет (WebException без Response), заказ мог создаться. Своего идентификатора заказа API не принимает, найти «тот самый» заказ по ссылке через API нельзя. Автоматический повтор в цикле — это два или три одинаковых заказа и списанный баланс. Поэтому:
  • на add никаких автоповторов;
  • ссылку из такого запроса пометьте как «неизвестно» и проверьте вручную в кабинете, в истории заказов;
  • повторяйте только запросы, которые ничего не меняют: services, status, balance.

Перед add сверьте quantity с min/max услуги из каталога — так ошибка не дойдёт до сервера.

6. Статус: один заказ и пачка​


Один заказ:

C#:
Развернуть Свернуть Копировать
string json = SmmApi.Call("status", new System.Collections.Generic.Dictionary<string, string> {
    { "order", project.Variables["order_id"].Value }
});
string err = SmmApi.Error(json);
if (err != null) throw new Exception("status: " + err);

project.Variables["order_status"].Value  = SmmApi.Field(json, "status");      // например Partial, In progress
project.Variables["order_remains"].Value = SmmApi.Field(json, "remains");     // сколько не докручено
project.Variables["order_start"].Value   = SmmApi.Field(json, "start_count"); // сколько было до старта
return project.Variables["order_status"].Value;

Если заказов десятки, опрашивайте пачкой — orders, до 100 ID через запятую. Ответ — объект, где ключ — ID заказа:

JSON:
Развернуть Свернуть Копировать
{
  "1":  { "charge": "…", "start_count": "3572", "status": "Partial", "remains": "157", "currency": "…" },
  "10": { "error": "Incorrect order ID" }
}

C#:
Развернуть Свернуть Копировать
// ids — до 100 ID через запятую, например из таблицы активных заказов
string ids = project.Variables["order_ids"].Value;
string json = SmmApi.Call("status", new System.Collections.Generic.Dictionary<string, string> { { "orders", ids } });

var report = new System.Text.StringBuilder();
foreach (System.Text.RegularExpressions.Match m in
         System.Text.RegularExpressions.Regex.Matches(json, "\"(\\d+)\"\\s*:\\s*\\{([^{}]*)\\}"))
{
    string id = m.Groups[1].Value, body = m.Groups[2].Value;
    string e = SmmApi.Error(body);
    report.AppendLine(id + ";" + (e != null ? "ERROR: " + e
                                            : SmmApi.Field(body, "status") + ";" + SmmApi.Field(body, "remains")));
}
return report.ToString();

Про значения статуса: в примерах документации есть Partial (выполнен частично, remains показывает недокрученное) и In progress. Полного списка в документации нет, поэтому незнакомый статус не считайте ошибкой: запишите его в лог и продолжайте опрос. Список того, какие статусы считать конечными для вашей логики, соберите из реальных ответов на тестовых заказах.

Частота опроса: заказы выполняются минутами и часами, поэтому опрос каждые несколько минут пачкой по 100 ID — с запасом. Чаще смысла нет.

7. Отмена​


Отменить можно только заказ услуги, у которой в каталоге cancel = true. Отмена принимает orders — до 100 ID:

C#:
Развернуть Свернуть Копировать
string json = SmmApi.Call("cancel", new System.Collections.Generic.Dictionary<string, string> {
    { "orders", project.Variables["order_id"].Value }
});
// Успех:  [{"order": 2, "cancel": 1}]
// Ошибка: [{"order": 9, "cancel": {"error": "Incorrect order ID"}}]
string err = SmmApi.Error(json);
if (err != null) throw new Exception("cancel: " + err);
return SmmApi.Field(json, "cancel");

Если отменяете пачку, разбирайте ответ так же, как статус пачкой: по объекту на заказ, ошибка у каждого своя.

8. Рефилл​


Рефилл — докрутка, если часть подписчиков или просмотров списалась в гарантийный срок. Доступен, когда у услуги refill = true; срок гарантии указан в названии услуги (R7, R30 и т. п.).

C#:
Развернуть Свернуть Копировать
// Создать рефилл: ответ {"refill": "1"} — это ID рефилла, не заказа
string json = SmmApi.Call("refill", new System.Collections.Generic.Dictionary<string, string> {
    { "order", project.Variables["order_id"].Value }
});
string err = SmmApi.Error(json);
if (err != null) throw new Exception("refill: " + err);
string refillId = SmmApi.Field(json, "refill");

// Позже — статус рефилла: {"status": "Completed"} или {"status": "Rejected"}
string st = SmmApi.Call("refill_status", new System.Collections.Generic.Dictionary<string, string> {
    { "refill", refillId }
});
return SmmApi.Error(st) ?? SmmApi.Field(st, "status");

9. Баланс​


Проверяйте баланс перед партией заказов, а не после первой ошибки посреди неё:

C#:
Развернуть Свернуть Копировать
string json = SmmApi.Call("balance");
string err = SmmApi.Error(json);
if (err != null) throw new Exception("balance: " + err);

decimal balance = SmmApi.Num(SmmApi.Field(json, "balance"));
string currency = SmmApi.Field(json, "currency");   // валюта выбирается в настройках аккаунта

decimal minBalance = SmmApi.Num(project.Variables["min_balance"].Value);
if (balance < minBalance) throw new Exception("Мало денег: " + balance + " " + currency);
return balance.ToString(System.Globalization.CultureInfo.InvariantCulture);

Стоимость конкретного заказа — в поле charge ответа status; её удобно складывать в отчёт по партии.

10. Без кода: кубиками​


Если C# в проекте не нужен, то же самое собирается стандартными действиями:
  1. Действие POST-запроса: URL https://market-smm.pro/api/v2, тело — key={-Variable.smm_key-}&action=status&order={-Variable.order_id-}, тип содержимого application/x-www-form-urlencoded, ответ — в переменную.
  2. Регулярное выражение по ответу: "error"\s*:\s*"([^"]*)". Нашлось — уходим в ветку ошибки.
  3. Ещё по регулярке на каждое нужное поле: "status"\s*:\s*"([^"]*)", "order"\s*:\s*(\d+) и т. д.
Минус кубиков: ключ оказывается в переменной проекта. Задавайте его через входные настройки при запуске задачи, а перед тем как отдать шаблон, убедитесь, что значение не сохранилось внутри.

11. Чек-лист перед запуском на ферме​

  • Ключ — вне шаблона, тело запроса в лог не пишется.
  • Каталог загружен свежий, quantity сверен с min/max.
  • На add нет автоповтора; таймаут → ручная проверка.
  • Каждый order_id записан вместе со ссылкой.
  • Статусы опрашиваются пачкой, незнакомый статус не роняет шаблон.
  • Отмена и рефилл — только для услуг с cancel / refill = true.
  • Баланс проверяется до партии, числа парсятся через InvariantCulture.

Где взять ключ и на чём попробовать​


Все примеры — на API MARKET SMM: 1500+ услуг для Telegram, MAX, YouTube, VK и других площадок, ключ — на странице «API» в кабинете после регистрации.

Если подключаете API к своей панели, боту или шаблону, дадим тестовый баланс 100 ₽, чтобы прогнать весь цикл из статьи: напишите в бот @marketsmm1_bot слово ZENNO. Тест — для новых аккаунтов, один раз.

Вопросы по коду и по API пишите в теме — отвечу здесь.
 

Кто просматривает тему: (Всего: 1, Пользователи: 0, Гости: 1)