全球數據保護和隱私法規賦予個人控制其數據的權利,包括請求刪除其數據的權利(通常稱為“刪除權”或“刪除權”)。如果您存儲任何個人數據或個人可識別信息(PII),例如用戶 ID,您有責任遵守適用的隱私框架,並在收到用戶請求後永久刪除此信息。更多信息可以在被遺忘的權利和創作者中找到。
考慮替代方案
對於數據存儲中的個人數據,最簡單的自動化選擇是配置 RTBF 刪除模板。當 Roblox 處理 RTBF 請求時,它會自動刪除匹配的標準或有序數據存儲條目,而無需您托管自定義代碼。要配置模板並查看支持的模式,請參見數據存儲被遺忘的權利 (RTBF)。
本頁的網路鉤子工作流程在以下情況下有用:
- 您在數據存儲之外存儲個人數據。
- 您的數據存儲架構或用例不受 RTBF 刪除模板的支持。
- 您需要自定義處理或刪除邏輯。
創作者網路鉤子工作流程
本教程演示如何設置網路鉤子並創建一個本地運行的 Discord 機器人,該機器人使用數據存儲的開放雲 API自動處理刪除權請求。此過程的工作流程如下:
- Roblox 支持從用戶那裡收到刪除權請求。
- Roblox 網路鉤子被觸發,包含用戶 ID 和他們加入的遊戲的起始地點 ID 列表作為有效負載。
- 您的機器人監聽這些網路鉤子通知,驗證其真實性,並利用數據存儲的開放雲 API刪除存儲在數據存儲中的 PII 數據。
- 機器人在 Discord 中回應網路鉤子消息,顯示刪除狀態。

使用第三方集成配置網路鉤子
在創建機器人之前,請在第三方消息應用程序上設置帶有網路鉤子集成的服務器。然後使用該服務器在創作者儀表板上配置網路鉤子。
設置服務器
以下步驟顯示如何使用 Discord 設置服務器。
創建一個新的 Discord 服務器。如果您不熟悉該過程,請參見Discord 支持。
服務器自動創建一個**#general頻道作為您的默認頻道。單擊#general頻道的編輯頻道**圖標。
在權限下,將頻道設置為私有。
與新服務器創建網路鉤子集成,將其命名為RTBF Hook。如果您不熟悉該過程,請參見Discord 支持。
複製網路鉤子 URL 並將其存儲在安全的地方。僅允許受信任的團隊成員訪問,因為洩露 URL 可能會使壞人發送假消息並可能刪除您的用戶數據。
在 Roblox 上配置網路鉤子
獲得第三方服務器 URL 後,使用它來配置網路鉤子在創作者儀表板上。確保您執行以下設置:
- 將 Discord 服務器 URL 添加為網路鉤子 URL。
- 包含自定義密鑰。雖然密鑰對於完成配置是可選的,但您應該包含一個以防止壞人冒充 Roblox 並刪除您的數據。有關密鑰使用的更多信息,請參見驗證網路鉤子安全性。
- 在觸發器下選擇刪除權請求。
您可以使用測試響應按鈕測試網路鉤子,以查看是否收到來自 Roblox 的通知在您服務器的**#general**頻道中。如果您沒有收到通知,請重試或檢查您的服務器設置以排除錯誤。
配置機器人
在您添加網路鉤子後,使用它來配置機器人,步驟如下。欲了解更多信息,請參見Discord 文檔。
- 轉到應用程序頁面。
- 創建一個新應用程序並將其命名為RTBF Bot。
- 系統將您重定向到機器人的常規信息設置。複製並安全地保存其應用程序 ID。
- 在設置菜單中,選擇OAuth2。
- 轉到OAuth2設置
- 啟用機器人範圍,顯示額外的機器人權限列表。
- 添加管理員權限。保存更改。
- 保存生成的 URL。
- 複製生成的 URL。保持應用程序設置頁面未關閉。
- 轉到生成的 URL。選擇目標服務器。單擊繼續按鈕,然後單擊授權按鈕。
- 返回應用程序設置頁面並轉到機器人設置。
- 在特權網關意圖部分,啟用消息內容意圖。
- 在機器人設置 > 構建機器人部分,將機器人令牌安全地保存以供後續步驟使用。如果您看不到令牌,請單擊重置令牌按鈕以生成新的令牌。
創建開放雲 API 密鑰
為了允許您的第三方機器人訪問您的數據存儲以存儲用戶的 PII 數據,請創建一個開放雲 API 密鑰,該密鑰可以訪問您的遊戲並添加數據存儲的刪除條目權限以進行數據刪除。如果您使用有序數據存儲來存儲 PII,則還需要添加有序數據存儲的寫入權限。完成後,複製並安全地保存 API 密鑰以便在後續步驟中使用。
獲取遊戲和地點的標識符
為了使機器人能夠定位用戶請求刪除的 PII 數據,獲取您打算為其使用機器人的所有遊戲的以下標識符:
- 宇宙 ID,您遊戲的唯一標識符。
- 起始地點 ID,遊戲的起始地點的唯一標識符。
要獲取這些標識符:
轉到創作者儀表板。
將鼠標懸停在遊戲的縮略圖上,單擊**⋯按鈕,然後分別選擇複製宇宙 ID和複製起始地點 ID**。

