Greasemonkey: Kleines Projekt: Wer wann online zuletzt?

Hallo, ich hab ein kurzes Script geschrieben, um zu sehen, Wer wann online zuletzt? Es funktioniert so, man öffnet ein Topic und scrollt erst mal nach unten und liest sich alles durch. Dann drückt man die o-Taste (kleines o, nicht Null). Es öffnet sich dann ein Alert mit folgenden Inhalt (Beispiel):

CyborgBeta	: 2017-06-10T07:58:12.764Z
Landei		: 2017-06-10T08:53:48.622Z
inv_zim		: 2017-06-09T20:53:16.863Z
ionutbaiu	: 2017-06-09T16:23:45.710Z
mdickie		: 2017-06-10T09:33:45.124Z
// siehe bitte Version 3

Hoffentlich ist das nützlich. :slight_smile:

Hallo, hier ist Version 2 des Scripts, und Kommentare:

// siehe bitte Version 3
Sun Jun 11 2017 00:45:02 GMT+0200	CyborgBeta
Sun Jun 11 2017 00:07:29 GMT+0200	Landei
Sat Jun 10 2017 23:47:56 GMT+0200	ionutbaiu
Sat Jun 10 2017 20:14:33 GMT+0200	mdickie
Sat Jun 10 2017 15:23:55 GMT+0200	inv_zim

Wie man sieht, ich war zuletzt online (zumindest bei den Teilnehmern eines Topics).

###Nachteil:
Wenn man einen Beitrag schreibt, sollte man das Script/den Greasemonkey wieder deaktivieren - ansonsten unerwünschtes Verhalten.

Könnt ihr mir bitte ein kurzes Feedback geben? Das wäre toll.

###Edit:

Oh sorry, mir ist gerade etwas aufgefallen. Also erstmal sollte die Taste vielleicht F12 sein, das ist der keycode 123, wenn ihr diese Taste noch nicht belegt habt.

Dann hab ich es immer nur getestet, wenn ich nicht angemeldet gewesen bin… Es gibt aber, wenn man angemeldet ist, z. B. den Link: https://forum.byte-welt.net/u/cyborgbeta/messages daraus würd er machen: https://forum.byte-welt.net/u/cyborgbeta/messages.json das gibt’s natürlich nicht, und er antwortet mit 500… Sorry, daran hatte ich nicht gedacht. Wenn gewünscht, abändere ich das mal.

Hier mit funktioniert’s ( f12, funktioniert nur 1x ):

// ==UserScript==
// @name        WerOnline
// @namespace   WerOnlineRaum
// @description Wer wann online?
// @include     https://forum.byte-welt.net/*
// @version     3
// @grant       none
// ==/UserScript==
window.addEventListener('keyup', function(e) {
    var key = e.keyCode ? e.keyCode : e.which;
    if (myBool && key === 123) {
        myBool = false;
        console.log('o pressed');

        // get all links
        var arr = [],
            l = document.links;
        for (var i = 0; i < l.length; i++) {
            arr.push(l**.href);
        }

        // get all user links and add '.json' ...
        var dic1 = {};
        for (var i = 0; i < arr.length; i++) {
            if (arr**.startsWith('https://forum.byte-welt.net/u/') && arr**.lastIndexOf('/') === 29) {
                dic1[arr**.substring(arr**.lastIndexOf('/') + 1)] = arr** + '.json';
            }
        }

        var dic2 = {};
        for (var prop in dic1) {
            // get user info
            var obj = httpGetJSON(dic1[prop]);
            // get all Dates
            dArr = [];
            getDates(obj);
            // sort the Dates
            dArr.sort();
            // put the newest Date in dictionary
            dic2[prop] = dArr[dArr.length - 1];
        }

        // sort the dictionary
        // Create items array
        var items = Object.keys(dic2).map(function(key) {
            return [key, dic2[key]];
        });
        // Sort the array based on the second element
        items.sort(function(first, second) {
            return second[1] - first[1];
        });

        // create an alert with items
        var str = '';
        items.forEach(function(item) {
            str += item[1] + '\t' + item[0] + '\n';
        });
        alert(str);
    }
}, true);
var myBool = true;

