TabunAva Reborn

Установка своего аватара на Табуне!

// ==UserScript==
// @name         TabunAva Reborn
// @namespace    http://tampermonkey.net/
// @version      1.5.9
// @description  Установка своего аватара на Табуне!
// @author       (IntelRug && (Kujivunia || Niko_de_Andjelo) && makise_homura)
// @match        https://tabun.everypony.ru/*
// @grant        none
// @license MIT
// ==/UserScript==

// Tabun Swarm: https://tabun.everypony.ru/blog/uniblog/194538.html
// IDENTICON: https://avatars.dicebear.com/styles/identicon

const GRemoteSettingsLink = 'https://raw.githubusercontent.com/Kujivunia/TabunAva-Reborn/main/settings.json';
const GEveryponyCdnStorageRegex = /(https?:)?\/\/cdn\.everypony\.ru\/storage\//;
const GEveryponyCdnStorageLink = '//cdn.everypony.ru/storage/';

let GAvaDictionary = {};
let GSettings = {};

function isTabunAvaSettingsPage() {
  return window.location.search.includes('tabun-ava');
}

function addLinkToNavigation() {
  const navPillsNode = document.querySelector('.nav-pills');
  if (!navPillsNode) return;

  const navItemNode = document.createElement('li');
  const navAnchorNode = document.createElement('a');
  navAnchorNode.setAttribute('href', '/settings/account?tabun-ava');
  navAnchorNode.innerHTML = 'TabunAva';
  navItemNode.appendChild(navAnchorNode);
  navPillsNode.appendChild(navItemNode);

  if (isTabunAvaSettingsPage()) {
    const activeNavItemNode = document.querySelector('.nav-pills li.active');
    if (activeNavItemNode) activeNavItemNode.classList.remove('active');
    navItemNode.classList.add('active');
  }
}

function getDefaultSettings() {
  return {
    faceless: 'default',
    faceless_picture: '',
    faceless_picture_f: '',
    blacklist: '',
    usercss: '',
    header_text: '',
    refresh_period: 10,
    refresh_unit: 'minutes',
    animated: true,
    priority: true,
    noregularava: false,
    fixavacorners: false,
    oldauthor: false,
    notabunava: false,
  };
}

function updateSettingsForm(settings = {}) {
  const mergedSettings = Object.assign({}, settings);

  Object.keys(mergedSettings).forEach((key) => {
    const node = document.getElementById(key);
    if (node) {
      if (node.type === 'checkbox') {
        node.checked = mergedSettings[key];
      } else {
        node.value = mergedSettings[key];
      }
    }
  });
}

/**
 *  Достает локальные настройки из localStorage и объединяет их с внешними настройками с гитхаба
 *  Если пришло время обновлять базу, получает внешние настройки с гитхаба, иначе загружает их
 *  сохранённую копию из localStorage
 */
function getSettings() {
  // Достаём локальные настройки из localStorage
  let settings = {};
  const jsonString = localStorage.getItem('TabunAvaReborn_Settings');
  if (jsonString) {
    try {
      settings = JSON.parse(jsonString);
    } catch (e) {
      settings = {};
    }
  }

  // Объединяем с настройками по-умолчанию, на случай, если в localStorage отсутствуют настройки
  settings = Object.assign({}, getDefaultSettings(), settings);

  const oldSettings = Object.assign({}, GSettings);
  GSettings = settings;
  const shouldUpdate = shouldUpdateAvatarsStorage();
  GSettings = oldSettings;

  // Получаем внешние настройки и объединяем с ними локальные настройки
  if (shouldUpdate) {
    return getRemoteSettings()
      .then((remoteSettings) => {
        settings.remote = remoteSettings;
        localStorage.setItem('TabunAvaReborn_Settings', JSON.stringify(settings));
        return settings;
      });
  } else {
    if (!settings.remote) {
      settings.remote = getDefaultRemoteSettings();
    }
    return Promise.resolve(settings);
  }
}

