// ==UserScript==
// @name YggTorrent Plus
// @match https://www.ygg.re/*
// @version 1.25.1
// @author J.H / clemente / Binouchette
// @license MIT
// @description Connexion automatique, boutons de téléchargement dans les résultats de recherche, prévisualisation des images de torrent, et bien d'autres améliorations.
// @run-at document-end
// @noframes
// @grant GM.getValue
// @grant GM.setValue
// @grant GM.deleteValue
// @grant GM_addStyle
// @grant GM_getResourceURL
// @grant GM_xmlhttpRequest
// @namespace https://greasyfork.org/fr/scripts/497739
// @icon https://www.google.com/s2/favicons?sz=64&domain=ygg.re
// ==/UserScript==
(async function() {
const CONFIG = {
HOVER_TABLE: true, // Change la couleur de fond du torrent au survol dans les résultats de recherche
LOGIN_AUTOMATICALLY: true, // Se connecte automatiquement (première connexion manuel obligatoire)
PREVIEWS_IMAGES_ON_HOVER: true, // Affiche une prévisualisation de l'image du torrent au survol
PREVIEWS_IMAGES_SIZE: 400, // Taille des images de prévisualisation (en pixels)
DOWNLOAD_BUTTONS_IN_RESULTS: true, // Ajoute un bouton de téléchargement dans les résultats de recherche
HIDE_SIDEBAR: false, // Masque la barre latérale
LARGER_NFO: true, // Agrandit la fenêtre de prévisualisation du NFO
SEARCH_BY_LATEST_FIRST: true, // Affiche les résultats de recherche par date de publication (du plus récent au plus ancien)
KEEP_SEARCH_WHEN_CLICKING_ON_SUBCATEGORY_ICON: true, // Garde les critères de recherche lorsqu'on clique sur une catégorie
};
const SELECTORS = {
HOVER_TABLE_LIGNE: '.table-responsive',
REGISTER_BUTTON: '.register',
TORRENT_NAME_LINK: 'a[id="torrent_name"]',
RESULTS_TABLE_ROW: '.results table tbody tr',
INPUT_USERNAME: 'input[name="id"]',
INPUT_PASSWORD: 'input[name="pass"]',
INPUT_SUBMIT: 'button[type="submit"]',
LOGOUT_LINK: 'a[href="https://www.ygg.re/user/logout"]',
NFO_MODAL: 'nfoModal',
SEARCH_FORM: 'form.search',
LOGIN_FORM: '.login-form',
};
const CONSTANTS = {
IMAGE_MODAL_STYLE: `min-width: ${CONFIG.PREVIEWS_IMAGES_SIZE}px; max-width: ${CONFIG.PREVIEWS_IMAGES_SIZE}px;`,
COOKIES_STORAGE_KEY: 'yggtorrent_credentials',
MOUSEENTER_DELAY: 100,
};
CONFIG.HOVER_TABLE && hoverTable();
CONFIG.DOWNLOAD_BUTTONS_IN_RESULTS && addDownloadButtonToTorrents();
CONFIG.HIDE_SIDEBAR && hideSidebar();
CONFIG.LARGER_NFO && displayLargerNfo();
CONFIG.SEARCH_BY_LATEST_FIRST && searchByLatestFirst();
CONFIG.KEEP_SEARCH_WHEN_CLICKING_ON_SUBCATEGORY_ICON && keepSearchWhenClickingOnSubcategoryIcon();
CONFIG.PREVIEWS_IMAGES_ON_HOVER && displayImageHandler();
CONFIG.LOGIN_AUTOMATICALLY && await handleLogin();
async function hoverTable() {
const style = document.createElement('style');
const lignes = document.querySelector(SELECTORS.HOVER_TABLE_LIGNE);
if(lignes) {
style.textContent = `
.table td {
padding: .35rem !important;
}
.table td:hover {
background-color: rgba(246, 246, 246, 0.5) !important;
}
.table tr:hover {
background-color: rgba(246, 246, 246, 0.5) !important;
opacity: .8 !important;
}
`;
document.head.appendChild(style);
}
}
async function handleLogin() {
const isNotLoggedIn = document.querySelector(SELECTORS.REGISTER_BUTTON);
const savedCredentials = await GM.getValue(CONSTANTS.COOKIES_STORAGE_KEY);
if (isNotLoggedIn && !savedCredentials) {
await getCredentials();
} else if (isNotLoggedIn && savedCredentials) {
await autoLogin(savedCredentials);
}
}
async function getCredentials() {
try {
const loginForm = document.querySelector(SELECTORS.LOGIN_FORM);
if (loginForm) {
loginForm.addEventListener('submit', async (e) => {
e.preventDefault();
const usernameInput = loginForm.querySelector(SELECTORS.INPUT_USERNAME);
const passwordInput = loginForm.querySelector(SELECTORS.INPUT_PASSWORD);
await saveCredentials(usernameInput.value, passwordInput.value);
});
}
} catch (error) {
console.error('Error getting credentials:', error);
}
}
async function autoLogin(credentials) {
try {
const loginForm = document.querySelector(SELECTORS.LOGIN_FORM);
if (loginForm) {
const usernameInput = loginForm.querySelector(SELECTORS.INPUT_USERNAME);
const passwordInput = loginForm.querySelector(SELECTORS.INPUT_PASSWORD);
const submitButton = loginForm.querySelector(SELECTORS.INPUT_SUBMIT);
if (usernameInput && passwordInput && submitButton) {
usernameInput.value = credentials.username;
passwordInput.value = credentials.password;
submitButton.click();
}
}
} catch (error) {
console.error('Error during login:', error);
}
}
const logoutLink = document.querySelector(SELECTORS.LOGOUT_LINK);
if (logoutLink) {
logoutLink.addEventListener('click', deleteCredentials);
}
async function deleteCredentials() {
try {
await GM.deleteValue(CONSTANTS.COOKIES_STORAGE_KEY);
} catch (error) {
console.error('Error deleting credentials:', error);
}
}
async function saveCredentials(username, password) {
try {
await GM.setValue(CONSTANTS.COOKIES_STORAGE_KEY, {
username,
password
});
} catch (error) {
console.error('Error saving credentials:', error);
}
}
function fetchImageSize(url) {
return new Promise((resolve, reject) => {
const img = new Image();
img.onload = () => resolve({
width: img.width,
height: img.height,
url
});
img.onerror = reject;
img.src = url;
});
}
function makeGetRequest(url) {
return new Promise((resolve, reject) => {
GM_xmlhttpRequest({
method: 'GET',
url,
onload: function(response) {
resolve(response.responseText);
},
onerror: function(error) {
reject(error);
},
});
});
}
async function getImages(url) {
const imageRet = [];
try {
const response = await makeGetRequest(url);
const parser = new DOMParser();
const doc = parser.parseFromString(response, 'text/html');
if (doc.title.includes('Just a moment...')) {
imageRet.push({
width: 250,
height: 250,
url: 'https://i.ibb.co/DQb8WVj/cflr.jpg',
});
return imageRet;
}
const imgElements = doc.getElementsByTagName('img');
for (const imgElement of imgElements) {
if (imgElement.src.includes('summer_icon') || imgElement.src.includes('yggtorrent') || imgElement.src.includes('avatars') || imgElement.src.startsWith('data:image/png;base64')) {
continue;
}
const image = await fetchImageSize(imgElement.src);
if (image.width > 250 && image.height > 250) {
imageRet.push(image);
break;
}
}
} catch (error) {
console.error('Error fetching images:', error);
}
return imageRet;
}
function displayImageHandler() {
createImageModal();
let timeout = null;
let canDisplay = false;
const torrents = document.querySelectorAll(SELECTORS.RESULTS_TABLE_ROW);
if (!torrents) return;
torrents.forEach((torrent) => {
const torrentNameLink = torrent.querySelector(SELECTORS.TORRENT_NAME_LINK);
if (!torrentNameLink) return;
torrentNameLink.addEventListener('mouseenter', async (e) => {
timeout = setTimeout(async () => {
if (e.target.id !== 'torrent_name') return;
canDisplay = true;
const images = await getImages(e.target.href);
if (canDisplay) {
displayImageModal(e, images[0]);
}
}, CONSTANTS.MOUSEENTER_DELAY);
});
torrentNameLink.addEventListener('mousemove', (e) => {
const modal = document.getElementById('imageModal');
if (modal) {
const mouseY = e.clientY - 25;
const outOfScreen =
mouseY + document.getElementById('imageModalImage').offsetHeight - window.innerHeight;
modal.style.top =
outOfScreen > -25 ? mouseY - outOfScreen - 25 + 'px' : mouseY + 'px';
modal.style.left = e.clientX + 125 + 'px';
}
});
torrentNameLink.addEventListener('mouseout', () => {
document.getElementById('imageModal').style.display = 'none';
canDisplay = false;
clearTimeout(timeout);
});
});
}
function createImageModal() {
const modal = document.createElement('div');
modal.id = 'imageModal';
modal.classList.add('modal');
modal.style.display = 'none';
const modalImage = document.createElement('img');
modalImage.id = 'imageModalImage';
modalImage.classList.add('modal-content');
modalImage.style = CONSTANTS.IMAGE_MODAL_STYLE;
modal.appendChild(modalImage);
document.body.append(modal);
}
function displayImageModal(event, image) {
const modal = document.getElementById('imageModal');
if (modal) {
const mouseY = event.clientY - 25;
const modalImage = document.getElementById('imageModalImage');
modalImage.src = image.url;
modal.style.left = event.clientX + 125 + 'px';
modal.style.display = 'block';
const outOfScreen =
mouseY + modalImage.offsetHeight - window.innerHeight;
modal.style.top =
outOfScreen > -25 ? mouseY - outOfScreen - 25 + 'px' : mouseY + 'px';
}
}
function addDownloadButtonToTorrents() {
const torrents = document.querySelectorAll(SELECTORS.RESULTS_TABLE_ROW);
torrents.forEach((torrent) => {
const torrentId = torrent.querySelector('a[target]')?.target;
if (!torrentId) return;
const downloadIcon = document.createElement('span');
downloadIcon.classList.add('ico_download');
downloadIcon.classList.add('custom_ygg');
const downloadButton = document.createElement('a');
downloadButton.href = `/engine/download_torrent?id=${torrentId}`;
downloadButton.append(downloadIcon);
downloadButton.style = 'color: rgb(98, 219, 168); vertical-align: middle; margin-right: 10px;';
const nameLink = torrent.querySelector('td:nth-child(3) a');
if (nameLink) {
nameLink.parentNode.insertBefore(downloadButton, nameLink);
}
});
}
function hideSidebar() {
const sidebar = document.getElementById('cat');
if (sidebar && sidebar.classList.contains('active')) {
sidebar.querySelector('.open').click();
}
}
function displayLargerNfo() {
const modal = document.getElementById(SELECTORS.NFO_MODAL);
if (!modal) return;
const modalDialog = modal.querySelector('.modal-dialog');
modalDialog.classList.remove('modal-sm');
modalDialog.classList.add('modal-lg');
const nfoDiv = document.querySelector('#nfo');
if (nfoDiv) {
GM_addStyle(`
@import url('https://fonts.googleapis.com/css2?family=Source+Code+Pro&display=swap&display=swap');
`);
GM_addStyle(`
#nfo {
padding: 0 !important;
}
#nfo pre {
font-family: 'Source Code Pro', monospace !important;
font-optical-sizing: auto !important;
font-weight: 500 !important;
font-style: normal !important;
background-color: #fff !important;
color: #4d4d4d !important;
margin: 0 !important;
padding: 2rem !important;
text-align: center !important;
}
`);
/*
nfoDiv.style.background = '#ffffff !important';
nfoDiv.style.color = '#4d4d4d !important';
nfoDiv.style.margin = 'auto !important';
*/
}
}
function searchByLatestFirst() {
const searchForm = document.querySelector(SELECTORS.SEARCH_FORM);
if (!searchForm) return;
const orderInput = document.createElement('input');
orderInput.name = 'order';
orderInput.value = 'desc';
orderInput.style = 'display: none';
const sortInput = document.createElement('input');
sortInput.name = 'sort';
sortInput.value = 'publish_date';
sortInput.style = 'display: none';
searchForm.append(orderInput);
searchForm.append(sortInput);
}
function keepSearchWhenClickingOnSubcategoryIcon() {
document.querySelectorAll('[class^="tag_subcat_"]').forEach((node) => {
const subcategoryId = node.className.split('tag_subcat_')[1];
node.parentNode.href = `${document.URL}&sub_category=${subcategoryId}`;
});
}
})();