Camamba Forums Search Library

fetches forums, threads and posts

Этот скрипт недоступен для установки пользователем. Он является библиотекой, которая подключается к другим скриптам мета-ключом // @require https://update.greasyfork.org/scripts/446112/1060781/Camamba%20Forums%20Search%20Library.js

Чтобы установить этот скрипт, вы сначала должны установить расширение браузера, например Tampermonkey, Greasemonkey или Violentmonkey.

Чтобы установить этот скрипт, вы сначала должны установить расширение браузера, например Tampermonkey или Violentmonkey.

Чтобы установить этот скрипт, вы сначала должны установить расширение браузера, например Tampermonkey или Violentmonkey.

Чтобы установить этот скрипт, вы сначала должны установить расширение браузера, например Tampermonkey или Userscripts.

Чтобы установить этот скрипт, сначала вы должны установить расширение браузера, например Tampermonkey.

Чтобы установить этот скрипт, вы должны установить расширение — менеджер скриптов.

(у меня уже есть менеджер скриптов, дайте мне установить скрипт!)

Чтобы установить этот стиль, сначала вы должны установить расширение браузера, например Stylus.

Чтобы установить этот стиль, сначала вы должны установить расширение браузера, например Stylus.

Чтобы установить этот стиль, сначала вы должны установить расширение браузера, например Stylus.

Чтобы установить этот стиль, сначала вы должны установить расширение — менеджер стилей.

Чтобы установить этот стиль, сначала вы должны установить расширение — менеджер стилей.

Чтобы установить этот стиль, сначала вы должны установить расширение — менеджер стилей.

(у меня уже есть менеджер стилей, дайте мне установить скрипт!)

// ==UserScript==
// @name         Camamba Forums Search Library
// @namespace    hoehleg.userscripts.private
// @version      0.0.3
// @description  fetches forums, threads and posts
// @author       Gerrit Höhle
// @license MIT
//
// @require      https://greasyfork.org/scripts/405144-httprequest/code/HttpRequest.js?version=1060129
//
// @grant        GM_xmlhttpRequest
//
// ==/UserScript==

/* jslint esversion: 9 */

/**
 * @typedef ForumDef
 * @property {number} id
 * @property {string} lng
 * @property {string} title
 */

/** 
 * @typedef ThreadDef
 * @property {string} title
 * @property {number} id 
 * @property {number} forumId
 * @property {number} postCount
 */

/** 
 * @typedef PostDef
 * @property {number} forumId
 * @property {number} threadId
 * @property {number} page
 * @property {number} id 
 * @property {Date} postDate
 * @property {string} text 
 * @property {string} uname 
 * @property {number} uid 
 */

/**
 * @typedef ThreadIdentifier
 * @property {number} forumId
 * @property {number} threadId
 */

class Post {
    /**
     * @param {PostDef} param0 
     */
    constructor({ forumId, threadId, page, id, postDate, text, uname, uid }) {
        /** @type {number} */
        this.forumId = forumId;
        /** @type {number} */
        this.threadId = threadId;
        /** @type {number} */
        this.page = page;
        /** @type {number} */
        this.id = id;
        /** @type {Date} */
        this.postDate = postDate;
        /** @type {string} */
        this.text = text;
        /** @type {string} */
        this.uname = uname;
        /** @type {number} */
        this.uid = uid;
    }

    async delete() {
        return await HttpRequest.send({
            method: 'GET', url: "https://www.camamba.com/forum_view.php", params: {
                thread: this.threadId,
                forum: this.forumId,
                delete: this.id,
                page: this.page,
            }
        });
    }
}

class Thread {

    /**
     * @param {ThreadDef} param0 
     */
    constructor({ title, id, forumId, postCount }) {
        /** @type {string} */
        this.title = title;
        /** @type {number} */
        this.id = id;
        /** @type {number} */
        this.forumId = forumId;
        /** @type {number} */
        this.postCount = postCount;
    }

