Camamba Users Search Library

fetches Users

이 스크립트는 직접 설치해서 쓰는 게 아닙니다. 다른 스크립트가 메타 명령 // @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();
    }
}