diff --git a/Skripte/Nachrichten.js b/Skripte/Nachrichten.js
index ba57009d5a87e468db23c1fee551189f9b3ae487..27621c8b80c7c10369518baa44b229d01d26a137 100644
--- a/Skripte/Nachrichten.js
+++ b/Skripte/Nachrichten.js
@@ -3,10 +3,6 @@ export default class MessageHandler
     Definitionen = {
         //Nur im Moderationskanal:
         Moderation: {
-            "info": {
-                Aliase: ["hilfe"],
-                Funktion: ModulModeration.Info
-            },
             "nachrichtankanal": {
                 Funktion: ModulModeration.NachrichtAnKanalSenden
             },
@@ -28,21 +24,9 @@ export default class MessageHandler
             "löschenachricht": {
                 Funktion: ModulModeration.NachrichtEntfernen
             },
-            "wichtelstatus": {
-                Funktion: ModulModeration.Wichtelstatus
-            },
-            "anmeldephasebeenden": {
-                Funktion: ModulModeration.AnmeldephaseBeenden
-            },
-            "ziehungausführen": {
-                Funktion: ModulModeration.ZiehungAusführen
-            },
             "steamnamenauflisten": {
                 Funktion: ModulModeration.SteamnamenAuflisten
             },
-            "steckbriefeverteilen": {
-                Funktion: ModulModeration.SteckbriefeVerteilen
-            }
         },
         //Nur in einem bestimmten Zustand gültig:
         Zustände: {
diff --git a/Skripte/Nachrichten/Moderation.js b/Skripte/Nachrichten/Moderation.js
index 4642554f19a41efeed2050af1dad63178fe609b4..4dbfbc7061061396fcdcbff49672edc2a711066c 100644
--- a/Skripte/Nachrichten/Moderation.js
+++ b/Skripte/Nachrichten/Moderation.js
@@ -177,92 +177,6 @@ function NachrichtEntfernen (Nachricht)
 }
 exports.NachrichtEntfernen = NachrichtEntfernen;
 
-/**
- * Ermittelt die aktuelle Anzahl an gegebener Wichtel.
- * @param {Object} Nachricht Die Nachricht, die per Discord erhalten wurde, ein Discordnachrichtenobjekt.
- */
-function Wichtelstatus (Nachricht)
-{
-    Datenbankverwaltung.Nutzerzahlen(function (Ergebnis)
-        {
-            let Antwort = Texte.Wichtelstatus;
-            Antwort = Antwort.replace(/\[\[GESAMT\]\]/g, Ergebnis.Gesamt);
-            Antwort = Antwort.replace(/\[\[TEILNEHMER\]\]/g, Ergebnis.Teilnehmer);
-
-            Nachricht.channel.send(Antwort);
-        }
-    );
-}
-exports.Wichtelstatus = Wichtelstatus;
-
-/**
- * Gibt eine Info zu den möglichen Moderationsbefehlen aus.
- * @param {Object} Nachricht Die Nachricht, die per Discord erhalten wurde, ein Discordnachrichtenobjekt.
- */
-function Info (Nachricht)
-{
-    Nachricht.channel.send(Texte.ModerationInfo);
-}
-exports.Info = Info;
-
-/**
- * Beendet die Anmeldephase und gibt allen Teilnehmern den Wichtelstatus.
- * @param {Object} Nachricht Die Nachricht, die per Discord erhalten wurde, ein Discordnachrichtenobjekt.
- */
-function AnmeldephaseBeenden (Nachricht)
-{
-    let Teilnehmerliste = [];
-
-    for (let Nutzer of Nutzerverwaltung.Liste.values())
-    {
-        //Wir prüfen hier auf "Teilnehmer" und "Wartend", damit im Falle eines Fehlers beim Eintragen in die Datenbank
-        //ein erneutes Ausführen des Befehls den Fehler bereinigen kann.
-        if ((Nutzer.Zustand == 'Teilnehmer') || (Nutzer.Zustand == 'Wartend'))
-        {
-            Teilnehmerliste.push(Nutzer.Id);
-            Nutzer.Zustand = 'Wartend';
-        }
-    }
-
-    Datenbankverwaltung.TeilnehmerZuWichtelnMachen(Teilnehmerliste, function (EintragenErfolgreich)
-        {
-            if (EintragenErfolgreich)
-            {
-                let Bestätigung = Texte.AnmeldephaseBeendenErfolgreich.replace(/\[\[ANZAHL\]\]/g, Teilnehmerliste.length);
-                Nachricht.reply("\n" + Bestätigung);
-            }
-            else
-                Nachricht.reply("\n" + Texte.AnmeldephaseBeendenFehlgeschlagen);
-        }
-    );
-}
-exports.AnmeldephaseBeenden = AnmeldephaseBeenden;
-
-/**
- * Führt die Ziehung der Wichtel aus.
- * @param {Object} Nachricht Die Nachricht, die per Discord erhalten wurde, ein Discordnachrichtenobjekt.
- */
-function ZiehungAusführen (Nachricht)
-{
-    let Prioritätenliste = [];
-
-    if (Nachricht.Parameter != '')
-        Prioritätenliste = Nachricht.Parameter.split("\n");
-
-    let Ziehung = require('../Ziehung.js');
-
-    Ziehung.Initialisieren(Nutzerverwaltung, Datenbankverwaltung, Klient);
-    Ziehung.Ausführen(Prioritätenliste, function (IstFehlerfrei)
-        {
-            if (IstFehlerfrei)
-                Nachricht.reply("\n" + Texte.ZiehungAusgeführt);
-            else
-                Nachricht.reply("\n" + Texte.ZiehungFehlgeschlagen);
-        }
-    );
-}
-exports.ZiehungAusführen = ZiehungAusführen;
-
 /**
  * Listet alle Steamnamen von Nutzern auf, die digital bewichtelt werden wollen.
  * @param {Object} Nachricht Die Nachricht, die per Discord erhalten wurde, ein Discordnachrichtenobjekt.
diff --git a/Skripte/Ziehung.js b/Skripte/Ziehung.js
deleted file mode 100644
index 39c0b911f863be94793529dab18326968ccbfae6..0000000000000000000000000000000000000000
--- a/Skripte/Ziehung.js
+++ /dev/null
@@ -1,250 +0,0 @@
-var Nutzerverwaltung;
-var Datenbankverwaltung;
-
-/**
- * Der Klient, der sich mit Discord verbunden hat.
- */
-var Klient;
-
-/**
- * Initialisiert die Ziehung/Losung der Wichtel.
- * @param {Object} Nutzerbibliothek
- * @param {Object} Datenbankbibliothek
- * @param {Object} NeuerKlient Der Discordklient, über den sich der Bot verbunden hat.
- */
-exports.Initialisieren = function (Nutzerbibliothek, Datenbankbibliothek, NeuerKlient)
-{
-    Nutzerverwaltung = Nutzerbibliothek;
-    Datenbankverwaltung = Datenbankbibliothek;
-    Klient = NeuerKlient;
-};
-
-/**
- * Führt die Ziehung aus.
- * @param {Array} Prioritätenliste Eine Parameterliste mit den IDs der Nutzer, die bevorzugt werden sollen.
- * @param {Array} ZiehungAusgeführt Callback, der ausgeführt wird, sobald die Ziehung vollendet ist. Parameter: {Boolean} IstFehlerfrei
- */
-exports.Ausführen = function (Prioritätenliste, ZiehungAusgeführt)
-{
-    let Teilnehmerliste = [];
-
-    //Alle Teilnehmer in die Teilnehmerliste füllen:
-    for (let Nutzer of Nutzerverwaltung.Liste.values())
-        if (Nutzer.Zustand == 'Wartend')
-            Teilnehmerliste.push(Nutzer);
-
-    /**
-     * Ordnet Nutzern einer möglichen Liste an Wichtelpartnern zu, um diese später zu ermitteln.
-     */
-    let Zuordnungsliste = new Map();
-
-    //Zuordnungsliste wird gefüllt:
-    for (let Teilnehmer of Teilnehmerliste)
-    {
-        let Wichtelliste = new Map();
-
-        //Aktuellen Teilnehmer herausfiltern und eigene Objekte mit Datenreferenzen auf den Wichtel in die Liste eintragen:
-        for (let Wichtel of Teilnehmerliste)
-            if (Wichtel.Id != Teilnehmer.Id)
-                Wichtelliste.set(Wichtel.Id, { Daten: Wichtel });
-
-        Zuordnungsliste.set(Teilnehmer.Id, {
-                Nutzer: Teilnehmer,
-                Wichtel: Wichtelliste,
-                FrühereWichtel: [],
-                SortierteWichtel: []
-            }
-        );
-    }
-
-    //Ausschlüsse ermitteln:
-    let Ausschlüsse = [];
-
-    for (let Eintrag of Zuordnungsliste.values())
-    {
-        DigitalAnalogAusschließen(Eintrag);
-        InternationalAusschließen(Eintrag);
-        Ausschlüsse.push(AusschlüsseErmitteln(Eintrag));
-    }
-
-    Promise.all(Ausschlüsse).then(function ()
-        {
-            //Erstmalig sortieren:
-            for (let Eintrag of Zuordnungsliste.values())
-            {
-                GewichtungenBerechnen(Eintrag);
-                Eintrag.SortierteWichtel = WichtelSortieren(Eintrag.Wichtel);
-            }
-
-            let SortierteZuordnungen = ZuordnungenSortieren(Prioritätenliste, Zuordnungsliste, true);
-
-            let Ergebnisliste = [];
-
-            //Eigentliche Ziehung durchführen:
-            while (SortierteZuordnungen.length != 0)
-            {
-                let ErsteZuordnung = SortierteZuordnungen.shift(); //Ersten Eintrag entfernen und Indizes anpassen.
-
-                //Wenn es kein Element in der Liste gab, ist für eine Person kein Wichtel übrig.
-                //In dem Falle haben wir ein unvollständiges Ergebnis, das korrigiert werden muss.
-                if (ErsteZuordnung.SortierteWichtel[0] == undefined)
-                {
-                    if ((Prioritätenliste.length < Zuordnungsliste.size) && (Prioritätenliste.indexOf(ErsteZuordnung.Nutzer.Id) == -1))
-                    {
-                        //Wenn noch nicht alle Nutzer Prioritäten sind und der Auslöser nicht bereits auf dieser Liste steht,
-                        //rufen wir die ganze Funktion nochmal mit einer weiteren Prioritäts-ID auf.
-                        //Dies ist rekursiv, bis eine Lösung gefunden wurde oder einer der beiden Bedingungen nicht mehr zutrifft.
-                        Prioritätenliste.push(ErsteZuordnung.Nutzer.Id);
-                        exports.Ausführen(Prioritätenliste, ZiehungAusgeführt);
-                    }
-                    else
-                        ZiehungAusgeführt(false);
-
-                    return; //In jedem Falle muss die Ausführung der übrigen Funktion unterbunden werden.
-                }
-
-                let Ergebnis = {
-                    Nutzer: ErsteZuordnung.Nutzer,
-                    Wichtel: ErsteZuordnung.SortierteWichtel[0].Daten,
-                    Wert: ErsteZuordnung.SortierteWichtel[0].Gewichtung
-                };
-
-                //Aktualisieren der gespeicherten Wichtel-ID des Nutzers auf die aktuelle:
-                Ergebnis.Nutzer.WichtelkindId = Ergebnis.Wichtel.Id;
-
-                Ergebnisliste.push(Ergebnis);
-
-                for (let Eintrag of SortierteZuordnungen)
-                    if (Eintrag.Wichtel.delete(Ergebnis.Wichtel.Id))
-                        Eintrag.SortierteWichtel = WichtelSortieren(Eintrag.Wichtel);
-
-                //Die ID des Nutzers muss aus der Wichtelliste des Wichtels entfernt werden.
-                //Das machen wir über die Map Zuordnungsliste, um nicht das Array SortierteZuordnungen durchsuchen zu müssen.
-                //Map und Array beinhalten dieselbe Referenz, daher erfolgt die Änderung bei beiden Listen.
-                Zuordnungsliste.get(Ergebnis.Wichtel.Id).Wichtel.delete(Ergebnis.Nutzer.Id);
-
-                SortierteZuordnungen = ZuordnungenSortieren(Prioritätenliste, SortierteZuordnungen);
-            }
-
-            //Ergebnisse eintragen:
-            Datenbankverwaltung.WichtelEintragen(Ergebnisliste, ZiehungAusgeführt);
-        }
-    );
-};
-
-function DigitalAnalogAusschließen (Eintrag)
-{
-    if (Eintrag.Nutzer.AnalogDigitalWichtel == 'beides')
-        return;
-
-    for (let Wichtel of Eintrag.Wichtel.values())
-        if ((Wichtel.Daten.AnalogDigitalSelbst != 'beides') && (Eintrag.Nutzer.AnalogDigitalWichtel != Wichtel.Daten.AnalogDigitalSelbst))
-            Eintrag.Wichtel.delete(Wichtel.Daten.Id);
-}
-
-function InternationalAusschließen (Eintrag)
-{
-    if ((Eintrag.Nutzer.AnalogDigitalWichtel == 'digital') || (Eintrag.Nutzer.International == 'ja'))
-        return;
-
-    for (let Wichtel of Eintrag.Wichtel.values())
-        if ((Eintrag.Nutzer.Land != Wichtel.Daten.Land) && (Wichtel.Daten.AnalogDigitalSelbst == 'analog'))
-            Eintrag.Wichtel.delete(Wichtel.Daten.Id);
-}
-
-function AusschlüsseErmitteln (Eintrag)
-{
-    return new Promise(function (ErfolgMelden)
-        {
-            Datenbankverwaltung.Ausschlüsse(Eintrag.Nutzer.Id, function (Ausschlüsse)
-                {
-                    for (let Ausschluss of Ausschlüsse)
-                    {
-                        if (Ausschluss.Grund == 'Wunsch')
-                            Eintrag.Wichtel.delete(Ausschluss.WichtelId);
-                        else
-                            Eintrag.FrühereWichtel.push(Ausschluss.WichtelId);
-                    }
-
-                    ErfolgMelden();
-                }
-            );
-        }
-    );
-}
-
-function GewichtungenBerechnen (Eintrag)
-{
-    for (let Wichtel of Eintrag.Wichtel.values())
-    {
-        Wichtel.Gewichtung = 0;
-
-        //Vergleich Analog/Digital:
-        if (Wichtel.Daten.AnalogDigitalSelbst == Eintrag.Nutzer.AnalogDigitalWichtel)
-            Wichtel.Gewichtung += 4;
-
-        //Vergleich Herkunftsland:
-        if (Wichtel.Daten.Land == Eintrag.Nutzer.Land)
-            Wichtel.Gewichtung += 2;
-    }
-
-    //Frühere Wichtel:
-    for (let WichtelId of Eintrag.FrühereWichtel)
-        if (Eintrag.Wichtel.has(WichtelId))
-            Eintrag.Wichtel.get(WichtelId).Gewichtung = -2;
-}
-
-function WichtelSortieren (Wichtelliste)
-{
-    let Ergebnis = Array.from(Wichtelliste.values());
-
-    Ergebnis.sort(function (WichtelA, WichtelB)
-        {
-            return WichtelB.Gewichtung - WichtelA.Gewichtung;
-        }
-    );
-
-    return Ergebnis;
-}
-
-function ZuordnungenSortieren (Prioritätenliste, Zuordnungsliste, IstMap = false)
-{
-    if (IstMap)
-        Zuordnungsliste = Array.from(Zuordnungsliste.values());
-
-    Zuordnungsliste.sort(function (ZuordnungA, ZuordnungB)
-        {
-            let AIstPriorität = (Prioritätenliste.indexOf(ZuordnungA.Nutzer.Id) != -1);
-            let BIstPriorität = (Prioritätenliste.indexOf(ZuordnungB.Nutzer.Id) != -1);
-
-            if (AIstPriorität || BIstPriorität)
-            {
-                if (AIstPriorität && BIstPriorität)
-                    return 0;
-                else if (AIstPriorität)
-                    return -1;
-                else
-                    return 1;
-            }
-
-            //Der Wert der Wichtelkette wird anhand der akkumulierten Gewichtungen aller Wichtel bestimmt plus deren Gesamtanzahl.
-            //Je mehr es davon gibt, desto nachrangiger ist der Nutzer für die Auszählung.
-            //Dies stellt einen Kompromiss dar zwischen "finde die höchstwertigen Kombinationen", "stelle sicher, dass alle bedient werden" und
-            //"versuche möglichst niemanden schlecht dastehen zu lassen".
-            let Akkumulation = (Akkumulator, AktuellerWichtel) => Akkumulator + AktuellerWichtel.Gewichtung;
-
-            ZuordnungA.Wert = ZuordnungA.SortierteWichtel.reduce(Akkumulation, 0) + ZuordnungA.SortierteWichtel.length;
-            ZuordnungB.Wert = ZuordnungB.SortierteWichtel.reduce(Akkumulation, 0) + ZuordnungB.SortierteWichtel.length;
-
-            let Ergebnis = ZuordnungA.Wert - ZuordnungB.Wert;
-
-            //Bei Gleichstand entscheidet die Anzahl der möglichen Wichtel über die Wertigkeit; weniger Wichtel heißt höherer Listenplatz.
-            if (Ergebnis == 0)
-                Ergebnis = ZuordnungA.SortierteWichtel.length - ZuordnungB.SortierteWichtel.length;
-
-            return Ergebnis;
-        }
-    );
-
-    return Zuordnungsliste;
-}
\ No newline at end of file
diff --git a/config/texts.json b/config/texts.json
index ce0bef6a57cab01f43ca417bd37527a5bca8837b..8fd2cf4d35cf36db1b4c8f30c8e1d55e92682f45 100644
--- a/config/texts.json
+++ b/config/texts.json
@@ -1,52 +1,12 @@
 {
-    "NichtVerstanden": "Häää? Probiers nochmal.",
-    "InfoImmer": "Wusstest du schon? \nWenn du nicht mehr weiterweißt, kannst du mit „Hilfe“ um Unterstützung bitten.",
-    "InfoTeilnehmer": "Mit „Ändern“ kannst du deine Angaben nochmals überarbeiten.",
-    "InfoWichtel": "Mit „Anonym meinem Wichtelpaten schreiben“ kannst du deinem Wichtelpaten schreiben. \nUnd mit „Anonym meinem Wichtelkind schreiben“ kannst du, naja, deinem Wichtelkind schreiben... aber sei vorsichtig! Es weiß nicht, wer du bist und das sollte auch so bleiben. \nEin „Paket ist unterwegs“ lässt mich und dein Wichtelkind wissen, dass eines unterwegs ist. Aber halte die Sendungsnummer bereit! \nSchreibe einfach „Paket empfangen“, wenn du ein Paket erhalten hast.",
-    "Kontaktaufnahme": "Hallo!\nSchön, dass du am 8ter Wichteln teilnehmen willst!\nSchreibe „registrieren“, um den Anmeldevorgang zu starten.",
-    "Registriert": "Okay, los gehts!\nDu kannst den Registrierungsvorgang jederzeit unterbrechen und einfach zu einem späteren Zeitpunkt weitermachen.",
-    "Regeln": "Infos vorab:\n\nBitte beachte: Wenn du deinen Wichtel analog bewichtelst, erhälst du sowohl den Klarnamen als auch eine Adresse. Bitte bedenke, dass dies Informationen sind, die nichts auf dem Discord zu suchen haben und gehe daher sensibel damit um.\n\nAuf dem Achterdiscord wird es dieses Mal drei verschiedene Kanäle für das Wichteln geben. Einen reinen Infokanal, in dem das Orgateam schreibt, einen spoilerfreien für Nachfragen und einen Hibbelkanal für Vorfreude, Fotos, Spekulationen etc.\nSo kann jede_r das Wichteln so genießen wie er_sie das möchte.\n\nAlles klar? Dann schreibe „ja“ um fortzufahren.",
-    "AnalogDigitalSelbst": "Möchtest **du selbst** lieber digital oder analog bewichtelt **werden**, oder lässt du dich gerne überraschen?\nDu hast die Wahl zwischen „analog“, „digital“ und „beides“.\nBitte beachte: Wählst du „beides“, erfährst du erst, sobald ein Paket oder eine E-Mail ankommt, wofür sich dein Wichtel entschieden hat.\n\nAchtung: Wählst du „analog“ oder „beides“, musst du im nächsten Schritt deinen Klarnamen und deine Adresse angeben. Diese muss zudem innerhalb Deutschlands, Österreichs, der Schweiz oder Luxemburgs liegen. Möchtest du in einem anderen EU-Land analog bewichtelt werden, melde dich bitte beim Orgateam, bevor du hier fortfährst. In den übrigen Teil der Welt (und im Weltraum) kann aufgrund der ansonsten unverhältnismäßigen Versandkosten nur digital bewichtelt werden.",
-    "AnalogDigitalWichtel": "Möchtest du **dein Wichtelkind** lieber digital (zum Beispiel via Steam oder E-Mail) oder analog (ein echtes Päckchen per Post **bewichteln**?\nDu hast die Wahl zwischen „analog“, „digital“ und „beides“ als Antwort.",
-    "Anschrift": "Bitte gib nun deinen Klarnamen und eine belieferbare Adresse ein (eine Packstation ist ebenfalls möglich).",
-    "Land": "In welchem Land befindet sich diese Adresse?\nMöglich sind „Deutschland“, „Österreich“, „Schweiz“ oder „Luxemburg“.",
-    "Steam": "Um digital bewichtelt werden zu können, gib bitte nun eine E-Mailadresse an.\nZusätzlich kannst du auch dein Steamprofil oder eine andere Plattform, über die du beschenkt werden möchtest, hier angeben.",
-    "International": "Wärest du bereit, Achten in anderen Ländern als deinem eigenen zu bewichteln?\nPakete international zu verschicken ist mitunter recht teuer. Um eine Orientierung zu bieten, hier eine kleine Liste, mit wie viel Versandkosten für ein Paket in etwa gerechnet werden muss:\nInnerhalb Deutschlands: 4-6 Euro\nVon Deutschland ins EU-Ausland: 14-16 Euro\nVon Deutschland in die Schweiz: 27 Euro\n\nWenn das für dich okay ist, schreibe bitte „ja“. Sollte dir das zu viel werden, schreibe bitte „nein“ und ich werde das bei der Auslosung berücksichtigen.",
-    "Wunschliste": "Worüber freust du dich?\nDu kannst hier einen kleinen Text schreiben, was dir so gefällt oder ganz konkret eine Steam- oder Amazonwunschliste verlinken. Alles ist erlaubt, was deinem Wichtel dabei hilft, sich ein Bild zu machen, womit er dir eine Freude bereiten könnte.\n\nZusätzlich erhälst du später noch die Möglichkeit, Dinge auszuschließen, die du nicht magst und etwas ganz allgemein über dich zu erzählen.",
-    "Allergien": "Hast du irgendwelche Allergien oder Unverträglichkeiten? Gerade bei Süßigkeiten in analogen Geschenken ist das wichtig zu wissen.",
-    "AusschlussGeschenk": "Möchtest du irgendetwas auf gar keinen Fall und unter keinen Umständen bekommen?",
-    "AusschlussWichtel": "Du möchtest irgendjemandem lieber nichts schenken, zum Beispiel, weil ihr eh gut befreundet seid? Dann darfst du hier bis zu drei Personen (bitte den Discordnamen) nennen, die dir nicht mehr zugelost werden können. Wenn du niemanden ausschließen möchtest, schreibe einfach „-“.\n\nUnabhängig hiervon werde ich versuchen, dir kein Wichtelkind zuzulosen, das du schon einmal bewichtelt hast.",
-    "Freitext": "Im Folgenden hast du 2000 Zeichen Platz, um noch ganz viel über dich zu erzählen, deine Hobbys, deine Pläne zur Weltherrschaft, oder was auch immer du deinem Wichtel noch mitteilen möchtest.",
-    "Teilnehmer": "Herzlichen Glückwunsch, du hast dich erfolgreich zum Wichteln „Weihnachten 2019“ registriert.\nToll!\n\nWas nun? Nun wird gewartet, bis der Anmeldeschluss kommt. Dann wird gelost, und du bekommst hier alle Informationen zu deinem Wichtel.\n\nDu hast einen Fehler gemacht oder etwas vergessen? Kein Problem, mit „ändern“ kannst du deine Angaben bis zum Anmeldeschluss jederzeit überarbeiten.\n\nFrohes Hibbeln bis zum Auslosetag!",
-    "ÄnderungBestätigen": "Bist du sicher, dass du deine Angaben ändern möchtest?",
-    "ÄnderungAbgebrochen": "Okay, alles bleibt beim Alten!",
-    "ÄnderungStarten": "Dann fangen wir mal an!",
-    "AlteDaten": "Dies findet sich aktuell dazu in meiner Datenbank:",
-    "Hilfe": "Keine Sorge! Hilfe ist unterwegs! (So schnell sie kann...)",
-    "HilfeOrgainformation": "@here Der Wichtel [[NUTZERNAME]] ([[NUTZERTAG]]) bittet um Hilfe.",
-    "Wichtelstatus": "**Wichtelstatus** \nGesamtzahl: [[GESAMT]] \nTeilnehmer: [[TEILNEHMER]]",
     "ModerationInfo": "Befehle: \n(Befehl und Parameter werden durch Absätze getrennt.) \n\n`!Info`: Zeigt diese Befehlsübersicht an. \n`!Wichtelstatus`: Gibt Informationen über den aktuellen Status des Wichtelns aus. \n`!NachrichtAnKanal <Nachricht>`: Sendet <Nachricht> an den öffentlichen Wichtelkanal. \n`!NachrichtAnNutzer <Nutzername> <Nachricht>`: Sendet <Nachricht> an <Nutzername>. \n`!NachrichtAnAlleNutzer <Nachricht>`: Sendet <Nachricht> an alle Nutzer, die dem Bot für das aktuelle Wichteln bekannt sind. \n`!NachrichtAnAlleTeilnehmer <Nachricht>`: Sendet <Nachricht> an alle Nutzer, die den Registrierungsprozess abgeschlossen haben und damit als „Teilnehmer“ gelistet sind. \n`!NachrichtAnAlleAusstehenden <Nachricht>`: Sendet <Nachricht> an alle Nutzer, die den Registrierungsprozess derzeit nicht vollständig abgeschlossen haben. \n`!NachrichtAnAlleSpätenWichtel <Nachricht>`: Sendet <Nachricht> an alle Wichtelpaten, die ihr Paket bisher noch nicht abgeschickt haben. \n`!LöscheNachricht <ID>`: Löscht eine Discordnachricht des Bots anhand der Nachrichten-ID <ID> (experimentell). \n`!AnmeldephaseBeenden`: Beendet die Anmeldephase, sodass keine Neuanmeldeungen mehr möglich sind. Teilnehmer werden zu Wartenden. \n`!ZiehungAusführen`: Führt die Ziehung der Wichtel aus. \n`!SteamnamenAuflisten`: Listet die Steamnamen aller Nutzer auf, die digital bewichtelt werden möchten. \n`!SteckbriefeVerteilen`: Verteilt die Steckbriefe ihrer Lospartner an alle Wichtel. \n\nPlatzhalter: \n(Einsetzbar in Texten an Nutzer.) \n\n`[[NUTZERNAME]]`: Der Discordname desjenigen Nutzers, der eine Nachricht erhält.",
     "ParameteranzahlUngenügend": "die Anzahl der Parameter ist ungenügend.",
     "NutzernameNichtGefunden": "der Nutzername wurde nicht gefunden.",
     "SendenErfolgreich": "das Senden der Nachricht war erfolgreich. \nAnzahl Empfänger: [[ANZAHL]]",
     "AnmeldephaseBeendenErfolgreich": "das Beenden der Anmeldephase war erfolgreich. Alle Teilnehmer sind nun Wartende. \nAnzahl Wartender: [[ANZAHL]]",
     "AnmeldephaseBeendenFehlgeschlagen": "beim Beenden der Anmeldephase ist ein Fehler aufgetreten.",
-    "ZiehungAusgeführt": "das Ziehen der Wichtel wurde erfolgreich ausgeführt.",
-    "ZiehungFehlgeschlagen": "bei der Ziehung ist ein Fehler aufgetreten!",
-    "Steckbrief": {
-        "Text": "Hallo [[NUTZERNAME]], \n\ndie Auslosung ist vorüber! Dein Wichtel ist [[WICHTELNAME]]! \n\nDies ist sein Steckbrief: \n\n**Bewichtelungsart:** \n[[ANALOGDIGITAL]] \n\n[[GESCHENKZIEL]] \n\n**Wunschliste:** \n[[WUNSCHLISTE]] [[ALLERGIENTEXT]] \n\n**Unerwünscht:** \n[[UNERWÜNSCHT]] \n\n**Freitext:** \n[[FREITEXT]]",
-        "Anschrift": "**Anschrift:** \n[[ANSCHRIFT]] \n([[LAND]])",
-        "Steam": "**Steamname:** \n[[STEAM]]",
-        "Allergien": "\n\n**Allergien:** \n[[ALLERGIEN]]"
-    },
     "SteckbriefeGesendet": "Alle Steckbriefe erfolgreich gesendet. \nAnzahl Empfänger: [[ANZAHL]]",
-    "GutenMorgen": "Guten Morgen, [[NUTZERNAME]]!",
-    "GutenTag": "Guten Tag, [[NUTZERNAME]]!",
-    "Hallo": "Hallööö, [[NUTZERNAME]]!",
-    "GuteNacht": "Schlaf schön, [[NUTZERNAME]]!",
-    "Sternenrose": "Vielen Dank, Sterni! :)",
     "Schweinchen": "Oink! Oink! :pig: :cookie:",
-    "Vielleicht": "Ein Vielleicht gibts nicht! Die Weltherrschaft braucht eindeutige Ergebnisse!",
     "AnmeldephaseBeendet": "die Anmeldephase ist leider bereits vorbei. Aber keine Sorge: Das nächste Wischtöööln kommt bestimmt!",
     "NachrichtAnWichtelpaten": "Was möchtest du deinem Wichtelpaten schreiben? \nDenke daran, dies ist nur für wirklich wichtige Anmerkungen und Korrekturen gedacht, nicht für Pläuschchen! \n\n...\nIm Ernst! Ich bekomme das mit!",
     "NachrichtAnWichtelkind": "Was möchtest du deinem Wichtelkind schreiben? \nBitte denke daran, dass dein Wichtelkind nicht weiß, wer du bist und das auch so bleiben sollte. Es wäre nicht schön, wenn die Überraschung vor dem Wichtelstream ruiniert würde! \nSchreibe ihm also bitte **nur**, wenn es **wirklich** nicht anders geht. Und wenn, dann halte dich kurz, denn schon dein Schreibstil könnte dich andernfalls schnell verraten!",
@@ -61,4 +21,4 @@
     "PaketGesendetVollständig": "Danke!",
     "PaketEmpfangenBenachrichtigung": "Hallo! \nIch wollte dir nur mitteilen: Dein Geschenk ist sicher beim Wichtelkind angekommen!",
     "PaketEmpfangenVollständig": "Danke!"
-}
\ No newline at end of file
+}
diff --git a/locale/de-DE.commands.json b/locale/de-DE.commands.json
index 46f6fe31fc35e4445e6267091ca9498ac38f8d2c..5ccc3620d522b036dcfc3eb954105c87cedd058e 100644
--- a/locale/de-DE.commands.json
+++ b/locale/de-DE.commands.json
@@ -9,7 +9,7 @@
             "Rufe Hilfe",
             "Ruf Hilfe"
         ],
