Запрос выплаты через Telegram бот

На коленке накидал бот, который позволяет создавать выплаты через панель.

:information_source: У нас нет публичного API, который позволяет создавать выплаты по соображениям безопасности. Ниже код, который имитирует запрос, словно он сделан через браузер.

:information_source: Использование

  • /start – пингует бота. Если бот ответил, значит установка правильная
  • /setcookie $cookie – сохраняет обязательный куки, без которого бот будет выдавать ошибку. Инструкция по получению выше
  • /settoken $token – также, как /setcookie
  • /balance – отображает баланс вашего аккаунта
  • /payout $method $purse $sum – запрашивает выплату на указанный реквизит. $method может быть card или usdttrc (на момент редакции поста 03.10.2024)

:building_construction: Установка

  1. Установите на сервер ggram (скачайте и киньте в addons)
  2. По пути addons/ggram-mod/lua/autorun/ggram-launcher.lua скопируйте и вставьте код загрузчика из этой страницы
  3. Создайте файл addons/ggram-mod/lua/ggram/bots/gmdbot/_init.lua и поместите в него код, который ниже
  4. На первой строке укажите токен своего бота. Получить можно в Telegram: Contact @BotFather
  5. На 6й строке замените 123456 на свой telegram id. Получить можно в Telegram: Contact @jsonson_bot
  6. На этом этапе после перезагрузки сервера, у вас в боте должна работать команда /start. Если не работает, значит дальше пока не читайте, пока не разберетесь в проблеме
  7. Если /start ответила “hello”, значит все хорошо. Теперь вам нужно вызвать команды /setcookie и /settoken в боте. :point_down: Информация ниже

Получаем значение для /setcookie

Можете либо извлечь значение куки без плагина или с плагином для браузера.

С плагином

Установите EditThisCookie, на странице выплат откройте окно расширения и скопируйте там это значение: скриншот

Без плагина

  1. Зайдите на сайт, сразу на страницу выплат.
  2. Откройте dev tools (F12)
  3. Обновите страницу и затем вот тут получите куки: скриншот. Копируйте значение после знака “равно” ( = ), но ДО точки с запятой ( ; )

Получаем значение для /settoken

  1. Открываем dev tools на странице выплат
  2. Меняем метод выплаты (например, с киви на карту или наоборот)
  3. Токен будет видно тут: скриншот

:man_technologist: Код бота

Путь: addons/ggram-mod/lua/ggram/bots/gmdbot/_init.lua (не забудьте установить ggram, как написано выше)

👉 Код (кликни сюда)
local bot = require("ggram")("your_bot_token")
require("ggram.polling").start(bot)

function bot.secureCommand(cmd, func)
	return bot.command(cmd, function(ctx)
		if ctx.from.id ~= 6014903768 then -- СЮДА НУЖНО ВСТАВИТЬ ТВОЙ TELEGRAM ID
		-- if ctx.chat.id ~= 1234567 then -- Либо можно дать доступ целому админ чату
			return ctx.reply.text("fak yu")
		end
		return func(ctx)
	end)
end

bot.command("start", function(ctx) ctx.reply.text("hello") end)

-- либа, которая в гмоде сохраняет все в sv.db
local cookie = cookie or require("gmod.cookie")
local HTTP   = HTTP   or require("gmod.globals").HTTP

local BASE_URL = "https://gm-donate.net"

local function request(method, endpoint, parameters_, fSuccess, fError)
	local session = cookie.GetString("gmd_session_cookie")
	if not session then fError("Сначала нужно выполнить /setcookie") return end
	if not cookie.GetString("gmd_post_token") then fError("Большинство запросов требуют /settoken") return end

	HTTP({
		method = method or "GET",
		url = BASE_URL .. endpoint,
		headers = {
			Cookie = "laravel_session=" .. session,
			Accept = "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"
		},
		success = function(code, body, headers)
			if body:find("cRay:") then fError("Запрос столкнулся с DDoS защитой. Напишите на @gm_donate") return end
			if body:find("© Valve Corporation") then fError("Ошибка авторизации. Возможно, неправильный /setcookie") return end
			if code == 419 then print("body", body) fError("Ошибка авторизации. Возможно, неправильный /settoken") return end
			fSuccess(body, code, headers)
		end,
		failed = fError,
		parameters = parameters_
	})
end

local function get_balance(cb)
	request("GET", "/panel/payouts", nil, function(body)
		local bal_str = body:match("fa fa%-rub.-</span>.-([%d,%.]+).*</a>") -- 2,332.65
		if not bal_str then print("gmd incorrect body", body) cb(false, "Ошибка парсинга баланса") return end
		local bal_num = bal_str:gsub(",", "")
		cb(true, tonumber(bal_num))
	end, function(err) cb(false, err) end)
end
-- get_balance(print)

local function request_payout(sum, cb)
	request("POST", "/panel/payouts/add", {
		_token = cookie.GetString("gmd_post_token"),
		sum = tostring(sum),
	}, function(body, _, headers)
		local redirect = (not not body:match("Redirecting to")) and headers.location
		if redirect then
			local string_PatternSafe = string.PatternSafe or require("gmod.string").PatternSafe
			request("GET", headers.location:gsub( string_PatternSafe(BASE_URL),"" ), nil, function(cont)
				local success = not not cont:match("Выплата запрошена")
				cb(success, "При запросе выплаты на странице не найден искомый текст. Проверьте вручную, создалась ли выплата")
			end, function(err2) cb(false, "Ошибка при редиректе после создания выплаты: " .. err2) end)
		else
			local success = not not cont:match("Выплата запрошена")
			cb(success, "Проверьте вручную, создалась ли выплата. На странице не найден текст подтверждения")
		end
	end, function(err) cb(false, err) end)
