damus

nostr ios client
git clone git://jb55.com/damus
Log | Files | Refs | README | LICENSE

commit 2a8b9f75c13d4b70cf77adc5d45e124b2749ad96
parent 7d323b65e47867e4d8209490b1b54ae79c3ac6f7
Author: William Casarin <jb55@jb55.com>
Date:   Wed, 25 Jan 2023 09:53:41 -0800

Initial NIP-51 Mute List Implementation

Diffstat:
Mdamus.xcodeproj/project.pbxproj | 8++++++++
Mdamus/Util/Keys.swift | 13+++++++++++++
Adamus/Util/Lists.swift | 91+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
AdamusTests/ListTests.swift | 71+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 183 insertions(+), 0 deletions(-)

diff --git a/damus.xcodeproj/project.pbxproj b/damus.xcodeproj/project.pbxproj @@ -157,6 +157,8 @@ 4CEE2B02280B39E800AB5EEF /* EventActionBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CEE2B01280B39E800AB5EEF /* EventActionBar.swift */; }; 4CF0ABD42980996B00D66079 /* Report.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CF0ABD32980996B00D66079 /* Report.swift */; }; 4CF0ABD629817F5B00D66079 /* ReportView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CF0ABD529817F5B00D66079 /* ReportView.swift */; }; + 4CF0ABD82981980C00D66079 /* Lists.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CF0ABD72981980C00D66079 /* Lists.swift */; }; + 4CF0ABDC2981A19E00D66079 /* ListTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CF0ABDB2981A19E00D66079 /* ListTests.swift */; }; 4FE60CDD295E1C5E00105A1F /* Wallet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FE60CDC295E1C5E00105A1F /* Wallet.swift */; }; 6439E014296790CF0020672B /* ProfileZoomView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6439E013296790CF0020672B /* ProfileZoomView.swift */; }; 647D9A8D2968520300A295DE /* SideMenuView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 647D9A8C2968520300A295DE /* SideMenuView.swift */; }; @@ -391,6 +393,8 @@ 4CEE2B01280B39E800AB5EEF /* EventActionBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventActionBar.swift; sourceTree = "<group>"; }; 4CF0ABD32980996B00D66079 /* Report.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Report.swift; sourceTree = "<group>"; }; 4CF0ABD529817F5B00D66079 /* ReportView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportView.swift; sourceTree = "<group>"; }; + 4CF0ABD72981980C00D66079 /* Lists.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Lists.swift; sourceTree = "<group>"; }; + 4CF0ABDB2981A19E00D66079 /* ListTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListTests.swift; sourceTree = "<group>"; }; 4FE60CDC295E1C5E00105A1F /* Wallet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Wallet.swift; sourceTree = "<group>"; }; 6439E013296790CF0020672B /* ProfileZoomView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileZoomView.swift; sourceTree = "<group>"; }; 647D9A8C2968520300A295DE /* SideMenuView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SideMenuView.swift; sourceTree = "<group>"; }; @@ -633,6 +637,7 @@ 4C3A1D3629637E0500558C0F /* PreviewCache.swift */, 64FBD06E296255C400D9D3B2 /* Theme.swift */, 4CB8838529656C8B00DC99E7 /* NIP05.swift */, + 4CF0ABD72981980C00D66079 /* Lists.swift */, ); path = Util; sourceTree = "<group>"; @@ -748,6 +753,7 @@ 4C3EA67A28FF7B3900C48A62 /* InvoiceTests.swift */, 3ACBCB77295FE5C70037388A /* TimeAgoTests.swift */, 4CB88399297322D200DC99E7 /* DMTests.swift */, + 4CF0ABDB2981A19E00D66079 /* ListTests.swift */, ); path = damusTests; sourceTree = "<group>"; @@ -1031,6 +1037,7 @@ 4C5F9114283D694D0052CD1C /* FollowTarget.swift in Sources */, 4CF0ABD629817F5B00D66079 /* ReportView.swift in Sources */, 4CB8838629656C8B00DC99E7 /* NIP05.swift in Sources */, + 4CF0ABD82981980C00D66079 /* Lists.swift in Sources */, 4C5C7E6A284EDE2E00A22DF5 /* SearchResultsView.swift in Sources */, 6439E014296790CF0020672B /* ProfileZoomView.swift in Sources */, 4CE6DF1627F8DEBF00C66700 /* RelayConnection.swift in Sources */, @@ -1091,6 +1098,7 @@ 4C363AA02828A8DD006E126D /* LikeTests.swift in Sources */, 4C90BD1C283AC38E008EE7EF /* Bech32Tests.swift in Sources */, 4CE6DEF827F7A08200C66700 /* damusTests.swift in Sources */, + 4CF0ABDC2981A19E00D66079 /* ListTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/damus/Util/Keys.swift b/damus/Util/Keys.swift @@ -12,12 +12,25 @@ import Vault let PUBKEY_HRP = "npub" let PRIVKEY_HRP = "nsec" +struct FullKeypair { + let pubkey: String + let privkey: String +} + struct Keypair { let pubkey: String let privkey: String? let pubkey_bech32: String let privkey_bech32: String? + func to_full() -> FullKeypair? { + guard let privkey = self.privkey else { + return nil + } + + return FullKeypair(pubkey: pubkey, privkey: privkey) + } + init(pubkey: String, privkey: String?) { self.pubkey = pubkey self.privkey = privkey diff --git a/damus/Util/Lists.swift b/damus/Util/Lists.swift @@ -0,0 +1,91 @@ +// +// Mute.swift +// damus +// +// Created by William Casarin on 2023-01-25. +// + +import Foundation + +func create_or_update_mutelist(keypair: FullKeypair, mprev: NostrEvent?, to_add: String) -> NostrEvent? { + return create_or_update_list_event(keypair: keypair, mprev: mprev, to_add: to_add, list_name: "mute", list_type: "p") +} + +func remove_from_mutelist(keypair: FullKeypair, prev: NostrEvent, to_remove: String) -> NostrEvent? { + return remove_from_list_event(keypair: keypair, prev: prev, to_remove: to_remove, tag_type: "p") +} + +func create_or_update_list_event(keypair: FullKeypair, mprev: NostrEvent?, to_add: String, list_name: String, list_type: String) -> NostrEvent? { + let pubkey = keypair.pubkey + + if let prev = mprev { + if let okprev = ensure_list_name(list: prev, name: list_name), prev.pubkey == keypair.pubkey { + return add_to_list_event(keypair: keypair, prev: okprev, to_add: to_add, tag_type: list_type) + } + } + + var tags = [["d", list_name], [list_type, to_add]] + let ev = NostrEvent(content: "", pubkey: pubkey, kind: 30000, tags: tags) + + ev.tags = tags + ev.id = calculate_event_id(ev: ev) + ev.sig = sign_event(privkey: keypair.privkey, ev: ev) + + return ev +} + +func remove_from_list_event(keypair: FullKeypair, prev: NostrEvent, to_remove: String, tag_type: String) -> NostrEvent? { + var exists = false + for tag in prev.tags { + if tag.count >= 2 && tag[0] == tag_type && tag[1] == to_remove { + exists = true + } + } + + // make sure we actually have the pubkey to remove + guard exists else { + return nil + } + + let new_tags = prev.tags.filter { tag in + !(tag.count >= 2 && tag[0] == tag_type && tag[1] == to_remove) + } + + let ev = NostrEvent(content: prev.content, pubkey: keypair.pubkey, kind: 30000, tags: new_tags) + ev.id = calculate_event_id(ev: ev) + ev.sig = sign_event(privkey: keypair.privkey, ev: ev) + + return ev +} + +func add_to_list_event(keypair: FullKeypair, prev: NostrEvent, to_add: String, tag_type: String) -> NostrEvent? { + for tag in prev.tags { + // we are already muting this user + if tag.count >= 2 && tag[0] == tag_type && tag[1] == to_add { + return nil + } + } + + let new = NostrEvent(content: prev.content, pubkey: keypair.pubkey, kind: 30000, tags: prev.tags) + new.tags.append([tag_type, to_add]) + new.id = calculate_event_id(ev: new) + new.sig = sign_event(privkey: keypair.privkey, ev: new) + + return new +} + +func ensure_list_name(list: NostrEvent, name: String) -> NostrEvent? { + for tag in list.tags { + if tag.count >= 2 && tag[0] == "d" { + if tag[1] != name { + return nil + } else { + return list + } + } + } + + list.tags.insert(["d", name], at: 0) + + return list +} diff --git a/damusTests/ListTests.swift b/damusTests/ListTests.swift @@ -0,0 +1,71 @@ +// +// ListTests.swift +// damusTests +// +// Created by William Casarin on 2023-01-25. +// + +import XCTest +@testable import damus + +final class ListTests: XCTestCase { + + override func setUpWithError() throws { + // Put setup code here. This method is called before the invocation of each test method in the class. + } + + override func tearDownWithError() throws { + // Put teardown code here. This method is called after the invocation of each test method in the class. + } + + func testCreateMuteList() throws { + let privkey = "87f313b03f2548e6eaf1c188db47078e08e894252949779b639b28db0891937a" + let pubkey = "4b0c29bf96496130c1253102f6870c0eee05db38a257315858272aa43fd19685" + let to_mute = "2fa2630fea3d2c188c49f2799fcd92f0e9879ea6a36ae60770a5428ed6c19edd" + let keypair = FullKeypair(pubkey: pubkey, privkey: privkey) + let mutelist = create_or_update_mutelist(keypair: keypair, mprev: nil, to_add: to_mute)! + + XCTAssertEqual(mutelist.pubkey, pubkey) + XCTAssertEqual(mutelist.content, "") + XCTAssertEqual(mutelist.tags.count, 2) + XCTAssertEqual(mutelist.tags[0][0], "d") + XCTAssertEqual(mutelist.tags[0][1], "mute") + XCTAssertEqual(mutelist.tags[1][0], "p") + XCTAssertEqual(mutelist.tags[1][1], to_mute) + } + + func testCreateAndRemoveMuteList() throws { + let privkey = "87f313b03f2548e6eaf1c188db47078e08e894252949779b639b28db0891937a" + let pubkey = "4b0c29bf96496130c1253102f6870c0eee05db38a257315858272aa43fd19685" + let to_mute = "2fa2630fea3d2c188c49f2799fcd92f0e9879ea6a36ae60770a5428ed6c19edd" + let keypair = FullKeypair(pubkey: pubkey, privkey: privkey) + let mutelist = create_or_update_mutelist(keypair: keypair, mprev: nil, to_add: to_mute)! + let new = remove_from_mutelist(keypair: keypair, prev: mutelist, to_remove: to_mute)! + + XCTAssertEqual(new.pubkey, pubkey) + XCTAssertEqual(new.content, "") + XCTAssertEqual(new.tags.count, 1) + XCTAssertEqual(new.tags[0][0], "d") + XCTAssertEqual(new.tags[0][1], "mute") + } + + func testAddToExistingMutelist() throws { + let privkey = "87f313b03f2548e6eaf1c188db47078e08e894252949779b639b28db0891937a" + let pubkey = "4b0c29bf96496130c1253102f6870c0eee05db38a257315858272aa43fd19685" + let to_mute = "2fa2630fea3d2c188c49f2799fcd92f0e9879ea6a36ae60770a5428ed6c19edd" + let to_mute_2 = "976b4ab41f8634119b4f21f57ef5836a4bef65d0bf72c7ced67b8b170ba4a38d" + let keypair = FullKeypair(pubkey: pubkey, privkey: privkey) + let mutelist = create_or_update_mutelist(keypair: keypair, mprev: nil, to_add: to_mute)! + let new = create_or_update_mutelist(keypair: keypair, mprev: mutelist, to_add: to_mute_2)! + + XCTAssertEqual(new.pubkey, pubkey) + XCTAssertEqual(new.content, "") + XCTAssertEqual(new.tags.count, 3) + XCTAssertEqual(new.tags[0][0], "d") + XCTAssertEqual(new.tags[0][1], "mute") + XCTAssertEqual(new.tags[1][0], "p") + XCTAssertEqual(new.tags[1][1], to_mute) + XCTAssertEqual(new.tags[2][0], "p") + XCTAssertEqual(new.tags[2][1], to_mute_2) + } +}