您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Добавляет больше ссылок в раздел "На других сайтах"
- // ==UserScript==
- // @name ShikiSearch+
- // @icon https://www.google.com/s2/favicons?domain=shikimori.me
- // @namespace https://shikimori.one
- // @version 1.8
- // @description Добавляет больше ссылок в раздел "На других сайтах"
- // @author LifeH
- // @match *://shikimori.org/*
- // @match *://shikimori.one/*
- // @match *://shikimori.me/*
- // @grant none
- // @license MIT
- // @require https://cdnjs.cloudflare.com/ajax/libs/Sortable/1.15.6/Sortable.min.js
- // ==/UserScript==
- (function() {
- 'use strict';
- const allowedPaths = ["/ranobe/", "/animes/", "/mangas/"];
- let isEditing = false;
- let editingIndex = null;
- const defaultLinks = [
- {
- title: "Anime-joy",
- icon: "anime-joy.ru",
- link: "https://anime-joy.ru/index.php?story={search}&do=search&subaction=search",
- searchMethod: "title",
- group: "animes",
- enabled: true,
- },
- {
- title: "Anilibria",
- icon: "anilibria.tv",
- link: "https://www.anilibria.tv/release/{search}.html",
- searchMethod: "slug",
- group: "animes",
- enabled: true,
- },
- {
- title: "Anilibria Top",
- icon: "anilibria.top",
- link: "https://anilibria.top/anime/releases/release/{id}",
- searchMethod: "anilibriaApi",
- group: "animes",
- enabled: true,
- },
- {
- title: "Animego",
- icon: "animego.me",
- link: "https://animego.me/search/all?q={search}",
- searchMethod: "title",
- group: "animes",
- enabled: true,
- },
- {
- title: "Anilib",
- icon: "anilib.me",
- link: "https://anilib.me/ru/catalog?q={search}",
- searchMethod: "title",
- group: "animes",
- enabled: true,
- },
- {
- title: "Jut.su",
- icon: "jut.su",
- link: "https://jut.su/search/?searchid=1893616&text={search}&web=0#",
- searchMethod: "title",
- group: "animes",
- enabled: true,
- },
- {
- title: "rutracker",
- icon: "rutracker.org",
- link: "https://rutracker.org/forum/tracker.php?nm={search}",
- searchMethod: "title",
- group: "animes",
- enabled: true,
- },
- {
- title: "Yummy-anime",
- icon: "yummy-anime.ru",
- link: "https://yummy-anime.ru/search?word={search}",
- searchMethod: "title",
- group: "animes",
- enabled: false,
- },
- {
- title: "Animevost",
- icon: "animevost.org",
- link: "https://animevost.org/index.php?do=search&subaction=search&search_start=0&full_search=0&result_from=1&story={search}",
- searchMethod: "title",
- group: "animes",
- enabled: false,
- },
- {
- title: "Crunchyroll",
- icon: "crunchyroll.com",
- link: "https://www.crunchyroll.com/search?q={search}",
- searchMethod: "originalTitle",
- group: "animes",
- enabled: false,
- },
- {
- title: "Amedia",
- icon: "amedia.lol",
- link: "https://amedia.lol/index.php?do=search&subaction=search&from_page=0&story={search}",
- searchMethod: "title",
- group: "animes",
- enabled: false,
- },
- {
- title: "Rezka",
- icon: "rezka.ag",
- link: "https://rezka.ag/search/?do=search&subaction=search&q={search}",
- searchMethod: "title",
- group: "animes",
- enabled: false,
- },
- {
- title: "Anime1",
- icon: "anime1.best",
- link: "https://anime1.best/index.php?do=search&subaction=search&search_start=0&full_search=0&result_from=1&story={search}",
- searchMethod: "title",
- group: "animes",
- enabled: false,
- }
- ];
- function getGroup() {
- const path = window.location.pathname;
- return allowedPaths.find((p) => path.startsWith(p))?.replace(/\//g, "");
- }
- function getTitle() {
- let titleElement = document.querySelector(".head h1");
- return titleElement
- ? titleElement.childNodes[0].textContent.trim()
- : null;
- }
- function getId() {
- const pathParts = window.location.pathname.split("/");
- const idPart = pathParts[2] || "";
- const match = idPart.match(/^[a-z]*(\d+)/);
- return match ? match[1] : null;
- }
- function getOriginalTitle() {
- const titleElement = document.querySelector(".head h1");
- if (!titleElement) return null;
- const separator = titleElement.querySelector(".b-separator.inline");
- if (!separator) return null;
- const originalTitle = separator.nextSibling?.textContent?.trim();
- return originalTitle || null;
- }
- function getSlug() {
- let path = window.location.pathname;
- let match = path.match(/\/(animes|mangas|ranobe)\/z?(\d+)-(.+)/);
- return match ? match[3] : null;
- }
- function getLinks() {
- let storedLinks = JSON.parse(localStorage.getItem("userLinks"));
- if (!storedLinks) {
- storedLinks = defaultLinks;
- saveLinks(storedLinks);
- return storedLinks;
- }
- let deletedLinks = JSON.parse(localStorage.getItem("deletedLinks")) || [];
- let updated = false;
- defaultLinks.forEach((defaultLink) => {
- if (deletedLinks.includes(defaultLink.title)) return;
- if (!storedLinks.some(link => link.title === defaultLink.title)) {
- storedLinks.push(defaultLink);
- updated = true;
- }
- });
- if (updated) {
- saveLinks(storedLinks);
- }
- return storedLinks;
- }
- function saveLinks(links) {
- localStorage.setItem("userLinks", JSON.stringify(links));
- }
- function linkBuilder({ icon, link, searchMethod, group: linkGroup, title, enabled }) {
- if (!enabled) return;
- const group = getGroup();
- if (linkGroup !== group) return;
- let parentBlock = document.querySelector(".subheadline.m8")?.parentElement;
- if (!parentBlock) return;
- let searchParam;
- if (searchMethod === "slug") {
- searchParam = getSlug();
- } else if (searchMethod === "id") {
- searchParam = getId();
- } else if (searchMethod === "originalTitle") {
- searchParam = getOriginalTitle();
- } else {
- searchParam = getTitle();
- }
- if (!searchParam) return;
- let url = link.replace("{search}", encodeURIComponent(searchParam));
- let linkContainer = document.createElement('div');
- linkContainer.className = 'b-external_link b-menu-line';
- let anchor = document.createElement('a');
- anchor.className = 'b-link';
- anchor.href = url;
- anchor.textContent = title;
- anchor.target = '_blank';
- if (icon) {
- let img = document.createElement('img');
- img.src = `https://www.google.com/s2/favicons?domain=${icon}`;
- img.style.width = '19px';
- img.style.height = '19px';
- img.style.marginRight = '5px';
- anchor.prepend(img);
- }
- linkContainer.appendChild(anchor);
- parentBlock.appendChild(linkContainer);
- if (searchMethod === "anilibriaApi") {
- const title = getTitle();
- const apiUrl = `https://anilibria.top/api/v1/app/search/releases?query=${encodeURIComponent(title)}`;
- fetch(apiUrl)
- .then(response => response.json())
- .then(data => {
- if (data && data.length > 0) {
- const id = data[0].id;
- const finalUrl = `https://anilibria.top/anime/releases/release/${id}`;
- anchor.href = finalUrl;
- }
- })
- .catch(error => {
- console.error(error);
- });
- }
- }
- function init() {
- let links = getLinks();
- links.forEach(linkBuilder);
- }
- function GUI() {
- const settingsBlock = document.querySelector('.block.edit-page.misc');
- if (!settingsBlock) return;
- if (document.querySelector('.shikisearch-config')) return;
- let container = document.createElement('div');
- container.className = 'shikisearch-config';
- container.style.padding = '20px';
- container.style.border = '1px solid #ccc';
- container.style.marginTop = '20px';
- container.style.background = '#f9f9f9';
- container.style.borderRadius = '8px';
- container.style.boxShadow = '0 2px 4px rgba(0,0,0,0.1)';
- container.style.position = 'relative';
- container.innerHTML = `
- <h3 style="margin-bottom: 20px; text-align: center;">[ShikiSearch+] Config</h3>
- <div style="display: flex; flex-direction: column; gap: 10px;">
- <input type="text" id="title" placeholder="Название" style="padding: 8px; border-radius: 4px; border: 1px solid #ccc;">
- <input type="text" id="icon" placeholder="Домен (например: shikimori.one)" style="padding: 8px; border-radius: 4px; border: 1px solid #ccc;">
- <input type="text" id="link" placeholder="Шаблон для ссылки поиска (используйте {search})" style="padding: 8px; border-radius: 4px; border: 1px solid #ccc;">
- <select id="searchMethod" style="padding: 8px; border-radius: 4px; border: 1px solid #ccc;">
- <option value="title">По названию</option>
- <option value="originalTitle">По названию (оригинал)</option>
- <option value="slug">По Slug</option>
- <option value="id">По ID</option>
- <option value="anilibriaApi">anilibriaApi</option>
- </select>
- <select id="group" style="padding: 8px; border-radius: 4px; border: 1px solid #ccc;">
- <option value="animes">Аниме</option>
- <option value="mangas">Манга</option>
- <option value="ranobe">Ранобэ</option>
- </select>
- <button id="addLink" style="padding: 10px; background-color: #4CAF50; color: white; border: none; border-radius: 4px; cursor: pointer;">Добавить</button>
- </div>
- <div id="linksList" style="margin-top: 20px; display: flex; flex-direction: column; gap: 10px;"></div>
- <span class="tooltip" style="position: absolute; top: 10px; right: 10px; cursor: help;">?
- <span class="tooltiptext">
- <strong>Информация:</strong><br>
- "По названию": Русское названия тайтла.<br>
- "По Slug": Использует часть ссылки.<br>
- </span>
- </span>
- `;
- settingsBlock.appendChild(container);
- let style = document.createElement('style');
- style.textContent = `
- .tooltip {
- position: relative;
- display: inline-block;
- cursor: help;
- font-size: 14px;
- color: #555;
- }
- .tooltip .tooltiptext {
- visibility: hidden;
- width: 250px;
- background-color: #555;
- color: #fff;
- text-align: left;
- border-radius: 6px;
- padding: 10px;
- position: absolute;
- z-index: 1000;
- top: 100%;
- right: 0;
- opacity: 0;
- transition: opacity 0.3s;
- font-size: 12px;
- white-space: normal;
- }
- .tooltip:hover .tooltiptext {
- visibility: visible;
- opacity: 1;
- }
- `;
- document.head.appendChild(style);
- document.getElementById('addLink').addEventListener('click', () => {
- let title = document.getElementById('title').value.trim();
- let icon = document.getElementById('icon').value.trim();
- let link = document.getElementById('link').value.trim();
- let searchMethod = document.getElementById('searchMethod').value;
- let group = document.getElementById('group').value;
- if (!title || !link) {
- alert('Заполните название и ссылку!');
- return;
- }
- let links = getLinks();
- if (isEditing) {
- links[editingIndex] = { title, icon, link, searchMethod, group, enabled: true };
- isEditing = false;
- editingIndex = null;
- document.getElementById('addLink').textContent = 'Добавить';
- } else {
- links.push({ title, icon, link, searchMethod, group, enabled: true });
- }
- saveLinks(links);
- updateLinksList();
- clearForm();
- });
- updateLinksList();
- }
- function updateLinksList() {
- let linksList = document.getElementById('linksList');
- linksList.innerHTML = '';
- let links = getLinks();
- links.forEach((link, index) => {
- let card = document.createElement('div');
- card.style.display = 'flex';
- card.style.alignItems = 'center';
- card.style.justifyContent = 'space-between';
- card.style.padding = '10px';
- card.style.border = '1px solid #ccc';
- card.style.borderRadius = '8px';
- card.style.background = '#fff';
- card.style.boxShadow = '0 2px 4px rgba(0,0,0,0.1)';
- card.style.cursor = 'grab';
- card.setAttribute('data-index', index);
- card.setAttribute('draggable', 'true');
- card.addEventListener('dragstart', (e) => {
- e.dataTransfer.setData('text/plain', '');
- e.target.style.opacity = '0.4';
- });
- card.addEventListener('dragend', (e) => {
- e.target.style.opacity = '1';
- });
- card.innerHTML = `
- <div style="display: flex; align-items: center; gap: 10px;">
- <img src="https://www.google.com/s2/favicons?domain=${link.icon}" style="width: 16px; height: 16px;">
- <span>${link.title} (${link.group})</span>
- </div>
- <div style="display: flex; align-items: center; gap: 10px;">
- <label class="toggle-switch">
- <input type="checkbox" ${link.enabled ? 'checked' : ''} onchange="toggleLink(${index}, this.checked)">
- <span class="slider"></span>
- </label>
- <button onclick="editLink(${index})" style="padding: 5px 10px; background-color: #FFC107; color: white; border: none; border-radius: 4px; cursor: pointer;">Редактировать</button>
- <button onclick="deleteLink(${index})" style="padding: 5px 10px; background-color: #F44336; color: white; border: none; border-radius: 4px; cursor: pointer;">Удалить</button>
- </div>
- `;
- linksList.appendChild(card);
- });
- new Sortable(linksList, {
- animation: 150,
- onEnd: function (evt) {
- let links = getLinks();
- let movedItem = links.splice(evt.oldIndex, 1)[0];
- links.splice(evt.newIndex, 0, movedItem);
- saveLinks(links);
- updateLinksList();
- }
- });
- let style = document.createElement('style');
- style.textContent = `
- .toggle-switch {
- position: relative;
- display: inline-block;
- width: 40px;
- height: 20px;
- }
- .toggle-switch input {
- opacity: 0;
- width: 0;
- height: 0;
- }
- .slider {
- position: absolute;
- cursor: pointer;
- top: 0;
- left: 0;
- right: 0;
- bottom: 0;
- background-color: #ccc;
- transition: 0.4s;
- border-radius: 20px;
- }
- .slider:before {
- position: absolute;
- content: "";
- height: 16px;
- width: 16px;
- left: 2px;
- bottom: 2px;
- background-color: white;
- transition: 0.4s;
- border-radius: 50%;
- }
- input:checked + .slider {
- background-color: #4CAF50;
- }
- input:checked + .slider:before {
- transform: translateX(20px);
- }
- .sortable-chosen {
- background-color: #f0f0f0;
- }
- .sortable-ghost {
- opacity: 0.5;
- }
- `;
- document.head.appendChild(style);
- }
- function clearForm() {
- document.getElementById('title').value = '';
- document.getElementById('icon').value = '';
- document.getElementById('link').value = '';
- document.getElementById('searchMethod').value = 'title';
- document.getElementById('group').value = 'animes';
- }
- window.deleteLink = function(index) {
- if (confirm('Вы уверены, что хотите удалить эту ссылку?')) {
- let links = getLinks();
- let deletedLink = links[index];
- if (defaultLinks.some(link => link.title === deletedLink.title)) {
- let deletedLinks = JSON.parse(localStorage.getItem("deletedLinks")) || [];
- if (!deletedLinks.includes(deletedLink.title)) {
- deletedLinks.push(deletedLink.title);
- localStorage.setItem("deletedLinks", JSON.stringify(deletedLinks));
- }
- }
- links.splice(index, 1);
- saveLinks(links);
- updateLinksList();
- }
- };
- window.editLink = function(index) {
- let links = getLinks();
- let link = links[index];
- document.getElementById('title').value = link.title;
- document.getElementById('icon').value = link.icon;
- document.getElementById('link').value = link.link;
- document.getElementById('searchMethod').value = link.searchMethod;
- document.getElementById('group').value = link.group;
- isEditing = true;
- editingIndex = index;
- document.getElementById('addLink').textContent = 'Сохранить изменения';
- };
- window.toggleLink = function(index, enabled) {
- let links = getLinks();
- links[index].enabled = enabled;
- saveLinks(links);
- };
- function ready(fn) {
- document.addEventListener('page:load', fn);
- document.addEventListener('turbolinks:load', fn);
- if (document.readyState !== "loading") {
- fn();
- } else {
- document.addEventListener('DOMContentLoaded', fn);
- }
- }
- ready(init);
- ready(GUI);
- })();