SHARE
TWEET

Untitled

a guest Feb 16th, 2020 104 Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. // ==UserScript==
  2. // @name         TORN: Stat Estimate
  3. // @namespace    dekleinekobini.statestimate
  4. // @version      2.7.0
  5. // @author       DeKleineKobini
  6. // @description  Estimate the stats of a player based on their rank.
  7. // @match        https://www.torn.com/index.php*
  8. // @match        https://www.torn.com/profiles.php*
  9. // @match        https://www.torn.com/userlist.php*
  10. // @match        https://www.torn.com/halloffame.php*
  11. // @match        https://www.torn.com/bounties.php*
  12. // @match        https://www.torn.com/blacklist.php*
  13. // @match        https://www.torn.com/factions.php*
  14. // @match        https://www.torn.com/competition.php*
  15. // @require      https://greasyfork.org/scripts/390917-dkk-torn-utilities/code/DKK%20Torn%20Utilities.js?version=744690
  16. // @grant        GM_xmlhttpRequest
  17. // @grant        GM_setValue
  18. // @grant        GM_getValue
  19. // ==/UserScript==
  20.  
  21. var settings = {
  22.     apiDelay: 1000, // in milliseconds, default 1000ms
  23.     pages: {
  24.         profile: true,
  25.         search: true,
  26.         searchAdvanced: true,
  27.         abroad: true,
  28.         hallOfFame: true,
  29.         bounties: true,
  30.         blacklist: true,
  31.         faction: {
  32.             wall: true,
  33.             profile: true
  34.         },
  35.         competition: true
  36.     },
  37.     ignore: {
  38.         enabled: true,
  39.         level: 101, // only show stats below this level
  40.         showEmpty: true
  41.     },
  42.     cache: { // in milliseconds, -1 is infinite
  43.         normal: 12 * 60 * 60 * 1000, // default 12h
  44.         last: 31 * 24 * 60 * 60 * 1000 // default 31d
  45.     }
  46. }
  47.  
  48. setDebug(true);
  49.  
  50. /* --------------------
  51. CODE - EDIT ON OWN RISK
  52. -------------------- */
  53. initScript("statestimate", "Stat Estimate", "SE", true);
  54.  
  55. var ranks = {
  56.     'Absolute beginner': 1,
  57.     'Beginner': 2,
  58.     'Inexperienced': 3,
  59.     'Rookie': 4,
  60.     'Novice': 5,
  61.     'Below average': 6,
  62.     'Average': 7,
  63.     'Reasonable': 8,
  64.     'Above average': 9,
  65.     'Competent': 10,
  66.     'Highly competent': 11,
  67.     'Veteran': 12,
  68.     'Distinguished': 13,
  69.     'Highly distinguished': 14,
  70.     'Professional': 15,
  71.     'Star': 16,
  72.     'Master': 17,
  73.     'Outstanding': 18,
  74.     'Celebrity': 19,
  75.     'Supreme': 20,
  76.     'Idolised': 21,
  77.     'Champion': 22,
  78.     'Heroic': 23,
  79.     'Legendary': 24,
  80.     'Elite': 25,
  81.     'Invincible': 26
  82. }
  83.  
  84. const EMPTY_CHAR = " ";
  85. const triggerLevel = [ 2, 6, 11, 26, 31, 50, 71, 100 ];
  86. const triggerCrime = [ 100, 5000, 10000, 20000, 30000, 50000 ];
  87. const triggerNetworth = [ 5000000, 50000000, 500000000, 5000000000, 50000000000 ];
  88.  
  89. const estimatedStats = [
  90.     "under 2k",
  91.     "2k - 25k",
  92.     "20k - 250k",
  93.     "200k - 2.5m",
  94.     "2m - 25m",
  95.     "20m - 250m",
  96.     "over 200m",
  97. ];
  98. const PREFIX_CACHE = "id_";
  99.  
  100. var cache = {};
  101.  
  102. getCachedEstimated("statestimate", true).then(c => {
  103.     cache = c || {};
  104.  
  105.     start();
  106. });
  107.  
  108. async function getCachedEstimated(key, subbed) {
  109.     let _obj = await GM_getValue(key, subbed ? "{}" : "{\"end\":0}");
  110.     let obj = JSON.parse(_obj);
  111.  
  112.     return obj;
  113. }
  114.  
  115. function start() {
  116.     var page = getCurrentPage();
  117.  
  118.     debug("Current page is '" + page + "' with step '" + new URL(window.location.href).searchParams.get("step") + "'");
  119.     if (!shouldLoad(page)) return;
  120.  
  121.     log("Starting listeners.");
  122.     startListeners(page);
  123. }
  124.  
  125. function startListeners(page){
  126.     var run = true;
  127.     var ignore = 0;
  128.  
  129.     switch(page) {
  130.         case "profiles": // profile
  131.             xhrIntercept((page, json, uri) => {
  132.                 if(page != "profiles" || !json) return;
  133.  
  134.                 if (run) {
  135.                     run = false;
  136.                     updateUser(json.user.userID, json.userInformation.level, function(result) {
  137.                         if (result === EMPTY_CHAR) return;
  138.  
  139.                         $(".content-title > h4").append("<div>" + result + "</div>");
  140.                     }, 0);
  141.                 }
  142.             });
  143.             break;
  144.         case "userlist": // (advanced) search
  145.             ajax((page, json, uri) => {
  146.                 if(page != "userlist" || !json) return;
  147.  
  148.                 let first = json.list[0].userID;
  149.                 let firstPage = $(".user-info-list-wrap > li:eq(0) .user > img").attr("alt");
  150.                 if (firstPage) firstPage = firstPage.substring(firstPage.indexOf("[") + 1, firstPage.length - 1);
  151.  
  152.                 if (firstPage != first) observeMutations(document, ".user-info-list-wrap > li:eq(0) .user > img[alt]", false, (mut, obs) => {
  153.                     firstPage = $(".user-info-list-wrap > li:eq(0) .user > img").attr("alt");
  154.                     firstPage = firstPage.substring(firstPage.indexOf("[") + 1, firstPage.length - 1);
  155.                     if (firstPage != first) return;
  156.  
  157.                     obs.disconnect();
  158.                     execute(json);
  159.                 }, { childList: true, subtree: true });
  160.                 else execute(json);
  161.  
  162.                 function execute() {
  163.                     if (!$(".se-header").length) $("<div class='se-header level t-hide left' style='border-left: 0px solid transparent; width: 75px;'>Stats</div>").insertAfter($(".level").eq(0));
  164.                     $(".user-icons").eq(0).css("width", "initial");
  165.                     json.list.forEach((player, index) => {
  166.                         let delay = settings.apiDelay * (index - ignore);
  167.  
  168.                         if (shouldIgnore(player.level * 1)){
  169.                             ignore++;
  170.  
  171.                             if (!settings.ignore.showEmpty) return;
  172.                             else delay = 0;
  173.                         }
  174.                         if (getSubCache(cache, PREFIX_CACHE + player.userID)) {
  175.                             ignore++;
  176.                             delay = 0;
  177.                         }
  178.  
  179.                         updateUser(player.userID, player.level * 1, (result) => {
  180.                             if (result === EMPTY_CHAR) return;
  181.                             var row = $(".user-info-list-wrap > li").eq(index);
  182.                             var iconWrap = row.find(".icons-wrap");
  183.  
  184.                             $("<span class='level' style='border-left: 0px solid transparent; width: 75px;'>" + result + "</p>").insertAfter(row.find(".level"));
  185.                             row.find(".user-icons").css("width", "initial");
  186.                             iconWrap.css("width", "initial");
  187.                             iconWrap.find("ul").css("width", "initial");
  188.                         }, delay);
  189.                     });
  190.                 }
  191.             });
  192.             break;
  193.         case "halloffame": // hall of fame
  194.             observeMutations(document, ".hall-of-fame-list-wrap", true, function(mut, obs){
  195.                 observeMutations($(".hall-of-fame-list-wrap")[0], ".players-list", false, function(mut, obs){
  196.                     ignore = 0;
  197.  
  198.                     var indexLevel;
  199.                     $(".table-titles > li").each(function(index) {
  200.                         if ($(this).html().includes("Level")) {
  201.                             indexLevel = index + 1;
  202.                         }
  203.                     });
  204.  
  205.                     $(".rank").eq(1).html("Stats");
  206.                     $(".players-list > li").each(function(index) {
  207.                         var row = $(this);
  208.                         let id = row.find(".player > .name").attr("href");
  209.                         if (!id) {
  210.                             ignore++;
  211.                             return;
  212.                         }
  213.                         id = id.substring(id.indexOf("=") + 1);
  214.                         let level = stripHtml(row.find(".player-info > li").eq(indexLevel).html());
  215.                         debug(level);
  216.                         level = level.substring(level.indexOf(":") + 1) * 1;
  217.  
  218.                         let delay = settings.apiDelay * (index - ignore);
  219.  
  220.                         if (shouldIgnore(level)){
  221.                             ignore++;
  222.  
  223.                             if (!settings.ignore.showEmpty) return;
  224.                             else delay = 0;
  225.                         }
  226.                         if (getSubCache(cache, PREFIX_CACHE + id)) {
  227.                             ignore++;
  228.                             delay = 0;
  229.                         }
  230.  
  231.                         updateUser(id, level, (result) => {
  232.                             row.find(".rank").html(result);
  233.                             let iconWrap = row.find(".icons-wrap");
  234.  
  235.                             row.find(".user-icons").css("width", "initial");
  236.                             iconWrap.css("width", "initial");
  237.                             iconWrap.find("ul").css("width", "initial");
  238.                         }, delay);
  239.                     });
  240.                 });
  241.             }, { childList: true, subtree: true });
  242.             break;
  243.         case "index": // travelling TODO - test
  244.             observeMutations(document, ".users-list", true, function(mut, obs){
  245.                 ignore = 0;
  246.  
  247.                 $(".users-list > li").each(function(index) {
  248.                     var row = $(this);
  249.                     let id = row.find(".name").attr("href");
  250.                     id = id.substring(id.indexOf("=") + 1);
  251.                     let level = stripHtml(row.find(".level").html());
  252.                     level = level.substring(level.indexOf(":") + 1) * 1;
  253.  
  254.                     let delay = settings.apiDelay * (index - ignore);
  255.  
  256.                     if (shouldIgnore(level)){
  257.                         ignore++;
  258.  
  259.                         if (!settings.ignore.showEmpty) return;
  260.                         else delay = 0;
  261.                     }
  262.                     if (getSubCache(cache, PREFIX_CACHE + id)) {
  263.                         ignore++;
  264.                         delay = 0;
  265.                     }
  266.  
  267.                     updateUser(id, level, (result) => {
  268.                         row.find(".rank").html(result);
  269.                         let iconWrap = row.find(".icons-wrap");
  270.  
  271.                         $("<span class='level' style='width: 75px;'>" + result + "</p>").insertAfter(row.find(".status"));
  272.                         row.find(".center-side-bottom").css("width", "initial");
  273.                         iconWrap.css("width", "initial");
  274.                         iconWrap.find("ul").css("width", "initial");
  275.                     }, delay);
  276.                 });
  277.             }, { childList: true, subtree: true });
  278.             break;
  279.         case "bounties": // bounties
  280.             ajax((page, json, uri) => {
  281.                 if (page != "bounties" || !uri) return;
  282.  
  283.                 observeMutations(document, ".bounties-list", true, (mut, obs) => {
  284.                     let names = [];
  285.                     ignore = 0;
  286.  
  287.                     $(".bounties-list-title .listed").html("STATS");
  288.                     $(".bounties-list > li[data-id]").slice(0, 20).each((index, el) => {
  289.                         let row = $(el);
  290.  
  291.                         let id = row.find(".target > a").attr("href");
  292.                         id = id.substring(id.indexOf("XID=") + 4);
  293.                         let level = row.find(".level").html().split("\n")[2];
  294.                         let name = row.find(".target > a").html();
  295.  
  296.                         let delay = settings.apiDelay * (index - ignore);
  297.                         if (names.includes(name)) {
  298.                             ignore++;
  299.                             return;
  300.                         } else if (shouldIgnore(level)){
  301.                             ignore++;
  302.  
  303.                             if (!settings.ignore.showEmpty) return;
  304.                             else delay = 0;
  305.                         }
  306.                         if (getSubCache(cache, PREFIX_CACHE + id)) {
  307.                             ignore++;
  308.                             delay = 0;
  309.                         }
  310.  
  311.                         names.push(name);
  312.                         updateUser(id, level, (result) => {
  313.                             $("li:has(a[href='profiles.php?XID=" + id + "'])").each(function(){
  314.                                 $(this).find(".listed").html(result);
  315.                             });
  316.                         }, delay);
  317.  
  318.                     });
  319.                 }, { childList: true, subtree: true, attributes: true });
  320.             });
  321.             break;
  322.         case "blacklist": // enemies
  323.             var checkBlacklist = () => {
  324.                 observeMutations($(".blacklist")[0], ".user-info-blacklist-wrap", true, function(mut, obs) {
  325.                     ignore = 0;
  326.  
  327.                     $(".user-info-blacklist-wrap > li").each(function(index) {
  328.                         var row = $(this);
  329.                         let id = row.find(".name").attr("href");
  330.                         id = id.substring(id.indexOf("XID=") + 4);
  331.                         let level = stripHtml(row.find(".level").html());
  332.                         level = level.substring(level.indexOf(":") + 1) * 1;
  333.  
  334.                         let delay = settings.apiDelay * (index - ignore);
  335.  
  336.                         if (shouldIgnore(level)){
  337.                             ignore++;
  338.  
  339.                             if (!settings.ignore.showEmpty) return;
  340.                             else delay = 0;
  341.                         }
  342.                         if (getSubCache(cache, PREFIX_CACHE + id)) {
  343.                             ignore++;
  344.                             delay = 0;
  345.                         }
  346.  
  347.                         updateUser(id, level, (result) => {
  348.                             var iconWrap = row.find(".description-editor");
  349.  
  350.                             var ele = $("<span class='level right' style='width: 75px;'>" + result + "</p>");
  351.                             ele.insertAfter(row.find(".edit"));
  352.                             ele.css("float", "right");
  353.                             row.find(".description .text").css("width", "initial");
  354.                         }, delay);
  355.                     });
  356.                 });
  357.             }
  358.  
  359.             checkBlacklist();
  360.             ajax((page, json, uri) => {
  361.                 if(page != "userlist" || !uri) return;
  362.  
  363.                 checkBlacklist();
  364.             });
  365.             break;
  366.         case "factions": // territory walls
  367.             if (settings.pages.faction.wall && hasSearchTag("step", "your")) {
  368.                 runOnEvent(() => {
  369.                     let hashSplit = window.location.hash.split("/");
  370.                     if (hashSplit[1] != "war" || isNaN(hashSplit[2])) return;
  371.  
  372.                     observeMutations(document, ".members-list", true, function(mut, obs) {
  373.                         $(".user-icons").eq(0).html("Stats");
  374.  
  375.                         updateWall();
  376.                         observeMutations($(".members-list")[0], ".members-list > .enemy", false, function(mut, obs) {
  377.                             debug("Member list got updated.")
  378.                             updateWall();
  379.                         });
  380.                     }, { childList: true, subtree: true });
  381.                 }, "hashchange", true);
  382.             } else if (settings.pages.faction.profile && hasSearchTag("step", "profile")) {
  383.                 $(".member-list > li").each((index, element) => {
  384.                     var row = $(element);
  385.  
  386.                     let id = row.find(".member .name").attr("href");
  387.                     id = id.substring(id.indexOf("=") + 1);
  388.                     let level = row.find(".lvl").html().split(" ")[2] * 1;
  389.  
  390.                     let delay = settings.apiDelay * (index - ignore);
  391.  
  392.                     if (shouldIgnore(level)){
  393.                         ignore++;
  394.  
  395.                         if (!settings.ignore.showEmpty) return;
  396.                         else delay = 0;
  397.                     }
  398.                     if (getSubCache(cache, PREFIX_CACHE + id)) {
  399.                         ignore++;
  400.                         delay = 0;
  401.                     }
  402.  
  403.                     updateUser(id, level, (result) => row.find(".days").html(result), delay);
  404.                 });
  405.             }
  406.             break;
  407.         case "competition": // competitions (elimination)
  408.             var linked = false;
  409.             runOnEvent(() => {
  410.                 if (linked) return;
  411.  
  412.                 if (hasSpecialTag("p", "team")) { // Elimination 2019
  413.                     log("Starting to check for elimination!");
  414.                     xhrIntercept((page, json, uri) => {
  415.                         if(page != "competition" || !uri || !hasSpecialTag("p", "team")) return;
  416.  
  417.                         observeMutations($("#competition-wrap")[0], ".competition-list", true, (mut, obs) => {
  418.                             ignore = 0;
  419.                             debug($(".competition-list").find(".name a").first().attr("data-placeholder"));
  420.                             $(".competition-list > li > ul").each(function(index) {
  421.                                 var row = $(this);
  422.                                 let id = row.find(".user").attr("href");
  423.                                 if (!id) {
  424.                                     ignore++;
  425.                                     return;
  426.                                 }
  427.                                 id = id.substring(id.indexOf("=") + 1);
  428.                                 let level = stripHtml(row.find(".level").html()) * 1;
  429.                                 debug(id + " / " + level)
  430.  
  431.                                 let delay = settings.apiDelay * (index - ignore);
  432.  
  433.                                 if (shouldIgnore(level)){
  434.                                     ignore++;
  435.  
  436.                                     if (!settings.ignore.showEmpty) return;
  437.                                     else delay = 0;
  438.                                 }
  439.                                 if (getSubCache(cache, PREFIX_CACHE + id)) {
  440.                                     ignore++;
  441.                                     delay = 0;
  442.                                 }
  443.  
  444.                                 updateUser(id, level, (result) => row.find(".icons").html(result), delay);
  445.                             });
  446.  
  447.                         });
  448.                     });
  449.                     linked = true;
  450.                 }
  451.             }, "hashchange", true);
  452.             break;
  453.     }
  454. }
  455.  
  456. function loadWall() {
  457.     var hashSplit = window.location.hash.split("/");
  458.     if (hashSplit[1] != "war" || isNaN(hashSplit[2])) return;
  459.  
  460.     observeMutations(document, ".members-list", true, function(mut, obs) {
  461.         $(".user-icons").eq(0).html("Stats");
  462.  
  463.         updateWall();
  464.         observeMutations($(".members-list")[0], ".members-list > .enemy", false, function(mut, obs) {
  465.             debug("Member list got updated.")
  466.             updateWall();
  467.         });
  468.     }, { childList: true, subtree: true });
  469. }
  470.  
  471. function updateWall() {
  472.     let ignore = 0;
  473.     $(".members-list > .enemy:not(:has(.estimate))").each(function(index) {
  474.         var row = $(this);
  475.         let id = row.find(".name").attr("href");
  476.         id = id.substring(id.indexOf("XID=") + 4);
  477.         let level = row.find(".level").html();
  478.         debug(`Found ${id} at level ${level}`);
  479.  
  480.         row.find(".member").addClass("estimate");
  481.  
  482.         let delay = settings.apiDelay * (index - ignore);
  483.  
  484.         if (shouldIgnore(level)){
  485.             ignore++;
  486.  
  487.             if (!settings.ignore.showEmpty) return;
  488.             else delay = 0;
  489.         }
  490.  
  491.         updateUser(id, level, function(result) {
  492.             if (result === EMPTY_CHAR) return;
  493.  
  494.             var iconWrap = row.find(".user-icons");
  495.  
  496.             $("<span class='level left' style='border-left: 0px solid transparent; width: 70px;'>" + result + "</p>").insertAfter(row.find(".level"));
  497.             iconWrap.css("width", "175px");
  498.             iconWrap.find("ul").css("width", "initial");
  499.         }, delay);
  500.     });
  501. }
  502.  
  503. function shouldLoad(page) {
  504.     if (settings.pages.profile && page == "profiles") return true;
  505.     if (settings.pages.search && page == "userlist" && hasSearchTag("step", "search")) return true;
  506.     if (settings.pages.searchAdvanced && page == "userlist" && hasSearchTag("step", "adv")) return true;
  507.     if (settings.pages.abroad && page == "index" && hasSearchTag("page", "people")) return true;
  508.     if (settings.pages.hallOfFame && page == "halloffame") return true;
  509.     if (settings.pages.bounties && page == "bounties") return true;
  510.     if (settings.pages.blacklist && page == "blacklist") return true;
  511.     if (page == "factions")  {
  512.         if (settings.pages.faction.wall && hasSearchTag("step", "your")) return true;
  513.         if (settings.pages.faction.profile && hasSearchTag("step", "profile")) return true;
  514.     }
  515.     if (settings.pages.competition && page == "competition") return true;
  516.  
  517.     return false;
  518. }
  519.  
  520. function shouldIgnore(level) {
  521.     return settings.ignore.enabled && (settings.ignore.level <= level);
  522. }
  523.  
  524. function getCurrentPage() {
  525.     var path = window.location.pathname;
  526.     return path.substring(1, path.length - 4);
  527. }
  528.  
  529. function hasSearchTag(tag, value) {
  530.     var params = new URL(window.location.href).searchParams;
  531.  
  532.     return !value ? params.has(tag) : params.get(tag) == value;
  533. }
  534.  
  535. function hasSpecialTag(tag, value) {
  536.     var params = new URLSearchParams(getSpecialSearch());
  537.  
  538.     return !value ? params.has(tag) : params.get(tag) == value;
  539. }
  540.  
  541. function updateUser(id, lvl, callback, delay) {
  542.     if (shouldIgnore(lvl)){
  543.         debug(`Ignoring ${id} with level ${lvl}.`)
  544.         if (settings.ignore.showEmpty) callback(EMPTY_CHAR);
  545.         return;
  546.     }
  547.  
  548.     debug(`Estimating for ${id} with level ${lvl}.`)
  549.  
  550.     let cached = getCachedStats(id);
  551.     if (cached) {
  552.         log(`Cached stats for ${id}! ${cached}`);
  553.  
  554.         callback(cached);
  555.         return;
  556.     } else {
  557.         debug(`NONE Cached stats for ${id}!`, cached);
  558.     }
  559.  
  560.     setTimeout(function(){
  561.         sendAPIRequest("user", id, "profile,personalstats,crimes").then(function(oData) {
  562.             debug("response", oData);
  563.             if (!oData){
  564.                 debug("oData is not found")
  565.                 return callback("ERROR API (UNKNOWN)");
  566.             }
  567.             if (!oData.rank) {
  568.                 debug("api returned error")
  569.                 return callback("ERROR API (" + oData.error.code + ")");
  570.             }
  571.             debug("Loaded information from the api!");
  572.  
  573.             var rankSpl = oData.rank.split(" ");
  574.             var rankStr = rankSpl[0];
  575.             if (rankSpl[1][0] === rankSpl[1][0].toLowerCase()) rankStr += " " + rankSpl[1];
  576.  
  577.             var level = oData.level;
  578.             var rank = ranks[rankStr];
  579.             var crimes = oData.criminalrecord ? oData.criminalrecord.total : 0;
  580.             var networth = oData.personalstats ? oData.personalstats.networth : 0;
  581.  
  582.             var trLevel = 0, trCrime = 0, trNetworth = 0;
  583.             for (let l in triggerLevel) {
  584.                 if (triggerLevel[l] <= level) trLevel++;
  585.             }
  586.             for (let c in triggerCrime) {
  587.                 if (triggerCrime[c] <= crimes) trCrime++;
  588.             }
  589.             for (let nw in triggerNetworth) {
  590.                 if (triggerNetworth[nw] <= networth) trNetworth++;
  591.             }
  592.  
  593.             var statLevel = rank - trLevel - trCrime - trNetworth - 1;
  594.  
  595.             var estimated = estimatedStats[statLevel];
  596.             if (!estimated) estimated = "N/A";
  597.  
  598.             debug(`Estimated stats for ${id} with level ${lvl}.`)
  599.  
  600.             cache[PREFIX_CACHE + id] = {};
  601.             cache[PREFIX_CACHE + id].value = estimated;
  602.             cache[PREFIX_CACHE + id].end = Date.now() + (estimated == estimatedStats[estimatedStats.length - 1] ? settings.cache.last : settings.cache.normal);
  603.  
  604.             setCache("statestimate", cache, -1, true);
  605.             callback(estimated);
  606.         });
  607.     }, delay);
  608. }
  609.  
  610. function getCachedStats(id) {
  611.     let cached = cache[PREFIX_CACHE + id];
  612.     if (cached) {
  613.         debug(`Cached stats for ${id}?`);
  614.  
  615.         let end = cached.end;
  616.         if (end == -1 || end >= Date.now())
  617.             return cached.value;
  618.         else
  619.             cache[PREFIX_CACHE + id] = undefined;
  620.     }
  621.     return undefined;
  622. }
RAW Paste Data
We use cookies for various purposes including analytics. By continuing to use Pastebin, you agree to our use of cookies as described in the Cookies Policy. OK, I Understand
Top