main 4b5c888921b3 cached
31 files
141.2 KB
47.1k tokens
130 symbols
1 requests
Download .txt
Repository: coderzhaoziwei/yande-re-chinese-patch
Branch: main
Commit: 4b5c888921b3
Files: 31
Total size: 141.2 KB

Directory structure:
gitextract_unlq4rb5/

├── .editorconfig
├── .gitignore
├── .vscode/
│   └── custom.code-snippets
├── bundle/
│   ├── index.js
│   └── index.user.js
├── config/
│   └── rollup.config.js
├── package.json
├── readme.md
├── readme_main.md
├── script/
│   ├── action.js
│   ├── bundle.sh
│   └── replace.js
├── source/
│   ├── app.js
│   ├── browse.js
│   ├── data/
│   │   ├── footers.json
│   │   ├── menus.json
│   │   └── tags.json
│   ├── hotkey.js
│   ├── html/
│   │   ├── body.html
│   │   ├── head.html
│   │   └── options.html
│   ├── index.js
│   ├── options.js
│   ├── post.js
│   ├── style/
│   │   └── fix.css
│   ├── style.js
│   └── translate.js
└── temp/
    ├── css_text_loader.js
    ├── index.js
    ├── index.user.js
    └── webpack.config.js

================================================
FILE CONTENTS
================================================

================================================
FILE: .editorconfig
================================================
# 编辑器插件 EditorConfig 的配置文件

root = true

[*]
charset = utf-8                 # 字符集
indent_style = space            # 缩进样式
indent_size = 2                 # 缩进尺寸
trim_trailing_whitespace = true # 清理行尾空白
end_of_line = lf                # 换行符
insert_final_newline = true     # 文末插入空白行


================================================
FILE: .gitignore
================================================
node_modules
yarn-error.log
.DS_Store


================================================
FILE: .vscode/custom.code-snippets
================================================
{
  "/* 单行注释 */": {
    "scope": "javascript,typescript,css",
    "prefix": "ll",
    "body": ["/* ${1:comments} */"],
    "description": "创建单行注释",
  },
  "/* 多行注释 */": {
    "scope": "javascript,typescript",
    "prefix": "lll",
    "body": ["/**", " * ${1:comments}", " */"],
    "description": "创建多行注释",
  },
  "console.log()": {
		"scope": "javascript,typescript",
		"prefix": "con",
		"body": ["console.log($1)", ""],
  },
  "导入模块": {
		"scope": "javascript,typescript",
		"prefix": "imp",
		"body": ["import $1 from '$1'", ""],
  },
}


================================================
FILE: bundle/index.js
================================================
// ==UserScript==
// @name         Yande.re 简体中文
// @namespace    com.coderzhaoziwei.yandere
// @version      2.1.47
// @author       Coder Zhao coderzhaoziwei@outlook.com
// @description  中文标签 | 界面优化 | 高清大图 | 键盘翻页 | 流体布局
// @homepage     https://greasyfork.org/scripts/421970
// @license      MIT
// @match        https://yande.re/*
// @exclude      https://yande.re/forum/*
// @match        https://konachan.com/*
// @exclude      https://konachan.com/forum/*
// @match        https://konachan.net/*
// @exclude      https://konachan.net/forum/*
// @supportURL   https://github.com/coderzhaoziwei/yande-re-chinese-patch/issues
// @grant        GM_download
// ==/UserScript==

/* eslint-env es2022 */
/* global jQuery:readonly */
/* global Vue:readonly */
/* global Vuetify:readonly */
/* global VueMasonry:readonly */

