Список пользователей

1
Админ
Постов: 101
2
Элита
Постов: 34
3
Элита
Постов: 28
4
VIP
Постов: 26
5
Дизайнер
Постов: 25
6
Пользователи
Постов: 25
7
Пользователи
Постов: 24
8
Проверенные
Постов: 21

  • Страница 1 из 1
  • 1
Скрипт ночной охотник за авами - продвинутый режим профи
Дата: Пятница, 31.10.2025, 15:13 | Сообщение # 1 | | Написал: Начинающий
Автор темы
Мурчанн не в сети
        Сообщений:101
         Регистрация:20.10.2016

Ночной охотник за аватарками - продвинутый стратегический скрипт

Что делает:

1. Находит ник последнего комментатора и пытается подгрузить его аватар.
2. Использует кеш (localStorage), карту userMap и парсинг профилей.
3. Перебирает несколько кандидатных URL и резервных селекторов.
4. Фильтрует дефолтные/системные картинки, ставит только корректные авы.

Поведение:

1. Работает автономно, устойчив к неудачам и CORS; логирует процесс в консоль.
2. Логика подробно описана в теле скрипта - читайте, чтобы понять, как он достигает цели.

Лозунг: Работает по ночам, находит всегда.

Код
<script>
/*
  Скрипт ночной охотник за авами - продвинутый режим профи
  - пробует найти аватар в .last_post (строго)
  - если не найдёт, пробует запасные селекторы
  - использует originalUsername для построения путей, нормализованный ключ для кэша
  - расширенный лог (console) для диагностики
  - устойчив к CORS/ошибкам fetch (в таком случае ставит дефолт)
*/

