您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
fetches forums, threads and posts
此脚本不应直接安装。它是供其他脚本使用的外部库,要使用该库请加入元指令 // @require https://update.greasyfork.icu/scripts/446112/1060781/Camamba%20Forums%20Search%20Library.js
// ==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), }; })();