Примечание:
- В теории полностью совместим с пользовательскими стилями.
- Может не работать на некоторых аниме страницах, в частности где нет блока "Новости", а также на страницах /animes/{a-z}{id} - буква пред id
Код
// ==UserScript==
// @name Интересные новости у аниме
// @namespace ---
// @version 1.1
// @description Добавляет сверху блока новостей раздел "Интересные новости"
// @match *://shikimori.one/*
// @grant GM.xmlHttpRequest
// @connect shikimori.one
// @connect raw.githubusercontent.com
// ==/UserScript==
(function () {
'use strict';
const targetSelector = '.b-menu-links.menu-topics-block.history.m30';
const checkInterval = 100;
const maxWait = 10000;
const FRANCHISES_URL = 'https://raw.githubusercontent.com/GRaf-NEET/franchises_counter/refs/heads/main/anime_franchises.json';
const REQUEST_DELAY = 300;
const RETRY_DELAY = 3000;
const progressTexts = [
"Ищем свежие спойлеры для ID",
"Парсим новости для текущего аниме",
"Загружаем новости для следующего ID",
"Сканируем форум на предмет новых публикаций",
"Проверяем каждую тему на актуальность",
"Собираем свежие обсуждения по франшизе",
"Читаем последние комментарии по ID",
"Проверяем все аниме франшизы на новые темы",
"Загружаем список последних публикаций",
"Сортируем новости по популярности",
"Подгружаем дополнительные данные для ID",
"Сканируем форум на скрытые обновления",
"Подготавливаем новости для отображения на странице",
"Ищем связанные тайтлы по вселенной",
"Избегаем ошибок 429",
"Декодируем JSON-ответ от сервера",
"Обходим анти-DDoS защиту",
"Отправляем запрос к серверу Shikimori",
"Чиним баг с отображением дат", // Было дело
"Переводим с японского через Google Translate",
"БУУУУ!!! Испугался?",
"Ищем горячие новости для фанатов",
"Отдыхаем Zzz",
"Ушли в аниме-сны...",
"Ждем ответа от аниме-богов...",
"Почему ты боишься срать в писсуары?"
];
function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
function gmFetch(url) {
return new Promise((resolve, reject) => {
GM.xmlHttpRequest({
method: "GET",
url,
onload: res => {
if (res.status >= 200 && res.status < 300) {
try {
resolve({
ok: true,
status: res.status,
json: async () => JSON.parse(res.responseText),
text: async () => res.responseText
});
} catch (e) {
reject(e);
}
} else if (res.status === 429) {
reject({ retry: true });
} else {
reject(new Error(`HTTP ${res.status}`));
}
},
onerror: err => reject(err),
ontimeout: err => reject(err)
});
});
}
async function fetchWithRetry(url, attempt = 1) {
try {
return await gmFetch(url);
} catch (e) {
if (e.retry) {
await delay(RETRY_DELAY);
return fetchWithRetry(url, attempt + 1);
}
console.error(`[News] Ошибка загрузки ${url}:`, e);
return null;
}
}
async function getRealAnimeId() {
const match = location.pathname.match(/\/animes\/([a-z]?(\d+))-/);
if (!match) return null;
const fullSlug = match[1];
const numericId = match[2];
if (!fullSlug.startsWith('z')) return parseInt(numericId, 10);
const res = await fetchWithRetry(`https://shikimori.one/api/animes/${fullSlug}`);
if (!res) return null;
const json = await res.json();
return json.id;
}
async function fetchFranchiseList() {
const res = await fetchWithRetry(FRANCHISES_URL);
if (!res) return [];
return await res.json();
}
async function fetchAnimeNews(id) {
if (!id) return [];
const url = `https://shikimori.one/api/topics?forum=news&linked_type=Anime&linked_id=${id}&type=Topics::NewsTopic&limit=100&order=comments_count&order_direction=desc`;
const res = await fetchWithRetry(url);
if (!res) return [];
const json = await res.json();
return json.map(t => ({
id: t.id,
title: t.topic_title,
url: `https://shikimori.one/forum/news/${t.id}`,
date: t.created_at
}));
}
function formatDateReadable(dateStr) {
const d = new Date(dateStr);
return d.toLocaleDateString('ru-RU', { day: 'numeric', month: 'short', year: 'numeric' });
}
function renderNewsHTML(newsArray) {
if (!newsArray?.length) return `<div class="b-menu-line empty">Новостей пока нет 😿</div>`;
return newsArray.map(n => `
<a class="b-menu-line entry b-link" href="${n.url}" target="_blank" itemprop="discussionUrl">
<span class="time"><time data-format="1_day_absolute" datetime="${n.date}">${formatDateReadable(n.date)}</time></span>
<span class="name">${n.title}</span>
</a>`).join('');
}
function renderBlockHTML(animeNews, franchiseNews, animeTitle, franchiseName) {
const truncate = (str, max = 40) => str.length > max ? str.slice(0, max-3) + '...' : str;
const animeHeader = `<div class="b-menu-line"><strong>По текущему аниме:</strong></div>
<div class="b-menu-line">${animeTitle ? truncate(animeTitle) : '—'}</div>`;
const franchiseHeader = `<div class="b-menu-line" style="margin-top:10px;border-top:1px solid #ccc;padding-top:5px;">
<strong>По франшизе:</strong></div>
<div class="b-menu-line">${franchiseName ? truncate(franchiseName) : '—'}</div>`;
return `
<div class="b-menu-links menu-topics-block history m30" id="interesting-news">
<div class="subheadline m5">Интересные новости</div>
<div class="block">
${animeHeader}
${renderNewsHTML(animeNews)}
${franchiseHeader}
${franchiseNews === null
? '<div class="b-menu-line empty">Нет франшизы</div>'
: renderNewsHTML(franchiseNews)}
</div>
</div>`;
}
function createProgressBlock(target) {
const div = document.createElement('div');
div.id = 'news-progress';
div.style.marginBottom = '10px';
div.style.fontStyle = 'italic';
div.style.color = '#555';
div.innerHTML = `
<div id="news-progress-text">Инициализация...</div>
<div id="news-progress-counter">Загружаем новости 0/0</div>
`;
target.parentNode.insertBefore(div, target);
return div;
}
function updateProgress(text, current, total) {
const textBlock = document.getElementById('news-progress-text');
const counterBlock = document.getElementById('news-progress-counter');
if (textBlock && counterBlock) {
textBlock.textContent = text;
counterBlock.textContent = `Загружаем новости ${current}/${total}`;
}
}
async function fetchAllFranchiseNews(ids) {
const results = [];
for (let i = 0; i < ids.length; i++) {
const text = progressTexts[Math.floor(Math.random() * progressTexts.length)];
updateProgress(text, i+1, ids.length);
const news = await fetchAnimeNews(ids[i]);
results.push(...news);
await delay(REQUEST_DELAY);
}
const unique = results.filter((v, i, a) => a.findIndex(t => t.id === v.id) === i);
return unique.sort((a, b) => new Date(b.date) - new Date(a.date));
}
async function insertInterestingNews() {
const animeId = await getRealAnimeId();
if (!animeId) return console.log('[News] ID аниме не найден');
const franchises = await fetchFranchiseList();
const current = franchises.find(f => f.id === animeId);
const franchiseName = current?.franchise || null;
const animeTitle = current?.russian || null;
const franchiseIds = franchiseName
? franchises.filter(f => f.franchise === franchiseName).map(f => f.id)
: [];
let waited = 0;
const timer = setInterval(async () => {
const target = document.querySelector(targetSelector);
if (target) {
clearInterval(timer);
// создаём прогресс-блок сразу
const progressDiv = createProgressBlock(target);
const animeNews = await fetchAnimeNews(animeId);
const franchiseNews = franchiseIds.length ? await fetchAllFranchiseNews(franchiseIds) : null;
// обновляем прогресс на завершено
updateProgress("Новости загружены!", franchiseIds.length, franchiseIds.length);
// заменяем прогресс на блок новостей
const html = renderBlockHTML(animeNews, franchiseNews, animeTitle, franchiseName);
progressDiv.outerHTML = html;
}
waited += checkInterval;
if (waited >= maxWait) {
clearInterval(timer);
console.log('[News] Таймаут ожидания блока');
}
}, checkInterval);
}
insertInterestingNews();
})();Как скачать:
1. Установите Tampermonkey в браузер.
1.1 Если у вас бразуер на движке хромиум (yandex, opera и т.д) включите режим разработчика.
browser://extensions/ - с права сверху.1.2 Или в настройках расширений браузера режим разработчика.
2. Создайте новый скрипт и вставьте код выше.
3. Сохраните и обновите страницу аниме на Shikimori.
Как выглядит:


