Il Regolamento generale sulla protezione dei dati (GDPR) è una normativa europea sulla protezione dei dati e sulla privacy. Esso concede agli individui il diritto di richiedere la cancellazione dei propri dati personali, noto come diritto all'oblio. Se memorizzi informazioni di identificazione personale (PII) dei tuoi utenti, come gli ID utente, devi rispettare i requisiti del GDPR eliminando queste informazioni al ricevimento di una richiesta da parte di un utente.
Invece di gestire le richieste manualmente, puoi impostare un webhook e utilizzare un bot all'interno di un'applicazione di messaggistica di terze parti per automatizzare il processo. Poiché i data store sono il modo più comune per memorizzare i dati PII, questo tutorial fornisce un esempio su come creare un bot all’interno di Discord che utilizza l'API Open Cloud per i data store per eliminare i dati PII come soluzione di automazione.
Flusso di lavoro
Al termine di questo tutorial, dovresti essere in grado di creare un programma personalizzato in esecuzione locale che automatizza la gestione delle richieste di diritto all'oblio da parte degli utenti. Il flusso di lavoro per questo processo è il seguente:
- Il supporto di Roblox riceve una richiesta di diritto all'oblio da un utente.
- Il webhook di Roblox viene attivato, contenente l'ID utente e un elenco di ID dei luoghi di avvio per i giochi ai quali hanno partecipato nel payload.
- Il tuo bot ascolta queste notifiche del webhook, verifica la loro autenticità e utilizza l'API Open Cloud per i data store per eliminare i dati PII memorizzati nei data store.
- Il bot risponde al messaggio del webhook in Discord con lo stato di eliminazione.

Configurare un webhook con integrazione di terze parti
Prima di creare un bot, imposta un server con integrazione webhook sull'applicazione di messaggistica di terze parti. Poi utilizza il server per configurare un webhook sul Creator Dashboard.
Imposta un server
I seguenti passi mostrano come impostare il server utilizzando Discord.
Crea un nuovo server Discord. Se non sei familiare con il processo, consulta il Supporto Discord.
Il server crea automaticamente un canale #general come tuo canale predefinito. Clicca sull'icona Modifica Canale del canale #general.
Sotto Permessi, imposta il canale su privato.
Crea un'integrazione webhook con il nuovo server, chiamalo GDPR Hook. Se non sei familiare con il processo, consulta il Supporto Discord.
Copia l'URL del webhook e conservalo in un luogo sicuro. Consenti l'accesso solo ai membri affidabili del team, poiché la divulgazione dell'URL può consentire ad attori malevoli di inviare messaggi falsi e potenzialmente eliminare i dati dei tuoi utenti.
Configura un webhook su Roblox
Dopo aver ottenuto l'URL del server di terze parti, utilizzalo per configurare un webhook sul Creator Dashboard. Assicurati di eseguire le seguenti impostazioni:
- Aggiungi l'URL del server Discord come Webhook URL.
- Includi un Segreto personalizzato. Sebbene un segreto sia opzionale per completare la configurazione, dovresti includerne uno per prevenire che attori malevoli impersonino Roblox ed eliminino i tuoi dati. Per ulteriori informazioni sull'uso di un segreto, consulta Verifica della sicurezza del webhook.
- Seleziona Richiesta di diritto all'oblio sotto Trigger.
Puoi testare il webhook utilizzando il pulsante Test Response per vedere se ricevi una notifica nel canale #general del tuo server da Roblox. Se non ricevi la notifica, riprova o controlla le impostazioni del tuo server per risolvere l'errore.
Configura un bot
Dopo aver aggiunto il webhook, utilizzalo per configurare il bot con i seguenti passi. Per ulteriori informazioni, consulta la documentazione di Discord.
- Naviga alla pagina delle applicazioni.
- Crea una nuova applicazione e chiamala GDPR Bot.
- Il sistema ti reindirizza alle impostazioni di Informazioni Generali del bot. Copia e conserva il suo ID applicazione in un luogo sicuro.
- Sotto il menu delle impostazioni, seleziona OAuth2.
- Naviga alle impostazioni OAuth2
- Abilita l'ambito bot, si aprirà un elenco di autorizzazioni aggiuntive per il Bot.
- Aggiungi l'autorizzazione Amministratore. Salva le tue modifiche.
- Salva l'URL generato.
- Copia l'URL generato. Non chiudere la pagina delle impostazioni dell'applicazione.
- Naviga all'URL generato. Seleziona il server di destinazione. Clicca sul pulsante Continua e poi sul pulsante Autorizza.
- Torna alla pagina delle impostazioni dell'applicazione e vai alle impostazioni del Bot.
- Nella sezione Privileged Gateway Intents, abilita Message Content Intent.
- Nelle impostazioni del Bot > sezione Build-A-Bot, conserva il token del bot in un luogo sicuro per i passaggi successivi. Se non vedi il token, clicca sul pulsante Reset Token per generare uno nuovo.
Crea una chiave API Open Cloud
Per consentire al tuo bot di terze parti di accedere ai tuoi data store per memorizzare i dati PII degli utenti, crea una chiave API Open Cloud che possa accedere ai tuoi giochi e aggiungi il permesso Elimina voce dei data store per l'eliminazione dei dati. Se utilizzi data store ordinati per memorizzare i dati PII, devi anche aggiungere il permesso Scrivi dei data store ordinati. Al termine, copia e conserva la chiave API in un luogo sicuro per utilizzarla nei passaggi successivi.
Ottieni identificatori di giochi e luoghi
Affinché il bot localizzi i dati PII richiesti dagli utenti per l'eliminazione, ottieni i seguenti identificatori di tutti i giochi che intendi utilizzare per il bot:
- L'ID Universale, l'identificatore unico del tuo gioco.
- L'ID Luogo di Avvio, l'identificatore unico del luogo di avvio di un gioco.
Per ottenere questi identificatori:
Naviga al Creator Dashboard.
Passa sopra la miniatura di un gioco, clicca sul pulsante ⋯ e seleziona Copia ID Universale e Copia ID Luogo di Avvio, rispettivamente.

