Sort Illustration by likes and display only those above the threshold on followed artist illustrations, artist illustrations, and tag illustrations pages.
// ==UserScript==
// @name Pixiv作品熱門程度排序與篩選器
// @name:ja Pixiv作品人気度ソート&フィルター
// @name:en Pixiv Illustration Popularity Sorter and Filter
// @namespace https://github.com/Max46656
// @description 在追蹤繪師作品、繪師作品、標籤作品頁面中以按讚數進行排序,並僅顯示高於閾值的作品。
// @description:ja フォローアーティスト作品、アーティスト作品、タグ作品ページで、いいね數でソートし、閾値以上の作品のみを表示します。
// @description:en Sort Illustration by likes and display only those above the threshold on followed artist illustrations, artist illustrations, and tag illustrations pages.
//
// @author Max
// @namespace https://github.com/Max46656/EverythingInGreasyFork/tree/main/%E7%9C%81%E6%99%82/PixivIllustPopularitySortAndFilter
// @supportURL https://github.com/Max46656/EverythingInGreasyFork/issues/new?template=bug_report.yml&labels=bug,userscript&title=[Pixiv作品熱門程度排序與篩選器] Bug回報-v1.11.1
// @license MPL2.0
//
// @version 1.11.1
// @match https://www.pixiv.net/bookmark_new_illust.php*
// @match https://www.pixiv.net/users/*
// @match https://www.pixiv.net/tags/*
// @match https://www.pixiv.net/*
// @exclude https://www.pixiv.net/*/novels
// @icon https://www.google.com/s2/favicons?sz=64&domain=pixiv.net
// @grant GM_registerMenuCommand
// @grant GM_setValue
// @grant GM_getValue
// @grant GM.info
// @grant GM_notification
// ==/UserScript==
/* TODO
*提示文字多語言翻譯
*/
class pageStrategy {
getThumbnailClass() {}
getArtsClass() {}
getRenderArtWallClass() {}
getButtonAtClass() {}
getAllButtonClass() {}
getArtsCountClass(){}
}
class userStrategy extends pageStrategy{
getThumbnailClass() {
return 'li[offset] img'
}
getArtsClass() {
return 'li[offset]';
}
getRenderArtWallClass() {
return 'div:not([class]) div div div:not([class]) div:not([class]) ul';
}
getArtWallAlignLeftClass(){
return 'iJEVBL';
}
getButtonAtClass() {
return 'nav:not(:has(polyline[points="1,2 5,6 9,2"]))';
}
getAllButtonClass() {
return ['charcoal-button','kpPxqZ'];
}
getArtsCountClass(){
return 'h2+div span:not([class])';
}
}
class tagsStrategy extends pageStrategy{
getThumbnailClass() {
return 'a[data-gtm-user-id] img'
}
getArtsClass() {
return 'section:not(:has(aside)) li:has(a[data-gtm-user-id])';
}
getRenderArtWallClass() {
return 'section div~div:has(a[data-gtm-user-id]):not(:has(aside))';
}
getArtWallAlignLeftClass(){
return 'iJEVBL';
}
getButtonAtClass() {
return 'div:nth-child(3) div:first-child div:first-child:has(div span+a+button)';
}
getAllButtonClass() {
return ['kBpizq','kBgAgO','kIZwSN','kIZQyE','eeVGDc','efgaHs'];
}
getArtsCountClass(){
return 'h3+div span:not([class])';
}
}
class subStrategy extends pageStrategy{
getThumbnailClass() {
return 'div[radius] img'
}
getArtsClass() {
return 'li[offset]';
}
getRenderArtWallClass() {
return 'section div:not([class]) div:not([class]) ul';
}
getArtWallAlignLeftClass(){
return 'iJEVBL';
}
getButtonAtClass() {
return 'section div:not([class]) div div:has(a[href="/novel/bookmark_new.php"])';
}
getAllButtonClass() {
return ['gtm-work-post-button-in-header-click','charcoal-button'];
}
getArtsCountClass(){
return null;
}
}
class artScraper {
constructor(targetPages,likesMinLimit) {
this.domain = 'https://www.pixiv.net';
this.allArts = [];
this.allArtsWithoutLike = [];
this.targetPages = GM_getValue("targetPages", 10) || targetPages;
this.likesMinLimit = GM_getValue("likesMinLimit", 50) || likesMinLimit;
this.discardLikesMinLimit = GM_getValue("discardLikesMinLimit",false);
this.strategy = this.setStrategy();
this.ensurePageParam();
this.currentArtCount=0;
// console.log(strategy.getThumbnailClass(),strategy.getArtsClass(),strategy.getRenderArtWallClass(),strategy.getButtonAtClass(),strategy.getAllButtonClass())
}
setStrategy(){
const url = self.location.href;
if (url.includes('https://www.pixiv.net/bookmark_new_illust')) {
return new subStrategy();
} else if (url.match(/^https:\/\/www\.pixiv\.net\/(en\/users|users)\/.*\/.*$/)) {
return new userStrategy();
} else if (url.match(/^https:\/\/www\.pixiv\.net\/(en\/tags|tags)\/.*\/.*$/)) {
return new tagsStrategy();
} else {
throw `${GM_info.script.name} Unsupported page type`;
}
}
ensurePageParam() {
if (!this.setStrategy()) return;
const params = new URLSearchParams(location.search);
const pValue = params.get('p');
const isValidP = pValue !== "" && !isNaN(pValue);
if (isValidP) return;
params.set('p', '1');
const newUrl = location.pathname + '?' + params.toString();
location.replace(newUrl);
}
async eatAllArts() {
const startTime = performance.now();
await this.executeAndcountUpSec('readingPages', () => this.readingPages(this.strategy.getThumbnailClass(), this.strategy.getArtsClass()));
await this.executeAndcountUpSec('sortArts', this.sortArts.bind(this));
let renderArtWallAtClass = this.strategy.getRenderArtWallClass();
await this.executeAndcountUpSec('renderArtWall', () => this.renderArtWall(renderArtWallAtClass));
this.addRerenderButton(renderArtWallAtClass, this.strategy.getButtonAtClass(), this.strategy.getAllButtonClass());
const endTime = performance.now();
const totalSeconds = (endTime - startTime) / 1000;
console.log(`${GM_info.script.name} 總耗時: ${totalSeconds.toFixed(2)} 秒`);
const hours = Math.floor(totalSeconds / 3600);
const minutes = Math.floor((totalSeconds % 3600) / 60);
const seconds = Math.floor(totalSeconds % 60);
let timeStr = '';
if (hours > 0) timeStr += `${hours}小時 `;
if (minutes > 0 || hours > 0) timeStr += `${minutes}分 `;
timeStr += `${seconds}秒`;
// ==UserScript==
// @name Pixiv作品熱門程度排序與篩選器
// @name:ja Pixiv作品人気度ソート&フィルター
// @name:en Pixiv Illustration Popularity Sorter and Filter
// @namespace https://github.com/Max46656
// @description 在追蹤繪師作品、繪師作品、標籤作品頁面中以按讚數進行排序,並僅顯示高於閾值的作品。
// @description:ja フォローアーティスト作品、アーティスト作品、タグ作品ページで、いいね數でソートし、閾値以上の作品のみを表示します。
// @description:en Sort Illustration by likes and display only those above the threshold on followed artist illustrations, artist illustrations, and tag illustrations pages.
//
// @author Max
// @namespace https://github.com/Max46656
// @supportURL https://github.com/Max46656/EverythingInGreasyFork/tree/main/%E7%9C%81%E6%99%82/PixivIllustPopularitySortAndFilter
// @license MPL2.0
//
// @version 1.10.19
// @match https://www.pixiv.net/bookmark_new_illust.php*
// @match https://www.pixiv.net/users/*
// @match https://www.pixiv.net/tags/*
// @match https://www.pixiv.net/*
// @exclude https://www.pixiv.net/*/novels
// @icon https://www.google.com/s2/favicons?sz=64&domain=pixiv.net
// @grant GM_registerMenuCommand
// @grant GM_setValue
// @grant GM_getValue
// @grant GM.info
// @grant GM_notification
// @downloadURL https://update.greasyfork.org/scripts/497015/Pixiv%E4%BD%9C%E5%93%81%E7%86%B1%E9%96%80%E7%A8%8B%E5%BA%A6%E6%8E%92%E5%BA%8F%E8%88%87%E7%AF%A9%E9%81%B8%E5%99%A8.user.js
// @updateURL https://update.greasyfork.org/scripts/497015/Pixiv%E4%BD%9C%E5%93%81%E7%86%B1%E9%96%80%E7%A8%8B%E5%BA%A6%E6%8E%92%E5%BA%8F%E8%88%87%E7%AF%A9%E9%81%B8%E5%99%A8.meta.js
// ==/UserScript==
/* TODO
*提示文字多語言翻譯
*/
class pageStrategy {
getThumbnailClass() {}
getArtsClass() {}
getRenderArtWallClass() {}
getButtonAtClass() {}
getAllButtonClass() {}
getArtsCountClass(){}
}
class userStrategy extends pageStrategy{
getThumbnailClass() {
return 'li[offset] img'
}
getArtsClass() {
return 'li[offset]';
}
getRenderArtWallClass() {
return 'div:not([class]) div div div:not([class]) div:not([class]) ul';
}
getArtWallAlignLeftClass(){
return 'iJEVBL';
}
getButtonAtClass() {
return 'nav:not(:has(polyline[points="1,2 5,6 9,2"]))';
}
getAllButtonClass() {
return ['charcoal-button','kpPxqZ'];
}
getArtsCountClass(){
return 'h2+div span:not([class])';
}
}
class tagsStrategy extends pageStrategy{
getThumbnailClass() {
return 'a[data-gtm-user-id] img'
}
getArtsClass() {
return 'section:not(:has(aside)) li:has(a[data-gtm-user-id])';
}
getRenderArtWallClass() {
return 'section div~div:has(a[data-gtm-user-id]):not(:has(aside))';
}
getArtWallAlignLeftClass(){
return 'iJEVBL';
}
getButtonAtClass() {
return 'div:nth-child(3) div:first-child div:first-child:has(div span+a+button)';
}
getAllButtonClass() {
return ['kBpizq','kBgAgO','kIZwSN','kIZQyE','eeVGDc','efgaHs'];
}
getArtsCountClass(){
return 'h3+div span:not([class])';
}
}
class subStrategy extends pageStrategy{
getThumbnailClass() {
return 'div[radius] img'
}
getArtsClass() {
return 'li[offset]';
}
getRenderArtWallClass() {
return 'section div:not([class]) div:not([class]) ul';
}
getArtWallAlignLeftClass(){
return 'iJEVBL';
}
getButtonAtClass() {
return 'section div:not([class]) div div:has(a[href="/novel/bookmark_new.php"])';
}
getAllButtonClass() {
return ['gtm-work-post-button-in-header-click','charcoal-button'];
}
getArtsCountClass(){
return null;
}
}
class artScraper {
constructor(targetPages,likesMinLimit) {
this.domain = 'https://www.pixiv.net';
this.allArts = [];
this.allArtsWithoutLike = [];
this.targetPages = GM_getValue("targetPages", 10) || targetPages;
this.likesMinLimit = GM_getValue("likesMinLimit", 50) || likesMinLimit;
this.discardLikesMinLimit = GM_getValue("discardLikesMinLimit",false);
this.strategy = this.setStrategy();
this.ensurePageParam();
this.currentArtCount=0;
// console.log(strategy.getThumbnailClass(),strategy.getArtsClass(),strategy.getRenderArtWallClass(),strategy.getButtonAtClass(),strategy.getAllButtonClass())
}
setStrategy(){
const url = self.location.href;
if (url.includes('https://www.pixiv.net/bookmark_new_illust')) {
return new subStrategy();
} else if (url.match(/^https:\/\/www\.pixiv\.net\/(en\/users|users)\/.*\/.*$/)) {
return new userStrategy();
} else if (url.match(/^https:\/\/www\.pixiv\.net\/(en\/tags|tags)\/.*\/.*$/)) {
return new tagsStrategy();
} else {
throw `${GM_info.script.name} Unsupported page type`;
}
}
ensurePageParam() {
if (!this.setStrategy()) return;
const params = new URLSearchParams(location.search);
const pValue = params.get('p');
const isValidP = pValue !== "" && !isNaN(pValue);
if (isValidP) return;
params.set('p', '1');
const newUrl = location.pathname + '?' + params.toString();
location.replace(newUrl);
}
async eatAllArts() {
const startTime = performance.now();
await this.executeAndcountUpSec('readingPages', () => this.readingPages(
this.strategy.getThumbnailClass(),
this.strategy.getArtsClass()
));
await this.executeAndcountUpSec('sortArts', this.sortArts.bind(this));
const renderArtWallAtClass = this.strategy.getRenderArtWallClass();
await this.executeAndcountUpSec('renderArtWall', () => this.renderArtWall(renderArtWallAtClass));
this.addRerenderButton(
renderArtWallAtClass,
this.strategy.getButtonAtClass(),
this.strategy.getAllButtonClass()
);
const endTime = performance.now();
const totalSeconds = (endTime - startTime) / 1000;
const hours = Math.floor(totalSeconds / 3600);
const minutes = Math.floor((totalSeconds % 3600) / 60);
const seconds = totalSecond >= 60 ? totalSeconds % 60 :totalSeconds;
let timeStr = '';
if (hours > 0) timeStr += `${hours}:`;
if (minutes > 0) timeStr += `${minutes}:`;
timeStr += `${seconds}`;
console.log(`${GM_info.script.name} 總耗時: ${totalSeconds} 秒 (${timeStr})`);
const message = this.getAPIMessageLocalization("sortCompleted", { waitTime: timeStr });
GM_notification({
title: document.title,
text: message,
image: "https://www.google.com/s2/favicons?sz=64&domain=pixiv.net",
timeout: 8000,
onclick: () => {
window.focus();
}
});
}
async readingPages(thumbnailClass, artsClass) {
const startTime = performance.now();
const allArtCount = this.getMaxPage();
const artInPage = this.getElementListBySelector(artsClass).length - 1;
const initPage = Number(document.querySelector("nav button span").textContent) - 1;
const endPage = this.targetPages + initPage;
const nextButton = document.querySelector('a:has(polyline[points="1,2 5,6 9,2"]):last-of-type');
let page = initPage;
for (let i = initPage; i <= endPage; i++) {
const iterationStartTime = performance.now();
page = Number(document.querySelector("nav button span").textContent);
if(page && i > page){
i--;
}else if(!page || page == 0){
console.error(this.getAPIMessageLocalization("pageZeroError"));
this.toPervPage();
await this.delay(3000);
i--;
}
await this.getArtsInPage(thumbnailClass, artsClass);
let nextPageLink = document.querySelectorAll('a:has(polyline[points="1,2 5,6 9,2"]');
let retryCount = 0;
if(nextPageLink[nextPageLink.length-1].hasAttribute("hidden")&& Number(new URL(nextPageLink[nextPageLink.length-1].href).searchParams.get('p')) === Number(document.querySelector('nav button span')?.textContent.trim())){
console.log(this.getAPIMessageLocalization("lastPageReached"));
break;
}else{
while(nextPageLink[nextPageLink.length-1].hasAttribute("hidden") && retryCount < 2000) {
await this.delay(1);
retryCount++;
}
if(retryCount >= 2000){
break;
console.error(this.getAPIMessageLocalization("pageZeroError"));
}
}
/*let checkInterval = Math.floor(Math.random() * 10) + 30;
let cooldown = Math.floor(Math.random() * 3000) + 2000;
if(i - initPage > 150 && (i - initPage) % (checkInterval * 10) == 0){
const cooldownMessage = this.getAPIMessageLocalization("apiCooldown", { waitTime: cooldown });
console.log(cooldownMessage);
const sorterContainer = document.getElementById("SorterBtnContainer");
const messageElement = document.createElement("span");
messageElement.textContent = cooldownMessage;
sorterContainer.appendChild(messageElement);
await this.delay(cooldown * 10);
sorterContainer.removeChild(messageElement);
}else if(i - initPage > 40 && (i - initPage) % checkInterval == 0){
const cooldownMessage = this.getAPIMessageLocalization("apiCooldown", { waitTime: cooldown });
console.log(cooldownMessage);
const sorterContainer = document.getElementById("SorterBtnContainer");
const messageElement = document.createElement("span");
messageElement.textContent = cooldownMessage;
sorterContainer.appendChild(messageElement);
await this.delay(cooldown);
sorterContainer.removeChild(messageElement);
}*/
if(this.allArtsWithoutLike.length >= 800){
while(this.allArtsWithoutLike.length != 0){
try{
await this.executeAndcountUpSec('appendLikeElementToAllArts',()=>this.appendLikeElementToAllArts());
}catch (e){
/*const cooldownMessage = this.getAPIMessageLocalization("apiCooldown", { waitTime: cooldown });
console.log(`${GM_info.script.name} `+cooldownMessage);
const sorterContainer = document.getElementById("SorterBtnContainer");
const messageElement = document.createElement("span");
messageElement.textContent = cooldownMessage;
sorterContainer.appendChild(messageElement);
await this.delay(cooldown);
sorterContainer.removeChild(messageElement);*/
}
}
}
if (i < endPage - 1) {
//if((endPage - i) % 15 ===0) await this.delay(Math.random() * 2000 + 1000);
this.toNextPage();
}
const iterationEndTime = performance.now();
}
while(this.allArtsWithoutLike.length != 0){
try{
await this.executeAndcountUpSec('appendLikeElementToAllArts',()=>this.appendLikeElementToAllArts());
}catch (e){
/*const cooldownMessage = this.getAPIMessageLocalization("apiCooldown", { waitTime: cooldown });
console.log(`${GM_info.script.name} `+cooldownMessage);
const sorterContainer = document.getElementById("SorterBtnContainer");
const messageElement = document.createElement("span");
messageElement.textContent = cooldownMessage;
sorterContainer.appendChild(messageElement);
await this.delay(cooldown);
sorterContainer.removeChild(messageElement);*/
}
}
}
async getArtsInPage(thumbnailClass, artsClass) {
//出於某種黑魔法不斷上下拖動有助於圖片元素的確實載入
while (true) {
let pageStandard = await this.getElementListBySelector(artsClass);
pageStandard = pageStandard.length;
let thumbnailCount = 0;
while (thumbnailCount < pageStandard) {
const thumbnails = await this.getElementListBySelector(thumbnailClass);
thumbnailCount = thumbnails.length;
if (thumbnailCount < pageStandard) {
console.log(`${GM_info.script.name}: 缺少${pageStandard - thumbnailCount}張圖片,請保持本分頁為本瀏覽器視窗的唯一分頁以確保所有圖片都載入`);
window.scrollBy(0, window.innerHeight);
await this.delay(100);
// 滑到頁面底部
if (window.innerHeight + window.scrollY >= document.documentElement.scrollHeight) {
window.scrollTo(0, 0);
pageStandard = await this.getElementListBySelector(artsClass);
pageStandard = pageStandard.length - 1;
}
}
}
const arts = await this.getElementListBySelector(artsClass);
//console.info(pageStandard,thumbnailCount,arts)
//console.log(`找到${arts.length}張圖片,開始抓取圖片`);
const artsArray = Array.from(arts);
const allArtsSet = new Set(this.allArtsWithoutLike);
const areFirstThreePresent = artsArray.slice(0, 3).every(art => allArtsSet.has(art));
const areAllPresent = artsArray.every(art => allArtsSet.has(art))
if (!areAllPresent && areFirstThreePresent) {
await this.delay(20);
window.scrollTo(0, 0);
await this.delay(30);
continue;
}
for (let art of arts) this.allArtsWithoutLike.push(art);
break;
}
}
async appendLikeElementToAllArts() {
this.allArtsWithoutLike = this.allArtsWithoutLike.filter(art => art !== undefined);
const ids = this.allArtsWithoutLike.filter(art => art.getElementsByTagName('a')[0] !== undefined)
.map(art => {
const href = art.getElementsByTagName('a')[0].getAttribute('href');
return href.match(/\/(\d+)/)[1];
});
const likeCounts = await Promise.all(ids.map(id => this.fetchLikeCount(id)));
likeCounts.forEach((likeCount, index) => {
const art = this.allArtsWithoutLike[index];
if (!art.getElementsByClassName('likes').length) {
if (this.discardLikesMinLimit && likeCount < this.likesMinLimit) return;
const referenceElement = art.getElementsByTagName('div')[0];
if (referenceElement) {
const likeCountElement = document.createElement('span');
likeCountElement.textContent = `${likeCount}`;
likeCountElement.className = 'likes';
likeCountElement.style.cssText =
'text-align: center !important; padding-bottom: 20px !important; color: #0069b1 !important; font-size: 12px !important; font-weight: bold !important; text-decoration: none !important; background-color: #cef !important; background-image: url("data:image/svg+xml;charset=utf8,<svg xmlns=%22http://www.w3.org/2000/svg%22 width=%2210%22 height=%2210%22 viewBox=%220 0 12 12%22><path fill=%22%230069B1%22 d=%22M9,1 C10.6568542,1 12,2.34314575 12,4 C12,6.70659075 10.1749287,9.18504759 6.52478604,11.4353705 L6.52478518,11.4353691 C6.20304221,11.6337245 5.79695454,11.6337245 5.4752116,11.4353691 C1.82507053,9.18504652 0,6.70659017 0,4 C1.1324993e-16,2.34314575 1.34314575,1 3,1 C4.12649824,1 5.33911281,1.85202454 6,2.91822994 C6.66088719,1.85202454 7.87350176,1 9,1 Z%22/></svg>") !important; background-position: center left 6px !important; background-repeat: no-repeat !important; padding: 3px 6px 3px 18px !important; border-radius: 3px !important;';
referenceElement.appendChild(likeCountElement);
}
this.allArts.push({ art, likeCount });
}
});
//console.info(this.allArtsWithoutLike.length,this.allArts.length);
this.allArtsWithoutLike = [];
}
toNextPage() {
let pageButtonsShape='a:has(polyline[points="1,2 5,6 9,2"]):last-of-type';
const nextPageButton = document.querySelector(pageButtonsShape);
nextPageButton.click();
}
toPervPage() {
let pageButtonsClass='a:has(polyline[points="1,2 5,6 9,2"]):first-of-type';
const pervPageButton = document.querySelectorAll(pageButtonsClass);
pervPageButton.click();
}
async fetchLikeCount(id) {
const response = await fetch(`https://www.pixiv.net/ajax/illust/${id}`, { credentials: 'omit' });
const json = await response.json();
return json.body.likeCount;
}
async sortArts() {
const artMap = new Map();
this.allArts
.sort((a, b) => b.likeCount - a.likeCount) // 依據 likeCount 排序
.forEach(({ art }) => {
if (art) {
const imgSrc = art.getElementsByTagName("img")[0];
if (imgSrc && !artMap.has(imgSrc)) {
artMap.set(imgSrc, art);
}
}
});
this.allArts = Array.from(artMap.values());
}
async renderArtWall(renderArtWallAtClass) {
const parentElement = await this.getElementBySelector(renderArtWallAtClass);
this.clearElement(parentElement);
const table = document.createElement('table');
table.classList.add('TableArtWall');
table.style.cssText = 'width: auto; overflow-y: auto; margin: 0 auto;';
const alignLeftClass =this.strategy.getArtWallAlignLeftClass();
/*pixiv將置中對齊的方式從擠一個元素變成使用調整相簿位置的css class(其作用為將該元素推右固定的長度)
* 其三個頁面中各有不同的數量的元素使用該CSS class來排版,若要在僅影響相簿置左排版的前提下,則需要對於其順序修改元素名稱。
*/
if(GM_getValue("leftAlign", true)){
if(self.location.href.includes('bookmark')){
//console.log(document.getElementsByClassName(alignLeftClass))
this.changeElementClassName(document.getElementsByClassName(alignLeftClass)[0],"leftAlign");
}else if(self.location.href.includes('users')){
this.changeElementClassName(document.getElementsByClassName(alignLeftClass)[3],"leftAlign");
}else if(self.location.href.includes('tags')){
this.changeElementClassName(document.getElementsByClassName(alignLeftClass)[4],"leftAlign");
}
}
const fragment = document.createDocumentFragment();
fragment.appendChild(table);
let tr = document.createElement('tr');
table.appendChild(tr);
let row = GM_getValue("rowsOfArtsWall", 7);
let artCount = 0;
for (let art of this.allArts) {
if (art.getElementsByClassName('likes')[0].textContent >= this.likesMinLimit) {
const td = document.createElement('td');
Array.from(art.attributes).forEach(attr => {
td.setAttribute(attr.name, attr.value);
});
td.innerHTML = art.innerHTML;
tr.appendChild(td);
artCount++;
if (tr.children.length % row === 0) {
tr = document.createElement('tr');
table.appendChild(tr);
}
}
}
parentElement.appendChild(fragment);
this.currentArtCount = artCount;
}
// 縮圖換原圖,以其他腳本獨立解決
/*async changeThumbToOriginal() {
for (const element of this.allArts) {
const img = element.getElementsByTagName('img')[0];
if (img) {
const originalSrc = img.src.replace(/\/c\/\d+x\d+_\d+/, '')
.replace('/img-master/', '/img-original/')
.replace('/custom-thumb/', '/img-original/')
.replace(/_square1200/, '')
.replace(/_custom1200/, '');
//console.log(originalSrc);
const newSrc = await this.testImageSrc(originalSrc);
img.src = newSrc;
//console.log(img.src);
}
}
}
async testImageSrc(src) {
return new Promise(resolve => {
const img = new Image();
img.onload = function() {
resolve(src);
};
img.onerror = function() {
resolve(src.replace('.jpg', '.png'));
};
img.src = src;
});
}*/
// 搜尋樣式
/* async addStartButton() {
let startButtonParentClass = '.sc-s8zj3z-5.eyagzq';
let startButtonClass = 'lkjHVk';
const parentElement = document.querySelector(startButtonParentClass);
if (!parentElement) {
await this.delay(50);
await this.addStartButton();
return;
}
const buttonsorterContainer = document.createElement('div');
buttonsorterContainer.style.display = 'flex';
buttonsorterContainer.style.alignItems = 'center';
buttonsorterContainer.className = 'startButton';
buttonsorterContainer.innerHTML= '<div class="hxckiU"><form class="ahao-search"><div class="hjxNtZ"><div class="bbSVxZ"></div><div class="dlaIss"><div class="lclerM"><svg viewBox="0 0 16 16" size="16" class="fiLugu"><path d="M8.25739 9.1716C7.46696 9.69512 6.51908 10 5.5 10C2.73858 10 0.5 7.76142 0.5 5C0.5 2.23858 2.73858 0 5.5 0C8.26142 0 10.5 2.23858 10.5 5C10.5 6.01908 10.1951 6.96696 9.67161 7.75739L11.7071 9.79288C12.0976 10.1834 12.0976 10.8166 11.7071 11.2071C11.3166 11.5976 10.6834 11.5976 10.2929 11.2071L8.25739 9.1716ZM8.5 5C8.5 6.65685 7.15685 8 5.5 8C3.84315 8 2.5 6.65685 2.5 5C2.5 3.34315 3.84315 2 5.5 2C7.15685 2 8.5 3.34315 8.5 5Z" transform="translate(3 3)" fill-rule="evenodd" clip-rule="evenodd"></path></svg></div></div></div></form><div class="kFcBON"></div></div>';
const inputField = document.createElement('input');
inputField.type = 'text';
inputField.value = this.targetPages;
inputField.className = 'gSIBXG';
inputField.addEventListener('input', (event) => {
this.targetPages = event.target.value;
});
const start = document.createElement('button');
start.textContent = 'Sort';
start.style.marginRight = '-10px';
start.className = startButtonClass;
start.addEventListener('click', async () => {
await this.eatAllArts();
});
const startButton = document.createElement('button');
startButton.textContent = 'Page Go';
startButton.className = startButtonClass;
startButton.addEventListener('click', async () => {
await this.eatAllArts();
});
buttonsorterContainer.appendChild(start);
buttonsorterContainer.appendChild(inputField);
buttonsorterContainer.appendChild(startButton);
parentElement.appendChild(buttonsorterContainer);
} */
// 拉桿樣式
async addStartButton(ParentClass,buttonClass) {
if(document.getElementById("StartButton")){
return;
}
const buttonsorterContainer = document.createElement('nav');
buttonsorterContainer.style.display = 'flex';
buttonsorterContainer.style.alignItems = 'center';
buttonsorterContainer.id = 'SorterBtnContainer';
const startButton = document.createElement('button');
this.addLikeRangeInput(buttonsorterContainer,startButton);
await this.addPageRangeInput(buttonsorterContainer,startButton);
startButton.textContent = `likes: ${this.likesMinLimit} for ${this.targetPages}Page Go!`;
buttonClass.forEach(cls => startButton.classList.add(cls));
startButton.id = "StartButton";
startButton.addEventListener('click', async () => {
GM_setValue("targetPages", this.targetPages);
GM_setValue("likesMinLimit", this.likesMinLimit);
await this.eatAllArts();
startButton.textContent = `likes: ${this.likesMinLimit} for ${this.targetPages}Page Go!`;
});
const parentElement = await this.getElementBySelector(ParentClass);
buttonsorterContainer.appendChild(startButton);
parentElement.appendChild(buttonsorterContainer);
}
async addRerenderButton(renderArtWallAtClass, ParentClass, buttonClass) {
if(document.getElementById("RerenderButton")){
document.getElementById("RerenderButton").textContent = `likes: ${this.likesMinLimit} Rerender Go! now:${this.currentArtCount}(${Math.round(this.currentArtCount/this.allArts.length *100)}%)`;
return;
}
document.getElementById("LikeRangeInput").remove();
document.getElementById("LikeIcon").remove();
document.getElementById("StartButton").remove();
[...document.getElementsByClassName("pageInput")].forEach(el => el.style.display = "none");
await this.delay(0);//黑魔法
const buttonsorterContainer = document.createElement('div');
buttonsorterContainer.style.display = 'flex';
buttonsorterContainer.style.alignItems = 'center';
const rerenderButton = document.createElement('button');
rerenderButton.textContent = `likes: ${this.likesMinLimit} Rerender Go! now:${this.currentArtCount}(${Math.round(this.currentArtCount/this.allArts.length *100)}%)`; // 顯示目前繪畫數量
buttonClass.forEach(cls => rerenderButton.classList.add(cls));
rerenderButton.id = "RerenderButton";
rerenderButton.addEventListener('click', async () => {
GM_setValue("likesMinLimit", this.likesMinLimit);
await this.renderArtWall(renderArtWallAtClass);
rerenderButton.textContent = `likes: ${this.likesMinLimit} Rerender Go! now:${this.currentArtCount}(${Math.round(this.currentArtCount/this.allArts.length *100)}%)`; // 更新繪畫數量
});
this.addLikeRangeInput(buttonsorterContainer, rerenderButton);
const parentElement = await this.getElementBySelector(ParentClass);
buttonsorterContainer.appendChild(rerenderButton);
parentElement.appendChild(buttonsorterContainer);
}
addLikeRangeInput(sorterContainer,Button) {
const likesMinLimitsRange = [0, 10, 25, 50, 100, 250, 500, 1000, 2500, 5000, 7500, 10000];
const likeIcon = document.createElementNS("http://www.w3.org/2000/svg", "svg");
likeIcon.id = "LikeIcon";
likeIcon.setAttributeNS(null, "viewBox", "0 0 32 32");
likeIcon.setAttributeNS(null, "height", "16");
likeIcon.setAttributeNS(null, "width", "16");
likeIcon.classList.add("dxYRhf", "fiLugu");
const path1 = document.createElementNS("http://www.w3.org/2000/svg", "path");
path1.setAttributeNS(null, "d", "M21,5.5 C24.8659932,5.5 28,8.63400675 28,12.5 C28,18.2694439 24.2975093,23.1517313 17.2206059,27.1100183 C16.4622493,27.5342993 15.5379984,27.5343235 14.779626,27.110148 C7.70250208,23.1517462 4,18.2694529 4,12.5 C4,8.63400691 7.13400681,5.5 11,5.5 C12.829814,5.5 14.6210123,6.4144028 16,7.8282366 C17.3789877,6.4144028 19.170186,5.5 21,5.5 Z");
const path2 = document.createElementNS("http://www.w3.org/2000/svg", "path");
path2.setAttributeNS(null, "d", "M16,11.3317089 C15.0857201,9.28334665 13.0491506,7.5 11,7.5 C8.23857625,7.5 6,9.73857647 6,12.5 C6,17.4386065 9.2519779,21.7268174 15.7559337,25.3646328 C15.9076021,25.4494645 16.092439,25.4494644 16.2441073,25.3646326 C22.7480325,21.7268037 26,17.4385986 26,12.5 C26,9.73857625 23.7614237,7.5 21,7.5 C18.9508494,7.5 16.9142799,9.28334665 16,11.3317089 Z");
path2.setAttributeNS(null, "class", "sc-j89e3c-0 iGgiqT");
likeIcon.appendChild(path1);
likeIcon.appendChild(path2);
const likeRangeInput = document.createElement('input');
likeRangeInput.type = 'range';
likeRangeInput.min = '0';
likeRangeInput.max = (likesMinLimitsRange.length - 1).toString();
likeRangeInput.value = likesMinLimitsRange.indexOf(this.likesMinLimit);
likeRangeInput.style.marginRight = '10px';
likeRangeInput.style.backgroundColor = 'red';
likeRangeInput.id="LikeRangeInput";
if(document.querySelector('.TableArtWall')){
likeRangeInput.addEventListener('input', (event) => {
this.likesMinLimit = likesMinLimitsRange[event.target.value];
Button.textContent = `likes: ${this.likesMinLimit} Rerender Go! now:${this.currentArtCount}(${Math.round(this.currentArtCount/this.allArts.length *100)}%)`;
});
}else{
likeRangeInput.addEventListener('input', (event) => {
this.likesMinLimit = likesMinLimitsRange[event.target.value];
Button.textContent = `likes: ${this.likesMinLimit} for ${this.targetPages}Page Go!`;
});
}
sorterContainer.appendChild(likeIcon);
sorterContainer.appendChild(likeRangeInput);
}
async addPageRangeInput(sorterContainer, startButton) {
const pageIcon = document.createElementNS("http://www.w3.org/2000/svg", "svg");
pageIcon.setAttributeNS(null, "viewBox", "0 0 16 16");
pageIcon.setAttributeNS(null, "height", "16");
pageIcon.setAttributeNS(null, "width", "16");
pageIcon.classList.add("pageInput", "iGgiqT");
const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
path.setAttributeNS(null, "d", "M8.25739 9.1716C7.46696 9.69512 6.51908 10 5.5 10C2.73858 10 0.5 7.76142 0.5 5C0.5 2.23858 2.73858 0 5.5 0C8.26142 0 10.5 2.23858 10.5 5C10.5 6.01908 10.1951 6.96696 9.67161 7.75739L11.7071 9.79288C12.0976 10.1834 12.0976 10.8166 11.7071 11.2071C11.3166 11.5976 10.6834 11.5976 10.2929 11.2071L8.25739 9.1716ZM8.5 5C8.5 6.65685 7.15685 8 5.5 8C3.84315 8 2.5 6.65685 2.5 5C2.5 3.34315 3.84315 2 5.5 2C7.15685 2 8.5 3.34315 8.5 5Z");
path.setAttributeNS(null, "transform", "translate(3 3)");
path.setAttributeNS(null, "fill-rule", "evenodd");
path.setAttributeNS(null, "clip-rule", "evenodd");
pageIcon.appendChild(path);
const pageRangeInput = document.createElement('input');
pageRangeInput.type = 'range';
pageRangeInput.min = '1';
let max = await this.getMaxPage();
const stepSize = Math.floor(max / 25) > 40 ? 39 : Math.floor(max / 25);
pageRangeInput.max = max > 1000 ? 1000 : max;
if (this.targetPages > max) {
pageRangeInput.value = max;
this.targetPages=max;
} else {
pageRangeInput.value = this.targetPages;
}
pageRangeInput.step = stepSize;
pageRangeInput.style.marginRight = '10px';
pageRangeInput.classList.add('pageInput');
pageRangeInput.addEventListener('input', (event) => {
this.targetPages = parseInt(event.target.value);
startButton.textContent = `likes: ${this.likesMinLimit} for ${this.targetPages}Page Go!`;
});
sorterContainer.appendChild(pageIcon);
sorterContainer.appendChild(pageRangeInput);
if(max > 50){
const pageInputBox = document.createElement('input');
pageInputBox.type = 'number';
pageInputBox.min = '1';
pageInputBox.max = max || 34;
pageInputBox.classList.add("gSIBXG");
pageInputBox.value = this.targetPages;
pageInputBox.style.width = '50px';
pageInputBox.style.marginRight = '10px';
pageInputBox.addEventListener('input', (event) => {
const value = parseInt(event.target.value);
if (value >= 1 && value <= max) {
this.targetPages = value;
startButton.textContent = `likes: ${this.likesMinLimit} for ${this.targetPages}Page Go!`;
}
});
sorterContainer.appendChild(pageInputBox);
}
}
async getMaxPage() {
if (this.strategy.getArtsCountClass() === null) {
return 34;
}
const artsCountElement = await this.getElementBySelector(this.strategy.getArtsCountClass());
//console.log(artsCountElement);
if (artsCountElement) {
// 刪除數字中的逗號
const artsCountText = artsCountElement.textContent.replace(/,/g, '');
const artsCount = parseInt(artsCountText);
const arts = await this.getElementListBySelector(this.strategy.getArtsClass());
const artsPerPage = arts.length;
const maxPage = Math.ceil(artsCount / artsPerPage);
return maxPage;
} else {
return 34;
}
}
async addRestoreButton(ParentClass,buttonClass) {
//const startButton = document.querySelector('nav.startButton');
//this.clearElement(startButton);
const restoreButton = document.createElement('button');
restoreButton.textContent = 'Back to Start';
restoreButton.style.marginRight = '10px';
restoreButton.className = buttonClass;
restoreButton.addEventListener('click', async () => {
const url = new URL(window.location.href);
const params = url.searchParams;
const currentPage = parseInt(params.get('p')) || 1;
const newPage = currentPage - (this.targetPages - 1);
params.set('p', newPage > 0 ? newPage : 1);
url.search = params.toString();
window.location.href = url.toString();
});
const parentElement = await this.getElementBySelector(ParentClass);
parentElement.appendChild(restoreButton);
}
async getElementBySelector(selector, timeoutMs = 30000) {
const start = Date.now();
while (Date.now() - start < timeoutMs) {
const el = document.querySelector(selector);
if (el) return el;
await this.delay(50);
}
throw new Error(`Timeout: 找不到元素 ${selector} (${timeoutMs}ms)`);
}
async getElementListBySelector(selector) {
let elements = document.querySelectorAll(selector);
while (elements.length === 0) {
await this.delay(50);
elements = document.querySelectorAll(selector);
//console.log("selector",selector,"找不到,將重試")
}
return elements;
}
clearElement(element) {
element.innerHTML='';
}
delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
changeElementClassName(element, newClassName) {
if (element && typeof newClassName === 'string') {
element.className = newClassName;
}
}
async executeAndcountUpSec(label, fn) {
const startTime = performance.now();
await fn();
const endTime = performance.now();
console.log(`${GM_info.script.name} ${label} 花費時間: ${(endTime - startTime) / 1000} 秒`);
}
getAPIMessageLocalization(word, params = {}) {
let display = {
"zh-TW": {
"sortCompleted": `排序完畢\n耗時:${params.waitTime}`,
"pageZeroError": `${GM_info.script.name} 觸發page0錯誤,停止排序`,
"lastPageReached": `${GM_info.script.name} 已經來到最後一頁,停止排序`,
"apiCooldown": `請等待API冷卻時間 ${params.waitTime/1000 || ''}秒`
},
"en": {
"sortCompleted": `Sorting completed\nTime taken: ${params.waitTime}`,
"pageZeroError": `${GM_info.script.name} Triggered page 0 error, stopping sorting`,
"lastPageReached": `${GM_info.script.name} Reached the last page, stopping sorting`,
"apiCooldown": `${GM_info.script.name} Please wait for API cooldown time ${params.waitTime/1000 || ''}sec`
},
"ja": {
"sortCompleted": `ソート完了\n所要時間:${params.waitTime}`,
"pageZeroError": `${GM_info.script.name} ページ0エラーが発生しました、ソートを停止します`,
"lastPageReached": `${GM_info.script.name} 最後のページに到達しました、ソートを停止します`,
"apiCooldown": `${GM_info.script.name} APIクールダウン時間をお待ちください ${params.waitTime/1000 || ''}秒`
}
};
return display[navigator.language]?.[word] ?? display["en"][word];
}
}
class customMenu {
constructor() {
this.registerMenuCommand(this);
}
rowsOfArtsWallMenu() {
const rows = parseInt(prompt(`${this.getFeatureMessageLocalization("rowsOfArtsWallPrompt")} ${GM_getValue("rowsOfArtsWall", 7)}`));
if (rows && Number.isInteger(rows) && rows > 0) {
GM_setValue("rowsOfArtsWall", rows);
} else {
alert(this.getFeatureMessageLocalization("rowsOfArtsWallMenuError"));
}
}
toggleLeftAlignMenu() {
const currentState = GM_getValue("leftAlign", false);
const newState = !currentState;
GM_setValue("leftAlign", newState);
alert(this.getFeatureMessageLocalization("leftAlignToggleMessage") + (newState ? this.getFeatureMessageLocalization("enabled") : this.getFeatureMessageLocalization("disabled")));
}
discardLikesMinLimitMenu(){
const currentState = GM_getValue("discardLikesMinLimit", false);
const newState = !currentState;
GM_setValue("discardLikesMinLimit", newState);
alert(this.getFeatureMessageLocalization("likesMinLimitDiscardMessage") + (newState ? this.getFeatureMessageLocalization("enabled") : this.getFeatureMessageLocalization("disabled")));
}
getFeatureMessageLocalization(word) {
let display = {
"zh-TW": {
"rowsOfArtsWall": "行數設定",
"rowsOfArtsWallPrompt": "一行顯示幾個繪畫?(請根據瀏覽器放大程度決定) 目前為:",
"rowsOfArtsWallMenuError": "請輸入一個數字,且不能小於1",
"leftAlign": "置左排版",
"leftAlignToggleMessage": "置左排版已",
"discardLikesMinLimit": "低讚數作品過濾",
"likesMinLimitDiscardMessage": "過濾低讚數作品(節省記憶體與加快載入)已",
"enabled": "啟用",
"disabled": "停用"
},
"en": {
"rowsOfArtsWall": "row setting",
"rowsOfArtsWallPrompt": "How many paintings should be displayed in one row?(Please decide based on browser magnification level) Currently:",
"rowsOfArtsWallMenuError": "Please enter a number, and it cannot be less than 1",
"leftAlign": "Left-aligned Layout",
"leftAlignToggleMessage": "Left-aligned layout is now ",
"discardLikesMinLimit": "Filter Low-Like Artworks",
"likesMinLimitDiscardMessage": "Filtering out low-like artworks (saves memory and speeds up loading) is now ",
"enabled": "enabled",
"disabled": "disabled"
},
"ja": {
"rowsOfArtsWall": "行設定",
"rowsOfArtsWallPrompt": "1 行に何枚の絵畫を表示する必要がありますか?(ブラウザの倍率レベルに基づいて決定してください) 現在:",
"rowsOfArtsWallMenuError": "數値を入力してください。1 未満にすることはできません",
"leftAlign": "左揃えレイアウト",
"leftAlignToggleMessage": "左揃えレイアウトが",
"discardLikesMinLimit": "低いいね數作品フィルタリング",
"likesMinLimitDiscardMessage": "低いいね數作品をフィルタリング(メモリ節約・読み込み高速化)が",
"enabled": "有効",
"disabled": "無効"
}
};
return display[navigator.language]?.[word] ?? display["en"][word];
}
registerMenuCommand(instance) {
GM_registerMenuCommand(instance.getFeatureMessageLocalization("rowsOfArtsWall"), () => instance.rowsOfArtsWallMenu());
GM_registerMenuCommand(instance.getFeatureMessageLocalization("leftAlign"), () => instance.toggleLeftAlignMenu());
GM_registerMenuCommand(instance.getFeatureMessageLocalization("discardLikesMinLimit"), () => instance.discardLikesMinLimitMenu());
}
}
class readingStand {
static expandAllArtworks() {
const artistHomePattern = /^https:\/\/www\.pixiv\.net\/(en\/users|users)\/[0-9]*$/;
const tagHomePattern = /^.*:\/\/www\.pixiv\.net\/(en\/tags|tags)\/.*$/;
const tagPagePattern = /^.*:\/\/www\.pixiv\.net\/(en\/tags|tags)\/.*\/artworks*/;
if (artistHomePattern.test(self.location.href) || !tagPagePattern.test(self.location.href) && tagHomePattern.test(self.location.href)) {
self.location.href = self.location.href + "/artworks?p=1";
}
}
}
//新增對網頁網址的檢查,以確保即便標題被其他程式修改,腳本仍能意識到是否在相同頁面
let pageUrl = window.location.href;
//網頁名稱不論載入或AJAX更換頁面都會在過程會觸發1次,hashchange與popstate在此無法正確處理,改使用自定事件
let currentTitle = document.title;
const titleDescriptor = {
get: function() {
return currentTitle;
},
set: function(newTitle) {
delete document.title;//避免無限遞迴
document.title = newTitle;
currentTitle = newTitle;
Object.defineProperty(document, 'title', titleDescriptor);
const event = new CustomEvent('titlechange', { detail: { newTitle: newTitle } });
window.dispatchEvent(event);
}
}
window.addEventListener('titlechange', (e) => {
readingStand.expandAllArtworks();
if (window.location.href === pageUrl) {
return;
}
pageUrl = window.location.href;
let johnTheHornyOne = new artScraper(10, 50);
johnTheHornyOne.addStartButton(johnTheHornyOne.strategy.getButtonAtClass(), johnTheHornyOne.strategy.getAllButtonClass());
});
//初始化
let johnTheHornyOne = new artScraper(10, 50);
johnTheHornyOne.addStartButton(johnTheHornyOne.strategy.getButtonAtClass(), johnTheHornyOne.strategy.getAllButtonClass());
const johnTheRestaurantWaiter = new customMenu();
GM_notification({
title: document.title,
text: `排序完畢\n耗時:${timeStr}`,
timeout: 8000,
onclick: () => {
window.focus();
}
});
}
async readingPages(thumbnailClass, artsClass) {
const startTime = performance.now();
const allArtCount = this.getMaxPage();
const artInPage = this.getElementListBySelector(artsClass).length - 1;
const initPage = Number(document.querySelector("nav button span").textContent) - 1;
const endPage = this.targetPages + initPage;
const nextButton = document.querySelector('a:has(polyline[points="1,2 5,6 9,2"]):last-of-type');
let page = initPage;
for (let i = initPage; i <= endPage; i++) {
const iterationStartTime = performance.now();
page = Number(document.querySelector("nav button span").textContent);
if(page && i > page){
i--;
}else if(!page || page == 0){
console.error(this.getAPIMessageLocalization("pageZeroError"));
this.toPervPage();
await this.delay(3000);
i--;
}
await this.getArtsInPage(thumbnailClass, artsClass);
let nextPageLink = document.querySelectorAll('a:has(polyline[points="1,2 5,6 9,2"]');
let retryCount = 0;
if(nextPageLink[nextPageLink.length-1].hasAttribute("hidden")&& Number(new URL(nextPageLink[nextPageLink.length-1].href).searchParams.get('p')) === Number(document.querySelector('nav button span')?.textContent.trim())){
console.log(this.getAPIMessageLocalization("lastPageReached"));
break;
}else{
while(nextPageLink[nextPageLink.length-1].hasAttribute("hidden") && retryCount < 2000) {
await this.delay(1);
retryCount++;
}
if(retryCount >= 2000){
break;
console.error(this.getAPIMessageLocalization("pageZeroError"));
}
}
/*let checkInterval = Math.floor(Math.random() * 10) + 30;
let cooldown = Math.floor(Math.random() * 3000) + 2000;
if(i - initPage > 150 && (i - initPage) % (checkInterval * 10) == 0){
const cooldownMessage = this.getAPIMessageLocalization("apiCooldown", { waitTime: cooldown });
console.log(cooldownMessage);
const sorterContainer = document.getElementById("SorterBtnContainer");
const messageElement = document.createElement("span");
messageElement.textContent = cooldownMessage;
sorterContainer.appendChild(messageElement);
await this.delay(cooldown * 10);
sorterContainer.removeChild(messageElement);
}else if(i - initPage > 40 && (i - initPage) % checkInterval == 0){
const cooldownMessage = this.getAPIMessageLocalization("apiCooldown", { waitTime: cooldown });
console.log(cooldownMessage);
const sorterContainer = document.getElementById("SorterBtnContainer");
const messageElement = document.createElement("span");
messageElement.textContent = cooldownMessage;
sorterContainer.appendChild(messageElement);
await this.delay(cooldown);
sorterContainer.removeChild(messageElement);
}*/
if(this.allArtsWithoutLike.length >= 800){
while(this.allArtsWithoutLike.length != 0){
try{
await this.executeAndcountUpSec('appendLikeElementToAllArts',()=>this.appendLikeElementToAllArts());
}catch (e){
/*const cooldownMessage = this.getAPIMessageLocalization("apiCooldown", { waitTime: cooldown });
console.log(`${GM_info.script.name} `+cooldownMessage);
const sorterContainer = document.getElementById("SorterBtnContainer");
const messageElement = document.createElement("span");
messageElement.textContent = cooldownMessage;
sorterContainer.appendChild(messageElement);
await this.delay(cooldown);
sorterContainer.removeChild(messageElement);*/
}
}
}
if (i < endPage - 1) {
//if((endPage - i) % 15 ===0) await this.delay(Math.random() * 2000 + 1000);
this.toNextPage();
}
const iterationEndTime = performance.now();
}
while(this.allArtsWithoutLike.length != 0){
try{
await this.executeAndcountUpSec('appendLikeElementToAllArts',()=>this.appendLikeElementToAllArts());
}catch (e){
/*const cooldownMessage = this.getAPIMessageLocalization("apiCooldown", { waitTime: cooldown });
console.log(`${GM_info.script.name} `+cooldownMessage);
const sorterContainer = document.getElementById("SorterBtnContainer");
const messageElement = document.createElement("span");
messageElement.textContent = cooldownMessage;
sorterContainer.appendChild(messageElement);
await this.delay(cooldown);
sorterContainer.removeChild(messageElement);*/
}
}
}
async getArtsInPage(thumbnailClass, artsClass) {
//出於某種黑魔法不斷上下拖動有助於圖片元素的確實載入
while (true) {
let pageStandard = await this.getElementListBySelector(artsClass);
pageStandard = pageStandard.length;
let thumbnailCount = 0;
while (thumbnailCount < pageStandard) {
const thumbnails = await this.getElementListBySelector(thumbnailClass);
thumbnailCount = thumbnails.length;
if (thumbnailCount < pageStandard) {
console.log(`${GM_info.script.name}: 缺少${pageStandard - thumbnailCount}張圖片,請保持本分頁為本瀏覽器視窗的唯一分頁以確保所有圖片都載入`);
window.scrollBy(0, window.innerHeight);
await this.delay(100);
// 滑到頁面底部
if (window.innerHeight + window.scrollY >= document.documentElement.scrollHeight) {
window.scrollTo(0, 0);
pageStandard = await this.getElementListBySelector(artsClass);
pageStandard = pageStandard.length - 1;
}
}
}
const arts = await this.getElementListBySelector(artsClass);
//console.info(pageStandard,thumbnailCount,arts)
//console.log(`找到${arts.length}張圖片,開始抓取圖片`);
const artsArray = Array.from(arts);
const allArtsSet = new Set(this.allArtsWithoutLike);
const areFirstThreePresent = artsArray.slice(0, 3).every(art => allArtsSet.has(art));
const areAllPresent = artsArray.every(art => allArtsSet.has(art))
if (!areAllPresent && areFirstThreePresent) {
await this.delay(20);
window.scrollTo(0, 0);
await this.delay(30);
continue;
}
for (let art of arts) this.allArtsWithoutLike.push(art);
break;
}
}
async appendLikeElementToAllArts() {
this.allArtsWithoutLike = this.allArtsWithoutLike.filter(art => art !== undefined);
const ids = this.allArtsWithoutLike.filter(art => art.getElementsByTagName('a')[0] !== undefined)
.map(art => {
const href = art.getElementsByTagName('a')[0].getAttribute('href');
return href.match(/\/(\d+)/)[1];
});
const likeCounts = await Promise.all(ids.map(id => this.fetchLikeCount(id)));
likeCounts.forEach((likeCount, index) => {
const art = this.allArtsWithoutLike[index];
if (!art.getElementsByClassName('likes').length) {
if (this.discardLikesMinLimit && likeCount < this.likesMinLimit) return;
const referenceElement = art.getElementsByTagName('div')[0];
if (referenceElement) {
const likeCountElement = document.createElement('span');
likeCountElement.textContent = `${likeCount}`;
likeCountElement.className = 'likes';
likeCountElement.style.cssText =
'text-align: center !important; padding-bottom: 20px !important; color: #0069b1 !important; font-size: 12px !important; font-weight: bold !important; text-decoration: none !important; background-color: #cef !important; background-image: url("data:image/svg+xml;charset=utf8,<svg xmlns=%22http://www.w3.org/2000/svg%22 width=%2210%22 height=%2210%22 viewBox=%220 0 12 12%22><path fill=%22%230069B1%22 d=%22M9,1 C10.6568542,1 12,2.34314575 12,4 C12,6.70659075 10.1749287,9.18504759 6.52478604,11.4353705 L6.52478518,11.4353691 C6.20304221,11.6337245 5.79695454,11.6337245 5.4752116,11.4353691 C1.82507053,9.18504652 0,6.70659017 0,4 C1.1324993e-16,2.34314575 1.34314575,1 3,1 C4.12649824,1 5.33911281,1.85202454 6,2.91822994 C6.66088719,1.85202454 7.87350176,1 9,1 Z%22/></svg>") !important; background-position: center left 6px !important; background-repeat: no-repeat !important; padding: 3px 6px 3px 18px !important; border-radius: 3px !important;';
referenceElement.appendChild(likeCountElement);
}
this.allArts.push({ art, likeCount });
}
});
//console.info(this.allArtsWithoutLike.length,this.allArts.length);
this.allArtsWithoutLike = [];
}
toNextPage() {
let pageButtonsShape='a:has(polyline[points="1,2 5,6 9,2"]):last-of-type';
const nextPageButton = document.querySelector(pageButtonsShape);
nextPageButton.click();
}
toPervPage() {
let pageButtonsClass='a:has(polyline[points="1,2 5,6 9,2"]):first-of-type';
const pervPageButton = document.querySelectorAll(pageButtonsClass);
pervPageButton.click();
}
async fetchLikeCount(id) {
const response = await fetch(`https://www.pixiv.net/ajax/illust/${id}`, { credentials: 'omit' });
const json = await response.json();
return json.body.likeCount;
}
async sortArts() {
const artMap = new Map();
this.allArts
.sort((a, b) => b.likeCount - a.likeCount) // 依據 likeCount 排序
.forEach(({ art }) => {
if (art) {
const imgSrc = art.getElementsByTagName("img")[0];
if (imgSrc && !artMap.has(imgSrc)) {
artMap.set(imgSrc, art);
}
}
});
this.allArts = Array.from(artMap.values());
}
async renderArtWall(renderArtWallAtClass) {
const parentElement = await this.getElementBySelector(renderArtWallAtClass);
this.clearElement(parentElement);
const table = document.createElement('table');
table.classList.add('TableArtWall');
table.style.cssText = 'width: auto; overflow-y: auto; margin: 0 auto;';
const alignLeftClass =this.strategy.getArtWallAlignLeftClass();
/*pixiv將置中對齊的方式從擠一個元素變成使用調整相簿位置的css class(其作用為將該元素推右固定的長度)
* 其三個頁面中各有不同的數量的元素使用該CSS class來排版,若要在僅影響相簿置左排版的前提下,則需要對於其順序修改元素名稱。
*/
if(GM_getValue("leftAlign", true)){
if(self.location.href.includes('bookmark')){
//console.log(document.getElementsByClassName(alignLeftClass))
this.changeElementClassName(document.getElementsByClassName(alignLeftClass)[0],"leftAlign");
}else if(self.location.href.includes('users')){
this.changeElementClassName(document.getElementsByClassName(alignLeftClass)[3],"leftAlign");
}else if(self.location.href.includes('tags')){
this.changeElementClassName(document.getElementsByClassName(alignLeftClass)[4],"leftAlign");
}
}
const fragment = document.createDocumentFragment();
fragment.appendChild(table);
let tr = document.createElement('tr');
table.appendChild(tr);
let row = GM_getValue("rowsOfArtsWall", 7);
let artCount = 0;
for (let art of this.allArts) {
if (art.getElementsByClassName('likes')[0].textContent >= this.likesMinLimit) {
const td = document.createElement('td');
Array.from(art.attributes).forEach(attr => {
td.setAttribute(attr.name, attr.value);
});
td.innerHTML = art.innerHTML;
tr.appendChild(td);
artCount++;
if (tr.children.length % row === 0) {
tr = document.createElement('tr');
table.appendChild(tr);
}
}
}
parentElement.appendChild(fragment);
this.currentArtCount = artCount;
}
// 縮圖換原圖,以其他腳本獨立解決
/*async changeThumbToOriginal() {
for (const element of this.allArts) {
const img = element.getElementsByTagName('img')[0];
if (img) {
const originalSrc = img.src.replace(/\/c\/\d+x\d+_\d+/, '')
.replace('/img-master/', '/img-original/')
.replace('/custom-thumb/', '/img-original/')
.replace(/_square1200/, '')
.replace(/_custom1200/, '');
//console.log(originalSrc);
const newSrc = await this.testImageSrc(originalSrc);
img.src = newSrc;
//console.log(img.src);
}
}
}
async testImageSrc(src) {
return new Promise(resolve => {
const img = new Image();
img.onload = function() {
resolve(src);
};
img.onerror = function() {
resolve(src.replace('.jpg', '.png'));
};
img.src = src;
});
}*/
// 搜尋樣式
/* async addStartButton() {
let startButtonParentClass = '.sc-s8zj3z-5.eyagzq';
let startButtonClass = 'lkjHVk';
const parentElement = document.querySelector(startButtonParentClass);
if (!parentElement) {
await this.delay(50);
await this.addStartButton();
return;
}
const buttonsorterContainer = document.createElement('div');
buttonsorterContainer.style.display = 'flex';
buttonsorterContainer.style.alignItems = 'center';
buttonsorterContainer.className = 'startButton';
buttonsorterContainer.innerHTML= '<div class="hxckiU"><form class="ahao-search"><div class="hjxNtZ"><div class="bbSVxZ"></div><div class="dlaIss"><div class="lclerM"><svg viewBox="0 0 16 16" size="16" class="fiLugu"><path d="M8.25739 9.1716C7.46696 9.69512 6.51908 10 5.5 10C2.73858 10 0.5 7.76142 0.5 5C0.5 2.23858 2.73858 0 5.5 0C8.26142 0 10.5 2.23858 10.5 5C10.5 6.01908 10.1951 6.96696 9.67161 7.75739L11.7071 9.79288C12.0976 10.1834 12.0976 10.8166 11.7071 11.2071C11.3166 11.5976 10.6834 11.5976 10.2929 11.2071L8.25739 9.1716ZM8.5 5C8.5 6.65685 7.15685 8 5.5 8C3.84315 8 2.5 6.65685 2.5 5C2.5 3.34315 3.84315 2 5.5 2C7.15685 2 8.5 3.34315 8.5 5Z" transform="translate(3 3)" fill-rule="evenodd" clip-rule="evenodd"></path></svg></div></div></div></form><div class="kFcBON"></div></div>';
const inputField = document.createElement('input');
inputField.type = 'text';
inputField.value = this.targetPages;
inputField.className = 'gSIBXG';
inputField.addEventListener('input', (event) => {
this.targetPages = event.target.value;
});
const start = document.createElement('button');
start.textContent = 'Sort';
start.style.marginRight = '-10px';
start.className = startButtonClass;
start.addEventListener('click', async () => {
await this.eatAllArts();
});
const startButton = document.createElement('button');
startButton.textContent = 'Page Go';
startButton.className = startButtonClass;
startButton.addEventListener('click', async () => {
await this.eatAllArts();
});
buttonsorterContainer.appendChild(start);
buttonsorterContainer.appendChild(inputField);
buttonsorterContainer.appendChild(startButton);
parentElement.appendChild(buttonsorterContainer);
} */
// 拉桿樣式
async addStartButton(ParentClass,buttonClass) {
if(document.getElementById("StartButton")){
return;
}
const buttonsorterContainer = document.createElement('nav');
buttonsorterContainer.style.display = 'flex';
buttonsorterContainer.style.alignItems = 'center';
buttonsorterContainer.id = 'SorterBtnContainer';
const startButton = document.createElement('button');
this.addLikeRangeInput(buttonsorterContainer,startButton);
await this.addPageRangeInput(buttonsorterContainer,startButton);
startButton.textContent = `likes: ${this.likesMinLimit} for ${this.targetPages}Page Go!`;
buttonClass.forEach(cls => startButton.classList.add(cls));
startButton.id = "StartButton";
startButton.addEventListener('click', async () => {
GM_setValue("targetPages", this.targetPages);
GM_setValue("likesMinLimit", this.likesMinLimit);
await this.eatAllArts();
startButton.textContent = `likes: ${this.likesMinLimit} for ${this.targetPages}Page Go!`;
});
const parentElement = await this.getElementBySelector(ParentClass);
buttonsorterContainer.appendChild(startButton);
parentElement.appendChild(buttonsorterContainer);
}
async addRerenderButton(renderArtWallAtClass, ParentClass, buttonClass) {
if(document.getElementById("RerenderButton")){
document.getElementById("RerenderButton").textContent = `likes: ${this.likesMinLimit} Rerender Go! now:${this.currentArtCount}(${Math.round(this.currentArtCount/this.allArts.length *100)}%)`;
return;
}
document.getElementById("LikeRangeInput").remove();
document.getElementById("LikeIcon").remove();
document.getElementById("StartButton").remove();
[...document.getElementsByClassName("pageInput")].forEach(el => el.style.display = "none");
await this.delay(0);//黑魔法
const buttonsorterContainer = document.createElement('div');
buttonsorterContainer.style.display = 'flex';
buttonsorterContainer.style.alignItems = 'center';
const rerenderButton = document.createElement('button');
rerenderButton.textContent = `likes: ${this.likesMinLimit} Rerender Go! now:${this.currentArtCount}(${Math.round(this.currentArtCount/this.allArts.length *100)}%)`; // 顯示目前繪畫數量
buttonClass.forEach(cls => rerenderButton.classList.add(cls));
rerenderButton.id = "RerenderButton";
rerenderButton.addEventListener('click', async () => {
GM_setValue("likesMinLimit", this.likesMinLimit);
await this.renderArtWall(renderArtWallAtClass);
rerenderButton.textContent = `likes: ${this.likesMinLimit} Rerender Go! now:${this.currentArtCount}(${Math.round(this.currentArtCount/this.allArts.length *100)}%)`; // 更新繪畫數量
});
this.addLikeRangeInput(buttonsorterContainer, rerenderButton);
const parentElement = await this.getElementBySelector(ParentClass);
buttonsorterContainer.appendChild(rerenderButton);
parentElement.appendChild(buttonsorterContainer);
}
addLikeRangeInput(sorterContainer,Button) {
const likesMinLimitsRange = [0, 10, 25, 50, 100, 250, 500, 1000, 2500, 5000, 7500, 10000];
const likeIcon = document.createElementNS("http://www.w3.org/2000/svg", "svg");
likeIcon.id = "LikeIcon";
likeIcon.setAttributeNS(null, "viewBox", "0 0 32 32");
likeIcon.setAttributeNS(null, "height", "16");
likeIcon.setAttributeNS(null, "width", "16");
likeIcon.classList.add("dxYRhf", "fiLugu");
const path1 = document.createElementNS("http://www.w3.org/2000/svg", "path");
path1.setAttributeNS(null, "d", "M21,5.5 C24.8659932,5.5 28,8.63400675 28,12.5 C28,18.2694439 24.2975093,23.1517313 17.2206059,27.1100183 C16.4622493,27.5342993 15.5379984,27.5343235 14.779626,27.110148 C7.70250208,23.1517462 4,18.2694529 4,12.5 C4,8.63400691 7.13400681,5.5 11,5.5 C12.829814,5.5 14.6210123,6.4144028 16,7.8282366 C17.3789877,6.4144028 19.170186,5.5 21,5.5 Z");
const path2 = document.createElementNS("http://www.w3.org/2000/svg", "path");
path2.setAttributeNS(null, "d", "M16,11.3317089 C15.0857201,9.28334665 13.0491506,7.5 11,7.5 C8.23857625,7.5 6,9.73857647 6,12.5 C6,17.4386065 9.2519779,21.7268174 15.7559337,25.3646328 C15.9076021,25.4494645 16.092439,25.4494644 16.2441073,25.3646326 C22.7480325,21.7268037 26,17.4385986 26,12.5 C26,9.73857625 23.7614237,7.5 21,7.5 C18.9508494,7.5 16.9142799,9.28334665 16,11.3317089 Z");
path2.setAttributeNS(null, "class", "sc-j89e3c-0 iGgiqT");
likeIcon.appendChild(path1);
likeIcon.appendChild(path2);
const likeRangeInput = document.createElement('input');
likeRangeInput.type = 'range';
likeRangeInput.min = '0';
likeRangeInput.max = (likesMinLimitsRange.length - 1).toString();
likeRangeInput.value = likesMinLimitsRange.indexOf(this.likesMinLimit);
likeRangeInput.style.marginRight = '10px';
likeRangeInput.style.backgroundColor = 'red';
likeRangeInput.id="LikeRangeInput";
if(document.querySelector('.TableArtWall')){
likeRangeInput.addEventListener('input', (event) => {
this.likesMinLimit = likesMinLimitsRange[event.target.value];
Button.textContent = `likes: ${this.likesMinLimit} Rerender Go! now:${this.currentArtCount}(${Math.round(this.currentArtCount/this.allArts.length *100)}%)`;
});
}else{
likeRangeInput.addEventListener('input', (event) => {
this.likesMinLimit = likesMinLimitsRange[event.target.value];
Button.textContent = `likes: ${this.likesMinLimit} for ${this.targetPages}Page Go!`;
});
}
sorterContainer.appendChild(likeIcon);
sorterContainer.appendChild(likeRangeInput);
}
async addPageRangeInput(sorterContainer, startButton) {
const pageIcon = document.createElementNS("http://www.w3.org/2000/svg", "svg");
pageIcon.setAttributeNS(null, "viewBox", "0 0 16 16");
pageIcon.setAttributeNS(null, "height", "16");
pageIcon.setAttributeNS(null, "width", "16");
pageIcon.classList.add("pageInput", "iGgiqT");
const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
path.setAttributeNS(null, "d", "M8.25739 9.1716C7.46696 9.69512 6.51908 10 5.5 10C2.73858 10 0.5 7.76142 0.5 5C0.5 2.23858 2.73858 0 5.5 0C8.26142 0 10.5 2.23858 10.5 5C10.5 6.01908 10.1951 6.96696 9.67161 7.75739L11.7071 9.79288C12.0976 10.1834 12.0976 10.8166 11.7071 11.2071C11.3166 11.5976 10.6834 11.5976 10.2929 11.2071L8.25739 9.1716ZM8.5 5C8.5 6.65685 7.15685 8 5.5 8C3.84315 8 2.5 6.65685 2.5 5C2.5 3.34315 3.84315 2 5.5 2C7.15685 2 8.5 3.34315 8.5 5Z");
path.setAttributeNS(null, "transform", "translate(3 3)");
path.setAttributeNS(null, "fill-rule", "evenodd");
path.setAttributeNS(null, "clip-rule", "evenodd");
pageIcon.appendChild(path);
const pageRangeInput = document.createElement('input');
pageRangeInput.type = 'range';
pageRangeInput.min = '1';
let max = await this.getMaxPage();
const stepSize = Math.floor(max / 25) > 40 ? 39 : Math.floor(max / 25);
pageRangeInput.max = max > 1000 ? 1000 : max;
if (this.targetPages > max) {
pageRangeInput.value = max;
this.targetPages=max;
} else {
pageRangeInput.value = this.targetPages;
}
pageRangeInput.step = stepSize;
pageRangeInput.style.marginRight = '10px';
pageRangeInput.classList.add('pageInput');
pageRangeInput.addEventListener('input', (event) => {
this.targetPages = parseInt(event.target.value);
startButton.textContent = `likes: ${this.likesMinLimit} for ${this.targetPages}Page Go!`;
});
sorterContainer.appendChild(pageIcon);
sorterContainer.appendChild(pageRangeInput);
if(max > 50){
const pageInputBox = document.createElement('input');
pageInputBox.type = 'number';
pageInputBox.min = '1';
pageInputBox.max = max || 34;
pageInputBox.classList.add("gSIBXG");
pageInputBox.value = this.targetPages;
pageInputBox.style.width = '50px';
pageInputBox.style.marginRight = '10px';
pageInputBox.addEventListener('input', (event) => {
const value = parseInt(event.target.value);
if (value >= 1 && value <= max) {
this.targetPages = value;
startButton.textContent = `likes: ${this.likesMinLimit} for ${this.targetPages}Page Go!`;
}
});
sorterContainer.appendChild(pageInputBox);
}
}
async getMaxPage() {
if (this.strategy.getArtsCountClass() === null) {
return 34;
}
const artsCountElement = await this.getElementBySelector(this.strategy.getArtsCountClass());
//console.log(artsCountElement);
if (artsCountElement) {
// 刪除數字中的逗號
const artsCountText = artsCountElement.textContent.replace(/,/g, '');
const artsCount = parseInt(artsCountText);
const arts = await this.getElementListBySelector(this.strategy.getArtsClass());
const artsPerPage = arts.length;
const maxPage = Math.ceil(artsCount / artsPerPage);
return maxPage;
} else {
return 34;
}
}
async addRestoreButton(ParentClass,buttonClass) {
//const startButton = document.querySelector('nav.startButton');
//this.clearElement(startButton);
const restoreButton = document.createElement('button');
restoreButton.textContent = 'Back to Start';
restoreButton.style.marginRight = '10px';
restoreButton.className = buttonClass;
restoreButton.addEventListener('click', async () => {
const url = new URL(window.location.href);
const params = url.searchParams;
const currentPage = parseInt(params.get('p')) || 1;
const newPage = currentPage - (this.targetPages - 1);
params.set('p', newPage > 0 ? newPage : 1);
url.search = params.toString();
window.location.href = url.toString();
});
const parentElement = await this.getElementBySelector(ParentClass);
parentElement.appendChild(restoreButton);
}
async getElementBySelector(selector, timeoutMs = 30000) {
const start = Date.now();
while (Date.now() - start < timeoutMs) {
const el = document.querySelector(selector);
if (el) return el;
await this.delay(50);
}
throw new Error(`Timeout: 找不到元素 ${selector} (${timeoutMs}ms)`);
}
async getElementListBySelector(selector) {
let elements = document.querySelectorAll(selector);
while (elements.length === 0) {
await this.delay(50);
elements = document.querySelectorAll(selector);
//console.log("selector",selector,"找不到,將重試")
}
return elements;
}
clearElement(element) {
element.innerHTML='';
}
delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
changeElementClassName(element, newClassName) {
if (element && typeof newClassName === 'string') {
element.className = newClassName;
}
}
async executeAndcountUpSec(label, fn) {
const startTime = performance.now();
await fn();
const endTime = performance.now();
console.log(`${GM_info.script.name} ${label} 花費時間: ${(endTime - startTime) / 1000} 秒`);
}
getAPIMessageLocalization(word, params = {}) {
let display = {
"zh-TW": {
"pageZeroError": `${GM_info.script.name} 觸發page0錯誤,停止排序`,
"lastPageReached": `${GM_info.script.name} 已經來到最後一頁,停止排序`,
"apiCooldown": `請等待API冷卻時間 ${params.waitTime/1000 || ''}秒`
},
"en": {
"pageZeroError": `${GM_info.script.name} Triggered page 0 error, stopping sorting`,
"lastPageReached": `${GM_info.script.name} Reached the last page, stopping sorting`,
"apiCooldown": `${GM_info.script.name} Please wait for API cooldown time ${params.waitTime/1000 || ''}sec`
},
"ja": {
"pageZeroError": `${GM_info.script.name} ページ0エラーが発生しました、ソートを停止します`,
"lastPageReached": `${GM_info.script.name} 最後のページに到達しました、ソートを停止します`,
"apiCooldown": `${GM_info.script.name} APIクールダウン時間をお待ちください ${params.waitTime/1000 || ''}秒`
}
};
return display[navigator.language]?.[word] ?? display["en"][word];
}
}
class customMenu {
constructor() {
this.registerMenuCommand(this);
}
rowsOfArtsWallMenu() {
const rows = parseInt(prompt(`${this.getFeatureMessageLocalization("rowsOfArtsWallPrompt")} ${GM_getValue("rowsOfArtsWall", 7)}`));
if (rows && Number.isInteger(rows) && rows > 0) {
GM_setValue("rowsOfArtsWall", rows);
} else {
alert(this.getFeatureMessageLocalization("rowsOfArtsWallMenuError"));
}
}
toggleLeftAlignMenu() {
const currentState = GM_getValue("leftAlign", false);
const newState = !currentState;
GM_setValue("leftAlign", newState);
alert(this.getFeatureMessageLocalization("leftAlignToggleMessage") + (newState ? this.getFeatureMessageLocalization("enabled") : this.getFeatureMessageLocalization("disabled")));
}
discardLikesMinLimitMenu(){
const currentState = GM_getValue("discardLikesMinLimit", false);
const newState = !currentState;
GM_setValue("discardLikesMinLimit", newState);
alert(this.getFeatureMessageLocalization("likesMinLimitDiscardMessage") + (newState ? this.getFeatureMessageLocalization("enabled") : this.getFeatureMessageLocalization("disabled")));
}
getFeatureMessageLocalization(word) {
let display = {
"zh-TW": {
"rowsOfArtsWall": "行數設定",
"rowsOfArtsWallPrompt": "一行顯示幾個繪畫?(請根據瀏覽器放大程度決定) 目前為:",
"rowsOfArtsWallMenuError": "請輸入一個數字,且不能小於1",
"leftAlign": "置左排版",
"leftAlignToggleMessage": "置左排版已",
"discardLikesMinLimit": "低讚數作品過濾",
"likesMinLimitDiscardMessage": "過濾低讚數作品(節省記憶體與加快載入)已",
"enabled": "啟用",
"disabled": "停用"
},
"en": {
"rowsOfArtsWall": "row setting",
"rowsOfArtsWallPrompt": "How many paintings should be displayed in one row?(Please decide based on browser magnification level) Currently:",
"rowsOfArtsWallMenuError": "Please enter a number, and it cannot be less than 1",
"leftAlign": "Left-aligned Layout",
"leftAlignToggleMessage": "Left-aligned layout is now ",
"discardLikesMinLimit": "Filter Low-Like Artworks",
"likesMinLimitDiscardMessage": "Filtering out low-like artworks (saves memory and speeds up loading) is now ",
"enabled": "enabled",
"disabled": "disabled"
},
"ja": {
"rowsOfArtsWall": "行設定",
"rowsOfArtsWallPrompt": "1 行に何枚の絵畫を表示する必要がありますか?(ブラウザの倍率レベルに基づいて決定してください) 現在:",
"rowsOfArtsWallMenuError": "數値を入力してください。1 未満にすることはできません",
"leftAlign": "左揃えレイアウト",
"leftAlignToggleMessage": "左揃えレイアウトが",
"discardLikesMinLimit": "低いいね數作品フィルタリング",
"likesMinLimitDiscardMessage": "低いいね數作品をフィルタリング(メモリ節約・読み込み高速化)が",
"enabled": "有効",
"disabled": "無効"
}
};
return display[navigator.language]?.[word] ?? display["en"][word];
}
registerMenuCommand(instance) {
GM_registerMenuCommand(instance.getFeatureMessageLocalization("rowsOfArtsWall"), () => instance.rowsOfArtsWallMenu());
GM_registerMenuCommand(instance.getFeatureMessageLocalization("leftAlign"), () => instance.toggleLeftAlignMenu());
GM_registerMenuCommand(instance.getFeatureMessageLocalization("discardLikesMinLimit"), () => instance.discardLikesMinLimitMenu());
}
}
class readingStand {
static expandAllArtworks() {
const artistHomePattern = /^https:\/\/www\.pixiv\.net\/(en\/users|users)\/[0-9]*$/;
const tagHomePattern = /^.*:\/\/www\.pixiv\.net\/(en\/tags|tags)\/.*$/;
const tagPagePattern = /^.*:\/\/www\.pixiv\.net\/(en\/tags|tags)\/.*\/artworks*/;
if (artistHomePattern.test(self.location.href) || !tagPagePattern.test(self.location.href) && tagHomePattern.test(self.location.href)) {
self.location.href = self.location.href + "/artworks?p=1";
}
}
}
//新增對網頁網址的檢查,以確保即便標題被其他程式修改,腳本仍能意識到是否在相同頁面
let pageUrl = window.location.href;
//網頁名稱不論載入或AJAX更換頁面都會在過程會觸發1次,hashchange與popstate在此無法正確處理,改使用自定事件
let currentTitle = document.title;
const titleDescriptor = {
get: function() {
return currentTitle;
},
set: function(newTitle) {
delete document.title;//避免無限遞迴
document.title = newTitle;
currentTitle = newTitle;
Object.defineProperty(document, 'title', titleDescriptor);
const event = new CustomEvent('titlechange', { detail: { newTitle: newTitle } });
window.dispatchEvent(event);
}
}
window.addEventListener('titlechange', (e) => {
readingStand.expandAllArtworks();
if (window.location.href === pageUrl) {
return;
}
pageUrl = window.location.href;
let johnTheHornyOne = new artScraper(10, 50);
johnTheHornyOne.addStartButton(johnTheHornyOne.strategy.getButtonAtClass(), johnTheHornyOne.strategy.getAllButtonClass());
});
//初始化
let johnTheHornyOne = new artScraper(10, 50);
johnTheHornyOne.addStartButton(johnTheHornyOne.strategy.getButtonAtClass(), johnTheHornyOne.strategy.getAllButtonClass());
const johnTheRestaurantWaiter = new customMenu();