// ==UserScript== // @name Pixiv Infinite Scroll // @name:ja Pixiv Infinite Scroll // @name:zh-CN Pixiv Infinite Scroll // @name:zh-TW Pixiv Infinite Scroll // @namespace https://github.com/chimaha/Pixiv-Infinite-Scroll // @match https://www.pixiv.net/* // @grant none // @version 1.2.1 // @author chimaha // @description Add infinite scroll feature to Pixiv. // @description:ja Pixivに無限スクロール機能を追加します。 // @description:zh-CN 为 Pixiv 添加无限滚动功能。 // @description:zh-TW 因為Pixiv有無限移動功能。 // @license MIT license // @icon https://www.pixiv.net/favicon.ico // @supportURL https://github.com/chimaha/Pixiv-Infinite-Scroll/issues // @downloadURL none // ==/UserScript== /*! Pixiv Infinite Scroll | MIT license | https://github.com/chimaha/Pixiv-Infinite-Scroll/blob/main/LICENSE */ // フォロー-------------------------------------------------------------------------------- function followingProcess() { function createDiv(userId, userName, profileImage, userComment, following, illustId, illustTitle, illustUrl, bookmarkData, illustAlt, r18, pageCount) { // langの値によって言語を変更する const followLanguage = []; const currentLanguage = document.querySelector("html").getAttribute("lang"); switch (currentLanguage) { case "ja": followLanguage.push("フォロー中", "フォローする"); break; case "ko": followLanguage.push("팔로우 중", "팔로우하기"); break; case "zh-CN": followLanguage.push("已关注", "加关注"); break; case "zh-TW": followLanguage.push("關注中", "加關注"); break; default: followLanguage.push("Following", "Follow"); } // フォロー中、フォローするを切り替え let resultFollow; let followClass; let followStyle = ""; if (following) { resultFollow = followLanguage[0]; followClass = "cnpwVx"; followStyle = 'style="background-color: var(--charcoal-surface3); color: var(--charcoal-text2); font-weight: bold; padding-right: 24px; padding-left: 24px; border-radius: 999999px; height: 40px;"'; } else { resultFollow = followLanguage[1]; followClass = "fOWAlD"; } // ブックマークを切り替え let bookmarkClass = []; let bookmarkStyle = []; for (const resultBookmark of bookmarkData) { if (resultBookmark) { bookmarkClass.push("bXjFLc"); bookmarkStyle.push('style="color: rgb(255, 64, 96); fill: currentcolor;"'); } else { bookmarkClass.push("dxYRhf"); } } // コメントに特定の記号が入っていた場合にエスケープ function escapleHtml(escapeText) { return escapeText .replace(/&/g, "&") .replace(//g, ">") .replace(/"/g, """) .replace(/'/g, "'"); } const escapedText = escapleHtml(userComment); // R18マーク let r18div = []; for (const nsfw of r18) { if (nsfw == "R-18") { r18div.push('
R-18
'); } else { r18div.push(""); } } // うごくイラスト再生マーク、イラスト数表示 let ugokuira = []; let pageCountHtml = []; pageCount.forEach((page, i) => { if (illustAlt[i].slice(-4) == "うごイラ") { ugokuira.push(''); } else { ugokuira.push(""); if (page > 2) { pageCountHtml.push(`
${page}
`); } else { pageCountHtml.push(""); } } }); // "div+="で一括追加にすると、なぜかundefinedが追加され続けるので一つずつ追加 const div = `
${escapedText}
`; document.querySelector(".sc-1y4z60g-4.cqwgCG").insertAdjacentHTML("beforeend", div); } // https://www.pixiv.net/ajax/user/*/following?offset=24&limit=24&rest=show // https://www.pixiv.net/users/*/following?p=2 if (document.querySelectorAll(".sc-1y4z60g-5.cPVjJh").length < 24) { return; } // URL作成 const matches = window.location.href.match(followingregex); let offset; if (matches[2]) { offset = (matches[2] * 24) + (pageNumber * 24); } else { offset = 24 + (pageNumber * 24); } pageNumber++; const url = `https://www.pixiv.net/ajax/user/${matches[1]}/following?offset=${offset}&limit=24&rest=show`; const pageAsync = async () => { const response = await fetch(url); const json = await response.json(); for (let i = 0; i < Object.keys(json.body.users).length; i++) { const users = json.body.users[i]; const userId = users.userId; const userName = users.userName; const profileImage = users.profileImageUrl; const userComment = users.userComment.slice(0, 98); const following = users.following; const illustId = []; const illustTitle = []; const illustUrl = []; const bookmarkData = []; const illustAlt = []; const r18 = []; const pageCount = []; for (let j = 0; j < Object.keys(json.body.users[i].illusts).length; j++) { const illusts = json.body.users[i].illusts[j]; illustId.push(illusts.id); illustTitle.push(illusts.title); illustUrl.push(illusts.url); bookmarkData.push(illusts.bookmarkData); illustAlt.push(illusts.alt); r18.push(illusts.tags[0]); pageCount.push(illusts.pageCount); } createDiv(userId, userName, profileImage, userComment, following, illustId, illustTitle, illustUrl, bookmarkData, illustAlt, r18, pageCount); } }; pageAsync(); } // ----------------------------------------------------------------------------------------- // ブックマーク、フォローユーザーの作品、タグ--------------------------------------------------- function anyoneProcess(changeMode) { function createDiv(worksId, worksTitle, worksUrl, userId, userName, pageCount, bookmarkData, worksAlt, profileImageUrl, modeClass, modeOtherClass, r18) { // ブックマークを切り替え let bookmarkClass = ""; let bookmarkStyle = ""; if (bookmarkData) { bookmarkClass = "bXjFLc"; bookmarkStyle = 'style="color: rgb(255, 64, 96); fill: currentcolor;"'; } else { bookmarkClass = "dxYRhf"; } // R18マーク let r18div = ""; if (r18 == "R-18") { r18div = `
R-18
`; } // うごくイラスト再生マーク、イラスト数表示 let ugokuira = ""; let pageCountHtml = ""; if (worksAlt.slice(-4) == "うごイラ") { ugokuira = ''; } else { if (pageCount > 2) { pageCountHtml = `
${pageCount}
`; } } div += ` ${modeClass}
${worksTitle}
`; } let div = ""; if (changeMode == "bookmark") { // ブックマーク // https://www.pixiv.net/ajax/user/*/illusts/bookmarks?tag=&offset=0&limit=48&rest=show // https://www.pixiv.net/users/*/bookmarks/artworks?p=2 if (document.querySelectorAll(".sc-9y4be5-2.kFAPOq").length < 48) { return; } // URL作成 const matches = window.location.href.match(bookmarkRegex); let offset; if (matches[2]) { offset = (matches[1] * 48) + (pageNumber * 48); } else { offset = 48 + (pageNumber * 48); } pageNumber++ const url = `https://www.pixiv.net/ajax/user/${matches[1]}/illusts/bookmarks?tag=&offset=${offset}&limit=48&rest=show`; const pageAsync = async () => { const response = await fetch(url); const json = await response.json(); for (let i = 0; i < Object.keys(json.body.works).length; i++) { const works = json.body.works[i]; const worksId = works.id; const worksTitle = works.title; const worksUrl = works.url; const userId = works.userId; const userName = works.userName; const pageCount = works.pageCount; const bookmarkData = works.bookmarkData; const worksAlt = works.alt; const profileImageUrl = works.profileImageUrl; const modeClass = '
  • '; const modeOtherClass = ""; const r18 = works.tags[0]; createDiv(worksId, worksTitle, worksUrl, userId, userName, pageCount, bookmarkData, worksAlt, profileImageUrl, modeClass, modeOtherClass, r18); } document.querySelector(".sc-9y4be5-1.jtUPOE").insertAdjacentHTML("beforeend", div); }; pageAsync(); } else if (changeMode == "follow") { // フォローユーザーの作品 // https://www.pixiv.net/ajax/follow_latest/illust?p=2&mode=all // https://www.pixiv.net/bookmark_new_illust.php?p=2 if (document.querySelectorAll(".sc-9y4be5-2.kFAPOq").length < 60) { return; } // URL作成 const matches = window.location.href.match(followUserRegex); let offset; ++pageNumber; if (matches[2]) { offset = matches[2] + pageNumber; } else { offset = 1 + pageNumber; } let mode = ""; if (matches[1]) { mode = "r18"; } else { mode = "all"; } const url = `https://www.pixiv.net/ajax/follow_latest/illust?p=${offset}&mode=${mode}`; const pageAsync = async () => { const response = await fetch(url); const json = await response.json(); for (let i = 0; i < Object.keys(json.body.thumbnails.illust).length; i++) { const illust = json.body.thumbnails.illust[i]; const illustId = illust.id; const illustTitle = illust.title; const illustUrl = illust.url; const userId = illust.userId; const userName = illust.userName; const pageCount = illust.pageCount; const bookmarkData = illust.bookmarkData; const illustAlt = illust.alt; const profileImageUrl = illust.profileImageUrl; const modeClass = '
  • '; const modeOtherClass = "gtm-followlatestpage-thumbnail-link"; const r18 = illust.tags[0]; createDiv(illustId, illustTitle, illustUrl, userId, userName, pageCount, bookmarkData, illustAlt, profileImageUrl, modeClass, modeOtherClass, r18); } document.querySelector(".sc-9y4be5-1.jtUPOE").insertAdjacentHTML("beforeend", div); }; pageAsync(); } else if (changeMode == "tag") { // タグ // https://www.pixiv.net/ajax/search/artworks/*?word=*&order=date_d&mode=all&p=1&s_mode=s_tag_full&type=all // https://www.pixiv.net/tags/*/artworks?p=2 if (document.querySelectorAll(".sc-l7cibp-2.gpVAva").length < 60) { return; } // URL作成 const matches = window.location.href.match(tagRegex); let offset; ++pageNumber; if (matches[7]) { offset = matches[7] + pageNumber; } else { offset = 1 + pageNumber; } let type = ""; let illustType; if (matches[2] == "manga") { type = "type=manga"; illustType = "manga"; } else if (matches[2] == "artworks") { type = "type=all"; illustType = "illustManga"; } else if (matches[9] == "illust") { type = "type=illust"; illustType = "illust"; } else if (matches[9] == "ugoira") { type = "type=ugoira"; illustType = "illust"; } else if (matches[2] == "illustrations") { type = "type=illust_and_ugoira"; illustType = "illust"; } let scd = ""; if (matches[5]) { scd = `&${matches[5]}`; } let ecd = ""; if (matches[6]) { ecd = `&${matches[6]}`; } let othertag = ""; if (matches[10]) { othertag = `&${matches[10]}`; } let mode = ""; if (matches[4] == "mode=safe" || matches[4] == "mode=r18") { mode = matches[4]; } else { mode = "mode=all"; } let order = ""; if (matches[3] == "order=date") { order = matches[3]; } else { order = "order=date_d"; } let tagMode = ""; if (matches[8] == "s_mode=s_tag" || matches[8] == "s_mode=s_tc") { tagMode = matches[8]; } else { tagMode = "s_mode=s_tag_full"; } const url = `https://www.pixiv.net/ajax/search/${matches[2]}/${matches[1]}?word=${matches[1]}&${order}&${mode}&p=${offset}&${tagMode}&${type}${scd}${ecd}${othertag}`; const pageAsync = async () => { const response = await fetch(url); const json = await response.json(); for (let i = 0; i < Object.keys(json.body[illustType].data).length; i++) { // jsonファイルに、なぜか必ず1つだけ欠けている部分があるのでスキップする if (!json.body[illustType].data[i].id) { continue; } const illust = json.body[illustType].data[i]; const illustId = illust.id; const illustTitle = illust.title; const illustUrl = illust.url; const userId = illust.userId; const userName = illust.userName; const pageCount = illust.pageCount; const bookmarkData = illust.bookmarkData; const illustAlt = illust.alt; const profileImageUrl = illust.profileImageUrl; const modeClass = '
  • '; const modeOtherClass = ""; const r18 = illust.tags[0]; createDiv(illustId, illustTitle, illustUrl, userId, userName, pageCount, bookmarkData, illustAlt, profileImageUrl, modeClass, modeOtherClass, r18); } document.querySelector(".sc-l7cibp-1.krFoBL").insertAdjacentHTML("beforeend", div); }; pageAsync(); } } // ----------------------------------------------------------------------------------------- let confirmProcess = false; let currentLocation; let pageNumber = 0; const followingregex = /https:\/\/www\.pixiv\.net(?:\/en)?\/users\/(\d+)\/following(?:\?p=(\d+))?/; const bookmarkRegex = /https:\/\/www\.pixiv\.net(?:\/en)?\/users\/(\d+)\/bookmarks\/artworks(?:\?p=(\d+))?/; const followUserRegex = /https:\/\/www\.pixiv\.net\/bookmark_new_illust(_r18)?\.php(?:\?p=(\d+))?/; const tagRegex = /https:\/\/www\.pixiv\.net\/tags\/(.+)\/(artworks|illustrations|manga)(?:\?(order=date))?(?:(?:&|\?)(mode=(?:r18|safe)))?(?:(?:&|\?)(scd=\d{4}\-\d{2}-\d{2}))?(?:(?:&|\?)(ecd=\d{4}\-\d{2}-\d{2}))?(?:(?:&|\?)p=(\d+))?(?:(?:&|\?)(s_mode=(?:s_tag|s_tc)))?(?:(?:&|\?)type=([^&]+))?(?:(?:&|\?)(.+))?/; const observer = new MutationObserver(() => { // URLが変更された際の処理 if (window.location.href != currentLocation) { confirmProcess = false; pageNumber = 0; // タグページで条件を変更した際に、追加した要素を削除する if (tagRegex.test(window.location.href)) { const addPictures = document.querySelectorAll(".nextPage"); for (const addPicture of addPictures) { addPicture.remove(); } } } if (followingregex.test(window.location.href)) { // フォロー currentLocation = window.location.href; const targetElement = document.querySelector(".sc-1y4z60g-4.cqwgCG"); if (targetElement && !confirmProcess) { confirmProcess = true; const scrollObserver = new IntersectionObserver(entries => { entries.forEach((entry) => { if (entry.isIntersecting) { followingProcess(); confirmProcess = false; scrollObserver.unobserve(entry.target); } }) }); scrollObserver.observe(document.querySelector(".sc-1y4z60g-5.cPVjJh:last-child").previousElementSibling); } } else if (bookmarkRegex.test(window.location.href) || followUserRegex.test(window.location.href) || tagRegex.test(window.location.href)) { // ブックマーク、フォローユーザーの作品、タグ currentLocation = window.location.href; let changeMode; if (bookmarkRegex.test(window.location.href)) { changeMode = "bookmark"; } else if (followUserRegex.test(window.location.href)) { changeMode = "follow"; } else { changeMode = "tag"; } let targetElement; if (bookmarkRegex.test(window.location.href) || followUserRegex.test(window.location.href)) { targetElement = document.querySelector(".sc-9y4be5-1.jtUPOE"); } else { targetElement = document.querySelector(".sc-l7cibp-1.krFoBL img"); } if (targetElement && !confirmProcess) { confirmProcess = true; const options = { rootMargin: "0px 0px 300px 0px" }; const scrollObserver = new IntersectionObserver(entries => { entries.forEach((entry) => { if (entry.isIntersecting) { anyoneProcess(changeMode); confirmProcess = false; scrollObserver.unobserve(entry.target); } }) }, options); if (bookmarkRegex.test(window.location.href) || followUserRegex.test(window.location.href)) { scrollObserver.observe(document.querySelector(".sc-9y4be5-2.kFAPOq:last-child")); } else { // タグページで条件を切り替えた際に、うまく動作しないのでsetTimeoutを使用。おそらく要素を取得するタイミングの問題 setTimeout(() => { scrollObserver.observe(document.querySelector(".sc-l7cibp-2.gpVAva:last-child")); }, 400); } } } }); const config = { childList: true, subtree: true }; observer.observe(document.querySelector("#root"), config);