function httpGetJSON(theUrl) {
    var xmlHttp = new XMLHttpRequest();
    xmlHttp.open('GET', theUrl, false); // false for synchronous request
    xmlHttp.send(null);
    return JSON.parse(xmlHttp.responseText);
}
var dArr = [];

function getDates(obj) {
    if (typeof obj === 'object') {
        for (var prop in obj) {
            if (prop === 'last_posted_at' || prop === 'last_seen_at') {
                dArr.push(new Date(obj[prop]));
            }
            getDates(obj[prop]);
        }
    }
}

Etwa 25 bis 30 KB traffic bei einem durchschnittlichen Topic, das sollte verschmerzbar sein?

Hi,

damit du hier nicht eine Diskussion mit dir selbst führen musst: Hier mein Feedback:

  1. Der Sinn des Tools ergibt sich mir nicht.

Das ist erst mal eine Feststellung. Das muss nicht unbedingt negativ sein. Ich gehe davon aus,d ass hier Feedback zum Code selbst gewünscht ist.

Daher hier:

Die Unterteilung in Funktionen ist gut umgesetzt worden. Die Kommentare sind auch OK gewählt worden.

Miserabel ist die Benennung der Variablen.

Ich bin jetzt nicht so ein JS Guru, aber diese Zeile sieht komisch aus:

if (typeof obj === ‚object‘)

Wenn das nicht undefined ist, ist es dann nicht immer ein object?

Schöne Grüße

Martin

Danke für den Ratschlag! Ja, diese iterieren Methode ist etwas unschön, aber erfüllt den Zweck. Nur Objekte sind Objekte… alles andere, was in Java String, Number usw. wäre, ist kein Objekt. Und ein Objekt ist ein dict. in JS.

Sinn und Zweck: Wenn ich weiß, wer wann zuletzt online war, kann ich vielleicht sehen, wer gerade online ist, und vielleicht ‘eine Antwort erstellt’.

Eigentlich kann man JS ohne StackOverflow eigentlich nicht richtig lernen, bekomme ich so vermittelt.

Aha? - Also objekte sind objekte und objekte sind dict.???

Aha? - Gibt es sonst keine Bücher zu dem Thema?

Kleine Verbesserung:

// ==UserScript==
// @name        WerOnline
// @namespace   WerOnlineRaum
// @description Wer wann online?
// @include     https://forum.byte-welt.net/*
// @version     4
// @grant       none
// ==/UserScript==
window.addEventListener('keyup', function(e) {
    var key = e.keyCode ? e.keyCode : e.which;
    if (notAlreadyPressed && key === 123) {
        notAlreadyPressed = false;
        console.log('f12 pressed');

        // get all links
        var linkArray = [],
            l = document.links;
        for (var i = 0; i < l.length; i++) {
            linkArray.push(l**.href);
        }

        // get all user links and add '.json' ...
        var usernameLink = {};
        for (var i = 0; i < linkArray.length; i++) {
            if (linkArray**.startsWith('https://forum.byte-welt.net/u/') && linkArray**.lastIndexOf('/') === 29) {
                usernameLink[linkArray**.substring(30).toLowerCase()] = linkArray** + '.json';
            }
        }

        var usernameDate = {};
        for (var prop in usernameLink) {
            // get user info
            var jsonObj = httpGetJSON(usernameLink[prop]);
            // get all Dates
            dateArray = [];
            getDates(jsonObj);
            // sort the Dates
            dateArray.sort();
            // put the newest Date in dictionary
            usernameDate[prop] = dateArray[dateArray.length - 1];
        }

        // sort the dictionary
        // Create items array
        var usernameDateArray = Object.keys(usernameDate).map(function(key) {
            return [key, usernameDate[key]];
        });
        // Sort the array based on the second element
        usernameDateArray.sort(function(first, second) {
            return second[1] - first[1];
        });

        // create an alert with items
        var alertString = '';
        usernameDateArray.forEach(function(item) {
            alertString += item[1] + '\t' + item[0] + '\n';
        });
        alert(alertString);
    }
}, true);
var notAlreadyPressed = true;

