Regulasi Perlindungan Data Umum (GDPR) adalah peraturan Eropa tentang perlindungan data dan privasi.Ini memberikan individu hak untuk meminta penghapusan data pribadi mereka, yang dikenal sebagai hak untuk menghapus.Jika Anda menyimpan informasi Pengenal Pribadi (PII) dari pengguna Anda, seperti User ID mereka, Anda harus mematuhi persyaratan GDPR dengan menghapus informasi ini setelah menerima permintaan pengguna.
Alih-alih menangani permintaan secara manual, Anda dapat mengatur webhook dan menggunakan bot dalam aplikasi pesan pihak ketiga untuk mengotomatisasi prosesnya.Sebagai penyimpanan data menjadi cara paling umum untuk menyimpan data PII, tutorial ini memberikan contoh tentang cara membuat bot di Guilded atau Discord yang menggunakan Open Cloud API untuk penyimpanan data untuk menghapus data PII sebagai solusi otomatisasi.
Alur kerja
Setelah menyelesaikan tutorial ini, Anda harus dapat membuat program khusus lokal yang menyederhanakan penanganan permintaan hak untuk menghapus dari pengguna.Alur kerja untuk proses ini adalah sebagai berikut:
- Dukungan Roblox menerima hak untuk permintaan hapus dari pengguna.
- Webhook Roblox diaktifkan, berisi ID Pengguna dan daftar ID Tempat Mulai untuk pengalaman yang telah mereka bergabung dalam payload.
- Bot Anda mendengarkan pemberitahuan webhook ini, memverifikasi keasliannya, dan menggunakan Open Cloud API untuk penyimpanan data untuk menghapus data PII yang disimpan di penyimpanan data.
- Bot merespon pesan webhook di Discord atau Guilded dengan status penghapusan.

Konfigurasi webhook dengan integrasi pihak ketiga
Sebelum membuat bot, buat server dengan integrasi webhook pada aplikasi pesan pihak ketiga.Kemudian gunakan server untuk mengonfigurasi webhook di Dasbor Kreator.
Menyiapkan server
Langkah berikut menunjukkan cara mengatur server menggunakan Guilded atau Discord.
- Buat server Guilded baru. Jika Anda tidak akrab dengan prosesnya, lihat Dukungan Guilded.
- Di bawah pengaturan Privasi , atur server menjadi privat.Server secara otomatis membuat saluran pribadi #general sebagai channeldefault Anda.
- Buat integrasi webhook dengan server baru dan beri nama yang dapat Anda mengerti dengan mudah, seperti GDPR Hook.Jika Anda tidak akrab dengan prosesnya, lihat Dukungan Guilded.
- Salin URL webhook dan simpan di tempat yang aman.Hanya izinkan anggota tim tepercaya untuk mengaksesnya, karena kebocoran URL dapat mengaktifkan aktor jahat untuk mengirim pesan palsu dan secara potensial menghapus data pengguna Anda.
Konfigurasi webhook di Roblox
Setelah mendapatkan URL server pihak ketiga, gunakan untuk konfigurasi webhook di Dasbor Kreator.pastikan Anda melakukan pengaturan berikut:
- Tambahkan URL server Guilded atau Discord sebagai URL Webhook .
- Terdiri dari sebuah kunci khusus Rahasia .Meskipun rahasia opsional untuk menyelesaikan konfigurasi, Anda harus menyertakan satu untuk mencegah aktor jahat menyamar sebagai Roblox dan menghapus data Anda.Untuk informasi lebih lanjut tentang penggunaan rahasia, lihat Verifikasi keamanan webhook.
- Pilih Hak untuk Permintaan Penghapusan di bawah Pemicu .
Anda dapat menguji webhook menggunakan tombol Balasan Tes untuk melihat apakah Anda menerima pemberitahuan di saluran #umum server Anda dari Roblox.Jika Anda tidak menerima notifikasi, coba lagi atau periksa pengaturan server Anda untuk memecahkan masalah kesalahan.

