diff --git a/d2bs/kolbot/data/deepstats/sample.json b/d2bs/kolbot/data/deepstats/sample.json new file mode 100644 index 000000000..417027f69 --- /dev/null +++ b/d2bs/kolbot/data/deepstats/sample.json @@ -0,0 +1,3 @@ +[ + "{\"item_id\":\"k6x1zs56$2604023116:522:3:1:3\",\"name\":\"The Stone of Jordan Ring\",\"fname\":\"Ring\\nThe Stone of Jordan\",\"description\":\"\\\\xffc4The Stone of Jordan (75)\\n\\\\xffc4Ring\\n\\\\xffc0Required Level: 29\\n\\\\xffc3+1 to All Skills\\n\\\\xffc3Adds 1-12 lightning damage\\n\\\\xffc3+20 to Mana\\n\\\\xffc3Increase Maximum Mana 25%\\n\\\\xffc0Area: Catacombs Level 4\\n\\\\xffc0Line: perfect.nip #268$\",\"action\":\"Kept\",\"ilvl\":75,\"lvlreq\":29,\"quality\":7,\"code\":\"rin4\",\"size\":[1,1],\"class_id\":522,\"type\":10,\"ethereal\":false,\"pickit_line\":\"perfect.nip #268\",\"act\":1,\"last_area\":37,\"difficulty\":2,\"player_count\":2,\"char_name\":\"DaBest\",\"char_level\":99,\"char_class\":1,\"char_mf\":416,\"ladder\":true,\"realm\":\"USWest\",\"hardcore\":false,\"expansion\":true,\"can_teleport\":true}" +] diff --git a/d2bs/kolbot/libs/OOG.js b/d2bs/kolbot/libs/OOG.js index e914af3c7..ce49c7545 100644 --- a/d2bs/kolbot/libs/OOG.js +++ b/d2bs/kolbot/libs/OOG.js @@ -383,6 +383,7 @@ var DataFile = { experience: 0, deaths: 0, lastArea: "", + lastAreaID: 0, gold: 0, level: 0, name: "", @@ -423,7 +424,7 @@ var DataFile = { print("Error reading DataFile. Using null values."); - return {runs: 0, experience: 0, lastArea: "", gold: 0, level: 0, name: "", gameName: "", ingameTick: 0, handle: 0, nextGame: ""}; + return {runs: 0, experience: 0, lastArea: "", lastAreaID: 0, gold: 0, level: 0, name: "", gameName: "", ingameTick: 0, handle: 0, nextGame: ""}; }, getStats: function () { @@ -463,6 +464,7 @@ var DataFile = { } obj.lastArea = Pather.getAreaName(me.area); + obj.lastAreaID = me.area; break; case "gold": @@ -499,6 +501,62 @@ var DataFile = { } }; +const DeepStats = { + create: function () { + let obj = [], + string; + + string = JSON.stringify(obj); + + Misc.fileAction("data/deepstats/" + me.profile + ".dump.json", 1, string); + + return obj; + }, + + getObj: function () { + let obj, string; + + if (!FileTools.exists("data/deepstats/" + me.profile + ".dump.json")) { + DeepStats.create(); + } + + string = Misc.fileAction("data/deepstats/" + me.profile + ".dump.json", 0); + + try { + obj = JSON.parse(string); + } catch (e) { + // If we failed, file might be corrupted, so create a new one + obj = this.create(); + } + + if (obj) { + return obj; + } + + print("Error reading DeepStats file. Using null values."); + + return []; + }, + + updateStats: function (value) { + while (me.ingame && !me.gameReady) { + delay(100); + } + + let currentStats, string; + + currentStats = this.getObj(); + + if (typeof value === "string") { + currentStats.push(value); + } + + string = JSON.stringify(currentStats); + + Misc.fileAction("data/deepstats/" + me.profile + ".dump.json", 1, string); + } +}; + var ControlAction = { mutedKey: false, diff --git a/d2bs/kolbot/libs/common/Config.js b/d2bs/kolbot/libs/common/Config.js index dbfe5ffb4..b7d90a3a6 100644 --- a/d2bs/kolbot/libs/common/Config.js +++ b/d2bs/kolbot/libs/common/Config.js @@ -195,6 +195,18 @@ var Config = { SkipException: [], ScanShrines: [], Debug: false, + DeepStats: { + StatsEnabled: false, + SOJReportsEnabled: false, + DCloneReportsEnabled: false, + FileLogOnly: false, + API: { + Token: "", // Auth token used to authenticate, if required, to your backend of choice + ReportItem: "", // Item endpoint at your backend of choice + ReportDClone: "", // DClone reporting endpoint at your backend of choice + ReportSOJsSold: "" // SOJs Sale reporting endpoint at your backend of choice + } + }, AutoMule: { Trigger: [], diff --git a/d2bs/kolbot/libs/common/Misc.js b/d2bs/kolbot/libs/common/Misc.js index fe72eac1e..a51b91f27 100644 --- a/d2bs/kolbot/libs/common/Misc.js +++ b/d2bs/kolbot/libs/common/Misc.js @@ -1201,6 +1201,15 @@ var Misc = { return false; }, + getEstimatedBuild: function () { + let counter, build = 0, builds = [49, 53, 56, 59, 64, 97, 106, 112, 147, 149, 151, 152]; + for (counter = 0; counter < builds.length; counter++) { + if (me.getSkill(builds[counter], 1) > build) { + build = builds[counter]; + } + } + }, + getItemDesc: function (unit) { var i, desc, index, stringColor = ""; @@ -1361,7 +1370,7 @@ var Misc = { return false; } - var i; + let i; if (!Config.LogKeys && ["pk1", "pk2", "pk3"].indexOf(unit.code) > -1) { return false; @@ -1397,7 +1406,8 @@ var Misc = { } } - var lastArea, code, desc, sock, itemObj, + let lastArea, code, desc, sock, itemObj, deepstatsData, + lastAreaID = 0, color = -1, name = unit.fname.split("\n").reverse().join(" ").replace(/ÿc[0-9!"+<:;.*]|\/|\\/g, "").trim(); @@ -1406,6 +1416,7 @@ var Misc = { if (action.match("kept", "i")) { lastArea = DataFile.getStats().lastArea; + lastAreaID = DataFile.getStats().lastAreaID; if (lastArea) { desc += ("\n\\xffc0Area: " + lastArea); @@ -1583,6 +1594,55 @@ var Misc = { sockets: this.getItemSockets(unit) }; + if (Config.DeepStats.StatsEnabled) { + deepstatsData = { + item_id: Date.now().toString(36) + "$" + unit.gid + ":" + unit.classid + ":" + unit.location + ":" + unit.x + ":" + unit.y + (unit.getFlag(0x400000) ? ":eth" : ""), + name: name, + fname: unit.fname, + description: desc, + action: action, + ilvl: unit.ilvl, + lvlreq: unit.lvlreq, + quality: unit.quality, + code: code, + class_id: unit.classid, + type: unit.itemType, + ethereal: !!unit.getFlag(0x400000), + last_area: lastAreaID, + difficulty: me.diff, + player_count: this.getPlayerCount(), + char_name: me.charname, + char_level: me.charlvl, + char_class: me.classid, + char_mf: me.getStat(80), + ladder: me.ladder > 0, + realm: me.realm, + hardcore: me.playertype, + expansion: me.gametype === 1, + can_teleport: me.getSkill(54, 1) > 0, + build: this.getEstimatedBuild(), + }; + + if (Config.DeepStats.FileLogOnly) { + DeepStats.updateStats(JSON.stringify(deepstatsData)); + } else if (!Config.DeepStats.API.Token) { + throw new Error("An auth token is required. Set Config.DeepStats.API.Token"); + } else { + const HTTP = require("../modules/HTTP"); + DeepStats.updateStats(JSON.stringify(deepstatsData)); + HTTP({ + url: Config.DeepStats.API.ReportItem, + method: "POST", + headers: { + "Authorization": "Token " + Config.DeepStats.API.Token, + "Content-Type": "application/json", + "Connection": "close" + }, + data: JSON.stringify(deepstatsData) + }); + } + } + D2Bot.printToItemLog(itemObj); return true; diff --git a/d2bs/kolbot/tools/ToolsThread.js b/d2bs/kolbot/tools/ToolsThread.js index 3eb682eb9..80c9b2009 100644 --- a/d2bs/kolbot/tools/ToolsThread.js +++ b/d2bs/kolbot/tools/ToolsThread.js @@ -48,6 +48,7 @@ function main() { CraftingSystem.buildLists(); Runewords.init(); Cubing.init(); + const HTTP = require("../libs/modules/HTTP"); for (i = 0; i < 5; i += 1) { timerLastDrink[i] = 0; @@ -473,6 +474,28 @@ function main() { if (Config.SoJWaitTime && me.gametype === 1) { // only do this in expansion D2Bot.printToConsole(param1 + " Stones of Jordan Sold to Merchants on IP " + me.gameserverip.split(".")[3], 7); + + if (Config.DeepStats.SOJReportsEnabled) { + if (!Config.DeepStats.API.Token) { + throw new Error("An auth token is required. Set Config.DeepStats.API.Token"); + } + let soj_data = { + ip_address: me.gameserverip.split(".")[3], + realm: me.realm, + ladder: me.ladder > 0, + current_count: param1, + }; + HTTP({ + url: Config.DeepStats.API.ReportSOJsSold, + method: "POST", + headers: { + "Authorization": "Token " + Config.DeepStats.API.Token, + "Content-Type": "application/json", + }, + data: JSON.stringify(soj_data) + }); + } + Messaging.sendToScript("default.dbj", "soj"); } @@ -498,6 +521,26 @@ function main() { me.maxgametime = 0; + if (Config.DeepStats.DCloneReportsEnabled) { + if (!Config.DeepStats.API.Token) { + throw new Error("An auth token is required. Set Config.DeepStats.API.Token"); + } + let dclone_data = { + ip_address: me.gameserverip.split(".")[3], + realm: me.realm, + ladder: me.ladder > 0, + }; + HTTP({ + url: Config.DeepStats.API.ReportDClone, + method: "POST", + headers: { + "Authorization": "Token " + Config.DeepStats.API.Token, + "Content-Type": "application/json", + }, + data: JSON.stringify(dclone_data) + }); + } + if (Config.KillDclone) { load("tools/clonekilla.js"); }