function httpGetJSON(theUrl) {
    var xmlHttp = new XMLHttpRequest();
    xmlHttp.open('GET', theUrl, false); // false for synchronous request
    xmlHttp.send(null);
    return JSON.parse(xmlHttp.responseText);
}
var dateArray = [];

function getDates(obj) {
    if (typeof obj === 'object') {
        for (var prop in obj) {
            if (prop === 'last_posted_at' || prop === 'last_seen_at') {
                dateArray.push(new Date(obj[prop]));
            }
            getDates(obj[prop]);
        }
    }
}
    • 1x lastIndexOf
    • 1x Literal
    • toLowerCase (es gab Links mit klein geschriebenen Benutzernamen)
    • sprechende Var names
  1. Version 4

Danke wieder für Feedbacks

Mit return second[2] - first[2]; sieht es so aus: (Ganz im Sinne des Datenschutzes D: ):

02 21:59.16 --- 03 16:37.51 --- marcinek
03 15:14.08 --- 03 16:37.23 --- nicodeluxe
05 10:02.19 --- 03 16:26.52 --- vfl_freak
03 16:10.01 --- 03 16:26.28 --- landei
00 08:48.32 --- 03 16:16.55 --- oniken
05 14:37.55 --- 03 16:08.23 --- michimitsu
03 12:24.13 --- 03 16:08.09 --- clayn
00 21:09.18 --- 03 15:32.24 --- mdickie
03 12:35.40 --- 03 15:28.18 --- spacerat
03 15:08.55 --- 03 15:12.34 --- greta
01 09:32.29 --- 03 15:11.14 --- slaterb
03 14:20.12 --- 03 14:28.11 --- maki
03 09:33.56 --- 03 14:23.21 --- tomate_salat
03 21:59.49 --- 03 14:07.06 --- darekkay
03 13:06.02 --- 03 13:55.16 --- crian
03 20:50.24 --- 03 13:04.44 --- mrbrown
00 08:46.18 --- 03 12:52.51 --- amunra
03 12:25.07 --- 03 12:25.27 --- cyborgbeta
03 10:41.00 --- 03 10:41.00 --- l-ectron-x
03 00:02.55 --- 03 04:25.03 --- dow_jones
03 01:15.56 --- 03 01:31.19 --- marco13
03 20:21.52 --- 03 01:17.27 --- tmii
02 21:49.35 --- 03 00:13.28 --- ionutbaiu
05 09:54.46 --- 02 22:31.32 --- timbeau
00 18:49.10 --- 02 21:54.30 --- sym
05 10:26.14 --- 02 18:47.06 --- mogel
05 11:17.49 --- 02 17:48.44 --- apo
02 15:14.02 --- 02 15:13.55 --- marie.koenig
01 17:36.27 --- 01 17:35.37 --- javaman1991
05 12:52.38 --- 01 10:54.36 --- softg
01 20:16.45 --- 05 14:02.13 --- cmrudolph
06 16:07.51 --- 06 00:45.52 --- timothy_truckle
01 10:50.28 --- 05 09:26.26 --- kokscola
01 08:24.05 --- 01 08:24.43 --- eagleeye
03 08:00.56 --- 03 22:35.57 --- katharsas
02 17:00.00 --- 02 17:01.55 --- kimjob
04 12:32.19 --- 01 01:25.43 --- thomast
02 09:58.47 --- 00 15:03.03 --- noctarius
02 19:46.00 --- 00 07:25.53 --- beni
02 10:12.50 --- 06 19:10.57 --- neoexpert
05 20:34.19 --- 03 19:04.21 --- kbowman
04 17:08.02 --- 05 12:04.46 --- pandaliebe
03 03:08.22 --- 04 07:20.42 --- scott
03 21:00.32 --- 03 20:16.44 --- butalive
00 10:59.07 --- 01 18:03.18 --- firephoenix
01 05:45.17 --- 01 09:26.21 --- eingang
00 19:04.52 --- 04 18:27.26 --- robertvox1977
01 07:38.21 --- 01 22:02.26 --- terston
06 12:41.57 --- 06 23:25.31 --- adam.law
04 13:42.22 --- 05 00:06.29 --- shanair
04 13:26.57 --- 04 14:04.25 --- kanexo
04 21:25.58 --- 00 19:04.00 --- inv_zim
02 10:11.57 --- 02 10:13.01 --- herrmann
01 22:48.29 --- 03 20:50.43 --- peterk
04 15:09.19 --- 05 14:45.59 --- jackperalta
03 15:11.20 --- 06 18:28.54 --- cronut
06 18:12.31 --- 01 05:48.08 --- bleiglanz
04 01:00.00 --- 01 19:46.15 --- pl4gu33
00 05:36.10 --- 06 20:22.46 --- system
04 01:00.00 --- 03 17:00.00 --- klemensyxyz
04 01:00.00 --- 03 17:00.00 --- mla.rue
04 01:00.00 --- 03 17:00.00 --- thedarkrose
04 01:00.00 --- 03 17:00.00 --- bygones
04 01:00.00 --- 03 17:00.00 --- jango
04 01:00.00 --- 03 17:00.00 --- conny
04 01:00.00 --- 03 17:00.00 --- akeshihiro
04 01:00.00 --- 03 17:00.00 --- natac
04 01:00.00 --- 03 17:00.00 --- swerflash 

