Camamba Users Search Library

fetches Users

Dieses Skript sollte nicht direkt installiert werden. Es handelt sich hier um eine Bibliothek für andere Skripte, welche über folgenden Befehl in den Metadaten eines Skriptes eingebunden wird // @require https://update.greasyfork.org/scripts/446634/1133718/Camamba%20Users%20Search%20Library.js

// ==UserScript==
// @name         Camamba Users Search Library
// @namespace    hoehleg.userscripts.private
// @version      0.0.9
// @description  fetches Users
// @author       Gerrit Höhle
// @license MIT
//
// @require      https://greasyfork.org/scripts/405144-httprequest/code/HttpRequest.js?version=1063408
//  
// @grant        GM_xmlhttpRequest
// @grant        GM.getValue
// @grant        GM.setValue
// @grant        GM.listValues
// ==/UserScript==

// https://greasyfork.org/scripts/446634-camamba-users-search-library/

/* jslint esversion: 11 */

const cus_cache = (() => {

    /**
     * @param {{ uid: number, name: string }} keyAsObject 
     * @returns {string?}
     */
    const objectToKey = (keyAsObject) => {
        if (!keyAsObject?.uid && !keyAsObject?.name) {
            return null;
        }
        return `cus${keyAsObject.uid || ""}'${keyAsObject.name || ""}`;
    };

    /**
     * @param {string} key 
     * @returns {{ uid: number, name: string }}
     */
    const keyToObject = (keyAsString) => {
        const keyAsArray = keyAsString.slice(3).split("'");
        return {
            uid: Number.parseInt(keyAsArray[0]) || 0,
            name: keyAsArray[1] || "",
        };
    };

    /** @type {object.<{ uid: number, name: string }>} */
    const cache = {};
    /** @type {object.<{ uid: number, name: string }>} */
    const keysByName = {};
    /** @type {object.<{ uid: number, name: string }>} */
    const keysByUid = {};

    const addToCache = ({ uid, name, content }) => {
        const key = { uid, name };

        if (uid) {
            const storedByUid = keysByUid[uid];

            if (name) {
                if (storedByUid && storedByUid.name !== name) {

                    if (keysByName.hasOwnProperty(storedByUid.name)) {
                        delete keysByName[storedByUid.name];
                    }

                    GM.deleteKey(objectToKey(storedByUid));
                }
                keysByName[name] = key;
            }

            keysByUid[uid] = key;
        }
        if (name) {
            keysByName[name] = key;
        }
        cache[objectToKey(key)] = content || null;
    };

    (async () => {
        for (const keyAsString of (await GM.listValues()).filter(key => key.startsWith("cus"))) {
            addToCache(keyToObject(keyAsString));
        }
    })();

    return {

        /**
         * @param {{ uid: number?, name?: string, guessLog: GuessLog?, userLevel: UserLevel?}} param0 
         */
        store: async ({ uid, name, guessLog, userLevel }) => {
            if (!name && !uid || !guessLog && !userLevel) {
                return;
            }

            const content = {};
            if (userLevel?.level) {
                content.lvl = userLevel.level;
            }
            if (userLevel?.timeStamp) {
                content.lvlTS = userLevel.timeStamp;
            }
            if (guessLog?.ipList?.length) {
                content.ips = guessLog.ipList;
            }
            if (guessLog?.prints?.length) {
                content.prints = guessLog.prints;
            }
            if (guessLog?.scorePassword) {
                content.scrPW = guessLog.scorePassword;
            }
            if (guessLog?.scoreFinal) {
                content.scrFinal = guessLog.scoreFinal;
            }
            if (guessLog?.timeStamp) {
                content.scrTS = guessLog.timeStamp;
            }

            const key = objectToKey({ name, uid });
            await GM.setValue(key, JSON.stringify(content));
            addToCache({ name, uid, content });
        },

        /**
         * @param {{ uid: number, name: string }} param0
         * @return {Promise<{ guessLog : GuessLog, userLevel: UserLevel }}> 
         */
        read: async ({ uid = 0, name = "" }) => {
            let keyAsObject = null;
            if (uid && keysByUid[uid]) {
                keyAsObject = keysByUid[uid];
            } else if (name && keysByName[name]) {
                keyAsObject = keysByName[name];
            }
            const key = objectToKey(keyAsObject);
            if (!key) {
                return {};
            }

            let data = {};
            if (!cache[key]) {
                cache[key] = JSON.parse(await GM.getValue(key, "{}"));
            }
            data = cache[key];

            const guessLog = new GuessLog({
                userName: keyAsObject?.name || name,
                ipList: data.ips || [],
                prints: data.prints || [],
                scorePassword: data.scrPW || 0,
                scoreFinal: data.scrFinal || 0,
                timeStamp: data.scrTS || 0,
            });

            const userLevel = new UserLevel({
                name: keyAsObject?.name || name,
                uid: keyAsObject?.uid || uid,
                level: data.lvl || 0,
                timeStamp: data.lvlTS || 0,
            });

            return { guessLog, userLevel };
        },
    };
})();

