Quy định bảo vệ dữ liệu chung GDPR là một quy định châu Âu về quyền riêng tưvệ dữ liệu và riêng tưNó cấp cho cá nhân quyền yêu cầu xóa dữ liệu cá nhân của họ, được gọi là quyền xóa.Nếu bạn lưu bất kỳ Thông tin Nhận dạng Cá nhân (TTCN) của người dùng, chẳng hạn như ID người dùng của họ, bạn phải tuân thủ các yêu cầu của GDPR bằng cách xóa thông tin này khi nhận được yêu cầu của người dùng.
Thay vì xử lý yêu cầu thủ công, bạn có thể thiết lập một webhook và sử dụng một bot trong ứng dụng trò chuyện bên thứ ba để tự động hóa quá trình.Vì cửa hàng dữ liệu là cách phổ biến nhất để lưu trữ dữ liệu PII, bài hướng dẫn này cung cấp một ví dụ về cách tạo một bot trong Guilded hoặc Discord sử dụng Open Cloud API cho cửa hàng dữ liệu để xóa dữ liệu PII như một giải pháp tự động hóa.
Quy trình làm việc
Sau khi hoàn thành hướng dẫn này, bạn nên có thể tạo một chương trình tùy chỉnh chạy địa phương tự động hóa việc xử lý yêu cầu xóa bỏ của người dùng.Quy trình làm việc cho quá trình này như sau:
- Hỗ trợ Roblox nhận được quyền yêu cầu xóa từ một người dùng.
- Webhook Roblox được kích hoạt, chứa ID người dùng và một danh sách các ID điểm bắt đầu cho các trải nghiệm họ đã tham gia trong payload.
- Bot của bạn lắng nghe các thông báo webhook này, xác minh tính xác thực của chúng và sử dụng Open Cloud API cho kho dữ liệu để xóa dữ liệu PII được lưu trong kho dữ liệu.
- Bot trả lời tin nhắn webhook trong Discord hoặc Guilded với tình trạng tháixóa bỏ.

Cài đặt một webhook với tích hợp bên thứ ba
Trước khi tạo một bot, hãy thiết lập một máy chủ với tích hợp webhook trên ứng dụng trò chuyện bên thứ ba.Sau đó sử dụng máy chủ để cấu hình một webhook trên Bảng điều khiển Nhà sáng tạo.
Thiết lập một máy chủ
Các bước tiếp theo cho thấy cách cài đặt máy chủ bằng cách sử dụng Guilded hoặc Discord.
- Tạo một máy chủ Guilded mới. Nếu bạn không quen thuộc với quá trình, hãy xem Hỗ trợ Guilded.
- Dưới cài đặt Riêng tư , hãy đặt máy chủ thành riêng tư.Máy chủ tự động tạo một kênh riêng tư #general là kênh mặc định của bạn.
- Tạo một tích hợp webhook với máy chủ mới và đặt tên cho nó mà bạn có thể dễ dàng hiểu, chẳng hạn như GDPR Hook .Nếu bạn không quen thuộc với quá trình, xem Hỗ trợ Guilded.
- Sao chép URL webhook và lưu nó ở một địa điểman toàn.Chỉ cho phép các thành viên trong nhóm tin cậy truy cập nó, vì rò rỉ URL có thể kích hoạt các diễn viên xấu gửi tin nhắn giả mạo và có thể xóa dữ liệu người dùng của bạn.
Cài đặt một webhook trên Roblox
Sau khi có được URL máy chủ bên thứ ba, hãy sử dụng nó để tùy chỉnh một webhook trên Bảng điều khiển Nhà sáng tạo.hãy chắc chắn bạn thực hiện các cài đặt sau:
- Thêm URL máy chủ Guilded hoặc Discord là URL Webhook .
- Bao gồm một bí mật tùy chỉnh Bí mật .Mặc dù một bí mật là tùy chọn để hoàn thành cấu hình, bạn nên bao gồm một để ngăn chặn các diễn viên xấu giả mạo Roblox và xóa dữ liệu của bạn.Để biết thêm thông tin về việc sử dụng một bí mật, xem Xác minh bảo mật webhook.
- Chọn Yêu cầu xóa bỏ bên phải dưới Kích hoạt .
Bạn có thể kiểm tra webhook bằng nút Trả lời kiểm tra để xem liệu bạn có nhận được thông báo trong kênh # général của máy chủ của bạn từ Roblox không.Nếu bạn không nhận được thông báo, hãy thử lại hoặc kiểm tra cài đặt máy chủ của bạn để giải quyết lỗi.

