Added link manager with update
This commit is contained in:
parent
a6e97767fe
commit
65bf9c77ba
613
link-manager.js
Normal file
613
link-manager.js
Normal file
@ -0,0 +1,613 @@
|
||||
// ==UserScript==
|
||||
// @name Check dead links and add multi links
|
||||
// @namespace http://tampermonkey.net/
|
||||
// @version v0.6.9
|
||||
// @description Check dead links and updates their status right next to links and adds file link to multi.
|
||||
// @author Gameil
|
||||
// @match https://f95zone.to/threads/*/
|
||||
// @icon https://www.google.com/s2/favicons?sz=64&domain=f95zone.to
|
||||
// @grant GM_xmlhttpRequest
|
||||
// ==/UserScript==
|
||||
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
let email = localStorage.getItem('mixdrop-email');
|
||||
let key = localStorage.getItem('mixdrop-key');
|
||||
|
||||
if (!email) {
|
||||
email = prompt('Please enter your Mixdrop email:');
|
||||
localStorage.setItem('mixdrop-email', email);
|
||||
}
|
||||
|
||||
if (!key) {
|
||||
key = prompt('Please enter your Mixdrop key:');
|
||||
localStorage.setItem('mixdrop-key', key);
|
||||
}
|
||||
|
||||
//API Keys for some hosts
|
||||
const apiMIXDROP = {
|
||||
email: email,
|
||||
key: key,
|
||||
};
|
||||
|
||||
// Approved files hosts. Add/Remove or Rename
|
||||
const ApprovedFileHost = [
|
||||
'ANONYMFILE',
|
||||
'BUNKRR', 'BUNKR',
|
||||
'CATBOX',
|
||||
'DELAFIL',
|
||||
'DOWNLOAD.GG', 'DOWNLOADGG',
|
||||
'DROPMEFILES',
|
||||
'FASTUPLOAD',
|
||||
'FILESADMIN',
|
||||
'FILEMAIL',
|
||||
'FILESDP',
|
||||
'FILESFM',
|
||||
'GOFILE',
|
||||
'GDRIVE',
|
||||
'HEXUPLOAD',
|
||||
'KRAKENFILES',
|
||||
'MEDIAFIRE',
|
||||
'MEGA',
|
||||
'MIXDROP',
|
||||
'OSHI',
|
||||
'PIXELDRAIN',
|
||||
'PROTON',
|
||||
'SENDGB',
|
||||
'TERMINAL',
|
||||
'TRANSFER.SH',
|
||||
'TRANSFERT', 'FREEFR',
|
||||
'UP2SHARE',
|
||||
'UPLOADHAVEN',
|
||||
'UPLOADNOW',
|
||||
'WDHO',
|
||||
'WETRANSFER',
|
||||
'WORKUPLOAD',
|
||||
'YOURFILESTORE',
|
||||
|
||||
//Below are unsupported or dead hosts
|
||||
'ANONFILE',
|
||||
'NOPY',
|
||||
'RACATY',
|
||||
'ZIPPYSHARE',
|
||||
];
|
||||
|
||||
const OP = document.querySelector('.bbWrapper'); // Grab OP.
|
||||
var button1Active = true,
|
||||
button2Active = true; // to prevent multiple clicks
|
||||
|
||||
(function addButton() {
|
||||
const actionBar = document.querySelector('.actionBar-set--internal');
|
||||
if (!actionBar) {
|
||||
setTimeout(addButton, 500);
|
||||
return;
|
||||
}
|
||||
|
||||
// Creating check link button
|
||||
const checkDeadLinkButton = document.createElement('a');
|
||||
checkDeadLinkButton.innerHTML = `<i class='fad fa-check'></i> <span>Check Links</span>`;
|
||||
checkDeadLinkButton.classList.add('actionBar-action');
|
||||
checkDeadLinkButton.style.cursor = 'pointer';
|
||||
checkDeadLinkButton.addEventListener('click', checkLinks);
|
||||
actionBar.appendChild(checkDeadLinkButton);
|
||||
|
||||
// Creating generate links for multi button
|
||||
const generateMultiButton = document.createElement('a');
|
||||
generateMultiButton.innerHTML = `<i class='fad fa-link'></i> <span>Generate Links for Multi</span>`;
|
||||
generateMultiButton.classList.add('actionBar-action');
|
||||
generateMultiButton.style.cursor = 'pointer';
|
||||
generateMultiButton.style.marginLeft = '10px';
|
||||
generateMultiButton.addEventListener('click', generateMultiLinks);
|
||||
actionBar.appendChild(generateMultiButton);
|
||||
})();
|
||||
|
||||
function generateMultiLinks(event) {
|
||||
// Disable button for 5 second to prevent missclick
|
||||
if (!button2Active) return;
|
||||
|
||||
button2Active = false;
|
||||
event.target.parentElement.style.color = 'grey';
|
||||
const workingIcon = addStatus(event.target.parentElement.parentElement);
|
||||
workingIcon.style.color = '#ec5555';
|
||||
|
||||
const pageURL = document.URL;
|
||||
const match = pageURL.match(/\.(\d+)(\/|$)/);
|
||||
if (match && match[1]) {
|
||||
makeGMRequest({
|
||||
method: 'GET',
|
||||
url: 'https://f95zone.to/sam/donor_ddl.php?id=' + match[1],
|
||||
timeout: 20000,
|
||||
})
|
||||
.then((response) => {
|
||||
if (response.status === 200) {
|
||||
parseDLLPage(response.responseText);
|
||||
} else {
|
||||
console.log('Unable to parse DonorDLL');
|
||||
updateStatus(workingIcon, 'Error');
|
||||
setTimeout(() => {
|
||||
workingIcon.remove();
|
||||
}, 4000);
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
updateStatus(workingIcon, error);
|
||||
setTimeout(() => {
|
||||
workingIcon.remove();
|
||||
}, 4000);
|
||||
});
|
||||
}
|
||||
setTimeout(() => {
|
||||
button2Active = true;
|
||||
event.target.parentElement.style.color = 'unset';
|
||||
}, 5000);
|
||||
|
||||
function parseDLLPage(page) {
|
||||
if (!page) return;
|
||||
// Parse the response page and grab file links
|
||||
const parsedPage = new DOMParser().parseFromString(
|
||||
page,
|
||||
'text/html'
|
||||
);
|
||||
const links = parsedPage.querySelectorAll('a.file-select');
|
||||
const textNodes = getAllTextNodes(OP.lastChild);
|
||||
|
||||
links.forEach((link) => {
|
||||
let linkText;
|
||||
if (
|
||||
!link.previousElementSibling &&
|
||||
link.parentElement.previousElementSibling
|
||||
) {
|
||||
linkText =
|
||||
link.parentElement.previousElementSibling.textContent.trim();
|
||||
} else {
|
||||
linkText = link.previousElementSibling.textContent.trim();
|
||||
}
|
||||
for (let i = 0; i < textNodes.length; i++) {
|
||||
if (
|
||||
textNodes[i].previousElementSibling &&
|
||||
textNodes[i].previousElementSibling.title ==
|
||||
'Multi Link'
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
const elementText = textNodes[i].textContent.trim();
|
||||
if (
|
||||
linkText === elementText ||
|
||||
linkText + ':' === elementText
|
||||
) {
|
||||
addMultiLink(textNodes[i], link.dataset.filename);
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (links.length == 0) {
|
||||
updateStatus(workingIcon, 'Not Supported');
|
||||
} else {
|
||||
updateStatus(workingIcon, 'Available');
|
||||
}
|
||||
setTimeout(() => {
|
||||
workingIcon.remove();
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
// Create a go to multi button and add it behind link's main label
|
||||
function addMultiLink(element, filename) {
|
||||
const gotoMultiButton = document.createElement('a');
|
||||
gotoMultiButton.innerHTML = `<i class='fad fa-external-link' style='margin: 5px 6px auto auto; scale: 1.4'></i>`;
|
||||
gotoMultiButton.target = '_blank';
|
||||
gotoMultiButton.title = 'Multi Link';
|
||||
gotoMultiButton.href =
|
||||
'https://upload.multizone.pw/#search=' + filename;
|
||||
element.parentElement.insertBefore(gotoMultiButton, element);
|
||||
}
|
||||
|
||||
// Grab all text nodes from download section of OP. Fuck html parsing.
|
||||
function getAllTextNodes(container) {
|
||||
let allNodes = [];
|
||||
|
||||
function traverse(node) {
|
||||
if (
|
||||
node.nodeName == '#text' &&
|
||||
node.parentNode.tagName !== 'A' &&
|
||||
!['-', ':', ''].includes(node.textContent.trim())
|
||||
) {
|
||||
allNodes.push(node);
|
||||
}
|
||||
for (let i = 0; i < node.childNodes.length; i++) {
|
||||
let childNode = node.childNodes[i];
|
||||
traverse(childNode);
|
||||
}
|
||||
}
|
||||
traverse(container);
|
||||
return allNodes;
|
||||
}
|
||||
}
|
||||
|
||||
// Check each download link
|
||||
function checkLinks(event) {
|
||||
if (!button1Active) return;
|
||||
|
||||
button1Active = false;
|
||||
event.target.style.color = 'grey';
|
||||
// Selecting all links inside the OP
|
||||
const downloadLinks = OP.lastChild.querySelectorAll('a.link');
|
||||
downloadLinks.forEach((linkElement, index) => {
|
||||
const domainName = linkElement.textContent.trim();
|
||||
if (!isFileHost(domainName)) return;
|
||||
const statusIcon = addStatus(linkElement);
|
||||
|
||||
setTimeout(() => {
|
||||
const requestLink = linkElement.href;
|
||||
switch (domainName) {
|
||||
case 'PIXELDRAIN':
|
||||
processPIXELDRAIN(requestLink).then((response) =>
|
||||
updateStatus(statusIcon, response)
|
||||
);
|
||||
break;
|
||||
case 'MEGA':
|
||||
processMEGA(requestLink).then((response) =>
|
||||
updateStatus(statusIcon, response)
|
||||
);
|
||||
break;
|
||||
case 'GOFILE':
|
||||
processGOFILE(requestLink).then((response) =>
|
||||
updateStatus(statusIcon, response)
|
||||
);
|
||||
break;
|
||||
case 'FILESFM':
|
||||
processFILESFM(requestLink).then((response) =>
|
||||
updateStatus(statusIcon, response)
|
||||
);
|
||||
break;
|
||||
case 'MEDIAFIRE':
|
||||
processMEDIAFIRE(requestLink).then((response) =>
|
||||
updateStatus(statusIcon, response)
|
||||
);
|
||||
break;
|
||||
case 'WORKUPLOAD':
|
||||
processWORKUPLOAD(requestLink).then((response) =>
|
||||
updateStatus(statusIcon, response)
|
||||
);
|
||||
break;
|
||||
case 'DELAFIL':
|
||||
processDELAFIL(requestLink).then((response) =>
|
||||
updateStatus(statusIcon, response)
|
||||
);
|
||||
break;
|
||||
case 'MIXDROP':
|
||||
processMIXDROP(requestLink).then((response) =>
|
||||
updateStatus(statusIcon, response)
|
||||
);
|
||||
break;
|
||||
case 'WETRANSFER':
|
||||
processWETRANSFER(requestLink).then((response) =>
|
||||
updateStatus(statusIcon, response)
|
||||
);
|
||||
break;
|
||||
case 'UPLOADHAVEN':
|
||||
setTimeout(() => {
|
||||
processUPLOADHAVEN(requestLink).then((response) =>
|
||||
updateStatus(statusIcon, response)
|
||||
);
|
||||
}, index * 100);
|
||||
break;
|
||||
case 'ANONFILE':
|
||||
case 'NOPY':
|
||||
case 'ZIPPYSHARE':
|
||||
case 'RACATY':
|
||||
updateStatus(statusIcon, 'Not Supported');
|
||||
break;
|
||||
default:
|
||||
makeGMRequest({
|
||||
url: requestLink,
|
||||
})
|
||||
.then((response) => {
|
||||
response.status === 200
|
||||
? updateStatus(statusIcon, 'Available')
|
||||
: updateStatus(statusIcon, 'Not Available');
|
||||
})
|
||||
.catch((error) => updateStatus(statusIcon, error));
|
||||
}
|
||||
}, index * 50);
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
button1Active = true;
|
||||
event.target.style.color = 'unset';
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
// adds status icons to be displayed next to links
|
||||
function addStatus(link) {
|
||||
const statusElement = document.createElement('i');
|
||||
statusElement.style =
|
||||
'padding-left: 7px; padding-right: 5px; scale:1.3;';
|
||||
statusElement.style.color = 'grey';
|
||||
statusElement.style.cursor = 'default';
|
||||
statusElement.classList = 'fad fa-spinner fa-pulse';
|
||||
link.append(statusElement);
|
||||
return statusElement;
|
||||
}
|
||||
|
||||
// Update the status icon
|
||||
function updateStatus(icon, status) {
|
||||
switch (status) {
|
||||
case 'Available':
|
||||
setIconProperties(icon, 'fad fa-check', 'green', 'Live');
|
||||
break;
|
||||
case 'Not Available':
|
||||
setIconProperties(icon, 'fad fa-unlink', 'red', 'Dead Link');
|
||||
break;
|
||||
case 'Error':
|
||||
setIconProperties(
|
||||
icon,
|
||||
'fal fa-exclamation-triangle',
|
||||
'orangered',
|
||||
'Error',
|
||||
1.2
|
||||
);
|
||||
break;
|
||||
case 'Timeout':
|
||||
setIconProperties(icon, 'fad fa-clock', 'orange', 'Timeout');
|
||||
break;
|
||||
case 'Not Supported':
|
||||
default:
|
||||
setIconProperties(
|
||||
icon,
|
||||
'fad fa-question',
|
||||
'grey',
|
||||
'Unsupported'
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
function setIconProperties(icon, iconClass, color, title, scale = 1.3) {
|
||||
icon.classList = iconClass;
|
||||
icon.style.color = color;
|
||||
icon.style.scale = scale;
|
||||
icon.title = title;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the link is a file host
|
||||
function isFileHost(link) {
|
||||
if (ApprovedFileHost.includes(link)) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Make a GM_xmlRequest
|
||||
function makeGMRequest(options) {
|
||||
return new Promise((resolve, reject) => {
|
||||
GM_xmlhttpRequest({
|
||||
method: 'HEAD',
|
||||
timeout: 15000,
|
||||
...options,
|
||||
onload: (res) => resolve(res),
|
||||
ontimeout: () => reject('Timeout'),
|
||||
onerror: (error) => {
|
||||
console.log('Error:', error);
|
||||
reject('Error');
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Process host links idividually that need more work
|
||||
function processPIXELDRAIN(link) {
|
||||
// Cature the file ID for both file and folder links.
|
||||
const matchU = link.match(/\/u\/([^\/]+)$/);
|
||||
const matchL = link.match(/\/l\/([^\/]+)$/);
|
||||
var newLink;
|
||||
if (matchU && matchU[1]) {
|
||||
newLink = 'https://pixeldrain.com/api/file/' + matchU[1];
|
||||
} else if (matchL && matchL[1]) {
|
||||
newLink = 'https://pixeldrain.com/api/list/' + matchL[1];
|
||||
} else {
|
||||
return new Promise.reject('Error');
|
||||
}
|
||||
const options = {
|
||||
url: newLink,
|
||||
};
|
||||
|
||||
return makeGMRequest(options)
|
||||
.then((res) => (res.status === 200 ? 'Available' : 'Not Available'))
|
||||
.catch((error) => error);
|
||||
}
|
||||
|
||||
function processMEGA(link) {
|
||||
// MEGA typically has 3 types of url, capture ID from each case.
|
||||
const captureId = link.match(/(?:file\/|#\!|folder\/)([^\/#!]+)/);
|
||||
|
||||
const id = captureId ? captureId[1] : null;
|
||||
if (!id) return 'Error';
|
||||
|
||||
const options = {
|
||||
method: 'POST',
|
||||
url: 'https://g.api.mega.co.nz/cs?id=0&n=' + id,
|
||||
data: 'a=g&ad=1&p=' + id,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
};
|
||||
|
||||
return makeGMRequest(options)
|
||||
.then((resolve) =>
|
||||
resolve.responseText === '-2' ? 'Available' : 'Not Available'
|
||||
)
|
||||
.catch((error) => error);
|
||||
}
|
||||
|
||||
function processGOFILE(link) {
|
||||
const options = {
|
||||
method: 'GET',
|
||||
url: link,
|
||||
headers: {
|
||||
'Content-Type': 'text/html',
|
||||
'User-Agent': 'curl',
|
||||
},
|
||||
};
|
||||
|
||||
return makeGMRequest(options)
|
||||
.then((resolve) => {
|
||||
const parsedPage = new DOMParser().parseFromString(
|
||||
resolve.responseText,
|
||||
'text/html'
|
||||
);
|
||||
return parsedPage.title ==
|
||||
'Gofile - Free Unlimited File Sharing and Storage'
|
||||
? 'Not Available'
|
||||
: 'Available';
|
||||
})
|
||||
.catch((error) => error);
|
||||
}
|
||||
|
||||
function processFILESFM(link) {
|
||||
const options = {
|
||||
method: 'GET',
|
||||
url: link,
|
||||
headers: {
|
||||
'Content-Type': 'text/html',
|
||||
'User-Agent': 'curl',
|
||||
},
|
||||
};
|
||||
return makeGMRequest(options)
|
||||
.then((resolve) => {
|
||||
const parsedPage = new DOMParser().parseFromString(
|
||||
resolve.responseText,
|
||||
'text/html'
|
||||
);
|
||||
return parsedPage.title ==
|
||||
'File upload & sharing. Send large photos and videos. Online cloud storage.'
|
||||
? 'Not Available'
|
||||
: 'Available';
|
||||
})
|
||||
.catch((error) => error);
|
||||
}
|
||||
|
||||
function processMEDIAFIRE(link) {
|
||||
return makeGMRequest({
|
||||
url: link,
|
||||
})
|
||||
.then((response) => {
|
||||
if (response.status === 404) {
|
||||
return 'Not Available';
|
||||
} else if (response.status === 200) {
|
||||
return response.finalUrl.match('error.php')
|
||||
? 'Not Available'
|
||||
: 'Available';
|
||||
}
|
||||
})
|
||||
.catch((error) => error);
|
||||
}
|
||||
|
||||
function processWORKUPLOAD(link) {
|
||||
const id = link.match(/\/file\/([^\/#]+)/);
|
||||
var apiLink;
|
||||
if (id && id[1])
|
||||
apiLink =
|
||||
'https://workupload.com/api/file/getDownloadServer/' + id[1];
|
||||
else return 'Error';
|
||||
const options = {
|
||||
method: 'GET',
|
||||
url: apiLink,
|
||||
headers: {
|
||||
Accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*',
|
||||
Cookie: 'token=tjuqjuuobv97ma180qn6vg92lu',
|
||||
'User-Agent': 'curl',
|
||||
},
|
||||
};
|
||||
|
||||
return makeGMRequest(options)
|
||||
.then((resolve) => {
|
||||
try {
|
||||
return JSON.parse(resolve.response).success
|
||||
? 'Available'
|
||||
: 'Not Available';
|
||||
} catch (error) {
|
||||
const parsedPage = new DOMParser().parseFromString(
|
||||
resolve.responseText,
|
||||
'text/html'
|
||||
);
|
||||
return parsedPage.title == 'workupload - Are you a human?'
|
||||
? 'Error'
|
||||
: 'Available';
|
||||
}
|
||||
})
|
||||
.catch((error) => error);
|
||||
}
|
||||
|
||||
function processDELAFIL(link) {
|
||||
return makeGMRequest({
|
||||
url: link,
|
||||
})
|
||||
.then((response) => {
|
||||
if (response.status === 200) {
|
||||
return response.finalUrl.match('error.html')
|
||||
? 'Not Available'
|
||||
: 'Available';
|
||||
} else {
|
||||
return response.status === 404 ? 'Not Available' : 'Error';
|
||||
}
|
||||
})
|
||||
.catch((error) => error);
|
||||
}
|
||||
|
||||
function processMIXDROP(link) {
|
||||
const id = link.match(/\/f\/([^\/#]+)/);
|
||||
if (!id[1]) return Promise.resolve('Error');
|
||||
|
||||
const apiLink = `https://api.mixdrop.ag/fileinfo2?email=${apiMIXDROP.email}&key=${apiMIXDROP.key}&ref[]=${id[1]}`;
|
||||
|
||||
return makeGMRequest({
|
||||
method: 'GET',
|
||||
url: apiLink,
|
||||
})
|
||||
.then((response) => {
|
||||
try {
|
||||
return JSON.parse(response.response).result[id[1]].status ==
|
||||
'OK'
|
||||
? 'Available'
|
||||
: 'Not Available';
|
||||
} catch (error) {
|
||||
return 'Error';
|
||||
}
|
||||
})
|
||||
.catch((error) => error);
|
||||
}
|
||||
|
||||
function processUPLOADHAVEN(link) {
|
||||
return makeGMRequest({
|
||||
url: link,
|
||||
})
|
||||
.then((response) =>
|
||||
response.status === 200 ? 'Available' : 'Not Available'
|
||||
)
|
||||
.catch((error) => error);
|
||||
}
|
||||
|
||||
function processWETRANSFER(link) {
|
||||
return makeGMRequest({
|
||||
method: 'GET',
|
||||
url: link,
|
||||
})
|
||||
.then((response) => {
|
||||
try {
|
||||
const page = new DOMParser().parseFromString(
|
||||
response.responseText,
|
||||
'text/html'
|
||||
);
|
||||
const data = JSON.parse(
|
||||
page.querySelector('#__NEXT_DATA__').textContent
|
||||
);
|
||||
return data.props.pageProps.metadata === null
|
||||
? 'Not Available'
|
||||
: 'Available';
|
||||
} catch (error) {
|
||||
return 'Error';
|
||||
}
|
||||
})
|
||||
.catch((error) => error);
|
||||
}
|
||||
})();
|
||||
Loading…
x
Reference in New Issue
Block a user