function getRefreshMillis() {
  let millis = 1000 * +GSettings.refresh_period; // 1 second

  if (GSettings.refresh_unit === 'minutes') {
    millis *= 60;
  } else if (GSettings.refresh_unit === 'hours') {
    millis *= 60 * 60;
  } else if (GSettings.refresh_unit === 'days') {
    millis *= 60 * 60 * 24;
  }

  return millis;
}

function saveSettings() {
  const settings = {
    remote: GSettings.remote || getDefaultRemoteSettings(),
  };

  Object.keys(getDefaultSettings()).forEach((key) => {
    const node = document.getElementById(key);
    if (node) {
      if (node.type === 'checkbox') {
        settings[key] = node.checked;
      } else {
        settings[key] = node.value;
      }
    }
  });

  localStorage.setItem('TabunAvaReborn_Settings', JSON.stringify(settings));
  alert('Настройки сохранены');
}

function getDefaultRemoteSettings() {
  return {
    post: 'https://tabun.everypony.ru/blog/TabunAva/203681.html',
    blacklist: [],
  };
}

function getRemoteSettings() {
  return fetch(GRemoteSettingsLink)
    .then((response) => {
      if (!response.ok) {
        return getDefaultRemoteSettings();
      }
      return response.json();
    })
    .then((settings) => {
      return Object.assign({}, getDefaultRemoteSettings(), settings);
    })
    .catch(() => {
      return getDefaultRemoteSettings();
    })
}

function replaceSettingsForm(formNode) {
  const securityLsKey = document.querySelector('[name=security_ls_key]');
  const node = formNode || document.querySelector('form.wrapper-content');

  node.innerHTML = getSettingsTemplate();
  if (securityLsKey) {
    node.innerHTML += '<input type="hidden" name="security_ls_key" value="' + securityLsKey.value + '">';
  }

  updateSettingsForm(GSettings);

  const saveButtonNode = document.querySelector('#save_button');
  saveButtonNode.addEventListener('click', (event) => {
    event.preventDefault();
    saveSettings(saveButtonNode);
  });

  const refreshButtonNode = document.querySelector('#refresh_button');
  refreshButtonNode.addEventListener('click', (event) => {
    event.preventDefault();
    refreshAvatarsStorage()
      .then(() => {
        alert('База данных обновлена');
      });
  });
}

function getSettingsButtonTemplate() {
  return '\
    <style>\
      .ta-button {\
        height: 27px !important;\
        display: inline-block;\
        width: 27px !important;\
        vertical-align: bottom;\
        background: linear-gradient(0deg, #f4f4f4, #f9fbfb);\
        color: #8a9198;\
        border-radius: 4px;\
        border: 1px solid #e3e6eb;\
        box-sizing: border-box;\
      }\
      \
      .ta-button > svg {\
        fill: currentColor;\
        width: 21px;\
        height: 21px;\
        margin-top: 2px;\
        margin-left: 2px;\
      }\
      \
      .ta-button:hover {\
        background: linear-gradient(0deg, #23b2fe, #4cc3ff);\
        border-color: #28adea;\
        color: #ffffff;\
      }\
    </style>\
    <a class="ta-button">\
      <svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px">\
        <path d="M0 0h24v24H0V0z" fill="none"></path>\
        <path d="M19 5v14H5V5h14m0-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-4.86 8.86l-3 3.87L9 13.14 6 17h12l-3.86-5.14z"></path>\
      </svg>\
    </a>\
  ';
}

