// ==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('
');
} 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(`
`);
} else {
pageCountHtml.push("");
}
}
});
// "div+="で一括追加にすると、なぜかundefinedが追加され続けるので一つずつ追加
const div = `
`;
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 = `
`;
}
// うごくイラスト再生マーク、イラスト数表示
let ugokuira = "";
let pageCountHtml = "";
if (worksAlt.slice(-4) == "うごイラ") {
ugokuira = '';
} else {
if (pageCount > 2) {
pageCountHtml = `
`;
}
}
div += `
${modeClass}
`;
}
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);