class GuessLog {
    /** @param {{ userName: string, ipList: string[], prints: string[], scorePassword: number, scoreFinal: number, timeStamp: number }} param0 */
    constructor({ userName, ipList, prints, scorePassword, scoreFinal, timeStamp }) {
        /** @type {string} */
        this.userName = userName;
        /** @type {string[]} */
        this.ipList = ipList;
        /** @type {string[]} */
        this.prints = prints;
        /** @type {number} */
        this.scorePassword = scorePassword;
        /** @type {number} */
        this.scoreFinal = scoreFinal;
        /** @type {number} */
        this.timeStamp = timeStamp;
    }
}

/**
 * @typedef {object} UserParams
 * @property {string} name
 * @property {uid} [number]
 * @property {'male'|'female'|'couple'?} [gender]
 * @property {number} [age]
 * @property {number} [level]
 * @property {number} [longitude]
 * @property {number} [latitude]
 * @property {string} [location]
 * @property {number} [distanceKM]
 * @property {boolean} [isReal]
 * @property {boolean} [hasPremium]
 * @property {boolean} [hasSuper]
 * @property {boolean} [isPerma]
 * @property {boolean} [isOnline]
 * @property {string} [room]
 * @property {Date} [lastSeen]
 * @property {Date} [regDate]
 * @property {string[]} [ipList]
 * @property {Date} [scorePassword]
 * @property {Date} [scoreFinal]
 * @property {(date: Date) => string} [dateToHumanReadable]
 */

class GuessLogSearch extends HttpRequestHtml {

    constructor(name) {
        /**
         * @param {string} labelText 
         * @param {string} textContent 
         * @returns {number}
         */
        const matchScore = (labelText, textContent) => {
            const regexLookBehind = new RegExp("(?<=" + labelText + ":\\s)");
            const regexFloat = /\d{1,2}\.?\d{0,20}/;
            const regexLookAhead = /(?=\spoints)/;

            for (const regexesToJoin of [
                [regexLookBehind, regexFloat, regexLookAhead],
                [regexLookBehind, regexFloat]
            ]) {
                const regexAsString = regexesToJoin.map(re => re.source).join("");
                const matcher = new RegExp(regexAsString, "i").exec(textContent);
                if (matcher != null) {
                    return Number.parseFloat(matcher[0]);
                }
            }
        };

        /**
        * @param {RegExp} regex 
        * @param {string} textContent 
        * @returns {Array<String>}
        */
        const matchList = (regex, textContent) => {
            const results = [...textContent.matchAll(regex)].reduce((a, b) => [...a, ...b], []);
            if (results.length) {
                const resultsDistinct = [...new Set(results)];
                return resultsDistinct;
            }
        };

        super({
            url: 'https://www.camamba.com/guesslog.php',
            params: { name },

            /**
             * @param {{ html: Document}} resp 
             * @returns {GuessLog}
             */
            resultTransformer: (resp) => {
                const textContent = resp.html.body.textContent;

                const ipList = matchList(/(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])/g, textContent);
                const prints = matchList(/(?<=Print\d{0,2}\schecked\sis\s)[0-9a-f]+/g, textContent);

                const scorePassword = matchScore("password check", textContent);
                const scoreFinal = matchScore("final score", textContent);

                return { userName: name, ipList, prints, scorePassword, scoreFinal, timeStamp: new Date().getTime() };
            }
        });
    }

    /** @returns {Promise<GuessLog>} */
    async send() {
        const maxHoursInCache = 6;

        const name = this.params.name;
        const cache = await cus_cache.read({ name });

        let result = cache?.guessLog;
        const timeStamp = result?.timeStamp;

        if (!timeStamp || new Date().getTime() - timeStamp >= maxHoursInCache * 60 * 60 * 1000) {
            result = await super.send();
            cus_cache.store({ ...(cache || {}), name, guessLog: result });
        }

        return result;
    }

    /**
     * @param {string} name 
     * @returns {Promise<GuessLog>}
     */
    static async send(name) {
        return await new GuessLogSearch(name).send();
    }
}