function getSettingsTemplate() {
  return '\
    <div class="wrapper-content">\
      <dl class="form-item">\
        <a href="https://tabun.everypony.ru/settings/account?tabun-ava" id="avatar-upload">Загрузить аватар</a>\
      </dl>\
      <dl class="form-item">\
        <label for="faceless" style="margin-bottom: 7px">Как отображать безликих пони:</label>\
        <select name="faceless" id="faceless" class="input-width-200">\
          <option value="default" selected="">Не изменять</option>\
          <option value="identicon">IDENTICON</option>\
          <option value="swarm">Swarm</option>\
          <option value="other">Своя картинка</option>\
        </select>\
      </dl>\
      <dl class="form-item">\
        <label for="faceless_picture" style="margin-bottom: 7px">\
          Своя картинка безликого пони:\
        </label>\
        <input\
          type="text"\
          name="faceless_picture"\
          id="faceless_picture"\
          class="input-text input-width-200"\
          placeholder="https://..."\
        >\
      </dl>\
      <dl class="form-item">\
        <label for="faceless_picture_f" style="margin-bottom: 7px">\
            Своя картинка безликой кобылки (если отличается от предыдущей):\
        </label>\
        <input\
          type="text"\
          name="faceless_picture_f"\
          id="faceless_picture_f"\
          class="input-text input-width-200"\
          placeholder="https://..."\
        >\
      </dl>\
      <dl class="form-item">\
        <label for="header_text" style="margin-bottom: 7px">\
          Заголовок Табуна:\
        </label>\
        <input\
          type="text"\
          name="header_text"\
          id="header_text"\
          class="input-text input-width-200"\
          placeholder="Да, это — Табун!"\
        >\
      </dl>\
      <dl class="form-item">\
        <label for="blacklist" style="margin-bottom: 7px">\
          Чёрный список:\
        </label>\
        <textarea\
          name="blacklist"\
          id="blacklist"\
          class="input-text input-width-200"\
          rows="2"\
          placeholder="Pony, Pony2, Pony3"\
          style="resize: vertical"\
        ></textarea>\
      </dl>\
      <dl class="form-item">\
        <label for="refresh_period" style="margin-bottom: 7px">\
          Частота обновления базы аватарок:\
        </label>\
        <input\
          type="text"\
          name="refresh_period"\
          id="refresh_period"\
          class="input-text"\
          placeholder="30"\
          style="width: 36px; margin-right: 4px;"\
        >\
        <select\
          name="refresh_unit"\
          id="refresh_unit"\
          style="width: 70px; margin-right: 4px;"\
        >\
          <option value="minutes" selected="">минут</option>\
          <option value="hours">часов</option>\
          <option value="days">дней</option>\
        </select>\
        <button\
          type="submit"\
          id="refresh_button"\
          name="refresh_button"\
          class="button button-primary"\
          style="width: 80px; height: 26px; margin-top: -4px;"\
        >\
          Обновить\
        </button>\
      </dl>\
      <dl class="form-item">\
        <label for="usercss" style="margin-bottom: 7px">\
          Свой CSS:\
        </label>\
        <textarea\
          name="usercss"\
          id="usercss"\
          class="input-text input-width-400"\
          rows="4"\
          placeholder="body {\n  background: #000;\n}"\
          style="resize: vertical"\
        ></textarea>\
      </dl>\
      <dl class="form-item">\
        <label>\
          <input type="checkbox" id="fixavacorners" name="priority" value="0" class="input-checkbox">\
          вернуть квадратные аватарки\
        </label>\
      </dl>\
      <dl class="form-item">\
        <label>\
          <input type="checkbox" id="oldauthor" name="priority" value="0" class="input-checkbox">\
          вернуть старую индикацию автора\
        </label>\
      </dl>\
      <dl class="form-item">\
        <label>\
          <input type="checkbox" id="animated" name="animated" value="1" class="input-checkbox">\
          анимированные аватарки\
        </label>\
      </dl>\
      <dl class="form-item">\
        <label>\
          <input type="checkbox" id="priority" name="priority" value="1" class="input-checkbox">\
          приоритет аватарок из темы над аватарками из профиля\
        </label>\
      </dl>\
      <dl class="form-item">\
        <label>\
          <input type="checkbox" id="noregularava" name="priority" value="0" class="input-checkbox">\
          игнорировать аватар табуна\
        </label>\
      </dl>\
      <dl class="form-item">\
        <label>\
          <input type="checkbox" id="notabunava" name="priority" value="0" class="input-checkbox">\
          игнорировать аватар TabunAva\
        </label>\
      </dl>\
      <button id="save_button" type="submit" name="submit" class="button button-primary">Сохранить</button>\
    </div>\
    <style>\
      form > .wrapper-content > .form-item #avatar-upload {\
        font-size: 18px;\
        line-height: 1.4;\
        font-weight: 600;\
        color: indianred;\
        padding: 10px 0;\
        display: block;\
      }\
    </style>\
';
}

