您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Модифицированный макрос хуякрос. Теперь он умеет полноценно фармить.
// ==UserScript== // @name [AoR] Фарм-Макрос-хуякрос // @namespace tuxuuman:aor.free.macros.farm // @version 0.1.0 // @description Модифицированный макрос хуякрос. Теперь он умеет полноценно фармить. // @author tuxuuman<[email protected]> // @match http://game.aor-game.ru/* // @grant GM_notification // @grant unsafeWindow // @grant GM_registerMenuCommand // ==/UserScript== (function () { 'use strict'; var $ = unsafeWindow.$; var mode = 0; // 0 - хуякрос, 1 - стандартный макрос var botData = {}; var timeItemsTimer; var botStarted = false; var macroData = null; var farmLocation = null; var drop = {}; var farmMacroStatus = -1; var gameMap = [ { "id": 119, "name": "Каменный молот" }, { "id": 15, "name": "Владения Байрон" }, { "id": 16, "name": "Логово жуков" }, { "id": 127, "name": "Подводная пещера Эгира" }, { "id": 39, "name": "Холм эльфов" }, { "id": 54, "name": "Башня огня" }, { "id": 62, "name": "Оплот гноллов" }, { "id": 63, "name": "Лес гремлинов" }, { "id": 67, "name": "Владения Фуриэ" }, { "id": 135, "name": "Подземелье Валлефора" }, { "id": 28, "name": "Долина ветра" }, { "id": 118, "name": "Лес пауков" }, { "id": 31, "name": "Темные пещеры" }, { "id": 121, "name": "Берег тритонов" }, { "id": 115, "name": "Река Раргон" }, { "id": 104, "name": "Лагерь орков" }, { "id": 30, "name": "Храм раздора" }, { "id": 74, "name": "Гробница короля" }, { "id": 20, "name": "Болото черного Дракона" }, { "id": 81, "name": "Внешний храм Метеоса" }, { "id": 29, "name": "Деревня кобольдов" }, { "id": 122, "name": "Подземелье нежити" }, { "id": 14, "name": "Храм апостолов" }, { "id": 66, "name": "Гнездовье гарпий" }, { "id": 8, "name": "Древний город Нарту" }, { "id": 60, "name": "Алтарь вурдалака" }, { "id": 105, "name": "Пещера древних пиратов" }, { "id": 80, "name": "Лабиринт Манога" }, { "id": 61, "name": "Водопад гигантов" }, { "id": 120, "name": "Озеро времени" }, { "id": 37, "name": "Аллея Джаратан" }, { "id": 116, "name": "Красная лощина" }, { "id": 9, "name": "Лестница древних" }, { "id": 1, "name": "Брошенная деревня" }, { "id": 131, "name": "Безымянная пещера" }, { "id": 53, "name": "Терраса злодея" }, { "id": 117, "name": "Роденский обрыв" }, { "id": 93, "name": "Руины полнолуния" }, { "id": 137, "name": "Деревня Эшборн" }, { "id": 46, "name": "Эльтер" }, { "id": 38, "name": "Возвышенность Эгира" } ]; var currentLocation = null; var characterHpMp = { hp: 100, max_hp: 100 }; var dropHistory = {}; var dropCfg = {}; var crazyFarmEnabled = false; unsafeWindow.getDrop = () => drop; function Chat(options) { unsafeWindow.Chat(options); console.log(options.chatType, options.message); } GM_registerMenuCommand("Упоротый фарм ВКЛ/ВЫКЛ", () => { if (crazyFarmEnabled) { crazyFarmEnabled = false; Chat({ chatType: "System", message: "Упоротый фарм ОТКЛЮЧЕН", color: "yellow" }); } else if (farmMacroStatus == -1) { crazyFarmEnabled = true; Chat({ chatType: "System", message: "Упоротый фарм ВКЛЮЧЕН", color: "yellow" }); } }); function getCharName() { return unsafeWindow.clientData.characters[unsafeWindow.clientData.charID]; } function notification (msg, title, ...args) { msg = `[${getCharName()}]` + msg; GM_notification(msg, title, ...args); } /** * Оборачивает оригинальную функцию из unsafeWindow * @param {string} fname Название функции * @param {(next: Function, ...args) => {}} fn Промежуточная функция */ function fwrapUnsafeWindow(fname, fn) { if (typeof (unsafeWindow[fname]) != "function") { throw new Error(`unsafeWindow[${fname}] is not function`); } let oldFn = unsafeWindow[`old_${fname}`] = unsafeWindow[fname]; unsafeWindow[fname] = function (...args) { if (typeof (fn) == "function") { fn((() => oldFn(...args)), ...args); } else { oldFn(...args); } } return unsafeWindow[fname]; } function initGameMap() { if (gameMap.initialized) return; // заполняем карту подлокациями for (let m of gameMap) { let location = unsafeWindow.Config.locations[m.id]; m.locations = [location.id]; let next = location.next; while (next && !m.locations.includes(next)) { m.locations.push(next); next = unsafeWindow.Config.locations[next].next; } let prev = location.prev; while (prev && !m.locations.includes(prev)) { m.locations.unshift(prev); prev = unsafeWindow.Config.locations[prev].prev; } } gameMap.initialized = true; } function SendPacket(e) { var f = true; if (f) { var g = { encode: function (s, k) { var c = ''; var d = ''; d = s.toString(); for (var i = 0; i < s.length; i++) { var a = s.charCodeAt(i); var b = a ^ k; c = c + String.fromCharCode(b); } return c; }, getNum: function () { if (!unsafeWindow['clientData'].numeric) { unsafeWindow['clientData'].numeric = 0; return 0; } else { return parseInt(unsafeWindow['clientData'].numeric); } } }; e.numericPacket = g.getNum(); e = g.encode(JSON.stringify(e), (199429672276830).toString()); client.send(e); unsafeWindow['clientData'].numeric++; if (unsafeWindow['clientData'].numeric > 255) { unsafeWindow['clientData'].numeric = 0; } } } // спарсить настройки макроса function parseMacroData() { let hp_items = []; // find hp items for (let i = 1; i <= 3; i++) { let item = $('#macrosItem' + i).children('.item'); if (item.length == 0) continue; let realid = item.data('realid'); let itemid = item.data('itemid'); let hp = $('#changeMacrosHp' + i).val(); if (item.data("from") != "skill") { hp_items.push({ type: 0, item_id: realid, id: itemid, hp }); } else { hp_items.push({ type: 1, id: realid, hp }); } } let time_items = []; //time_items $('.macrosSkillsPotionsItem:not(.macrosSkillsPotionsBlockedItem)').each((i, e) => { e = $(e); let item = e.find('.item'); if (item.length == 0) return; let realid = item.data('realid'); let itemid = item.data('itemid'); let time = parseInt(e.find('.macrosTimeWaiting').val()); if (item.data("from") != "skill") { time_items.push({ type: 0, item_id: realid, id: itemid, time }); } else { time_items.push({ type: 1, id: realid, time }); } }) let mob = $("#macrosMob").data('value'); return { hp_items, time_items, mob } } function getInventoryItem(propName, value) { for (let i in unsafeWindow.clientData.inventory) { let item = unsafeWindow.clientData.inventory[i]; if (item.hasOwnProperty(propName) && item[propName] == value) return item; } return null; } // получить локацию фарма function getFarmLocation() { if (macroData) { let locations = Object.values(unsafeWindow.Config.locations); for (let loc of locations) { if (loc.mobs && loc.mobs.find(mob => mob.id == macroData.mob)) { return loc; } } } else return 0; } // получить инфу о выбитом дропе function getDrop() { return Object.values(drop); } //очистить дроп function clearDrop() { drop = {}; } function addDrop(item) { let cfgItem = Config.items[item.item_id]; // не учитываем серебро и шмотки пол замком if (![409].includes(item.item_id) && !item.lock) { if (drop[item.unique_id] == undefined) { drop[item.unique_id] = Object.assign({}, item); } else { drop[item.unique_id].amount += item.amount; } } console.log("AddDrop", cfgItem.name, item.amount); if (dropHistory[item.item_id] == undefined) { dropHistory[item.item_id] = Object.assign({}, item); } else { dropHistory[item.item_id].amount += item.amount; } } // получить дроп на продажу function getSoldDrop() { return getDrop().filter((item) => dropCfg[item.item_id] == 2); } // получить дроп на хранение function getStorableDrop() { return getDrop().filter((item) => dropCfg[item.item_id] != 2); } // получить список покупаемых банок у алхимика function getShopItems() { let items = []; if (macroData) { for (let item of [].concat(macroData.hp_items, macroData.time_items)) { if (item.type == 0 && item.maxAmount) { // ищем такую банку в инвентаре let invItem = getInventoryItem("item_id", item.item_id); let count = item.maxAmount; if (invItem) { count -= invItem.amount; } if (count > 0) { items.push({ item_id: item.item_id, amount: count, type: "BuyItem", id: 17 // id npc }); } } } } // обязательно покупаем 1 зельку тп, елси про них забыли if (!items.find(item => item.item_id == 357) && !getInventoryItem("item_id", 357)) { items.push({ item_id: 357, amount: 1, type: "BuyItem", id: 17 }); } return items; } function getMacroData() { return { mob: macroData.mob, hp_items: macroData.hp_items.map((item) => { if (item.type == 0) { item.id = (getInventoryItem("item_id", item.item_id) || {}).unique_id } return item; }).sort((a, b) => { // сортируем хп итмесы по кол-ву хп в убывании // если это зелька тп то она должна быть первой if (a.item_id == 357 || b.item_id == 357) { return 1; } let aTest = /%/.test(String(a.hp)); let bTest = /%/.test(String(b.hp)); if (aTest && bTest) { let aHp = parseInt(a.hp.replace("%", "")); let bHp = parseInt(b.hp.replace("%", "")); if (aHp < bHp) return -1; else if (aHp > bHp) return 1; return 0; } else if (aTest) { return 1; } else if (bTest) { return -1; } else { if (a.hp < b.hp) return -1; else if (a.hp > b.hp) return 1; return 0; } }), time_items: macroData.time_items.map((item) => { if (item.type == 0) { return { id: (getInventoryItem("item_id", item.item_id) || {}).unique_id, item_id: item.item_id, type: 0, time: item.time } } else { return item; } }) } } function initConfigs() { if (unsafeWindow.Config.initialized) return; // добавляем недостающего шмота в конфигах unsafeWindow.Config.npcs[17].sale_items.push({ item_id: 7424, price: 150 }); unsafeWindow.Config.initialized = true; } function initGui() { $('.macrosSkillsPotionsItem').removeClass('macrosSkillsPotionsBlockedItem'); $('.macrosTimeWaiting').prop('disabled', false); var hyacrosBtn = $('<span>', { text: "Хуякрос", css: { cursor: "pointer" } }); var macrosBtn = $('<span>', { text: "Макрос", css: { "text-decoration": "line-through", cursor: "pointer" } }); hyacrosBtn.on("click", function () { if (botStarted) return; $(this).css("text-decoration", "none"); macrosBtn.css("text-decoration", "line-through"); mode = 0; }); macrosBtn.on("click", function () { if (botStarted) return; $(this).css("text-decoration", "none"); hyacrosBtn.css("text-decoration", "line-through"); mode = 1; }); $('.macros>.windowHeader').html("").append(macrosBtn, " ", hyacrosBtn); $("#startMacros").off('click'); $("#startMacros").on('click', function () { if ($(this).hasClass('disabled')) return; macroData = parseMacroData(); farmLocation = getFarmLocation(); for (let item of [].concat(macroData.hp_items, macroData.time_items)) { // если это предмет и он продатся у алхимика if (item.type == 0 && unsafeWindow.Config.npcs[17].sale_items.find(i => i.item_id == item.item_id)) { let invItem = getInventoryItem("item_id", item.item_id); if (invItem) { item.maxAmount = invItem.amount; } } } botStart(); }); $("#stopMacros").off("click"); $("#stopMacros").on('click', function () { if ($(this).hasClass('disabled')) return; botStop(); }); } fwrapUnsafeWindow("BlockDrop", function () { }); fwrapUnsafeWindow("DropList", function (next, data) { next(); $(".dropItem").attr("disabled", null); $(".dropItem").unbind('click'); let setBgColor = (el, item_id) => { switch (dropCfg[el.data("dropid")]) { case 0: el.css({ "background-color": "inherit" }); break; case 1: el.css({ "background-color": "red" }); break; case 2: el.css({ "background-color": "green" }); break; } } $(".dropItem").each((i, e) => setBgColor($(e))); $(".dropItem").on("click", function () { let el = $(this); let item_id = el.data("dropid"); let item_view = el.data('view'); if (dropCfg[item_id] == undefined) { dropCfg[item_id] = 0; } dropCfg[item_id] += 1; if (dropCfg[item_id] > 2) { dropCfg[item_id] = 0; } if (dropCfg[item_id] == 1) { SendPacket({ type: "BlockDrop", item_id, item_view, disabled: false }); } else if (dropCfg[item_id] == 2) { SendPacket({ type: "BlockDrop", item_id, item_view, disabled: true }); } setBgColor(el); }) }); fwrapUnsafeWindow("StartGame", function (next, startData) { next(); initConfigs(); initGameMap(); initGui(); client.off("message", messageHandler); currentLocation = unsafeWindow.Config.locations[startData.location]; console.log("Бот готов к использованию!"); }); function botStart() { if (botStarted) return; botData = getMacroData(); if (!Config.monsters[botData.mob]) { Chat({ message: "Не выбран монстр!", chatType: "System", color: "red" }); return; } botStarted = true; farmMacroStatus = -1; clearDrop(); currentLocation = farmLocation; $('.macrosSkillsPotionsItem').removeClass('macrosSkillsPotionsBlockedItem'); $('.macrosTimeWaiting').prop('disabled', false); $("#startMacros").addClass('disabled'); $("#stopMacros").removeClass('disabled'); if (mode == 0) { client.on("message", messageHandler); timeItemsTimer = setInterval(function () { // первым делом юзаем хп итемы let pcHp = Math.round(characterHpMp.hp / characterHpMp.max_hp * 100); // текущее hp в процентах for (let item of botData.hp_items) { // если это банка тп и мы уже в деревне то пропускаем её if (item.item_id == 357) { if (currentLocation && currentLocation.name == "Деревня Эшборн") { continue; } } let heal = false; if (/%/.test(item.hp)) { // процентное сравнение hp let pc = parseInt(item.hp.replace("%", "")) || 80; // при скольки % юзать предмет, по умолчанию 80 if (pcHp < pc) { heal = true; } } else if (characterHpMp.hp <= parseInt(item.hp)) { // числовое сравнение heal = true; } if (heal) { if (item.type == 0) useItem(item.id); else useSkill(item.id); break; } } // потом если с хп все норм юзаем тайм итемсы for (let item of botData.time_items) { if (item.lastUse < Date.now() || item.lastUse == undefined) { if (item.type == 1) { useSkill(item.id); } else { useItem(item.id); } item.lastUse = Date.now() + item.time * 1000; break; } } }, 1000); setTarget(); Chat({ message: "Хуякрос запущен!", chatType: "System", color: "green" }); } else { SendPacket({ type: "StartMacros", data: botData }); Chat({ message: "Макрос запущен!", chatType: "System", color: "green" }); } } let messages = [ "что это", "чо эт такое?", "какого хрена", "блэд, как я суда попал", "где это я", "опять? :/", "что за баг", "?????", "wtf", "4to eto", "мама я в телике :D здарова", "оооооо, привет! ))", "Дратути ^^, Чем могу помоч?", "Сеееергооо :))). Что делаем", "ого. сам админ :D. замутите ивент на пушки", "как отсюда уйти, помогите" ]; function rnd(max) { return Math.floor(Math.random() * max); } function getRandomMessage() { return messages[rnd(messages.length)]; } function useTp() { let tpitem = getInventoryItem("item_id", 357); if (tpitem) { useItem(tpitem); } else { Chat({ message: "Нет банок тп", chatType: "System", color: "green" }); } } // получить карту локации function getLocationMap(locationId) { return gameMap.find(m => m.locations.includes(locationId)) } function moveToLocation(currentLocId, targetLocId) { let currentLoc = unsafeWindow.Config.locations[currentLocId]; let targetLoc = unsafeWindow.Config.locations[targetLocId]; if (currentLoc == targetLoc) { console.log("Мы в нужной локации", targetLoc); return true; } // для пп перемещение немного другое if (targetLocId == 113 || targetLocId == 114) { if (currentLoc.name == "Деревня Эшборн") { if (currentLoc.id == 152) { SendPacket({ "type": "Dialog", "id": 11, "num": 0 }); setTimeout(() => { SendPacket({ "type": "NPC_EnterPremium2", "id": 11 }); }, 1000); } else { SendPacket({ type: "MoveLocation", id: currentLoc.next }); } } else if (currentLocId == 113) { SendPacket({ type: "MoveLocation", id: 114 }); } else { SendPacket({ type: "Map", id: 137 }); } return; } // перемещение для акры if ([3, 4, 5, 6].includes(targetLocId)) { if (currentLoc.name == "Деревня Эшборн") { if (currentLoc.id != 156) { SendPacket({ type: "MoveLocation", id: currentLoc.next }); } else { SendPacket({ type: "Dialog", id: 15, num: 0 }); setTimeout(() => { SendPacket({ type: "NPC_Teleport", to: 4, id: 15 }); }, 500); } return; } } let currentLocMap = getLocationMap(currentLoc.id); let targetLocMap = getLocationMap(targetLoc.id); // чекаем есть ли лока на карте if (currentLocMap == targetLocMap) { console.log("Мы находимся в нужной карте", targetLocMap); if (currentLoc.id < targetLoc.id) { console.log("Идем вперед", currentLoc.next); SendPacket({ type: "MoveLocation", id: currentLoc.next }); } else { console.log("Идем назад", currentLoc.prev); SendPacket({ type: "MoveLocation", id: currentLoc.prev }); } } else { console.log("Мы находимся в разных картах. Нужно тпхаться сюда:", targetLocMap); SendPacket({ type: "Map", id: targetLocMap.id }); } return false; } function printDropHistory() { let chatMsg = "<pre>\nНафармлено:\n"; for (let item of Object.values(dropHistory)) { let cfg = unsafeWindow.Config.items[item.item_id]; chatMsg += ` ${cfg.name} ${item.amount} шт.\n`; } chatMsg += "\n</pre>" Chat({ chatType: "System", message: chatMsg, color: "green" }) console.log(chatMsg); } function clearDropHistory() { dropHistory = {}; } unsafeWindow.printDropHistory = printDropHistory; function botStop() { if (!botStarted) return; printDropHistory(); clearDropHistory(); clearDrop(); botStarted = false; farmMacroStatus = -1; if (mode == 0) { clearInterval(timeItemsTimer); client.off("message", messageHandler); $("#startMacros").removeClass('disabled'); $("#stopMacros").addClass('disabled'); Chat({ message: "Хуякрос остановлен!", chatType: "System", color: "orange" }); } else { SendPacket({ type: "StopMacros" }); Chat({ message: "Макрос остановлен!", chatType: "System", color: "orange" }); } } function setTarget() { setTimeout(function () { SendPacket({ type: "SetTarget", is: "mob", id: botData.mob }); }, Math.round(Math.random() * 200 + 100)); } function messageHandler(e) { switch (e.type) { // отмена цели case "UnTarget": setTarget(); break; // изменение hp case "CharacterHpMp": characterHpMp = e; if ((characterHpMp.hp / characterHpMp.max_hp * 100) < 15 && currentLocation.name != "Деревня Эшборн") { Chat({ message: `Критически низкий ур здоровья: ${characterHpMp.hp}. Тп в город.`, chatType: "System", color: "orange" }); useTp(); } break; case "CharacterWeight": // если вес больше 70% то тп в город чтоб сложить хлам в кладовку if ((e.weight / e.max_weight * 100) > 70 && currentLocation.name != "Деревня Эшборн") { Chat({ message: `Персонаж загружен на ${Math.round((e.weight / e.max_weight * 100))}%`, chatType: "System", color: "orange" }); useTp(); } break; case "AddItemToInventory": if (farmMacroStatus == -1) { let item = unsafeWindow.decodeItem(e.item); addDrop(item); } else { console.log("AddDropError farmMacroStatus != -1"); } break; case "NewsInfo": if (e.newsType == "YOU_IN_TARGET") { if (!crazyFarmEnabled) { notification(`На вас напал ${e.attr[0]}\n Бот отключен!`, "[ФармХуякрос]"); botStop(); } else { notification(`На вас напал ${e.attr[0]}\n Но мне пох, продожаем фармить ^^,`, "[ФармХуякрос]"); farmMacroStatus = -2; } } break; case "InitLocation": currentLocation = unsafeWindow.Config.locations[e.location]; if (e.location == 158) { // знач мы в локи админов botStop(); notification("ВАС ПРОВЕРЯЕТ АДМИН", "СРОЧНО!!"); setTimeout(function () { SendPacket({ type: "Chat", chatType: "Simple", message: getRandomMessage() }); }, rnd(3000) + 1000); setTimeout(function () { let tpitem = getInventoryItem("item_id", 357); if (tpitem) useItem(tpitem.unique_id); }, 500); } else { if (!botStarted) return; if (currentLocation == undefined) { console.error("ЛОКАЦИЯ НЕ НАЙДЕНА", e) return; } if (currentLocation.name == "Деревня Эшборн" && farmMacroStatus != 2) { switch (farmMacroStatus) { case -2: let duraion = 1000 * 60 * 3; // 3 мин duraion += rnd(1000 * 60 * 2); // + рандомные пару мин console.log(`Ждем ${Math.round(duraion / 1000)} сек и снова фармить ^^,`); setTimeout(() => { if (farmMacroStatus != -2) return; farmMacroStatus = 0; SendPacket({ type: "MoveLocation", id: currentLocation.prev }); }, duraion) break; case -1: // ожидаем окончания боя (пару сек) потом идем дальше console.log("Ждем окончания боя"); setTimeout(() => { if (farmMacroStatus != -1) return; farmMacroStatus = 0; SendPacket({ type: "MoveLocation", id: currentLocation.prev }); }, 5000); break; case 0: // идем складировать шмот if (e.location == 153) { console.log("Складываем шмот!!", getStorableDrop()); for (let item of getStorableDrop()) { SendPacket({ type: "PutItem", id: 10, unique_id: item.unique_id, amount: item.amount }); } farmMacroStatus = 1; setTimeout(() => { SendPacket({ type: "MoveLocation", id: currentLocation.prev }); }, 3000); } else { console.log("Идем к кладовке.."); SendPacket({ type: "MoveLocation", id: currentLocation.prev }); } break; case 1: // продаем хлам и закупаемся хилками у алхимика if (e.location == 137) { console.log("Продаем хлам!", getSoldDrop()); for (let item of getSoldDrop()) { SendPacket({ type: "SellItem", id: 17, unique_id: item.unique_id, amount: item.amount }); } console.log("Закупаемся у алхимика", getShopItems()); for (let packet of getShopItems()) { SendPacket(packet); } farmMacroStatus = 2; setTimeout(() => { botData = getMacroData(); SendPacket({ type: "MoveLocation", id: currentLocation.prev }); }, 5000); } else { console.log("Идем к алхимику."); SendPacket({ type: "MoveLocation", id: currentLocation.prev }); } break; } } else if (farmMacroStatus == 2) { //идем на фарм if (moveToLocation(currentLocation.id, farmLocation.id)) { clearDrop(); farmMacroStatus = -1; botData = getMacroData(); console.log("Мы прибыли к месту фарма. Запускаем макрос!", farmLocation, botData); setTarget(); } else { console.log("Шагаем к месту фарма", farmLocation); } } } break; } } function useItem(data) { if (typeof (data) == "object") { SendPacket({ type: "UseItem", uniqueID: data.unique_id }); } else { if (getInventoryItem("unique_id", data)) { SendPacket({ type: "UseItem", uniqueID: data }); } else { Chat({ message: `Несуществующий предмет ${data}`, chatType: "System", color: "red" }); } } } function useSkill(skill_id) { let skillInfo = unsafeWindow.Config.skills[skill_id]; let items = []; if (skillInfo.params.need_item) { for (let need_item of skillInfo.params.need_item) { let item = getInventoryItem("item_id", need_item.id); items.push(item.unique_id); } } SendPacket({ type: "UseSkill", id: skill_id, target: "", items: items, friends: false }); } })();