-        "info": "Schicke eine Nachricht an die Wichtelorga. Es wird sich dann jemand bei dir melden und dir bei deinem Problem helfen."
+        "info": "Schickt eine Nachricht an die Wichtelorga. Es wird sich dann jemand bei dir melden und dir bei deinem Problem helfen."
     },
     "changeInformation": {
         "commands": [
@@ -92,6 +92,31 @@
     "maybe": {
         "commands": ["vielleicht", "vllt", "eventuell", "evtl"]
     },
+    "moddingDistributeWichtelProfiles":
+    {
+        "commands": [
+            "SteckbriefeVerteilen"
+        ],
+        "info": "Verteilt die Steckbriefe der Wichtelkinder an alle Wichtelpaten."
+    },
+    "moddingEndRegistration":
+    {
+        "commands": [
+            "RegistrierungsphaseBeenden",
+            "RegistrierungBeenden",
+            "AnmeldephaseBeenden",
+            "AnmeldungBeenden"
+        ],
+        "info": "Beendet die Registrierungsphase und versetzt alle vollständig registrierten Teilnehmer in den Wartezustand."
+    },
+    "moddingRunAssignment":
+    {
+        "commands": [
+            "Auslosen",
+            "ZiehungAusführen"
+        ],
+        "info": "Führt die Auslosung der Wichtel aus."
+    },
     "moddingStatus":
     {
         "commands": [
diff --git a/locale/de-DE.texts.json b/locale/de-DE.texts.json
index 0401117030d05a5ec3bfacb84b6f0e8012e0ab10..8cf1332679a797f64db0c1a54a04f8fbb3d0886e 100644
--- a/locale/de-DE.texts.json
+++ b/locale/de-DE.texts.json
@@ -1,4 +1,6 @@
 {
+    "assignmentError": "Die Auslosung ist fehlgeschlagen.",
+    "assignmentSuccessful": "Die Auslosung war erfolgreich.",
     "becameMember": "Herzlichen Glückwunsch, du hast dich erfolgreich zum Wichteln „{var.currentEventName}“ registriert.\nToll!\n\nWas nun? Nun wird gewartet, bis der Anmeldeschluss kommt. Dann wird gelost und du bekommst hier alle Informationen zu deinem Wichtel.\n\nDu hast einen Fehler gemacht oder etwas vergessen? Kein Problem, mit „ändern“ kannst du deine Angaben bis zum Anmeldeschluss jederzeit überarbeiten. (Achte allerdings darauf, dass alle Änderungen bis Registrierungsschluss abgeschlossen sein müssen, sonst bist du nicht registriert!)\n\nFrohes Hibbeln bis zum Auslosetag!",
     "changedInformation": "Angaben erfolgreich geändert. Du kannst jederzeit mittels „ändern“ die Angaben erneut anpassen.",
     "commandInfo": "`{command.name}`: {command.info}",
@@ -36,6 +38,8 @@
     "notUnderstood": "Häää? Probiers nochmal.",
     "oldInformation": "Dies findet sich aktuell dazu in meiner Datenbank:\n>>> {var.informationValue}",
     "moderationNeedHelp": "<@&{var.moderationRoleId}> Der Wichtel {contact.nickname} ({contact.tag}) bittet um Hilfe.",
+    "moderationRegistrationEnded": "Registrierungsphase beendet. \nAnzahl Wartender: {var.waitingMemberCount}",
+    "moderationProfilesDistributed": "Alle {var.profileCount} Steckbriefe wurden verteilt.",
     "moderationStatus": "**Status des Wichtelns {var.currentEventName}**\nAktuelle Phase: {var.eventPhaseString}\n\nAnzahl Teilnehmer: {var.contactCount}\nDavon vollständig registriert: {var.waitingMemberCount}\n\nBewichtelungsarten:\nAls Wichtelpate: {var.analogueGiverCount} mal analog, {var.digitalGiverCount} mal digital, {var.allGiverCount} mal beides\nAls Wichtelkind: {var.analogueTakerCount} mal analog, {var.digitalTakerCount} mal digital, {var.allTakerCount} mal beides\n\nPakete: {var.parcelSentCount} versendet, {var.parcelReceivedCount} erhalten",
     "moderationStatusEventPhase": "{var.currentEventPhase}",
     "moderationStatusEventPhaseWithNextPhase": "{var.currentEventPhase} ({var.nextEventPhase} startet am {date.day}.{date.month}.{date.year} um {date.hour}:{date.minute} Uhr)",
@@ -55,5 +59,6 @@
     "sentComponentText": "Klicken! Du sollst klicken!",
     "sternenrose": "Vielen Dank, Sterni! :)",
     "thankYouResponse": "Bitte!",
+    "wichtelProfileDistribution": "Hallo {contact.nickname},\ndie Auslosung ist vorüber!\nHier ist der Steckbrief deines Wichtelkindes:",
     "yourAreWelcomeRespone": "🙂"
 }
diff --git a/scripts/.eslintrc.json b/scripts/.eslintrc.json
index 2e3acab29202fd75bc5e94b41a98f23e6f9c075f..17ffccad300d9bbad8b1519a5846355d93700961 100644
--- a/scripts/.eslintrc.json
+++ b/scripts/.eslintrc.json
@@ -59,6 +59,12 @@
                 "ignoreReadBeforeAssign": true
             }
         ],
+        "no-constant-condition": [
+            "error",
+            {
+                "checkLoops": false
+            }
+        ],
         "@typescript-eslint/no-non-null-assertion": "off",
         "@typescript-eslint/no-unused-vars": "off", // Provided by the tsconfig.json
         "@typescript-eslint/no-explicit-any": "off",
diff --git a/scripts/utility/localisation.ts b/scripts/utility/localisation.ts
index 6a5c812e0442ca062b82e58d27bce9064ad1f662..97d4d5f41b916c94a047fef83af35d736489eda7 100644
--- a/scripts/utility/localisation.ts
+++ b/scripts/utility/localisation.ts
@@ -27,6 +27,9 @@ interface Commands
     informationBothAnalogueAndDigital: CommandInfo;
     informationDigital: CommandInfo;
     maybe: CommandInfo;
+    moddingDistributeWichtelProfiles: CommandInfo;
+    moddingEndRegistration: CommandInfo;
+    moddingRunAssignment: CommandInfo; // TODO: Rename "modding" commands to "moderation".
     moddingStatus: CommandInfo;
     no: CommandInfo;
     registration: CommandInfo;
@@ -39,6 +42,8 @@ interface Commands
 // TODO: Documentation
 interface Texts
 {
+    assignmentError: TokenString;
+    assignmentSuccessful: TokenString;
     becameMember: TokenString;
     changedInformation: TokenString;
     commandInfo: TokenString;
@@ -76,6 +81,8 @@ interface Texts
     notUnderstood: TokenString;
     oldInformation: TokenString;
     moderationNeedHelp: TokenString; // TODO: This includes a Discord specific mention which is not portable to other implementations.
+    moderationRegistrationEnded: TokenString;
+    moderationProfilesDistributed: TokenString;
     moderationStatus: TokenString;
     moderationStatusEventPhase: TokenString;
     moderationStatusEventPhaseWithNextPhase: TokenString;
@@ -95,6 +102,7 @@ interface Texts
     sentComponentText: TokenString;
     sternenrose: TokenString;
     thankYouResponse: TokenString;
+    wichtelProfileDistribution: TokenString;
     yourAreWelcomeRespone: TokenString;
 }
 
diff --git a/scripts/wichtelbot/classes/exclusion.ts b/scripts/wichtelbot/classes/exclusion.ts
new file mode 100644
index 0000000000000000000000000000000000000000..fc7929eb06096f6d9f8ea9f9e05c0771cd52c6df
--- /dev/null
+++ b/scripts/wichtelbot/classes/exclusion.ts
@@ -0,0 +1,40 @@
+import { ExclusionReason } from "../types/exclusionReason";
+import Utils from "../../utility/utils";
+
+export interface ExclusionData
+{
+    giverId: string;
+    takerId: string;
+    reason: ExclusionReason;
+}
+
+function instanceOfExclusion (object: any): object is Exclusion
+{
+    const potentialExclusion = object as Exclusion;
+
+    return (potentialExclusion.lastUpdateTime !== undefined);
+}
+
+export class Exclusion implements ExclusionData
+{
+    public giverId: string;
+    public takerId: string;
+    public reason: ExclusionReason;
+    public lastUpdateTime: number;
+
+    constructor (exclusion: Exclusion|ExclusionData)
+    {
+        if (instanceOfExclusion(exclusion))
+        {
+            this.lastUpdateTime = exclusion.lastUpdateTime;
+        }
+        else
+        {
+            this.lastUpdateTime = Utils.getCurrentUnixTime();
+        }
+
+        this.giverId = exclusion.giverId;
+        this.takerId = exclusion.takerId;
+        this.reason = exclusion.reason;
+    }
+}
diff --git a/scripts/wichtelbot/classes/relationship.ts b/scripts/wichtelbot/classes/relationship.ts
new file mode 100644
index 0000000000000000000000000000000000000000..35b346ec7dc5ea59e1a4ad41c08b75690ee6fb98
--- /dev/null
+++ b/scripts/wichtelbot/classes/relationship.ts
@@ -0,0 +1,36 @@
+import Config from "../../utility/config";
+
+export interface RelationshipData
+{
+    giverId: string;
+    takerId: string;
+}
+
+function instanceOfRelationship (object: any): object is Relationship
+{
+    const potentialRelationship = object as Relationship;
+
+    return (potentialRelationship.wichtelEvent !== undefined);
+}
+
+export class Relationship implements RelationshipData
+{
+    public wichtelEvent: string;
+    public giverId: string;
+    public takerId: string;
+
+    constructor (relationship: Relationship|RelationshipData)
+    {
+        if (instanceOfRelationship(relationship))
+        {
+            this.wichtelEvent = relationship.wichtelEvent;
+        }
+        else
+        {
+            this.wichtelEvent = Config.main.currentEvent.name;
+        }
+
+        this.giverId = relationship.giverId;
+        this.takerId = relationship.takerId;
+    }
+}
diff --git a/scripts/wichtelbot/database/database.ts b/scripts/wichtelbot/database/database.ts
index 46eba384c826af38cd683a67d96184119ca6a176..0bfa9f870db9f516266a469ea4449424d2c1099d 100644
--- a/scripts/wichtelbot/database/database.ts
+++ b/scripts/wichtelbot/database/database.ts
@@ -1,6 +1,7 @@
 import * as fs from 'fs';
-
 import Contact, { ContactCoreData, ContactData } from '../classes/contact';
+import { Exclusion, ExclusionData } from '../classes/exclusion';
+import { Relationship, RelationshipData } from '../classes/relationship';
 import Config from '../../utility/config';
 import ContactType from '../types/contactType';
 import GiftType from '../types/giftType';
@@ -205,7 +206,7 @@ export default class Database
     }
 
     /**
-     * NOTE: The contact objects's lastUpdateTime will be updated.
+     * NOTE: The contact object's lastUpdateTime will be updated.
      * TODO: Give the save methods a better name like "insert" or "create" or "saveNew".
      */
     public saveContact (contact: Contact): void
@@ -243,25 +244,36 @@ export default class Database
     }
 
     /**
-     * NOTE: The contact objects's lastUpdateTime will be updated.
+     * NOTE: The contact object's lastUpdateTime will be updated.
      */
     public updateContact (contact: Contact): void
     {
-        contact.lastUpdateTime = Utils.getCurrentUnixTime();
+        this.updateContacts([contact]);
+    }
 