function initSettingsPage() {
  if (!isTabunAvaSettingsPage()) {
    const widemodeNode = document.querySelector('#widemode');
    const span = document.createElement('span');
    span.innerHTML = getSettingsButtonTemplate().trim();
    widemodeNode.appendChild(span);

    const popup = document.createElement('div');
    popup.classList.add('ta-popup');
    popup.style.display = 'none';
    popup.innerHTML = '\
      <style>\
        .ta-popup {\
          position: fixed;\
          bottom: 50px;\
          right: 0;\
          width: 276px;\
          z-index: 999;\
          background: #ffffff;\
          border-radius: 10px 0 0 10px;\
          padding: 16px;\
          box-sizing: border-box;\
          border: 1px solid #ccc;\
        }\
      </style>\
      <div id="settings-popup-content"></div>\
    ';
    document.body.appendChild(popup);

    widemodeNode.querySelector('.ta-button')
      .addEventListener('click', () => {
      if (popup.style.display === 'none') {
        popup.style.display = 'block';
      } else {
        popup.style.display = 'none';
      }
    });

    const settingsNode = document.querySelector('#settings-popup-content');
    replaceSettingsForm(settingsNode);
  }

  if (window.location.href.includes('/settings')) {
    addLinkToNavigation();

    if (isTabunAvaSettingsPage()) {
      replaceSettingsForm();
    }
  }
}

function getAvatarsDocument() {
  return fetch(GSettings.remote.post)
    .then((response) => {
      return response.text();
    })
    .then((text) => {
      let domParser = new DOMParser();
      return domParser.parseFromString(text, "text/html");
    });
}

function fillAvatarsDictionary(avaDocument) {
  GAvaDictionary = {};
  const commentNodes = avaDocument.querySelectorAll('#comments .comment');

  commentNodes.forEach((commentNode) => {
    const authorNode = commentNode.querySelector('.comment-author');
    const username = authorNode && authorNode.textContent.trim();
    if (!username) return;

    const contentNode = commentNode.querySelector('.comment-content');
    const imageNode = contentNode && contentNode.querySelector('.text > img');

    if (imageNode && imageNode.hasAttribute('src')) {
      GAvaDictionary[username] = imageNode
        .getAttribute('src')
        .replace(GEveryponyCdnStorageRegex, ''); // Сокращаем количество сохраняемых символов
    }
  });
}

// Сохранить список аватаров в локальное хранилище браузера
function saveAvatarsDictionary() {
  const jsonString = JSON.stringify(GAvaDictionary);
  localStorage.setItem('TabunAvaReborn_Avatars', jsonString);
  localStorage.setItem('TabunAvaReborn_LastUpdate', Date.now().toString());
}

function refreshAvatarsStorage() {
  return getRemoteSettings()
    .then((remoteSettings) => {
      GSettings.remote = remoteSettings;
      localStorage.setItem('TabunAvaReborn_Settings', JSON.stringify(GSettings));
      return getAvatarsDocument();
    })
    .then((avaDocument) => {
      fillAvatarsDictionary(avaDocument);
      saveAvatarsDictionary();
    });
}

function shouldUpdateAvatarsStorage() {
  const lastUpdate = +(localStorage.getItem('TabunAvaReborn_LastUpdate') || '0');
  return (Date.now() - lastUpdate) > getRefreshMillis(); // прошло больше 10 минут с последнего обновления
}

