diff --git a/src/FishyFlip.Tests/AuthorizedTests.cs b/src/FishyFlip.Tests/AuthorizedTests.cs index 53fc04e..6137e2e 100644 --- a/src/FishyFlip.Tests/AuthorizedTests.cs +++ b/src/FishyFlip.Tests/AuthorizedTests.cs @@ -39,7 +39,7 @@ public async Task GetPopularFeedGeneratorsAsync() }); } - [Fact] + [Fact(Skip = "Not working in Sandbox.")] public async Task GetFeedAsyncTest() { var atUri = ATUri.Create("at://did:plc:hqmafuxb77d6cepxvqwlcekl/app.bsky.feed.generator/sandsky"); @@ -55,7 +55,7 @@ public async Task GetFeedAsyncTest() }); } - [Fact] + [Fact(Skip = "Not working in Sandbox.")] public async Task GetFeedGeneratorAsyncTest() { var atUri = ATUri.Create("at://did:plc:hqmafuxb77d6cepxvqwlcekl/app.bsky.feed.generator/sandsky"); @@ -71,7 +71,6 @@ public async Task GetFeedGeneratorAsyncTest() }); } - [Fact] public async Task GetProfileAsyncTest() { var test1did = ATDid.Create("did:plc:ix37rgpewy5wtl5qzhunsldu"); diff --git a/src/FishyFlip.Tests/FishyFlip.Tests.csproj b/src/FishyFlip.Tests/FishyFlip.Tests.csproj index 8ab02e7..a0eb2fb 100644 --- a/src/FishyFlip.Tests/FishyFlip.Tests.csproj +++ b/src/FishyFlip.Tests/FishyFlip.Tests.csproj @@ -1,7 +1,7 @@ - net7.0 + net7.0;net8.0 enable enable diff --git a/src/FishyFlip/ATProtoDebug.cs b/src/FishyFlip/ATProtoDebug.cs deleted file mode 100644 index d822a33..0000000 --- a/src/FishyFlip/ATProtoDebug.cs +++ /dev/null @@ -1,75 +0,0 @@ -// -// Copyright (c) Drastic Actions. All rights reserved. -// - -namespace FishyFlip; - -/// -/// Debug methods for accessing AT Protocol directly. -/// With this, you can pass in your own endpoints and get the raw data back. -/// -public sealed class ATProtoDebug -{ - private ATProtocol proto; - - /// - /// Initializes a new instance of the class. - /// - /// . - internal ATProtoDebug(ATProtocol proto) - { - this.proto = proto; - } - - private ATProtocolOptions Options => this.proto.Options; - - private HttpClient Client => this.proto.Client; - - /// - /// Get a raw response from the AT Protocol. - /// - /// ATProtocol Endpoint. - /// Cancellation Token. Defaults to null. - /// Result of dynamic JSON. - public Task> GetAsync( - string path, - CancellationToken cancellationToken = default) - => this.Client.Get(path, this.Options.JsonSerializerOptions, cancellationToken, this.Options.Logger); - - /// - /// Get a raw response as a CAR file from the AT Protocol. - /// - /// ATProtocol Endpoint. - /// Delegate to decode the CAR response. - /// Cancellation Token. Defaults to null. - /// Result of . Use to read response. - public Task> GetCarAsync( - string path, - OnCarDecoded decode, - CancellationToken cancellationToken = default) - => this.Client.GetCarAsync(path, this.Options.JsonSerializerOptions, cancellationToken, this.Options.Logger, decode); - - /// - /// Post a raw response to the AT Protocol. - /// - /// ATProtocol Endpoint. - /// Cancellation Token. Defaults to null. - /// Result of dynamic JSON. - public Task> PostAsync( - string path, - CancellationToken cancellationToken = default) - => this.Client.Post(path, this.Options.JsonSerializerOptions, cancellationToken, this.Options.Logger); - - /// - /// Post a raw response to the AT Protocol. - /// - /// ATProtocol Endpoint. - /// to post. - /// Cancellation Token. Defaults to null. - /// Result of dynamic JSON. - public Task> PostAsync( - string path, - StreamContent content, - CancellationToken cancellationToken = default) - => this.Client.Post(path, this.Options.JsonSerializerOptions, content, cancellationToken, this.Options.Logger); -} diff --git a/src/FishyFlip/ATProtoIdentity.cs b/src/FishyFlip/ATProtoIdentity.cs index e3aa2b1..1253353 100644 --- a/src/FishyFlip/ATProtoIdentity.cs +++ b/src/FishyFlip/ATProtoIdentity.cs @@ -33,7 +33,7 @@ public ATProtoIdentity(ATProtocol proto) public async Task> ResolveHandleAsync(ATHandle handler, CancellationToken cancellationToken = default) { string url = $"{Constants.Urls.ATProtoIdentity.ResolveHandle}?handle={handler}"; - return await this.Client.Get(url, this.Options.JsonSerializerOptions, cancellationToken); + return await this.Client.Get(url, this.Options.SourceGenerationContext.HandleResolution, this.Options.JsonSerializerOptions, cancellationToken); } /// @@ -44,6 +44,6 @@ public ATProtoIdentity(ATProtocol proto) /// Result of . public async Task> UpdateHandleAsync(ATHandle handle, CancellationToken cancellationToken = default) { - return await this.Client.Post(Constants.Urls.ATProtoIdentity.UpdateHandle, this.Options.JsonSerializerOptions, new UpdateHandle(handle.Handle.ToString()), cancellationToken); + return await this.Client.Post(Constants.Urls.ATProtoIdentity.UpdateHandle, this.Options.SourceGenerationContext.UpdateHandle, this.Options.SourceGenerationContext.Success, this.Options.JsonSerializerOptions, new UpdateHandle(handle.Handle.ToString()), cancellationToken); } } diff --git a/src/FishyFlip/ATProtoModeration.cs b/src/FishyFlip/ATProtoModeration.cs index 1223668..8f92d3f 100644 --- a/src/FishyFlip/ATProtoModeration.cs +++ b/src/FishyFlip/ATProtoModeration.cs @@ -24,20 +24,41 @@ internal ATProtoModeration(ATProtocol proto) private HttpClient Client => this.proto.Client; + /// + /// Creates a moderation report for a post asynchronously. + /// + /// The type of the moderation reason. + /// The URI of the post. + /// The CID of the post. + /// The reason for the moderation report. This is optional. + /// A token that may be used to cancel the operation. This is optional. + /// A task that represents the asynchronous operation. The task result contains a that represents the moderation record. public Task> CreateModerationReportPostAsync(ModerationReasonType reasonType, ATUri uri, Cid cid, string? reason = default, CancellationToken cancellationToken = default) { return this.Client.Post( Constants.Urls.ATProtoModeration.CreateReport, + this.Options.SourceGenerationContext.CreateModerationReportPost, + this.Options.SourceGenerationContext.ModerationRecord!, this.Options.JsonSerializerOptions, new CreateModerationReportPost(reasonType.ToEndpointString(), new RepoStrongRef(uri, cid), reason), cancellationToken, this.Options.Logger); } + /// + /// Creates a moderation report for a repository asynchronously. + /// + /// The type of the moderation reason. + /// The decentralized identifier (DID) of the subject. + /// The reason for the moderation report. This is optional. + /// A token that may be used to cancel the operation. This is optional. + /// A task that represents the asynchronous operation. The task result contains a that represents the moderation record. public Task> CreateModerationReportRepoAsync(ModerationReasonType reasonType, ATDid subject, string? reason = default, CancellationToken cancellationToken = default) { return this.Client.Post( Constants.Urls.ATProtoModeration.CreateReport, + this.Options.SourceGenerationContext.CreateModerationReportRepo, + this.Options.SourceGenerationContext.ModerationRecord!, this.Options.JsonSerializerOptions, new CreateModerationReportRepo(reasonType.ToEndpointString(), new AdminRepoRef(subject), reason), cancellationToken, diff --git a/src/FishyFlip/ATProtoRepo.cs b/src/FishyFlip/ATProtoRepo.cs index 851561c..6ca7d75 100644 --- a/src/FishyFlip/ATProtoRepo.cs +++ b/src/FishyFlip/ATProtoRepo.cs @@ -2,6 +2,7 @@ // Copyright (c) Drastic Actions. All rights reserved. // +using System.Text.Json.Serialization.Metadata; using System.Threading; using static FishyFlip.Constants; @@ -27,6 +28,14 @@ internal ATProtoRepo(ATProtocol proto) private HttpClient Client => this.proto.Client; + /// + /// Creates a like asynchronously. + /// + /// The CID of the like. + /// The URI of the like. + /// The creation date of the like. If not specified, the current UTC date and time will be used. + /// The cancellation token. + /// A task that represents the asynchronous operation. The task result contains the reference to the created like record. public Task> CreateLikeAsync( Cid cid, ATUri uri, @@ -38,9 +47,17 @@ public Task> CreateLikeAsync( this.proto.SessionManager!.Session!.Did.ToString()!, new LikeRecord(new Subject(cid, uri), createdAt ?? DateTime.UtcNow)); - return this.CreateRecord(record, cancellationToken); + return this.CreateRecord(record, this.Options.SourceGenerationContext.CreateLikeRecord, this.Options.SourceGenerationContext.RecordRef, cancellationToken); } + /// + /// Creates a repost asynchronously. + /// + /// The CID of the repost. + /// The URI of the repost. + /// The creation date of the repost. If null, the current UTC date will be used. + /// The cancellation token. + /// A task that represents the asynchronous operation. The task result contains the reference to the created repost. public Task> CreateRepostAsync( Cid cid, ATUri uri, @@ -52,9 +69,16 @@ public Task> CreateRepostAsync( this.proto.SessionManager!.Session!.Did.ToString()!, new RepostRecord(new Subject(cid, uri), createdAt ?? DateTime.UtcNow)); - return this.CreateRecord(record, cancellationToken); + return this.CreateRecord(record, this.Options.SourceGenerationContext.CreateRepostRecord, this.Options.SourceGenerationContext.RecordRef, cancellationToken); } + /// + /// Creates a follow record asynchronously. + /// + /// The ATDid to create a follow record for. + /// The creation date of the follow record. If null, the current UTC date will be used. + /// The cancellation token. + /// A task that represents the asynchronous operation. The task result contains the created record reference. public Task> CreateFollowAsync( ATDid did, DateTime? createdAt = null, @@ -65,9 +89,17 @@ public Task> CreateFollowAsync( this.proto.SessionManager!.Session!.Did.ToString()!, new FollowRecord(did, createdAt ?? DateTime.UtcNow)); - return this.CreateRecord(record, cancellationToken); + return this.CreateRecord(record, this.Options.SourceGenerationContext.CreateFollowRecord, this.Options.SourceGenerationContext.RecordRef, cancellationToken); } + /// + /// Creates a new list item asynchronously. + /// + /// The ATDid of the subject. + /// The ATUri of the list. + /// The optional creation date of the list item. If not provided, the current UTC date and time will be used. + /// The cancellation token. + /// A task that represents the asynchronous operation. The task result contains the created list item's RecordRef. public Task> CreateListItemAsync( ATDid subject, ATUri list, @@ -79,9 +111,21 @@ public Task> CreateListItemAsync( this.proto.SessionManager!.Session!.Did.ToString()!, new ListItemRecord(subject, list, createdAt ?? DateTime.UtcNow)); - return this.CreateRecord(record, cancellationToken); + return this.CreateRecord(record, this.Options.SourceGenerationContext.CreateListItemRecord, this.Options.SourceGenerationContext.RecordRef, cancellationToken); } + /// + /// Creates a post asynchronously. + /// + /// The text of the post. + /// The facets associated with the post. + /// The embed associated with the post. + /// The languages associated with the post. + /// The creation date of the post. + /// The rkey associated with the post. + /// The swap commit associated with the post. + /// The cancellation token. + /// A task that represents the asynchronous operation. The task result contains the result of creating the post. public Task> CreatePostAsync( string text, Facet[]? facets = null, @@ -98,9 +142,16 @@ public Task> CreatePostAsync( new Post(embed, facets, createdAt ?? DateTime.UtcNow, null, text, langs, Constants.FeedType.Post), rkey, swapCommit); - return this.CreateRecord(record, cancellationToken); + return this.CreateRecord(record, this.Options.SourceGenerationContext.CreatePostRecord, this.Options.SourceGenerationContext.CreatePostResponse, cancellationToken); } + /// + /// Creates a block asynchronously. + /// + /// The ATDid. + /// The creation date of the block. If null, the current UTC date and time will be used. + /// The cancellation token. + /// A task that represents the asynchronous operation. The task result contains the created block's record reference. public Task> CreateBlockAsync( ATDid did, DateTime? createdAt = null, @@ -111,16 +162,26 @@ public Task> CreateBlockAsync( this.proto.SessionManager!.Session!.Did.ToString()!, new BlockRecord(did, createdAt ?? DateTime.UtcNow)); - return - this.Client - .Post( - Constants.Urls.ATProtoRepo.CreateRecord, - this.Options.JsonSerializerOptions, - record, - cancellationToken, - this.Options.Logger); + return this.Client.Post( + Constants.Urls.ATProtoRepo.CreateRecord, + this.Options.SourceGenerationContext.CreateBlockRecord, + this.Options.SourceGenerationContext.RecordRef, + this.Options.JsonSerializerOptions, + record, + cancellationToken, + this.Options.Logger); } + /// + /// Creates a curate list asynchronously. + /// + /// The name of the list. + /// The description of the list. + /// The creation date of the list. (optional). + /// The rkey of the list. (optional). + /// The swap commit of the list. (optional). + /// The cancellation token. (optional). + /// A task that represents the asynchronous operation. The task result contains the created record reference. public Task> CreateCurateListAsync( string name, string description, @@ -136,35 +197,66 @@ public Task> CreateCurateListAsync( this.proto.SessionManager!.Session!.Did.ToString(), listRecord); - return - this.Client - .Post( - Constants.Urls.ATProtoRepo.CreateRecord, - this.Options.JsonSerializerOptions, - record, - cancellationToken, - this.Options.Logger); + return this.Client.Post( + Constants.Urls.ATProtoRepo.CreateRecord, + this.Options.SourceGenerationContext.CreateListRecord, + this.Options.SourceGenerationContext.RecordRef, + this.Options.JsonSerializerOptions, + record, + cancellationToken, + this.Options.Logger); } + /// + /// Retrieves a post asynchronously. + /// + /// The AT identifier. + /// The record key. + /// The CID (Content Identifier) of the post. + /// The cancellation token. + /// A task that represents the asynchronous operation. The task result contains the retrieved post record, or null if not found. public async Task> GetPostAsync(ATIdentifier repo, string rkey, Cid? cid = null, CancellationToken cancellationToken = default) - => await this.GetRecordAsync(Constants.FeedType.Post, repo, rkey, cid, cancellationToken); + => await this.GetRecordAsync(Constants.FeedType.Post, this.Options.SourceGenerationContext.PostRecord, repo, rkey, cid, cancellationToken); + /// + /// Retrieves an actor asynchronously. + /// + /// The AT identifier of the repository. + /// The CID of the actor record. If null, the latest record will be retrieved. + /// The cancellation token. + /// A task that represents the asynchronous operation. The task result contains the actor record, or null if not found. public async Task> GetActorAsync(ATIdentifier repo, Cid? cid = null, CancellationToken cancellationToken = default) - => await this.GetRecordAsync(Constants.ActorTypes.Profile, repo, "self", cid, cancellationToken); + => await this.GetRecordAsync(Constants.ActorTypes.Profile, this.Options.SourceGenerationContext.ActorRecord, repo, "self", cid, cancellationToken); + /// + /// Uploads a blob asynchronously. + /// + /// The content of the blob as a stream. + /// A token that may be used to cancel the operation. This is optional. + /// A task that represents the asynchronous operation. The task result contains the response from the blob upload operation. public Task> UploadBlobAsync(StreamContent content, CancellationToken cancellationToken = default) { - return - this.Client - .Post( - Constants.Urls.ATProtoRepo.UploadBlob, - this.Options.JsonSerializerOptions, - content, - cancellationToken, - this.Options.Logger); + return this.Client.Post( + Constants.Urls.ATProtoRepo.UploadBlob, + this.Options.SourceGenerationContext.UploadBlobResponse, + this.Options.JsonSerializerOptions, + content, + cancellationToken, + this.Options.Logger); } - public async Task> GetRecordAsync(string collection, ATIdentifier repo, string rkey, Cid? cid = null, CancellationToken cancellationToken = default) + /// + /// Asynchronously retrieves a record of type T from a specified repository. + /// + /// The type of the record to retrieve. Must implement ATFeedTypeAPI. + /// The name of the collection where the record is stored. + /// The JsonTypeInfo of the record type T. Used for JSON serialization and deserialization. + /// The ATIdentifier of the repository where the record is stored. + /// The key of the record to retrieve. + /// Optional. The CID (Content Identifier) of the record. If specified, the method retrieves the record with this CID. + /// Optional. A CancellationToken that can be used to cancel the operation. + /// A Task that represents the asynchronous operation. The task result contains a Result object with the retrieved record of type T, or null if the record was not found. + public async Task> GetRecordAsync(string collection, JsonTypeInfo type, ATIdentifier repo, string rkey, Cid? cid = null, CancellationToken cancellationToken = default) where T : ATFeedTypeAPI { string url = $"{Constants.Urls.ATProtoRepo.GetRecord}?collection={collection}&repo={repo}&rkey={rkey}"; @@ -173,15 +265,29 @@ public Task> UploadBlobAsync(StreamContent content, C url += $"&cid={cid}"; } - return await this.Client.Get(url, this.Options.JsonSerializerOptions, cancellationToken, this.Options.Logger); + return await this.Client.Get(url, type, this.Options.JsonSerializerOptions, cancellationToken, this.Options.Logger); } + /// + /// Asynchronously retrieves the description of a repository. + /// + /// The ATIdentifier of the repository to describe. + /// Optional. A CancellationToken that can be used to cancel the operation. + /// A Task that represents the asynchronous operation. The task result contains a Result object with the description of the repository, or null if the repository was not found. public async Task> DescribeRepoAsync(ATIdentifier identifier, CancellationToken cancellationToken = default) { string url = $"{Constants.Urls.ATProtoRepo.DescribeRepo}?repo={identifier}"; - return await this.Client.Get(url, this.Options.JsonSerializerOptions, cancellationToken, this.Options.Logger); + return await this.Client.Get(url, this.Options.SourceGenerationContext.DescribeRepo!, this.Options.JsonSerializerOptions, cancellationToken, this.Options.Logger); } + /// + /// Asynchronously deletes a follow record. + /// + /// The key of the record to delete. + /// Optional. The CID (Content Identifier) of the record to swap with. If specified, the method swaps the record with this CID before deleting it. + /// Optional. The CID (Content Identifier) of the commit to swap with. If specified, the method swaps the commit with this CID before deleting the record. + /// Optional. A CancellationToken that can be used to cancel the operation. + /// A Task that represents the asynchronous operation. The task result contains a Success object indicating whether the operation was successful. public Task> DeleteFollowAsync( string rkey, Cid? swapRecord = null, @@ -189,6 +295,14 @@ public Task> DeleteFollowAsync( CancellationToken cancellationToken = default) => this.DeleteRecordAsync(Constants.GraphTypes.Follow, rkey, swapRecord, swapCommit, cancellationToken); + /// + /// Asynchronously deletes a block record. + /// + /// The key of the record to delete. + /// Optional. The CID (Content Identifier) of the record to swap with. If specified, the method swaps the record with this CID before deleting it. + /// Optional. The CID (Content Identifier) of the commit to swap with. If specified, the method swaps the commit with this CID before deleting the record. + /// Optional. A CancellationToken that can be used to cancel the operation. + /// A Task that represents the asynchronous operation. The task result contains a Success object indicating whether the operation was successful. public Task> DeleteBlockAsync( string rkey, Cid? swapRecord = null, @@ -196,6 +310,14 @@ public Task> DeleteBlockAsync( CancellationToken cancellationToken = default) => this.DeleteRecordAsync(Constants.GraphTypes.Block, rkey, swapRecord, swapCommit, cancellationToken); + /// + /// Asynchronously deletes a like record. + /// + /// The key of the record to delete. + /// Optional. The CID (Content Identifier) of the record to swap with. If specified, the method swaps the record with this CID before deleting it. + /// Optional. The CID (Content Identifier) of the commit to swap with. If specified, the method swaps the commit with this CID before deleting the record. + /// Optional. A CancellationToken that can be used to cancel the operation. + /// A Task that represents the asynchronous operation. The task result contains a Success object indicating whether the operation was successful. public Task> DeleteLikeAsync( string rkey, Cid? swapRecord = null, @@ -203,6 +325,14 @@ public Task> DeleteLikeAsync( CancellationToken cancellationToken = default) => this.DeleteRecordAsync(Constants.FeedType.Like, rkey, swapRecord, swapCommit, cancellationToken); + /// + /// Asynchronously deletes a post record. + /// + /// The key of the record to delete. + /// Optional. The CID (Content Identifier) of the record to swap with. If specified, the method swaps the record with this CID before deleting it. + /// Optional. The CID (Content Identifier) of the commit to swap with. If specified, the method swaps the commit with this CID before deleting the record. + /// Optional. A CancellationToken that can be used to cancel the operation. + /// A Task that represents the asynchronous operation. The task result contains a Success object indicating whether the operation was successful. public Task> DeletePostAsync( string rkey, Cid? swapRecord = null, @@ -210,6 +340,14 @@ public Task> DeletePostAsync( CancellationToken cancellationToken = default) => this.DeleteRecordAsync(Constants.FeedType.Post, rkey, swapRecord, swapCommit, cancellationToken); + /// + /// Asynchronously deletes a repost record. + /// + /// The key of the record to delete. + /// Optional. The CID (Content Identifier) of the record to swap with. If specified, the method swaps the record with this CID before deleting it. + /// Optional. The CID (Content Identifier) of the commit to swap with. If specified, the method swaps the commit with this CID before deleting the record. + /// Optional. A CancellationToken that can be used to cancel the operation. + /// A Task that represents the asynchronous operation. The task result contains a Success object indicating whether the operation was successful. public Task> DeleteRepostAsync( string rkey, Cid? swapRecord = null, @@ -217,6 +355,14 @@ public Task> DeleteRepostAsync( CancellationToken cancellationToken = default) => this.DeleteRecordAsync(Constants.FeedType.Repost, rkey, swapRecord, swapCommit, cancellationToken); + /// + /// Asynchronously deletes a list. + /// + /// The key of the record to delete. + /// Optional. The CID (Content Identifier) of the record to swap with. If specified, the method swaps the record with this CID before deleting it. + /// Optional. The CID (Content Identifier) of the commit to swap with. If specified, the method swaps the commit with this CID before deleting the record. + /// Optional. A CancellationToken that can be used to cancel the operation. + /// A Task that represents the asynchronous operation. The task result contains a Success object indicating whether the operation was successful. public Task> DeleteListAsync( string rkey, Cid? swapRecord = null, @@ -224,6 +370,14 @@ public Task> DeleteListAsync( CancellationToken cancellationToken = default) => this.DeleteRecordAsync(Constants.GraphTypes.List, rkey, swapRecord, swapCommit, cancellationToken); + /// + /// Asynchronously deletes a list item. + /// + /// The key of the record to delete. + /// Optional. The CID (Content Identifier) of the record to swap with. If specified, the method swaps the record with this CID before deleting it. + /// Optional. The CID (Content Identifier) of the commit to swap with. If specified, the method swaps the commit with this CID before deleting the record. + /// Optional. A CancellationToken that can be used to cancel the operation. + /// A Task that represents the asynchronous operation. The task result contains a Success object indicating whether the operation was successful. public Task> DeleteListItemAsync( string rkey, Cid? swapRecord = null, @@ -231,34 +385,116 @@ public Task> DeleteListItemAsync( CancellationToken cancellationToken = default) => this.DeleteRecordAsync(Constants.GraphTypes.ListItem, rkey, swapRecord, swapCommit, cancellationToken); + /// + /// Asynchronously retrieves a list of follow records from a specified repository. + /// + /// The ATIdentifier of the repository where the records are stored. + /// The maximum number of records to retrieve. Default is 50. + /// Optional. A string that represents the starting point for the next set of records. + /// Optional. A boolean that indicates whether the records should be retrieved in reverse order. + /// Optional. A CancellationToken that can be used to cancel the operation. + /// A Task that represents the asynchronous operation. The task result contains a Result object with a list of follow records, or null if no records were found. [Obsolete("Use ListFollowsAsync instead")] public Task> ListFollowAsync(ATIdentifier repo, int limit = 50, string? cursor = default, bool? reverse = default, CancellationToken cancellationToken = default) => this.ListRecordsAsync(Constants.GraphTypes.Follow, repo, limit, cursor, reverse, cancellationToken); + /// + /// Asynchronously retrieves a list of follow records from a specified repository. + /// + /// The ATIdentifier of the repository where the records are stored. + /// The maximum number of records to retrieve. Default is 50. + /// Optional. A string that represents the starting point for the next set of records. + /// Optional. A boolean that indicates whether the records should be retrieved in reverse order. + /// Optional. A CancellationToken that can be used to cancel the operation. + /// A Task that represents the asynchronous operation. The task result contains a Result object with a list of follow records, or null if no records were found. public Task> ListFollowsAsync(ATIdentifier repo, int limit = 50, string? cursor = default, bool? reverse = default, CancellationToken cancellationToken = default) => this.ListRecordsAsync(Constants.GraphTypes.Follow, repo, limit, cursor, reverse, cancellationToken); + /// + /// Asynchronously retrieves a list of block records from a specified repository. + /// + /// The ATIdentifier of the repository where the records are stored. + /// The maximum number of records to retrieve. Default is 50. + /// Optional. A string that represents the starting point for the next set of records. + /// Optional. A boolean that indicates whether the records should be retrieved in reverse order. + /// Optional. A CancellationToken that can be used to cancel the operation. + /// A Task that represents the asynchronous operation. The task result contains a Result object with a list of block records, or null if no records were found. [Obsolete("Use ListBlocksAsync instead")] public Task> ListBlockAsync(ATIdentifier repo, int limit = 50, string? cursor = default, bool? reverse = default, CancellationToken cancellationToken = default) => this.ListRecordsAsync(Constants.GraphTypes.Block, repo, limit, cursor, reverse, cancellationToken); + /// + /// Asynchronously retrieves a list of block records from a specified repository. + /// + /// The ATIdentifier of the repository where the records are stored. + /// The maximum number of records to retrieve. Default is 50. + /// Optional. A string that represents the starting point for the next set of records. + /// Optional. A boolean that indicates whether the records should be retrieved in reverse order. + /// Optional. A CancellationToken that can be used to cancel the operation. + /// A Task that represents the asynchronous operation. The task result contains a Result object with a list of block records, or null if no records were found. public Task> ListBlocksAsync(ATIdentifier repo, int limit = 50, string? cursor = default, bool? reverse = default, CancellationToken cancellationToken = default) => this.ListRecordsAsync(Constants.GraphTypes.Block, repo, limit, cursor, reverse, cancellationToken); + /// + /// Asynchronously retrieves a list of like records from a specified repository. + /// + /// The ATIdentifier of the repository where the records are stored. + /// The maximum number of records to retrieve. Default is 50. + /// Optional. A string that represents the starting point for the next set of records. + /// Optional. A boolean that indicates whether the records should be retrieved in reverse order. + /// Optional. A CancellationToken that can be used to cancel the operation. + /// A Task that represents the asynchronous operation. The task result contains a Result object with a list of records, or null if no records were found. [Obsolete("Use ListLikesAsync instead")] public Task> ListLikeAsync(ATIdentifier repo, int limit = 50, string? cursor = default, bool? reverse = default, CancellationToken cancellationToken = default) => this.ListRecordsAsync(Constants.FeedType.Like, repo, limit, cursor, reverse, cancellationToken); + /// + /// Asynchronously retrieves a list of like records from a specified repository. + /// + /// The ATIdentifier of the repository where the records are stored. + /// The maximum number of records to retrieve. Default is 50. + /// Optional. A string that represents the starting point for the next set of records. + /// Optional. A boolean that indicates whether the records should be retrieved in reverse order. + /// Optional. A CancellationToken that can be used to cancel the operation. + /// A Task that represents the asynchronous operation. The task result contains a Result object with a list of records, or null if no records were found. public Task> ListLikesAsync(ATIdentifier repo, int limit = 50, string? cursor = default, bool? reverse = default, CancellationToken cancellationToken = default) => this.ListRecordsAsync(Constants.FeedType.Like, repo, limit, cursor, reverse, cancellationToken); + /// + /// Asynchronously retrieves a list of post records from a specified repository. + /// + /// The ATIdentifier of the repository where the records are stored. + /// The maximum number of records to retrieve. Default is 50. + /// Optional. A string that represents the starting point for the next set of records. + /// Optional. A boolean that indicates whether the records should be retrieved in reverse order. + /// Optional. A CancellationToken that can be used to cancel the operation. + /// A Task that represents the asynchronous operation. The task result contains a Result object with a list of records, or null if no records were found. [Obsolete("Use ListPostsAsync instead")] public Task> ListPostAsync(ATIdentifier repo, int limit = 50, string? cursor = default, bool? reverse = default, CancellationToken cancellationToken = default) => this.ListRecordsAsync(Constants.FeedType.Post, repo, limit, cursor, reverse, cancellationToken); + /// + /// Asynchronously retrieves a list of post records from a specified repository. + /// + /// The ATIdentifier of the repository where the records are stored. + /// The maximum number of records to retrieve. Default is 50. + /// Optional. A string that represents the starting point for the next set of records. + /// Optional. A boolean that indicates whether the records should be retrieved in reverse order. + /// Optional. A CancellationToken that can be used to cancel the operation. + /// A Task that represents the asynchronous operation. The task result contains a Result object with a list of records, or null if no records were found. public Task> ListPostsAsync(ATIdentifier repo, int limit = 50, string? cursor = default, bool? reverse = default, CancellationToken cancellationToken = default) => this.ListRecordsAsync(Constants.FeedType.Post, repo, limit, cursor, reverse, cancellationToken); + /// + /// Asynchronously retrieves a list of records from a specified repository. + /// + /// The name of the collection where the records are stored. + /// The ATIdentifier of the repository where the records are stored. + /// The maximum number of records to retrieve. Default is 50. + /// Optional. A string that represents the starting point for the next set of records. + /// Optional. A boolean that indicates whether the records should be retrieved in reverse order. + /// Optional. A CancellationToken that can be used to cancel the operation. + /// A Task that represents the asynchronous operation. The task result contains a Result object with a list of records, or null if no records were found. public async Task> ListRecordsAsync(string collection, ATIdentifier repo, int limit = 50, string? cursor = default, bool? reverse = default, CancellationToken cancellationToken = default) { string url = $"{Constants.Urls.ATProtoRepo.ListRecords}?collection={collection}&repo={repo}&limit={limit}"; @@ -272,27 +508,31 @@ public Task> DeleteListItemAsync( url += $"&reverse={reverse}"; } - return await this.Client.Get(url, this.Options.JsonSerializerOptions, cancellationToken, this.Options.Logger); + return await this.Client.Get(url, this.Options.SourceGenerationContext.ListRecords, this.Options.JsonSerializerOptions, cancellationToken, this.Options.Logger); } - private Task> PutRecord(T record, CancellationToken cancellationToken = default) + private Task> PutRecord(T record, JsonTypeInfo c1, JsonTypeInfo c2, CancellationToken cancellationToken = default) { return this.Client .Post( Constants.Urls.ATProtoRepo.PutRecord, + c1, + c2, this.Options.JsonSerializerOptions, record, cancellationToken, this.Options.Logger); } - private Task> CreateRecord(T record, CancellationToken cancellationToken = default) + private Task> CreateRecord(T record, JsonTypeInfo c1, JsonTypeInfo c2, CancellationToken cancellationToken = default) { return this.Client .Post( Constants.Urls.ATProtoRepo.CreateRecord, + c1, + c2, this.Options.JsonSerializerOptions, record, cancellationToken, @@ -307,6 +547,6 @@ private async Task> DeleteRecordAsync(string collection, string rkey, swapRecord, swapCommit); - return await this.Client.Post(Constants.Urls.ATProtoRepo.DeleteRecord, this.Options.JsonSerializerOptions, record, cancellationToken, this.Options.Logger); + return await this.Client.Post(Constants.Urls.ATProtoRepo.DeleteRecord, this.Options.SourceGenerationContext.DeleteRecord, this.Options.SourceGenerationContext.Success, this.Options.JsonSerializerOptions, record, cancellationToken, this.Options.Logger); } } diff --git a/src/FishyFlip/ATProtoServer.cs b/src/FishyFlip/ATProtoServer.cs index fdd72dc..c10e41c 100644 --- a/src/FishyFlip/ATProtoServer.cs +++ b/src/FishyFlip/ATProtoServer.cs @@ -24,10 +24,17 @@ internal ATProtoServer(ATProtocol proto) private HttpClient Client => this.proto.Client; + /// + /// Asynchronously creates a new session. + /// + /// The identifier of the user. + /// The password of the user. + /// Optional. A CancellationToken that can be used to cancel the operation. + /// A Task that represents the asynchronous operation. The task result contains a Result object with the session details, or null if the session could not be created. public async Task> CreateSessionAsync(string identifier, string password, CancellationToken cancellationToken = default) { Result result = - await this.Client.Post(Constants.Urls.ATProtoServer.CreateSession, this.Options.JsonSerializerOptions, new Login(identifier, password), cancellationToken); + await this.Client.Post(Constants.Urls.ATProtoServer.CreateSession, this.Options.SourceGenerationContext.Login, this.Options.SourceGenerationContext.Session, this.Options.JsonSerializerOptions, new Login(identifier, password), cancellationToken); return result @@ -40,15 +47,21 @@ public async Task> CreateSessionAsync(string identifier, string error => error!); } + /// + /// Asynchronously refreshes an existing session. + /// + /// The current session that needs to be refreshed. + /// Optional. A CancellationToken that can be used to cancel the operation. + /// A Task that represents the asynchronous operation. The task result contains a Result object with the refreshed session details, or null if the session could not be refreshed. public async Task> RefreshSessionAsync( - Session session, - CancellationToken cancellationToken = default) + Session session, + CancellationToken cancellationToken = default) { this.Client .DefaultRequestHeaders .Authorization = new AuthenticationHeaderValue("Bearer", session.RefreshJwt); - var result = await this.Client.Post(Constants.Urls.ATProtoServer.RefreshSession, this.Options.JsonSerializerOptions, cancellationToken, this.Options.Logger); + var result = await this.Client.Post(Constants.Urls.ATProtoServer.RefreshSession, this.Options.SourceGenerationContext.Session, this.Options.JsonSerializerOptions, cancellationToken, this.Options.Logger); return result .Match( @@ -60,28 +73,65 @@ public async Task> RefreshSessionAsync( error => error!); } + /// + /// Asynchronously creates a new invite code. + /// + /// The number of times the invite code can be used. + /// Optional. The ATDid of the account for which the invite code is created. + /// Optional. A CancellationToken that can be used to cancel the operation. + /// A Task that represents the asynchronous operation. The task result contains a Result object with the invite code details, or null if the invite code could not be created. public async Task> CreateInviteCodeAsync(int useCount, ATDid? forAccount = default, CancellationToken cancellationToken = default) { - return await this.Client.Post(Constants.Urls.ATProtoServer.CreateInviteCode, this.Options.JsonSerializerOptions, new CreateInviteCode(useCount, forAccount), cancellationToken); + return await this.Client.Post(Constants.Urls.ATProtoServer.CreateInviteCode, this.Options.SourceGenerationContext.CreateInviteCode, this.Options.SourceGenerationContext.InviteCode, this.Options.JsonSerializerOptions, new CreateInviteCode(useCount, forAccount), cancellationToken); } + /// + /// Asynchronously creates multiple invite codes. + /// + /// The number of times each invite code can be used. + /// Optional. The number of invite codes to create. Default is 1. + /// Optional. An array of ATDid for the accounts for which the invite codes are created. + /// Optional. A CancellationToken that can be used to cancel the operation. + /// A Task that represents the asynchronous operation. The task result contains a Result object with the invite codes details, or null if the invite codes could not be created. public async Task> CreateInviteCodesAsync(int useCount, int codeCount = 1, ATDid[]? forAccounts = default, CancellationToken cancellationToken = default) { - return await this.Client.Post(Constants.Urls.ATProtoServer.CreateInviteCode, this.Options.JsonSerializerOptions, new CreateInviteCodes(codeCount, useCount, forAccounts), cancellationToken); + return await this.Client.Post(Constants.Urls.ATProtoServer.CreateInviteCode, this.Options.SourceGenerationContext.CreateInviteCodes, this.Options.SourceGenerationContext.InviteCodes, this.Options.JsonSerializerOptions, new CreateInviteCodes(codeCount, useCount, forAccounts), cancellationToken); } + /// + /// Asynchronously retrieves the current session information. + /// + /// Optional. A CancellationToken that can be used to cancel the operation. + /// A Task that represents the asynchronous operation. The task result contains a Result object with the session information, or null if no session information was found. public Task> GetSessionAsync(CancellationToken cancellationToken = default) - => this.Client.Get(Constants.Urls.ATProtoServer.GetSession, this.Options.JsonSerializerOptions, cancellationToken); + => this.Client.Get(Constants.Urls.ATProtoServer.GetSession, this.Options.SourceGenerationContext.SessionInfo, this.Options.JsonSerializerOptions, cancellationToken); + /// + /// Asynchronously retrieves the invite codes for the current account. + /// + /// Optional. A boolean that indicates whether to include used invite codes. Default is true. + /// Optional. A boolean that indicates whether to create available invite codes. Default is true. + /// Optional. A CancellationToken that can be used to cancel the operation. + /// A Task that represents the asynchronous operation. The task result contains a Result object with the invite codes for the account, or null if no invite codes were found. public Task> GetAccountInviteCodesAsync(bool includeUsed = true, bool createAvailable = true, CancellationToken cancellationToken = default) { var url = $"{Constants.Urls.ATProtoServer.GetAccountInviteCodes}?includeUsed={includeUsed.ToString().ToLowerInvariant()}&createAvailable={createAvailable.ToString().ToLowerInvariant()}"; - return this.Client.Get(url, this.Options.JsonSerializerOptions, cancellationToken); + return this.Client.Get(url, this.Options.SourceGenerationContext.AccountInviteCodes, this.Options.JsonSerializerOptions, cancellationToken); } + /// + /// Asynchronously retrieves the list of application passwords. + /// + /// Optional. A CancellationToken that can be used to cancel the operation. + /// A Task that represents the asynchronous operation. The task result contains a Result object with the list of application passwords, or null if no application passwords were found. public Task> ListAppPasswordsAsync(CancellationToken cancellationToken = default) - => this.Client.Get(Constants.Urls.ATProtoServer.ListAppPasswords, this.Options.JsonSerializerOptions, cancellationToken); + => this.Client.Get(Constants.Urls.ATProtoServer.ListAppPasswords, this.Options.SourceGenerationContext.AppPasswords, this.Options.JsonSerializerOptions, cancellationToken); + /// + /// Asynchronously retrieves the server description. + /// + /// Optional. A CancellationToken that can be used to cancel the operation. + /// A Task that represents the asynchronous operation. The task result contains a Result object with the server description, or null if no server description was found. public Task> DescribeServerAsync(CancellationToken cancellationToken = default) - => this.Client.Get(Constants.Urls.ATProtoServer.DescribeServer, this.Options.JsonSerializerOptions, cancellationToken); + => this.Client.Get(Constants.Urls.ATProtoServer.DescribeServer, this.Options.SourceGenerationContext.DescribeServer, this.Options.JsonSerializerOptions, cancellationToken); } diff --git a/src/FishyFlip/ATProtoSync.cs b/src/FishyFlip/ATProtoSync.cs index 6b35a9b..a75d9a8 100644 --- a/src/FishyFlip/ATProtoSync.cs +++ b/src/FishyFlip/ATProtoSync.cs @@ -47,6 +47,7 @@ internal ATProtoSync(ATProtocol proto) public Task> GetHeadAsync(ATDid did, CancellationToken cancellationToken = default) => this.Client.Get( $"{Constants.Urls.ATProtoSync.GetHead}?did={did}", + this.Options.SourceGenerationContext.Head, this.Options.JsonSerializerOptions, cancellationToken, this.Options.Logger); @@ -60,6 +61,7 @@ internal ATProtoSync(ATProtocol proto) public Task> GetLatestCommitAsync(ATDid did, CancellationToken cancellationToken = default) => this.Client.Get( $"{Constants.Urls.ATProtoSync.GetLatestCommit}?did={did}", + this.Options.SourceGenerationContext.LatestCommit, this.Options.JsonSerializerOptions, cancellationToken, this.Options.Logger); @@ -71,7 +73,7 @@ internal ATProtoSync(ATProtocol proto) /// Optional cancellation token. /// Result of Success. public Task> NotifyOfUpdateAsync(string hostname, CancellationToken cancellationToken = default) - => this.Client.Get($"{Constants.Urls.ATProtoSync.NotifyOfUpdate}?hostname={hostname}", this.Options.JsonSerializerOptions, cancellationToken, this.Options.Logger); + => this.Client.Get($"{Constants.Urls.ATProtoSync.NotifyOfUpdate}?hostname={hostname}", this.Options.SourceGenerationContext.Success, this.Options.JsonSerializerOptions, cancellationToken, this.Options.Logger); /// /// Request crawl for given hostname. @@ -80,7 +82,7 @@ internal ATProtoSync(ATProtocol proto) /// Optional cancellation token. /// Result of Success. public Task> RequestCrawlAsync(string hostname, CancellationToken cancellationToken = default) - => this.Client.Get($"{Constants.Urls.ATProtoSync.RequestCrawl}?hostname={hostname}", this.Options.JsonSerializerOptions, cancellationToken, this.Options.Logger); + => this.Client.Get($"{Constants.Urls.ATProtoSync.RequestCrawl}?hostname={hostname}", this.Options.SourceGenerationContext.Success, this.Options.JsonSerializerOptions, cancellationToken, this.Options.Logger); /// /// Get Repo. @@ -146,7 +148,7 @@ internal ATProtoSync(ATProtocol proto) url += $"&earliest={earliest}"; } - return await this.Client.Get(url, this.Options.JsonSerializerOptions, cancellationToken, this.Options.Logger); + return await this.Client.Get(url, this.Options.SourceGenerationContext.CommitPath, this.Options.JsonSerializerOptions, cancellationToken, this.Options.Logger); } /// @@ -324,7 +326,7 @@ internal ATProtoSync(ATProtocol proto) url += $"&cursor={cursor}"; } - return await this.Client.Get(url, this.Options.JsonSerializerOptions, cancellationToken, this.Options.Logger); + return await this.Client.Get(url, this.Options.SourceGenerationContext.RepoList, this.Options.JsonSerializerOptions, cancellationToken, this.Options.Logger); } /// @@ -350,6 +352,6 @@ internal ATProtoSync(ATProtocol proto) url += $"&since={since}"; } - return await this.Client.Get(url, this.Options.JsonSerializerOptions, cancellationToken, this.Options.Logger); + return await this.Client.Get(url, this.Options.SourceGenerationContext.ListBlobs, this.Options.JsonSerializerOptions, cancellationToken, this.Options.Logger); } } diff --git a/src/FishyFlip/ATProtocol.cs b/src/FishyFlip/ATProtocol.cs index 372d79d..c8bd979 100644 --- a/src/FishyFlip/ATProtocol.cs +++ b/src/FishyFlip/ATProtocol.cs @@ -103,12 +103,6 @@ internal ATProtocol(ATProtocolOptions options) /// public ATProtoModeration Moderation => new(this); - /// - /// Gets a debug protocol handle for interacting with ATProto. - /// . - /// - public ATProtoDebug Debug => new(this); - /// /// Gets the ATProto Unspecced Protocol. /// @@ -208,6 +202,14 @@ public void UpdateOptions(ATProtocolOptions options) } } + /// + /// Refreshes the current session asynchronously. + /// + /// + /// A task that represents the asynchronous operation. + /// If the session manager is null, the task will complete immediately. + /// Otherwise, the task will complete when the session has been refreshed. + /// public Task RefreshSessionAsync() => this.sessionManager?.RefreshTokenAsync() ?? Task.CompletedTask; diff --git a/src/FishyFlip/ATProtocolBuilder.cs b/src/FishyFlip/ATProtocolBuilder.cs index 97a583d..064a618 100644 --- a/src/FishyFlip/ATProtocolBuilder.cs +++ b/src/FishyFlip/ATProtocolBuilder.cs @@ -35,6 +35,7 @@ public ATProtocolBuilder(ATProtocolOptions options) /// Set a custom HttpClient. /// /// HttpClient. + /// Enables the default values to be set. /// . public ATProtocolBuilder WithHttpClient(HttpClient client, bool setDefaults = true) { @@ -69,7 +70,7 @@ public ATProtocolBuilder WithUserAgent(string userAgent) /// Sets UseServiceEndpointUponLogin. /// /// Value for UseServiceEndpointUponLogin. - /// .s + /// .s. public ATProtocolBuilder WithServiceEndpointUponLogin(bool serviceEndpointUponLogin) { this.atProtocolOptions.UseServiceEndpointUponLogin = serviceEndpointUponLogin; diff --git a/src/FishyFlip/ATProtocolOptions.cs b/src/FishyFlip/ATProtocolOptions.cs index f9b4fb8..e003d83 100644 --- a/src/FishyFlip/ATProtocolOptions.cs +++ b/src/FishyFlip/ATProtocolOptions.cs @@ -17,12 +17,6 @@ public ATProtocolOptions() // HACK: Decodes a message to load the default Cid protocols. Cid.Decode("bafyreiezjt5bqt2xpcdfvisud7jrd4zuxygz4ssnuge3ddjcoptanvcnsa"); this.HttpClient = new HttpClient(new HttpClientHandler { MaxRequestContentBufferSize = int.MaxValue }); - this.JsonSerializerOptions = new JsonSerializerOptions() - { - PropertyNamingPolicy = JsonNamingPolicy.CamelCase, - PropertyNameCaseInsensitive = true, - DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull | JsonIgnoreCondition.WhenWritingDefault, - }; this.Url = new Uri("https://bsky.social"); this.JsonSerializerOptions = new JsonSerializerOptions() { @@ -40,6 +34,7 @@ public ATProtocolOptions() }, }; + this.SourceGenerationContext = new SourceGenerationContext(this.JsonSerializerOptions); this.UserAgent = $"FishyFlip {System.Reflection.Assembly.GetExecutingAssembly().GetName().Version}"; } @@ -89,6 +84,11 @@ public ATProtocolOptions() /// public bool UseServiceEndpointUponLogin { get; internal set; } = true; + /// + /// Gets the source generation context. + /// + internal SourceGenerationContext SourceGenerationContext { get; } + /// /// Update the existing HttpClient with a new URI endpoint. /// diff --git a/src/FishyFlip/ATWebSocketProtocolBuilder.cs b/src/FishyFlip/ATWebSocketProtocolBuilder.cs index 01bd4ad..c4627eb 100644 --- a/src/FishyFlip/ATWebSocketProtocolBuilder.cs +++ b/src/FishyFlip/ATWebSocketProtocolBuilder.cs @@ -32,7 +32,7 @@ public ATWebSocketProtocolBuilder(ATWebSocketProtocolOptions options) /// Set the instance url to connect to. /// /// Instance Url. - /// + /// . public ATWebSocketProtocolBuilder WithInstanceUrl(Uri url) { this.atProtocolOptions.Url = url; @@ -43,7 +43,7 @@ public ATWebSocketProtocolBuilder WithInstanceUrl(Uri url) /// Adds a logger. /// /// Logger. - /// + /// . public ATWebSocketProtocolBuilder WithLogger(ILogger? logger) { this.atProtocolOptions.Logger = logger; @@ -53,7 +53,7 @@ public ATWebSocketProtocolBuilder WithLogger(ILogger? logger) /// /// Returns the ATWebSocketProtocolOptions. /// - /// ATWebSocketProtocolOptions + /// ATWebSocketProtocolOptions. public ATWebSocketProtocolOptions BuildOptions() { return this.atProtocolOptions; diff --git a/src/FishyFlip/BlueskyActor.cs b/src/FishyFlip/BlueskyActor.cs index 138fa3a..b5ea188 100644 --- a/src/FishyFlip/BlueskyActor.cs +++ b/src/FishyFlip/BlueskyActor.cs @@ -24,19 +24,38 @@ internal BlueskyActor(ATProtocol proto) private HttpClient Client => this.proto.Client; + /// + /// Asynchronously retrieves the profile of a specified actor. + /// + /// The identifier of the actor whose profile is to be retrieved. + /// Optional. A CancellationToken that can be used to cancel the operation. + /// A Task that represents the asynchronous operation. The task result contains a Result object with the actor's profile, or null if the actor's profile could not be retrieved. public Task> GetProfileAsync(ATIdentifier identifier, CancellationToken cancellationToken = default) { string url = $"{Constants.Urls.Bluesky.Actor.GetActorProfile}?actor={identifier}"; - return this.Client.Get(url, this.Options.JsonSerializerOptions, cancellationToken, this.Options.Logger); + return this.Client.Get(url, this.Options.SourceGenerationContext.FeedProfile, this.Options.JsonSerializerOptions, cancellationToken, this.Options.Logger); } + /// + /// Asynchronously retrieves the profiles of specified actors. + /// + /// An array of identifiers of the actors whose profiles are to be retrieved. + /// Optional. A CancellationToken that can be used to cancel the operation. + /// A Task that represents the asynchronous operation. The task result contains a Result object with the actors' profiles, or null if the actors' profiles could not be retrieved. public Task> GetProfilesAsync(ATIdentifier[] identifiers, CancellationToken cancellationToken = default) { var identList = string.Join("&", identifiers.Select(n => $"actors={n}")); string url = $"{Constants.Urls.Bluesky.Actor.GetActorProfiles}?{identList}"; - return this.Client.Get(url, this.Options.JsonSerializerOptions, cancellationToken, this.Options.Logger); + return this.Client.Get(url, this.Options.SourceGenerationContext.FeedProfiles, this.Options.JsonSerializerOptions, cancellationToken, this.Options.Logger); } + /// + /// Asynchronously retrieves actor suggestions. + /// + /// Optional. The maximum number of actor suggestions to retrieve. Default is 50. + /// Optional. A cursor that can be used to paginate through actor suggestions. + /// Optional. A CancellationToken that can be used to cancel the operation. + /// A Task that represents the asynchronous operation. The task result contains a Result object with the actor suggestions, or null if no actor suggestions were found. public Task> GetSuggestionsAsync(int limit = 50, string? cursor = null, CancellationToken cancellationToken = default) { string url = $"{Constants.Urls.Bluesky.Actor.GetActorSuggestions}?limit={limit}"; @@ -45,9 +64,17 @@ internal BlueskyActor(ATProtocol proto) url += $"&cursor={cursor}"; } - return this.Client.Get(url, this.Options.JsonSerializerOptions, cancellationToken, this.Options.Logger); + return this.Client.Get(url, this.Options.SourceGenerationContext.ActorResponse, this.Options.JsonSerializerOptions, cancellationToken, this.Options.Logger); } + /// + /// Asynchronously searches for actors based on a query. + /// + /// The search query. + /// Optional. The maximum number of actors to retrieve. Default is 50. + /// Optional. A cursor that can be used to paginate through actors. + /// Optional. A CancellationToken that can be used to cancel the operation. + /// A Task that represents the asynchronous operation. The task result contains a Result object with the actors that match the query, or null if no matching actors were found. public Task> SearchActorsAsync(string query, int limit = 50, string? cursor = null, CancellationToken cancellationToken = default) { string url = $"{Constants.Urls.Bluesky.Actor.SearchActors}?term={query}&limit={limit}"; @@ -56,13 +83,20 @@ internal BlueskyActor(ATProtocol proto) url += $"&cursor={cursor}"; } - return this.Client.Get(url, this.Options.JsonSerializerOptions, cancellationToken, this.Options.Logger); + return this.Client.Get(url, this.Options.SourceGenerationContext.ActorResponse, this.Options.JsonSerializerOptions, cancellationToken, this.Options.Logger); } + /// + /// Asynchronously searches for actors based on a query, with typeahead functionality. + /// + /// The search query. + /// Optional. The maximum number of actors to retrieve. Default is 50. + /// Optional. A CancellationToken that can be used to cancel the operation. + /// A Task that represents the asynchronous operation. The task result contains a Result object with the actors that match the query, or null if no matching actors were found. public Task> SearchActorsTypeaheadAsync(string query, int limit = 50, CancellationToken cancellationToken = default) { string url = $"{Constants.Urls.Bluesky.Actor.SearchActorsTypeahead}?term={query}&limit={limit}"; - return this.Client.Get(url, this.Options.JsonSerializerOptions, cancellationToken, this.Options.Logger); + return this.Client.Get(url, this.Options.SourceGenerationContext.ActorResponse, this.Options.JsonSerializerOptions, cancellationToken, this.Options.Logger); } } diff --git a/src/FishyFlip/BlueskyFeed.cs b/src/FishyFlip/BlueskyFeed.cs index 5551737..d1e690c 100644 --- a/src/FishyFlip/BlueskyFeed.cs +++ b/src/FishyFlip/BlueskyFeed.cs @@ -4,6 +4,9 @@ namespace FishyFlip; +/// +/// Bluesky Feed. +/// public sealed class BlueskyFeed { private ATProtocol proto; @@ -21,6 +24,13 @@ internal BlueskyFeed(ATProtocol proto) private HttpClient Client => this.proto.Client; + /// + /// Asynchronously retrieves the thread of a post. + /// + /// The URI of the post whose thread is to be retrieved. + /// Optional. The depth of the thread. Default is 0. + /// Optional. A CancellationToken that can be used to cancel the operation. + /// A Task that represents the asynchronous operation. The task result contains a Result object with the thread of the post, or null if the thread could not be retrieved. public async Task> GetPostThreadAsync( ATUri uri, int depth = 0, @@ -32,13 +42,22 @@ public async Task> GetPostThreadAsync( url += $"&depth={depth}"; } - Multiple result = await this.Client.Get(url, this.Options.JsonSerializerOptions, cancellationToken, this.Options.Logger); + Multiple result = await this.Client.Get(url, this.Options.SourceGenerationContext.ThreadPostViewFeed, this.Options.JsonSerializerOptions, cancellationToken, this.Options.Logger); return result .Match>( timeline => timeline!, error => error!); } + /// + /// Asynchronously retrieves the reposts of a post. + /// + /// The URI of the post whose reposts are to be retrieved. + /// Optional. The maximum number of reposts to retrieve. Default is 50. + /// Optional. A CID that can be used to paginate through reposts. + /// Optional. A cursor that can be used to paginate through reposts. + /// Optional. A CancellationToken that can be used to cancel the operation. + /// A Task that represents the asynchronous operation. The task result contains a Result object with the reposts of the post, or null if no reposts were found. public async Task> GetRepostedByAsync( ATUri uri, int limit = 50, @@ -58,13 +77,22 @@ public async Task> GetRepostedByAsync( url += $"&cursor={cursor}"; } - Multiple result = await this.Client.Get(url, this.Options.JsonSerializerOptions, cancellationToken, this.Options.Logger); + Multiple result = await this.Client.Get(url, this.Options.SourceGenerationContext.RepostedFeed, this.Options.JsonSerializerOptions, cancellationToken, this.Options.Logger); return result .Match>( timeline => (timeline ?? new RepostedFeed(Array.Empty(), null))!, error => error!); } + /// + /// Asynchronously retrieves the likes of a post. + /// + /// The URI of the post whose likes are to be retrieved. + /// Optional. The maximum number of likes to retrieve. Default is 50. + /// Optional. A CID that can be used to paginate through likes. + /// Optional. A cursor that can be used to paginate through likes. + /// Optional. A CancellationToken that can be used to cancel the operation. + /// A Task that represents the asynchronous operation. The task result contains a Result object with the likes of the post, or null if no likes were found. public async Task> GetLikesAsync(ATUri uri, int limit = 50, Cid? cid = default, string? cursor = default, CancellationToken cancellationToken = default) { string url = $"{Constants.Urls.Bluesky.Feed.GetLikes}?uri={uri.ToString()}&limit={limit}"; @@ -79,24 +107,39 @@ public async Task> GetLikesAsync(ATUri uri, int limit = 50, Ci url += $"&cursor={cursor}"; } - Multiple result = await this.Client.Get(url, this.Options.JsonSerializerOptions, cancellationToken, this.Options.Logger); + Multiple result = await this.Client.Get(url, this.Options.SourceGenerationContext.LikesFeed, this.Options.JsonSerializerOptions, cancellationToken, this.Options.Logger); return result .Match>( timeline => (timeline ?? new LikesFeed(Array.Empty(), null))!, error => error!); } + /// + /// Asynchronously retrieves the feed of a list. + /// + /// The URI of the list whose feed is to be retrieved. + /// Optional. The maximum number of posts to retrieve. Default is 30. + /// Optional. A CancellationToken that can be used to cancel the operation. + /// A Task that represents the asynchronous operation. The task result contains a Result object with the feed of the list, or null if no feed was found. public async Task> GetListFeedAsync(ATUri uri, int limit = 30, CancellationToken cancellationToken = default) { string url = $"{Constants.Urls.Bluesky.Feed.GetListFeed}?list={uri.ToString()}&limit={limit}"; - Multiple result = await this.Client.Get(url, this.Options.JsonSerializerOptions, cancellationToken, this.Options.Logger); + Multiple result = await this.Client.Get(url, this.Options.SourceGenerationContext.ListFeed, this.Options.JsonSerializerOptions, cancellationToken, this.Options.Logger); return result .Match>( timeline => (timeline ?? new ListFeed(Array.Empty(), null))!, error => error!); } + /// + /// Asynchronously retrieves the feed of an author. + /// + /// The handle of the author whose feed is to be retrieved. + /// Optional. The maximum number of posts to retrieve. Default is 50. + /// Optional. A cursor that can be used to paginate through posts. + /// Optional. A CancellationToken that can be used to cancel the operation. + /// A Task that represents the asynchronous operation. The task result contains a Result object with the feed of the author, or null if no feed was found. public async Task> GetAuthorFeedAsync(ATIdentifier handle, int limit = 50, string? cursor = default, CancellationToken cancellationToken = default) { string url = $"{Constants.Urls.Bluesky.Feed.GetAuthorFeed}?actor={handle.ToString()}&limit={limit}"; @@ -105,13 +148,21 @@ public async Task> GetAuthorFeedAsync(ATIdentifier handle, int url += $"&cursor={cursor}"; } - Multiple result = await this.Client.Get(url, this.Options.JsonSerializerOptions, cancellationToken, this.Options.Logger); + Multiple result = await this.Client.Get(url, this.Options.SourceGenerationContext.Timeline, this.Options.JsonSerializerOptions, cancellationToken, this.Options.Logger); return result .Match>( authorFeed => (authorFeed ?? new Timeline(Array.Empty(), null))!, error => error!); } + /// + /// Asynchronously retrieves the likes of an actor. + /// + /// The handle of the actor whose likes are to be retrieved. + /// Optional. The maximum number of likes to retrieve. Default is 50. + /// Optional. A cursor that can be used to paginate through likes. + /// Optional. A CancellationToken that can be used to cancel the operation. + /// A Task that represents the asynchronous operation. The task result contains a Result object with the likes of the actor, or null if no likes were found. public async Task> GetActorLikesAsync(ATIdentifier handle, int limit = 50, string? cursor = default, CancellationToken cancellationToken = default) { string url = $"{Constants.Urls.Bluesky.Feed.GetActorLikes}?actor={handle.ToString()}&limit={limit}"; @@ -120,24 +171,38 @@ public async Task> GetActorLikesAsync(ATIdentifier handle, int url += $"&cursor={cursor}"; } - Multiple result = await this.Client.Get(url, this.Options.JsonSerializerOptions, cancellationToken, this.Options.Logger); + Multiple result = await this.Client.Get(url, this.Options.SourceGenerationContext.Timeline, this.Options.JsonSerializerOptions, cancellationToken, this.Options.Logger); return result .Match>( authorFeed => (authorFeed ?? new Timeline(Array.Empty(), null))!, error => error!); } + /// + /// Asynchronously retrieves posts based on a query. + /// + /// An array of URIs of the posts to be retrieved. + /// Optional. A CancellationToken that can be used to cancel the operation. + /// A Task that represents the asynchronous operation. The task result contains a Result object with the posts, or null if no posts were found. public async Task> GetPostsAsync(IEnumerable query, CancellationToken cancellationToken = default) { var answer = string.Join("&", query.Select(n => $"uris={n}")); string url = $"{Constants.Urls.Bluesky.Feed.GetPosts}?{answer}"; - Multiple result = await this.Client.Get(url, this.Options.JsonSerializerOptions, cancellationToken); + Multiple result = await this.Client.Get(url, this.Options.SourceGenerationContext.PostCollection, this.Options.JsonSerializerOptions, cancellationToken); return result .Match>( timeline => (timeline ?? new PostCollection(new PostView[0]))!, error => error!); } + /// + /// Asynchronously retrieves a timeline. + /// + /// Optional. The maximum number of posts to retrieve. Default is 50. + /// Optional. A cursor that can be used to paginate through posts. + /// Optional. The algorithm to use for retrieving the timeline. Default is "reverse-chronological". + /// Optional. A CancellationToken that can be used to cancel the operation. + /// A Task that represents the asynchronous operation. The task result contains a Result object with the timeline, or null if no timeline was found. public async Task> GetTimelineAsync(int limit = 50, string? cursor = default, string algorithm = "reverse-chronological", CancellationToken cancellationToken = default) { string url = $"{Constants.Urls.Bluesky.Feed.GetTimeline}?algorithm={algorithm}&limit={limit}"; @@ -146,13 +211,21 @@ public async Task> GetTimelineAsync(int limit = 50, string? cur url += $"&cursor={cursor}"; } - Multiple result = await this.Client.Get(url, this.Options.JsonSerializerOptions, cancellationToken, this.Options.Logger); + Multiple result = await this.Client.Get(url, this.Options.SourceGenerationContext.Timeline, this.Options.JsonSerializerOptions, cancellationToken, this.Options.Logger); return result .Match>( timeline => (timeline ?? new Timeline(Array.Empty(), null))!, error => error!); } + /// + /// Asynchronously retrieves a feed. + /// + /// The URI of the feed to be retrieved. + /// Optional. The maximum number of posts to retrieve. Default is 30. + /// Optional. A cursor that can be used to paginate through posts. + /// Optional. A CancellationToken that can be used to cancel the operation. + /// A Task that represents the asynchronous operation. The task result contains a Result object with the feed, or null if no feed was found. public async Task> GetFeedAsync(ATUri uri, int limit = 30, string? cursor = default, CancellationToken cancellationToken = default) { string url = $"{Constants.Urls.Bluesky.Feed.GetFeed}?feed={uri}&limit={limit}"; @@ -161,32 +234,51 @@ public async Task> GetFeedAsync(ATUri uri, int limit = 30, url += $"&cursor={cursor}"; } - Multiple result = await this.Client.Get(url, this.Options.JsonSerializerOptions, cancellationToken, this.Options.Logger); + Multiple result = await this.Client.Get(url, this.Options.SourceGenerationContext.FeedPostList, this.Options.JsonSerializerOptions, cancellationToken, this.Options.Logger); return result .Match>( timeline => (timeline ?? new FeedPostList(Array.Empty(), null))!, error => error!); } + /// + /// Asynchronously retrieves a feed generator. + /// + /// The URI of the feed generator to be retrieved. + /// Optional. A CancellationToken that can be used to cancel the operation. + /// A Task that represents the asynchronous operation. The task result contains a Result object with the feed generator, or null if no feed generator was found. public async Task> GetFeedGeneratorAsync(ATUri uri, CancellationToken cancellationToken = default) { string url = $"{Constants.Urls.Bluesky.Feed.GetFeedGenerator}?feed={uri}"; - Multiple result = await this.Client.Get(url, this.Options.JsonSerializerOptions, cancellationToken, this.Options.Logger); + Multiple result = await this.Client.Get(url, this.Options.SourceGenerationContext.FeedGeneratorRecord, this.Options.JsonSerializerOptions, cancellationToken, this.Options.Logger); return result .Match>( timeline => timeline!, error => error!); } + /// + /// Asynchronously retrieves feed generators based on a query. + /// + /// An array of URIs of the feed generators to be retrieved. + /// Optional. A CancellationToken that can be used to cancel the operation. + /// A Task that represents the asynchronous operation. The task result contains a Result object with the feed generators, or null if no feed generators were found. public async Task> GetFeedGeneratorsAsync(IEnumerable query, CancellationToken cancellationToken = default) { var answer = string.Join("&", query.Select(n => $"feeds={n}")); string url = $"{Constants.Urls.Bluesky.Feed.GetFeedGenerators}?{answer}"; - return await this.Client.Get(url, this.Options.JsonSerializerOptions, cancellationToken, this.Options.Logger); + return await this.Client.Get(url, this.Options.SourceGenerationContext.FeedCollection!, this.Options.JsonSerializerOptions, cancellationToken, this.Options.Logger); } + /// + /// Asynchronously retrieves suggested feeds. + /// + /// Optional. The maximum number of suggested feeds to retrieve. Default is 50. + /// Optional. A cursor that can be used to paginate through suggested feeds. + /// Optional. A CancellationToken that can be used to cancel the operation. + /// A Task that represents the asynchronous operation. The task result contains a Result object with the suggested feeds, or null if no suggested feeds were found. public async Task> GetSuggestedFeedsAsync(int limit = 50, string? cursor = default, CancellationToken cancellationToken = default) { string url = $"{Constants.Urls.Bluesky.Feed.GetSuggestedFeeds}?limit={limit}"; @@ -195,9 +287,17 @@ public async Task> GetFeedGeneratorAsync(ATUri uri, url += $"&cursor={cursor}"; } - return await this.Client.Get(url, this.Options.JsonSerializerOptions, cancellationToken, this.Options.Logger); + return await this.Client.Get(url, this.Options.SourceGenerationContext.GeneratorFeed, this.Options.JsonSerializerOptions, cancellationToken, this.Options.Logger); } + /// + /// Asynchronously retrieves the feeds of an actor. + /// + /// The identifier of the actor whose feeds are to be retrieved. + /// Optional. The maximum number of feeds to retrieve. Default is 50. + /// Optional. A cursor that can be used to paginate through feeds. + /// Optional. A CancellationToken that can be used to cancel the operation. + /// A Task that represents the asynchronous operation. The task result contains a Result object with the feeds of the actor, or null if no feeds were found. public async Task> GetActorFeedsAsync(ATIdentifier actor, int limit = 50, string? cursor = default, CancellationToken cancellationToken = default) { string url = $"{Constants.Urls.Bluesky.Feed.GetActorFeeds}?actor={actor}&limit={limit}"; @@ -206,7 +306,7 @@ public async Task> GetFeedGeneratorAsync(ATUri uri, url += $"&cursor={cursor}"; } - return await this.Client.Get(url, this.Options.JsonSerializerOptions, cancellationToken, this.Options.Logger); + return await this.Client.Get(url, this.Options.SourceGenerationContext.GeneratorFeed, this.Options.JsonSerializerOptions, cancellationToken, this.Options.Logger); } /// @@ -229,6 +329,6 @@ public async Task> GetFeedGeneratorAsync(ATUri uri, url += $"&cursor={cursor}"; } - return await this.Client.Get(url, this.Options.JsonSerializerOptions, cancellationToken, this.Options.Logger); + return await this.Client.Get(url, this.Options.SourceGenerationContext.SearchResults, this.Options.JsonSerializerOptions, cancellationToken, this.Options.Logger); } } diff --git a/src/FishyFlip/BlueskyGraph.cs b/src/FishyFlip/BlueskyGraph.cs index 9459731..c6f51e9 100644 --- a/src/FishyFlip/BlueskyGraph.cs +++ b/src/FishyFlip/BlueskyGraph.cs @@ -24,6 +24,13 @@ internal BlueskyGraph(ATProtocol proto) private HttpClient Client => this.proto.Client; + /// + /// Asynchronously retrieves the blocks of an actor. + /// + /// Optional. The maximum number of blocks to retrieve. Default is 50. + /// Optional. A cursor that can be used to paginate through blocks. + /// Optional. A CancellationToken that can be used to cancel the operation. + /// A Task that represents the asynchronous operation. The task result contains a Result object with the blocks of the actor, or null if no blocks were found. public async Task> GetBlocksAsync( int limit = 50, string? cursor = default, @@ -36,9 +43,16 @@ internal BlueskyGraph(ATProtocol proto) url += $"&cursor={cursor}"; } - return await this.Client.Get(url, this.Options.JsonSerializerOptions, cancellationToken, this.Options.Logger); + return await this.Client.Get(url, this.Options.SourceGenerationContext.ActorBlocks, this.Options.JsonSerializerOptions, cancellationToken, this.Options.Logger); } + /// + /// Asynchronously retrieves the mutes of an actor. + /// + /// Optional. The maximum number of mutes to retrieve. Default is 50. + /// Optional. A cursor that can be used to paginate through mutes. + /// Optional. A CancellationToken that can be used to cancel the operation. + /// A Task that represents the asynchronous operation. The task result contains a Result object with the mutes of the actor, or null if no mutes were found. public async Task> GetMutesAsync( int limit = 50, string? cursor = default, @@ -51,9 +65,17 @@ internal BlueskyGraph(ATProtocol proto) url += $"&cursor={cursor}"; } - return await this.Client.Get(url, this.Options.JsonSerializerOptions, cancellationToken, this.Options.Logger); + return await this.Client.Get(url, this.Options.SourceGenerationContext.ActorMutes, this.Options.JsonSerializerOptions, cancellationToken, this.Options.Logger); } + /// + /// Asynchronously retrieves the follows of an actor. + /// + /// The identifier of the actor whose follows are to be retrieved. + /// Optional. The maximum number of follows to retrieve. Default is 50. + /// Optional. A cursor that can be used to paginate through follows. + /// Optional. A CancellationToken that can be used to cancel the operation. + /// A Task that represents the asynchronous operation. The task result contains a Result object with the follows of the actor, or null if no follows were found. public async Task> GetFollowsAsync( ATIdentifier identifier, int limit = 50, @@ -67,9 +89,17 @@ internal BlueskyGraph(ATProtocol proto) url += $"&cursor={cursor}"; } - return await this.Client.Get(url, this.Options.JsonSerializerOptions, cancellationToken, this.Options.Logger); + return await this.Client.Get(url, this.Options.SourceGenerationContext.ActorFollows, this.Options.JsonSerializerOptions, cancellationToken, this.Options.Logger); } + /// + /// Asynchronously retrieves the followers of an actor. + /// + /// The identifier of the actor whose followers are to be retrieved. + /// Optional. The maximum number of followers to retrieve. Default is 50. + /// Optional. A cursor that can be used to paginate through followers. + /// Optional. A CancellationToken that can be used to cancel the operation. + /// A Task that represents the asynchronous operation. The task result contains a Result object with the followers of the actor, or null if no followers were found. public async Task> GetFollowersAsync( ATIdentifier identifier, int limit = 50, @@ -83,33 +113,65 @@ internal BlueskyGraph(ATProtocol proto) url += $"&cursor={cursor}"; } - return await this.Client.Get(url, this.Options.JsonSerializerOptions, cancellationToken, this.Options.Logger); + return await this.Client.Get(url, this.Options.SourceGenerationContext.ActorFollowers, this.Options.JsonSerializerOptions, cancellationToken, this.Options.Logger); } + /// + /// Asynchronously mutes an actor. + /// + /// The DID of the actor to be muted. + /// Optional. A CancellationToken that can be used to cancel the operation. + /// A Task that represents the asynchronous operation. The task result contains a Result object indicating whether the operation was successful. public Task> MuteActorAsync(ATDid did, CancellationToken cancellationToken = default) { var muteRecord = new CreateMuteRecord(did); - return this.Client.Post(Constants.Urls.Bluesky.Graph.MuteActor, this.Options.JsonSerializerOptions, muteRecord, cancellationToken, this.Options.Logger); + return this.Client.Post(Constants.Urls.Bluesky.Graph.MuteActor, this.Options.SourceGenerationContext.CreateMuteRecord, this.Options.SourceGenerationContext.Success, this.Options.JsonSerializerOptions, muteRecord, cancellationToken, this.Options.Logger); } + /// + /// Asynchronously unmutes an actor. + /// + /// The DID of the actor to be unmuted. + /// Optional. A CancellationToken that can be used to cancel the operation. + /// A Task that represents the asynchronous operation. The task result contains a Result object indicating whether the operation was successful. public Task> UnmuteActorAsync(ATDid did, CancellationToken cancellationToken = default) { var muteRecord = new CreateMuteRecord(did); - return this.Client.Post(Constants.Urls.Bluesky.Graph.UnmuteActor, this.Options.JsonSerializerOptions, muteRecord, cancellationToken, this.Options.Logger); + return this.Client.Post(Constants.Urls.Bluesky.Graph.UnmuteActor, this.Options.SourceGenerationContext.CreateMuteRecord, this.Options.SourceGenerationContext.Success, this.Options.JsonSerializerOptions, muteRecord, cancellationToken, this.Options.Logger); } + /// + /// Asynchronously mutes an actor list. + /// + /// The URI of the list to be muted. + /// Optional. A CancellationToken that can be used to cancel the operation. + /// A Task that represents the asynchronous operation. The task result contains a Result object indicating whether the operation was successful. public Task> MuteActorListAsync(ATUri list, CancellationToken cancellationToken = default) { var muteRecord = new CreateMuteListRecord(list); - return this.Client.Post(Constants.Urls.Bluesky.Graph.MuteActorList, this.Options.JsonSerializerOptions, muteRecord, cancellationToken, this.Options.Logger); + return this.Client.Post(Constants.Urls.Bluesky.Graph.MuteActorList, this.Options.SourceGenerationContext.CreateMuteListRecord, this.Options.SourceGenerationContext.Success, this.Options.JsonSerializerOptions, muteRecord, cancellationToken, this.Options.Logger); } + /// + /// Asynchronously unmutes an actor list. + /// + /// The URI of the list to be unmuted. + /// Optional. A CancellationToken that can be used to cancel the operation. + /// A Task that represents the asynchronous operation. The task result contains a Result object indicating whether the operation was successful. public Task> UnmuteActorListAsync(ATUri list, CancellationToken cancellationToken = default) { var muteRecord = new CreateMuteListRecord(list); - return this.Client.Post(Constants.Urls.Bluesky.Graph.UnmuteActorList, this.Options.JsonSerializerOptions, muteRecord, cancellationToken, this.Options.Logger); + return this.Client.Post(Constants.Urls.Bluesky.Graph.UnmuteActorList, this.Options.SourceGenerationContext.CreateMuteListRecord, this.Options.SourceGenerationContext.Success, this.Options.JsonSerializerOptions, muteRecord, cancellationToken, this.Options.Logger); } + /// + /// Asynchronously retrieves the lists of an actor. + /// + /// The identifier of the actor whose lists are to be retrieved. + /// Optional. The maximum number of lists to retrieve. Default is 50. + /// Optional. A cursor that can be used to paginate through lists. + /// Optional. A CancellationToken that can be used to cancel the operation. + /// A Task that represents the asynchronous operation. The task result contains a Result object with the lists of the actor, or null if no lists were found. public async Task> GetListsAsync(ATIdentifier identifier, int limit = 50, string? cursor = default, CancellationToken cancellationToken = default) { var url = Constants.Urls.Bluesky.Graph.GetLists + $"?actor={identifier}&limit={limit}"; @@ -119,9 +181,16 @@ public Task> UnmuteActorListAsync(ATUri list, CancellationToken url += $"&cursor={cursor}"; } - return await this.Client.Get(url, this.Options.JsonSerializerOptions, cancellationToken, this.Options.Logger); + return await this.Client.Get(url, this.Options.SourceGenerationContext.ListViewRecord, this.Options.JsonSerializerOptions, cancellationToken, this.Options.Logger); } + /// + /// Asynchronously retrieves the list mutes of an actor. + /// + /// Optional. The maximum number of list mutes to retrieve. Default is 50. + /// Optional. A cursor that can be used to paginate through list mutes. + /// Optional. A CancellationToken that can be used to cancel the operation. + /// A Task that represents the asynchronous operation. The task result contains a Result object with the list mutes of the actor, or null if no list mutes were found. public async Task> GetListMutesAsync(int limit = 50, string? cursor = default, CancellationToken cancellationToken = default) { var url = Constants.Urls.Bluesky.Graph.GetListMutes + $"?limit={limit}"; @@ -131,9 +200,16 @@ public Task> UnmuteActorListAsync(ATUri list, CancellationToken url += $"&cursor={cursor}"; } - return await this.Client.Get(url, this.Options.JsonSerializerOptions, cancellationToken, this.Options.Logger); + return await this.Client.Get(url, this.Options.SourceGenerationContext.ListViewRecord, this.Options.JsonSerializerOptions, cancellationToken, this.Options.Logger); } + /// + /// Asynchronously retrieves the list blocks of an actor. + /// + /// Optional. The maximum number of list blocks to retrieve. Default is 50. + /// Optional. A cursor that can be used to paginate through list blocks. + /// Optional. A CancellationToken that can be used to cancel the operation. + /// A Task that represents the asynchronous operation. The task result contains a Result object with the list blocks of the actor, or null if no list blocks were found. public async Task> GetListBlocksAsync(int limit = 50, string? cursor = default, CancellationToken cancellationToken = default) { var url = Constants.Urls.Bluesky.Graph.GetListBlocks + $"?limit={limit}"; @@ -143,9 +219,17 @@ public Task> UnmuteActorListAsync(ATUri list, CancellationToken url += $"&cursor={cursor}"; } - return await this.Client.Get(url, this.Options.JsonSerializerOptions, cancellationToken, this.Options.Logger); + return await this.Client.Get(url, this.Options.SourceGenerationContext.ListViewRecord, this.Options.JsonSerializerOptions, cancellationToken, this.Options.Logger); } + /// + /// Asynchronously retrieves a list. + /// + /// The URI of the list to be retrieved. + /// Optional. The maximum number of items to retrieve from the list. Default is 50. + /// Optional. A cursor that can be used to paginate through list items. + /// Optional. A CancellationToken that can be used to cancel the operation. + /// A Task that represents the asynchronous operation. The task result contains a Result object with the list, or null if the list was not found. public async Task> GetListAsync(ATUri list, int limit = 50, string? cursor = default, CancellationToken cancellationToken = default) { var url = Constants.Urls.Bluesky.Graph.GetList + $"?list={list}&limit={limit}"; @@ -155,12 +239,18 @@ public Task> UnmuteActorListAsync(ATUri list, CancellationToken url += $"&cursor={cursor}"; } - return await this.Client.Get(url, this.Options.JsonSerializerOptions, cancellationToken, this.Options.Logger); + return await this.Client.Get(url, this.Options.SourceGenerationContext.ListItemViewRecord, this.Options.JsonSerializerOptions, cancellationToken, this.Options.Logger); } + /// + /// Asynchronously retrieves suggested follows for a specific actor. + /// + /// The identifier of the actor for whom to retrieve suggested follows. + /// Optional. A CancellationToken that can be used to cancel the operation. + /// A Task that represents the asynchronous operation. The task result contains a Result object with the suggested follows for the actor, or null if no suggestions were found. public async Task> GetSuggestedFollowsByActorAsync(ATIdentifier identifier, CancellationToken cancellationToken = default) { var url = Constants.Urls.Bluesky.Graph.GetSuggestedFollowsByActor + $"?actor={identifier}"; - return await this.Client.Get(url, this.Options.JsonSerializerOptions, cancellationToken, this.Options.Logger); + return await this.Client.Get(url, this.Options.SourceGenerationContext.SuggestionsRecord, this.Options.JsonSerializerOptions, cancellationToken, this.Options.Logger); } } diff --git a/src/FishyFlip/BlueskyNotification.cs b/src/FishyFlip/BlueskyNotification.cs index af8f72f..17cb709 100644 --- a/src/FishyFlip/BlueskyNotification.cs +++ b/src/FishyFlip/BlueskyNotification.cs @@ -26,6 +26,12 @@ internal BlueskyNotification(ATProtocol proto) private HttpClient Client => this.proto.Client; + /// + /// Asynchronously retrieves the count of unread notifications. + /// + /// Optional. The date and time at which the notifications were last seen. If provided, only notifications received after this date and time will be counted. + /// Optional. A CancellationToken that can be used to cancel the operation. + /// A Task that represents the asynchronous operation. The task result contains a Result object with the count of unread notifications, or null if the count could not be retrieved. public Task> GetUnreadCountAsync(DateTime? seenAt = null, CancellationToken cancellationToken = default) { var url = Constants.Urls.Bluesky.Notification.NotificationGetUnreadCount; @@ -34,17 +40,31 @@ internal BlueskyNotification(ATProtocol proto) url += $"?seenAt={seenAt.Value.ToUniversalTime().ToString("o", CultureInfo.InvariantCulture)}"; } - return this.Client.Get(url, this.Options.JsonSerializerOptions, cancellationToken, this.Options.Logger); + return this.Client.Get(url, this.Options.SourceGenerationContext.UnreadCount, this.Options.JsonSerializerOptions, cancellationToken, this.Options.Logger); } + /// + /// Asynchronously updates the date and time at which the notifications were last seen. + /// + /// The date and time at which the notifications were last seen. + /// Optional. A CancellationToken that can be used to cancel the operation. + /// A Task that represents the asynchronous operation. The task result contains a Result object indicating whether the operation was successful, or null if the operation failed. public Task> UpdateSeenAsync(DateTime seenAt, CancellationToken cancellationToken = default) { var createSeenAtRecord = new CreateSeenAtRecord(seenAt.ToUniversalTime().ToString("o", CultureInfo.InvariantCulture)); var url = Constants.Urls.Bluesky.Notification.NotificationUpdateSeen; - return this.Client.Post(url, this.Options.JsonSerializerOptions, createSeenAtRecord, cancellationToken, this.Options.Logger); + return this.Client.Post(url, this.Options.SourceGenerationContext.CreateSeenAtRecord, this.Options.SourceGenerationContext.Success!, this.Options.JsonSerializerOptions, createSeenAtRecord, cancellationToken, this.Options.Logger); } + /// + /// Asynchronously retrieves a list of notifications. + /// + /// Optional. The maximum number of notifications to retrieve. Default is 50. + /// Optional. A cursor that can be used to paginate through notifications. + /// Optional. The date and time at which the notifications were last seen. If provided, only notifications received after this date and time will be retrieved. + /// Optional. A CancellationToken that can be used to cancel the operation. + /// A Task that represents the asynchronous operation. The task result contains a Result object with the list of notifications, or null if no notifications were found. public async Task> ListNotificationsAsync( int limit = 50, string? cursor = default, @@ -62,6 +82,6 @@ internal BlueskyNotification(ATProtocol proto) url += $"&cursor={cursor}"; } - return await this.Client.Get(url, this.Options.JsonSerializerOptions, cancellationToken, this.Options.Logger); + return await this.Client.Get(url, this.Options.SourceGenerationContext.NotificationCollection, this.Options.JsonSerializerOptions, cancellationToken, this.Options.Logger); } } diff --git a/src/FishyFlip/BlueskyUnspecced.cs b/src/FishyFlip/BlueskyUnspecced.cs index c8d70fb..9e0fcea 100644 --- a/src/FishyFlip/BlueskyUnspecced.cs +++ b/src/FishyFlip/BlueskyUnspecced.cs @@ -24,6 +24,14 @@ internal BlueskyUnspecced(ATProtocol proto) private HttpClient Client => this.proto.Client; + /// + /// Asynchronously retrieves popular feed generators. + /// + /// Optional. The maximum number of feed generators to retrieve. Default is 50. + /// Optional. A cursor that can be used to paginate through feed generators. + /// Optional. A query that can be used to filter the feed generators. + /// Optional. A CancellationToken that can be used to cancel the operation. + /// A Task that represents the asynchronous operation. The task result contains a Result object with the popular feed generators, or null if no feed generators were found. public async Task> GetPopularFeedGeneratorsAsync(int limit = 50, string? cursor = default, string? query = default, CancellationToken cancellationToken = default) { string url = $"{Constants.Urls.Bluesky.Unspecced.GetPopularFeedGenerators}?limit={limit}"; @@ -37,7 +45,7 @@ public async Task> GetPopularFeedGeneratorsAsync(int limi url += $"&query={query}"; } - Multiple result = await this.Client.Get(url, this.Options.JsonSerializerOptions, cancellationToken, this.Options.Logger); + Multiple result = await this.Client.Get(url, this.Options.SourceGenerationContext.FeedResultList, this.Options.JsonSerializerOptions, cancellationToken, this.Options.Logger); return result .Match>( timeline => (timeline ?? new FeedResultList(Array.Empty(), null))!, diff --git a/src/FishyFlip/Constants.cs b/src/FishyFlip/Constants.cs index 6dc123e..b2378e8 100644 --- a/src/FishyFlip/Constants.cs +++ b/src/FishyFlip/Constants.cs @@ -3,6 +3,7 @@ // namespace FishyFlip; + #pragma warning disable SA1600 // Elements should be documented public static class Constants { diff --git a/src/FishyFlip/Events/SubscriptionConnectionStatusEventArgs.cs b/src/FishyFlip/Events/SubscriptionConnectionStatusEventArgs.cs index 271edda..cde34c2 100644 --- a/src/FishyFlip/Events/SubscriptionConnectionStatusEventArgs.cs +++ b/src/FishyFlip/Events/SubscriptionConnectionStatusEventArgs.cs @@ -4,12 +4,22 @@ namespace FishyFlip.Events; +/// +/// Represents the event arguments for the subscription connection status. +/// public class SubscriptionConnectionStatusEventArgs : EventArgs { + /// + /// Initializes a new instance of the class. + /// + /// The state of the WebSocket connection. public SubscriptionConnectionStatusEventArgs(WebSocketState connected) { this.State = connected; } + /// + /// Gets the state of the WebSocket connection. + /// public WebSocketState State { get; } -} +} \ No newline at end of file diff --git a/src/FishyFlip/FishyFlip.csproj b/src/FishyFlip/FishyFlip.csproj index 0306f9f..4cb0a36 100644 --- a/src/FishyFlip/FishyFlip.csproj +++ b/src/FishyFlip/FishyFlip.csproj @@ -1,12 +1,14 @@ - net7.0 + net7.0;net8.0 enable enable true - false + true nullable + true + $(NoWarn);SA0001 diff --git a/src/FishyFlip/FishyFlip.sln b/src/FishyFlip/FishyFlip.sln new file mode 100644 index 0000000..548caa8 --- /dev/null +++ b/src/FishyFlip/FishyFlip.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.5.002.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FishyFlip", "FishyFlip.csproj", "{446F5C64-A3BD-4E7F-8F6D-9920E4CAE728}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {446F5C64-A3BD-4E7F-8F6D-9920E4CAE728}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {446F5C64-A3BD-4E7F-8F6D-9920E4CAE728}.Debug|Any CPU.Build.0 = Debug|Any CPU + {446F5C64-A3BD-4E7F-8F6D-9920E4CAE728}.Release|Any CPU.ActiveCfg = Release|Any CPU + {446F5C64-A3BD-4E7F-8F6D-9920E4CAE728}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {2E957A1F-0206-43FC-801B-0CEEC0A1E239} + EndGlobalSection +EndGlobal diff --git a/src/FishyFlip/Models/ATDid.cs b/src/FishyFlip/Models/ATDid.cs index f67c412..8d292fb 100644 --- a/src/FishyFlip/Models/ATDid.cs +++ b/src/FishyFlip/Models/ATDid.cs @@ -14,7 +14,7 @@ public class ATDid : ATIdentifier /// /// Actor Handler. [JsonConstructor] - protected ATDid(string handler) + internal ATDid(string handler) { this.Handler = handler; } diff --git a/src/FishyFlip/Models/ATFeedTypeAPI.cs b/src/FishyFlip/Models/ATFeedTypeAPI.cs index cd90999..0fff9b3 100644 --- a/src/FishyFlip/Models/ATFeedTypeAPI.cs +++ b/src/FishyFlip/Models/ATFeedTypeAPI.cs @@ -4,8 +4,16 @@ namespace FishyFlip.Models; +/// +/// Represents an abstract base class for ATFeedTypeAPI. +/// public abstract class ATFeedTypeAPI { + /// + /// Initializes a new instance of the class. + /// + /// The URI of the feed type. + /// The CID of the feed type. [JsonConstructor] public ATFeedTypeAPI(ATUri? uri, Cid? cid) { @@ -13,7 +21,13 @@ public ATFeedTypeAPI(ATUri? uri, Cid? cid) this.Cid = cid; } + /// + /// Gets the URI. + /// public ATUri? Uri { get; } + /// + /// Gets the CID. + /// public Cid? Cid { get; } } diff --git a/src/FishyFlip/Models/ATHandle.cs b/src/FishyFlip/Models/ATHandle.cs index d718d7c..ca3122c 100644 --- a/src/FishyFlip/Models/ATHandle.cs +++ b/src/FishyFlip/Models/ATHandle.cs @@ -14,7 +14,7 @@ public class ATHandle : ATIdentifier /// /// Handle. [JsonConstructor] - protected ATHandle(string handle) + internal ATHandle(string handle) { this.Handle = handle; } diff --git a/src/FishyFlip/Models/ATIdentifier.cs b/src/FishyFlip/Models/ATIdentifier.cs index 8c19197..94b6d38 100644 --- a/src/FishyFlip/Models/ATIdentifier.cs +++ b/src/FishyFlip/Models/ATIdentifier.cs @@ -4,8 +4,16 @@ namespace FishyFlip.Models; +/// +/// Represents an abstract class for an identifier. +/// public abstract class ATIdentifier { + /// + /// Creates an instance of ATIdentifier based on the provided ID. + /// + /// The ID to create the ATIdentifier from. + /// An instance of ATIdentifier if the ID is valid, otherwise null. public static ATIdentifier? Create(string id) { if (ATDid.IsValid(id)) diff --git a/src/FishyFlip/Models/ATRecord.cs b/src/FishyFlip/Models/ATRecord.cs index e5000b1..c7dd853 100644 --- a/src/FishyFlip/Models/ATRecord.cs +++ b/src/FishyFlip/Models/ATRecord.cs @@ -9,12 +9,19 @@ namespace FishyFlip.Models; /// public abstract class ATRecord { + /// + /// Initializes a new instance of the class. + /// + /// The type. [JsonConstructor] public ATRecord(string? type) { this.Type = type; } + /// + /// Initializes a new instance of the class. + /// public ATRecord() { } @@ -25,6 +32,12 @@ public ATRecord() [JsonPropertyName("$type")] public string? Type { get; internal set; } + /// + /// Creates an AT Record from a CBORObject. + /// + /// The CBORObject to convert. + /// The logger to use. This is optional and defaults to null. + /// An AT Record if the conversion is successful; otherwise, null. public static ATRecord? FromCBORObject(CBORObject blockObj, ILogger? logger = default) { if (blockObj["$type"] is not null) diff --git a/src/FishyFlip/Models/ATUri.cs b/src/FishyFlip/Models/ATUri.cs index c9b9111..3877c5a 100644 --- a/src/FishyFlip/Models/ATUri.cs +++ b/src/FishyFlip/Models/ATUri.cs @@ -4,6 +4,9 @@ namespace FishyFlip.Models; +/// +/// Represents an ATUri object that parses and manipulates AT URIs. +/// public class ATUri { private static readonly Regex AtpUriRegex = new Regex( @@ -12,6 +15,11 @@ public class ATUri private string host; + /// + /// Initializes a new instance of the class. + /// + /// The URI. + /// Thrown if format is invalid. internal ATUri(string uri) { Match match = AtpUriRegex.Match(uri); @@ -28,31 +36,73 @@ internal ATUri(string uri) this.Handle = ATHandle.Create(this); } + /// + /// Gets the hash value. + /// public string Hash { get; private set; } + /// + /// Gets the pathname value. + /// public string Pathname { get; private set; } + /// + /// Gets the protocol value. + /// public string Protocol => "at:"; + /// + /// Gets the origin value. + /// public string Origin => $"at://{this.host}"; + /// + /// Gets the hostname value. + /// public string Hostname => this.host; - public static ATUri Create(string uri) - => new ATUri(uri); - + /// + /// Gets the associated AT DID. + /// public ATDid? Did { get; } + /// + /// Gets the associated AT handle. + /// public ATHandle? Handle { get; } + /// + /// Gets the identity value. + /// public string? Identity => this.Did?.ToString() ?? this.Handle?.ToString(); + /// + /// Gets the collection value. + /// public string Collection => this.Pathname.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries)[0]; + /// + /// Gets the rkey value. + /// public string Rkey => this.Pathname?.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries).ElementAtOrDefault(1) ?? string.Empty; + /// + /// Gets the href value. + /// public string Href => this.ToString(); + /// + /// Creates a new instance of the class. + /// + /// The URI. + /// A new instance of the class. + public static ATUri Create(string uri) + => new ATUri(uri); + + /// + /// Returns a string representation of the AT URI. + /// + /// A string representation of the AT URI. public override string ToString() { var buffer = new System.Text.StringBuilder(); diff --git a/src/FishyFlip/Models/AccountInviteCode.cs b/src/FishyFlip/Models/AccountInviteCode.cs index c8c48e3..efc156e 100644 --- a/src/FishyFlip/Models/AccountInviteCode.cs +++ b/src/FishyFlip/Models/AccountInviteCode.cs @@ -4,4 +4,13 @@ namespace FishyFlip.Models; -public record AccountInviteCode(string Code, int Available, bool Disabled, ATDid ForAccount, ATDid CreatedAt, Used[] Uses); +/// +/// Represents an account invite code. +/// +/// The invite code. +/// The number of times the invite code can be used. +/// Indicates whether the invite code is disabled. +/// The account for which the invite code is created. +/// The time when the invite code was created. +/// An array of uses of the invite code. +public record AccountInviteCode(string Code, int Available, bool Disabled, ATDid ForAccount, ATDid CreatedAt, Used[] Uses); \ No newline at end of file diff --git a/src/FishyFlip/Models/AccountInviteCodes.cs b/src/FishyFlip/Models/AccountInviteCodes.cs index effa947..d26efb9 100644 --- a/src/FishyFlip/Models/AccountInviteCodes.cs +++ b/src/FishyFlip/Models/AccountInviteCodes.cs @@ -4,4 +4,7 @@ namespace FishyFlip.Models; +/// +/// Represents a collection of account invite codes. +/// public record AccountInviteCodes(AccountInviteCode[] Codes); diff --git a/src/FishyFlip/Models/ActorBlocks.cs b/src/FishyFlip/Models/ActorBlocks.cs index 87cbb96..593231d 100644 --- a/src/FishyFlip/Models/ActorBlocks.cs +++ b/src/FishyFlip/Models/ActorBlocks.cs @@ -4,4 +4,7 @@ namespace FishyFlip.Models; +/// +/// Represents a collection of actor blocks. +/// public record ActorBlocks(FeedProfile[]? Blocks, string? Cursor); \ No newline at end of file diff --git a/src/FishyFlip/Models/ActorFollowers.cs b/src/FishyFlip/Models/ActorFollowers.cs index d0d8d38..5e318fd 100644 --- a/src/FishyFlip/Models/ActorFollowers.cs +++ b/src/FishyFlip/Models/ActorFollowers.cs @@ -4,4 +4,7 @@ namespace FishyFlip.Models; +/// +/// Represents the followers of an actor in a feed. +/// public record ActorFollowers(FeedProfile? Subject, FeedProfile[]? Followers, string? Cursor); \ No newline at end of file diff --git a/src/FishyFlip/Models/ActorFollows.cs b/src/FishyFlip/Models/ActorFollows.cs index 8a39c10..6e67ea7 100644 --- a/src/FishyFlip/Models/ActorFollows.cs +++ b/src/FishyFlip/Models/ActorFollows.cs @@ -4,4 +4,7 @@ namespace FishyFlip.Models; +/// +/// Represents the relationship between an actor and the profiles they follow. +/// public record ActorFollows(FeedProfile? Subject, FeedProfile[]? Follows, string? Cursor); \ No newline at end of file diff --git a/src/FishyFlip/Models/ActorMutes.cs b/src/FishyFlip/Models/ActorMutes.cs index d3dc721..840ed7d 100644 --- a/src/FishyFlip/Models/ActorMutes.cs +++ b/src/FishyFlip/Models/ActorMutes.cs @@ -4,4 +4,7 @@ namespace FishyFlip.Models; +/// +/// Represents a collection of actor mutes. +/// public record ActorMutes(FeedProfile[]? Mutes, string? Cursor); \ No newline at end of file diff --git a/src/FishyFlip/Models/ActorProfile.cs b/src/FishyFlip/Models/ActorProfile.cs index 48e749e..fa7a876 100644 --- a/src/FishyFlip/Models/ActorProfile.cs +++ b/src/FishyFlip/Models/ActorProfile.cs @@ -4,4 +4,15 @@ namespace FishyFlip.Models; +/// +/// Represents an actor profile. +/// +/// The actor's DID (Decentralized Identifier). +/// The actor's handle. +/// The actor's display name. +/// The actor's description. +/// The URL of the actor's avatar. +/// The date and time when the actor profile was indexed. +/// The viewer information. +/// An array of labels associated with the actor profile. public record ActorProfile(ATDid? Did, string? Handle, string? DisplayName, string? Description, string? Avatar, DateTime? IndexedAt, Viewer? Viewer, Label[]? Labels); diff --git a/src/FishyFlip/Models/ActorRecord.cs b/src/FishyFlip/Models/ActorRecord.cs index 74c66e6..eb0ea08 100644 --- a/src/FishyFlip/Models/ActorRecord.cs +++ b/src/FishyFlip/Models/ActorRecord.cs @@ -4,8 +4,17 @@ namespace FishyFlip.Models; +/// +/// Represents an actor record. +/// public class ActorRecord : ATFeedTypeAPI { + /// + /// Initializes a new instance of the class. + /// + /// The profile of the actor. + /// The ATUri of the actor. + /// The Cid of the action. [JsonConstructor] public ActorRecord(Profile? value, ATUri? uri, Cid? cid) : base(uri, cid) @@ -13,5 +22,8 @@ public ActorRecord(Profile? value, ATUri? uri, Cid? cid) this.Value = value; } + /// + /// Gets the profile value of the actor record. + /// public Profile? Value { get; } } \ No newline at end of file diff --git a/src/FishyFlip/Models/ActorResponse.cs b/src/FishyFlip/Models/ActorResponse.cs index c9ab332..10fc038 100644 --- a/src/FishyFlip/Models/ActorResponse.cs +++ b/src/FishyFlip/Models/ActorResponse.cs @@ -4,4 +4,7 @@ namespace FishyFlip.Models; +/// +/// Represents the response containing actor data. +/// public record ActorResponse(ActorProfile[]? Actors, string? Cursor); diff --git a/src/FishyFlip/Models/AdminRepoRef.cs b/src/FishyFlip/Models/AdminRepoRef.cs index d9c1f83..6a188e9 100644 --- a/src/FishyFlip/Models/AdminRepoRef.cs +++ b/src/FishyFlip/Models/AdminRepoRef.cs @@ -10,8 +10,15 @@ namespace FishyFlip.Models; +/// +/// Represents a reference to an admin repository. +/// public class AdminRepoRef { + /// + /// Initializes a new instance of the class. + /// + /// The ATDid. [JsonConstructor] public AdminRepoRef(ATDid did) { @@ -19,8 +26,14 @@ public AdminRepoRef(ATDid did) this.Type = "com.atproto.admin.defs#repoRef"; } + /// + /// Gets the admin repository's DID (Decentralized Identifier). + /// public ATDid Did { get; } + /// + /// Gets the type of the admin repository reference. + /// [JsonPropertyName("$type")] public string Type { get; } } diff --git a/src/FishyFlip/Models/AppPassword.cs b/src/FishyFlip/Models/AppPassword.cs index d1841a7..dd6d2f0 100644 --- a/src/FishyFlip/Models/AppPassword.cs +++ b/src/FishyFlip/Models/AppPassword.cs @@ -4,4 +4,7 @@ namespace FishyFlip.Models; +/// +/// Represents an application password. +/// public record AppPassword(string Name, DateTime CreatedAt); \ No newline at end of file diff --git a/src/FishyFlip/Models/AppPasswords.cs b/src/FishyFlip/Models/AppPasswords.cs index 6fcc2f0..261429a 100644 --- a/src/FishyFlip/Models/AppPasswords.cs +++ b/src/FishyFlip/Models/AppPasswords.cs @@ -4,4 +4,7 @@ namespace FishyFlip.Models; +/// +/// Represents a collection of application passwords. +/// public record AppPasswords(AppPassword[] Passwords); diff --git a/src/FishyFlip/Models/BSList.cs b/src/FishyFlip/Models/BSList.cs index e196166..58b640e 100644 --- a/src/FishyFlip/Models/BSList.cs +++ b/src/FishyFlip/Models/BSList.cs @@ -4,8 +4,15 @@ namespace FishyFlip.Models; +/// +/// Represents a BSList object. +/// public class BSList : ATRecord { + /// + /// Initializes a new instance of the class. + /// + /// The CBORObject containing the BSList data. public BSList(CBORObject obj) { this.CreatedAt = obj["createdAt"].ToDateTime(); @@ -15,11 +22,23 @@ public BSList(CBORObject obj) this.Name = obj["name"]?.AsString(); } + /// + /// Gets the name of the BSList. + /// public string? Name { get; } + /// + /// Gets the purpose of the BSList. + /// public string? Purpose { get; } + /// + /// Gets the description of the BSList. + /// public string? Description { get; } + /// + /// Gets the creation date and time of the BSList. + /// public DateTime? CreatedAt { get; } } \ No newline at end of file diff --git a/src/FishyFlip/Models/BSListItem.cs b/src/FishyFlip/Models/BSListItem.cs index 52dd0be..6bd776c 100644 --- a/src/FishyFlip/Models/BSListItem.cs +++ b/src/FishyFlip/Models/BSListItem.cs @@ -4,8 +4,15 @@ namespace FishyFlip.Models; +/// +/// Represents a list item. +/// public class BSListItem : ATRecord { + /// + /// Initializes a new instance of the class. + /// + /// The CBOR object containing the list item data. public BSListItem(CBORObject obj) { this.CreatedAt = obj["createdAt"].ToDateTime(); @@ -14,9 +21,18 @@ public BSListItem(CBORObject obj) this.Name = obj["name"]?.AsString(); } + /// + /// Gets the name of the list item. + /// public string? Name { get; } + /// + /// Gets the subject of the list item. + /// public string? Subject { get; } + /// + /// Gets the creation date and time of the list item. + /// public DateTime? CreatedAt { get; } } diff --git a/src/FishyFlip/Models/Blob.cs b/src/FishyFlip/Models/Blob.cs index 3569854..7852d68 100644 --- a/src/FishyFlip/Models/Blob.cs +++ b/src/FishyFlip/Models/Blob.cs @@ -4,12 +4,22 @@ namespace FishyFlip.Models; +/// +/// Represents a blob of binary data. +/// public class Blob { + /// + /// Initializes a new instance of the class. + /// + /// The binary data. public Blob(byte[]? data) { this.Data = data; } + /// + /// Gets the binary data. + /// public byte[]? Data { get; } } diff --git a/src/FishyFlip/Models/BlobRecord.cs b/src/FishyFlip/Models/BlobRecord.cs index a2fbd24..56b37b8 100644 --- a/src/FishyFlip/Models/BlobRecord.cs +++ b/src/FishyFlip/Models/BlobRecord.cs @@ -4,8 +4,17 @@ namespace FishyFlip.Models; +/// +/// Represents a blob record. +/// public class BlobRecord : ATRecord { + /// + /// Initializes a new instance of the class. + /// + /// The MIME type of the blob. + /// The size of the blob. + /// The type of the blob. [JsonConstructor] public BlobRecord(string? mimeType, int size, string? type) : base(type) @@ -14,13 +23,26 @@ public BlobRecord(string? mimeType, int size, string? type) this.Size = size; } + /// + /// Gets the MIME type of the blob. + /// public string? MimeType { get; } + /// + /// Gets the size of the blob in bytes. + /// public int Size { get; } + /// + /// Gets or sets the reference to the blob. + /// [JsonPropertyName("ref")] public Cid? Ref { get; set; } + /// + /// Converts the blob record to an image. + /// + /// An image object. public Image ToImage() => new(this.MimeType, this.Size, "blob") { diff --git a/src/FishyFlip/Models/BlockRecord.cs b/src/FishyFlip/Models/BlockRecord.cs index fabe05f..b1f8d4d 100644 --- a/src/FishyFlip/Models/BlockRecord.cs +++ b/src/FishyFlip/Models/BlockRecord.cs @@ -4,17 +4,31 @@ namespace FishyFlip.Models; +/// +/// Represents a block record. +/// public class BlockRecord : ATRecord { + /// + /// Initializes a new instance of the class. + /// + /// The Subject of the Block. + /// When the block was created. Otherwise set to DateTime.UtcNow. [JsonConstructor] public BlockRecord(ATDid? subject, DateTime? createdAt) { this.Subject = subject; - this.CreatedAt = createdAt ?? DateTime.Now; + this.CreatedAt = createdAt ?? DateTime.UtcNow; this.Type = Constants.GraphTypes.Block; } + /// + /// Gets the subject of the block record. + /// public ATDid? Subject { get; } + /// + /// Gets the creation date and time of the block record. + /// public DateTime CreatedAt { get; } } \ No newline at end of file diff --git a/src/FishyFlip/Models/CommitPath.cs b/src/FishyFlip/Models/CommitPath.cs index 8b9343b..04cd3d5 100644 --- a/src/FishyFlip/Models/CommitPath.cs +++ b/src/FishyFlip/Models/CommitPath.cs @@ -4,4 +4,7 @@ namespace FishyFlip.Models; +/// +/// Represents a commit path. +/// public record CommitPath(Cid[]? Commits); \ No newline at end of file diff --git a/src/FishyFlip/Models/CreatePostResponse.cs b/src/FishyFlip/Models/CreatePostResponse.cs index 34f56da..4e29fda 100644 --- a/src/FishyFlip/Models/CreatePostResponse.cs +++ b/src/FishyFlip/Models/CreatePostResponse.cs @@ -4,8 +4,16 @@ namespace FishyFlip.Models; +/// +/// Represents the response object for creating a post. +/// public class CreatePostResponse { + /// + /// Initializes a new instance of the class. + /// + /// The Post URI. + /// The reference to the post. [JsonConstructor] public CreatePostResponse(ATUri? uri, Cid? cid) { @@ -13,7 +21,13 @@ public CreatePostResponse(ATUri? uri, Cid? cid) this.Uri = uri; } + /// + /// Gets the Cid of the created post. + /// public Cid? Cid { get; } + /// + /// Gets the Uri of the created post. + /// public ATUri? Uri { get; } } \ No newline at end of file diff --git a/src/FishyFlip/Models/DescribeRepo.cs b/src/FishyFlip/Models/DescribeRepo.cs index f4cf5b5..f62b35d 100644 --- a/src/FishyFlip/Models/DescribeRepo.cs +++ b/src/FishyFlip/Models/DescribeRepo.cs @@ -10,8 +10,19 @@ namespace FishyFlip.Models; +/// +/// Represents a description of a repository. +/// public class DescribeRepo { + /// + /// Initializes a new instance of the class. + /// + /// The handle of the repository. + /// The ATDid of the repository. + /// The DidDoc of the repository. + /// The collections of the repository. + /// A value indicating whether the handle is correct. [JsonConstructor] public DescribeRepo(string handle, ATDid did, DidDoc didDoc, List collections, bool handleIsCorrect) { @@ -22,50 +33,28 @@ public DescribeRepo(string handle, ATDid did, DidDoc didDoc, List collec this.HandleIsCorrect = handleIsCorrect; } + /// + /// Gets the handle of the repository. + /// public string Handle { get; } + /// + /// Gets the ATDid of the repository. + /// public ATDid Did { get; } + /// + /// Gets the DidDoc of the repository. + /// public DidDoc DidDoc { get; } + /// + /// Gets the collections of the repository. + /// public List Collections { get; } + /// + /// Gets a value indicating whether the handle is correct. + /// public bool HandleIsCorrect { get; } } - -public class VerificationMethod -{ - [JsonConstructor] - public VerificationMethod(string id, string type, string controller, string publicKeyMultibase) - { - this.Id = id; - this.Type = type; - this.Controller = controller; - this.PublicKeyMultibase = publicKeyMultibase; - } - - public string Id { get; } - - public string Type { get; } - - public string Controller { get; } - - public string PublicKeyMultibase { get; } -} - -public class Service -{ - [JsonConstructor] - public Service(string id, string type, string serviceEndpoint) - { - this.Id = id; - this.Type = type; - this.ServiceEndpoint = serviceEndpoint; - } - - public string Id { get; } - - public string Type { get; } - - public string ServiceEndpoint { get; } -} diff --git a/src/FishyFlip/Models/DescribeServer.cs b/src/FishyFlip/Models/DescribeServer.cs index 314ac73..830f98e 100644 --- a/src/FishyFlip/Models/DescribeServer.cs +++ b/src/FishyFlip/Models/DescribeServer.cs @@ -4,4 +4,10 @@ namespace FishyFlip.Models; +/// +/// Represents a server description. +/// +/// The available user domains. +/// Indicates if an invite code is required to join the server. +/// The server link properties. public record DescribeServer(string[] AvailableUserDomains, bool InviteCodeRequired, ServerLinkProperties Links); diff --git a/src/FishyFlip/Models/Embed.cs b/src/FishyFlip/Models/Embed.cs index ba96842..2b04783 100644 --- a/src/FishyFlip/Models/Embed.cs +++ b/src/FishyFlip/Models/Embed.cs @@ -4,13 +4,23 @@ namespace FishyFlip.Models; +/// +/// Represents an abstract class for embedding content. +/// public abstract class Embed : ATRecord { + /// + /// Initializes a new instance of the class with the specified type. + /// + /// The type of the embed. public Embed(string? type) : base(type) { } + /// + /// Initializes a new instance of the class. + /// public Embed() { } diff --git a/src/FishyFlip/Models/Error.cs b/src/FishyFlip/Models/Error.cs index 3c31279..0379ce8 100644 --- a/src/FishyFlip/Models/Error.cs +++ b/src/FishyFlip/Models/Error.cs @@ -4,6 +4,9 @@ namespace FishyFlip.Models; +/// +/// Represents an error with a status code and optional error detail. +/// public record Error(int StatusCode, ErrorDetail? Detail = default) { } \ No newline at end of file diff --git a/src/FishyFlip/Models/ErrorBody.cs b/src/FishyFlip/Models/ErrorBody.cs index 3b20868..6aa5d58 100644 --- a/src/FishyFlip/Models/ErrorBody.cs +++ b/src/FishyFlip/Models/ErrorBody.cs @@ -4,9 +4,18 @@ namespace FishyFlip.Models; +/// +/// Represents an error response body. +/// public class ErrorBody { + /// + /// Gets or sets the error code. + /// public string? Error { get; set; } + /// + /// Gets or sets the error message. + /// public string? Message { get; set; } } \ No newline at end of file diff --git a/src/FishyFlip/Models/ErrorDetail.cs b/src/FishyFlip/Models/ErrorDetail.cs index bd11e21..f0fcdd4 100644 --- a/src/FishyFlip/Models/ErrorDetail.cs +++ b/src/FishyFlip/Models/ErrorDetail.cs @@ -4,6 +4,9 @@ namespace FishyFlip.Models; +/// +/// Represents an error detail. +/// public record ErrorDetail(string Error, string Message) { } \ No newline at end of file diff --git a/src/FishyFlip/Models/External.cs b/src/FishyFlip/Models/External.cs index b893cbe..e1d914e 100644 --- a/src/FishyFlip/Models/External.cs +++ b/src/FishyFlip/Models/External.cs @@ -4,8 +4,18 @@ namespace FishyFlip.Models; +/// +/// Represents an external resource with a thumbnail, title, description, and URI. +/// public class External { + /// + /// Initializes a new instance of the class. + /// + /// The thumbnail image. + /// The title of the resource. + /// The description of the resource. + /// The URI of the resource. [JsonConstructor] public External(Image? thumb, string? title, string? description, string? uri) { @@ -15,6 +25,10 @@ public External(Image? thumb, string? title, string? description, string? uri) this.Uri = uri; } + /// + /// Initializes a new instance of the class from a CBOR object. + /// + /// The CBOR object. public External(CBORObject obj) { if (obj["thumb"] is not null) @@ -27,11 +41,23 @@ public External(CBORObject obj) this.Description = obj["description"].AsString(); } + /// + /// Gets the thumbnail image. + /// public Image? Thumb { get; } + /// + /// Gets the title of the resource. + /// public string? Title { get; } + /// + /// Gets the description of the resource. + /// public string? Description { get; } + /// + /// Gets the URI of the resource. + /// public string? Uri { get; } } \ No newline at end of file diff --git a/src/FishyFlip/Models/ExternalEmbed.cs b/src/FishyFlip/Models/ExternalEmbed.cs index 2902ef0..cd0cb2c 100644 --- a/src/FishyFlip/Models/ExternalEmbed.cs +++ b/src/FishyFlip/Models/ExternalEmbed.cs @@ -4,8 +4,16 @@ namespace FishyFlip.Models; +/// +/// Represents an embed that contains external content. +/// public class ExternalEmbed : Embed { + /// + /// Initializes a new instance of the class with the specified external content and type. + /// + /// The external content. + /// The type of the embed. [JsonConstructor] public ExternalEmbed(External external, string? type) : base(type) @@ -14,14 +22,17 @@ public ExternalEmbed(External external, string? type) } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class from a CBORObject. /// - /// CBORObject. + /// The CBORObject representing the external content. public ExternalEmbed(CBORObject obj) { this.Type = Constants.EmbedTypes.Record; this.External = new External(obj); } + /// + /// Gets the external content of the embed. + /// public External? External { get; } } \ No newline at end of file diff --git a/src/FishyFlip/Models/ExternalView.cs b/src/FishyFlip/Models/ExternalView.cs new file mode 100644 index 0000000..e0538ac --- /dev/null +++ b/src/FishyFlip/Models/ExternalView.cs @@ -0,0 +1,10 @@ +// +// Copyright (c) Drastic Actions. All rights reserved. +// + +namespace FishyFlip.Models; + +/// +/// Represents an external view of a resource. +/// +public record ExternalView(string Thumb, string Title, string Uri, string Description); \ No newline at end of file diff --git a/src/FishyFlip/Models/ExternalViewEmbed.cs b/src/FishyFlip/Models/ExternalViewEmbed.cs index 6394199..d6cd7cd 100644 --- a/src/FishyFlip/Models/ExternalViewEmbed.cs +++ b/src/FishyFlip/Models/ExternalViewEmbed.cs @@ -4,15 +4,23 @@ namespace FishyFlip.Models; +/// +/// Represents an embed for an external view. +/// public class ExternalViewEmbed : Embed { + /// + /// Initializes a new instance of the class. + /// + /// The external view. public ExternalViewEmbed(ExternalView external) { this.External = external; this.Type = Constants.EmbedTypes.ExternalView; } + /// + /// Gets the external view. + /// public ExternalView External { get; } -} - -public record ExternalView(string Thumb, string Title, string Uri, string Description); \ No newline at end of file +} \ No newline at end of file diff --git a/src/FishyFlip/Models/Facet.cs b/src/FishyFlip/Models/Facet.cs index 738408a..6436ef1 100644 --- a/src/FishyFlip/Models/Facet.cs +++ b/src/FishyFlip/Models/Facet.cs @@ -4,8 +4,16 @@ namespace FishyFlip.Models; +/// +/// Represents a facet in the FishyFlip application. +/// public class Facet : ATRecord { + /// + /// Initializes a new instance of the class with the specified index and features. + /// + /// The index of the facet. + /// The features of the facet. [JsonConstructor] public Facet(FacetIndex? index, FacetFeature[] features) { @@ -13,28 +21,64 @@ public Facet(FacetIndex? index, FacetFeature[] features) this.Features = features; } + /// + /// Initializes a new instance of the class with the specified index and feature. + /// + /// The index of the facet. + /// The feature of the facet. public Facet(FacetIndex index, FacetFeature feature) { this.Index = index; this.Features = new FacetFeature[] { feature }; } + /// + /// Initializes a new instance of the class from the specified CBOR object. + /// + /// The CBOR object representing the facet. public Facet(CBORObject obj) { this.Index = obj["index"] is not null ? new FacetIndex(obj["index"]) : null; this.Features = obj["features"] is not null ? obj["features"].Values.Select(n => new FacetFeature(n)).ToArray() : null; } + /// + /// Gets the index of the facet. + /// public FacetIndex? Index { get; } + /// + /// Gets the features of the facet. + /// public FacetFeature[]? Features { get; } + /// + /// Creates a facet with a link feature. + /// + /// The start index of the link. + /// The end index of the link. + /// The URI of the link. + /// The created facet. public static Facet CreateFacetLink(int start, int end, string uri) => new(new FacetIndex(start, end), new FacetFeature[] { FacetFeature.CreateLink(uri) }); + /// + /// Creates a facet with a hashtag feature. + /// + /// The start index of the hashtag. + /// The end index of the hashtag. + /// The hashtag value. + /// The created facet. public static Facet CreateFacetHashtag(int start, int end, string hashtag) => new(new FacetIndex(start, end), new FacetFeature[] { FacetFeature.CreateHashtag(hashtag) }); + /// + /// Creates a facet with a mention feature. + /// + /// The start index of the mention. + /// The end index of the mention. + /// The mention value. + /// The created facet. public static Facet CreateFacetMention(int start, int end, ATDid mention) => new(new FacetIndex(start, end), new FacetFeature[] { FacetFeature.CreateMention(mention) }); } diff --git a/src/FishyFlip/Models/FacetFeature.cs b/src/FishyFlip/Models/FacetFeature.cs index 030982d..0f8b457 100644 --- a/src/FishyFlip/Models/FacetFeature.cs +++ b/src/FishyFlip/Models/FacetFeature.cs @@ -4,8 +4,18 @@ namespace FishyFlip.Models; +/// +/// Represents a facet feature. +/// public class FacetFeature { + /// + /// Initializes a new instance of the class. + /// + /// The type of the facet feature. + /// The URI of the facet feature. + /// The tag of the facet feature. + /// The DID (Decentralized Identifier) of the facet feature. [JsonConstructor] public FacetFeature(string? type, string? uri, string? tag, ATDid? did) { @@ -15,6 +25,10 @@ public FacetFeature(string? type, string? uri, string? tag, ATDid? did) this.Did = did; } + /// + /// Initializes a new instance of the class from a CBOR object. + /// + /// The CBOR object representing the facet feature. public FacetFeature(CBORObject obj) { this.Type = obj["$type"].AsString(); @@ -23,24 +37,48 @@ public FacetFeature(CBORObject obj) this.Did = obj["did"] is not null ? ATDid.Create(obj["did"].AsString()) : null; } + /// + /// Gets the type of the facet feature. + /// [JsonPropertyName("$type")] public string? Type { get; } + /// + /// Gets the URI of the facet feature. + /// public string? Uri { get; } + /// + /// Gets the tag of the facet feature. + /// public string? Tag { get; } /// - /// Gets a of the actor. + /// Gets the DID (Decentralized Identifier) of the facet feature. /// public ATDid? Did { get; } + /// + /// Creates a facet feature of type "Link" with the specified URI. + /// + /// The URI of the facet feature. + /// A new instance of the class. public static FacetFeature CreateLink(string uri) => new(Constants.FacetTypes.Link, uri, null, null); + /// + /// Creates a facet feature of type "Mention" with the specified DID. + /// + /// The DID (Decentralized Identifier) to mention. + /// A new instance of the class. public static FacetFeature CreateMention(ATDid mention) => new(Constants.FacetTypes.Mention, null, null, mention); + /// + /// Creates a facet feature of type "Tag" with the specified tag. + /// + /// The tag of the facet feature. + /// A new instance of the class. public static FacetFeature CreateHashtag(string tag) => new(Constants.FacetTypes.Tag, null, tag, null); } \ No newline at end of file diff --git a/src/FishyFlip/Models/FacetIndex.cs b/src/FishyFlip/Models/FacetIndex.cs index 0e7beb3..f10308f 100644 --- a/src/FishyFlip/Models/FacetIndex.cs +++ b/src/FishyFlip/Models/FacetIndex.cs @@ -4,8 +4,16 @@ namespace FishyFlip.Models; +/// +/// Represents an index of a facet in a data structure. +/// public class FacetIndex { + /// + /// Initializes a new instance of the class with the specified byte start and byte end values. + /// + /// The starting byte position of the facet. + /// The ending byte position of the facet. [JsonConstructor] public FacetIndex(int byteStart, int byteEnd) { @@ -13,13 +21,23 @@ public FacetIndex(int byteStart, int byteEnd) this.ByteEnd = byteEnd; } + /// + /// Initializes a new instance of the class from a CBOR object. + /// + /// The CBOR object containing the byte start and byte end values. public FacetIndex(CBORObject obj) { this.ByteStart = obj["byteStart"].AsInt32(); this.ByteEnd = obj["byteEnd"].AsInt32(); } + /// + /// Gets the ending byte position of the facet. + /// public int ByteEnd { get; } + /// + /// Gets the starting byte position of the facet. + /// public int ByteStart { get; } } diff --git a/src/FishyFlip/Models/FeedCollection.cs b/src/FishyFlip/Models/FeedCollection.cs new file mode 100644 index 0000000..d1d0b3e --- /dev/null +++ b/src/FishyFlip/Models/FeedCollection.cs @@ -0,0 +1,10 @@ +// +// Copyright (c) Drastic Actions. All rights reserved. +// + +namespace FishyFlip.Models; + +/// +/// Represents a collection of feed records. +/// +public record FeedCollection(FeedRecord[] Feeds); diff --git a/src/FishyFlip/Models/FeedCreator.cs b/src/FishyFlip/Models/FeedCreator.cs new file mode 100644 index 0000000..36c32a0 --- /dev/null +++ b/src/FishyFlip/Models/FeedCreator.cs @@ -0,0 +1,15 @@ +// +// Copyright (c) Drastic Actions. All rights reserved. +// + +namespace FishyFlip.Models; + +/// +/// Represents a feed creator. +/// +public record FeedCreator(string Did, + string Handle, + string DisplayName, + string Avatar, + Viewer Viewer, + IReadOnlyList