+    /**
+     * NOTE: The contact objects's lastUpdateTime will be updated.
+     */
+    public updateContacts (contacts: Contact[]): void
+    {
         const statement = this.mainDatabase.prepare(
             `UPDATE
-                contact
-            SET
-                tag = :tag, name = :name, nickname = :nickname,
-                lastUpdateTime = :lastUpdateTime, type = :type, state = :state
-            WHERE
-                id = :id`
+                 contact
+             SET
+                 tag = :tag, name = :name, nickname = :nickname,
+                 lastUpdateTime = :lastUpdateTime, type = :type, state = :state
+             WHERE
+                 id = :id`
         );
 
-        statement.run(
-            this.getBindablesFromObject(contact)
-        );
+        for (const contact of contacts)
+        {
+            contact.lastUpdateTime = Utils.getCurrentUnixTime();
+
+            statement.run(
+                this.getBindablesFromObject(contact)
+            );
+        }
     }
 
     public getContactCount (): number
@@ -379,6 +391,36 @@ export default class Database
         return member;
     }
 
+    public getMembersByState (state: State): Member[]
+    {
+        const statement = this.mainDatabase.prepare(
+            `SELECT
+                contact.*, information.*
+            FROM
+                contact
+            LEFT JOIN
+                information
+                    ON information.contactId = contact.id
+            WHERE
+                contact.state = ?`
+        );
+
+        statement.expand(true); // Expands the result to have one sub-object for each table.
+
+        const resultData = statement.all(state) as { contact: ContactData, information: InformationData }[];
+
+        const members: Member[] = [];
+
+        for (const result of resultData)
+        {
+            const member = new Member(result.contact, result.information);
+
+            members.push(member);
+        }
+
+        return members;
+    }
+
     /**
      * Updates an existing member (contact and information data) in the database. \
      * NOTE: The contact and the information objects' lastUpdateTime will be updated.
@@ -425,15 +467,6 @@ export default class Database
         runTransaction();
     }
 
-    public getWaitingMemberCount (): number
-    {
-        const statement = this.mainDatabase.prepare(
-            'SELECT COUNT(*) FROM contact WHERE contact.state = ?'
-        );
-
-        return this.getCount(statement, State.Waiting);
-    }
-
     /**
      * Will return the type of contact that can be found for this ID. \
      * If no contact is found, the given contactCoreData will be returned instead.
@@ -465,6 +498,98 @@ export default class Database
         }
     }
 
+    public getWaitingMemberCount (): number
+    {
+        const statement = this.mainDatabase.prepare(
+            'SELECT COUNT(*) FROM contact WHERE contact.state = ?'
+        );
+
+        return this.getCount(statement, State.Waiting);
+    }
+
+    public getUserExclusions (): Exclusion[]
+    {
+        const statement = this.mainDatabase.prepare(
+            'SELECT * FROM exclusion'
+        );
+
+        const rawExclusions = statement.all() as Exclusion[];
+
+        const exclusions: Exclusion[] = [];
+
+        for (const rawExclusion of rawExclusions)
+        {
+            const exclusion = new Exclusion(rawExclusion);
+
+            exclusions.push(exclusion);
+        }
+
+        return exclusions;
+    }
+
+    public saveUserExclusions (exclusions: ExclusionData[]): void
+    {
+        const statement = this.mainDatabase.prepare(
+            `INSERT INTO
+                exclusion (giverId, takerId, reason, lastUpdateTime)
+            VALUES
+                (:giverId, :takerId, :reason, :lastUpdateTime)`
+        );
+
+        for (const exclusion of exclusions)
+        {
+            const parameters = {
+                lastUpdateTime: Utils.getCurrentUnixTime(),
+                ...this.getBindablesFromObject(exclusion)
+            };
+
+            statement.run(parameters);
+        }
+    }
+
+    public getRelationships (): Relationship[]
+    {
+        // TODO: Could these kind of statements be abstracted as "get all and return as this class instances"?
+
+        const statement = this.mainDatabase.prepare(
+            'SELECT * FROM relationship'
+        );
+
+        const rawRelationships = statement.all() as Relationship[];
+
+        const relationships: Relationship[] = [];
+
+        for (const rawRelationship of rawRelationships)
+        {
+            const relationship = new Relationship(rawRelationship);
+
+            relationships.push(relationship);
+        }
+
+        return relationships;
+    }
+
+    public saveRelationships (relationships: RelationshipData[]): void
+    {
+        const statement = this.mainDatabase.prepare(
+            `INSERT INTO
+                relationship (wichtelEvent, giverId, takerId)
+            VALUES
+                (:wichtelEvent, :giverId, :takerId)`
+        );
+
+        for (const relationship of relationships)
+        {
+            const parameters = {
+                wichtelEvent: Config.main.currentEvent.name,
+                giverId: relationship.giverId,
+                takerId: relationship.takerId,
+            };
+
+            statement.run(parameters);
+        }
+    }
+
     public getGiftTypeStatistics (): GiftTypeStatistics
     {
         const statement = this.mainDatabase.prepare(
@@ -517,67 +642,6 @@ export default class Database
 
 /*
 
-/**
- * Lädt alle Nutzer aus der Datenbank.
- * @param {Function} Callback Callback, der für jeden gefundenen Nutzer ausgeführt wird. Parameter: {Object} Reihe.
- * /
-export function AlleNutzerLaden (Callback)
-{
-    mainDatabase.each(
-        `SELECT Nutzer.*, Informationen.*, Wichtelkinder.WichtelId AS WichtelkindId, Wichtelpaten.NutzerId AS WichtelpateId FROM Nutzer
-         LEFT JOIN Informationen ON Nutzer.Id = Informationen.NutzerId
-         LEFT JOIN Wichtel AS Wichtelkinder ON Nutzer.Id = Wichtelkinder.NutzerId
-         LEFT JOIN Wichtel AS Wichtelpaten ON Nutzer.Id = Wichtelpaten.WichtelId`,
-        function (Fehler, Reihe)
-        {
-            Fehlerbehandlung(Fehler);
-            Callback(Reihe);
-        }
-    );
-}
-
-/**
- * Ermittelt die relevanten Nutzerzahlen: Gesamtzahl, Teilnehmer; abzüglich Mods.
- * @param {Function} Callback Callback, der nach dem Erhalt des Ergebnisses ausgeführt wird. Parameter: {Object} Reihe.
- * /
-export function Nutzerzahlen (Callback)
-{
-    mainDatabase.get(
-        `SELECT
-            COUNT(*) AS Gesamt,
-            SUM(CASE WHEN Zustand = 'Teilnehmer' THEN 1 ELSE 0 END) AS Teilnehmer
-        FROM Nutzer
-        WHERE Id NOT IN (SELECT NutzerId FROM Mods)`,
-        function (Fehler, Reihe)
-        {
-            Fehlerbehandlung(Fehler);
-            Callback(Reihe);
-        }
-    );
-}
-
-/**
- * Liest alle eingetragenen Ausschlüsse aus der Datenbank.
- * @param {Number} NutzerId Die Discord-ID des Nutzers.
- * @param {Function} Callback Callback, der nach dem Laden der Ausschlüsse ausgeführt wird. Parameter: {Array} Ausschlusswichtel.
- * /
-export function Ausschlüsse (NutzerId, Callback)
-{
-    mainDatabase.all(
-        'SELECT * FROM Ausschluesse WHERE NutzerId = ? ORDER BY Zeit',
-        NutzerId,
-        function (Fehler, Reihen)
-        {
-            Fehlerbehandlung(Fehler);
-
-            if (Fehler)
-                Reihen = [];
-
-            Callback(Reihen);
-        }
-    );
-}
-
 /**
  * Liest alle Steamnamen aus der Datenbank.
  * @param {Function} Callback Callback, der nach dem Laden der Ausschlüsse ausgeführt wird. Parameter: {Array} Ausschlusswichtel.
@@ -620,48 +684,6 @@ export function WichtelOhneVerschicktesPaket (Callback)
     );
 }
 
-/**
- * Trägt eine Liste an Nutzer-Wichtel-Zuordnungen in die Datenbank ein.
- * @param {Array} Zuordnungen Eine Liste von Objekten mit .Nutzer.Id und .Wichtel.Id.
- * @param {Function} Callback Callback, der nach dem Eintragen der Wichtel ausgeführt wird.
- * /
-export function WichtelEintragen (Zuordnungen, Callback)
-{
-    let Transaktion = NeueTransaktion(mainDatabase.Name);
-
-    let Vorgang = Transaktion.prepare("INSERT INTO Wichtel (NutzerId, WichtelId) VALUES (?, ?)", Transaktion.Fehlerbehandlung);
-
-    for (let Zuordnung of Zuordnungen)
-        Vorgang.run(Zuordnung.Nutzer.Id, Zuordnung.Wichtel.Id, Transaktion.Fehlerbehandlung);
-
-    Vorgang.finalize(function ()
-        {
-            Callback(Transaktion.Schließen());
-        }
-    );
-}
-
-/**
- * Setzt den Status einer Liste von Nutzern (Teilnehmern) auf "Wichtel".
- * @param {Array} Teilnehmerliste Eine Liste von Nutzer-IDs, dessen Status zu "Wichtel" geändert werden soll.
- * @param {Function} Callback Callback, der nach dem Eintragen der Wichtel ausgeführt wird.
- * /
-export function TeilnehmerZuWichtelnMachen (Teilnehmerliste, Callback)
-{
-    let Transaktion = NeueTransaktion(mainDatabase.Name);
-
-    let Vorgang = Transaktion.prepare("UPDATE Nutzer SET Zustand = 'Wartend' WHERE Id = ?", Transaktion.Fehlerbehandlung);
-
-    for (let TeilnehmerId of Teilnehmerliste)
-        Vorgang.run(TeilnehmerId, Transaktion.Fehlerbehandlung);
-
-    Vorgang.finalize(function ()
-        {
-            Callback(Transaktion.Schließen());
-        }
-    );
-}
-
 /**
  * Legt ein Paket in der Datenbank an.
  * @param {String} SenderId Die ID des Senders, also des Wichtels, der das Paket abgeschickt hat.
diff --git a/scripts/wichtelbot/endpoint/definitions/state.ts b/scripts/wichtelbot/endpoint/definitions/state.ts
index 9f4db0ab08504923b9f24415fbbad6c7f9a6cbf6..e65a0e19e137e9eab18e968ea748da294b94e54e 100644
--- a/scripts/wichtelbot/endpoint/definitions/state.ts
+++ b/scripts/wichtelbot/endpoint/definitions/state.ts
@@ -17,9 +17,13 @@ enum State
     InformationUserExclusion = 'informationUserExclusion',
     InformationFreeText = 'informationFreeText',
     // As member:
-    Waiting = 'waiting', // Waiting for becoming a wichtel.
+    /** Waiting for becoming a Wichtel. */
+    Waiting = 'waiting', // TODO: Rename to "registered".
     ConfirmDeregistration = 'confirmDeregistration',
+    /** While assignment is running; cannot change information. */
+    Assignment = 'assignment',
     // As wichtel:
+    Wichteling = 'wichteling',
     MessageToGiftGiver = 'messageToGiftGiver',
     MessageToGiftTaker = 'messageToGiftTaker',
     ParcelSendConsignmentNumber = 'sendParcelConsignmentNumber',
diff --git a/scripts/wichtelbot/message/handlingDefinition.ts b/scripts/wichtelbot/message/handlingDefinition.ts
index 76b857e67bdbc3b743be6f9b5f5a2ae372aab509..f8009ee26c590d5c2978f4617f3493b551e8cc09 100644
--- a/scripts/wichtelbot/message/handlingDefinition.ts
+++ b/scripts/wichtelbot/message/handlingDefinition.ts
@@ -1,5 +1,6 @@
 import { CommandHandlerFunction, StateCommandHandlerFunction } from './handlingTools/handlerFunctions';
 import Localisation, { CommandInfo } from '../../utility/localisation';
+import { AssignmentModule } from './modules/assignmentModule';
 import { ComponentBuilder } from './handlingTools/componentBuilder';
 import Config from '../../utility/config';
 import GeneralModule from './modules/generalModule';
@@ -51,17 +52,23 @@ export default class HandlingDefinition
     protected generalModule: GeneralModule;
     protected informationModule: InformationModule;
     protected moderationModule: ModerationModule;
+    protected assignmentModule: AssignmentModule;
 
     private get maxShortMessageLength (): number
     {
         return Math.floor(Config.main.maxMessageLength / 2);
     }
 
-    constructor (generalModule: GeneralModule, informationModule: InformationModule, moderationModule: ModerationModule)
-    {
+    constructor (
+        generalModule: GeneralModule,
+        informationModule: InformationModule,
+        moderationModule: ModerationModule,
+        assignmentModule: AssignmentModule
+    ) {
         this.generalModule = generalModule;
         this.informationModule = informationModule;
         this.moderationModule = moderationModule;
+        this.assignmentModule = assignmentModule;
     }
 
     public stateCommands: StateCommandDefinition[] = [
@@ -490,6 +497,30 @@ export default class HandlingDefinition
     ];
 
     public moderatorCommands: CommandDefinition[] = [
+        {
+            commandInfo: Localisation.commands.moddingDistributeWichtelProfiles,
+            handlerFunction: async (message: Message): Promise<void> => this.moderationModule.distributeWichtelProfiles(message)
+        },
+        {
+            commandInfo: Localisation.commands.moddingEndRegistration, // TODO: Remove with cron feature that does this automatically.
+            handlerFunction: async (message: Message): Promise<void> => this.moderationModule.endRegistration(message)
+        },
+        {
+            commandInfo: Localisation.commands.moddingRunAssignment,
+            handlerFunction: async (message: Message): Promise<void> =>
+            {
+                const asssignmentResult = this.assignmentModule.runAssignment();
+
+                if (asssignmentResult)
+                {
+                    await this.generalModule.reply(message, Localisation.texts.assignmentSuccessful);
+                }
+                else
+                {
+                    await this.generalModule.reply(message, Localisation.texts.assignmentError);
+                }
+            }
+        },
         {
             commandInfo: Localisation.commands.moddingStatus,
             handlerFunction: async (message: Message): Promise<void> => this.moderationModule.sendStatus(message)
diff --git a/scripts/wichtelbot/message/handlingTools/handlingUtils.ts b/scripts/wichtelbot/message/handlingTools/handlingUtils.ts
new file mode 100644
index 0000000000000000000000000000000000000000..2460c1da4546b2f2e5e7dca00a9abbcb5bfe144f
--- /dev/null
+++ b/scripts/wichtelbot/message/handlingTools/handlingUtils.ts
@@ -0,0 +1,93 @@
+import { Visualisation, VisualisationType } from "../../endpoint/definitions";
+import GiftType from "../../types/giftType";
+import Localisation from "../../../utility/localisation";
+import Member from "../../classes/member";
+
+export abstract class HandlingUtils
+{
+    public static getProfileVisualisations (member: Member): Visualisation[]
+    {
+        const visualisations: Visualisation[] = [
+            {
+                headline: Localisation.texts.profileName.process(member),
+                text: member.name,
+                type: VisualisationType.Compact
+            },
+            {
+                headline: Localisation.texts.profileGiftType.process(member),
+                text: Localisation.translateGiftType(member.information.giftTypeAsTaker),
+                type: VisualisationType.Compact
+            }
+        ];
+
+        const sendAnalogue = member.information.giftTypeAsTaker == GiftType.Analogue || member.information.giftTypeAsTaker == GiftType.All;
+        const sendDigital = member.information.giftTypeAsGiver == GiftType.Digital || member.information.giftTypeAsGiver == GiftType.All;
+
+        if (sendAnalogue)
+        {
+            visualisations.push(
+                {
+                    headline: Localisation.texts.profileCounty.process(member),
+                    text: Localisation.translateCountry(member.information.country),
+                    type: VisualisationType.Compact
+                }
+            );
+
+            visualisations.push(
+                {
+                    headline: Localisation.texts.profileAddress.process(member),
+                    text: member.information.address,
+                    type: VisualisationType.Compact
+                }
+            );
+        }
+
+        if (sendDigital)
+        {
+            visualisations.push(
+                {
+                    headline: Localisation.texts.profileDigitalAddress.process(member),
+                    text: member.information.digitalAddress,
+                    type: VisualisationType.Compact
+                }
+            );
+        }
+
+        visualisations.push(
+            {
+                headline: Localisation.texts.profileWishlist.process(member),
+                text: member.information.wishList,
+                type: VisualisationType.Normal
+            }
+        );
+
+        if (sendAnalogue)
+        {
+            visualisations.push(
+                {
+                    headline: Localisation.texts.profileAllergies.process(member),
+                    text: member.information.allergies,
+                    type: VisualisationType.Normal
+                }
+            );
+        }
+
+        visualisations.push(
+            {
+                headline: Localisation.texts.profileExclusion.process(member),
+                text: member.information.giftExclusion,
+                type: VisualisationType.Normal
+            }
+        );
+
+        visualisations.push(
+            {
+                headline: Localisation.texts.profileFreeText.process(member),
+                text: member.information.freeText,
+                type: VisualisationType.Normal
+            }
+        );
+
+        return visualisations;
+    }
+}
diff --git a/scripts/wichtelbot/message/messageHandler.ts b/scripts/wichtelbot/message/messageHandler.ts
index 41574479aa0e1a2ee0e4f9c95ed048d99c96c38f..fccb3af32a9e4afc30766cbd5eaa5b118b857640 100644
--- a/scripts/wichtelbot/message/messageHandler.ts
+++ b/scripts/wichtelbot/message/messageHandler.ts
@@ -1,5 +1,6 @@
 import { ChannelType, Message, State } from '../endpoint/definitions';
 import Localisation, { CommandInfo } from '../../utility/localisation';
+import { AssignmentModule } from './modules/assignmentModule';
 import { CommandHandlerFunction } from './handlingTools/handlerFunctions';
 import Config from '../../utility/config';
 import Database from '../database/database';
@@ -31,6 +32,7 @@ export default class MessageHandler
     protected generalModule: GeneralModule;
     protected informationModule: InformationModule;
     protected moderationModule: ModerationModule;
+    protected assignmentModule: AssignmentModule;
 
     // In private messages:
     protected stateCommands = new StateCommandMap();
@@ -59,8 +61,14 @@ export default class MessageHandler
         this.generalModule = new GeneralModule(database);
         this.informationModule = new InformationModule(database);
         this.moderationModule = new ModerationModule(database);
+        this.assignmentModule = new AssignmentModule(database);
 
-        this.handlingDefinition = new HandlingDefinition(this.generalModule, this.informationModule, this.moderationModule);
+        this.handlingDefinition = new HandlingDefinition(
+            this.generalModule,
+            this.informationModule,
+            this.moderationModule,
+            this.assignmentModule
+        );
 
         this.applyHandlingDefinition();
     }
diff --git a/scripts/wichtelbot/message/modules/assignmentModule.ts b/scripts/wichtelbot/message/modules/assignmentModule.ts
new file mode 100644
index 0000000000000000000000000000000000000000..7b2e29473f466cc80d2d0511415da4ed9cfc6c86
--- /dev/null
+++ b/scripts/wichtelbot/message/modules/assignmentModule.ts
@@ -0,0 +1,392 @@
+import Database from '../../database/database';
+import { Exclusion } from '../../classes/exclusion';
+import { ExclusionReason } from '../../types/exclusionReason';
+import GiftType from '../../types/giftType';
+import Member from '../../classes/member';
+import { RelationshipData } from '../../classes/relationship';
+import { State } from '../../endpoint/definitions';
+
+interface Candidate
+{
+    /** The member that could possibly become the taker Wichtel of the assignment. */
+    taker: Member;
+    /** The score for the taker Wichtel. The higher the better. */
+    score: number;
+}
+
+interface Pairing
+{
+    /** The future giver Wichtel. */
+    giver: Member;
+    /** The list of possible candidates for this assignment. */
+    candidates: Candidate[];
+}
+
+interface Assignment
+{
+    giver: Member;
+    taker: Member;
+}
+
+export class AssignmentModule
+{
+    private database: Database;
+
+    constructor (database: Database)
+    {
+        this.database = database;
+    }
+
+    /**
+     * Run the assignment.
+     * @returns True if successful, false otherwise.
+     */
+    public runAssignment (): boolean // TODO: Return the reason for failure.
+    {
+        const members = this.database.getMembersByState(State.Assignment);
+
+        if (members.length === 0)
+        {
+            return false;
+        }
+
+        const pairings = this.assemblePairings(members);
+
+        const preparationResult = this.prepareCandidates(pairings);
+
+        if (!preparationResult)
+        {
+            return false;
+        }
+
+        const assignments = this.assignCandidates(pairings);
+
+        if (assignments.length === 0)
+        {
+            return false;
+        }
+
+        const relationships: RelationshipData[] = assignments.map(
+            assignment =>
+            {
+                return {
+                    giverId: assignment.giver.id,
+                    takerId: assignment.taker.id,
+                };
+            }
+        );
+
+        this.database.saveRelationships(relationships);
+
+        return true;
+    }
+
+    private assemblePairings (members: Member[]): Pairing[]
+    {
+        const pairings: Pairing[] = [];
+
+        for (const member of members)
+        {
+            const candidates: Candidate[] = [];
+
+            for (const takerWichtel of members)
+            {
+                if (member === takerWichtel)
+                {
+                    continue;
+                }
+
+                candidates.push(
+                    {
+                        taker: takerWichtel,
+                        score: 0,
+                    }
+                );
+            }
+
+            pairings.push(
+                {
+                    giver: member,
+                    candidates: candidates,
+                }
+            );
+        }
+
+        return pairings;
+    }
+
+    /**
+     * Prepare the canidates assignment map by removing the incompatible members and calculating the scores/rank.
+     * @returns True if successful, false otherwise.
+     */
+    private prepareCandidates (pairings: Pairing[]): boolean
+    {
+        const exclusions = this.database.getUserExclusions();
+        const wishExclusions = this.extractWishExclusions(exclusions);
+        const wichtelFromThePastExclusions = this.extractWichtelFromThePastExclusions(exclusions);
+
+        for (const pairing of pairings)
+        {
+            for (let i = pairing.candidates.length - 1; i >= 0; i--)
+            {
+                const candidate = pairing.candidates[i];
+
+                if (this.isGiftTypeIncompatible(pairing.giver, candidate.taker)
+                    || this.isInternationalIncompatible(pairing.giver, candidate.taker)
+                    || this.isExcluded(pairing.giver, candidate.taker, wishExclusions))
+                {
+                    pairing.candidates.splice(i, 1);
+                }
+
+                candidate.score = this.calculateScore(pairing.giver, candidate.taker, wichtelFromThePastExclusions);
+            }
+
+            if (pairing.candidates.length === 0)
+            {
+                // Empty candidates list means no valid solution for the assignment:
+                return false;
+            }
+
+            this.sortCandidatesByScore(pairing.candidates);
+        }
+
+        return true;
+    }
+
+    private extractWishExclusions (exclusions: Exclusion[]): Exclusion[]
+    {
+        return exclusions.filter(exclusion => exclusion.reason === ExclusionReason.Wish);
+    }
+
+    private extractWichtelFromThePastExclusions (exclusions: Exclusion[]): Exclusion[]
+    {
+        return exclusions.filter(exclusion => exclusion.reason === ExclusionReason.WichtelFromThePast);
+    }
+
+    private isGiftTypeIncompatible (giverWichtel: Member, takerWichtel: Member): boolean
+    {
+        if (giverWichtel.information.giftTypeAsGiver === takerWichtel.information.giftTypeAsTaker)
+        {
+            return false;
+        }
+
+        const result = (giverWichtel.information.giftTypeAsGiver != GiftType.All)
+            && (takerWichtel.information.giftTypeAsTaker != GiftType.All);
+
+        return result;
+    }
+
+    private isInternationalIncompatible (giverWichtel: Member, takerWichtel: Member): boolean
+    {
+        if (giverWichtel.information.internationalAllowed)
+        {
+            return false;
+        }
+
+        if ((giverWichtel.information.giftTypeAsGiver != GiftType.Analogue)
+            && (takerWichtel.information.giftTypeAsTaker != GiftType.Analogue))
+        {
+            return false;
+        }
+
+        // NOTE: If one or both have "all" as gift type, they are compatible but should have a lower score if from different countries.
+
+        const result = (giverWichtel.information.country != takerWichtel.information.country);
+
+        return result;
+    }
+
+    private isExcluded (giverWichtel: Member, takerWichtel: Member, exclusions: Exclusion[]): boolean
+    {
+        for (const exclusion of exclusions)
+        {
+            if ((exclusion.giverId === giverWichtel.id) && (exclusion.takerId === takerWichtel.id))
+            {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    private calculateScore (giverWichtel: Member, takerWichtel: Member, exclusions: Exclusion[]): number
+    {
+        let score = 0;
+
+        if (giverWichtel.information.giftTypeAsGiver === takerWichtel.information.giftTypeAsTaker)
+        {
+            score += 4;
+        }
+
+        if (giverWichtel.information.country === takerWichtel.information.country)
+        {
+            score += 2;
+        }
+        else if (!giverWichtel.information.internationalAllowed)
+        {
+            if ((giverWichtel.information.giftTypeAsGiver == GiftType.All) && (takerWichtel.information.giftTypeAsTaker == GiftType.All))
+            {
+                // Both have "all" but the analogue way is blocked, thus a decrease:
+                score -= 3;
+            }
+        }
+
+        for (const exclusion of exclusions)
+        {
+            if ((exclusion.giverId === giverWichtel.id) && (exclusion.takerId === takerWichtel.id))
+            {
+                score -= 8;
+                break;
+            }
+        }
+
+        return score;
+    }
+
+    private sortCandidatesByScore (candidates: Candidate[]): void
+    {
+        candidates.sort(
+            (a, b) =>
+            {
+                return a.score - b.score;
+            }
+        );
+    }
+
+    private assignCandidates (pairings: Pairing[]): Assignment[]
+    {
+        const priorities: Set<Member> = new Set();
+
+        while (priorities.size < pairings.length)
+        {
+            const remaining = this.clonePairings(pairings);
+            this.sortPairings(remaining, priorities);
+
+            const completed: Assignment[] = [];
+
+            while (true)
+            {
+                const pairing = remaining.shift();
+
+                if (pairing === undefined)
+                {
+                    // All pairings have been assigned.
+                    break;
+                }
+
+                if (pairing.candidates.length == 0)
+                {
+                    // If there are no candidates left, we have an incomplete solution.
+                    // In this case, we add the member to the priority list and rerun the assignment.
+
+                    if (priorities.has(pairing.giver))
+                    {
+                        // If the member is already in the priority list, there is no possible solution with this algorithm:
+                        return [];
+                    }
+                    else
+                    {
+                        priorities.add(pairing.giver);
+                    }
+
+                    break;
+                }
+
+                const [firstCandidate] = pairing.candidates;
+
+                const assignment: Assignment = {
+                    giver: pairing.giver,
+                    taker: firstCandidate.taker,
+                };
+
+                completed.push(assignment);
+
+                for (const pairing of remaining)
+                {
+                    for (let i = pairing.candidates.length - 1; i >= 0; i--)
+                    {
+                        const candidate = pairing.candidates[i];
+
+                        // Remove the candidate from all other remaining pairings for it to not be assigned twice and
+                        // the giver from the taker's candidates to prevent a one-to-one circular assignment:
+                        if ((candidate.taker === assignment.taker)
+                            || ((pairing.giver === assignment.taker) && (candidate.taker === assignment.giver)))
+                        {
+                            pairing.candidates.splice(i, 1);
+                        }
+                    }
+                }
+
+                // Sort again because the assignment changed all pairing scores:
+                this.sortPairings(remaining, priorities);
+            }
+
+            if (completed.length === pairings.length)
+            {
+                // Found a solution!
+
+                return completed;
+            }
+        }
+
+        // If all members are prioritised, there could no possible solution be found:
+        return [];
+    }
+
+    private clonePairings (pairings: Pairing[]): Pairing[]
+    {
+        const clones: Pairing[] = [];
+
+        for (const pairing of pairings)
+        {
+            const clone: Pairing = {
+                giver: pairing.giver,
+                candidates: [...pairing.candidates],
+            };
+
+            clones.push(clone);
+        }
+
+        return clones;
+    }
+
+    private sortPairings (pairing: Pairing[], priorities: Set<Member>): void
+    {
+        pairing.sort(
+            (pairingA: Pairing, pairingB: Pairing): number =>
+            {
+                const aIsPriority = priorities.has(pairingA.giver);
+                const bIsPriority = priorities.has(pairingB.giver);
+
+                if (aIsPriority && !bIsPriority)
+                {
+                    return -1;
+                }
+                else if (bIsPriority && !aIsPriority)
+                {
+                    return 1;
+                }
+                // If both are prioritised, sort by score as if both were not.
+
+                // The value of the chain is determined by the accumulated weighting of all the wichtel plus their total number.
+                // The higher the value, the less important the user is for the selection.
+                // This sets a balance between "find the most valuable combinations", "make sure everyone is served" and "try to avoid
+                // making too poor assignments".
+                const accumulateScores = (totalScore: number, candidate: Candidate): number => totalScore + candidate.score;
+
+                const totalScoreA = pairingA.candidates.reduce(accumulateScores, 0) + pairingA.candidates.length;
+                const totalScoreB = pairingB.candidates.reduce(accumulateScores, 0) + pairingB.candidates.length;
+
+                let result = totalScoreA - totalScoreB;
+
+                // If both scores are the same, the number of candidates is used as a tie breaker:
+                if (result === 0)
+                {
+                    result = pairingA.candidates.length - pairingB.candidates.length;
+                }
+
+                return result;
+            }
+        );
+    }
+}
diff --git a/scripts/wichtelbot/message/modules/informationModule.ts b/scripts/wichtelbot/message/modules/informationModule.ts
index 6f5ac22f14a6de09d6fdc12ff71f7b343023c542..73770c1e3884c10ba31a45e93fcc1ebdf5ef1282 100644
--- a/scripts/wichtelbot/message/modules/informationModule.ts
+++ b/scripts/wichtelbot/message/modules/informationModule.ts
@@ -1,8 +1,8 @@
-import { Visualisation, VisualisationType } from "../../endpoint/definitions";
 import Config from "../../../utility/config";
 import ContactType from "../../types/contactType";
 import Database from "../../database/database";
 import GiftType from "../../types/giftType";
+import { HandlingUtils } from "../handlingTools/handlingUtils";
 import { KeyValuePairList } from "../../../utility/keyValuePair";
 import Localisation from "../../../utility/localisation";
 import Member from "../../classes/member";
@@ -313,93 +313,7 @@ export default class InformationModule
 
         // Send an overview of all information gathered in the form of the own profile:
         const profileOverviewText = Localisation.texts.registrationProfileOverview.process(member);
-        const profileVisualisations = this.getProfileVisualisations(member);
+        const profileVisualisations = HandlingUtils.getProfileVisualisations(member);
         await message.reply(profileOverviewText, profileVisualisations);
     }
-
-    public getProfileVisualisations (member: Member): Visualisation[]
-    {
-        const visualisations: Visualisation[] = [
-            {
-                headline: Localisation.texts.profileName.process(member),
-                text: member.name,
-                type: VisualisationType.Compact
-            },
-            {
-                headline: Localisation.texts.profileGiftType.process(member),
-                text: Localisation.translateGiftType(member.information.giftTypeAsTaker),
-                type: VisualisationType.Compact
-            }
-        ];
-
-        const sendAnalogue = member.information.giftTypeAsTaker == GiftType.Analogue || member.information.giftTypeAsTaker == GiftType.All;
-        const sendDigital = member.information.giftTypeAsGiver == GiftType.Digital || member.information.giftTypeAsGiver == GiftType.All;
-
-        if (sendAnalogue)
-        {
-            visualisations.push(
-                {
-                    headline: Localisation.texts.profileCounty.process(member),
-                    text: Localisation.translateCountry(member.information.country),
-                    type: VisualisationType.Compact
-                }
-            );
-
-            visualisations.push(
-                {
-                    headline: Localisation.texts.profileAddress.process(member),
-                    text: member.information.address,
-                    type: VisualisationType.Compact
-                }
-            );
-        }
-
-        if (sendDigital)
-        {
-            visualisations.push(
-                {
-                    headline: Localisation.texts.profileDigitalAddress.process(member),
-                    text: member.information.digitalAddress,
-                    type: VisualisationType.Compact
-                }
-            );
-        }
-
-        visualisations.push(
-            {
-                headline: Localisation.texts.profileWishlist.process(member),
-                text: member.information.wishList,
-                type: VisualisationType.Normal
-            }
-        );
-
-        if (sendAnalogue)
-        {
-            visualisations.push(
-                {
-                    headline: Localisation.texts.profileAllergies.process(member),
-                    text: member.information.allergies,
-                    type: VisualisationType.Normal
-                }
-            );
-        }
-
-        visualisations.push(
-            {
-                headline: Localisation.texts.profileExclusion.process(member),
-                text: member.information.giftExclusion,
-                type: VisualisationType.Normal
-            }
-        );
-
-        visualisations.push(
-            {
-                headline: Localisation.texts.profileFreeText.process(member),
-                text: member.information.freeText,
-                type: VisualisationType.Normal
-            }
-        );
-
-        return visualisations;
-    }
 }
diff --git a/scripts/wichtelbot/message/modules/moderationModule.ts b/scripts/wichtelbot/message/modules/moderationModule.ts
index bd0367f1a19c9a898dfc0b556f11e2ecd0612dc1..ef3d32f54c0144902b8ec6dd906586b0737ed116 100644
--- a/scripts/wichtelbot/message/modules/moderationModule.ts
+++ b/scripts/wichtelbot/message/modules/moderationModule.ts
@@ -1,8 +1,9 @@
+import { Message, State } from "../../endpoint/definitions";
 import Config from "../../../utility/config";
 import Database from "../../database/database";
+import { HandlingUtils } from "../handlingTools/handlingUtils";
 import { KeyValuePairList } from "../../../utility/keyValuePair";
 import Localisation from "../../../utility/localisation";
-import { Message } from "../../endpoint/definitions";
 import Utils from "../../../utility/utils";
 import WichtelEventPhase from "../../../utility/wichtelEvent";
 
@@ -18,6 +19,7 @@ export class ModerationModule
     public async sendStatus (message: Message): Promise<void>
     {
         // TODO: This could be improved with visualisations.
+        // TODO: What about mods?
 
         const currentEventPhase = Localisation.translateWichtelEventPhase(Config.currentEventPhase);
 
@@ -90,4 +92,52 @@ export class ModerationModule
 
         await message.reply(answer);
     }
+
+    /**
+     * End the registration phase and give all members that have completed the registration the "assignment" status.
+     */
+    public async endRegistration (message: Message): Promise<void>
+    {
+        const members = this.database.getMembersByState(State.Waiting);
+
+        for (const member of members)
+        {
+            member.state = State.Assignment;
+        }
+
+        // NOTE: We can use "updateContacts" instead of "updateMembers" because we changed the state, which is only part of the contact:
+        this.database.updateContacts(members);
+
+        const parameters = new KeyValuePairList('waitingMemberCount', `${members.length}`);
+        const answer = Localisation.texts.moderationRegistrationEnded.process(message.author, parameters);
+
+        await message.reply(answer);
+    }
+
+    public async distributeWichtelProfiles (message: Message): Promise<void>
+    {
+        // TODO: This is relatively slow. Could it be sped up? Getting the contacts/members could be cached or maybe the profile is slow?
+
+        const relationships = this.database.getRelationships();
+
+        for (const relationship of relationships)
+        {
+            const giver = this.database.getContact(relationship.giverId);
+            const taker = this.database.getMember(relationship.takerId);
+
+            const profileOverviewText = Localisation.texts.wichtelProfileDistribution.process(giver);
+            const profileVisualisations = HandlingUtils.getProfileVisualisations(taker);
+
+            const giverUser = await message.client.fetchUser(giver.id);
+            await giverUser.send(profileOverviewText, profileVisualisations);
+
+            giver.state = State.Wichteling;
+            this.database.updateContact(giver);
+        }
+
+        const parameters = new KeyValuePairList('profileCount', `${relationships.length}`);
+        const answer = Localisation.texts.moderationProfilesDistributed.process(message.author, parameters);
+
+        await message.reply(answer);
+    }
 }
diff --git a/scripts/wichtelbot/types/exclusionReason.ts b/scripts/wichtelbot/types/exclusionReason.ts
new file mode 100644
index 0000000000000000000000000000000000000000..331520c5df7503006ef4077f9d655b34679a82d6
--- /dev/null
+++ b/scripts/wichtelbot/types/exclusionReason.ts
@@ -0,0 +1,5 @@
+export enum ExclusionReason
+{
+    WichtelFromThePast = 'wichtelFromThePast',
+    Wish = 'wish',
+}
diff --git a/tests/performance/assignment.ts b/tests/performance/assignment.ts
new file mode 100644
index 0000000000000000000000000000000000000000..56653273fbcebced2974b3a12eede552e47e4145
--- /dev/null
+++ b/tests/performance/assignment.ts
@@ -0,0 +1,66 @@
+import 'mocha';
+import { AssignmentModule } from '../../scripts/wichtelbot/message/modules/assignmentModule';
+import ContactTestUtility from '../utility/contact';
+import Database from '../../scripts/wichtelbot/database/database';
+import Member from '../../scripts/wichtelbot/classes/member';
+
+const testCount = 1000;
+
+function performanceTest (this: Mocha.Suite): void
+{
+    let database: Database;
+    let assignmentModule: AssignmentModule;
+
+    const members: Member[] = [];
+
+    // We do not want a timeout in a performance test:
+    this.timeout(0);
+
+    before(
+        function ()
+        {
+            for (let i = 0; i < testCount; i++)
+            {
+                const member = ContactTestUtility.createRandomMemberWithMostCompatibleInformation();
+
+                members.push(member);
+            }
+        }
+    );
+
+    beforeEach(
+        function ()
+        {
+            // Initialise dependencies:
+            database = new Database('mainTest', 'logTest', true);
+            assignmentModule = new AssignmentModule(database);
+
+            for (const member of members)
+            {
+                database.saveContact(member);
+                database.saveMember(member);
+            }
+        }
+    );
+
+    afterEach(
+        function ()
+        {
+            database.close();
+        }
+    );
+
+    it('members.',
+        function ()
+        {
+            assignmentModule.runAssignment();
+        }
+    );
+}
+
+describe.skip('Performance',
+    function ()
+    {
+        describe('of assignment for ' + testCount.toString(), performanceTest);
+    }
+);
diff --git a/tests/tests/wichtelbot.message.module.assignment.ts b/tests/tests/wichtelbot.message.module.assignment.ts
new file mode 100644
index 0000000000000000000000000000000000000000..20acf74f5b216db427731e6a286bd899f7cac1b5
--- /dev/null
+++ b/tests/tests/wichtelbot.message.module.assignment.ts
@@ -0,0 +1,288 @@
+import 'mocha';
+import { assert } from 'chai';
+import { AssignmentModule } from '../../scripts/wichtelbot/message/modules/assignmentModule';
+import ContactTestUtility from '../utility/contact';
+import Database from '../../scripts/wichtelbot/database/database';
+import { ExclusionData } from '../../scripts/wichtelbot/classes/exclusion';
+import { ExclusionReason } from '../../scripts/wichtelbot/types/exclusionReason';
+import GiftType from '../../scripts/wichtelbot/types/giftType';
+import Member from '../../scripts/wichtelbot/classes/member';
+import { RelationshipTestUtility } from '../utility/relationship';
+
+describe('assignment module',
+    function ()
+    {
+        let database: Database;
+        let assignmentModule: AssignmentModule;
+
+        beforeEach(
+            function ()
+            {
+                // Initialise dependencies:
+                database = new Database('mainTest', 'logTest', true);
+                assignmentModule = new AssignmentModule(database);
+            }
+        );
+
+        afterEach(
+            function ()
+            {
+                database.close();
+            }
+        );
+
+        it('can assign three simple members.',
+            function ()
+            {
+                const newMembers: Member[] = [];
+
+                for (let i = 0; i < 3; i++)
+                {
+                    const newMember = ContactTestUtility.createRandomMemberWithMostCompatibleInformation();
+
+                    newMembers.push(newMember);
+                    database.saveContact(newMember);
+                    database.saveMember(newMember);
+                }
+
+                const successful = assignmentModule.runAssignment();
+
+                assert.isTrue(successful);
+
+                const relationships = database.getRelationships();
+
+                assert.equal(relationships.length, 3);
+
+                RelationshipTestUtility.assertValidity(relationships, newMembers);
+            }
+        );
+
+        it('can assign three members all with analogue gift types',
+            function ()
+            {
+                const newMembers: Member[] = [];
+
+                for (let i = 0; i < 3; i++)
+                {
+                    const newMember = ContactTestUtility.createRandomMemberWithMostCompatibleInformation();
+                    newMember.information.giftTypeAsGiver = GiftType.Analogue;
+                    newMember.information.giftTypeAsTaker = GiftType.Analogue;
+
+                    newMembers.push(newMember);
+                    database.saveContact(newMember);
+                    database.saveMember(newMember);
+                }
+
+                const successful = assignmentModule.runAssignment();
+
+                assert.isTrue(successful);
+
+                const relationships = database.getRelationships();
+
+                assert.equal(relationships.length, 3);
+
+                RelationshipTestUtility.assertValidity(relationships, newMembers);
+            }
+        );
+
+        it('can assign three members all with digital gift types',
+            function ()
+            {
+                const newMembers: Member[] = [];
+
+                for (let i = 0; i < 3; i++)
+                {
+                    const newMember = ContactTestUtility.createRandomMemberWithMostCompatibleInformation();
+                    newMember.information.giftTypeAsGiver = GiftType.Digital;
+                    newMember.information.giftTypeAsTaker = GiftType.Digital;
+
+                    newMembers.push(newMember);
+                    database.saveContact(newMember);
+                    database.saveMember(newMember);
+                }
+
+                const successful = assignmentModule.runAssignment();
+
+                assert.isTrue(successful);
+
+                const relationships = database.getRelationships();
+
+                assert.equal(relationships.length, 3);
+
+                RelationshipTestUtility.assertValidity(relationships, newMembers);
+            }
+        );
+
+        it('can assign members, one with analogue, one with digital und two with all gift type',
+            function ()
+            {
+                const newMembers: Member[] = [];
+
+                for (let i = 0; i < 4; i++)
+                {
+                    const newMember = ContactTestUtility.createRandomMemberWithMostCompatibleInformation();
+
+                    newMembers.push(newMember);
+                }
+
+                newMembers[0].information.giftTypeAsGiver = GiftType.Analogue;
+                newMembers[0].information.giftTypeAsTaker = GiftType.Analogue;
+                newMembers[0].id = 'Analogue';
+                newMembers[1].information.giftTypeAsGiver = GiftType.Digital;
+                newMembers[1].information.giftTypeAsTaker = GiftType.Digital;
+                newMembers[1].id = 'Digital';
+                newMembers[2].information.giftTypeAsGiver = GiftType.All;
+                newMembers[2].information.giftTypeAsTaker = GiftType.All;
+                newMembers[2].id = 'All 1';
+                newMembers[3].information.giftTypeAsGiver = GiftType.All;
+                newMembers[3].information.giftTypeAsTaker = GiftType.All;
+                newMembers[3].id = 'All 2';
+
+                for (const newMember of newMembers)
+                {
+                    newMember.information.contactId = newMember.id;
+
+                    database.saveContact(newMember);
+                    database.saveMember(newMember);
+                }
+
+                const successful = assignmentModule.runAssignment();
+
+                assert.isTrue(successful);
+
+                const relationships = database.getRelationships();
+
+                assert.equal(relationships.length, 4);
+
+                RelationshipTestUtility.assertValidity(relationships, newMembers);
+                RelationshipTestUtility.assertCompatibility(relationships, newMembers);
+            }
+        );
+
+        it('can assign members with exclusions',
+            function ()
+            {
+                const newMembers: Member[] = [];
+
+                for (let i = 0; i < 4; i++)
+                {
+                    const newMember = ContactTestUtility.createRandomMemberWithMostCompatibleInformation();
+
+                    newMembers.push(newMember);
+                    database.saveContact(newMember);
+                    database.saveMember(newMember);
+                }
+
+                const exclusions: ExclusionData[] = [
+                    {
+                        giverId: newMembers[0].id,
+                        takerId: newMembers[1].id,
+                        reason: ExclusionReason.Wish,
+                    },
+                    {
+                        giverId: newMembers[0].id,
+                        takerId: newMembers[2].id,
+                        reason: ExclusionReason.Wish,
+                    },
+                    {
+                        giverId: newMembers[1].id,
+                        takerId: newMembers[2].id,
+                        reason: ExclusionReason.Wish,
+                    },
+                    {
+                        giverId: newMembers[1].id,
+                        takerId: newMembers[3].id,
+                        reason: ExclusionReason.Wish,
+                    },
+                ];
+
+                database.saveUserExclusions(exclusions);
+
+                const successful = assignmentModule.runAssignment();
+
+                assert.isTrue(successful);
+
+                const relationships = database.getRelationships();
+
+                assert.equal(relationships.length, 4);
+
+                RelationshipTestUtility.assertValidity(relationships, newMembers);
+                RelationshipTestUtility.assertCompatibility(relationships, newMembers, exclusions);
+            }
+        );
+
+        it('cannot assign with zero members',
+            function ()
+            {
+                const successful = assignmentModule.runAssignment();
+
+                assert.isFalse(successful);
+            }
+        );
+
+        it('cannot assign with one member',
+            function ()
+            {
+                const newMember = ContactTestUtility.createRandomMemberWithMostCompatibleInformation();
+
+                database.saveContact(newMember);
+                database.saveMember(newMember);
+
+                const successful = assignmentModule.runAssignment();
+
+                assert.isFalse(successful);
+            }
+        );
+
+        it('cannot assign with two members',
+            function ()
+            {
+                const newMembers: Member[] = [];
+
+                for (let i = 0; i < 2; i++)
+                {
+                    const newMember = ContactTestUtility.createRandomMemberWithMostCompatibleInformation();
+
+                    newMembers.push(newMember);
+                    database.saveContact(newMember);
+                    database.saveMember(newMember);
+                }
+
+                const successful = assignmentModule.runAssignment();
+
+                assert.isFalse(successful);
+            }
+        );
+
+        it('cannot assign members, two with analogue and one with digital gift type',
+            function ()
+            {
+                const newMembers: Member[] = [];
+
+                for (let i = 0; i < 3; i++)
+                {
+                    const newMember = ContactTestUtility.createRandomMemberWithMostCompatibleInformation();
+
+                    newMembers.push(newMember);
+                    database.saveContact(newMember);
+                }
+
+                newMembers[0].information.giftTypeAsGiver = GiftType.Analogue;
+                newMembers[0].information.giftTypeAsTaker = GiftType.Analogue;
+                newMembers[1].information.giftTypeAsGiver = GiftType.Analogue;
+                newMembers[1].information.giftTypeAsTaker = GiftType.Analogue;
+                newMembers[2].information.giftTypeAsGiver = GiftType.Digital;
+                newMembers[2].information.giftTypeAsTaker = GiftType.Digital;
+
+                for (const newMember of newMembers)
+                {
+                    database.saveMember(newMember);
+                }
+
+                const successful = assignmentModule.runAssignment();
+
+                assert.isFalse(successful);
+            }
+        );
+    }
+);
diff --git a/tests/utility/contact.ts b/tests/utility/contact.ts
index 3eed18110333a2594864fe42d3d543feebbabba5..6227b321c673dead079adfeb4d64179c9412666c 100644
--- a/tests/utility/contact.ts
+++ b/tests/utility/contact.ts
@@ -101,11 +101,23 @@ export default abstract class ContactTestUtility
     public static createRandomMember (): Member
     {
         const contact = ContactTestUtility.createRandomContact();
-        const information = ContactTestUtility.createRandomMemberInformation();
+        const information = ContactTestUtility.createRandomMemberInformation(contact.id);
 
         const member = new Member(contact, information);
         member.type = ContactType.Member;
 
         return member;
     }
+
+    public static createRandomMemberWithMostCompatibleInformation (): Member
+    {
+        const member = ContactTestUtility.createRandomMember();
+        member.state = State.Assignment;
+        member.information.country = 'deutschland';
+        member.information.giftTypeAsGiver = GiftType.All;
+        member.information.giftTypeAsTaker = GiftType.All;
+        member.information.internationalAllowed = true;
+
+        return member;
+    }
 }
diff --git a/tests/utility/relationship.ts b/tests/utility/relationship.ts
new file mode 100644
index 0000000000000000000000000000000000000000..68e6a34d0e9a5fe668dedf75ead643ef7a564fb5
--- /dev/null
+++ b/tests/utility/relationship.ts
@@ -0,0 +1,98 @@
+import { assert } from "chai";
+import { ExclusionData } from "../../scripts/wichtelbot/classes/exclusion";
+import { ExclusionReason } from "../../scripts/wichtelbot/types/exclusionReason";
+import GiftType from "../../scripts/wichtelbot/types/giftType";
+import Member from "../../scripts/wichtelbot/classes/member";
+import { Relationship } from "../../scripts/wichtelbot/classes/relationship";
+
+export abstract class RelationshipTestUtility
+{
+    /**
+     * Validate the relationship by checking that for every member exactly one relationship with him as giver and one as taker exists.
+     */
+    public static assertValidity (relationships: Relationship[], members: Member[]): void
+    {
+        for (const member of members)
+        {
+            let foundGiver = false;
+            let foundTaker = false;
+
+            for (const relationship of relationships)
+            {
+                assert.notEqual(relationship.giverId, relationship.takerId, 'Validity check, giver and taker are identical');
+
+                if (relationship.giverId === member.id)
+                {
+                    assert.isFalse(foundGiver, 'Validity check, giver appeared twice');
+                    foundGiver = true;
+                }
+                else if (relationship.takerId === member.id)
+                {
+                    assert.isFalse(foundTaker, 'Validity check, taker appeared twice');
+                    foundTaker = true;
+                }
+            }
+
+            assert.isTrue(foundGiver, 'Validity check, one giver is missing');
+            assert.isTrue(foundTaker, 'Validity check, one taker is missing');
+        }
+    }
+
+    /**
+     * Proofs the compatibility of every relationship.
+    */
+    public static assertCompatibility (relationships: Relationship[], members: Member[], exclusions: ExclusionData[] = []): void
+    {
+        for (const relationship of relationships)
+        {
+            let giver: Member|null = null;
+            let taker: Member|null = null;
+
+            for (const member of members)
+            {
+                if (member.id === relationship.giverId)
+                {
+                    giver = member;
+                }
+                else if (member.id === relationship.takerId)
+                {
+                    taker = member;
+                }
+            }
+
+            if (giver === null || taker === null)
+            {
+                // NOTE: One should think that an assert would assert a type, but it doesn't...
+
+                assert.isNotNull(giver);
+                assert.isNotNull(taker);
+
+                continue;
+            }
+
+            const giftTypesAreTheSame = giver.information.giftTypeAsGiver === taker.information.giftTypeAsTaker;
+            const giverHasAllGiftType = giver.information.giftTypeAsGiver === GiftType.All;
+            const takerHasAllGiftType = taker.information.giftTypeAsTaker === GiftType.All;
+            const isGiftTypeCompatible = giftTypesAreTheSame || giverHasAllGiftType || takerHasAllGiftType;
+
+            assert.isTrue(isGiftTypeCompatible, 'Gift type compatibility check failed');
+
+            const countriesAreTheSame = giver.information.country === taker.information.country;
+            const noneHasAnalogueGiftType = giver.information.giftTypeAsGiver === GiftType.Analogue
+                || taker.information.giftTypeAsTaker === GiftType.Analogue;
+            const isInternationalCompatible = countriesAreTheSame || noneHasAnalogueGiftType || giver.information.internationalAllowed;
+
+            assert.isTrue(isInternationalCompatible, 'International compatibility check failed');
+
+            for (const exclusion of exclusions)
+            {
+                if (exclusion.reason == ExclusionReason.Wish)
+                {
+                    const isNoExclusion = exclusion.giverId !== giver.id || exclusion.takerId !== taker.id;
+
+                    assert.isTrue(isNoExclusion, 'Exclusion compatibility check failed');
+                }
+            }
+        }
+    }
+}