Was soll das eigentlich?

  • Man sieht schnell, wer länger nicht mehr online war z. B. cmrudolph
  • wer möglicherw. antworten wird z. B. marcinek (eine Wahrscheinlichkeit dafür wäre cool :smiley: viell baue ich das noch ein :wink: )
  • wer das Forum komplett liegen gelassen hat :frowning: z. B. klemensyxyz oder mla.rue

Neue Version:

// ==UserScript==
// @name        WerOnline
// @namespace   WerOnlineRaum
// @description Wer wann online?
// @include     https://forum.byte-welt.net/*
// @version     6
// @grant       none
// ==/UserScript==
window.addEventListener('keyup', function (e) {
    var key = e.keyCode ? e.keyCode : e.which;
    if (notAlreadyPressed && key === 123) {
        notAlreadyPressed = false;
        console.log('f12 keyup');

        setTimeout(getUsernameDates, 0);
    }
}, true);

var notAlreadyPressed = true;
var usernameDatesObj = {};
var usernameDatesSortable = [];

function getUsernameDates() {
    // get all links
    var linkArray = [],
        l = document.links;
    for (var i = 0; i < l.length; i++) {
        linkArray.push(l**.href);
    }

    // get all user links and add '.json'...
    var usernameLinkObj = {};
    for (var i = 0; i < linkArray.length; i++) {
        if (linkArray**.startsWith('https://forum.byte-welt.net/u/') && linkArray**.lastIndexOf('/') === 29) {
            usernameLinkObj[linkArray**.substring(30).toLowerCase()] = linkArray** + '.json';
        }
    }

    for (var username in usernameLinkObj) {
        // get the user info
        var jsonObj = httpGetJSON(usernameLinkObj[username]);
        // get all Dates
        getDates(username, jsonObj);
    }

    for (var username in usernameDatesObj) {
        usernameDatesSortable.push([username].concat(usernameDatesObj[username])); // ich kann Marco schon hören^^
    }
    usernameDatesSortable.sort(function (first, second) {
        return second[2] - first[2];
    });

    alertUsernameDates();
}

