Updated helper capabilities
|
@ -1 +1,2 @@
|
||||||
src/core/vendor/**
|
src/core/vendor/**
|
||||||
|
src/web/static/clippy_assets/**
|
|
@ -151,7 +151,7 @@ module.exports = function (grunt) {
|
||||||
},
|
},
|
||||||
configs: ["*.{js,mjs}"],
|
configs: ["*.{js,mjs}"],
|
||||||
core: ["src/core/**/*.{js,mjs}", "!src/core/vendor/**/*", "!src/core/operations/legacy/**/*"],
|
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}"],
|
node: ["src/node/**/*.{js,mjs}"],
|
||||||
tests: ["tests/**/*.{js,mjs}"],
|
tests: ["tests/**/*.{js,mjs}"],
|
||||||
},
|
},
|
||||||
|
|
|
@ -5,6 +5,8 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import clippy from "clippyjs";
|
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.
|
* Waiter to handle seasonal events and easter eggs.
|
||||||
|
@ -35,8 +37,9 @@ class SeasonalWaiter {
|
||||||
|
|
||||||
// Clippy
|
// Clippy
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
//if (now.getMonth() === 3 && now.getDate() === 1) {
|
if (now.getMonth() === 3 && now.getDate() === 1) {
|
||||||
if (now.getMonth() === 2 && now.getDate() === 22) {
|
this.addClippyOption();
|
||||||
|
this.manager.addDynamicListener(".option-item #clippy", "change", this.setupClippy, this);
|
||||||
this.setupClippy();
|
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() {
|
setupClippy() {
|
||||||
//const clippyAssets = "./agents/";
|
// Destroy any previous agents
|
||||||
const clippyAssets = undefined;
|
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;
|
const self = this;
|
||||||
clippy.load("Clippy", (agent) => {
|
clippy.load("Clippy", (agent) => {
|
||||||
window.agent = agent;
|
shimClippyAgent(agent);
|
||||||
shimClippy(agent);
|
|
||||||
self.clippyAgent = agent;
|
self.clippyAgent = agent;
|
||||||
|
|
||||||
agent.show();
|
agent.show();
|
||||||
//agent.animate();
|
agent.speak("Hello, I'm Clippy, your personal cyber assistant!");
|
||||||
agent.speak("Hello, I'm Clippy, your personal cyber assistant!", true);
|
|
||||||
}, undefined, clippyAssets);
|
}, undefined, clippyAssets);
|
||||||
|
|
||||||
// Watch for the Auto Magic button appearing
|
// Watch for the Auto Magic button appearing
|
||||||
const magic = document.getElementById("magic");
|
const magic = document.getElementById("magic");
|
||||||
const observer = new MutationObserver((mutationsList, observer) => {
|
const observer = new MutationObserver((mutationsList, observer) => {
|
||||||
|
// Read in message and recipe
|
||||||
let msg, recipe;
|
let msg, recipe;
|
||||||
for (const mutation of mutationsList) {
|
for (const mutation of mutationsList) {
|
||||||
if (mutation.attributeName === "data-original-title") {
|
if (mutation.attributeName === "data-original-title") {
|
||||||
|
@ -93,42 +126,145 @@ class SeasonalWaiter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cancel current animation and hide balloon (after it has finished)
|
// Close balloon if it is currently showing a magic hint
|
||||||
self.clippyAgent._queue.clear();
|
const balloon = self.clippyAgent._balloon._balloon;
|
||||||
self.clippyAgent._queue.next();
|
if (balloon.is(":visible") && balloon.text().indexOf("That looks like encoded data") >= 0) {
|
||||||
self.clippyAgent.stopCurrent();
|
self.clippyAgent._balloon.hide(true);
|
||||||
self.clippyAgent._balloon._hold = false;
|
this.clippyAgent._balloon._hidden = true;
|
||||||
self.clippyAgent._balloon.hide();
|
}
|
||||||
|
|
||||||
|
// If a recipe was found, get Clippy to tell the user
|
||||||
if (recipe) {
|
if (recipe) {
|
||||||
recipe = this.manager.controls.generateStateUrl(true, true, JSON.parse(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
|
// Stop current balloon activity immediately and trigger speak again
|
||||||
self.clippyAgent._balloon.pause();
|
this.clippyAgent.closeBalloonImmediately();
|
||||||
delete self.clippyAgent._balloon._addWord;
|
|
||||||
self.clippyAgent._balloon._active = false;
|
|
||||||
self.clippyAgent._balloon.hide(true);
|
|
||||||
self.clippyAgent.speak(msg, true);
|
self.clippyAgent.speak(msg, true);
|
||||||
|
// self.clippyAgent._queue.next();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
observer.observe(document.getElementById("magic"), {
|
observer.observe(document.getElementById("magic"), {attributes: true});
|
||||||
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)')&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')&input=aHR0cDovL21hbHdhcmV6LmJpei9iZWFjb24ucGhwP2tleT0wZTkzMmE1YyZkYXRhPThkYjdkNWViZTM4NjYzYTU0ZWNiYjMzNGUzZGIxMQ\">Click here for an example</a>.", 15000);
|
||||||
|
self.clippyAgent.play("CheckingSomething");
|
||||||
|
}, 5 * 60 * 1000));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shims various ClippyJS functions to modify behaviour.
|
* 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
|
* @param {Agent} agent - The Clippy Agent
|
||||||
*/
|
*/
|
||||||
function shimClippy(agent) {
|
function shimClippyAgent(agent) {
|
||||||
// Turn off all sounds
|
// Turn off all sounds
|
||||||
agent._animator._playSound = () => {};
|
agent._animator._playSound = () => {};
|
||||||
|
|
||||||
// Improve speak function
|
// Improve speak function to support HTML markup
|
||||||
const self = agent._balloon;
|
const self = agent._balloon;
|
||||||
agent._balloon.speak = (complete, text, hold) => {
|
agent._balloon.speak = (complete, text, hold) => {
|
||||||
self._hidden = false;
|
self._hidden = false;
|
||||||
|
@ -147,10 +283,11 @@ function shimClippy(agent) {
|
||||||
|
|
||||||
self._complete = complete;
|
self._complete = complete;
|
||||||
self._sayWords(text, hold, complete);
|
self._sayWords(text, hold, complete);
|
||||||
|
if (hold) agent._queue.next();
|
||||||
};
|
};
|
||||||
|
|
||||||
// Improve the _sayWords function to allow HTML
|
// Improve the _sayWords function to allow HTML and support timeouts
|
||||||
agent._balloon.WORD_SPEAK_TIME = 100;
|
agent._balloon.WORD_SPEAK_TIME = 60;
|
||||||
agent._balloon._sayWords = (text, hold, complete) => {
|
agent._balloon._sayWords = (text, hold, complete) => {
|
||||||
self._active = true;
|
self._active = true;
|
||||||
self._hold = hold;
|
self._hold = hold;
|
||||||
|
@ -158,6 +295,7 @@ function shimClippy(agent) {
|
||||||
const time = self.WORD_SPEAK_TIME;
|
const time = self.WORD_SPEAK_TIME;
|
||||||
const el = self._content;
|
const el = self._content;
|
||||||
let idx = 1;
|
let idx = 1;
|
||||||
|
clearTimeout(self.holdTimeout);
|
||||||
|
|
||||||
self._addWord = $.proxy(function () {
|
self._addWord = $.proxy(function () {
|
||||||
if (!self._active) return;
|
if (!self._active) return;
|
||||||
|
@ -167,6 +305,12 @@ function shimClippy(agent) {
|
||||||
if (!self._hold) {
|
if (!self._hold) {
|
||||||
complete();
|
complete();
|
||||||
self.hide();
|
self.hide();
|
||||||
|
} else if (typeof hold === "number") {
|
||||||
|
self.holdTimeout = setTimeout(() => {
|
||||||
|
self._hold = false;
|
||||||
|
complete();
|
||||||
|
self.hide();
|
||||||
|
}, hold);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
el.html(words.slice(0, idx).join(" "));
|
el.html(words.slice(0, idx).join(" "));
|
||||||
|
@ -178,10 +322,24 @@ function shimClippy(agent) {
|
||||||
self._addWord();
|
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._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;
|
export default SeasonalWaiter;
|
||||||
|
|
|
@ -591,7 +591,7 @@
|
||||||
What sort of things can I do with CyberChef?
|
What sort of things can I do with CyberChef?
|
||||||
</a>
|
</a>
|
||||||
<div class="collapse" id="faq-examples">
|
<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>
|
<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=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>
|
<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>
|
||||||
|
|
Before Width: | Height: | Size: 815 KiB |
Before Width: | Height: | Size: 1 MiB |
Before Width: | Height: | Size: 962 KiB |
Before Width: | Height: | Size: 1.1 MiB |
Before Width: | Height: | Size: 800 KiB |
Before Width: | Height: | Size: 1,013 KiB |
Before Width: | Height: | Size: 1.8 MiB |
Before Width: | Height: | Size: 1.2 MiB |
Before Width: | Height: | Size: 682 KiB |