Steam Rep4Rep - Comment to Friends

Add the ability to comment on multiple friends' profiles at once on Steam friends page. Select friends with checkboxes and post comments in bulk with customizable delay between posts.

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         Steam Rep4Rep - Comment to Friends
// @name:pt      Steam Rep4Rep - Comentar em Amigos
// @namespace    http://tampermonkey.net/
// @version      1.1
// @description  Add the ability to comment on multiple friends' profiles at once on Steam friends page. Select friends with checkboxes and post comments in bulk with customizable delay between posts.
// @description:pt Adiciona a funcionalidade de comentar em múltiplos amigos na página de amigos do Steam. Selecione amigos com checkboxes e poste comentários em lote com delay personalizável entre posts.
// @author       bernardopg
// @icon         https://store.steampowered.com/favicon.ico
// @match        https://steamcommunity.com/*/friends
// @match        https://steamcommunity.com/*/friends/
// @match        https://steamcommunity.com/id/*/friends
// @match        https://steamcommunity.com/id/*/friends/
// @match        https://steamcommunity.com/profiles/*/friends
// @match        https://steamcommunity.com/profiles/*/friends/
// @grant        none
// @license      MIT
// @homepage     https://github.com/bernardopg/steam-rep4rep
// @supportURL   https://github.com/bernardopg/steam-rep4rep/issues
// ==/UserScript==

