Предупреждение: бот прост до безобразия. Вы вряд ли научитесь чему-то новому. Статья just for fun.
Решил, что спустя год можно поглядеть, чем френды вконтакте развлекаются. Кроме традиционных тонн политоты, картинок с котятами и прочих непотребств обнаружил ссылку на браузерку Angry Pets. В браузерки никогда не играл, поэтому решил посмотреть, что это за зверь такой. Выяснилось следующее: картинки кавайнейшие (милые котики, пингвинчики и белочки бьют друг другу морды), донат анальнейший (за деньги доход умножают в 8 раз), сюжет, стратегия, безбажность, мануал и прочие привычные удобства отсутствуют.
Игра чуть менее, чем полностью, состоит из следующих нехитрых операций: построить здание, подождать 10 минут, построить юнитов, подождать 10 минут, найти жертву, отправить юниты в атаку, подождать 10 минут, найти жертву, отправить юниты в атаку, подождать 10 минут… (10 минут на высоких уровнях растут экспоненциально. За ускорение — отдельная плата.)
Что делать с такой игрой? Правильно, писать бота. Не играть же в это, честное слово.

Ставим задачу:
Чтобы бот не спалился сразу, во-первых, жертвами дляизбиения младенцев фарма выбираем игроков, долго не выходивших в онлайн, чтобы они на нас не пожаловались куда следует; во-вторых, имитируем действия пользователя, а не отправляем голые AJAX запросы; в-третьих, задержку между действиями делаем не нулевой, а случайной.
На радость авторам ботов скрипты на сайте не обфусцированы. Единственные упакованные скрипты — это jQuery и иже. И да, есть jQuery, поэтому пользуемся всеми благами цивилизации.
Большинство действий в игре приводят к AJAX запросам. Нажимаешь элемент, уходит запрос, приходит или практически полный экран, или отдельное окошко. Исследуя в отладчике ссылки, видим, что они выглядят как
Как сделать ожидание выполнения AJAX запроса? В jQuery есть славная функция $.ajaxSuccess, в которую можно передать коллбэк, вызываемый после каждого успешного запроса. В него сваливаются событие, объект XMLHttpRequest и аргументы вызова $.ajax. Соответственно, при получении заданного УРЛа вызываем наш коллбэк.
Ну и чисто для единообразия к waitForAjax добавляем waitForAction, когда нужно не ждать AJAX, а просто сделать задержку.
Как простой неавтоматизированный смертный часто фармит? Жмёт по кнопке почты, переход в логи, выбирает недавно атакованный город, жмёт «Атаковать», выбирает юниты, жмёт «Набить морду».

Вот и будем повторять эту операцию по кругу. Конечно, в логах нужный город на нужной странице будет не всегда, но писать сложную логику перелистывания без особой мотивации — откровенно лень.
Выбираем жертву и формируем ссылки…
Переходим по ссылкам и жмём кнопки…
Либо выбираем конкретные юниты (доступное количество — в атрибуте инпута max), либо выбираем все (отдельная кнопка), а затем самое важное: нажать «Атаковать».
Попапы-слои при переходе по AJAX-страницам закрываются сами, можно не заморачиваться.
Собственно, всё. Только красивый список атакуемых городов можно прикрутить, чтобы видеть, кого мочим.
Полный код:
Инструкция по применению:
Вообще, меня забавляет наглость хозяев игры. «Абонентская плата» в виде Озверина (без которого играть ещё более уныло) — это 180 руб/мес (WoW, в котором есть всё, чего нет в этой игре, стоит всего лишь в 2 раза дороже). Ресурсов можно покупать на до 120 руб/день (день!), увеличивая скорость добычи ресурсов в 8 раз (соревнование кошельков, неплатящие вообще идут лесом). Просторы длявымогательства доната на ускорялки, детали для ракеты, бонусы для атаки, прохождение квестов (ага, заплатил 300 руб — квест прошёл) и прочую мелочь — вообще безграничны. Обмен ресурсов, вступление в клан и прочее — только за деньги. У них в оплате «выбор игроков» — это монетки на 1500 руб. И это всё прикрывается железным аргументом: «Часть дохода идёт в WWF! Вы спасаете милых пушистых зверьков! Будете возмущаться — забаним нафиг!»
И ведь люди платят. Я фигею.
Решил, что спустя год можно поглядеть, чем френды вконтакте развлекаются. Кроме традиционных тонн политоты, картинок с котятами и прочих непотребств обнаружил ссылку на браузерку Angry Pets. В браузерки никогда не играл, поэтому решил посмотреть, что это за зверь такой. Выяснилось следующее: картинки кавайнейшие (милые котики, пингвинчики и белочки бьют друг другу морды), донат анальнейший (за деньги доход умножают в 8 раз), сюжет, стратегия, безбажность, мануал и прочие привычные удобства отсутствуют.
Игра чуть менее, чем полностью, состоит из следующих нехитрых операций: построить здание, подождать 10 минут, построить юнитов, подождать 10 минут, найти жертву, отправить юниты в атаку, подождать 10 минут, найти жертву, отправить юниты в атаку, подождать 10 минут… (10 минут на высоких уровнях растут экспоненциально. За ускорение — отдельная плата.)Что делать с такой игрой? Правильно, писать бота. Не играть же в это, честное слово.

