กฎหมายคุ้มครองข้อมูลทั่วไป GDPR เป็นกฎหมายของยุโรปเกี่ยวกับการคุ้มครองข้อมูลและความเป็นส่วนตัวมันให้สิทธิ์แก่บุคคลในการขอลบข้อมูลส่วนบุคคลที่รู้จักกันในชื่อ สิทธิ์ในการลบหากคุณจัดเก็บข้อมูลระบุตัวบุคคลใดๆ (ข้อมูลที่ใช้ระบุตัวบุคคลได้ (PII)) ของผู้ใช้ของคุณ เช่น รหัสผู้ใช้ของพวกเขา คุณต้องปฏิบัติตามข้อกำหนดของ GDPR โดยลบข้อมูลนี้เมื่อได้รับคำขอจากผู้ใช้
แทนที่จะจัดการคำขอด้วยตนเองคุณสามารถ ตั้งค่าเว็บฮุค และใช้บอทภายในแอปส่งข้อความของบุคคลที่สามเพื่ออัตโนมัติ化กระบวนการเนื่องจาก ร้านข้อมูล เป็นวิธีที่พบบ่อยที่สุดสำหรับการจัดเก็บข้อมูล PII คู่มือนี้จะให้ตัวอย่างวิธีการสร้างบอทภายใน Guilded หรือ Discord ที่ใช้ Open Cloud API สำหรับร้านข้อมูล เพื่อลบข้อมูล PII ในฐานะที่เป็นโซลูชันการอัตโนมัติ
การไหลของงาน
เมื่อเสร็จสิ้นบทแนะนำนี้แล้ว คุณควรสามารถสร้างโปรแกรมกำหนดเองที่ทำงานในท้องถิ่นที่สามารถอัตโนมัติการจัดการคำขอลบของผู้ใช้ได้กระบวนการทำงานสำหรับกระบวนการนี้มีดังนี้:
- การสนับสนุน Roblox ได้รับสิทธิ์ในการร้องขอการลบจากผู้ใช้
- เวบฮุคของ Roblox ถูกกระตุ้นโดยมีรหัสผู้ใช้และรายการรหัสสถานที่เริ่มต้นสำหรับประสบการณ์ที่พวกเขาได้เข้าร่วมในเพย์โหลด
- บอทของคุณฟังการแจ้งเตือนเว็บฮุคเหล่านี้ ตรวจสอบความถูกต้อง และใช้ API เปิดคลาวด์สำหรับร้านข้อมูล เพื่อลบข้อมูล PII ที่เก็บไว้ในร้านข้อมูล
- บอทตอบกลับข้อความ webhook ใน Discord หรือ Guilded ด้วยสถานะการลบ

กำหนดค่าเว็บฮุคด้วยการผสานรวมของบุคคลที่สาม
ก่อนที่จะสร้างบอท ตั้งค่าเซิร์ฟเวอร์ด้วยการผสานรวมเว็บฮุกบนแอปพลิเคชันการส่งข้อความบุคคลที่สามจากนั้นใช้เซิร์ฟเวอร์เพื่อกำหนดค่าเว็บฮุคบนแดชบอร์ดของผู้สร้าง
ตั้งค่าเซิร์ฟเวอร์
ขั้นตอนต่อไปแสดงวิธีการตั้งค่าเซิร์ฟเวอร์โดยใช้ Guilded หรือ Discord
- สร้างเซิร์ฟเวอร์ Guilded ใหม่ หากคุณไม่คุ้นเคยกับกระบวนการ ดู การสนับสนุน Guilded
- ภายใต้การตั้งค่า ความเป็นส่วนตัว ตั้งเซิร์ฟเวอร์ให้เป็นส่วนตัวเซิร์ฟเวอร์สร้างช่องส่วนตัว #general โดยอัตโนมัติเป็นช่องเริ่มต้นของคุณ
- สร้างการผสานรวมเว็บฮุคกับเซิร์ฟเวอร์ใหม่และให้ชื่อที่คุณสามารถเข้าใจได้ง่ายเช่น GDPR Hookหากคุณไม่คุ้นเคยกับกระบวนการดู การสนับสนุน Guilded
- คัดลอก URL ของ webhook และเก็บไว้ในสถานที่ปลอดภัยอนุญาตให้สมาชิกทีมที่เชื่อถือได้เข้าถึงเท่านั้น เนื่องจากการรั่วไหลของ URL สามารถเปิดใช้งานผู้กระทำผิดร้ายแรงที่สามารถส่งข้อความปลอมและลบข้อมูลผู้ใช้ของคุณได้
กำหนดค่าเว็บฮุคบน Roblox
หลังจากได้รับ URL เซิร์ฟเวอร์ของบุคคลที่สามแล้วใช้มันเพื่อ กำหนดค่าเว็บฮุค แดชบอร์ดครีเอเตอร์:
- เพิ่ม URL เซิร์ฟเวอร์ของ Guilded หรือ Discord เป็น URL เว็บฮุค * รวมความลับที่กําหนดเอง ลับ แม้ว่าความลับจะเป็นตัวเลือกสำหรับการสรุปการกำหนดค่า คุณควรรวมหนึ่งเพื่อป้องกันไม่ให้ผู้แสวงหาประโยชน์ปลอมตัวเป็น Roblox และลบข้อมูลของคุณสำหรับข้อมูลเพิ่มเติมเกี่ยวกับการใช้งานของความลับดูที่ ตรวจสอบความปลอดภัยของเว็บฮุค
- เลือก สิทธิ์ในการขอลบ ภายใต้ การกระตุ้น .
คุณสามารถทดสอบ webhook โดยใช้ปุ่ม ตอบสนองการทดสอบ เพื่อดูว่าคุณได้รับการแจ้งเตือนในช่อง #ทั่วไป ของเซิร์ฟเวอร์ของคุณจาก Robloxหากคุณไม่ได้รับการแจ้งเตือน ลองอีกครั้งหรือตรวจสอบการตั้งค่าเซิร์ฟเวอร์ของคุณเพื่อแก้ไขข้อผิดพลาด

