// ==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 = ` Check Links`;
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 = ` Generate Links for Multi`;
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 = ``;
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);
}
})();