Aggiungi script
Dopo aver completato la configurazione del webhook, del bot e della chiave API per i data store, aggiungili agli script che implementano la logica di automazione del bot. Il seguente esempio utilizza Python 3.
Installa le librerie Python utilizzando i seguenti comandi:
Installa Libreriepip3 install discordpip3 install requestspip3 install urllib3==1.26.6Copia e conserva i seguenti script corrispondenti a diverse parti della logica del bot nella stessa directory:
bot_config.pyBOT_TOKEN = ""OPEN_CLOUD_API_KEY = ""ROBLOX_WEBHOOK_SECRET = ""# Dizionario dell'ID Luogo di Avvio a# (ID universale, elenco di (nome dei data store, ambito e chiave di voce)) per# Data Store Standard# I dati utente memorizzati sotto queste voci saranno eliminatiSTANDARD_DATA_STORE_ENTRIES = {# ID Luogo di Avvio111111111: (# ID Universale222222222,[("StandardDataStore1", "Scope1", "Key1_{user_id}"),("StandardDataStore1", "Scope1", "Key2_{user_id}"),("StandardDataStore2", "Scope1", "Key3_{user_id}")]),33333333: (444444444,[("StandardDataStore3", "Scope1", "Key1_{user_id}")])}# Dizionario dell'ID Luogo di Avvio a# (ID universale, elenco di (nome dei data store, ambito e chiave di voce)) per# Data Store Ordinati# I dati utente memorizzati sotto queste voci saranno eliminatiORDERED_DATA_STORE_ENTRIES = {111111111: (222222222,[("OrderedDataStore1", "Scope2", "Key4_{user_id}")])}data_stores_api.pyimport requestsimport bot_configfrom collections import defaultdict"""Chiama l'API Open Cloud dei Data Store per eliminare tutte le voci per un user_id configurato inSTANDARD_DATA_STORE_ENTRIES. Restituisce un elenco di eliminazioni riuscite e fallimenti di eliminazione."""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"""Chiama l'API Open Cloud dei Data Store Ordinati per eliminare tutte le voci per un user_id configurato inORDERED_DATA_STORE_ENTRIES. Restituisce un elenco di eliminazioni riuscite e fallimenti di eliminazione."""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"""Analizza il messaggio ricevuto per la firma e il timestamp di Roblox, il piè di pagina è impostato solo se haiconfigurato il segreto del 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"""Verifica la firma di Roblox con il segreto configurato per controllare la validità"""def validate_signature(message, signature, timestamp):if not message or not signature or not timestamp:return False# Previene attacchi di replay all'interno di una finestra di 300 secondirequest_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# Valida la firmatimestamp_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# Firma validareturn True"""Analizza un messaggio webhook ricevuto su Discord. Estrae l'ID utente, previene attacchi di replaybasati sul timestamp ricevuto e verifica la firma di Roblox con il segreto configurato per controllare lavalidità."""def parse_message(message):# Analizza il messaggio ricevuto per l'ID utente e l'ID giocoif len(message.embeds) != 1 or \not message.embeds[0].description:return "", []description_match = re.match(r"Hai ricevuto una nuova notifica per il diritto all'oblio per l'ID utente: (.*) nel " +r"gioco(i) con 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} sta ascoltando i messaggi di diritto all'oblio")"""Gestore per i messaggi webhook da Roblox"""@client.eventasync def on_message(message):# Analizza e verifica il messaggiouser_id, start_place_ids = message_parser.parse_message(message)if not user_id or not start_place_ids:return# Elimina i dati degli utenti dei data store standard[successes, failures] = data_stores_api.delete_standard_data_stores(user_id, start_place_ids)if successes:await message.reply(f"Eliminati i dati dei data store standard per " +f"l'ID utente: {user_id}, dati: {dict(successes)}")if failures:await message.reply(f"Errore nell'eliminazione dei dati dei data store standard per " +f"l'ID utente: {user_id}, dati: {dict(failures)}")# Elimina i dati degli utenti dei data store ordinati[successes, failures] = data_stores_api.delete_ordered_data_stores(user_id, start_place_ids)if successes:await message.reply(f"Eliminati i dati dei data store ordinati per " +f"l'ID utente: {user_id}, dati: {dict(successes)}")if failures:await message.reply(f"Errore nell'eliminazione dei dati dei data store ordinati per " +f"l'ID utente: {user_id}, dati: {dict(failures)}")client.run(bot_config.BOT_TOKEN)if __name__ == "__main__":run()Nel file bot_config.py per la configurazione principale del bot:
- Imposta BOT_TOKEN sul token generato dal tuo bot.
- Imposta OPEN_CLOUD_API_KEY come la chiave API che hai creato.
- Imposta ROBLOX_WEBHOOK_SECRET come il segreto che hai impostato quando configuravi il webhook sul Creator Dashboard.
- Nei dizionari STANDARD_DATA_STORE_ENTRIES e ORDERED_DATA_STORE_ENTRIES per localizzare il data store di ogni record da eliminare:
- Aggiungi i tuoi ID Luogo di Avvio copiati come chiavi.
- Aggiungi gli ID Universali come primo elemento della tupla valore.
- Sostituisci il secondo elemento della tupla con il nome, l'ambito, il nome della chiave di voce e l'ID utente associato dei tuoi data store. Se utilizzi uno schema dati diverso, modifica per corrispondere al tuo schema dati.
Esegui il seguente comando per eseguire il bot:
Esegui Bot Discordpython3 discord_bot.pyIl bot quindi inizia ad ascoltare e verificare i webhook di Roblox per le richieste di diritto all'oblio e chiama l'endpoint Open Cloud per eliminare il corrispondente data store.
Test
Puoi creare ed eseguire un messaggio di test per verificare che il tuo programma personalizzato possa gestire correttamente le richieste di diritto all'oblio e cancellare i dati PII:
Invia una richiesta HTTP POST al tuo server webhook di Discord con il seguente corpo della richiesta:
Esempio di Richiestacurl -X POST {serverUrl}-H 'Content-Type: application/json'-d '{"embeds":[{"title":"RichiestaDiDirittoAllOblio","description":"Hai ricevuto una nuova notifica per il diritto all'oblio per l'ID utente: {userIds} nel gioco(i) con ID: {gameIds}","footer":{"icon_url":"https://create.roblox.com/dashboard/assets/webhooks/roblox_logo_metal.png","text":"Roblox-Signature: {robloxSignature}, Timestamp: {timestamp}"}}]}'Se hai un segreto webhook:
- Genera una Roblox-Signature applicando l'encoding HMAC-SHA256 alla chiave segreta del tuo webhook.
- Imposta l'ora corrente utilizzando il timestamp UTC in secondi come Timestamp.
Metti insieme la description nel seguente formato:
Formato del Campo Descrizione{Timestamp}. Hai ricevuto una nuova notifica per il diritto all'oblio per l'ID utente: {userId} nel gioco(i) con ID: {gameIds}`.Ad esempio:
Esempio Campo Descrizione1683927229. Hai ricevuto una nuova notifica per il diritto all'oblio per l'ID utente: 2425654247 nel gioco(i) con ID: 10539205763, 13260950955
Il tuo programma dovrebbe essere in grado di identificare che il tuo messaggio proviene dalla fonte ufficiale di Roblox poiché hai codificato il messaggio con il tuo segreto. Dovrebbe quindi eliminare i dati PII associati alla tua richiesta.
Esempio Corpo
{
"embeds": [
{
"title": "RichiestaDiDirittoAllOblio",
"description": "Hai ricevuto una nuova notifica per il diritto all'oblio per l'ID utente: 2425654247 nel gioco(i) con ID: 10539205763, 13260950955",
"footer": {
"icon_url": "https://create.roblox.com/dashboard/assets/webhooks/roblox_logo_metal.png",
"text": "Roblox-Signature: UIe6GJ78MHCmU/zUKBYP3LV0lAqwWRFR6UEfPt1xBFw=, Timestamp: 1683927229"
}
}
]
}