Add multiples shortcuts + custom ones
// ==UserScript== // @name AniList Shortcuts // @version 1.0 // @description Add multiples shortcuts + custom ones // @author Mio. // @namespace https://github.com/dear-clouds/mio-userscripts // @supportURL https://github.com/dear-clouds/mio-userscripts/issues // @icon https://www.google.com/s2/favicons?sz=64&domain=anilist.co // @license GPL-3.0 // @match *://*.anilist.co/* // @grant none // ==/UserScript== (function () { 'use strict'; // Function to inject Font Awesome CSS function injectFontAwesome() { const faLink = document.createElement('link'); faLink.rel = 'stylesheet'; faLink.href = 'https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css'; faLink.integrity = 'sha512-pap5K1fL5c4sLcXmpopbPWha8z36H1EJGgUK6YyE1Wfo2jydN12wPuABanVbBv8d5kZdO8+8PpJ1f8kz0gJ0Mg=='; faLink.crossOrigin = 'anonymous'; faLink.referrerPolicy = 'no-referrer'; document.head.appendChild(faLink); } injectFontAwesome(); // Utility function to wait for an element to appear in the DOM function waitForElement(selector, timeout = 10000) { return new Promise((resolve, reject) => { const intervalTime = 100; let timeElapsed = 0; const interval = setInterval(() => { const element = document.querySelector(selector); if (element) { clearInterval(interval); resolve(element); } timeElapsed += intervalTime; if (timeElapsed >= timeout) { clearInterval(interval); reject(`Element ${selector} not found within ${timeout}ms`); } }, intervalTime); }); } // Function to add a link with Font Awesome icon function addLinkWithIcon(element, url, linkText, iconName) { const link = document.createElement('a'); link.href = url; link.target = '_blank'; link.rel = 'noopener noreferrer'; link.style.textDecoration = 'none'; link.style.color = 'inherit'; link.style.display = 'flex'; link.style.alignItems = 'center'; link.style.marginLeft = '10px'; const icon = document.createElement('i'); icon.className = `fa fa-${iconName}`; icon.style.marginRight = '5px'; link.appendChild(icon); link.appendChild(document.createTextNode(linkText)); element.appendChild(link); } // Function to create a sticky box on forum comments function createStickyBoxLink() { // Prevent multiple sticky boxes if (document.querySelector('.sticky-box')) return; const stickyBox = document.createElement('div'); stickyBox.classList.add('sticky-box'); stickyBox.style.position = 'fixed'; stickyBox.style.right = '10px'; stickyBox.style.top = '200px'; stickyBox.style.width = '200px'; stickyBox.style.padding = '10px'; stickyBox.style.backgroundColor = 'rgb(var(--color-foreground))'; stickyBox.style.borderRadius = '4px'; stickyBox.style.transition = 'height 0.5s, opacity 0.5s'; stickyBox.style.overflow = 'hidden'; stickyBox.style.zIndex = '1000'; const header = document.createElement('h3'); header.innerText = 'Shortcuts'; header.style.fontSize = 'medium'; header.style.marginTop = '5'; stickyBox.appendChild(header); const linksContainer = document.createElement('div'); stickyBox.appendChild(linksContainer); const addIcon = document.createElement('span'); addIcon.innerText = '+'; addIcon.style.position = 'absolute'; addIcon.style.top = '5px'; addIcon.style.right = '5px'; addIcon.style.cursor = 'pointer'; addIcon.style.fontWeight = 'bold'; addIcon.onclick = function () { const isHidden = userInput.style.display === 'none'; userInput.style.display = isHidden ? 'block' : 'none'; shortcutNameInput.style.display = isHidden ? 'block' : 'none'; validateButton.style.display = isHidden ? 'block' : 'none'; }; stickyBox.appendChild(addIcon); const toggleVisibilityIcon = document.createElement('i'); toggleVisibilityIcon.className = 'fa fa-eye'; toggleVisibilityIcon.style.cursor = 'pointer'; toggleVisibilityIcon.style.position = 'absolute'; toggleVisibilityIcon.style.top = '5px'; toggleVisibilityIcon.style.left = '5px'; toggleVisibilityIcon.style.fontSize = '14px'; toggleVisibilityIcon.onclick = function () { if (stickyBox.style.height !== '25px') { stickyBox.style.width = '25px'; stickyBox.style.height = '25px'; linksContainer.style.display = 'none'; header.style.display = 'none'; addIcon.style.display = 'none'; toggleVisibilityIcon.className = 'fa fa-eye-slash'; } else { stickyBox.style.width = '200px'; stickyBox.style.height = 'auto'; linksContainer.style.display = 'block'; header.style.display = 'block'; addIcon.style.display = 'block'; toggleVisibilityIcon.className = 'fa fa-eye'; } }; stickyBox.appendChild(toggleVisibilityIcon); function appendLinkToContainer(linkName, linkURL) { const linkElement = document.createElement('a'); linkElement.href = linkURL; linkElement.innerText = linkName; linkElement.style.fontSize = 'smaller'; linkElement.target = '_blank'; linkElement.style.display = 'flex'; linkElement.style.alignItems = 'center'; linkElement.style.marginBottom = '5px'; linkElement.style.color = 'var(--color-blue)'; linkElement.style.textDecoration = 'none'; linkElement.addEventListener('click', (e) => { e.preventDefault(); window.open(linkURL, '_blank'); }); const favicon = document.createElement('img'); try { const urlObj = new URL(linkURL); favicon.src = `https://www.google.com/s2/favicons?domain=${urlObj.hostname}`; } catch { favicon.src = ''; } favicon.style.marginRight = '5px'; favicon.style.width = '16px'; favicon.style.height = '16px'; linkElement.prepend(favicon); const deleteIcon = document.createElement('span'); deleteIcon.innerText = ' ×'; deleteIcon.style.color = 'rgb(var(--color-blue))'; deleteIcon.style.cursor = 'pointer'; deleteIcon.style.marginLeft = 'auto'; deleteIcon.onclick = function (event) { event.stopPropagation(); linksContainer.removeChild(linkElement); const savedLinks = JSON.parse(localStorage.getItem('MioAniListShortcuts') || '[]'); const updatedLinks = savedLinks.filter(l => l.url !== linkURL); localStorage.setItem('MioAniListShortcuts', JSON.stringify(updatedLinks)); }; linkElement.appendChild(deleteIcon); linksContainer.appendChild(linkElement); } const userInput = document.createElement('input'); userInput.type = 'text'; userInput.placeholder = 'Enter your link'; userInput.style.display = 'none'; userInput.style.backgroundColor = 'rgb(var(--color-background))'; userInput.style.color = 'rgb(var(--color-blue))'; userInput.style.border = '1px solid var(--color-border)'; userInput.style.borderRadius = '3px'; userInput.style.padding = '5px'; userInput.style.fontSize = 'smaller'; userInput.style.marginTop = '5px'; stickyBox.appendChild(userInput); const shortcutNameInput = document.createElement('input'); shortcutNameInput.type = 'text'; shortcutNameInput.placeholder = 'Name of the shortcut'; shortcutNameInput.style.display = 'none'; shortcutNameInput.style.backgroundColor = 'rgb(var(--color-background))'; shortcutNameInput.style.color = 'rgb(var(--color-blue))'; shortcutNameInput.style.border = '1px solid var(--color-border)'; shortcutNameInput.style.borderRadius = '3px'; shortcutNameInput.style.padding = '5px'; shortcutNameInput.style.fontSize = 'smaller'; shortcutNameInput.style.marginTop = '5px'; stickyBox.appendChild(shortcutNameInput); const validateButton = document.createElement('button'); validateButton.innerText = 'Add'; validateButton.style.display = 'none'; validateButton.style.backgroundColor = 'var(--color-button)'; validateButton.style.color = 'var(--color-button-text)'; validateButton.style.border = 'none'; validateButton.style.borderRadius = '3px'; validateButton.style.padding = '5px 10px'; validateButton.style.fontSize = 'smaller'; validateButton.style.marginTop = '5px'; validateButton.style.cursor = 'pointer'; validateButton.onclick = function () { const link = userInput.value.trim(); const name = shortcutNameInput.value.trim(); if (link && name) { const savedLinks = JSON.parse(localStorage.getItem('MioAniListShortcuts') || '[]'); // Avoid duplicates if (!savedLinks.some(l => l.url === link)) { savedLinks.push({ name, url: link }); localStorage.setItem('MioAniListShortcuts', JSON.stringify(savedLinks)); appendLinkToContainer(name, link); userInput.value = ''; shortcutNameInput.value = ''; userInput.style.display = 'none'; shortcutNameInput.style.display = 'none'; validateButton.style.display = 'none'; } else { alert('This link already exists in your shortcuts.'); } } else { alert('Please enter both name and URL.'); } }; stickyBox.appendChild(validateButton); // Load saved links const savedLinks = JSON.parse(localStorage.getItem('MioAniListShortcuts') || '[]'); for (const linkObj of savedLinks) { appendLinkToContainer(linkObj.name, linkObj.url); } document.body.appendChild(stickyBox); } // Function to add AniCalendar by KangieDanie link in Activity History // https://anilist.co/forum/thread/63096 function addAniCalendarLink() { // Prevent adding multiple links if (document.querySelector('.ani-calendar-link')) return; let attempts = 0; const maxAttempts = 10; // 20 seconds max const interval = setInterval(() => { const headers = document.querySelectorAll('h2.section-header'); let activityHistoryHeader = null; headers.forEach(header => { if (header.textContent.trim() === 'Activity History') { activityHistoryHeader = header; } }); if (activityHistoryHeader) { // Prevent adding multiple links if (activityHistoryHeader.querySelector('.ani-calendar-link')) { clearInterval(interval); return; } const aniCalendarContainer = document.createElement('span'); aniCalendarContainer.classList.add('ani-calendar-link'); aniCalendarContainer.style.float = 'right'; aniCalendarContainer.style.display = 'flex'; aniCalendarContainer.style.alignItems = 'center'; const aniCalendarLink = document.createElement('a'); aniCalendarLink.href = 'https://ani-calendar.vercel.app/'; aniCalendarLink.target = '_blank'; aniCalendarLink.rel = 'noopener noreferrer'; aniCalendarLink.textContent = 'AniCalendar'; aniCalendarLink.style.fontSize = 'smaller'; aniCalendarLink.style.marginLeft = '10px'; aniCalendarLink.style.color = 'var(--color-blue)'; aniCalendarLink.style.display = 'flex'; aniCalendarLink.style.alignItems = 'center'; aniCalendarLink.style.textDecoration = 'none'; const calendarIcon = document.createElement('i'); calendarIcon.className = 'fa fa-calendar'; calendarIcon.style.marginRight = '5px'; aniCalendarLink.prepend(calendarIcon); aniCalendarContainer.appendChild(aniCalendarLink); activityHistoryHeader.appendChild(aniCalendarContainer); clearInterval(interval); console.log('AniCalendar link added to Activity History.'); } else { attempts++; console.log(`Activity History section header not found. Attempt ${attempts}/${maxAttempts}. Retrying in 2 seconds...`); if (attempts >= maxAttempts) { clearInterval(interval); console.warn('Failed to find Activity History section header after multiple attempts.'); } } }, 2000); } // Function to add AniTools link in Social tab function addAniToolsLink() { const socialFilterGroup = document.querySelector('div.filter-group'); if (socialFilterGroup) { // Prevent adding multiple links if (socialFilterGroup.querySelector('.ani-tools-link')) return; const aniToolsLink = document.createElement('a'); aniToolsLink.href = 'https://anitools.koopz.rocks/'; aniToolsLink.target = '_blank'; aniToolsLink.rel = 'noopener noreferrer'; aniToolsLink.textContent = 'AniTools'; aniToolsLink.style.color = 'var(--color-blue)'; aniToolsLink.style.display = 'flex'; aniToolsLink.style.alignItems = 'center'; aniToolsLink.style.textDecoration = 'none'; const wrenchIcon = document.createElement('i'); wrenchIcon.className = 'fa fa-tools'; wrenchIcon.style.marginRight = '5px'; aniToolsLink.prepend(wrenchIcon); const aniToolsContainer = document.createElement('span'); aniToolsContainer.classList.add('ani-tools-link'); aniToolsContainer.appendChild(aniToolsLink); socialFilterGroup.appendChild(aniToolsContainer); } else { console.log('Social filter group not found.'); } } // Function to initialize features based on current URL function initializeFeatures() { const url = window.location.href; // Check if the page is an AniList user profile if (url.includes('/user/') && !url.includes('/social')) { addAniCalendarLink(); } // Check if the page is the social tab of an AniList user profile if (url.includes('/user/') && url.includes('/social')) { addAniToolsLink(); } // Check if the page is an AniList forum thread comment if (url.includes('/forum/thread/') && url.includes('/comment/')) { createStickyBoxLink(); } else { const existingStickyBox = document.querySelector('.sticky-box'); if (existingStickyBox) { existingStickyBox.remove(); } } } // Function to handle URL changes function onUrlChange(callback) { let lastUrl = location.href; const observer = new MutationObserver(() => { const currentUrl = location.href; if (currentUrl !== lastUrl) { lastUrl = currentUrl; callback(); } }); observer.observe(document, { subtree: true, childList: true }); window.addEventListener('popstate', () => { callback(); }); const pushState = history.pushState; const replaceState = history.replaceState; history.pushState = function () { pushState.apply(history, arguments); callback(); }; history.replaceState = function () { replaceState.apply(history, arguments); callback(); }; } // Initialize features on initial load window.addEventListener('load', () => { setTimeout(() => { initializeFeatures(); }, 1000); }); // Initialize features on URL changes onUrlChange(() => { setTimeout(() => { initializeFeatures(); }, 1000); }); })();