function alertUsernameDates() {
    // create an alert with usernameDates
    var ul = document.createElement('UL');
    var alertString = '';
    usernameDatesSortable.forEach(function (usernameDates) {
        var fp = formatPretty(usernameDates);
        var li = document.createElement('LI'), tn = document.createTextNode(fp);
        li.appendChild(tn);
        ul.appendChild(li);
        alertString += fp;
    });
    document.body.appendChild(ul);
    alert(alertString);
}

function httpGetJSON(theUrl) {
    var xmlHttp = new XMLHttpRequest();
    xmlHttp.open('GET', theUrl, false); // false for synchronous request
    xmlHttp.send(null);
    return JSON.parse(xmlHttp.responseText);
}

function getDates(username, dates) {
    if (typeof dates === 'object') {
        for (var prop in dates) {
            if (prop === 'last_posted_at' || prop === 'last_seen_at') {
                if (!usernameDatesObj[username]) {
                    usernameDatesObj[username] = [];
                }
                usernameDatesObj[username].push(new Date(dates[prop]));
            }
        }
        for (var prop in dates) {
            getDates(username, dates[prop]);
        }
    }
}

function formatPretty(usernameDates) {
    return fd(usernameDates[1]) + ' --- ' + fd(usernameDates[2]) + ' --- ' + usernameDates[0] + '\n';
}

function fd(date) {
    var d = new Date(date);
    return ml(1 + d.getDay()) + ' ' + ml(d.getHours()) + ':' + ml(d.getMinutes()) + '.' + ml(d.getSeconds());
}

function ml(value) {
    var s = '' + value;
    while (s.length < 2) {
        s = '0' + s;
    }
    return s;
}

Oha, vielleicht hat sich schon jemand gewundert, dass mit dem Datum etwas nicht stimmen kann. Dieser Monat fing mit einem Sonntag an.

1 + d.getDay() liefert den Wochentag zurück, korrekt wäre:

getDate() Returns the day of the month (from 1-31)

an dieser Stelle. Also, falls das Datum anstatt der Wochentag gewünscht ist, müsstet ihr das an der Stelle ändern.


… Schade, dass niemand auch nur die kleinste Rückmeldung geben möchte…

Nutzt das noch jemand? Es klappt jetzt wahrscheinlich nicht mehr, denn

  1. seltsamerweise gibt es 5x **, welche ich, imo, nicht eingefügt hatte,
  2. startsWith muss jetzt durch indexOf ... === 0 ersetzt werden,
  3. XMLHttpRequest ist jetzt wohl “harmful”…

Falls es jemand (noch) (womit?) nutzt, bitte hier melden, danke.

Eben mit Greasemonkey probiert, es funktioniert doch noch,

// ==UserScript==
// @name        WerOnline
// @namespace   WerOnlineRaum
// @description Wer wann online?
// @include     https://forum.byte-welt.net/*
// @version     7
// @grant       none
// ==/UserScript==
window.addEventListener('keyup', function (e) {
    var key = e.keyCode ? e.keyCode : e.which;
    if (notAlreadyPressed && key === 123) {
        notAlreadyPressed = false;
        console.log('f12 keyup');

        setTimeout(getUsernameDates, 0);
    }
}, true);

var notAlreadyPressed = true;
var usernameDatesObj = {};
var usernameDatesSortable = [];

function getUsernameDates() {
    // get all links
    var linkArray = [],
        l = document.links;
    for (var i = 0; i < l.length; i++) {
        linkArray.push(l[i].href);
    }

    // get all user links and add '.json'...
    var usernameLinkObj = {};
    for (var i = 0; i < linkArray.length; i++) {
        if (linkArray[i].indexOf('https://forum.byte-welt.net/u/') === 0 && linkArray[i].lastIndexOf('/') === 29) {
            usernameLinkObj[linkArray[i].substring(30).toLowerCase()] = linkArray[i] + '.json';
        }
    }

    for (var username in usernameLinkObj) {
        // get the user info
        var jsonObj = httpGetJSON(usernameLinkObj[username]);
        // get all Dates
        getDates(username, jsonObj);
    }

    for (var username in usernameDatesObj) {
        usernameDatesSortable.push([username].concat(usernameDatesObj[username])); // ich kann Marco schon hören^^
    }
    usernameDatesSortable.sort(function (first, second) {
        return second[2] - first[2];
    });

    alertUsernameDates();
}