/**
 * @typedef {Object} BanLog
 * @property {string} moderator - user or moderator who triggered the log
 * @property {string} user - user who is subject
 * @property {Date} date - date of this log
 * @property {string} reason - content
 */

class BannLogSearch extends HttpRequestHtml {
    /**
     * @param {number} uid 
     */
    constructor(uid = null) {
        super({
            url: 'https://www.camamba.com/banlog.php',
            params: uid ? { admin: uid } : {},
            resultTransformer: (response, _request) => {
                const results = [];
                const xPathExpr = "//tr" + ['User', 'Moderator', 'Date', 'Reason'].map(hdrText => `[td[span[text()='${hdrText}']]]`).join("");
                let tr = (response.html.evaluate(xPathExpr, response.html.body, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue || {}).nextElementSibling;

                while (tr) {
                    const tds = tr.querySelectorAll('td');
                    const user = tds[0].querySelector("a") || tds[0].textContent;
                    const moderator = tds[1].textContent;

                    let date;
                    const dateMatch = /(\d{2}).(\d{2}).(\d{4})<br>(\d{1,2}):(\d{2}):(\d{2})/.exec(tds[2].innerHTML);
                    if (dateMatch) {
                        const day = dateMatch[1];
                        const month = dateMatch[2];
                        const year = dateMatch[3];
                        const hour = dateMatch[4];
                        const minute = dateMatch[5];
                        const second = dateMatch[6];
                        date = new Date(year, month - 1, day, hour, minute, second);
                    }

                    const reason = tds[3].textContent;
                    results.push({ user, moderator, date, reason });

                    tr = tr.nextElementSibling;
                }

                return results;
            }
        });
    }

    /**
     * @param {number} uid
     * @returns {Promise<BanLog[]>}
     */
    static async send(uid) {
        return await new BannLogSearch(uid).send();
    }
}

class GalleryImage {
    constructor({ dataURI, href }) {
        /** @type {string} */
        this.dataURI = dataURI;
        /** @type {string} */
        this.href = href;
    }
}


class UserLevel {
    /** @param {{ level: number, uid: number, name: string, timeStamp: number }} param0 */
    constructor({ level, uid = null, name = null, timeStamp = null }) {
        /** @type {number} */
        this.level = level !== null ? Number.parseInt(level) : null;
        /** @type {number} */
        this.uid = uid !== null ? Number.parseInt(uid) : null;
        /** @type {string} */
        this.name = name;
        /** @type {number} */
        this.timeStamp = timeStamp !== null ? Number.parseInt(timeStamp) : null;
    }
}

class UserLevelSearch extends HttpRequestHtml {
    constructor(uid) {
        super({
            url: 'https://www.camamba.com/user_level.php',
            params: { uid },

            /**
             * @param {{ html: Document }} response 
             * @param {{ params: { uid: number }}} request 
             * @returns {UserLevel} 
             */
            resultTransformer: (response, request) => {
                const html = response.html;

                let name = null, level = null;

                const nameElement = html.querySelector('b');
                if (nameElement) {
                    name = nameElement.textContent;
                }

                const levelElement = html.querySelector('font.xxltext');
                if (levelElement) {
                    const levelMatch = /\d{1,3}/.exec(levelElement.textContent);
                    if (levelMatch) {
                        level = Number.parseInt(levelMatch);
                    }
                }

                return new UserLevel({ uid: request.params.uid, name, level, timeStamp: new Date().getTime() });
            }
        });
    }

    /**
     * @returns {Promise<UserLevel>}
     */
    async send() {
        const maxHoursInCache = 24;

        const uid = this.params.uid;
        const cache = await cus_cache.read({ uid });

        let result = cache?.userLevel;
        const timeStamp = result?.timeStamp;

        if (!timeStamp || new Date().getTime() - timeStamp >= maxHoursInCache * 60 * 60 * 1000) {
            result = await super.send();
            cus_cache.store({ ...(cache || {}), uid, userLevel: result });
        }

        return result;
    }