// Достать список аватаров из локального хранилища браузера или обновить из поста
function loadAvatarsDictionary() {
  if (shouldUpdateAvatarsStorage()) {
    return refreshAvatarsStorage();
  } else {
    const jsonString = localStorage.getItem('TabunAvaReborn_Avatars');
    if (jsonString) {
      try {
        GAvaDictionary = JSON.parse(jsonString);
        return Promise.resolve();
      } catch (e) {
        return refreshAvatarsStorage();
      }
    }
  }
}

function isDefaultAvatar(link) {
  if(GSettings.noregularava) return true;
  return /(\/local\/avatar_male_)/.test(link) || /(\/local\/avatar_female)/.test(link);
}

function getIdenticonAvatar(username) {
  return 'https://api.dicebear.com/8.x/identicon/svg?seed=' + username + '&scale=100&size=48';
}

function getNewTabunAvatar(username) {
  if(GSettings.notabunava) return false;
  if (GAvaDictionary[username]) {
    if (!/^(https?:)?\/\//.test(GAvaDictionary[username])) {
      return GEveryponyCdnStorageLink + GAvaDictionary[username];
    }
    return GAvaDictionary[username];
  }
  return false;
}

function isGIF(src) {
  return src.includes('.gif');
}

function freezeGIF(imageNode) {
  const c = document.createElement('canvas');
  const w = c.width = imageNode.width;
  const h = c.height = imageNode.height;
  c.getContext('2d').drawImage(imageNode, 0, 0, w, h);
  try {
    imageNode.src = c.toDataURL("image/gif"); // if possible, retain all css aspects
  } catch (e) { // cross-domain -- mimic original with all its tag attributes
    for (let j = 0, a; a = imageNode.attributes[j]; j++)
      c.setAttribute(a.name, a.value);
    imageNode.parentNode.replaceChild(c, imageNode);
  }
}

function getBlackList() {
  let blacklist = GSettings.blacklist
    .replace(/ /g, '')
    .split(',');

  if (Array.isArray(GSettings.remote && GSettings.remote.blacklist)) {
    blacklist = blacklist.concat(GSettings.remote.blacklist);
  }

  return blacklist;
}

function replaceAvatarInImageNode(imageNode, username) {
  const ignore = getBlackList();
  const tabunAvatar = getNewTabunAvatar(username);
  if (tabunAvatar && !ignore.includes(username)) {
    if(GSettings.priority || !imageNode.getAttribute('src') || isDefaultAvatar(imageNode.getAttribute('src'))) {
      imageNode.setAttribute('src', tabunAvatar);

      if (!GSettings.animated && isGIF(tabunAvatar)) {
        freezeGIF(imageNode);
      }
    }
  } else if (
    !imageNode.getAttribute('src')
    || isDefaultAvatar(imageNode.getAttribute('src'))
  ) {
    if (GSettings.faceless === 'identicon') {
      imageNode.setAttribute('src', getIdenticonAvatar(username));
    } else if (GSettings.faceless === 'swarm') {
      const domain = '//cdn.everypony.ru/storage/00/28/16/2020/03/19/';
      const src = imageNode.getAttribute('src');

      if (src.includes('female_48x48.png')) {
        imageNode.setAttribute('src', domain + 'be9038d210.jpg');
      } else if (src.includes('male_48x48.png')) {
        imageNode.setAttribute('src', domain + '02dcb0e9c1.jpg');
      } else if (src.includes('female_24x24.png')) {
        imageNode.setAttribute('src', domain + 'f46f457af7.jpg');
      } else if (src.includes('male_24x24.png')) {
        imageNode.setAttribute('src', domain + 'b76b8f4e75.jpg');
      } else if (src.includes('female')) {
        imageNode.setAttribute('src', domain + '4d43849b81.jpg');
      } else if (src.includes('male')) {
        imageNode.setAttribute('src', domain + '4dac2ae27e.jpg');
      }
    } else if (GSettings.faceless === 'other' && GSettings.faceless_picture) {
      if (imageNode.getAttribute('src').includes('female') && GSettings.faceless_picture_f) {
        imageNode.setAttribute('src', GSettings.faceless_picture_f);
      }
      else {
        imageNode.setAttribute('src', GSettings.faceless_picture);
      }
    }
  }
}

