Browse Source

Updated helper capabilities

n1474335 6 years ago
parent
commit
bce0895392
42 changed files with 192 additions and 33 deletions
  1. 1 0
      .eslintignore
  2. 1 1
      Gruntfile.js
  3. 189 31
      src/web/SeasonalWaiter.mjs
  4. 1 1
      src/web/html/index.html
  5. 0 0
      src/web/static/clippy_assets/agents/Bonzi/agent.js
  6. BIN
      src/web/static/clippy_assets/agents/Bonzi/map.png
  7. 0 0
      src/web/static/clippy_assets/agents/Bonzi/sounds-mp3.js
  8. 0 0
      src/web/static/clippy_assets/agents/Bonzi/sounds-ogg.js
  9. 0 0
      src/web/static/clippy_assets/agents/Clippy/sounds-mp3.js
  10. 0 0
      src/web/static/clippy_assets/agents/Clippy/sounds-ogg.js
  11. 0 0
      src/web/static/clippy_assets/agents/F1/agent.js
  12. BIN
      src/web/static/clippy_assets/agents/F1/map.png
  13. 0 0
      src/web/static/clippy_assets/agents/F1/sounds-mp3.js
  14. 0 0
      src/web/static/clippy_assets/agents/F1/sounds-ogg.js
  15. 0 0
      src/web/static/clippy_assets/agents/Genie/agent.js
  16. BIN
      src/web/static/clippy_assets/agents/Genie/map.png
  17. 0 0
      src/web/static/clippy_assets/agents/Genie/sounds-mp3.js
  18. 0 0
      src/web/static/clippy_assets/agents/Genie/sounds-ogg.js
  19. 0 0
      src/web/static/clippy_assets/agents/Genius/agent.js
  20. BIN
      src/web/static/clippy_assets/agents/Genius/map.png
  21. 0 0
      src/web/static/clippy_assets/agents/Genius/sounds-mp3.js
  22. 0 0
      src/web/static/clippy_assets/agents/Genius/sounds-ogg.js
  23. 0 0
      src/web/static/clippy_assets/agents/Links/agent.js
  24. BIN
      src/web/static/clippy_assets/agents/Links/map.png
  25. 0 0
      src/web/static/clippy_assets/agents/Links/sounds-mp3.js
  26. 0 0
      src/web/static/clippy_assets/agents/Links/sounds-ogg.js
  27. 0 0
      src/web/static/clippy_assets/agents/Merlin/agent.js
  28. BIN
      src/web/static/clippy_assets/agents/Merlin/map.png
  29. 0 0
      src/web/static/clippy_assets/agents/Merlin/sounds-mp3.js
  30. 0 0
      src/web/static/clippy_assets/agents/Merlin/sounds-ogg.js
  31. 0 0
      src/web/static/clippy_assets/agents/Peedy/agent.js
  32. BIN
      src/web/static/clippy_assets/agents/Peedy/map.png
  33. 0 0
      src/web/static/clippy_assets/agents/Peedy/sounds-mp3.js
  34. 0 0
      src/web/static/clippy_assets/agents/Peedy/sounds-ogg.js
  35. 0 0
      src/web/static/clippy_assets/agents/Rocky/agent.js
  36. BIN
      src/web/static/clippy_assets/agents/Rocky/map.png
  37. 0 0
      src/web/static/clippy_assets/agents/Rocky/sounds-mp3.js
  38. 0 0
      src/web/static/clippy_assets/agents/Rocky/sounds-ogg.js
  39. 0 0
      src/web/static/clippy_assets/agents/Rover/agent.js
  40. BIN
      src/web/static/clippy_assets/agents/Rover/map.png
  41. 0 0
      src/web/static/clippy_assets/agents/Rover/sounds-mp3.js
  42. 0 0
      src/web/static/clippy_assets/agents/Rover/sounds-ogg.js

+ 1 - 0
.eslintignore

