Globalne przepisy dotyczące ochrony danych i prywatności dają jednostkom prawo do kontroli swoich danych, w tym prawo do żądania ich usunięcia (często określane jako "prawo do usunięcia" lub "prawo do skasowania"). Jeśli przechowujesz jakiekolwiek dane osobowe lub Dane Osobowe Identyfikowalne (PII), takie jak identyfikatory użytkowników, jesteś odpowiedzialny za przestrzeganie odpowiednich ram prywatności poprzez trwałe usunięcie tych informacji po otrzymaniu prośby od użytkownika. Więcej informacji można znaleźć w RTBF i twórcy.
Zamiast obsługiwać wnioski ręcznie, możesz ustawić webhook i użyć bota w aplikacji do przesyłania wiadomości trzeciej strony, aby zautomatyzować ten proces. Ponieważ bazy danych są najczęstszym sposobem przechowywania danych PII, ten samouczek przedstawia przykład, jak stworzyć bota w Discordzie, który wykorzystuje Open Cloud API dla baz danych do usuwania danych PII jako rozwiązania automatyzacyjnego.
Przepływ pracy
Po ukończeniu tego samouczka powinieneś być w stanie stworzyć lokalny program, który automatyzuje obsługę wniosków o prawo do usunięcia danych od użytkowników. Przepływ pracy w tym procesie przebiega następująco:
- Zespół wsparcia Roblox otrzymuje prośbę o prawo do usunięcia danych od użytkownika.
- Webhook Roblox jest wywoływany, zawierający identyfikator użytkownika oraz listę identyfikatorów miejsc startowych gier, w których uczestniczył, w ładunku.
- Twój bot nasłuchuje tych powiadomień webhook, weryfikując ich autentyczność, a następnie wykorzystuje Open Cloud API dla baz danych do usuwania danych PII przechowywanych w bazach danych.
- Bot odpowiada na wiadomość webhooka w Discordzie ze statusem usunięcia.

Skonfiguruj webhook z integracją zewnętrzną
Zanim stworzysz bota, skonfiguruj serwer z integracją webhook w aplikacji do przesyłania wiadomości trzeciej strony. Następnie użyj serwera do skonfigurowania webhooka na Creator Dashboard.
Ustaw serwer
Poniższe kroki pokazują, jak skonfigurować serwer przy użyciu Discorda.
Stwórz nowy serwer Discord. Jeśli nie jesteś zaznajomiony z tym procesem, zobacz Wsparcie Discorda.
Serwer automatycznie tworzy kanał #general jako domyślny. Kliknij ikonę Edytuj kanał w kanale #general.
Pod Uprawnienia ustaw kanał na prywatny.
Utwórz integrację webhook z nowym serwerem, nadając jej nazwę RTBF Hook. Jeśli nie jesteś zaznajomiony z tym procesem, zobacz Wsparcie Discorda.
Skopiuj adres URL webhooka i przechowuj go w bezpiecznym miejscu. Pozwól tylko zaufanym członkom zespołu mieć do niego dostęp, ponieważ ujawnienie adresu URL może umożliwić niepożądanym osobom wysyłanie fałszywych wiadomości oraz potencjalne usunięcie twoich danych użytkowników.
Skonfiguruj webhooka w Robloxie
Po uzyskaniu adresu URL serwera zewnętrznego, użyj go do skonfigurowania webhooka na Creator Dashboard. upewnij się, że wykonujesz następujące ustawienia:
- Dodaj adres URL serwera Discorda jako Webhook URL.
- Dodaj niestandardowy Secret. Chociaż sekret jest opcjonalny do zakończenia konfiguracji, powinieneś go dodać, aby zapobiec podszywaniu się niepożądanych osób pod Roblox i usunięciu twoich danych. Aby uzyskać więcej informacji na temat użycia sekretu, zobacz Weryfikacja bezpieczeństwa webhooka.
- Wybierz Wniosek o prawo do usunięcia pod Wyzwalacze.
Możesz przetestować webhook, używając przycisku Testuj odpowiedź, aby sprawdzić, czy otrzymujesz powiadomienie w kanale #general twojego serwera od Roblox. Jeśli nie otrzymasz powiadomienia, spróbuj ponownie lub sprawdź ustawienia swojego serwera, aby zdiagnozować błąd.
Skonfiguruj bota
Po dodaniu webhooka użyj go do skonfigurowania bota zgodnie z następującymi krokami. Aby uzyskać więcej informacji, zobacz dokumentację Discorda.
- Przejdź do strony aplikacji.
- Utwórz nową aplikację i nazwij ją RTBF Bot.
- System przekierowuje cię do ustawień Informacje ogólne bota. Skopiuj i zapisz jego identyfikator aplikacji w bezpiecznym miejscu.
- W menu ustawień wybierz OAuth2.
- Przejdź do ustawień OAuth2:
- Włącz zakres bot, a lista dodatkowych uprawnień bota zostanie wyświetlona.
- Dodaj uprawnienie Administrator. Zapisz swoje zmiany.
- Zapisz wygenerowany adres URL.
- Skopiuj wygenerowany adres URL. Nie zamykaj strony ustawień aplikacji.
- Przejdź do wygenerowanego adresu URL. Wybierz docelowy serwer. Kliknij przycisk Kontynuuj, a następnie przycisk Autoryzuj.
- Przejdź z powrotem do strony ustawień aplikacji i przejdź do ustawień bota.
- W sekcji Uprzywilejowane intencje bramy włącz Intencję treści wiadomości.
- W ustawieniach bota > sekcji Zbuduj bota zapisz token bota w bezpiecznym miejscu na późniejsze kroki. Jeśli nie widzisz tokena, kliknij przycisk Zresetuj token, aby wygenerować nowy.
Stwórz klucz API Open Cloud
Aby umożliwić twojemu botowi zewnętrznemu dostęp do twoich baz danych w celu przechowywania danych PII użytkowników, stwórz klucz API Open Cloud, który będzie miał dostęp do twoich gier i dodaj uprawnienie Usuń wpis baz danych w celu usunięcia danych. Jeśli używasz uporządkowanych baz danych do przechowywania PII, musisz również dodać uprawnienie Zapisz w uporządkowanych bazach danych. Po zakończeniu skopiuj i zapisz klucz API w bezpiecznym miejscu, aby użyć go w późniejszych krokach.
Uzyskaj identyfikatory gier i miejsc
Aby bot mógł zlokalizować dane PII żądane przez użytkowników do usunięcia, uzyskaj następujące identyfikatory wszystkich gier, dla których zamierzasz użyć bota:
- ID Uniwersum, unikalny identyfikator twojej gry.
- ID miejsca startowego, unikalny identyfikator miejsca startowego gry.
Aby uzyskać te identyfikatory:
Przejdź do Creator Dashboard.
Najedź na miniaturę gry, kliknij przycisk ⋯, a następnie wybierz Kopiuj ID uniwersum i Kopiuj ID miejsca startowego odpowiednio.

