mirror of
https://github.com/permissionlesstech/bitchat.git
synced 2026-08-08 06:56:10 +00:00
Don't suggest blocked people in @-mentions (#1543)
* Keep blocked peers out of @-mention suggestions Blocked mesh nicknames and blocked geohash pubkeys no longer show up in the composer autocomplete list. Co-authored-by: Cursor <cursoragent@cursor.com> * Fix blocked-mention test resetting private(set) state Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
e8f95e9a88
commit
ab835e58c9
@ -31,6 +31,10 @@ protocol ChatComposerContext: AnyObject {
|
||||
/// The transport's own nickname (excluded from autocomplete candidates).
|
||||
var meshNickname: String { get }
|
||||
func meshPeerNicknames() -> [PeerID: String]
|
||||
/// True when this mesh nickname belongs to a blocked peer.
|
||||
func isMeshNicknameBlocked(_ nickname: String) -> Bool
|
||||
/// True when this geohash pubkey is blocked for location chats.
|
||||
func isNostrBlocked(pubkeyHexLowercased: String) -> Bool
|
||||
|
||||
// MARK: Geohash identity (shared with the other contexts)
|
||||
var geoNicknames: [String: String] { get }
|
||||
@ -40,8 +44,8 @@ protocol ChatComposerContext: AnyObject {
|
||||
extension ChatViewModel: ChatComposerContext {
|
||||
// `autocompleteSuggestions`, `autocompleteRange`, `showAutocomplete`,
|
||||
// `selectedAutocompleteIndex`, `nickname`, `myPeerID`, `activeChannel`,
|
||||
// `geoNicknames`, `meshPeerNicknames()`, and
|
||||
// `deriveNostrIdentity(forGeohash:)` are shared requirements with the
|
||||
// `geoNicknames`, `meshPeerNicknames()`, `isNostrBlocked(pubkeyHexLowercased:)`,
|
||||
// and `deriveNostrIdentity(forGeohash:)` are shared requirements with the
|
||||
// other contexts or satisfied by existing `ChatViewModel` members. The
|
||||
// members below flatten nested service accesses into intent-named calls.
|
||||
|
||||
@ -60,6 +64,13 @@ extension ChatViewModel: ChatComposerContext {
|
||||
var meshNickname: String {
|
||||
meshService.myNickname
|
||||
}
|
||||
|
||||
func isMeshNicknameBlocked(_ nickname: String) -> Bool {
|
||||
for (peerID, nick) in meshService.getPeerNicknames() where nick == nickname {
|
||||
if isPeerBlocked(peerID) { return true }
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
@ -136,11 +147,14 @@ private extension ChatComposerCoordinator {
|
||||
switch context.activeChannel {
|
||||
case .mesh:
|
||||
let values = context.meshPeerNicknames().values
|
||||
return Array(values.filter { $0 != context.meshNickname })
|
||||
return Array(values.filter { nick in
|
||||
nick != context.meshNickname && !context.isMeshNicknameBlocked(nick)
|
||||
})
|
||||
|
||||
case .location(let channel):
|
||||
var tokens = Set<String>()
|
||||
for (pubkey, nick) in context.geoNicknames {
|
||||
guard !context.isNostrBlocked(pubkeyHexLowercased: pubkey) else { continue }
|
||||
tokens.insert("\(nick)#\(pubkey.suffix(4))")
|
||||
}
|
||||
if let identity = try? context.deriveNostrIdentity(forGeohash: channel.geohash) {
|
||||
|
||||
@ -52,9 +52,19 @@ private final class MockChatComposerContext: ChatComposerContext {
|
||||
var activeChannel: ChannelID = .mesh
|
||||
var meshNickname = "me"
|
||||
var meshNicknamesByPeerID: [PeerID: String] = [:]
|
||||
var blockedMeshNicknames: Set<String> = []
|
||||
var blockedNostrPubkeys: Set<String> = []
|
||||
|
||||
func meshPeerNicknames() -> [PeerID: String] { meshNicknamesByPeerID }
|
||||
|
||||
func isMeshNicknameBlocked(_ nickname: String) -> Bool {
|
||||
blockedMeshNicknames.contains(nickname)
|
||||
}
|
||||
|
||||
func isNostrBlocked(pubkeyHexLowercased: String) -> Bool {
|
||||
blockedNostrPubkeys.contains(pubkeyHexLowercased.lowercased())
|
||||
}
|
||||
|
||||
// Geohash identity
|
||||
var geoNicknames: [String: String] = [:]
|
||||
static let dummyIdentity = NostrIdentity(
|
||||
@ -120,6 +130,34 @@ struct ChatComposerCoordinatorContextTests {
|
||||
#expect(context.queriedPeerCandidates == [["carol#dddd"]])
|
||||
}
|
||||
|
||||
@Test @MainActor
|
||||
func updateAutocomplete_excludesBlockedMeshAndGeohashPeers() {
|
||||
let context = MockChatComposerContext()
|
||||
let coordinator = ChatComposerCoordinator(context: context)
|
||||
context.meshNicknamesByPeerID = [
|
||||
PeerID(str: "1111111111111111"): "alice",
|
||||
PeerID(str: "2222222222222222"): "eve",
|
||||
PeerID(str: "3333333333333333"): "me"
|
||||
]
|
||||
context.blockedMeshNicknames = ["eve"]
|
||||
context.queryResult = (["@alice"], NSRange(location: 0, length: 3))
|
||||
|
||||
coordinator.updateAutocomplete(for: "@a", cursorPosition: 2)
|
||||
#expect(context.queriedPeerCandidates == [["alice"]])
|
||||
|
||||
let geoContext = MockChatComposerContext()
|
||||
let geoCoordinator = ChatComposerCoordinator(context: geoContext)
|
||||
geoContext.activeChannel = .location(GeohashChannel(level: .city, geohash: "u4pruydq"))
|
||||
geoContext.geoNicknames = [
|
||||
"aaaabbbbccccdddd": "carol",
|
||||
"bbbbccccddddeeee": "blocked"
|
||||
]
|
||||
geoContext.blockedNostrPubkeys = ["bbbbccccddddeeee"]
|
||||
|
||||
geoCoordinator.updateAutocomplete(for: "@", cursorPosition: 1)
|
||||
#expect(geoContext.queriedPeerCandidates == [["carol#dddd"]])
|
||||
}
|
||||
|
||||
@Test @MainActor
|
||||
func completeNickname_appliesSuggestionResetsStateAndReturnsCursor() {
|
||||
let context = MockChatComposerContext()
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user