@@ -1 +1,2 @@
 src/core/vendor/**
+src/web/static/clippy_assets/**

+ 1 - 1
Gruntfile.js

@@ -151,7 +151,7 @@ module.exports = function (grunt) {
             },
             configs: ["*.{js,mjs}"],
             core: ["src/core/**/*.{js,mjs}", "!src/core/vendor/**/*", "!src/core/operations/legacy/**/*"],
-            web: ["src/web/**/*.{js,mjs}"],
+            web: ["src/web/**/*.{js,mjs}", "!src/web/static/**/*"],
             node: ["src/node/**/*.{js,mjs}"],
             tests: ["tests/**/*.{js,mjs}"],
         },

+ 189 - 31
src/web/SeasonalWaiter.mjs

@@ -5,6 +5,8 @@
  */
 
 import clippy from "clippyjs";
+import "./static/clippy_assets/agents/Clippy/agent.js";
+import clippyMap from "./static/clippy_assets/agents/Clippy/map.png";
 
 /**
  * Waiter to handle seasonal events and easter eggs.
@@ -35,8 +37,9 @@ class SeasonalWaiter {
 
         // Clippy
         const now = new Date();
-        //if (now.getMonth() === 3 && now.getDate() === 1) {
-        if (now.getMonth() === 2 && now.getDate() === 22) {
+        if (now.getMonth() === 3 && now.getDate() === 1) {
+            this.addClippyOption();
+            this.manager.addDynamicListener(".option-item #clippy", "change", this.setupClippy, this);
             this.setupClippy();
         }
     }
@@ -63,26 +66,56 @@ class SeasonalWaiter {
     }
 
     /**
-     * Sets up Clippy on April Fools Day
+     * Creates an option in the Options menu for turning Clippy on or off
+     */
+    addClippyOption() {
+        const optionsBody = document.getElementById("options-body"),
+            optionItem = document.createElement("span");
+
+        optionItem.className = "bmd-form-group is-filled";
+        optionItem.innerHTML = `<div class="checkbox option-item">
+            <label for="clippy">
+                <input type="checkbox" option="clippy" id="clippy" checked="">
+                Use the Clippy helper
+            </label>
+        </div>`;
+        optionsBody.appendChild(optionItem);
+
+        this.manager.options.load();
+    }
+
+    /**
+     * Sets up Clippy for April Fools Day
      */
     setupClippy() {
-        //const clippyAssets = "./agents/";
-        const clippyAssets = undefined;
+        // Destroy any previous agents
+        if (this.clippyAgent) {
+            this.clippyAgent.closeBalloonImmediately();
+            this.clippyAgent.hide();
+        }
+
+        if (!this.app.options.clippy) {
+            this.clippyTimeouts.forEach(t => clearTimeout(t));
+            return;
+        }
+
+        // Set base path to # to prevent external network requests
+        const clippyAssets = "#";
+        // Shim the library to prevent external network requests
+        shimClippy(clippy);
 
         const self = this;
         clippy.load("Clippy", (agent) => {
-            window.agent = agent;
-            shimClippy(agent);
+            shimClippyAgent(agent);
             self.clippyAgent = agent;
-
             agent.show();
-            //agent.animate();
-            agent.speak("Hello, I'm Clippy, your personal cyber assistant!", true);
+            agent.speak("Hello, I'm Clippy, your personal cyber assistant!");
         }, undefined, clippyAssets);
 
         // Watch for the Auto Magic button appearing
         const magic = document.getElementById("magic");
         const observer = new MutationObserver((mutationsList, observer) => {
+            // Read in message and recipe
             let msg, recipe;
             for (const mutation of mutationsList) {
                 if (mutation.attributeName === "data-original-title") {
@@ -93,42 +126,145 @@ class SeasonalWaiter {
                 }
             }
 
-            // Cancel current animation and hide balloon (after it has finished)
-            self.clippyAgent._queue.clear();
-            self.clippyAgent._queue.next();
-            self.clippyAgent.stopCurrent();
-            self.clippyAgent._balloon._hold = false;
-            self.clippyAgent._balloon.hide();
+            // Close balloon if it is currently showing a magic hint
+            const balloon = self.clippyAgent._balloon._balloon;
+            if (balloon.is(":visible") && balloon.text().indexOf("That looks like encoded data") >= 0) {
+                self.clippyAgent._balloon.hide(true);
+                this.clippyAgent._balloon._hidden = true;
+            }
 
+            // If a recipe was found, get Clippy to tell the user
             if (recipe) {
                 recipe = this.manager.controls.generateStateUrl(true, true, JSON.parse(recipe));
-                msg = `That looks like encoded data!<br><br>${msg}<br><br>Click <a href="${recipe}">here</a> to load this recipe.`;
+                msg = `That looks like encoded data!<br><br>${msg}<br><br>Click <a class="clippyMagicRecipe" href="${recipe}">here</a> to load this recipe.`;
 
-                // Stop balloon activity immediately and trigger speak again
-                self.clippyAgent._balloon.pause();
-                delete self.clippyAgent._balloon._addWord;
-                self.clippyAgent._balloon._active = false;
-                self.clippyAgent._balloon.hide(true);
+                // Stop current balloon activity immediately and trigger speak again
+                this.clippyAgent.closeBalloonImmediately();
                 self.clippyAgent.speak(msg, true);
+                // self.clippyAgent._queue.next();
             }
         });
-        observer.observe(document.getElementById("magic"), {
-            attributes: true
-        });
+        observer.observe(document.getElementById("magic"), {attributes: true});
+
+        // Play animations for various things
+        this.manager.addListeners("#search", "click", () => {
+            this.clippyAgent.play("Searching");
+        }, this);
+        this.manager.addListeners("#save,#save-to-file", "click", () => {
+            this.clippyAgent.play("Save");
+        }, this);
+        this.manager.addListeners("#clr-recipe,#clr-io", "click", () => {
+            this.clippyAgent.play("EmptyTrash");
+        }, this);
+        this.manager.addListeners("#bake", "click", e => {
+            if (e.target.closest("button").textContent.toLowerCase().indexOf("bake") >= 0) {
+                this.clippyAgent.play("Thinking");
+            } else {
+                this.clippyAgent.play("EmptyTrash");
+            }
+            this.clippyAgent._queue.clear();
+        }, this);
+        this.manager.addListeners("#input-text", "keydown", () => {
+            this.clippyAgent.play("Writing");
+            this.clippyAgent._queue.clear();
+        }, this);
+        this.manager.addDynamicListener("a.clippyMagicRecipe", "click", (e) => {
+            this.clippyAgent.play("Congratulate");
+        }, this);
+
+        this.clippyTimeouts = [];
+        // Show challenge after timeout
+        this.clippyTimeouts.push(setTimeout(() => {
+            const hex = "1f 8b 08 00 ae a1 9b 5c 00 ff 05 40 a1 12 00 10 0c fd 26 61 5b 76 aa 9d 26 a8 02 02 37 84 f7 fb bb c5 a4 5f 22 c6 09 e5 6e c5 4c 2d 3f e9 30 a6 ea 41 a2 f2 ac 1c 00 00 00";
+            self.clippyAgent.speak(`How about a fun challenge?<br><br>Try decoding this (click to load):<br><a href="#recipe=[]&input=${encodeURIComponent(btoa(hex))}">${hex}</a>`, true);
+            self.clippyAgent.play("GetAttention");
+        }, 1 * 60 * 1000));
+
+        this.clippyTimeouts.push(setTimeout(() => {
+            self.clippyAgent.speak("<i>Did you know?</i><br><br>You can load files into CyberChef up to around 500MB using drag and drop or the load file button.", 15000);
+            self.clippyAgent.play("Wave");
+        }, 2 * 60 * 1000));
+
+        this.clippyTimeouts.push(setTimeout(() => {
+            self.clippyAgent.speak("<i>Did you know?</i><br><br>You can use the 'Fork' operation to split up your input and run the recipe over each branch separately.<br><br><a class='clippyMagicRecipe' href=\"#recipe=Fork('%5C%5Cn','%5C%5Cn',false)From_UNIX_Timestamp('Seconds%20(s)')&amp;input=OTc4MzQ2ODAwCjEwMTI2NTEyMDAKMTA0NjY5NjQwMAoxMDgxMDg3MjAwCjExMTUzMDUyMDAKMTE0OTYwOTYwMA\">Here's an example</a>.", 15000);
+            self.clippyAgent.play("Print");
+        }, 3 * 60 * 1000));
+
+        this.clippyTimeouts.push(setTimeout(() => {
+            self.clippyAgent.speak("<i>Did you know?</i><br><br>The 'Magic' operation uses a number of methods to detect encoded data and the operations which can be used to make sense of it. A technical description of these methods can be found <a href=\"https://github.com/gchq/CyberChef/wiki/Automatic-detection-of-encoded-data-using-CyberChef-Magic\">here</a>.", 15000);
+            self.clippyAgent.play("Alert");
+        }, 4 * 60 * 1000));
+
+        this.clippyTimeouts.push(setTimeout(() => {
+            self.clippyAgent.speak("<i>Did you know?</i><br><br>You can use parts of the input as arguments to operations.<br><br><a class='clippyMagicRecipe' href=\"#recipe=Register('key%3D(%5B%5C%5Cda-f%5D*)',true,false)Find_/_Replace(%7B'option':'Regex','string':'.*data%3D(.*)'%7D,'$1',true,false,true)RC4(%7B'option':'Hex','string':'$R0'%7D,'Hex','Latin1')&amp;input=aHR0cDovL21hbHdhcmV6LmJpei9iZWFjb24ucGhwP2tleT0wZTkzMmE1YyZkYXRhPThkYjdkNWViZTM4NjYzYTU0ZWNiYjMzNGUzZGIxMQ\">Click here for an example</a>.", 15000);
+            self.clippyAgent.play("CheckingSomething");
+        }, 5 * 60 * 1000));
     }
 
 }
 