Dodaj skrypty
Po zakończeniu konfiguracji webhooka, bota i klucza API dla baz danych, dodaj je do skryptów implementujących logikę automatyzacji bota. Poniższy przykład używa Pythona 3.
Zainstaluj biblioteki Pythona, używając następujących poleceń:
Zainstaluj bibliotekipip3 install discordpip3 install requestspip3 install urllib3==1.26.6Skopiuj i zapisz poniższe skrypty, odpowiadające różnym częściom logiki bota, w tym samym katalogu:
bot_config.pyBOT_TOKEN = ""OPEN_CLOUD_API_KEY = ""ROBLOX_WEBHOOK_SECRET = ""# Słownik związku ID miejsca startowego z# (ID uniwersum, lista (nazwa bazy danych, zakres, i klucz wpisu)) dla# Standardowych Baz Danych# Dane użytkownika przechowywane pod tymi wpisami będą usunięteSTANDARD_DATA_STORE_ENTRIES = {# ID miejsca startowego111111111: (# ID uniwersum222222222,[("StandardDataStore1", "Scope1", "Key1_{user_id}"),("StandardDataStore1", "Scope1", "Key2_{user_id}"),("StandardDataStore2", "Scope1", "Key3_{user_id}")]),33333333: (444444444,[("StandardDataStore3", "Scope1", "Key1_{user_id}")])}# Słownik związku ID miejsca startowego z# (ID uniwersum, lista (nazwa bazy danych, zakres, i klucz wpisu)) dla# Uporządkowanych Baz Danych# Dane użytkownika przechowywane pod tymi wpisami będą usunięteORDERED_DATA_STORE_ENTRIES = {111111111: (222222222,[("OrderedDataStore1", "Scope2", "Key4_{user_id}")])}data_stores_api.pyimport requestsimport bot_configfrom collections import defaultdict"""Wywołuje Open Cloud API Baz Danych, aby usunąć wszystkie wpisy dla user_id skonfigurowanego wSTANDARD_DATA_STORE_ENTRIES. Zwraca listę udanych usunięć oraz niepowodzeń w usunięciu."""def delete_standard_data_stores(user_id, start_place_ids):successes = defaultdict(list)failures = defaultdict(list)for owned_start_place_id in bot_config.STANDARD_DATA_STORE_ENTRIES:if owned_start_place_id not in start_place_ids:continueuniverse_id, universe_entries = bot_config.STANDARD_DATA_STORE_ENTRIES[owned_start_place_id]for (data_store_name, scope, entry_key) in universe_entries:entry_key = entry_key.replace("{user_id}", user_id)response = requests.delete(f"https://apis.roblox.com/datastores/v1/universes/{universe_id}/standard-datastores/datastore/entries/entry",headers={"x-api-key": bot_config.OPEN_CLOUD_API_KEY},params={"datastoreName": data_store_name,"scope": scope,"entryKey": entry_key})if response.status_code in [200, 204]:successes[owned_start_place_id].append((data_store_name, scope, entry_key))else:failures[owned_start_place_id].append((data_store_name, scope, entry_key))return successes, failures"""Wywołuje Open Cloud API Uporządkowanych Baz Danych, aby usunąć wszystkie wpisy dla user_id skonfigurowanego wORDERED_DATA_STORE_ENTRIES. Zwraca listę udanych usunięć oraz niepowodzeń w usunięciu."""def delete_ordered_data_stores(user_id, start_place_ids):successes = defaultdict(list)failures = defaultdict(list)for owned_start_place_id in bot_config.ORDERED_DATA_STORE_ENTRIES:if owned_start_place_id not in start_place_ids:continueuniverse_id, universe_entries = bot_config.ORDERED_DATA_STORE_ENTRIES[owned_start_place_id]for (data_store_name, scope, entry_key) in universe_entries:entry_key = entry_key.replace("{user_id}", user_id)response = requests.delete(f"https://apis.roblox.com/ordered-data-stores/v1/universes/{universe_id}/orderedDatastores/{data_store_name}/scopes/{scope}/entries/{entry_key}",headers={"x-api-key": bot_config.OPEN_CLOUD_API_KEY})if response.status_code in [200, 204, 404]:successes[owned_start_place_id].append((data_store_name, scope, entry_key))else:failures[owned_start_place_id].append((data_store_name, scope, entry_key))return successes, failuresmessage_parser.pyimport timeimport hmacimport hashlibimport reimport base64import bot_config"""Analizuje odebraną wiadomość, aby uzyskać podpis i znacznik czasu Roblox, stopka jest ustawiona tylko, jeśliskonfigurowano sekret webhook."""def parse_footer(message):if not message.embeds[0].footer or \not message.embeds[0].footer.text:return "", 0footer_match = re.match(r"Roblox-Signature: (.*), Timestamp: (.*)",message.embeds[0].footer.text)if not footer_match:return "", 0else:signature = footer_match.group(1)timestamp = int(footer_match.group(2))return signature, timestamp"""Weryfikuje podpis Roblox za pomocą skonfigurowanego sekretu, aby sprawdzić jego ważność"""def validate_signature(message, signature, timestamp):if not message or not signature or not timestamp:return False# Zapobiega atakowi powtórzeniowemu w oknie 300 sekundrequest_timestamp_ms = timestamp * 1000window_time_ms = 300 * 1000oldest_timestamp_allowed = round(time.time() * 1000) - window_time_msif request_timestamp_ms < oldest_timestamp_allowed:return False# Weryfikuje podpistimestamp_message = "{}.{}".format(timestamp, message.embeds[0].description)digest = hmac.new(bot_config.ROBLOX_WEBHOOK_SECRET.encode(),msg=timestamp_message.encode(),digestmod=hashlib.sha256).digest()validated_signature = base64.b64encode(digest).decode()if signature != validated_signature:return False# Ważny podpisreturn True"""Analizuje odebraną wiadomość webhook na Discordzie. Ekstrahuje ID użytkownika, zapobiega atakowi powtórzeniowemuna podstawie otrzymanego znacznika czasu i weryfikuje podpis Roblox za pomocą skonfigurowanego sekretu."""def parse_message(message):# Analizuje odebraną wiadomość w poszukiwaniu ID użytkownika i ID gryif len(message.embeds) != 1 or \not message.embeds[0].description:return "", []description_match = re.match(r"Otrzymałeś nowe powiadomienie o prawie do usunięcia danych dla ID Użytkownika: (.*) w " +r"grze(ach) z ID: (.*)",message.embeds[0].description)if not description_match:return "", []user_id = description_match.group(1)start_place_ids = set(int(item.strip()) for item in description_match.group(2).split(","))signature, timestamp = parse_footer(message)if validate_signature(message, signature, timestamp):return user_id, start_place_idselse:return "", []discord_bot.pyimport discordimport bot_configimport data_stores_apiimport message_parserdef run():intents = discord.Intents.default()intents.message_content = Trueclient = discord.Client(intents=intents)@client.eventasync def on_ready():print(f"{client.user} nasłuchuje wiadomości o prawie do usunięcia danych")"""Obsługuje wiadomości webhook od Roblox"""@client.eventasync def on_message(message):# Analizuje i weryfikuje wiadomośćuser_id, start_place_ids = message_parser.parse_message(message)if not user_id or not start_place_ids:return# Usuwa dane użytkownika z standardowych baz danych[successes, failures] = data_stores_api.delete_standard_data_stores(user_id, start_place_ids)if successes:await message.reply(f"Usunięto dane użytkownika z standardowych baz danych dla " +f"ID użytkownika: {user_id}, dane: {dict(successes)}")if failures:await message.reply(f"Nie udało się usunąć danych użytkownika z standardowych baz danych dla " +f"ID użytkownika: {user_id}, dane: {dict(failures)}")# Usuwa dane użytkownika z uporządkowanych baz danych[successes, failures] = data_stores_api.delete_ordered_data_stores(user_id, start_place_ids)if successes:await message.reply(f"Usunięto dane użytkownika z uporządkowanych baz danych dla " +f"ID użytkownika: {user_id}, dane: {dict(successes)}")if failures:await message.reply(f"Nie udało się usunąć danych użytkownika z uporządkowanych baz danych dla " +f"ID użytkownika: {user_id}, dane: {dict(failures)}")client.run(bot_config.BOT_TOKEN)if __name__ == "__main__":run()W pliku bot_config.py dla głównej konfiguracji bota:
- Ustaw BOT_TOKEN na token wygenerowany przez twój bot.
- Ustaw OPEN_CLOUD_API_KEY jako klucz API, który utworzyłeś.
- Ustaw ROBLOX_WEBHOOK_SECRET jako sekret, który ustawiłeś podczas konfigurowania webhooka w Creator Dashboard.
- W słownikach STANDARD_DATA_STORE_ENTRIES i ORDERED_DATA_STORE_ENTRIES, aby zlokalizować bazę danych każdego rekordu do usunięcia:
- Dodaj skopiowane ID miejsc startowych jako klucze.
- Dodaj ID uniwersum jako pierwszy element krotki.
- Zastąp drugi element krotki nazwą, zakresem, nazwą klucza wpisu oraz związanym ID użytkownika twoich baz danych. Jeśli używasz innej schematy danych, zmodyfikuj, aby odpowiadała twojej schematy danych.
Wykonaj następujące polecenie, aby uruchomić bota:
Uruchom bota Discordpython3 discord_bot.pyBot zaczyna nasłuchiwać i weryfikować webhooki Roblox dla wniosków o prawo do usunięcia danych oraz wywołuje punkt końcowy Open Cloud w celu usunięcia odpowiednich danych z bazy danych.
Test
Możesz stworzyć i uruchomić testową wiadomość, aby zweryfikować, że twój program może poprawnie obsłużyć wnioski o prawo do usunięcia danych i usunąć dane PII:
Wyślij żądanie HTTP POST do twojego serwera webhook Discord z następującym ciałem żądania:
Przykładowe żądaniecurl -X POST {serverUrl}-H 'Content-Type: application/json'-d '{"embeds":[{"title":"RightToErasureRequest","description":"Otrzymałeś nowe powiadomienie o prawie do usunięcia danych dla ID Użytkownika: {userIds} w grze(grze) z ID: {gameIds}","footer":{"icon_url":"https://create.roblox.com/dashboard/assets/webhooks/roblox_logo_metal.png","text":"Roblox-Signature: {robloxSignature}, Timestamp: {timestamp}"}}]}'Jeśli masz sekret webhook:
- Wygeneruj Roblox-Signature, stosując kodowanie HMAC-SHA256 do swojego klucza sekretu webhook.
- Ustaw aktualny czas, używając znaku czasu UTC w sekundach jako Timestamp.
Złóż razem description w następującym formacie:
Format pola opisu{Timestamp}. Otrzymałeś nowe powiadomienie o prawie do usunięcia danych dla ID Użytkownika: {userId} w grze(grze) z ID: {gameIds}`.Na przykład:
Przykładowe pole opisu1683927229. Otrzymałeś nowe powiadomienie o prawie do usunięcia danych dla ID Użytkownika: 2425654247 w grze(grze) z ID: 10539205763, 13260950955
Twój program powinien być w stanie zidentyfikować, że twoja wiadomość pochodzi z oficjalnego źródła Roblox, ponieważ zakodowałeś wiadomość za pomocą swojego sekretu. Powinien następnie usunąć dane PII związane z twoim żądaniem.
{
"embeds": [
{
"title": "RightToErasureRequest",
"description": "Otrzymałeś nowe powiadomienie o prawie do usunięcia danych dla ID Użytkownika: 2425654247 w grze(grze) z ID: 10539205763, 13260950955",
"footer": {
"icon_url": "https://create.roblox.com/dashboard/assets/webhooks/roblox_logo_metal.png",
"text": "Roblox-Signature: UIe6GJ78MHCmU/zUKBYP3LV0lAqwWRFR6UEfPt1xBFw=, Timestamp: 1683927229"
}
}
]
}