// Замена собственного аватара пользователя в шапке страницы
function replaceHeaderAvatar() {
  const imageNode = document.querySelector('#dropdown-user img');
  if (!imageNode) return;

  const usernameNode = document.querySelector("#dropdown-user .username");
  const username = usernameNode && usernameNode.textContent.trim();
  if (!username) return;

  replaceAvatarInImageNode(imageNode, username);
}

// Замена аватара пользователя в профиле
function replaceProfileAvatar() {
  const imageNode = document.querySelector('.profile img.avatar');
  if (!imageNode) return;

  const usernameNode = document.querySelector(".profile [itemprop=nickname]");
  const username = usernameNode && usernameNode.textContent.trim();
  if (!username) return;

  replaceAvatarInImageNode(imageNode, username);
}

// Замена аватара друзей в профиле
function replaceProfileFriendAvatars() {
  const userNodes = document.querySelectorAll('.user-list-avatar > *');
  userNodes.forEach((userNode) => {
    const imageNode = userNode.querySelector('img');
    if (!imageNode) return;

    const username = userNode.textContent.trim();
    if (!username) return;

    imageNode.width = 48;
    imageNode.height = 48;

    replaceAvatarInImageNode(imageNode, username);
  });
}

// Замена аватара автора поста
function replaceTopicAuthorAvatars() {
  const topicNodes = document.querySelectorAll('.topic');
  topicNodes.forEach((topicNode) => {
    const imageNode = topicNode.querySelector('.avatar');
    if (!imageNode) return;

    const usernameNode = topicNode.querySelector("[rel=author]");
    const username = usernameNode && usernameNode.textContent.trim();
    if (!username) return;

    replaceAvatarInImageNode(imageNode, username);
  });
}

// Замена аватаров в комментариях
function replaceCommentAvatars() {
  const commentNodes = document.querySelectorAll('.comment');
  commentNodes.forEach((commentNode) => {
    const authorNode = commentNode.querySelector('.comment-author');
    const username = authorNode && authorNode.textContent.trim();
    if (!username) return;
    const imageNode = commentNode.querySelector('img.comment-avatar');
    if (!imageNode) return;
    replaceAvatarInImageNode(imageNode, username);
  });
}

// Замена аватаров в ленте активности
function replaceStreamAvatars() {
  const streamNodes = document.querySelectorAll('.stream-item');
  streamNodes.forEach((streamNode) => {
    const authorNode = streamNode.querySelector('.info a');
    const username = authorNode && authorNode.textContent.trim();
    if (!username) return;

    const imageNode = streamNode.querySelector('img');
    if (!imageNode) return;

    replaceAvatarInImageNode(imageNode, username);
  });
}

// Замена аватаров в окошках просмотра оценок
function replaceVoteAvatars() {
  const voteNodes = document.querySelectorAll('.vote-list-item');
  voteNodes.forEach((voteNode) => {
    const authorNode = voteNode.querySelector('.ls-user');
    const username = authorNode && authorNode.textContent.trim();
    if (!username) return;

    const imageNode = authorNode.querySelector('img');
    if (!imageNode) return;

    replaceAvatarInImageNode(imageNode, username);
  });
}

// Замена аватаров в разделе "Брони"
function replacePeopleAvatars() {
  const userNodes = document.querySelectorAll('.table-users .cell-name');
  userNodes.forEach((userNode) => {
    const authorNode = userNode.querySelector('.username');
    const username = authorNode && authorNode.textContent.trim();
    if (!username) return;

    const imageNode = userNode.querySelector('img');
    if (!imageNode) return;

    imageNode.width = 48;
    imageNode.height = 48;

    replaceAvatarInImageNode(imageNode, username);
  });
}

