入門
NIP-09 事件刪除
深入了解 Nostr 的事件刪除機制,刪除請求和中繼器處理。
8 分鐘
什麼是 NIP-09?
NIP-09 定義了 Nostr 中的事件刪除機制。用戶可以發布 kind 5 事件來請求刪除 自己先前發布的事件。這是一種「刪除請求」而非強制刪除,中繼器可以選擇是否執行。
重要: 刪除在 Nostr 中不是強制性的。中繼器可能忽略刪除請求, 其他用戶可能已經保存了你的事件。發布前請謹慎考慮。
事件結構
{
"kind": 5,
"pubkey": "<author-pubkey>",
"content": "刪除原因(可選)",
"tags": [
["e", "<event-id-to-delete>"],
["e", "<another-event-id>"],
["a", "<kind>:<pubkey>:<d-tag>"] // 刪除可替換事件
],
"created_at": 1234567890,
"id": "...",
"sig": "..."
} 刪除類型
| 標籤 | 用途 | 格式 |
|---|---|---|
| "e" | 刪除特定事件 | ["e", "<event-id>"] |
| "a" | 刪除可替換事件 | ["a", "<kind>:<pubkey>:<d>"] |
範例
刪除單個事件
{
"kind": 5,
"content": "發錯了",
"tags": [
["e", "abc123..."]
]
} 刪除多個事件
{
"kind": 5,
"content": "清理舊貼文",
"tags": [
["e", "abc123..."],
["e", "def456..."],
["e", "ghi789..."]
]
} 刪除可替換事件(如長文)
{
"kind": 5,
"content": "撤回文章",
"tags": [
["a", "30023:abc123...:my-article-slug"]
]
}
// "a" 標籤格式:<kind>:<pubkey>:<d-tag>
// 用於 kind 30000+ 的可替換事件 程式碼範例
發送刪除請求
async function deleteEvent(eventId, reason = '') {
const deleteEvent = {
kind: 5,
created_at: Math.floor(Date.now() / 1000),
content: reason,
tags: [
['e', eventId]
]
}
// 簽名
const signed = await window.nostr.signEvent(deleteEvent)
// 發送到中繼器
relay.send(JSON.stringify(['EVENT', signed]))
return signed
}
// 使用
await deleteEvent('abc123...', '發布錯誤')
await deleteEvent('def456...') // 無原因 批量刪除
async function deleteEvents(eventIds, reason = '') {
const deleteEvent = {
kind: 5,
created_at: Math.floor(Date.now() / 1000),
content: reason,
tags: eventIds.map(id => ['e', id])
}
const signed = await window.nostr.signEvent(deleteEvent)
relay.send(JSON.stringify(['EVENT', signed]))
return signed
}
// 批量刪除
await deleteEvents([
'abc123...',
'def456...',
'ghi789...'
], '清理舊內容') 檢查事件是否已刪除
async function isDeleted(eventId, authorPubkey) {
return new Promise((resolve) => {
const sub = relay.subscribe([{
kinds: [5],
authors: [authorPubkey],
'#e': [eventId]
}])
let deleted = false
sub.on('event', (event) => {
// 確認刪除事件是同一作者發的
if (event.pubkey === authorPubkey) {
deleted = true
}
})
sub.on('eose', () => {
sub.close()
resolve(deleted)
})
})
}
// 使用
const deleted = await isDeleted('abc123...', 'author-pubkey')
if (deleted) {
console.log('此事件已被作者刪除')
} 中繼器行為
刪除事件
中繼器收到 kind 5 後,刪除對應的事件
拒絕重發
已刪除的事件不再接受重新發布
保留刪除記錄
保存 kind 5 事件供客戶端查詢
忽略刪除
某些中繼器可能完全忽略刪除請求
驗證規則
function validateDeletion(deleteEvent, targetEvent) {
// 1. 必須是 kind 5
if (deleteEvent.kind !== 5) return false
// 2. 刪除者必須是原事件作者
if (deleteEvent.pubkey !== targetEvent.pubkey) return false
// 3. 刪除事件必須引用目標事件
const eTags = deleteEvent.tags.filter(t => t[0] === 'e')
const hasTarget = eTags.some(t => t[1] === targetEvent.id)
if (!hasTarget) return false
return true
}
// 注意:只有作者可以刪除自己的事件
// 其他人的刪除請求應該被忽略 客戶端處理
應該做
- • 查詢時檢查 kind 5 事件
- • 隱藏已刪除的事件
- • 顯示「已刪除」提示
- • 允許用戶刪除自己的內容
避免
- • 執行他人的刪除請求
- • 假設刪除一定成功
- • 永久刪除本地快取
- • 忽略刪除事件
查詢刪除事件
// 查詢特定事件是否被刪除
["REQ", "check-deleted", {
"kinds": [5],
"#e": ["<event-id>"]
}]
// 查詢用戶的所有刪除請求
["REQ", "user-deletions", {
"kinds": [5],
"authors": ["<pubkey>"]
}]
// 查詢特定可替換事件的刪除
["REQ", "check-addr-deleted", {
"kinds": [5],
"#a": ["30023:<pubkey>:<d-tag>"]
}] 限制與考量
刪除不是萬能的:
- • 其他用戶可能已保存你的事件
- • 某些中繼器可能不支援刪除
- • 刪除前的內容可能已被引用或轉發
- • 網路快照服務可能保留記錄
最佳實踐: 在發布敏感內容前三思。使用刪除功能作為「盡力而為」的補救措施, 而非可靠的隱私保護機制。
已複製連結