Rufuker 2ch

Culturally enriches the pidorussian lingamus on 2ch

Vous devrez installer une extension telle que Tampermonkey, Greasemonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Userscripts pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey pour installer ce script.

Vous devrez installer une extension de gestionnaire de script utilisateur pour installer ce script.

(J'ai déjà un gestionnaire de scripts utilisateur, laissez-moi l'installer !)

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

(J'ai déjà un gestionnaire de style utilisateur, laissez-moi l'installer!)

// ==UserScript==
// @name         Rufuker 2ch
// @name:ru      Руфакер для Двач 2ch
// @namespace    https://2ch.hk/
// @version      0.55
// @description  Culturally enriches the pidorussian lingamus on 2ch
// @description:ru  Культурна облагарожывает росейскую языку на Дваче 2ch
// @author       Anon
// @copyright    2021-2022, Anon
// @match        *://2ch.hk/*
// @match        *://2ch.pm/*
// @match        *://2ch.life/*
// @license      GPL-3.0-only
// @homepageURL  https://github.com/adisloom/rufuker/blob/main/README.md
// @supportURL   https://github.com/adisloom/rufuker/issues
// @icon         https://www.google.com/s2/favicons?domain=2ch.life
// @defaulticon  https://www.google.com/s2/favicons?domain=2ch.life
// @icon64       https://www.google.com/s2/favicons?domain=2ch.life&sz=64
// @grant        none
// ==/UserScript==

/**********************************************************************************
*
*                           NOTICE
*
* The script requires the browser plugin "Tampermonkey".
* Set "Run only in top frame" to "No" in plugin's settings for the script.
*
* It may also work in other usercript manager plugins:
* GreaseMonkey, ViolentMonkey, FireMonkey.
*
***********************************************************************************/

