Super Rule34

Adds tag buttons for search and editing and fixes dates

이 스크립트를 설치하려면 Tampermonkey, Greasemonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

You will need to install an extension such as Tampermonkey to install this script.

이 스크립트를 설치하려면 Tampermonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Userscripts와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 유저 스크립트 관리자 확장 프로그램이 필요합니다.

(이미 유저 스크립트 관리자가 설치되어 있습니다. 설치를 진행합니다!)

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

(이미 유저 스타일 관리자가 설치되어 있습니다. 설치를 진행합니다!)

// ==UserScript==
// @name        Super Rule34
// @namespace   Violentmonkey Scripts
// @match       *://rule34.xxx/*
// @grant       none
// @version     1.2
// @author      SuperStas0
// @description Adds tag buttons for search and editing and fixes dates
// @icon        https://www.google.com/s2/favicons?sz=64&domain=rule34.xxx
// @run-at      document-idle
// @license     MIT
// ==/UserScript==

(() => {
    const url = window.location.href
    const months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
    const backgroundColors = ["#8f8f8f", "#93b393", "#ff3030"]
    const foregroundColors = ["#303a30", "#303a30", "#ffffff"]
    const editTags = [
        "1girls",
        "raionart",
        "simple_background",
        "doki_doki_literature_club",
        "sayori_(doki_doki_literature_club)",
        "monika_(doki_doki_literature_club)",
        "natsuki_(doki_doki_literature_club)",
        "yuri_(doki_doki_literature_club)",
        "patreon_username"
    ]
    const tags = [
        "sort:score",
        "1girls",
        "video",
    ]
    const artists = [
        "raionart",
        "haloowl",
        "foxgumie",
        "bluethebone",
        "rox_hoxten",
        "myst",
        "rtil",
        "minus8",
        "unepicroachy",
        "zerlix_fox",
        "fukuro_ko_(greentea)",
        "postblue98",
        "redboard",
        "drunkavocado",
        "saucymojo",
        "lavandyra",
        "lewddoodler",
    ]

    function date(day, month, year, array) {
        if (array) [day, month, year] = [array[day], array[month], array[year]]
        month = months[+month - 1] ?? month
        year = +year < 2000 ? +year + 2000 : +year
        return `${day} ${month} ${year}`
    }

    class Manager {
        static editEntry = null
        static editDiv = null
        static editClearButton = null
        static editTagButtons = []
        static searchEntry = null
        static searchDiv = null
        static searchClearButton = null
        static searchTagButtons = []
        static artistsText = null
        static artistsToggleButton = null
        static artistsButtons = []

        static init() {
            if (document.querySelector(".awesomplete")) {
                this.searchDiv = document.querySelector(".awesomplete")
                this.searchEntry = document.querySelector('[name="tags"]')
            }
            if (url.includes("page=post")) {
                if (url.includes("s=add")) {
                    this.editDiv = document.querySelector(".awesomplete")
                    this.editEntry = document.querySelector('[name="tags"]')
                    this.searchDiv = null
                    this.searchEntry = null
                }
                else if (url.includes("s=view")) {
                    this.editEntry = document.querySelectorAll('[name="tags"]')[1]
                    this.editDiv = document.querySelectorAll(".awesomplete")[1]
                }
            }

            if (this.editEntry) {
                this.editEntry.addEventListener("input", () => this.update())
                editTags.forEach(item => {
                    item = new EditTagButton(item)
                    this.editTagButtons.push(item)
                    this.editDiv.appendChild(item.element)
                })
                this.editClearButton = new ClearButton(this.editEntry)
                this.editDiv.appendChild(this.editClearButton.element)
            }

            if (this.searchEntry) {
                this.searchEntry.addEventListener("input", () => this.update())
                tags.forEach(item => {
                    item = new SearchTagButton(item)
                    this.searchTagButtons.push(item)
                    this.searchDiv.appendChild(item.element)
                })
                this.artistsToggleButton = new ArtistsToggleButton(+localStorage.getItem("artState"))
                this.searchDiv.appendChild(this.artistsToggleButton.element)
                artists.forEach(item => {
                    item = new ArtistButton(item)
                    this.artistsButtons.push(item)
                })
                this.artistsText = this.artistsString()
                this.searchClearButton = new ClearButton(this.searchEntry)
                this.searchDiv.appendChild(this.searchClearButton.element)
                this.updateArtist(false)
            }

            this.update()
            setInterval(() => this.update(), 500)

        }

        static trim() {
            if (this.editEntry) this.editEntry.value = this.editEntry.value.split(/\s+/g).join(" ").trim()
            if (this.searchEntry) this.searchEntry.value = this.searchEntry.value.split(/\s+/g).join(" ").trim()
        }

        static update() {
            this.editTagButtons.forEach(item => {
                item.state = this.includes(true, item.text)
                item.update()
            })
            this.searchTagButtons.forEach(item => {
                item.state = this.includes(false, item.text)
                item.update()
            })
            this.artistsButtons.forEach(item => {
                item.state = this.includes(false, item.text)
                item.update()
            })
            this.artistsText = this.artistsString()
        }

        static includes(edit, text) {
            return (edit ? this.editEntry : this.searchEntry).value.split(" ").includes(text) ? 1 : (edit ? this.editEntry : this.searchEntry).value.split(" ").includes(`-${text}`) ? 2 : 0
        }

        static replace(edit, oldValue, newValue) {
            (edit ? this.editEntry : this.searchEntry).value = (edit ? this.editEntry : this.searchEntry).value.split(" ").map(item => ((item == oldValue) ? newValue : item)).join(" ")
            this.trim()
            this.update()
        }

        static add(edit, value) {
            (edit ? this.editEntry : this.searchEntry).value += value
            this.trim()
            this.update()
        }

        static updateArtist() {
            this.artistsButtons.forEach(item => {
                if (this.artistsToggleButton.state) {
                    this.searchDiv.insertBefore(item.element, this.searchClearButton.element)
                }
                else {
                    try {
                        this.searchDiv.removeChild(item.element)
                    }
                    catch {}
                }
            })
            this.artistsToggleButton.update()
            localStorage.setItem("artState", +this.artistsToggleButton.state)
        }

        static updateArtists() {
            let value = this.artistsString()
            if (this.artistsText == value) return
            if (this.artistsText) {
                this.searchEntry.value = this.searchEntry.value.replaceAll(this.artistsText, value)
                this.trim()
                this.update()
            }
            else this.add(false, ` ${value}`)
        }

        static artistsString() {
            let enabled = this.artistsButtons.filter(item => item.state).map(item => item.text)
            if (enabled.length == 0) return ""
            if (enabled.length == 1) return enabled[0]
            return `( ${enabled.join(' ~ ')} )`
        }
    }


    class Button {
        constructor (text, flat = false, weight = "", radius = "10px") {
            this.element = document.createElement("super")
            this.text = text
            this.state = 0
            this.flat = flat
            this.element.innerHTML = text
            this.element.className = "super"
            this.element.style.cursor = "pointer"
            this.element.style.display = "block"
            this.element.style.userSelect = "none"
            this.element.style.textAlign = "center"
            this.element.style.fontFamily = "Verdana"
            this.element.style.fontWeight = weight
            this.element.style.marginTop = "2px"
            this.element.style.borderRightWidth = "1px"
            this.element.style.borderLeftWidth = "1px"
            this.element.style.borderTopWidth = "1px"
            this.element.style.borderBottomWidth = "1px"
            this.element.style.borderRightStyle = "solid"
            this.element.style.borderLeftStyle = "solid"
            this.element.style.borderTopStyle = "solid"
            this.element.style.borderBottomStyle = "solid"
            this.element.style.borderRadius = radius
            this.element.onclick = () => this.onClick()
            this.update()
        }
        update() {
            this.element.style.color = foregroundColors[+this.state]
            this.element.style.backgroundColor = backgroundColors[+this.state]
        }
        stateBump() {
            this.state = (this.state + 1) % (this.flat ? 2 : 3)
            this.update()
        }
        onClick() {
            this.stateBump()
        }

    }


    class ClearButton extends Button{
        constructor (entry) {
            super("Clear", false, "bold", null)
            this.entry = entry
            this.state = 2
            this.update()
            this.element.style.marginTop = "9px"
            this.element.style.marginBottom = "5px"
        }
        onClick() {
            this.entry.value = ""
            Manager.update()
        }
    }


    class EditTagButton extends Button {
        constructor (text) {
            super(text, true)
        }
        onClick() {
            super.onClick()
            if (this.state == 1) {
                Manager.add(true, ` ${this.text}`)
            }
            else {
                Manager.replace(true, `${this.text}`, ``)
            }
        }
    }


    class SearchTagButton extends Button {
        constructor (text, flat) {
            super(text, flat ?? text.includes(":"))
        }
        onClick() {
            super.onClick()
            if (this.state == 1) {
                Manager.add(false, ` ${this.text}`)
            }
            else if (this.state == 2) {
                Manager.replace(false, `${this.text}`, `-${this.text}`)
            }
            else {
                Manager.replace(false, `-${this.text}`, ``)
                Manager.replace(false, `${this.text}`, ``)
            }
        }
    }


    class ArtistsToggleButton extends Button {
        constructor (state) {
            super("ARTISTS", true, "bold", null)
            this.state = state
        }
        onClick() {
            super.onClick()
            Manager.updateArtist()
        }
    }


    class ArtistButton extends Button {
        constructor (text) {
            super(text, true)
        }
        update() {
            super.update()

        }
        onClick() {
            super.onClick()
            Manager.updateArtists()
        }
    }


    // Comments date
    if (url.includes("page=comment")) {
        var div = document.querySelector("#comment-list");
        if (div) {
            div.innerHTML = div.innerHTML.replace(/\b([A-Z])\w{2}\s\d{1,2},\s\d\d\d\d/g, el => {
                el = el.replaceAll(",","").split(' ')
                return date(1, 0, 2, el)
            })
            div.innerHTML = div.innerHTML.replace(/\b\d\d\d\d-\d\d-\d\d\b/g, el => {
                el = el.split('-')
                return date(2, 1, 0, el)
            })
        }
    }

    // Forum date
    if (url.includes("page=forum")) {
        var div = document.querySelector("#forum");
        if (div) {
            div.innerHTML = div.innerHTML.replace(/\b\w{3}, \w{3} \d\d '\d\d\b/g, el => {
                el = el.replaceAll("'", "").replaceAll(",", "").split(' ')
                return date(2, 1, 3, el)
            })
            div.innerHTML = div.innerHTML.replace(/\b\d\d\/\d\d\/\d\d\b/g, el => {
                el = el.split('/')
                return date(1, 0, 2, el)
            })
        }
    }

    // Post date
    if (url.includes("page=post")) {
        var div = document.querySelector("#stats");
        if (div) {
            div.innerHTML = div.innerHTML.replace(/\b\d\d\d\d-\d\d-\d\d\b/g, el => {
                el = el.split("-")
                return date(2, 1, 0, el)
            })
        }
        var div = document.querySelector("#post-comments");
        if (div) {
            div.innerHTML = div.innerHTML.replace(/\b\d\d\d\d-\d\d-\d\d\b/g, el => {
                el = el.split("-")
                return date(2, 1, 0, el)
            })
        }
    }

    Manager.init()

})()