Ezekiel / Acc Chat logger

// ==UserScript==
// @name         Acc Chat logger
// @namespace    http://tampermonkey.net/
// @version      0.0.3
// @copyright    2023, Ezekiel (https://openuserjs.org/users/Ezekiel)
// @license      GPL-3.0-or-later
// @description  AAC Chat logger
// @author       Ezekiel
// @include      /^https?:\/\/(www\.)?anime\.academy\/chat/
// @icon         https://www.google.com/s2/favicons?domain=anime.academy
// @grant none
// ==/UserScript==



(async function () {
  'use strict';

  /************************************
   *
   *
   * CSS
   *
   *
   ************************************/

  function addGlobalStyle(css) {
    let head, style;
    head = document.getElementsByTagName('head')[0];
    if (!head) {
      return;
    }
    style = document.createElement('style');
    style.type = 'text/css';
    style.innerHTML = css;
    head.appendChild(style);
  }

  addGlobalStyle(
    '.autocomplete-flex {' +
      '  background-color: #484b52;' +
      '  font-weight: 600;' +
      '  display: flex;' +
      '  align-items: center;' +
      '  gap: 10px;' +
      '  padding: 5px;' +
      '}' +
      '.autocomplete-flex:hover {' +
      '  background-color: #5f646e;' +
      '}' +
      '' +
      '.autocomplete-flex:focus {' +
      '  background-color: #5f646e;' +
      '}' +
      '' +
      '.name-link:hover {' +
      '  text-decoration: none;' +
      '}' +
      '.autocomplete-icon {' +
      '  width: 45px !important;' +
      '  height: 45px !important;' +
      '  border-radius: 50%;' +
      '}' +
      '.chatMessage {' +
      '  transition: padding 0.4s ease 0s;' +
      '}' +
      '.chatMessage:hover {' +
      '  padding-top: 5px;' +
      '  padding-bottom: 5px;' +
      '  position: relative;' +
      '  background-color: #5f646e !important;' +
      '  border-radius: 3px;' +
      '  transition: padding 0.4s ease 0s;' +
      '}' +
      '.messageIcons {' +
      '  display: flex;' +
      '  justify-content: center;' +
      '  align-items: center;' +
      '  height: 32px;' +
      '  width: 32px;' +
      '  background-color: #484b52;' +
      '  position: absolute;' +
      '  right: 0;' +
      '  top: -20px;' +
      '  display: none;' +
      '  border-radius: 3px;' +
      '}' +
      '.messageIcons:hover {' +
      '  background-color: #5f646e;' +
      '  filter: drop-shadow(5px 7px 14px black);' +
      '}' +
      '' +
      '.collapsible-wrap {' +
      '  margin: 10px 0 10px 0;' +
      '}' +
      '' +
      '.collapsible-button {' +
      '  cursor: pointer;' +
      '  padding: 18px;' +
      '  width: 100%;' +
      '  text-align: left;' +
      '  outline: none;' +
      '  font-size: 15px;' +
      '}' +
      '' +
      '.collapsible-button:focus {' +
      '  transition: background-color 0.5s ease;' +
      '  background-color: #3e1e80 !important;' +
      '}' +
      '' +
      '.collapsible-button:hover {' +
      '  transition: background-color 0.5s ease;' +
      '}' +
      '' +
      '.active {' +
      '  border-bottom-right-radius: 0 !important;' +
      '  border-bottom-left-radius: 0 !important;' +
      '  background-color: #3e1e80 !important;' +
      '}' +
      '' +
      '.collapsible-button:after {' +
      "  content: '\\002B';" +
      '  font-weight: bold;' +
      '  float: right;' +
      '  margin-left: 5px;' +
      '}' +
      '' +
      '.active:after {' +
      "  content: '\\2212';" +
      '}' +
      '' +
      '.collapsible-content {' +
      '  padding: 0 10px;' +
      '  max-height: 0;' +
      '  overflow: hidden;' +
      '  transition: max-height 0.2s ease-out;' +
      '  background-color: #2c2f33;' +
      '  border-bottom-left-radius: 3px;' +
      '  border-bottom-right-radius: 3px;' +
      '}' +
      '' +
      '.avatar-grid {' +
      '  width: 100%;' +
      '  display: grid;' +
      '  grid-template-columns: repeat(auto-fill, 8em);' +
      '  grid-gap: 10px 15px;' +
      '}' +
      '' +
      '.avatar-div {' +
      '  height: 80px;' +
      '  width: 80px;' +
      '  display: flex;' +
      '  align-items: center;' +
      '  justify-content: center;' +
      '  gap: 5px;' +
      '}' +
      '' +
      '.avatar-div:hover {' +
      '  background-color: #484b52;' +
      '  transition: background-color 0.5s ease;' +
      '  border-radius: 3px;' +
      '}' +
      '' +
      '.avatarIcon {' +
      '  height: 20px;' +
      '  width: 20px;' +
      '  background-color: #3e1e80;' +
      '  border-radius: 50%;' +
      '  display: none;' +
      '  align-items: center;' +
      '  justify-content: center;' +
      '}' +
      '' +
      '.avatarIcon:hover {' +
      '  background-color: #6b36d9;' +
      '  filter: drop-shadow(5px 7px 14px #2c2f33);' +
      '  transition: background-color 0.5s ease;' +
      '}' +
      '' +
      '.ionicon {' +
      '  width: 13px;' +
      '  height: 13px;' +
      '  fill: #ddd;' +
      '}' +
      '' +
      '.collection-selection {' +
      '  position: absolute;' +
      '  z-index: 10000;' +
      '  margin-left: auto;' +
      '  margin-right: auto;' +
      '  right: 0;' +
      '  left: 0;' +
      '  top: 80px;' +
      '  width: 300px;' +
      '  background-color: #484b52;' +
      '  padding: 10px;' +
      '  text-align: center;' +
      '  overflow: hidden scroll;' +
      '  border-radius: 3px;' +
      '  max-height: 200px;' +
      '}' +
      '' +
      '.collection-selectable {' +
      '  font-weight: 700;' +
      '  padding: 10px 0;' +
      '  margin-bottom: 5px;' +
      '}' +
      '' +
      '.collection-selectable:hover {' +
      '  background-color: #3e1e80;' +
      '  border-radius: 3px;' +
      '  transition: background-color 0.5s ease;' +
      '  filter: drop-shadow(5px 7px 14px #2c2f33);' +
      '}' +
      '' +
      '#createCollectionBtn {' +
      '  font-weight: 700;' +
      '}' +
      '' +
      '.removeCategory-btn {' +
      '  background-color: #fd2f2f;' +
      '}' +
      '' +
      '.request-delete-collection {' +
      '  position: absolute;' +
      '  z-index: 10000;' +
      '  margin-left: auto;' +
      '  margin-right: auto;' +
      '  right: 0;' +
      '  left: 0;' +
      '  top: 80px;' +
      '  width: 300px;' +
      '  background-color: #484b52;' +
      '  padding: 10px;' +
      '  text-align: center;' +
      '  overflow: hidden scroll;' +
      '  border-radius: 3px;' +
      '  max-height: 200px;' +
      '}' +
      '' +
      '.message-image {' +
      '  width: 100%;' +
      '  height: auto;' +
      '  border-radius: 3px;' +
      '}' +
      '' +
      '.message-image-container {' +
      '  margin-top: 5px;' +
      '}' +
      '' +
      '#username-results {' +
      '  border: none;' +
      '  max-height: 200px;' +
      '  overflow: hidden scroll;' +
      '  max-width: 30%;' +
      '  position: absolute;' +
      '  left: 0;' +
      '  right: 0;' +
      '  bottom: calc(100% + 8px);' +
      '  margin-left: 8px;' +
      '  border-radius: 5px;' +
      '}' +
      ''
  );


// Necessary for autoreconnect
  window.onbeforeunload = null;

  const scope = angular.element(document.getElementById('topbar')).scope();
  /************************************
   *
   * Global Socket Hook
   *
   ************************************/

  const globalSocketReady = new Event('globalSocketReady');

  io.Socket.prototype.o_emit = io.Socket.prototype.o_emit || io.Socket.prototype.emit;
  io.Socket.prototype.emit = function (eventName, ...args) {
    if (!window.socket) {
      window.socket = this;
      window.dispatchEvent(globalSocketReady);
    }

    window.dispatchEvent(new CustomEvent('socketEmit', { detail: { eventName: eventName, args: [...args] } }));

    return this.o_emit(eventName, ...args);
  };

  /************************************
   *
   * Autoreconnnect
   *
   ************************************/

  const disconnectReasons = [
    'transport error',
    'transport close',
    'io client disconnect',
    'io server disconnect',
    'ping timeout',
  ];

  const disconnected = JSON.parse(localStorage.getItem('disconnected'));

  if (disconnected) {
    localStorage.setItem('disconnected', JSON.stringify(false));
    window.addEventListener('globalSocketReady', () => {
      setTimeout(() => {
        window.socket.emit('moveAvatar', JSON.parse(localStorage.getItem('avatarPosition')));
      }, 1500);
    });
  }

  window.addEventListener('socketEmit', (event) => {
    if (event.detail.eventName === 'moveAvatar') {
      localStorage.setItem('avatarPosition', JSON.stringify(event.detail.args[0]));
    }
    if (event.detail.eventName === 'disconnect' && disconnectReasons.includes(event.detail.args[0])) {
      localStorage.setItem('disconnected', JSON.stringify(true));
      location.reload();
    }
  });

  /************************************
   *
   * Reload and log Chat History
   *
   ************************************/


const maxStoredMessagesPerRoom = 999999;
const maxMessageAge = 1000 * 60 * 999999; // 999999 Minutes

window.addEventListener('globalSocketReady', () => {
  window.socket.on('updateChatLines', (data) => {
    const currentRoom = window.location.href.split('=')[1];

    let roomData;

    if (!localStorage.hasOwnProperty('rooms')) {
      localStorage.setItem('rooms', JSON.stringify({}));
    }
    if (!JSON.parse(localStorage.getItem('rooms')).hasOwnProperty(currentRoom)) {
      roomData = JSON.parse(localStorage.getItem('rooms'));
      roomData[currentRoom] = {
        messages: [],
      };
      roomData = JSON.stringify(roomData);
      localStorage.setItem('rooms', roomData);
    }

    roomData = JSON.parse(localStorage.getItem('rooms'));
    if (data.user !== 'System') roomData[currentRoom].messages.push(data);
    if (roomData[currentRoom].messages.length > maxStoredMessagesPerRoom) roomData[currentRoom].messages.splice(0, 1);

    roomData = JSON.stringify(roomData);
    localStorage.setItem('rooms', roomData);

    restoreChatlogs();
  });

  window.addEventListener('socketEmit', (event) => {
    if (event.detail.eventName === 'changeRoom') {
      setTimeout(() => {
        restoreChatlogs();
      }, 100);
    }
  });

  setInterval(() => {
    restoreChatlogsWithTimestamp();
  }, 240000); // 4 minutes

  restoreChatlogsWithTimestamp();
});

function restoreChatlogsWithTimestamp() {
  const currentRoom = window.location.href.split('=')[1];

  if (!JSON.parse(localStorage.getItem('rooms')).hasOwnProperty(currentRoom)) {
    return;
  }

  const roomData = JSON.parse(localStorage.getItem('rooms'))[currentRoom];

  const currentDate = new Date();
  const timestamp =
    currentDate.getFullYear() +
    '-' +
    (currentDate.getMonth() + 1).toString().padStart(2, '0') +
    '-' +
    currentDate.getDate().toString().padStart(2, '0') +
    '-' +
    currentDate.getHours().toString().padStart(2, '0') +
    currentDate.getMinutes().toString().padStart(2, '0') +
    '-';

  const blob = new Blob([JSON.stringify(roomData.messages, null, 2)], { type: 'text/plain' });
  const url = URL.createObjectURL(blob);

  const link = document.createElement('a');
  link.setAttribute('href', url);
  link.setAttribute('download', `${currentRoom}-${timestamp}.txt`);
  link.style.display = 'none';
  document.body.appendChild(link);

  link.click();

  setTimeout(() => {
    URL.revokeObjectURL(url);
    document.body.removeChild(link);
  }, 0);
}
  /************************************
   *
   * Chat Data Collector
   *
   ************************************/
if (localStorage.hasOwnProperty('rooms')) {
  const chatLogs = JSON.parse(localStorage.getItem('rooms'));

  if (chatLogs) {
    for (const room in chatLogs) {
      const tooOldMessages = [];
      for (const message in chatLogs[room].messages) {
        if (Date.now() - chatLogs[room].messages[message].timestamp > maxMessageAge) {
          tooOldMessages.push(message);
        }
      }

      for (const index of tooOldMessages) {
        chatLogs[room].messages.splice(index, 1);
      }

      if (chatLogs[room].messages.length === 0) {
        delete chatLogs[room];
      }
    }
  }

  localStorage.setItem('rooms', JSON.stringify(chatLogs));
}

    })();