function alertUsernameDates() {
    // create an alert with usernameDates
    var ul = document.createElement('UL');
    var alertString = '';
    usernameDatesSortable.forEach(function (usernameDates) {
        var fp = formatPretty(usernameDates);
        var li = document.createElement('LI'), tn = document.createTextNode(fp);
        li.appendChild(tn);
        ul.appendChild(li);
        alertString += fp;
    });
    document.body.appendChild(ul);
    alert(alertString);
}

function httpGetJSON(theUrl) {
    var xmlHttp = new XMLHttpRequest();
    xmlHttp.open('GET', theUrl, false); // false for synchronous request
    xmlHttp.send(null);
    return JSON.parse(xmlHttp.responseText);
}

function getDates(username, dates) {
    if (typeof dates === 'object') {
        for (var prop in dates) {
            if (prop === 'last_posted_at' || prop === 'last_seen_at') {
                if (!usernameDatesObj[username]) {
                    usernameDatesObj[username] = [];
                }
                usernameDatesObj[username].push(new Date(dates[prop]));
            }
        }
        for (var prop in dates) {
            getDates(username, dates[prop]);
        }
    }
}

function formatPretty(usernameDates) {
    return fd(usernameDates[1]) + ' - ' + fd(usernameDates[2]) + ' - ' + usernameDates[0] + '\n';
}

function fd(date) {
    var d = new Date(date);
    return ml(d.getDate()) + '. ' + ml(d.getHours()) + ':' + ml(d.getMinutes()) + '.' + ml(d.getSeconds());
}

function ml(value) {
    var s = '' + value;
    while (s.length < 2) {
        s = '0' + s;
    }
    return s;
}

grafik

viel Spaß

Ich hab das für Tampermonkey/google chrome angepasst:

// ==UserScript==
// @name         New Userscript BW
// @namespace    http://tampermonkey.net/
// @version      0.1
// @description  try to take over the world!
// @author       You
// @match        https://forum.byte-welt.net/*
// @grant        none
// @require      https://code.jquery.com/jquery-3.6.0.min.js
// ==/UserScript==

(function () {
    'use strict';
    let pressed = false;
    document.addEventListener('keyup', (e) => {
        let key = e.keyCode;
        if (!pressed && key === 56) {
            pressed = true;

            let u1 = [];
            let u2 = $("a[class='poster trigger-user-card']");
            u2.each(function (i) {
                u1.push($(this).attr("data-user-card"));
            });
            console.log(u1);

            let u3 = [];
            u1.forEach(u => {
                $.ajax({
                    dataType: "json",
                    url: "https://forum.byte-welt.net/u/" + u + ".json",
                    success: function (result) {
                        u3.push({
                            username: u,
                            last_seen_at: new Date(result.user.last_seen_at)
                        });
                    },
                    async: false
                });
            });
            console.log(u3);

            u3.sort((a, b) => {
                return b.last_seen_at.getTime() - a.last_seen_at.getTime();
            });
            createTable(u3);
        }
    });

    function createTable(tableData) {
        var table = document.createElement('table');
        var tableBody = document.createElement('tbody');

        tableData.forEach(function (rowData) {
            var row = document.createElement('tr');

            [rowData.username, rowData.last_seen_at].forEach(function (cellData) {
                var cell = document.createElement('td');
                cell.appendChild(document.createTextNode(cellData));
                row.appendChild(cell);
            });

            tableBody.appendChild(row);
        });

        table.appendChild(tableBody);
        document.body.prepend(table);
    }
})();

