// ==UserScript==
// @name Pixiv Arts Preview & Followed Atrists Coloring
// @name:ru Pixiv Arts Preview & Followed Atrists Coloring
// @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. Settings can be changed in proper menu.
// @description:ru Увеличённый предпросмотр артов и манги по наведению мышки на большинстве страниц. Клик ЛКМ по превью арта для открытия исходника в новой вкладке, СКМ для открытия страницы с артом, Alt + клик ЛКМ для добавления в закладки, Ctrl + клик ЛКМ для сохранения оригиналов артов. Имена авторов, на которых вы уже подписаны, подсвечиваются зелёным цветом. Настройки можно изменить в соответствующем меню.
// @author NightLancerX
// @version 2.55
// @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 techorus-cdn.com
// @connect i-cf.pximg.net
// @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
// @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:"HIDE_FOLLOWED_USERS_ON_DISCOVERY_PAGE"}
];
//---------------------------------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
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: 30px;';
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,
artsLoaded = 0,
totalHits = 0,
lastImgId = -1,
PREVIEWSIZE,
siteImgMaxWidth = 184, //2,7,12 [NEW]| quite useless on this pages because of square previews...
mangaWidth = 1200,
maxRequestTime = 30000,
bookmarkObj,
isBookmarked = false, //rework or delete. Arts can be bookmarked on art page.
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') && localStorage.getObj('followedCheck').id || 0;
this.status = localStorage.getObj('followedCheck') && localStorage.getObj('followedCheck').status || 0;
this.date = localStorage.getObj('followedCheck') && localStorage.getObj('followedCheck').date || 0;
//this.status = localStorage.getObj('followedCheck')?.status || 0; //wanna use this,
//this.date = localStorage.getObj('followedCheck')?.date || 0; //but compatibility... -_-
}
};
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.php?')) return 0; //New illustrations - Old +
if (document.URL==='https://www.pixiv.net/discovery') return 1; //Discovery page(works) - Old +
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 + | actually matches any page...
return -1;
}
console.log('PAGETYPE:',PAGETYPE);
//Old: 0 1 4 6 14
//New: 2 7 8 10 12 13
//==============----------------------------
//Coloring: = 1 = 4 6 7 8 10 12 == ~~ //
//Profile card: 0 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
|| Object.keys(localStorage).filter(e => e.match('viewed_illust')).map(a => a.match(/\d+/)).flat()[0]
|| (followedCheck && followedCheck.id) && followedCheck.id
|| (document.cookie.match(/user_id=\d+/)) && document.cookie.match(/user_id=\d+/)[0].split("=").pop()
|| (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 requestFollowed separately for obtaining count of followed users, both public/private
let response0 = [];
try{
response0 = await Promise.all([requestFollowed(BOOKMARK_URL+'&rest=show&offset=0'), requestFollowed(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] = true); //todo: '1' instead -> less localStorage space
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(requestFollowed));
}
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 ${new Date(followedCheck.date).toLocaleString()}`);
}
}
//-----------------------------------------------------------------------------------
async function requestFollowed(url)
{
return new Promise(function (resolve, reject){
let xhr = new XMLHttpRequest();
xhr.responseType = 'json';
xhr.timeout = 10000;
xhr.open('GET', url, true);
xhr.onload = function(){
if (xhr.status == 200)
resolve(this.response);
else
reject({status: this.status, message: this.response.message, url:this.responseURL});
};
xhr.onerror = xhr.ontimeout = function(){
reject({status: this.status, message: this.response.message, url:this.responseURL});
};
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 = getArtsContainers();
++c;
if (c>5)
{
console.error('Error while waiting for arts loading! [Timeout 10s]');
break;
}
}
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 followed users amount
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 in 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;
}
}
//---------------------------------------------------------------------------------
console.log('arts loaded:', artsContainersLength, 'Total:', getArtsContainers().length);
let hitContainers = [];
let currentHits = 0;
hitContainers = [].filter.call(artsContainers, container => followedUsersId[getAuthorIdFromContainer(container)] === true); // -> '1' -> '=='
if (currentSettings["HIDE_FOLLOWED_USERS_ON_DISCOVERY_PAGE"] && PAGETYPE===1)
hitContainers.forEach(container => container.remove());
else
hitContainers.forEach(container => container.setAttribute("style", "background-color: green; !important"));
currentHits = hitContainers.length;
totalHits += currentHits;
console.log('hits: '+currentHits + ' (Total: '+(totalHits)+')');
}
//-----------------------------------------------------------------------------------
function getArtsContainers()
{
switch (PAGETYPE){
case 1:
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'); //('li > div > div > a'); Done?
default: console.error('Unprocessed PAGETYPE in getArtsContainers()!');
}
return null;
}
//-----------------------------------------------------------------------------------
function getAuthorIdFromContainer(artContainer)
{
let userId = -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 || PAGETYPE===4 || PAGETYPE===6){
userId = artContainer.getAttribute('data-user_id') || artContainer.querySelector('.ui-profile-popup').getAttribute('data-user_id');
}
else if (PAGETYPE===12){
userId = artContainer.querySelector('[href*="/users/"]').getAttribute('href').split('/').pop(); //searchNearestNode if needed
}
else if (PAGETYPE===7 || PAGETYPE===10){
userId = searchNearestNode(artContainer,'[href*="/users/"]').getAttribute('href').split('/').pop();
//Array.slice(artcontainers).map((e) => searchNearestNode(e,'[href*="/users/"]').getAttribute('href').split('/').pop() )
}
else if(PAGETYPE===8){
let node = searchNearestNode(artContainer,'[href*="/users/"]');
userId = (node)? node.getAttribute('href').split('/').pop(): -1;
}
return +userId;
}
//-----------------------------------------------------------------------------------
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 1:
case 4: return $('.gtm-illust-recommend-zone')[0]
case 6: return $('.ranking-items')[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 promo blocks
}
else{
arr.push(node);
}
});
});
if (arr.length>0) colorFollowed(arr);
}
//-----------------------------------------------------------------------------------
observer.init(observerBody);
//-----------------------------------------------------------------------------------
async function initMutationObject(options)
{
let mainDiv = getArtSectionContainers();
let c = 0;
while(!mainDiv)
{
console.log('Waiting for arts container...');
await sleep(1000);
mainDiv = getArtSectionContainers();
++c;
if (c>10) {console.error('Error while waiting for arts containers! [Timeout 10s]'); return -1}
}
console.log(mainDiv);
observer.observe(mainDiv, 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, isNew) //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] = true; // '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: [],
// timestamp: {},
//
// init(){
// this.ids = localStorage.getObj('viewed_illust_ids') || USER_ID && localStorage.getObj('viewed_illust_ids_' + USER_ID).data || [];
// this.timestamp = localStorage.getObj('viewed_illust_timestamp') || USER_ID && localStorage.getObj('viewed_illust_timestamp_' + USER_ID).data || {};
// console.log('History initialized');
// },
//
// save(){
// localStorage.setObj('viewed_illust_ids', this.ids); //viewed_illust_ids
// localStorage.setObj('viewed_illust_timestamp', this.timestamp); //viewed_illust_timestamp
// },
//
// add_record(illust_id, view_date){
// if (this.ids.indexOf(illust_id.toString()) == -1){
// this.ids.push(illust_id.toString());
// }
// this.timestamp[illust_id] = view_date || Date.now()/1000;
//
// this.save();
// console.log(illust_id, "has been added to history record");
// },
//
// delete_record(illust_id){
// let index = this.ids.indexOf(illust_id.toString());
// if (index > -1){
// this.ids.splice(index, 1);
// }
// delete this.timestamp[illust_id];
//
// this.save();
// console.log(illust_id, "has been deleted from history record");
// }
// }
// getUserId().then(v => illust_history.init()).catch(e => console.error('History not initialized: no userID'));
//===================================================================================
if (PAGETYPE===0 || PAGETYPE===1) 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 - 80;
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(){
let buttons, menuButton; //put to global scope if (menuButton) is needed elsewhere
let c = 0;
while (!menuButton){
if ([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'); //$('#js-mount-point-header button'); (Replace with own button later?...)
menuButton = buttons[buttons.length - 1]; // last is the menu button
console.log(menuButton);
await sleep(1000);
++c;
if (c>5){
console.log('*Setting menu currently unavailable on this page*');
break;
}
}
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 ([2,7,8,12].includes(PAGETYPE)){
followSelector = 'button:contains("Follow")';
}
else if([1,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();
//=================================================================================
function reInitMutationObservers()
{
//----------------------------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]); //todo: revoke in case of no ID
}
//-----------------------------Init illust fetch listener------------------------
if (PAGETYPE===1 || PAGETYPE===4 || PAGETYPE===6)
{
initMutationObject({'childList': true});
}
//----------------------------------Search (tags)--------------------------------
if (PAGETYPE===8)
{
initMutationObject({'childList': true});
}
//-------------------------------Home page listener------------------------------
if (PAGETYPE===10)
{
initMutationObject({'childList': true, 'subtree': true});
}
//---------------------------------Pixiv Member 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;
bookmarksInit();
});
$('body').on('mouseup', 'a[href*="/illustrations"]', function(){
console.log('PAGETYPE: '+ PAGETYPE+' -> 2');
PAGETYPE = 2;
});
}
//-------------------------------------------------------------------------------
async function bookmarksInit()
{
await checkFollowedArtists();
let c = 0;
let mainDiv = document.querySelector('section');
while(!mainDiv)
{
console.log('Waiting for arts section [bookmarksInit]');
await sleep(1000);
mainDiv = document.querySelector('section');
++c;
if (c>10) {console.error('Error while waiting for arts section [bookmarksInit]! [Timeout 10s]'); return -1;}
}
console.log(mainDiv);
colorFollowed();
//Promise.all([checkFollowedArtists(), getArtsSection()]).then(v => colorFollowed(), e => {console.log(e); console.trace();}); //TODO
}
//------------------------------------Bookmarks----------------------------------
if (PAGETYPE===7)
{
bookmarksInit();
}
} //reInitMutationObservers()
//-------------------------------------------------------------------------------
reInitMutationObservers();
//=================================================================================
//***************************************HOVER*************************************
//=================================================================================
//------------------------------------Profile card--------------------------------- //0,1,4,6,9 [2,7,8,10,12]
function initProfileCard()
{
if ([0,1,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();
//-------------------------NEW ILLUSTRATIONS, DISCOVERY[ARTWORKS] ----------------- //0,1
function setPreviewEventListeners()
{
//single art hover---------------------------------------------------------------
$('body').on(previewEventType, 'a[href*="/artworks/"] > div:only-child', function(e)
{
e.preventDefault();
bookmarkObj = $(this).parent().parent().children(".thumbnail-menu").children("._one-click-bookmark");
checkBookmark(this);
setHover(this);
});
//manga-style arts hover---------------------------------------------------------
$('body').on(previewEventType, 'a[href*="/artworks/"] > div:nth-child(2)', function(e)
{
e.preventDefault();
if (this.parentNode.firstChild.childNodes.length)
{
bookmarkObj = $(this).parent().parent().children(".thumbnail-menu").children("._one-click-bookmark");
checkBookmark(this);
setMangaHover(this, this.parentNode.firstChild.firstChild.textContent);
}
});
}
//-----------------------------------DISCOVERY[USERS]------------------------------ //13
function setDiscoveryUsersPreviewEventListeners()
{
$('body').on(previewEventType, 'a[href*="/artworks/"]', 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
});
}
//--------------------------------------------------------------------------------- //1->13
function setTabSwitchingListenerW_U() //no need anymore? - excessive for now
{
$('body').off('mouseup','a[href="/discovery"]');
$('body').on('mouseup','a[href="/discovery/users"]', function()
{
console.log('Works -> Users');
PAGETYPE = 13;
artsLoaded = totalHits = 0; //clearing loaded arts count when switching on tabs
$('body').off(previewEventType, 'a[href*="/artworks/"]');
$('body').off(previewEventType, 'a[href*="/artworks/"] > div:only-child');
$('body').off(previewEventType, 'a[href*="/artworks/"] > div:nth-child(2)');
setDiscoveryUsersPreviewEventListeners();
setTabSwitchingListenerU_W();
});
}
//--------------------------------------------------------------------------------- //13->1
function setTabSwitchingListenerU_W() //no need anymore? - excessive for now
{
$('body').off('mouseup','a[href="/discovery/users"]');
$('body').on('mouseup','a[href="/discovery"]', function()
{
console.log('Users -> Works');
PAGETYPE = 1;
artsLoaded = totalHits = 0; //not necessary
checkFollowedArtists();
initMutationObject({'childList': true});
$('body').off(previewEventType, 'a[href*="/artworks/"]');
$('body').off(previewEventType, 'a[href*="/artworks/"] > div:only-child');
$('body').off(previewEventType, 'a[href*="/artworks/"] > div:nth-child(2)');
setPreviewEventListeners();
setTabSwitchingListenerW_U();
});
}
//=================================================================================
//*******************************Initialize Preview Listeners**********************
//=================================================================================
function initPreviewListeners()
{
//--------------------------------DISCOVERY[Artworks]---------------------------- //1
if (PAGETYPE === 1) //Works
{
setTabSwitchingListenerW_U(); //pixiv has deleted async loading from this pages?
setPreviewEventListeners();
}
//----------------------------------DISCOVERY[Users]----------------------------- //13
else if (PAGETYPE === 13) //Users
{
setTabSwitchingListenerU_W(); //pixiv has deleted async loading from this pages?
setDiscoveryUsersPreviewEventListeners();
}
//----------------------------------NEW ILLUSTRATIONS---------------------------- //0
else if (PAGETYPE === 0)
{
setPreviewEventListeners();
}
//---------ARTIST WORKS, "TOP" PAGES, Home page, Search, Bookmarks--------------- //2,7,8,10,12
else if (PAGETYPE === 2 || PAGETYPE === 7 || PAGETYPE === 12 || PAGETYPE === 8 || PAGETYPE === 10)
{
$('body').on(previewEventType, 'a[href*="/artworks/"] img', function(e)
{
e.preventDefault();
//filtering preview card-----------------------------------------------------
if (getElementByXpath("//a[text()='View Profile']")){
//multiple
if (this.parentNode.parentNode.querySelector('span'))
setMangaHover(this, this.parentNode.parentNode.textContent);
else
setHover(this, getOffsetRect(this).top+112+5+'px', true);
}
else{
//manga-style arts hover---------------------------------------------------
if (this.parentNode.parentNode.querySelector('span')){
setMangaHover(this, this.parentNode.parentNode.textContent.replace(/R-18(G)?/,""));
}
//single art hover---------------------------------------------------------
else{
setHover(this);
}
bookmarkObj = searchNearestNode(this, 'button');
}
});
}
//----------------------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();
if (this.childNodes.length == 1 && this.childNodes[0].nodeName=="DIV") //single art
{
bookmarkObj = $(this.firstChild.firstChild).parent().children("._one-click-bookmark");
checkBookmark(this);
setHover(this.firstChild.firstChild);
}
else if (this.children[1] && this.children[1].className == 'page-count') //manga
{
bookmarkObj = $(this.firstChild.firstChild).parent().children("._one-click-bookmark");
checkBookmark(this);
setMangaHover(this.firstChild.firstChild, this.children[1].children[1].textContent);
}
});
}
//-------------------------------------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);
});
//---------------------------------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');
if ([8,10].includes(PAGETYPE)) colorFollowed(); //extra coloring for missing arts
reInitMutationObservers();
initPreviewListeners();
initMenu();
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, PREVIEWSIZE);
imgContainer.style.top = top || getOffsetRect(thisObj.parentNode.parentNode).top+'px';
//adjusting preview position considering expected image width
//---------------------------------------------------------------------------------
let l = (![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 ([0,1,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);
}
}
}
//---------------------------------------------------------------------------------
if (isBookmarked) $(imgContainer).css("background", "rgb(255, 64, 96)");
else $(imgContainer).css("background", "rgb(34, 34, 34)");
}
//-----------------------------------------------------------------------------------
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)
{
clearTimeout(timerId);
clearInterval(tInt);
imgContainer.style.visibility = 'hidden'; //just in case
mangaOuterContainer.style.top = getOffsetRect(thisObj.parentNode.parentNode).top+'px';
if (!currentSettings["ACCURATE_MANGA_PREVIEW"]) mangaOuterContainer.style.left = '30px';
if (isBookmarked) $(mangaOuterContainer).css("background", "rgb(255, 64, 96)");
else $(mangaOuterContainer).css("background", "rgb(34, 34, 34)");
imgsArrInit(parseImgUrl(thisObj, PREVIEWSIZE), +count, thisObj);
}
//-----------------------------------------------------------------------------------
function imgsArrInit(primaryLink, count, 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, PREVIEWSIZE)
{
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-10+'px';
else mangaOuterContainer.style.left = '30px';
}
//-----------------------------------------------------------------------------------
function checkBookmark(thisObj) //let this be until it works
{
isBookmarked = ($(bookmarkObj).hasClass("on"));
}
//-----------------------------------------------------------------------------------
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 //todo
// }
// 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.substring(src.indexOf("_")+2, src.lastIndexOf("_"));
}
getOriginalUrl(ajaxIllustUrl, pageNum, toSave);
}
//-----------------------------Alt + LMB-click-----------------------------------
else if (event.altKey) {
$(bookmarkObj).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 - 80;
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
//Global TODO: hiding already viewed arts on Daily Rankings when moving to older dates
// arts viewed history extending for 10000 entries(as in premium) with no time limit