(function () {
  'use strict';

  const initStyle = function() {
    document.head.insertAdjacentHTML("beforeend", `<style>[{ path: "source/style/fix.css" }]</style>`);
  };

  const initHotKey = function() {
    window.addEventListener("keyup", function(event) {
      console.log('keyup:', event.key);
      if (/^(TEXTAREA|INPUT|SELECT|BUTTON)$/.test(document.activeElement.tagName)) return
      const prev = document.querySelector(".pagination>.previous_page") || jQuery("li:contains('Previous') a[href]")[0];
      if (prev && (event.key == "ArrowLeft" || event.key == "a" || event.key == "A")) {
        prev.click();
        return event.preventDefault()
      }
      const next = document.querySelector(".pagination>.next_page") || jQuery("li:contains('Next') a[href]")[0];
      if (next && (event.key == "ArrowRight" || event.key === "d" || event.key == "D")) {
        next.click();
        return event.preventDefault()
      }
      const show = document.querySelector("#png") || document.querySelector("#highres");
      if (show && (event.key === "s" || event.key === "S")) {
        show.click();
        return event.preventDefault()
      }
      const where = jQuery("li:contains('Source:') a")[0];
      if (where && (event.key === "w" || event.key === "W")) {
        where.click();
        return event.preventDefault()
      }
    });
    const sidebar = document.querySelector("#post-list > div.sidebar") || document.querySelector("#post-view > div.sidebar");
    if (sidebar) {
      sidebar.insertAdjacentHTML("beforeend", "<div>" +
        "<h5>快捷键说明</h5>" +
        "<div style='color: #ee8888'>上一页:A / ←</div>" +
        "<div style='color: #ee8888'>下一页:D / →</div>" +
        "<div style='color: #ee8888'>显示当前作品原图:S</div>" +
        "<div style='color: #ee8888'>显示当前作品来源:W</div>" +
      "</div>");
    }
  };

  class Post {
    constructor(data) {
      if (typeof data !== "object") data = {};
      this.id = data.id || 0;
      this.score = data.score || 0;
      this.tags = data.tags || "";
      this.source = data.source || "";
      this.author = data.author || "";
      this.creatorId = data.creator_id || 0;
      this.createdAt = data.created_at || 0;
      this.updatedAt = data.updated_at || 0;
      this.rating = data.rating || "s";
      this.fileUrl = data.file_url || "";
      this.fileExt = data.file_ext || "";
      this.fileSize = data.file_size || 0;
      this.width = data.width || 0;
      this.height = data.height || 0;
      this.jpegUrl = data.jpeg_url || "";
      this.jpegSize = data.jpeg_file_size || 0;
      this.jpegWidth = data.jpeg_width || 0;
      this.jpegHeight = data.jpeg_height || 0;
      this.sampleUrl = data.sample_url;
      this.sampleSize = data.sample_file_size || 0;
      this.sampleWidth = data.sample_width || 0;
      this.sampleHeight = data.sample_height || 0;
      this.previewUrl = data.preview_url;
      this.previewWidth = data.actual_preview_width || 0;
      this.previewHeight = data.actual_preview_height || 0;
      this.favorite = false;
    }
    get isRatingS() {
      return this.rating === "s"
    }
    get isRatingQ() {
      return this.rating === "q"
    }
    get isRatingE() {
      return this.rating === "e"
    }
    get aspectRatio() {
      return this.width / this.height
    }
    getSizeText(size) {
      if (size > 1024 * 1024) {
        return (size / (1024 * 1024)).toFixed(2) + "MB"
      }
      if (size > 1024) {
        return (size / 1024).toFixed(2) + "KB"
      }
      return (size).toFixed(2) + "B"
    }
    get sampleSizeText() {
      return this.getSizeText(this.sampleSize)
    }
    get sampleDownloadText() {
      return `下载缩略图 ${this.sampleWidth}×${this.sampleHeight} [${this.sampleSizeText}]`
    }
    get sampleDownloadName() {
      return `${location.hostname}.${this.id}.${this.sampleWidth}x${this.sampleHeight}`.replace(/\./g, "_")
    }
    get jpegSizeText() {
      return this.getSizeText(this.jpegSize)
    }
    get jpegDownloadText() {
      return `下载高清图 ${this.jpegWidth}×${this.jpegHeight} [${this.jpegSizeText}]`
    }
    get jpegDownloadName() {
      return `${location.hostname}.${this.id}.${this.jpegWidth}x${this.jpegHeight}`.replace(/\./g, "_")
    }
    get fileSizeText() {
      return this.getSizeText(this.fileSize)
    }
    get fileDownloadText() {
      return `下载原文件 ${this.width}×${this.height} [${this.fileSizeText}] ${this.fileExt.toUpperCase()}`
    }
    get fileDownloadName() {
      return `${location.hostname}.${this.id}.${this.width}x${this.height}`.replace(/\./g, "_")
    }
    get createdTime() {
      const date = new Date(this.createdAt * 1000);
      return `${date.toLocaleDateString()} ${date.toLocaleTimeString("en-DE")}`
    }
    get updatedTime() {
      const date = new Date(this.updatedAt * 1000);
      return `${date.toLocaleDateString()} ${date.toLocaleTimeString("en-DE")}`
    }
    get sourceUrl() {
      if (/^https:\/\/i\.pximg\.net\/img-original\/img\/[\d\/]{19}\/([\d]{1,})_p[\d]{1,}\.(jpg|png)$/.test(this.source)) {
        const pid = RegExp.$1;
        return `https://pixiv.net/artworks/${pid}`
      }
      return this.source
    }
  }

  const App = {
    template: "#app-template",
    data() {
      return {
        showDrawer: false,
        showImageSelected: false,
        showImageInfo: true,
        showRatingQ: JSON.parse(localStorage.getItem("showRatingQ") || "true"),
        showRatingE: JSON.parse(localStorage.getItem("showRatingE") || "false"),
        imageList: [],
        imageSelectedIndex: 0,
        imageSelectedDetail: {},
        params: new URLSearchParams(location.search),
        requestState: false,
        requestStop: false,
        innerWidth: window.innerWidth,
        innerHeight: window.innerHeight,
        imageCountInRow: JSON.parse(localStorage.getItem("imageCountInRow") || "3"),
        imageQualityHigh: JSON.parse(localStorage.getItem("imageQualityHigh") || "false"),
        showFavoriteSuccess: false,
      }
    },
    computed: {
      isMobile() {
        try {
          return this.$vuetify.breakpoint.mobile
        } catch(error) {
          return false
        }
      },
      title() {
        return `${this.imageList.length} Posts`
      },
      version() {
        return GM_info.script.version
      },
      imageSelected() {
        return this.imageList[this.imageSelectedIndex] || new Post()
      },
      imageSelectedWidth() {
        const width = parseInt(Math.min(this.innerWidth * 0.9, this.imageSelected.sampleWidth));
        const height = Math.min(this.innerHeight * 0.9, this.imageSelected.sampleHeight);
        const width2 = parseInt(height * this.imageSelected.aspectRatio);
        return Math.min(width, width2)
      },
      imageSelectedHeight() {
        const width = Math.min(this.innerWidth * 0.9, this.imageSelected.sampleWidth);
        const height = parseInt(Math.min(this.innerHeight * 0.9, this.imageSelected.sampleHeight));
        const height2 = parseInt(width / this.imageSelected.aspectRatio);
        return Math.min(height, height2)
      },
    },
    watch: {
      showRatingQ(value) {
        localStorage.setItem("showRatingQ", JSON.stringify(value));
      },
      showRatingE(value) {
        localStorage.setItem("showRatingE", JSON.stringify(value));
      },
      imageCountInRow(value) {
        localStorage.setItem("imageCountInRow", JSON.stringify(value));
      },
      imageQualityHigh(value) {
        localStorage.setItem("imageQualityHigh", JSON.stringify(value));
      },
      showFavoriteSuccess(value) {
        console.log('showFavoriteSuccess: ', value);
      },
      showImageSelected(value) {
        if (!value) {
          this.imageSelectedDetail = {};
          return
        }
        this.getPostDetail(this.imageSelected.id).then(res => {
          if (!res) return
          this.imageSelectedDetail = res;
        });
      }
    },
    methods: {
      async request() {
        this.requestState = true;
        const url = location.origin + location.pathname + ".json?" + this.params.toString();
        const response = await new Promise(resolve => {
          console.log(url);
          jQuery.get(url, data => resolve(data));
        });
        if (response instanceof Array && response.length > 0) {
          window.history.pushState("", "", location.pathname + "?" + this.params.toString());
          response.forEach(item => this.imageList.push(new Post(item)));
          const page = Number(this.params.get("page")) || 1;
          this.params.set("page", page + 1);
          setTimeout(() => (this.requestState = false), 1000);
        } else {
          this.requestStop = true;
        }
      },
      download(src, filename) {
        const match = src.match(/[.](?<extension>png|jpg|jpeg)$/);
        if (match) {
          const extension = match.groups.extension;
          GM_download(src, filename + "." + extension);
        } else {
          GM_download(src, filename);
        }
      },
      onFavorite(id) {
        $.ajax({
          method: 'POST',
          url: "https://yande.re/post/vote.json",
          beforeSend: xhr => xhr.setRequestHeader('x-csrf-token', window.csrfToken),
          data: { id, score: 3 },
          success: data => {
            if (data.success === true) {
              this.imageList[this.imageSelectedIndex].favorite = true;
              this.imageSelectedDetail.favorite = true;
            }
          },
        });
      },
      async getPostDetail(id) {
        try {
          if (!id) return
          const response = await fetch(`/post.json?api_version=2&tags=id:${id}&include_tags=1&include_votes=1`);
          const result = await response.json();
          return {
            favorite: result.votes[id] == 3,
            artist: Object.keys(result.tags).find(k => result.tags[k] == 'artist')
          }
        } catch (error) {
          console.log('getPostDetail error:', error);
        }
      }
    },
    mounted() {
      const timeInterval = setInterval(() => {
        if (this.requestStop === true) {
          clearInterval(timeInterval);
          return
        }
        const scrollTop = document.documentElement.scrollTop;
        const scrollHeight = document.documentElement.scrollHeight;
        const height = window.innerHeight;
        if (scrollTop + height >= scrollHeight * 0.75) {
          if (this.requestState === false) {
            this.request();
          }
        }
      }, 1000);
      window.addEventListener("resize", () => {
        this.innerWidth = window.innerWidth;
        this.innerHeight = window.innerHeight;
      });
    },
  };

  async function enterBrowseMode() {
    function getScript(url) {
      return new Promise(resolve => jQuery.getScript(url, () => resolve()))
    }
    await getScript("https://cdn.jsdelivr.net/npm/vue@2.6.12/dist/vue.min.js");
    await getScript("https://cdn.jsdelivr.net/npm/vuetify@2.5.0/dist/vuetify.min.js");
    await getScript("https://cdn.jsdelivr.net/npm/vue-masonry-css@1.0.3/dist/vue-masonry.min.js");
    await getScript("https://cdn.jsdelivr.net/npm/jquery@3.6.0/dist/jquery.min.js");
    window.csrfToken = jQuery('[name="csrf-token"]').attr('content');
    document.head.innerHTML = `[{ path: "source/html/head.html" }]`;
    document.body.innerHTML = `[{ path: "source/html/body.html" }]`;
    Vue.use(VueMasonry);
    new Vue({
      vuetify: new Vuetify({
        theme: { dark: true },
      }),
      render: h => h(App)
    }).$mount("#app");
  }

  const onChangeLeftBar = function() {
    const value = Boolean(document.getElementById("showLeftBar").selectedIndex);
    localStorage.setItem("showLeftBar", JSON.stringify(value));
    const element = document.querySelector("#post-list > .sidebar");
    element.setAttribute("show-left-bar", value);
    console.log("showLeftBar", value);
  };
  const onChangeRatingE = function() {
    const value = Boolean(document.getElementById("showRatingE").selectedIndex);
    localStorage.setItem("showRatingE", JSON.stringify(value));
    const elementList = document.querySelectorAll(".javascript-hide");
    elementList.forEach(element => element.setAttribute("show-rating-e", value));
    console.log("showRatingE", value);
  };
  const onChangeImageHD = function() {
    const index = document.getElementById("showImageHD").selectedIndex;
    const elementList = document.querySelectorAll("#post-list-posts > li > .inner");
    elementList.forEach(element => element.setAttribute("show-image-hd", index));
    localStorage.setItem("showImageHD", JSON.stringify(index));
    console.log("showImageHD", index);
  };
  let taskArray = [];
  let maxLoadingSampleNum = 4;
  let doLoadSampleUrl = () => {
    let loadingNum = 0;
    let loadSampleUrl = () => {
      if (taskArray.length == 0) return
      loadingNum++;
      let { element, sampleUrl } = taskArray.shift();
      element.onerror = () => {
        element.src = sampleUrl;
      };
      element.onload = () => {
        loadingNum--;
      };
      element.src = sampleUrl;
    };
    setInterval(() => {
      if (taskArray.length == 0) return
      let needloadNum = maxLoadingSampleNum - loadingNum;
      while (needloadNum--) {
        loadSampleUrl();
      }
    }, 1000);
  };
  const initOptions = function() {
    if (/^\/user\/show\/[\d]{1,}/.test(location.pathname)) return
    if (document.getElementById("post-list-posts") === null) return
    document.getElementById("post-list-posts").insertAdjacentHTML("beforebegin", `[{ path: "source/html/options.html" }]`);
    const imageList = document.querySelectorAll("img.preview");
    const samples = JSON.parse(localStorage.getItem("sample_urls"));
    imageList.forEach(element => {
      if (/\/post\/show\/([\d]{1,})/.test(element.nextElementSibling.innerText)) {
        const id = RegExp.$1;
        const sampleUrl = samples[id];
        if (sampleUrl !== undefined) {
          element.src = sampleUrl;
        }
      }
    });
    doLoadSampleUrl();
    document.getElementById("showLeftBar").addEventListener("change", onChangeLeftBar);
    document.getElementById("showRatingE").addEventListener("change", onChangeRatingE);
    document.getElementById("showImageHD").addEventListener("change", onChangeImageHD);
    const showLeftBar = JSON.parse(localStorage.getItem("showLeftBar") || "true");
    const showRatingE = JSON.parse(localStorage.getItem("showRatingE") || "true");
    const showImageHD = JSON.parse(localStorage.getItem("showImageHD") || "0");
    document.getElementById("showLeftBar").selectedIndex = showLeftBar;
    document.getElementById("showRatingE").selectedIndex = showRatingE;
    document.getElementById("showImageHD").selectedIndex = showImageHD;
    onChangeLeftBar();
    onChangeRatingE();
    onChangeImageHD();
    document.getElementById("enterBrowseMode").addEventListener("click", enterBrowseMode);
  };

  const tags = [{ path: "source/data/tags.json" }];
  const menus = [{ path: "source/data/menus.json" }];
  const footers = [{ path: "source/data/footers.json" }];
  const translateTags = function() {
    const elementList = Array.from(document.getElementsByTagName("a"));
    elementList.forEach(element => {
      const href = element.getAttribute("href");
      if (typeof href === "string" && /^\/post\?tags=(\S+)$/.test(href)) {
        const en = RegExp.$1;
        const cn = tags[en];
        if (cn) {
          element.innerText = `[${cn}]${en.replace(/_/g, " ")}`;
        }
      }
    });
  };
  const translateMenus = function() {
    const mainMenuList = Array.from(document.querySelectorAll("#main-menu>ul>li>a"));
    const subMenuList = Array.from(document.querySelectorAll("ul.submenu>li>a"));
    const elementList = [...mainMenuList, ...subMenuList];
    elementList.forEach(element => {
      if (element.getAttribute("href") === "#") return
      const en = element.innerText;
      const cn = menus[en];
      if (cn) {
        element.innerText = cn;
      }
    });
  };
  const translateNotice = function() {
    const elementList = Array.from(document.querySelectorAll(".status-notice"));
    elementList.forEach(element => {
      console.log(element.innerHTML);
      element.innerHTML = element.innerHTML
        .replace(/^[\s]+This image has been resized. Click on the /, "这张图片已经被压缩,单击侧边栏中的")
        .replace(/View larger version/, "显示高清图")
        .replace(/ link in the sidebar for a high-quality version./, "可以获取更高质量的版本。")
        .replace(/Hide this message<\/a>\./, "不再提醒</a>")
        .replace(/This post belongs to a /, "这张图片从属于一个")
        .replace(/parent post<\/a>\./, "相关父作品</a>。")
        .replace(/This post has /, "这张图片从属于一个")
        .replace(/child posts<\/a>\. \(post #/, "作品集</a>。相关子作品:")
        .replace(/a child post<\/a>\. \(post #/, "作品集</a>。相关子作品:")
        .replace(/<\/a>, <a /, "</a> | <a ")
        .replace(/<\/a>\)/, "</a>");
    });
  };
  const translateButtons = function() {
    [
      ['#highres-show', 'View larger version', '显示高清图'],
      ['#highres', 'Download larger version', '下载高清图'],
      ['#png', 'Download PNG', '下载 PNG 图'],
      ['li#add-to-favs>a', 'Add to favorites', '添加收藏'],
      ['li#set-avatar>a', 'Set avatar', '设置头像'],
      ['h4>a.js-posts-show-edit-tab', 'Edit', '编辑'],
      ['h4>a.js-posts-show-comments-tab', 'Respond', '评论'],
      ['.pagination>.previous_page', '← Previous', '上一页'],
      ['.pagination>.next_page', 'Next →', '下一页'],
    ].forEach(data => {
      const [selector, en, cn] = data;
      const element = document.querySelector(selector);
      if (element) {
        element.innerText = element.innerText.replace(en, cn);
      }
    });
  };
  const translateFooters = function() {
    const elementList = Array.from(document.querySelectorAll('#subnavbar>li>a'));
    elementList.forEach(element => {
      const en = element.innerText;
      const cn = footers[en];
      if (cn) {
        element.innerText = cn;
      }
    });
  };
  const initTranslate = function() {
    translateTags();
    translateMenus();
    translateNotice();
    translateButtons();
    translateFooters();
  };

  jQuery(document).ready(function() {
    initStyle();
    initHotKey();
    initOptions();
    initTranslate();
    if (document.cookie.includes('locale=zh_CN') === false) {
      document.cookie = "locale=zh_CN";
      location.href = location.href;
    }
  });

}());


================================================
FILE: bundle/index.user.js
================================================
// ==UserScript==
// @name         Yande.re 简体中文
// @namespace    com.coderzhaoziwei.yandere
// @version      2.1.47
// @author       Coder Zhao coderzhaoziwei@outlook.com
// @description  中文标签 | 界面优化 | 高清大图 | 键盘翻页 | 流体布局
// @homepage     https://greasyfork.org/scripts/421970
// @license      MIT
// @match        https://yande.re/*
// @exclude      https://yande.re/forum/*
// @match        https://konachan.com/*
// @exclude      https://konachan.com/forum/*
// @match        https://konachan.net/*
// @exclude      https://konachan.net/forum/*
// @supportURL   https://github.com/coderzhaoziwei/yande-re-chinese-patch/issues
// @grant        GM_download
// ==/UserScript==

/* eslint-env es2022 */
/* global jQuery:readonly */
/* global Vue:readonly */
/* global Vuetify:readonly */
/* global VueMasonry:readonly */

(function () {
  'use strict';

  const initStyle = function() {
    document.head.insertAdjacentHTML("beforeend", `<style>
body {
  font-size: 12px;
  padding: 0 0.5rem;
}
body::-webkit-scrollbar {
  display: none;
  width: 0px !important;
}
/* 标题居中 */
div#header {
  margin: 0;
}
div#header > div#title {
  display: flex;
  place-content: center;
  margin: 0 !important;
  height: fit-content;
}
div#header > div#title > h2#site-title {
  display: flex !important;
  flex-direction: column;
}
div#header > div#title > h2#site-title > span {
  font-size: 12px;
  font-weight: normal;
  text-align: right;
}
div#header > div#main-menu {
  padding: 0 !important;
  margin: 0 !important;
  display: flex !important;
  justify-content: center;
  font-size: 14px;
  line-height: 2rem;
  height: 2rem;
}
div#header > div#main-menu > ul {
  margin: 0;
}
/* 通知 */
.status-notice {
  text-align: center;
}
/* 标签前缀 */
li.tag-type-artist a[href^="/post"]:not(.no-browser-link)::before {
  content: "[画师]";
}
li.tag-type-copyright a[href^="/post"]:not(.no-browser-link)::before {
  content: "[原作]";
}
li.tag-type-character a[href^="/post"]:not(.no-browser-link)::before {
  content: "[角色]";
}
li.tag-type-circle a[href^="/post"]:not(.no-browser-link)::before {
  content: "[公司]";
}
/* 图区 */
#post-list {
  display: flex;
  flex-direction: row;
}
#post-list > .sidebar {
  width: auto;
  max-width: 200px;
  flex: 0 0 auto;
}
#post-list > .content {
  width: auto;
  flex: 1 1 auto;
}
#post-list > div.lsidebar {
  display: none;
}
ul#post-list-posts {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  place-content: center;
  place-items: center;
}
ul#post-list-posts > li {
  width: fit-content !important;
  height: 100%;
  margin: 0 !important;
  border: none;
}
ul#post-list-posts > li > div.inner {
  width: auto !important;
  height: fit-content !important;
}
ul#post-list-posts > li > a.directlink {
  font-size: 12px;
  height: 12px;
  line-height: 12px;
  margin: 0;
  padding: 0;
  overflow: hidden;
  background: rgb(16, 16, 16);
}
ul#post-list-posts > li > a.directlink > span.directlink-res {
  display: inline;
}
ul#post-list-posts > li > a.directlink > span.directlink-info {
  display: none;
}
ul#post-list-posts > li > div.inner > a.thumb {
  height: auto;
}
ul#post-list-posts > li > div.inner > a.thumb > img.preview {
  margin: 0 !important; /* @konachan */
  border: none;
}
/* 分页器 */
div#paginator {
  padding: 0;
}
div#paginator > div.pagination {
  line-height: 2rem;
}
/* 页脚 */
#content > div:nth-child(2) > div.sidebar {
  display: none;
}
#content div.footer {
  font-size: 14px;
  margin: 1rem;
}

/* show-left-bar */
.sidebar[show-left-bar=false] {
  display: none !important;
}
/* show-rating-e */
.javascript-hide[show-rating-e=true] {
  display: block !important;
  position: relative;
}
.javascript-hide[show-rating-e=true]::after {
  content: "";
  position: absolute;
  width: 100%;
  height: 100%;
  top: 0;
  box-shadow: 0px 0px 12px rgb(255, 0, 0) inset;
  pointer-events: none;
}
/* show-image-hd */
#post-list-posts > li > .inner[show-image-hd="1"] {
  zoom: 2;
}
#post-list-posts > li > .inner[show-image-hd="2"] {
  zoom: 3;
}
#post-list-posts > li > .inner[show-image-hd="3"] {
  zoom: 4;
}
</style>`);
  };

  const initHotKey = function() {
    window.addEventListener("keyup", function(event) {
      console.log('keyup:', event.key);
      if (/^(TEXTAREA|INPUT|SELECT|BUTTON)$/.test(document.activeElement.tagName)) return
      const prev = document.querySelector(".pagination>.previous_page") || jQuery("li:contains('Previous') a[href]")[0];
      if (prev && (event.key == "ArrowLeft" || event.key == "a" || event.key == "A")) {
        prev.click();
        return event.preventDefault()
      }
      const next = document.querySelector(".pagination>.next_page") || jQuery("li:contains('Next') a[href]")[0];
      if (next && (event.key == "ArrowRight" || event.key === "d" || event.key == "D")) {
        next.click();
        return event.preventDefault()
      }
      const show = document.querySelector("#png") || document.querySelector("#highres");
      if (show && (event.key === "s" || event.key === "S")) {
        show.click();
        return event.preventDefault()
      }
      const where = jQuery("li:contains('Source:') a")[0];
      if (where && (event.key === "w" || event.key === "W")) {
        where.click();
        return event.preventDefault()
      }
    });
    const sidebar = document.querySelector("#post-list > div.sidebar") || document.querySelector("#post-view > div.sidebar");
    if (sidebar) {
      sidebar.insertAdjacentHTML("beforeend", "<div>" +
        "<h5>快捷键说明</h5>" +
        "<div style='color: #ee8888'>上一页:A / ←</div>" +
        "<div style='color: #ee8888'>下一页:D / →</div>" +
        "<div style='color: #ee8888'>显示当前作品原图:S</div>" +
        "<div style='color: #ee8888'>显示当前作品来源:W</div>" +
      "</div>");
    }
  };

  class Post {
    constructor(data) {
      if (typeof data !== "object") data = {};
      this.id = data.id || 0;
      this.score = data.score || 0;
      this.tags = data.tags || "";
      this.source = data.source || "";
      this.author = data.author || "";
      this.creatorId = data.creator_id || 0;
      this.createdAt = data.created_at || 0;
      this.updatedAt = data.updated_at || 0;
      this.rating = data.rating || "s";
      this.fileUrl = data.file_url || "";
      this.fileExt = data.file_ext || "";
      this.fileSize = data.file_size || 0;
      this.width = data.width || 0;
      this.height = data.height || 0;
      this.jpegUrl = data.jpeg_url || "";
      this.jpegSize = data.jpeg_file_size || 0;
      this.jpegWidth = data.jpeg_width || 0;
      this.jpegHeight = data.jpeg_height || 0;
      this.sampleUrl = data.sample_url;
      this.sampleSize = data.sample_file_size || 0;
      this.sampleWidth = data.sample_width || 0;
      this.sampleHeight = data.sample_height || 0;
      this.previewUrl = data.preview_url;
      this.previewWidth = data.actual_preview_width || 0;
      this.previewHeight = data.actual_preview_height || 0;
      this.favorite = false;
    }
    get isRatingS() {
      return this.rating === "s"
    }
    get isRatingQ() {
      return this.rating === "q"
    }
    get isRatingE() {
      return this.rating === "e"
    }
    get aspectRatio() {
      return this.width / this.height
    }
    getSizeText(size) {
      if (size > 1024 * 1024) {
        return (size / (1024 * 1024)).toFixed(2) + "MB"
      }
      if (size > 1024) {
        return (size / 1024).toFixed(2) + "KB"
      }
      return (size).toFixed(2) + "B"
    }
    get sampleSizeText() {
      return this.getSizeText(this.sampleSize)
    }
    get sampleDownloadText() {
      return `下载缩略图 ${this.sampleWidth}×${this.sampleHeight} [${this.sampleSizeText}]`
    }
    get sampleDownloadName() {
      return `${location.hostname}.${this.id}.${this.sampleWidth}x${this.sampleHeight}`.replace(/\./g, "_")
    }
    get jpegSizeText() {
      return this.getSizeText(this.jpegSize)
    }
    get jpegDownloadText() {
      return `下载高清图 ${this.jpegWidth}×${this.jpegHeight} [${this.jpegSizeText}]`
    }
    get jpegDownloadName() {
      return `${location.hostname}.${this.id}.${this.jpegWidth}x${this.jpegHeight}`.replace(/\./g, "_")
    }
    get fileSizeText() {
      return this.getSizeText(this.fileSize)
    }
    get fileDownloadText() {
      return `下载原文件 ${this.width}×${this.height} [${this.fileSizeText}] ${this.fileExt.toUpperCase()}`
    }
    get fileDownloadName() {
      return `${location.hostname}.${this.id}.${this.width}x${this.height}`.replace(/\./g, "_")
    }
    get createdTime() {
      const date = new Date(this.createdAt * 1000);
      return `${date.toLocaleDateString()} ${date.toLocaleTimeString("en-DE")}`
    }
    get updatedTime() {
      const date = new Date(this.updatedAt * 1000);
      return `${date.toLocaleDateString()} ${date.toLocaleTimeString("en-DE")}`
    }
    get sourceUrl() {
      if (/^https:\/\/i\.pximg\.net\/img-original\/img\/[\d\/]{19}\/([\d]{1,})_p[\d]{1,}\.(jpg|png)$/.test(this.source)) {
        const pid = RegExp.$1;
        return `https://pixiv.net/artworks/${pid}`
      }
      return this.source
    }
  }

  const App = {
    template: "#app-template",
    data() {
      return {
        showDrawer: false,
        showImageSelected: false,
        showImageInfo: true,
        showRatingQ: JSON.parse(localStorage.getItem("showRatingQ") || "true"),
        showRatingE: JSON.parse(localStorage.getItem("showRatingE") || "false"),
        imageList: [],
        imageSelectedIndex: 0,
        imageSelectedDetail: {},
        params: new URLSearchParams(location.search),
        requestState: false,
        requestStop: false,
        innerWidth: window.innerWidth,
        innerHeight: window.innerHeight,
        imageCountInRow: JSON.parse(localStorage.getItem("imageCountInRow") || "3"),
        imageQualityHigh: JSON.parse(localStorage.getItem("imageQualityHigh") || "false"),
        showFavoriteSuccess: false,
      }
    },
    computed: {
      isMobile() {
        try {
          return this.$vuetify.breakpoint.mobile
        } catch(error) {
          return false
        }
      },
      title() {
        return `${this.imageList.length} Posts`
      },
      version() {
        return GM_info.script.version
      },
      imageSelected() {
        return this.imageList[this.imageSelectedIndex] || new Post()
      },
      imageSelectedWidth() {
        const width = parseInt(Math.min(this.innerWidth * 0.9, this.imageSelected.sampleWidth));
        const height = Math.min(this.innerHeight * 0.9, this.imageSelected.sampleHeight);
        const width2 = parseInt(height * this.imageSelected.aspectRatio);
        return Math.min(width, width2)
      },
      imageSelectedHeight() {
        const width = Math.min(this.innerWidth * 0.9, this.imageSelected.sampleWidth);
        const height = parseInt(Math.min(this.innerHeight * 0.9, this.imageSelected.sampleHeight));
        const height2 = parseInt(width / this.imageSelected.aspectRatio);
        return Math.min(height, height2)
      },
    },
    watch: {
      showRatingQ(value) {
        localStorage.setItem("showRatingQ", JSON.stringify(value));
      },
      showRatingE(value) {
        localStorage.setItem("showRatingE", JSON.stringify(value));
      },
      imageCountInRow(value) {
        localStorage.setItem("imageCountInRow", JSON.stringify(value));
      },
      imageQualityHigh(value) {
        localStorage.setItem("imageQualityHigh", JSON.stringify(value));
      },
      showFavoriteSuccess(value) {
        console.log('showFavoriteSuccess: ', value);
      },
      showImageSelected(value) {
        if (!value) {
          this.imageSelectedDetail = {};
          return
        }
        this.getPostDetail(this.imageSelected.id).then(res => {
          if (!res) return
          this.imageSelectedDetail = res;
        });
      }
    },
    methods: {
      async request() {
        this.requestState = true;
        const url = location.origin + location.pathname + ".json?" + this.params.toString();
        const response = await new Promise(resolve => {
          console.log(url);
          jQuery.get(url, data => resolve(data));
        });
        if (response instanceof Array && response.length > 0) {
          window.history.pushState("", "", location.pathname + "?" + this.params.toString());
          response.forEach(item => this.imageList.push(new Post(item)));
          const page = Number(this.params.get("page")) || 1;
          this.params.set("page", page + 1);
          setTimeout(() => (this.requestState = false), 1000);
        } else {
          this.requestStop = true;
        }
      },
      download(src, filename) {
        const match = src.match(/[.](?<extension>png|jpg|jpeg)$/);
        if (match) {
          const extension = match.groups.extension;
          GM_download(src, filename + "." + extension);
        } else {
          GM_download(src, filename);
        }
      },
      onFavorite(id) {
        $.ajax({
          method: 'POST',
          url: "https://yande.re/post/vote.json",
          beforeSend: xhr => xhr.setRequestHeader('x-csrf-token', window.csrfToken),
          data: { id, score: 3 },
          success: data => {
            if (data.success === true) {
              this.imageList[this.imageSelectedIndex].favorite = true;
              this.imageSelectedDetail.favorite = true;
            }
          },
        });
      },
      async getPostDetail(id) {
        try {
          if (!id) return
          const response = await fetch(`/post.json?api_version=2&tags=id:${id}&include_tags=1&include_votes=1`);
          const result = await response.json();
          return {
            favorite: result.votes[id] == 3,
            artist: Object.keys(result.tags).find(k => result.tags[k] == 'artist')
          }
        } catch (error) {
          console.log('getPostDetail error:', error);
        }
      }
    },
    mounted() {
      const timeInterval = setInterval(() => {
        if (this.requestStop === true) {
          clearInterval(timeInterval);
          return
        }
        const scrollTop = document.documentElement.scrollTop;
        const scrollHeight = document.documentElement.scrollHeight;
        const height = window.innerHeight;
        if (scrollTop + height >= scrollHeight * 0.75) {
          if (this.requestState === false) {
            this.request();
          }
        }
      }, 1000);
      window.addEventListener("resize", () => {
        this.innerWidth = window.innerWidth;
        this.innerHeight = window.innerHeight;
      });
    },
  };

  async function enterBrowseMode() {
    function getScript(url) {
      return new Promise(resolve => jQuery.getScript(url, () => resolve()))
    }
    await getScript("https://cdn.jsdelivr.net/npm/vue@2.6.12/dist/vue.min.js");
    await getScript("https://cdn.jsdelivr.net/npm/vuetify@2.5.0/dist/vuetify.min.js");
    await getScript("https://cdn.jsdelivr.net/npm/vue-masonry-css@1.0.3/dist/vue-masonry.min.js");
    await getScript("https://cdn.jsdelivr.net/npm/jquery@3.6.0/dist/jquery.min.js");
    window.csrfToken = jQuery('[name="csrf-token"]').attr('content');
    document.head.innerHTML = `
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no, minimal-ui">
<title>Yande.re 简体中文</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/necolas/normalize.css/normalize.css">
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@mdi/font@5.9.55/css/materialdesignicons.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/vuetify@2.5.0/dist/vuetify.min.css">
<style>
::-webkit-scrollbar {
  display: none;
  width: 0px !important;
}
</style>
`;
    document.body.innerHTML = `
<div id="app"></div>

<script type="text/template" id="app-template">
<v-app>

  <v-app-bar app dense>
    <v-app-bar-nav-icon :x-small="isMobile" @click="showDrawer=!showDrawer"></v-app-bar-nav-icon>
    <v-toolbar-title :style="isMobile ? 'font-size: 12px;' : ''" v-text="title"></v-toolbar-title>
    <!-- 设置分级制度 -->
    <v-menu offset-y>
      <template v-slot:activator="{ on, attrs }">
        <v-btn :x-small="isMobile" class="white--text ml-2" dark v-bind="attrs" v-on="on">
          S{{ showRatingQ ? 'Q' : '' }}{{ showRatingE ? 'E' : '' }}
        </v-btn>
      </template>
      <v-list dense>
        <v-list-item dense>
          <v-list-item-title style="cursor: pointer;" @click="showRatingQ = !showRatingQ;">
            {{ showRatingQ ? '隐藏 Q 级内容' : '显示 Q 级内容' }}
          </v-list-item-title>
        </v-list-item>
        <v-list-item dense>
          <v-list-item-title style="cursor: pointer;" @click="showRatingE = !showRatingE;">
            {{ showRatingE ? '隐藏 E 级内容' : '显示 E 级内容' }}
          </v-list-item-title>
        </v-list-item>
      </v-list>
    </v-menu>
    <!-- 设置图片质量 -->
    <v-menu offset-y>
      <template v-slot:activator="{ on, attrs }">
        <v-btn :x-small="isMobile" class="white--text ml-2" dark v-bind="attrs" v-on="on">{{ imageQualityHigh ? 'HD' : '速' }}</v-btn>
      </template>
      <v-list dense>
        <v-list-item dense>
          <v-list-item-title style="cursor: pointer;" @click="imageQualityHigh = false;">
            图片质量:速览
          </v-list-item-title>
        </v-list-item>
        <v-list-item dense>
          <v-list-item-title style="cursor: pointer;" @click="imageQualityHigh = true;">
            图片质量:高清
          </v-list-item-title>
        </v-list-item>
      </v-list>
    </v-menu>
    <!-- 设置每行几张 -->
    <v-menu offset-y>
      <template v-slot:activator="{ on, attrs }">
        <v-btn :x-small="isMobile" class="white--text ml-2" dark v-bind="attrs" v-on="on">{{imageCountInRow}}列</v-btn>
      </template>
      <v-list dense>
        <v-list-item dense v-for="number in [1, 2, 3, 4, 5, 6, 8, 10, 12, 14, 16, 20]" :key="number">
          <v-list-item-title style="cursor: pointer;" @click="imageCountInRow = number;">
            {{ number }}列
          </v-list-item-title>
        </v-list-item>
      </v-list>
    </v-menu>

    <v-spacer></v-spacer>
    <v-btn
      :style="isMobile ? 'flex: 0 1 auto; overflow: hidden;' : ''" :x-small="isMobile"
      text v-text="'v' + version" color="#ffffff" disabled>
    </v-btn>
  </v-app-bar>

  <v-navigation-drawer v-model="showDrawer" app temporary>
    <v-list-item>
      <v-list-item-content>
        <v-list-item-title class="title">Yande.re 简体中文</v-list-item-title>
        <v-list-item-subtitle>浏览器脚本程序</v-list-item-subtitle>
      </v-list-item-content>
    </v-list-item>

    <v-divider></v-divider>

    <v-list dense nav>
      <v-list-item-content>
        <v-list-item-title class="title">设置</v-list-item-title>
        <v-list-item-subtitle></v-list-item-subtitle>
      </v-list-item-content>
      <!-- s -->
      <v-list-item link>
        <v-list-item-icon class="mr-2">
          <v-icon>mdi-check</v-icon>
        </v-list-item-icon>
        <v-list-item-content>
          <v-list-item-title>显示 S 分级内容</v-list-item-title>
          <v-list-item-subtitle>S(safe) 安全的全年龄内容</v-list-item-subtitle>
        </v-list-item-content>
      </v-list-item>
      <!-- q -->
      <v-list-item link @click="showRatingQ=!showRatingQ;">
        <v-list-item-icon class="mr-2">
          <v-icon v-text="showRatingQ ? 'mdi-check' : 'mdi-close'"></v-icon>
        </v-list-item-icon>
        <v-list-item-content>
          <v-list-item-title v-text="showRatingQ ? '显示 Q 分级内容' : '隐藏 Q 分级内容'"></v-list-item-title>
          <v-list-item-subtitle>Q(questionable) 疑似的成人内容</v-list-item-subtitle>
        </v-list-item-content>
      </v-list-item>
      <!-- e -->
      <v-list-item link @click="showRatingE=!showRatingE;">
        <v-list-item-icon class="mr-2">
          <v-icon v-text="showRatingE ? 'mdi-check' : 'mdi-close'"></v-icon>
        </v-list-item-icon>
        <v-list-item-content>
          <v-list-item-title v-text="showRatingE ? '显示 E 分级内容' : '隐藏 E 分级内容'"></v-list-item-title>
          <v-list-item-subtitle>E(explicit) 明确的成人内容</v-list-item-subtitle>
        </v-list-item-content>
      </v-list-item>
    </v-list>

    <v-divider></v-divider>

    <v-list dense nav>
      <v-list-item-content>
        <v-list-item-title class="title">关于</v-list-item-title>
        <v-list-item-subtitle></v-list-item-subtitle>
      </v-list-item-content>
      <v-list-item link @click="window.open('https://github.com/coderzhaoziwei/yande-re-chinese-patch/blob/main/readme.md')">
        <v-list-item-icon class="mr-2"><v-icon>mdi-file-document-outline</v-icon></v-list-item-icon>
        <v-list-item-content>
          <v-list-item-title>简介</v-list-item-title>
          <v-list-item-subtitle>说明文档 / 功能介绍</v-list-item-subtitle>
        </v-list-item-content>
      </v-list-item>
      <v-list-item link @click="window.open('https://github.com/coderzhaoziwei/yande-re-chinese-patch/issues')">
        <v-list-item-icon class="mr-2"><v-icon>mdi-github</v-icon></v-list-item-icon>
        <v-list-item-content>
          <v-list-item-title>反馈</v-list-item-title>
          <v-list-item-subtitle>发现错误 / 提出建议</v-list-item-subtitle>
        </v-list-item-content>
      </v-list-item>
      <v-list-item link @click="window.open('https://github.com/coderzhaoziwei/yande-re-chinese-patch')">
        <v-list-item-icon class="mr-2"><v-icon>mdi-star</v-icon></v-list-item-icon>
        <v-list-item-content>
          <v-list-item-title>Github</v-list-item-title>
          <v-list-item-subtitle>觉得好用就 Star 支持一下</v-list-item-subtitle>
        </v-list-item-content>
      </v-list-item>
      <v-list-item link>
        <v-list-item-icon class="mr-2"><v-icon>mdi-google-controller</v-icon></v-list-item-icon>
        <v-list-item-content>
          <v-list-item-title>QQ</v-list-item-title>
          <v-list-item-subtitle>3158492760</v-list-item-subtitle>
        </v-list-item-content>
      </v-list-item>
      <v-list-item link>
        <v-list-item-icon class="mr-2"><v-icon>mdi-email</v-icon></v-list-item-icon>
        <v-list-item-content>
          <v-list-item-title>邮箱</v-list-item-title>
          <v-list-item-subtitle>coderzhaoziwei@outlook.com</v-list-item-subtitle>
        </v-list-item-content>
      </v-list-item>
    </v-list>
  </v-navigation-drawer>

  <v-main app>
    <v-container class="pa-2" fluid>
      <masonry ref="masonry" :cols="imageCountInRow" gutter="8px" :key="imageCountInRow">
        <v-card class="mb-2" v-for="(image, index) in imageList" :key="index">
          <v-img
            :src="
              image.isRatingS || (image.isRatingQ && showRatingQ) || (image.isRatingE && showRatingE)
                ? (imageQualityHigh ? image.sampleUrl : image.previewUrl) : ''
            "
            :aspect-ratio="image.aspectRatio"
            @click="if(image.isRatingS||(image.isRatingQ && showRatingQ)||(image.isRatingE && showRatingE)){imageSelectedIndex=index;showImageSelected=true;}"
            @click.middle="imageSelectedIndex = index; window.open('/post/show/' + imageSelected.id)"
          >
            <template v-slot:placeholder>
              <v-row v-if="image.isRatingS||(image.isRatingQ && showRatingQ)||(image.isRatingE && showRatingE)"
                class="fill-height ma-0" align="center" justify="center"
              >
                <v-progress-circular indeterminate color="#ee8888"></v-progress-circular>
              </v-row>
            </template>
            <v-row
              v-if="(image.isRatingS||(image.isRatingQ && showRatingQ)||(image.isRatingE && showRatingE))===false"
              class="fill-height ma-0 text-h5" align="center" justify="center"
              style="color:#ee8888;"
              v-text="image.rating.toUpperCase()"
            ></v-row>
          </v-img>
        </v-card>
      </masonry>

      <div class="d-flex justify-center">
        <v-btn
          :disabled="requestState===false"
          color="#ee8888" text
          v-text="requestStop ? '下面没有了...' : requestState ? '正在加载中...' : ''"
        ></v-btn>
      </div>

      <v-dialog v-model="showImageSelected" :width="imageSelectedWidth" :height="imageSelectedHeight">
        <v-img
          :src="imageSelected.sampleUrl"
          :lazy-src="imageSelected.previewUrl"
          @click="showImageInfo = !showImageInfo;"
        >
          <div
            :style="showImageInfo
              ? 'display: flex; flex-direction: column; height: 100%; padding: 4px; grid-gap: 4px;'
              : 'display: none !important;'"
          >
            <div style="height: 100%; flex: 1 1 auto;"></div>

            <div style="display: flex; flex-direction: column; grid-gap: 4px;">
              <v-chip style="width: fit-content;" color="#009ff088" text-color="#ffffff" small
                v-text="imageSelected.sampleDownloadText"
                @click.stop="download(imageSelected.sampleUrl, imageSelected.sampleDownloadName)"
              ></v-chip>
              <v-chip style="width: fit-content;" color="#009ff088" text-color="#ffffff" small
                v-if="imageSelected.jpegSize !== 0"
                v-text="imageSelected.jpegDownloadText"
                @click.stop="download(imageSelected.jpegUrl, imageSelected.jpegDownloadName)"
              ></v-chip>
              <v-chip style="width: fit-content;" color="#009ff088" text-color="#ffffff" small
                v-text="imageSelected.fileDownloadText"
                @click.stop="download(imageSelected.fileUrl, imageSelected.fileDownloadName)"
              ></v-chip>
            </div>
            <div style="display: flex; grid-gap: 4px; flex-wrap: wrap;">
              <v-chip
                style="width: fit-content;" color="#ee888888" text-color="#ffffff" small
                v-text="imageSelected.id + ' ' + imageSelected.rating.toUpperCase()" @click.stop
              ></v-chip>
              <v-chip class="mr-1" style="width: fit-content;" color="#009ff088" text-color="#ffffff" small
                v-if="imageSelectedDetail.artist"
                @click.stop="window.open('/post?tags='+imageSelectedDetail.artist)"
              >画师 {{imageSelectedDetail.artist}}</v-chip>
              <v-chip class="mr-1" style="width: fit-content;" color="#009ff088" text-color="#ffffff" small
                v-if="imageSelected.sourceUrl !== ''"
                v-text="'来源链接'"
                @click.stop="window.open(imageSelected.sourceUrl)"
              ></v-chip>
              <v-chip class="mr-1" style="width: fit-content;" color="#009ff088" text-color="#ffffff" small
                v-text="'本站链接'"
                @click.stop="window.open('/post/show/' + imageSelected.id)"
              ></v-chip>
              <v-chip class="mr-1" style="width: fit-content;" text-color="#ffffff" small
                :color="imageSelectedDetail.favorite ? '#00900088' : '#009ff088'"
                v-text="imageSelectedDetail.favorite ? '已收藏' : '添加收藏'"
                @click.stop="imageSelectedDetail.favorite ? (void 0) : onFavorite(imageSelected.id)"
              ></v-chip>
            </div>
          </div>
        </v-img>
      </v-dialog>
    </v-container>
  </v-main>
</v-app>
</script>
`;
    Vue.use(VueMasonry);
    new Vue({
      vuetify: new Vuetify({
        theme: { dark: true },
      }),
      render: h => h(App)
    }).$mount("#app");
  }

  const onChangeLeftBar = function() {
    const value = Boolean(document.getElementById("showLeftBar").selectedIndex);
    localStorage.setItem("showLeftBar", JSON.stringify(value));
    const element = document.querySelector("#post-list > .sidebar");
    element.setAttribute("show-left-bar", value);
    console.log("showLeftBar", value);
  };
  const onChangeRatingE = function() {
    const value = Boolean(document.getElementById("showRatingE").selectedIndex);
    localStorage.setItem("showRatingE", JSON.stringify(value));
    const elementList = document.querySelectorAll(".javascript-hide");
    elementList.forEach(element => element.setAttribute("show-rating-e", value));
    console.log("showRatingE", value);
  };
  const onChangeImageHD = function() {
    const index = document.getElementById("showImageHD").selectedIndex;
    const elementList = document.querySelectorAll("#post-list-posts > li > .inner");
    elementList.forEach(element => element.setAttribute("show-image-hd", index));
    localStorage.setItem("showImageHD", JSON.stringify(index));
    console.log("showImageHD", index);
  };
  let taskArray = [];
  let maxLoadingSampleNum = 4;
  let doLoadSampleUrl = () => {
    let loadingNum = 0;
    let loadSampleUrl = () => {
      if (taskArray.length == 0) return
      loadingNum++;
      let { element, sampleUrl } = taskArray.shift();
      element.onerror = () => {
        element.src = sampleUrl;
      };
      element.onload = () => {
        loadingNum--;
      };
      element.src = sampleUrl;
    };
    setInterval(() => {
      if (taskArray.length == 0) return
      let needloadNum = maxLoadingSampleNum - loadingNum;
      while (needloadNum--) {
        loadSampleUrl();
      }
    }, 1000);
  };
  const initOptions = function() {
    if (/^\/user\/show\/[\d]{1,}/.test(location.pathname)) return
    if (document.getElementById("post-list-posts") === null) return
    document.getElementById("post-list-posts").insertAdjacentHTML("beforebegin", `
<div style="padding: 1rem; user-select: none; text-align: center;">
  <select id="showLeftBar" style="height: 1.5rem; line-height: 1.5rem;">
    <option>隐藏左栏</option>
    <option>显示左栏</option>
  </select>
  <select id="showRatingE" style="height: 1.5rem; line-height: 1.5rem; margin-left: 0.25rem;">
    <option>隐藏默认</option>
    <option>显示全部</option>
  </select>
  <select id="showImageHD" style="height: 1.5rem; line-height: 1.5rem; margin-left: 0.25rem;">
    <option>默认尺寸</option>
    <option>二倍尺寸</option>
    <option>三倍尺寸</option>
    <option>四倍尺寸</option>
  </select>
  <button id="enterBrowseMode" style="margin-left: 0.25rem;">进入浏览模式</button>
</div>
`);
    const imageList = document.querySelectorAll("img.preview");
    const samples = JSON.parse(localStorage.getItem("sample_urls"));
    imageList.forEach(element => {
      if (/\/post\/show\/([\d]{1,})/.test(element.nextElementSibling.innerText)) {
        const id = RegExp.$1;
        const sampleUrl = samples[id];
        if (sampleUrl !== undefined) {
          element.src = sampleUrl;
        }
      }
    });
    doLoadSampleUrl();
    document.getElementById("showLeftBar").addEventListener("change", onChangeLeftBar);
    document.getElementById("showRatingE").addEventListener("change", onChangeRatingE);
    document.getElementById("showImageHD").addEventListener("change", onChangeImageHD);
    const showLeftBar = JSON.parse(localStorage.getItem("showLeftBar") || "true");
    const showRatingE = JSON.parse(localStorage.getItem("showRatingE") || "true");
    const showImageHD = JSON.parse(localStorage.getItem("showImageHD") || "0");
    document.getElementById("showLeftBar").selectedIndex = showLeftBar;
    document.getElementById("showRatingE").selectedIndex = showRatingE;
    document.getElementById("showImageHD").selectedIndex = showImageHD;
    onChangeLeftBar();
    onChangeRatingE();
    onChangeImageHD();
    document.getElementById("enterBrowseMode").addEventListener("click", enterBrowseMode);
  };

  const tags = {
  "4koma": "四格漫画",
  "5-toubun_no_hanayome": "五等分的新娘",
  "anal": "肛交",
  "angel": "天使",
  "angel_beats!": "Angel Beats!",
  "animal_ears": "兽耳",
  "anthropomorphization": "拟人化",
  "anus": "肛门露出",
  "areola": "乳晕",
  "arknights": "明日方舟",
  "armor": "盔甲/装甲",
  "artist_revision": "画师修改",
  "ass": "臀部",
  "ass_grab": "持股/捏臀",
  "atelier": "炼金工房系列",
  "autographed": "亲笔签名",
  "azur_lane": "碧蓝航线",
  "bakemonogatari": "化物语",
  "bandages": "绷带",
  "bandaid": "创可贴/绷带",
  "bang_dream!": "BanG Dream!",
  "baseball": "棒球",
  "basketball": "篮球",
  "bathing": "沐浴",
  "benghuai_xueyuan": "崩坏学园",
  "bike_shorts": "自行车短裤",
  "bikini": "比基尼",
  "bikini_armor": "比基尼装甲/轻薄盔甲",
  "bikini_top": "比基尼乳罩",
  "black_rock_shooter": "黑岩射手",
  "blood": "血腥",
  "bloomers": "灯笼裤/宽松短裤",
  "blue_archive": "碧蓝档案",
  "bodysuit": "紧身衣裤",
  "boku_wa_tomodachi_ga_sukunai": "我的朋友很少",
  "bondage": "束缚",
  "bottomless": "下身露出",
  "bra": "乳罩",
  "breast_grab": "握乳",
  "breast_hold": "托乳",
  "breasts": "乳",
  "bukkake": "颜射",
  "bunny_ears": "兔耳",
  "bunny_girl": "兔女郎",
  "buruma": "运动短裤",
  "business_suit": "西装/职业服",
  "calendar": "日历",
  "cameltoe": "阴户凸显",
  "card": "卡牌",
  "card_captor_sakura": "魔卡少女樱",
  "censored": "有码",
  "cg": "CG/计算机动画",
  "chainsaw": "电锯",
  "character_design": "角色设计",
  "cheerleader": "啦啦队队员",
  "chibi": "Q版",
  "chinadress": "旗袍",
  "choujigen_game_neptune": "超次元游戏海王星",
  "christmas": "圣诞",
  "cleavage": "乳沟",
  "code_geass": "反叛的鲁路修",
  "condom": "避孕套",
  "corset": "(束腰)紧身内衣",
  "cosplay": "角色扮演",
  "cream": "奶油",
  "cropped": "裁剪图",
  "crossdress": "变装",
  "crossover": "作品联动/混合同人",
  "cum": "精液",
  "cunnilingus": "品玉/舔阴",
  "dakimakura": "抱枕",
  "darling_in_the_franxx": "DARLING in the FRANXX",
  "date_a_live": "约会大作战",
  "detexted": "去字图片",
  "devil": "魔鬼/恶魔",
  "digital_version": "数字版",
  "dildo": "假阳具",
  "disc_cover": "光盘封面",
  "dress": "连衣裙",
  "dress_shirt": "衬衫",
  "duplicate": "重复图片",
  "elf": "精灵",
  "endcard": "片尾插图",
  "erect_nipples": "乳尖",
  "expression": "角色展示/立绘",
  "extreme_content": "极端",
  "eyepatch": "眼罩",
  "fairy": "精灵/小精灵",
  "fate/kaleid_liner_prisma_illya": "Fate/kaleid liner 魔法少女☆伊莉雅",
  "feet": "足",
  "fellatio": "口交",
  "final_fantasy": "最终幻想",
  "final_fantasy_vii": "最终幻想 VII",
  "final_fantasy_xiv": "最终幻想 14",
  "fingering": "指交",
  "fire_emblem": "火焰纹章",
  "fire_emblem_heroes": "火焰之纹章:英雄云集",
  "fire_emblem_kakusei": "火焰之纹章:觉醒",
  "fire_emblem_three_houses": "火焰之纹章:风花雪月",
  "fishnets": "鱼网袜",
  "fixed": "修改",
  "footjob": "足交",
  "fundoshi": "褌/兜裆布",
  "futanari": "扶她",
  "game_cg": "游戏CG",
  "gangbang": "乱交",
  "garter": "袜带",
  "garter_belt": "吊袜腰带",
  "genderswap": "性转",
  "genshin_impact": "原神",
  "girls_frontline": "少女前线",
  "girls_und_panzer": "少女与战车",
  "gochuumon_wa_usagi_desu_ka?": "请问您今天要来点兔子吗?",
  "gothic_lolita": "哥特式洛丽塔",
  "granblue_fantasy": "碧蓝幻想",
  "guitar": "吉他",
  "gun": "枪炮",
  "gundam": "高达",
  "guro": "猎奇",
  "halloween": "万圣节前夜",
  "handjob": "打手枪",
  "headphones": "耳机",
  "heels": "高跟鞋",
  "heterochromia": "虹膜异色",
  "hibike!_euphonium": "吹响吧!上低音号",
  "highschool_dxd": "恶魔高校D×D",
  "honkai_impact": "崩坏 3",
  "horns": "角",
  "index_page": "索引页面",
  "infinite_stratos": "IS/无限斯特拉托斯",
  "inumimi": "犬耳",
  "japanese_clothes": "日式服装",
  "k-on!": "轻音少女",
  "kaguya-sama_wa_kokurasetai_~tensai-tachi_no_renai_zunousen~": "辉夜大小姐想让我告白~天才们的恋爱头脑战~",
  "kantai_collection": "舰队 Collection",
  "kemono_friends": "兽娘动物园",
  "kimetsu_no_yaiba": "鬼灭之刃",
  "kimono": "和服",
  "kitsune": "狐狸",
  "kobayashi-san_chi_no_maid_dragon": "小林家的龙女仆",
  "kono_subarashii_sekai_ni_shukufuku_wo!": "为美好的世界献上祝福!",
  "lactation": "泌乳",
  "landscape": "风景画",
  "league_of_legends": "英雄联盟",
  "leotard": "紧身连衣裤",
  "line_art": "线条画",
  "lingerie": "贴身内衣",
  "little_busters!": "Little Busters!",
  "loli": "萝莉",
  "lolita_fashion": "洛丽塔",
  "love_live!_nijigasaki_high_school_idol_club": "Love Live! 虹咲学园学园偶像同好会",
  "lucky_star": "幸运星",
  "maebari": "前貼り/遮盖私处",
  "mahou_shoujo_lyrical_nanoha": "魔法少女奈叶",
  "mahou_shoujo_lyrical_nanoha_strikers": "魔法少女奈叶 StrikerS",
  "maid": "女仆",
  "male": "男性",
  "masturbation": "自摸/手淫",
  "mecha": "机甲",
  "mecha_musume": "机甲娘",
  "megane": "眼镜",
  "megaten": "女神转生系列",
  "mermaid": "美人鱼",
  "miko": "巫女",
  "monochrome": "单色",
  "monster": "怪物",
  "monster_girl": "怪物女孩",
  "monster_musume_no_iru_nichijou": "魔物娘的相伴日常",
  "naked": "裸体",
  "naked_apron": "裸体围裙",
  "naked_cape": "裸体披风",
  "naked_ribbon": "裸体丝带",
  "neko": "猫",
  "nekomimi": "猫耳",
  "neon_genesis_evangelion": "新世纪福音战士",
  "nier_automata": "尼尔:自动人形",
  "nijisanji": "彩虹社",
  "ninja": "忍者",
  "nipple_slip": "露点",
  "nipples": "乳头",
  "no_bra": "无乳罩",
  "nopan": "无胖次",
  "nun": "修女",
  "nurse": "护士",
  "official_watermark": "官方水印",
  "onsen": "温泉",
  "open_shirt": "衬衫敞开",
  "ore_no_imouto_ga_konnani_kawaii_wake_ga_nai": "我的妹妹哪有这么可爱!",
  "overalls": "工装连衣裤",
  "overwatch": "守望先锋",
  "paizuri": "乳交",
  "pajama": "睡衣",
  "panties": "内裤",
  "pantsu": "胖次",
  "panty_pull": "胖次脱下",
  "pantyhose": "吊带袜",
  "parody": "仿拟/谐拟",
  "partial_scan": "局部扫描",
  "pasties": "乳贴",
  "pee": "尿尿",
  "penguin": "企鹅",
  "penis": "阴茎",
  "photo": "照片/现实背景",
  "photoshop": "PS 改图",
  "pirate": "海盗",
  "pointy_ears": "尖耳朵",
  "pokemon": "精灵宝可梦",
  "possible_duplicate": "可能重复",
  "pregnant": "孕妇",
  "pretty_cure": "光之美少女",
  "princess_connect": "公主连结",
  "princess_connect!_re:dive": "公主连结 Re:Dive",
  "profile_page": "角色资料页",
  "pubic_hair": "阴毛",
  "puella_magi_madoka_magica": "魔法少女小圆",
  "pussy": "阴户",
  "pussy_juice": "妹汁",
  "queen's_blade": "女王之刃",
  "raw_scan": "扫描原图",
  "re_zero_kara_hajimeru_isekai_seikatsu": "Re:从零开始的异世界生活",
  "robe": "长袍/礼服/睡袍",
  "saenai_heroine_no_sodatekata": "路人女主的养成方法",
  "sailor_moon": "美少女战士",
  "sake": "日本清酒",
  "sample": "样品图",
  "sarashi": "晒し/缠胸布",
  "school_swimsuit": "学校泳衣",
  "see_through": "透视",
  "seifuku": "制服",
  "selfie": "自拍",
  "senran_kagura": "闪乱神乐",
  "sex": "性交",
  "sheets": "床单",
  "shimapan": "条纹胖次",
  "shirt_lift": "衬衫掀起",
  "shota": "正太",
  "silhouette": "剪影/暗色轮廓/体形",
  "sketch": "素描",
  "skirt_lift": "裙摆掀起",
  "sling_bikini": "吊带比基尼",
  "smoking": "吸烟",
  "soccer": "足球",
  "sono_bisque_doll_wa_koi_wo_suru": "更衣人偶坠入爱河",
  "spy_x_family": "间谍过家家",
  "ssss.gridman": "SSSS.古立特",
  "stick_poster": "海报",
  "stockings": "长筒袜",
  "strike_witches": "强袭魔女",
  "string_panties": "细绳胖次",
  "summer_dress": "夏装",
  "suzumiya_haruhi_no_yuuutsu": "凉宫春日的忧郁",
  "sweater": "毛衣",
  "swimsuits": "泳衣",
  "sword": "刀剑",
  "sword_art_online": "刀剑神域",
  "symmetrical_docking": "乳乳相接",
  "tagme": "标签",
  "tail": "兽尾",
  "tan_lines": "日晒线",
  "tattoo": "文身",
  "tennis": "网球",
  "tentacles": "触手",
  "text": "文本",
  "the_idolm@ster": "偶像大师",
  "the_idolm@ster_cinderella_girls": "偶像大师灰姑娘女孩",
  "the_idolm@ster_million_live!": "偶像大师百万现场",
  "the_idolm@ster_shiny_colors": "偶像大师闪耀色彩",
  "thighhighs": "过膝袜",
  "thong": "丁字裤",
  "to_aru_kagaku_no_railgun": "某科学的超电磁炮",
  "to_aru_majutsu_no_index": "魔法禁书目录",
  "to_heart_(series)": "To Heart 系列",
  "to_heart_2": "To Heart 2",
  "to_love_ru": "出包王女",
  "to_love_ru_darkness": "出包王女 Darkness",
  "topless": "上身露出",
  "torn_clothes": "破衣",
  "touhou": "东方",
  "towel": "浴巾",
  "translated": "文字已翻译(英文)",
  "transparent_png": "背景透明",
  "trap": "伪娘",
  "tribadism": "磨豆腐/交叉体位",
  "tutorial": "教程",
  "uma_musume_pretty_derby": "赛马娘",
  "umbrella": "伞",
  "uncensored": "无码",
  "underboob": "南半球",
  "underwear": "内衣",
  "undressing": "脱衣",
  "uniform": "制服",
  "valentine": "情人节",
  "vibrator": "跳蛋",
  "wa_maid": "和风女仆",
  "waitress": "女侍",
  "wallpaper": "壁纸",
  "wardrobe_malfunction": "走光",
  "weapon": "武器",
  "wedding_dress": "婚纱",
  "wet": "湿身",
  "wet_clothes": "湿衣",
  "wings": "翅膀",
  "witch": "女巫",
  "xenoblade": "异度神剑",
  "xenoblade_chronicles_2": "异度神剑 2",
  "yahari_ore_no_seishun_lovecome_wa_machigatteiru.": "我的青春恋爱喜剧果然有问题",
  "yaoi": "蔷薇/男同",
  "yukata": "浴衣",
  "yuri": "百合",
  "zhanjianshaonv": "战舰少女"
};
  const menus = 
{
  "My Account": "账户",
  "Posts": "作品",
  "Comments": "评论",
  "Notes": "笔记",
  "Artists": "画师",
  "Tags": "标签",
  "Forum": "论坛",
  "Help": "帮助",
  "More »": "更多>>",
  "New Mail": "新消息",
  "My Profile": "我的资料",
  "My Mail": "我的消息",
  "My Favorites": "我的收藏",
  "Settings": "设置",
  "Change Password": "修改密码",
  "Logout": "退出登录",
  "View Posts": "浏览作品",
  "Search Posts": "搜索作品",
  "Upload": "上传",
  "Random": "随机浏览",
  "Popular": "热门",
  "Image Search": "搜索图片",
  "History": "历史",
  "View Comments": "浏览评论",
  "Search Comments": "搜索评论",
  "View Notes": "浏览笔记",
  "Search Notes": "搜索笔记",
  "View Artists": "浏览画师",
  "Search Artists": "搜索画师",
  "Create": "创建",
  "View Tags": "浏览标签",
  "Search Tags": "搜索标签",
  "Aliases": "别名",
  "Implications": "含义",
  "View Pools": "浏览 Pools",
  "Search Pools": "搜索 Pools",
  "Create New Pool": "创建 Pool",
  "View Wiki Index": "浏览 Wiki 主页",
  "Search Wiki": "搜索 Wiki",
  "Create New Page": "创建新页面",
  "Mark All Read": "全部标记已读"
}
;
  const footers = 
{
  "List": "首页",
  "Browse": "翻阅",
  "Upload": "上传",
  "Random": "随机",
  "Popular": "热门",
  "Image Search": "寻图",
  "History": "历史",
  "Help": "帮助"
}
;
  const translateTags = function() {
    const elementList = Array.from(document.getElementsByTagName("a"));
    elementList.forEach(element => {
      const href = element.getAttribute("href");
      if (typeof href === "string" && /^\/post\?tags=(\S+)$/.test(href)) {
        const en = RegExp.$1;
        const cn = tags[en];
        if (cn) {
          element.innerText = `[${cn}]${en.replace(/_/g, " ")}`;
        }
      }
    });
  };
  const translateMenus = function() {
    const mainMenuList = Array.from(document.querySelectorAll("#main-menu>ul>li>a"));
    const subMenuList = Array.from(document.querySelectorAll("ul.submenu>li>a"));
    const elementList = [...mainMenuList, ...subMenuList];
    elementList.forEach(element => {
      if (element.getAttribute("href") === "#") return
      const en = element.innerText;
      const cn = menus[en];
      if (cn) {
        element.innerText = cn;
      }
    });
  };
  const translateNotice = function() {
    const elementList = Array.from(document.querySelectorAll(".status-notice"));
    elementList.forEach(element => {
      console.log(element.innerHTML);
      element.innerHTML = element.innerHTML
        .replace(/^[\s]+This image has been resized. Click on the /, "这张图片已经被压缩,单击侧边栏中的")
        .replace(/View larger version/, "显示高清图")
        .replace(/ link in the sidebar for a high-quality version./, "可以获取更高质量的版本。")
        .replace(/Hide this message<\/a>\./, "不再提醒</a>")
        .replace(/This post belongs to a /, "这张图片从属于一个")
        .replace(/parent post<\/a>\./, "相关父作品</a>。")
        .replace(/This post has /, "这张图片从属于一个")
        .replace(/child posts<\/a>\. \(post #/, "作品集</a>。相关子作品:")
        .replace(/a child post<\/a>\. \(post #/, "作品集</a>。相关子作品:")
        .replace(/<\/a>, <a /, "</a> | <a ")
        .replace(/<\/a>\)/, "</a>");
    });
  };
  const translateButtons = function() {
    [
      ['#highres-show', 'View larger version', '显示高清图'],
      ['#highres', 'Download larger version', '下载高清图'],
      ['#png', 'Download PNG', '下载 PNG 图'],
      ['li#add-to-favs>a', 'Add to favorites', '添加收藏'],
      ['li#set-avatar>a', 'Set avatar', '设置头像'],
      ['h4>a.js-posts-show-edit-tab', 'Edit', '编辑'],
      ['h4>a.js-posts-show-comments-tab', 'Respond', '评论'],
      ['.pagination>.previous_page', '← Previous', '上一页'],
      ['.pagination>.next_page', 'Next →', '下一页'],
    ].forEach(data => {
      const [selector, en, cn] = data;
      const element = document.querySelector(selector);
      if (element) {
        element.innerText = element.innerText.replace(en, cn);
      }
    });
  };
  const translateFooters = function() {
    const elementList = Array.from(document.querySelectorAll('#subnavbar>li>a'));
    elementList.forEach(element => {
      const en = element.innerText;
      const cn = footers[en];
      if (cn) {
        element.innerText = cn;
      }
    });
  };
  const initTranslate = function() {
    translateTags();
    translateMenus();
    translateNotice();
    translateButtons();
    translateFooters();
  };

  jQuery(document).ready(function() {
    initStyle();
    initHotKey();
    initOptions();
    initTranslate();
    if (document.cookie.includes('locale=zh_CN') === false) {
      document.cookie = "locale=zh_CN";
      location.href = location.href;
    }
  });

}());


================================================
FILE: config/rollup.config.js
================================================
import { version } from "../package.json"
import cleanup from "rollup-plugin-cleanup"
import json from "@rollup/plugin-json"

const banner = `// ==UserScript==
// @name         Yande.re 简体中文
// @namespace    com.coderzhaoziwei.yandere
// @version      ${ version }
// @author       Coder Zhao coderzhaoziwei@outlook.com
// @description  中文标签 | 界面优化 | 高清大图 | 键盘翻页 | 流体布局
// @homepage     https://greasyfork.org/scripts/421970
// @license      MIT
// @match        https://yande.re/*
// @exclude      https://yande.re/forum/*
// @match        https://konachan.com/*
// @exclude      https://konachan.com/forum/*
// @match        https://konachan.net/*
// @exclude      https://konachan.net/forum/*
// @supportURL   https://github.com/coderzhaoziwei/yande-re-chinese-patch/issues
// @grant        GM_download
// ==/UserScript==

/* eslint-env es2022 */
/* global jQuery:readonly */
/* global Vue:readonly */
/* global Vuetify:readonly */
/* global VueMasonry:readonly */
`

export default {
  input: "source/index.js",
  output: {
    file: "bundle/index.js",
    format: "iife",
    banner,
  },
  plugins: [ cleanup(), json() ],
}


================================================
FILE: package.json
================================================
{
  "name": "yande-re-chinese-patch",
  "version": "2.1.47",
  "author": "Coder Zhao",
  "description": "Yande.re Chinese Patch | Y 站简体中文补丁",
  "repository": "https://github.com/coderzhaoziwei/yande.re-chinese-patch.git",
  "license": "MIT",
  "scripts": {
    "bundle": "sh script/bundle.sh"
  },
  "devDependencies": {
    "@rollup/plugin-json": "^4.1.0",
    "rollup": "^2.47.0",
    "rollup-plugin-cleanup": "^3.2.1"
  }
}


================================================
FILE: readme.md
================================================
![version][img-version]
![license][img-license]
![stars][img-stars]
![cover][img-cover]

<!-- omit in toc -->
# 目录

- [前言](#前言)
- [预览](#预览)
- [功能](#功能)
  - [标签翻译](#标签翻译)
  - [操作优化](#操作优化)
  - [浏览模式](#浏览模式)
- [安装](#安装)
  - [安装浏览器插件](#安装浏览器插件)
  - [安装脚本程序](#安装脚本程序)
- [常见问题](#常见问题)
  - [Q: 安卓手机如何安装 Tampermonkey?](#q-安卓手机如何安装-tampermonkey)
- [相关链接](#相关链接)
- [开源许可](#开源许可)
- [附:标签翻译表](#附标签翻译表)

# 前言

最初,我在浏览 [yande.re 站](https://yande.re)(以下简称 Y 站)的时候,遇到了一些难懂的标签名。在使用搜索引擎多方查询之后,我才终于明白个别晦涩单词的含义,可是过了一段时间再次遇到,又忘记了。然后我就敲了一个脚本程序,自动遍历页面中的标签名,添加中文翻译。为了让与我有同样困扰的其他朋友节约时间,我将脚本程序发布到了脚本网站 [Greasy Fork](https://greasyfork.org/) 上。

后来,有朋友提议添加更优化的操作方式,于是陆续增加了显示大图、键盘翻页等功能。再后来有一天,我躺在床上用手机登陆了 Y 站,网站无法适配移动端屏幕实在是太特么难受了,于是我就敲了响应式布局的浏览模式,手机可以自动加载图片资源,单屏一滑到底。

终于,舒服了。

- https://yande.re
- ~~https://oreno.imouto.us (Y 站镜像,无需魔法上网)~~(已失效)
- https://konachan.com (K 站已兼容)
- https://konachan.net (K 站安全模式,.net 域名下默认隐藏成人内容)

# 预览

- 样式优化,高清图源,尺寸自选。

![1](https://cdn.jsdelivr.net/gh/coderzhaoziwei/yande-re-chinese-patch/source/img/1.png)

- 流体布局,自动加载,一屏到底。

![2](https://cdn.jsdelivr.net/gh/coderzhaoziwei/yande-re-chinese-patch/source/img/2.png)

- 单图预览,聚合详情,一键下载。

![3](https://cdn.jsdelivr.net/gh/coderzhaoziwei/yande-re-chinese-patch/source/img/3.png)

- 显隐自如,更多功能,欢迎体验。

![4](https://cdn.jsdelivr.net/gh/coderzhaoziwei/yande-re-chinese-patch/source/img/4.png)

# 功能

## 标签翻译

翻译了 Y 站出现频率较高的 100 多个标签,详细内容请查看最下方的标签翻译表。欢迎校正或补充。

## 操作优化

- 对于 Y 站默认隐藏的成人内容,提供了显示或者隐藏的选项。
- 网页左侧的边栏,提供了显示或者隐藏的选项。
- 图源默认自动替换为高清资源,并提供了 1 ~ 4 倍尺寸的选项。
- 快捷键
  - 上一页:A / ←
  - 下一页:D / →
  - 显示当前作品原图:S
  - 显示当前作品来源:W

## 浏览模式

浏览任意图片列表时,可以进入浏览模式。

# 安装

## 安装浏览器插件

首先你需要为你的浏览器安装一个用户脚本管理器,推荐使用插件 [Tampermonkey](https://www.tampermonkey.net/)。

> 主流浏览器有 Chrome、Microsoft Edge、Firefox、Safari 等,如果你的浏览器无法安装 Tampermonkey,那么也就无法安装此脚本程序。

## 安装脚本程序

使用浏览器直接访问 [Greasy Fork - Yande.re 简体中文 - 主页](https://greasyfork.org/scripts/421970),点击安装即可。

如果你没有 Greasy Fork 的账号,访问以上链接可能会提示你:`此脚本不再在本网站上匿名可用。请登录并检查您的 Greasy Fork 账号设置。`

因为此脚本程序涉及成人内容,所以在 Greasy Fork 站点必须登录才可以浏览或安装。如果你不想注册成为 Greasy Fork 的用户,可以访问成人脚本站点 [Sleasy Fork](https://sleazyfork.org/scripts/421970) 直接安装,或者直接获取[仓库文件](https://github.com/coderzhaoziwei/yande-re-chinese-patch/raw/main/index.user.js)来安装。

# 常见问题

## Q: 安卓手机如何安装 Tampermonkey?

我推荐安卓 Yandex 浏览器,可以直接安装 Chrome 插件。当你发现谷歌市场的 Tampermonkey 显示不兼容时,点击 Yandex 菜单栏,切换为桌面模式即可。

其他安卓浏览器也可以的,Firefox、Kiwi、Iceraven 等自行尝试。

# 相关链接

[Github 仓库](https://github.com/coderzhaoziwei/yande-re-chinese-patch) |
[Greasy Fork 脚本主页](https://greasyfork.org/scripts/421970) |
[封面原图 Yande#388833](https://yande.re/post/show/388833)

# 开源许可

MIT

# 附:标签翻译表

||English|简体中文|
|:-:|:-|:-|
|1|4koma|四格漫画|
|2|5-toubun no hanayome|五等分的新娘|
|3|anal|肛交|
|4|angel|天使|
|5|angel beats!|Angel Beats!|
|6|animal ears|兽耳|
|7|anthropomorphization|拟人化|
|8|anus|肛门露出|
|9|areola|乳晕|
|10|arknights|明日方舟|
|11|armor|盔甲/装甲|
|12|artist revision|画师修改|
|13|ass|臀部|
|14|ass grab|持股/捏臀|
|15|atelier|炼金工房系列|
|16|autographed|亲笔签名|
|17|azur lane|碧蓝航线|
|18|bakemonogatari|化物语|
|19|bandages|绷带|
|20|bandaid|创可贴/绷带|
|21|bang dream!|BanG Dream!|
|22|baseball|棒球|
|23|basketball|篮球|
|24|bathing|沐浴|
|25|benghuai xueyuan|崩坏学园|
|26|bike shorts|自行车短裤|
|27|bikini|比基尼|
|28|bikini armor|比基尼装甲/轻薄盔甲|
|29|bikini top|比基尼乳罩|
|30|black rock shooter|黑岩射手|
|31|blood|血腥|
|32|bloomers|灯笼裤/宽松短裤|
|33|blue archive|碧蓝档案|
|34|bodysuit|紧身衣裤|
|35|boku wa tomodachi ga sukunai|我的朋友很少|
|36|bondage|束缚|
|37|bottomless|下身露出|
|38|bra|乳罩|
|39|breast grab|握乳|
|40|breast hold|托乳|
|41|breasts|乳|
|42|bukkake|颜射|
|43|bunny ears|兔耳|
|44|bunny girl|兔女郎|
|45|buruma|运动短裤|
|46|business suit|西装/职业服|
|47|calendar|日历|
|48|cameltoe|阴户凸显|
|49|card|卡牌|
|50|card captor sakura|魔卡少女樱|
|51|censored|有码|
|52|cg|CG/计算机动画|
|53|chainsaw|电锯|
|54|character design|角色设计|
|55|cheerleader|啦啦队队员|
|56|chibi|Q版|
|57|chinadress|旗袍|
|58|choujigen game neptune|超次元游戏海王星|
|59|christmas|圣诞|
|60|cleavage|乳沟|
|61|code geass|反叛的鲁路修|
|62|condom|避孕套|
|63|corset|(束腰)紧身内衣|
|64|cosplay|角色扮演|
|65|cream|奶油|
|66|cropped|裁剪图|
|67|crossdress|变装|
|68|crossover|作品联动/混合同人|
|69|cum|精液|
|70|cunnilingus|品玉/舔阴|
|71|dakimakura|抱枕|
|72|darling in the franxx|DARLING in the FRANXX|
|73|date a live|约会大作战|
|74|detexted|去字图片|
|75|devil|魔鬼/恶魔|
|76|digital version|数字版|
|77|dildo|假阳具|
|78|disc cover|光盘封面|
|79|dress|连衣裙|
|80|dress shirt|衬衫|
|81|duplicate|重复图片|
|82|elf|精灵|
|83|endcard|片尾插图|
|84|erect nipples|乳尖|
|85|expression|角色展示/立绘|
|86|extreme content|极端|
|87|eyepatch|眼罩|
|88|fairy|精灵/小精灵|
|89|fate/kaleid liner prisma illya|Fate/kaleid liner 魔法少女☆伊莉雅|
|90|feet|足|
|91|fellatio|口交|
|92|final fantasy|最终幻想|
|93|final fantasy vii|最终幻想 VII|
|94|final fantasy xiv|最终幻想 14|
|95|fingering|指交|
|96|fire emblem|火焰纹章|
|97|fire emblem heroes|火焰之纹章:英雄云集|
|98|fire emblem kakusei|火焰之纹章:觉醒|
|99|fire emblem three houses|火焰之纹章:风花雪月|
|100|fishnets|鱼网袜|
|101|fixed|修改|
|102|footjob|足交|
|103|fundoshi|褌/兜裆布|
|104|futanari|扶她|
|105|game cg|游戏CG|
|106|gangbang|乱交|
|107|garter|袜带|
|108|garter belt|吊袜腰带|
|109|genderswap|性转|
|110|genshin impact|原神|
|111|girls frontline|少女前线|
|112|girls und panzer|少女与战车|
|113|gochuumon wa usagi desu ka?|请问您今天要来点兔子吗?|
|114|gothic lolita|哥特式洛丽塔|
|115|granblue fantasy|碧蓝幻想|
|116|guitar|吉他|
|117|gun|枪炮|
|118|gundam|高达|
|119|guro|猎奇|
|120|halloween|万圣节前夜|
|121|handjob|打手枪|
|122|headphones|耳机|
|123|heels|高跟鞋|
|124|heterochromia|虹膜异色|
|125|hibike! euphonium|吹响吧!上低音号|
|126|highschool dxd|恶魔高校D×D|
|127|honkai impact|崩坏 3|
|128|horns|角|
|129|index page|索引页面|
|130|infinite stratos|IS/无限斯特拉托斯|
|131|inumimi|犬耳|
|132|japanese clothes|日式服装|
|133|k-on!|轻音少女|
|134|kaguya-sama wa kokurasetai ~tensai-tachi no renai zunousen~|辉夜大小姐想让我告白~天才们的恋爱头脑战~|
|135|kantai collection|舰队 Collection|
|136|kemono friends|兽娘动物园|
|137|kimetsu no yaiba|鬼灭之刃|
|138|kimono|和服|
|139|kitsune|狐狸|
|140|kobayashi-san chi no maid dragon|小林家的龙女仆|
|141|kono subarashii sekai ni shukufuku wo!|为美好的世界献上祝福!|
|142|lactation|泌乳|
|143|landscape|风景画|
|144|league of legends|英雄联盟|
|145|leotard|紧身连衣裤|
|146|line art|线条画|
|147|lingerie|贴身内衣|
|148|little busters!|Little Busters!|
|149|loli|萝莉|
|150|lolita fashion|洛丽塔|
|151|love live! nijigasaki high school idol club|Love Live! 虹咲学园学园偶像同好会|
|152|lucky star|幸运星|
|153|maebari|前貼り/遮盖私处|
|154|mahou shoujo lyrical nanoha|魔法少女奈叶|
|155|mahou shoujo lyrical nanoha strikers|魔法少女奈叶 StrikerS|
|156|maid|女仆|
|157|male|男性|
|158|masturbation|自摸/手淫|
|159|mecha|机甲|
|160|mecha musume|机甲娘|
|161|megane|眼镜|
|162|megaten|女神转生系列|
|163|mermaid|美人鱼|
|164|miko|巫女|
|165|monochrome|单色|
|166|monster|怪物|
|167|monster girl|怪物女孩|
|168|monster musume no iru nichijou|魔物娘的相伴日常|
|169|naked|裸体|
|170|naked apron|裸体围裙|
|171|naked cape|裸体披风|
|172|naked ribbon|裸体丝带|
|173|neko|猫|
|174|nekomimi|猫耳|
|175|neon genesis evangelion|新世纪福音战士|
|176|nier automata|尼尔:自动人形|
|177|nijisanji|彩虹社|
|178|ninja|忍者|
|179|nipple slip|露点|
|180|nipples|乳头|
|181|no bra|无乳罩|
|182|nopan|无胖次|
|183|nun|修女|
|184|nurse|护士|
|185|official watermark|官方水印|
|186|onsen|温泉|
|187|open shirt|衬衫敞开|
|188|ore no imouto ga konnani kawaii wake ga nai|我的妹妹哪有这么可爱!|
|189|overalls|工装连衣裤|
|190|overwatch|守望先锋|
|191|paizuri|乳交|
|192|pajama|睡衣|
|193|panties|内裤|
|194|pantsu|胖次|
|195|panty pull|胖次脱下|
|196|pantyhose|吊带袜|
|197|parody|仿拟/谐拟|
|198|partial scan|局部扫描|
|199|pasties|乳贴|
|200|pee|尿尿|
|201|penguin|企鹅|
|202|penis|阴茎|
|203|photo|照片/现实背景|
|204|photoshop|PS 改图|
|205|pirate|海盗|
|206|pointy ears|尖耳朵|
|207|pokemon|精灵宝可梦|
|208|possible duplicate|可能重复|
|209|pregnant|孕妇|
|210|pretty cure|光之美少女|
|211|princess connect|公主连结|
|212|princess connect! re:dive|公主连结 Re:Dive|
|213|profile page|角色资料页|
|214|pubic hair|阴毛|
|215|puella magi madoka magica|魔法少女小圆|
|216|pussy|阴户|
|217|pussy juice|妹汁|
|218|queen's blade|女王之刃|
|219|raw scan|扫描原图|
|220|re zero kara hajimeru isekai seikatsu|Re:从零开始的异世界生活|
|221|robe|长袍/礼服/睡袍|
|222|saenai heroine no sodatekata|路人女主的养成方法|
|223|sailor moon|美少女战士|
|224|sake|日本清酒|
|225|sample|样品图|
|226|sarashi|晒し/缠胸布|
|227|school swimsuit|学校泳衣|
|228|see through|透视|
|229|seifuku|制服|
|230|selfie|自拍|
|231|senran kagura|闪乱神乐|
|232|sex|性交|
|233|sheets|床单|
|234|shimapan|条纹胖次|
|235|shirt lift|衬衫掀起|
|236|shota|正太|
|237|silhouette|剪影/暗色轮廓/体形|
|238|sketch|素描|
|239|skirt lift|裙摆掀起|
|240|sling bikini|吊带比基尼|
|241|smoking|吸烟|
|242|soccer|足球|
|243|sono bisque doll wa koi wo suru|更衣人偶坠入爱河|
|244|spy x family|间谍过家家|
|245|ssss.gridman|SSSS.古立特|
|246|stick poster|海报|
|247|stockings|长筒袜|
|248|strike witches|强袭魔女|
|249|string panties|细绳胖次|
|250|summer dress|夏装|
|251|suzumiya haruhi no yuuutsu|凉宫春日的忧郁|
|252|sweater|毛衣|
|253|swimsuits|泳衣|
|254|sword|刀剑|
|255|sword art online|刀剑神域|
|256|symmetrical docking|乳乳相接|
|257|tagme|标签|
|258|tail|兽尾|
|259|tan lines|日晒线|
|260|tattoo|文身|
|261|tennis|网球|
|262|tentacles|触手|
|263|text|文本|
|264|the idolm@ster|偶像大师|
|265|the idolm@ster cinderella girls|偶像大师灰姑娘女孩|
|266|the idolm@ster million live!|偶像大师百万现场|
|267|the idolm@ster shiny colors|偶像大师闪耀色彩|
|268|thighhighs|过膝袜|
|269|thong|丁字裤|
|270|to aru kagaku no railgun|某科学的超电磁炮|
|271|to aru majutsu no index|魔法禁书目录|
|272|to heart (series)|To Heart 系列|
|273|to heart 2|To Heart 2|
|274|to love ru|出包王女|
|275|to love ru darkness|出包王女 Darkness|
|276|topless|上身露出|
|277|torn clothes|破衣|
|278|touhou|东方|
|279|towel|浴巾|
|280|translated|文字已翻译(英文)|
|281|transparent png|背景透明|
|282|trap|伪娘|
|283|tribadism|磨豆腐/交叉体位|
|284|tutorial|教程|
|285|uma musume pretty derby|赛马娘|
|286|umbrella|伞|
|287|uncensored|无码|
|288|underboob|南半球|
|289|underwear|内衣|
|290|undressing|脱衣|
|291|uniform|制服|
|292|valentine|情人节|
|293|vibrator|跳蛋|
|294|wa maid|和风女仆|
|295|waitress|女侍|
|296|wallpaper|壁纸|
|297|wardrobe malfunction|走光|
|298|weapon|武器|
|299|wedding dress|婚纱|
|300|wet|湿身|
|301|wet clothes|湿衣|
|302|wings|翅膀|
|303|witch|女巫|
|304|xenoblade|异度神剑|
|305|xenoblade chronicles 2|异度神剑 2|
|306|yahari ore no seishun lovecome wa machigatteiru.|我的青春恋爱喜剧果然有问题|
|307|yaoi|蔷薇/男同|
|308|yukata|浴衣|
|309|yuri|百合|
|310|zhanjianshaonv|战舰少女|

[img-version]: https://img.shields.io/github/package-json/v/coderzhaoziwei/yande-re-chinese-patch?style=flat-square
[img-license]: https://shields.io/badge/license-MIT-blue?style=flat-square
[img-stars]: https://img.shields.io/github/stars/coderzhaoziwei/yande-re-chinese-patch?label=star&style=social
[img-cover]: https://cdn.jsdelivr.net/gh/coderzhaoziwei/yande-re-chinese-patch/preview.png


================================================
FILE: readme_main.md
================================================
![version][img-version]
![license][img-license]
![stars][img-stars]
![cover][img-cover]

<!-- omit in toc -->
# 目录

- [前言](#前言)
- [预览](#预览)
- [功能](#功能)
  - [标签翻译](#标签翻译)
  - [操作优化](#操作优化)
  - [浏览模式](#浏览模式)
- [安装](#安装)
  - [安装浏览器插件](#安装浏览器插件)
  - [安装脚本程序](#安装脚本程序)
- [常见问题](#常见问题)
  - [Q: 安卓手机如何安装 Tampermonkey?](#q-安卓手机如何安装-tampermonkey)
- [相关链接](#相关链接)
- [开源许可](#开源许可)
- [附:标签翻译表](#附标签翻译表)

# 前言

最初,我在浏览 [yande.re 站](https://yande.re)(以下简称 Y 站)的时候,遇到了一些难懂的标签名。在使用搜索引擎多方查询之后,我才终于明白个别晦涩单词的含义,可是过了一段时间再次遇到,又忘记了。然后我就敲了一个脚本程序,自动遍历页面中的标签名,添加中文翻译。为了让与我有同样困扰的其他朋友节约时间,我将脚本程序发布到了脚本网站 [Greasy Fork](https://greasyfork.org/) 上。

后来,有朋友提议添加更优化的操作方式,于是陆续增加了显示大图、键盘翻页等功能。再后来有一天,我躺在床上用手机登陆了 Y 站,网站无法适配移动端屏幕实在是太特么难受了,于是我就敲了响应式布局的浏览模式,手机可以自动加载图片资源,单屏一滑到底。

终于,舒服了。

- https://yande.re
- ~~https://oreno.imouto.us (Y 站镜像,无需魔法上网)~~(已失效)
- https://konachan.com (K 站已兼容)
- https://konachan.net (K 站安全模式,.net 域名下默认隐藏成人内容)

# 预览

- 样式优化,高清图源,尺寸自选。

![1](https://cdn.jsdelivr.net/gh/coderzhaoziwei/yande-re-chinese-patch/source/img/1.png)

- 流体布局,自动加载,一屏到底。

![2](https://cdn.jsdelivr.net/gh/coderzhaoziwei/yande-re-chinese-patch/source/img/2.png)

- 单图预览,聚合详情,一键下载。

![3](https://cdn.jsdelivr.net/gh/coderzhaoziwei/yande-re-chinese-patch/source/img/3.png)

- 显隐自如,更多功能,欢迎体验。

![4](https://cdn.jsdelivr.net/gh/coderzhaoziwei/yande-re-chinese-patch/source/img/4.png)

# 功能

## 标签翻译

翻译了 Y 站出现频率较高的 100 多个标签,详细内容请查看最下方的标签翻译表。欢迎校正或补充。

## 操作优化

- 对于 Y 站默认隐藏的成人内容,提供了显示或者隐藏的选项。
- 网页左侧的边栏,提供了显示或者隐藏的选项。
- 图源默认自动替换为高清资源,并提供了 1 ~ 4 倍尺寸的选项。
- 快捷键
  - 上一页:A / ←
  - 下一页:D / →
  - 显示当前作品原图:S
  - 显示当前作品来源:W

## 浏览模式

浏览任意图片列表时,可以进入浏览模式。

# 安装

## 安装浏览器插件

首先你需要为你的浏览器安装一个用户脚本管理器,推荐使用插件 [Tampermonkey](https://www.tampermonkey.net/)。

> 主流浏览器有 Chrome、Microsoft Edge、Firefox、Safari 等,如果你的浏览器无法安装 Tampermonkey,那么也就无法安装此脚本程序。

## 安装脚本程序

使用浏览器直接访问 [Greasy Fork - Yande.re 简体中文 - 主页](https://greasyfork.org/scripts/421970),点击安装即可。

如果你没有 Greasy Fork 的账号,访问以上链接可能会提示你:`此脚本不再在本网站上匿名可用。请登录并检查您的 Greasy Fork 账号设置。`

因为此脚本程序涉及成人内容,所以在 Greasy Fork 站点必须登录才可以浏览或安装。如果你不想注册成为 Greasy Fork 的用户,可以访问成人脚本站点 [Sleasy Fork](https://sleazyfork.org/scripts/421970) 直接安装,或者直接获取[仓库文件](https://github.com/coderzhaoziwei/yande-re-chinese-patch/raw/main/index.user.js)来安装。

# 常见问题

## Q: 安卓手机如何安装 Tampermonkey?

我推荐安卓 Yandex 浏览器,可以直接安装 Chrome 插件。当你发现谷歌市场的 Tampermonkey 显示不兼容时,点击 Yandex 菜单栏,切换为桌面模式即可。

其他安卓浏览器也可以的,Firefox、Kiwi、Iceraven 等自行尝试。

# 相关链接

[Github 仓库](https://github.com/coderzhaoziwei/yande-re-chinese-patch) |
[Greasy Fork 脚本主页](https://greasyfork.org/scripts/421970) |
[封面原图 Yande#388833](https://yande.re/post/show/388833)

# 开源许可

MIT

# 附:标签翻译表

[[ TAGS ]]

[img-version]: https://img.shields.io/github/package-json/v/coderzhaoziwei/yande-re-chinese-patch?style=flat-square
[img-license]: https://shields.io/badge/license-MIT-blue?style=flat-square
[img-stars]: https://img.shields.io/github/stars/coderzhaoziwei/yande-re-chinese-patch?label=star&style=social
[img-cover]: https://cdn.jsdelivr.net/gh/coderzhaoziwei/yande-re-chinese-patch/preview.png


================================================
FILE: script/action.js
================================================
#!/usr/bin/env node

const fs = require('fs')
const path = require('path')
const tagsData = Object()
const version = process.env['npm_package_version'] || ''

formatTagsFile()
generateReadMeFile()

function formatTagsFile() {
  info('正在格式化标签数据')
  const tagsPath = path.resolve(__dirname, '../source/data/tags.json')
  const data = JSON.parse(fs.readFileSync(tagsPath))
  Object.keys(data).sort().forEach(key => tagsData[key.replace(/ /g, '_')] = data[key])
  fs.writeFileSync(tagsPath, JSON.stringify(tagsData, null, 2))
}

function generateReadMeFile() {
  info('正在生成说明文档')
  const main = fs.readFileSync(path.resolve(__dirname, '../readme_main.md'), 'utf8')
  const tags = Object.keys(tagsData).reduce((data, key, index) => {
    const en = key.replace(/_/g, ' ')
    const cn = tagsData[key]
    return `${ data }\n|${ index + 1 }|${en}|${cn}|`
  }, '||English|简体中文|\n|:-:|:-|:-|')

  fs.writeFileSync(path.resolve(__dirname, '../readme.md'), main.replace('[[ TAGS ]]', tags))
}

function info(log) {
  console.log(`\u001b[35m[${version}] \u001b[34m${log}`)
}


================================================
FILE: script/bundle.sh
================================================
# clear
echo "\033[2J"

# version
yarn version --no-git-tag-version --patch

node script/action.js

# rollup
yarn rollup --config config/rollup.config.js

node script/replace.js

# copy
if (type pbcopy >/dev/null 2>&1) then
  pbcopy < bundle/index.user.js
  echo "\033[32mcopied \033[1;36mbundle/valkyrie.user.js \033[0;32mto clipboard.\033[0m"
fi
# end
echo ""


================================================
FILE: script/replace.js
================================================
#!/usr/bin/env node

const fs = require("fs")

const origin = fs.readFileSync("bundle/index.js", "utf8")
const result = handler(origin)

fs.writeFile("bundle/index.user.js", result, error => {
  if (error) console.log(error)
})



function handler(content) {
  // [{ path: "source/body.html" }]
  const regexp = /\[{ path: ([\S]+) }\]/i
  while (regexp.test(content)) {
    const path = JSON.parse(RegExp.$1)
    console.log(path)
    const replacement = fs.readFileSync(path, "utf8")
    content = content.replace(regexp, replacement)
  }
  return content
}


================================================
FILE: source/app.js
================================================
import Post from "./post"

const App = {
  template: "#app-template",
  data() {
    return {
      showDrawer: false,
      showImageSelected: false,
      showImageInfo: true,

      showRatingQ: JSON.parse(localStorage.getItem("showRatingQ") || "true"),
      showRatingE: JSON.parse(localStorage.getItem("showRatingE") || "false"),

      imageList: [],
      imageSelectedIndex: 0,
      imageSelectedDetail: {},

      params: new URLSearchParams(location.search),
      requestState: false,
      requestStop: false,

      innerWidth: window.innerWidth,
      innerHeight: window.innerHeight,

      imageCountInRow: JSON.parse(localStorage.getItem("imageCountInRow") || "3"),
      imageQualityHigh: JSON.parse(localStorage.getItem("imageQualityHigh") || "false"),

      showFavoriteSuccess: false,
    }
  },
  computed: {
    isMobile() {
      try {
        return this.$vuetify.breakpoint.mobile
      } catch(error) {
        return false
      }
    },
    title() {
      return `${this.imageList.length} Posts`
    },
    version() {
      return GM_info.script.version
    },
    imageSelected() {
      return this.imageList[this.imageSelectedIndex] || new Post()
    },
    imageSelectedWidth() {
      const width = parseInt(Math.min(this.innerWidth * 0.9, this.imageSelected.sampleWidth))
      const height = Math.min(this.innerHeight * 0.9, this.imageSelected.sampleHeight)
      const width2 = parseInt(height * this.imageSelected.aspectRatio)
      return Math.min(width, width2)
    },
    imageSelectedHeight() {
      const width = Math.min(this.innerWidth * 0.9, this.imageSelected.sampleWidth)
      const height = parseInt(Math.min(this.innerHeight * 0.9, this.imageSelected.sampleHeight))
      const height2 = parseInt(width / this.imageSelected.aspectRatio)
      return Math.min(height, height2)
    },
  },
  watch: {
    showRatingQ(value) {
      localStorage.setItem("showRatingQ", JSON.stringify(value))
    },
    showRatingE(value) {
      localStorage.setItem("showRatingE", JSON.stringify(value))
    },
    imageCountInRow(value) {
      localStorage.setItem("imageCountInRow", JSON.stringify(value))
    },
    imageQualityHigh(value) {
      localStorage.setItem("imageQualityHigh", JSON.stringify(value))
    },
    showFavoriteSuccess(value) {
      console.log('showFavoriteSuccess: ', value)
    },
    showImageSelected(value) {
      if (!value) {
        this.imageSelectedDetail = {}
        return
      }
      this.getPostDetail(this.imageSelected.id).then(res => {
        if (!res) return
        this.imageSelectedDetail = res
      })
    }
  },
  methods: {
    async request() {
      this.requestState = true
      const url = location.origin + location.pathname + ".json?" + this.params.toString()
      const response = await new Promise(resolve => {
        console.log(url)
        jQuery.get(url, data => resolve(data))
      })
      if (response instanceof Array && response.length > 0) {
        window.history.pushState("", "", location.pathname + "?" + this.params.toString())
        response.forEach(item => this.imageList.push(new Post(item)))
        const page = Number(this.params.get("page")) || 1
        this.params.set("page", page + 1)
        // 延迟
        setTimeout(() => (this.requestState = false), 1000)
      } else {
        this.requestStop = true
      }
    },
    download(src, filename) {
      // 添加文件后缀
      const match = src.match(/[.](?<extension>png|jpg|jpeg)$/)
      if (match) {
        const extension = match.groups.extension
        GM_download(src, filename + "." + extension)
      } else {
        GM_download(src, filename)
      }
    },
    // 添加收藏
    onFavorite(id) {
      $.ajax({
        method: 'POST',
        url: "https://yande.re/post/vote.json",
        beforeSend: xhr => xhr.setRequestHeader('x-csrf-token', window.csrfToken),
        data: { id, score: 3 },
        success: data => {
          if (data.success === true) {
            this.imageList[this.imageSelectedIndex].favorite = true // 更新收藏状态
            this.imageSelectedDetail.favorite = true
          }
        },
      })
    },
    async getPostDetail(id) {
      try {
        if (!id) return
        const response = await fetch(`/post.json?api_version=2&tags=id:${id}&include_tags=1&include_votes=1`)
        const result = await response.json()
        return {
          favorite: result.votes[id] == 3,
          artist: Object.keys(result.tags).find(k => result.tags[k] == 'artist')
        }
      } catch (error) {
        console.log('getPostDetail error:', error)
      }
    }
  },
  mounted() {
    // 自动加载数据
    const timeInterval = setInterval(() => {
      if (this.requestStop === true) {
        clearInterval(timeInterval)
        return
      }
      const scrollTop = document.documentElement.scrollTop
      const scrollHeight = document.documentElement.scrollHeight
      const height = window.innerHeight
      if (scrollTop + height >= scrollHeight * 0.75) {
        if (this.requestState === false) {
          this.request()
        }
      }
    }, 1000)
    // 记录窗口尺寸
    window.addEventListener("resize", () => {
      this.innerWidth = window.innerWidth
      this.innerHeight = window.innerHeight
    })
  },
}

export default App


================================================
FILE: source/browse.js
================================================
import app from "./app"

export async function enterBrowseMode() {
  function getScript(url) {
    return new Promise(resolve => jQuery.getScript(url, () => resolve()))
  }

  await getScript("https://cdn.jsdelivr.net/npm/vue@2.6.12/dist/vue.min.js")
  await getScript("https://cdn.jsdelivr.net/npm/vuetify@2.5.0/dist/vuetify.min.js")
  await getScript("https://cdn.jsdelivr.net/npm/vue-masonry-css@1.0.3/dist/vue-masonry.min.js")
  await getScript("https://cdn.jsdelivr.net/npm/jquery@3.6.0/dist/jquery.min.js")

  window.csrfToken = jQuery('[name="csrf-token"]').attr('content')

  document.head.innerHTML = `[{ path: "source/html/head.html" }]`
  document.body.innerHTML = `[{ path: "source/html/body.html" }]`

  Vue.use(VueMasonry)

  new Vue({
    vuetify: new Vuetify({
      theme: { dark: true },
    }),
    render: h => h(app)
  }).$mount("#app")
}


================================================
FILE: source/data/footers.json
================================================

{
  "List": "首页",
  "Browse": "翻阅",
  "Upload": "上传",
  "Random": "随机",
  "Popular": "热门",
  "Image Search": "寻图",
  "History": "历史",
  "Help": "帮助"
}


================================================
FILE: source/data/menus.json
================================================

{
  "My Account": "账户",
  "Posts": "作品",
  "Comments": "评论",
  "Notes": "笔记",
  "Artists": "画师",
  "Tags": "标签",
  "Forum": "论坛",
  "Help": "帮助",
  "More »": "更多>>",
  "New Mail": "新消息",
  "My Profile": "我的资料",
  "My Mail": "我的消息",
  "My Favorites": "我的收藏",
  "Settings": "设置",
  "Change Password": "修改密码",
  "Logout": "退出登录",
  "View Posts": "浏览作品",
  "Search Posts": "搜索作品",
  "Upload": "上传",
  "Random": "随机浏览",
  "Popular": "热门",
  "Image Search": "搜索图片",
  "History": "历史",
  "View Comments": "浏览评论",
  "Search Comments": "搜索评论",
  "View Notes": "浏览笔记",
  "Search Notes": "搜索笔记",
  "View Artists": "浏览画师",
  "Search Artists": "搜索画师",
  "Create": "创建",
  "View Tags": "浏览标签",
  "Search Tags": "搜索标签",
  "Aliases": "别名",
  "Implications": "含义",
  "View Pools": "浏览 Pools",
  "Search Pools": "搜索 Pools",
  "Create New Pool": "创建 Pool",
  "View Wiki Index": "浏览 Wiki 主页",
  "Search Wiki": "搜索 Wiki",
  "Create New Page": "创建新页面",
  "Mark All Read": "全部标记已读"
}


================================================
FILE: source/data/tags.json
================================================
{
  "4koma": "四格漫画",
  "5-toubun_no_hanayome": "五等分的新娘",
  "anal": "肛交",
  "angel": "天使",
  "angel_beats!": "Angel Beats!",
  "animal_ears": "兽耳",
  "anthropomorphization": "拟人化",
  "anus": "肛门露出",
  "areola": "乳晕",
  "arknights": "明日方舟",
  "armor": "盔甲/装甲",
  "artist_revision": "画师修改",
  "ass": "臀部",
  "ass_grab": "持股/捏臀",
  "atelier": "炼金工房系列",
  "autographed": "亲笔签名",
  "azur_lane": "碧蓝航线",
  "bakemonogatari": "化物语",
  "bandages": "绷带",
  "bandaid": "创可贴/绷带",
  "bang_dream!": "BanG Dream!",
  "baseball": "棒球",
  "basketball": "篮球",
  "bathing": "沐浴",
  "benghuai_xueyuan": "崩坏学园",
  "bike_shorts": "自行车短裤",
  "bikini": "比基尼",
  "bikini_armor": "比基尼装甲/轻薄盔甲",
  "bikini_top": "比基尼乳罩",
  "black_rock_shooter": "黑岩射手",
  "blood": "血腥",
  "bloomers": "灯笼裤/宽松短裤",
  "blue_archive": "碧蓝档案",
  "bodysuit": "紧身衣裤",
  "boku_wa_tomodachi_ga_sukunai": "我的朋友很少",
  "bondage": "束缚",
  "bottomless": "下身露出",
  "bra": "乳罩",
  "breast_grab": "握乳",
  "breast_hold": "托乳",
  "breasts": "乳",
  "bukkake": "颜射",
  "bunny_ears": "兔耳",
  "bunny_girl": "兔女郎",
  "buruma": "运动短裤",
  "business_suit": "西装/职业服",
  "calendar": "日历",
  "cameltoe": "阴户凸显",
  "card": "卡牌",
  "card_captor_sakura": "魔卡少女樱",
  "censored": "有码",
  "cg": "CG/计算机动画",
  "chainsaw": "电锯",
  "character_design": "角色设计",
  "cheerleader": "啦啦队队员",
  "chibi": "Q版",
  "chinadress": "旗袍",
  "choujigen_game_neptune": "超次元游戏海王星",
  "christmas": "圣诞",
  "cleavage": "乳沟",
  "code_geass": "反叛的鲁路修",
  "condom": "避孕套",
  "corset": "(束腰)紧身内衣",
  "cosplay": "角色扮演",
  "cream": "奶油",
  "cropped": "裁剪图",
  "crossdress": "变装",
  "crossover": "作品联动/混合同人",
  "cum": "精液",
  "cunnilingus": "品玉/舔阴",
  "dakimakura": "抱枕",
  "darling_in_the_franxx": "DARLING in the FRANXX",
  "date_a_live": "约会大作战",
  "detexted": "去字图片",
  "devil": "魔鬼/恶魔",
  "digital_version": "数字版",
  "dildo": "假阳具",
  "disc_cover": "光盘封面",
  "dress": "连衣裙",
  "dress_shirt": "衬衫",
  "duplicate": "重复图片",
  "elf": "精灵",
  "endcard": "片尾插图",
  "erect_nipples": "乳尖",
  "expression": "角色展示/立绘",
  "extreme_content": "极端",
  "eyepatch": "眼罩",
  "fairy": "精灵/小精灵",
  "fate/kaleid_liner_prisma_illya": "Fate/kaleid liner 魔法少女☆伊莉雅",
  "feet": "足",
  "fellatio": "口交",
  "final_fantasy": "最终幻想",
  "final_fantasy_vii": "最终幻想 VII",
  "final_fantasy_xiv": "最终幻想 14",
  "fingering": "指交",
  "fire_emblem": "火焰纹章",
  "fire_emblem_heroes": "火焰之纹章:英雄云集",
  "fire_emblem_kakusei": "火焰之纹章:觉醒",
  "fire_emblem_three_houses": "火焰之纹章:风花雪月",
  "fishnets": "鱼网袜",
  "fixed": "修改",
  "footjob": "足交",
  "fundoshi": "褌/兜裆布",
  "futanari": "扶她",
  "game_cg": "游戏CG",
  "gangbang": "乱交",
  "garter": "袜带",
  "garter_belt": "吊袜腰带",
  "genderswap": "性转",
  "genshin_impact": "原神",
  "girls_frontline": "少女前线",
  "girls_und_panzer": "少女与战车",
  "gochuumon_wa_usagi_desu_ka?": "请问您今天要来点兔子吗?",
  "gothic_lolita": "哥特式洛丽塔",
  "granblue_fantasy": "碧蓝幻想",
  "guitar": "吉他",
  "gun": "枪炮",
  "gundam": "高达",
  "guro": "猎奇",
  "halloween": "万圣节前夜",
  "handjob": "打手枪",
  "headphones": "耳机",
  "heels": "高跟鞋",
  "heterochromia": "虹膜异色",
  "hibike!_euphonium": "吹响吧!上低音号",
  "highschool_dxd": "恶魔高校D×D",
  "honkai_impact": "崩坏 3",
  "horns": "角",
  "index_page": "索引页面",
  "infinite_stratos": "IS/无限斯特拉托斯",
  "inumimi": "犬耳",
  "japanese_clothes": "日式服装",
  "k-on!": "轻音少女",
  "kaguya-sama_wa_kokurasetai_~tensai-tachi_no_renai_zunousen~": "辉夜大小姐想让我告白~天才们的恋爱头脑战~",
  "kantai_collection": "舰队 Collection",
  "kemono_friends": "兽娘动物园",
  "kimetsu_no_yaiba": "鬼灭之刃",
  "kimono": "和服",
  "kitsune": "狐狸",
  "kobayashi-san_chi_no_maid_dragon": "小林家的龙女仆",
  "kono_subarashii_sekai_ni_shukufuku_wo!": "为美好的世界献上祝福!",
  "lactation": "泌乳",
  "landscape": "风景画",
  "league_of_legends": "英雄联盟",
  "leotard": "紧身连衣裤",
  "line_art": "线条画",
  "lingerie": "贴身内衣",
  "little_busters!": "Little Busters!",
  "loli": "萝莉",
  "lolita_fashion": "洛丽塔",
  "love_live!_nijigasaki_high_school_idol_club": "Love Live! 虹咲学园学园偶像同好会",
  "lucky_star": "幸运星",
  "maebari": "前貼り/遮盖私处",
  "mahou_shoujo_lyrical_nanoha": "魔法少女奈叶",
  "mahou_shoujo_lyrical_nanoha_strikers": "魔法少女奈叶 StrikerS",
  "maid": "女仆",
  "male": "男性",
  "masturbation": "自摸/手淫",
  "mecha": "机甲",
  "mecha_musume": "机甲娘",
  "megane": "眼镜",
  "megaten": "女神转生系列",
  "mermaid": "美人鱼",
  "miko": "巫女",
  "monochrome": "单色",
  "monster": "怪物",
  "monster_girl": "怪物女孩",
  "monster_musume_no_iru_nichijou": "魔物娘的相伴日常",
  "naked": "裸体",
  "naked_apron": "裸体围裙",
  "naked_cape": "裸体披风",
  "naked_ribbon": "裸体丝带",
  "neko": "猫",
  "nekomimi": "猫耳",
  "neon_genesis_evangelion": "新世纪福音战士",
  "nier_automata": "尼尔:自动人形",
  "nijisanji": "彩虹社",
  "ninja": "忍者",
  "nipple_slip": "露点",
  "nipples": "乳头",
  "no_bra": "无乳罩",
  "nopan": "无胖次",
  "nun": "修女",
  "nurse": "护士",
  "official_watermark": "官方水印",
  "onsen": "温泉",
  "open_shirt": "衬衫敞开",
  "ore_no_imouto_ga_konnani_kawaii_wake_ga_nai": "我的妹妹哪有这么可爱!",
  "overalls": "工装连衣裤",
  "overwatch": "守望先锋",
  "paizuri": "乳交",
  "pajama": "睡衣",
  "panties": "内裤",
  "pantsu": "胖次",
  "panty_pull": "胖次脱下",
  "pantyhose": "吊带袜",
  "parody": "仿拟/谐拟",
  "partial_scan": "局部扫描",
  "pasties": "乳贴",
  "pee": "尿尿",
  "penguin": "企鹅",
  "penis": "阴茎",
  "photo": "照片/现实背景",
  "photoshop": "PS 改图",
  "pirate": "海盗",
  "pointy_ears": "尖耳朵",
  "pokemon": "精灵宝可梦",
  "possible_duplicate": "可能重复",
  "pregnant": "孕妇",
  "pretty_cure": "光之美少女",
  "princess_connect": "公主连结",
  "princess_connect!_re:dive": "公主连结 Re:Dive",
  "profile_page": "角色资料页",
  "pubic_hair": "阴毛",
  "puella_magi_madoka_magica": "魔法少女小圆",
  "pussy": "阴户",
  "pussy_juice": "妹汁",
  "queen's_blade": "女王之刃",
  "raw_scan": "扫描原图",
  "re_zero_kara_hajimeru_isekai_seikatsu": "Re:从零开始的异世界生活",
  "robe": "长袍/礼服/睡袍",
  "saenai_heroine_no_sodatekata": "路人女主的养成方法",
  "sailor_moon": "美少女战士",
  "sake": "日本清酒",
  "sample": "样品图",
  "sarashi": "晒し/缠胸布",
  "school_swimsuit": "学校泳衣",
  "see_through": "透视",
  "seifuku": "制服",
  "selfie": "自拍",
  "senran_kagura": "闪乱神乐",
  "sex": "性交",
  "sheets": "床单",
  "shimapan": "条纹胖次",
  "shirt_lift": "衬衫掀起",
  "shota": "正太",
  "silhouette": "剪影/暗色轮廓/体形",
  "sketch": "素描",
  "skirt_lift": "裙摆掀起",
  "sling_bikini": "吊带比基尼",
  "smoking": "吸烟",
  "soccer": "足球",
  "sono_bisque_doll_wa_koi_wo_suru": "更衣人偶坠入爱河",
  "spy_x_family": "间谍过家家",
  "ssss.gridman": "SSSS.古立特",
  "stick_poster": "海报",
  "stockings": "长筒袜",
  "strike_witches": "强袭魔女",
  "string_panties": "细绳胖次",
  "summer_dress": "夏装",
  "suzumiya_haruhi_no_yuuutsu": "凉宫春日的忧郁",
  "sweater": "毛衣",
  "swimsuits": "泳衣",
  "sword": "刀剑",
  "sword_art_online": "刀剑神域",
  "symmetrical_docking": "乳乳相接",
  "tagme": "标签",
  "tail": "兽尾",
  "tan_lines": "日晒线",
  "tattoo": "文身",
  "tennis": "网球",
  "tentacles": "触手",
  "text": "文本",
  "the_idolm@ster": "偶像大师",
  "the_idolm@ster_cinderella_girls": "偶像大师灰姑娘女孩",
  "the_idolm@ster_million_live!": "偶像大师百万现场",
  "the_idolm@ster_shiny_colors": "偶像大师闪耀色彩",
  "thighhighs": "过膝袜",
  "thong": "丁字裤",
  "to_aru_kagaku_no_railgun": "某科学的超电磁炮",
  "to_aru_majutsu_no_index": "魔法禁书目录",
  "to_heart_(series)": "To Heart 系列",
  "to_heart_2": "To Heart 2",
  "to_love_ru": "出包王女",
  "to_love_ru_darkness": "出包王女 Darkness",
  "topless": "上身露出",
  "torn_clothes": "破衣",
  "touhou": "东方",
  "towel": "浴巾",
  "translated": "文字已翻译(英文)",
  "transparent_png": "背景透明",
  "trap": "伪娘",
  "tribadism": "磨豆腐/交叉体位",
  "tutorial": "教程",
  "uma_musume_pretty_derby": "赛马娘",
  "umbrella": "伞",
  "uncensored": "无码",
  "underboob": "南半球",
  "underwear": "内衣",
  "undressing": "脱衣",
  "uniform": "制服",
  "valentine": "情人节",
  "vibrator": "跳蛋",
  "wa_maid": "和风女仆",
  "waitress": "女侍",
  "wallpaper": "壁纸",
  "wardrobe_malfunction": "走光",
  "weapon": "武器",
  "wedding_dress": "婚纱",
  "wet": "湿身",
  "wet_clothes": "湿衣",
  "wings": "翅膀",
  "witch": "女巫",
  "xenoblade": "异度神剑",
  "xenoblade_chronicles_2": "异度神剑 2",
  "yahari_ore_no_seishun_lovecome_wa_machigatteiru.": "我的青春恋爱喜剧果然有问题",
  "yaoi": "蔷薇/男同",
  "yukata": "浴衣",
  "yuri": "百合",
  "zhanjianshaonv": "战舰少女"
}

================================================
FILE: source/hotkey.js
================================================
export const initHotKey = function() {

  window.addEventListener("keyup", function(event) {
    console.log('keyup:', event.key)
    // 有输入框被激活时,禁止触发方向键。
    if (/^(TEXTAREA|INPUT|SELECT|BUTTON)$/.test(document.activeElement.tagName)) return

    // 上一页 ← a A
    const prev = document.querySelector(".pagination>.previous_page") || jQuery("li:contains('Previous') a[href]")[0]
    if (prev && (event.key == "ArrowLeft" || event.key == "a" || event.key == "A")) {
      prev.click()
      return event.preventDefault()
    }
    // 下一页 → d D
    const next = document.querySelector(".pagination>.next_page") || jQuery("li:contains('Next') a[href]")[0]
    if (next && (event.key == "ArrowRight" || event.key === "d" || event.key == "D")) {
      next.click()
      return event.preventDefault()
    }
    // 显示 s S
    const show = document.querySelector("#png") || document.querySelector("#highres")
    if (show && (event.key === "s" || event.key === "S")) {
      show.click()
      return event.preventDefault()
    }
    // 来源 w W
    const where = jQuery("li:contains('Source:') a")[0]
    if (where && (event.key === "w" || event.key === "W")) {
      where.click()
      return event.preventDefault()
    }
  })

  const sidebar = document.querySelector("#post-list > div.sidebar") || document.querySelector("#post-view > div.sidebar")
  if (sidebar) {
    sidebar.insertAdjacentHTML("beforeend", "<div>" +
      "<h5>快捷键说明</h5>" +
      "<div style='color: #ee8888'>上一页:A / ←</div>" +
      "<div style='color: #ee8888'>下一页:D / →</div>" +
      "<div style='color: #ee8888'>显示当前作品原图:S</div>" +
      "<div style='color: #ee8888'>显示当前作品来源:W</div>" +
    "</div>")
  }

}


================================================
FILE: source/html/body.html
================================================

<div id="app"></div>

<script type="text/template" id="app-template">
<v-app>

  <v-app-bar app dense>
    <v-app-bar-nav-icon :x-small="isMobile" @click="showDrawer=!showDrawer"></v-app-bar-nav-icon>
    <v-toolbar-title :style="isMobile ? 'font-size: 12px;' : ''" v-text="title"></v-toolbar-title>
    <!-- 设置分级制度 -->
    <v-menu offset-y>
      <template v-slot:activator="{ on, attrs }">
        <v-btn :x-small="isMobile" class="white--text ml-2" dark v-bind="attrs" v-on="on">
          S{{ showRatingQ ? 'Q' : '' }}{{ showRatingE ? 'E' : '' }}
        </v-btn>
      </template>
      <v-list dense>
        <v-list-item dense>
          <v-list-item-title style="cursor: pointer;" @click="showRatingQ = !showRatingQ;">
            {{ showRatingQ ? '隐藏 Q 级内容' : '显示 Q 级内容' }}
          </v-list-item-title>
        </v-list-item>
        <v-list-item dense>
          <v-list-item-title style="cursor: pointer;" @click="showRatingE = !showRatingE;">
            {{ showRatingE ? '隐藏 E 级内容' : '显示 E 级内容' }}
          </v-list-item-title>
        </v-list-item>
      </v-list>
    </v-menu>
    <!-- 设置图片质量 -->
    <v-menu offset-y>
      <template v-slot:activator="{ on, attrs }">
        <v-btn :x-small="isMobile" class="white--text ml-2" dark v-bind="attrs" v-on="on">{{ imageQualityHigh ? 'HD' : '速' }}</v-btn>
      </template>
      <v-list dense>
        <v-list-item dense>
          <v-list-item-title style="cursor: pointer;" @click="imageQualityHigh = false;">
            图片质量:速览
          </v-list-item-title>
        </v-list-item>
        <v-list-item dense>
          <v-list-item-title style="cursor: pointer;" @click="imageQualityHigh = true;">
            图片质量:高清
          </v-list-item-title>
        </v-list-item>
      </v-list>
    </v-menu>
    <!-- 设置每行几张 -->
    <v-menu offset-y>
      <template v-slot:activator="{ on, attrs }">
        <v-btn :x-small="isMobile" class="white--text ml-2" dark v-bind="attrs" v-on="on">{{imageCountInRow}}列</v-btn>
      </template>
      <v-list dense>
        <v-list-item dense v-for="number in [1, 2, 3, 4, 5, 6, 8, 10, 12, 14, 16, 20]" :key="number">
          <v-list-item-title style="cursor: pointer;" @click="imageCountInRow = number;">
            {{ number }}列
          </v-list-item-title>
        </v-list-item>
      </v-list>
    </v-menu>

    <v-spacer></v-spacer>
    <v-btn
      :style="isMobile ? 'flex: 0 1 auto; overflow: hidden;' : ''" :x-small="isMobile"
      text v-text="'v' + version" color="#ffffff" disabled>
    </v-btn>
  </v-app-bar>

  <v-navigation-drawer v-model="showDrawer" app temporary>
    <v-list-item>
      <v-list-item-content>
        <v-list-item-title class="title">Yande.re 简体中文</v-list-item-title>
        <v-list-item-subtitle>浏览器脚本程序</v-list-item-subtitle>
      </v-list-item-content>
    </v-list-item>

    <v-divider></v-divider>

    <v-list dense nav>
      <v-list-item-content>
        <v-list-item-title class="title">设置</v-list-item-title>
        <v-list-item-subtitle></v-list-item-subtitle>
      </v-list-item-content>
      <!-- s -->
      <v-list-item link>
        <v-list-item-icon class="mr-2">
          <v-icon>mdi-check</v-icon>
        </v-list-item-icon>
        <v-list-item-content>
          <v-list-item-title>显示 S 分级内容</v-list-item-title>
          <v-list-item-subtitle>S(safe) 安全的全年龄内容</v-list-item-subtitle>
        </v-list-item-content>
      </v-list-item>
      <!-- q -->
      <v-list-item link @click="showRatingQ=!showRatingQ;">
        <v-list-item-icon class="mr-2">
          <v-icon v-text="showRatingQ ? 'mdi-check' : 'mdi-close'"></v-icon>
        </v-list-item-icon>
        <v-list-item-content>
          <v-list-item-title v-text="showRatingQ ? '显示 Q 分级内容' : '隐藏 Q 分级内容'"></v-list-item-title>
          <v-list-item-subtitle>Q(questionable) 疑似的成人内容</v-list-item-subtitle>
        </v-list-item-content>
      </v-list-item>
      <!-- e -->
      <v-list-item link @click="showRatingE=!showRatingE;">
        <v-list-item-icon class="mr-2">
          <v-icon v-text="showRatingE ? 'mdi-check' : 'mdi-close'"></v-icon>
        </v-list-item-icon>
        <v-list-item-content>
          <v-list-item-title v-text="showRatingE ? '显示 E 分级内容' : '隐藏 E 分级内容'"></v-list-item-title>
          <v-list-item-subtitle>E(explicit) 明确的成人内容</v-list-item-subtitle>
        </v-list-item-content>
      </v-list-item>
    </v-list>

    <v-divider></v-divider>

    <v-list dense nav>
      <v-list-item-content>
        <v-list-item-title class="title">关于</v-list-item-title>
        <v-list-item-subtitle></v-list-item-subtitle>
      </v-list-item-content>
      <v-list-item link @click="window.open('https://github.com/coderzhaoziwei/yande-re-chinese-patch/blob/main/readme.md')">
        <v-list-item-icon class="mr-2"><v-icon>mdi-file-document-outline</v-icon></v-list-item-icon>
        <v-list-item-content>
          <v-list-item-title>简介</v-list-item-title>
          <v-list-item-subtitle>说明文档 / 功能介绍</v-list-item-subtitle>
        </v-list-item-content>
      </v-list-item>
      <v-list-item link @click="window.open('https://github.com/coderzhaoziwei/yande-re-chinese-patch/issues')">
        <v-list-item-icon class="mr-2"><v-icon>mdi-github</v-icon></v-list-item-icon>
        <v-list-item-content>
          <v-list-item-title>反馈</v-list-item-title>
          <v-list-item-subtitle>发现错误 / 提出建议</v-list-item-subtitle>
        </v-list-item-content>
      </v-list-item>
      <v-list-item link @click="window.open('https://github.com/coderzhaoziwei/yande-re-chinese-patch')">
        <v-list-item-icon class="mr-2"><v-icon>mdi-star</v-icon></v-list-item-icon>
        <v-list-item-content>
          <v-list-item-title>Github</v-list-item-title>
          <v-list-item-subtitle>觉得好用就 Star 支持一下</v-list-item-subtitle>
        </v-list-item-content>
      </v-list-item>
      <v-list-item link>
        <v-list-item-icon class="mr-2"><v-icon>mdi-google-controller</v-icon></v-list-item-icon>
        <v-list-item-content>
          <v-list-item-title>QQ</v-list-item-title>
          <v-list-item-subtitle>3158492760</v-list-item-subtitle>
        </v-list-item-content>
      </v-list-item>
      <v-list-item link>
        <v-list-item-icon class="mr-2"><v-icon>mdi-email</v-icon></v-list-item-icon>
        <v-list-item-content>
          <v-list-item-title>邮箱</v-list-item-title>
          <v-list-item-subtitle>coderzhaoziwei@outlook.com</v-list-item-subtitle>
        </v-list-item-content>
      </v-list-item>
    </v-list>
  </v-navigation-drawer>

  <v-main app>
    <v-container class="pa-2" fluid>
      <masonry ref="masonry" :cols="imageCountInRow" gutter="8px" :key="imageCountInRow">
        <v-card class="mb-2" v-for="(image, index) in imageList" :key="index">
          <v-img
            :src="
              image.isRatingS || (image.isRatingQ && showRatingQ) || (image.isRatingE && showRatingE)
                ? (imageQualityHigh ? image.sampleUrl : image.previewUrl) : ''
            "
            :aspect-ratio="image.aspectRatio"
            @click="if(image.isRatingS||(image.isRatingQ && showRatingQ)||(image.isRatingE && showRatingE)){imageSelectedIndex=index;showImageSelected=true;}"
            @click.middle="imageSelectedIndex = index; window.open('/post/show/' + imageSelected.id)"
          >
            <template v-slot:placeholder>
              <v-row v-if="image.isRatingS||(image.isRatingQ && showRatingQ)||(image.isRatingE && showRatingE)"
                class="fill-height ma-0" align="center" justify="center"
              >
                <v-progress-circular indeterminate color="#ee8888"></v-progress-circular>
              </v-row>
            </template>
            <v-row
              v-if="(image.isRatingS||(image.isRatingQ && showRatingQ)||(image.isRatingE && showRatingE))===false"
              class="fill-height ma-0 text-h5" align="center" justify="center"
              style="color:#ee8888;"
              v-text="image.rating.toUpperCase()"
            ></v-row>
          </v-img>
        </v-card>
      </masonry>

      <div class="d-flex justify-center">
        <v-btn
          :disabled="requestState===false"
          color="#ee8888" text
          v-text="requestStop ? '下面没有了...' : requestState ? '正在加载中...' : ''"
        ></v-btn>
      </div>

      <v-dialog v-model="showImageSelected" :width="imageSelectedWidth" :height="imageSelectedHeight">
        <v-img
          :src="imageSelected.sampleUrl"
          :lazy-src="imageSelected.previewUrl"
          @click="showImageInfo = !showImageInfo;"
        >
          <div
            :style="showImageInfo
              ? 'display: flex; flex-direction: column; height: 100%; padding: 4px; grid-gap: 4px;'
              : 'display: none !important;'"
          >
            <div style="height: 100%; flex: 1 1 auto;"></div>

            <div style="display: flex; flex-direction: column; grid-gap: 4px;">
              <v-chip style="width: fit-content;" color="#009ff088" text-color="#ffffff" small
                v-text="imageSelected.sampleDownloadText"
                @click.stop="download(imageSelected.sampleUrl, imageSelected.sampleDownloadName)"
              ></v-chip>
              <v-chip style="width: fit-content;" color="#009ff088" text-color="#ffffff" small
                v-if="imageSelected.jpegSize !== 0"
                v-text="imageSelected.jpegDownloadText"
                @click.stop="download(imageSelected.jpegUrl, imageSelected.jpegDownloadName)"
              ></v-chip>
              <v-chip style="width: fit-content;" color="#009ff088" text-color="#ffffff" small
                v-text="imageSelected.fileDownloadText"
                @click.stop="download(imageSelected.fileUrl, imageSelected.fileDownloadName)"
              ></v-chip>
            </div>
            <div style="display: flex; grid-gap: 4px; flex-wrap: wrap;">
              <v-chip
                style="width: fit-content;" color="#ee888888" text-color="#ffffff" small
                v-text="imageSelected.id + ' ' + imageSelected.rating.toUpperCase()" @click.stop
              ></v-chip>
              <v-chip class="mr-1" style="width: fit-content;" color="#009ff088" text-color="#ffffff" small
                v-if="imageSelectedDetail.artist"
                @click.stop="window.open('/post?tags='+imageSelectedDetail.artist)"
              >画师 {{imageSelectedDetail.artist}}</v-chip>
              <v-chip class="mr-1" style="width: fit-content;" color="#009ff088" text-color="#ffffff" small
                v-if="imageSelected.sourceUrl !== ''"
                v-text="'来源链接'"
                @click.stop="window.open(imageSelected.sourceUrl)"
              ></v-chip>
              <v-chip class="mr-1" style="width: fit-content;" color="#009ff088" text-color="#ffffff" small
                v-text="'本站链接'"
                @click.stop="window.open('/post/show/' + imageSelected.id)"
              ></v-chip>
              <v-chip class="mr-1" style="width: fit-content;" text-color="#ffffff" small
                :color="imageSelectedDetail.favorite ? '#00900088' : '#009ff088'"
                v-text="imageSelectedDetail.favorite ? '已收藏' : '添加收藏'"
                @click.stop="imageSelectedDetail.favorite ? (void 0) : onFavorite(imageSelected.id)"
              ></v-chip>
            </div>
          </div>
        </v-img>
      </v-dialog>
    </v-container>
  </v-main>
</v-app>
</script>


================================================
FILE: source/html/head.html
================================================

<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no, minimal-ui">
<title>Yande.re 简体中文</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/necolas/normalize.css/normalize.css">
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@mdi/font@5.9.55/css/materialdesignicons.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/vuetify@2.5.0/dist/vuetify.min.css">
<style>
::-webkit-scrollbar {
  display: none;
  width: 0px !important;
}
</style>


================================================
FILE: source/html/options.html
================================================

<div style="padding: 1rem; user-select: none; text-align: center;">
  <select id="showLeftBar" style="height: 1.5rem; line-height: 1.5rem;">
    <option>隐藏左栏</option>
    <option>显示左栏</option>
  </select>
  <select id="showRatingE" style="height: 1.5rem; line-height: 1.5rem; margin-left: 0.25rem;">
    <option>隐藏默认</option>
    <option>显示全部</option>
  </select>
  <select id="showImageHD" style="height: 1.5rem; line-height: 1.5rem; margin-left: 0.25rem;">
    <option>默认尺寸</option>
    <option>二倍尺寸</option>
    <option>三倍尺寸</option>
    <option>四倍尺寸</option>
  </select>
  <button id="enterBrowseMode" style="margin-left: 0.25rem;">进入浏览模式</button>
</div>


================================================
FILE: source/index.js
================================================
import { initStyle } from "./style"
import { initHotKey } from "./hotkey"
import { initOptions } from "./options"
import { initTranslate } from "./translate"


jQuery(document).ready(function() {
  initStyle()
  initHotKey()
  initOptions()
  initTranslate()

  if (document.cookie.includes('locale=zh_CN') === false) {
    document.cookie = "locale=zh_CN"
    location.href = location.href
  }

})


================================================
FILE: source/options.js
================================================
import { enterBrowseMode } from "./browse"

export const onChangeLeftBar = function() {
  const value = Boolean(document.getElementById("showLeftBar").selectedIndex)
  localStorage.setItem("showLeftBar", JSON.stringify(value))
  const element = document.querySelector("#post-list > .sidebar")
  element.setAttribute("show-left-bar", value)

  console.log("showLeftBar", value)
}
export const onChangeRatingE = function() {
  const value = Boolean(document.getElementById("showRatingE").selectedIndex)
  localStorage.setItem("showRatingE", JSON.stringify(value))
  const elementList = document.querySelectorAll(".javascript-hide")
  elementList.forEach(element => element.setAttribute("show-rating-e", value))

  console.log("showRatingE", value)
}
export const onChangeImageHD = function() {
  // 获取 index 值
  const index = document.getElementById("showImageHD").selectedIndex
  // 修改属性 show-image-hd
  const elementList = document.querySelectorAll("#post-list-posts > li > .inner")
  elementList.forEach(element => element.setAttribute("show-image-hd", index))
  // 缓存 index 值
  localStorage.setItem("showImageHD", JSON.stringify(index))
  console.log("showImageHD", index)
  // 设置网格布局宽
  // document.querySelector("#post-list-posts").style.gridTemplateColumns = `repeat(auto-fill, ${(index + 1) * 150}px)`
}
// 网站域名 例如'https://oreno.imouto.us'
const origin = window.location.origin
let taskArray = []
// 'https://oreno.imouto.us'域名下每秒钟尝试加载的Sample图片数
let maxLoadingSampleNum = 4
let doLoadSampleUrl = () => {
  let loadingNum = 0
  let loadSampleUrl = () => {
    if (taskArray.length == 0) return
    loadingNum++
    let { element, sampleUrl } = taskArray.shift()
    element.onerror = () => {
      element.src = sampleUrl
    }
    element.onload = () => {
      loadingNum--
    }
    element.src = sampleUrl
  }
  setInterval(() => {
    if (taskArray.length == 0) return
    let needloadNum = maxLoadingSampleNum - loadingNum
    while (needloadNum--) {
      loadSampleUrl()
    }
  }, 1000)
}
export const initOptions = function() {
  // https://yande.re/user/show/507475
  if (/^\/user\/show\/[\d]{1,}/.test(location.pathname)) return
  if (document.getElementById("post-list-posts") === null) return
  // 插入文档元素
  document.getElementById("post-list-posts").insertAdjacentHTML("beforebegin", `[{ path: "source/html/options.html" }]`)
  // 替换图片元素
  const imageList = document.querySelectorAll("img.preview")
  const samples = JSON.parse(localStorage.getItem("sample_urls"))
  imageList.forEach(element => {
    if (/\/post\/show\/([\d]{1,})/.test(element.nextElementSibling.innerText)) {
      const id = RegExp.$1
      const sampleUrl = samples[id]
      if (sampleUrl !== undefined) {
        element.src = sampleUrl
      }
    }
  })
  doLoadSampleUrl()

  // 监听
  document.getElementById("showLeftBar").addEventListener("change", onChangeLeftBar)
  document.getElementById("showRatingE").addEventListener("change", onChangeRatingE)
  document.getElementById("showImageHD").addEventListener("change", onChangeImageHD)

  // 获取本地记录
  const showLeftBar = JSON.parse(localStorage.getItem("showLeftBar") || "true")
  const showRatingE = JSON.parse(localStorage.getItem("showRatingE") || "true")
  const showImageHD = JSON.parse(localStorage.getItem("showImageHD") || "0")
  document.getElementById("showLeftBar").selectedIndex = showLeftBar
  document.getElementById("showRatingE").selectedIndex = showRatingE
  document.getElementById("showImageHD").selectedIndex = showImageHD
  onChangeLeftBar()
  onChangeRatingE()
  onChangeImageHD()

  // 浏览模式
  document.getElementById("enterBrowseMode").addEventListener("click", enterBrowseMode)
}


================================================
FILE: source/post.js
================================================
/*
approver_id: null
change: 4106973
frames: []
frames_pending: []
frames_pending_string: ""
frames_string: ""
has_children: false
is_held: false
is_note_locked: false
is_pending: false
is_rating_locked: false
is_shown_in_index: true
last_commented_at: 0
last_noted_at: 0
parent_id: null
status: "active"
*/

export default class Post {
  constructor(data) {
    if (typeof data !== "object") data = {}
    this.id = data.id || 0
    this.score = data.score || 0
    this.tags = data.tags || ""
    this.source = data.source || ""

    this.author = data.author || ""
    this.creatorId = data.creator_id || 0
    this.createdAt = data.created_at || 0
    this.updatedAt = data.updated_at || 0

    this.rating = data.rating || "s"
    // 文件
    this.fileUrl = data.file_url || ""
    this.fileExt = data.file_ext || ""
    this.fileSize = data.file_size || 0
    this.width = data.width || 0
    this.height = data.height || 0
    // 高清图
    this.jpegUrl = data.jpeg_url || ""
    this.jpegSize = data.jpeg_file_size || 0
    this.jpegWidth = data.jpeg_width || 0
    this.jpegHeight = data.jpeg_height || 0
    // 缩略图
    this.sampleUrl = data.sample_url
    this.sampleSize = data.sample_file_size || 0
    this.sampleWidth = data.sample_width || 0
    this.sampleHeight = data.sample_height || 0
    // 预览图 用作懒加载的占位图
    this.previewUrl = data.preview_url
    this.previewWidth = data.actual_preview_width || 0
    this.previewHeight = data.actual_preview_height || 0

    this.favorite = false
  }
  // 全年龄 safe
  get isRatingS() {
    return this.rating === "s"
  }
  // 擦边球 questionable
  get isRatingQ() {
    return this.rating === "q"
  }
  // 成人向 explicit
  get isRatingE() {
    return this.rating === "e"
  }
  // 长宽比
  get aspectRatio() {
    return this.width / this.height
  }

  getSizeText(size) {
    if (size > 1024 * 1024) {
      return (size / (1024 * 1024)).toFixed(2) + "MB"
    }
    if (size > 1024) {
      return (size / 1024).toFixed(2) + "KB"
    }
    return (size).toFixed(2) + "B"
  }

  get sampleSizeText() {
    return this.getSizeText(this.sampleSize)
  }
  get sampleDownloadText() {
    return `下载缩略图 ${this.sampleWidth}×${this.sampleHeight} [${this.sampleSizeText}]`
  }
  get sampleDownloadName() {
    return `${location.hostname}.${this.id}.${this.sampleWidth}x${this.sampleHeight}`.replace(/\./g, "_")
  }

  get jpegSizeText() {
    return this.getSizeText(this.jpegSize)
  }
  get jpegDownloadText() {
    return `下载高清图 ${this.jpegWidth}×${this.jpegHeight} [${this.jpegSizeText}]`
  }
  get jpegDownloadName() {
    return `${location.hostname}.${this.id}.${this.jpegWidth}x${this.jpegHeight}`.replace(/\./g, "_")
  }

  get fileSizeText() {
    return this.getSizeText(this.fileSize)
  }
  get fileDownloadText() {
    return `下载原文件 ${this.width}×${this.height} [${this.fileSizeText}] ${this.fileExt.toUpperCase()}`
  }
  get fileDownloadName() {
    return `${location.hostname}.${this.id}.${this.width}x${this.height}`.replace(/\./g, "_")
  }

  get createdTime() {
    const date = new Date(this.createdAt * 1000)
    return `${date.toLocaleDateString()} ${date.toLocaleTimeString("en-DE")}`
  }
  get updatedTime() {
    const date = new Date(this.updatedAt * 1000)
    return `${date.toLocaleDateString()} ${date.toLocaleTimeString("en-DE")}`
  }

  // 解析图源 i.pximg.net => pixiv.net
  // https://i.pximg.net/img-original/img/2021/05/15/09/41/42/89846615_p0.jpg
  // https://www.pixiv.net/artworks/89846615
  get sourceUrl() {
    if (/^https:\/\/i\.pximg\.net\/img-original\/img\/[\d\/]{19}\/([\d]{1,})_p[\d]{1,}\.(jpg|png)$/.test(this.source)) {
      const pid = RegExp.$1
      return `https://pixiv.net/artworks/${pid}`
    }
    return this.source
  }
}


================================================
FILE: source/style/fix.css
================================================

body {
  font-size: 12px;
  padding: 0 0.5rem;
}
body::-webkit-scrollbar {
  display: none;
  width: 0px !important;
}
/* 标题居中 */
div#header {
  margin: 0;
}
div#header > div#title {
  display: flex;
  place-content: center;
  margin: 0 !important;
  height: fit-content;
}
div#header > div#title > h2#site-title {
  display: flex !important;
  flex-direction: column;
}
div#header > div#title > h2#site-title > span {
  font-size: 12px;
  font-weight: normal;
  text-align: right;
}
div#header > div#main-menu {
  padding: 0 !important;
  margin: 0 !important;
  display: flex !important;
  justify-content: center;
  font-size: 14px;
  line-height: 2rem;
  height: 2rem;
}
div#header > div#main-menu > ul {
  margin: 0;
}
/* 通知 */
.status-notice {
  text-align: center;
}
/* 标签前缀 */
li.tag-type-artist a[href^="/post"]:not(.no-browser-link)::before {
  content: "[画师]";
}
li.tag-type-copyright a[href^="/post"]:not(.no-browser-link)::before {
  content: "[原作]";
}
li.tag-type-character a[href^="/post"]:not(.no-browser-link)::before {
  content: "[角色]";
}
li.tag-type-circle a[href^="/post"]:not(.no-browser-link)::before {
  content: "[公司]";
}
/* 图区 */
#post-list {
  display: flex;
  flex-direction: row;
}
#post-list > .sidebar {
  width: auto;
  max-width: 200px;
  flex: 0 0 auto;
}
#post-list > .content {
  width: auto;
  flex: 1 1 auto;
}
#post-list > div.lsidebar {
  display: none;
}
ul#post-list-posts {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  place-content: center;
  place-items: center;
}
ul#post-list-posts > li {
  width: fit-content !important;
  height: 100%;
  margin: 0 !important;
  border: none;
}
ul#post-list-posts > li > div.inner {
  width: auto !important;
  height: fit-content !important;
}
ul#post-list-posts > li > a.directlink {
  font-size: 12px;
  height: 12px;
  line-height: 12px;
  margin: 0;
  padding: 0;
  overflow: hidden;
  background: rgb(16, 16, 16);
}
ul#post-list-posts > li > a.directlink > span.directlink-res {
  display: inline;
}
ul#post-list-posts > li > a.directlink > span.directlink-info {
  display: none;
}
ul#post-list-posts > li > div.inner > a.thumb {
  height: auto;
}
ul#post-list-posts > li > div.inner > a.thumb > img.preview {
  margin: 0 !important; /* @konachan */
  border: none;
}
/* 分页器 */
div#paginator {
  padding: 0;
}
div#paginator > div.pagination {
  line-height: 2rem;
}
/* 页脚 */
#content > div:nth-child(2) > div.sidebar {
  display: none;
}
#content div.footer {
  font-size: 14px;
  margin: 1rem;
}

/* show-left-bar */
.sidebar[show-left-bar=false] {
  display: none !important;
}
/* show-rating-e */
.javascript-hide[show-rating-e=true] {
  display: block !important;
  position: relative;
}
.javascript-hide[show-rating-e=true]::after {
  content: "";
  position: absolute;
  width: 100%;
  height: 100%;
  top: 0;
  box-shadow: 0px 0px 12px rgb(255, 0, 0) inset;
  pointer-events: none;
}
/* show-image-hd */
#post-list-posts > li > .inner[show-image-hd="1"] {
  zoom: 2;
}
#post-list-posts > li > .inner[show-image-hd="2"] {
  zoom: 3;
}
#post-list-posts > li > .inner[show-image-hd="3"] {
  zoom: 4;
}


================================================
FILE: source/style.js
================================================
export const initStyle = function() {
  document.head.insertAdjacentHTML("beforeend", `<style>[{ path: "source/style/fix.css" }]</style>`)
}


================================================
FILE: source/translate.js
================================================
const tags = [{ path: "source/data/tags.json" }]
const menus = [{ path: "source/data/menus.json" }]
const footers = [{ path: "source/data/footers.json" }]

// 翻译标签
export const translateTags = function() {
  const elementList = Array.from(document.getElementsByTagName("a"))
  elementList.forEach(element => {
    const href = element.getAttribute("href")
    if (typeof href === "string" && /^\/post\?tags=(\S+)$/.test(href)) {
      const en = RegExp.$1
      const cn = tags[en]
      if (cn) {
        element.innerText = `[${cn}]${en.replace(/_/g, " ")}`
      }
    }
  })
}

// 翻译菜单
export const translateMenus = function() {
  const mainMenuList = Array.from(document.querySelectorAll("#main-menu>ul>li>a"))
  const subMenuList = Array.from(document.querySelectorAll("ul.submenu>li>a"))
  const elementList = [...mainMenuList, ...subMenuList]
  elementList.forEach(element => {
    if (element.getAttribute("href") === "#") return
    const en = element.innerText
    const cn = menus[en]
    if (cn) {
      element.innerText = cn
    }
  })
}

// 翻译提示
export const translateNotice = function() {
  // EN: This image has been resized. Click on the `View larger version` link in the sidebar
  //     for a high-quality version. Hide this message
  // CN: 这张图片已经被压缩,单击侧边栏中的 `显示高清图` 可以获取更高质量的版本。不再提醒
  // EN: This post belongs to a parent post.
  // EN: This post has child posts. (post #728160, 746235)
  // EN: This post has a child post. (post #383703)
  const elementList = Array.from(document.querySelectorAll(".status-notice"))
  elementList.forEach(element => {
    console.log(element.innerHTML)
    element.innerHTML = element.innerHTML
      .replace(/^[\s]+This image has been resized. Click on the /, "这张图片已经被压缩,单击侧边栏中的")
      .replace(/View larger version/, "显示高清图")
      .replace(/ link in the sidebar for a high-quality version./, "可以获取更高质量的版本。")
      .replace(/Hide this message<\/a>\./, "不再提醒</a>")
      /* 相关父作品 */
      .replace(/This post belongs to a /, "这张图片从属于一个")
      .replace(/parent post<\/a>\./, "相关父作品</a>。")
      /* 相关子作品 */
      .replace(/This post has /, "这张图片从属于一个")
      .replace(/child posts<\/a>\. \(post #/, "作品集</a>。相关子作品:")
      .replace(/a child post<\/a>\. \(post #/, "作品集</a>。相关子作品:")
      .replace(/<\/a>, <a /, "</a> | <a ")
      .replace(/<\/a>\)/, "</a>")
  })
}

// 翻译点击
export const translateButtons = function() {
  [
    ['#highres-show', 'View larger version', '显示高清图'],
    ['#highres', 'Download larger version', '下载高清图'],
    ['#png', 'Download PNG', '下载 PNG 图'],
    ['li#add-to-favs>a', 'Add to favorites', '添加收藏'],
    ['li#set-avatar>a', 'Set avatar', '设置头像'],
    ['h4>a.js-posts-show-edit-tab', 'Edit', '编辑'],
    ['h4>a.js-posts-show-comments-tab', 'Respond', '评论'],
    ['.pagination>.previous_page', '← Previous', '上一页'],
    ['.pagination>.next_page', 'Next →', '下一页'],
  ].forEach(data => {
    const [selector, en, cn] = data
    const element = document.querySelector(selector)
    if (element) {
      element.innerText = element.innerText.replace(en, cn)
    }
  })
}

// 翻译页脚
export const translateFooters = function() {
  const elementList = Array.from(document.querySelectorAll('#subnavbar>li>a'))
  elementList.forEach(element => {
    const en = element.innerText
    const cn = footers[en]
    if (cn) {
      element.innerText = cn
    }
  })
}

// 合并
export const initTranslate = function() {
  translateTags()
  translateMenus()
  translateNotice()
  translateButtons()
  translateFooters()
}


================================================
FILE: temp/css_text_loader.js
================================================
module.exports = function(source) {
  return 'export default ' + JSON.stringify(source)
}


================================================
FILE: temp/index.js
================================================
/**
 * 默认显示隐藏的作品,并提供可开关的选项。
 */
;(function() {
  const SET_JS_HIDE = 'set-javascript-hide'
  const INPUT_HTML = `<input type="checkbox" id="${SET_JS_HIDE}"> <label for="${SET_JS_HIDE}">显示隐藏的作品</label>`

  const target = document.getElementById('post-list-posts')
  if (target && target.parentNode) {
    const div = document.createElement('div')
    target.parentNode.insertBefore(div, target)
    div.innerHTML = INPUT_HTML
    // div.setAttribute('style', 'user-select: none; text-align: left;')
    div.setAttribute('id', 'script-addition')
  } else return

  const checkbox = document.getElementById(SET_JS_HIDE)
  checkbox.checked = JSON.parse(localStorage.getItem(SET_JS_HIDE))
  update()
  checkbox.addEventListener('change', update)

  function update() {
    Array.from(document.querySelectorAll('.javascript-hide')).forEach(element => {
      if (checkbox.checked) element.removeClassName(SET_JS_HIDE)
      else element.addClassName(SET_JS_HIDE)
    })
    localStorage.setItem(SET_JS_HIDE, checkbox.checked)
  }
})()

/**
 * 默认尺寸 h=150px
 * 大图模式 h=300px
 * 高清模式 显示高清大图 parent
 */
;(function() {
  const target = document.getElementById(`script-addition`)

  if (target) {
    // const SET_POST_HD = `set-post-hd`
    // const isHD = Boolean(JSON.parse(localStorage.getItem(SET_POST_HD)))

    const SET_POST_SIZE = `set-post-size`
    if (getHDValue() === false) {
      target.insertAdjacentHTML(`beforeend`, `<input type="checkbox" id="${SET_POST_SIZE}1">`)
      target.insertAdjacentHTML(`beforeend`, `<label for="${SET_POST_SIZE}1">默认尺寸</label>`)
      target.insertAdjacentHTML(`beforeend`, `<input type="checkbox" id="${SET_POST_SIZE}2">`)
      target.insertAdjacentHTML(`beforeend`, `<label for="${SET_POST_SIZE}2">大图模式</label>`)
    }
    target.insertAdjacentHTML(`beforeend`, `<input type="checkbox" id="${SET_POST_SIZE}3">`)
    target.insertAdjacentHTML(`beforeend`, `<label for="${SET_POST_SIZE}2">高清模式</label>`)

    const checkbox1 = document.getElementById(`${SET_POST_SIZE}1`)
    const checkbox2 = document.getElementById(`${SET_POST_SIZE}2`)
    const checkbox3 = document.getElementById(`${SET_POST_SIZE}3`)
    update()

    checkbox1 && checkbox1.addEventListener(`change`, changeValue)
    checkbox2 && checkbox2.addEventListener(`change`, changeValue)
    checkbox3.addEventListener(`change`, changeHDValue)

    function getValue() {
      return Boolean(JSON.parse(localStorage.getItem(SET_POST_SIZE)))
    }
    function changeValue() {
      const value = getValue()
      localStorage.setItem(SET_POST_SIZE, JSON.stringify(!value))
      location.reload() // 刷新页面
    }
    function getHDValue() {
      return Boolean(JSON.parse(localStorage.getItem(`set-post-hd`)))
    }
    function changeHDValue() {
      const value = getHDValue()
      localStorage.setItem(`set-post-hd`, JSON.stringify(!value))
      location.reload() // 刷新页面
    }
    function update() {
      if (getHDValue() === true) {
        checkbox3.checked = true
        target.insertAdjacentHTML(`beforeend`, `<div id="script-notice-hd">高清模式加载缓慢,可能需要等待一段时间。</div>`)
        // setTimeout(() => document.getElementById(`script-notice-hd`).remove(), 10000)
        const imageList = document.querySelectorAll(`img.preview`)
        imageList.forEach(x => x.src = x.parentNode.parentNode.nextElementSibling.href)
        document.head.insertAdjacentHTML(`beforeend`, `<style>#post-list-posts>li>.inner{zoom:2.5;}</style>`)
      } else if (getValue() === false) {
        checkbox1.checked = true
        checkbox2.checked = false
        checkbox3.checked = false
      } else {
        checkbox1.checked = false
        checkbox2.checked = true
        checkbox3.checked = false
        document.head.insertAdjacentHTML(`beforeend`, `<style>img.preview{width:auto;height:auto;}</style>`)
        document.head.insertAdjacentHTML(`beforeend`, `<style>#post-list-posts>li>.inner{height:auto !important;}</style>`)
      }
    }
  }
})()


================================================
FILE: temp/index.user.js
================================================
// ==UserScript==
// @name         Yande.re 简体中文
// @namespace    com.coderzhaoziwei.yandere
// @version      1.0.10
// @author       Coder Zhao
// @description  Y 站简体中文补丁| 显示隐藏作品 | 高清大图模式 | 界面布局优化 | 方向键翻页 | Simplified Chinese patch for Yande.re
// @modified     2021/5/7 01:12:31
// @license      MIT
// @homepage     https://greasyfork.org/zh-CN/scripts/421970
// @match        https://yande.re/*
// @exclude      https://yande.re/forum/*
// @match        https://yande.in/*
// @match        https://oreno.imouto.us/*
// @match        https://konachan.com/*
// @supportURL   https://github.com/coderzhaoziwei/yande-re-chinese-patch/issues
// @grant        none
// ==/UserScript==

/******/ (() => { // webpackBootstrap
/******/ 	"use strict";
/******/ 	var __webpack_modules__ = ({

/***/ 798:
/***/ (() => {


;// CONCATENATED MODULE: ./tags.json
const tags_namespaceObject = JSON.parse('{"anal":"肛交","angel":"天使","animal_ears":"兽耳","anus":"肛门露出","areola":"乳晕","armor":"盔甲/装甲","artist_revision":"画师修改","ass":"臀部","ass_grab":"持股/捏臀","bandages":"绷带","bathing":"沐浴","bikini":"比基尼","bikini_armor":"比基尼装甲/轻薄盔甲","bikini_top":"比基尼乳罩","blood":"血腥","bloomers":"灯笼裤/宽松短裤","bodysuit":"紧身衣裤","bondage":"束缚","bottomless":"下身露出","bra":"乳罩","breast_grab":"握乳","breast_hold":"托乳","breasts":"乳","bunny_ears":"兔耳","bunny_girl":"兔女郎","buruma":"运动短裤","calendar":"日历","cameltoe":"阴户凸显","censored":"有码","cheerleader":"啦啦队队员","chibi":"Q版","chinadress":"旗袍","christmas":"圣诞","cleavage":"乳沟","cream":"奶油","crossdress":"变装","cum":"精液","dakimakura":"抱枕","digital_version":"数字版","dildo":"假阳具","disc_cover":"光盘封面","dress":"连衣裙","dress_shirt":"衬衫","elf":"精灵","erect_nipples":"乳尖","extreme_content":"极端","eyepatch":"眼罩","feet":"足","fellatio":"口交","fishnets":"鱼网袜","fixed":"修改","footjob":"足交","futanari":"扶她","game_cg":"游戏CG","gangbang":"乱交","garter":"袜带","garter_belt":"吊袜腰带","guitar":"吉他","gun":"枪炮","guro":"猎奇","halloween":"万圣节前夜","handjob":"打手枪","headphones":"耳机","heels":"高跟鞋","heterochromia":"虹膜异色","horns":"角","japanese_clothes":"日式服装","kimono":"和服","kitsune":"狐狸","landscape":"风景画","leotard":"紧身连衣裤","lingerie":"贴身内衣","loli":"萝莉","lolita_fashion":"洛丽塔","maid":"女仆","male":"男性","masturbation":"自摸/手淫","mecha":"机甲","megane":"眼镜","miko":"巫女","monochrome":"单色","naked":"裸体","naked_apron":"裸体围裙","naked_cape":"裸体披风","neko":"猫","nekomimi":"猫耳","nipples":"乳头","no_bra":"无乳罩","nopan":"无胖次","nurse":"护士","onsen":"温泉","open_shirt":"衬衫敞开","paizuri":"乳交","pajama":"睡衣","pantsu":"胖次","panty_pull":"胖次脱下","pantyhose":"吊带袜","partial_scan":"局部扫描","penis":"阴茎","pointy_ears":"尖耳朵","pubic_hair":"阴毛","pussy":"阴户","pussy_juice":"妹汁","school_swimsuit":"学校泳衣","see_through":"透视","seifuku":"制服","sex":"性交","sheets":"床单","shimapan":"条纹胖次","shirt_lift":"衬衫掀起","shota":"正太","sketch":"素描","skirt_lift":"裙摆掀起","stockings":"长筒袜","string_panties":"细绳胖次","sweater":"毛衣","swimsuits":"泳衣","sword":"刀剑","symmetrical_docking":"乳乳相接","tagme":"标签","tail":"兽尾","tan_lines":"日晒线","tattoo":"文身","tentacles":"触手","text":"文本","thighhighs":"过膝袜","thong":"丁字裤","topless":"上身露出","torn_clothes":"破衣","towel":"浴巾","transparent_png":"背景透明","trap":"伪娘","umbrella":"伞","uncensored":"无码","underboob":"南半球/下乳露出","undressing":"脱衣","uniform":"制服","vibrator":"跳蛋","waitress":"女侍","wallpaper":"壁纸","weapon":"武器","wedding_dress":"婚纱","wet":"湿身","wet_clothes":"湿衣","wings":"翅膀","witch":"女巫","yaoi":"蔷薇/男同","yukata":"浴衣","yuri":"百合"}');
;// CONCATENATED MODULE: ./style.css
/* harmony default export */ const style = ("/* 标签前缀 */\nli.tag-type-artist a:nth-child(4)::before {\n  content: '[画师]';\n}\nli.tag-type-copyright a:nth-child(4)::before {\n  content: '[原作]';\n}\nli.tag-type-character a:nth-child(4)::before {\n  content: '[角色]';\n}\nli.tag-type-circle a:nth-child(4)::before {\n  content: '[公司]';\n}\n\n/* 字体大小 */\nbody {\n  font-size: 12px;\n  padding: 12px 4px;\n}\n\n/* 标题居中 */\n#title {\n  display: flex;\n  justify-content: center;\n  margin: 0 0 0 0 !important;\n}\n#site-title {\n  display: flex !important;\n}\n#main-menu {\n  padding: 0 !important;\n  margin: 0 !important;\n  display: flex !important;\n  justify-content: center;\n}\n\n/* 通知居中 */\n.status-notice {\n  text-align: center;\n}\n\n/* 图片区域 */\n#post-list {\n  display: flex;\n  flex-direction: row;\n}\n#post-list > .sidebar {\n  width: auto;\n  max-width: 200px;\n  flex: 0 0 auto;\n}\n#post-list > .content {\n  width: auto;\n  flex: 0 1 auto;\n}\n\n#post-list-posts {\n  display: flex !important;\n  flex-wrap: wrap;\n  justify-content: center;\n}\n#post-list-posts > li {\n  width: auto !important;\n  height: auto !important;\n  margin: 0 8px 8px 0 !important; /* 图片区域间距 */\n  border: 1px solid rgba(0, 0, 0, 0);\n}\n#post-list-posts > li.javascript-hide:not(.set-javascript-hide) {\n  display: block !important;\n  position: relative;\n}\n#post-list-posts > li.javascript-hide::after {\n  content: \"\";\n  position: absolute;\n  width: 100%;\n  height: 100%;\n  top: 0;\n  box-shadow: 0px 0px 12px rgb(255, 0, 0) inset;\n  pointer-events: none;\n}\n#post-list-posts > li > .inner {\n  width: auto !important;\n  height: 150px !important;\n  display: flex;\n  align-items: center;\n}\n#post-list-posts > li > .inner > .thumb {\n  height: auto;\n}\n#post-list-posts > li > .largeimg.directlink {\n  height: 12px;\n  font-size: 12px;\n  line-height: 12px;\n  padding: 0;\n  margin: 2px 0 0 0;\n  overflow: hidden;\n}\n\n/* 脚本添加的内容 */\n#script-addition {\n  user-select: none;\n  /* text-align: right; */\n  /* font-weight: 100; */\n  padding: 8px 12px;\n  display: flex;\n}\n#script-addition > input {\n  margin-left: 1em;\n}\n#script-notice-hd {\n  margin-left: 0.5em;\n  font-weight: bold;\n  color: #ee8887;\n}\n\n/* 隐藏浏览器默认显示的滚动条 */\nbody::-webkit-scrollbar {\n  display: none;\n  width: 0px !important;\n}\n");
;// CONCATENATED MODULE: ./index.js

(function() {
  const list = Array.from(document.getElementsByTagName('a'))
  list.forEach(a => {
    const href = a.getAttribute('href')
    if (typeof href === 'string' && /^\/post\?tags=(\S+)$/.test(href)) {
      const en = RegExp.$1
      const cn = tags_namespaceObject[en]
      if (cn) a.innerText = `[${cn}]${en.replace(/_/g, ' ')}`
    }
  })
})()

/**
 * 创建 <style> 标签,导入 style.css 的内容。
 */
;(function() {
  const element = document.createElement('style')
  element.innerHTML = style
  document.head.appendChild(element)
})()

/**
 * 导航栏,替换主菜单和子菜单的部分文本内容。
 */
;(function() {
  const list1 = Array.from(document.querySelectorAll('#main-menu>ul>li>a'))
  const list2 = Array.from(document.querySelectorAll('ul.submenu>li>a'))
  const list  = [...list1, ...list2]

  list.forEach(menu => {
    if (menu.getAttribute('href') === '#') return
    const en = menu.innerText
    const cn = {
      /* 主菜单 */
      'My Account': '账户',
      'Posts':      '作品',
      'Comments':   '评论',
      'Notes':      '笔记',
      'Artists':    '画师',
      'Tags':       '标签',
      'Forum':      '论坛',
      'Help':       '帮助',
      'More »':     '更多>>',
      'New Mail':   '新消息',
      /* 子菜单 */
      'My Profile':      '我的资料',
      'My Mail':         '我的消息',
      'My Favorites':    '我的收藏',
      'Settings':        '设置',
      'Change Password': '修改密码',
      'Logout':          '退出登录',
      'View Posts':      '浏览作品',
      'Search Posts':    '搜索作品',
      'Upload':          '上传',
      'Random':          '随机浏览',
      'Popular':         '热门',
      'Image Search':    '搜索图片',
      'History':         '历史',
      'View Comments':   '浏览评论',
      'Search Comments': '搜索评论',
      'View Notes':      '浏览笔记',
      'Search Notes':    '搜索笔记',
      'View Artists':    '浏览画师',
      'Search Artists':  '搜索画师',
      'Create':          '创建',
      'View Tags':       '浏览标签',
      'Search Tags':     '搜索标签',
      'Aliases':         '别名',
      'Implications':    '含义',
      'View Pools':      '浏览 Pools',
      'Search Pools':    '搜索 Pools',
      'Create New Pool': '创建 Pool',
      'View Wiki Index': '浏览 Wiki 主页',
      'Search Wiki':     '搜索 Wiki',
      'Create New Page': '创建新页面',
      'Mark All Read':   '全部标记已读',
    }[en]
    if (typeof cn === 'string') menu.innerText = cn
  })
})()

/**
 * 提示信息
 * EN: This image has been resized. Click on the `View larger version` link in the sidebar
 *     for a high-quality version. Hide this message
 * CN: 这张图片已经被压缩,单击侧边栏中的 `显示高清图` 可以获取更高质量的版本。不再提醒
 *
 * EN: This post belongs to a parent post.
 * EN: This post has child posts. (post #728160, 746235)
 * EN: This post has a child post. (post #383703)
  </div>
 */
;(function() {
  Array.from(document.querySelectorAll('.status-notice')).forEach(element => {
    console.log(element.innerHTML)
    element.innerHTML = element.innerHTML
      .replace(/^[\s]+This image has been resized. Click on the /, '这张图片已经被压缩,单击侧边栏中的')
      .replace(/View larger version/, '显示高清图')
      .replace(/ link in the sidebar for a high-quality version./, '可以获取更高质量的版本。')
      .replace(/Hide this message<\/a>\./, '不再提醒</a>')
      /* 相关父作品 */
      .replace(/This post belongs to a /, '这张图片从属于一个').replace(/parent post<\/a>\./, '相关父作品</a>。')
      /* 相关子作品 */
      .replace(/This post has /, '这张图片从属于一个')
      .replace(/child posts<\/a>\. \(post #/, '作品集</a>。相关子作品:')
      .replace(/a child post<\/a>\. \(post #/, '作品集</a>。相关子作品:')
      .replace(/<\/a>, <a /, '</a> | <a ').replace(/<\/a>\)/, '</a>')
  })
})()

/**
 * 快捷操作,替换指定的元素的文本内容。
 */
;(function() {
  const translate = function(selector, en, cn) {
    const element = document.querySelector(selector)
    if (element) element.innerText = element.innerText.replace(en, cn)
  }
  const list = [
    ['#highres-show',                   'View larger version',     '显示高清图'],
    ['#highres',                        'Download larger version', '下载高清图'],
    ['#png',                            'Download PNG',            '下载 PNG 图'],
    ['li#add-to-favs>a',                'Add to favorites',        '添加收藏'],
    ['li#set-avatar>a',                 'Set avatar',              '设置头像'],
    ['h4>a.js-posts-show-edit-tab',     'Edit',                    '编辑'],
    ['h4>a.js-posts-show-comments-tab', 'Respond',                 '评论'],
    ['.pagination>.previous_page',      '← Previous',              '上一页'],
    ['.pagination>.next_page',          'Next →',                  '下一页'],
  ]
  list.forEach(item => translate(...item))
  /* 页脚 */
  Array.from(document.querySelectorAll('#subnavbar>li>a')).forEach(a => {
    const en = a.innerText
    const cn = {
      'List': '首页',
      'Browse': '翻阅',
      'Upload': '上传',
      'Random': '随机',
      'Popular': '热门',
      'Image Search': '寻图',
      'History': '历史',
      'Help': '帮助',
    }[en]
    if (cn) a.innerText = cn
  })
})()

/**
 * 翻页功能,使用键盘左右方向键控制。
 */
;(function() {
  window.addEventListener('keyup', function(event) {
    /* 在输入的情况下,方向键禁止触发翻页。 */
    if (/^(TEXTAREA|INPUT|SELECT|BUTTON)$/.test(document.activeElement.tagName)) return

    const prev = document.querySelector('.pagination>.previous_page')
    const next = document.querySelector('.pagination>.next_page')
    if (event.key == 'ArrowLeft'  && prev) {
      prev.click()
      return event.preventDefault()
    }
    if (event.key == 'ArrowRight' && next) {
      next.click()
      return event.preventDefault()
    }
  })
})()

/**
 * 默认显示隐藏的作品,并提供可开关的选项。
 */
;(function() {
  const SET_JS_HIDE = 'set-javascript-hide'
  const INPUT_HTML = `<input type="checkbox" id="${SET_JS_HIDE}"> <label for="${SET_JS_HIDE}">显示隐藏的作品</label>`

  const target = document.getElementById('post-list-posts')
  if (target && target.parentNode) {
    const div = document.createElement('div')
    target.parentNode.insertBefore(div, target)
    div.innerHTML = INPUT_HTML
    // div.setAttribute('style', 'user-select: none; text-align: left;')
    div.setAttribute('id', 'script-addition')
  } else return

  const checkbox = document.getElementById(SET_JS_HIDE)
  checkbox.checked = JSON.parse(localStorage.getItem(SET_JS_HIDE))
  update()
  checkbox.addEventListener('change', update)

  function update() {
    Array.from(document.querySelectorAll('.javascript-hide')).forEach(element => {
      if (checkbox.checked) element.removeClassName(SET_JS_HIDE)
      else element.addClassName(SET_JS_HIDE)
    })
    localStorage.setItem(SET_JS_HIDE, checkbox.checked)
  }
})()

/**
 * 默认尺寸 h=150px
 * 大图模式 h=300px
 * 高清模式 显示高清大图
 */
;(function() {
  const target = document.getElementById(`script-addition`)

  if (target) {
    // const SET_POST_HD = `set-post-hd`
    // const isHD = Boolean(JSON.parse(localStorage.getItem(SET_POST_HD)))

    const SET_POST_SIZE = `set-post-size`
    if (getHDValue() === false) {
      target.insertAdjacentHTML(`beforeend`, `<input type="checkbox" id="${SET_POST_SIZE}1">`)
      target.insertAdjacentHTML(`beforeend`, `<label for="${SET_POST_SIZE}1">默认尺寸</label>`)
      target.insertAdjacentHTML(`beforeend`, `<input type="checkbox" id="${SET_POST_SIZE}2">`)
      target.insertAdjacentHTML(`beforeend`, `<label for="${SET_POST_SIZE}2">大图模式</label>`)
    }
    target.insertAdjacentHTML(`beforeend`, `<input type="checkbox" id="${SET_POST_SIZE}3">`)
    target.insertAdjacentHTML(`beforeend`, `<label for="${SET_POST_SIZE}2">高清模式</label>`)

    const checkbox1 = document.getElementById(`${SET_POST_SIZE}1`)
    const checkbox2 = document.getElementById(`${SET_POST_SIZE}2`)
    const checkbox3 = document.getElementById(`${SET_POST_SIZE}3`)
    update()

    checkbox1 && checkbox1.addEventListener(`change`, changeValue)
    checkbox2 && checkbox2.addEventListener(`change`, changeValue)
    checkbox3.addEventListener(`change`, changeHDValue)

    function getValue() {
      return Boolean(JSON.parse(localStorage.getItem(SET_POST_SIZE)))
    }
    function changeValue() {
      const value = getValue()
      localStorage.setItem(SET_POST_SIZE, JSON.stringify(!value))
      location.reload() // 刷新页面
    }
    function getHDValue() {
      return Boolean(JSON.parse(localStorage.getItem(`set-post-hd`)))
    }
    function changeHDValue() {
      const value = getHDValue()
      localStorage.setItem(`set-post-hd`, JSON.stringify(!value))
      location.reload() // 刷新页面
    }
    function update() {
      if (getHDValue() === true) {
        checkbox3.checked = true
        target.insertAdjacentHTML(`beforeend`, `<div id="script-notice-hd">高清模式加载缓慢,可能需要等待一段时间。</div>`)
        // setTimeout(() => document.getElementById(`script-notice-hd`).remove(), 10000)
        const imageList = document.querySelectorAll(`img.preview`)
        imageList.forEach(x => x.src = x.parentNode.parentNode.nextElementSibling.href)
        document.head.insertAdjacentHTML(`beforeend`, `<style>#post-list-posts>li>.inner{zoom:2.5;}</style>`)
      } else if (getValue() === false) {
        checkbox1.checked = true
        checkbox2.checked = false
        checkbox3.checked = false
      } else {
        checkbox1.checked = false
        checkbox2.checked = true
        checkbox3.checked = false
        document.head.insertAdjacentHTML(`beforeend`, `<style>img.preview{width:auto;height:auto;}</style>`)
        document.head.insertAdjacentHTML(`beforeend`, `<style>#post-list-posts>li>.inner{height:auto !important;}</style>`)
      }
    }
  }
})()


/***/ })

/******/ 	});
/************************************************************************/
/******/ 	
/******/ 	// startup
/******/ 	// Load entry module and return exports
/******/ 	// This entry module doesn't tell about it's top-level declarations so it can't be inlined
/******/ 	var __webpack_exports__ = {};
/******/ 	__webpack_modules__[798]();
/******/ 	
/******/ })()
;

================================================
FILE: temp/webpack.config.js
================================================
const path = require('path')
const config = require('./package.json')
const { BannerPlugin } = require('webpack')

const banner = `
`

module.exports = {
  mode: 'production',
  entry: './index.js',
  output: {
    path: path.resolve(__dirname, ''),
    filename: `index.user.js`,
  },
  module: {
    rules: [
      { test: /\.css$/, use: './css_text_loader.js' },
    ],
  },
  plugins: [
    new BannerPlugin({ banner, raw: true, entryOnly: true }),
  ],
  optimization: {
    minimize: true,
    minimizer: [],
  },
}
Download .txt
gitextract_unlq4rb5/

├── .editorconfig
├── .gitignore
├── .vscode/
│   └── custom.code-snippets
├── bundle/
│   ├── index.js
│   └── index.user.js
├── config/
│   └── rollup.config.js
├── package.json
├── readme.md
├── readme_main.md
├── script/
│   ├── action.js
│   ├── bundle.sh
│   └── replace.js
├── source/
│   ├── app.js
│   ├── browse.js
│   ├── data/
│   │   ├── footers.json
│   │   ├── menus.json
│   │   └── tags.json
│   ├── hotkey.js
│   ├── html/
│   │   ├── body.html
│   │   ├── head.html
│   │   └── options.html
│   ├── index.js
│   ├── options.js
│   ├── post.js
│   ├── style/
│   │   └── fix.css
│   ├── style.js
│   └── translate.js
└── temp/
    ├── css_text_loader.js
    ├── index.js
    ├── index.user.js
    └── webpack.config.js
Download .txt
SYMBOL INDEX (130 symbols across 9 files)

FILE: bundle/index.js
  class Post (line 69) | class Post {
    method constructor (line 70) | constructor(data) {
    method isRatingS (line 99) | get isRatingS() {
    method isRatingQ (line 102) | get isRatingQ() {
    method isRatingE (line 105) | get isRatingE() {
    method aspectRatio (line 108) | get aspectRatio() {
    method getSizeText (line 111) | getSizeText(size) {
    method sampleSizeText (line 120) | get sampleSizeText() {
    method sampleDownloadText (line 123) | get sampleDownloadText() {
    method sampleDownloadName (line 126) | get sampleDownloadName() {
    method jpegSizeText (line 129) | get jpegSizeText() {
    method jpegDownloadText (line 132) | get jpegDownloadText() {
    method jpegDownloadName (line 135) | get jpegDownloadName() {
    method fileSizeText (line 138) | get fileSizeText() {
    method fileDownloadText (line 141) | get fileDownloadText() {
    method fileDownloadName (line 144) | get fileDownloadName() {
    method createdTime (line 147) | get createdTime() {
    method updatedTime (line 151) | get updatedTime() {
    method sourceUrl (line 155) | get sourceUrl() {
  method data (line 166) | data() {
  method isMobile (line 187) | isMobile() {
  method title (line 194) | title() {
  method version (line 197) | version() {
  method imageSelected (line 200) | imageSelected() {
  method imageSelectedWidth (line 203) | imageSelectedWidth() {
  method imageSelectedHeight (line 209) | imageSelectedHeight() {
  method showRatingQ (line 217) | showRatingQ(value) {
  method showRatingE (line 220) | showRatingE(value) {
  method imageCountInRow (line 223) | imageCountInRow(value) {
  method imageQualityHigh (line 226) | imageQualityHigh(value) {
  method showFavoriteSuccess (line 229) | showFavoriteSuccess(value) {
  method showImageSelected (line 232) | showImageSelected(value) {
  method request (line 244) | async request() {
  method download (line 261) | download(src, filename) {
  method onFavorite (line 270) | onFavorite(id) {
  method getPostDetail (line 284) | async getPostDetail(id) {
  method mounted (line 298) | mounted() {
  function enterBrowseMode (line 320) | async function enterBrowseMode() {

FILE: bundle/index.user.js
  class Post (line 226) | class Post {
    method constructor (line 227) | constructor(data) {
    method isRatingS (line 256) | get isRatingS() {
    method isRatingQ (line 259) | get isRatingQ() {
    method isRatingE (line 262) | get isRatingE() {
    method aspectRatio (line 265) | get aspectRatio() {
    method getSizeText (line 268) | getSizeText(size) {
    method sampleSizeText (line 277) | get sampleSizeText() {
    method sampleDownloadText (line 280) | get sampleDownloadText() {
    method sampleDownloadName (line 283) | get sampleDownloadName() {
    method jpegSizeText (line 286) | get jpegSizeText() {
    method jpegDownloadText (line 289) | get jpegDownloadText() {
    method jpegDownloadName (line 292) | get jpegDownloadName() {
    method fileSizeText (line 295) | get fileSizeText() {
    method fileDownloadText (line 298) | get fileDownloadText() {
    method fileDownloadName (line 301) | get fileDownloadName() {
    method createdTime (line 304) | get createdTime() {
    method updatedTime (line 308) | get updatedTime() {
    method sourceUrl (line 312) | get sourceUrl() {
  method data (line 323) | data() {
  method isMobile (line 344) | isMobile() {
  method title (line 351) | title() {
  method version (line 354) | version() {
  method imageSelected (line 357) | imageSelected() {
  method imageSelectedWidth (line 360) | imageSelectedWidth() {
  method imageSelectedHeight (line 366) | imageSelectedHeight() {
  method showRatingQ (line 374) | showRatingQ(value) {
  method showRatingE (line 377) | showRatingE(value) {
  method imageCountInRow (line 380) | imageCountInRow(value) {
  method imageQualityHigh (line 383) | imageQualityHigh(value) {
  method showFavoriteSuccess (line 386) | showFavoriteSuccess(value) {
  method showImageSelected (line 389) | showImageSelected(value) {
  method request (line 401) | async request() {
  method download (line 418) | download(src, filename) {
  method onFavorite (line 427) | onFavorite(id) {
  method getPostDetail (line 441) | async getPostDetail(id) {
  method mounted (line 455) | mounted() {
  function enterBrowseMode (line 477) | async function enterBrowseMode() {

FILE: script/action.js
  function formatTagsFile (line 11) | function formatTagsFile() {
  function generateReadMeFile (line 19) | function generateReadMeFile() {
  function info (line 31) | function info(log) {

FILE: script/replace.js
  function handler (line 14) | function handler(content) {

FILE: source/app.js
  method data (line 5) | data() {
  method isMobile (line 32) | isMobile() {
  method title (line 39) | title() {
  method version (line 42) | version() {
  method imageSelected (line 45) | imageSelected() {
  method imageSelectedWidth (line 48) | imageSelectedWidth() {
  method imageSelectedHeight (line 54) | imageSelectedHeight() {
  method showRatingQ (line 62) | showRatingQ(value) {
  method showRatingE (line 65) | showRatingE(value) {
  method imageCountInRow (line 68) | imageCountInRow(value) {
  method imageQualityHigh (line 71) | imageQualityHigh(value) {
  method showFavoriteSuccess (line 74) | showFavoriteSuccess(value) {
  method showImageSelected (line 77) | showImageSelected(value) {
  method request (line 89) | async request() {
  method download (line 107) | download(src, filename) {
  method onFavorite (line 118) | onFavorite(id) {
  method getPostDetail (line 132) | async getPostDetail(id) {
  method mounted (line 146) | mounted() {

FILE: source/browse.js
  function enterBrowseMode (line 3) | async function enterBrowseMode() {

FILE: source/post.js
  class Post (line 20) | class Post {
    method constructor (line 21) | constructor(data) {
    method isRatingS (line 58) | get isRatingS() {
    method isRatingQ (line 62) | get isRatingQ() {
    method isRatingE (line 66) | get isRatingE() {
    method aspectRatio (line 70) | get aspectRatio() {
    method getSizeText (line 74) | getSizeText(size) {
    method sampleSizeText (line 84) | get sampleSizeText() {
    method sampleDownloadText (line 87) | get sampleDownloadText() {
    method sampleDownloadName (line 90) | get sampleDownloadName() {
    method jpegSizeText (line 94) | get jpegSizeText() {
    method jpegDownloadText (line 97) | get jpegDownloadText() {
    method jpegDownloadName (line 100) | get jpegDownloadName() {
    method fileSizeText (line 104) | get fileSizeText() {
    method fileDownloadText (line 107) | get fileDownloadText() {
    method fileDownloadName (line 110) | get fileDownloadName() {
    method createdTime (line 114) | get createdTime() {
    method updatedTime (line 118) | get updatedTime() {
    method sourceUrl (line 126) | get sourceUrl() {

FILE: temp/index.js
  function update (line 22) | function update() {
  function getValue (line 62) | function getValue() {
  function changeValue (line 65) | function changeValue() {
  function getHDValue (line 70) | function getHDValue() {
  function changeHDValue (line 73) | function changeHDValue() {
  function update (line 78) | function update() {

FILE: temp/index.user.js
  function update (line 222) | function update() {
  function getValue (line 262) | function getValue() {
  function changeValue (line 265) | function changeValue() {
  function getHDValue (line 270) | function getHDValue() {
  function changeHDValue (line 273) | function changeHDValue() {
  function update (line 278) | function update() {
Condensed preview — 31 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (172K chars).
[
  {
    "path": ".editorconfig",
    "chars": 282,
    "preview": "# 编辑器插件 EditorConfig 的配置文件\n\nroot = true\n\n[*]\ncharset = utf-8                 # 字符集\nindent_style = space            # 缩进样"
  },
  {
    "path": ".gitignore",
    "chars": 38,
    "preview": "node_modules\nyarn-error.log\n.DS_Store\n"
  },
  {
    "path": ".vscode/custom.code-snippets",
    "chars": 541,
    "preview": "{\n  \"/* 单行注释 */\": {\n    \"scope\": \"javascript,typescript,css\",\n    \"prefix\": \"ll\",\n    \"body\": [\"/* ${1:comments} */\"],\n "
  },
  {
    "path": "bundle/index.js",
    "chars": 19199,
    "preview": "// ==UserScript==\n// @name         Yande.re 简体中文\n// @namespace    com.coderzhaoziwei.yandere\n// @version      2.1.47\n// "
  },
  {
    "path": "bundle/index.user.js",
    "chars": 43777,
    "preview": "// ==UserScript==\n// @name         Yande.re 简体中文\n// @namespace    com.coderzhaoziwei.yandere\n// @version      2.1.47\n// "
  },
  {
    "path": "config/rollup.config.js",
    "chars": 1130,
    "preview": "import { version } from \"../package.json\"\nimport cleanup from \"rollup-plugin-cleanup\"\nimport json from \"@rollup/plugin-j"
  },
  {
    "path": "package.json",
    "chars": 427,
    "preview": "{\n  \"name\": \"yande-re-chinese-patch\",\n  \"version\": \"2.1.47\",\n  \"author\": \"Coder Zhao\",\n  \"description\": \"Yande.re Chines"
  },
  {
    "path": "readme.md",
    "chars": 10168,
    "preview": "![version][img-version]\n![license][img-license]\n![stars][img-stars]\n![cover][img-cover]\n\n<!-- omit in toc -->\n# 目录\n\n- [前"
  },
  {
    "path": "readme_main.md",
    "chars": 3009,
    "preview": "![version][img-version]\n![license][img-license]\n![stars][img-stars]\n![cover][img-cover]\n\n<!-- omit in toc -->\n# 目录\n\n- [前"
  },
  {
    "path": "script/action.js",
    "chars": 1064,
    "preview": "#!/usr/bin/env node\n\nconst fs = require('fs')\nconst path = require('path')\nconst tagsData = Object()\nconst version = pro"
  },
  {
    "path": "script/bundle.sh",
    "chars": 362,
    "preview": "# clear\necho \"\\033[2J\"\n\n# version\nyarn version --no-git-tag-version --patch\n\nnode script/action.js\n\n# rollup\nyarn rollup"
  },
  {
    "path": "script/replace.js",
    "chars": 559,
    "preview": "#!/usr/bin/env node\n\nconst fs = require(\"fs\")\n\nconst origin = fs.readFileSync(\"bundle/index.js\", \"utf8\")\nconst result = "
  },
  {
    "path": "source/app.js",
    "chars": 5262,
    "preview": "import Post from \"./post\"\n\nconst App = {\n  template: \"#app-template\",\n  data() {\n    return {\n      showDrawer: false,\n "
  },
  {
    "path": "source/browse.js",
    "chars": 860,
    "preview": "import app from \"./app\"\n\nexport async function enterBrowseMode() {\n  function getScript(url) {\n    return new Promise(re"
  },
  {
    "path": "source/data/footers.json",
    "chars": 152,
    "preview": "\n{\n  \"List\": \"首页\",\n  \"Browse\": \"翻阅\",\n  \"Upload\": \"上传\",\n  \"Random\": \"随机\",\n  \"Popular\": \"热门\",\n  \"Image Search\": \"寻图\",\n  \"H"
  },
  {
    "path": "source/data/menus.json",
    "chars": 962,
    "preview": "\n{\n  \"My Account\": \"账户\",\n  \"Posts\": \"作品\",\n  \"Comments\": \"评论\",\n  \"Notes\": \"笔记\",\n  \"Artists\": \"画师\",\n  \"Tags\": \"标签\",\n  \"For"
  },
  {
    "path": "source/data/tags.json",
    "chars": 7872,
    "preview": "{\n  \"4koma\": \"四格漫画\",\n  \"5-toubun_no_hanayome\": \"五等分的新娘\",\n  \"anal\": \"肛交\",\n  \"angel\": \"天使\",\n  \"angel_beats!\": \"Angel Beats"
  },
  {
    "path": "source/hotkey.js",
    "chars": 1680,
    "preview": "export const initHotKey = function() {\n\n  window.addEventListener(\"keyup\", function(event) {\n    console.log('keyup:', e"
  },
  {
    "path": "source/html/body.html",
    "chars": 11430,
    "preview": "\n<div id=\"app\"></div>\n\n<script type=\"text/template\" id=\"app-template\">\n<v-app>\n\n  <v-app-bar app dense>\n    <v-app-bar-n"
  },
  {
    "path": "source/html/head.html",
    "chars": 649,
    "preview": "\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1, maximum-scale=1, user-scalab"
  },
  {
    "path": "source/html/options.html",
    "chars": 660,
    "preview": "\n<div style=\"padding: 1rem; user-select: none; text-align: center;\">\n  <select id=\"showLeftBar\" style=\"height: 1.5rem; l"
  },
  {
    "path": "source/index.js",
    "chars": 399,
    "preview": "import { initStyle } from \"./style\"\nimport { initHotKey } from \"./hotkey\"\nimport { initOptions } from \"./options\"\nimport"
  },
  {
    "path": "source/options.js",
    "chars": 3651,
    "preview": "import { enterBrowseMode } from \"./browse\"\n\nexport const onChangeLeftBar = function() {\n  const value = Boolean(document"
  },
  {
    "path": "source/post.js",
    "chars": 3712,
    "preview": "/*\napprover_id: null\nchange: 4106973\nframes: []\nframes_pending: []\nframes_pending_string: \"\"\nframes_string: \"\"\nhas_child"
  },
  {
    "path": "source/style/fix.css",
    "chars": 3104,
    "preview": "\nbody {\n  font-size: 12px;\n  padding: 0 0.5rem;\n}\nbody::-webkit-scrollbar {\n  display: none;\n  width: 0px !important;\n}\n"
  },
  {
    "path": "source/style.js",
    "chars": 141,
    "preview": "export const initStyle = function() {\n  document.head.insertAdjacentHTML(\"beforeend\", `<style>[{ path: \"source/style/fix"
  },
  {
    "path": "source/translate.js",
    "chars": 3488,
    "preview": "const tags = [{ path: \"source/data/tags.json\" }]\nconst menus = [{ path: \"source/data/menus.json\" }]\nconst footers = [{ p"
  },
  {
    "path": "temp/css_text_loader.js",
    "chars": 90,
    "preview": "module.exports = function(source) {\n  return 'export default ' + JSON.stringify(source)\n}\n"
  },
  {
    "path": "temp/index.js",
    "chars": 3949,
    "preview": "/**\n * 默认显示隐藏的作品,并提供可开关的选项。\n */\n;(function() {\n  const SET_JS_HIDE = 'set-javascript-hide'\n  const INPUT_HTML = `<input "
  },
  {
    "path": "temp/index.user.js",
    "chars": 15493,
    "preview": "// ==UserScript==\n// @name         Yande.re 简体中文\n// @namespace    com.coderzhaoziwei.yandere\n// @version      1.0.10\n// "
  },
  {
    "path": "temp/webpack.config.js",
    "chars": 522,
    "preview": "const path = require('path')\nconst config = require('./package.json')\nconst { BannerPlugin } = require('webpack')\n\nconst"
  }
]

About this extraction

This page contains the full source code of the coderzhaoziwei/yande-re-chinese-patch GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 31 files (141.2 KB), approximately 47.1k tokens, and a symbol index with 130 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!