您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Shows a clan icon next to any username on the site that belongs to your clan
// ==UserScript== // @name AnimeStars Clan Highlighter // @namespace animestars // @author Allystark // @version 1.6.1 // @description Shows a clan icon next to any username on the site that belongs to your clan // @match https://astars.club/* // @match https://asstars1.astars.club/* // @match https://animestars.org/* // @match https://as1.astars.club/* // @match https://asstars.tv/* // @grant none // ==/UserScript== ;(async function() { 'use strict'; const STORAGE_KEY = 'myClanData'; const CACHE_DURATION = 24 * 60 * 60 * 1000; // 1 day const DEFAULT_ICON = '🐇'; async function getClanData() { let raw = localStorage.getItem(STORAGE_KEY); if (raw) { try { const data = JSON.parse(raw); if (Date.now() - data.fetched < CACHE_DURATION) { return data; } } catch {} } const menu = document.querySelector('.lgn__menus .lgn__menu'); const clubA = menu?.querySelector('a[href^="/clubs/"]'); if (!clubA) return { members: [], icon: null, fetched: Date.now() }; const res = await fetch(clubA.href, { credentials: 'include' }); const html = await res.text(); const doc = new DOMParser().parseFromString(html, 'text/html'); const members = Array.from(doc.querySelectorAll('.club__member-name')) .map(el => el.textContent.trim()); const iconEl = doc.querySelector('.nclub-enter__main-name img'); const icon = iconEl ? iconEl.src : null; const clanData = { members, icon, fetched: Date.now() }; localStorage.setItem(STORAGE_KEY, JSON.stringify(clanData)); return clanData; } function annotateEl(container, iconUrl, offsetBottom=-4, offsetLeft=-4) { // avoid double-annotation if (container.querySelector('.clan-marker')) return; container.style.position = container.style.position || 'relative'; const marker = iconUrl ? document.createElement('img') : document.createElement('span'); marker.className = 'clan-marker'; Object.assign(marker.style, { position: 'absolute', bottom: `${offsetBottom}px`, left: `${offsetLeft}px`, width: iconUrl ? '16px' : 'auto', height: iconUrl ? '16px' : 'auto', fontSize: iconUrl ? '' : '16px', lineHeight: iconUrl ? '' : '1', borderRadius: '0px' }); if (iconUrl) { marker.src = iconUrl; marker.alt = 'Clan icon'; } else { marker.textContent = DEFAULT_ICON; } container.append(marker); } function scanAndAnnotate(members, icon) { // 1) card owners document.querySelectorAll('a.card-show__owner').forEach(a => { const name = a.querySelector('.card-show__owner-name')?.textContent.trim(); if (name && members.includes(name)) annotateEl(a, icon); }); // 2) wishlisted/not needed document.querySelectorAll('a.profile__friends-item').forEach(a => { const name = a.querySelector('.profile__friends-name')?.textContent.trim(); if (name && members.includes(name)) annotateEl(a, icon); }); // 3) card author document.querySelectorAll('a.ncard__meta-item.ncard__user-name').forEach(a => { const name = Array.from(a.childNodes) .filter(n => n.nodeType === Node.TEXT_NODE) .map(n => n.textContent.trim()).join(''); if (name && members.includes(name)) annotateEl(a, icon); }); // 4) dropdown notifications document.querySelectorAll('div.dropdown-item').forEach(div => { const link = div.querySelector('span.font-weight-bold a[href^="/user/"]'); const name = link?.textContent.trim(); if (name && members.includes(name)) annotateEl(div, icon, 26, 8); }); // 5) trade list items document.querySelectorAll('a.trade__list-item').forEach(el => { const txt = el.querySelector('.trade__list-name')?.textContent.trim() || ''; const avatar = el.querySelector('div.trade__list-image'); const name = txt.replace(/^от |^для /, '').trim(); if (name && members.includes(name)) annotateEl(avatar, icon); }); // 6) notification blocks document.querySelectorAll('div.notification').forEach(el => { const link = el.querySelector('.notification__text a[href^="/user/"]'); const name = link?.textContent.trim(); if (name && members.includes(name)) annotateEl(el, icon, 8, 8); }); // 7) trade header document.querySelectorAll('div.trade__header').forEach(div => { const name = div.querySelector('a.trade__header-name')?.textContent.trim() || ''; const avatar = div.querySelector('a.trade__header-avatar'); if (name && members.includes(name)) annotateEl(avatar, icon, -8, -8); }); // 8) trade history names document.querySelectorAll('div.history__name a[href^="/user/"]') .forEach(a => { const name = a.textContent.trim(); if (name && members.includes(name)) annotateEl(a.parentElement.parentElement, icon, 0, 0); }); // 9) PMs list document.querySelectorAll('a.dpm-users-profile').forEach(a => { const match = a.href.match(/\/pm\/([^\/]+)\//); const name = match ? match[1] : null; const avatar = a.querySelector('div.dpm-users-left'); if (name && members.includes(name)) annotateEl(avatar, icon); }); // 10) PM header profile document.querySelectorAll('a.dpm-header-profile').forEach(a => { let match = a.href.match(/\/user\/([^\/]+)\//); let name = match ? match[1] : null; if (name && members.includes(name)) annotateEl(a.parentElement, icon, 16, 16); }); // 11) exchange offer document.querySelectorAll('a.card-show__name').forEach(a => { // extract the username after “с пользователем ” let match = a.textContent.match(/с пользователем\s+([\w-]+)/); let name = match ? match[1] : null; if (name && members.includes(name)) annotateEl(a, icon); }); // 12) user's collection document.querySelectorAll('h1.secondary-title.text-center a[href^="/user/"]').forEach(a => { const name = a.textContent.trim(); if (name && members.includes(name)) annotateEl(a, icon, 24, 0); }); // 13) users' top document.querySelectorAll('div.ncard__users-toplist a.card-inline').forEach(a => { const name = a.querySelector('.card-inline__name')?.textContent.trim(); if (name && members.includes(name)) annotateEl(a, icon, 0, 0); }); // 14) friends list document.querySelectorAll('div.usn-fr-list div.card-inline').forEach(el => { const a = el.querySelector('a.pc_gs'); const name = a?.textContent.trim(); if (name && members.includes(name)) annotateEl(el, icon, 0, 0); }); } const { members, icon } = await getClanData(); scanAndAnnotate(members, icon); new MutationObserver(() => scanAndAnnotate(members, icon)) .observe(document.body, { childList: true, subtree: true }); })();