+
 /**
  * Shims various ClippyJS functions to modify behaviour.
  *
+ * @param {Clippy} clippy - The Clippy library
+ */
+function shimClippy(clippy) {
+    // Shim _loadSounds so that it doesn't actually try to load any sounds
+    clippy.load._loadSounds = function _loadSounds (name, path) {
+        let dfd = clippy.load._sounds[name];
+        if (dfd) return dfd;
+
+        // set dfd if not defined
+        dfd = clippy.load._sounds[name] = $.Deferred();
+
+        // Resolve immediately without loading
+        dfd.resolve({});
+
+        return dfd.promise();
+    };
+
+    // Shim _loadMap so that it uses the local copy
+    clippy.load._loadMap = function _loadMap (path) {
+        let dfd = clippy.load._maps[path];
+        if (dfd) return dfd;
+
+        // set dfd if not defined
+        dfd = clippy.load._maps[path] = $.Deferred();
+
+        const src = clippyMap;
+        const img = new Image();
+
+        img.onload = dfd.resolve;
+        img.onerror = dfd.reject;
+
+        // start loading the map;
+        img.setAttribute("src", src);
+
+        return dfd.promise();
+    };
+
+    // Make sure we don't request the remote map
+    clippy.Animator.prototype._setupElement = function _setupElement (el) {
+        const frameSize = this._data.framesize;
+        el.css("display", "none");
+        el.css({ width: frameSize[0], height: frameSize[1] });
+        el.css("background", "url('" + clippyMap + "') no-repeat");
+
+        return el;
+    };
+}
+
+/**
+ * Shims various ClippyJS Agent functions to modify behaviour.
+ *
  * @param {Agent} agent - The Clippy Agent
  */