Ihr müsst einmal die Taste 8 drücken, dann erscheint oben Links eine kleine Tabelle.

Leider läuft aber irgendwann etwas nicht mehr ganz rund. :confused: Ich weiß noch nicht, woran das liegt.

Es gab Konflikte mit den jQuery Versionen. Discourse verwendet sein eigenes jQuery.

Bitte verwendet das:

// ==UserScript==
// @name         New Userscript BW
// @namespace    http://tampermonkey.net/
// @version      0.1
// @description  try to take over the world!
// @author       You
// @match        https://forum.byte-welt.net/*
// @grant        none
// ==/UserScript==

let pressed_8 = false;

$('body').ready(function () {
    document.addEventListener('keyup', (e) => {
        let key = e.keyCode;
        if (!pressed_8 && key === 56) {
            pressed_8 = true;

            let u1 = [];
            let u2 = $("a[class='poster trigger-user-card']");
            u2.each(function (i) {
                u1.push($(this).attr("data-user-card"));
            });
            console.log(u1);

            let u3 = [];
            u1.forEach(u => {
                $.ajax({
                    dataType: "json",
                    url: "/u/" + u + ".json",
                    success: function (result) {
                        u3.push({
                            username: u,
                            last_seen_at: new Date(result.user.last_seen_at)
                        });
                    },
                    async: false
                });
            });
            console.log(u3);

            u3.sort((a, b) => {
                return b.last_seen_at.getTime() - a.last_seen_at.getTime();
            });
            createTable(u3);
        }
    })
});

function createTable(tableData) {
    let table = document.createElement('table');
    let tableBody = document.createElement('tbody');

    tableData.forEach(function (rowData) {
        let row = document.createElement('tr');

        [rowData.username, rowData.last_seen_at].forEach(function (cellData) {
            let cell = document.createElement('td');
            cell.appendChild(document.createTextNode(cellData));
            row.appendChild(cell);
        });

        tableBody.appendChild(row);
    });

    table.appendChild(tableBody);
    document.body.prepend(table);
}

So sieht es noch schöner aus:

// ==UserScript==
// @name         New Userscript BW
// @namespace    http://tampermonkey.net/
// @version      0.1
// @description  try to take over the world!
// @author       You
// @match        https://forum.byte-welt.net/*
// @grant        none
// ==/UserScript==

let pressed_8 = false;

$(document.body).ready(function () {
    document.addEventListener('keyup', (e) => {
        let key = e.keyCode;
        if (!pressed_8 && key === 56) {
            pressed_8 = true;

            let u1 = [];
            let u2 = $("a[class='poster trigger-user-card']");
            u2.each(function (i) {
                u1.push($(this).attr('data-user-card'));
            });
            console.log(u1);

            let u3 = [];
            u1.forEach(u => {
                $.ajax({
                    dataType: 'json',
                    url: '/u/' + u + '.json',
                    success: function (result) {
                        u3.push({
                            username: u,
                            last_seen_at: new Date(result.user.last_seen_at)
                        });
                    },
                    async: false
                });
            });
            console.log(u3);

            u3.sort((a, b) => {
                return b.last_seen_at.getTime() - a.last_seen_at.getTime();
            });
            createTable(u3);
        }
    })
});

function createTable(tableData) {
    let myDiv = document.createElement('div');
    let table = document.createElement('table');
    let tableBody = document.createElement('tbody');

    tableData.forEach(function (rowData) {
        let row = document.createElement('tr');

        [rowData.username, rowData.last_seen_at].forEach(function (cellData) {
            let cell = document.createElement('td');
            cell.appendChild(document.createTextNode(cellData));
            row.appendChild(cell);
        });

        tableBody.appendChild(row);
    });

    table.appendChild(tableBody);
    myDiv.appendChild(table);
    $("section[class='avatars clearfix']").append(myDiv);
}

Aber ich arbeite noch an einem Zeitstrahl. :crazy_face: