Skip to content

Commit

Permalink
Added quoting & reposting functionality (#7)
Browse files Browse the repository at this point in the history
* Added quotes functionality

https://developers.facebook.com/docs/threads/posts/quote-posts

* Added reposting functionality

https://developers.facebook.com/docs/threads/posts/reposts

* Updated docs

* Fixed compile error
  • Loading branch information
itsWindows11 authored Oct 13, 2024
1 parent bf8aa9d commit 6ee4217
Show file tree
Hide file tree
Showing 4 changed files with 74 additions and 9 deletions.
19 changes: 10 additions & 9 deletions docs/api-reference/ThreadSharp/Internal/ThreadsUserClient.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,13 @@ Client for all things user related for the current authenticated user, including

## Methods

| Method | Summary | Return Value |
|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `GetAsync(string[]?, CancellationToken cancellationToken = default)` | Gets the profile of the user associated with this client. | The result, containing either the [`ThreadsProfile`](../Models/Api/ThreadsProfile) or an error. |
| `GetThreadsAsync(PostPagingParameters?, int limit = 25, CancellationToken cancellationToken = default)` | Gets the threads of the user associated with this client. | The result, containing either a list of [`ThreadsPost`](../Models/Api/ThreadsPost)s or an error. |
| `GetRepliesAsync(PostPagingParameters?, int limit = 25, CancellationToken cancellationToken = default)` | Gets the replies of the user associated with this client. | The result, containing either a list of [`ThreadsPost`](../Models/Api/ThreadsPost)s or an error. |
| `GetPublishingLimitAsync(CancellationToken cancellationToken = default)` | Gets the publishing limit of the currently authenticated user. | The result, containing either the [`ThreadsPublishingLimitData`](../Models/Api/ThreadsPublishingLimitData) or an error. |
| `GetMediaContainerAsync(string, string[]?, CancellationToken cancellationToken = default)` | Gets a media container, with the provided fields. | The result, containing either a [`ThreadsIdContainer`](../Models/Api/ThreadsIdContainer) in which the additional fields are in its UnrecognizedData property, or an error. |
| `CreateMediaContainerAsync(BaseMediaContainerContent, string?, string?, ReplyControl = ReplyControl.Everyone, string[]? allowlistedCountryCodes = null, CancellationToken cancellationToken = default)` | Creates a media container. | The result, containing either a [`ThreadsIdContainer`](../Models/Api/ThreadsIdContainer) or an error. |
| `PublishMediaContainerAsync(string mediaContainerId, CancellationToken cancellationToken = default)` | Publishes a media container. | The result, containing either a [`ThreadsIdContainer`](../Models/Api/ThreadsIdContainer) or an error. |
| Method | Summary | Return Value |
|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `GetAsync(string[]?, CancellationToken cancellationToken = default)` | Gets the profile of the user associated with this client. | The result, containing either the [`ThreadsProfile`](../Models/Api/ThreadsProfile) or an error. |
| `GetThreadsAsync(PostPagingParameters?, int limit = 25, CancellationToken cancellationToken = default)` | Gets the threads of the user associated with this client. | The result, containing either a list of [`ThreadsPost`](../Models/Api/ThreadsPost)s or an error. |
| `GetRepliesAsync(PostPagingParameters?, int limit = 25, CancellationToken cancellationToken = default)` | Gets the replies of the user associated with this client. | The result, containing either a list of [`ThreadsPost`](../Models/Api/ThreadsPost)s or an error. |
| `GetPublishingLimitAsync(CancellationToken cancellationToken = default)` | Gets the publishing limit of the currently authenticated user. | The result, containing either the [`ThreadsPublishingLimitData`](../Models/Api/ThreadsPublishingLimitData) or an error. |
| `GetMediaContainerAsync(string, string[]?, CancellationToken cancellationToken = default)` | Gets a media container, with the provided fields. | The result, containing either a [`ThreadsIdContainer`](../Models/Api/ThreadsIdContainer) in which the additional fields are in its UnrecognizedData property, or an error. |
| `CreateMediaContainerAsync(BaseMediaContainerContent, string? text = null, string? replyToId = null, string? quotePostId = null, ReplyControl = ReplyControl.Everyone, string[]? allowlistedCountryCodes = null, CancellationToken cancellationToken = default)` | Creates a media container. | The result, containing either a [`ThreadsIdContainer`](../Models/Api/ThreadsIdContainer) or an error. |
| `PublishMediaContainerAsync(string mediaContainerId, CancellationToken cancellationToken = default)` | Publishes a media container. | The result, containing either a [`ThreadsIdContainer`](../Models/Api/ThreadsIdContainer) or an error. |
| `RepostAsync(string mediaContainerId, CancellationToken cancellationToken = default)` | Reposts a post on the user's profile ("Reposts" tab). | The result, containing either a <see cref="ThreadsIdContainer"/> or an error. |
6 changes: 6 additions & 0 deletions src/ThreadSharp/Helpers/ThreadsPostingHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ public static class ThreadsPostingHelpers
/// <param name="threadsUserClient">The client to use for publishing.</param>
/// <param name="text">The text to use in the post.</param>
/// <param name="replyToId">The post/media container ID to reply to.</param>
/// <param name="quotePostId">The post/media container ID to quote.</param>
/// <param name="replyControl">An option to restrict replies or open them to everyone.</param>
/// <param name="allowlistedCountryCodes">List of valid ISO 3166-1 alpha-2 country codes to restrict viewing the post to.</param>
/// <param name="cancellationToken">
Expand All @@ -29,6 +30,7 @@ public static async Task<ThreadsResult<ThreadsIdContainer>> CreateTextPostAsync(
this ThreadsUserClient threadsUserClient,
string text,
string? replyToId = null,
string? quotePostId = null,
ReplyControl replyControl = ReplyControl.Everyone,
string[]? allowlistedCountryCodes = null,
CancellationToken cancellationToken = default
Expand All @@ -41,6 +43,7 @@ public static async Task<ThreadsResult<ThreadsIdContainer>> CreateTextPostAsync(
new EmptyContainerContent(),
text,
replyToId,
quotePostId,
replyControl,
allowlistedCountryCodes,
cancellationToken
Expand All @@ -62,6 +65,7 @@ public static async Task<ThreadsResult<ThreadsIdContainer>> CreateTextPostAsync(
/// The cancellation token to use in case the caller chooses to cancel the operation.
/// </param>
/// <param name="replyToId">The post/media container ID to reply to.</param>
/// <param name="quotePostId">The post/media container ID to quote.</param>
/// <param name="replyControl">An option to restrict replies or open them to everyone.</param>
/// <param name="allowlistedCountryCodes">List of valid ISO 3166-1 alpha-2 country codes to restrict viewing the post to.</param>
/// <exception cref="InvalidOperationException">
Expand All @@ -75,6 +79,7 @@ public static async Task<ThreadsResult<ThreadsIdContainer>> CreateCarouselPostAs
IEnumerable<ThreadsMediaContainerStatus> mediaContainerStatuses,
string? text = null,
string? replyToId = null,
string? quotePostId = null,
ReplyControl replyControl = ReplyControl.Everyone,
string[]? allowlistedCountryCodes = null,
CancellationToken cancellationToken = default
Expand All @@ -90,6 +95,7 @@ public static async Task<ThreadsResult<ThreadsIdContainer>> CreateCarouselPostAs
},
text,
replyToId,
quotePostId,
replyControl,
allowlistedCountryCodes,
cancellationToken
Expand Down
8 changes: 8 additions & 0 deletions src/ThreadSharp/IThreadSharpRefitClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ Task<ApiResponse<Stream>> CreateMediaContainerAsync(
BaseMediaContainerContent postContent,
[AliasAs("reply_control")] ReplyControl replyControl = ReplyControl.Everyone,
[AliasAs("reply_to_id")] string? replyToId = null,
[AliasAs("quote_post_id")] string? quotePostId = null,
string? text = null,
[AliasAs("allowlisted_country_codes")] string? allowlistedCountryCodes = null,
CancellationToken cancellationToken = default
Expand All @@ -139,4 +140,11 @@ Task<ApiResponse<Stream>> PublishMediaContainerAsync(
[AliasAs("creation_id")] string threadsMediaIdToPublish,
CancellationToken cancellationToken = default
);

[Post("/v1.0/{threadsPostId}/repost")]
Task<ApiResponse<Stream>> RepostAsync(
[AliasAs("access_token")] string accessToken,
string threadsPostId,
CancellationToken cancellationToken = default
);
}
50 changes: 50 additions & 0 deletions src/ThreadSharp/Internal/ThreadsUserClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,7 @@ public Task<ThreadsResult<ThreadsIdContainer>> GetMediaContainerAsync(string med
/// </param>
/// <param name="text">The text to put in the post.</param>
/// <param name="replyToId">The post/media container ID to reply to.</param>
/// <param name="quotePostId">The post/media container ID to quote.</param>
/// <param name="replyControl">An option to restrict replies or open them to everyone.</param>
/// <param name="allowlistedCountryCodes">List of valid ISO 3166-1 alpha-2 country codes to restrict viewing the post to.</param>
/// <param name="cancellationToken">
Expand All @@ -321,6 +322,7 @@ public Task<ThreadsResult<ThreadsIdContainer>> CreateMediaContainerAsync(
BaseMediaContainerContent postContent,
string? text = null,
string? replyToId = null,
string? quotePostId = null,
ReplyControl replyControl = ReplyControl.Everyone,
string[]? allowlistedCountryCodes = null,
CancellationToken cancellationToken = default
Expand Down Expand Up @@ -348,6 +350,7 @@ public Task<ThreadsResult<ThreadsIdContainer>> CreateMediaContainerAsync(
replyControl,
text,
replyToId,
quotePostId,
allowlistedCountryCodes is not null ? string.Join(",", allowlistedCountryCodes) : null,
cancellationToken
);
Expand Down Expand Up @@ -430,4 +433,51 @@ public Task<ThreadsResult<ThreadsIdContainer>> PublishMediaContainerAsync(string
return new ThreadsResult<ThreadsIdContainer>(content, response.StatusCode);
}, _getMaxRetriesOnServerError());
}

/// <summary>
/// Reposts a post on the user's profile ("Reposts" tab).
/// </summary>
/// <param name="mediaContainerId">The ID of the post/media container to repost.</param>
/// <param name="cancellationToken">
/// The cancellation token to use in case the caller chooses to cancel the operation.
/// </param>
/// <returns>The result, containing either a <see cref="ThreadsIdContainer"/> or an error.</returns>
public Task<ThreadsResult<ThreadsIdContainer>> RepostAsync(string mediaContainerId, CancellationToken cancellationToken = default)
{
if (_threadsUserId is not null)
throw new InvalidOperationException("Cannot repost as another user.");

return RetryHelpers.RetryOnServerErrorAsync(async () =>
{
using var response = await _refitClient.RepostAsync(_getAccessToken(), mediaContainerId, cancellationToken);

if (response.Content == null)
return new ThreadsResult<ThreadsIdContainer>(error: new ThreadsBlankResponseException(), response.StatusCode);

if (!response.IsSuccessStatusCode)
{
if (response.StatusCode == HttpStatusCode.Unauthorized)
return new ThreadsResult<ThreadsIdContainer>(error: new ThreadsUnauthenticatedException(), response.StatusCode);

var errorContent = await JsonSerializer.DeserializeAsync(
response.Content,
ThreadsSourceGenerationContext.Default.DictionaryStringJsonElement,
cancellationToken
);

if (errorContent == null)
return new ThreadsResult<ThreadsIdContainer>(error: new ThreadsBlankResponseException(), response.StatusCode);

return new ThreadsResult<ThreadsIdContainer>(new ThreadsRequestException(errorContent), response.StatusCode);
}

var content = await JsonSerializer.DeserializeAsync(
response.Content,
ThreadsSourceGenerationContext.Default.ThreadsIdContainer,
cancellationToken: cancellationToken
);

return new ThreadsResult<ThreadsIdContainer>(content, response.StatusCode);
});
}
}

0 comments on commit 6ee4217

Please sign in to comment.