-function shimClippy(agent) {
+function shimClippyAgent(agent) {
     // Turn off all sounds
     agent._animator._playSound = () => {};
 
-    // Improve speak function
+    // Improve speak function to support HTML markup
     const self = agent._balloon;
     agent._balloon.speak = (complete, text, hold) => {
         self._hidden = false;
@@ -147,10 +283,11 @@ function shimClippy(agent) {
 
         self._complete = complete;
         self._sayWords(text, hold, complete);
+        if (hold) agent._queue.next();
     };
 
-    // Improve the _sayWords function to allow HTML
-    agent._balloon.WORD_SPEAK_TIME = 100;
+    // Improve the _sayWords function to allow HTML and support timeouts
+    agent._balloon.WORD_SPEAK_TIME = 60;
     agent._balloon._sayWords = (text, hold, complete) => {
         self._active = true;
         self._hold = hold;
@@ -158,6 +295,7 @@ function shimClippy(agent) {
         const time = self.WORD_SPEAK_TIME;
         const el = self._content;
         let idx = 1;
+        clearTimeout(self.holdTimeout);
 
         self._addWord = $.proxy(function () {
             if (!self._active) return;
@@ -167,6 +305,12 @@ function shimClippy(agent) {
                 if (!self._hold) {
                     complete();
                     self.hide();
+                } else if (typeof hold === "number") {
+                    self.holdTimeout = setTimeout(() => {
+                        self._hold = false;
+                        complete();
+                        self.hide();
+                    }, hold);
                 }
             } else {
                 el.html(words.slice(0, idx).join(" "));
@@ -178,10 +322,24 @@ function shimClippy(agent) {
         self._addWord();
     };
 
-    // Close the balloon on click
+    // Add break-word to balloon CSS
+    agent._balloon._balloon.css("word-break", "break-word");
+
+    // Close the balloon on click (unless it was a link)
     agent._balloon._balloon.click(e => {
-        agent._balloon._finishHideBalloon();
+        if (e.target.nodeName !== "A") {
+            agent._balloon.hide(true);
+            agent._balloon._hidden = true;
+        }
     });
+
+    // Add function to immediately close the balloon even if it is currently doing something
+    agent.closeBalloonImmediately = () => {
+        agent._queue.clear();
+        agent._balloon.hide(true);
+        agent._balloon._hidden = true;
+        agent._queue.next();
+    };
 }
 
 export default SeasonalWaiter;

+ 1 - 1
src/web/html/index.html

@@ -591,7 +591,7 @@
                                     What sort of things can I do with CyberChef?
                                 </a>
                                 <div class="collapse" id="faq-examples">
-                                    <p>There are around 200 operations in CyberChef allowing you to carry out simple and complex tasks easily. Here are some examples:</p>
+                                    <p>There are around 300 operations in CyberChef allowing you to carry out simple and complex tasks easily. Here are some examples:</p>
                                     <ul>
                                         <li><a href="#recipe=From_Base64('A-Za-z0-9%2B/%3D',true)&input=VTI4Z2JHOXVaeUJoYm1RZ2RHaGhibXR6SUdadmNpQmhiR3dnZEdobElHWnBjMmd1">Decode a Base64-encoded string</a></li>
                                         <li><a href="#recipe=Translate_DateTime_Format('Standard%20date%20and%20time','DD/MM/YYYY%20HH:mm:ss','UTC','dddd%20Do%20MMMM%20YYYY%20HH:mm:ss%20Z%20z','Australia/Queensland')&input=MTUvMDYvMjAxNSAyMDo0NTowMA">Convert a date and time to a different time zone</a></li>

File diff suppressed because it is too large
+ 0 - 0
src/web/static/clippy_assets/agents/Bonzi/agent.js


BIN
src/web/static/clippy_assets/agents/Bonzi/map.png


File diff suppressed because it is too large
+ 0 - 0
src/web/static/clippy_assets/agents/Bonzi/sounds-mp3.js


File diff suppressed because it is too large
+ 0 - 0
src/web/static/clippy_assets/agents/Bonzi/sounds-ogg.js


File diff suppressed because it is too large
+ 0 - 0
src/web/static/clippy_assets/agents/Clippy/sounds-mp3.js


File diff suppressed because it is too large
+ 0 - 0
src/web/static/clippy_assets/agents/Clippy/sounds-ogg.js


File diff suppressed because it is too large
+ 0 - 0
src/web/static/clippy_assets/agents/F1/agent.js


BIN
src/web/static/clippy_assets/agents/F1/map.png


File diff suppressed because it is too large
+ 0 - 0
src/web/static/clippy_assets/agents/F1/sounds-mp3.js


File diff suppressed because it is too large
+ 0 - 0
src/web/static/clippy_assets/agents/F1/sounds-ogg.js


File diff suppressed because it is too large
+ 0 - 0
src/web/static/clippy_assets/agents/Genie/agent.js


BIN
src/web/static/clippy_assets/agents/Genie/map.png


File diff suppressed because it is too large
+ 0 - 0
src/web/static/clippy_assets/agents/Genie/sounds-mp3.js


File diff suppressed because it is too large
+ 0 - 0
src/web/static/clippy_assets/agents/Genie/sounds-ogg.js


File diff suppressed because it is too large
+ 0 - 0
src/web/static/clippy_assets/agents/Genius/agent.js


BIN
src/web/static/clippy_assets/agents/Genius/map.png


File diff suppressed because it is too large
+ 0 - 0
src/web/static/clippy_assets/agents/Genius/sounds-mp3.js


File diff suppressed because it is too large
+ 0 - 0
src/web/static/clippy_assets/agents/Genius/sounds-ogg.js


File diff suppressed because it is too large
+ 0 - 0
src/web/static/clippy_assets/agents/Links/agent.js


BIN
src/web/static/clippy_assets/agents/Links/map.png


File diff suppressed because it is too large
+ 0 - 0
src/web/static/clippy_assets/agents/Links/sounds-mp3.js


File diff suppressed because it is too large
+ 0 - 0
src/web/static/clippy_assets/agents/Links/sounds-ogg.js


File diff suppressed because it is too large
+ 0 - 0
src/web/static/clippy_assets/agents/Merlin/agent.js


BIN
src/web/static/clippy_assets/agents/Merlin/map.png


File diff suppressed because it is too large
+ 0 - 0
src/web/static/clippy_assets/agents/Merlin/sounds-mp3.js


File diff suppressed because it is too large
+ 0 - 0
src/web/static/clippy_assets/agents/Merlin/sounds-ogg.js


File diff suppressed because it is too large
+ 0 - 0
src/web/static/clippy_assets/agents/Peedy/agent.js


BIN
src/web/static/clippy_assets/agents/Peedy/map.png


File diff suppressed because it is too large
+ 0 - 0
src/web/static/clippy_assets/agents/Peedy/sounds-mp3.js


File diff suppressed because it is too large
+ 0 - 0
src/web/static/clippy_assets/agents/Peedy/sounds-ogg.js


File diff suppressed because it is too large
+ 0 - 0
src/web/static/clippy_assets/agents/Rocky/agent.js


BIN
src/web/static/clippy_assets/agents/Rocky/map.png


File diff suppressed because it is too large
+ 0 - 0
src/web/static/clippy_assets/agents/Rocky/sounds-mp3.js


File diff suppressed because it is too large
+ 0 - 0
src/web/static/clippy_assets/agents/Rocky/sounds-ogg.js


File diff suppressed because it is too large
+ 0 - 0
src/web/static/clippy_assets/agents/Rover/agent.js


BIN
src/web/static/clippy_assets/agents/Rover/map.png


File diff suppressed because it is too large
+ 0 - 0
src/web/static/clippy_assets/agents/Rover/sounds-mp3.js


File diff suppressed because it is too large
+ 0 - 0
src/web/static/clippy_assets/agents/Rover/sounds-ogg.js


Some files were not shown because too many files changed in this diff