hklene / FaceCards

// ==UserScript==
// @name        FaceCards
// @namespace   https://openuserjs.org/scripts/hklene/FaceCards
// @description Gesichter lernen mit Karteikarten
// @copyright   2018, hklene (https://openuserjs.org/users/hklene)
// @license     GPL-3.0-or-later; http://www.gnu.org/licenses/gpl-3.0.txt
// @version     2020-05-18
// @include     https://*/browsepeople.action*
// @include     https://*/dopeopledirectorysearch.action*
// @icon        data:image/png;base64,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
// @grant       none
// ==/UserScript==

// ==OpenUserJS==
// @author hklene
// ==OpenUserJS==



// Der Inhalt dieser umschließenden Funktion wird komplett in die Seite injiziert (benötigt keinen Zugriff auf besondere GreaseMonkey-API).
function injectContent() {
    // BEGIN injectContent()

function debug(message) {
    if (false) {
        alert(message);
    }
}

// http://de.selfhtml.org/xml/darstellung/xpathsyntax.htm
function viaXpath(path, node, ordered) {
    return document.evaluate(path, node ? node : document, null,
        ordered ? XPathResult.ORDERED_NODE_SNAPSHOT_TYPE : XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE,
        null);
}

// https://developer.mozilla.org/en-US/docs/Introduction_to_using_XPath_in_JavaScript#First_Node
function viaXpath0(path, node, ordered) {
    return document.evaluate(path, node ? node : document, null,
        ordered ? XPathResult.FIRST_ORDERED_NODE_TYPE : XPathResult.ANY_UNORDERED_NODE_TYPE,
        null).singleNodeValue;
}

// Lightbox nach Denis Potschien: http://www.drweb.de/magazin/css3-lightbox-komplett-ohne-javascript
function lightboxStyle() { /*
DIV.lightbox {
    overflow: hidden;
    position: fixed;
    width:   0;
    height:  0;
    left:    0;
    top:     0;
    opacity: 0;
    background: rgba(127,127,127, 0.95);
    -moz-transition:    opacity 0.5s;
    -o-transition:      opacity 0.5s;
    -webkit-transition: opacity 0.5s;
}

DIV.lightbox:target {
    width:  100%;
    height: 100%;
    opacity: 1;
    z-index: 100;
}
*/}

function lightboxContent() { /*
<TABLE width="100%" height="100%" border="0" style="padding:10px;"><TR>
    <TD><A id="auswertung" style="visibility:hidden" href="javascript:auswertung();" title="Auswertung"><H1>A</H1></A></TD>
    <TD><CENTER id="FaceCardsNumber"></CENTER></TD>
    <TD><A href="#" title="Schließen"><H1 style="text-align:right;">X</H1></A>
</TD></TR><TR><TD colspan=3 align="center" height="100%">
    <IMG id="FaceCardsImg" style="height:100%; max-height:512px;">
</TD></TR><TR><TD colspan=3 align="center">
    <DIV id="FaceCardsQuestion">
        <H1>Wer ist das?</H1>
        <BUTTON style="width:15%;" onclick="solve();">Lösung</BUTTON>
    </DIV>
    <DIV id="FaceCardsResult"><H1 style="font-weight:bold;"></H1><H2></H2><BR>
        <H2>Bewerte, wie gut Du Dich an dieses Gesicht erinnern konntest:</H2><BR>
        <BUTTON style="width:15%; background:#F77;" onclick="showRandomEntry(1);">Keine Ahnung</BUTTON>
        <BUTTON style="width:15%; background:#FB7;" onclick="showRandomEntry(2);">Lange überlegt</BUTTON>
        <BUTTON style="width:15%; background:#FF7;" onclick="showRandomEntry(3);">Beinahe</BUTTON>
        <BUTTON style="width:15%; background:#BF7;" onclick="showRandomEntry(6);">Zuversichtlich</BUTTON>
        <BUTTON style="width:15%; background:#7F7;" onclick="showRandomEntry(9);">Sicher erkannt</BUTTON>
    </DIV>
</TD></TR>
</TABLE>
*/}

// https://developer.mozilla.org/en-US/docs/Web/API/Web_Storage_API/Using_the_Web_Storage_API
var storage, prefix = "FaceCards_";
try {
    // detect availability and ensure functionality
    storage = window.localStorage;
    storage.setItem(prefix, prefix);
    storage.removeItem(prefix);
} catch(e) {
    storage = null;
}

var FaceCardsNumber, FaceCardsImg, FaceCardsQuestion, FaceCardsResult;

function solve() {
    FaceCardsQuestion.style.visibility = "hidden";
    FaceCardsQuestion.style.height     = "0px";
    FaceCardsResult  .style.visibility = "visible";
    FaceCardsResult  .style.height     = null;
    viaXpath0("BUTTON[3]", FaceCardsResult).focus();
}

function Entry(src) {
    this.src = src;
    this.name = "";
    this.email = "";
    this.confidence = 0;
    this.hash = null;

    this.load = function() {
        // Falls lokaler Web Speicher verfügbar, versuche alten Lernfortschritt zu lesen
        if (storage) {
            this.hash = prefix + MD5_hexhash(src);
            this.confidence = parseInt(storage.getItem(this.hash));
            if (!this.confidence) {
                this.confidence = 0;
            }
        }
    }

    this.store = function() {
        if (storage && this.confidence > 0) {
            storage.setItem(this.hash, this.confidence);
        }
    }

    this.toString = function() {
        return this.name + "\n" + this.confidence + "\n" + this.email + "\n" + this.src;
    }
}

var entries = null,
    lesson = null,
    showing = false,
    rawCount = 0,
    enoughForToday = 0,
    enoughTotal = 0,
    hotspot = new Array(),
    hotspotSize = 20,
    confidenceThreshold = 9,
    confidenceModulo = 3;

// in zufälliger Reihenfolge in Hotspot einfügen
function addToHotspot(index) {
    if (hotspot.length < hotspotSize && hotspot.indexOf(index) < 0) {
        if (0 == hotspot.length) {
            hotspot.push(index);
        } else {
            var i = (Math.random() * hotspot.length) | 0;
            hotspot.push(hotspot[i]);
            hotspot[i] = index;
        }
        // debug("inserted i:" + index + " in h:" + hotspot.join());
    }
}

function countSaved(location) {
    var saved = 0, expected = 0;
    if (storage) {
        for(var propertyName in storage) {
            if (propertyName.substr(0,prefix.length) == prefix) {
                saved++;
            }
        }
        for (var i = 0; i < entries.length; i++) {
            if (0 < entries[i].confidence) {
                expected++;
            }
        }
        if (saved != expected) {
            debug("Abweichung: " + saved + " != " + expected + "\nin: " + location);
        }
    }
    return saved;
}

function cleanup() {
    if (waitOnNavigation) {
        return;
    }

    var before = countSaved("clean before");

    // normalisiere auf das Minimum
    var min = confidenceThreshold;
    for (var i = 0; i < entries.length; i++) {
        if (0 == entries[i].confidence) {
            continue;
        }
        min = Math.min(min, entries[i].confidence);
        if (min < confidenceModulo) {
            break;
        }
    }

    if (storage) {
        // Alle Werte aus dem Speicher löschen, auch die, zu denen es keinen Eintrag mehr gibt.
        for(var propertyName in storage) {
            if (propertyName.substr(0,prefix.length) == prefix) {
                storage.removeItem(propertyName);
            }
        }
    }

    if (confidenceModulo < min) {
        min--;
        min = min - min % confidenceModulo;
    } else {
        min = 0;
    }

    var foundNew = 0;
    for (var i = 0; i < entries.length; i++) {
        if (0 == entries[i].confidence) {
            foundNew++;
            continue;
        }
        if (entries[i].confidence == min){
            debug("Killing " + entries[i]);
        }
        entries[i].confidence -= min;
        entries[i].store();
        if (entries[i].confidence < confidenceThreshold) {
            // bevorzuge "alte" unbekannte vor neuen Gesichtern
            addToHotspot(i);
        }
    }
    if (foundNew && hotspot.length < hotspotSize) {
        // fülle den Hotspot am Ende mit unbekannten Gesichtern auf
        // wer schon die meisten kennt, kommt so schneller an die wenigen neuen Kollegen
        foundNew = hotspot.length;
        for (var i = 0; i < entries.length; i++) {
            if (0 == entries[i].confidence) {
                addToHotspot(i);
                if (hotspot.length == hotspotSize) {
                    break;
                }
            }
        }
        debug("Hotspot aufgefüllt mit " + (hotspot.length - foundNew) + " neuen Kollegen!");
    }

    var after = countSaved("clean after");
    if (before != after) {
        debug("Anzahl " + before + " -> " + after);
    }
}

function padding(zahl, length) {
    var string = "" + zahl;
    if (!length) {
        length = 2;
    }
    while (string.length < length) {
        // 2 Leerzeichen sind so breit wie eine Ziffer
        string = "  " + string;
    }
    return string;
}

function auswertung() {
    cleanup();

    // Liste alle Werte auf
    var min = entries[0].confidence, max = min;
    var w = "Werte: " + entries.length + "\n", map = new Object();
    for (var i = 0; i < entries.length; i++) {
        min = Math.min(min, entries[i].confidence);
        max = Math.max(max, entries[i].confidence);
        w += padding(entries[i].confidence ? entries[i].confidence : '_') + (i % 10 == 9 ? ",\n" : ", ");
        map[entries[i].confidence] = (map[entries[i].confidence] ? map[entries[i].confidence] + 1 : 1);
    }

    // Verteilung zwischen min und max
    var v = "", c = 0, d = 0;
    for (var k in map) {
        v += padding(k) + "=" + padding(map[k]) + (++c % 5 == 0 ? ",\n" : ", ");
        d += map[k];
    }
    v = "\nVerteilung: " + c + " Gruppen " + d + " Werte\n" + v;

    var h = "\nHotspot: " + hotspot.length + "\n";
    for (var i = 0; i < hotspot.length; i++) {
        h += padding(hotspot[i]) + (i % 10 == 9 ? ",\n" : ", ");
    }

    alert("Auswertung\nAngemeldet: " + rawCount + "\nmit Foto: " + entries.length
        + "\ngespeichert: " + countSaved("auswertung")
        + "\nmin:" + min + " max:" + max + v.replace(/,\s$/, "\n") + w + h);
}

function showRandomEntry(lessonLearned) {
    // summiere den aktuellen Lernfortschritt
    lesson.confidence += lessonLearned;
    if (confidenceThreshold < lesson.confidence && lesson.confidence % confidenceModulo != 0) {
        lesson.confidence = lesson.confidence - lesson.confidence % confidenceModulo;
    }
    lesson.store();

    // einer der linken beiden Buttons
    // oder der mittlere Button und Gesamtsicherheit ist noch zu gering
    // -> versuche es im Hotspot unterzubringen
    var old = entries.indexOf(lesson);
    if (0 < lessonLearned) {
        if (lessonLearned < confidenceModulo
                || lessonLearned == confidenceModulo && lesson.confidence <= confidenceThreshold) {
            addToHotspot(old);
            enoughForToday--;
        } else {
            // aus Hotspot entfernen
            var hi = hotspot.indexOf(old);
            if (0 <= hi) {
                hotspot.splice(hi, 1);
            }
            enoughForToday++;
        }
        enoughTotal++;
        if (hotspotSize < Math.abs(enoughForToday) || 3 * hotspotSize < enoughTotal) {
            cleanup();
            if (confirm("Genug für heute?")) {
                window.location.hash = "#";
            } else {
                enoughForToday = enoughTotal = 0;
            }
        }
    }

    var j, category = '', hr = hotspot.length;
    if (0 < hr // 20% für letzten Eintrag bis 80% wenn hotspot voll
            && (hr - 1) / (hotspotSize - 1) * 3 > 5 * Math.random() - 1
            && hotspot[hr = hr * Math.random() | 0] != old) {
        j = hotspot[hr];
        lesson = entries[j];
        category = 'Hotspot';
    } else {
        // wähle zufällig die nächste Herausforderung, möglichst mit geringerem Lernfortschritt
        for (var i = 0; ; i++) {
            j = (Math.random() * entries.length) | 0;
            var candidate = entries[j];
            if (candidate.confidence < lesson.confidence || i == confidenceThreshold) {
                // wähle geringeren Lernfortschritt oder akzeptiere den letzten Versuch
                lesson = candidate;
                category = 0 <= hotspot.indexOf(j) ? 'hotspot' : 'random';
                break;
            }
        }
    }
    FaceCardsNumber.innerHTML = "Punkte: " + lesson.confidence + ", Foto: " + j + " von " + entries.length
        + ", Auswahl: " + category + " bei " + hotspot.length + " " + enoughForToday + " " + enoughTotal;
    FaceCardsImg.src = lesson.src;

    FaceCardsResult.style.visibility = "hidden";
    FaceCardsResult.style.height = "0px";
    FaceCardsResult.firstChild.innerHTML = lesson.name;
    viaXpath0("H2", FaceCardsResult).innerHTML = lesson.email ? lesson.email : "";

    FaceCardsQuestion.style.visibility = "visible";
    FaceCardsQuestion.style.height = null;
    viaXpath0("BUTTON", FaceCardsQuestion).focus();
    showing = true;
}

function collectData(myBody) {
    var img = viaXpath("//DIV[@class='profile-macro']", myBody);
    rawCount += img.snapshotLength;
    for (var i = 0; i < img.snapshotLength; i++) {
        var parent = img.snapshotItem(i);
        var current = viaXpath0(".//A/IMG[@class='userLogo logo']", parent);
        if (current.src.indexOf('/default.svg') >= 0 || current.src.indexOf('/default.png') >= 0) {
            continue;
        }
        // parent.style.border = "5px solid red";
        var entry = new Entry(current.src);
        entry.load();
        current.setAttribute("test", i);

        current = viaXpath0(".//DIV[@class='values']/H4/A[contains(@class, 'confluence-userlink')]", parent);
        entry.name = current.firstChild.data;

        current = viaXpath0(".//DIV[@class='values']/A[@class='email']", parent);
        if (current && current.firstChild) {
            entry.email = current.firstChild.data;
        }
        entries.push(entry);
        if (0 < entry.confidence && entry.confidence < confidenceThreshold) {
            addToHotspot(entries.length - 1);
        }
    }
//     debug("collectData " + img.snapshotLength + " " + entries.length + " " + lesson);
}

function loadNextPage(link, direction) {
//     debug("loadNextPage " + link + " " + direction);
    var childDoc = new XMLHttpRequest();
    childDoc.open("GET", link, false);
    childDoc.send();
    childDoc = childDoc.responseText;
    childDoc = childDoc.substring(childDoc.indexOf("<body"), childDoc.lastIndexOf("</body>")).replace(/^.*>/, "");

    // weil eine HTML-Seite und kein XML abgefragt wird, weise den Inhalt einem dummy-Knoten zu
    var childBody = document.createElement("DIV");
    childBody.innerHTML = childDoc;
    collectData(childBody);
    navigateRecursive(childBody, direction);
}

var waitOnNavigation = 0;

function navigateRecursive(myBody, direction) {
    var link = viaXpath0("//OL[@class]/LI[@class='" + direction + "']/A[@href]", myBody);
    if (!link) {
        waitOnNavigation--;
        if (!waitOnNavigation) {
            document.getElementById("auswertung").removeAttribute("style");
            cleanup();
        }
        return;
    }
    if (showing || entries.length == 0) {
        // erstes Foto wird bereits angezeigt oder noch keines gefunden -> suche synchron auf der nächsten Seite
        loadNextPage(link.href, direction);
    } else {
        // es wurde bereits ein Foto gefunden -> breche hier ab um es sofort anzuzeigen und lade die weiteren Seiten erst im Anschluss
        window.setTimeout("loadNextPage('" + link.href + "', '" + direction + "');", 0);
    }
}

function initialize() {
    // definiere CSS für die Lightbox
    var lightbox = document.createElement("STYLE");
    lightbox.type = "text/css";
    lightboxStyle = lightboxStyle.toString();
    lightboxStyle = lightboxStyle.substring(lightboxStyle.indexOf("/*") + 2, lightboxStyle.lastIndexOf("*/") - 1);
    lightbox.innerHTML = lightboxStyle;
    document.head.appendChild(lightbox);

    // füge die Lightbox selbst unten an das Dokument an (wird dank CSS später die anderen Inhalte überdecken)
    lightbox = document.createElement("DIV");
    lightbox.id = "FaceCards";
    lightbox.setAttribute("class", "lightbox");
    lightboxContent = lightboxContent.toString();
    lightboxContent = lightboxContent.substring(lightboxContent.indexOf("/*") + 2, lightboxContent.lastIndexOf("*/") - 1);
    lightbox.innerHTML = lightboxContent;
    document.body.appendChild(lightbox);

    // Esc key action
    $(lightbox).keyup(function(e) {
        if (e.keyCode == 27) {
            window.location.hash = "#";
        }
    });

    FaceCardsNumber   = document.getElementById("FaceCardsNumber");
    FaceCardsImg      = document.getElementById("FaceCardsImg");
    FaceCardsQuestion = document.getElementById("FaceCardsQuestion");
    FaceCardsResult   = document.getElementById("FaceCardsResult");

    // durchsuche die Original-Seite nach Fotos und sammle die Informationen
    collectData(document.body);
    waitOnNavigation = 2;
    navigateRecursive(document.body, "aui-nav-previous");
    navigateRecursive(document.body, "aui-nav-next");
}

function show() {
    enoughForToday = enoughTotal = 0;
    // faule Initialisierung der Lightbox
    if (!entries) {
        entries = new Array();
        initialize();
    }
    if (entries.length == 0) {
        alert("Es wurden keine Fotos auf dieser Seite gefunden! " + rawCount);
        return;
    }

    // rufe die Lightbox auf
    window.location.hash = "#FaceCards";
    lesson = entries[0];
    showRandomEntry(0);
}

// Füge einen Link zum Aufruf der Initialisierung hinzu
var nav = viaXpath0("//DIV[@class='aui-navgroup-inner']/UL[@class='aui-nav']");
var link = document.createElement("li");
link.innerHTML = "<A href='javascript:show();' title='Zeige Karteikarten der Fotos'>Namen lernen</A>";
nav.appendChild(link);

// TODO Debug: rufe die Lightbox sofort auf und warte nicht auf den Klick
// initialize();
// show();

    // END injectContent()
}