Ставим задачу:
- Бот должен уметь атаковать заданные списком города по расписанию.
- Боту можно задавать, какими юнитами атаковать.
- Бот должен не сразу палиться. :)
Чтобы бот не спалился сразу, во-первых, жертвами для
На радость авторам ботов скрипты на сайте не обфусцированы. Единственные упакованные скрипты — это jQuery и иже. И да, есть jQuery, поэтому пользуемся всеми благами цивилизации.
Большинство действий в игре приводят к AJAX запросам. Нажимаешь элемент, уходит запрос, приходит или практически полный экран, или отдельное окошко. Исследуя в отладчике ссылки, видим, что они выглядят как
где метод Main.goToUrl принимает либо строки, либо элементы-ссылки. У большинства игровых ссылок в начале стоит префикс с идентификатором профиля игрока. Сохраняем его.<a href="/10193192/city/view/10009466" data-link-handled="1" onclick="Main.goToUrl(this);return false;"></a>
profilePath: window.location.pathname.match(/^\/\d+\//)[0],
Как сделать ожидание выполнения AJAX запроса? В jQuery есть славная функция $.ajaxSuccess, в которую можно передать коллбэк, вызываемый после каждого успешного запроса. В него сваливаются событие, объект XMLHttpRequest и аргументы вызова $.ajax. Соответственно, при получении заданного УРЛа вызываем наш коллбэк.
ajaxCallbacks: {}, run: function () { ... $('html').ajaxSuccess(Bot.ajaxSuccess); ... }, ajaxSuccess: function (e, xhr, settings) { var ajaxUrl = null, ajaxCallback = null; $.each(Bot.ajaxCallbacks, function (url, callback) { var fullUrl = Bot.profilePath + url; if (settings.url.substr(0, fullUrl.length) == fullUrl) { ajaxUrl = url; ajaxCallback = callback; } }); if (ajaxCallback) { Bot.ajaxCallbacks[ajaxUrl] = null; setTimeout(ajaxCallback, Bot.getClickDelay()); } }, waitForAjax: function (pageUrl, gotoPage, success) { Bot.ajaxCallbacks[pageUrl] = success; gotoPage(); },
Ну и чисто для единообразия к waitForAjax добавляем waitForAction, когда нужно не ждать AJAX, а просто сделать задержку.
waitForAction: function (action, success) { action(); setTimeout(success, Bot.getClickDelay()); },
Как простой неавтоматизированный смертный часто фармит? Жмёт по кнопке почты, переход в логи, выбирает недавно атакованный город, жмёт «Атаковать», выбирает юниты, жмёт «Набить морду».

Вот и будем повторять эту операцию по кругу. Конечно, в логах нужный город на нужной странице будет не всегда, но писать сложную логику перелистывания без особой мотивации — откровенно лень.
Выбираем жертву и формируем ссылки…
attackNext: function () { if (Bot.targetCities.length == 0) return; if (!Bot.targetCities[Bot.currentTargetCity]) Bot.currentTargetCity = 0; var targetCity = Bot.targetCities[Bot.currentTargetCity++], targetCityUrl = 'city/view/' + targetCity, attackCityUrl = 'attack/' + targetCity;
Переходим по ссылкам и жмём кнопки…
Bot.waitForAjax('pm/inbox', function () { Main.goToUrl(Bot.profilePath + 'pm/inbox'); }, function () { Bot.waitForAjax('pm/logs', function () { Main.goToUrl(Bot.profilePath + 'pm/logs'); }, function () { Bot.waitForAjax(targetCityUrl, function () { Main.goToUrl(Bot.profilePath + targetCityUrl); }, function () { Bot.waitForAjax(attackCityUrl, function () { $('button[onclick^="Attack.showAttackAlert"]').click(); }, function () {
Либо выбираем конкретные юниты (доступное количество — в атрибуте инпута max), либо выбираем все (отдельная кнопка), а затем самое важное: нажать «Атаковать».
Bot.waitForAction(function () { var count = 0; $.each(Bot.attackUnits, function (unitType, unitNum) { var ctl = $('input[name="units[' + unitType + ']"]'); ctl.val(Math.min(ctl.attr('max'), unitNum)).change(); count++; }); if (count == 0) { $('span[onclick^="Attack.ChooseEveryone"]').click(); } }, function () { $('button[type=submit]').click(); setTimeout(Bot.attackNext, Bot.getAttackInterval()); })
Попапы-слои при переходе по AJAX-страницам закрываются сами, можно не заморачиваться.
Собственно, всё. Только красивый список атакуемых городов можно прикрутить, чтобы видеть, кого мочим.
Полный код:
window.Bot = { attackInterval: /*5.5*/8 * 60 * 1000, // 8 min attackIntervalRandom: 1.2 * 60 * 1000, // 1.2 min clickDelay: 3 * 1000, // 3 sec clickDelayRandom: 4 * 1000, // 4 sec targetCities: [ //12345678 ], attackUnits: { //101: 99 }, profilePath: window.location.pathname.match(/^\/\d+\//)[0], currentTargetCity: 0, ajaxCallbacks: {}, run: function () { var box = '<div style="position: absolute; background: #fff; padding: 10px; border-radius: 10px; left: 20px; top: 20px; z-index: 666666">'; $.each(Bot.targetCities, function (_, cityId) { box += '<a class="bot-target-city" data-link-handled="1" onclick="Main.goToUrl(this);return false;"' + ' id="bot-target-city-' + cityId + '"' + ' href="' + Bot.profilePath + 'city/view/' + cityId + '">' + cityId + '</a><br>'; }); box += '</div>'; $('body').append(box); $('html').ajaxSuccess(Bot.ajaxSuccess); Bot.attackNext(); }, ajaxSuccess: function (e, xhr, settings) { var ajaxUrl = null, ajaxCallback = null; $.each(Bot.ajaxCallbacks, function (url, callback) { var fullUrl = Bot.profilePath + url; if (settings.url.substr(0, fullUrl.length) == fullUrl) { ajaxUrl = url; ajaxCallback = callback; } }); if (ajaxCallback) { Bot.ajaxCallbacks[ajaxUrl] = null; setTimeout(ajaxCallback, Bot.getClickDelay()); } else { console.log('Not recognized ' + settings.url); } }, waitForAjax: function (pageUrl, gotoPage, success) { Bot.ajaxCallbacks[pageUrl] = success; gotoPage(); }, waitForAction: function (action, success) { action(); setTimeout(success, Bot.getClickDelay()); }, getAttackInterval: function () { return parseInt(Bot.attackInterval + Math.random() * Bot.attackIntervalRandom); }, getClickDelay: function () { return parseInt(Bot.clickDelay + Math.random() * Bot.clickDelayRandom); }, attackNext: function () { if (Bot.targetCities.length == 0) return; if (!Bot.targetCities[Bot.currentTargetCity]) Bot.currentTargetCity = 0; var targetCity = Bot.targetCities[Bot.currentTargetCity++], targetCityUrl = 'city/view/' + targetCity, attackCityUrl = 'attack/' + targetCity; $('a.bot-target-city').css({ fontWeight: 'normal' }); $('a#bot-target-city-' + targetCity).css({ fontWeight: 'bold' }); Bot.waitForAjax('pm/inbox', function () { Main.goToUrl(Bot.profilePath + 'pm/inbox'); }, function () { Bot.waitForAjax('pm/logs', function () { Main.goToUrl(Bot.profilePath + 'pm/logs'); }, function () { Bot.waitForAjax(targetCityUrl, function () { Main.goToUrl(Bot.profilePath + targetCityUrl); }, function () { Bot.waitForAjax(attackCityUrl, function () { $('button[onclick^="Attack.showAttackAlert"]').click(); }, function () { Bot.waitForAction(function () { var count = 0; $.each(Bot.attackUnits, function (unitType, unitNum) { var ctl = $('input[name="units[' + unitType + ']"]'); ctl.val(Math.min(ctl.attr('max'), unitNum)).change(); count++; }); if (count == 0) { $('span[onclick^="Attack.ChooseEveryone"]').click(); } }, function () { $('button[type=submit]').click(); setTimeout(Bot.attackNext, Bot.getAttackInterval()); }) }); }); }); }); } }; Bot.run();
Инструкция по применению:
- В коде массив targetCities заполнить идентификаторами атакуемых городов. При просмотре страницы города идентификатор — это число в самом конце.
- В коде объект attackUnits заполнить парами тип юнита — количество юнитов. Идентификаторы типов любезно предоставлены разработчиками (101 из примера — кошачий «боец»).
- Задержки настроить в соответствии со своими возможностями: attackInterval — интервал между атаками, clickDelay — задержка при кликах, значения Random — диапазон случайно добавляемой задержки.
- Зайти в игру.
- Запустить код из отладочной консоли.
- Если всё верно, то слева появится список идентификаторов атакуемых городов, а скрипт приступит к атаке на первый город. Текущий город выделяется жирным.
Вообще, меня забавляет наглость хозяев игры. «Абонентская плата» в виде Озверина (без которого играть ещё более уныло) — это 180 руб/мес (WoW, в котором есть всё, чего нет в этой игре, стоит всего лишь в 2 раза дороже). Ресурсов можно покупать на до 120 руб/день (день!), увеличивая скорость добычи ресурсов в 8 раз (соревнование кошельков, неплатящие вообще идут лесом). Просторы для
И ведь люди платят. Я фигею.
