Initial commit

This commit is contained in:
2026-08-24 22:20:36 +02:00
commit 62f7b1278a
251 changed files with 16670 additions and 0 deletions
+134
View File
@@ -0,0 +1,134 @@
class DoubleLinkedListNode {
constructor(key, value) {
this.key = key;
this.value = value;
this.next = null;
this.prev = null;
};
};
class DoubleLinkedList {
constructor() {
this.map = new Map();
this.head = null;
this.tail = null;
};
getElement(id) {
const element = this.map.get(id);
if (!element) return;
return element.value;
}
hasElement(id) {
return this.map.has(id);
}
isEmpty() {
return this.head === null;
}
addFirst(key, value) {
if (this.map.has(key)) this.removeItem(key);
const newNode = new DoubleLinkedListNode(key, value);
const oldHead = this.head;
this.head = newNode;
this.head.prev = null;
if (oldHead) {
oldHead.prev = newNode;
this.head.next = oldHead;
};
this.map.set(key, {
value,
node: newNode
});
};
accumulateItem(key, value) {
if (isNaN(value)) return;
const entry = this.map.get(key);
const updatedValue = entry ? entry.value + value : value;
this.addItem(key, updatedValue);
}
addItem(key, value) {
if (this.map.has(key)) this.removeItem(key);
const temp = new DoubleLinkedListNode(key, value);
if (!this.head) {
this.head = temp;
this.tail = temp;
} else {
this.tail.next = temp;
temp.prev = this.tail;
this.tail = this.tail.next;
};
this.map.set(key, {
value,
node: temp
});
return value;
}
removeItem(key) {
const entry = this.map.get(key);
if (!entry) return;
const node = entry.node;
if (node.prev) node.prev.next = node.next;
if (node.next) node.next.prev = node.prev;
if (node === this.head) this.head = node.next;
if (node === this.tail) this.tail = node.prev;
this.map.delete(key);
return entry;
}
each(cb) {
if (this.isEmpty()) return;
if (!cb) return;
let isRunning = true;
let current = this.head;
while (current && (isRunning === undefined || isRunning)) { // I'm afraid this would backfire one day
isRunning = cb(current.key, current.value);
current = current.next;
};
return isRunning;
}
clearEach(cb) {
if (this.isEmpty()) return;
if (!cb) return;
let current = this.head;
while (current) {
cb(current.key, current.value);
current.prev = null;
current = current.next;
};
this.head = null;
this.tail = null;
this.map.clear();
}
};
module.exports = DoubleLinkedList;
+73
View File
@@ -0,0 +1,73 @@
class LinkedListNode {
constructor(key, value) {
this.key = key;
this.value = value;
this.next = null;
};
};
class LinkedList {
constructor() {
this.map = new Map();
this.head = null;
this.tail = null;
};
getElement(id) {
const element = this.map.get(id);
if (!element) return;
return element.value;
}
hasElement(id) {
return this.map.has(id);
}
isEmpty() {
return this.head === null;
}
addItem(key, value) {
const temp = new LinkedListNode(key, value);
if (!this.head) {
this.head = temp;
this.tail = temp;
} else {
this.tail.next = temp;
this.tail = temp;
};
this.map.set(key, temp);
}
each(cb) {
if (this.isEmpty()) return;
let current = this.head;
while (current) {
cb(current.key, current.value);
current = current.next;
};
}
clearEach(cb) {
if (this.isEmpty()) return;
let current = this.head;
while (current) {
cb(current.key, current.value);
current = current.next;
};
this.head = null;
this.tail = null;
this.map.clear();
}
};
module.exports = LinkedList;
+30
View File
@@ -0,0 +1,30 @@
class PriorityQueue {
constructor() {
this.items = [];
};
enqueue(item, priority) {
const element = { item, priority };
let added = false;
for (let i = 0; i < this.items.length; i++) {
if (priority < this.items[i].priority) {
this.items.splice(i, 0, element);
added = true;
break;
};
};
if (!added) this.items.push(element);
}
dequeue() {
return this.isEmpty() ? null : this.items.shift().item;
}
isEmpty() {
return this.items.length === 0;
}
};
module.exports = PriorityQueue;
+273
View File
@@ -0,0 +1,273 @@
'use strict';
var defaultcomparator = function (a, b) {
return a < b;
};
// the provided comparator function should take a, b and return *true* when a < b
function FastPriorityQueue(comparator) {
if (!(this instanceof FastPriorityQueue)) return new FastPriorityQueue(comparator);
this.array = [];
this.size = 0;
this.compare = comparator || defaultcomparator;
}
// copy the priority queue into another, and return it. Queue items are shallow-copied.
// Runs in `O(n)` time.
FastPriorityQueue.prototype.clone = function() {
var fpq = new FastPriorityQueue(this.compare);
fpq.size = this.size;
fpq.array = this.array.slice(0, this.size);
return fpq;
};
// Add an element into the queue
// runs in O(log n) time
FastPriorityQueue.prototype.add = function(value, obj) {
var i = this.size;
this.array[this.size] = { value, obj };
this.size += 1;
var p;
var ap;
while (i > 0) {
p = (i - 1) >> 1;
ap = this.array[p];
if (!this.compare(value, ap.value)) break;
this.array[i] = ap;
i = p;
};
this.array[i] = { value, obj };
};
// replace the content of the heap by provided array and "heapify it"
FastPriorityQueue.prototype.heapify = function(arr) {
this.array = arr;
this.size = arr.length;
var i;
for (i = this.size >> 1; i >= 0; i--) {
this._percolateDown(i);
}
};
// for internal use
FastPriorityQueue.prototype._percolateUp = function(i, force) {
var myval = this.array[i];
var p;
var ap;
while (i > 0) {
p = (i - 1) >> 1;
ap = this.array[p];
// force will skip the compare
if (!force && !this.compare(myval.value, ap.value)) {
break;
}
this.array[i] = ap;
i = p;
}
this.array[i] = myval;
};
// for internal use
FastPriorityQueue.prototype._percolateDown = function(i) {
var size = this.size;
var hsize = this.size >>> 1;
var ai = this.array[i];
var l;
var r;
var bestc;
while (i < hsize) {
l = (i << 1) + 1;
r = l + 1;
bestc = this.array[l];
if (r < size) {
if (this.compare(this.array[r].value, bestc.value)) {
l = r;
bestc = this.array[r];
}
}
if (!this.compare(bestc.value, ai.value)) {
break;
}
this.array[i] = bestc;
i = l;
}
this.array[i] = ai;
};
// internal
// _removeAt(index) will remove the item at the given index from the queue,
// retaining balance. returns the removed item, or undefined if nothing is removed.
FastPriorityQueue.prototype._removeAt = function(index) {
if (index > this.size - 1 || index < 0) return undefined;
// impl1:
//this.array.splice(index, 1);
//this.heapify(this.array);
// impl2:
this._percolateUp(index, true);
return this.poll();
};
// remove(myval) will remove an item matching the provided value from the
// queue, checked for equality by using the queue's comparator.
// return true if removed, false otherwise.
FastPriorityQueue.prototype.remove = function(myval) {
for (var i = 0; i < this.size; i++) {
if (!this.compare(this.array[i], myval) && !this.compare(myval, this.array[i])) {
// items match, comparator returns false both ways, remove item
this._removeAt(i);
return true;
}
}
return false;
};
// removeOne(callback) will execute the callback function for each item of the queue
// and will remove the first item for which the callback will return true.
// return the removed item, or undefined if nothing is removed.
FastPriorityQueue.prototype.removeOne = function(callback) {
if (typeof callback !== "function") {
return undefined;
}
for (var i = 0; i < this.size; i++) {
if (callback(this.array[i])) {
return this._removeAt(i);
}
}
};
// remove(callback[, limit]) will execute the callback function for each item of
// the queue and will remove each item for which the callback returns true, up to
// a max limit of removed items if specified or no limit if unspecified.
// return an array containing the removed items.
// The callback function should be a pure function.
FastPriorityQueue.prototype.removeMany = function(callback, limit) {
// Skip unnecessary processing for edge cases
if (typeof callback !== "function" || this.size < 1) {
return [];
}
limit = limit ? Math.min(limit, this.size) : this.size;
// Prepare the results container to hold up to the results limit
var resultSize = 0;
var result = new Array(limit);
// Prepare a temporary array to hold items we'll traverse through and need to keep
var tmpSize = 0;
var tmp = new Array(this.size);
while (resultSize < limit && !this.isEmpty()) {
// Dequeue items into either the results or our temporary array
var item = this.poll();
if (callback(item)) {
result[resultSize++] = item;
} else {
tmp[tmpSize++] = item;
}
}
// Update the result array with the exact number of results
result.length = resultSize;
// Re-add all the items we can keep
var i = 0;
while (i < tmpSize) {
const elem = tmp[i++];
this.add(elem.value, elem.obj);
}
return result;
};
// Look at the top of the queue (one of the smallest elements) without removing it
// executes in constant time
//
// Calling peek on an empty priority queue returns
// the "undefined" value.
// https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/undefined
//
FastPriorityQueue.prototype.peek = function() {
if (this.size == 0) return undefined;
return this.array[0];
};
// remove the element on top of the heap (one of the smallest elements)
// runs in logarithmic time
//
// If the priority queue is empty, the function returns the
// "undefined" value.
// https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/undefined
//
// For long-running and large priority queues, or priority queues
// storing large objects, you may want to call the trim function
// at strategic times to recover allocated memory.
FastPriorityQueue.prototype.poll = function() {
if (this.size == 0) return undefined;
var ans = this.array[0];
if (this.size > 1) {
this.array[0] = this.array[--this.size];
this._percolateDown(0);
} else {
this.size -= 1;
}
return ans;
};
// This function adds the provided value to the heap, while removing
// and returning one of the smallest elements (like poll). The size of the queue
// thus remains unchanged.
FastPriorityQueue.prototype.replaceTop = function(myval) {
if (this.size == 0) return undefined;
var ans = this.array[0];
this.array[0] = myval;
this._percolateDown(0);
return ans;
};
// recover unused memory (for long-running priority queues)
FastPriorityQueue.prototype.trim = function() {
this.array = this.array.slice(0, this.size);
};
// Check whether the heap is empty
FastPriorityQueue.prototype.isEmpty = function() {
return this.size === 0;
};
FastPriorityQueue.prototype.forEach = function(callback) {
if (this.isEmpty() || typeof callback != 'function') return;
var i = 0;
var fpq = this.clone();
let isRunning = true;
while (!fpq.isEmpty() && isRunning) {
isRunning = callback(fpq.poll(), i++);
}
};
// return the k 'smallest' elements of the queue as an array,
// runs in O(k log k) time, the elements are not removed
// from the priority queue.
FastPriorityQueue.prototype.kSmallest = function(k) {
if ((this.size == 0) || (k<=0)) return [];
k = Math.min(this.size, k);
const newSize = Math.min(this.size, (2 ** (k - 1)) + 1);
if (newSize < 2) { return [this.peek()] }
const fpq = new FastPriorityQueue(this.compare);
fpq.size = newSize;
fpq.array = this.array.slice(0, newSize);
const smallest = new Array(k);
for (let i = 0; i < k; i++) {
smallest[i] = fpq.poll();
}
return smallest;
}
module.exports = FastPriorityQueue;
+71
View File
@@ -0,0 +1,71 @@
class Router {
constructor(base) {
this.routes = [];
this.base = base;
};
addRoute(method, path, handler) {
const paramNames = [];
const regexPath = path
.replace(/:([^/]+)/g, (match, paramName) => {
paramNames.push(paramName);
return '([^/]+)';
})
.replace(/\*/g, '.*');
this.routes.push({
method,
path,
regex: new RegExp(`^${regexPath}$`),
paramNames,
handler
});
}
get(path, handler) {
this.addRoute("GET", this.base ? this.base + path : path, handler);
console.info(this.base ? this.base + path : path)
}
post(path, handler) {
this.addRoute("POST", this.base ? this.base + path : path, handler);
console.info("POST", this.base ? this.base + path : path)
}
put(path, handler) {
this.addRoute("PUT", this.base ? this.base + path : path, handler);
}
delete(path, handler) {
this.addRoute("DELETE", this.base ? this.base + path : path, handler);
}
handle(res, req) {
const method = req.getMethod().toUpperCase();
const urlPrepare = req.getUrl().split("/");
const urlRelevant = urlPrepare.slice(2, urlPrepare.length);
const url = "/" + urlRelevant.join("/");
console.info("hm?", method)
for (const route of this.routes) {
if (route.method === method) {
const match = url.match(route.regex);
console.info(url, route, method)
if (!match) continue;
const params = {};
route.paramNames.forEach((name, index) => params[name] = match[index + 1]);
req.params = params;
return route.handler(res, req);
};
};
res
.writeStatus("404 Not Found")
.end("Invalid route");
}
};
module.exports = Router;
+15
View File
@@ -0,0 +1,15 @@
function chooseWeightedRandom(items, weights) {
let i;
let length = weights.length;
weights = [...weights];
for (i = 0; i < length; i++) weights[i] += weights[i - 1] || 0;
const random = Math.random() * weights[length - 1];
for (i = 0; i < length; i++) if (weights[i] > random) break;
return items[i];
};
module.exports = chooseWeightedRandom;
+76
View File
@@ -0,0 +1,76 @@
function extractBehaviourFeatures(actions) {
if (!actions || actions.length < 2) return null;
const actionCounts = {};
const idleThreshold = 5000;
const windowSize = 5;
const intervals = [];
const sequenceBuffer = [];
let totalInterval = 0;
let minInterval = Infinity;
let maxInterval = -Infinity;
let idleTimeCount = 0;
let idleTimeTotal = 0;
let lastTimestamp = actions[0].timestamp;
let prevAction = actions[0].opcode;
let sequenceMap = {};
for (let i = 1; i < actions.length; i++) {
const current = actions[i];
const interval = current.timestamp - lastTimestamp;
lastTimestamp = current.timestamp;
intervals.push(interval);
totalInterval += interval;
minInterval = Math.min(minInterval, interval);
maxInterval = Math.max(maxInterval, interval);
actionCounts[current.opcode] = (actionCounts[current.opcode] || 0) + 1;
if (interval > idleThreshold) {
idleTimeCount++;
idleTimeTotal += interval;
};
sequenceBuffer.push(current.opcode);
if (sequenceBuffer.length === windowSize) {
const key = sequenceBuffer.join(",");
sequenceMap[key] = (sequenceMap[key] || 0) + 1;
sequenceBuffer.shift();
};
prevAction = current.opcode;
};
const meanInterval = totalInterval / intervals.length;
const stdDevInterval = Math.sqrt(intervals.reduce((sum, val) => sum + (val - meanInterval) ** 2, 0) / intervals.length);
const uniqueActionsCount = Object.keys(actionCounts).length;
const idleTimeRatio = idleTimeCount / (actions.length - 1);
const maxSequenceRepeats = Math.max(...Object.values(sequenceMap)) || 0;
const totalActions = actions.length;
let entropy = 0;
for (const count of Object.values(actionCounts)) {
const p = count / totalActions;
entropy -= p * Math.log2(p);
};
return {
timestamp: new Date(),
timestampStart: null,
meanInterval,
stdDevInterval,
minInterval,
maxInterval,
uniqueActionsCount,
actionEntropy: entropy,
maxSequenceRepeats,
idleTimeRatio
};
};
module.exports = extractBehaviourFeatures;
+5
View File
@@ -0,0 +1,5 @@
function getFixedDate(seconds) {
return Date.now() - (Date.now() % (seconds * 1000) - seconds * 1000);
};
module.exports = getFixedDate;
+36
View File
@@ -0,0 +1,36 @@
const redis = require("../Miscellaneous/redis");
async function isAuthenticated(res, req) {
const cookieHeader = req.getHeader("cookie");
const cookie = cookieHeader.split("ps=")[1];
if (!cookie) {
res.cork(() => res
.writeStatus("400")
.end("Unauthenticated"));
return false;
};
const formattedCookie = cookie.split(".")[0].slice(4);
const cookieInDB = await redis.get(`sess:${formattedCookie}`);
if (!cookieInDB) {
res.cork(() => res
.writeStatus("400")
.end("Unauthenticated"));
return false;
};
const parsedCookie = JSON.parse(cookieInDB);
if (!parsedCookie.isAdmin) {
res.cork(() => res
.writeStatus("400")
.end("Unauthorized"));
return false;
};
return true;
};
module.exports = isAuthenticated;
+131
View File
@@ -0,0 +1,131 @@
(function(w) {
/**
* The main constructor function.
* @constructor
* @param {Function} onTick The tick function.
* @param {Number} interval The tick duration.
* @returns {Timer} The instance of timer.
*/
var Timer = function(onTick, interval) {
this.setInterval(interval);
this.setOnTick(onTick);
this._timer = null;
this._active = false;
this._startTime = 0;
};
/**
* Start the timer.
* The tick function is called with one argument, the drift amount (int).
* @returns {Timer} this.
*/
Timer.prototype.start = function() {
let drift = 0,
actual = 0,
ideal = 0,
counter = 0,
time = 0,
that = this,
now = performance.now;
function onTick() {
that._onTick(drift);
++counter;
ideal = that._interval * counter;
actual = now() - that._startTime;
drift = actual - ideal;
time = that._interval - drift;
if (that._active) setTimeout(onTick, time);
}
that._active = true;
that._startTime = now();
that._timer = setTimeout(onTick, that._interval);
return this;
};
/**
* Stop the timer.
* @returns {Timer} this.
*/
Timer.prototype.stop = function() {
this._active = false;
clearTimeout(this._timer);
return this;
};
/**
* Set the tick duration. Can be called whilst timer is running.
* @param {Number} ms The tick duration.
* @returns {Timer} this.
*/
Timer.prototype.setInterval = function(ms) {
if(isNaN(ms)) {
throw new Error('Timer.setInterval requires a number');
}
else if(ms < 10) {
ms = 10;
}
this._interval = ms;
return this;
};
/**
* Allow for replacement of the tick function, even whilst the timer
* is running.
* @param {Function} onTick The new tick function to be called on each cycle.
* @returns {Timer} this.
*/
Timer.prototype.setOnTick = function(onTick) {
this._onTick = onTick;
return this;
};
/**
* Allow the `Timer` constructor to be used as a replacement for
* setInterval.
* @param {Function} tick The tick function to call every `interval` ms.
* @param {Number} interval The tick duration.
* @returns {Timer} A new instance of the `Timer` constructor.
*/
var setTimer = function(tick, interval) {
var timer = new Timer(tick, interval);
timer.start();
return timer;
};
/**
* Imitate the clearTimeout method...
* @param {Timer}
*/
var clearTimer = function(timer) {
timer.stop();
};
w.Timer = Timer;
w.setTimer = setTimer;
}(global));