tkhquang / Steam Community - Leave Banned Groups

// ==UserScript==
// @name         Steam Community - Leave Banned Groups
// @icon         https://store.steampowered.com/favicon.ico
// @namespace    https://github.com/tkhquang
// @version      1.21
// @description  Add a button to leave invisble banned groups on steam
// @author       Quang Trinh
// @license      MIT; https://raw.githubusercontent.com/tkhquang/userscripts/master/LICENSE
// @homepage     https://greasyfork.org/en/scripts/391660-steam-community-leave-banned-groups
// @include      /^https?:\/\/steamcommunity\.com\/(?:id|profiles)\/[\w-_]+\/groups\/?$/
// @run-at       document-idle
// @require      https://code.jquery.com/jquery-3.4.1.min.js
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_deleteValue
// @grant        unsafeWindow
// ==/UserScript==

/* jshint esversion: 8 */
/* global GM_getValue:true, GM_setValue:true, GM_deleteValue:true, unsafeWindow */

(function (window) {
  "use strict";

  const $ = window.jQuery.noConflict(true);

  const steamId = window.g_steamID;
  const sessionId = window.g_sessionID;

  // On not logged in, exit
  if (!steamId || !sessionId) {
    return;
  }

  // On someone else's profile, exit
  if (window.g_rgProfileData.steamid !== steamId) {
    return;
  }

  const GMChecks = [
    typeof GM_setValue !== "undefined",
    typeof GM_getValue !== "undefined",
    typeof GM_deleteValue !== "undefined",
  ];

  const useLocalStorage = !GMChecks.every(function (check) {
    return Boolean(check);
  });

  if (useLocalStorage) {
    GM_getValue = function (key, def) {
      return window.localStorage[key] || def;
    };
    GM_setValue = function (key, value) {
      window.localStorage[key] = value;
    };
    GM_deleteValue = function (key) {
      return window.localStorage.removeItem(key);
    };
  }

  const SteamApiKey = (function () {
    let currentKey = GM_getValue("LBG_STEAM_API_KEY", "");
    while (currentKey !== null && !/[0-9A-Z]{32}/.test(currentKey)) {
      currentKey = prompt(
        "Steam Leave Banned Group Userscript\n" +
          "Please enter your Steam API Key\n" +
          "Get from here:\n" +
          "https://steamcommunity.com/dev/apikey\n"
      );
    }
    return currentKey;
  })();

  // On cancel, exit
  if (SteamApiKey === null) {
    return;
  }

  GM_setValue("LBG_STEAM_API_KEY", SteamApiKey);

  console.log("LBG - API Key", SteamApiKey);

  async function doAjax(opts) {
    try {
      const data = await $.ajax(opts);
      return data;
    } catch (response) {
      if (response instanceof Error) {
        return Promise.reject(response);
      }
      // is jqXHR
      const error = new Error();
      error.message = response.statusText;
      return Promise.reject(error);
    }
  }

  // Get all groups which the user is in
  async function getAllGroups() {
    try {
      // This return the id [g:1:${id}]
      const { response } = await doAjax({
        type: "GET",
        url: "https://api.steampowered.com/ISteamUser/GetUserGroupList/v1/",
        data: {
          key: SteamApiKey,
          steamid: steamId,
        },
        dataType: "json",
      });

      return response.groups.map(function (groups) {
        return groups.gid;
      });
    } catch (error) {
      return Promise.reject(error);
    }
  }

  function getVisibleGroups() {
    const pattern = /group_(\d+)/;

    try {
      const visibleGroups = $("#search_results")
        .find("div[id^='group_']")
        .toArray()
        .map(function (item) {
          return pattern.exec($(item).attr("id"))[1];
        });
      return visibleGroups;
    } catch (error) {
      return error;
    }
  }

  // Filter out other types of groups (offcial game groups, .etc)
  async function filterOutNotBanned(groupIds) {
    try {
      const results = await Promise.all(
        groupIds.map(async function (groupId) {
          const html = await doAjax({
            url: `https://steamcommunity.com/gid/[g:1:${groupId}]?l=english`,
            type: "get",
            dataType: "html",
          });
          return /This group has been removed for violating/.test(html) ? groupId : null;
        })
      );
      return results.filter(function (result) {
        return result !== null;
      });
    } catch (error) {
      return Promise.reject(error);
    }
  }

  async function getBannedGroupInfo(groupIds) {
    const pattern = /<!\[CDATA\[(.*)]]>/;

    try {
      const groupInfo = await Promise.all(
        groupIds.map(async function (groupId) {
          const xml = await doAjax({
            url: `https://steamcommunity.com/gid/[g:1:${groupId}]/memberslistxml/`,
            data: {
              xml: 1,
            },
            type: "GET",
            dataType: "xml",
          });
          return {
            groupID64: $(xml).find("groupID64").html(),
            groupName: pattern.exec($(xml).find("groupName").html())[1],
          };
        })
      );
      return groupInfo;
    } catch (error) {
      return Promise.reject(error);
    }
  }

  async function sendLeaveRequest(groupInfo) {
    const data = {
      action: "leave_group",
      sessionid: sessionId,
      ajax: 1,
      steamid: steamId,
      steamids: [groupInfo.groupID64],
    };

    try {
      const response = await doAjax({
        url: `https://steamcommunity.com/profiles/${steamId}/friends/action`,
        type: "POST",
        dataType: "json",
        data,
      });
      console.log(`Left ${groupInfo.groupName}`, response);
      return response;
    } catch (error) {
      console.log(`Error leaving ${groupInfo.groupName}`, error);
      return Promise.reject(error);
    }
  }
  $(function () {
    $("<button />", {
      id: "lbg_btn",
      title: "Click to leave banned groups",
      text: "Leave banned groups",
      type: "button",
    }).appendTo(".friends_nav");

    $("<a />", {
      id: "lbg_clear_btn",
      title: "Click to remove current Steam API Key",
      text: "Remove current Steam API Key",
      href: "javascript:void(0)",
    }).appendTo(".friends_nav");

    $("<style>")
      .prop("type", "text/css")
      .html(
        `
        #lbg_btn {
          width: 100%;
          background-color: #015e80;
          border-radius: 5px;
          border: 1px solid #015e80;
          display: inline-block;
          cursor: pointer;
          color: #ffffff;
          padding: 8px 16px;
          text-decoration: none;
          text-shadow: 0px 1px 0px #2f6627;
        }
        #lbg_btn:hover:enabled {
          background-color: #004a50;
        }
        #lbg_btn:active {
          position: relative;
          top: 1px;
        }
        #lbg_btn:disabled {
          cursor: not-allowed;
        }
        #lbg_clear_btn {
          background-color: none;
          text-align: center;
          padding: 5px;
        }
        `
      )
      .appendTo("head");

    const btn = $("#lbg_btn");
    const btnClear = $("#lbg_clear_btn");
    const originalText = btn.text();

    let timer;

    function setText(el, text) {
      const $this = el;
      clearTimeout(timer);

      $this.text(text);
      timer = setTimeout(function () {
        $this.text(originalText);
      }, 5000);
    }

    function setPermText(el, text) {
      const $this = el;
      $this.text(text);
    }

    async function initialize() {
      btn.attr("disabled", true);
      setPermText(btn, "Checking...");

      const allGroups = await getAllGroups();
      const visibleGroups = getVisibleGroups();

      // This may be offical game groups or banned groups, .etc
      const unknownGroups = $(allGroups).not(visibleGroups).get();

      // Contains verified banned groups
      const bannedGroups = await filterOutNotBanned(unknownGroups);

      if (!bannedGroups.length) {
        setText(btn, "No banned groups to leave");
        console.log("No banned groups to leave");
        alert("No banned groups to leave");
        return;
      }

      // Get groupID64 and groupName
      const bannedGroupWithInfo = await getBannedGroupInfo(bannedGroups);

      setPermText(btn, `Leaving ${bannedGroupWithInfo.length} groups...`);

      // Send requests to leave those groups
      await Promise.all(
        bannedGroupWithInfo.map(function (groupInfo) {
          return sendLeaveRequest(groupInfo);
        })
      );

      // Success
      console.log(`Left ${bannedGroupWithInfo.length} Steam Groups successfully`);
      console.log("Groups", bannedGroupWithInfo);
      setText(btn, `Left ${bannedGroupWithInfo.length} groups`);
      alert(`Left ${bannedGroupWithInfo.length} groups\nCheck the browser console for more information`);
    }

    btn.click(async function (e) {
      e.preventDefault();
      try {
        await initialize();
      } catch (error) {
        console.log("An error occured", error);
        alert("An error occured");
        setText(btn, "An error occured");
      } finally {
        btn.removeAttr("disabled");
      }
    });

    btnClear.click(function (e) {
      e.preventDefault();
      try {
        GM_deleteValue("LBG_STEAM_API_KEY");
        setPermText(btnClear, "Remove API Key Success");
        setTimeout(function () {
          window.location.reload();
        }, 3000);
      } catch (error) {
        console.log("An error occured", error);
        alert("An error occured");
        setText(btnClear, "An error occured");
      }
    });
  });
})(unsafeWindow);