    /**
     * @param {number} uid 
     * @returns {Promise<UserLevel>}
     */
    static async send(uid) {
        return await new UserLevelSearch(uid).send();
    }
}

class User {
    /** @param {UserParams} param0 */
    constructor({
        name, uid = 0, gender = null, age = null,
        longitude = null, latitude = null, location = null, distanceKM = null,
        isReal = null, hasPremium = null, hasSuper = null, isPerma = null,
        isOnline = null, room = null, lastSeen = null, regDate = null,
        dateToHumanReadable = (date) => date ?
            date.toLocaleString('de-DE', { timeStyle: "medium", dateStyle: "short", timeZone: 'CET' }) : '',
    }) {
        /** @type {string} */
        this.name = String(name);
        /** @type {number?} */
        this.uid = uid;
        /** @type {'male'|'female'|'couple'?} */
        this.gender = gender;
        /** @type {number?} */
        this.age = age;

        /** @type {number?} */
        this.longitude = longitude;
        /** @type {number?} */
        this.latitude = latitude;
        /** @type {string?} */
        this.location = location;
        /** @type {number?} */
        this.distanceKM = distanceKM;

        /** @type {boolean?} */
        this.isReal = isReal;
        /** @type {boolean?} */
        this.hasPremium = hasPremium;
        /** @type {boolean?} */
        this.hasSuper = hasSuper;
        /** @type {boolean?} */
        this.isPerma = isPerma;

        /** @type {boolean?} */
        this.isOnline = isOnline;
        /** @type {string?} */
        this.room = room;
        /** @type {Date?} */
        this.lastSeen = lastSeen;
        /** @type {Date?} */
        this.regDate = regDate;

        /** @type {string[]} */
        this.prints = [];
        /** @type {string[]} */
        this.ipList = [];
        /** @type {number?} */
        this.scorePassword = null;
        /** @type {number?} */
        this.scoreFinal = null;
        /** @type {number} */
        this.guessLogTS = null;

        /** @type {(date: Date) => string} */
        this.dateToHumanReadable = dateToHumanReadable;

        /** @type {number?} */
        this.level = null;
        /** @type {number} */
        this.levelTS = null;

        /** @type {string[]} */
        this.galleryData = [];
        /** @type {number} */
        this.galleryDataTS = null;
    }

    /** @type {string} @readonly */
    get lastSeenHumanReadable() {
        return this.dateToHumanReadable(this.lastSeen);
    }

    /** @type {string} @readonly */
    get regDateHumanReadable() {
        return this.dateToHumanReadable(this.regDate);
    }

    get galleryAsImgElements() {
        if (!this.galleryData) {
            return [];
        }

        return this.galleryData.map(data => Object.assign(document.createElement('img'), {
            src: data.dataURI
        }));
    }

    async updateGalleryHref() {
        const pictureLinks = (await HttpRequestHtml.send({
            url: "https://www.camamba.com/profile_view.php",
            params: Object.assign(
                { m: 'gallery' },
                this.uid ? { uid: this.uid } : { user: this.name }
            ),

            pageNr: 0,
            pagesMaxCount: 500,

            resultTransformer: (response) => {
                const hrefList = [...response.html.querySelectorAll("img.picborder")].map(img => img.src);
                return hrefList.map(href => href.slice(0, 0 - ".s.jpg".length) + ".l.jpg");
            },
            hasNextPage: (_resp, _httpRequestHtml, lastResult) => {
                return lastResult.length >= 15;
            },
            paramsConfiguratorForPageNr: (params, pageNr) => ({ ...params, page: pageNr }),
        })).flat();

        this.galleryData = pictureLinks.map(href => ({ href }));
        this.galleryDataTS = new Date().getTime();
    }

    async updateGalleryData(includeUpdateOfHref = true) {
        if (includeUpdateOfHref) {
            await this.updateGalleryHref();
        }

        const readGalleryData = this.galleryData.map(({ href }) => (async () => {
            const dataURI = await HttpRequestBlob.send({ url: href });
            return new GalleryImage({ dataURI, href });
        })());

        this.galleryData = await Promise.all(readGalleryData);
        this.galleryDataTS = new Date().getTime();
    }