end
-- request_payout(500, print)

-- 1 qiwi, 2 card
local function update_payout_method(sMethod, cb)
	request("GET", "/panel/payouts/setPaymentMethod/" .. sMethod, {
		_token = cookie.GetString("gmd_post_token"),
	}, function(body, code)
		if code ~= 200 then -- iMethod = 3 => 404
			cb(false, "Статус код ожидался 200, но получен " .. code)
			return
		end

		local bodyok = not not body:find("Метод выплат успешно изменен")
		cb(bodyok, "На странице не найден текст подтверждения успешной смены реквизитов")
	end, function(err) cb(false, err) end)
end
-- update_payout_method("usdttrc", function(ok) print("ok", ok) end)

local function update_purse(sMethod, purse, cb)
	local params = {
		_token = cookie.GetString("gmd_post_token"),
		method = sMethod,
		purse  = purse,
	}

	request("POST", "/panel/payouts/save-settings", params, function(body, code, headers)
		local redirect = (not not body:match("Redirecting to")) and headers.location
		if redirect then -- #todo нет авторедиректов :(
			local string_PatternSafe = string.PatternSafe or require("gmod.string").PatternSafe
			request("GET", headers.location:gsub( string_PatternSafe(BASE_URL),"" ), nil, function(cont)
				local success = not not cont:match("Реквизиты платежного метода изменены")
				cb(success, "Проверьте вручную, изменились ли реквизиты и напишите в GMD support")
			end, function(err2) cb(false, err2) end)
		else
			local success = not not cont:match("Реквизиты платежного метода изменены")
			cb(success, "При попытке изменить реквизиты на странице не нашелся текст, подтверждающий успешность запроса")
		end
	end, function(err) cb(false, err) end)
end
-- update_purse("usdttrc", "TJL2bcC4o9pBH14EpHuoB5Jx8AUG4M628U", print)
-- update_purse("card", "1111222233334444", print)

-- Брать тут: https://file.def.pm/JYOF0cFb.jpg
bot.secureCommand("setcookie", function(ctx)
	local cook = ctx.args()[1]
	if not cook then
		ctx.reply.text("Введите laravel_session куки вторым параметром.\n\n" ..
			"Инструкция тут: https://forum.gm-donate.net/t/4396")

		return
	end

	cookie.Set("gmd_session_cookie", cook)
	ctx.reply.text("OK")
end)

-- Брать тут (из post запросов): https://file.def.pm/7d4c86D1.jpg
bot.secureCommand("settoken", function(ctx)
	local token = ctx.args()[1]
	if not token then
		ctx.reply.text("Введите _token значение вторым параметром.\n\n" ..
			"Инструкция тут: https://forum.gm-donate.net/t/4396")

		return
	end

	if token:len() ~= 40 then
		ctx.reply.text("Неправильная длина токена. Должно быть 40 символов")
		return
	end

	cookie.Set("gmd_post_token", token)
	ctx.reply.text("OK")
end)

bot.secureCommand("balance", function(ctx)
	get_balance(function(ok, bal)
		local str = ok and ("Баланс: " .. bal) or ("Ошибка запроса: " .. bal)
		ctx.reply.text(str)
	end)
end)

bot.secureCommand("payout", function(ctx)
	local args = ctx.args()
	local method, purse, sum = args[1], args[2], tonumber(args[3])
	if not sum then
		ctx.reply.markdown("Пример: `/payout $метод $реквизит $сумма`\n\n" ..
			"Метод на момент написания кода (3 сент 2024) может быть `card`, либо `usdttrc`\n\n" ..
			"Сумма может быть указана с копейками через точку, например 123.45")

		return
	end

	update_payout_method(method, function(ok1, err)
		if not ok1 then ctx.reply.text("Ошибка смены метода выплаты: " .. err) return end

		update_purse(method, purse, function(ok2, err2)
			if not ok2 then ctx.reply.text("Ошибка смены реквизитов: " .. err2) return end

			request_payout(sum, function(ok3, err3)
				ctx.reply.text(ok3 and "Запрос создан" or err3)
			end)
		end)
	end)
end)

Примечания

  • Код слишком хрупкий, может сломаться в любой момент, поскольку все данные просто парсит из html страницы-ответа, которая в будущем может меняться. Тем не менее, я готов делать фиксы, если это будет востребованно в этой теме
  • Здесь нет проверки статуса выплаты, это сильно усложнило бы код, тем не менее такую функцию можно доделать самостоятельно (после создания выплаты парсите таблицу выплат и проверяете статус раз в пару минут)
  • Код будет работать как на Garry’s Mod, так и на чистом Lua (за пределами гаррисмод). Тестировалось как раз в обычном Lua. Вот здесь я немного писал об этой библиотеке для ботов
  • Мы не принимаем никаких жалоб на то, что у вас украли деньги. Вы сами в ответе за секретные данные и сами отвечаете за их защиту. Если вдруг у вас украдут код сервера, а с ним и сделают выплаты – мы не поможем. Могу лишь порекомендовать не хранить секретные данные в lua файлах или делать выплаты вручную
  • Время от времени куки и токен может потребоваться обновлять. Я не могу точно сказать, насколько часто это нужно будет делать, но полагаю, что не очень часто
1 лайк

Сейчас бот может не работать. Если он вам нужен, я починю его сразу, как только кто-то напишет о желании его использовать здесь ниже в комментариях

:point_down:

Решил починить, не дожидаясь. Починил, проверил. Главное сообщение обновлено

1 лайк