Regulasi perlindungan data dan privasi global memberikan individu hak untuk mengontrol data mereka, termasuk hak untuk meminta penghapusannya (sering disebut sebagai "hak untuk dihapus" atau "hak untuk menghapus"). Jika Anda menyimpan data pribadi atau Informasi Identifikasi Pribadi (PII), seperti ID Pengguna, Anda bertanggung jawab untuk mematuhi kerangka kerja privasi yang berlaku dengan menghapus informasi ini secara permanen setelah menerima permintaan dari pengguna. Informasi lebih lanjut dapat ditemukan di RTBF dan kreator.
Alih-alih menangani permintaan secara manual, Anda dapat mengatur webhook dan menggunakan bot dalam aplikasi pesan pihak ketiga untuk mengotomatiskan proses tersebut. Karena penyimpanan data adalah cara yang paling umum untuk menyimpan data PII, tutorial ini memberikan contoh cara membuat bot di Discord yang menggunakan Open Cloud API untuk penyimpanan data untuk menghapus data PII sebagai solusi otomatisasi.
Alur kerja
Setelah menyelesaikan tutorial ini, Anda seharusnya dapat membuat program kustom yang berjalan secara lokal yang mengotomatiskan penanganan permintaan hak untuk dihapus dari pengguna. Alur kerja untuk proses ini adalah sebagai berikut:
- Dukungan Roblox menerima permintaan hak untuk dihapus dari seorang pengguna.
- Webhook Roblox diaktifkan, berisi ID Pengguna dan daftar ID Tempat Awal untuk permainan yang telah mereka ikuti dalam payload.
- Bot Anda mendengarkan notifikasi webhook ini, memverifikasi keasliannya, dan memanfaatkan Open Cloud API untuk penyimpanan data untuk menghapus data PII yang tersimpan di penyimpanan data.
- Bot merespons pesan webhook di Discord dengan status penghapusan.

Konfigurasi webhook dengan integrasi pihak ketiga
Sebelum membuat bot, atur server dengan integrasi webhook di aplikasi pesan pihak ketiga. Kemudian gunakan server untuk mengonfigurasi webhook di Dasbor Pembuat.
Atur server
Langkah-langkah berikut menunjukkan cara mengatur server menggunakan Discord.
Buat server Discord baru. Jika Anda tidak familiar dengan proses tersebut, lihat Dukungan Discord.
Server secara otomatis membuat saluran #general sebagai saluran default Anda. Klik ikon Edit Channel dari saluran #general.
Di bawah Permissions, atur saluran menjadi pribadi.
Buat integrasi webhook dengan server baru, namai RTBF Hook. Jika Anda tidak familiar dengan proses tersebut, lihat Dukungan Discord.
Salin URL webhook dan simpan di tempat yang aman. Hanya izinkan anggota tim tepercaya untuk mengaksesnya, karena membocorkan URL dapat memungkinkan aktor jahat untuk mengirim pesan palsu dan berpotensi menghapus data pengguna Anda.
Konfigurasi webhook di Roblox
Setelah memperoleh URL server pihak ketiga, gunakan untuk mengonfigurasi webhook di Dasbor Pembuat. pastikan Anda melakukan pengaturan berikut:
- Tambahkan URL server Discord sebagai Webhook URL.
- Sertakan Secret kustom. Meskipun secret adalah opsional untuk menyelesaikan konfigurasi, Anda harus menyertakannya untuk mencegah aktor jahat berpura-pura menjadi Roblox dan menghapus data Anda. Untuk informasi lebih lanjut tentang penggunaan secret, lihat Verifikasi keamanan webhook.
- Pilih Permintaan Hak untuk Dihapus di bawah Triggers.
Anda dapat menguji webhook menggunakan tombol Tes Respons untuk melihat apakah Anda menerima notifikasi di saluran #general server Anda dari Roblox. Jika Anda tidak menerima notifikasi, coba lagi atau periksa pengaturan server Anda untuk memecahkan kesalahan.
Mengonfigurasi bot
Setelah Anda menambahkan webhook, gunakan untuk mengonfigurasi bot dengan langkah-langkah berikut. Untuk informasi lebih lanjut, lihat dokumentasi Discord.
- Navigasi ke Halaman Aplikasi.
- Buat aplikasi baru dan namai RTBF Bot.
- Sistem mengalihkan Anda ke pengaturan Informasi Umum bot. Salin dan simpan ID aplikasi di tempat yang aman.
- Di bawah menu pengaturan, pilih OAuth2.
- Navigasikan ke pengaturan OAuth2
- Aktifkan ruang lingkup bot, daftar izin Bot tambahan akan ditampilkan.
- Tambahkan izin Administrator. Simpan perubahan Anda.
- Simpan URL yang dihasilkan.
- Salin URL yang dihasilkan. Tinggalkan halaman pengaturan aplikasi tidak ditutup.
- Navigasikan ke URL yang dihasilkan. Pilih server target. Klik tombol Lanjutkan dan kemudian tombol Otorisasi.
- Kembali ke halaman pengaturan aplikasi dan navigasikan ke pengaturan Bot.
- Di bawah bagian Privileged Gateway Intents, aktifkan Message Content Intent.
- Di pengaturan Bot > bagian Build-A-Bot, simpan token bot di tempat yang aman untuk langkah selanjutnya. Jika Anda tidak melihat token, klik tombol Reset Token untuk menghasilkan yang baru.
Buat kunci API Open Cloud
Untuk memungkinkan bot pihak ketiga Anda mengakses penyimpanan data Anda untuk menyimpan PII pengguna, buat kunci API Open Cloud yang dapat mengakses permainan Anda dan menambahkan izin Hapus Entri dari penyimpanan data untuk penghapusan data. Jika Anda menggunakan penyimpanan data yang terurut untuk menyimpan PII, Anda juga perlu menambahkan izin Tulis penyimpanan data yang terurut. Setelah selesai, salin dan simpan kunci API di lokasi yang aman untuk digunakan di langkah-langkah selanjutnya.
Peroleh pengidentifikasi permainan dan tempat
Agar bot dapat menemukan data PII yang diminta oleh pengguna untuk dihapus, peroleh pengidentifikasi berikut dari semua permainan yang Anda berniat menggunakan bot:
- Universe ID, pengidentifikasi unik permainan Anda.
- Start Place ID, pengidentifikasi unik tempat awal dari sebuah permainan.
Untuk memperoleh pengidentifikasi ini:
Navigasikan ke Dasbor Pembuat.
Arahkan kursor ke thumbnail permainan, klik tombol ⋯, dan pilih Salin Universe ID dan Salin Start Place ID, masing-masing.

Tambahkan skrip
Setelah Anda selesai mengatur webhook, bot, dan kunci API untuk penyimpanan data, tambahkan ke skrip yang menerapkan logika otomatisasi bot. Contoh berikut menggunakan Python 3.
Instal pustaka Python menggunakan perintah berikut:
Instal Pustakapip3 install discordpip3 install requestspip3 install urllib3==1.26.6Salin dan simpan skrip berikut yang sesuai dengan bagian yang berbeda dari logika bot di direktori yang sama:
bot_config.pyBOT_TOKEN = ""OPEN_CLOUD_API_KEY = ""ROBLOX_WEBHOOK_SECRET = ""# Kamus ID Start Place ke# (universe ID, daftar (nama penyimpanan data, scope, dan entry key)) untuk# Penyimpanan Data Standar# Data pengguna yang disimpan di bawah entri ini akan dihapusSTANDARD_DATA_STORE_ENTRIES = {# Start Place ID111111111: (# Universe ID222222222,[("StandardDataStore1", "Scope1", "Key1_{user_id}"),("StandardDataStore1", "Scope1", "Key2_{user_id}"),("StandardDataStore2", "Scope1", "Key3_{user_id}")]),33333333: (444444444,[("StandardDataStore3", "Scope1", "Key1_{user_id}")])}# Kamus ID Start Place ke# (universe ID, daftar (nama penyimpanan data, scope, dan entry key)) untuk# Penyimpanan Data Terurut# 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"""Memanggil API Open Cloud Penyimpanan Data untuk menghapus semua entri untuk user_id yang dikonfigurasi diSTANDARD_DATA_STORE_ENTRIES. Mengembalikan daftar penghapusan yang berhasil dan kegagalan untuk menghapus."""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"""Memanggil API Open Cloud Penyimpanan Data Terurut untuk menghapus semua entri untuk user_id yang dikonfigurasi diORDERED_DATA_STORE_ENTRIES. Mengembalikan daftar penghapusan yang berhasil dan kegagalan untuk menghapus."""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"""Menganalisis pesan yang diterima untuk tanda tangan Roblox dan timestamp, footer hanya disetel jika Andamengonfigurasi secret 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"""Memverifikasi tanda tangan Roblox dengan secret yang dikonfigurasi untuk memeriksa keabsahan"""def validate_signature(message, signature, timestamp):if not message or not signature or not timestamp:return False# Mencegah serangan replay 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# Memvalidasi 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"""Menganalisis pesan webhook yang diterima di Discord. Mengambil ID pengguna, mencegah serangan replayberdasarkan timestamp yang diterima, dan memverifikasi tanda tangan Roblox dengan secret yang dikonfigurasi untuk memeriksa keabsahan."""def parse_message(message):# Menganalisis pesan yang diterima 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 "", []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} sedang mendengarkan pesan Hak untuk Dihapus")"""Penangan untuk pesan webhook dari Roblox"""@client.eventasync def on_message(message):# Menganalisis dan memvalidasi pesanuser_id, start_place_ids = message_parser.parse_message(message)if not user_id or not start_place_ids:return# Menghapus data pengguna dari penyimpanan data standar[successes, failures] = data_stores_api.delete_standard_data_stores(user_id, start_place_ids)if successes:await message.reply(f"Data penyimpanan data standar dihapus untuk " +f"ID pengguna: {user_id}, data: {dict(successes)}")if failures:await message.reply(f"Gagal menghapus data penyimpanan data standar untuk " +f"ID pengguna: {user_id}, data: {dict(failures)}")# Menghapus data pengguna dari penyimpanan data terurut[successes, failures] = data_stores_api.delete_ordered_data_stores(user_id, start_place_ids)if successes:await message.reply(f"Data penyimpanan data terurut dihapus untuk " +f"ID pengguna: {user_id}, data: {dict(successes)}")if failures:await message.reply(f"Gagal menghapus data penyimpanan data terurut untuk " +f"ID pengguna: {user_id}, data: {dict(failures)}")client.run(bot_config.BOT_TOKEN)if __name__ == "__main__":run()Di file bot_config.py untuk konfigurasi utama bot:
- Atur BOT_TOKEN ke token yang dihasilkan oleh bot Anda.
- Atur OPEN_CLOUD_API_KEY sebagai kunci API yang Anda buat.
- Atur ROBLOX_WEBHOOK_SECRET sebagai secret yang Anda atur saat mengonfigurasi webhook di Dasbor Pembuat.
- Di kamus STANDARD_DATA_STORE_ENTRIES dan ORDERED_DATA_STORE_ENTRIES untuk menemukan penyimpanan data dari setiap catatan yang akan dihapus:
- Tambahkan ID Tempat Awal yang Anda salin sebagai kunci.
- Tambahkan ID Universe sebagai elemen pertama dari nilai tuple.
- Ganti elemen kedua dari tuple dengan nama, scope, nama entry key, dan ID Pengguna yang terkait dari penyimpanan data Anda. Jika Anda menggunakan skema data yang berbeda, modifikasi agar sesuai dengan skema data Anda sendiri.
Eksekusi perintah berikut untuk menjalankan bot:
Jalankan Bot Discordpython3 discord_bot.pyBot kemudian mulai mendengarkan dan memverifikasi webhook Roblox untuk permintaan hak untuk dihapus dan memanggil endpoint Open Cloud untuk menghapus penyimpanan data yang sesuai.
Uji
Anda dapat membuat dan menjalankan pesan uji untuk memverifikasi bahwa program kustom Anda dapat menangani permintaan hak untuk dihapus dengan baik dan menghapus data PII:
Kirim permintaan HTTP POST ke server webhook Discord Anda dengan body permintaan berikut:
Contoh Permintaancurl -X POST {serverUrl}-H 'Content-Type: application/json'-d '{"embeds":[{"title":"RightToErasureRequest","description":"Anda telah menerima notifikasi baru untuk Hak untuk Dihapus untuk ID Pengguna: {userIds} dalam permainan dengan ID: {gameIds}","footer":{"icon_url":"https://create.roblox.com/dashboard/assets/webhooks/roblox_logo_metal.png","text":"Roblox-Signature: {robloxSignature}, Timestamp: {timestamp}"}}]}'Jika Anda memiliki secret webhook:
- Hasilkan Roblox-Signature dengan menerapkan encoding HMAC-SHA256 pada kunci secret webhook Anda.
- Atur waktu saat ini menggunakan timestamp UTC dalam detik sebagai Timestamp.
Rakit description dalam format berikut:
Format Kolom Deskripsi{Timestamp}. Anda telah menerima notifikasi baru untuk Hak untuk Dihapus untuk ID Pengguna: {userId} dalam permainan dengan ID: {gameIds}`.Misalnya:
Contoh Kolom Deskripsi1683927229. Anda telah menerima notifikasi baru untuk Hak untuk Dihapus untuk ID Pengguna: 2425654247 dalam permainan dengan ID: 10539205763, 13260950955
Program Anda seharusnya dapat mengidentifikasi bahwa pesan Anda berasal dari sumber resmi Roblox karena Anda telah mengenkripsi pesan dengan secret Anda. Ini harus kemudian menghapus data PII yang terkait dengan permintaan Anda.
{
"embeds": [
{
"title": "RightToErasureRequest",
"description": "Anda telah menerima notifikasi baru untuk Hak untuk Dihapus untuk ID Pengguna: 2425654247 dalam permainan dengan ID: 10539205763, 13260950955",
"footer": {
"icon_url": "https://create.roblox.com/dashboard/assets/webhooks/roblox_logo_metal.png",
"text": "Roblox-Signature: UIe6GJ78MHCmU/zUKBYP3LV0lAqwWRFR6UEfPt1xBFw=, Timestamp: 1683927229"
}
}
]
}