(function () {
  "use strict";

  // Sistema de internacionalização
  const translations = {
    en: {
      title: "Rep4Rep - Comment to Friends",
      commentPlaceholder: "Type your comment here...",
      selectAll: "Select All",
      deselectAll: "Deselect All",
      postComments: "Post Comments",
      selectFriendsAlert: "Please select at least one friend.",
      typeCommentAlert: "Please type a comment.",
      startingProcess: "Starting comment posting...",
      processing: "Processed",
      friends: "friends",
      processComplete: "Process completed!",
      successPosted: "Comment successfully posted to",
      errorPosting: "Error posting to",
      connectionFailed: "Connection failed for",
      unknownError: "Unknown error",
    },
    pt: {
      title: "Rep4Rep - Comentar em Amigos",
      commentPlaceholder: "Digite seu comentário aqui...",
      selectAll: "Selecionar Todos",
      deselectAll: "Desmarcar Todos",
      postComments: "Postar Comentários",
      selectFriendsAlert: "Por favor, selecione pelo menos um amigo.",
      typeCommentAlert: "Por favor, digite um comentário.",
      startingProcess: "Iniciando postagem de comentários...",
      processing: "Processados",
      friends: "amigos",
      processComplete: "Processo concluído!",
      successPosted: "Comentário postado com sucesso para",
      errorPosting: "Erro ao postar para",
      connectionFailed: "Falha na conexão para",
      unknownError: "Erro desconhecido",
    },
  };

  // Detecta o idioma baseado na URL ou configuração do Steam
  function detectLanguage() {
    const urlParams = new URLSearchParams(window.location.search);
    const urlLang = urlParams.get("l");

    // Mapeia idiomas do Steam para nossos códigos
    const steamLangMap = {
      brazilian: "pt",
      portuguese: "pt",
      english: "en",
    };

    // Verifica URL, HTML lang ou padrão para inglês
    const htmlLang = document.documentElement.lang;
    const detectedLang =
      steamLangMap[urlLang] ||
      steamLangMap[htmlLang] ||
      (htmlLang && htmlLang.startsWith("pt") ? "pt" : "en");

    return translations[detectedLang] || translations.en;
  }

  const t = detectLanguage();

  // Aguarda o carregamento da página
  function waitForElement(selector, callback) {
    if (document.querySelector(selector)) {
      callback();
    } else {
      setTimeout(() => waitForElement(selector, callback), 500);
    }
  }

  // Adiciona estilos CSS
  function addStyles() {
    const style = document.createElement("style");
    style.textContent = `
            .rep4rep-container {
                background: #1b2838;
                border: 1px solid #3c3c41;
                border-radius: 4px;
                padding: 15px;
                margin: 15px 0;
                color: #c6d4df;
            }

            .rep4rep-textarea {
                width: 100%;
                height: 60px;
                background: #0e1419;
                border: 1px solid #3c3c41;
                border-radius: 3px;
                color: #c6d4df;
                padding: 8px;
                font-family: Arial, sans-serif;
                font-size: 13px;
                resize: vertical;
            }

            .rep4rep-button {
                background: linear-gradient(to bottom, #75b022 5%, #588a1b 95%);
                border: 1px solid #4b8f29;
                border-radius: 2px;
                color: #d2e885;
                padding: 8px 15px;
                cursor: pointer;
                font-size: 12px;
                text-decoration: none;
                display: inline-block;
                margin: 5px 5px 0 0;
            }

            .rep4rep-button:hover {
                background: linear-gradient(to bottom, #588a1b 5%, #75b022 95%);
            }

            .rep4rep-button:disabled {
                background: #3c3c41;
                border-color: #3c3c41;
                color: #898989;
                cursor: not-allowed;
            }

            .rep4rep-checkbox {
                margin-right: 8px;
                transform: scale(1.2);
            }

            .rep4rep-friend-item {
                padding: 5px;
                margin: 2px 0;
                background: rgba(255, 255, 255, 0.02);
                border-radius: 3px;
                cursor: pointer;
                user-select: none;
            }

            .rep4rep-friend-item:hover {
                background: rgba(255, 255, 255, 0.05);
            }

            .rep4rep-friend-item.selected {
                background: rgba(117, 176, 34, 0.2);
                border: 1px solid #75b022;
            }

            .rep4rep-log {
                margin-top: 15px;
                padding: 10px;
                background: #0e1419;
                border-radius: 3px;
                max-height: 200px;
                overflow-y: auto;
                font-size: 12px;
            }

            .rep4rep-controls {
                margin: 10px 0;
            }

            .rep4rep-title {
                color: #75b022;
                font-size: 16px;
                font-weight: bold;
                margin-bottom: 10px;
            }
        `;
    document.head.appendChild(style);
  }

  // Cria a interface principal
  function createInterface() {
    const container = document.createElement("div");
    container.className = "rep4rep-container";
    container.innerHTML = `
            <div class="rep4rep-title">${t.title}</div>
            <textarea class="rep4rep-textarea" id="rep4rep-comment" placeholder="${t.commentPlaceholder}"></textarea>
            <div class="rep4rep-controls">
                <button class="rep4rep-button" id="rep4rep-select-all">${t.selectAll}</button>
                <button class="rep4rep-button" id="rep4rep-deselect-all">${t.deselectAll}</button>
                <button class="rep4rep-button" id="rep4rep-post-comments">${t.postComments}</button>
            </div>
            <div id="rep4rep-log" class="rep4rep-log" style="display: none;"></div>
        `;

    return container;
  }

  // Adiciona checkboxes aos amigos
  function addCheckboxesToFriends() {
    const friends = document.querySelectorAll(
      ".friend_block_v2, .selectable_friend"
    );

    friends.forEach((friend, index) => {
      if (friend.querySelector(".rep4rep-checkbox")) return; // Já foi processado

      const checkbox = document.createElement("input");
      checkbox.type = "checkbox";
      checkbox.className = "rep4rep-checkbox";
      checkbox.id = `rep4rep-friend-${index}`;

      // Tenta pegar o SteamID do amigo
      const steamidAttr =
        friend.getAttribute("data-steamid") ||
        friend.querySelector("[data-steamid]")?.getAttribute("data-steamid");

      if (steamidAttr) {
        checkbox.setAttribute("data-steamid", steamidAttr);

        // Adiciona o checkbox no início do elemento do amigo
        friend.style.position = "relative";
        friend.style.paddingLeft = "25px";
        checkbox.style.position = "absolute";
        checkbox.style.left = "5px";
        checkbox.style.top = "5px";
        checkbox.style.zIndex = "10";

        friend.appendChild(checkbox);

        // Adiciona evento de clique para selecionar/deselecionar
        friend.addEventListener("click", (e) => {
          if (e.target !== checkbox) {
            checkbox.checked = !checkbox.checked;
            friend.classList.toggle("rep4rep-selected", checkbox.checked);
          }
        });

        checkbox.addEventListener("change", () => {
          friend.classList.toggle("rep4rep-selected", checkbox.checked);
        });
      }
    });
  }

  // Função principal que inicializa o script
  function initialize() {
    addStyles();

    // Procura por onde inserir a interface
    const friendsContainer =
      document.querySelector(
        ".friends_content, .profile_friends, #friends_list"
      ) || document.querySelector(".responsive_page_content");

    if (friendsContainer) {
      const interfaceElement = createInterface();
      friendsContainer.insertBefore(
        interfaceElement,
        friendsContainer.firstChild
      );

      // Adiciona checkboxes aos amigos
      addCheckboxesToFriends();

      // Observer para detectar novos amigos carregados dinamicamente
      const observer = new MutationObserver(() => {
        addCheckboxesToFriends();
      });
      observer.observe(friendsContainer, { childList: true, subtree: true });

      // Event listeners
      document
        .getElementById("rep4rep-select-all")
        .addEventListener("click", () => {
          document.querySelectorAll(".rep4rep-checkbox").forEach((cb) => {
            cb.checked = true;
            cb.closest(".friend_block_v2, .selectable_friend").classList.add(
              "rep4rep-selected"
            );
          });
        });

      document
        .getElementById("rep4rep-deselect-all")
        .addEventListener("click", () => {
          document.querySelectorAll(".rep4rep-checkbox").forEach((cb) => {
            cb.checked = false;
            cb.closest(".friend_block_v2, .selectable_friend").classList.remove(
              "rep4rep-selected"
            );
          });
        });

      document
        .getElementById("rep4rep-post-comments")
        .addEventListener("click", postComments);
    }
  }

  // Função para postar comentários
  function postComments() {
    const selectedFriends = document.querySelectorAll(
      ".rep4rep-checkbox:checked"
    );
    const comment = document.getElementById("rep4rep-comment").value.trim();
    const logDiv = document.getElementById("rep4rep-log");

    if (selectedFriends.length === 0) {
      alert(t.selectFriendsAlert);
      return;
    }

    if (!comment) {
      alert(t.typeCommentAlert);
      return;
    }

    logDiv.style.display = "block";
    logDiv.innerHTML = `<div style="color: #75b022; font-weight: bold;">${t.startingProcess}</div>`;

    const total = selectedFriends.length;
    let processed = 0;

    selectedFriends.forEach((checkbox, index) => {
      const steamID = checkbox.getAttribute("data-steamid");

      setTimeout(() => {
        fetch(
          `https://steamcommunity.com/comment/Profile/post/${steamID}/-1/`,
          {
            method: "POST",
            headers: {
              "Content-Type": "application/x-www-form-urlencoded",
            },
            body: new URLSearchParams({
              comment: comment,
              count: "6",
              sessionid: window.g_sessionID || "",
            }),
          }
        )
          .then((response) => response.json())
          .then((data) => {
            processed++;
            if (data.success) {
              logDiv.innerHTML += `<br><span style="color: #75b022;">✓ ${t.successPosted} ${steamID}</span>`;
            } else {
              logDiv.innerHTML += `<br><span style="color: #d94441;">✗ ${
                t.errorPosting
              } ${steamID}: ${data.error || t.unknownError}</span>`;
            }

            logDiv.innerHTML = logDiv.innerHTML.replace(
              new RegExp(t.startingProcess + ".*"),
              `${t.processing} ${processed}/${total} ${t.friends}...`
            );

            if (processed === total) {
              logDiv.innerHTML += `<br><div style="color: #75b022; font-weight: bold; margin-top: 10px;">${t.processComplete}</div>`;
            }

            logDiv.scrollTop = logDiv.scrollHeight;
          })
          .catch((error) => {
            processed++;
            logDiv.innerHTML += `<br><span style="color: #d94441;">✗ ${t.connectionFailed} ${steamID}</span>`;
            logDiv.scrollTop = logDiv.scrollHeight;
          });
      }, index * 6000); // 6 segundos de delay entre cada post
    });
  }

  // Inicia o script quando a página estiver carregada
  waitForElement("body", initialize);
})();