Advertisement
sparkychild

Pokémon Showdown Client side PM Logging script.

Nov 29th, 2016 (edited)
659
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. // ==UserScript==
  2. // @name       PM Logs
  3. // @namespace  http://use.i.E.your.homepage/
  4. // @version    1.2
  5. // @description  Pokemon Showdown! moderator's best friend
  6. // @include     http://play.pokemonshowdown.com/
  7. // @include     https://play.pokemonshowdown.com/
  8. // @include     http://play.pokemonshowdown.com/*
  9. // @include     https://play.pokemonshowdown.com/*
  10. // @include     http://*.psim.us/
  11. // @include     https://*.psim.us/
  12. // @include     http://*.psim.us/*
  13. // @include     https://*.psim.us/*
  14. // ==/UserScript==
  15.  
  16. ///////////////////////////////////////
  17. // PS PM - LOG SCRIPT by sparkychild //
  18. ///////////////////////////////////////
  19.  
  20. var maxPmLogs = 37500;
  21.  
  22. if (!localStorage.pmLogs) {
  23.     localStorage.pmLogs = "[]";
  24. }
  25.  
  26. function packLog(stamp, data) {
  27.     var date = new Date(parseInt(stamp)).toLocaleString();
  28.     return date + "|" + data.from + "|" + data.to + "|" + data.msg;
  29. }
  30.  
  31. // backwards compatability for pm logs
  32. if (localStorage.pmLogs && localStorage.pmLogs.charAt(0) === "{") {
  33.     var data = JSON.parse(localStorage.pmLogs);
  34.     var newData = {};
  35.     // convert to a new format
  36.     for (var user in data) {
  37.         for (var stamp in data[user]) {
  38.             newData[stamp] = packLog(stamp, data[user][stamp]);
  39.         }
  40.     }
  41.     var blob = Object.keys(newData).sort().map(d => newData[d]);
  42.     localStorage.setItem("pmLogs", JSON.stringify(blob));
  43. }
  44.  
  45. var pmLogs = JSON.parse(localStorage.pmLogs);
  46. if (maxPmLogs && pmLogs.length > maxPmLogs) pmLogs = pmLogs.slice(-maxPmLogs);
  47.  
  48. function toId(s) {
  49.     return "" + (s && typeof s === "string" ? s : "").toLowerCase().replace(/[^a-z0-9]+/g, "");
  50. }
  51.  
  52. function escapeHTML(str) {
  53.     return ('' + str).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/'/g, '&apos;').replace(/\//g, '&#x2f;');
  54. }
  55.  
  56.  
  57. class CmdLine {
  58.     constructor(cmd) {
  59.         this.matches = [];
  60.  
  61.         let matches = cmd.match(/\-[^\s]+(?:\s(?!\-)(?:(\"|\')(?:(?!\1).)+\1|[^\s]+))?/g);
  62.         if (matches) {
  63.             let cmds = [];
  64.             for (let i = 0; i < matches.length; i++) {
  65.                 let [command, ...values] = matches[i].trim().split(" ");
  66.                 cmds.push({
  67.                     cmd: command.slice(1),
  68.                     value: values && values.length ? values.join(" ").replace(/(^["']|['"]$)/g, "") : "",
  69.                 });
  70.             }
  71.             this.matches = cmds;
  72.         }
  73.     }
  74.  
  75.     // find all matches with the command
  76.     find(nonEmpty, ...types) {
  77.         if (typeof nonEmpty !== 'boolean') {
  78.             types = [nonEmpty, ...types];
  79.             nonEmpty = false;
  80.         }
  81.         let results = this.matches
  82.         .filter(t => types.includes(t.cmd) && (!nonEmpty || t.value))
  83.         .map(t => t.value);
  84.         return results;
  85.     }
  86.  
  87.     // require the command to have a value
  88.     findValues(...types) {
  89.         return this.find(true, ...types);
  90.     }
  91. }
  92.  
  93. function verifyRegex(regex) {
  94.     try {
  95.         new RegExp(regex, 'i');
  96.         return regex;
  97.     } catch (e) {
  98.         return escapeRegex(regex);
  99.     }
  100. }
  101.  
  102. function escapeRegex(str) {
  103.     return (str && typeof str === 'string' ? str : '').replace(/[\\.+*?!=()|[\]{}^$#<>]/g, '\\$&');
  104. }
  105.  
  106. function toUserId(text) {
  107.     if (typeof text !== 'string' && typeof text !== 'number') return '';
  108.     return '^\\W?' + ('' + text).toLowerCase().replace(/[^a-z0-9]+/g, '').replace(/[a-z0-9]/g, '$&[^a-zA-Z0-9]*') + '$';
  109. }
  110.  
  111. function runPmLogs(query) {
  112.     if (!pmLogs.length) return {logs: null};
  113.     var directory = pmLogs.slice(0);
  114.     if (!query) {
  115.         return {lines: 100, logs: directory.slice(-100).reverse()};
  116.     }
  117.     let parser = new CmdLine(query);
  118.  
  119.     // find values for user.
  120.     var userRegex = parser.findValues('ur', 'userregex').map(e => verifyRegex(e));
  121.     var userPlain = parser.findValues('u', 'user').map(e => escapeRegex(e));
  122.     var userId = parser.findValues('userid', 'ui').map(e => toUserId(e));
  123.  
  124.     var userSearch = parser.findValues('ur', 'userregex', 'u', 'user', 'userid', 'ui'); // display purposes
  125.  
  126.     // values for message contents
  127.     var messageRegex = parser.findValues('mr', 'messageregex', 'msgregex').map(e => verifyRegex(e));
  128.     var messagePlain = parser.findValues('m', 'message', 'msg').map(e => escapeRegex(e));
  129.  
  130.     var messageSearch = parser.findValues('m', 'message', 'msg', 'mr', 'messageregex', 'msgregex'); // display purposes
  131.  
  132.     // values for date
  133.     var dateRegex = parser.findValues('dr', 'dateregex').map(e => verifyRegex(e));
  134.     var datePlain = parser.findValues('d', 'date').map(e => e === 'today' ? new Date().toLocaleDateString() : escapeRegex(e));
  135.  
  136.     var dateSearch = parser.findValues('dr', 'dateregex', 'd', 'date');
  137.  
  138.     // values for lines
  139.     var lines = parser.findValues('l', 'lines', 't', 'tail');
  140.  
  141.     // values for reverse
  142.     var reverse = !parser.find('r', 'reverse').length;
  143.  
  144.     // all lines
  145.     var all = parser.find('a', 'all').length;
  146.  
  147.     var complexSearch = !!userSearch || !!dateSearch || !!messageSearch;
  148.  
  149.     var message = !!messageSearch ? new RegExp('(' + [...messagePlain, ...messageRegex].join('|') + ')', 'i') : null;
  150.     var date = !!dateSearch ? new RegExp('(' + [...datePlain, ...dateRegex].join('|') + ')', 'i') : null;
  151.     var user = !!userSearch ? new RegExp('(' + [...userPlain, ...userId, ...userRegex].join('|') + ')', 'i') : null;
  152.  
  153.     // run the actual search
  154.     directory = directory.filter(l => {
  155.         var p = l.split("|");
  156.         var d = p[0];
  157.         var u = p[1];
  158.         var u2 = p[2];
  159.         var m = p.slice(3).join("|");
  160.         return !/\/(raw|html)\s.+?\<(p|br)/i.test(m) && (!(message && !message.test(m)) && !(date && !date.test(d)) && !(user && !user.test(u) && !user.test(u2)));
  161.     });
  162.  
  163.     var maxLength = directory.length;
  164.  
  165.     lines = all ? maxLength : (parseInt(lines[0]) || 100);
  166.     if (lines > maxLength) lines = maxLength;
  167.  
  168.     directory = directory.slice(-lines);
  169.  
  170.     if (reverse) directory = directory.reverse();
  171.  
  172.     if (!directory.length) return {userSearch, dateSearch, messageSearch, reverse, lines, logs: null};
  173.     return {complexSearch, userSearch, dateSearch, messageSearch, reverse, lines, logs: directory};
  174. }
  175.  
  176. function formatPmLogs(data) {
  177.     let title = data.complexSearch ? "<details><summary>Search Criteria: </summary>User: " + escapeHTML(data.userSearch.join(", ")) + "<br />Date: " + escapeHTML(data.dateSearch.join(', ')) + "<br />Message: " + escapeHTML(data.messageSearch.join(', ')) + "<br />Lines: " + data.lines + "<br />Reverse: " + !data.reverse + "</details><br />" : !data.logs ? '' : "Last " + data.lines + " lines:<br />";
  178.     let logs = !data.logs ? 'No Logs Found.' :
  179.     data.logs.map(l => {
  180.         var line = l.split("|");
  181.         let pm = line.slice(3).join("|");
  182.         if (pm.indexOf("/raw") === 0 || pm.indexOf("/html") === 0) {
  183.             pm = pm.replace(/^\/html\s/i, "");
  184.         } else if (pm.indexOf("/error") === 0) {
  185.             pm = "<font color=red>" + escapeHTML(pm.slice(6)) + "</font>";
  186.         } else {
  187.             pm = BattleLog.parseMessage(pm);
  188.         }
  189.         return "<p><font color=slategray>[" + line[0] + "]</font> <b><font color=" + BattleLog.usernameColor(toId(line[1])) + ">" + line[1].trim() + ":</font></b> <font color=slategray>(Private to <font color=" + BattleLog.usernameColor(toId(line[2])) + ">" + line[2].trim() + "</font>)</font> " + pm + "</p>";
  190.     }).join("");
  191.     return {title, logs};
  192. }
  193.  
  194. function renderNewTab(name, contents) {
  195.     // >view-[name]
  196.     var buffer = `>view-${toId(name)}\n|init|html\n|title|${name}\n|pagehtml|${contents}`;
  197.     unsafeWindow.app.socket.onmessage({data: buffer});
  198. }
  199.  
  200. try {
  201.     function main() {
  202.         probeInterval = setInterval(function () {
  203.             if (unsafeWindow.app && unsafeWindow.app.socket && unsafeWindow.app.socket.onmessage &&
  204.                 unsafeWindow.app.socket.onmessage.toString().indexOf("self.receive") >= 0) {
  205.                 clearInterval(probeInterval);
  206.                 unsafeWindow.app.socket._onmessage = unsafeWindow.app.socket.onmessage;
  207.                 unsafeWindow.app.socket.onmessage = function (msg) {
  208.                     receive(msg.data);
  209.                     return this._onmessage(msg);
  210.                 };
  211.                 unsafeWindow.app.socket._send = unsafeWindow.app.socket.send;
  212.                 unsafeWindow.app.socket.send = function (msg) {
  213.                     if (!msg) return;
  214.                     if (msg.indexOf("|/") >= 0 && (msg.indexOf("/") - msg.indexOf("|") === 1) && (msg.indexOf("|/") !== msg.indexOf("|//"))) {
  215.                         if (!msg.split("|/")[1]) return;
  216.                         var tCmd = toId(msg.split("|/")[1].split(" ")[0]);
  217.                         var roomid = (msg.split("|")[0] || "lobby").toLowerCase().replace(/[^a-z0-9\-]/g, "");
  218.                         var room = unsafeWindow.app.rooms[(msg.split("|")[0].replace(/[^a-z0-9\-]+/g, "") || "lobby")];
  219.                         var arg = msg.split("|/").slice(1).join("|/").split(" ").slice(1).join(" ") || " ";
  220.                         switch (tCmd) {
  221.                             case "js":
  222.                                 room.receive(">> " + arg);
  223.                                 try {
  224.                                     var result = eval(arg);
  225.                                     room.receive("<< " + JSON.stringify(result));
  226.                                 } catch (e) {
  227.                                     room.receive("<< ERROR!");
  228.                                     room.receive(e.stack);
  229.                                 }
  230.                                 return;
  231.                                 break;
  232.                             case "help":
  233.                                 var buffer = [];
  234.                                 if (toId(arg) === "js") {
  235.                                     buffer.push("/js [code] - runs arbitrary code.");
  236.                                 }
  237.                                 if (toId(arg) === "convo") {
  238.                                     buffer.push("/convo [userid] - Loads up all PM logs with that user.");
  239.                                 }
  240.                                 if (toId(arg) === "pmsearch") {
  241.                                     buffer.push("/pmsearch [string] - Loads up all PM logs with that string in the message.");
  242.                                 }
  243.                                 if (toId(arg) === "pmlogs") {
  244.                                     return unsafeWindow.app.socket._onmessage({
  245.                                         data: "|popup||html|" +
  246.                                         '<b>How to search logs using the /pmlogs command:</b><br /><br /><center><b>Searching  content</b></center><div style="border: 2px solid; border-color: brown; background-color: cornsilk; border-radius: 10px; padding: 8px;"><u>The <b>-user (-u, -ur, -ui)</b> tag</u><br />• <code>-u [target]</code> searches for a simple username as either the sender or the reciever.<br />• <code>-ur [target]</code> uses a regex search for a pattern in the username.<br />• <code>-ui [target]</code> checks for logs with that userid, regardless of formatting in the name.<br /><br /><u>The <b>-message (-m, -mr)</b> tag</u><br />• <code>-m [target]</code> searches for a phrase in the message.<br />• <code>-mr [target]</code> uses a regex search for a pattern in the message.<br /><br />' +
  247.                                         '<u>The <b>-date (-d, -dr)</b> tag</u><br />• <code>-d [target]</code> searches for a phrase in the date.<br />• <code>-dr [target]</code> uses a regex search for a pattern in the date.<br /><br />Add an extra <b>r</b> after the command (<b>-u</b> becomes <b>-ur</b>) to make it accept regex. If you don&apos;t know what you&apos;re doing DO NOT use this. ReDoS can crash the browser session, or potentially do even worse.<br />To do a search with spaces in it, put the search phrase around <code>"</code>\'s or <code>\'<code>\'s.</div>' +
  248.                                         '<br /><center><b>Searching  content</b></center><div style="border: 2px solid; border-color: brown; background-color: cornsilk; border-radius: 10px; padding: 8px;"><u>The <b>-tail (-t)</b> and the <b>-all (-a)</b> tag</u><br />• <code>-tail [# of lines]</code> to control how many lines the popup will show.  Default is the most recent 100.<br />• <code>-all</code> shows every single line that matches the search<br /><br/><u>The <b>-reverse (-r)</b> tag</u><br />• <code>-r</code> shows the logs in chronological order instead of the newest at the top.</div>' +
  249.                                         '<br /><center><b>Application</b></center><div style="border: 2px solid; border-color: brown; background-color: cornsilk; border-radius: 10px; padding: 8px;">To put it all together, put the arguments one after the other.<br /><br />Example:<br />• A search of <code>-ur sparky(?!child)(.+?) -d 2016 -mr \b[a-z]+?urry\b</code> would search for a user with the word sparky but not sparkychild, the log happening in 2016, containing a word that ends in "urry", such as furry.<br />• A search of <code>-ui trickster -ui eyan</code> would search for users with the userids "trickster" or "eyan".  Matches would include "T♥r♥i♥c♥k♥s♥t♥e♥r" and "E書y呆a子n".</div>'
  250.                                     });
  251.                                 }
  252.  
  253.                                 if (!arg || !toId(arg)) {
  254.                                     room.receive("|raw|<b>Client commands:</b>");
  255.                                     room.receive("|raw|/js, /pmlogs, /convo, /pmsearch");
  256.                                     room.receive("|raw|<b>Server commands:</b>");
  257.                                 }
  258.                                 if (!arg || !toId(arg)) room.receive("|raw|<b>Server commands:</b>");
  259.                                 if (buffer.length > 0) {
  260.                                     room.receive("|raw|" + buffer.join("<br>"));
  261.                                 }
  262.                                 if (toId(arg) && buffer.length > 0) return;
  263.                                 break;
  264.                             case "convo":
  265.                                 return this.send("|/pmlogs -a -ui " + arg);
  266.                                 break;
  267.                             case "pmsearch":
  268.                                 return this.send("|/pmlogs -a -m " + arg);
  269.                                 break;
  270.                             case "pmlogs":
  271.                                 Promise.resolve(
  272.                                     runPmLogs(arg)
  273.                                 )
  274.                                     .then(res => {
  275.                                     Promise.resolve(
  276.                                         formatPmLogs(res)
  277.                                     )
  278.                                         .then(res => {
  279.                                         var display = "<center><h3>PM Logs</h3></center><div style='padding-left: 10px; padding-right: 10px; word-wrap: break-word;'>" + res.title + res.logs + "</div>";
  280.                                         renderNewTab('PM Logs', display);
  281.                                     });
  282.                                 })
  283.                                     .catch(err => {
  284.                                     room.receive('Oops, something failed while formatting the logs.');
  285.                                     room.receive('|raw|<div style="color: red">' + err.stack + '</div>');
  286.                                 })
  287.                                 .catch(err => {
  288.                                     room.receive('Oops, something failed while running the search.');
  289.                                     room.receive('|raw|<div style="color: red">' + err.stack + '</div>');
  290.                                 });
  291.                                 return;
  292.                         }
  293.                     }
  294.                     return this._send(msg);
  295.                 };
  296.             }
  297.         }, 0);
  298.     }
  299.  
  300.     function receive(entry) {
  301.         var room = "lobby";
  302.         messages = entry.split("\n");
  303.         for (var i = 0; i < messages.length; i++) {
  304.             if (messages[i].indexOf("|pm|") === 0) parsePM(messages[i]);
  305.         }
  306.     }
  307.  
  308.     function parsePM(message) {
  309.         var parts = message.split("|");
  310.  
  311.         var date = Date.now();
  312.  
  313.         pmLogs.push(packLog(date, {
  314.             from: parts[2],
  315.             to: parts[3],
  316.             msg: parts.slice(4).join("|")
  317.         }));
  318.         localStorage.setItem("pmLogs", JSON.stringify(pmLogs));
  319.     }
  320.  
  321.     setTimeout(function () {
  322.         main();
  323.     }, 2000);
  324. } catch (e) {
  325.     console.log(e);
  326. }
  327. // ==/UserScript==
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement