diff --git a/Sources/EffemKit/EffemAPI.swift b/Sources/EffemKit/EffemAPI.swift index 80c9bda..710dceb 100644 --- a/Sources/EffemKit/EffemAPI.swift +++ b/Sources/EffemKit/EffemAPI.swift @@ -18,6 +18,7 @@ enum EffemAPI { case getEpisodeStates(did: String, feedId: Int?, saved: Bool?, showHidden: Bool?, cursor: String?, limit: Int) case getInbox(did: String, cursor: String?, limit: Int) case getProfile(did: String) + case getBlocks(did: String, cursor: String?, limit: Int) // MARK: - Podcast Index Proxy (cached by AppView) @@ -58,6 +59,7 @@ extension EffemAPI: EndpointType { case .getEpisodeStates: "/xrpc/xyz.effem.feed.getEpisodeStates" case .getInbox: "/xrpc/xyz.effem.feed.getInbox" case .getProfile: "/xrpc/xyz.effem.actor.getProfile" + case .getBlocks: "/xrpc/xyz.effem.feed.getBlocks" // Podcast Index proxy case .searchPodcasts: "/xrpc/xyz.effem.search.podcasts" case .searchEpisodes: "/xrpc/xyz.effem.search.episodes" @@ -137,6 +139,11 @@ extension EffemAPI: EndpointType { case .getProfile(let did): return .requestParameters(encoding: .urlEncoding(parameters: ["did": did])) + case .getBlocks(let did, let cursor, let limit): + var params: Parameters = ["did": did, "limit": limit] + if let cursor { params["cursor"] = cursor } + return .requestParameters(encoding: .urlEncoding(parameters: params)) + // MARK: Podcast Index Proxy case .searchPodcasts(let query, let max, let searchType): diff --git a/Sources/EffemKit/EffemRepoService.swift b/Sources/EffemKit/EffemRepoService.swift index 7d3ec2d..e45591d 100644 --- a/Sources/EffemKit/EffemRepoService.swift +++ b/Sources/EffemKit/EffemRepoService.swift @@ -120,6 +120,59 @@ public struct EffemRepoService: Sendable { ) } + // MARK: - Blocks + + /// Creates a block record for a user in the authenticated user's AT Proto repo. + public func blockUser( + subjectDID: String, + repo: String + ) async throws -> CreateRecordResponse { + let record: [String: Any] = [ + "$type": "xyz.effem.graph.block", + "subject": subjectDID, + "createdAt": Self.now(), + ] + return try await createRecord( + repo: repo, + collection: "xyz.effem.graph.block", + record: record + ) + } + + /// Removes a block record from the authenticated user's AT Proto repo. + public func unblockUser(rkey: String, repo: String) async throws { + try await deleteRecord( + repo: repo, + collection: "xyz.effem.graph.block", + rkey: rkey + ) + } + + // MARK: - Reports + + /// Creates a moderation report in the authenticated user's AT Proto repo. + public func reportContent( + subject: String, + reasonType: ReportReasonType, + reason: String? = nil, + repo: String + ) async throws -> CreateRecordResponse { + var record: [String: Any] = [ + "$type": "xyz.effem.moderation.report", + "subject": subject, + "reasonType": reasonType.rawValue, + "createdAt": Self.now(), + ] + if let reason { + record["reason"] = reason + } + return try await createRecord( + repo: repo, + collection: "xyz.effem.moderation.report", + record: record + ) + } + // MARK: - Recommendations /// Creates an episode recommendation record in the user's AT Proto repo. diff --git a/Sources/EffemKit/EffemService.swift b/Sources/EffemKit/EffemService.swift index 34dc0a8..b227d5e 100644 --- a/Sources/EffemKit/EffemService.swift +++ b/Sources/EffemKit/EffemService.swift @@ -137,6 +137,17 @@ public struct EffemService: Sendable { try await execute(.getProfile(did: did)) } + // MARK: - Blocks + + /// Returns the authenticated user's block list. + public func getBlocks( + did: String, + cursor: String? = nil, + limit: Int = 50 + ) async throws -> BlocksResponse { + try await execute(.getBlocks(did: did, cursor: cursor, limit: limit)) + } + // MARK: - Podcast Index Proxy /// Searches for podcasts via the AppView's Podcast Index proxy. diff --git a/Sources/EffemKit/Models/Block.swift b/Sources/EffemKit/Models/Block.swift new file mode 100644 index 0000000..647bc5f --- /dev/null +++ b/Sources/EffemKit/Models/Block.swift @@ -0,0 +1,17 @@ +import Foundation + +/// A user block as indexed by the AppView. +public struct Block: Codable, Sendable, Identifiable { + public let did: String + public let rkey: String + public let subjectDID: String + public let createdAt: String + + public var id: String { "\(did)/\(rkey)" } +} + +/// Response from `xyz.effem.feed.getBlocks`. +public struct BlocksResponse: Codable, Sendable { + public let blocks: [Block] + public let cursor: String? +} diff --git a/Sources/EffemKit/Models/Report.swift b/Sources/EffemKit/Models/Report.swift new file mode 100644 index 0000000..73614d1 --- /dev/null +++ b/Sources/EffemKit/Models/Report.swift @@ -0,0 +1,9 @@ +import Foundation + +/// Reason types for reporting content or users. +public enum ReportReasonType: String, Codable, Sendable { + case spam + case abuse + case harassment + case other +}