n30v1m / TGStat Exporter

// ==UserScript==
// @name        TGStat Exporter
// @namespace   Scripts
// @author      n30v1m
// @match       https://tgstat.ru/channels/search*
// @grant       none
// @version     1.13
// @require     https://cdn.jsdelivr.net/npm/@violentmonkey/dom@2
// @description TGStat Search CSV Exporter Button
// @license     MIT
// @downloadURL https://openuserjs.org/install/n30v1m/TGStat_Exporter.user.js
// @updateURL   https://openuserjs.org/meta/n30v1m/TGStat_Exporter.meta.js
// ==/UserScript==

(function () {
  'use strict';

  let channelsData = [];
  let uniqueLinks = new Set();
  let downloadButtonText = "Download 0";

  function convertLink(tgstatLink) {
    if (!tgstatLink || typeof tgstatLink !== 'string') {
      return "";
    }

    const usernameMatch = tgstatLink.match(/@([\w\d_]+)/);
    if (usernameMatch && usernameMatch[1]) {
      return `https://t.me/${usernameMatch[1]}`;
    }

    if (tgstatLink.includes('tgstat.ru/channel/')) {
      const basePathMatch = tgstatLink.match(/(https:\/\/tgstat\.ru\/channel\/[^/?#]+)/);
      if (basePathMatch && basePathMatch[1]) {
        return basePathMatch[1];
      }
      return tgstatLink;
    }
    return "";
  }

  function csvSafe(value) {
    if (value === null || typeof value === 'undefined') {
      return '';
    }
    let strValue = String(value);
    strValue = strValue.replace(/[\r\n]+/g, ' ');
    strValue = strValue.replace(/"/g, '""');
    if (strValue.includes(',') || strValue.includes('"')) {
      return `"${strValue}"`;
    }
    return strValue;
  }

  function parseChannels() {
    let newChannelsFound = false;
    document.querySelectorAll('.peer-item-row').forEach(channelElement => {
      let name = channelElement.querySelector('.row .font-16')?.textContent.trim() || "Unknown";
      let linkAnchor = channelElement.querySelector('a[href*="/channel/"]');
      let tgstatPageLink = "";

      if (linkAnchor) {
        tgstatPageLink = new URL(linkAnchor.getAttribute('href'), document.baseURI).href;
      }

      let finalLink = convertLink(tgstatPageLink);

      if (finalLink && !uniqueLinks.has(finalLink)) {
        channelsData.push({
          'name': name,
          'link': finalLink
        });
        uniqueLinks.add(finalLink);
        newChannelsFound = true;
      }
    });

    if (newChannelsFound || (channelsData.length > 0 && downloadButtonText === "Download 0") || (channelsData.length === 0 && document.querySelector('.peer-item-row') && downloadButtonText === "Download 0")) {
      downloadButtonText = "Download " + channelsData.length;
      updateDownloadButton();
    }
    else if (channelsData.length === 0 && !document.querySelector('.peer-item-row')) {
      downloadButtonText = "Download 0";
      updateDownloadButton();
    }
  }

  function downloadCSV() {
    if (channelsData.length === 0) {
      alert("Нет данных для экспорта.");
      return;
    }

    const csvHeader = "название канала,ссылка\n";
    const csvRows = channelsData.map(e => {
      const name = csvSafe(e.name);
      const link = csvSafe(e.link);
      return `${name},${link}`;
    }).join("\n");

    const csvString = csvHeader + csvRows;
    const BOM = "";
    const blob = new Blob([BOM + csvString], {
      type: 'text/csv;charset=utf-8;'
    });
    const url = URL.createObjectURL(blob);

    const linkElement = document.createElement("a");
    linkElement.setAttribute("href", url);
    linkElement.setAttribute("download", "tgstat_channels.csv");
    linkElement.style.visibility = 'hidden';
    document.body.appendChild(linkElement);
    linkElement.click();
    document.body.removeChild(linkElement);
    URL.revokeObjectURL(url);
  }

  let prevButton = null;

  function updateDownloadButton() {
    const container = document.querySelector('#sticky-right-column__inner > div > div');
    if (container) {
      if (prevButton) {
        prevButton.remove();
      }
      let button = document.createElement("button");
      prevButton = button;
      button.innerText = downloadButtonText;
      button.className = "btn btn-warning w-100 mt-2";
      button.setAttribute("id", "gtstat-csv-download-button-111");
      button.addEventListener("click", function (event) {
        event.preventDefault();
        downloadCSV();
      });
      container.appendChild(button);
    }
  }

  parseChannels();

  VM.observe(document.body, () => {
    const targetNode = document.querySelector('.channels-list');
    if (targetNode) {
      VM.observe(targetNode, () => {
        parseChannels();
      });
      return true;
    }
    return false;
  });

})();