// Замена аватаров в блоке "Пожертвования"
function replaceDonationAvatars() {
  const userNodes = document.querySelectorAll('.donation-list > *');
  userNodes.forEach((userNode) => {
    const username = userNode.textContent.trim();
    if (!username) return;

    const imageNode = userNode.querySelector('img');
    if (!imageNode) return;

    imageNode.width = 24;
    imageNode.height = 24;

    replaceAvatarInImageNode(imageNode, username);
  });
}
// Замена аватаров в блоке "Пожертвования"
function replaceProfileSettingsAvatar() {
  const imageNode = document.querySelector('#avatar-img');
  if (!imageNode) return;

  const usernameNode = document.querySelector("#dropdown-user .username");
  const username = usernameNode && usernameNode.textContent.trim();
  if (!username) return;

  replaceAvatarInImageNode(imageNode, username);
}

function replaceAvatarsOnCommentsRefresh() {
  const countCommentsNode = document.querySelector('#count-comments');
  if (!countCommentsNode) return;

  countCommentsNode.addEventListener('DOMSubtreeModified', () => {
    replaceCommentAvatars();
  });
}

function replaceAvatarsOnVotesRefresh() {
  document.addEventListener('DOMSubtreeModified', () => {
    replaceVoteAvatars();
  });
}

function replaceAvatarsOnRepliesRefresh() {
  const avatarNodes = document.querySelectorAll('.tabun-replies-container img.comment-avatar')
  avatarNodes.forEach((imageNode) => {
    const authorNode = imageNode.parentElement
    if(!authorNode || !authorNode.getAttribute('href')) return;
    const username = authorNode.getAttribute('href').replace('/profile/', '').replace('/', '')
    if (!username) return;
    replaceAvatarInImageNode(imageNode, username);
  })
}

function getFileBase64(file) {
  return new Promise((resolve, reject) => {
    const reader = new FileReader();
    reader.readAsDataURL(file);
    reader.onload = (e) => {
      resolve(e.target.result);
    };

    reader.onerror = reject;
  })
}

function getImageDimensions(url) {
  const img = document.createElement('img');

  const promise = new Promise((resolve, reject) => {
    img.onload = () => {
      // Natural size is the actual image size regardless of rendering.
      // The 'normal' width/height are for the **rendered** size.
      const width  = img.naturalWidth;
      const height = img.naturalHeight;

      // Resolve promise with the width and height
      resolve({ width, height });
    };

    // Reject promise on error
    img.onerror = reject;
  });

  // Setting the source makes it start downloading and eventually call onload
  img.src = url;

  return promise;
}