    async updateLevel() {
        const { level, timeStamp, name } = await UserLevelSearch.send(this.uid);
        this.level = level;
        this.levelTS = timeStamp;
        this.name = name;
    }

    async updateGuessLog() {
        /** @type {GuessLog} */
        const guessLog = await GuessLogSearch.send(this.name);
        this.guessLogTS = new Date().getTime();

        this.ipList = guessLog.ipList;
        this.prints = guessLog.prints;
        this.scorePassword = guessLog.scorePassword;
        this.scoreFinal = guessLog.scoreFinal;
    }

    async addNote(text) {
        if (!this.uid) {
            return await Promise.reject({
                status: 500,
                statusText: "missing uid"
            });
        }

        return await new Promise((res, rej) => GM_xmlhttpRequest({
            url: 'https://www.camamba.com/profile_view.php',
            method: 'POST',
            data: `uid=${this.uid}&modnote=${encodeURIComponent(text)}&m=admin&nomen=1`,
            headers: {
                "Content-Type": "application/x-www-form-urlencoded"
            },
            onload: (xhr) => {
                res(xhr.responseText);
            },
            onerror: (xhr) => rej({
                status: xhr.status,
                statusText: xhr.statusText
            }),
        }));
    }
}

class UserSearch extends HttpRequestHtml {
    /** @param {{ 
     * name: string?, 
     * uid: number?,
     * gender: ('any' | 'male' | 'female' |'couple')?, 
     * isOnline: boolean?, hasReal: boolean?, hasPremium: boolean?, hasSuper: boolean?, hasPicture: boolean?,
     * isSortByRegDate: boolean?,
     * isSortByDistance: boolean?,
     * isShowAll: boolean?,
     * pageNr: number?,
     * pagesMaxCount: number?,
     * keepInCacheTimoutMs: number?
     * }} param0 */
    constructor({
        name = null,
        uid = 0,
        gender = 'any',
        isOnline = null,
        hasReal = null,
        hasPremium = null,
        hasSuper = null,
        hasPicture = null,
        isSortByRegDate = null,
        isSortByDistance = null,
        isShowAll = null,
        pageNr = 1,
        pagesMaxCount = 1,
        keepInCacheTimoutMs
    } = {}) {
        let params = Object.assign(
            (name ? {
                nick: name
            } : {}),
            {
                gender: gender.toLowerCase(),
            },
            Object.fromEntries(Object.entries({
                online: isOnline,
                isreal: hasReal,
                isprem: hasPremium,
                issuper: hasSuper,
                picture: hasPicture,
                sortreg: isSortByRegDate,
                byDistance: isSortByDistance,
                showall: isShowAll,
            })
                .filter(([_k, v]) => typeof v !== 'undefined' && v !== null)
                .map(([k, v]) => ([[k], v ? 1 : 0])))
        );

        params = Object.entries(params).map(([key, value]) => key + '=' + value).join('&');

        if (params.length) {
            params += "&";
        }
        params += `page=${Math.max(pageNr - 1, 0)}`;

        super({
            url: 'https://www.camamba.com/search.php',
            params,
            pageNr: Math.max(pageNr, 1),
            pagesMaxCount: Math.max(pagesMaxCount, 1),
            keepInCacheTimoutMs,

            resultTransformer: (response) => {
                /** @type {Array<User>} */
                const users = [];

                for (const trNode of response.html.querySelectorAll('.searchSuper tr, .searchNormal tr')) {
                    const innerHTML = trNode.innerHTML;

                    const nameMatch = /<a\s+?href=["']javascript:openProfile\(["'](.+?)["']\)/.exec(innerHTML);

                    if (!nameMatch) {
                        break;
                    }

                    const user = new User({
                        name: nameMatch[1],
                        isReal: /<img src="\/gfx\/real.png"/.test(innerHTML),
                        hasPremium: /<a href="\/premium.php">/.test(innerHTML),
                        hasSuper: /<img src="\/gfx\/super_premium.png"/.test(innerHTML),
                        isOnline: /Online\snow(\s\in|,\snot in chat)/.test(innerHTML),
                    });

                    const uidMatch = /<a\s+?href=["']javascript:sendMail\(["'](\d{1,8})["']\)/.exec(innerHTML) || /<img\ssrc="\/userpics\/(\d{1,8})/.exec(innerHTML);
                    if (uidMatch) {
                        user.uid = Number.parseInt(uidMatch[1]);
                    }

                    // Längengrad, Breitengrad, Ortsname
                    const locationMatch = /<a\s+?href="javascript:openMap\((-?\d{1,3}\.\d{8}),(-?\d{1,3}\.\d{8})\);">(.+?)<\/a>/.exec(innerHTML);
                    if (locationMatch) {
                        user.longitude = Number.parseFloat(locationMatch[1]);
                        user.latitude = Number.parseFloat(locationMatch[2]);
                        user.location = locationMatch[3];
                    }

                    // Entfernung in km
                    const distanceMatch = /(\d{1,5})\skm\sfrom\syou/.exec(innerHTML);
                    if (distanceMatch) {
                        user.distanceKM = parseInt(distanceMatch[1]);
                    }

                    // Geschlecht und Alter
                    const genderAgeMatch = /(male|female|couple),\s(\d{1,4})(?:<br>){2}Online/.exec(innerHTML);
                    if (genderAgeMatch) {
                        user.gender = genderAgeMatch[1];
                        user.age = genderAgeMatch[2];
                    }

                    // zuletzt Online
                    if (user.isOnline) {
                        user.lastSeen = new Date();
                    } else {
                        const lastSeenMatch = /(\d{1,4})\s(minutes|hours|days)\sago/.exec(innerHTML);
                        if (lastSeenMatch) {
                            const value = parseInt(lastSeenMatch[1]);

                            const factorToMillis = {
                                'minutes': 1000 * 60,
                                'hours': 1000 * 60 * 60,
                                'days': 1000 * 60 * 60 * 24,
                            }[lastSeenMatch[2]];

                            user.lastSeen = new Date(Date.now() - value * factorToMillis);
                        }
                    }

                    // Raumname
                    const roomMatch = /(?:ago|now)\sin\s([\w\s]+?|p\d{1,8})<br>/.exec(innerHTML);
                    if (roomMatch) {
                        user.room = roomMatch[1];
                    }

                    // regDate
                    const regDateMatch = /(\d{2}).(\d{2}).(\d{4})\s(\d{1,2}):(\d{2}):(\d{2})/.exec(innerHTML);
                    if (regDateMatch) {
                        const regDateDay = regDateMatch[1];
                        const regDateMonth = regDateMatch[2];
                        const regDateYear = regDateMatch[3];
                        const regDateHour = regDateMatch[4];
                        const regDateMinute = regDateMatch[5];
                        const regDateSecond = regDateMatch[6];
                        user.regDate = new Date(regDateYear, regDateMonth - 1, regDateDay, regDateHour, regDateMinute, regDateSecond);
                    }

                    users.push(user);
                }

                return users;
            },

            hasNextPage: (_resp, _httpRequestHtml, lastResult) => {
                return lastResult.length >= 50;
            },

            paramsConfiguratorForPageNr: (params, pageNr) => {
                return params.replace(/page=\d+(?:$)/, `page=${pageNr - 1}`);
            },
        });
        this.uid = uid || null;
    }

    /** @returns {Promise<User[]>} */
    async send() {
        if (this.uid) {
            const user = new User({ uid: this.uid });
            await user.updateLevel();

            if (!user.name || user.level) {
                return [];
            }
            if (this.params.nick) {
                const unameURIencoded = encodeURIComponent(user.name.toLowerCase());
                const unameFromSearchParam = encodeURIComponent(this.params.nick.toLowerCase()).trim();
                if (unameURIencoded.includes(unameFromSearchParam)) {
                    return [];
                }
            }

            this.params.nick = user.name;
            const result = (await super.send()).flat().find(u => u.uid == this.uid);
            if (!result) {
                return [];
            }

            return [Object.assign(user, result)];
        }

        return (await super.send()).flat();
    }

    /**
     * @param {{ 
     * name: string, 
     * uid: number?,
     * gender: 'any' | 'male' | 'female' |'couple', 
     * isOnline: boolean,hasReal: boolean, hasPremium: boolean, hasSuper: boolean, hasPicture: boolean,
     * isSortByRegDate: boolean,
     * isSortByDistance: boolean,
     * isShowAll: boolean,
     * pageNr: number,
     * pagesMaxCount: number,
     * keepInCacheTimoutMs: number
     * }} param0 
     * @returns {Promise<User[]>} 
     */
    static async send(param0) {
        return await new UserSearch(param0).send();
    }
}