(function(){
  'use strict';

  const DEFAULT_AVATAR = '/template/amg/images/img/noava.png';
  const CHECK_DELAY = 160;
  const CACHE_KEY = 'autoAvatar_user_cache_v3_debug';
  const cache = JSON.parse(localStorage.getItem(CACHE_KEY) || '{}');

const userMap = {
  "admin": "#",
  "Модератор": "#",
  "Ник": "#",
  "Ник": "#",
  "Ник": "#",
  "Ник": "#",
  "Ник": "#",
  "Ник": "ссылка на аву",
  "NoName": "https://jordan.moy.su/template/amg/images/img/noava.png" // запасной дефолт
};

  function saveCache(){ localStorage.setItem(CACHE_KEY, JSON.stringify(cache)); }

  function normalizeKey(name){
    return (name || '').trim().toLowerCase();
  }

  function findAvatarSrcInDoc(doc){
    const selectors = [
      // специфичные селекторы для uCoz и популярных движков
      '.user_avatar img',
      'img[src*="/avatar/"]',
      'img[src*="/ava/"]',
      '.ipsUserPhoto img',
      '.ipsUserPhotoLink img',
      'img.avatar',
      'img.profile-avatar',
      '.profile-avatar img'
    ];
    for(const s of selectors){
      const el = doc.querySelector(s);
      if(el && el.src){
        const src = el.src;
        if(!/noava|default|guest|anon|empty/i.test(src)){
          return new URL(src, location.href).href;
        }
      }
    }
    return null;
  }

  async function fetchAndFindAvatar(url){
    try{
      const res = await fetch(url, { cache: 'no-cache' });
      if(!res.ok){
        console.debug('autoAvatar: fetch returned not ok', url, res.status);
        return null;
      }
      const txt = await res.text();
      const parser = new DOMParser();
      const doc = parser.parseFromString(txt, 'text/html');
      return findAvatarSrcInDoc(doc);
    }catch(e){
      // Может быть CORS или сетевой сбой
      console.warn('autoAvatar: fetch failed (CORS/network?)', url, e);
      return null;
    }
  }

  function buildProfileCandidates(href, originalUsername){
    const candidates = [];
    if(href){
      try{ candidates.push(new URL(href, location.href).href); }catch(e){}
    }
    if(originalUsername){
      // оставляем вариант с кодировкой и без — иногда работает один из них
      const safe = encodeURIComponent(originalUsername.replace(/\s+/g, '-'));
      candidates.push(location.origin + '/index/8-0-' + safe);
      candidates.push(location.origin + '/index/8-0-' + originalUsername);
      // иногда используется /user/ID или /users/USERNAME
      candidates.push(location.origin + '/user/' + originalUsername);
      candidates.push(location.origin + '/users/' + originalUsername);
    }
    return Array.from(new Set(candidates)).filter(Boolean);
  }

  // возвращает блок .last_post (если есть) и информацию о нике и href
  function getLastCommentInfo(topicEl){
    // сначала целевой блок last_post
    const lastBlock = topicEl.querySelector('.last_post, .lastpost, .last-post');
    let anchor = null;
    if(lastBlock){
      anchor = lastBlock.querySelector('.uLPost, a, .lastpost_author a, .username, .user a');
    }
    // запасной вариант — искать по общим селекторам внутри темы
    if(!anchor){
      anchor = topicEl.querySelector('.last_post .uLPost, .last_post a, .uLPost, .last_post span a, a[href*="/index/8-"], a[href*="/user/"], .last_user a, .lastpost_author a, .username a');
    }
    if(!anchor) return null;
    const username = (anchor.textContent || '').trim();
    const href = anchor.getAttribute('href') || null;
    return { username: username || null, href: href };
  }

  // по теме пытаемся найти элемент img для замены.
  // сначала строго внутри .last_post, иначе пробуем ряд запасных контейнеров,
  // но помним — не хотим случайно поставить аву автора темы вместо последнего коммента.
  function findAvatarImgElement(topicEl){
    // 1) строго в .last_post
    let lastBlock = topicEl.querySelector('.last_post, .lastpost, .last-post');
    if(lastBlock){
      const img = lastBlock.querySelector('img[class*="avatar"], img.ipsUserPhoto, .user_avatar img, img.profile-avatar');
      if(img) return img;
    }

    // 2) запасной — элемент .uLPost рядом с ником
    const uL = topicEl.querySelector('.uLPost, .lastpost_author, .last_user');
    if(uL){
      const maybe = uL.querySelector('img[class*="avatar"], img.ipsUserPhoto, img.profile-avatar');
      if(maybe) return maybe;
    }

    // 3) более общий запасной — ищем img рядом с селектором последнего автора
    const infoAnchor = topicEl.querySelector('.last_post a, .last_post .uLPost, .lastpost_author a, .last_user a, a[href*="/index/8-"], a[href*="/user/"]');
    if(infoAnchor){
      // ищем ближайший <img> в родителях/соседях
      let p = infoAnchor.parentElement;
      for(let i=0;i<4 && p;i++, p = p.parentElement){
        const img = p.querySelector('img[class*="avatar"], img.ipsUserPhoto, img.profile-avatar, .user_avatar img');
        if(img) return img;
      }
    }

    // 4) если совсем ничего — возвращаем null
    return null;
  }

  async function handleTopic(topicEl){
    try{
      const avatarImg = findAvatarImgElement(topicEl);
      if(!avatarImg){
        console.debug('autoAvatar: avatar element not found for topic', topicEl);
        return;
      }
      if(avatarImg.dataset.autoAvatarDone) return;
      avatarImg.dataset.autoAvatarDone = '1';

      const info = getLastCommentInfo(topicEl);
      if(!info || !info.username){
        console.debug('autoAvatar: no last author info for topic');
        return;
      }

      const originalUsername = info.username;
      const cacheKey = normalizeKey(originalUsername);

      console.debug('autoAvatar: processing', originalUsername);

      // 0) если уже стоит нормальная картинка (и не дефолт) — ничего не делать
      try{
        const cur = avatarImg.getAttribute('src') || avatarImg.src;
        if(cur && !/noava|default|guest|anon|empty/i.test(cur)){
          console.debug('autoAvatar: current img looks valid, skip', cur);
          // но всё равно кешируем под ключ если нет в кэше
          if(!cache[cacheKey]){ cache[cacheKey] = cur; saveCache(); }
          return;
        }
      }catch(e){ /* ignore read errors */ }

      // 1) кэш
      if(cache[cacheKey]){
        avatarImg.src = cache[cacheKey];
        console.info('autoAvatar: set from cache', originalUsername, cache[cacheKey]);
        return;
      }

      // 2) userMap (используем нормализованный ключ)
      if(userMap[cacheKey]){
        avatarImg.src = userMap[cacheKey];
        cache[cacheKey] = userMap[cacheKey];
        saveCache();
        console.info('autoAvatar: set from userMap', originalUsername, userMap[cacheKey]);
        return;
      }

      // 3) пробуем fetch по кандидатам
      const candidates = buildProfileCandidates(info.href, originalUsername);
      if(candidates.length === 0){
        console.debug('autoAvatar: no profile candidates for', originalUsername);
      }
      for(const c of candidates){
        console.debug('autoAvatar: trying', c);
        const found = await fetchAndFindAvatar(c);
        if(found){
          avatarImg.src = found;
          cache[cacheKey] = found;
          saveCache();
          console.info('autoAvatar: found avatar for', originalUsername, found);
          return;
        }
        await new Promise(r => setTimeout(r, CHECK_DELAY));
      }

      // 4) если всё не удалось - ставим дефолт
      avatarImg.src = DEFAULT_AVATAR;
      cache[cacheKey] = DEFAULT_AVATAR;
      saveCache();
      console.warn('autoAvatar: placed default avatar for', originalUsername);

    }catch(e){
      console.error('autoAvatar: handleTopic error', e);
    }
  }

  async function processAll(root = document){
    // расширенный набор селекторов тем (попытаемся покрыть разные шаблоны)
    const topics = Array.from(root.querySelectorAll(
      '.__topic, tr.__topic, .bb_box .ipb_table .__topic, .topic, .forum-row, .threads-list-item'
    ));
    for(const t of topics){
      await handleTopic(t);
      await new Promise(r=>setTimeout(r, CHECK_DELAY));
    }
  }

  // запускаем как можно раньше, но если DOMContentLoaded уже был- process сразу
  if(document.readyState === 'loading'){
    document.addEventListener('DOMContentLoaded', ()=>{ processAll().catch(console.error); });
  } else {
    // даём маленькую паузу, чтобы динамический контент успел вставиться
    setTimeout(()=> processAll().catch(console.error), 20);
  }

  const obs = new MutationObserver(muts=>{
    for(const m of muts){
      for(const n of m.addedNodes || []){
        if(n.nodeType === 1){
          processAll(n).catch(console.error);
        }
      }
    }
  });
  obs.observe(document.body, { childList: true, subtree: true });

  // экспорт для отладки: window.autoAvatarDebug = { cache, saveCache };
})();
</script>


Мурчанн

Признаюсь, не знаю почему, но глядя на звезды мне всегда хочется мечтать.
  • Страница 1 из 1
  • 1
Поиск: