Search posts by user

Search for posts by user(-s) in current topic

Verze ze dne 17. 12. 2017. Zobrazit nejnovější verzi.

// ==UserScript==
// @name           Search posts by user
// @name:ru        Поиск постов пользователя
// @description    Search for posts by user(-s) in current topic
// @description:ru Найти посты пользователя(-ей) в текущей теме
// @version        1.3.1
// @date           17.12.2017
// @author         Halibut
// @namespace      https://greasyfork.org/en/users/145947-halibut
// @homepageURL    https://greasyfork.org/en/scripts/36319-search-posts-by-user
// @supportURL     https://forum.ru-board.com/topic.cgi?forum=2&topic=5673&glp
// @license        HUG-WARE
// @include        http*://forum.ru-board.com/topic.cgi?forum=*&topic=*
// @noframes
// @grant          none
// ==/UserScript==

/******************************************************************************
 * "THE HUG-WARE LICENSE" (Revision 2): As long as you retain this notice you *
 * can do whatever you want with this stuff. If we meet some day, and you     *
 * think this stuff is worth it, you can give me/us a hug.                    *
******************************************************************************/

((body, img, listener) => {
    'use strict';
    if (listener.isFF) {
        const context = body.getAttribute("contextmenu"),
              menu = context
                        ? document.getElementById(context)
                        : body.appendChild(document.createElement("menu")),
              mitem = menu.appendChild(document.createElement("menuitem"));
        if (!context) {
            menu.id = "GM_page-actions";
            menu.type = "context";
            body.setAttribute("contextmenu", "GM_page-actions");
        };
        mitem.label = "Найти собщения пользователя(-ей)";
        mitem.addEventListener("click", listener)
    };
    const button = listener.actnBox.getElementsByTagName("td")[0].appendChild(document.createTextNode("   "))
                         && listener.actnBox.getElementsByTagName("td")[0].appendChild(document.createElement("a")),
          imgNode = button.appendChild(document.createElement("img"));
    imgNode.src = img;
    button.addEventListener("click", listener)
    body.addEventListener("contextmenu", e => {
        let target = e.target.closest("a.m") || e.target.closest("a[href*='?action=show&member=']");
        if (!target || e.altKey || e.shiftKey || e.ctrlKey || e.metaKey || !!listener.getSel() || target.closest("#spu-spoiler-body")) return;
        listener.setName = target.className == "m" ? target.textContent : target.search.split("&member=")[1];
        listener.handleEvent(e)
    })
})(
    document.body
    , "data:image/gif;base64,R0lGODlhXgAPAJEAAAAAAP///////wAAACH5BAEAAAIALAAAAABeAA8AAAKUhI+py+0PIwq02ouz3rz7HxjgSJamJ57qym5plcAvAMs0dYfuhL/hzPvlgrLLEFfT/ZDMWy5zVCqjzKozWbREt7qrsQpNcsNYr7T3bYq74N6TPF3Dpc4Dxke3ImlmuWYcNwf4hjeIFhiIp+Vn9uTIaERYNjNJ9bZo5WOHaWNTeNkSKgqiOGp6Gomquuom4foKGwtRAAA7"
    , {
        options: {
            // !!!ОПЦИИ СКРИПТА!!! (пока неполные)
            showAlerts: false // Показывать алерты по окончании поиска
            , scrollToSearchResults: true  // Автоматически прокручивать к рещультатам поиска
            , autoOpenSpoilerWithResult: false // Раскрывать спойлер с результатами автоматически
            , reversPostSorting: true // обратить сортировку по дате для найденных постов (от новых к старым)
        }
        , name: []
        , ttlShow: '\u25BA Показать результаты поиска'
        , ttlHide: '\u25BC Скрыть результаты поиска'
        , ttlLdng: '\u23F3 Поиск...'
        , ttlNotFnd: '\u26A0 Постов не найдено!'
        , set setName(str) {
            this.name.push(str)
        }
        , get win() {
            delete this.win;
            return this.win = window.top
        }
        , get loc() {
            delete this.loc;
            return this.loc = this.win.location.href
        }
        , get actnBox() {
            delete this.actnBox;
            return this.actnBox = [...document.getElementsByTagName("table")].find(el => el.querySelector("a[href^='post.cgi?action=new']")
                                                                                   || !el.getElementsByTagName('td')[0].children.length)
        }
        , get isFF() {
            delete this.isFF;
            return this.isFF = this.win.navigator && this.win.navigator.userAgent && this.win.navigator.userAgent.toLowerCase().includes("firefox")
        }
        , get spHd() {
            delete this.spHd;
            return this.spHd = this.getSpHd()
        }
        , get spBd() {
            delete this.spBd;
            return this.spBd = this.getSpBd()
        }
        , get spTTl() {
            delete this.spTTl;
            return this.spTTl = this.spHd && this.spHd.getElementsByTagName('td')[0]
        }
        , getSel() {
            return this.win.getSelection && this.win.getSelection().toString() || ""
        }
        , prompt() {
            const prompt = this.win.prompt('Задайте имя для поиска', '')
            return prompt || undefined
        }
        , getPosts(url, name) {
            return new Promise((res, rej) => {
                let posts;
                if (url == this.loc) {
                    posts = document.getElementsByClassName('tb');
                    if (posts && !!posts.length) {
                        posts = this.filterPosts(posts, name);
                        return res(posts.map(el => el.cloneNode(true)));
                    }
                };
                const xhr = new XMLHttpRequest();
                xhr.open("GET", url, true);
                xhr.onload = re => {
                    if (!(xhr.readyState == 4 && xhr.status == 200)) return rej("error");
                    posts = xhr.responseXML.getElementsByClassName('tb');
                    if (posts && !!posts.length) {
                        posts = this.filterPosts(posts, name);
                        return res(posts);
                    }
                };
                xhr.onerror = xhr.onabort = xhr.ontimeout = () => rej("error");
                xhr.responseType = "document";
                xhr.send();
            })
        }
        , procesPosts(posts) {
            return new Promise((res, rej) => {
                posts = this.flatPosts(posts);
                posts = this.stylePosts(posts);
                if (posts && !!posts.length) {
                    this.options.reversPostSorting && (posts = posts.reverse());
                    for (const post of posts)
                        this.spBd.appendChild(post);
                    res(posts.length)
                }
                else
                    rej("not found")
            })
        }
        , filterPosts(posts, name) {
            return [...posts].filter(post => !post.querySelector('a.tpc[href$="&postno=1"]')
                                && name.includes((post.querySelector("td.dats > a.m > b") || post.querySelector("td.dats > b"))
                                     .textContent.toLowerCase().replace(/\s/g, '_')));
        }
        , stylePosts(posts) {
            const color1 = "#EEEEEE",
                  color2 = "#FFFFFF"
            return posts.map((post,indx) => {
                const tdEls =  post.getElementsByTagName("td");
                for (const td of tdEls)
                    td.bgColor && (td.bgColor = !(indx % 2) ? color1 : color2);
                return post
            })
        }
        , flatPosts(posts) {
            return posts.reduce((a, b) => a.concat(b), []);
        }
        , getUsrName() {
            const name = this.name.join(",") || this.getSel() || this.prompt()
            return name && name.trim().toLowerCase().replace(/\s/g, "_").split(",");
        }
        , getPages() {
            const paginator = [...document.getElementsByTagName("p")].find(el => el.textContent && el.textContent.startsWith("Страницы: "));
            if (paginator)
                return [...paginator.getElementsByTagName("td")[0].children].map(el => el.href || this.loc);
            else
                return [this.loc]
        }
        , getSpHd() {
            let spoilerHead = document.getElementById("spu-spoiler-head");
            if (!spoilerHead) {
                const dummyNode = this.actnBox.parentNode.insertBefore(document.createElement('div'), this.actnBox.nextElementSibling);
                dummyNode.outerHTML = '<table id="spu-spoiler-head" style="-moz-user-select: none ! important; cursor: pointer ! important;" width="95%" cellspacing="1" cellpadding="3" bgcolor="#999999" align="center" border="0"><tbody><tr><td valign="middle" bgcolor="#dddddd" align="left"></td></tr><td class="small" bgcolor="#dddddd" align="center">Найдено: <span id="spu-fndd"></span>   Ошибок: <span id="spu-errs"></span></td></tbody></table>';
                spoilerHead = this.actnBox.nextElementSibling;
                spoilerHead.id = "spu-spoiler-head";
                spoilerHead.hidden = true;
                const spTitle = spoilerHead.getElementsByTagName('td')[0];
                spoilerHead.style.cssText = 'font-family: Segoe UI Emoji, bitstreamcyberbit, quivira, Verdana, Arial, Helvetica, sans-serif; -moz-user-select: none !important;-webkit-user-select: none !important; -ms-user-select: none !important; user-select: none !important; cursor: pointer !important';
                spoilerHead.onclick = e => {
                    const spoilerBody = this.spBd;
                    if (e.button != 0 || !spoilerBody || !(spoilerBody && spoilerBody.ready)) return;
                    e.preventDefault(); e.stopPropagation();
                    spTitle.textContent = spoilerBody.hidden ? this.ttlHide : this.ttlShow;
                    spoilerBody.hidden = !spoilerBody.hidden;
                }
            }
            return spoilerHead;
        }
        , getSpBd() {
            let spoilerBody = document.getElementById("spu-spoiler-body");
            if (!spoilerBody) {
                const spoilerHead = this.spHd;
                spoilerBody = spoilerHead.parentNode.insertBefore(document.createElement("table"), spoilerHead.nextElementSibling);
                spoilerBody.id = "spu-spoiler-body";
                spoilerBody.align = "center";
                spoilerBody.width = "95%";
                spoilerBody.style.border = "1px solid black";
                spoilerBody.style.padding = "1em .5em"
                spoilerBody.hidden = true
            }
            return spoilerBody;
        }
        , endNotify(fndd,errs,ntfnd) {
            if (fndd) {
                const spoilerHead = this.spHd,
                      spoilerBody = this.spBd,
                      confirm = this.win.confirm("Поиск окончен!\nНайдено: " + fndd + (errs ? "\nОшибок: " + errs : "") + "\nПерейти к резульататам?");
                if (confirm) {
                    spoilerHead.scrollIntoView({behavior:"smooth"});
                    if (this.options.autoOpenSpoilerWithResult) {
                        this.spTTl.textContent = this.ttlHide;
                        spoilerBody.hidden = false;
                    }
                }
            }
            else
                this.win.alert("Постов не найдено!" + (errs ? "\nОшибок: " + errs : ""))
        }
        , handleEvent(e) {
            e.preventDefault(); e.stopPropagation(); e.stopImmediatePropagation();
            const name = this.getUsrName();
            if (!name || name && !name.length) return;
            const pageLinks = this.getPages(),
                  spoilerHead = this.spHd,
                  spoilerBody = this.spBd,
                  spoilerTitle = this.spTTl,
                  errsLbl = document.getElementById("spu-errs"),
                  findedLbl = document.getElementById("spu-fndd");
            spoilerHead.hidden = false; spoilerTitle.textContent = this.ttlLdng;
            spoilerBody.hidden = true; spoilerBody.ready = false;
            this.options.scrollToSearchResults && !this.options.showAlerts && spoilerHead.scrollIntoView({behavior:"smooth"});
            let errorsCntr = 0;
            errsLbl.textContent = findedLbl.textContent = 0;
            if (spoilerBody.children && !!spoilerBody.children.length)
                for (const node of [...spoilerBody.children])
                    node.remove();
            Promise.all(pageLinks.map(link => {
                return this.getPosts(link, name)
            })).then(
                posts => {
                    return this.procesPosts(posts)
                }
                , error => {
                    errorsCntr += 1;
                    errsLbl.textContent = errorsCntr;
                }
            ).then(
                founded => {
                    findedLbl.textContent = founded;
                    spoilerTitle.textContent = this.ttlShow;
                    spoilerBody.ready = true;
                    if (this.options.showAlerts)
                        this.endNotify(founded, errorsCntr)
                    else if (this.options.autoOpenSpoilerWithResult) {
                        spoilerBody.hidden = false
                        spoilerTitle.textContent = this.ttlHide;
                    };
                    return
                }
                , notfound => {
                    spoilerTitle.textContent = this.ttlNotFnd;
                    this.options.showAlerts && this.endNotify(null, errorsCntr, notfound);
                    return
                }
            ).catch(err => {
                errorsCntr += 1; errsLbl.textContent = errorsCntr
            })
            this.name.length = 0;
        }
    }
);