    /**
     * @returns {Promise<Array<Post>>}
     */
    async getPosts(resultSizeLimit = 0) {
        return Thread.getPosts({ threadId: this.id, forumId: this.forumId, resultSizeLimit });
    }

    async delete() {
        for (const post of await this.getPosts()) {
            post.delete();
        }
    }

    /**
     * @param {ThreadIdentifier} param0 
     * @returns {Promise<Array<Post>>}
     */
    static async getPosts({ threadId, forumId, resultSizeLimit = 0 }) {
        return (await HttpRequestHtml.send({
            url: "https://www.camamba.com/forum_view.php",
            params: {
                thread: threadId,
                forum: forumId,
            },
            pageNr: 1,
            pagesMaxCount: resultSizeLimit ? Math.ceil(resultSizeLimit / 10) : 10000,
            paramsConfiguratorForPageNr: (params, pageNr) => ({ ...params, page: pageNr - 1 }),
            hasNextPage: (res, _req) => res.html.querySelectorAll("td.psmallbox").length >= 30,
            resultTransformer: (res, req) => {

                return [...res.html.querySelectorAll("td.psmallbox")].map(td => {
                    let postId = null, text = "", postDate = null;

                    const div = td.querySelector("div");
                    if (div) {
                        const deleteLink = div.querySelector('a[href^="javascript:deletePost"]');

                        if (deleteLink) {
                            const postIdMatch = /(?<=javascript:deletePost)\d+(?=\(\))/.exec(deleteLink.href);
                            postId = postIdMatch === null ? null : Number.parseInt(postIdMatch[0]);
                        }

                        text = [...div.childNodes]
                            .filter(el => el.tagName !== "SCRIPT")
                            .filter(el => el.tagName !== "A" || (!["javascript:deletePost", "javascript:parent.location", "javascript:var noop="].some(s => el.href.startsWith(s))))
                            .map(el => el.textContent).join("").trim();
                    }

                    let uname = "", uid = null;

                    const tdLeft = td.previousElementSibling;
                    if (tdLeft && [...tdLeft.classList].includes("psmallbox2")) {
                        const linkOpenProfile = tdLeft.querySelector('a[href^="javascript:openProfile("]');
                        if (linkOpenProfile) {
                            const unameMatch = /(?<=javascript:openProfile\(['"])\w+(?=['"]\))/.exec(linkOpenProfile.href);
                            if (unameMatch) {
                                uname = unameMatch[0];
                            }
                        }

                        const imgUserpic = tdLeft.querySelector('img[src^="/userpics"]');
                        if (imgUserpic) {
                            const uidMatch = /(?<=userpics\/)\d+(?=\.s\.jpg)/.exec(imgUserpic.src);
                            if (uidMatch) {
                                uid = uidMatch[0];
                            }
                        }

                        const divDate = tdLeft.querySelector('div.smalltext');
                        if (divDate) {
                            const postDateMatch = /(\d{2}).(\d{2}).(\d{4})\s(\d{1,2}):(\d{2}):(\d{2})/.exec(divDate.innerText.trim());
                            if (postDateMatch) {
                                const day = postDateMatch[1];
                                const month = postDateMatch[2];
                                const year = postDateMatch[3];
                                const hour = postDateMatch[4];
                                const minute = postDateMatch[5];
                                const second = postDateMatch[6];
                                postDate = new Date(year, month - 1, day, hour, minute, second);
                            }
                        }
                    }

                    let page = req.params.page;
                    return { forumId, threadId, page, id: postId, postDate, text, uname, uid };
                }).filter(p => p.id && p.text).map(p => new Post(p));
            },
        })).flat();
    }
}

const Forum = (() => {

    /**
     * @param {number} forumId
     * @param {number} [resultSizeLimit]
     * @returns {HttpRequestHtml}
     */
    const createRequest = (forumId, resultSizeLimit = 0) => new HttpRequestHtml({
        url: "https://www.camamba.com/forum_view.php",
        params: { forum: forumId },
        pageNr: 1,
        pagesMaxCount: resultSizeLimit ? Math.ceil(resultSizeLimit / 10) : 10000,
        paramsConfiguratorForPageNr: (params, pageNr) => ({ ...params, page: pageNr - 1 }),
        hasNextPage: (res, _req) => res.html.querySelectorAll('a[href^="?thread="]').length >= 10,
        resultTransformer: (res, _req) => {
            return [...res.html.querySelectorAll('a[href^="?thread="]')].map(a => {

                const matchThreadId = new RegExp(`(?<=\\?thread=)\\d+(?=&forum=${forumId})`).exec(a.href);
                const threadId = matchThreadId === null ? null : Number.parseInt(matchThreadId[0]);

                let postCount = (() => {

                    let postCountTD = a.parentElement;

                    while (postCountTD !== null && postCountTD.tagName !== 'TD') {
                        postCountTD = postCountTD.parentElement;
                    }

                    for (let i = 0; i < 2 && postCountTD !== null; i++) {
                        postCountTD = postCountTD.nextElementSibling;
                    }

                    if (postCountTD != null) {
                        let postCountMatch = /^\d+$/.exec(postCountTD.innerText.trim());
                        if (postCountMatch && postCountMatch.length) {
                            return Number.parseInt(postCountMatch[0]);
                        }
                    }

                    return 0;
                })();

                return { title: a.innerHTML, id: threadId, forumId, postCount };

            }).filter(t => t.id && t.forumId && t.postCount).map(t => new Thread(t));
        },
    });

    return class Forum {
        /** @param {ForumDef} param0 */
        constructor({ id, lng, title }) {
            /** @type {number} */
            this.id = id;
            /** @type {string} */
            this.lng = lng;
            /** @type {string} */
            this.title = title;
        }

        /**
         * @returns {Promise<Array<Thread>>}
         */
        async getThreads(resultSizeLimit = 0) {
            this.request = createRequest(this.id, resultSizeLimit);
            this.lastResults = (await this.request.send()).flat();
            return this.lastResults;
        }

        /**
         * @returns {Promise<Array<Thread>>}
         */
        async getNextThreads() {
            if (!(this.request && this.lastResults && this.lastResults.length)) {
                return [];
            }
            this.request.pageNr += (this.request.pagesMaxCount);
            return (await this.request.send()).flat();
        }

        /**
         * @param {number} forumId 
         * @returns {Promise<Array<Thread>>}
         */
        static async getThreads(forumId, resultSizeLimit = 0) {
            return (await createRequest(forumId, resultSizeLimit).send()).flat();
        }
    };
})();



const Foren = (() => {

    /**
     * @type {Promise<Array<Forum>>}
     */
    const foren = HttpRequestHtml.send({
        url: 'https://www.camamba.com/forum.php',
        params: { mode: 'all' },
        resultTransformer: (resp, _req) => [...resp.html.querySelectorAll('a[href^="/forum_view"]')].map(a => {

            const idMatch = /(?<=forum=)\d{1,2}$/.exec(a.href);
            const id = idMatch === null ? null : Number.parseInt(idMatch[0]);

            const lngMatch = /(?<=^\[)\D\D(?=\])/g.exec(a.textContent);
            const lng = lngMatch === null ? null : lngMatch[0].toLowerCase();

            const titleMatch = /(?<=^\[\D\D\]\s).+$/.exec(a.textContent);
            const title = titleMatch === null ? null : titleMatch[0];

            return { id, lng, title };

        }).filter(forum => forum.id && forum.lng).map(f => new Forum(f)),
    });

    return {
        /**
        * @returns {Promise<Array<Forum>>}
        */
        getAll: async () => await foren,

        /**
         * @param {string} lng 
         * @returns {Promise<Array<Forum>>}
         */
        byLanguage: async (lng) => (await foren).filter(f => f.lng.toUpperCase() === lng.toUpperCase()),

        /**
         * @param {number} id 
         * @returns {Promise<Forum>}
         */
        byId: async (id) => (await foren).find(f => f.id == id),
    };
})();