กำหนดค่าบอท
หลังจากที่คุณเพิ่ม webhook ใช้มันเพื่อกำหนดค่าบอทด้วยขั้นตอนต่อไปนี้:
เปิดรายการ เซิร์ฟเวอร์ทั้งหมด โดยคลิกที่ไอคอนหรือใช้ลัด:
- CtrlS บน Windows
- ⌘S บน Mac
เลือกเซิร์ฟเวอร์ของคุณเพื่อรับสิทธิ์แจ้งเตือนการลบ
ขยายรายการภายใต้ บ้านเซิร์ฟเวอร์ และเลือก จัดการบอท
เซิร์ฟเวอร์สร้างช่องส่วนตัว #general โดยอัตโนมัติเป็นช่องเริ่มต้นของคุณ
คลิกที่ปุ่ม สร้างบอท และเพิ่มชื่อบอท Guilded เปลี่ยนเส้นทางคุณไปยังหน้าการกำหนดค่าบอท
เลือกส่วน API บนหน้าการกำหนดค่าบอท
ในส่วน โทเค็น คลิกปุ่ม สร้างโทเค็น
บันทึกและเก็บโทเคนที่สร้างขึ้นในสถานที่ปลอดภัย
สร้างคีย์ API เปิดของคลาวด์
เพื่ออนุญาตให้บอทบุคคลที่สามเข้าถึงสถานที่เก็บข้อมูลของคุณเพื่อจัดเก็บข้อมูล PII ของผู้ใช้ สร้างคีย์ API เปิดของคลาวด์ ที่สามารถเข้าถึงประสบการณ์ของคุณและเพิ่มสิทธิ์ ลบรายการ ของสถานที่เก็บข้อมูลสำหรับการลบข้อมูลหากคุณใช้ร้านข้อมูลที่สั่งซื้อเพื่อเก็บ ข้อมูลที่ใช้ระบุตัวบุคคลได้ (PII)คุณยังต้องเพิ่มสิทธิ์ เขียน ของร้านข้อมูลที่สั่งซื้อหลังจากเสร็จสิ้นแล้ว คัดลอกและบันทึกคีย์ API ในตำแหน่งที่ปลอดภัยเพื่อใช้ในขั้นตอนต่อไป
รับตัวระบุประสบการณ์และสถานที่
เพื่อให้บอทค้นหาข้อมูล PII ที่ผู้ใช้ร้องขอให้ลบ รับตัวระบุต่อไปนี้ของประสบการณ์ทั้งหมดที่คุณตั้งใจจะใช้บอท:
- ID จักรวาล **** รหัสที่ไม่ซ้ำกันของประสบการณ์ของคุณ
- รหัสสถานที่เริ่มต้น ID ซึ่งเป็นรหัสระบุเฉพาะของสถานที่เริ่มต้นของประสบการณ์
เพื่อรับตัวระบุเหล่านี้:
นําทางไปยัง แดชบอร์ดผู้สร้าง
เลื่อนเมาส์ไปที่ภาพรวมของประสบการณ์, คลิกปุ่ม ⋯ และเลือก คัดลอก ID จักรวาล และ คัดลอก ID จุดเริ่มต้น ตามลำดับ
เพิ่มสคริปต์
หลังจากที่คุณเสร็จสิ้นการตั้งค่าเว็บฮุค บอท และคีย์ API สําหรับร้านข้อมูล เพิ่มลงในสคริปที่ใช้โลจิสติกส์การอัตโนมัติของบอทตัวอย่างต่อไปนี้ใช้ Python 3:
ติดตั้งไลบรารี Python โดยใช้คำสั่งต่อไปนี้:
ติดตั้งไลบรารีpip3 install guilded.py==1.8.0pip3 install requestspip3 install urllib3==1.26.6คัดลอกและบันทึกสคริปต์ต่อไปนี้ตามส่วนต่างๆ ของโลจิสติกของบอทในไดเรกที่เดียวกัน:
บอท_config.pyBOT_TOKEN = ""OPEN_CLOUD_API_KEY = ""ROBLOX_WEBHOOK_SECRET = ""# สารานุกรมของรหัสสถานที่เริ่มต้นไปยัง# (ID จักรวาล, รายการ (ชื่อเก็บข้อมูล, ขอบเขต, คีย์)) สำหรับ# ร้านข้อมูลมาตรฐาน# ข้อมูลผู้ใช้ที่จัดเก็บภายใต้รายการเหล่านี้จะถูกลบSTANDARD_DATA_STORE_ENTRIES = {# รหัสสถานที่เริ่มต้น111111111: (# ID จักรวาล222222222,[("StandardDataStore1", "Scope1", "Key1_{user_id}"),("StandardDataStore1", "Scope1", "Key2_{user_id}"),("StandardDataStore2", "Scope1", "Key3_{user_id}")]),33333333: (444444444,[("StandardDataStore3", "Scope1", "Key1_{user_id}")])}# สารานุกรมของรหัสสถานที่เริ่มต้นไปยัง# (ID จักรวาล, รายการ (ชื่อเก็บข้อมูล, ขอบเขต, คีย์)) สำหรับ# จัดเก็บข้อมูลตามลําดับ# ข้อมูลผู้ใช้ที่จัดเก็บภายใต้รายการเหล่านี้จะถูกลบORDERED_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# ป้องกันการโจมตีเล่นซ้ำภายในหน้าต่าง 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"""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):# แยกข้อความที่ได้รับสำหรับรหัสผู้ใช้และรหัสเกมif 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):# แยกและตรวจสอบข้อความ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"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)}")# ลบข้อมูลสตอเรจข้อมูลที่สั่งซื้อของผู้ใช้[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()ในไฟล์ bot_config.py สำหรับการกำหนดค่าหลักของบอท:
- ตั้ง BOT_TOKEN ให้เป็นโทเค็นที่สร้างโดยบอทของคุณ
- ตั้ง OPEN_CLOUD_API_KEY เป็นคีย์ API ที่คุณสร้าง
- ตั้ง ROBLOX_WEBHOOK_SECRET เป็นความลับที่คุณกำหนดเมื่อกำหนดค่าเว็บฮุคบนแดชบอร์ดของผู้สร้าง
- ใน STANDARD_DATA_STORE_ENTRIES และ ORDERED_DATA_STORE_ENTRIES สารานุกรมสำหรับการค้นหาคลังข้อมูลของแต่ละบันทึกที่จะลบ:
- เพิ่ม ID จุดเริ่มต้นที่คัดลอกของคุณเป็นคีย์
- เพิ่ม ID จักรวาลเป็นองค์ประกอบแรกของค่า tuple
- แทนที่องค์ประกอบที่สองของ tuple ด้วยชื่อ ขอบเขต ชื่อกุญแจเข้า และรหัสผู้ใช้ที่เกี่ยวข้องกับคลังข้อมูลของคุณหากคุณใช้สเคมาโลจิกข้อมูลที่แตกต่างกันแก้ไขให้เหมาะกับสเคมาโลจิกข้อมูลของคุณตามลำดับ
ดำเนินการตามคำสั่งต่อไปนี้เพื่อดําเนินการบอท:
เรียกใช้บอท Guildedpython3 guilded_bot.pyบอทจะเริ่มฟังและตรวจสอบ webhooks Roblox สําหรับสิทธิ์ในการลบคําขอและโทรไปที่จุดสิ้นสุดของเมฆเปิดเพื่อลบคลังข้อมูลที่เกี่ยวข้อง
ทดสอบ
คุณสามารถสร้างและดำเนินการส่งข้อความทดสอบเพื่อตรวจสอบว่าโปรแกรมกำหนดเองของคุณสามารถจัดการกับคำขอลบได้อย่างถูกต้องและลบข้อมูล PII ได้:
ส่งคำขอ HTTP POST ไปยังเซิร์ฟเวอร์เว็บฮุคของ Guilded หรือ Discord ร่างกาย:
ตัวอย่างคำขอ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}"}}]}'หากคุณมีความลับเว็บฮุค:
- สร้าง Roblox-Signature โดยใช้การเข้ารหัส HMAC-SHA256 กับคีย์ลับของ webhook ของคุณ
- ตั้งเวลาปัจจุบันโดยใช้เวลาที่บันทึก UTC ในวินาทีเป็น Timestamp
รวบรวม description ในรูปแบบต่อไปนี้:
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}`.ตัวอย่าง:
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
โปรแกรมของคุณควรสามารถระบุได้ว่าข้อความของคุณมาจากแหล่ง 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/ashboard/assets/webhooks/roblox_logo_metal.png",
"text": "Roblox-Signature: UIe6GJ78MHCmU/zUKBYP3LV0lAqwWRFR6UEfPt1xBFw=, Timestamp: 1683927229"
}
}
]
}