Włamanie na serwer Playergency i automat, który odbanował wszystkich

W październiku 2024 ktoś wyrzucił i zbanował członków serwera Discord społeczności, dla której od lat robię bota. Discord nie miał przycisku „odbanuj wszystkich”, więc napisałem go sam w discord.js.

· 4 min czytania

Spisane w listopadzie 2026 na podstawie notatek z tamtego czasu.

Playergency to społeczność graczy, wcześniej znana jako eRPe, działająca od 2016 roku. Nie jest moja: od 2017 roku tworzę dla niej bota i stronę. W październiku 2024 jej serwer Discord został zaatakowany: członkowie byli masowo wyrzucani i banowani. Najbardziej prawdopodobna przyczyna to przejęte lub zainfekowane publiczne boty, które miały na serwerze uprawnienia moderatora.

Skala i brak przycisku

Po wszystkim na liście banów byli praktycznie wszyscy. Discord nie miał wtedy opcji „odbanuj wszystkich”, a wsparcie Discorda nie pomogło. Zostało klikanie: jeden użytkownik, menu, „Cofnij bana”, potwierdzenie, następny. Przy całej społeczności na liście banów to monotonna praca i gwarancja pomyłek.

Zamiast klikać, napisałem w discord.js automat, który przeszedł przez całą listę banów i zdjął je jeden po drugim.

Jak by to dziś wyglądało

Oryginalny kod się nie zachował. Poniżej rekonstrukcja: tak bym to napisał dzisiaj, z tymi samymi założeniami. To nie jest kod z 2024 roku.

// Rekonstrukcja (2026), nie oryginalny kod z 2024 roku.
import { Client, Events, GatewayIntentBits } from 'discord.js';

const client = new Client({ intents: [GatewayIntentBits.Guilds] });

client.once(Events.ClientReady, async () => {
  const guild = await client.guilds.fetch(process.env.GUILD_ID!);
  let after: string | undefined;
  let lifted = 0;

  // Discord zwraca bany stronami po maksymalnie 1000, posortowane po ID użytkownika.
  for (;;) {
    const page = await guild.bans.fetch({ limit: 1000, after, cache: false });
    if (page.size === 0) break;

    for (const ban of page.values()) {
      // Celowo jeden po drugim: discord.js kolejkuje żądania per trasa
      // i sam czeka po odpowiedzi 429, więc pętla nie wpada w limit.
      await guild.bans.remove(ban.user.id, 'Mass unban after the raid');
      lifted += 1;
      if (lifted % 100 === 0) console.log(`lifted ${lifted}`);
    }
    after = page.lastKey();
  }

  console.log(`done, lifted ${lifted}`);
  await client.destroy();
});

await client.login(process.env.DISCORD_TOKEN);

Trzy rzeczy warte uwagi w tym kodzie.

Stronicowanie. Lista banów nie przychodzi w całości. Endpoint zwraca najwyżej tysiąc wpisów, a kolejną stronę pobiera się parametrem after z ID ostatniego użytkownika. Kursor to ID, a nie numer strony, więc zdejmowanie banów w trakcie przeglądania niczego nie przesuwa.

Limity zapytań. Pokusa jest taka, żeby wrzucić wszystkie żądania naraz w Promise.all. Discord ma limity per trasa i dla całego bota, a po ich przekroczeniu odpowiada kodem 429. discord.js ma wbudowaną kolejkę, która czeka tyle, ile każe nagłówek odpowiedzi, ale równoległe żądania i tak trafiają w ten sam limit, więc nic się nie zyskuje, a w logach robi się bałagan. Pętla wykonująca żądania po kolei idzie tak szybko, jak pozwala Discord, i nic więcej tu nie potrzeba.

Powód w audycie. Drugi argument remove trafia do dziennika audytu serwera. Po takim incydencie moderatorzy powinni móc odróżnić bany cofnięte automatem od decyzji podjętych ręcznie.

Gdybym robił to dziś, dodałbym jeszcze jeden krok: zamiast zdejmować wszystkie bany, pobrałbym z dziennika audytu (AuditLogEvent.MemberBanAdd) te, które nałożyło konkretne, przejęte konto bota, i cofnąłbym tylko je. Wtedy bany nałożone wcześniej świadomie przez moderatorów zostają na miejscu.

Koszt, którego nie cofnie żaden skrypt

Automat zdjął bany, ale nie przywrócił ludzi. Osoba wyrzucona z serwera musi sama wrócić przez zaproszenie, a mniej więcej połowa nigdy tego nie zrobiła. Techniczne odtworzenie stanu okazało się tylko częścią odpowiedzi na incydent. Zaufania i nawyku zaglądania na serwer nie da się przywrócić pętlą.

Co bym doradził każdemu, kto prowadzi serwer Discord

To nie jest lista tego, co zostało zrobione po incydencie, tylko rady, które z niego wynikają:

  • Najmniejsze możliwe uprawnienia dla botów. Publiczny bot z prawem do banowania to klucz do serwera w cudzej kieszeni. Jeśli bot nie musi moderować, nie powinien móc.
  • Przegląd botów raz na jakiś czas. Każdy dodany bot to zależność, której kodu i bezpieczeństwa nie kontrolujesz. Te, których nikt już nie używa, najlepiej usunąć.
  • Miej narzędzie na najgorszy dzień, zanim nadejdzie. Skrypt do masowego cofania działań, przetestowany na serwerze testowym, to kilkadziesiąt linii kodu. Pisanie go w trakcie incydentu to stres, którego da się uniknąć.

Wszystkie wpisy