@_Just_Monika_95_, У него даже блока новостей нет, странно@_Just_Monika_95_, ща пофиксим@_Just_Monika_95_@Graf_NEET, На примере Ран 3 Фильм.У Дворецкого Глянул особо новостей нет, но Гатари то
@_Just_Monika_95_, Вообще хрень какая-то, api возвращает пустой jsonсам api запрос
@_Just_Monika_95_@Graf_NEET, на Фрирен работает, а на Ран нет. Хоть и есть новости.Недоработка, видать
@Graf_NEET, сайт недавно выдавал 502, может чтот и не работает.@Graf_NEET@_Just_Monika_95_, меня смущает z{id}, это буква z много чего портит мне кажется@_Just_Monika_95_Но если в таком виде, то она не нужна:
Проверил на Монологе, вот на ней работает.
@Graf_NEET@_Just_Monika_95_, Ну эта буква создает нормальные такие проблемы.Kuroshitsuji: Book of CircusТёмный дворецкий: Глава о цирке
тут в ссылке нет и все работает
хотя тоже темный дворецкий
@_Just_Monika_95_@Graf_NEET, в общем скрипт будет работать не везде походу.1 сезон Дворецкого просто нету новостей. Решил взять История Ран, вот на них новости есть, но скрипт не может из за буквы.
@Graf_NEET@_Just_Monika_95_, да не идея есть, подсчет франшиз помнишь?там впринцепе есть франшизы и можно просто делать запросы для всех аниме во франшизе
@_Just_Monika_95_@Graf_NEET, Тут не знаю. Надо будет на отдельном фильме попробовать.Повелитель, работает. и фильм в том числе.
@Graf_NEET@_Just_Monika_95_, странные приколы сайт выдает@_Just_Monika_95_@Graf_NEET, вот еще, правда опять Гатари:С Судьба Начало, тоже самое.
Видать только с буквами скрипт не может.
@Graf_NEET@_Just_Monika_95_, че за "y" - знать бы что значит хотя бы@_Just_Monika_95_@Graf_NEET, там бывает и xhttps://shikimori.one/animes/x17074-monogatari-series-second-seasonhttps://shikimori.one/animes/z25537-fate-stay-night-movie-heaven-s-feel-i-presage-flowerРезультат и так ясен.
Не во всей франшизе ставится. И вот так работает. Что с этим делать понятия не имею.
@Graf_NEET , кстати можешь задать вопрос в Клубе CSS,. может быть там ответят что означают эти буквы.@Graf_NEET, .@Graf_NEET,@Graf_NEET@_Just_Monika_95_, У тебя тоже сайт работает со скоростью улитки?@kost12000@_Just_Monika_95_, я обновил попробуй (я сам топик изменил)@_Just_Monika_95_@Graf_NEET,По разному.
@Graf_NEET@kost12000, Значит я не один такой@kost12000@Graf_NEET,Ну да. Ну меняется только степень медленности.)
У меня фильм в 2к быстрее грузиться, чем сайт временами.