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.20
// @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}]`,
          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);