function initAvatarUpload() {
  const avatarUploadNode = document.querySelector('#avatar-upload');
  const avatarRemoveNode = document.querySelector('#avatar-remove');
  const securityLsKey = document.querySelector('[name=security_ls_key]');
  if (!securityLsKey || !avatarUploadNode) return;

  if (avatarRemoveNode) {
    avatarRemoveNode.style.display = 'none';
  }

  const input = document.createElement('input')
  input.type = 'file';
  input.accept = 'image/jpeg,image/png,image/gif';

  input.addEventListener('change', () => {
    if (!input.files[0]) return;

    getFileBase64(input.files[0])
      .then((base64) => {
        return getImageDimensions(base64)
      })
      .then((dimensions) => {
        if (dimensions.width > 100 || dimensions.height > 100) {
          throw new Error('Максимальный размер картинки - 100x100 пикселей');
        }

        const uploadBody = new FormData();
        uploadBody.append('img_file', input.files[0]);
        uploadBody.append('security_ls_key', securityLsKey.value);

        return fetch('https://tabun.everypony.ru/ajax/upload/image/', {
          method: 'POST',
          body: uploadBody,
        });
      })
      .then((response) => {
        if (!response.ok) {
          throw new Error('Не удалось загрузить автар, обратитесь к создателю скрипта. Код ошибки: UPLOAD_IMAGE_ERR');
        }

        return response.text();
      })
      .then((text) => {
        if (!text) {
          throw new Error('Не удалось загрузить автар, обратитесь к создателю скрипта. Код ошибки: UPLOAD_IMAGE_EMPTY');
        }

        text = text.replace(/&quot;/g, '"')
          .replace(/\\\//g, '/');

        const match = text.match(/src=\\"([^ ]+)\\"/);

        if (!(match && match[1])) {
          throw new Error('Не удалось загрузить автар, обратитесь к создателю скрипта. Код ошибки: NO_MATCH');
        }

        const postIdMatches = GSettings.remote.post.match(/(\d+).html$/);

        if (!postIdMatches || !postIdMatches[1]) {
          throw new Error('Не удалось загрузить автар, обратитесь к создателю скрипта. Код ошибки: NO_POST_ID');
        }

        const commentBody = new FormData();
        commentBody.append('comment_text', '<img src="' + match[1] + '" alt="avatar">');
        commentBody.append('reply', '0');
        commentBody.append('cmt_target_id', postIdMatches[1]);
        commentBody.append('security_ls_key', securityLsKey.value);

        return fetch('https://tabun.everypony.ru/blog/ajaxaddcomment/', {
          method: 'POST',
          body: commentBody,
        })
      })
      .then((response) => {
        if (!response.ok) {
          throw new Error('Не удалось загрузить автар, обратитесь к создателю скрипта. Код ошибки: ADD_COMMENT_ERR');
        }

        localStorage.setItem('TabunAvaReborn_LastUpdate', '0');
        window.location.reload();
      })
      .catch((error) => {
        alert(error.message);
      });
  });

  avatarUploadNode.addEventListener('click', (e) => {
    e.preventDefault();
    input.click();
  });
}

function replaceHeaderText() {
  if (GSettings.header_text) {
    const logoNode = document.querySelector('a#logolink');
    if (!logoNode) return;

    logoNode.text = GSettings.header_text;
  }
}

function fixStyles() {
  var styleSheet = document.createElement("style");
  styleSheet.innerText = GSettings.usercss;

  if (GSettings.fixavacorners) {
    styleSheet.innerText += "img.comment-avatar {border-radius: 0px !important; } ";
  }
  if (GSettings.oldauthor) {
    styleSheet.innerText += ".comment-info .comment-author.comment-topic-author span { color: #4b5468; } ";
    styleSheet.innerText += ".comment-info .comment-author.comment-topic-author::after { display: none; } ";
  }
  document.head.appendChild(styleSheet);
}

function updateMargins() {
  const itm = document.querySelectorAll("dl.form-item");
  if (!itm) return;
  if (window.innerHeight > 1200) {
    itm.forEach((i) => { i.style['marginBottom'] = '' });
  } else {
    itm.forEach((i) => { i.style['marginBottom'] = '2px' });
  }
}

getSettings()
  .then((settings) => {
    GSettings = settings;

    initSettingsPage();
    initAvatarUpload();

    replaceHeaderText();
    fixStyles();
    updateMargins();
    window.addEventListener('resize', updateMargins);

    loadAvatarsDictionary()
      .then(() => {
        var commentsNode = document.querySelector('#content-wrapper');
        var repliesNode = document.querySelector('.tabun-replies-container');
        if (commentsNode) new MutationObserver(replaceAvatarsOnCommentsRefresh).observe(commentsNode, {childList: true, subtree: true});
        if (commentsNode) new MutationObserver(replaceAvatarsOnVotesRefresh).observe(commentsNode, {childList: true, subtree: true});
        if (repliesNode) new MutationObserver(replaceAvatarsOnRepliesRefresh).observe(repliesNode, {childList: true, subtree: true});

        replaceHeaderAvatar();
        replaceCommentAvatars();
        replaceTopicAuthorAvatars();
        replaceProfileAvatar();
        replaceProfileFriendAvatars();
        replaceStreamAvatars();
        replacePeopleAvatars();
        replaceDonationAvatars();
        replaceProfileSettingsAvatar();
      });
  })