Konfigurasi bot
Setelah Anda menambahkan webhook, gunakan untuk mengonfigurasi bot dengan langkah berikut:
Buka daftar Semua server dengan mengklik ikonnya atau gunakan pintasan:
- CtrlS pada Windows.
- ⌘S pada Mac.
Pilih server Anda untuk menerima hak untuk menerima pemberitahuan hapus.
Perluas daftar di bawah Rumah server dan pilih Kelola Bot .
Server secara otomatis membuat saluran pribadi #general sebagai channeldefault Anda.
Klik tombol Buat bot dan tambahkan nama bot. Guilded mengarahkan Anda ke halaman konfigurasi bot.
Pilih bagian API pada halaman konfigurasi bot.
Di bawah bagian Token , klik tombol Menghasilkan Token .
Simpan dan simpan token yang dihasilkan di tempat yang aman.
Buat kunci API Cloud unit
Untuk mengizinkan bot pihak ketiga Anda mengakses penyimpanan data Anda untuk menyimpan data PII pengguna, buat kunci API Cloud Terbuka yang dapat mengakses pengalaman Anda dan menambahkan izin Hapus Entri penyimpanan data untuk penghapusan data.Jika Anda menggunakan penyimpanan data tertata untuk menyimpan PII, Anda juga perlu menambahkan izin Tulis dari penyimpanan data tertata.Setelah selesai, salin dan simpan kunci API di lokasi yang aman untuk digunakan di langkah selanjutnya.
Dapatkan identifikasi pengalaman dan tempat
Untuk bot menemukan data PII yang diminta oleh pengguna untuk dihapus, dapatkan identifikator berikut dari semua pengalaman yang Anda rencanakan untuk menggunakan bot:
- ID Alam Semesta , identifikator unik dari pengalaman Anda.
- ID Tempat Mulai , identifikasi unik dari tempat mulai pengalaman.
Untuk mendapatkan identifikator ini:
Navigasikan ke Dashboard Pencipta.
Pasang mouse di atas thumbnail pengalaman, klik tombol ⋯ dan pilih Copy Universe ID dan Copy Start Place ID , masing-masing.
Tambahkan skrip
Setelah Anda menyelesaikan pengaturan webhook, bot, dan kunci API untuk penyimpanan data, tambahkan ke skrip yang menerapkan logika otomatisasi bot.Contoh berikut menggunakan Python 3:
Pasang perpustakaan Python menggunakan perintah berikut:
Pasang Perpustakaanpip3 install guilded.py==1.8.0pip3 install requestspip3 install urllib3==1.26.6Salin dan simpan skrip berikut yang sesuai dengan berbagai bagian logika bot di direktori yang sama:
bot_config.pyBOT_TOKEN = ""OPEN_CLOUD_API_KEY = ""ROBLOX_WEBHOOK_SECRET = ""# Kamus dari ID tempat awal ke# (ID alam semesta, daftar (nama penyimpan data, scope, dan kunci unit)) untuk# Penyimpanan Data Standard# Data pengguna yang disimpan di bawah entri ini akan dihapusSTANDARD_DATA_STORE_ENTRIES = {# ID Tempat Mulai111111111: (# ID Alam Semesta222222222,[("StandardDataStore1", "Scope1", "Key1_{user_id}"),("StandardDataStore1", "Scope1", "Key2_{user_id}"),("StandardDataStore2", "Scope1", "Key3_{user_id}")]),33333333: (444444444,[("StandardDataStore3", "Scope1", "Key1_{user_id}")])}# Kamus dari ID tempat awal ke# (ID alam semesta, daftar (nama penyimpan data, scope, dan kunci unit)) untuk# Toko Data Teratur# Data pengguna yang disimpan di bawah entri ini akan dihapusORDERED_DATA_STORE_ENTRIES = {111111111: (222222222,[("OrderedDataStore1", "Scope2", "Key4_{user_id}")])}data_stores_api.pyimport requestsimport bot_configfrom collections import defaultdict"""Calls Data Stores Open Cloud API to delete all entries for a user_id configured inSTANDARD_DATA_STORE_ENTRIES. Returns a list of successful deletions and failures to delete."""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"""Calls Ordered Data Stores Open Cloud API to delete all entries for a user_id configured inORDERED_DATA_STORE_ENTRIES. Returns a list of successful deletions and failures to delete."""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"""Parses received message for Roblox signature and timestamp, the footer is only set if youconfigured webhook secret"""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"""Verifies Roblox signature with configured secret to check for validity"""def validate_signature(message, signature, timestamp):if not message or not signature or not timestamp:return False# Mencegah serangan putar ulang dalam jendela 300 detikrequest_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# Memeriksa tanda tangantimestamp_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# Tanda tangan yang validreturn True"""Parses a received webhook messaged on Discord or Guilded. Extracts user ID, prevents replay attackbased on timestamp received, and verifies Roblox signature with configured secret to check forvalidity."""def parse_message(message):# Parses menerima pesan untuk ID pengguna dan ID permainanif len(message.embeds) != 1 or \not message.embeds[0].description:return "", []description_match = re.match(r"You have received a new notification for Right to Erasure for the User Id: (.*) in " +r"the game\(s\) with Ids: (.*)",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 "", []guilded_bot.pyimport guildedimport jsonimport bot_configimport data_stores_apiimport message_parserdef run():client = guilded.Client()@client.eventasync def on_ready():print(f"{client.user} is listening to Right to Erasure messages")"""Handler for webhook messages from Roblox"""@client.eventasync def on_message(message):# Memeriksa dan memverifikasi pesanuser_id, start_place_ids = message_parser.parse_message(message)if not user_id or not start_place_ids:return# Menghapus data toko data standar pengguna[successes, failures] = data_stores_api.delete_standard_data_stores(user_id, start_place_ids)if successes:await message.reply(f"Deleted standard data stores data for " +f"user ID: {user_id}, data: {dict(successes)}")if failures:await message.reply(f"Failed to delete standard data stores data for " +f"user ID: {user_id}, data: {dict(failures)}")# Menghapus data toko data yang diperintahkan pengguna[successes, failures] = data_stores_api.delete_ordered_data_stores(user_id, start_place_ids)if successes:await message.reply(f"Deleted ordered data stores data for " +f"user ID: {user_id}, data: {dict(successes)}")if failures:await message.reply(f"Failed to delete ordered data stores data for " +f"user ID: {user_id}, data: {dict(failures)}")client.run(bot_config.BOT_TOKEN)if __name__ == "__main__":run()Pada file bot_config.py untuk konfigurasi utama bot:
- Tetapkan BOT_TOKEN ke token yang dihasilkan oleh bot Anda.
- Tetapkan OPEN_CLOUD_API_KEY sebagai kunci API yang Anda buat.
- Tetapkan ROBLOX_WEBHOOK_SECRET sebagai rahasia yang Anda atur saat mengkonfigurasi webhook di Dasbor Kreator.
- Dalam STANDARD_DATA_STORE_ENTRIES dan ORDERED_DATA_STORE_ENTRIES kamus untuk menemukan penyimpanan data masing-masing rekaman untuk dihapus:
- Tambahkan ID Tempat Awal yang disalin sebagai kunci.
- Tambahkan ID Univers sebagai elemen pertama dari nilai tuple.
- Ganti elemen kedua dari tuple dengan nama, scope, nama kunci entri, dan ID Pengguna terkait dari penyimpanan data Anda.Jika Anda menggunakan skema data yang berbeda, modifikasi untuk sesuai dengan skema data Anda sendiri sesuai.
Jalankan perintah berikut untuk menjalankan bot:
Jalankan Bot Guildedpython3 guilded_bot.pyBot kemudian mulai mendengarkan dan memverifikasi webhook Roblox untuk hak menghapus Permintaan dan panggilan ke titik akhir Open Cloud untuk menghapus tokodata yang sesuai.
Uji
Anda dapat membuat dan menjalankan pesan pengujian untuk memverifikasi bahwa program khusus Anda dapat menangani dengan benar permintaan hapus dan menghapus data PII:
Kirim permintaan HTTP POST ke server webhook Guilded atau Discord dengan tubuh permintaan berikut:
Permintaan Contohcurl -X POST {serverUrl}-H 'Content-Type: application/json'-d '{"embeds":[{"title":"RightToErasureRequest","description":"You have received a new notification for Right to Erasure for the User Id: {userIds} in the game(s) with Ids: {gameIds}","footer":{"icon_url":"https://create.roblox.com/dashboard/assets/webhooks/roblox_logo_metal.png","text":"Roblox-Signature: {robloxSignature}, Timestamp: {timestamp}"}}]}'Jika Anda memiliki rahasia webhook:
- Hasilkan Roblox-Signature dengan menerapkan enkripsi HMAC-SHA256 ke kunci unitwebhook Anda.
- Tetapkan waktu saat ini menggunakan timestamp UTC dalam detik sebagai Timestamp .
Rakit bersama description dalam format berikut:
Description Field Format{Timestamp}. You have received a new notification for Right to Erasure for the User Id: {userId} in the game(s) with Ids: {gameIds}`.Sebagai contoh:
Example Description Field1683927229. You have received a new notification for Right to Erasure for the User Id: 2425654247 in the game(s) with Ids: 10539205763, 13260950955
Program Anda harus dapat mengidentifikasi bahwa pesan Anda berasal dari sumber Roblox resmi karena Anda mengodekan pesan dengan rahasia Anda.Kemudian harus menghapus data PII yang terkait dengan permintaan Anda.
Contoh Tubuh
{
"embeds": [
{
"title": "RightToErasureRequest",
"description": "You have received a new notification for Right to Erasure for the User Id: 2425654247 in the game(s) with Ids: 10539205763, 13260950955",
"footer": {
"icon_url": "https://create.roblox.com/ashboard/aset/webhook/roblox_logo_metal.png",
"text": "Roblox-Signature: UIe6GJ78MHCmU/zUKBYP3LV0lAqwWRFR6UEfPt1xBFw=, Timestamp: 1683927229"
}
}
]
}