🏠 Home 

AniList Shortcuts

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);
});
})();