(function() {
    'use strict';

    if (!document.getElementById('js-posts'))
        if (!document.getElementById('posts-form'))
            return 1;


     /* 
     *  Converts a string of text according to the rules.
     *  Optionial argument (bool) to disable uppercase 
     *  text conversion. Default - enabled 
     */
    class Rufuker {
        rufuker_replacement_rules = [
            // xx -> yy
            ['ий народ', 'ай на рот'],
            ['осси', 'абсе'],
            ['сски', 'зке'],
            ['еще', 'есчо'],
            ['когда', 'када'],
            ['деньг', 'тэньг'],
            ['денег', 'дынек'],
            ['денеж', 'дыняш'],
            ['ого([ \\s,\\.\\-:])', 'ава$1'],
            ['о([влрт])о', 'а$1а'],
            ['[иы]й([ \\s,\\.\\-:])', 'ы$1'],
            ['([^ \\s,\\.\\-:])ие([ \\s,\\.\\-:])', '$1$1е$2'],
            ['ри', 'ґы'],
            ['ре', 'ґе'],
            ['ря', 'ґя'],
            ['рь', 'гх'],
            ['ти', 'це'],
            ['те', 'ця'],
            ['тя', 'ца'],
            ['ди', 'дэ'],
            ['де', 'ды'],
            ['ши', 'шэ'],
            ['ше', 'ша'],
            ['жи', 'жэ'],
            ['же', 'жа'],
            ['си', 'се'],
            ['ио', 'её'],
            ['иа', 'ея'],
            ['иу', 'ею'],
            ['ие', 'ее'],
            ['ться', 'ца'],
            ['тся', 'тсо'],
            ['дь', 'ц'],
            ['ть', 'ц'],
            ['ли', 'ле'],
            ['че', 'це'],
            ['([жшч])ь', '$1'],
            ['щ', 'ш'],
            ['([^ \\s,\\.\\-:])и([ \\s,\\.\\-:])', '$1е$2'],
            ['ъе', 'йэ'],
            ['ъё', 'йо'],
            ['ъю', 'йу'],
            ['ъя', 'йа'],
            [' и([ \\s,\\.\\-:])', ' ды$1'],
            ['и', 'ы'] ];
        addUpperCase = true;
        aReplacement = [];

        constructor(uppercaseOption){
            if (typeof uppercaseOption === 'boolean') this.addUpperCase = uppercaseOption;
            this.compileRegex();
            this.rufukString = this.covertText.bind(this);
        }
        compileRegex() {
            this.aReplacement = this.rufuker_replacement_rules.map( c => ({ sRegex: new RegExp(c[0],'g'), sSubst: c[1] }) );
            if (!this.addUpperCase) return;
            var aUpcasedReplacement = this.rufuker_replacement_rules.map( function(c) {
                let rgx = c[0];
                let upRgx = '';
                for (let i = 0; i < rgx.length; i++){
                    let res = rgx[i].match(/[а-я]/);
                    if (res) upRgx = upRgx + res[0].toString().toUpperCase();
                    else upRgx = upRgx + rgx[i];
                }
                let substitute = c[1].at(0).toUpperCase() + c[1].slice(1); //capitalize the first letter
                return { sRegex: new RegExp(upRgx,'g'), sSubst: substitute };
            });
            this.aReplacement = this.aReplacement.concat(aUpcasedReplacement);
        }
        covertText(txt) {
            var flagAllCaps = this.detectAllCaps(txt);
            for (let r of this.aReplacement) {
                let substitute = r.sSubst.toString();
                if (flagAllCaps) substitute = substitute.toUpperCase();
                txt = txt.toString().replace(r.sRegex, substitute);
            }
            return txt;
        }
        detectAllCaps(str){
           let part = str.slice(-200);
           let res;
           if (res = part.match(/[А-Я]/g))
               if (res.length / part.length > 0.30) return true;
           else return false;
        }
    } //class


    /* 
     *  Can traverse 2ch and replace text in all the posts
     *  including popups and dynamically loaded messages.
     *  Argument - a function for text conversion.
     */
    class TextReplacer2ch {
        workingElement;
        #flagObserveNewPosts = true;
        #flagObserveScrollAndPopup = true;
        delayPopup = 100; //ms

        constructor (txtConverter) {
            this.txtConverter = txtConverter;

            if (this.workingElement = document.getElementById('js-posts'));
              else if (this.workingElement = document.getElementById('posts-form'));
            		else return 1;

            const aThreads = this.workingElement.querySelectorAll('div.thread');
            if (aThreads.length === 0) return 2; //wrong page
            this.replaceAllDecendantArticles(this.workingElement);
            if (this.#flagObserveScrollAndPopup) {
                const board_observer = new MutationObserver(this.replaceScrollAndPopup.bind(this));
                board_observer.observe(this.workingElement, {childList:true});
            }
            //single thread page needs one more observer for added posts
            if (this.#flagObserveNewPosts && aThreads.length === 1) {
                const thread_observer = new MutationObserver(this.replaceNewPosts.bind(this));
                thread_observer.observe(aThreads[0], {childList:true});
            }
        } //constructor

        replaceAllDecendantArticles(pe) {
            const articles = pe.querySelectorAll('article.post__message');
            articles.forEach(a => a.innerHTML = this.txtConverter(a.innerHTML));
        }

        replaceArticleByNum(idNum) {
            const id_article = 'm' + idNum;
            const el = document.getElementById(id_article);
            el.innerHTML = this.txtConverter(el.innerHTML);
        }

        replaceScrollAndPopup(mutationsList, observer) {
            let postClasses = ['post', 'post_type_reply', 'post_preview'];
            setTimeout( () => {
                for(const mutation of mutationsList) {
                    if (mutation.type !== 'childList' || mutation.addedNodes.length === 0) continue;
                    mutation.addedNodes.forEach( n => {
                        if (postClasses.every(name => n.classList.contains(name))) {
                            for (const idx in n.children) {
                                if (n.children[idx].nodeName === 'ARTICLE') {
                                    n.children[idx].innerHTML = this.txtConverter(n.children[idx].innerHTML);
                                }
                            }
                        } else if (n.className === 'thread') {
                            const thread = document.getElementById(n.id);
                            this.replaceAllDecendantArticles(thread);
                        }
                    });
                }
            }, this.delayPopup);
        }

        replaceNewPosts (mutationsList, observer) {
            for(const mutation of mutationsList) {
                if (mutation.type !== 'childList' || mutation.addedNodes.length === 0) continue;
                for (const n of mutation.addedNodes[0].children) {
                    if (n.nodeName !== 'DIV' || ! n.hasAttribute('id')) continue;
                    let idNum = n.id.match( /\d{3,}/g).pop(); //last 3+ digits
                    this.replaceArticleByNum(idNum);
                }
            } //for
        }
    } //class

    var txtConverter = new Rufuker();
    new TextReplacer2ch(txtConverter.rufukString);

})();