// ==UserScript==
// @name Pixiv Arts Preview & Followed Atrists Coloring & Extended History
// @name:ru Pixiv Arts Preview & Followed Atrists Coloring & Extended History
// @namespace Pixiv
// @description Enlarged preview of arts and manga on mouse hovering on most pages. Click on image preview to open original art in new tab, or MMB-click to open art illustration page, Alt+LMB-click to to add art to bookmarks, Ctrl+LMB-click for saving originals of artworks. The names of the authors you are already subscribed to are highlighted with green. Extended history for non-premium users. Settings can be changed in proper menu.
// @description:ru Увеличённый предпросмотр артов и манги по наведению мышки на большинстве страниц. Клик ЛКМ по превью арта для открытия исходника в новой вкладке, СКМ для открытия страницы с артом, Alt + клик ЛКМ для добавления в закладки, Ctrl + клик ЛКМ для сохранения оригиналов артов. Имена авторов, на которых вы уже подписаны, подсвечиваются зелёным цветом. Расширенная история для не премиальных аккаунтов. Настройки можно изменить в соответствующем меню.
// @author NightLancerX
// @version 3.50
// @match https://www.pixiv.net/bookmark_new_illust.php*
// @match https://www.pixiv.net/discovery*
// @match https://www.pixiv.net/ranking.php*
// @match https://www.pixiv.net/*artworks/*
// @match https://www.pixiv.net/*users/*
// @match https://www.pixiv.net/history.php*
// @match https://www.pixiv.net/bookmark_detail.php?illust_id=*
// @match https://www.pixiv.net/*tags/*
// @match https://www.pixiv.net/*
// @connect i.pximg.net
// @connect i-f.pximg.net
// @connect i-cf.pximg.net
// @connect techorus-cdn.com
// @homepageURL https://github.com/NightLancer/PixivPreview
// @supportURL https://greasyfork.org/uk/users/167506-nightlancerx
// @license MIT
// @copyright NightLancerX
// @grant GM_xmlhttpRequest
// @grant GM.xmlHttpRequest
// @require https://code.jquery.com/jquery-3.3.1.min.js
// @require https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/1.3.8/FileSaver.min.js
// @compatible firefox >= 74
// @compatible chrome >= 80
// @noframes
// ==/UserScript==
//=======================================================================================
(function ()
{
'use strict';
if (window.top == window.self && window.jQuery) jQuery(function($) //window.top check may be useless because of @noframes
{
console.log('MyPixivJS');
//---------------------------***CUSTOM PREFERENCES***--------------------------------
let propList = [
{paramIndex:0, array:[false,true], name:"PREVIEW_ON_CLICK"},
{paramIndex:0, array:[0, 100, 200, 300, 500, 1000, 1500], name:"DELAY_BEFORE_PREVIEW"},
{paramIndex:0, array:["auto", 600, 1200], name:"PREVIEW_SIZE"},
{paramIndex:0, array:[false,true], name:"ACCURATE_MANGA_PREVIEW"},
{paramIndex:0, array:[false,true], name:"DISABLE_MANGA_PREVIEW_SCROLLING_PROPAGATION"},
{paramIndex:1, array:[false,true], name:"SCROLL_INTO_VIEW_FOR_SINGLE_IMAGE"},
{paramIndex:0, array:[false,true], name:"DISABLE_SINGLE_PREVIEW_BACKGROUND_SCROLLING"},
{paramIndex:0, array:[false,true], name:"HIDE_PEOPLE_WHO_BOOKMARKED_THIS"},
{paramIndex:0, array:[false,true], name:"KEEP_OLD_DATE_OF_ALREADY_VIEWED_ARTWORKS"},
{paramIndex:1, array:[false,true], name:"ENABLE_AUTO_PAGINATION"}
];
//---------------------------------DEFAULT VALUES------------------------------------
// ■ PREVIEW_ON_CLICK =
// false : showing preview on mouseover (default)
// true : showing preview after LMB-click
//
// ■ DELAY_BEFORE_PREVIEW =
// 0 : no delay before preview (default)
// 1000 : 1 second delay (2000 for 2 seconds, etc)
//
// ■ PREVIEW_SIZE =
// auto : automatically calculate preview size (1200 or 600) depending of current screen size (default)
// 600 : up to 600px x 600px
// 1200 : up to 1200px x 1200px
//
// ■ ACCURATE_MANGA_PREVIEW =
// false : quicker, but less accurate in some cases (default)
// true : takes 1sec before preview showing for more accurate positioning
//
// ■ DISABLE_MANGA_PREVIEW_SCROLLLING_PROPAGATION =
// false : keeping page scrolling after end of manga preview scrolling (default)
// true : disable page scrolling when viewing manga preview (move mouse out of preview to re-enable scrolling)
//
// ■ SCROLL_INTO_VIEW_FOR_SINGLE_IMAGE =
// true : preview of single image will smoothly fit to vertical screen border after one scroll (default)
// false : manually scrolling (may need in case of forced 1200px vertical preview with small user screen)
//
// ■ DISABLE_SINGLE_PREVIEW_BACKGROUND_SCROLLING =
// false: standard behavior (default)
// true : disable page scrolling when viewing single preview (works only if previous setting set to true)
//
// ■ HIDE_PEOPLE_WHO_BOOKMARKED_THIS =
// false: don't change `bookmark_detail.php` page (default)
// true: hide "People who bookmarked this" section
//
// ■ KEEP_OLD_DATE_OF_ALREADY_VIEWED_ARTWORKS =
// false: update date every time artwork page opens (default)
// true: don't renew date and keep first one (NOTE: art will not appear at the top of the history)
//
// ■ ENABLE_AUTO_PAGINATION =
// false: disable auto pagination
// true: enable auto-pagination on `bookmark_new_illust` page (default)
let currentSettings = {};
//-----------------------------------------------------------------------------------
let hoverImg = document.createElement('img');
let imgContainer = document.createElement('div');
imgContainer.style = 'position:absolute; display:block; visibility:visible; z-index:1000; background:#222; padding:5px; margin:-5px;';
imgContainer.appendChild(hoverImg);
let mangaContainer = document.createElement('div');
mangaContainer.id = 'mangaContainer';
mangaContainer.style = 'display:block; z-index:1500; background:#111; overflow-x:auto; maxWidth:1200px; white-space:nowrap;';
let mangaOuterContainer = document.createElement('div');
mangaOuterContainer.style = 'position:absolute; display:block; visibility:hidden; z-index:1000; padding:5px; background:#111; maxWidth:1200px; marginY:-5px; marginX: auto; left: 25px;';
mangaOuterContainer.appendChild(mangaContainer);
let imgsArr = [], //for manga-style image packs...
followedUsersId = {}, //storing followed users pixiv ID
BOOKMARK_URL = 'https://www.pixiv.net/ajax/user/XXXXXXXX/following?limit=100&tag=&lang=en',//&offset=0&rest=show'
USER_ID,
totalHits = 0,
lastImgId = -1,
PREVIEWSIZE,
siteImgMaxWidth = 184, //2,7,12 [NEW]| quite useless on this pages because of square previews...
mangaWidth = 1200,
maxRequestTime = 30000,
bookmarkContainer,
DELTASCALE = ('mozInnerScreenX' in window)?70:4,
previewEventType,
PAGETYPE = checkPageType(),
followedCheck = {
id:0, //backuping user id in case of cookie errors
status:0, //-1: error, 0:default, 1:in progress, 2:done
date:0, //date of last successful check
saveState(){
localStorage.setObj('followedCheck', this);
},
loadState(){
this.id = localStorage.getObj('followedCheck')?.id || 0;
this.status = localStorage.getObj('followedCheck')?.status || 0;
this.date = localStorage.getObj('followedCheck')?.date || 0;
}
};
var timerId, tInt, menuTimer;
//-----------------------------------------------------------------------------------
Storage.prototype.setObj = function(key, obj){
return this.setItem(key, JSON.stringify(obj))
}
Storage.prototype.getObj = function(key){
return JSON.parse(this.getItem(key))
}
//===================================================================================
//************************************PageType***************************************
//===================================================================================
function checkPageType()
{
if (document.URL.match(/https:\/\/www.pixiv.net\/bookmark_new_illust(?:_r18)?.php/)) return 0; //New illustrations - New +
if (document.URL.match(/^https:\/\/www.pixiv.net\/discovery(?:\?mode=(safe|r18))?$/)) return 1; //Discovery page(works) - New +
if (document.URL.match('https://www.pixiv.net/bookmark_detail.php?')) return 4; //Bookmark information - Old +
if (document.URL.match('https://www.pixiv.net/ranking.php?')) return 6; //Daily rankings - Old +
if (document.URL.match(/https:\/\/www\.pixiv\.net\/(?:en\/)?users\/\d+\/bookmarks\/artworks/)) return 7; //Bookmarks page - New +
if (document.URL.match(/https:\/\/www\.pixiv\.net\/(?:en\/)?users/)) return 2; //Artist works page - New +
if (document.URL.match(/https:\/\/www\.pixiv\.net\/(?:en\/)?tags/)) return 8; //Search page - New +
if (document.URL.match(/https:\/\/www\.pixiv\.net\/(?:en\/)?artworks/)) return 12; //Illust page - New* +
if (document.URL.match('https://www.pixiv.net/discovery/users?')) return 13; //Discovery page(users) New +
if (document.URL.match('https://www.pixiv.net/history.php')) return 14; //History - Old +
if (document.URL.match(/^https:\/\/www\.pixiv\.net\/(?:en\/)?$/)) return 10; //Home page - New +
return -1;
}
console.log('PAGETYPE:',PAGETYPE);
//-----------------------------------------------------------------------------------
//Old: 4 6 14
//New: 0 1 2 7 8 10 12 13
//==============----------------------------
//Coloring: = 1 = 4 6 7 8 10 12 == ~~ //
//Profile card: ± 1 = 4 6 7 8 10 12 == == //
//On following: = 1 2 4 6 7 8 ?? 12 13 == //
//===================================================================================
function setCurrentSettings(){
for (let i = 0; i < propList.length; i++){
currentSettings[propList[i].name] = propList[i].array[propList[i].paramIndex];
}
resetPreviewSize(); //needed because of "auto" feature
}
//-----------------------------------------------------------------------------------
function saveSettings(){
for (let i = 0; i < propList.length; i++){
localStorage.setObj(propList[i].name, propList[i].paramIndex);
}
console.log("Settings saved");
}
//-----------------------------------------------------------------------------------
function loadSavedSettings(){
for (let i = 0; i < propList.length; i++){
propList[i].paramIndex = localStorage.getObj(propList[i].name) || propList[i].paramIndex; //load saved setting value, or let default if not found
if ((propList[i].paramIndex < 0) || (propList[i].paramIndex >= propList[i].array.length)){
propList[i].paramIndex = 0; // "0" is not default for all settings...
console.error(`localStorage error! Setting ${propList[i].name} has been reset to default value! [${propList[i].array[propList[i].paramIndex]}]`);
}
}
console.log("Settings loaded");
}
//-----------------------------------------------------------------------------------
loadSavedSettings();
setCurrentSettings();
//-----------------------------------------------------------------------------------
previewEventType = (currentSettings["PREVIEW_ON_CLICK"])?'click':'mouseenter'; //need to be 'click' for overwriting default site event handlers
function resetPreviewSize(){PREVIEWSIZE = (currentSettings["PREVIEW_SIZE"] > 0)?currentSettings["PREVIEW_SIZE"]:(window.innerHeight>1200 & document.body.clientWidth>1200)?1200:600}
//===================================================================================
//**********************************ColorFollowed************************************
//===================================================================================
if ([1,4,6,7,8,10,12].includes(PAGETYPE))
{
checkFollowedArtists();
}
//-----------------------------------------------------------------------------------
function makeArgs(baseUrl, total){
let arr = [];
for(let i = 1; i < Math.ceil(total / 100); i++){ //from 1 - because we already have first 100 users
arr.push(baseUrl + "&offset=" + i + "00");
}
return arr;
}
//-----------------------------------------------------------------------------------
async function getUserId(){
USER_ID = USER_ID
|| followedCheck?.id
|| document.cookie.match(/user_id=\d+/)?.[0].split("=").pop()
|| Object.keys(localStorage).filter(e => e.match(/viewed_illust_ids_\d+/)).map(a => a.match(/\d+/))[0]
|| (await fetch('https://www.pixiv.net/bookmark.php')).url.match(/\d{3,}/)[0];
if (!USER_ID) return Promise.reject('FATAL ERROR in obtaining user ID! Please report this on GitHub "Issues"');
}
//-----------------------------------------------------------------------------------
async function checkFollowedArtists()
{
followedCheck.loadState();
if (((Date.now()-23*60*60*1000) > followedCheck.date) || (followedCheck.status < 2) || !localStorage['followedUsersId']){
console.log('*Followed check started*');
followedCheck.status = 1;
followedCheck.saveState();
await getUserId().catch(e => followedCheckError(e));
if (USER_ID>0){
BOOKMARK_URL = BOOKMARK_URL.replace("XXXXXXXX", USER_ID);
}
else return -1;
//make first request separately for obtaining count of followed users, both public/private
let response0 = [];
try{
response0 = await Promise.all([request(BOOKMARK_URL+'&rest=show&offset=0'), request(BOOKMARK_URL+'&rest=hide&offset=0')]);
}
catch(error){
console.error("Error with initial bookmark url!");
followedCheckError(error);
return -1;
}
for(const i of response0) i.body.users.forEach(user => followedUsersId[user.userId] = 1);
let args = [];
let len = response0.map(r => r.body.total);
args = makeArgs(BOOKMARK_URL+'&rest=show', len[0]); //public
args.concat(makeArgs(BOOKMARK_URL+'&rest=hide', len[1])); //private
//100 parallel requests in case of 10K users. TODO: find maximum amount and part requests
let responseArray = [];
try{
responseArray = await Promise.all(args.map(request));
}
catch(error){
followedCheckError(error);
return -1;
}
for(const r of responseArray) r.body.users.forEach(user => followedUsersId[user.userId] = true);
localStorage.setObj('followedUsersId', followedUsersId);
followedCheck.id = USER_ID;
followedCheck.status = 2;
followedCheck.date = Date.now();
followedCheck.saveState();
console.log('*Followed check completed*');
console.log('Obtained', Object.keys(followedUsersId).length, 'followed users');
}
else{
followedUsersId = localStorage.getObj('followedUsersId');
console.log(`followedCheck is up to date of %c${new Date(followedCheck.date).toLocaleString()}`, 'color:violet;');
}
}
//-----------------------------------------------------------------------------------
async function request(url, responseType)
{
return new Promise(function (resolve, reject){
let xhr = new XMLHttpRequest();
xhr.responseType = responseType || 'json';
xhr.timeout = 10000;
xhr.open('GET', url, true);
xhr.onload = function (){
resolve(this.response);
};
xhr.onerror = xhr.ontimeout = function(){
reject(this);
};
xhr.send();
});
}
//-----------------------------------------------------------------------------------
function followedCheckError(error){
console.error(error);
followedCheck.status = -1;
followedCheck.saveState();
}
//-----------------------------------------------------------------------------------
async function colorFollowed(artsContainers)
{
let c = 0, d = 0;
while (!artsContainers || artsContainers.length === 0) //first call -> daily rankings, illust page
{
console.log('waiting for arts...');
await sleep(2000);
artsContainers = getAllArtsContainers();
++c;
if (c>5) {console.error('Error while waiting for arts loading! [Timeout 10s]'); return}
}
let artsContainersLength = artsContainers.length;
//wait until last XHR completed if it is not---------------------------------------
followedCheck.loadState();
if (followedCheck.status == 1){
while (followedCheck.status !== 2){
console.log("waiting for followed users..."); //this could happen in case of huge amount of followed users
await sleep(2000);
followedCheck.loadState();
++d;
if (d*2000 > maxRequestTime || followedCheck.status == -1){
console.error(`ERROR while EXPECTING for subscriptions list! [${d*2000/1000}s]`);
break;
}
}
}
//load from localStorage on any errors
if (followedCheck.status <= 0 || Object.keys(followedUsersId).length == 0){
console.error(`There was some error during followed users check [Error Code: ${followedCheck.status}]`);
console.log(`Trying to load cached followedUsersId by date of ${new Date(followedCheck.date).toLocaleString()} ...`);
followedUsersId = localStorage.getObj('followedUsersId');
if (followedUsersId && Object.keys(followedUsersId).length > 0){
console.log("Loaded cached", Object.keys(followedUsersId).length, "followed users");
}
else{
console.error('There is no locally stored followed users entries!');
return -1;
}
}
//---------------------------------------------------------------------------------
if (PAGETYPE!==1) console.log('arts loaded:', artsContainersLength, 'Total:', getAllArtsContainers().length);
let hitContainers = [];
let currentHits = 0;
hitContainers = [].filter.call(artsContainers, container => followedUsersId[getAuthorIdFromContainer(container)] == 1);
hitContainers.forEach(container => container.setAttribute("style", "background-color: green; !important"));
currentHits = hitContainers.length;
totalHits += currentHits;
if (PAGETYPE!==1) console.log('hits: '+currentHits + ' (Total: '+(totalHits)+')');
}
//-----------------------------------------------------------------------------------
function getAllArtsContainers()
{
switch (PAGETYPE){
case 1: return document.querySelectorAll('li > div');
case 4:
case 6: return document.querySelectorAll('.ui-profile-popup'); //document.querySelectorAll('.gtm-illust-recommend-user-name');
case 12: return document.querySelectorAll('.gtm-illust-recommend-title');
case 7:
case 8:
case 10: return document.querySelectorAll('a[href*="/artworks/"]:only-child');
default: console.error('Unprocessed PAGETYPE in getAllArtsContainers()!');
}
return null;
}
//-----------------------------------------------------------------------------------
function getAuthorIdFromContainer(artContainer)
{
let authorId = -1;
if (!artContainer){
console.error('UNPROCESSED getAuthorIdFromContainer() call!');
}
else if (typeof artContainer.hasAttribute !== 'function'){
console.log(artContainer, 'has been filtered out.');
}
else if (PAGETYPE===1){
authorId = searchNearestNode(artContainer,'[href*="/users/"]').getAttribute('href').split('/').pop();
}
else if (PAGETYPE===12){
authorId = artContainer.querySelector('[href*="/users/"]').getAttribute('href').split('/').pop(); //searchNearestNode if needed
}
else if (PAGETYPE===7 || PAGETYPE===10){
authorId = searchNearestNode(artContainer,'[href*="/users/"]').getAttribute('href').split('/').pop(); //Array.slice(artcontainers).map((e) => searchNearestNode(e,'[href*="/users/"]').getAttribute('href').split('/').pop() )
}
else if (PAGETYPE===4 || PAGETYPE===6){
authorId = artContainer.getAttribute('data-user_id') || artContainer.querySelector('.ui-profile-popup').getAttribute('data-user_id');
}
else if (PAGETYPE===8){
let node = searchNearestNode(artContainer,'[href*="/users/"]');
authorId = (node)? node.getAttribute('href').split('/').pop(): -8;
}
return +authorId;
}
//-----------------------------------------------------------------------------------
function sleep(ms)
{
return new Promise(resolve => setTimeout(resolve, ms));
}
//-----------------------------------------------------------------------------------
function getElementByXpath(path)
{
return document.evaluate(path, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
}
//-----------------------------------------------------------------------------------
function getArtSectionContainers()
{
switch(PAGETYPE)
{
case 0: return $('section ul')[0];
case 1:
case 4: return $('.gtm-illust-recommend-zone')[0]
case 6: return $('.ranking-items')[0]
case 7: return $('section')[0]
case 8: return $("section>div>ul")[0]
case 10: return $("div[id='root']>div>div:nth-child(2)")[0]
case 12: return $('.gtm-illust-recommend-zone ul')[0]
default: return 0;
}
}
//-----------------------------------------------------------------------------------
let observer = {
mutationObserver: null,
init(func){
this.mutationObserver = new MutationObserver((mutations)=>{func(mutations)});
},
observe(mainDiv, options){
this.mutationObserver.observe(mainDiv, options);
console.log('Observer has been set');
},
disconnect(){
this.mutationObserver.disconnect();
}
}
//-----------------------------------------------------------------------------------
let renewObserver = Object.assign({}, observer); //copy new instance of object
//-----------------------------------------------------------------------------------
function observerBody(mutations)
{
let arr = [];
mutations.forEach(function(mutation)
{
mutation.addedNodes.forEach(function(node)
{
if (PAGETYPE == 10){
if (node.nodeName == "IMG" && node.matches('img:not([class*=" "])')){ //todo: very unstable condition(>=2 img classes)
arr.push(searchNearestNode(node, 'a[href*="/artworks/"]'));
}
else if (node.nodeName == "DIV"){
node.querySelectorAll('a[href*="/artworks/"]:only-child').forEach((el)=>arr.push(el));
}
}
else if (PAGETYPE == 12 && (!!node.querySelector('iframe'))){
node.remove(); //filtering ads
}
else if (PAGETYPE == 1){
node.querySelectorAll('li > div').forEach((el) => arr.push(el));
}
else{
arr.push(node);
}
});
});
if (arr.length>0) colorFollowed(arr);
}
//-----------------------------------------------------------------------------------
observer.init(observerBody);
//-----------------------------------------------------------------------------------
async function waitForArtSectionContainers()
{
let mainDiv = getArtSectionContainers();
let count = 0;
while(!mainDiv)
{
console.log('Waiting for arts container...');
await sleep(1000);
mainDiv = getArtSectionContainers();
++count;
if (count>10) {console.error('Error while waiting for arts containers! [Timeout 10s]'); return -1}
}
console.log(mainDiv);
return mainDiv;
}
//-----------------------------------------------------------------------------------
async function initMutationObject(options)
{
observer.observe(await waitForArtSectionContainers(), options);
}
//-----------------------------------------------------------------------------------
function searchNearestNode(el, selector)
{
let nearestNode = el.querySelector(selector);
while ((!nearestNode) && (el != document.body)){
el = el.parentNode;
nearestNode = el.querySelector(selector);
}
return nearestNode;
}
//-----------------------------------------------------------------------------------
function followage(thisObj, toFollow) //In case of followed check lasting too long, async queue may be a solution
{
console.log('toFollow: '+ toFollow);
let userId = searchNearestNode(thisObj, '[href*="/users/"]').getAttribute('href').split('/').filter(el => el.match(/\d+/))[0];
if (!(userId>0)) console.error(`Wrong userId! ${userId}`);
if (localStorage.getObj('followedCheck').status == 2) //at least basic check until queue is developed
{
if (Object.keys(followedUsersId).length == 0)
followedUsersId = localStorage.getObj('followedUsersId');
if (toFollow){
followedUsersId[userId] = 1;
if ([2,12].includes(PAGETYPE)){
initFollowagePreview();
}
}
else
delete followedUsersId[userId];
localStorage.setObj('followedUsersId', followedUsersId);
console.log('userId ' + userId + [(toFollow)?' added to':' deleted from'] + ' localStorage. Followed: '+ Object.keys(followedUsersId).length);
}
else console.error('Slow down! You have subscribed too many to handle this by now! Wait for the next updates or let me know.');
}
//-------------------------------------Followage-------------------------------------
async function initFollowagePreview()
{
let recommendationBlock;
let c = 0;
while(!recommendationBlock)
{
console.log('Waiting for FollowagePreview');
await sleep(1000);
recommendationBlock = getElementByXpath("//div[contains(., 'Recommended users')]");
++c;
if (c>10) {console.error("Error while waiting for recommendationBlock! [Timeout 10s]"); return -1}
}
console.log('*FollowagePreview loaded*');
//let recommendationObserver = Object.assign({}, observer);
let scrollBackward = recommendationBlock.querySelector('div:nth-child(3) > div:nth-child(2) > button:nth-child(1)');
let scrollForward = recommendationBlock.querySelector('div:nth-child(3) > div:nth-child(2) > button:nth-child(2)');
recommendationBlock.onwheel = function(e){
e.preventDefault(); //no need
if (e.deltaY > 0) scrollForward.click()
else scrollBackward.click();
};
$(recommendationBlock).on(previewEventType, 'a:not([href*="/users/"]) img', function(e)
{
e.preventDefault();
//let top = window.innerHeight - PREVIEWSIZE - 5 + window.scrollY + 'px';
let top = window.scrollY + 5 + 'px';
setHover(this, top);
});
}
//---------------------------------------History-------------------------------------
let illust_history = {
ids: [],
timestamps: {},
load(){
this.ids = localStorage.getObj('viewed_illust_ids') || USER_ID && localStorage.getObj('viewed_illust_ids_' + USER_ID).data || [];
this.timestamps = localStorage.getObj('viewed_illust_timestamps') || USER_ID && localStorage.getObj('viewed_illust_timestamp_' + USER_ID).data || {};
},
save(){
localStorage.setObj('viewed_illust_ids', this.ids); //viewed_illust_ids
localStorage.setObj('viewed_illust_timestamps', this.timestamps); //viewed_illust_timestamp
},
add_record(illust_id){
this.load();
if (this.ids.indexOf(illust_id.toString()) == -1){
this.ids.push(illust_id.toString());
this.timestamps[illust_id] = Date.now()/1000;
console.log(+illust_id, "has been added to history");
}
else if (currentSettings["KEEP_OLD_DATE_OF_ALREADY_VIEWED_ARTWORKS"] == false){
this.timestamps[illust_id] = Date.now()/1000;
console.log(+illust_id, ": updated view date");
}
else console.log(+illust_id, "already in history");
this.save();
},
delete_record(illust_id){
this.load();
let index = this.ids.indexOf(illust_id.toString());
if (index > -1){
this.ids.splice(index, 1);
}
delete this.timestamps[illust_id];
this.save();
console.log(+illust_id, "has been deleted from history");
},
override(){
this.load();
let date = Date.now()+365*24*60*60*1000;
localStorage.setObj('viewed_illust_ids_' + USER_ID, {data:this.ids, expires:date});
localStorage.setObj('viewed_illust_timestamp_' + USER_ID, {data:this.timestamps, expires:date});
console.info(`History overrided [%c${this.ids.length}%c records]`, 'color:lime;', 'color:;');
let count = 0, t = setInterval(()=>{
document.querySelectorAll('._history-item.trial').forEach(e => e.style.opacity = 1);
if (count>10) clearInterval(t); ++count;
}, 1000);
},
check_space(){
let spaceConsumed = +((new Blob([Object.values(localStorage), Object.keys(localStorage),
localStorage.viewed_illust_ids, localStorage.viewed_illust_timestamps]).size)/(5000*1024)).toFixed(3); //duplicating records not the best solution... but simplest [solve this later if needed]
if (spaceConsumed > 0.95){
this.add_record = this.override = ()=>{};
return Promise.reject(`Too many space consumed [${spaceConsumed*100}%] — history is disabled`); //~100.000 entries
}
else console.log('History initialized');
}
};
getUserId().then(() => illust_history.check_space()).catch(e => console.error('History not initialized —', e));
//===================================================================================
if (PAGETYPE===0) siteImgMaxWidth = 198;
else if (PAGETYPE===4) siteImgMaxWidth = 150;
else if (PAGETYPE===6 || PAGETYPE===14) siteImgMaxWidth = 240;
//-----------------------------------------------------------------------------------
$(document).ready(function ()
{
console.log('$(document).ready');
mangaWidth = document.body.clientWidth - 60;
mangaContainer.style.maxWidth = mangaOuterContainer.style.maxWidth = mangaWidth+'px';
document.body.appendChild(imgContainer);
document.body.appendChild(mangaOuterContainer);
//---------------------------------Settings menu-----------------------------------
let menu = document.createElement("div");
menu.id = "menu";
menu.style = `
position: absolute;
display: block;
visibility: hidden;
top: 60px;
left: 10px;
padding: 5px 5px 5px 20px;
border: 2px solid deepskyblue;
border-radius: 15px;
background: white;
font-size: 14px;
line-height: 17px;
color: rgb(0, 0, 0);
border-radius: 15px;
word-wrap: normal;
`;
//filling menu fields with values and property names
for (let i = 0; i < propList.length; i++){
menu.innerHTML += `<li style = 'font:inherit;'><button style = 'width: 40px; padding: 0px; margin-right: 5px;'>${propList[i].array[propList[i].paramIndex]}</button>${propList[i].name}</li>`
}
document.body.appendChild(menu);
//---------------------------------------------------------------------------------
function changeMenuValues(menuDiv){
let index = Array.prototype.indexOf.call(menuDiv.parentNode.parentNode.children, menuDiv.parentNode);
propList[index].paramIndex+=1;
if (propList[index].paramIndex >= propList[index].array.length) propList[index].paramIndex = 0;
menuDiv.textContent = propList[index].array[propList[index].paramIndex];
//foolproof protection
if (propList[0].paramIndex == 1){
menu.childNodes[1].childNodes[0].disabled = true;
propList[1].paramIndex = 0;
menu.childNodes[1].childNodes[0].textContent = "0";
}
else{
menu.childNodes[1].childNodes[0].disabled = false;
}
}
$('#menu').on('click', 'button', function(){
changeMenuValues(this);
});
//---------------------------------------------------------------------------------
async function initMenu(){
if ([-1,14].includes(PAGETYPE)) return;
let buttons, menuButton; //put to global scope if (menuButton) is needed elsewhere
let count = 0;
while (!menuButton && count<5){
if ([0,1,2,7,8,10,12].includes(PAGETYPE))
buttons = document.querySelectorAll('body > div#root > div > div:nth-child(1) button');
else
buttons = document.querySelectorAll('body > div#js-mount-point-header > div:nth-child(1) button');
menuButton = buttons[buttons.length - 1]; // last is the menu button
console.log(menuButton);
await sleep(1000);
++count;
}
if (menuButton)
menuButton.addEventListener("click", function(){
menu.style.visibility = 'visible';
clearTimeout(menuTimer);
menuTimer = setTimeout(()=>{menu.style.visibility = 'hidden'}, 60*1000); //closing menu after 60s to prevent "hanging" it in one tab
});
else
console.error("menuButton is undefined!");
}
//---------------------------------------------------------------------------------
$(document).mouseup(function (e){
if (!($(menu).has(e.target).length) && (menu.style.visibility == 'visible')){
menu.style.visibility = 'hidden';
saveSettings();
setCurrentSettings();
clearTimeout(menuTimer);
}
});
//---------------------------------------------------------------------------------
initMenu();
//-------------------------------Follow onclick------------------------------------
let toFollow, followSelector;
//---------------------------------------------------------------------------------
function reInitFollowagePreview()
{
if ([1,2,7,8,12].includes(PAGETYPE)){
followSelector = 'button:contains("Follow")';
}
else if ([4,6,13].includes(PAGETYPE)){
followSelector = '.follow-button';
}
else return 0;
$('body').off('mouseup', followSelector); //clearing previous events
if ([1,2,4,6,7,8,12,13].includes(PAGETYPE))
{
$('body').on('mouseup', followSelector, function(){
toFollow = (this.textContent == 'Follow'); //~mustn't work on non-English locale| todo: add some locale-specific text condition?
followage(this, toFollow);
});
}
}
//---------------------------------------------------------------------------------
reInitFollowagePreview();
//====================================PAGINATION===================================
async function autoPagination(){
if (!currentSettings['ENABLE_AUTO_PAGINATION'] || ![0].includes(PAGETYPE)) return;
let pageCount = location.href.match(/(?<=[?|&]p=)\d+/)?.[0] || 1;
let mode = location.href.match(/r18/)?.[0] || "All";
//-------------------------------------------------------------------------------
let x_csrf_token; //for bookmarks
request('/en/', 'document').then(response => x_csrf_token = response.documentElement.innerHTML.match(/(?<=token":")[\dA-z]+/));
//-------------------------------------------------------------------------------
let artsSection = await waitForArtSectionContainers();
await sleep(2500);
let art = $(artsSection.querySelector('span')).parents('li')[0].cloneNode(true);
art.querySelectorAll('img').forEach(el => el.src='');
//-------------------------------------------------------------------------------
let running = false;
window.onscroll = async function(){
if ((window.innerHeight + window.scrollY) >= document.body.scrollHeight){
if (running || pageCount>=35) return; //seems current limit for following is 35 pages
running = true;
pageCount++;
console.log('Loading', pageCount, 'page...');
let url = `https:\/\/www\.pixiv\.net\/ajax\/follow_latest\/illust\?p=${pageCount}\&mode=${mode}\&lang=en`;
fetch(url).then(r => r.json()).then(response => {
response.body.thumbnails.illust.forEach(obj => {
let el = art.cloneNode(true);
if (obj.pageCount > 1) el.querySelectorAll('span')[2].textContent = obj.pageCount;
else $(el.querySelector('span')).parents('a > div')[0].remove();
//-------------------------------------------------------------------------
//start of ****ing frameworks layout. Hope this won't break in less than a year-_-
let s = el.querySelector('[href]').href.match('/en/')?.[0] || '/';
let hrefs = el.querySelectorAll('[href]');
hrefs[0].setAttribute('data-gtm-value', obj.id);
hrefs[0].href = s + "artworks/" + obj.id;
hrefs[1].href = s + "artworks/" + obj.id;
hrefs[1].textContent = obj.title;
hrefs[2].setAttribute('data-gtm-value', obj.userId);
hrefs[2].href = s + "users/" + obj.userId;
hrefs[3].setAttribute('data-gtm-value', obj.userId);
hrefs[3].href = s + "users/" + obj.userId;
hrefs[3].textContent = obj.userName;
//
let imgs = el.querySelectorAll('img');
imgs[0].src = obj.urls["360x360"];
imgs[1].src = obj.profileImageUrl;
//
if (obj.bookmarkData) el.querySelectorAll('path:not(:only-child)').forEach(e => {
e.setAttribute("style", "fill: rgb(255, 64, 96); !important")
});
//-------------------------------------------------------------------------
el.style.display = "flex";
artsSection.appendChild(el);
});
console.log('Loaded', response.body.thumbnails.illust.length, 'arts');
running = false;
});
} //endif
} //onscroll
//-------------------------------------------------------------------------------
$(artsSection).on('click', 'button', function(event){
event.preventDefault();
let illust_id = searchNearestNode(this,'[href*="/artworks/"]').href.match(/\d+/)[0];
fetch('/ajax/illusts/bookmarks/add', {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json; charset=utf-8',
'x-csrf-token': x_csrf_token
},
body: JSON.stringify({"illust_id":illust_id,"restrict":0,"comment":"","tags":[]})
})
.then(() => this.querySelectorAll('path:not(:only-child)').forEach(e => e.setAttribute("style", "fill: rgb(255, 64, 96);")))
.catch(err => console.log(err));
});
}
//=================================================================================
function reInitMutationObservers()
{
if (PAGETYPE===0){
autoPagination();
$('body').on('click', 'a[href*="/bookmark_new_illust"]', function(e){
e.preventDefault();
location.href = this.href;
});
}
//-------------------------------------------------------------------------------
if (PAGETYPE===1){
colorFollowed();
initMutationObject({'childList': true, 'subtree': true});
let timeout_1;
$('body').on('mouseup', 'a[href*="/discovery"]', function(){
clearTimeout(timeout_1);
timeout_1 = setTimeout(() => {
if (PAGETYPE===1){
colorFollowed();
initMutationObject({'childList': true, 'subtree': true});
}
}, 2000);
});
}
//----------------------------Bookmark detail page cleaning----------------------
if (PAGETYPE===4)
{
if (currentSettings["HIDE_PEOPLE_WHO_BOOKMARKED_THIS"])
$('.bookmark-list-unit')[0].remove();
}
//------------------------------Daily rankings ad cleaning-----------------------
if (PAGETYPE===6)
{
colorFollowed();
$('.ad-printservice').remove();
}
//-------------------------------Illust page extra check-------------------------
if (PAGETYPE===12)
{
initMutationObject({'childList': true});
illust_history.add_record(location.href.match(/\d+/)[0]);
}
//-----------------------------Init illust fetch listener------------------------
if (PAGETYPE===4 || PAGETYPE===6 || PAGETYPE===8)
{
initMutationObject({'childList': true});
}
//-------------------------------Home page listener------------------------------
if (PAGETYPE===10)
{
initMutationObject({'childList': true, 'subtree': true});
}
//--------------------------------Pixiv User pages-------------------------------
if (PAGETYPE===2 || PAGETYPE===7)
{
$('body').off('mouseup', 'a[href*="bookmarks/artworks"]');
$('body').off('mouseup', 'a[href*="/illustrations"]');
$('body').on('mouseup', 'a[href*="bookmarks/artworks"]', function(){
console.log('PAGETYPE: '+ PAGETYPE+' -> 7');
PAGETYPE = 7;
Promise.all([checkFollowedArtists(), waitForArtSectionContainers()])
.then(() => colorFollowed(), err => console.error(err));
});
$('body').on('mouseup', 'a[href*="/illustrations"]', function(){
console.log('PAGETYPE: '+ PAGETYPE+' -> 2');
PAGETYPE = 2;
});
}
//------------------------------------Bookmarks----------------------------------
if (PAGETYPE===7)
{
Promise.all([checkFollowedArtists(), waitForArtSectionContainers()])
.then(() => colorFollowed(), err => console.error(err));
}
//------------------------------------History------------------------------------
if (PAGETYPE===14){
let trial = document.querySelector('span.trial');
if (trial){
getUserId().then(() => illust_history.override());
trial.textContent = "Extended Version";
}
}
//-------------------------------------------------------------------------------
}
//---------------------------------------------------------------------------------
reInitMutationObservers();
//=================================================================================
//***************************************HOVER*************************************
//=================================================================================
//------------------------------------Profile card--------------------------------- //4,6,9 [~0,1,2,7,8,10,12]
function initProfileCard()
{
if ([4,6,9].includes(PAGETYPE))
{
$('body').on(previewEventType, 'section._profile-popup a[href*="/artworks/"]', function(e)
{
console.log('Profile card');
e.preventDefault();
setHover(this, getOffsetRect(this).top+200+'px', true);
});
}
}
initProfileCard();
//=================================================================================
//*******************************Initialize Preview Listeners**********************
//=================================================================================
function initPreviewListeners()
{
//New illustrations, Discovery[Artworks], Artist pages, Bookmarks, Search, Home page, Artwork page //0,1,2,7,8,10,12
if (PAGETYPE === 0 || PAGETYPE === 1 || PAGETYPE === 2 || PAGETYPE === 7 || PAGETYPE === 8 || PAGETYPE === 10 || PAGETYPE === 12)
{
console.info('new');
$('body').on(previewEventType, 'a[href*="/artworks/"] img', function(e)
{
e.preventDefault();
//---------------------------filtering preview card--------------------------
if (getElementByXpath("//a[text()='View Profile']")){
if (this.parentNode.parentNode.querySelector('span'))
setMangaHover(this, this.parentNode.parentNode.textContent, getOffsetRect(this).top+112+'px');
else
setHover(this, getOffsetRect(this).top+112+5+'px', true);
}
//-------------------------filtering recommended users-----------------------
else if (getElementByXpath("//div[text()='Recommended users']")){
let top = window.scrollY + 5 + 'px';
setHover(this, top);
}
//--------------------------------Normal case--------------------------------
else{
//multiple
if (this.parentNode.parentNode.querySelector('span'))
setMangaHover(this, this.parentNode.parentNode.textContent.replace(/R-18(G)?/,""));
//single
else setHover(this);
}
//---------------------------------------------------------------------------
});
//-----------------------------------------------------------------------------
if (PAGETYPE === 12) $('body').on('click', '[role="presentation"] img', function(event){
if (event.ctrlKey){
event.preventDefault();
event.stopPropagation();
let isManga = !!document.querySelector('.gtm-manga-viewer-preview-modal-open');
onClickActions(this, event, isManga);
}
});
}
//----------------------DAILY RANKINGS & BOOKMARK INFORMATION PAGES-------------- //4,6
else if (PAGETYPE === 4 || PAGETYPE === 6)
{
$('body').on(previewEventType, 'a[href*="/artworks/"]', function(e) //direct div selector works badly with "::before"
{
e.preventDefault();
//single
if (this.childNodes.length == 1 && this.childNodes[0].nodeName=="DIV"){
setHover(this.firstChild.firstChild);
}
//multiple
else if (this.children[1] && this.children[1].className == 'page-count'){
setMangaHover(this.firstChild.firstChild, this.children[1].children[1].textContent);
}
});
}
//----------------------------------DISCOVERY[USERS]----------------------------- //13
else if (PAGETYPE === 13)
{
$('body').on(previewEventType, 'a[href*="/artworks/"] img', function(e){
e.preventDefault();
if (this.childNodes.length == 0) setHover(this); //single art
else if (this.childNodes.length == 1) setMangaHover(this, this.firstChild.textContent); //manga
});
}
//-------------------------------------History----------------------------------- //14
else if (PAGETYPE === 14)
{
$('body').on(previewEventType, '._history-item', function(e){
e.preventDefault();
setHover(this, getOffsetRect(this).top + 'px');
});
}
}
//---------------------------------------------------------------------------------
initPreviewListeners();
//=================================================================================
if (currentSettings["DELAY_BEFORE_PREVIEW"]>0) $('body').on('mouseleave', 'a[href*="/artworks/"]', function()
{
clearTimeout(timerId);
clearInterval(tInt);
});
//---------------------------------Async page change-------------------------------
function renewAll()
{
if (PAGETYPE != checkPageType())
{
console.log('PAGETYPE:', PAGETYPE, '->', PAGETYPE = checkPageType());
clearTimeout(timerId);
clearInterval(tInt);
observer.disconnect();
$('body').off(previewEventType, 'a[href*="/artworks/"]');
$('body').off(previewEventType, 'a[href*="/artworks/"] img');
$('body').off('mouseup', 'a[href*="bookmarks/artworks"]');
$('body').off('mouseup', 'a[href*="/illustrations"]');
$('body').off('mouseup', 'a[href*="/discovery"]');
$('body').off('click', '[role="presentation"] img');
$('section ul').off('click', 'button');
window.onscroll = null;
if (PAGETYPE === -1) return;
if ([8,10].includes(PAGETYPE)) colorFollowed(); //extra coloring for missing arts
initPreviewListeners();
reInitMutationObservers();
initMenu();
autoPagination();
reInitFollowagePreview();
initProfileCard();
}
}
//---------------------------------------------------------------------------------
renewObserver.init(renewAll);
renewObserver.observe($('body')[0], {childList: true, subtree: true});
//---------------------------------------------------------------------------------
}); //end of document.ready
//===================================================================================
//-----------------------------------------------------------------------------------
function checkDelay(container, thisObj)
{
if (currentSettings["DELAY_BEFORE_PREVIEW"]>0){
clearTimeout(timerId);
timerId = setTimeout(()=>{
if ([].indexOf.call(document.querySelectorAll(':hover'), thisObj) > -1) container.style.visibility = 'visible'
}, currentSettings["DELAY_BEFORE_PREVIEW"]);
}
else container.style.visibility = 'visible';
}
//-----------------------------------------------------------------------------------
function setHover(thisObj, top, profileCard)
{
clearTimeout(timerId);
clearInterval(tInt);
imgContainer.style.visibility = 'hidden';
mangaOuterContainer.style.visibility = 'hidden';
hoverImg.src=''; //just in case
hoverImg.src = parseImgUrl(thisObj);
imgContainer.style.top = top || getOffsetRect(thisObj.parentNode.parentNode).top+'px';
//adjusting preview position considering expected image width
//---------------------------------------------------------------------------------
let l = (![0,1,2,7,10,12,13,14].includes(PAGETYPE)) //more accurate on discovery users and history
?getOffsetRect(thisObj.parentNode.parentNode).left
:getOffsetRect(thisObj).left;
let dcw = document.body.clientWidth;
let previewWidth = PREVIEWSIZE;
if (hoverImg.naturalWidth>0){ //cached (previously viewed)
adjustSinglePreview(dcw, l, hoverImg.naturalWidth, thisObj);
//console.log("cached");
}
else{
if ([4,6,14].includes(PAGETYPE) && !profileCard){
previewWidth = PREVIEWSIZE*(((PAGETYPE==6 || PAGETYPE==14)?thisObj.clientWidth:thisObj.parentNode.parentNode.clientWidth)/siteImgMaxWidth)+5; //if not on NEW layout - width is predictable
adjustSinglePreview(dcw, l, previewWidth, thisObj);
//console.log("count");
}
else{
if (dcw - l - PREVIEWSIZE - 5 > 0){ //if it is obvious that preview will fit on the screen then there is no need in setInterval(trying to use as minimun setInterval`s as possible)
imgContainer.style.left = l+'px';
checkDelay(imgContainer, thisObj);
//console.log("excessive");
}
else{ //when on NEW layout - need to wait until image width is received
let tLimit = 0;
tInt = setInterval(function(){
if (hoverImg.naturalWidth>0){
clearInterval(tInt);
adjustSinglePreview(dcw, l, hoverImg.naturalWidth, thisObj); //position mismatching due to old `thisObj` => clearing in hoverImg.mouseleave
}
++tLimit;
//console.log(tInt, tLimit);
if (tLimit*40>3000){ //timeout 3s in case of loading errors
clearInterval(tInt);
hoverImg.src='';
console.error('setInterval error');
return;
}
}, 40);
}
}
}
//---------------------------------------------------------------------------------
checkBookmark(thisObj, imgContainer);
}
//-----------------------------------------------------------------------------------
function adjustSinglePreview(dcw, l, contentWidth, thisObj)
{
if (l<0) l = 5; //followage preview
let d = dcw - l - contentWidth - 5; //5 - padding - todo...
imgContainer.style.left = (d>=0)?l+'px':l+d+'px';
checkDelay(imgContainer, thisObj);
}
//-----------------------------------------------------------------------------------
function setMangaHover(thisObj, count, top)
{
clearTimeout(timerId);
clearInterval(tInt);
imgContainer.style.visibility = 'hidden'; //just in case
mangaOuterContainer.style.top = top || getOffsetRect(thisObj.parentNode.parentNode).top+'px';
if (!currentSettings["ACCURATE_MANGA_PREVIEW"]) mangaOuterContainer.style.left = '25px';
checkBookmark(thisObj, mangaOuterContainer);
imgsArrInit(thisObj, +count);
}
//-----------------------------------------------------------------------------------
function imgsArrInit(thisObj, count)
{
let primaryLink = parseImgUrl(thisObj);
let currentImgId = getImgId(primaryLink);
//console.log('lastImgId: ' + lastImgId);
//console.log('currentImgId: ' + currentImgId);
//---------------------------------------------------------------------------------
if (currentImgId != lastImgId)
{
for(let j=0; j<imgsArr.length; j++)
{
imgsArr[j].src = '';
}
lastImgId = currentImgId;
for(let i=0; i<count; i++)
{
if (!(!!imgsArr[i])) //if [i] img element doesn't exist
{
imgsArr[i] = document.createElement('img');
mangaContainer.appendChild(imgsArr[i]);
}
imgsArr[i].src = primaryLink.replace('p0','p'+i);
}
if (currentSettings["ACCURATE_MANGA_PREVIEW"] == true || currentSettings["DELAY_BEFORE_PREVIEW"] >= 1000) //more accurate frame adjusting
{
setTimeout(function()
{
adjustMargins(mangaOuterContainer.scrollWidth);
checkDelay(mangaOuterContainer, thisObj);
}, Math.max(1000, currentSettings["DELAY_BEFORE_PREVIEW"]));
}
else //some blind frame adjusting
{
adjustMargins(count*PREVIEWSIZE);
checkDelay(mangaOuterContainer, thisObj);
}
}
//---------------------------------------------------------------------------------
else
{
adjustMargins(mangaOuterContainer.scrollWidth);
checkDelay(mangaOuterContainer, thisObj);
}
}
//-----------------------------------------------------------------------------------
function parseImgUrl(thisObj)
{
let url = (thisObj.src)? thisObj.src: thisObj.style.backgroundImage.slice(5,-2);
url = url.replace(/\/...x..[0|8]/, '/'+PREVIEWSIZE+'x'+PREVIEWSIZE).
replace('_80_a2','').
replace('_square1200','_master1200').
replace('_70','').
replace('_custom1200','_master1200').
replace('custom-thumb','img-master')
;
return url;
}
//-----------------------------------------------------------------------------------
function adjustMargins(width)
{
let margins = document.body.clientWidth - width;
if (margins > 0) mangaOuterContainer.style.left = margins/2+'px';
else mangaOuterContainer.style.left = '25px';
}
//-----------------------------------------------------------------------------------
function checkBookmark(thisContainer, previewContainer)
{
if ([0,1,2,7,8,10,12].includes(PAGETYPE))
bookmarkContainer = searchNearestNode(thisContainer, 'button');
else if ([4,6].includes(PAGETYPE))
bookmarkContainer = searchNearestNode(thisContainer, "._one-click-bookmark")
else return; //no favourite button
if ($(bookmarkContainer).hasClass("on"))
$(previewContainer).css("background", "rgb(255, 64, 96)"); //purple
else
$(previewContainer).css("background", "rgb(34, 34, 34)"); //grey
}
//-----------------------------------------------------------------------------------
function getImgId(str)
{
return str.substring(str.lastIndexOf("/")+1, str.indexOf("_"));
}
//-----------------------------------------------------------------------------------
function getOffsetRect(elem)
{
// (1)
let box = elem.getBoundingClientRect();
// (2)
let body = document.body;
let docElem = document.documentElement;
// (3)
let scrollTop = window.pageYOffset || docElem.scrollTop || body.scrollTop;
let scrollLeft = window.pageXOffset || docElem.scrollLeft || body.scrollLeft;
// (4)
let clientTop = docElem.clientTop || body.clientTop || 0;
let clientLeft = docElem.clientLeft || body.clientLeft || 0;
// (5)
let top = box.top + scrollTop - clientTop;
let left = box.left + scrollLeft - clientLeft;
return { top: Math.round(top), left: Math.round(left) };
}
//===================================================================================
//**************************************Hide*****************************************
//===================================================================================
imgContainer.onmouseleave = function()
{
imgContainer.style.visibility = 'hidden';
hoverImg.src='';
clearTimeout(timerId);
clearInterval(tInt);
};
//-----------------------------------------------------------------------------------
mangaOuterContainer.onmouseleave = function()
{
mangaOuterContainer.style.visibility = 'hidden';
clearTimeout(timerId);
};
//===================================================================================
//***********************************Art Clicks**************************************
//===================================================================================
//-----------------------------Single arts onclick actions---------------------------
hoverImg.onmouseup = function (event)
{
onClickActions(this, event, false);
};
//-----------------------------Manga arts onclick actions----------------------------
$('body').on('mouseup', 'div#mangaContainer > img', function(event)
{
onClickActions(this, event, true);
});
//---------------------------------onClickActions------------------------------------
async function onClickActions(imgContainerObj, event, isManga)
{
event.preventDefault();
let illustId = getImgId(imgContainerObj.src);
//----------------------------Middle Mouse Button click----------------------------
if (event.button == 1)
{
let illustPageUrl = 'https://www.pixiv.net/artworks/' + illustId;
window.open(illustPageUrl,'_blank'); //open illust page in new tab(in background — with FF pref "browser.tabs.loadDivertedInBackground" set to "true")
}
//----------------------------Left Mouse Button clicks...--------------------------
else if (event.button == 0)
{
//----------------------------Single LMB-click-----------------------------------
if (event.shiftKey){
illust_history.delete_record(illustId); //Shift + LMB-click -> delete record from history
document.querySelector(`[style*="/${illustId}_"]`).style.opacity = ".25";
}
else if (!event.altKey)
{
let toSave = event.ctrlKey; //Ctrl + LMB-click -> saving image
let pageNum = 0;
//Single (general url)
let ajaxIllustUrl = 'https://www.pixiv.net/ajax/illust/' + illustId;
//https://www.pixiv.net/rpc/index.php?mode=get_illust_detail_by_ids&illust_ids=
//Manga
if (isManga)
{
let src = imgContainerObj.src;
pageNum = src.match(/(?<=\/\d+_p)\d+(?=[_|.])/)[0];
}
getOriginalUrl(ajaxIllustUrl, pageNum, toSave);
}
//-----------------------------Alt + LMB-click-----------------------------------
else if (event.altKey){
$(bookmarkContainer).click();
if (!isManga) $(imgContainerObj).parent().css("background", "rgb(255, 64, 96)");
else $(mangaOuterContainer).css("background", "rgb(255, 64, 96)");
}
//-------------------------------------------------------------------------------
}
//---------------------------------------------------------------------------------
}
//---------------------------------getOriginalUrl------------------------------------
async function getOriginalUrl(illustPageUrl, pageNum, toSave)
{
let xhr = new XMLHttpRequest();
xhr.responseType = 'json';
xhr.open("GET", illustPageUrl, true);
xhr.onload = function ()
{
let originalArtUrl = this.response.body.urls.original; //this.response.body.url.big;
if (pageNum>0) originalArtUrl = originalArtUrl.replace('p0','p'+pageNum);
if (toSave) saveImgByUrl(originalArtUrl);
else window.open(originalArtUrl, '_blank');
};
xhr.send();
}
//-----------------------------------------------------------------------------------
async function saveImgByUrl(sourceUrl)
{
const filename = sourceUrl.split('/').pop();
const illustId = filename.split('_')[0];
const ext = filename.split('.').pop().toLowerCase();
const GMR = (typeof(GM_xmlhttpRequest)==='function')?GM_xmlhttpRequest:GM.xmlHttpRequest;
//Thanx to FlandreKawaii(c)
GMR({
method: 'GET',
url: sourceUrl,
responseType: 'arraybuffer', //TM
binary: true, //GM
headers: {
Referer: `https://www.pixiv.net/en/artworks/${illustId}`,
},
onload: function(response)
{
if (ext === 'jpg' || ext === 'jpeg')
saveAs(new File([response.response], filename, { type: 'image/jpeg' }));
else if (ext === 'png')
saveAs(new File([response.response], filename, { type: 'image/png' }));
}
});
}
//===================================================================================
//**************************************Other****************************************
//===================================================================================
mangaContainer.onwheel = function(e)
{
if (e.deltaY<0 && (mangaOuterContainer.getBoundingClientRect().top < 0))
{
mangaOuterContainer.scrollIntoView({block: "start", behavior: "smooth"}); //aligning to top screen side on scrollUp if needed
}
else if (e.deltaY>0 && (mangaOuterContainer.getBoundingClientRect().bottom > document.documentElement.clientHeight))
{
mangaOuterContainer.scrollIntoView({block: "end", behavior: "smooth"}); //aligning to bottom screen side on scrollDown if needed
}
let scrlLft = mangaContainer.scrollLeft;
if ((currentSettings["DISABLE_MANGA_PREVIEW_SCROLLING_PROPAGATION"]) || ((scrlLft>0 && e.deltaY<0) || ((scrlLft<(mangaContainer.scrollWidth-mangaContainer.clientWidth)) && e.deltaY>0)))
{
e.preventDefault();
mangaContainer.scrollLeft += e.deltaY*DELTASCALE;
}
};
//-----------------------------------------------------------------------------------
if (currentSettings["SCROLL_INTO_VIEW_FOR_SINGLE_IMAGE"]) imgContainer.onwheel = function(e)
{
if (currentSettings["DISABLE_SINGLE_PREVIEW_BACKGROUND_SCROLLING"]) e.preventDefault();
if (e.deltaY<0 && (imgContainer.getBoundingClientRect().top < 0))
{
imgContainer.scrollIntoView({block: "start", behavior: "smooth"}); //aligning to top screen side on scrollUp if needed
}
else if (e.deltaY>0 && (imgContainer.getBoundingClientRect().bottom > document.documentElement.clientHeight))
{
imgContainer.scrollIntoView({block: "end", behavior: "smooth"}); //aligning to bottom screen side on scrollDown if needed
}
};
//-----------------------------------------------------------------------------------
window.onresize = function()
{
mangaWidth = document.body.clientWidth - 60;
mangaContainer.style.maxWidth = mangaOuterContainer.style.maxWidth = mangaWidth+'px';
resetPreviewSize();
};
//-----------------------------------------------------------------------------------
document.onkeyup = function(e) //Enlarge with shift
{
//console.log(e.keyCode);
if (e.keyCode == 16 && hoverImg.src && PREVIEWSIZE<1200)
{
let l = getOffsetRect(imgContainer).left;
let w = hoverImg.naturalWidth*2+5;
hoverImg.src = hoverImg.src.replace(/\/...x..[0|8]/, '/1200x1200');
imgContainer.style.left = (document.body.clientWidth-l < w)? document.body.clientWidth-w +'px': l +'px';
}
}
//===================================================================================
//***********************************************************************************
//===================================================================================
});
}) (); //function