添加腳本
在您完成設置網路鉤子、機器人和數據存儲的 API 密鑰後,將它們添加到實現機器人自動化邏輯的腳本中。以下示例使用 Python 3。
使用以下命令安裝 Python 庫:
安裝庫pip3 install discordpip3 install requestspip3 install urllib3==1.26.6複製並保存以下腳本,對應於機器人邏輯的不同部分,並將其放在同一目錄中:
bot_config.pyBOT_TOKEN = ""OPEN_CLOUD_API_KEY = ""ROBLOX_WEBHOOK_SECRET = ""# 起始地點 ID 到# (宇宙 ID,(數據存儲名稱、範圍和條目鍵) 的列表) 的字典# 存儲在這些條目下的用戶數據將被刪除STANDARD_DATA_STORE_ENTRIES = {# 起始地點 ID111111111: (# 宇宙 ID222222222,[("StandardDataStore1", "Scope1", "Key1_{user_id}"),("StandardDataStore1", "Scope1", "Key2_{user_id}"),("StandardDataStore2", "Scope1", "Key3_{user_id}")]),33333333: (444444444,[("StandardDataStore3", "Scope1", "Key1_{user_id}")])}# 起始地點 ID 到# (宇宙 ID,(數據存儲名稱、範圍和條目鍵) 的列表) 的字典# 存儲在這些條目下的用戶數據將被刪除ORDERED_DATA_STORE_ENTRIES = {111111111: (222222222,[("OrderedDataStore1", "Scope2", "Key4_{user_id}")])}data_stores_api.pyimport requestsimport bot_configfrom collections import defaultdict"""調用數據存儲開放雲 API 刪除所有為 user_id 配置的條目STANDARD_DATA_STORE_ENTRIES。返回成功刪除和刪除失敗的列表。"""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"""調用有序數據存儲開放雲 API 刪除所有為 user_id 配置的條目ORDERED_DATA_STORE_ENTRIES。返回成功刪除和刪除失敗的列表。"""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"""解析接收到的消息以獲取 Roblox 簽名和時間戳,只有在您配置了網路鉤子密鑰時,頁腳才會設置"""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"""使用配置的密鑰驗證 Roblox 簽名以檢查有效性"""def validate_signature(message, signature, timestamp):if not message or not signature or not timestamp:return False# 防止在 300 秒窗口內的重放攻擊request_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# 驗證簽名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# 有效簽名return True"""解析 Discord 上接收到的網路鉤子消息。提取用戶 ID,根據接收到的時間戳防止重放攻擊,並使用配置的密鑰驗證 Roblox 簽名以檢查有效性。"""def parse_message(message):# 解析接收到的消息以獲取用戶 ID 和遊戲 IDif 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} 正在監聽刪除權消息")"""處理來自 Roblox 的網路鉤子消息"""@client.eventasync def on_message(message):# 解析並驗證消息user_id, start_place_ids = message_parser.parse_message(message)if not user_id or not start_place_ids:return# 刪除標準數據存儲用戶數據[successes, failures] = data_stores_api.delete_standard_data_stores(user_id, start_place_ids)if successes:await message.reply(f"已刪除標準數據存儲數據 " +f"用戶 ID: {user_id}, 數據: {dict(successes)}")if failures:await message.reply(f"未能刪除標準數據存儲數據 " +f"用戶 ID: {user_id}, 數據: {dict(failures)}")# 刪除有序數據存儲用戶數據[successes, failures] = data_stores_api.delete_ordered_data_stores(user_id, start_place_ids)if successes:await message.reply(f"已刪除有序數據存儲數據 " +f"用戶 ID: {user_id}, 數據: {dict(successes)}")if failures:await message.reply(f"未能刪除有序數據存儲數據 " +f"用戶 ID: {user_id}, 數據: {dict(failures)}")client.run(bot_config.BOT_TOKEN)if __name__ == "__main__":run()在bot_config.py文件中進行機器人的主要配置:
- 將BOT_TOKEN設置為您機器人生成的令牌。
- 將OPEN_CLOUD_API_KEY設置為您創建的 API 密鑰。
- 將ROBLOX_WEBHOOK_SECRET設置為您在創作者儀表板上配置網路鉤子時設置的密鑰。
- 在STANDARD_DATA_STORE_ENTRIES和ORDERED_DATA_STORE_ENTRIES字典中定位要刪除的每條記錄的數據存儲:
- 將您複製的起始地點 ID 作為鍵添加。
- 將宇宙 ID 作為元組值的第一個元素添加。
- 將元組的第二個元素替換為數據存儲的名稱、範圍、條目鍵名稱和相關的用戶 ID。如果您使用不同的數據架構,請根據自己的數據架構進行修改。
執行以下命令以運行機器人:
運行 Discord 機器人python3 discord_bot.py機器人然後開始監聽並驗證 Roblox 網路鉤子以獲取刪除權請求,並調用開放雲端端點以刪除相應的數據存儲。
測試
您可以創建並運行測試消息,以驗證您的自定義程序是否能夠正確處理刪除權請求並刪除 PII 數據:
向您的 Discord 網路鉤子服務器發送 HTTP POST 請求,請求主體如下:
示例請求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}"}}]}'如果您有網路鉤子密鑰:
- 通過對您的網路鉤子密鑰應用 HMAC-SHA256 編碼來生成Roblox-Signature。
- 使用 UTC 時間戳的當前時間設置Timestamp。
將description組合成以下格式:
描述字段格式{Timestamp}. You have received a new notification for Right to Erasure for the User Id: {userId} in the game(s) with Ids: {gameIds}`.例如:
示例描述字段1683927229. You have received a new notification for Right to Erasure for the User Id: 2425654247 in the game(s) with Ids: 10539205763, 13260950955
您的程序應能夠識別您的消息來自官方 Roblox 源,因為您使用密鑰對消息進行了編碼。然後,它應刪除與您的請求相關的 PII 數據。
{
"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/dashboard/assets/webhooks/roblox_logo_metal.png",
"text": "Roblox-Signature: UIe6GJ78MHCmU/zUKBYP3LV0lAqwWRFR6UEfPt1xBFw=, Timestamp: 1683927229"
}
}
]
}