// --------------------------------------------------------------------------------------------------------------------

// Message Digest MD5 - Hash Funktion zur Verschlüsselung personenbezogener Daten vor der Speicherung lokal im Browser
// http://www.onicos.com/staff/iz/amuse/javascript/expert/md5.txt
function injectMD5() {
    // BEGIN injectMD5()
/* md5.js - MD5 Message-Digest
 * Copyright (C) 1999,2002 Masanao Izumo <iz@onicos.co.jp>
 * Version: 2.0.0
 * LastModified: May 13 2002
 *
 * This program is free software.  You can redistribute it and/or modify
 * it without any warranty.  This library calculates the MD5 based on RFC1321.
 * See RFC1321 for more information and algorism.
 */

/* Interface:
 * md5_128bits = MD5_hash(data);
 * md5_hexstr = MD5_hexhash(data);
 */

/* ChangeLog
 * 2002/05/13: Version 2.0.0 released
 * NOTICE: API is changed.
 * 2002/04/15: Bug fix about MD5 length.
 */


//    md5_T[i] = parseInt(Math.abs(Math.sin(i)) * 4294967296.0);
var MD5_T = new Array(0x00000000, 0xd76aa478, 0xe8c7b756, 0x242070db,
		      0xc1bdceee, 0xf57c0faf, 0x4787c62a, 0xa8304613,
		      0xfd469501, 0x698098d8, 0x8b44f7af, 0xffff5bb1,
		      0x895cd7be, 0x6b901122, 0xfd987193, 0xa679438e,
		      0x49b40821, 0xf61e2562, 0xc040b340, 0x265e5a51,
		      0xe9b6c7aa, 0xd62f105d, 0x02441453, 0xd8a1e681,
		      0xe7d3fbc8, 0x21e1cde6, 0xc33707d6, 0xf4d50d87,
		      0x455a14ed, 0xa9e3e905, 0xfcefa3f8, 0x676f02d9,
		      0x8d2a4c8a, 0xfffa3942, 0x8771f681, 0x6d9d6122,
		      0xfde5380c, 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60,
		      0xbebfbc70, 0x289b7ec6, 0xeaa127fa, 0xd4ef3085,
		      0x04881d05, 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8,
		      0xc4ac5665, 0xf4292244, 0x432aff97, 0xab9423a7,
		      0xfc93a039, 0x655b59c3, 0x8f0ccc92, 0xffeff47d,
		      0x85845dd1, 0x6fa87e4f, 0xfe2ce6e0, 0xa3014314,
		      0x4e0811a1, 0xf7537e82, 0xbd3af235, 0x2ad7d2bb,
		      0xeb86d391);

var MD5_round1 = new Array(new Array( 0, 7, 1), new Array( 1,12, 2),
			   new Array( 2,17, 3), new Array( 3,22, 4),
			   new Array( 4, 7, 5), new Array( 5,12, 6),
			   new Array( 6,17, 7), new Array( 7,22, 8),
			   new Array( 8, 7, 9), new Array( 9,12,10),
			   new Array(10,17,11), new Array(11,22,12),
			   new Array(12, 7,13), new Array(13,12,14),
			   new Array(14,17,15), new Array(15,22,16));

var MD5_round2 = new Array(new Array( 1, 5,17), new Array( 6, 9,18),
			   new Array(11,14,19), new Array( 0,20,20),
			   new Array( 5, 5,21), new Array(10, 9,22),
			   new Array(15,14,23), new Array( 4,20,24),
			   new Array( 9, 5,25), new Array(14, 9,26),
			   new Array( 3,14,27), new Array( 8,20,28),
			   new Array(13, 5,29), new Array( 2, 9,30),
			   new Array( 7,14,31), new Array(12,20,32));

var MD5_round3 = new Array(new Array( 5, 4,33), new Array( 8,11,34),
			   new Array(11,16,35), new Array(14,23,36),
			   new Array( 1, 4,37), new Array( 4,11,38),
			   new Array( 7,16,39), new Array(10,23,40),
			   new Array(13, 4,41), new Array( 0,11,42),
			   new Array( 3,16,43), new Array( 6,23,44),
			   new Array( 9, 4,45), new Array(12,11,46),
			   new Array(15,16,47), new Array( 2,23,48));

var MD5_round4 = new Array(new Array( 0, 6,49), new Array( 7,10,50),
			   new Array(14,15,51), new Array( 5,21,52),
			   new Array(12, 6,53), new Array( 3,10,54),
			   new Array(10,15,55), new Array( 1,21,56),
			   new Array( 8, 6,57), new Array(15,10,58),
			   new Array( 6,15,59), new Array(13,21,60),
			   new Array( 4, 6,61), new Array(11,10,62),
			   new Array( 2,15,63), new Array( 9,21,64));

function MD5_F(x, y, z) { return (x & y) | (~x & z); }
function MD5_G(x, y, z) { return (x & z) | (y & ~z); }
function MD5_H(x, y, z) { return x ^ y ^ z;          }
function MD5_I(x, y, z) { return y ^ (x | ~z);       }

var MD5_round = new Array(new Array(MD5_F, MD5_round1),
			  new Array(MD5_G, MD5_round2),
			  new Array(MD5_H, MD5_round3),
			  new Array(MD5_I, MD5_round4));

function MD5_pack(n32) {
  return String.fromCharCode(n32 & 0xff) +
	 String.fromCharCode((n32 >>> 8) & 0xff) +
	 String.fromCharCode((n32 >>> 16) & 0xff) +
	 String.fromCharCode((n32 >>> 24) & 0xff);
}

function MD5_unpack(s4) {
  return  s4.charCodeAt(0)        |
	 (s4.charCodeAt(1) <<  8) |
	 (s4.charCodeAt(2) << 16) |
	 (s4.charCodeAt(3) << 24);
}

function MD5_number(n) {
  while (n < 0)
    n += 4294967296;
  while (n > 4294967295)
    n -= 4294967296;
  return n;
}

function MD5_apply_round(x, s, f, abcd, r) {
  var a, b, c, d;
  var kk, ss, ii;
  var t, u;

  a = abcd[0];
  b = abcd[1];
  c = abcd[2];
  d = abcd[3];
  kk = r[0];
  ss = r[1];
  ii = r[2];

  u = f(s[b], s[c], s[d]);
  t = s[a] + u + x[kk] + MD5_T[ii];
  t = MD5_number(t);
  t = ((t<<ss) | (t>>>(32-ss)));
  t += s[b];
  s[a] = MD5_number(t);
}

function MD5_hash(data) {
  var abcd, x, state, s;
  var len, index, padLen, f, r;
  var i, j, k;
  var tmp;

  state = new Array(0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476);
  len = data.length;
  index = len & 0x3f;
  padLen = (index < 56) ? (56 - index) : (120 - index);
  if(padLen > 0) {
    data += "\x80";
    for(i = 0; i < padLen - 1; i++)
      data += "\x00";
  }
  data += MD5_pack(len * 8);
  data += MD5_pack(0);
  len  += padLen + 8;
  abcd = new Array(0, 1, 2, 3);
  x    = new Array(16);
  s    = new Array(4);

  for(k = 0; k < len; k += 64) {
    for(i = 0, j = k; i < 16; i++, j += 4) {
      x[i] = data.charCodeAt(j) |
	    (data.charCodeAt(j + 1) <<  8) |
	    (data.charCodeAt(j + 2) << 16) |
	    (data.charCodeAt(j + 3) << 24);
    }
    for(i = 0; i < 4; i++)
      s[i] = state[i];
    for(i = 0; i < 4; i++) {
      f = MD5_round[i][0];
      r = MD5_round[i][1];
      for(j = 0; j < 16; j++) {
	MD5_apply_round(x, s, f, abcd, r[j]);
	tmp = abcd[0];
	abcd[0] = abcd[3];
	abcd[3] = abcd[2];
	abcd[2] = abcd[1];
	abcd[1] = tmp;
      }
    }

    for(i = 0; i < 4; i++) {
      state[i] += s[i];
      state[i] = MD5_number(state[i]);
    }
  }

  return MD5_pack(state[0]) +
	 MD5_pack(state[1]) +
	 MD5_pack(state[2]) +
	 MD5_pack(state[3]);
}

function MD5_hexhash(data) {
    var i, out, c;
    var bit128;

    bit128 = MD5_hash(data);
    out = "";
    for(i = 0; i < 16; i++) {
	c = bit128.charCodeAt(i);
	out += "0123456789abcdef".charAt((c>>4) & 0xf);
	out += "0123456789abcdef".charAt(c & 0xf);
    }
    return out;
}
    // END injectMD5()
}

// --------------------------------------------------------------------------------------------------------------------

// Wandle die Funktion in eine Zeichenkette und hänge sie an den Dokumenten-Kopf
injectMD5 = injectMD5.toString();
injectMD5 = injectMD5.substring(injectMD5.indexOf("{") + 1, injectMD5.lastIndexOf("}") - 1);

injectContent = injectContent.toString();
injectContent = injectContent.substring(injectContent.indexOf("{") + 1, injectContent.lastIndexOf("}") - 1);

var inPageScript = document.createElement("SCRIPT");
inPageScript.type = "text/javascript";
inPageScript.innerHTML = injectMD5 + "\n" + injectContent;
document.head.appendChild(inPageScript);