CrazyJeux / PostFinder

// ==UserScript==
// @name        PostFinder
// @namespace   CrazyJeux/Daring-Do
// @author		CrazyJeux/Daring-Do
// @version     9
// @description Permet de rechercher des messages dans un topic.
// @icon        https://i.imgur.com/x23FBdh.jpg
// @match		*://www.jeuxvideo.com/*
// @match		*://www.forumjv.com/*
// @match		*://*.jvstalker.fr/*
// @match		*://*.jvarchive.fr/*
// @match		*://*.openuserjs.org/scripts/CrazyJeux/PostFinder/source
// @resource	colpickCSS				https://raw.githubusercontent.com/josedvq/colpick-jQuery-Color-Picker/master/css/colpick.css
// @resource	spectrumCSS				https://cdnjs.cloudflare.com/ajax/libs/spectrum/1.7.0/spectrum.css
// @resource	timepickerCSS			https://cdnjs.cloudflare.com/ajax/libs/jquery-ui-timepicker-addon/1.4.5/jquery-ui-timepicker-addon.min.css
// @resource	findAndReplaceDOMTextJS	https://raw.githubusercontent.com/padolsey/findAndReplaceDOMText/master/src/findAndReplaceDOMText.js
// @resource	datepickerFRJS			https://github.com/jquery/jquery-ui/raw/master/ui/i18n/datepicker-fr.js
// @resource	bluebirdJS				https://cdn.jsdelivr.net/bluebird/latest/bluebird.min.js
// @resource	colpickJS				https://raw.githubusercontent.com/josedvq/colpick-jQuery-Color-Picker/master/js/colpick.js
// @resource	spectrumJS				https://cdnjs.cloudflare.com/ajax/libs/spectrum/1.7.0/spectrum.js
// @resource	jQueryJS				https://cdnjs.cloudflare.com/ajax/libs/jquery/1.12.0/jquery.min.js
// @resource	jQueryUIJS				https://ajax.googleapis.com/ajax/libs/jqueryui/1.11.4/jquery-ui.min.js
// @resource	timepickerJS			https://cdnjs.cloudflare.com/ajax/libs/jquery-ui-timepicker-addon/1.4.5/jquery-ui-timepicker-addon.min.js
// @grant       GM_getResourceText
// @grant       GM_getResourceURL
// @grant		GM_addStyle
// @grant		GM_info
// @grant		unsafeWindow
// ==/UserScript==

	function toCall() {
		function scriptContent() {
			//"use strict";

			/*
			 * https://javascriptweblog.wordpress.com/2011/08/08/fixing-the-javascript-typeof-operator/
			 * Gets the real type of a variable.
			 * @param {???} obj: the variable.
			 * @returns {String}: the real type.
			 */
			function getRealType(obj) {
				return ({}).toString.call(obj).match(/\s([a-zA-Z]+)/)[1];
			}

			/*
			 * Adapted from http://stackoverflow.com/a/2837646
			 * Escapes special characters, removes accents and replaces spaces/tabs/etc. with hyphens.
			 * @param {String} str: the string to format.
			 * @returns {String}: the formatted string.
			 */
			function format(str) {
				/*
				 * http://stackoverflow.com/a/18123985
				 * Remove the accents.
				 * @param {String} str: the string to remove the diacritics from.
				 * @returns {String}: the string without accents.
				 */
				function removeDiacritics(str) {
					var defaultDiacriticsRemovalMap = [
						{'base': 'A', 'letters': /[\u0041\u24B6\uFF21\u00C0\u00C1\u00C2\u1EA6\u1EA4\u1EAA\u1EA8\u00C3\u0100\u0102\u1EB0\u1EAE\u1EB4\u1EB2\u0226\u01E0\u00C4\u01DE\u1EA2\u00C5\u01FA\u01CD\u0200\u0202\u1EA0\u1EAC\u1EB6\u1E00\u0104\u023A\u2C6F]/g},
						{'base': 'AA', 'letters': /[\uA732]/g},
						{'base': 'AE', 'letters': /[\u00C6\u01FC\u01E2]/g},
						{'base': 'AO', 'letters': /[\uA734]/g},
						{'base': 'AU', 'letters': /[\uA736]/g},
						{'base': 'AV', 'letters': /[\uA738\uA73A]/g},
						{'base': 'AY', 'letters': /[\uA73C]/g},
						{'base': 'B', 'letters': /[\u0042\u24B7\uFF22\u1E02\u1E04\u1E06\u0243\u0182\u0181]/g},
						{'base': 'C', 'letters': /[\u0043\u24B8\uFF23\u0106\u0108\u010A\u010C\u00C7\u1E08\u0187\u023B\uA73E]/g},
						{'base': 'D', 'letters': /[\u0044\u24B9\uFF24\u1E0A\u010E\u1E0C\u1E10\u1E12\u1E0E\u0110\u018B\u018A\u0189\uA779]/g},
						{'base': 'DZ', 'letters': /[\u01F1\u01C4]/g},
						{'base': 'Dz', 'letters': /[\u01F2\u01C5]/g},
						{'base': 'E', 'letters': /[\u0045\u24BA\uFF25\u00C8\u00C9\u00CA\u1EC0\u1EBE\u1EC4\u1EC2\u1EBC\u0112\u1E14\u1E16\u0114\u0116\u00CB\u1EBA\u011A\u0204\u0206\u1EB8\u1EC6\u0228\u1E1C\u0118\u1E18\u1E1A\u0190\u018E]/g},
						{'base': 'F', 'letters': /[\u0046\u24BB\uFF26\u1E1E\u0191\uA77B]/g},
						{'base': 'G', 'letters': /[\u0047\u24BC\uFF27\u01F4\u011C\u1E20\u011E\u0120\u01E6\u0122\u01E4\u0193\uA7A0\uA77D\uA77E]/g},
						{'base': 'H', 'letters': /[\u0048\u24BD\uFF28\u0124\u1E22\u1E26\u021E\u1E24\u1E28\u1E2A\u0126\u2C67\u2C75\uA78D]/g},
						{'base': 'I', 'letters': /[\u0049\u24BE\uFF29\u00CC\u00CD\u00CE\u0128\u012A\u012C\u0130\u00CF\u1E2E\u1EC8\u01CF\u0208\u020A\u1ECA\u012E\u1E2C\u0197]/g},
						{'base': 'J', 'letters': /[\u004A\u24BF\uFF2A\u0134\u0248]/g},
						{'base': 'K', 'letters': /[\u004B\u24C0\uFF2B\u1E30\u01E8\u1E32\u0136\u1E34\u0198\u2C69\uA740\uA742\uA744\uA7A2]/g},
						{'base': 'L', 'letters': /[\u004C\u24C1\uFF2C\u013F\u0139\u013D\u1E36\u1E38\u013B\u1E3C\u1E3A\u0141\u023D\u2C62\u2C60\uA748\uA746\uA780]/g},
						{'base': 'LJ', 'letters': /[\u01C7]/g},
						{'base': 'Lj', 'letters': /[\u01C8]/g},
						{'base': 'M', 'letters': /[\u004D\u24C2\uFF2D\u1E3E\u1E40\u1E42\u2C6E\u019C]/g},
						{'base': 'N', 'letters': /[\u004E\u24C3\uFF2E\u01F8\u0143\u00D1\u1E44\u0147\u1E46\u0145\u1E4A\u1E48\u0220\u019D\uA790\uA7A4]/g},
						{'base': 'NJ', 'letters': /[\u01CA]/g},
						{'base': 'Nj', 'letters': /[\u01CB]/g},
						{'base': 'O', 'letters': /[\u004F\u24C4\uFF2F\u00D2\u00D3\u00D4\u1ED2\u1ED0\u1ED6\u1ED4\u00D5\u1E4C\u022C\u1E4E\u014C\u1E50\u1E52\u014E\u022E\u0230\u00D6\u022A\u1ECE\u0150\u01D1\u020C\u020E\u01A0\u1EDC\u1EDA\u1EE0\u1EDE\u1EE2\u1ECC\u1ED8\u01EA\u01EC\u00D8\u01FE\u0186\u019F\uA74A\uA74C]/g},
						{'base': 'OI', 'letters': /[\u01A2]/g},
						{'base': 'OO', 'letters': /[\uA74E]/g},
						{'base': 'OU', 'letters': /[\u0222]/g},
						{'base': 'P', 'letters': /[\u0050\u24C5\uFF30\u1E54\u1E56\u01A4\u2C63\uA750\uA752\uA754]/g},
						{'base': 'Q', 'letters': /[\u0051\u24C6\uFF31\uA756\uA758\u024A]/g},
						{'base': 'R', 'letters': /[\u0052\u24C7\uFF32\u0154\u1E58\u0158\u0210\u0212\u1E5A\u1E5C\u0156\u1E5E\u024C\u2C64\uA75A\uA7A6\uA782]/g},
						{'base': 'S', 'letters': /[\u0053\u24C8\uFF33\u1E9E\u015A\u1E64\u015C\u1E60\u0160\u1E66\u1E62\u1E68\u0218\u015E\u2C7E\uA7A8\uA784]/g},
						{'base': 'T', 'letters': /[\u0054\u24C9\uFF34\u1E6A\u0164\u1E6C\u021A\u0162\u1E70\u1E6E\u0166\u01AC\u01AE\u023E\uA786]/g},
						{'base': 'TZ', 'letters': /[\uA728]/g},
						{'base': 'U', 'letters': /[\u0055\u24CA\uFF35\u00D9\u00DA\u00DB\u0168\u1E78\u016A\u1E7A\u016C\u00DC\u01DB\u01D7\u01D5\u01D9\u1EE6\u016E\u0170\u01D3\u0214\u0216\u01AF\u1EEA\u1EE8\u1EEE\u1EEC\u1EF0\u1EE4\u1E72\u0172\u1E76\u1E74\u0244]/g},
						{'base': 'V', 'letters': /[\u0056\u24CB\uFF36\u1E7C\u1E7E\u01B2\uA75E\u0245]/g},
						{'base': 'VY', 'letters': /[\uA760]/g},
						{'base': 'W', 'letters': /[\u0057\u24CC\uFF37\u1E80\u1E82\u0174\u1E86\u1E84\u1E88\u2C72]/g},
						{'base': 'X', 'letters': /[\u0058\u24CD\uFF38\u1E8A\u1E8C]/g},
						{'base': 'Y', 'letters': /[\u0059\u24CE\uFF39\u1EF2\u00DD\u0176\u1EF8\u0232\u1E8E\u0178\u1EF6\u1EF4\u01B3\u024E\u1EFE]/g},
						{'base': 'Z', 'letters': /[\u005A\u24CF\uFF3A\u0179\u1E90\u017B\u017D\u1E92\u1E94\u01B5\u0224\u2C7F\u2C6B\uA762]/g},
						{'base': 'a', 'letters': /[\u0061\u24D0\uFF41\u1E9A\u00E0\u00E1\u00E2\u1EA7\u1EA5\u1EAB\u1EA9\u00E3\u0101\u0103\u1EB1\u1EAF\u1EB5\u1EB3\u0227\u01E1\u00E4\u01DF\u1EA3\u00E5\u01FB\u01CE\u0201\u0203\u1EA1\u1EAD\u1EB7\u1E01\u0105\u2C65\u0250]/g},
						{'base': 'aa', 'letters': /[\uA733]/g},
						{'base': 'ae', 'letters': /[\u00E6\u01FD\u01E3]/g},
						{'base': 'ao', 'letters': /[\uA735]/g},
						{'base': 'au', 'letters': /[\uA737]/g},
						{'base': 'av', 'letters': /[\uA739\uA73B]/g},
						{'base': 'ay', 'letters': /[\uA73D]/g},
						{'base': 'b', 'letters': /[\u0062\u24D1\uFF42\u1E03\u1E05\u1E07\u0180\u0183\u0253]/g},
						{'base': 'c', 'letters': /[\u0063\u24D2\uFF43\u0107\u0109\u010B\u010D\u00E7\u1E09\u0188\u023C\uA73F\u2184]/g},
						{'base': 'd', 'letters': /[\u0064\u24D3\uFF44\u1E0B\u010F\u1E0D\u1E11\u1E13\u1E0F\u0111\u018C\u0256\u0257\uA77A]/g},
						{'base': 'dz', 'letters': /[\u01F3\u01C6]/g},
						{'base': 'e', 'letters': /[\u0065\u24D4\uFF45\u00E8\u00E9\u00EA\u1EC1\u1EBF\u1EC5\u1EC3\u1EBD\u0113\u1E15\u1E17\u0115\u0117\u00EB\u1EBB\u011B\u0205\u0207\u1EB9\u1EC7\u0229\u1E1D\u0119\u1E19\u1E1B\u0247\u025B\u01DD]/g},
						{'base': 'f', 'letters': /[\u0066\u24D5\uFF46\u1E1F\u0192\uA77C]/g},
						{'base': 'g', 'letters': /[\u0067\u24D6\uFF47\u01F5\u011D\u1E21\u011F\u0121\u01E7\u0123\u01E5\u0260\uA7A1\u1D79\uA77F]/g},
						{'base': 'h', 'letters': /[\u0068\u24D7\uFF48\u0125\u1E23\u1E27\u021F\u1E25\u1E29\u1E2B\u1E96\u0127\u2C68\u2C76\u0265]/g},
						{'base': 'hv', 'letters': /[\u0195]/g},
						{'base': 'i', 'letters': /[\u0069\u24D8\uFF49\u00EC\u00ED\u00EE\u0129\u012B\u012D\u00EF\u1E2F\u1EC9\u01D0\u0209\u020B\u1ECB\u012F\u1E2D\u0268\u0131]/g},
						{'base': 'j', 'letters': /[\u006A\u24D9\uFF4A\u0135\u01F0\u0249]/g},
						{'base': 'k', 'letters': /[\u006B\u24DA\uFF4B\u1E31\u01E9\u1E33\u0137\u1E35\u0199\u2C6A\uA741\uA743\uA745\uA7A3]/g},
						{'base': 'l', 'letters': /[\u006C\u24DB\uFF4C\u0140\u013A\u013E\u1E37\u1E39\u013C\u1E3D\u1E3B\u017F\u0142\u019A\u026B\u2C61\uA749\uA781\uA747]/g},
						{'base': 'lj', 'letters': /[\u01C9]/g},
						{'base': 'm', 'letters': /[\u006D\u24DC\uFF4D\u1E3F\u1E41\u1E43\u0271\u026F]/g},
						{'base': 'n', 'letters': /[\u006E\u24DD\uFF4E\u01F9\u0144\u00F1\u1E45\u0148\u1E47\u0146\u1E4B\u1E49\u019E\u0272\u0149\uA791\uA7A5]/g},
						{'base': 'nj', 'letters': /[\u01CC]/g},
						{'base': 'o', 'letters': /[\u006F\u24DE\uFF4F\u00F2\u00F3\u00F4\u1ED3\u1ED1\u1ED7\u1ED5\u00F5\u1E4D\u022D\u1E4F\u014D\u1E51\u1E53\u014F\u022F\u0231\u00F6\u022B\u1ECF\u0151\u01D2\u020D\u020F\u01A1\u1EDD\u1EDB\u1EE1\u1EDF\u1EE3\u1ECD\u1ED9\u01EB\u01ED\u00F8\u01FF\u0254\uA74B\uA74D\u0275]/g},
						{'base': 'oi', 'letters': /[\u01A3]/g},
						{'base': 'ou', 'letters': /[\u0223]/g},
						{'base': 'oo', 'letters': /[\uA74F]/g},
						{'base': 'p', 'letters': /[\u0070\u24DF\uFF50\u1E55\u1E57\u01A5\u1D7D\uA751\uA753\uA755]/g},
						{'base': 'q', 'letters': /[\u0071\u24E0\uFF51\u024B\uA757\uA759]/g},
						{'base': 'r', 'letters': /[\u0072\u24E1\uFF52\u0155\u1E59\u0159\u0211\u0213\u1E5B\u1E5D\u0157\u1E5F\u024D\u027D\uA75B\uA7A7\uA783]/g},
						{'base': 's', 'letters': /[\u0073\u24E2\uFF53\u00DF\u015B\u1E65\u015D\u1E61\u0161\u1E67\u1E63\u1E69\u0219\u015F\u023F\uA7A9\uA785\u1E9B]/g},
						{'base': 't', 'letters': /[\u0074\u24E3\uFF54\u1E6B\u1E97\u0165\u1E6D\u021B\u0163\u1E71\u1E6F\u0167\u01AD\u0288\u2C66\uA787]/g},
						{'base': 'tz', 'letters': /[\uA729]/g},
						{'base': 'u', 'letters': /[\u0075\u24E4\uFF55\u00F9\u00FA\u00FB\u0169\u1E79\u016B\u1E7B\u016D\u00FC\u01DC\u01D8\u01D6\u01DA\u1EE7\u016F\u0171\u01D4\u0215\u0217\u01B0\u1EEB\u1EE9\u1EEF\u1EED\u1EF1\u1EE5\u1E73\u0173\u1E77\u1E75\u0289]/g},
						{'base': 'v', 'letters': /[\u0076\u24E5\uFF56\u1E7D\u1E7F\u028B\uA75F\u028C]/g},
						{'base': 'vy', 'letters': /[\uA761]/g},
						{'base': 'w', 'letters': /[\u0077\u24E6\uFF57\u1E81\u1E83\u0175\u1E87\u1E85\u1E98\u1E89\u2C73]/g},
						{'base': 'x', 'letters': /[\u0078\u24E7\uFF58\u1E8B\u1E8D]/g},
						{'base': 'y', 'letters': /[\u0079\u24E8\uFF59\u1EF3\u00FD\u0177\u1EF9\u0233\u1E8F\u00FF\u1EF7\u1E99\u1EF5\u01B4\u024F\u1EFF]/g},
						{'base': 'z', 'letters': /[\u007A\u24E9\uFF5A\u017A\u1E91\u017C\u017E\u1E93\u1E95\u01B6\u0225\u0240\u2C6C\uA763]/g}
					];

					for (var i = 0; i < defaultDiacriticsRemovalMap.length; i++) {
						str = str.replace(defaultDiacriticsRemovalMap[i].letters, defaultDiacriticsRemovalMap[i].base);
					}

					return str;
				}

				return removeDiacritics(str.replace(/\s/g, "-").replace(/[!"#$%&'()*+,.\/:;<=>?@[\\\]^`{|}~]/g, "\\$&"));
			}

			/*
			 * Handles an exception thrown somewhere in the code.
			 * @param {Error|String} e: the exception.
			 * @returns {undefined}
			 */
			function handleException(e1) {
				if (e1 === "abandon") {
					return;
				}
				log("Call stack:", new Error().stack);
				var $warning = s("#" + currentOption).find(".warning");
				$warning.attr("data-selector", ".warning");
				if ($warning.length === 0) {
					$warning = s(".mainErrors");
					$warning.attr("data-selector", ".mainErrors");
					if ($warning.length === 0) {
						log("Error: ", e1);
						alert("L'erreur suivante est survenue :\n\n" + e1 + "\n\nMerci de recharger la page.");
						return;
					}
				}
				if ($warning.html() !== "") {
					$warning.append("<br />");
				} else {
					var title = "Effacer les erreurs affichées";
					if (useFontAwesome) {
						$warning.append("<span style='cursor: pointer; font-size: 175%;' title='" + title + "' class='clearErrors fa fa-trash-o'></span>");
					} else {
						$warning.append("<button style='font-size: 90%;' class='clearErrors'>" + title + "</button>");
					}
					$warning.append("<br />");
				}
				log("ExceptionHandler:", e1);
				var str = "Une erreur s'est produite : " + e1 + ".";
				$warning.show().append(str);
				$warning.append("<br />Merci de recharger la page.");
				str += "\n\nMerci de recharger la page.";
				alert(str);
				/*if (getRealType(e1) === "String") {
				 e1 = new Error(e1);
				 }
				 throw e1;*/
			}

			/*
			 * Executes the JVC side of the script.
			 * @returns {undefined}
			 */
			function handleJVC() {
				/*
				 * Adds a direct link on the date of a message, exactly as if it was in a topic.
				 * @param {jQuery selector} $mess: the message.
				 * @param {String} permalink: the link to the message.
				 * @returns {undefined}
				 */
				function addLinkToSelf($mess, permalink) {
					var $dateMess = $mess.find(".bloc-date-msg");
					var dateMessText = $dateMess.text();
					$dateMess.html('<a href="' + permalink + '" target="_blank" class="xXx lien-jv">' + dateMessText + '</a>');
				}

				/*
				 * Adds one or more CSS rules to the page.
				 * @param {String} css: the CSS rules.
				 * @returns {undefined}
				 */
				function addStyle(css) {
					var style = document.createElement('style');
					style.type = 'text/css';
					if (style.styleSheet) {
						style.styleSheet.cssText = css;
					} else {
						style.appendChild(document.createTextNode(css));
					}
					document.head.appendChild(style);
				}

				/*
				 * Adds a jQuery UI theme and removes the previous one (if any).
				 * @param {String} name: the theme.
				 * @returns {undefined}
				 */
				function addTheme(name) {
					var url = "https://ajax.googleapis.com/ajax/libs/jqueryui/1.11.4/themes/" + name + "/jquery-ui.css";
					var el;
					el = document.createElement("link");
					el.className += " toremove";
					el.setAttribute("rel", "stylesheet");
					el.setAttribute("type", "text/css");
					el.setAttribute("href", url);
					document.head.appendChild(el);
				}

				/*
				 * Makes an ajax call and converts JvCare to real links.
				 * @param {String} url: the page to load.
				 * @param {Function} doneCallback: the callback to fire if the page is loaded.
				 * @param {Function} failCallback: the callback to fire if the page could not be loaded.
				 * @returns {undefined}
				 */
				function callAjax(url, doneCallback, failCallback) {
					log("Loading '" + url + "'...");
					var start = performance.now();
					$.ajax({
						type: "GET",
						async: false,
						url: url,
						timeout: 10000
					}).done(function (data, textStatus, jqXHR) {
						log("Page loaded successfully in " + (performance.now() - start) + " ms.");

						var h = $.parseHTML(data);
						var $container = $("<div></div>");
						for (var i = 0; i < h.length; i++) {
							var el = h[i];
							if (typeof el.querySelector !== "undefined") {
								var res = el.querySelector("#forum-main-col");
								if (res !== null) {
									$container.append(res);
									break;
								}
							}
						}
						convertJvCareToRealLink($container);
						data = $container[0].outerHTML;
						doneCallback(data, textStatus, jqXHR);

						return;

						//var $forumMainCol = $(data).find("#forum-main-col");
						var container = document.createElement("div");
						container.innerHTML = data;
						var $forumMainCol = $(container.querySelector("#forum-main-col"));
						//log("$forumMainCol length: " + $forumMainCol.length);
						var $data = $("<div>" + $forumMainCol.html() + "</div>");
						convertJvCareToRealLink($data);
						data = $data[0].outerHTML;
						//log("before doneCallback");
						doneCallback(data, textStatus, jqXHR);
						//log("doneCallback done");
					}).fail(function (jqXHR, textStatus, errorThrown) {
						log("(always) jqXHR:", jqXHR, "\ntextStatus:", textStatus, "\nerrorThrown:", errorThrown);
						if (typeof failCallback !== "undefined") {
							failCallback(jqXHR, textStatus, errorThrown);
						} else {
							log("jqXHR:", jqXHR, "\ntextStatus:", textStatus, "\nerrorThrown:", errorThrown);
							throw "La page <a target='_blank' href='" + url + "'>" + url + "</a> n'a pas pu être chargée. Erreur : '" + errorThrown + "'.";
						}
					});
				}

				/*
				 * Changes the current option.
				 * @param {Number} i: the index of the selected option.
				 * @returns {undefined}
				 */
				function changeCurrentOption(i) {
					currentOption = optionsInfo[i].name;
					if (!useTabs) {
						var item = format(currentOption), temp = "";
						for (var j = 0; j < optionsName.length; j++) {
							temp = format(optionsName[j]);
							if (temp !== item) {
								s("#" + temp).hide();
							}
						}
						s("#" + item).show();
					}
				}

				/*
				 * Hides/shows inputs for min/max limits.
				 * @param {String} type: the type of the min/max limit.
				 * @param {jQuery selector} $limitArea: the min/max area.
				 * @returns {undefined}
				 */
				function changeLimitType(type, $limitArea) {
					var $pageArea = $limitArea.nextAll("#pageArea:first");
					var $permalienArea = $limitArea.nextAll(".permalinkArea:first");
					var $dateArea = $limitArea.nextAll(".dateArea:first");
					switch (type) {
						case "Page":
							$permalienArea.hide();
							$dateArea.hide();
							$pageArea.show();
							break;
						case "Date":
							$pageArea.hide();
							$permalienArea.hide();
							$dateArea.show();
							break;
						case "Permalien":
							$pageArea.hide();
							$dateArea.hide();
							$permalienArea.show();
							break;
						case "Fin":
							$pageArea.hide();
							$dateArea.hide();
							$permalienArea.hide();
							break;
						default:
							throw "Erreur fonction changeLimitType : (" + getRealType(type) + ") type='" + type + "'";
					}
				}

				/*
				 * Converts JvCare classes into real links.
				 * @param {(List of) jQuery selector(s)} $msgs: the messages with JvCare.
				 * @returns {undefined}
				 */
				function convertJvCareToRealLink($msgs) {
					/*
					 * Converts JvCare's info into a URL. Thanks, Kiwec.
					 * @param {String} link: the JvCare info.
					 * @returns {String}: the URL.
					 */
					function jvCake(link) {
						var _base16 = "0A12B34C56D78E9F";
						var str, rurl = "";
						var ch, cl, j, p, d = 0;
						p = link.indexOf(' ');
						d = link.indexOf(' ', p + 1);
						if (d === -1) {
							d = link.length;
						}
						if (p > 0) {
							str = link.substr(p + 1, d - p - 1);
							for (j = 0; j < str.length; j += 2) {
								ch = _base16.indexOf(str.charAt(j));
								cl = _base16.indexOf(str.charAt(j + 1));
								rurl += String.fromCharCode((ch * 16) + cl);
							}
						}
						return rurl;
					}

					$msgs.find(".JvCare").each(function () {
						var userClass = "", url2, contentG;
						if ($(this).is(".bloc-pseudo-msg")) {
							userClass = " bloc-pseudo-msg " + $.grep(this.className.split(" "), function (v, i) {
								return v.indexOf('text-') === 0;
							}).join();
						}
						contentG = $(this).html();
						url2 = jvCake($(this).attr("class"));
						$(this).replaceWith('<a target="_blank" href="' + url2 + '" class="xXx lien-jv' + userClass + '">' +
							contentG + '</a>');
					});
				}

				/*
				 * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent
				 * Properly escapes user input for future usage in an URL.
				 * @param {String} str: the user input.
				 * @returns {String}: the escaped input.
				 */
				function encodeRFC5987ValueChars(str) {
					return encodeURIComponent(str).
						// Note that although RFC3986 reserves "!", RFC5987 does not,
						// so we do not need to escape it
						replace(/['()]/g, escape).// i.e., %27 %28 %29
						replace(/\*/g, '%2A').
						// The following are not required for percent-encoding per RFC5987,
						// so we can allow for a little better readability over the wire: |`^
						replace(/%(?:7C|60|5E)/g, unescape);
				}

				/*
				 * Gets a complete ("Avancée") research form.
				 * @param {Number} formNb: the index of the new form in the current type of research.
				 * @returns {String}: the research form to add.
				 */
				function getAdvancedForm(formNb) {
					/*
					 * Gets a colorpicker area.
					 * @param {String} highlightOrFont: "highlight" or "font".
					 * @param {String} matchOrAll: "match" or "all".
					 * @returns {String}: the colorpicker area.
					 */
					function getColorPickerArea(highlightOrFont, matchOrAll) {
						var text, alt;
						if (matchOrAll === "match") {
							text = "texte correspondant";
							alt = "TC";
						} else {
							text = "reste du message";
							alt = "RDM";
						}
						return "<span data-compact='" + alt + "'>" + text + "</span>&nbsp;" +
							//"<button class='colorpicker " + highlightOrFont + " " + matchOrAll + "'>&nbsp;</button>&nbsp;&nbsp;" +
							"<input type='text' class='colorpicker " + highlightOrFont + " " + matchOrAll + "' />&nbsp;&nbsp;";
					}

					var str, temp = "";

					str = "<div id='researchForm" + formNb + "' class='researchForm' " +
						"data-number='" + formNb + "' data-disabled='no'>" +
						"<b data-compact='R'>Recherche n°</b><b>" + formNb + "</b>";

					var formInputsNames = ["Message", "Auteur", "Image"];

					for (var i = 0; i < formInputsNames.length; i++) {
						temp = format(formInputsNames[i]);
						str += "<div class='input" + temp + " researchInput toggleInline' data-name='" + temp + "'>" +
							"<b data-compact=''>" + formInputsNames[i] + "</b>&nbsp;&nbsp;" +
							"<textarea class='longInput' />&nbsp;" +
							"<span class='researchInputParams'>";
						for (var j = 0; j < formInputsParams.length; j++) {
							if (formInputsNames[i] !== "Message" && formInputsParams[j].name === "textOrHTML") {
								continue;
							}
							if (j > 0) {
								str += "&nbsp;<span data-compact=''>|</span>&nbsp;";
							}
							str += "<span data-param='" + formInputsParams[j].name + "' class='inputParamName' data-compact='" + formInputsParams[j].compactedFirst + "'>" +
								formInputsParams[j].first + "</span>";
						}
						str += "</span>";
						str += "</div>";
					}

					str += "<br /><span class='authorNickname'>" +
						"<span data-compact=''>Pseudo : </span>";
					for (var i = 0; i < authorNicknames.length; i++) {
						//Color is commented out because it may not render well with dark themes (JVC Dark Side 2, for example)
						str += "<label class='boldFont'>" + // style='color: " + authorNicknames[i].color + "'>"
							"<span data-compact='" + authorNicknames[i].type.substring(0, 1) + "'>" + authorNicknames[i].type +
							"</span>&nbsp;<input class='authorNicknames' type='checkbox' value='" + authorNicknames[i].type +
							"' checked='checked'>&nbsp;&nbsp;" +
							"</label>";
					}
					str += "</span>";

					str += "<div class='colorPickers toggleInline'>" +
						"<div class='carriageReturn'>&nbsp;</div>" +
						"<div class='toggleInline'>" +
						"<span data-compact=' S '>Surlignage : </span>" +
						getColorPickerArea("highlight", "match") +
						getColorPickerArea("highlight", "all") +
						"</div>" +
						"<div class='toggleInline'>" +
						"<span data-compact=' T '>Texte : </span>" +
						getColorPickerArea("font", "match") +
						getColorPickerArea("font", "all") +
						"</div>" +
						"<span data-compact=' '>Exemple : </span>" +
						"<span class='resultAllSample' data-compact='foo'>blablabla</span>" +
						"<span class='resultMatchSample' data-compact='bar'>(correspondance)</span>" +
						"<span class='resultAllSample' data-compact='foo'>blablabla</span>&nbsp;&nbsp;" +
						"</div>";

					str += (
						(!doingInit)
						? "<span title='Supprimer ce formulaire' class='del fa fa-minus-circle'></span>"
						: "") +
						"<span title='Ajouter un formulaire' class='add fa fa-plus-circle'></span>" +
						"</div>";
					return str;
				}

				/*
				 * Gets some data about a direct link.
				 * @param {String} permalink: the direct link.
				 * @param {Array} JVCDirectMessages: the array that will contain the message.
				 * @param {Function} failF: the function to fire if an error is triggered.
				 * @returns {Object}: an object containing the anchor and the date of the message.
				 */
				function getDataFromDirectLink(permalink, JVCDirectMessages, failF) {
					//log("in getDataFromDirectLink, permalink:", permalink);
					var permalinkType = getRealType(permalink), ret = {};
					if (permalinkType !== "String" || permalink === "") {
						throw "Erreur fonction getDataFromDirectLink : (" + permalinkType + ") '" + permalink + "' n'est pas un lien direct valide !";
					}
					callAjax(permalink, function (data) {
						//log("getDataFromDirectLink, in callAjax callback, begin");
						var $data = $(data);
						var $msg = $(data).find(".bloc-message-forum:not(.msg-pseudo-blacklist)");
						if ($msg.length === 0) {
							log("getDataFromDirectLink, in callAjax callback, there is not any message!");
							if (typeof failF !== "undefined") {
								failF();
							}
							return;
						}
						//log("before setting ret");
						ret.permalink = "http://" + window.location.hostname + $data.find(".btn-actu-new-list-forum").parent().attr("href");
						//log("ret 1:", ret);
						ret.date = $data.find(".bloc-date-msg:first").text().trim();
						//log("ret 2:", ret);
						if (typeof JVCDirectMessages !== "undefined") {
							var $mess = $data.find(".bloc-message-forum");
							addLinkToSelf($mess, permalink);
							JVCDirectMessages.push($mess[0].outerHTML);
						}
					}, function (jqXHR, textStatus, errorThrown) {
						if (typeof failF !== "undefined") {
							failF(jqXHR, textStatus, errorThrown);
						} else {
							throw "Erreur fonction getDataFromDirectLink : la page '" + permalink + "' n'a pas pu être chargée...";
						}
					});
					return ret;
				}

				/*
				 * Gets a direct link from an anchor.
				 * @param {String|Null} anchor: the anchor. If null, just set permalinkBase.
				 * @param {Object} permalinkBase: if its property isSet is true, then its property str contains the "template" of direct links to messages on this topic.
				 * @returns {String}: the direct link.
				 */
				function getDirectLinkFromAnchor(anchor, permalinkBase) {
					//log("getDirectLinkFromAnchor begin, anchor=" + anchor + ", permalinkBase.isSet=" + permalinkBase.isSet);
					if (permalinkBase.isSet === true) {
						return permalinkBase.str + getPermalinkID(anchor);
					} else { //Get the author's nickname
						var topicAuthor;
						//Current page is 1
						if (realPage === 1) {
							topicAuthor = getMessageData(1, $("#realPage .bloc-message-forum:not(.msg-pseudo-blacklist):first"), "nick");
						} else { //Page 1 is in the cache
							if (usePageCache && s("#page1").length > 0) {
								topicAuthor = getMessageData(1, s("#page1 .bloc-message-forum:not(.msg-pseudo-blacklist):first"), "nick");
							} else { //Load the page 1
								var urlSplit = window.location.href.split("-");
								urlSplit[3] = 1;
								var url = urlSplit.join("-");
								callAjax(url, function (data) {
									var $data = $(data);
									var $msg = $data.find(".bloc-message-forum:not(.msg-pseudo-blacklist)");
									if ($msg.length === 0) {
										throw "Erreur fonction getDirectLinkFromAnchor : la première page n'a pas pu être chargée...";
									}
									loadMsgsIntoCache(1, $msg, url);
									//log("$msg first() html:", $msg.first()[0].outerHTML);
									topicAuthor = getMessageData(1, $msg.first(), "nick");
								});
							}
						}
						permalinkBase.isSet = true;
						permalinkBase.author = topicAuthor.toLowerCase();
						permalinkBase.str = "http://" + window.location.hostname + "/" + encodeRFC5987ValueChars(permalinkBase.author) + "/forums/message/";
						//log("getDirectLinkFromAnchor end, anchor:", anchor, "permalinkBase:", permalinkBase);
						if (anchor !== null) {
							return permalinkBase.str + getPermalinkID(anchor);
						} else {
							return true;
						}
					}
				}

				/*
				 * Gets the forum name for JV Stalker.
				 * @param {String} currentForumId: the current forum's ID.
				 * @returns {String|null}: the forum name, null if N/A for JV Stalker.
				 */
				function getForumName(currentForumId) {
					switch (currentForumId) {
						case "15":
							return "moins15";
						case "50":
							return "bla1518";
						case "51":
							return "bla1825";
						case "1000021":
							return "communaute";
						case "20":
							return "football";
						case "36":
							return "guerredesconsoles";
						case "69":
							return "actualites";
						case "33574":
							return "footballmanager2015";
						case "19163":
							return "leagueoflegends";
						case "30637":
							return "hearthstone";
						default:
							return null;
					}
				}

				/*
				 * Gets some info from a message.
				 * @param {Number|Null} page: the number of the page where the message is. Null if N/A.
				 * @param {jQuery selector} $mess: the selector of the message.
				 * @param {String} info: the info we should get.
				 * @param (undefined|Boolean) retSel: if undefined or false, returns the text().trim() info. If true, returns the selector.
				 * @returns {String}: the info.
				 */
				function getMessageData(page, $mess, info, retSel) {
					/*
					 * Converts a JVC date string into a JS date.
					 * @param {String} dateParam: a JVC date string. Example: "01 janvier 2015 à 00:00:00".
					 * @returns {Date}: the real date.
					 */
					function convertToTrueDate(dateParam) {
						/*
						 * Gets the index of a French month.
						 * @param {String} m: the month.
						 * @returns {Number}: the index.
						 */
						function getMonthNumber(m) {
							var frenchMonths = ["janvier", "février", "mars", "avril", "mai", "juin", "juillet", "août", "septembre", "octobre", "novembre", "décembre"];
							return frenchMonths.indexOf(m.toLowerCase()) + 1;
						}

						dateParam = dateParam.replace(/\n/g, '');
						var useRegex = false; //indexOf is a little faster
						//JJ mois AAAA à HH:MM:SS
						if (useRegex) {
							var reg = /(.{2}) (.+) (.{4}) à (.{2}):(.{2}):(.{2})/;
							var t = dateParam.match(reg);
							if (t === null) {
								throw "Erreur fonction convertToTrueDate : (" + getRealType(dateParam) + ") dateParam='" + dateParam + "'";
							}
							var trueDate = new Date(t[3], (getMonthNumber(t[2]) - 1), t[1], t[4], t[5], t[6]);
							return trueDate;
						} else {
							//Day
							var day = dateParam.substring(0, 2);
							//Month
							dateParam = dateParam.substring(3);
							var monthEnd = dateParam.indexOf(" ");
							var month = getMonthNumber(dateParam.substring(0, monthEnd));
							//Year
							dateParam = dateParam.substring(monthEnd + 1);
							var yearEnd = dateParam.indexOf(" ");
							var year = dateParam.substring(0, yearEnd);
							//Hour
							dateParam = dateParam.substring(yearEnd + 3);
							var hourEnd = dateParam.indexOf(":");
							var hour = dateParam.substring(0, hourEnd);
							//Minute
							dateParam = dateParam.substring(hourEnd + 1);
							var minuteEnd = dateParam.indexOf(":");
							var minute = dateParam.substring(0, minuteEnd);
							//Second
							dateParam = dateParam.substring(minuteEnd + 1);
							var second = dateParam.substring(0, 2);
							//log("'" + dateParam + "' becomes (d/m/y à h:m:s): " + day + "/" + month + "/" + year + " à " + hour + ":" + minute + ":" + second);
							var trueDate = new Date(year, month - 1, day, hour, minute, second);
							//log("trueDate is " + trueDate);
							return trueDate;
						}
					}

					//log("getMessageData begin, page=" + page + ", $mess.length: " + $mess.length + ", $mess.html():", $mess.html(), "info='" + info + "'");
					if (getRealType($mess) === "HTMLDivElement") {
						$mess = $($mess);
					}
					switch (info) {
						case "nick":
							var $e = $mess.find(".bloc-pseudo-msg");
							if (retSel === true) {
								return $e;
							} else {
								return $e.text().trim();
							}
							break;
						case "date":
							var $e = $mess.find(".bloc-date-msg");
							if (retSel === true) {
								return $e;
							} else {
								return convertToTrueDate($e.text().trim());
							}
							break;
						case "msgAnchor":
							var urlSplit = window.location.href.split("-");
							urlSplit[3] = page;
							return urlSplit.join("-") + "#post_" + $mess.attr("data-id");
						case "msgDirect":
							if ($mess.find(".bloc-date-msg a").length === 0) {
								return null;
							}
							return "http://" + window.location.hostname + $mess.find(".bloc-date-msg a").attr("href");
							break;
						default:
							throw "Erreur switch getMessageData: (" + getRealType(info) + ") info='" + info + "'";
					}
				}

				/*
				 * Gets some info about the first or last message of a page.
				 * @param {String firstOrLast: gets the "first" or "last" message of the page.
				 * @param {Number} pageToLoad: the number of the page where the message is.
				 * @returns {Object}: an object containing the direct link and the date of the message.
				 */
				function getMessageDataFromPage(firstOrLast, pageToLoad) {
					var ret = {}, divID;
					getPageContent(pageToLoad);
					if (usePageCache) {
						divID = "#page" + pageToLoad;
					} else {
						divID = "#page";
					}
					ret.permalink = getMessageData(pageToLoad, s(divID + " .bloc-message-forum:not(.msg-pseudo-blacklist):" + firstOrLast), "msgDirect");
					ret.date = getMessageData(pageToLoad, s(divID + " .bloc-message-forum:not(.msg-pseudo-blacklist):" + firstOrLast), "date");
					return ret;
				}

				/*
				 * Gets the content of a page and stores the messages of the page into the cache.
				 * @param {Number} (not always the case) pageNb: the number of the page to load.
				 * @returns {undefined}
				 */
				function getPageContent(pageNb) {
					var pageNbType = getRealType(pageNb);
					if (pageNbType !== "Number") {
						if (isNaN(pageNb)) {
							throw "Erreur fonction getPageContent : (" + pageNbType + ") pageNb='" + pageNb + "'";
						} else {
							pageNb = parseInt(pageNb, 10);
						}
					}
					if (pageNb < 1) {
						throw "Erreur fonction getPageContent : (" + pageNbType + ") pageNb='" + pageNb + "' est inférieur à 1";
					}
					if (usePageCache && s("#page" + pageNb).length > 0) {
						//log("Page " + pageNb + " is already cached.");
						return;
					}
					var $msgs, urlSplit = window.location.href.split("-");
					urlSplit[3] = pageNb;
					var url = urlSplit.join("-");
					if (pageNb === realPage && usePageCache) {
						//log("Page " + pageNb + " is the current page.");
						$msgs = $("#realPage .bloc-message-forum:not(.msg-pseudo-blacklist)").clone();
						loadMsgsIntoCache(pageNb, $msgs, url);
						return;
					}
					callAjax(url, function (data) {
						$msgs = $(data).find(".bloc-message-forum:not(.msg-pseudo-blacklist)");
						if ($msgs.length === 0) {
							throw "Erreur fonction getPageContent : la page " + pageNb + " n'a pas pu être chargée...";
						}
						loadMsgsIntoCache(pageNb, $msgs, url);
					});
				}

				/*
				 * Gets the page number of a URL.
				 * @param {String} url: the URL.
				 * @returns {Number}: the page number.
				 */
				function getPageNumberFromURL(url) {
					return parseInt(String(url).split('-')[3], 10);
				}

				/*
				 * Gets the page URL of a permalink.
				 * @param {String} permalink: the permalink to use.
				 * @returns {String}: the page URL.
				 */
				function getPageURLFromPermalink(permalink) {
					var permalinkType = getRealType(permalink);
					log("in getPageURLFromPermalink, permalink:", permalink, "permalinkType:", permalinkType);
					if (permalinkType !== "String" || permalink === "") {
						throw "Erreur fonction getPageURLFromPermalink : (" + permalinkType + ") '" + permalink + "' n'est pas un permalien valide !";
					}
					var i = permalink.lastIndexOf("#");
					//Anchor
					if (i >= 0) {
						log("permalink is anchor");
						return permalink.substring(0, i);
					}
					//Direct link
					log("permalink is direct link");
					var ret = getDataFromDirectLink(permalink);
					log("getDataFromDirectLink ret:", ret);
					ret = ret.permalink;
					i = ret.lastIndexOf("#");
					ret = ret.substring(0, i);
					return ret;
				}

				/*
				 * Gets the identifier (data-id) of a message.
				 * @param {String} permalink: the permalink (anchor or direct link).
				 * @returns {Number}: the id.
				 */
				function getPermalinkID(permalink) {
					if (permalink === null) {
						return 0;
					}
					var type = getRealType(permalink);
					if (type !== "String" || permalink === "") {
						throw "Erreur fonction getPermalinkID : (" + type + ") permalink='" + permalink + "'";
					}
					if (permalink === "Date" || permalink === "Fin") {
						return 0;
					}
					var reg = /([0-9]+)$/m, m = null;
					m = permalink.match(reg);
					if (m === null) {
						throw "Erreur fonction getPermalinkID : (" + type + ") permalink='" + permalink + "' (id introuvable)";
					}
					return parseInt(m[1], 10);
				}

				/*
				 * Wraps pause and abandon checks.
				 * @param {String} functionName: the name of the function.
				 * @param {Function|Null} rej: the "reject" function of the caller.
				 * @param {Function} a: the function to call on abandon.
				 * @param {Function} u: the function to call on unpause (usually the caller).
				 * @returns {Boolean}: true if the caller should end its execution (return) after having called this function, false otherwise.
				 */
				function handlePauseAndAbandon(functionName, rej, a, u) {
					if (pauseRech || abandonRech) {
						if (pauseRech) {
							//log(functionName + ": I have been told to pause.");
						}
						var pauseLoop = setInterval(function () {
							try {
								if (abandonRech) {
									//log(functionName + ": I have been told to abandon.");
									clearInterval(pauseLoop);
									abandonRech = false;
									doingAResearch = false;
									unpause();
									if (a !== null) {
										a();
									}
									if (rej !== null) {
										rej("abandon");
									}
									return;
								}
								if (!pauseRech) {
									//log(functionName + ": I have been told to continue.");
									clearInterval(pauseLoop);
									u();
									return;
								}
							} catch (e) {
								if (rej !== null) {
									rej(e);
								}
							}
						}, 250);
						return true;
					} else {
						return false;
					}
				}

				/*
				 * Initializes the colorpickers. Fires on loadJVCScript() and when a new form is added.
				 * @param {undefined|jQuery selector} $newForm: if undefined, inits all the colorpickers. If defined, inits only the colorpickers in this new form.
				 * @returns {undefined}
				 */
				function initColorPickers($newForm) {
					var $sel = (typeof $newForm === "undefined" ? s('.colorpicker') : $newForm.find('.colorpicker'));
					$sel.each(function (index) {
						/*var colP = "ffffff";
						 if ($(this).hasClass("font")) {
						 colP = "000000";
						 }*/
						$(this).spectrum({
							//color: '#' + colP,
							color: null,
							showAlpha: true,
							showInput: true,
							clickoutFiresChange: true,
							allowEmpty: true,
							appendTo: "parent",
							chooseText: "OK",
							cancelText: "Annuler",
							//showButtons: false,
							move: function (color) {
								try {
									if (color === null) {
										$(this).prev().removeAttr('style');
										var $colorPickersWrapper = $(this).parents("div.colorPickers:first");
										if ($(this).hasClass("match")) {
											($(this).hasClass("highlight"))
												? $colorPickersWrapper.find(".resultMatchSample").css('background-color', '')
												: $colorPickersWrapper.find(".resultMatchSample").css('color', '');
										} else {
											($(this).hasClass("highlight"))
												? $colorPickersWrapper.find(".resultAllSample").css('background-color', '')
												: $colorPickersWrapper.find(".resultAllSample").css('color', '');
										}
									} else {
										if (improveColorSelectorReadability) {
											var o = Math.round(((parseInt(color._r, 10) * 299) + (parseInt(color._g, 10) * 587) + (parseInt(color._b, 10) * 114)) / 1000);
											if (o > 125) {
												($(this).hasClass("highlight"))
													? $(this).prev().css('color', 'black')
													: $(this).prev().css('background-color', 'black');
											} else {
												($(this).hasClass("highlight"))
													? $(this).prev().css('color', 'white')
													: $(this).prev().css('background-color', 'white');
											}
										}
										($(this).hasClass("highlight")) ? $(this).prev().css('background-color', color.toRgbString()) : $(this).prev().css('color', color.toRgbString());
										var $colorPickersWrapper = $(this).parents("div.colorPickers:first");
										if ($(this).hasClass("match")) {
											($(this).hasClass("highlight"))
												? $colorPickersWrapper.find(".resultMatchSample").css('background-color', color.toRgbString())
												: $colorPickersWrapper.find(".resultMatchSample").css('color', color.toRgbString());
										} else {
											($(this).hasClass("highlight"))
												? $colorPickersWrapper.find(".resultAllSample").css('background-color', color.toRgbString())
												: $colorPickersWrapper.find(".resultAllSample").css('color', color.toRgbString());
										}
									}
								} catch (e) {
									handleException(e);
								}
							}
						});
						/*$(this).colpick({
						 color: colP,
						 submit: 0,
						 onChange: function (hsb, hex, rgb, $colorPicker, bySetColor) {
						 var o = Math.round(((parseInt(rgb.r, 10) * 299) + (parseInt(rgb.g, 10) * 587) + (parseInt(rgb.b, 10) * 114)) / 1000);
						 if (o > 125) {
						 ($colorPicker.hasClass("highlight"))
						 ? $colorPicker.prev().css('color', 'black')
						 : $colorPicker.prev().css('background-color', 'black');
						 } else {
						 ($colorPicker.hasClass("highlight"))
						 ? $colorPicker.prev().css('color', 'white')
						 : $colorPicker.prev().css('background-color', 'white');
						 }

						 ($colorPicker.hasClass("highlight")) ? $colorPicker.prev().css('background-color', '#' + hex) : $colorPicker.prev().css('color', '#' + hex);
						 var elem = $colorPicker.parents("div.colorPickers:first");
						 if ($colorPicker.hasClass("match")) {
						 ($colorPicker.hasClass("highlight"))
						 ? $colorPickersWrapper.find(".resultMatchSample").css('background-color', '#' + hex)
						 : $colorPickersWrapper.find(".resultMatchSample").css('color', '#' + hex);
						 } else {
						 ($colorPicker.hasClass("highlight"))
						 ? $colorPickersWrapper.find(".resultAllSample").css('background-color', '#' + hex)
						 : $colorPickersWrapper.find(".resultAllSample").css('color', '#' + hex);
						 }
						 }});*/
					});
				}

				/*
				 * Creates the first form in a research option, along with the research options.
				 * @param {jQuery selector} $place: the research option.
				 * @returns {undefined}
				 */
				function initializeResearchOption($place) {
					/*
					 * Gets the research options.
					 * @returns {String}: the complete options, which will be put below the research forms.
					 */
					function getResearchOptions() {
						/*
						 * Gets the limit type selector ("Page", "Date", etc.) for a specific limit ("Départ" or "Arrivée").
						 * @param {String} type: the limit type ("Départ" or "Avancée").
						 * @returns {String}: the limit method selector.
						 */
						function getLimitTypeSelector(type) {
							return "<span id='" + ((type === "Départ") ? "min" : "max") + "LimitSelector'>" +
								"<b data-compact='" + type.substring(0, 1) + " '>" + type + " : </b>" +
								"<select class='limitType " + type + "'>" +
								"<option value='Page'>Page</option>" +
								"<option value='Date'>Date</option>" +
								"<option value='Permalien'>Permalien</option>" +
								((type === "Arrivée") ? "<option value='Fin' selected='selected'>Fin</option>" : "") +
								"</select>&nbsp;" +
								"<input id='pageArea' type='number' min='1' max='" + lastP + "'>" +
								"<textarea class='normalSize permalinkArea' />" +
								"<input class='dateArea' type='text'>" +
								"</span>";
						}

						/*
						 * Gets the area where will be shown some info about the min/max messages.
						 * @param {String} limit: "min" or "max" message.
						 * @returns {String}: the area.
						 */
						function getMinMaxMessagesInfoArea(limit) {
							return "<span id='" + ((limit === "max") ? "lastPermalink" : "firstPermalink") + "Area'>&nbsp;&nbsp;" +
								"<span class='dateProgress'></span>" +
								"<a data-message-id='" + ((limit === "max")
									? "lastMessage"
									: "firstMessage") +
								"' class='showMessageOnHover link' target='_blank' href='#'>" +
								((limit === "max") ? "Arrivée" : "Départ") +
								"</a>" +
								"</span>";
						}

						var toAppend = getLimitTypeSelector("Départ") +
							"&nbsp;&nbsp;&nbsp;" +
							getLimitTypeSelector("Arrivée") +
							"<br /><div class='chooseResultsOrder toggleInline'>" +
							"<br />" +
							"<span data-compact=' '>Affichage des résultats :</span> " +
							"<label>" +
							"<span data-compact='Tps'>Ordre chronologique&nbsp;</span>&nbsp;" +
							"<input type='radio' name='resultsOrderType' value='Temps'>" +
							"</label>" +
							"&nbsp;&nbsp;<label>" +
							"<span data-compact='Rech.'>Par recherche&nbsp;</span>&nbsp;" +
							"<input type='radio' name='resultsOrderType' value='Recherche'>" +
							"</label>" +
							"&nbsp;&nbsp;<label>" +
							"<span data-compact='Aut.'>Par auteur&nbsp;</span>&nbsp;" +
							"<input type='radio' name='resultsOrderType' value='Auteur'>" +
							"</label>&nbsp;&nbsp;&nbsp;" +
							"</div>" +
							"<div class='toggleInline Inv'>" +
							"<label>" +
							"<span data-compact='Inv. '>Partir de l'arrivée vers le début : </span>" +
							"<input id='readBackwards' type='checkbox'>" +
							"</label>" +
							"</span>" +
							"<div class='carriageReturn'>&nbsp;</div>" +
							"</div>&nbsp;&nbsp;" +
							"<button class='executeResearch'></button><br /><br />";
						var title = "Copier les permaliens des résultats";
						if (useFontAwesome) {
							toAppend += "<span title='" + title + "' class='copyResultsToClipboard fa fa-clipboard'></span>";
						} else {
							toAppend += "<button class='copyResultsToClipboard'>" + title + "</button>";
						}
						toAppend += "<span id='minMaxInfo'>" +
							getMinMaxMessagesInfoArea("min") +
							getMinMaxMessagesInfoArea("max") +
							"</span>";
						title = "Mettre la recherche en pause";
						if (useFontAwesome) {
							toAppend += "<span title='" + title + "' class='pauseResearch fa fa-pause'></span>";
						} else {
							toAppend += "<button class='pauseResearch'>" + title + "</button>";
						}
						toAppend += "<div id='firstMessage' class='hideObject'></div>" +
							"<div id='lastMessage' class='hideObject'></div>";
						return toAppend;
					}

					$place.append("<div data-compact=''>" +
						"Rechercher des messages vérifiant tous les critères indiqués pour chaque recherche :</div>" +
						getAdvancedForm(1) + getResearchOptions());
					changeLimitType("Page", $place.find(".Départ.limitType"));
					changeLimitType("Fin", $place.find(".Arrivée.limitType"));
					$place.find("input[name='resultsOrderType']").attr("name", $place.attr("id") + "ResultsOrderType");
					$place.find("input[name='" + $place.attr("id") + "ResultsOrderType']:first").click();
					$place.find("#minMaxInfo a").hide();
					$place.find(".pauseResearch").hide();
					$place.find("#minLimitSelector #pageArea").val(1);
					$place.find("#maxLimitSelector #pageArea").val(lastP);
					initColorPickers();
					var today = new Date();
					var $dateArea = $place.find("#maxLimitSelector .dateArea");
					$dateArea.datetimepicker({changeYear: true,
						yearRange: "1997:+0",
						maxDate: '0',
						defaultDate: today});
					$dateArea.datetimepicker('setDate', today);
					var yesterday = today;
					yesterday.setDate(yesterday.getDate() - 1);
					$dateArea = $place.find("#minLimitSelector .dateArea");
					$dateArea.datetimepicker({changeYear: true,
						yearRange: "1997:+0",
						maxDate: '0',
						defaultDate: yesterday});
					$dateArea.datetimepicker('setDate', yesterday);
				}

				/*
				 * (Re)loads the JVC side of the script.
				 * @returns {undefined}
				 */
				function loadJVCScript() {
					/*
					 * Adds event listeners.
					 * @returns {undefined}
					 */
					function addEventHandlers() {
						/*
						 * Hides or shows a message. Used for min/max messages.
						 * @param {String} id: the id of the message to hide/show: "firstMessage" or "lastMessage".
						 * @returns {undefined}
						 */
						function hideOrShowMessage(id) {
							var $mess = s("research").find("#" + id);
							$mess.toggleClass("hideObject");
						}

						/*
						 * Shows the last add button and hides the other ones.
						 * @returns {undefined}
						 */
						function manageTheAddButtons() {
							var $lastActiveElement = s("current").find(".researchForm[data-disabled='no']:last");
							$lastActiveElement.parent().find(".researchForm").each(function (index) {
								if ($(this).attr("data-disabled") === "no") {
									$(this).find(".add:first").hide();
								}
							});
							$lastActiveElement.find(".add:first").show();
						}

						window.addEventListener('beforeunload', function (event) {
							try {
								if (doingAResearch && typeof event !== "undefined") {
									event.preventDefault();
								}
							} catch (e) {
								handleException(e);
							}
						}, false);

						s().on("click", ".executeResearch", function (event) {
							try {
								startResearch();
							} catch (e) {
								handleException(e);
							}
						}).on("click", ".copyResultsToClipboard", function (event) {
							try {
								var res = "", $scriptresult = s("current").find(".scriptresult");
								if ($scriptresult.length === 0) {
									alert("Aucun résultat n'a été trouvé...");
									return;
								}
								$scriptresult.each(function () {
									var $link = $(this).find(".bloc-date-msg a");
									if ($link.length > 0) {
										var href = "http://" + window.location.hostname + $link.attr("href");
										res += href + "\n";
									}
								});
								$("#tempTextarea").remove();
								var tempTextarea = document.createElement("textarea");
								tempTextarea.style.width = "0px";
								tempTextarea.style.height = "0px";
								tempTextarea.value = res;
								tempTextarea.id = "tempTextarea";
								var x = window.scrollX, y = window.scrollY;
								if (scriptIsAtTheTop) {
									document.body.insertBefore(tempTextarea, document.body.firstChild);
								} else {
									document.body.appendChild(tempTextarea);
								}
								tempTextarea.addEventListener("copy", function () {
									alert((res.split("\n").length - 1) + " permaliens ont été mis dans votre presse-papiers.");
									var that = this;
									setTimeout(function () {
										that.remove();
									}, 0);
								}, false);
								alert("Après avoir fermé cette notification, veuillez utiliser la combinaison de touches appropriée pour copier du texte.");
								tempTextarea.focus();
								tempTextarea.selectionStart = 0;
								tempTextarea.selectionEnd = res.length;
								setTimeout(function () {
									window.scrollTo(x, y);
								}, 25);
							} catch (e) {
								handleException(e);
							}
						}).on("click", ".clearErrors", function (event) {
							try {
								s($(this).parent().attr("data-selector")).empty();
							} catch (e) {
								handleException(e);
							}
						}).on("keypress", "textarea, input[type!='radio']", function (event) {
							try {
								if (carriageReturnMeansGo) {
									var keyCode = event.keyCode;
									if (keyCode === 13) {
										event.stopPropagation();
										event.preventDefault();
										startResearch();
									}
								}
							} catch (e) {
								handleException(e);
							}
						}).on("click", ".pauseResearch", function (event) {
							try {
								pause();
							} catch (e) {
								handleException(e);
							}
						}).on("click", ".unpauseResearch", function (event) {
							try {
								unpause();
							} catch (e) {
								handleException(e);
							}
						}).on("click", ".JVStalkerHideOrShowIframe", function (event) {
							try {
								var $JVStalkerIframeWrapper = s("#JVStalkerIframe").parent();
								if ($JVStalkerIframeWrapper.is(":visible")) {
									$JVStalkerIframeWrapper.hide();
									var title = "Afficher la fenêtre JV Stalker";
									if (useFontAwesome) {
										s(".JVStalkerHideOrShowIframe:first").addClass("fa-eye").removeClass("fa-eye-slash").attr("title", title);
									} else {
										s(".JVStalkerHideOrShowIframe:first").html(title);
									}
									s(".JVStalkerHideOrShowIframe:last").hide();
								} else {
									$JVStalkerIframeWrapper.show();
									var title = "Cacher la fenêtre JV Stalker";
									if (useFontAwesome) {
										s(".JVStalkerHideOrShowIframe:first").addClass("fa-eye-slash").removeClass("fa-eye").attr("title", title);
									} else {
										s(".JVStalkerHideOrShowIframe:first").html(title);
									}
									s(".JVStalkerHideOrShowIframe:last").show();
								}
							} catch (e) {
								handleException(e);
							}
						}).on("click", ".hideOrShowJVCResults", function (event) {
							try {
								var $JVCVersionOfResults = s("#JVStalkerJVCVersionOfResults");
								if ($JVCVersionOfResults.is(":visible")) {
									$JVCVersionOfResults.hide();
									var title = "Afficher les messages version JVC";
									if (useFontAwesome) {
										s(".hideOrShowJVCResults").removeClass("fa-eye-slash").addClass("fa-eye").attr("title", title);
									} else {
										s(".hideOrShowJVCResults").html(title);
									}
									s(".hideOrShowJVCResults:last").hide();
								} else {
									$JVCVersionOfResults.show();
									var title = "Cacher les messages version JVC";
									if (useFontAwesome) {
										s(".hideOrShowJVCResults").removeClass("fa-eye").addClass("fa-eye-slash").attr("title", title);
									} else {
										s(".hideOrShowJVCResults").html(title);
									}
									s(".hideOrShowJVCResults:last").show();
								}
							} catch (e) {
								handleException(e);
							}
						}).on("click", ".hideResults, .showResults", function (event) {
							try {
								if ($(this).hasClass("hideResults")) {
									var $firstButton = s("current").find(".hideResults:first");
									$firstButton.nextAll().hide();
									$firstButton.removeClass("hideResults").addClass("showResults");
									var title = "Afficher les résultats";
									if (useFontAwesome) {
										$firstButton.removeClass("fa-eye-slash").addClass("fa-eye").attr("title", title);
									} else {
										$firstButton.html(title);
									}
								} else {
									var $firstButton = s("current").find(".showResults:first");
									$firstButton.nextAll().show();
									$firstButton.removeClass("showResults").addClass("hideResults");
									var title = "Cacher les résultats";
									if (useFontAwesome) {
										$(this).removeClass("fa-eye").addClass("fa-eye-slash").attr("title", title);
									} else {
										$(this).html(title);
									}
								}
							} catch (e) {
								handleException(e);
							}
						}).on("click", "#results .sortElement", function (event) {
							try {
								var type = $(this).text();
								//log("Click: sorting with '" + type + "'...");
								sortMessages(type, true);
								//log("Click: sorting with '" + type + "' done.");
							} catch (e) {
								handleException(e);
							}
						}).on("click", ".structureOption", function (event) {
							try {
								changeCurrentOption($(this).attr("data-index"));
							} catch (e) {
								handleException(e);
							}
						}).on("click", "#hideOrShow", function (event) {
							try {
								hideOrShowScript();
							} catch (e) {
								handleException(e);
							}
						}).on("click", "#reduceOrExtend", function (event) {
							try {
								reduceOrExtendButton();
							} catch (e) {
								handleException(e);
							}
						}).on("click", "#hideOrShowPage", function (event) {
							try {
								var $realPage = $("#realPage");
								var $pageElements = $realPage.nextAll().andSelf().filter(function () {
									return this.id !== "PostFinder";
								});
								if ($realPage.is(":visible")) {
									$pageElements.hide();
									var title = "Afficher la page";
									if (useFontAwesome) {
										s("#hideOrShowPage").removeClass("fa-eye-slash").addClass("fa-eye").attr("title", title);
									} else {
										s("#hideOrShowPage").html(title);
									}
								} else {
									$pageElements.show();
									var title = "Cacher la page";
									if (useFontAwesome) {
										s("#hideOrShowPage").removeClass("fa-eye").addClass("fa-eye-slash").attr("title", title);
									} else {
										s("#hideOrShowPage").html(title);
									}
								}
							} catch (e) {
								handleException(e);
							}
						}).on("click", "#emptyCache", function (event) {
							try {
								if (doingAResearch) {
									alert("Merci de terminer la recherche en cours avant de vider le cache.");
									return;
								}
								var r = confirm("Voulez-vous vraiment vider le cache de PostFinder ?\n\nSi oui, alors la prochaine recherche sera plus lente, mais fournira des résultats à jour.");
								if (r) {
									s(".pageCache").remove();
								}
							} catch (e) {
								handleException(e);
							}
						}).on("click", "#reload", function (event) {
							try {
								if (doingAResearch) {
									alert("Merci de terminer ou d'abandonner la recherche en cours avant de recharger PostFinder.");
									return;
								}
								s().remove();
								loadJVCScript();
							} catch (e) {
								handleException(e);
							}
						}).on("click", ".add", function (event) {
							try {
								//Show a previously hidden form if it exists...
								if (previouslyHiddenFormsID[currentOption].length > 0) {
									var lastHiddenFormID = previouslyHiddenFormsID[currentOption].pop();
									s("current").find("#" + lastHiddenFormID).show().attr("data-disabled", "no");
								}
								//... or create a new form.
								else {
									var nbActiveForms = s("current").find(".researchForm[data-disabled='no']").length + 1;
									var $currentResearchForm = $(this).parents(".researchForm:first");
									$currentResearchForm.after(getAdvancedForm(nbActiveForms));
									var $newForm = s("current").find(".researchForm[data-disabled='no']:last");
									//If the current mode is "compact", then compact the new form
									if (!isExtended) {
										reduceOrExtend($newForm);
									}
									initColorPickers($newForm);
									//Removes advanced items if research is "Simple"
									var index = optionsName.indexOf(currentOption);
									if (optionsInfo[index].hasOwnProperty("afterNewForm")) {
										optionsInfo[index].afterNewForm();
									}
								}
								manageTheAddButtons();
							} catch (e) {
								handleException(e);
							}
						}).on("click", ".del", function (event) {
							try {
								var parentID = $(this).parents(".researchForm:first").attr("id");
								s("current").find("#" + parentID).attr("data-disabled", "yes").hide();
								previouslyHiddenFormsID[currentOption].push(parentID);
								manageTheAddButtons();
							} catch (e) {
								handleException(e);
							}
						}).on("click", ".inputParamName", function (event) {
							try {
								var currentText = $(this).html();
								var tabIndex = -1, n = $(this).attr("data-param");
								for (var i = 0; i < formInputsParams.length; i++) {
									if (formInputsParams[i].name === n) {
										tabIndex = i;
										break;
									}
								}
								var first = formInputsParams[tabIndex].first;
								var compactedFirst = formInputsParams[tabIndex].compactedFirst;
								var second = formInputsParams[tabIndex].second;
								var compactedSecond = formInputsParams[tabIndex].compactedSecond;
								var trueFirst = ((isExtended) ? first : compactedFirst);
								var trueSecond = ((isExtended) ? second : compactedSecond);
								var titleFirst = ((isExtended) ? compactedFirst : first);
								var titleSecond = ((isExtended) ? compactedSecond : second);
								if (currentText.replace(/["']/g, "") === trueSecond.replace(/["']/g, "")) {
									$(this).html(trueFirst).attr("data-compact", titleFirst).attr("title", titleFirst);
								} else {
									$(this).html(trueSecond).attr("data-compact", titleSecond).attr("title", titleSecond);
								}
							} catch (e) {
								handleException(e);
							}
						}).on("change", "input[type='checkbox'], input[type='radio']", function (event) {
							try {
								var type = $(this).attr("type");
								switch (type) {
									case "checkbox":
										$(this).parent().toggleClass("boldFont");
										break;
									case "radio":
										var selectedName = $(this).attr("name");
										var selectedValue = $(this).attr("value");
										s("input[type='radio'][name='" + selectedName + "'][value='" + selectedValue + "']").parent().addClass("boldFont");
										s("input[type='radio'][name='" + selectedName + "'][value!='" + selectedValue + "']").parent().removeClass("boldFont");
										break;
									default: //... Should never happen
										throw "Erreur fonction improveReadability : (" + getRealType(type) + ") type='" + type + "'";
								}
							} catch (e) {
								handleException(e);
							}
						}).on("change", ".parametersRadioButton", function (event) {
							try {
								if (doingInit) {
									return;
								}
								var param = parametersInfo[$(this).attr("data-index")];
								if (param.hasOwnProperty("onChange")) {
									param.onChange(format($(this).attr("id")));
								} else {
									var opts = param.display.options;
									if (typeof opts === "function") {
										opts = opts();
									}
									//By default, we assume that there are only 2 possible options (opts[0] and opts[1]), so the parameter's value is a boolean. The first option means "true".
									var res = ($(this).attr("value") === opts[0]);
									var n = param.name;
									localStorage.setItem(prefix + n, res);
									window[n] = res;
								}
							} catch (e) {
								handleException(e);
							}
						}).on("change", ".limitType", function (event) {
							try {
								changeLimitType($(this).find("option:selected").attr("value"), $(this));
							} catch (e) {
								handleException(e);
							}
						}).on({
							mouseenter: function (e) {
								try {
									hideOrShowMessage($(this).attr("data-message-id"));
								} catch (err) {
									handleException(err);
								}
							},
							mouseleave: function (e) {
								try {
									hideOrShowMessage($(this).attr("data-message-id"));
								} catch (err) {
									handleException(err);
								}
							}
						}, ".showMessageOnHover");
					}

					/*
					 * Gets the current page number.
					 * @returns {Number}: the current page number.
					 */
					function getCurrentPageNumber() {
						return parseInt(String(window.location.href).split('-')[3], 10);
					}

					/*
					 * Gets the nth index of a pattern in a string.
					 * @param {String} str: the string.
					 * @param {String} pat: the pattern.
					 * @param {Number} n: the nth index.
					 * @returns {Number}
					 */
					function getNthIndex(str, pat, n) {
						var L = str.length, i = -1;
						while (n-- && i++ < L) {
							i = str.indexOf(pat, i);
						}
						return i;
					}

					/*
					 * Hides or shows the script.
					 * @returns {undefined}
					 */
					function hideOrShowScript() {
						if (s("#options").is(":visible")) {
							s("#options").hide();
							var title = "Afficher le script";
							if (useFontAwesome) {
								s("#hideOrShow").removeClass("fa-minus-square").addClass("fa-plus-square").attr("title", title);
							} else {
								s("#hideOrShow").html(title);
							}
						} else {
							s("#options").show();
							var title = "Cacher le script";
							if (useFontAwesome) {
								s("#hideOrShow").removeClass("fa-plus-square").addClass("fa-minus-square").attr("title", title);
							} else {
								s("#hideOrShow").html(title);
							}
						}
					}

					/*
					 * Loads the content of the options ("Simple", etc.).
					 * @returns {undefined}
					 */
					function loadContent() {
						for (var i = 0; i < optionsInfo.length; i++) {
							if (optionsInfo[i].hasOwnProperty("load")) {
								optionsInfo[i].load();
							}
							if (optionsInfo[i].hasOwnProperty("afterNewForm")) {
								optionsInfo[i].afterNewForm();
							}
						}
					}

					/*
					 * Loads the structure of the options ("Simple", "Avancée", etc.).
					 * @returns {undefined}
					 */
					function loadStructure() {
						var formattedOption, selectors, containers = "", strFinal, formattedDefaultOption = format(defaultOption), labelWidth = (100 / optionsName.length) - 1;
						if (useTabs) {
							selectors = "<div id='tabs' style='margin-top: 10px;'><ul>";
						} else {
							selectors = "<div style='margin-top: 10px; margin-bottom: 10px;'>";
						}
						for (var i = 0; i < optionsName.length; i++) {
							formattedOption = format(optionsName[i]);
							if (useTabs) {
								selectors += '<li><a data-index="' + i + '" href="#' + formattedOption + '">' + optionsName[i] + '</a></li>';
							} else {
								selectors += "<label" + ((formattedOption === formattedDefaultOption) ? " class='boldFont'" : "") + " style='width: " + labelWidth + "%; text-align: center;'>" +
									"<input type='radio' data-index='" + i + "' name='structureOption' class='structureOption' " + ((formattedOption === formattedDefaultOption) ? "checked='checked'"
										: "") + " value='" + formattedOption + "' id='" + formattedOption +
									"Selector'></input>&nbsp;&nbsp;" + optionsName[i] + "</label>";
							}
							containers += "<div id='" + formattedOption + "'></div>";
						}
						if (useTabs) {
							strFinal = selectors + "</ul>" + containers + "</div>";
							s("#options").append(strFinal);
							s("#tabs").tabs({active: optionsName.indexOf(defaultOption)},
							{activate: function (event, ui) {
									changeCurrentOption($(ui.newTab).find("a").attr("data-index"));
								}});
						} else {
							strFinal = selectors + "<br />" + containers + "</div>";
							s("#options").append(strFinal);
						}
					}

					/*
					 * Shows the main bar.
					 * @returns {undefined}
					 */
					function showMainBar() {
						/*
						 * Creates and returns the HTML of an element to append to the main bar.
						 * @param {String} elementID: the id of the element.
						 * @param {String} elementClass: the class of the element (Font-Awesome, minus the "fa-" prefix).
						 * @param {String} elementText: the text of the element.
						 * @returns {String}: the HTML result.
						 */
						function getMainBarElement(elementID, elementClass, elementText) {
							var str;
							if (useFontAwesome) {
								str = "<span style='cursor: pointer; font-size: 125%;' id='" + elementID + "' title='" + elementText + "' class='fa fa-" + elementClass + "'></span>";
							} else {
								str = "<button style='font-size: 90%;' id='" + elementID + "'>" + elementText + "</button>";
							}
							return str + "&nbsp;&nbsp;";
						}

						var str = "<div id='mainBar' style='font: 150% Arial;'>";
						str += "<span id='scriptName'>PostFinder</span>&nbsp;&nbsp;";
						str += getMainBarElement("hideOrShow", "minus-square", "Cacher");
						str += getMainBarElement("reload", "refresh", "Recharger");
						str += getMainBarElement("reduceOrExtend", "compress", "Réduire");
						str += getMainBarElement("emptyCache", "trash-o", "Vider le cache");
						str += getMainBarElement("hideOrShowPage", "eye-slash", "Cacher la page");
						str += "</div>";
						s().append(str);
					}

					var listOfParameters = "PostFinder parameters:";
					for (var i = 0; i < parametersInfo.length; i++) {
						var n = parametersInfo[i].name;
						window[n] = localStorage.getItem(prefix + n);
						if (window[n] === null || window[n] === "") {
							window[n] = parametersInfo[i].defaultValue;
						}
						if (window[n] === "true" || window[n] === "false") {
							window[n] = JSON.parse(window[n]);
						}
						//For debugging purposes
						//window[n] = parametersInfo[i].defaultValue;
						listOfParameters += "\n" + n + "=" + window[n];
					}
					//log(listOfParameters);
					var beginIndex = getNthIndex(window.location.href, "-", 7) + 1;
					var lastIndex = window.location.href.lastIndexOf(".");
					currentTopicNameInURL = window.location.href.substring(beginIndex, lastIndex);
					isExtended = true;
					var $previousDiv = ((scriptIsAtTheTop) ? $(".bloc-pagi-default:first") : $(".bloc-pagi-default:last"));
					doingInit = true;
					doingAResearch = false;
					abandonRech = false;
					unpause();
					originalTitle = document.title;
					originalURL = window.location.href;
					realPage = getCurrentPageNumber();
					if ($(".pagi-fin-actif").length > 0) {
						lastP = getPageNumberFromURL("http://" + window.location.hostname + $(".pagi-fin-actif:first").attr("href"));
					} else {
						lastP = realPage;
					}
					$previousDiv.after("<div id='PostFinder'></div>");
					s().append("<div id='page' class='hideObject'></div>");
					if ($("#realPage").length === 0) {
						$(".bloc-message-forum").wrapAll("<div id='realPage' />");
					}
					$("#realPage .bloc-message-forum:not(.msg-pseudo-blacklist)").each(function () {
						$(this).attr("data-anchor", originalURL + "#message_" + $(this).attr("data-id"));
					});
					addEventHandlers();
					showMainBar();
					addTheme(appearance);
					$.datepicker.setDefaults($.datepicker.regional['fr']);
					$.timepicker.regional['fr'] = {
						timeFormat: "HH:mm:ss",
						timeText: 'Résultat',
						hourText: 'Heure',
						minuteText: 'Minute',
						secondText: 'Seconde',
						currentText: 'Aujourd\'hui',
						closeText: 'OK',
						separator: " à "
					};
					$.timepicker.setDefaults($.timepicker.regional['fr']);
					var sel = "#PostFinder";
					addStyle(sel + " .boldFont {font-weight: bold!important;} " +
						sel + " label {display: inline-block!important; font-weight: normal!important;} " +
						sel + " textarea, " + sel + " input, " + sel + " select {color: black;} " +
						sel + " {margin-bottom: 10px; margin-top: 10px;} " +
						sel + " #results {width:100%;} " +
						sel + " .fa-plus-circle, " + sel + " .fa-play {color:green;} " +
						sel + " .fa-minus-circle, " + sel + " .fa-pause {color:red;} " +
						sel + " .fa {transform: none!important;} " +
						sel + " .hideObject {display: none;} " +
						sel + " #results .sortElement {text-align: center; cursor: pointer; font-weight: bold;} " +
						sel + " .progressLabel {float: left; margin-left: 45%; line-height: 25px; font-weight: bold;} " +
						sel + " .researchForm {margin-top: 5px; margin-bottom: 35px;} " +
						sel + " textarea {height: 24px;} " +
						sel + " *:not(.fa) {font-family: Arial;} " +
						sel + " .pauseText {color: red;} " +
						sel + " button {color: black;} " +
						sel + " .pauseResearch, " + sel + " .unpauseResearch {margin-left: 10px; cursor:pointer;} " +
						sel + " .executeResearch {width: 60px; height: 19px; border:none; cursor:pointer; play:block; background-image:url('http://image.jeuxvideo.com/pics/recherche_bt_valider.gif')} " +
						sel + " input[type='number'] {width: 55px;} " +
						sel + " #options .fa {margin-left: 10px; cursor:pointer; font-size: 150%;} " +
						sel + " .help {cursor: help!important;} " +
						sel + " .inputParamName {cursor: pointer;} " +
						sel + " .inlineDiv {display: inline!important;} " +
						sel + " .longInput {width: 115px;} " +
						sel + " .shortInput {width: 90px;} " +
						sel + " .dateArea {width: 140px;} " +
						sel + " button {color: black;} " +
						sel + " .mainErrors, " + sel + " .warning, " + sel + " .warningJVC {color: red; font-weight: bold;} " +
						sel + "  a.link {color: #0a77b8!important; text-decoration: underline!important} " +
						sel + " #results a span.match {font-size: 100%!important; left: 0!important; position: relative!important} " +
						sel + " .sp-preview {width: 10px!important; height: 10px!important; margin-right: 0!important} " +
						sel + " .sp-dd {display: none!important;} " +
						sel + " .executeResearch {vertical-align: middle;} " +
						sel + " .permalinkArea {vertical-align: middle;}" +
						sel + " #Parametres label, " + sel + " .parametersTitle { margin-right: 10px; }" +
						"#realPage {margin-top: 25px!important;} " +
						".colpick {z-index: 99999!important;} ");
					s().append("<div id='options'></div>");
					loadStructure();
					loadContent();
					s("#options").before("<div class='mainErrors'></div>");
					s("#options").after("<div class='mainErrors'></div>");
					if (compactAtStartup) {
						reduceOrExtendButton();
					}
					doingInit = false;
					changeCurrentOption(optionsName.indexOf(defaultOption));
					if (!showAtStartup) {
						hideOrShowScript();
					}
					$("#loadingScript").remove();
					log("PostFinder: load done.");
				}

				/*
				 * Loads some messages into the cache.
				 * @param {Number} pageNb: the number of the page where the messages come from.
				 * @param {List of jQuery selectors} $msgs: the list of the messages to store.
				 * @param {String} url: the url of the page where these messages are from.
				 * @returns {undefined}
				 */
				function loadMsgsIntoCache(pageNb, $msgs, url) {
					if ($msgs.length === 0) {
						throw "Erreur fonction loadMsgsIntoCache : il n'y a aucun message à mettre en cache !";
					}
					if (s("#page" + pageNb).length === 0) {
						s().prepend("<div id='page" + pageNb + "' class='pageCache hideObject'></div>");
					}
					var html = "";
					$msgs.each(function () {
						$(this).attr("data-anchor", url + "#" + $(this).attr("data-id"));
						html += this.outerHTML;
					});
					if (!usePageCache) {
						s("#page").html(html);
					}
					s("#page" + pageNb).html(html);
				}

				/*
				 * Pauses the research.
				 * @returns {undefined}
				 */
				function pause() {
					pauseRech = true;
					s(".pauseResearch").removeClass("pauseResearch")
						.addClass("unpauseResearch");
					var title = "Reprendre la recherche";
					if (useFontAwesome) {
						s(".unpauseResearch").removeClass("fa-pause")
							.addClass("fa-play")
							.attr("title", title);
					} else {
						s(".unpauseResearch").html(title);
					}
					s(".pauseText").append(" En pause");
					//log("Paused.");
				}

				/*
				 * Reduces/extends the research forms.
				 * @param {undefined|jQuery selector} $newForm: undefined if from button, selector of the new form otherwise.
				 * @returns {undefined}
				 */
				function reduceOrExtend($newForm) {
					var $sel = ((typeof $newForm !== "undefined") ? $newForm : s());
					$sel.find(".researchInput textarea:not(.normalSize)").each(function () {
						$(this).toggleClass("longInput").toggleClass("shortInput");
						if (!isExtended) {
							$(this).attr("placeholder", $(this).prev().text());
						} else {
							if ($(this).is("[placeholder]")) {
								$(this).removeAttr("placeholder");
							}
						}
					});
					$sel.find("*[data-compact]").each(function () {
						var current = $(this).text();
						var alt = $(this).attr("data-compact");
						$(this).attr("data-compact", current).attr("title", current).html(alt);
					});
					$sel.find(".toggleInline").toggleClass("inlineDiv");
					$sel.find(".carriageReturn").toggleClass("hideObject");
				}

				/*
				 * Handles "Réduire"/"Agrandir".
				 * @returns {undefined}
				 */
				function reduceOrExtendButton() {
					isExtended = !isExtended;
					reduceOrExtend();
					if (isExtended) {
						var title = "Réduire";
						if (useFontAwesome) {
							s("#reduceOrExtend").removeClass("fa-expand").addClass("fa-compress").attr("title", title);
						} else {
							s("#reduceOrExtend").html(title);
						}
					} else {
						var title = "Agrandir";
						if (useFontAwesome) {
							s("#reduceOrExtend").removeClass("fa-compress").addClass("fa-expand").attr("title", title);
						} else {
							s("#reduceOrExtend").html(title);
						}
					}
				}

				/*
				 * Sorts the results.
				 * @param {String} param: the parameter used to sort the results.
				 * @param {Boolean} reverse: if false, this sort will not affect the next one with the same parameter. If true, reverses the order of the next sort with the same parameter.
				 * @returns {undefined}
				 */
				function sortMessages(param, reverse) {
					/**
					 * https://github.com/padolsey/jquery.fn/blob/master/sortElements/jquery.sortElements.js
					 * jQuery.fn.sortElements
					 * --------------
					 * @param Function comparator:
					 *   Exactly the same behaviour as [1,2,3].sort(comparator)
					 *
					 * @param Function getSortable
					 *   A function that should return the element that is
					 *   to be sorted. The comparator will run on the
					 *   current collection, but you may want the actual
					 *   resulting sort to occur on a parent or another
					 *   associated element.
					 *
					 *   E.g. $('td').sortElements(comparator, function(){
					 *      return this.parentNode;
					 *   })
					 *
					 *   The <td>'s parent (<tr>) will be sorted instead
					 *   of the <td> itself.
					 */
					jQuery.fn.sortElements = (function () {
						var sort = [].sort;
						return function (comparator, getSortable) {
							getSortable = getSortable || function () {
								return this;
							};
							var placements = this.map(function () {
								var sortElement = getSortable.call(this),
									parentNode = sortElement.parentNode,
									// Since the element itself will change position, we have
									// to have some way of storing its original position in
									// the DOM. The easiest way is to have a 'flag' node:
									nextSibling = parentNode.insertBefore(document.createTextNode(''),
										sortElement.nextSibling);
								return function () {
									if (parentNode === this) {
										throw new Error("You can't sort elements if any one is a descendant of another.");
									}
									// Insert before flag:
									parentNode.insertBefore(this, nextSibling);
									// Remove flag:
									parentNode.removeChild(nextSibling);
								};
							});
							return sort.call(this, comparator).each(function (i) {
								placements[i].call(getSortable.call(this));
							});
						};
					})();
					if (param === "Recherche" && enteredData.nbForms === 1) {
						return;
					}
					var $msgList = s("research").find("#results #messages .bloc-message-forum");
					var inverse = false;
					switch (param) {
						case "Temps":
						case "Recherche":
						case "Auteur":
							inverse = invTab[param];
							break;
						default:
							throw "Erreur switch sortElements : (" + getRealType(param) + ") param='" + param + "'";
					}
					$msgList.sortElements(function (a, b) {
						switch (param) {
							case "Temps":
								a = new Date($(a).attr("data-date"));
								b = new Date($(b).attr("data-date"));
								break;
							case "Recherche":
								a = parseInt($(a).attr("data-form-nb"), 10);
								b = parseInt($(b).attr("data-form-nb"), 10);
								break;
							case "Auteur":
								a = getMessageData(null, $(a), "nick");
								b = getMessageData(null, $(b), "nick");
								if (caseInsensitiveAuthorSort) {
									a = a.toLowerCase();
									b = b.toLowerCase();
								}
								break;
								//"default" is already handled
						}
						//log("Sorting the results with '" + param + "'... inverse=" + inverse + ", a='" + a + "', b='" + b + "'");
						return (a > b)
							? inverse ? -1 : 1
							: inverse ? 1 : -1;
					}, function () {
						return this;
					});
					if (reverse) {
						switch (param) {
							case "Temps":
							case "Recherche":
							case "Auteur":
								invTab[param] = !invTab[param];
								break;
								//"default" is already handled
						}
					}
					updateMessagesIndexes();
				}

				/*
				 * Starts a research.
				 * @returns {undefined}
				 */
				function startResearch() {
					/*
					 * Aborts the load of the JV Stalker iframe.
					 * @returns {undefined}
					 */
					function abortIframeLoad() {
						try {
							window.frames[0].stop();
						} catch (e) { //The iframe might have fully loaded at this point, which would cause the stop() method to throw an exception. Therefore, if it happens, do nothing.
							return;
						}
						try {
							window.removeEventListener("message", handleResponseFromJVStalker, false);
							endJVStalkerResearch("Le chargement de la page distante a été annulé.");
						} catch (e) {
							handleException(e);
						}
					}

					/*
					 * Fires when the min limit has been determined.
					 * @returns {undefined}
					 */
					function afterMinLimit() {
						/*
						 * Fires when the max limit has been determined.
						 * @returns {undefined}
						 */
						function afterMaxLimit() {
							/*
							 * Gets tbe page from a min/max limit value.
							 * @param {String} permalink: the min/max limit value.
							 * @param {String} firstOrLast: If applicable, whether to return the "first" or "last" page.
							 * @returns {Number|String}: the page or "Fin".
							 */
							function getActualPage(permalink, firstOrLast) {
								if (permalink === "Date") {
									return ((firstOrLast === "first") ? 1 : lastP);
								}
								if (permalink === "Fin") {
									return ((enteredData.readBackwards) ? lastP : "Fin");
								}
								var type = getRealType(permalink);
								if (type !== "String" || permalink === "" || permalink === null) {
									throw "Erreur fonction getActualPage : (" + type + ") permalink='" + permalink + "'";
								}
								return getPageNumberFromURL(getPageURLFromPermalink(permalink));
							}

							/*
							 * Returns the HTML of the element which will hide/show the results.
							 * @returns {String}
							 */
							function getHideOrShowResultsElementHTML() {
								var str, title = "Cacher les résultats";
								if (useFontAwesome) {
									str = "<span title='" + title + "' class='hideResults fa fa-eye-slash'></span>";
								} else {
									str = "<button class='hideResults'>" + title + "</button>";
								}
								return str;
							}

							/*
							 * Returns the theads of the results table.
							 * @returns {String}: the theads.
							 */
							function getSortElements() {
								return "<span class='sortElement' title='Classer les messages dans l&#39;ordre chronologique'>Temps<span class='fa fa-sort'></span></span>" +
									((enteredData.nbForms > 1) ? "<span class='sortElement' title='Classer les messages par recherche'>Recherche<span class='fa fa-sort'></span></span>" : "") +
									"<span class='sortElement' title='Classer les messages selon le pseudo de l&#39;auteur'>Auteur<span class='fa fa-sort'></span></span>";
							}

							/*
							 * Searchs in a page the matching messages.
							 * @param {Number} beginPage: the first page.
							 * @param {Number} destPage: the last page.
							 * @param {Date} firstDate: the first date.
							 * @param {Date|Null} lastDate: the last date. Null if "Fin".
							 * @returns {Promise}: promise.
							 */
							function searchPage(beginPage, destPage, firstDate, lastDate) {
								/*
								 * Executes after having checked a page.
								 * @param {Boolean} ret: whether to continue tocheck pages.
								 * @returns {P}: promise.
								 */
								function afterCheckPage(ret) {
									return new P(function (resolve, reject) {
										nbPagesRead++;
										log("Page " + beginPage + " is checked. Continue? " + ret);
										if (ret === false) {
											endSearch();
											resolve();
											return;
										}
										beginPage += ((enteredData.readBackwards) ? (-1) : 1);
										if (beginPage === 0) {
											endSearch();
											resolve();
											return;
										}
										if (getRealType(beginPage) === "Number" && getRealType(destPage) === "Number" &&
											((beginPage > destPage && !enteredData.readBackwards) ||
												(beginPage < destPage && enteredData.readBackwards))) {
											endSearch();
											resolve();
											return;
										}
										progress();
										setZeroTimeout(function () {
											searchPage(beginPage, destPage, firstDate, lastDate).then(resolve).catch(reject);
										});
									});
								}

								/*
								 * Reads the page and checks if valid messages exist in it.
								 * @param {Number} pageToCheck: the number of the page to read.
								 * @param {Date} firstDate: the min date.
								 * @param {Date} lastDate: the max date.
								 * @returns {Promise}: promise.
								 */
								function checkPage(pageToCheck, firstDate, lastDate) {
									/*
									 * Checks a message and queues the next one.
									 * @param {Number} msgNb: the index of the current message.
									 * @param {List of jQuery selectors} $messages: the list of the messages in the current page.
									 * @param {Number} max: the number of messages in the current page.
									 * @returns {Promise}: promise.
									 */
									function checkMessage(msgNb, $messages, max) {
										/*
										 * Adds a message to the results.
										 * @param {jQuery selector} $msg: the message.
										 * @param {Date} curDate: the date of the message.
										 * @param {Number} formNb: the number of the (first if deleteDuplicates) form which matched the message.
										 * @returns {undefined}
										 */
										function addResult($msg, curDate, formNb) {
											//log("Form number: " + formNb + ", date: " + curDate + ", author: " + getMessageData(pageToCheck, $msg, "nick"));
											var $results = s("research").find("#results #messages");
											$msg.attr("data-form-nb", formNb)
												.attr("data-element-index", ($results.find(".bloc-message-forum:not(.msg-pseudo-blacklist)").length + 1))
												.attr("data-date", curDate).addClass("scriptresult");
											$results.append($msg);
											if (enteredData.usingDisplay && !sortWhenFinished) {
												if (enteredData.display !== "Temps") {
													//log("Temp sorting with '" + enteredData.display + "'...");
													sortMessages(enteredData.display, false);
													//log("Temp sorting with '" + enteredData.display + "' done.");
												}
											}
										}

										/*
										 * Magic. Do not touch.
										 * Determines whether a message matches a research input; if yes, highlights the matching parts.
										 * @param {jQuery selector} $part: selector of the part of the message which needs to be checked.
										 * @param {Object} cInput: a research input. cInput.name = "Message", "Auteur", etc. cInput.value = its value.
										 * @returns {Boolean}: true if the message matches the input, false otherwise.
										 */
										function doesItMatchIfYesHighlight($part, cInput) {
											/*
											 * Highlights the matching parts of a message.
											 * @param {Boolean} icase: if true, regex should be case-insensitive.
											 * @param {Boolean} iO: if true, we should escape special characters.
											 * @param {String|RegExp} search: a string or a regex to search in the message.
											 * @param {jQuery selector} $part: the part of the message that should be checked.
											 * @returns {undefined}
											 */
											function highlight(icase, iO, search, $part) {
												var expr = search;
												if (iO) {
													//Escaping the regex
													var search2 = search.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
													try {
														expr = new RegExp(search2, "mg" + ((icase) ? "i" : ""));
													} catch (e) {
														throw "Cette expression rationnelle : '" + search2 + "' est incorrectement construite !\n\n" +
															"Cause : script incomplet, merci de me contacter et de m'indiquer précisément ce que vous aviez entré et le contenu de cette alerte.\n\n" +
															"Erreur : " + e;
													}
												}
												try {
													findAndReplaceDOMText($part[0], {
														find: expr,
														wrap: $("<span class='match'></span>")[0]
													});
												} catch (e) { //Should never happen. "Regular" regexes (no pun intended) are already filtered prior to the calling of this function.
													throw "Cette expression rationnelle : '" + expr + "' est incorrectement construite !\n\n" +
														"Erreur : " + e;
												}
											}

											var v = cInput.value, n = cInput.name, contentA;
											//If no parameters, default params are: N/iO/IC/T
											if (!cInput.usingParams) {
												var v2 = v.toLowerCase();
												//"Image"
												if (n === "Image") {
													var ret = false;
													$part.find(".img-shack").each(function () {
														if ($(this).attr("src").replace("//image.noelshack.com/minis/", "").toLowerCase().indexOf(v2) >= 0) {
															ret = true;
															return false;
														}
													});
													return ret;
												}
												//Not "Image": it's "Message" or "Auteur"
												contentA = $part.text();
												if (n === "Auteur") {
													contentA = contentA.trim();
												}
												if (contentA.toLowerCase().indexOf(v2) < 0) {
													return false;
												}
												//Match!
												highlight(true, true, v, $part);
											} else { //There's at least one parameter
												var inv = false, R = false, cs = false, html = false;
												for (var i = 0; i < cInput.params.length; i++) {
													var param = cInput.params[i];
													if (param.paramName === "contains" && (param.value === "I" || param.value === "Inversé ")) {
														inv = true;
														continue;
													}
													if (param.paramName === "regexOrIndexOf" && (param.value === "R" || param.value === "Regex ")) {
														R = true;
														continue;
													}
													var second = null, compactedSecond = null;
													for (var j = 0; i < formInputsParams.length; j++) {
														if (formInputsParams[j].name === "case") {
															second = formInputsParams[j].second;
															compactedSecond = formInputsParams[j].compactedSecond;
															break;
														}
													}
													if (param.paramName === "case" && (param.value === compactedSecond || param.value === second)) {
														cs = true;
														continue;
													}
													if (param.paramName === "textOrHTML" && (param.value === "H" || param.value === "HTML ")) {
														html = true;
														continue;
													}
												}
												if (html) {
													contentA = $part.html();
												} else {
													contentA = $part.text();
												}
												if (n === "Auteur") {
													contentA = contentA.trim();
												}
												if (!cs) {
													contentA = contentA.toLowerCase();
												}
												/*log("inv + R + cs + html, contentA - " + inv + ", " + R + ", " + cs + ", " + html, contentA);
												 alert("pause");*/
												//indexOf
												if (!R) {
													var v2 = v;
													if (!cs) {
														v2 = v2.toLowerCase();
													}
													//"Image"
													if (n === "Image") {
														var found = false;
														$part.find(".img-shack").each(function () {
															var currentSrc = $(this).attr("src").replace("//image.noelshack.com/minis/", "");
															if (!cs) {
																currentSrc = currentSrc.toLowerCase();
															}
															if (currentSrc.indexOf(v2) >= 0) {
																found = true;
																return false;
															}
														});
														return (found !== inv);
													}
													//Not "Image": it's "Message" or "Auteur"
													var currentText = contentA;
													if (currentText.indexOf(v2) < 0 === !inv) {
														return false;
													} else {
														//Nothing has been found and inv is true
														if (inv) {
															return true;
														}
														//Match!
														highlight(!cs, true, v, $part);
													}
												} else { //Regex
													var ret, v2;
													try {
														v2 = RegExp(v, "m" + ((!cs) ? "i" : ""));
													} catch (e) {
														throw "Cette expression rationnelle : '" + v + "' est incorrectement construite !\n\n" +
															"Erreur : " + e;
													}
													//"Image"
													if (n === "Image") {
														var found = false;
														$part.find(".img-shack").each(function () {
															var currentSrc = $(this).attr("src").replace("//image.noelshack.com/minis/", "");
															ret = v2.test(currentSrc);
															if (ret === true) {
																found = true;
																return false;
															}
														});
														return (found !== inv);
													}
													//Not "Image": it's "Message" or "Auteur"
													ret = v2.test(contentA);
													if ((ret === false) === !inv) {
														return false;
													} else {
														//Nothing has been found and inv is true
														if (inv) {
															return true;
														}
														//Match!
														v2 = RegExp(v, "mg" + ((!cs) ? "i" : ""));
														highlight(!cs, false, v2, $part);
													}
												}
											}
											return true;
										}

										var args = arguments;
										return new P(function (resolve, reject) {
											log("Checking message " + (msgNb + 1) + "/" + max + "...");
											var datePauseStart = performance.now();
											//Pretty much useless
											var ret = handlePauseAndAbandon("checkMessage", reject, endSearch, function () {
												var datePauseEnd = performance.now();
												rechStartDate = rechStartDate + (datePauseEnd - datePauseStart);
												checkMessage.apply(this, args).then(resolve).catch(reject);
											});
											if (ret === true) {
												return;
											}
											if (msgNb >= max) {
												log("checkMessage: I have read all the messages of this page.");
												resolve(true);
												return;
											}
											var $mess = $messages.eq(msgNb);
											var curID = getPermalinkID(getMessageData(pageToCheck, $mess, "msgAnchor"));
											log("curID=" + curID + ", foundMessages:", foundMessages);
											/*If multiple researches find the same message and deleteDuplicates === true.
											 If there is a single research, it could also happen: if a message that we haven't read (yet?) is deleted while we're reading backwards, for instance.*/
											if (deleteDuplicates && foundMessages.indexOf(curID) >= 0) {
												log("Skipping an already found message...");
												nextTick(function () {
													checkMessage(msgNb + 1, $messages, max).then(resolve).catch(reject);
												});
												return;
											}

											var curDate = getMessageData(pageToCheck, $mess, "date");
											var curDirectLink = getMessageData(pageToCheck, $mess, "msgDirect");

											/* If it's the very first page, skip the messages until we get to the first message to read
											 (N/A if we're reading backwards with "Fin")*/
											if (!isSkipDone && !(enteredData.readBackwards && enteredData.limits.max.type === "Fin")) {
												var skip = false;
												if (!enteredData.readBackwards) {
													if (permalinkMin === "Date") {
														if (curDate < firstDate) {
															skip = true;
														}
													} else { //permalinkMin !== "Date"
														if (getPermalinkID(curDirectLink) !== getPermalinkID(permalinkMin)) {
															skip = true;
														}
													}
												} else { //enteredData.readBackwards === true
													if (permalinkMax === "Date") {
														if (curDate > lastDate) {
															skip = true;
														}
													} else { //permalinkMax !== "Date"
														if (getPermalinkID(curDirectLink) !== getPermalinkID(permalinkMax)) {
															skip = true;
														}
													}
												}
												if (skip === true) {
													//log("Skipping until the first message...");
													nextTick(function () {
														checkMessage(msgNb + 1, $messages, max).then(resolve).catch(reject);
													});
													return;
												} else {
													isSkipDone = true;
												}
											}

											//If the limit is behind us, we should stop.
											if ((enteredData.readBackwards && curDate < firstDate) ||
												(!enteredData.readBackwards && lastDate !== null && curDate > lastDate)) {
												log("The limit is behind us, stop.");
												resolve(false);
												return;
											}

											//log("Checking...");
											for (var i = 0; i < enteredData.nbForms; i++) {
												if (deleteDuplicates && foundMessages.indexOf(curID) >= 0) {
													log("Skipping an already found result...");
													break;
												}

												//Author's nickname
												if (enteredData.forms[i].authorsNickname.usingAuthorNickname) {
													var isAdmin = ($mess.find(".bloc-pseudo-msg.text-admin").length > 0);
													var isMod = ($mess.find(".bloc-pseudo-msg.text-modo").length > 0);
													var isUser = ($mess.find(".bloc-pseudo-msg.text-user").length > 0);
													var isDeleted = !$mess.find(".bloc-pseudo-msg").is("a");
													var found = false;
													for (var j = 0; j < enteredData.forms[i].authorsNickname.list.length; j++) {
														if ((enteredData.forms[i].authorsNickname.list[j] === "Supprimé" && isDeleted) ||
															(enteredData.forms[i].authorsNickname.list[j] === "Forumeur" && isUser) ||
															(enteredData.forms[i].authorsNickname.list[j] === "Modérateur" && isMod) ||
															(enteredData.forms[i].authorsNickname.list[j] === "Administrateur" && isAdmin)) {
															found = true;
															break;
														}
													}
													if (!found) {
														log("Does not match author nickname");
														continue;
													}
												}

												//Inputs
												var $matchingMsg = $($mess[0].outerHTML);
												if ($matchingMsg.css("display") === "none") {
													if (!showBlacklistedMessages) {
														break;
													} else {
														$matchingMsg.css("display", "block");
													}
												}
												var continueLoop = false;
												for (var j = 0; j < enteredData.forms[i].inputs.length; j++) {
													var input = enteredData.forms[i].inputs[j];
													log("Form[" + i + "]: input.name & value: " + input.name + " & '" + input.value + "'");
													if (input.value !== "") {
														var str;
														switch (input.name) {
															case "Message":
															case "Image":
																str = $matchingMsg.find(".txt-msg");
																break;
															case "Auteur":
																str = getMessageData(pageToCheck, $matchingMsg, "nick", true);
																break;
															default:
																throw "Erreur switch dans la fonction checkMessage : (" + getRealType(input.name) + ") input.name='" + input.name + "'";
														}
														var ret = doesItMatchIfYesHighlight(str, input);
														if (ret === false) {
															log("No match");
															continueLoop = true;
															break;
														}
														log("Match");
													}
												}
												if (continueLoop) {
													continue;
												}
												log("Form #" + enteredData.forms[i].nb + ": the message #" + msgNb + " matches; id=" + curID);
												foundMessages.push(curID);
												$matchingMsg.addClass("resultResearch" + enteredData.forms[i].nb);
												addResult($matchingMsg, curDate, i + 1);
											}

											//If we've just reached the limit, we should stop.
											if ((enteredData.readBackwards && permalinkMin !== "Date" && getPermalinkID(curDirectLink) === getPermalinkID(permalinkMin)) ||
												(!enteredData.readBackwards && permalinkMax !== "Fin" && permalinkMax !== "Date" && getPermalinkID(curDirectLink) === getPermalinkID(permalinkMax))) {
												log("The limit has been reached, stop.");
												resolve(false);
												return;
											}

											nextTick(function () {
												checkMessage(msgNb + 1, $messages, max).then(resolve).catch(reject);
											});
										});
									}

									return new P(function (resolve, reject) {
										var $messagesWrapper;
										if (usePageCache) {
											$messagesWrapper = s("#page" + pageToCheck);
										} else {
											$messagesWrapper = s("#page");
										}
										var $messages = ((enteredData.readBackwards)
											? $($messagesWrapper.find(".bloc-message-forum:not(.msg-pseudo-blacklist)").get().reverse())
											: $messagesWrapper.find(".bloc-message-forum:not(.msg-pseudo-blacklist)"));
										log("Checking page " + pageToCheck + "...");
										checkMessage(0, $messages, $messages.length).then(resolve).catch(reject);
									});
								}

								/*
								 * Checks if the next page exists.
								 * @param {Number} page: the page to check.
								 * @returns {Promise}: promise.
								 */
								function pageExists(page) {
									return new P(function (resolve, reject) {
										if (page < 1) {
											throw "Erreur fonction pageExists : le numéro de la page (donné en paramètre) est inférieur à 1 !";
										}
										if (usePageCache && s("#page" + page).length > 0) {
											resolve(true);
											return;
										}
										var urlSplit = window.location.href.split("-"), url, r;
										urlSplit[3] = page;
										url = urlSplit.join("-");
										r = new XMLHttpRequest();
										r.timeout = 10000;
										r.ontimeout = function () {
											log("The request for '" + url + "' timed out.");
											resolve(false);
										};
										r.open("GET", url, true);
										r.overrideMimeType("text/xml");
										r.onload = function () {
											try {
												if (r.responseURL !== url) { //If redirected to another place
													log("Page " + page + " does not exist (redirects elsewhere).");
													resolve(false);
													return;
												}
												var data = r.responseText;
												var $msg = $(data).find(".bloc-message-forum:not(.msg-pseudo-blacklist)");
												if ($msg.length === 0) {
													log("Page " + page + " does not exist (no message found).");
													resolve(false);
													return;
												}
												log("Page " + page + " does exist.");
												convertJvCareToRealLink($msg);
												loadMsgsIntoCache(page, $msg, url);
												resolve(true);
												return;
											} catch (e) {
												reject(e);
											}
										};
										r.send(null);
									});
								}

								/*
								 * Don't you use your fancy mathematics to muddy the issue!
								 * Updates the progress info.
								 * @returns {undefined}
								 */
								function progress() {
									//Progress bar
									if ($progBar.data("progressbar") !== null) {
										if (showProgressBar) {
											$progBar.show();
										} else {
											$progBar.hide();
										}
										var precisePrevProgress = parseFloat($progBar.attr("data-precise")) + (100 / nbPagesToRead);
										var rounded2 = Math.round(precisePrevProgress * 10) / 10;
										if (rounded2 >= 100 && enteredData.limits.max.type === "Fin") {
											$progBar.progressbar("value", false);
										} else {
											$progBar.progressbar("value", rounded2);
										}
										$progBar.attr("data-precise", precisePrevProgress);
									}
									//Time progress
									var $progressTime = s("research").find(".progressTime");
									if (showProgressTime) {
										$progressTime.show();
									} else {
										$progressTime.hide();
									}
									var timeSpentMs = performance.now() - rechStartDate;
									var timeSpentS = Math.round(timeSpentMs / 1000);
									realTimeSpent = timeSpentS;
									var previousTimeSpentMs;
									var $lastTimeSpent = s("research").find(".progressTime .lastTimeSpent:first");
									previousTimeSpentMs = $lastTimeSpent.attr("data-ms");
									var remainingTimeS = Math.round(((nbPagesToRead - nbPagesRead) * (timeSpentMs - previousTimeSpentMs)) / 1000);
									var refDate = new Date(null);
									refDate.setSeconds(timeSpentS);
									var showTimeSpent = refDate.toISOString().substr(11, 8);
									refDate.setSeconds(remainingTimeS);
									var showRemainingTime = refDate.toISOString().substr(11, 8);
									$progressTime.html("Temps écoulé : <b><span class='lastTimeSpent' data-ms='" + timeSpentMs + "'>" + showTimeSpent + "</span></b>. Temps restant estimé : <b>" + showRemainingTime + "</b>.");
								}

								var args = arguments;
								return new P(function (resolve, reject) {
									//log("searchPage(" + beginPage + ", " + destPage + ", " + firstDate + ", " + lastDate + ")");
									var datePauseStart = performance.now();
									var ret = handlePauseAndAbandon("searchPage", reject, endSearch, function () {
										var datePauseEnd = performance.now();
										rechStartDate = rechStartDate + (datePauseEnd - datePauseStart);
										searchPage.apply(this, args).then(resolve).catch(reject);
									});
									if (ret === true) {
										return;
									}
									s("research").find(".searchPageProgress").html("<i>Page " + beginPage + " => " + destPage + "<i>");

									pageExists(beginPage).then(function (nextPageExists) {
										if (!nextPageExists) {
											endSearch();
											resolve();
											return;
										}

										checkPage(beginPage, firstDate, lastDate).then(afterCheckPage).then(resolve).catch(reject);
									}).catch(reject);
								});
							}

							log("Max limit done. permalinkMax=" + permalinkMax);
							showPermalink(permalinkMax, "max");
							resetDatePageRange();
							if (lastDate !== null && lastDate < firstDate) {
								throw "L'arrivée est antérieure au départ !";
							}
							var beginPage, destPage;
							if (enteredData.readBackwards) {
								beginPage = getActualPage(permalinkMax, "last");
								destPage = getActualPage(permalinkMin, "first");
								nbPagesToRead = beginPage - destPage + 1;
							} else {
								destPage = getActualPage(permalinkMax, "last");
								beginPage = getActualPage(permalinkMin, "first");
								if (destPage === "Fin") {
									nbPagesToRead = lastP - beginPage + 1;
								} else {
									nbPagesToRead = destPage - beginPage + 1;
								}
							}
							//Percentage done with each page
							rounded = Math.round((100 / nbPagesToRead) * 10) / 10;
							foundMessages = [];
							if (hideBeforeResearch && $("#realPage").is(":visible")) {
								s("#hideOrShowPage").click();
							}
							var $previousData = s("research").find("#results")
								.add(s("research").find(".hideResults"))
								.add(s("research").find(".searchPageProgress"));
							if ($previousData.length > 0) {
								$previousData.remove();
							}
							var globalProgress = "<div class='globalProgress'><br />";
							globalProgress += "<div class='progressBar' data-precise='0'><div class='progressLabel'>Chargement...</div></div>";
							globalProgress += "<div class='progressTime'>Temps écoulé : <b><span class='lastTimeSpent' data-ms='0'>00:00:00</span></b>. Temps restant estimé : calcul en cours...</div>";
							globalProgress += "<span class='searchPageProgress'></span>" +
								"&nbsp;&nbsp;<b class='pauseText'></b>";
							var title = "Mettre la recherche en pause";
							if (useFontAwesome) {
								globalProgress += "<span title='" + title + "' class='pauseResearch fa fa-pause'></span>";
							} else {
								globalProgress += "<button class='pauseResearch'>" + title + "</button>";
							}
							globalProgress += "</div>";
							s("research").append("<div class='resultsArea'>" + globalProgress + "<br />" +
								"<div class='info'></div><br />" +
								getHideOrShowResultsElementHTML() +
								"<br /><div class='warning'></div><br /><div id='results'>" +
								((enteredData.usingDisplay)
									? getSortElements()
									: "") +
								"<br style='clear: both;'/><div id='messages'></div>" +
								((enteredData.usingDisplay)
									? getSortElements()
									: "") +
								"</div><br /><br />" +
								getHideOrShowResultsElementHTML() +
								"<br /><br /><div class='info'></div>" + globalProgress +
								"<br /><div class='warning'></div></div>");
							s("research").find(".sortElement").css("float", "left").css("width", ((enteredData.nbForms > 1) ? 33 : 50) + "%");
							$progBar = s("research").find(".progressBar");
							if (!showProgressBar) {
								$progBar.hide();
							}
							var $progLab = s("research").find(".progressLabel");
							$progBar.progressbar({
								value: 0,
								change: function () {
									try {
										var percentage = $progBar.progressbar("value");
										if (percentage === false || percentage === "false") {
											$progLab.text("Lecture des pages au-delà de " + lastP + "...");
											if (showProgressInTitle) {
												document.title = '[...] ' + originalTitle;
											} else {
												document.title = originalTitle;
											}
										} else {
											$progLab.text(percentage + "%");
											if (showProgressInTitle) {
												document.title = '[' + percentage + '%] ' + originalTitle;
											} else {
												document.title = originalTitle;
											}
										}
									} catch (e) {
										handleException(e);
									}
								},
								complete: function () {
									$progLab.text("Fini !");
								}
							});
							if (!showProgressTime) {
								s("research").find(".progressTime").hide();
							}
							for (var i = 0; i < enteredData.nbForms; i++) {
								if (enteredData.forms[i].colors.usingColors) {
									if (enteredData.forms[i].colors.else.txt !== null || enteredData.forms[i].colors.else.bg !== null) {
										addStyle("#" + format(enteredData.typeOfResearch) + " .resultResearch" + enteredData.forms[i].nb + " * {" +
											((enteredData.forms[i].colors.else.txt !== null) ? "color: " + enteredData.forms[i].colors.else.txt + "!important;" : "") +
											((enteredData.forms[i].colors.else.bg !== null) ? "background-color: " + enteredData.forms[i].colors.else.bg + "!important;" : "") +
											"}");
									}
									if (enteredData.forms[i].colors.match.txt !== null || enteredData.forms[i].colors.match.bg !== null) {
										addStyle("#" + format(enteredData.typeOfResearch) + " .resultResearch" + enteredData.forms[i].nb + " .match {" +
											((enteredData.forms[i].colors.match.txt !== null) ? "color: " + enteredData.forms[i].colors.match.txt + "!important;" : "") +
											((enteredData.forms[i].colors.match.bg !== null) ? "background-color: " + enteredData.forms[i].colors.match.bg + "!important;" : "") +
											"}");
									}
								}
							}

							isSkipDone = false;
							rechStartDate = performance.now();
							log("before searchPage");
							searchPage(beginPage, destPage, firstDate, lastDate).catch(handleException);
							log("after searchPage");
						}

						/*
						 * Stores a min/max message (visible on hover) and shows its permalink ("Départ" or "Arrivée").
						 * @param {String} permalink: the permalink.
						 * @param {String} limit: "min" => "Départ", "max" => "Arrivée".
						 * @returns {undefined}
						 */
						function showPermalink(permalink, limit) {
							log("showPermalink begin, permalink:", permalink, "limit:", limit);
							if (permalink === "Date" || permalink === "Fin") {
								return;
							}
							log("a");
							var t = getRealType(permalink);
							if (t !== "String" || permalink === "" || permalink === null) {
								throw "Erreur fonction showPermalink : (" + t + ") '" + permalink + "' n'est pas un permalien valide !";
							}
							log("b");
							var $minMaxInfo = s("research").find("#minMaxInfo");
							log("c, $minMaxInfo length=" + $minMaxInfo.length);
							var $area = $minMaxInfo.find("#" + ((limit === "max") ? "lastPermalink" : "firstPermalink") + "Area");
							log("d, $area length = " + $area.length);
							var url;
							try {
								url = getPageURLFromPermalink(permalink);
							} catch (e) {
								log("erreur capturée :", e);
								throw e;
							}
							log("e, url:", url);
							var pageN = getPageNumberFromURL(url);
							log("f, pageN:", pageN);
							var id = "post_" + getPermalinkID(permalink);
							id = getPermalinkID(permalink);
							var messSelector = ".bloc-message-forum[data-id='" + id + "']";
							log("g, id/messSelector:", id, messSelector);
							var $msg;
							$minMaxInfo.find(".pauseResearch").hide();
							$area.find(".dateProgress").hide();
							$area.find("a").attr("href", permalink).show();
							var $mess = s("research").find("#" + ((limit === "max") ? "lastMessage" : "firstMessage"));
							log("h, $mess length=" + $mess.length);
							$mess.html("<br />");

							if (usePageCache && s("#page" + pageN).length > 0) {
								$msg = s("#page" + pageN).find(messSelector);
								if ($msg.length === 0) {
									throw "Erreur fonction showPermalink : le message trouvé (" + limit + ") n'existe pas dans le cache !";
								}
								$mess.append($msg[0].outerHTML);
								log("i1");
								return true;
							}

							if (pageN === realPage && usePageCache) {
								$msg = $("#realPage").find(messSelector);
								if ($msg.length === 0) {
									throw "Erreur fonction showPermalink : le message trouvé (" + limit + ") n'existe pas dans la page actuelle !";
								}
								$mess.append($msg[0].outerHTML);
								log("i2");
								return true;
							}

							log("before i3");
							callAjax(url, function (data) {
								log("in i3, in callback inside callAjax");
								var $data = $(data);
								var $msgs = $data.find(".bloc-message-forum:not(.msg-pseudo-blacklist)");
								if ($msgs.length === 0) {
									throw "Erreur fonction showPermalink : la page " + pageN + " n'a pas pu être chargée...";
								}
								log("before loadMsgsIntoCache, pageN:", pageN, "$msgs length=" + $msgs.length + ", url:", url);
								loadMsgsIntoCache(pageN, $msgs, url);
								log("after loadMsgsIntoCache");
								$msg = $msgs.filter(messSelector);
								if ($msg.length === 0) {
									throw "Erreur fonction showPermalink : le message trouvé (" + limit + ") n'existe pas dans la page chargée !";
								}
								$mess.append($msg);
								log("i3");
							});
							log("showPermalink done");
						}

						log("Min limit done. permalinkMin=" + permalinkMin);
						showPermalink(permalinkMin, "min");
						log("showPermalink min done");
						resetDatePageRange();
						log("resetDatePageRange done");
						var executeAfterMaxLimit = true;
						switch (enteredData.limits.max.type) {
							case "Date":
								lastDate = enteredData.limits.max.value;
								if (researchMessageByDate) {
									executeAfterMaxLimit = false;
									var $dateProgress = $minMaxInfo.find("#lastPermalinkArea .dateProgress");
									$minMaxInfo.find(".pauseResearch").show();
									$dateProgress.show();
									//log("Calling getPermalinkFromDate for the max limit...");
									getPermalinkFromDate(realPage, enteredData.limits.max.value, "max", false).then(function (ret) {
										//log("getPermalinkFromDate for the max limit done. ret="+ret);
										cleanUp("lastPermalinkArea");
										if (doItAgain) {
											doingAResearch = false;
											startResearch();
										} else {
											permalinkMax = ret;
											afterMaxLimit();
										}
									}).catch(function (e1) {
										try {
											cleanUp("lastPermalinkArea");
										} catch (e2) {
											handleException(e2);
										} finally {
											doingAResearch = false;
											if (e1 !== "abandon") {
												handleException(e1);
											}
										}
									});
								} else {
									permalinkMax = "Date";
								}
								break;
							case "Page":
								var ret = getMessageDataFromPage("last", enteredData.limits.max.value);
								permalinkMax = ret.permalink;
								lastDate = ret.date;
								break;
							case "Permalien":
								permalinkMax = enteredData.limits.max.value;
								var isDirectLink = (permalinkMax.lastIndexOf("#") < 0);
								if (isDirectLink) {
									var ret = getDataFromDirectLink(permalinkMax);
									lastDate = ret.date;
								} else {
									throw "Merci de fournir un permalien direct.";
								}
								break;
							case "Fin":
								permalinkMax = "Fin";
								lastDate = null;
								break;
							default:
								doingAResearch = false;
								throw "Erreur switch type arrivée : (" + getRealType(enteredData.limits.max.type) + ") type='" + enteredData.limits.max.type + "'";
						}
						if (executeAfterMaxLimit) {
							afterMaxLimit();
						}
						log("afterMinLimit done");
					}

					/*
					 * Cleans up an area.
					 * @param {String} id: the id of the area.
					 * @returns {undefined}
					 */
					function cleanUp(id) {
						unpause();
						s("research").find("#minMaxInfo").find(".pauseResearch").hide();
						s("research").find("#" + id).find(".dateProgress").empty();
						abandonRech = false;
					}

					/*
					 * Ends JVStalker's research.
					 * @param {String} str: the info to show.
					 * @returns {undefined}
					 */
					function endJVStalkerResearch(str) {
						s("#JVStalker #info").html(str);
						unpause();
						s("#JVStalker #pauseWrapper").hide();
						doingAResearch = false;
						if (doItAgain) {
							doItAgain = false;
							startResearch();
						}
					}

					/*
					 * Fires when the global research ends. Among other things, it sorts the results (if needed) and updates the progress info.
					 * @returns {undefined}
					 */
					function endSearch() {
						/*
						 * Gets some information about every research.
						 * @returns {String}: the info.
						 */
						function getResearchesInfo() {
							var str = "";
							for (var i = 0; i < enteredData.nbForms; i++) {
								str += "Recherche n°" + enteredData.forms[i].nb + " : <b>" + s("research").find("#results #messages div.resultResearch" + enteredData.forms[i].nb).length + " message(s)</b>.<br />";
							}
							return str;
						}

						var $msgList = s("research").find("#results #messages .bloc-message-forum");
						if ($msgList.length > 0) {
							if (enteredData.usingDisplay && sortWhenFinished) {
								if (enteredData.display !== "Temps") {
									//log("Sorting with '" + enteredData.display + "'...");
									sortMessages(enteredData.display, true);
									//log("Sorting with '" + enteredData.display + "' done.");
								}
								if (enteredData.display === "Temps" && !enteredData.readBackwards) {
									invTab.Temps = true;
								}
							}
							updateMessagesIndexes();
							if (enteredData.usingDisplay && !sortWhenFinished &&
								(enteredData.display !== "Temps" ||
									(enteredData.display === "Temps" && !enteredData.readBackwards))) {
								invTab[enteredData.display] = true;
							}
						}
						if (typeof $progBar !== "undefined" && showProgressBar && typeof $progBar.progressbar === "function") {
							$progBar.progressbar("value", 100);
						}
						var $globalProgress = s("research").find(".globalProgress");
						if ($globalProgress.length > 0) {
							$globalProgress.remove();
						}
						var researchesInfo = getResearchesInfo();
						if (typeof realTimeSpent === "undefined") {
							realTimeSpent = 0;
						}
						s("research").find(".info").append("Recherche terminée. <b>" + nbPagesRead + " page(s)</b> lues en <b>" + realTimeSpent + "s</b>." +
							"<br />" + researchesInfo);
						if ($msgList.length === 0) {
							s("research").find(".info:last, #results, .hideResults").hide();
							s("research").find(".info:first").nextUntil(null, "br").hide();
						}
						doingAResearch = false;
						unpause();
						abandonRech = false;
						log("Finished.");
						if (doItAgain) {
							startResearch();
						}
					}

					/*
					 * Hic sunt dracones... Necne.
					 * Gets the closest permalink (anchor) after (resp. before) the min (resp. max) date.
					 * @param {Number} currentP: the current page.
					 * @param {Date} d: the date.
					 * @param {String} limit: "min"/"max".
					 * @param {Boolean} notify: true if the previous page is right before/after this one. Useful if the correct message is the very first/last message of the previous page.
					 * @returns {Promise}: promise.
					 */
					function getPermalinkFromDate(currentP, d, limit, notify) {
						/*
						 * Fires after having checked a whole page.
						 * @param {Object} obj: an object with the relevent info.
						 * @param {Number} nbMsg: the number of messages in this page.
						 * @param {String} selector: the selector of this page;
						 * @returns {P}: promise.
						 */
						function afterCheckDateMessage(obj, nbMsg, selector) {
							return new P(function (resolve, reject) {
								log("getPermalinkFromDate: page " + currentP + " is checked.");
								if (obj.itIsBefore || obj.itIsAfter) {
									if (notify) {
										log("Notifying parent...");
										resolve(true);
										return;
									}
									var tempKey = ((obj.itIsBefore) ? "end" : "begin");
									datePageRange[tempKey] = currentP;
									var returnedData = getTheNextPage(currentP, d, ((obj.itIsBefore) ? "before" : "after"), selector, limit);
									var nextPage = returnedData;
									log("Not here, checking another page: " + nextPage + "...");
									setZeroTimeout(function () {
										getPermalinkFromDate(nextPage, d, limit, false).then(function (ret) {
											log("Another page has found the message. link=" + ret);
											resolve(ret);
										}).catch(reject);
									});
									return;
								}
								/*Something has been found
								 (It should always be the case here)*/
								if (obj.permalink !== null) {
									//Maybe there's an older message in the previous page(s), or a newer one in the next page(s)
									if ((obj.trueIndex === 0 && currentP !== 1 && limit === "min") || //Older?
										((obj.trueIndex + 1) === nbMsg && currentP !== lastP && limit === "max")) { //Newer?
										var tempKey = ((limit === "min") ? "end" : "begin");
										datePageRange[tempKey] = currentP;
										log("Checking another page, to be sure...");
										try {
											var returnedData = getTheNextPage(currentP, d, ((obj.trueIndex === 0) ? "before" : "after"), selector, limit);
										} catch (e) {
											if (e === "found") {
												log("Skipping the check, the correct message has been found here.");
												resolve(obj.permalink);
											} else {
												throw e;
											}
											return;
										}
										var nextPage = returnedData;
										log("No shortcut used in checking other pages, nextPage = " + nextPage);
										var notifyNextPage = ((nextPage === currentP - 1) || (nextPage === currentP + 1));
										setZeroTimeout(function () {
											getPermalinkFromDate(nextPage, d, limit, notifyNextPage).then(function (ret) {
												log("Check (getPermalinkFromDate) done. It was in page " + nextPage + ", " + ((notifyNextPage) ? "(notify because +/- 1)," : "") + " and returned: " + ret);
												if (ret !== true) {
													log("A child instance has found a better message.");
												} else {
													log("We already had found the correct message.");
												}
												resolve(((ret === true) ? obj.permalink : ret));
											}).catch(reject);
										});
										return;
									}

									//We're sure that the correct permalink is on this page
									log("#" + (obj.trueIndex + 1) + " found! Returning the permalink (" + obj.permalink + ") to the parent...");
									resolve(obj.permalink);
									return;
								}
								throw "Erreur fonction getPermalinkFromDate : la fonction n'aurait jamais dû atteindre cet endroit...";
							});
						}

						/*
						 * Checks the date of a message and queues the next one.
						 * @param {Object} args: contains the parameters. I could send them all back with resolve(), but it'd be less easy to maintain because there are several resolve() calls in this function and a lot of parameters...
						 * Args' keys are:
						 * {Number} i: the index of the current message.
						 * (List of jQuery selectors) $pageMessages: the messages of the current page.
						 * {Number} max: the number of messages in the current page.
						 * {Date} dateToFind: the date to find.
						 * {Date} previousTrueDate: the date of the previous message which could fit.
						 * {String} prevPermalink: the permalink of the previous message which could fit.
						 * {Boolean} itIsBefore: true if the correct message if before this page, false otherwise.
						 * {Boolean} itIsAfter: true if the correct message if after this page, false otherwise.
						 * {String} permalink: the permalink of the correct message.
						 * {Number} trueIndex: the index of the correct message.
						 * @returns {Promise}: promise.
						 */
						function checkDateMessage(args) {
							return new P(function (resolve, reject) {
								log("checkDateMessage: message #" + (args.i + 1) + "/" + args.max + "...");
								//Pretty much useless
								var ret = handlePauseAndAbandon("checkDateMessage", reject, null, function () {
									checkDateMessage.call(this, args).then(resolve).catch(reject);
								});
								if (ret === true) {
									return;
								}
								if (args.i >= args.max) {
									log("checkDateMessage: I have read all the messages of this page.");
									resolve();
									return;
								}
								var $mess = args.$pageMessages.eq(args.i);
								var trueDate = getMessageData(currentP, $mess, "date");
								log("Comparing " + trueDate + " to " + args.dateToFind + ".");
								//Limits (first post)
								if (args.dateToFind < trueDate && args.i === 0) {
									if (limit === "max") {
										if (currentP === 1) {
											reject(getDateError(args.dateToFind, "tooSoon"));
											return;
										}
										log("It is before.");
										args.itIsBefore = true;
									} else {
										args.permalink = getMessageData(currentP, $mess, "msgDirect");
										args.trueIndex = args.i;
										if (currentP === 1) {
											log("The min permalink is the message #" + (args.i + 1) + ": " + args.permalink);
										} else {
											log("Maybe the min permalink is the message #" + (args.i + 1) + ": " + args.permalink);
										}
									}
									resolve();
									return;
								}
								//Limits (last post)
								if (args.dateToFind > trueDate && (args.i + 1) === args.max) {
									if (limit === "min") {
										if (currentP === lastP) {
											reject(getDateError(args.dateToFind, "tooLate"));
										} else {
											log("It is after.");
											args.itIsAfter = true;
										}
									} else {
										args.permalink = getMessageData(currentP, $mess, "msgDirect");
										args.trueIndex = args.i;
										if (currentP === lastP) {
											log("The max permalink is the message #" + (args.i + 1) + ": " + args.permalink);
										} else {
											log("Maybe the max permalink is the message #" + (args.i + 1) + ": " + args.permalink);
										}
									}
									resolve();
									return;
								}
								//If the current message was sent the same date (or after) as the date to find, and we want to get the first one
								if (args.dateToFind <= trueDate && limit === "min") {
									args.permalink = getMessageData(currentP, $mess, "msgDirect");
									args.trueIndex = args.i;
									resolve();
									return;
								}
								//From now on, limit === "max"
								/* If the current page has only one message (first and last post at once)...
								 * ... and this post is sent at the exact same date as the specified one.*/
								if (args.max === 1) {
									args.permalink = getMessageData(currentP, $mess, "msgDirect");
									args.trueIndex = args.i;
									resolve();
									return;
								}
								/*If the previous message is before (or equals) the date to find, and (the current message is after the date to find...
								 or is at the date to find and is the last one on the page)*/
								if (args.previousTrueDate !== null && //Not the 1st message of the page
									args.previousTrueDate <= args.dateToFind &&
									(trueDate > args.dateToFind ||
										(i === args.max &&
											args.dateToFind.toString() === trueDate.toString()))) {
									//Get the last message sent at the provided date, or sooner
									args.permalink = ((args.dateToFind.toString() === trueDate.toString()) ?
										getMessageData(currentP, $mess, "msgDirect") :
										args.prevPermalink);
									args.trueIndex = ((args.dateToFind.toString() === trueDate.toString()) ?
										args.i :
										(args.i - 1));
									resolve();
									return;
								}
								args.previousTrueDate = trueDate;
								args.prevPermalink = getMessageData(currentP, $mess, "msgDirect");
								nextTick(function () {
									args.i++;
									checkDateMessage.call(this, args).then(resolve).catch(reject);
								});
							});
						}

						/*
						 * Gets a date error message.
						 * @param {Date} d: the date.
						 * @param {String} tooSoonOrTooLate: if the date d is "tooSoon" or "tooLate".
						 * @returns {String}: the error message.
						 */
						function getDateError(d, tooSoonOrTooLate) {
							return ((tooSoonOrTooLate === "tooSoon") ?
								"La date d'arrivée (" + d.toLocaleString() + ") est antérieure au premier message de ce sujet !" :
								"La date de départ (" + d.toLocaleString() + ") est postérieure au dernier message de ce sujet !");
						}

						/*
						 * Gets the next page to visit.
						 * @param {Number} currentP: the current page.
						 * @param {Date} d: the date.
						 * @param {String} direction: if the message to find is "before" of "after" the current page.
						 * @param {jQuery selector} selector: the div where the current page is.
						 * @param {String} limit: if the message to find is the "min" or "max" limit.
						 * @returns {Number}: the next page to check.
						 */
						function getTheNextPage(currentP, d, direction, selector, limit) {
							/*
							 * Finds the next page to check.
							 * @param {Date} date1: the date of the first message.
							 * @param {Date} date2: the date of the last message.
							 * @param {Date} d: the date to reach.
							 * @param {String} direction: where to go if we're on an already checked page.
							 * @returns Number: the next page to check.
							 */
							function computePosition(date1, date2, d, direction) {
								//log("computePosition: " + date1 + ", " + date2 + ", " + d + ", " + direction);
								var t1, t2, t, altT2, altT, nbPagesToCheck, timeRangePerPage, pageToReturn;
								t1 = date1.getTime();
								t2 = date2.getTime();
								if (t2 < t1) {
									throw "Erreur fonction computePosition : " + date2.toLocaleString() + " < " + date1.toLocaleString();
								}
								t = d.getTime();
								altT2 = t2 - t1;
								altT = t - t1;
								nbPagesToCheck = ((datePageRange.end !== "") ? datePageRange.end : lastP) -
									((datePageRange.begin !== "") ? datePageRange.begin : 1) -
									((datePageRange.end !== "" && datePageRange.begin !== "") ? 1 : 0); //begin and end are set (<=> already visited) => do not read them again
								//log("t1, t2, t, altT2, altT, nbPagesToCheck", t1 + ", " + t2 + ", " + t + ", " + altT2 + ", " + altT + ", " + nbPagesToCheck);
								/* For instance, for the min message:
								 * Reads the page x, doesn't find the message >= the given date.
								 * Reads the page x+1, the first message >= the given date, so it's the correct one.
								 * But it'll check for a better message in page x, which leads us here (0 pages to check).
								 * Throwing "found" will tell the calling function not to check another page.*/
								if (nbPagesToCheck === 0) {
									throw "found";
								}
								if (nbPagesToCheck === 1 || altT === 0 || altT2 === 0) {
									var key = ((datePageRange.begin !== "") ? "begin" : "end");
									return datePageRange[key] + ((key === "begin") ? 1 : (-1));
								}
								timeRangePerPage = altT2 / nbPagesToCheck;
								//log("timeRangePerPage=" + timeRangePerPage + ", datePageRange:", datePageRange);
								pageToReturn = ((datePageRange.begin !== "") ? datePageRange.begin : 1) + Math.floor(altT / timeRangePerPage);
								//If we already have checked that page, get another one
								while (datePageRange.checkedPages.indexOf(pageToReturn) >= 0) {
									pageToReturn = pageToReturn + ((direction === "after") ? 1 : (-1));
								}
								//log("computePosition says: page " + pageToReturn);
								if (pageToReturn < ((datePageRange.begin !== "") ? datePageRange.begin : 1)) {
									return ((datePageRange.begin !== "") ? datePageRange.begin + 1 : 1);
								}
								if (pageToReturn > ((datePageRange.end !== "") ? datePageRange.end : lastP)) {
									return ((datePageRange.end !== "") ? datePageRange.end - 1 : lastP);
								}
								return pageToReturn;
							}

							var date1, date2, pageKey = ((direction === "before") ? "begin" : "end"), $localMessage, perm1, perm2, divID, trueDate = d;
							//log("getTheNextPage: " + d + ", " + direction + ", " + lastP + ", '" + selector + "', " + limit);
							$localMessage = ((direction === "before") ?
								s(selector + " .bloc-message-forum:not(.msg-pseudo-blacklist):first") :
								s(selector + " .bloc-message-forum:not(.msg-pseudo-blacklist):last"));
							perm1 = getMessageData(currentP, $localMessage, "msgDirect");
							//log("Getting the date of a permalink (" + perm1 + ")...");
							var returnedDate = getMessageData(currentP, $localMessage, "date");
							//log("Returned date is: " + returnedDate);
							if (direction === "before") {
								date2 = returnedDate;
								if (date2 < trueDate && limit === "min") {
									alert(getDateError(d, "tooLate"));
									throw "abandon";
								}
							} else {
								date1 = returnedDate;
								if (date1 > trueDate && limit === "max") {
									alert(getDateError(d, "tooSoon"));
									throw "abandon";
								}
							}
							//log("pageKey=" + pageKey + ", datePageRange:", datePageRange);
							var distantPage = ((datePageRange[pageKey] !== "") ?
								datePageRange[pageKey] :
								((pageKey === "begin") ?
									1 :
									lastP));
							//log("Getting the date of the " + ((direction === "before") ? "first" : "last") + " message of the page " + distantPage + "...");
							var ret = getMessageDataFromPage(((direction === "before") ? "first" : "last"), distantPage);
							perm2 = ret.permalink;
							//log("perm2=" + perm2);
							divID = "#page" + ((usePageCache) ? distantPage : "");
							returnedDate = getMessageData(distantPage, s(divID + " .bloc-message-forum:not(.msg-pseudo-blacklist):" + ((direction === "before") ? "first" : "last")), "date");
							//log("Returned date is: '" + returnedDate + "'");
							if (direction === "before") {
								date1 = returnedDate;
								if (date1 > trueDate && limit === "max") {
									alert(getDateError(d, "tooSoon"));
									throw "abandon";
								}
							} else {
								date2 = returnedDate;
								if (date2 < trueDate && limit === "min") {
									alert(getDateError(d, "tooLate"));
									throw "abandon";
								}
							}
							if (date1 > date2) {
								throw "Erreur fonction getTheNextPage : la date de départ est postérieure à la date d'arrivée !";
							}
							//log("date1, date2, trueDate, limit: " + date1 + ", " + date2 + ", " + trueDate + ", " + limit);

							//Shortcut
							if ((date1 >= trueDate && limit === "min") ||
								(date2 <= trueDate && limit === "max")) {
								return distantPage;
							}

							//log("Before computePosition, " + date1 + ", " + date2 + ", looking for " + d);
							return computePosition(date1, date2, d, direction);
						}

						var args = arguments;
						return new P(function (resolve, reject) {
							var ret = handlePauseAndAbandon("getPermalinkFromDate", reject, null, function () {
								getPermalinkFromDate.apply(this, args).then(resolve).catch(reject);
							});
							if (ret === true) {
								return;
							}
							var currentPType = getRealType(currentP);
							if (currentPType !== "Number") {
								throw "Erreur fonction getPermalinkFromDate : (" + currentPType + ") page='" + currentP + "'";
							}
							//I am not sure if we need this, but too scared to delete
							if (datePageRange.checkedPages.indexOf(currentP) >= 0) {
								resolve(true);
								return;
							}
							//log("getPermalinkFromDate:", "Page " + currentP + ", original page = " + realPage + ", limit = " + limit + ", d = " + d + ((notify) ? ", notify" : "") + ", lastP = " + lastP);
							datePageRange.checkedPages.push(currentP);
							//log("datePageRange:", datePageRange);
							var $dateProgress = $minMaxInfo.find("#" + ((limit === "max") ? "lastPermalink" : "firstPermalink") + "Area .dateProgress");
							$dateProgress.html("<span>" + ((limit === "min") ? "Départ" : "Arrivée") +
								" : page " + currentP + "...</span>" +
								"<b class='pauseText'></b>");
							var permalink = null, prevPermalink = null, previousTrueDate = null, dateToFind = d, itIsBefore = false, itIsAfter = false, nbMsg, selector, nextPage, trueIndex;
							if (usePageCache) {
								selector = "#page" + currentP;
							} else {
								selector = "#page";
							}
							getPageContent(currentP);
							var $pageMessages = s(selector + " .bloc-message-forum:not(.msg-pseudo-blacklist)");
							nbMsg = $pageMessages.length;
							//log(nbMsg + " messages to check for the page " + currentP + "...");
							if (nbMsg < 1) {
								throw "Erreur fonction getPermalinkFromDate : nbMsg=" + nbMsg + ", pour la page " + currentP + " ! selector=" + selector;
							}
							//var start = performance.now();

							//Oh God, what have I done
							var obj = {"i": 0, "$pageMessages": $pageMessages, "max": nbMsg, "dateToFind": dateToFind, "previousTrueDate": previousTrueDate, "prevPermalink": prevPermalink, "itIsBefore": itIsBefore, "itIsAfter": itIsAfter, "permalink": permalink, "trueIndex": trueIndex};
							checkDateMessage(obj).then(function () {
								return afterCheckDateMessage(obj, nbMsg, selector);
							}).then(resolve).catch(reject);
						});
					}

					/*
					 * Resets the datePageRange object.
					 * @returns {undefined}
					 */
					function resetDatePageRange() {
						datePageRange.begin = "";
						datePageRange.end = "";
						datePageRange.checkedPages = [];
					}

					/*
					 * Saves the entered data into an object.
					 * @param {String} realTypeOfResearch: the current type of research, which will set the research's type.
					 * @returns {undefined}
					 */
					function saveInput(realTypeOfResearch) {
						/*
						 * Stores the entered page into an object.
						 * @param {Object} o: contains the type and the value of the limit.
						 * @param {String} limit: "min"/"max".
						 * @returns {undefined}
						 */
						function storePage(o, limit) {
							var gotVal, type, $pageArea = s("#" + format(realTypeOfResearch) + " #" + limit + "LimitSelector #pageArea");
							gotVal = parseInt($pageArea.val(), 10);
							type = getRealType(gotVal);
							if (type !== "Number") {
								throw "La page " + ((limit === "min") ? "de départ" : "d'arrivée") + " : (" + type + ") '" + gotVal + "' est vide ou n'est pas un nombre !";
							}
							if (gotVal < $pageArea.attr("min") || gotVal > $pageArea.attr("max")) {
								throw "La page " + ((limit === "min") ? "de départ" : "d'arrivée") + " : (" + type + ") '" + gotVal + "' est hors des limites du sujet !";
							}
							o.value = gotVal;
						}

						enteredData = {};
						var input, nbForm, forms = [], i = 0, j, min = {}, max = {};
						s("#" + format(realTypeOfResearch) + " .researchForm[data-disabled='no']").each(function () {
							nbForm = $(this).attr("data-number");
							forms[i] = {};
							forms[i].nb = nbForm;
							forms[i].inputs = [];
							$(this).find(".researchInput").each(function () {
								input = {};
								input.name = $(this).attr("data-name");
								input.value = $(this).find("textarea").val();
								input.params = {};
								if ($(this).find(".inputParamName").length === 0) {
									input.usingParams = false;
								} else {
									input.usingParams = true;
									input.params = [];
									$(this).find(".inputParamName").each(function () {
										var index = input.params.length;
										input.params[index] = {};
										input.params[index].paramName = $(this).attr("data-param");
										input.params[index].value = $(this).text();
									});
								}
								forms[i].inputs.push(input);
							});
							forms[i].authorsNickname = {usingAuthorNickname: false};
							if ($(this).find(".authorNickname").length > 0) {
								forms[i].authorsNickname.usingAuthorNickname = true;
								forms[i].authorsNickname.list = [];
								j = 0;
								$(this).find(".authorNickname input:checked").each(function () {
									//log($(this).attr("value") + " is checked.");
									forms[i].authorsNickname.list[j] = $(this).attr("value");
									j++;
								});
								if (forms[i].authorsNickname.list.length === 0) {
									alert("Vous avez décoché toutes les cases concernant les pseudos possibles des auteurs " +
										"dans le formulaire de recherche n°" + nbForm + ".\n\n" +
										"Cette sous-recherche ne prendra donc pas en compte le pseudo de l'auteur.");
									forms[i].authorsNickname.usingAuthorNickname = false;
								}
							}

							forms[i].colors = {usingColors: false, match: {txt: null, bg: null}, else: {txt: null, bg: null}};
							if ($(this).find(".colorPickers").length > 0) {
								forms[i].colors.usingColors = true;
								var curVal = "";
								$(this).find(".colorpicker").each(function () {
									if ($(this).spectrum("get") === null) {
										return true;
									}
									//Not using $(this).spectrum("get") because it doesn't easily give a rgb color
									curVal = $(this).prevAll("span:first").css((($(this).hasClass("font")) ? "color" : "background-color"));
									forms[i].colors[(($(this).hasClass("match")) ? "match" : "else")][(($(this).hasClass("font")) ? "txt" : "bg")] = curVal;
								});
							}

							i++;
						});
						if (i === 0) {
							throw "Erreur fonction saveInput : il n'y a pas de recherches actives !";
						}
						enteredData.nbForms = i;
						enteredData.forms = forms;
						min.type = s("#" + format(realTypeOfResearch) + " .limitType.Départ option:selected").attr("value");
						switch (min.type) {
							case "Page":
								storePage(min, "min");
								break;
							case "Date":
								var departVal = s("#" + format(realTypeOfResearch) + " #minLimitSelector .dateArea").datetimepicker('getDate');
								if (departVal === null || departVal === "") {
									throw "La date de départ est vide !";
								} else {
									min.value = departVal;
								}
								break;
							case "Permalien":
								min.value = s("#" + format(realTypeOfResearch) + " #minLimitSelector .permalinkArea").val();
								if (min.value === "") {
									throw "Le permalien de départ est vide !";
								}
								break;
							default:
								throw "Erreur fonction saveInput switch type départ : (" + getRealType(min.type) + ") min.type='" + min.type + "'";
						}
						max.type = s("#" + format(realTypeOfResearch) + " .limitType.Arrivée option:selected").attr("value");
						switch (max.type) {
							case "Page":
								storePage(max, "max");
								break;
							case "Date":
								var arriveeVal = s("#" + format(realTypeOfResearch) + " #maxLimitSelector .dateArea").datetimepicker('getDate');
								if (arriveeVal === null || arriveeVal === "") {
									throw "La date d'arrivée est vide !";
								} else {
									max.value = arriveeVal;
								}
								break;
							case "Permalien":
								max.value = s("#" + format(realTypeOfResearch) + " #maxLimitSelector .permalinkArea").val();
								if (max.value === "") {
									throw "Le permalien d'arrivée est vide !";
								}
								break;
							case "Fin":
								max.value = "Fin";
								break;
							default:
								throw "Erreur fonction saveInput switch type arrivée : (" + getRealType(max.type) + ") max.type='" + max.type + "'";
						}
						enteredData.limits = {"min": min, "max": max};
						if (s("#" + format(realTypeOfResearch) + " .chooseResultsOrder").length === 0) {
							enteredData.usingDisplay = false;
						} else {
							enteredData.usingDisplay = true;
							enteredData.display = s("#" + format(realTypeOfResearch) + " .chooseResultsOrder input:checked").attr("value");
						}
						enteredData.readBackwards = (s("#" + format(realTypeOfResearch) + " .Inv").length > 0 &&
							s("#" + format(realTypeOfResearch) + " .Inv #readBackwards").prop("checked"));
						enteredData.typeOfResearch = realTypeOfResearch;
						//log("Entered data:", enteredData);
					}

					/*
					 * Shows a dialog when a research is already running.
					 * @returns {undefined}
					 */
					function showDialog() {
						var dialog;
						var dialogButtons = {
							"Nouvelle recherche": function () {
								dialog.dialog('close');
								if (!doingAResearch) {
									startResearch();
								} else {
									log('Aborting so as to start a new research...');
									abandonRech = true;
									doItAgain = true;
								}
							},
							"Abandonner": function () {
								dialog.dialog('close');
								if (doingAResearch) {
									log('Aborting so as to end...');
									abandonRech = true;
								}
							},
							"Ne rien faire": function () {
								dialog.dialog('close');
							}
						};
						if (pauseRech) {
							dialogButtons.Reprendre = function () {
								dialog.dialog('close');
								if (doingAResearch && pauseRech) {
									log('Unpausing so as to continue...');
									unpause();
								}
							};
							delete dialogButtons["Mettre en pause"];
						} else {
							dialogButtons["Mettre en pause"] = function () {
								dialog.dialog('close');
								if (doingAResearch && !pauseRech) {
									log('Pausing...');
									pause();
								}
							};
							delete dialogButtons.Reprendre;
						}
						dialog = $('<p>Une autre recherche est en ' + ((pauseRech) ? "pause" : "cours") + '. Que désirez-vous faire ?</p>').dialog({
							title: "Choisir une action",
							width: "auto",
							buttons: dialogButtons
						});
					}

					/*
					 * Shows an error about a message that couldn't be loaded.
					 * @param {String} url: the URL of the message.
					 * @param {String} sel: the selector of the warning divs.
					 * @returns {undefined}
					 */
					function showMessageError(url, sel) {
						var $warning = s("#JVStalker " + sel);
						//If we add the first error
						if ($warning.first().find(".title").length === 0) {
							$warning.html("<span class='title'>Ce message est introuvable</span> :<span class='links'></span>.<br />");
						}
						//If we add the second error
						if ($warning.first().find("a").length === 1) {
							$warning.find(".title").text("Ces messages sont introuvables");
						}
						$warning.find(".links").append(" <a target='_blank' href='" + url + "'>" + ($warning.first().find("a").length + 1) + "</a>");
					}

					var handleResponseFromJVStalker = null;

					log("Go!");
					if (currentOption === "JVStalker") {
						var author = s("#JVStalkerAuthor textarea").val();
						var message = s("#JVStalkerMessage textarea").val();
						//log("JV Stalker. Author='" + author + "', message='" + message + "'.");
						if (author.indexOf(" ") >= 0 || author.trim() === "") {
							alert("L'auteur doit être renseigné et ne peut pas contenir d'espaces !");
							return;
						}
						if (doingAResearch) {
							showDialog();
							return;
						}
						doingAResearch = true;
						var stopAll = false;
						var isDone = false;
						var firstHeight = true;
						var hrefs = [];
						var trueHrefs = [];
						var JVCDirectMessages = [];
						unpause();
						s("#JVStalker .warning").empty();
						var currentForumId = String(window.location.href).split('-')[1];
						var realForumId = getForumName(currentForumId);
						if (realForumId === null) {
							throw "JV Stalker ne peut pas utiliser ce forum !";
						}
						var url = "http://jvarchive.fr/message/recherche/" + encodeRFC5987ValueChars(author) +
							"/" + realForumId +
							"/1/" +
							encodeRFC5987ValueChars(message);
						var iframe, permalinkBase = {"isSet": false}, max;

						handleResponseFromJVStalker = function (e) {
							/*
							 * Adds the JVC version of results div.
							 * @returns {undefined}
							 */
							function addJVCVersionOfResultsDiv() {
								var title = "Cacher les messages version JVC";
								var hideOrShowJVCResults;
								if (useFontAwesome) {
									hideOrShowJVCResults = "<span title='" + title + "' class='hideOrShowJVCResults fa fa-eye-slash'></span><br />";
								} else {
									hideOrShowJVCResults = "<button class='hideOrShowJVCResults'>" + title + "</button><br /><br />";
								}
								s("#JVStalker .warning:first").before("<div id='JVCVersionOfResultsWrapperAll'><div class='warningJVC'></div><br /><div id='JVCVersionOfResultsWrapper'>" + hideOrShowJVCResults + "<div id='JVStalkerJVCVersionOfResults'></div>" + hideOrShowJVCResults + "</div><br /><div class='warningJVC'></div></div>");
							}

							/*
							 * Checks, for each message in a list, if it belongs to the current topic.
							 * @param {Number} index: the index of the message in $mess.
							 * @param {Array} JVCDirectMessages: the array that will contain the JVC messages (from direct links only).
							 * @param {Array} messagesArray: the list of the messages to check.
							 * @param {jQuery selector} $messNb: the selector of the element to update for each message.
							 * @param {Array} arr: the array to update.
							 * @param {Number} realMax: the number of direct links in $mess.
							 * @param {Object} permalinkBase: if its property isSet is true, then its property str contains the "template" of direct links to messages on this topic.
							 * @returns {Promise}: promise.
							 */
							function checkBelonging(index, JVCDirectMessages, messagesArray, $messNb, arr, realMax, permalinkBase) {
								var args = arguments;
								return new P(function (resolve, reject) {
									var pauseStart = performance.now();
									var ret = handlePauseAndAbandon("checkBelonging", reject, null, function () {
										var pauseEnd = performance.now();
										JVStalkerStart = JVStalkerStart + (pauseEnd - pauseStart);
										checkBelonging.apply(this, args).then(resolve).catch(reject);
									});
									if (ret === true) {
										return;
									}
									//log("checkBelonging, index=" + index + ", messagesArray.length=" + messagesArray.length);
									if (index < messagesArray.length) {
										var directLink = messagesArray[index], anchor = null;
										$messNb.text(index + 1);
										anchor = getDataFromDirectLink(directLink, JVCDirectMessages, function () {
											showMessageError(directLink, ".warning");
										});
										anchor = anchor.permalink;
										//log("Direct link='" + directLink + "', anchor='" + anchor + "'");
										if (anchor === null || jQuery.isEmptyObject(anchor)) {
											arr.push(false);
										} else {
											var messTopicId = anchor.split('-')[2];
											//log("messTopicId:", messTopicId, "currentTopicId:", currentTopicId);
											if (messTopicId === currentTopicId) {
												arr.push(true);
											} else {
												arr.push(false);
												JVCDirectMessages.pop();
											}
										}
										setZeroTimeout(function () {
											checkBelonging(index + 1, JVCDirectMessages, messagesArray, $messNb, arr, realMax, permalinkBase).then(resolve).catch(reject);
										});
									} else { //index >= messagesArray.length
										resolve();
									}
								});
							}

							/*
							 * Loads JVC messages into a div.
							 * @param {Number} index: the current index.
							 * @param {Array} hrefs: the array containing all the links to the messages to load.
							 * @param {Array} JVCDirectMessages: the array containing all the already loaded messages (only for direct links).
							 * @param {jQuery selector} $dest: the div where to put the results into.
							 * @param {jQuery selector} $messNb: selector of the element to update for each message.
							 * @param {Object} permalinkBase: if its property isSet is true, then its property str contains the "template" of direct links to messages on this topic.
							 * @returns {Promise}: promise.
							 */
							function loadJVCMessages(index, hrefs, JVCDirectMessages, $dest, $messNb, permalinkBase) {
								/*
								 * http://stackoverflow.com/a/7924240
								 * Returns the number of getNbOfOccurences of a substring in a string.
								 * @param {String} string: the string.
								 * @param {String} subString: the string to search for.
								 * @param {Boolean} allowOverlapping: whether to allow overlapping. False by default.
								 */
								function getNbOfOccurences(string, subString, allowOverlapping) {
									string += "";
									subString += "";
									if (subString.length <= 0) return string.length + 1;

									var n = 0, pos = 0;
									var step = (allowOverlapping) ? (1) : (subString.length);

									while (true) {
										pos = string.indexOf(subString, pos);
										if (pos >= 0) {
											n++;
											pos += step;
										} else break;
									}
									return(n);
								}

								/*
								 * Start the next iteration of the loadJVCMessages function.
								 * @param {Number} i: the next index.
								 * @returns {undefined}
								 */
								function nextIteration(i) {
									return new P(function (resolve, reject) {
										setZeroTimeout(function () {
											loadJVCMessages(i, hrefs, JVCDirectMessages, $dest, $messNb, permalinkBase).then(resolve).catch(reject);
										});
									});
								}

								/*
								 * Loads a direct message into $dest.
								 * @param {String} url: the url to load.
								 * @param {jQuery selector} $dest: the target div.
								 * @param {Object} permalinkBase: if its property isSet is true, then its property str contains the "template" of direct links to messages on this topic.
								 * @returns {undefined}
								 */
								function loadDirectMsg(url, $dest, permalinkBase) {
									//log("Loading '" + url + "'...");
									callAjax(url, function (data) {
										//log("Load done.");
										var $data = $(data);
										var $mess = $data.find(".bloc-message-forum:not(.msg-pseudo-blacklist)");
										if ($mess.length === 0) {
											showMessageError(url, ".warningJVC");
											return;
										}
										addLinkToSelf($mess, url);
										$mess.addClass("scriptresult").appendTo($dest);
										if (permalinkBase.isSet === false) {
											var topicAuthor = $data.find(".bloc-pre-pagi-forum .group-one strong").text();
											permalinkBase.isSet = true;
											permalinkBase.author = topicAuthor.toLowerCase();
											permalinkBase.str = "http://" + window.location.hostname + "/" + encodeRFC5987ValueChars(topicAuthor.toLowerCase()) + "/forums/message/";
										}
									}, function (jqXHR, textStatus, errorThrown) {
										showMessageError(url, ".warningJVC");
									});
								}

								/*
								 * Updates the id of a permalink (for messages sent before Respawn).
								 * @param {String} url: the permalink.
								 * @returns {String}: the new permalink.
								 */
								function updateId(url) {
									var ind = url.lastIndexOf("#");
									var n = getPermalinkID(url);
									var ret = url;
									if (n !== '') {
										var r = get_ajax_session('liste_messages');
										$.ajax({
											type: 'POST',
											async: false,
											url: '/forums/ajax_redirect_permalink_jv_respawn.php',
											timeout: 10000,
											data: {
												id_forum: id_forum,
												old_id_message: n,
												id_topic: id_topic,
												ajax_timestamp: r.time,
												ajax_hash: r.hash
											},
											dataType: 'json',
											success: function (e) {
												if (e.newid_message !== 0) {
													ret = url.substring(0, ind + 1);
													ret += 'post_' + e.newid_message;
												}
											}
										});
									}
									return ret;
								}

								return new P(function (resolve, reject) {
									//log("loadJVCMessages, " + index + " -> " + hrefs.length);
									if (index < hrefs.length) {
										$messNb.text(index + 1);
										var pauseStart = performance.now();
										var ret = handlePauseAndAbandon("loadJVCMessages", reject, null, function () {
											var pauseEnd = performance.now();
											JVStalkerStart = JVStalkerStart + (pauseEnd - pauseStart);
											nextIteration(index).then(resolve).catch(reject);
										});
										if (ret === true) {
											return;
										}
										var ind = hrefs[index].lastIndexOf("#");
										//Anchor (message sent before Respawn)
										if (ind >= 0) {
											//JVStalker's error: "http://www.jeuxvideo.com/" might appear twice
											if (getNbOfOccurences(hrefs[index], "http://www.jeuxvideo.com/", false) === 2) {
												hrefs[index] = hrefs[index].replace("http://www.jeuxvideo.com/", "");
											}
											//log("hrefs[" + index + "]='" + hrefs[index] + "'");
											/*If the anchor's page is above the last page of the topic...
											 ... then convert the anchor to a direct link.*/
											var pageNb = getPageNumberFromURL(hrefs[index]);
											if (pageNb > lastP) {
												//log(pageNb + " > " + lastP);
												hrefs[index] = updateId(hrefs[index]);
												hrefs[index] = getDirectLinkFromAnchor(hrefs[index], permalinkBase);
												loadDirectMsg(hrefs[index], $dest, permalinkBase);
												nextIteration(index + 1).then(resolve).catch(reject);
												return;
											}
											ind = hrefs[index].lastIndexOf("#");
											//Replace the last "0" (before the ".htm") with the topic name
											var ind0 = hrefs[index].substring(0, ind).lastIndexOf("0");
											var tmp = hrefs[index].substring(0, ind0) + currentTopicNameInURL + hrefs[index].substring(ind0 + 1);
											hrefs[index] = tmp;
											ind = hrefs[index].lastIndexOf("#");
											hrefs[index] = updateId(hrefs[index]);
											var id = "post_" + getPermalinkID(hrefs[index]);
											var messSelector = ".bloc-message-forum[data-id='" + id + "']";
											var $target, stop = false;
											if (usePageCache && s("#page" + pageNb).length > 0) {
												$target = s("#page" + pageNb);
											} else {
												//log("Going to load the anchor '" + hrefs[index] + "'...");
												callAjax(hrefs[index], function (data) {
													//log("Anchor loaded.");
													var $msgs = $(data).find(".bloc-message-forum:not(.msg-pseudo-blacklist)");
													if ($msgs.length === 0) {
														//log("Error while loading the anchor, trying direct link...");
														hrefs[index] = getDirectLinkFromAnchor(hrefs[index], permalinkBase);
														loadDirectMsg(hrefs[index], $dest, permalinkBase);
														stop = true;
														return;
													}
													loadMsgsIntoCache(pageNb, $msgs, hrefs[index]);
													$target = s("#page" + ((!usePageCache) ? "" : pageNb));
												}, function (jqXHR, textStatus, errorThrown) {
													//log("Error while loading the anchor, trying direct link...");
													hrefs[index] = getDirectLinkFromAnchor(hrefs[index], permalinkBase);
													loadDirectMsg(hrefs[index], $dest, permalinkBase);
													stop = true;
												});
											}
											if (stop === false) {
												var $mess = $target.find(messSelector);
												//If deleted or in another page
												if ($mess.length === 0) {
													//log("No message found, maybe in another page? Trying direct link...");
													hrefs[index] = getDirectLinkFromAnchor(hrefs[index], permalinkBase);
													loadDirectMsg(hrefs[index], $dest, permalinkBase);
												} else { //There is a message
													//log("There is a message");
													$mess.clone().addClass("scriptresult").appendTo($dest);
												}
											}
										} else { //Direct link, already saved in JVCDirectMessages
											//log("Already loaded direct link");
											//log("JVCDirectMessages["+index+"]:", JVCDirectMessages[index]);
											$dest.append(JVCDirectMessages[index]);
											$dest.find(".bloc-message.forum:last").addClass("scriptresult");
										}
										nextIteration(index + 1).then(resolve).catch(reject);
									} else { //index >= hrefs.length
										resolve();
									}
								});
							}

                            var origin = e.origin || e.originalEvent.origin;
							if (origin !== 'http://jvstalker.fr' && origin !== "http://jvarchive.fr") {
								return;
							}
							if (e.data.hasOwnProperty("newPage")) {
								//log("newPage occured in child iframe");
								doingAResearch = true;
								stopAll = false;
								isDone = false;
								firstHeight = true;
								hrefs = [];
								trueHrefs = [];
								JVCDirectMessages = [];
								s("#JVStalker .warning").empty();
								s("#JVStalker #JVCVersionOfResultsWrapperAll").remove();
								s("#JVStalker #pauseWrapper").show();
								s("#JVStalker #info").html("Une nouvelle page a été chargée. Récupération de la taille de la fenêtre...");
								e.source.postMessage({'height': true}, origin);
								return;
							}
							//log("stopAll:", stopAll, "firstHeight:", firstHeight, "isDone:", isDone);
							if (stopAll === true) {
								return;
							}
							//log("in jvc and from jvstalker, e.data:", e.data);
							if (e.data.hasOwnProperty("height")) {
								var h = parseInt(e.data.height, 10) + 20;
								s('#JVStalkerIframe').attr("height", h + "px");
								if (firstHeight) {
									firstHeight = false;
									s("#JVStalker #info").html("Redimensionnement effectué. Récupération des liens...");
									e.source.postMessage({"gethrefs": arr}, origin);
								} else { //Final resizing
									if (isDone === true) {
										stopAll = true;
										if (s("#loadJVCMessages").is(":checked")) {
											s("#JVStalker #info").html("Chargement des messages version JVC...<br /><br />" +
												"Message <span id='messageNb'>1</span>/" + trueHrefs.length + "...");
											if (s("#JVCVersionOfResultsWrapper").length > 0) {
												s("#JVCVersionOfResultsWrapper").remove();
											}
											addJVCVersionOfResultsDiv();
											JVStalkerStart = performance.now();
											loadJVCMessages(0, trueHrefs, JVCDirectMessages, s("#JVStalkerJVCVersionOfResults"), s("#JVStalker #messageNb"), permalinkBase).then(function () {
												log("Time spent to load JVC messages: " + (performance.now() - JVStalkerStart) + "ms");
												endJVStalkerResearch("La recherche sur cette page de JVStalker est terminée.");
											}).catch(function (ret) {
												if (ret === "abandon") {
													endJVStalkerResearch("La recherche a été abandonnée.");
												} else {
													handleException("Une erreur est survenue : " + ret + ".");
												}
											});
										} else {
											endJVStalkerResearch("La recherche sur cette page de JVStalker est terminée.");
										}
									}
								}
								return;
							}
							if (e.data.hasOwnProperty("hrefs")) {
								if (isDone === true) {
									return;
								}
								hrefs = e.data.hrefs;
								var arr = [];
								if (e.data.hrefs.length > 0) {
									max = hrefs.length;
									s("#JVStalker #info").html("Liens récupérés. Filtrage des messages en cours...<br /><br />" +
										"Message <span id='messageNb'>1</span>/" + max + "...");
									var $messNb = s("#JVStalker #info #messageNb");
									JVStalkerStart = performance.now();
									checkBelonging(0, JVCDirectMessages, hrefs, $messNb, arr, max, permalinkBase).then(function () {
										log("Time spent to filter: " + (performance.now() - JVStalkerStart) + "ms");
										var ind = 0;
										trueHrefs = hrefs.filter(function () {
											var val = arr[ind];
											ind++;
											return (val === true);
										});
										s("#JVStalker #info").html("Filtrage effectué. Envoi des informations...");
										e.source.postMessage({"hrefsresponse": arr}, origin);
									}).catch(function (ret) {
										if (ret === "abandon") {
											endJVStalkerResearch("La recherche a été abandonnée.");
										} else {
											handleException("Une erreur est survenue : " + ret + ".");
										}
									});
								} else {
									endJVStalkerResearch("Aucun message n'a été trouvé. La recherche sur cette page de JVStalker est terminée.");
								}
								return;
							}
							if (e.data.hasOwnProperty("done")) {
								if (isDone === true) {
									return;
								}
								s("#JVStalker #info").html("Messages non conformes supprimés. Redimensionnement final...");
								isDone = true;
								e.source.postMessage({'height': true}, origin);
								return;
							}
                            if (e.data.hasOwnProperty("started")) {
                                if (e.data.started === true) {
                                    s("#JVStalker").off("click", ".pauseResearch", abortIframeLoad);
                                    log("Load of '" + url + "' done.");
                                    s("#JVStalker #info").html("Messages chargés. Récupération de la taille de la fenêtre...");
                                    iframe.contentWindow.postMessage({'height': true}, origin);
                                }
                                return;
                            }
						};

						window.addEventListener("message", handleResponseFromJVStalker, false);

						s("#JVCVersionOfResultsWrapper").remove();
						s("#JVStalkerIframeWrapper").empty();
						iframe = document.createElement("iframe");
						s("#JVStalker #infoWrapper").show().find("#info").html("Chargement... Cela peut prendre beaucoup de temps.");
						s("#JVStalker #pauseWrapper").show();
						iframe.id = "JVStalkerIframe";
						var title = "Cacher la fenêtre JV Stalker";
						var hideOrShowIframe;
						if (useFontAwesome) {
							hideOrShowIframe = "<span title='" + title + "' class='JVStalkerHideOrShowIframe fa fa-eye-slash'></span>";
						} else {
							hideOrShowIframe = "<button class='JVStalkerHideOrShowIframe'>" + title + "</button>";
						}
						log("Loading " + url + "...");
						s("#JVStalker").on("click", ".pauseResearch", abortIframeLoad);
						s("#JVStalkerIframeWrapper").show()
							.append(hideOrShowIframe + "<div><div><br /></div>")
							.append(iframe)
							.append("<div><br />" + hideOrShowIframe + "</div></div>")
							.find("iframe")
							.attr("width", "100%")
							.attr("height", "250px")
							.attr("scrolling", "auto");
						iframe.src = url;
						return;
					}

					if (doingAResearch) {
						showDialog();
						return;
					}
					doItAgain = false;
					abandonRech = false;
					unpause();
					isSkipDone = false;
					s("current").find(".warning, .warningJVC").empty();
					nbPagesRead = 0;
					document.title = originalTitle;
					var firstDate, lastDate, realTypeOfResearch = currentOption, permalinkMin = "", permalinkMax = "", $minMaxInfo = s("#" + format(realTypeOfResearch) + " #minMaxInfo");
					saveInput(realTypeOfResearch);
					doingAResearch = true;
					try {
						invTab = {Temps: false, Recherche: false, Auteur: false};
						s("#" + format(realTypeOfResearch) + " .info").remove();
						$minMaxInfo.find(".dateProgress").empty();
						$minMaxInfo.find(".pauseResearch").hide();
						var $res = s("#" + format(realTypeOfResearch) + " .resultsArea");
						if ($res.length > 0) {
							$res.remove();
						}
						$minMaxInfo.find("a").attr("href", "#").hide();
						resetDatePageRange();
						var ret;
						var executeAfterMinLimit = true;
						switch (enteredData.limits.min.type) {
							case "Date":
								firstDate = enteredData.limits.min.value;
								if (researchMessageByDate) {
									executeAfterMinLimit = false;
									var $dateProgress = $minMaxInfo.find("#firstPermalinkArea .dateProgress");
									$dateProgress.show();
									$minMaxInfo.find(".pauseResearch").show();
									//log("Calling getPermalinkFromDate for the min limit...");
									getPermalinkFromDate(realPage, enteredData.limits.min.value, "min", false).then(function (ret) {
										log("getPermalinkFromDate for the min limit done. ret=" + ret);
										cleanUp("firstPermalinkArea");
										if (doItAgain) {
											doingAResearch = false;
											startResearch();
										} else {
											permalinkMin = ret;
											afterMinLimit();
										}
									}).catch(function (e1) {
										try {
											cleanUp("firstPermalinkArea");
										} catch (e2) {
											handleException(e2);
										} finally {
											doingAResearch = false;
											if (e1 !== "abandon") {
												handleException(e1);
											}
										}
									});
								} else {
									permalinkMin = "Date";
								}
								break;
							case "Page":
								ret = getMessageDataFromPage("first", enteredData.limits.min.value, true);
								permalinkMin = ret.permalink;
								firstDate = ret.date;
								break;
							case "Permalien":
								permalinkMin = enteredData.limits.min.value;
								var isDirectLink = (permalinkMin.lastIndexOf("#") < 0);
								//We have the direct link
								if (isDirectLink) {
									ret = getDataFromDirectLink(permalinkMin);
									firstDate = ret.date;
								} else { //Too bored to implement this, and it'd be very rarely used...
									throw "Merci de fournir un permalien direct.";
								}
								break;
							default:
								doingAResearch = false;
								throw "Erreur switch type départ : (" + getRealType(enteredData.limits.min.type) + ") type='" + enteredData.limits.min.type + "'";
						}
						if (executeAfterMinLimit) {
							afterMinLimit();
						}
					} catch (e) {
						endSearch();
						throw e;
					}
				}

				/*
				 * Unpauses the research.
				 * @returns {undefined}
				 */
				function unpause() {
					pauseRech = false;
					s(".unpauseResearch").removeClass("unpauseResearch")
						.addClass("pauseResearch");
					var title = "Mettre la recherche en pause";
					if (useFontAwesome) {
						s(".pauseResearch").removeClass("fa-play")
							.addClass("fa-pause")
							.attr("title", title);
					} else {
						s(".pauseResearch").html(title);
					}
					s(".pauseText").empty();
					//log("Unpaused.");
				}

				/*
				 * Updates the results' indexes (visible on hover).
				 * @returns {undefined}
				 */
				function updateMessagesIndexes() {
					var $msgList = s("research").find("#results #messages .bloc-message-forum"), nth, nbRes = $msgList.length;
					$msgList.each(function () {
						nth = $(this).index() + 1;
						$(this).attr("data-element-index", nth).attr("title", "Message n°" + nth + "/" + nbRes + ", recherche n°" + $(this).attr("data-form-nb"));
					});
				}

				/*
				 * If you want to add an option:
				 * add an object in the optionsInfo array, with:
				 * a unique name,
				 * a boolean canHasMultipleForms,
				 * (optional) a load() function to execute when this script loads,
				 * (optional) and an afterNewForm() function which will execute every time a new form is added in this option.
				 */
				var optionsInfo = [
					//I probably will implement this option when JVC's native research by message (in a forum) will be back.
					/*{
					 name: "Native",
					 canHasMultipleForms: true,
					 load: function () {
					 }
					 }, */{
						name: "Simple",
						canHasMultipleForms: true,
						load: function () {
							initializeResearchOption(s("#Simple"));
						},
						afterNewForm: function () {
							var advancedItems = [
								".inputImage",
								".researchInputParams",
								".authorNickname",
								".colorPickers",
								".chooseResultsOrder",
								".Inv"
							];
							for (var i = 0; i < advancedItems.length; i++) {
								s("#Simple " + advancedItems[i]).remove();
							}
						}
					}, {
						name: "Avancée",
						canHasMultipleForms: true,
						load: function () {
							initializeResearchOption(s("#Avancee"));
						}
					}, {
						name: "Paramètres",
						canHasMultipleForms: false,
						load: function () {
							var $paramDiv, n, option, properlyFormattedOption, opts;
							for (var i = 0; i < parametersInfo.length; i++) {
								n = parametersInfo[i].name;
								opts = parametersInfo[i].display.options;
								if (typeof opts === "function") {
									opts = opts();
								}
								//log("Adding param #" + i + ": '" + n + "'. Values are:", opts);
								s("#Parametres").append("<div id='parametersName" + n + "'></div>");
								$paramDiv = s("#parametersName" + n);
								$paramDiv.append("<span class='parametersTitle' id='parametersTitle" + n + "'>" +
									"<u>" + parametersInfo[i].display.show + " :" + "</u>" +
									"</span>");
								for (var j = 0; j < opts.length; j++) {
									option = opts[j];
									properlyFormattedOption = format(option);
									//log("Adding option #" + j + ": '" + option + "'...");
									$paramDiv.append("<label>" + option + "&nbsp;" +
										"<input class='parametersRadioButton' type='radio' name='" + n + "' " +
										"id='" + n + properlyFormattedOption + "' " +
										"data-index='" + i + "' " +
										"value='" + option + "'>" +
										"</input>" +
										"</label>");
								}
								if (parametersInfo[i].hasOwnProperty("init")) {
									//log(n + " is a special param, so custom init");
									parametersInfo[i].init();
								} else {
									//log(n + " is a regular param, so default init. Its value is: ' + window[n] + "', options are:", opts);
									//We consider the param as a boolean; the first option means "true".
									(window[n]) ?
										s(".parametersRadioButton[name='" + n + "'][value='" + opts[0] + "']").click() :
										s(".parametersRadioButton[name='" + n + "'][value='" + opts[1] + "']").click();
								}
							}
						}
					}, {
						name: "Aide",
						canHasMultipleForms: false,
						load: function () {
							/*
							 * Lists the authors' possible nicknames.
							 * @returns {String}: the list.
							 */
							function listAuthors() {
								var showColors = false;
								var str = "";
								str += "<b " + ((showColors) ? "style='color: " + authorNicknames[0].color + "'" : "") + ">" + authorNicknames[0].type + "</b>";
								for (var i = 1; i < (authorNicknames.length - 1); i++) {
									str += ", <b " + ((showColors) ? "style='color: " + authorNicknames[i].color + "'" : "") + ">" + authorNicknames[i].type + "</b>";
								}
								str += " et <b " + ((showColors) ? "style='color: " + authorNicknames[i].color + "'" : "") + ">" + authorNicknames[i].type + "</b>";
								return str;
							}

							s("#Aide").append(/*"<h4>Recherche native</h4><br />" +

							 "La recherche native utilise la recherche par message déjà implémentée par JVC. PostFinder cherchera ce sujet dans les résultats donnés par la recherche, et, s'il s'y trouve, listera les messages trouvés. Cette méthode, bien qu'elle soit la plus basique, est toutefois la plus rapide si votre recherche concerne un gros sujet dont les résultats apparaissent parmi les premiers messages trouvés. Toutefois, plus les messages du sujet sont anciens, plus longtemps vous devrez attendre.<br /><br />" +

							 "<h4>Formulaires de recherche</h4><br />" +

							 */"Tous les champs sont optionnels. Si vous cliquez directement sur <b>Valider</b>, PostFinder listera tous les messages du sujet. Cela peut servir de \"dérouleur de sujet\", ce qui vous permet d'éviter d'avoir à cliquer tout le temps sur la page suivante si vous souhaitez lire un sujet dans son intégralité.<br /><br />" +
								"Si vous cliquez sur le bouton <b>Réduire</b>, il vous suffira de survoler le texte avec la souris pour afficher la version agrandie.<br /><br />" +
								"Le champ <b>Image</b> est destiné à contenir tout ou partie de l'identifiant d'une miniature Noelshack postée dans le sujet. Exemple : pour trouver une miniature dont le lien est \"<a class='link' target='_blank' href='http://image.noelshack.com/fichiers/2015/10/1425423166-image.jpg'>http://image.noelshack.com/fichiers/<b>2015/10/1425423166-image.jpg</b></a>\", entrez tout ou partie de \"2015/10/1425423166-image.jpg\".<br /><br />" +
								"Le paramètre <b>Normal</b> (resp. <b>Inversé</b>) servira à rechercher les messages qui correspondent au champ (resp. qui ne le lui correspondent pas).<br /><br />" +
								"Le paramètre <b>indexOf</b> (resp. <b>Regex</b>) fera en sorte que la recherche utilise le contenu du champ tel quel (resp. l'interprète).<br />" +
								"Les regex (<i><b>Reg</b>ular <b>Ex</b>pressions</i>, Expressions Rationnelles) constituent un outil puissant servant à interpréter une expression de façon abstraite. Exemple : si vous ne vous souvenez plus du pseudonyme complet de l'auteur d'un message que vous cherchez, mais vous vous souvenez que cela commençait par \"Twi\" et que cela finissait par \"ght\", il vous suffira de taper : \"Twi.*ght\".<br />" +
								"Note : si vous utilisez les regex, vous devrez échapper tous les caractères spéciaux que vous voulez utiliser en tant que caractères simples. C'est-à-dire les préfixer avec un backslash : \"\\\".<br />" +
								"Pour plus d'informations sur ce sujet, vous pouvez visiter <a class='link' target='_blank' href='https://developer.mozilla.org/fr/docs/Web/JavaScript/Guide/Expressions_r%C3%A9guli%C3%A8res'>ce site</a>.<br /><br />" +
								"Le paramètre <b>Texte</b> (resp. <b>HTML</b>) sert à indiquer s'il faut rechercher le contenu du champ uniquement dans le texte ou dans tout le contenu HTML.<br /><br />" +
								"Les cases à cocher " + listAuthors() + " détermineront si des messages correspondent ou non, selon le type du pseudo de l'auteur du message. PostFinder ne garde pas en mémoire les auteurs trouvés, il se peut donc qu'il ne trouve pas de messages de modérateurs si ceux-ci postent en tant que forumeur, par exemple.<br /><br />" +
								"Le choix de la couleur du texte et de l'arrière-plan, pour le texte correspondant et pour le reste du message, est destiné à améliorer la lisibilité et à séparer clairement les résultats des recherches multiples, surtout si vous ne les avez pas regroupés par recherche.<br /><br />" +
								"Le choix de la méthode de tri des messages trouvés est aussi modifiable après la recherche : il vous suffit de cliquer sur l'un des trois boutons <b>Temps</b>, <b>Auteur</b> ou <b>Recherche</b> ; un deuxième clic inversera l'ordre du tri.<br /><br />" +
								"Si vous avez sélectionné l'option <b>Date</b> pour au moins l'un des deux sélecteurs de départ ou d'arrivée, ainsi que le paramètre <b>Chercher le message correspondant à une date sélectionnée</b>, le script recherchera d'abord le message correspondant à la date indiquée. Il trouvera le premier (resp. dernier) message dont la date est égale ou supérieure (resp. égale ou inférieure) à la date indiquée, si celle-ci correspond au message de départ (resp. d'arrivée). C'est donc utile si vous souhaitez trouver rapidement des messages postés à une date particulière.<br /><br />" +
								"L'option <b>Fin</b> pour le message d'arrivée est utile en cas de recherche sur un sujet particulièrement actif : si, au cours de la recherche, de nouvelles pages sont créées, le script les tiendra en compte.<br /><br />");
						}
					}, {
						name: "Contact",
						canHasMultipleForms: false,
						load: function () {
							s("#Contact").append("Une suggestion ? Une question ? Un problème ? Un petit remerciement ? <img src='http://image.jeuxvideo.com/smileys_img/nyu.gif' alt=':cute:' style='vertical-align:baseline;'> Vous pouvez me contacter sur <a class='link' href='http://www.jeuxvideo.com/forums/42-1000021-40020280-1-0-1-0-postfinder-recherchez-des-messages-dans-un-topic.htm'>ce sujet dédié</a> ou <a class='link' href='http://www.jeuxvideo.com/messages-prives/nouveau.php?all_dest=Daring-Do'>par message privé</a>.");
						}
					}
				];
				var optionsName = [];
				var prefix = "PostFinder.";
				/*
				 * If you want to add a parameter:
				 * add an object to the parametersInfo array, with:
				 * a unique name,
				 * a default value,
				 * a "display" object, containing what is shown in "Paramètres"; if the parameter is a boolean, the first value of "options" means true,
				 * (optional) an init function so as to select the correct radio button in the "Paramètres" option; by default, the parameter is considered as a boolean and its 1st option means "true",
				 * (optional) and an onChange function that will set the parameter to its new value (the default procedure), plus any code you want.
				 */
				var parametersInfo = [
					{
						name: "showAtStartup",
						defaultValue: true,
						display: {
							show: "Afficher ou cacher le script par défaut",
							options: ["Afficher", "Cacher"]
						}
					}, {
						name: "compactAtStartup",
						defaultValue: false,
						display: {
							show: "Utiliser le mode réduit par défaut",
							options: ["Oui", "Non"]
						}
					}, {
						name: "defaultOption",
						defaultValue: "Simple",
						display: {
							show: "Choisir l'option par défaut",
							options: function () {
								return optionsName;
							}
						},
						init: function () {
							s(".parametersRadioButton[name='defaultOption'][value='" + defaultOption + "']").click();
						},
						onChange: function (id) {
							var res = s("#" + id).attr("value");
							if (res !== defaultOption) {
								localStorage.setItem(prefix + "defaultOption", res);
								defaultOption = res;
							}
						}
					}, {
						name: "useFontAwesome",
						defaultValue: true,
						display: {
							show: "Utiliser des icônes au lieu de boutons",
							options: ["Oui", "Non"]
						},
						onChange: function (id) {
							var res = s("#" + id).attr("value") === "Oui";
							if (useFontAwesome !== res) {
								localStorage.setItem(prefix + "useFontAwesome", res);
								alert("Veuillez recharger PostFinder afin " + ((res) ? "d'utiliser les icônes." : "de ne plus utiliser d'icônes."));
							}
						}
					}, {
						name: "deleteDuplicates",
						defaultValue: false,
						display: {
							show: "Supprimer les doublons des recherches",
							options: ["Oui", "Non"]
						}
					}, {
						name: "showBlacklistedMessages",
						defaultValue: false,
						display: {
							show: "Afficher les messages des forumeurs blacklistés dans les résultats",
							options: ["Oui", "Non"]
						}
					}, {
						name: "hideBeforeResearch",
						defaultValue: false,
						display: {
							show: "Cacher les messages de la page courante avant une recherche",
							options: ["Oui", "Non"]
						}
					}, {
						name: "caseInsensitiveAuthorSort",
						defaultValue: true,
						display: {
							show: "Ignorer la casse pendant un tri par auteur",
							options: ["Oui", "Non"]
						}
					}, {
						name: "researchMessageByDate",
						defaultValue: true,
						display: {
							show: "Chercher le message correspondant à une date sélectionnée",
							options: ["Oui (plus rapide)", "Non"]
						}
					}, {
						name: "usePageCache",
						defaultValue: true,
						display: {
							show: "Utiliser le cache",
							options: ["Oui (plus rapide)", "Non"]
						},
						onChange: function (id) {
							var res = s("#" + id).attr("value") === "Oui (plus rapide)";
							if (usePageCache !== res) {
								localStorage.setItem(prefix + "usePageCache", res);
								if (doingAResearch && typeof enteredData !== "undefined" && enteredData.hasOwnProperty("typeOfResearch")) {
									alert("Une recherche est en " + ((!pauseRech) ? "cours" : "pause") + ".\n\nLe cache ne sera " + ((res) ? "activé" : "désactivé") + " que lorsque vous aurez rechargé PostFinder.");
								} else {
									usePageCache = res;
								}
							}
						}
					}, {
						name: "useTabs",
						defaultValue: true,
						display: {
							show: "Utiliser les onglets ou les boutons radio",
							options: ["Onglets", "Boutons radio"]
						},
						onChange: function (id) {
							var res = (s("#" + id).attr("value") === "Onglets");
							if (useTabs !== res) {
								localStorage.setItem(prefix + "useTabs", res);
								alert("Veuillez recharger PostFinder afin " + ((res) ? "d'activer" : "de désactiver") + " les onglets.");
							}
						}
					}, {
						name: "showProgressBar",
						defaultValue: true,
						display: {
							show: "Afficher une barre de progression pendant une recherche",
							options: ["Oui", "Non"]
						}
					}, {
						name: "showProgressTime",
						defaultValue: true,
						display: {
							show: "Afficher le temps écoulé et le temps restant estimé pendant une recherche",
							options: ["Oui", "Non"]
						}
					}, {
						name: "showProgressInTitle",
						defaultValue: true,
						display: {
							show: "Afficher la progression d'une recherche dans le titre de la page",
							options: ["Oui", "Non"]
						}
					}, {
						name: "improveColorSelectorReadability",
						defaultValue: true,
						display: {
							show: "Améliorer par contraste la lisibilité des couleurs sélectionnées",
							options: ["Oui", "Non"]
						}
					}, {
						name: "sortWhenFinished",
						defaultValue: true,
						display: {
							show: "Trier les résultats",
							options: ["À la fin (plus rapide)", "Pendant la recherche"]
						}
					}, {
						name: "carriageReturnMeansGo",
						defaultValue: true,
						display: {
							show: "Appuyer sur Entrée dans un des champs lance une recherche",
							options: ["Oui", "Non"]
						}
					}, {
						name: "scriptIsAtTheTop",
						defaultValue: true,
						display: {
							show: "Emplacement de PostFinder par rapport à la page",
							options: ["Haut", "Bas"]
						},
						onChange: function (id) {
							var res = s("#" + id).attr("value") === "Haut";
							if (scriptIsAtTheTop !== res) {
								localStorage.setItem(prefix + "scriptIsAtTheTop", res);
								alert("Veuillez recharger PostFinder afin de changer son emplacement.");
							}
						}
					}, {
						name: "appearance",
						defaultValue: "smoothness",
						display: {
							show: "Apparence",
							options: ["Black Tie", "Blitzer", "Cupertino", "Dark Hive", "Dot Luv",
								"Eggplant", "Excite Bike", "Flick", "Hot Sneaks", "Humanity",
								"Le Frog", "Mint Choc", "Overcast", "Pepper Grinder", "Redmond",
								"Smoothness", "South Street", "Start", "Sunny", "Swanky Purse",
								"Trontastic", "UI Darkness", "UI Lightness", "Vader"]
						},
						init: function () {
							s(".parametersRadioButton[name='appearance']").filter(function () {
								return format($(this).val().toLowerCase()) === appearance;
							}).click();
						},
						onChange: function (id) {
							var res = format(s("#" + id).attr("value").toLowerCase());
							if (appearance !== res) {
								appearance = res;
								localStorage.setItem(prefix + "appearance", res);
								$(".toremove").remove();
								addTheme(res);
							}
						}
					}
				];
				var formInputsParams = [
					{"name": "contains",
						"first": "Normal ",
						"compactedFirst": "N",
						"second": "Inversé ",
						"compactedSecond": "I"},
					{"name": "regexOrIndexOf",
						"first": "indexOf ",
						"compactedFirst": "iO",
						"second": "Regex ",
						"compactedSecond": "R"},
					{"name": "case",
						"first": "Insensible à la casse ",
						"compactedFirst": "IC",
						"second": "Sensible à la casse ",
						"compactedSecond": "SC"},
					{"name": "textOrHTML",
						"first": "Texte ",
						"compactedFirst": "T",
						"second": "HTML ",
						"compactedSecond": "H"}
				];

				/* Implementation of setZeroTimeout.
				 * https://gist.github.com/mathiasbynens/579895 */
				(function () {
					var timeouts = [];
					var messageName = "zero-timeout-message";
					function setZeroTimeoutPostMessage(fn) {
						timeouts.push(fn);
						window.postMessage(messageName, "*");
					}

					function setZeroTimeoutFallback(fn) {
						setTimeout(function () {
							try {
								fn();
							} catch (e) {
								handleException(e);
							}
						}, 0);
					}

					function handleMessage(event) {
						if (event.source == window && event.data == messageName) {
							if (event.stopPropagation) {
								event.stopPropagation();
							}
							if (timeouts.length) {
								var f = timeouts.shift();
								try {
									f();
								} catch (e) {
									handleException(e);
								}
							}
						}
					}

					if (window.postMessage) {
						window.addEventListener('message', handleMessage, true);
						window.setZeroTimeout = setZeroTimeoutPostMessage;
					} else {
						window.setZeroTimeout = setZeroTimeoutFallback;
					}
				})();

				//This implementation executes its function parameter much faster than the setZeroTimeout function does, but the user cannot interact with the GUI.
				var nextTick = function () {
					var resolved = Promise.resolve();
					function nextTick2(fn) {
						resolved.then(fn).catch(handleException);
					}
					return nextTick2;
				}();

				var doingAResearch;
				var authorNicknames = [
					{type: "Supprimé", color: "#000"}, //black
					{type: "Forumeur", color: "#000"}, //black
					{type: "Modérateur", color: "#3a9d23"}, //green
					{type: "Administrateur", color: "#db0f0f"} //red
				];
				var previouslyHiddenFormsID = {};
				for (var i = 0; i < optionsInfo.length; i++) {
					optionsName[i] = optionsInfo[i].name;
					if (optionsInfo[i].canHasMultipleForms === false) {
						continue;
					}
					previouslyHiddenFormsID[optionsInfo[i].name] = [];
				}
				//Used when looking for the closest message to a date
				var datePageRange = {"begin": "", "end": "", "checkedPages": []};
				var pauseRech = false;
				var abandonRech = false;
				var doingInit;
				var currentTopicNameInURL;
				currentOption = "Simple";
				var isExtended;
				var doItAgain = false;
				//Progress
				var nbPagesRead = 0, nbPagesToRead, rounded, $progBar, rechStartDate, realTimeSpent = 0, originalTitle, originalURL;
				var foundMessages;
				var invTab;
				var realPage;
				var lastP;
				var isSkipDone = false;
				var JVStalkerStart;

				loadJVCScript();

				//Check for updates
				var updateSrc = "https://openuserjs.org/scripts/CrazyJeux/PostFinder/source";
				var updateScript = "https://openuserjs.org/src/scripts/CrazyJeux/PostFinder.user.js";
				var iframe = document.createElement("iframe");
				iframe.setAttribute("width", "1px");
				iframe.setAttribute("height", "1px");
				iframe.id = "CheckUpdateIframe";
				document.body.appendChild(iframe);

				window.addEventListener("message", function (evt) {
                    var origin = evt.origin || evt.originalEvent.origin;
					if (origin !== 'https://openuserjs.org') {
						return;
					}
                    if (evt.data.hasOwnProperty("started")) {
                        if (evt.data.started === true) {
                            iframe.contentWindow.postMessage({'getversion': true}, origin);
                        }
                        return;
                    }
					if (evt.data.hasOwnProperty("versionnumber")) {
						var remoteVersionNumber = parseInt(evt.data.versionnumber, 10);
						if (remoteVersionNumber === 0) {
							s("#options").before('<span style="color: red; font-weight: bold; font-size: 125%;"><span class="fa fa-exclamation-triangle"></span>&nbsp;Erreur : la recherche de mises à jour a échoué...</span>');
						} else {
							if (remoteVersionNumber > currentVersionNumber) {
								s("#options").before('<span style="color: green; font-weight: bold; font-size: 125%;"><span class="fa fa-info-circle"></span>&nbsp;Une mise à jour est disponible ! <a href=' + updateScript + ' target="_blank">Cliquez ici pour la télécharger...</a></span>');
							}
						}
						iframe.remove();
						return;
					}
				}, false);

				log("loading iframe...");
				iframe.src = updateSrc;
			}

			/*
			 * Logs its parameters in a legible fashion.
			 * @returns {undefined}
			 */
			function log() {
				try {
					return;
					var args = Array.prototype.slice.call(arguments), str = "", type;
					for (var i = 0; i < args.length; i++) {
						type = getRealType(args[i]);
						str += (i > 0 ? "\n" : "") + "(" + type + ") ";
						type = type.toLowerCase();
						switch (type) {
							case "arguments":
							case "array":
							case "object":
								var cache = [];
								str += JSON.stringify(args[i], function (key, value) {
									if (typeof value === 'object' && value !== null) {
										if (cache.indexOf(value) >= 0) {
											return "(Already shown here)";
										}
										cache.push(value);
									}
									return value;
								}, 4);
								break;
							case "undefined":
								str += "undefined";
								break;
							default:
								str += args[i];
						}
					}
					window.console && window.console.log && window.console.log(str);
				} catch (e) {
				}
			}

			/*
			 * s = selector
			 * Custom selector.
			 * @param {undefined|String} toFind: if "current", returns the current option's div. If "research", returns the current research's option's div. If undefined, returns the script div. Finally, if it's a string, returns its location in the script div.
			 * @returns {jQuery selector}: the selector to return.
			 */
			function s(toFind) {
				if (typeof toFind === "undefined") {
					return $("#PostFinder");
				}
				if (toFind === "current") {
					return s("#" + format(currentOption));
				}
				if (toFind === "research") {
					return s("#" + format(enteredData.typeOfResearch));
				}
				return $("#PostFinder").find(toFind);
			}

			try {
				var enteredData;
				var currentOption;
				var currentTopicId = String(window.location.href).split('-')[2];
				if (s().length > 0) {
					s().remove();
				}
				handleJVC();
			} catch (e) {
				handleException(e);
			}

			//The dark, still night, where we begin our new lives, the lives of radiance – the lives we dream for ourselves after the End of the World.
		}

		function handleCheckUpdate() {
			function handleResponse(evt) {
                var origin = evt.origin || evt.originalEvent.origin;
				if (origin === 'http://www.jeuxvideo.com') {
					if (evt.data.hasOwnProperty("getversion")) {
						var editor = document.querySelector("pre#editor");
						setTimeout(function () {
							var lines = editor.querySelectorAll(".ace_content .ace_comment");
							var versionNumber = 0;
							//console.log("lines.length: " + lines.length);
							for (var i = 0; i < lines.length; i++) {
								var line = lines[i].innerHTML;
								//console.log("#" + i + "/" + lines.length + ": '" + line + "'");
								if (line.indexOf("@version") >= 0) {
									//console.log("has version in it");
									versionNumber = line.match(/\d+/g)[0];
									break;
								}
							}
							//console.log("versionNumber: " + versionNumber);
							//console.log("editor.innerHTML:\n" + editor.innerHTML);
							evt.source.postMessage({"versionnumber": versionNumber}, origin);
						}, 3000);
						return;
					}
				}
			}

			window.addEventListener("message", handleResponse, false);
            
            window.parent.postMessage({started: true}, "*");
		}

		function handleJVStalker() {
			/*
			 * Handles messages sent by this script from JVC.
			 * @param {PostMessage event} evt: event fired when this iframe receives a message.
			 * @returns {undefined}
			 */
			function handleResponse(evt) {
                var origin = evt.origin || evt.originalEvent.origin;
				if (origin === 'http://www.jeuxvideo.com') {
					if (source === null) {
						source = evt.source;
					}
					if (evt.data.hasOwnProperty("height")) {
						//console.log("in jvstalker, height is asked");
						curHeight = document.documentElement.offsetHeight;
						source.postMessage({"height": curHeight, "pageNb": curPageNb}, origin);
						return;
					}
					var i;
					if (evt.data.hasOwnProperty("gethrefs")) {
						if (messagesFound.length > 0) {
							var arr = [];
							for (i = 0; i < messagesFound.length; i++) {
								arr.push(messagesFound[i].querySelector(".panel-body > p a:last-child").getAttribute("href"));
							}
							source.postMessage({"hrefs": arr}, origin);
						} else {
							source.postMessage({"hrefs": []}, origin);
						}
						return;
					}
					if (evt.data.hasOwnProperty("hrefsresponse")) {
						for (i = 0; i < messagesFound.length; i++) {
							if (evt.data.hrefsresponse[i] === false) {
								messagesFound[i].remove();
							}
						}
						source.postMessage({"done": true}, origin);
						return;
					}
				}
			}

			/*
			 * Get the page number of an URL.
			 * @param {String} url: the url.
			 * @returns {String}: the part of the URL.
			 */
			function JVStalkerGetPageNbFromURL(url) {
				return url.split("/")[7];
			}

			/*
			 * Fires when the user clicks on another page of the research.
			 * @param {Event} e: the click event.
			 * @returns {undefined}
			 */
			function onOtherPageClick(e) {
				var li = e.target.parentNode;
				if (li.nodeName === "A") {
					li = li.parentNode;
				}
				//console.log("li.nodeName: '" + li.nodeName + "'");
				//console.log("is disabled? " + (li.className.indexOf("disabled") >= 0));
				if (li.nodeName !== "LI" || li.className.indexOf("disabled") >= 0) {
					//console.log("not li or is disabled");
					return;
				}
				var a = li.querySelector("a");
				if (a === null) {
					//console.log("no a");
					return;
				}
				e.stopImmediatePropagation();
				e.stopPropagation();
				e.preventDefault();
				var url = a.getAttribute("href");
				if (url === null || url === "") {
					//console.log("url is empty");
					return;
				}
				var otherPageNb = JVStalkerGetPageNbFromURL(url);
				//console.log("otherPageNb='" + otherPageNb + "'");
				var r = new XMLHttpRequest();
				r.open("GET", url, true);
				//r.overrideMimeType("text/xml");
				r.onload = function () {
					var data = r.responseText;
					var div = document.createElement("div");
					var wrapper = document.createElement("div");
					wrapper.style.display = "none";
					wrapper.appendChild(div);
					document.body.appendChild(wrapper);
					div.innerHTML = data;
					//console.log("wrapper.querySelector .container is null? " + (wrapper.querySelector(".container") === null));
					//console.log("wrapper.querySelector .container .panel length? " + wrapper.querySelectorAll(".container .panel").length);
					var navs = wrapper.querySelectorAll("nav.navbar");
					for (var i = 0; i < navs.length; i++) {
						navs[i].remove();
					}
					var content = wrapper.querySelector(".container").innerHTML;
					//console.log("content:\n" + content);
					//console.log("wrapper innerHTML:\n" + wrapper.innerHTML);
					wrapper.remove();
					document.querySelector("body > div.container").innerHTML = content;
					window.history.pushState("", "", url);
					messagesFound = document.querySelectorAll(".container .panel");
					//console.log("messagesFound length is now " + messagesFound.length);
					curPageNb = otherPageNb;
					//Notify the parent frame
					if (source !== null) {
						//console.log("notify parent frame");
						source.postMessage({"newPage": true}, origin);
					}
				};
				r.send(null);
				return false;
			}

			var curHeight, messagesFound = document.querySelectorAll(".container .panel");
			window.addEventListener("message", handleResponse, false);
			var firstURLNumber = JVStalkerGetPageNbFromURL(window.location.href);
			var source = null, origin = null;
			var curPageNb = firstURLNumber;

			document.addEventListener("click", onOtherPageClick, true);
            
            window.parent.postMessage({started: true}, "*");
		}

		function inIframe() {
			try {
				return window.self !== window.top;
			} catch (e) {
				return true;
			}
		}

		function loadScript() {
			var searchboxLocation = document.getElementById("searchbox-post-finder");
			if (searchboxLocation === null) {
				return;
			}
			
			function addLibraries(libs, type) {
				return new Promise(function (mainResolve, mainReject) {
					function loop(index) {
						return new Promise(function (resolve, reject) {
							if (index >= libs.length) {
								resolve();
								return;
							}
							var content = GM_getResourceText(libs[index]);
							var el;
							if (type === "js") {
								el = document.createElement("script");
								el.type = "text/javascript";
							} else { //"css"
								el = document.createElement("style");
								el.type = "text/css";
							}
							el.onerror = afterError;
							el.innerHTML = content;
							el.setAttribute("data-info", libs[index]);
							document.head.appendChild(el);
							setTimeout(function () {
								loop(index + 1).then(resolve).catch(reject);
							}, 5);
						});
					}

					var isRejected = false;
					var afterError = function () {
						if (!isRejected && !isResolved) {
							isRejected = true;
							mainReject("Le script " + this.getAttribute("data-info") + " n'a pas pu être chargé...");
						}
					};

					var isResolved = false;
					setTimeout(function () {
						if (!isResolved) {
							mainReject("Les scripts nécessaires pour PostFinder n'ont pas pu être chargés...");
						}
					}, 15000);

					if (type === "js" && typeof unsafeWindow.jQuery === "undefined") {
						var jQueryEl = document.createElement("script");
						jQueryEl.type = "text/javascript";
						jQueryEl.onerror = afterError;
						var content = GM_getResourceText("jQueryJS");
						jQueryEl.innerHTML = content;
						jQueryEl.setAttribute("data-info", "jQueryJS");
						document.head.appendChild(jQueryEl);
					}
					loop(0).then(function () {
						isResolved = true;
						mainResolve();
					}).catch(mainReject);
				});
			}

			function finish() {
				var p = performance.now();
				var script = document.createElement("script");
				script.type = "text/javascript";
				script.onload = function () {
					console.log("PostFinder loaded in " + (performance.now() - p) + " ms");
				};
				script.onerror = function () {
					alert("PostFinder n'a pas pu être chargé...");
				};
				var currentVersionNumber = GM_info.script.version;
				script.innerHTML = "(function(){ " + scriptContent.toString() + " var currentVersionNumber = " + currentVersionNumber + "; scriptContent();})();";
				console.log("Appending script...");
				document.head.appendChild(script);
				console.log("Done.");
			}

			console.log("PostFinder is loading...");

			var div = document.createElement("div");
			div.innerHTML = "Chargement...";
			div.style.marginBottom = "15px";
			div.style.marginTop = "15px";
			div.style.fontWeight = "bold";
			div.id = "loadingScript";
			searchboxLocation.parentNode.replaceChild(div, searchboxLocation);

			var JSLibraries = ["jQueryUIJS",
				"findAndReplaceDOMTextJS",
				"bluebirdJS",
				//"colpickJS",
				"spectrumJS",
				"datepickerFRJS",
				"timepickerJS"];
			var CSSLibraries = [//"colpickCSS",
				"spectrumCSS",
				"timepickerCSS"];
			addLibraries(JSLibraries, "js").then(function () {
				return addLibraries(CSSLibraries, "css");
			}).then(function () {
				var el = document.createElement("link");
				el.setAttribute("rel", "stylesheet");
				el.setAttribute("type", "text/css");
				document.head.appendChild(el);
				el.onload = function () {
					finish();
				};
				el.onerror = function () {
					alert("FontAwesome n'a pas pu être chargé... PostFinder fonctionnera mais aura des icônes bizarres.");
					finish();
				};
				el.setAttribute("href", "http://maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css");
			}).catch(function (e) {
				alert("Erreur : '" + e + "'");
			});
		}


		if (window.location.hostname === "jvstalker.fr" || window.location.hostname === "jvarchive.fr") {
			if (inIframe()) {
				handleJVStalker();
			}
			return;
		}

		if (window.location.hostname === "openuserjs.org") {
			if (inIframe()) {
				handleCheckUpdate();
			}
			return;
		}

		if (window.location.hostname === "www.jeuxvideo.com" || window.location.hostname === "www.forumjv.com") {
			var index = window.location.pathname.substring(1).indexOf("/");
			var firstDirectory = window.location.pathname.substring(1, index + 1);
			if (firstDirectory !== "forums") {
				return;
			}
			if (document.querySelector(".bloc-message-forum") === null) {
				return;
			}

			var searchboxLocation = document.createElement("span");
			searchboxLocation.id = "searchbox-post-finder";

			var position = document.querySelector(".bloc-pagi-default");
			if (position !== null) {
			    position.parentNode.insertBefore(searchboxLocation, position.nextSibling);
			}

			var div = document.createElement("div");
			div.id = "btn-post-finder";
			div.className = "bloc-rech-forum";
			div.style.float = "left";
			div.style.marginTop = "0rem";
			div.style.padding = "0rem";

			var button = document.createElement("button");
			button.className = "btn btn-lancer-rech";

			var span = document.createElement("span");
			span.className = "icon-search";

			button.appendChild(span);
			button.addEventListener("click", loadScript, true);

			div.appendChild(button);

			var position = document.querySelector(".btn-actualiser-forum");
			if (position !== null) {
			    position.parentNode.insertBefore(div, position);
			}
		}
	}

//console.log("First message.");

toCall();

addEventListener('instantclick:newpage', toCall);