data-manager

handles loaded html, takes care of data, applying filters

目前為 2024-07-17 提交的版本,檢視 最新版本

// ==UserScript==
// @name         data-manager
// @description  handles loaded html, takes care of data, applying filters
// @namespace    http://tampermonkey.net/
// @author       smartacephale
// @license      MIT
// @version      1.2
// @match        *://*/*
// ==/UserScript==
/* globals LazyImgLoader stringToWords GM_addStyle */

/** Manages thumbs, applying filters, lazy loading, keep list unique */
class DataManager {
    /**
  * @param {Rules} rules - WEBSITE_RULES class which have to implement methods:
  * GET_THUMBS,
  * THUMB_URL,
  * THUMB_DATA,
  * THUMB_IMG_DATA (required for lazy loading, return undefined if no need it)
  * IS_PRIVATE * optional
  *
  * @param {Object} state - object with props:
  * filterDuration
  * filterDurationFrom
  * filterDurationTo
  * filterExclude
  * filterExcludeWords
  * filterInclude
  * filterIncludeWords
  * filterPublic * optional
  * filterPrivate * optional
  */
    constructor(rules, state) {
        this.rules = rules;
        this.state = state;
        this.data = new Map();
        this.setupFilters();
        this.lazyImgLoader = LazyImgLoader.create((target) => !this.isFiltered(target));
    }

    dataFilters = {
        filterPublic: {
            tag: 'filtered-public',
            createFilter() {
                return (v) => [this.tag, !this.rules.IS_PRIVATE(v.element) && this.state.filterPublic];
            }
        },
        filterPrivate: {
            tag: 'filtered-private',
            createFilter(){
                return (v) => [this.tag, this.rules.IS_PRIVATE(v.element) && this.state.filterPrivate];
            }
        },
        filterDuration: {
            tag: 'filtered-duration',
            createFilter() {
                return (v) => {
                    const notInRange = v.duration < this.state.filterDurationFrom || v.duration > this.state.filterDurationTo;
                    return [this.tag, this.state.filterDuration && notInRange];
                }
            }
        },
        filterExclude: {
            tag: 'filtered-exclude',
            createFilter() {
                const tags = this.filterDSLToRegex(this.state.filterExcludeWords);
                return (v) => {
                    const containTags = tags.some(tag => tag.test(v.title));
                    return [this.tag, this.state.filterExclude && containTags];
                }
            }
        },
        filterInclude: {
            tag: 'filtered-include',
            createFilter() {
                const tags = this.filterDSLToRegex(this.state.filterIncludeWords);
                return (v) => {
                    const containTagsNot = tags.some(tag => !tag.test(v.title));
                    return [this.tag, this.state.filterInclude && containTagsNot];
                }
            }
        }
    }

    filterDSLToRegex(str) {
        const toFullWord = w => `(^|\ )${w}($|\ )`;
        const words = stringToWords(str).map(expr => {
            let w = expr;
            if (w.startsWith('f:')) {
                w = toFullWord(w.replace('f:', ''));
            }
            return new RegExp(w, 'i');
        });
        return words;
    }

    setupFilters() {
        Object.keys(this.dataFilters).forEach(k => {
            if (!Object.hasOwn(this.state, k)) {
                delete this.dataFilters[k];
            }
        });

        const tags = Object.keys(this.dataFilters).map(k => `.${this.dataFilters[k].tag}`).join(',');
        GM_addStyle(`${tags} { display: none !important; }`);

        Object.values(this.dataFilters).forEach(f => {
            f.state = this.state;
            f.rules = this.rules;
            f.filterDSLToRegex = this.filterDSLToRegex;
        });
    }

    isFiltered(el) {
        return el.className.includes('filtered');
    }

    filter_ = (filters, offset = 0) => {
        const runFilters = [];

        for (const f of Object.keys(filters)) {
            runFilters.push(this.dataFilters[f].createFilter());
        }

        let offset_counter = 1;
        for (const v of this.data.values()) {
            offset_counter++;
            if (offset_counter > offset) {
                for (const rf of runFilters) {
                    const [tag, condition] = rf(v);
                    v.element.classList.toggle(tag, condition);
                }
            }
        }
    }

    filterAll = (offset) => {
        const applyFilters = Object.assign({}, ...Object.keys(this.dataFilters).map(f => ({ [f]: this.state[f] })));
        this.filter_(applyFilters, offset);
    }

    handleLoadedHTML = (html, container, removeDuplicates = false, shouldLazify = true) => {
        const thumbs = this.rules.GET_THUMBS(html);
        const data_offset = this.data.size;

        for (const thumbElement of thumbs) {
            const url = this.rules.THUMB_URL(thumbElement);
            if (!url || this.data.has(url)) {
                if (removeDuplicates) thumbElement.remove();
                continue;
            }

            const { title, duration } = this.rules.THUMB_DATA(thumbElement);
            this.data.set(url, { element: thumbElement, duration, title });

            if (shouldLazify) {
                const { img, imgSrc } = this.rules.THUMB_IMG_DATA(thumbElement);
                this.lazyImgLoader.lazify(thumbElement, img, imgSrc);
            }

            const parent = container || this.rules.CONTAINER;
            if (!parent.contains(thumbElement)) parent.appendChild(thumbElement);
        }

        this.filterAll(data_offset);
    };
}

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址