Cài đặt một bot
Sau khi bạn thêm webhook, sử dụng nó để cấu hình bot với các bước sau:
Mở danh sách tất cả các máy chủ bằng cách nhấp vào biểu tượng của nó hoặc sử dụng tắt:
- CtrlS trên Windows.
- ⌘S trên Mac.
Chọn máy chủ của bạn để nhận được thông báo xóa bỏ đúng.
Mở rộng danh sách dưới Trang chủ máy chủ và chọn Quản lý bots .
Máy chủ tự động tạo một kênh riêng tư #general là kênh mặc định của bạn.
Nhấp vào nút Tạo một bot và thêm tên bot. Guilded chuyển bạn đến trang cấu hình bot.
Chọn phần API trên trang cấu hình bot.
Trong phần Token , nhấp vào nút Tạo Token .
Lưu và lưu token được tạo ra ở một địa điểman toàn.
Tạo một chìa khóa API Mở đám mây
Để cho phép bot bên thứ ba truy cập các kho dữ liệu của bạn để lưu trữ dữ liệu PII của người dùng, tạo một chìa khóa API Mở đám mây có thể truy cập kinh nghiệm của bạn và thêm quyền Xóa mục cho các kho dữ liệu để xóa dữ liệu.Nếu bạn sử dụng cửa hàng dữ liệu xếp hạng để lưu trữ TTCN, bạn cũng cần thêm quyền Viết của cửa hàng dữ liệu xếp hạng.Sau khi hoàn thành, sao chép và lưu chìa khóa API ở một vị trí an toàn để sử dụng trong các bước sau.
Nhận định danh của trải nghiệm và địa điểm
Đối với bot để tìm dữ liệu PII được yêu cầu bởi người dùng để xóa, lấy các nhận dạng sau của tất cả các trải nghiệm mà bạn dự định sử dụng bot:
- ID Vũ trụ , nhận dạng duy nhất của trải nghiệm của bạn.
- ID địa điểm bắt đầu Bắt đầu ID , nhận dạng duy nhất của địa điểm bắt đầu của một trải nghiệm.
Để có được các nhận dạng này:
Điều hướng đến Bảng điều khiển Nhà sáng tạo.
Vượt qua hình nhỏcủa một trải nghiệm, nhấp vào nút ⋯ và chọn Sao chép ID Vũ trụ và Sao chép ID điểm bắt đầu , tương ứng.
Thêm các kịch bản
Sau khi bạn hoàn thành việc thiết lập webhook, bot và chìa khóa API cho các kho lưu trữ dữ liệu, thêm chúng vào các tập lệnh thực hiện logic tự động hóa của bot.Ví dụ sau đây sử dụng Python 3:
Cài đặt thư viện Python bằng các lệnh sau:
Cài đặt thư việnpip3 install guilded.py==1.8.0pip3 install requestspip3 install urllib3==1.26.6Sao chép và lưu các kịch bản sau đây tương ứng với các phần khác nhau của logic bot trong cùng một thư mục:
bot_config.pyBOT_TOKEN = ""OPEN_CLOUD_API_KEY = ""ROBLOX_WEBHOOK_SECRET = ""# Từ điển của ID nơi bắt đầu đến# (ID vũ trụ, danh sách (tên cửa hàng dữ liệu, phạm vi và chìa khóa nhập)) cho# Kho dữ liệu tiêu chuẩn# Dữ liệu người dùng được lưu dưới các mục này sẽ bị xóaSTANDARD_DATA_STORE_ENTRIES = {# Bắt đầu ID địa điểm111111111: (# ID Vũ trụ222222222,[("StandardDataStore1", "Scope1", "Key1_{user_id}"),("StandardDataStore1", "Scope1", "Key2_{user_id}"),("StandardDataStore2", "Scope1", "Key3_{user_id}")]),33333333: (444444444,[("StandardDataStore3", "Scope1", "Key1_{user_id}")])}# Từ điển của ID nơi bắt đầu đến# (ID vũ trụ, danh sách (tên cửa hàng dữ liệu, phạm vi và chìa khóa nhập)) cho# Cửa hàng dữ liệu được sắp xếp# Dữ liệu người dùng được lưu dưới các mục này sẽ bị xóaORDERED_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# Ngăn chặn cuộc tấn công lặp lại trong vòng 300 giâyrequest_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# Xác minh chữ kýtimestamp_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# Chữ ký hợp lệreturn 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):# Xử lý tin nhận cho ID người dùng và ID trò chơiif 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 "", []bot guilded_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):# Phân tích và xác minh tin nhắnuser_id, start_place_ids = message_parser.parse_message(message)if not user_id or not start_place_ids:return# Xóa dữ liệu cửa hàng dữ liệu tiêu chuẩn của người dùng[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)}")# Xóa dữ liệu cửa hàng dữ liệu được xếp hạng dữ liệu người dùng[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()Trên tập tin bot_config.py cho cấu hình chính của bot:
- Set BOT_TOKEN đến token được tạo bởi bot của bạn.
- Set OPEN_CLOUD_API_KEY như chìa khóa API bạn đã tạo.
- Set ROBLOX_WEBHOOK_SECRET như bí mật bạn đặt khi cấu hình webhook trên Bảng điều khiển Nhà sáng tạo.
- Trong STANDARD_DATA_STORE_ENTRIES và ORDERED_DATA_STORE_ENTRIES từ điển để tìm kiếm kho lưu trữ dữ liệu của mỗi bản ghi để xóa:
- Thêm ID Điểm Bắt đầu sao chép của bạn làm chìa khóa.
- Thêm ID vũ trụ làm thành phần đầu tiên của giá trị tuple.
- Thay thế yếu tố thứ hai của tuple bằng tên, phạm vi, tên khóa nhập và ID người dùng liên quan của các kho dữ liệu của bạn.Nếu bạn sử dụng một sơ đồ dữ liệu khác, thay đổi để phù hợp với sơ đồ dữ liệu của riêng bạn theo thứ tự.
Thực hiện lệnh sau để chạy bot:
Chạy Bot Guildedpython3 guilded_bot.pyBot sau đó bắt đầu lắng nghe và xác minh webhook Roblox để có quyền xóa Yêu cầu và gọi điểm cuối của Đám mây Mở để xóa kho dữ cửa hàngtương ứng.
Thử nghiệm
Bạn có thể tạo và chạy một tin nhắn thử để xác minh rằng chương trình tùy chỉnh của bạn có thể xử lý đúng các yêu cầu xóa bỏ và xóa dữ liệu PII:
Gửi một yêu cầu HTTP POST đến máy chủ webhook Guilded hoặc Discord của bạn với cơ thânyêu cầu sau:
Yêu cầu ví dụcurl -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}"}}]}'Nếu bạn có một bí mật webhook:
- Tạo một Roblox-Signature bằng cách áp dụng mã hóa HMAC-SHA256 cho chìa khóa bí mật webhook của bạn.
- Đặt thời gian hiện tại bằng cách sử dụng thời gian UTC trong giây lúc Timestamp .
Xếp lại description theo định dạng sau:
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}`.Ví dụ:
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
Chương trình của bạn nên có thể xác định rằng tin nhắn của bạn đến từ nguồn chính thức của Roblox vì bạn đã mã hóa tin nhắn bằng bí mật của bạn.Sau đó, nó nên xóa dữ liệu PII liên quan đến yêu cầu của bạn.
Ví dụ về cơ thể
{
"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/assets/webhooks/roblox_logo_metal.png",
"text": "Roblox-Signature: UIe6GJ78MHCmU/zUKBYP3LV0lAqwWRFR6UEfPt1xBFw=, Timestamp: 1683927229"
}
}
]
}