├── .github
├── FUNDING.yml
├── workflows
│ ├── nuget.yml
│ └── docs-deploy.yml
└── SECURITY.md
├── assets
├── logo.png
└── banner.png
├── docs
├── public
│ ├── favicon.ico
│ ├── assets
│ │ ├── icon.png
│ │ └── hero-icon.png
│ └── apple-touch-icon.png
├── docs
│ ├── index.md
│ ├── profile.md
│ ├── calling-unsupported-endpoints.md
│ ├── insights.md
│ ├── threads
│ │ ├── get-current-user-threads.md
│ │ ├── getting-threads.md
│ │ └── create-new-thread.md
│ └── getting-started.md
├── api-reference
│ ├── index.md
│ └── ThreadSharp
│ │ ├── index.md
│ │ ├── Enums
│ │ ├── index.md
│ │ ├── Breakdown.md
│ │ ├── ReplyContol.md
│ │ ├── MetricPeriod.md
│ │ ├── MediaType.md
│ │ ├── ThreadsPublishingStatusCode.md
│ │ ├── ThreadsPostMediaType.md
│ │ └── ThreadsPublishingErrorCode.md
│ │ ├── Models
│ │ ├── index.md
│ │ ├── Api
│ │ │ ├── index.md
│ │ │ ├── Content
│ │ │ │ ├── index.md
│ │ │ │ ├── EmptyContainerContent.md
│ │ │ │ ├── BaseMediaContainerContent.md
│ │ │ │ ├── AttachmentLinkContainerContent.md
│ │ │ │ ├── MediaContainerContent.md
│ │ │ │ └── CarouselContainerContent.md
│ │ │ ├── Insights
│ │ │ │ ├── index.md
│ │ │ │ ├── ThreadsUserInsightLikesData.md
│ │ │ │ ├── ThreadsUserInsightQuotesData.md
│ │ │ │ ├── ThreadsUserInsightRepliesData.md
│ │ │ │ ├── ThreadsUserInsightRepostsData.md
│ │ │ │ ├── ThreadsUserInsightTotalFollowersData.md
│ │ │ │ ├── ThreadsUserInsightViewsData.md
│ │ │ │ ├── ThreadsMediaInsightItem.md
│ │ │ │ └── ThreadsUserInsightFollowerDemographicsData.md
│ │ │ ├── ThreadsIdContainer.md
│ │ │ ├── ThreadsManageReplyResult.md
│ │ │ ├── ThreadsProfile.md
│ │ │ ├── ThreadsMediaContainerStatus.md
│ │ │ ├── ThreadsUserInsightDataBase.md
│ │ │ ├── ThreadsPublishingLimitData.md
│ │ │ └── ThreadsPost.md
│ │ ├── BaseJsonUnrecognizedDataModel.md
│ │ ├── PostPagingParameters.md
│ │ ├── UserMetricPagingParameters.md
│ │ ├── ThreadsResult.md
│ │ └── ThreadsDataContainer.md
│ │ ├── Exceptions
│ │ ├── index.md
│ │ ├── ThreadsBlankResponseException.md
│ │ ├── ThreadsUnauthenticatedException.md
│ │ ├── ThreadsBeforeAfterException.md
│ │ ├── ThreadsServerErrorException.md
│ │ ├── ThreadsRequestException.md
│ │ └── ThreadsException.md
│ │ ├── Internal
│ │ ├── index.md
│ │ ├── ThreadsInsightsClient.md
│ │ ├── ThreadsThreadManagementClient.md
│ │ └── ThreadsUserClient.md
│ │ └── ThreadsClient.md
├── package.json
├── .vitepress
│ ├── theme
│ │ ├── index.ts
│ │ └── style.css
│ └── config.ts
└── index.md
├── src
├── ThreadSharp
│ ├── Models
│ │ ├── Api
│ │ │ ├── Content
│ │ │ │ ├── EmptyContainerContent.cs
│ │ │ │ ├── BaseMediaContainerContent.cs
│ │ │ │ ├── AttachmentLinkContainerContent.cs
│ │ │ │ ├── CarouselContainerContent.cs
│ │ │ │ └── MediaContainerContent.cs
│ │ │ ├── ThreadsIdContainer.cs
│ │ │ ├── ThreadsManageReplyResult.cs
│ │ │ ├── Insights
│ │ │ │ ├── ThreadsUserInsightLikesData.cs
│ │ │ │ ├── ThreadsUserInsightQuotesData.cs
│ │ │ │ ├── ThreadsUserInsightRepliesData.cs
│ │ │ │ ├── ThreadsUserInsightRepostsData.cs
│ │ │ │ ├── ThreadsUserInsightTotalFollowersData.cs
│ │ │ │ ├── ThreadsUserInsightViewsData.cs
│ │ │ │ ├── ThreadsMediaInsightItem.cs
│ │ │ │ └── ThreadsUserInsightFollowerDemographicsData.cs
│ │ │ ├── ThreadsMediaContainerStatus.cs
│ │ │ ├── ThreadsProfile.cs
│ │ │ ├── ThreadsPublishingLimitData.cs
│ │ │ ├── ThreadsUserInsightDataBase.cs
│ │ │ └── ThreadsPost.cs
│ │ ├── BaseJsonUnrecognizedDataModel.cs
│ │ ├── UserMetricPagingParameters.cs
│ │ ├── PostPagingParameters.cs
│ │ ├── ThreadsResult.cs
│ │ └── ThreadsDataContainer.cs
│ ├── Exceptions
│ │ ├── ThreadsBlankResponseException.cs
│ │ ├── ThreadsUnauthenticatedException.cs
│ │ ├── ThreadsServerErrorException.cs
│ │ ├── ThreadsBeforeAfterException.cs
│ │ ├── ThreadsRequestException.cs
│ │ └── ThreadsException.cs
│ ├── Enums
│ │ ├── MetricPeriod.cs
│ │ ├── ReplyControl.cs
│ │ ├── Breakdown.cs
│ │ ├── MediaType.cs
│ │ ├── ThreadsPublishingStatusCode.cs
│ │ ├── ThreadsPostMediaType.cs
│ │ └── ThreadsPublishingErrorCode.cs
│ ├── Converters
│ │ ├── DateTimeConverter.cs
│ │ ├── StringToMetricPeriodConverter.cs
│ │ ├── StringToThreadsPublishingStatusCodeConverter.cs
│ │ ├── StringToThreadsPostMediaTypeConverter.cs
│ │ └── StringToThreadsPublishingErrorCodeConverter.cs
│ ├── Helpers
│ │ ├── RetryHelpers.cs
│ │ └── ThreadsPostingHelpers.cs
│ ├── Internal
│ │ └── ThreadsSourceGenerationContext.cs
│ ├── ThreadSharp.csproj
│ ├── ThreadsClient.cs
│ └── IThreadSharpRefitClient.cs
├── Samples
│ ├── GetInsightsSample
│ │ ├── CustomJsonSerializerContext.cs
│ │ ├── GetInsightsSample.csproj
│ │ └── Program.cs
│ ├── ReplyToPostSample
│ │ ├── CustomJsonSerializerContext.cs
│ │ ├── ReplyToSelfPostSample.csproj
│ │ └── Program.cs
│ └── GetCurrentUserProfileAndPrintDetails
│ │ ├── CustomJsonSerializerContext.cs
│ │ ├── GetCurrentUserProfileAndPrintDetailsSample.csproj
│ │ └── Program.cs
└── README.md
├── LICENSE
├── README.md
├── .gitattributes
└── ThreadSharp.sln
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | github: itsWindows11
--------------------------------------------------------------------------------
/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/itsWindows11/ThreadSharp/HEAD/assets/logo.png
--------------------------------------------------------------------------------
/assets/banner.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/itsWindows11/ThreadSharp/HEAD/assets/banner.png
--------------------------------------------------------------------------------
/docs/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/itsWindows11/ThreadSharp/HEAD/docs/public/favicon.ico
--------------------------------------------------------------------------------
/docs/public/assets/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/itsWindows11/ThreadSharp/HEAD/docs/public/assets/icon.png
--------------------------------------------------------------------------------
/docs/public/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/itsWindows11/ThreadSharp/HEAD/docs/public/apple-touch-icon.png
--------------------------------------------------------------------------------
/docs/public/assets/hero-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/itsWindows11/ThreadSharp/HEAD/docs/public/assets/hero-icon.png
--------------------------------------------------------------------------------
/docs/docs/index.md:
--------------------------------------------------------------------------------
1 | # Documentation
2 |
3 | This page is an index of the complete ThreadSharp documentation.
4 |
5 | Select a subject or page from the menu to learn more.
--------------------------------------------------------------------------------
/docs/api-reference/index.md:
--------------------------------------------------------------------------------
1 | # API Reference
2 |
3 | This is the ThreadSharp API reference page.
4 |
5 | Select an item in the API Reference menu to learn more about a specific API/model.
--------------------------------------------------------------------------------
/docs/api-reference/ThreadSharp/index.md:
--------------------------------------------------------------------------------
1 | # ThreadSharp
2 |
3 | The root namespace for ThreadSharp.
4 |
5 | To learn more, see [the documentation](/docs/) or browse the children classes & enums in the menu.
--------------------------------------------------------------------------------
/docs/api-reference/ThreadSharp/Enums/index.md:
--------------------------------------------------------------------------------
1 | # [ThreadSharp](../).Enums
2 |
3 | Namespace for all the enums in ThreadSharp.
4 |
5 | For a full list of enums, check the menu by expanding the `ThreadSharp` section, then the `Enums` section.
--------------------------------------------------------------------------------
/src/ThreadSharp/Models/Api/Content/EmptyContainerContent.cs:
--------------------------------------------------------------------------------
1 | namespace ThreadSharp.Models.Api.Content;
2 |
3 | ///
4 | /// Media container content that's empty. Usually used for text only posts.
5 | ///
6 | public class EmptyContainerContent : BaseMediaContainerContent { }
--------------------------------------------------------------------------------
/docs/api-reference/ThreadSharp/Models/index.md:
--------------------------------------------------------------------------------
1 | # [ThreadSharp](../).Models
2 |
3 | Namespace containing all the models & responses returned from the Threads API, in a way interpreted by ThreadSharp.
4 |
5 | To learn more, see [the documentation](/docs/) or browse the children classes & enums in the menu.
--------------------------------------------------------------------------------
/docs/api-reference/ThreadSharp/Exceptions/index.md:
--------------------------------------------------------------------------------
1 | # [ThreadSharp](../).Exceptions
2 |
3 | Namespace for exceptions in ThreadSharp, when an error occurs from any end other than the library consumer.
4 |
5 | For a full list of exceptions, check the menu by expanding the `ThreadSharp` section, then the `Exceptions` section.
--------------------------------------------------------------------------------
/docs/api-reference/ThreadSharp/Models/Api/index.md:
--------------------------------------------------------------------------------
1 | # [ThreadSharp](../../).[Models](../).Api
2 |
3 | Namespace containing all the models & responses returned from the Threads API, in a way interpreted by ThreadSharp.
4 |
5 | To learn more, see [the documentation](/docs/) or browse the children classes & enums in the menu.
--------------------------------------------------------------------------------
/src/Samples/GetInsightsSample/CustomJsonSerializerContext.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json;
2 | using System.Text.Json.Serialization;
3 |
4 | namespace ReplyToSelfPostSample;
5 |
6 | [JsonSerializable(typeof(Dictionary))]
7 | internal sealed partial class CustomJsonSerializerContext : JsonSerializerContext
8 | {
9 | }
--------------------------------------------------------------------------------
/src/Samples/ReplyToPostSample/CustomJsonSerializerContext.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json;
2 | using System.Text.Json.Serialization;
3 |
4 | namespace ReplyToSelfPostSample;
5 |
6 | [JsonSerializable(typeof(Dictionary))]
7 | internal sealed partial class CustomJsonSerializerContext : JsonSerializerContext
8 | {
9 | }
--------------------------------------------------------------------------------
/src/Samples/GetCurrentUserProfileAndPrintDetails/CustomJsonSerializerContext.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json;
2 | using System.Text.Json.Serialization;
3 |
4 | namespace ReplyToSelfPostSample;
5 |
6 | [JsonSerializable(typeof(Dictionary))]
7 | internal sealed partial class CustomJsonSerializerContext : JsonSerializerContext
8 | {
9 | }
--------------------------------------------------------------------------------
/docs/api-reference/ThreadSharp/Models/Api/Content/index.md:
--------------------------------------------------------------------------------
1 | # [ThreadSharp](../../../).[Models](../../).[Api](../).Content
2 |
3 | Namespace for classes that represent the post content.
4 |
5 | For a full list of classes, check the menu by expanding the `ThreadSharp` section, then the `Models` section, then the `Api` section, then the `Content` section.
--------------------------------------------------------------------------------
/docs/api-reference/ThreadSharp/Models/Api/Insights/index.md:
--------------------------------------------------------------------------------
1 | # [ThreadSharp](../../../).[Models](../../).[Api](../).Insights
2 |
3 | Namespace for classes that represent the user insight data.
4 |
5 | For a full list of classes, check the menu by expanding the `ThreadSharp` section, then the `Models` section, then the `Api` section, then the `Insights` section.
--------------------------------------------------------------------------------
/docs/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "module",
3 | "scripts": {
4 | "dev": "vitepress dev",
5 | "build": "vitepress build",
6 | "preview": "vitepress preview"
7 | },
8 | "devDependencies": {
9 | "@types/node": "^22.7.3",
10 | "vue": "^3.5.9"
11 | },
12 | "dependencies": {
13 | "vitepress": "^1.3.4"
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/docs/api-reference/ThreadSharp/Internal/index.md:
--------------------------------------------------------------------------------
1 | # [ThreadSharp](../).Internal
2 |
3 | Classes that are supposed to be internal or initialized only internally, but exposed to the public through public classes/interfaces' properties are listed here.
4 |
5 | For a full list of classes, check the menu by expanding the `ThreadSharp` section, then the `Internal` section.
--------------------------------------------------------------------------------
/src/ThreadSharp/Exceptions/ThreadsBlankResponseException.cs:
--------------------------------------------------------------------------------
1 | namespace ThreadSharp.Exceptions;
2 |
3 | ///
4 | /// Represents the exception thrown when the response received from the Threads API is blank.
5 | ///
6 | public sealed class ThreadsBlankResponseException : ThreadsException
7 | {
8 | internal ThreadsBlankResponseException() : base("Response received from the Threads API is blank.")
9 | {
10 | }
11 | }
--------------------------------------------------------------------------------
/docs/.vitepress/theme/index.ts:
--------------------------------------------------------------------------------
1 | // https://vitepress.dev/guide/custom-theme
2 | import { h } from 'vue'
3 | import Theme from 'vitepress/theme'
4 | import './style.css'
5 |
6 | export default {
7 | ...Theme,
8 | Layout: () => {
9 | return h(Theme.Layout, null, {
10 | // https://vitepress.dev/guide/extending-default-theme#layout-slots
11 | })
12 | },
13 | enhanceApp({ app, router, siteData }) {
14 | // ...
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/ThreadSharp/Exceptions/ThreadsUnauthenticatedException.cs:
--------------------------------------------------------------------------------
1 | namespace ThreadSharp.Exceptions;
2 |
3 | ///
4 | /// Represents the exception thrown when an invalid access token is being used.
5 | ///
6 | public sealed class ThreadsUnauthenticatedException : ThreadsException
7 | {
8 | internal ThreadsUnauthenticatedException() : base("The access token has either expired, or an invalid access token was passed.")
9 | {
10 | }
11 | }
--------------------------------------------------------------------------------
/src/ThreadSharp/Models/Api/Content/BaseMediaContainerContent.cs:
--------------------------------------------------------------------------------
1 | using Refit;
2 |
3 | namespace ThreadSharp.Models.Api.Content;
4 |
5 | ///
6 | /// Base media container content.
7 | ///
8 | public class BaseMediaContainerContent
9 | {
10 | ///
11 | /// Whether or not this media container item is a carousel item.
12 | ///
13 | [AliasAs("is_carousel_item")]
14 | public bool IsCarouselItem { get; set; }
15 | }
--------------------------------------------------------------------------------
/docs/api-reference/ThreadSharp/Models/Api/Content/EmptyContainerContent.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: EmptyContainerContent
3 | ---
4 |
5 | # [ThreadSharp](../../../).[Models](../../).[Api](../).[Content](./).EmptyContainerContent
6 |
7 | ## Definition
8 |
9 | ```c#
10 | public class EmptyContainerContent : BaseMediaContainerContent
11 | ```
12 |
13 | Media container content that's empty. Usually used for text only posts.
14 |
15 | Derived from: [`EmptyContainerContent`](./BaseMediaContainerContent).
--------------------------------------------------------------------------------
/src/ThreadSharp/Models/Api/ThreadsIdContainer.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json.Serialization;
2 |
3 | namespace ThreadSharp.Models.Api;
4 |
5 | ///
6 | /// Represents a container that contains a Threads ID, could be a media container.
7 | ///
8 | public class ThreadsIdContainer : BaseJsonUnrecognizedDataModel
9 | {
10 | ///
11 | /// The ID of the item.
12 | ///
13 | [JsonPropertyName("id")]
14 | public required string Id { get; set; }
15 | }
--------------------------------------------------------------------------------
/src/ThreadSharp/Exceptions/ThreadsServerErrorException.cs:
--------------------------------------------------------------------------------
1 | namespace ThreadSharp.Exceptions;
2 |
3 | ///
4 | /// Represents the exception thrown when there's an unknown error on the Threads API end.
5 | ///
6 | public sealed class ThreadsServerErrorException : ThreadsException
7 | {
8 | internal ThreadsServerErrorException() : base("An unknown error occurred on the Threads API end. The service may either be down currently, or this operation isn't supported by the server.")
9 | {
10 | }
11 | }
--------------------------------------------------------------------------------
/src/ThreadSharp/Exceptions/ThreadsBeforeAfterException.cs:
--------------------------------------------------------------------------------
1 | namespace ThreadSharp.Exceptions;
2 |
3 | ///
4 | /// Represents the exception used when both the Before
5 | /// and After properties are set when fetching Threads
6 | /// posts.
7 | ///
8 | internal class ThreadsBeforeAfterException : ThreadsException
9 | {
10 | internal ThreadsBeforeAfterException()
11 | : base("Both Before and After parameters cannot be set at the same time when fetching Threads posts.")
12 | {
13 | }
14 | }
--------------------------------------------------------------------------------
/src/ThreadSharp/Models/Api/Content/AttachmentLinkContainerContent.cs:
--------------------------------------------------------------------------------
1 | using Refit;
2 |
3 | namespace ThreadSharp.Models.Api.Content;
4 |
5 | ///
6 | /// Media container content that includes a link attachment.
7 | ///
8 | public class AttachmentLinkContainerContent : BaseMediaContainerContent
9 | {
10 | ///
11 | /// The link to add to the post as an attachment.
12 | ///
13 | [AliasAs("link_attachment")]
14 | public required string LinkAttachment { get; set; }
15 | }
--------------------------------------------------------------------------------
/docs/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | # https://vitepress.dev/reference/default-theme-home-page
3 | layout: home
4 |
5 | hero:
6 | name: "ThreadSharp"
7 | tagline: "A C# wrapper for the Threads API."
8 | actions:
9 | - theme: brand
10 | text: View Documentation
11 | link: /docs/
12 | - theme: alt
13 | text: Samples
14 | link: https://github.com/itsWindows11/ThreadSharp/tree/main/src/Samples
15 | image:
16 | src: assets/hero-icon.png
17 | alt: "ThreadSharp logo, consisting of a hash symbol."
18 | ---
19 |
--------------------------------------------------------------------------------
/src/ThreadSharp/Models/Api/Content/CarouselContainerContent.cs:
--------------------------------------------------------------------------------
1 | using Refit;
2 |
3 | namespace ThreadSharp.Models.Api.Content;
4 |
5 | ///
6 | /// Media container content for carousel posts.
7 | ///
8 | public class CarouselContainerContent : BaseMediaContainerContent
9 | {
10 | ///
11 | /// List of statuses of the media containers to include in the carousel.
12 | ///
13 | [AliasAs("children")]
14 | public required IList Children { get; set; }
15 | }
--------------------------------------------------------------------------------
/src/Samples/GetInsightsSample/GetInsightsSample.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | net8.0
6 | enable
7 | enable
8 | true
9 | true
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/src/Samples/ReplyToPostSample/ReplyToSelfPostSample.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | net8.0
6 | enable
7 | enable
8 | true
9 | true
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/src/Samples/GetCurrentUserProfileAndPrintDetails/GetCurrentUserProfileAndPrintDetailsSample.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | net8.0
6 | enable
7 | enable
8 | true
9 | true
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/src/ThreadSharp/Models/Api/ThreadsManageReplyResult.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json.Serialization;
2 |
3 | namespace ThreadSharp.Models.Api;
4 |
5 | ///
6 | /// Represents the result when managing a reply.
7 | ///
8 | public sealed class ThreadsManageReplyResult : BaseJsonUnrecognizedDataModel
9 | {
10 | ///
11 | /// Whether the operation is successful.
12 | ///
13 | [JsonPropertyName("success")]
14 | public required bool Success { get; set; }
15 |
16 | ///
17 | public override string ToString()
18 | => $"Success: {Success}";
19 | }
--------------------------------------------------------------------------------
/docs/api-reference/ThreadSharp/Enums/Breakdown.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Breakdown
3 | ---
4 |
5 | # [ThreadSharp](../).[Enums](./).Breakdown
6 |
7 | ## Definition
8 |
9 | ```c#
10 | [Flags]
11 | public enum Breakdown
12 | ```
13 |
14 | Enum representing breakdown values.
15 |
16 | ## Values
17 |
18 | | Value | Summary | Numeric Value |
19 | |---------|-----------------------|---------------|
20 | | Country | Breakdown by country. | `1 << 0` |
21 | | City | Breakdown by city. | `1 << 1` |
22 | | Age | Breakdown by age. | `1 << 2` |
23 | | Gender | Breakdown by gender. | `1 << 3` |
--------------------------------------------------------------------------------
/docs/api-reference/ThreadSharp/Exceptions/ThreadsBlankResponseException.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: ThreadsBlankResponseException
3 | ---
4 |
5 | # [ThreadSharp](../).[Exceptions](./).ThreadsBlankResponseException
6 |
7 | ## Definition
8 |
9 | ```c#
10 | internal class ThreadsBlankResponseException : ThreadsException
11 | ```
12 |
13 | Represents the exception thrown when the response received from the Threads API is blank.
14 |
15 | Derived from: [`ThreadsException`](./ThreadsException).
16 |
17 | ## Constructors
18 |
19 | ```c#
20 | internal ThreadsBlankResponseException()
21 | : base("Response received from the Threads API is blank.")
22 | ```
--------------------------------------------------------------------------------
/src/ThreadSharp/Exceptions/ThreadsRequestException.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json;
2 |
3 | namespace ThreadSharp.Exceptions;
4 |
5 | ///
6 | /// Represents the exception thrown when a request error occurrs.
7 | ///
8 | public sealed class ThreadsRequestException : ThreadsException
9 | {
10 | internal ThreadsRequestException(string message, IEnumerable> errorData) : base(message, errorData)
11 | {
12 | }
13 |
14 | internal ThreadsRequestException(IEnumerable> errorData) : base("A Threads API error has occurred.", errorData)
15 | {
16 | }
17 | }
--------------------------------------------------------------------------------
/docs/api-reference/ThreadSharp/Exceptions/ThreadsUnauthenticatedException.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: ThreadsUnauthenticatedException
3 | ---
4 |
5 | # [ThreadSharp](../).[Exceptions](./).ThreadsUnauthenticatedException
6 |
7 | ## Definition
8 |
9 | ```c#
10 | public sealed class ThreadsUnauthenticatedException : ThreadsException
11 | ```
12 |
13 | Represents the exception thrown when an invalid access token is being used.
14 |
15 | Derived from: [`ThreadsException`](./ThreadsException).
16 |
17 | ## Constructors
18 |
19 | ```c#
20 | internal ThreadsUnauthenticatedException() : base("The access token has either expired, or an invalid access token was passed.")
21 | ```
--------------------------------------------------------------------------------
/src/ThreadSharp/Enums/MetricPeriod.cs:
--------------------------------------------------------------------------------
1 | using System.Runtime.Serialization;
2 |
3 | namespace ThreadSharp.Enums;
4 |
5 | ///
6 | /// Enum representing possible user metric period options.
7 | ///
8 | public enum MetricPeriod
9 | {
10 | ///
11 | /// Metrics in a day.
12 | ///
13 | [EnumMember(Value = "day")]
14 | Day,
15 | ///
16 | /// Metrics in the lifetime of an account.
17 | ///
18 | [EnumMember(Value = "lifetime")]
19 | Lifetime,
20 | ///
21 | /// For metric periods not yet supported by ThreadSharp.
22 | ///
23 | Unknown
24 | }
--------------------------------------------------------------------------------
/docs/api-reference/ThreadSharp/Enums/ReplyContol.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: ReplyControl
3 | ---
4 |
5 | # [ThreadSharp](../).[Enums](./).ReplyControl
6 |
7 | ## Definition
8 |
9 | ```c#
10 | public enum ReplyControl
11 | ```
12 |
13 | Enum representing possible reply control values.
14 |
15 | ## Values
16 |
17 | | Value | Summary | Numeric Value |
18 | |-------------------|---------------------------------|---------------|
19 | | Everyone | Everyone can reply. | -- |
20 | | AccountsYouFollow | Accounts you follow can reply. | -- |
21 | | MentionedOnly | Accounts you mention can reply. | -- |
--------------------------------------------------------------------------------
/docs/api-reference/ThreadSharp/Exceptions/ThreadsBeforeAfterException.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: ThreadsBeforeAfterException
3 | ---
4 |
5 | # [ThreadSharp](../).[Exceptions](./).ThreadsBeforeAfterException
6 |
7 | ## Definition
8 |
9 | ```c#
10 | internal class ThreadsBeforeAfterException : ThreadsException
11 | ```
12 |
13 | Represents the exception used when both the Before and After properties are set when fetching Threads posts.
14 |
15 | Derived from: [`ThreadsException`](./ThreadsException).
16 |
17 | ## Constructors
18 |
19 | ```c#
20 | internal ThreadsBeforeAfterException()
21 | : base("Both Before and After parameters cannot be set at the same time when fetching Threads posts.")
22 | ```
--------------------------------------------------------------------------------
/docs/api-reference/ThreadSharp/Exceptions/ThreadsServerErrorException.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: ThreadsServerErrorException
3 | ---
4 |
5 | # [ThreadSharp](../).[Exceptions](./).ThreadsServerErrorException
6 |
7 | ## Definition
8 |
9 | ```c#
10 | public sealed class ThreadsServerErrorException : ThreadsException
11 | ```
12 |
13 | Represents the exception thrown when there's an unknown error on the Threads API end.
14 |
15 | Derived from: [`ThreadsException`](./ThreadsException).
16 |
17 | ## Constructors
18 |
19 | ```c#
20 | internal ThreadsServerErrorException() : base("An unknown error occurred on the Threads API end. The service may either be down currently, or this operation isn't supported by the server.")
21 | ```
--------------------------------------------------------------------------------
/docs/api-reference/ThreadSharp/Models/Api/Content/BaseMediaContainerContent.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: BaseMediaContainerContent
3 | ---
4 |
5 | # [ThreadSharp](../../../).[Models](../../).[Api](../).[Content](./).BaseMediaContainerContent
6 |
7 | ## Definition
8 |
9 | ```c#
10 | public class BaseMediaContainerContent
11 | ```
12 |
13 | Base media container content.
14 |
15 | ## Properties
16 |
17 | | Property | Type | Summary | Default Value |
18 | |----------------|--------|--------------------------------------------------------------|---------------|
19 | | IsCarouselItem | `bool` | Whether or not this media container item is a carousel item. | -- |
--------------------------------------------------------------------------------
/src/ThreadSharp/Enums/ReplyControl.cs:
--------------------------------------------------------------------------------
1 | using System.Runtime.Serialization;
2 |
3 | namespace ThreadSharp.Enums;
4 |
5 | ///
6 | /// Enum representing possible reply control values.
7 | ///
8 | public enum ReplyControl
9 | {
10 | ///
11 | /// Everyone can reply.
12 | ///
13 | [EnumMember(Value = "everyone")]
14 | Everyone,
15 | ///
16 | /// Accounts you follow can reply.
17 | ///
18 | [EnumMember(Value = "accounts_you_follow")]
19 | AccountsYouFollow,
20 | ///
21 | /// Accounts you mention can reply.
22 | ///
23 | [EnumMember(Value = "mentioned_only")]
24 | MentionedOnly
25 | }
--------------------------------------------------------------------------------
/docs/api-reference/ThreadSharp/Models/Api/ThreadsIdContainer.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: ThreadsIdContainer
3 | ---
4 |
5 | # [ThreadSharp](../../).[Models](../).[Api](./).ThreadsIdContainer
6 |
7 | ## Definition
8 |
9 | ```c#
10 | public class ThreadsIdContainer : BaseJsonUnrecognizedDataModel
11 | ```
12 |
13 | Represents a container that contains a Threads ID, could be a media container.
14 |
15 | Derived from: [`BaseJsonUnrecognizedDataModel`](../BaseJsonUnrecognizedDataModel).
16 |
17 | ## Properties
18 |
19 | | Property | Type | Summary | Default Value |
20 | |----------|--------------|----------------------------|---------------|
21 | | Id | `string` | The ID of the item. | -- |
--------------------------------------------------------------------------------
/src/ThreadSharp/Models/BaseJsonUnrecognizedDataModel.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json;
2 | using System.Text.Json.Serialization;
3 |
4 | namespace ThreadSharp.Models;
5 |
6 | ///
7 | /// A model that is used as a base class to provide its derivatives with additional data.
8 | ///
9 | ///
10 | /// This class is not intended for public use by apps or libraries that consume this library.
11 | ///
12 | public class BaseJsonUnrecognizedDataModel
13 | {
14 | ///
15 | /// Additional data that is available from the API, but
16 | /// not included in the POCO.
17 | ///
18 | [JsonExtensionData]
19 | public Dictionary? UnrecognizedData { get; set; }
20 | }
--------------------------------------------------------------------------------
/src/ThreadSharp/Converters/DateTimeConverter.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json;
2 | using System.Text.Json.Serialization;
3 |
4 | namespace ThreadSharp.Converters;
5 |
6 | internal sealed class DateTimeConverter : JsonConverter
7 | {
8 | public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
9 | {
10 | if (DateTime.TryParse(reader.GetString(), out var result))
11 | return result;
12 |
13 | return default;
14 | }
15 |
16 | public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options)
17 | {
18 | writer.WriteStringValue(value.ToUniversalTime().ToString("yyyy'-'MM'-'dd'T'HH':'mm':'ssZ"));
19 | }
20 | }
--------------------------------------------------------------------------------
/docs/api-reference/ThreadSharp/Enums/MetricPeriod.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: UserMetricPeriod
3 | ---
4 |
5 | # [ThreadSharp](../).[Enums](./).MetricPeriod
6 |
7 | ## Definition
8 |
9 | ```c#
10 | public enum MetricPeriod
11 | ```
12 |
13 | Enum representing possible user metric period options.
14 |
15 | ## Values
16 |
17 | | Value | Summary | Numeric Value |
18 | |----------|------------------------------------------------------|---------------|
19 | | Day | Metrics in a day. | -- |
20 | | Lifetime | Metrics in the lifetime of an account. | -- |
21 | | Unknown | For metric periods not yet supported by ThreadSharp. | -- |
--------------------------------------------------------------------------------
/docs/api-reference/ThreadSharp/Exceptions/ThreadsRequestException.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: ThreadsRequestException
3 | ---
4 |
5 | # [ThreadSharp](../).[Exceptions](./).ThreadsRequestException
6 |
7 | ## Definition
8 |
9 | ```c#
10 | public sealed class ThreadsRequestException : ThreadsException
11 | ```
12 |
13 | Represents the exception thrown when a request error occurrs.
14 |
15 | Derived from: [`ThreadsException`](./ThreadsException).
16 |
17 | ## Constructors
18 |
19 | ```c#
20 | internal ThreadsRequestException(string message, IEnumerable> errorData) : base(message, errorData)
21 | ```
22 |
23 | ```c#
24 | internal ThreadsRequestException(IEnumerable> errorData) : base("A Threads API error has occurred.", errorData)
25 | ```
--------------------------------------------------------------------------------
/src/ThreadSharp/Enums/Breakdown.cs:
--------------------------------------------------------------------------------
1 | using System.Runtime.Serialization;
2 |
3 | namespace ThreadSharp.Enums;
4 |
5 | ///
6 | /// Enum representing breakdown values.
7 | ///
8 | [Flags]
9 | public enum Breakdown
10 | {
11 | ///
12 | /// Breakdown by country.
13 | ///
14 | [EnumMember(Value = "country")]
15 | Country = 1 << 0,
16 | ///
17 | /// Breakdown by city.
18 | ///
19 | [EnumMember(Value = "city")]
20 | City = 1 << 1,
21 | ///
22 | /// Breakdown by age.
23 | ///
24 | [EnumMember(Value = "age")]
25 | Age = 1 << 2,
26 | ///
27 | /// Breakdown by gender.
28 | ///
29 | [EnumMember(Value = "gender")]
30 | Gender = 1 << 3
31 | }
--------------------------------------------------------------------------------
/docs/api-reference/ThreadSharp/Models/Api/Content/AttachmentLinkContainerContent.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: AttachmentLinkContainerContent
3 | ---
4 |
5 | # [ThreadSharp](../../../).[Models](../../).[Api](../).[Content](./).AttachmentLinkContainerContent
6 |
7 | ## Definition
8 |
9 | ```c#
10 | public class AttachmentLinkContainerContent : BaseMediaContainerContent
11 | ```
12 |
13 | Media container content that includes a link attachment.
14 |
15 | Derived from: [`BaseMediaContainerContent`](./BaseMediaContainerContent).
16 |
17 | ## Properties
18 |
19 | | Property | Type | Summary | Default Value |
20 | |----------------|----------|-----------------------------------------------|---------------|
21 | | LinkAttachment | `string` | The link to add to the post as an attachment. | -- |
--------------------------------------------------------------------------------
/docs/api-reference/ThreadSharp/Enums/MediaType.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: MediaType
3 | ---
4 |
5 | # [ThreadSharp](../).[Enums](./).MediaType
6 |
7 | ## Definition
8 |
9 | ```c#
10 | public enum MediaType
11 | ```
12 |
13 | Enum representing media types to use for creating media containers.
14 |
15 | ## Values
16 |
17 | | Value | Summary | Numeric Value |
18 | |----------|--------------------------------------------|---------------|
19 | | Text | For text containers. | -- |
20 | | Image | For image containers. | -- |
21 | | Video | For video containers. | -- |
22 | | Carousel | For carousel containers. | -- |
23 | | Unknown | Used for unknown/unrecognized media types. | -- |
--------------------------------------------------------------------------------
/src/ThreadSharp/Models/Api/Insights/ThreadsUserInsightLikesData.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json.Serialization;
2 |
3 | namespace ThreadSharp.Models.Api.Insights;
4 |
5 | ///
6 | /// Likes data for Threads user insights.
7 | ///
8 | public sealed class ThreadsUserInsightLikesData : ThreadsUserInsightDataBase
9 | {
10 | ///
11 | /// The total value of the data.
12 | ///
13 | [JsonPropertyName("total_value")]
14 | public ThreadsUserInsightLikesValue? TotalValue { get; set; }
15 |
16 | ///
17 | /// Value of the data.
18 | ///
19 | public sealed class ThreadsUserInsightLikesValue
20 | {
21 | ///
22 | /// Value of the data.
23 | ///
24 | [JsonPropertyName("value")]
25 | public long Value { get; set; }
26 | }
27 | }
--------------------------------------------------------------------------------
/src/ThreadSharp/Models/Api/Insights/ThreadsUserInsightQuotesData.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json.Serialization;
2 |
3 | namespace ThreadSharp.Models.Api.Insights;
4 |
5 | ///
6 | /// Quotes data for Threads user insights.
7 | ///
8 | public sealed class ThreadsUserInsightQuotesData : ThreadsUserInsightDataBase
9 | {
10 | ///
11 | /// Total value of the data.
12 | ///
13 | [JsonPropertyName("total_value")]
14 | public ThreadsUserInsightQuotesValue? TotalValue { get; set; }
15 |
16 | ///
17 | /// Value of the data.
18 | ///
19 | public sealed class ThreadsUserInsightQuotesValue
20 | {
21 | ///
22 | /// Value of the data.
23 | ///
24 | [JsonPropertyName("value")]
25 | public long Value { get; set; }
26 | }
27 | }
--------------------------------------------------------------------------------
/src/ThreadSharp/Models/Api/Insights/ThreadsUserInsightRepliesData.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json.Serialization;
2 |
3 | namespace ThreadSharp.Models.Api.Insights;
4 |
5 | ///
6 | /// Replies data for Threads user insights.
7 | ///
8 | public sealed class ThreadsUserInsightRepliesData : ThreadsUserInsightDataBase
9 | {
10 | ///
11 | /// The total value of the data.
12 | ///
13 | [JsonPropertyName("total_value")]
14 | public ThreadsUserInsightRepliesValue? TotalValue { get; set; }
15 |
16 | ///
17 | /// Value of the data.
18 | ///
19 | public sealed class ThreadsUserInsightRepliesValue
20 | {
21 | ///
22 | /// Value of the data.
23 | ///
24 | [JsonPropertyName("value")]
25 | public long Value { get; set; }
26 | }
27 | }
--------------------------------------------------------------------------------
/src/ThreadSharp/Models/Api/Insights/ThreadsUserInsightRepostsData.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json.Serialization;
2 |
3 | namespace ThreadSharp.Models.Api.Insights;
4 |
5 | ///
6 | /// Reposts data for Threads user insights.
7 | ///
8 | public sealed class ThreadsUserInsightRepostsData : ThreadsUserInsightDataBase
9 | {
10 | ///
11 | /// The total value of the data.
12 | ///
13 | [JsonPropertyName("total_value")]
14 | public ThreadsUserInsightRepostsValue? TotalValue { get; set; }
15 |
16 | ///
17 | /// Value of the data.
18 | ///
19 | public sealed class ThreadsUserInsightRepostsValue
20 | {
21 | ///
22 | /// Value of the data.
23 | ///
24 | [JsonPropertyName("value")]
25 | public long Value { get; set; }
26 | }
27 | }
--------------------------------------------------------------------------------
/src/ThreadSharp/Helpers/RetryHelpers.cs:
--------------------------------------------------------------------------------
1 | using System.Net;
2 | using ThreadSharp.Exceptions;
3 | using ThreadSharp.Models;
4 |
5 | namespace ThreadSharp.Helpers;
6 |
7 | internal static class RetryHelpers
8 | {
9 | internal const int DefaultMaxRetries = 5;
10 |
11 | internal static async Task> RetryOnServerErrorAsync(Func>> operation, int maxRetries = DefaultMaxRetries)
12 | {
13 | int retries = 0;
14 | ThreadsResult result;
15 |
16 | do
17 | {
18 | result = await operation();
19 |
20 | if ((int)result.StatusCode < 500 || (int)result.StatusCode > 599)
21 | return result;
22 |
23 | retries++;
24 | }
25 | while (retries < maxRetries);
26 |
27 | return new ThreadsResult(new ThreadsServerErrorException(), result.StatusCode);
28 | }
29 | }
--------------------------------------------------------------------------------
/src/ThreadSharp/Models/Api/Insights/ThreadsUserInsightTotalFollowersData.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json.Serialization;
2 |
3 | namespace ThreadSharp.Models.Api.Insights;
4 |
5 | ///
6 | /// Total follower data for Threads user insights.
7 | ///
8 | public sealed class ThreadsUserInsightTotalFollowersData : ThreadsUserInsightDataBase
9 | {
10 | ///
11 | /// The total value of the data.
12 | ///
13 | [JsonPropertyName("total_value")]
14 | public ThreadsUserInsightTotalFollowersValue? TotalValue { get; set; }
15 |
16 | ///
17 | /// Value of the data.
18 | ///
19 | public sealed class ThreadsUserInsightTotalFollowersValue
20 | {
21 | ///
22 | /// Value of the data.
23 | ///
24 | [JsonPropertyName("value")]
25 | public long Value { get; set; }
26 | }
27 | }
--------------------------------------------------------------------------------
/src/ThreadSharp/Enums/MediaType.cs:
--------------------------------------------------------------------------------
1 | using System.Runtime.Serialization;
2 |
3 | namespace ThreadSharp.Enums;
4 |
5 | ///
6 | /// Enum representing media types to use for creating media containers.
7 | ///
8 | public enum MediaType
9 | {
10 | ///
11 | /// For text containers.
12 | ///
13 | [EnumMember(Value = "TEXT")]
14 | Text,
15 | ///
16 | /// For image containers.
17 | ///
18 | [EnumMember(Value = "IMAGE")]
19 | Image,
20 | ///
21 | /// For video containers.
22 | ///
23 | [EnumMember(Value = "VIDEO")]
24 | Video,
25 | ///
26 | /// For carousel containers.
27 | ///
28 | [EnumMember(Value = "CAROUSEL")]
29 | Carousel,
30 | ///
31 | /// Used for unknown/unrecognized media types.
32 | ///
33 | Unknown
34 | }
--------------------------------------------------------------------------------
/src/ThreadSharp/Enums/ThreadsPublishingStatusCode.cs:
--------------------------------------------------------------------------------
1 | namespace ThreadSharp.Enums;
2 |
3 | ///
4 | /// Enum representing the status code for publishing a certain media container.
5 | ///
6 | public enum ThreadsPublishingStatusCode
7 | {
8 | ///
9 | /// The container is expired.
10 | ///
11 | Expired,
12 | ///
13 | /// An error occurred while creating the container.
14 | ///
15 | Error,
16 | ///
17 | /// The container has finished processing, and is ready for publishing.
18 | ///
19 | Finished,
20 | ///
21 | /// The container creation is in progress.
22 | ///
23 | InProgress,
24 | ///
25 | /// The container is published.
26 | ///
27 | Published,
28 | ///
29 | /// For undefined values.
30 | ///
31 | Unknown
32 | }
--------------------------------------------------------------------------------
/.github/workflows/nuget.yml:
--------------------------------------------------------------------------------
1 | name: Build & Test & Publish NuGet Package
2 |
3 | on:
4 | push:
5 | branches:
6 | - "**"
7 | tags:
8 | - "v[0-9]+.[0-9]+.[0-9]+"
9 | pull_request:
10 | branches:
11 | - "main"
12 |
13 | jobs:
14 | build:
15 | runs-on: ubuntu-latest
16 | steps:
17 | - uses: actions/checkout@v3
18 | - name: Setup .NET
19 | uses: actions/setup-dotnet@v2
20 | with:
21 | dotnet-version: 8.0.x
22 | - name: Restore dependencies
23 | run: dotnet restore
24 | - name: Build
25 | run: dotnet build --no-restore --configuration Release
26 | - name: Test
27 | run: dotnet test --no-build --verbosity normal
28 | - name: Upload NuGet Package
29 | if: github.ref_type == 'tag' && startsWith(github.ref, 'refs/tags/v')
30 | run: dotnet nuget push ./src/ThreadSharp/bin/Release/ThreadSharp.*.**.*.nupkg -k ${{ secrets.NUGET_API_KEY }} -s https://api.nuget.org/v3/index.json
--------------------------------------------------------------------------------
/docs/api-reference/ThreadSharp/Exceptions/ThreadsException.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: ThreadsException
3 | ---
4 |
5 | # [ThreadSharp](../).[Exceptions](./).ThreadsException
6 |
7 | ## Definition
8 |
9 | ```c#
10 | public class ThreadsException : Exception
11 | ```
12 |
13 | Exception thrown when an error occurs in the Threads API, or when an error from the library is thrown.
14 |
15 | ::: tip REMARKS
16 | Usually, derivatives are used instead of this class, to allow for easier handling of specific errors.
17 | :::
18 |
19 | ## Constructors
20 |
21 | ```c#
22 | internal ThreadsException(string message)
23 | ```
24 |
25 | ```c#
26 | internal ThreadsException() : this(DefaultErrorMessage)
27 | ```
28 |
29 | ```c#
30 | internal ThreadsException(string message, IEnumerable> errorData) : this(message)
31 | ```
32 |
33 | ```c#
34 | internal ThreadsException(IEnumerable> errorData)
35 | : this(DefaultErrorMessage, errorData)
36 | ```
--------------------------------------------------------------------------------
/docs/api-reference/ThreadSharp/Models/Api/Content/MediaContainerContent.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: MediaContainerContent
3 | ---
4 |
5 | # [ThreadSharp](../../../).[Models](../../).[Api](../).[Content](./).MediaContainerContent
6 |
7 | ## Definition
8 |
9 | ```c#
10 | public class MediaContainerContent : BaseMediaContainerContent
11 | ```
12 |
13 | Media container content for image or video containers.
14 |
15 | Derived from: [`BaseMediaContainerContent`](./BaseMediaContainerContent).
16 |
17 | ## Properties
18 |
19 | | Property | Type | Summary | Default Value |
20 | |----------|----------|-----------------------------------------------------|---------------|
21 | | ImageUrl | `string` | The URL of the image to use in the media container. | `null` |
22 | | VideoUrl | `string` | The URL of the video to use in the media container. | `null` |
23 | | AltText | `string` | The alt text for the media. | `null` |
--------------------------------------------------------------------------------
/src/ThreadSharp/Models/Api/ThreadsMediaContainerStatus.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json.Serialization;
2 | using ThreadSharp.Converters;
3 | using ThreadSharp.Enums;
4 |
5 | namespace ThreadSharp.Models.Api;
6 |
7 | ///
8 | /// Represents the status of a media container.
9 | ///
10 | public sealed class ThreadsMediaContainerStatus : ThreadsIdContainer
11 | {
12 | ///
13 | /// The status of a media container. Indicates whether the publishing went smoothly or not.
14 | ///
15 | [JsonPropertyName("status")]
16 | [JsonConverter(typeof(StringToThreadsPublishingStatusCodeConverter))]
17 | public required ThreadsPublishingStatusCode Status { get; set; }
18 |
19 | ///
20 | /// The error message, if exists.
21 | ///
22 | [JsonPropertyName("error_message")]
23 | [JsonConverter(typeof(StringToThreadsPublishingErrorCodeConverter))]
24 | public ThreadsPublishingErrorCode? ErrorMessage { get; set; }
25 | }
--------------------------------------------------------------------------------
/docs/api-reference/ThreadSharp/Models/BaseJsonUnrecognizedDataModel.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: BaseJsonUnrecognizedDataModel
3 | ---
4 |
5 | # [ThreadSharp](../).[Models](./).BaseJsonUnrecognizedDataModel
6 |
7 | ## Definition
8 |
9 | ```c#
10 | public sealed class BaseJsonUnrecognizedDataModel
11 | ```
12 |
13 | A model that is used as a base class to provide its derivatives with additional data.
14 |
15 | ::: tip REMARKS
16 | This class is not intended for public use by apps or libraries that consume this library.
17 | :::
18 |
19 | ## Properties
20 |
21 | | Property | Type | Summary | Default Value |
22 | |------------------|-----------------------------------|-------------------------------------------------------------------------------|---------------|
23 | | UnrecognizedData | `Dictionary` | Additional data that is available from the API, but not included in the POCO. | `null` |
--------------------------------------------------------------------------------
/docs/api-reference/ThreadSharp/Models/Api/Content/CarouselContainerContent.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: CarouselContainerContent
3 | ---
4 |
5 | # [ThreadSharp](../../../).[Models](../../).[Api](../).[Content](./).CarouselContainerContent
6 |
7 | ## Definition
8 |
9 | ```c#
10 | public class CarouselContainerContent : BaseMediaContainerContent
11 | ```
12 |
13 | Media container content for carousel posts.
14 |
15 | Derived from: [`BaseMediaContainerContent`](./BaseMediaContainerContent).
16 |
17 | ## Properties
18 |
19 | | Property | Type | Summary | Default Value |
20 | |----------|------------------------------------------------------------------------|----------------------------------------------------------------------|---------------|
21 | | Children | [`IList`](../ThreadsMediaContainerStatus) | List of statuses of the media containers to include in the carousel. | -- |
--------------------------------------------------------------------------------
/docs/docs/profile.md:
--------------------------------------------------------------------------------
1 | # Getting the Current User's Profile
2 |
3 | To get the currently authenticated user's profile details, you can call [`GetAsync()`](/api-reference/ThreadSharp/Internal/ThreadsUserClient#methods) on the [user client](#user-client), optionally specifying the specific fields to retrieve.
4 |
5 | ```c#
6 | // GetThreadsClient() is a placeholder method for getting a ThreadsClient.
7 | var threadsClient = GetThreadsClient();
8 |
9 | var getProfileResult = await threadsClient.Me.GetAsync();
10 |
11 | if (getProfileResult.IsSuccessStatusCode && getProfileResult.Value is not null)
12 | {
13 | ThreadsProfile profile = getProfileResult.Value;
14 |
15 | // Read the profile details, or get the ID.
16 | } else
17 | {
18 | // Handle gracefully based on the exception data in the result's "Error" property & the Value if exists.
19 | }
20 | ```
21 |
22 | ## Definitions
23 |
24 | ### User Client
25 |
26 | An instance of [`ThreadsUserClient`](/api-reference/ThreadSharp/Internal/ThreadsUserClient), usually obtained from a `ThreadsClient`'s `Me` property.
--------------------------------------------------------------------------------
/docs/api-reference/ThreadSharp/Models/PostPagingParameters.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: PostPagingParameters
3 | ---
4 |
5 | # [ThreadSharp](../).[Models](./).PostPagingParameters
6 |
7 | ## Definition
8 |
9 | ```c#
10 | public sealed class PostPagingParameters
11 | ```
12 |
13 | Parameters for paging posts.
14 |
15 | ## Properties
16 |
17 | | Property | Type | Summary | Default Value |
18 | |----------|------------------|-------------------------------------------------------|---------------|
19 | | Limit | `int` | Limit of posts to retrieve. | 25 |
20 | | Since | `DateTimeOffset` | Since date for posts. | `null` |
21 | | Until | `DateTimeOffset` | Until date for posts. | `null` |
22 | | Before | `DateTimeOffset` | The date offset to begin retrieving prior posts. | `null` |
23 | | After | `DateTimeOffset` | The date offset to begin retrieving subsequent posts. | `null` |
--------------------------------------------------------------------------------
/docs/api-reference/ThreadSharp/Models/Api/ThreadsManageReplyResult.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: ThreadsManageReplyResult
3 | ---
4 |
5 | # [ThreadSharp](../../).[Models](../).[Api](./).ThreadsManageReplyResult
6 |
7 | ## Definition
8 |
9 | ```c#
10 | public sealed class ThreadsManageReplyResult : BaseJsonUnrecognizedDataModel
11 | ```
12 |
13 | Represents the result when managing a reply.
14 |
15 | Derived from: [`BaseJsonUnrecognizedDataModel`](../BaseJsonUnrecognizedDataModel).
16 |
17 | ## Properties
18 |
19 | | Property | Type | Summary | Default Value |
20 | |----------|--------|--------------------------------------|---------------|
21 | | Success | `bool` | Whether the operation is successful. | -- |
22 |
23 | ## Methods
24 |
25 | | Method | Summary |
26 | |--------------|---------------------------------------------------------------------------------------|
27 | | `ToString()` | Returns a string that represents the current object.
**This method is overriden.** |
--------------------------------------------------------------------------------
/docs/api-reference/ThreadSharp/Models/UserMetricPagingParameters.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: UserMetricPagingParameters
3 | ---
4 |
5 | # [ThreadSharp](../).[Models](./).UserMetricPagingParameters
6 |
7 | ## Definition
8 |
9 | ```c#
10 | public sealed class UserMetricPagingParameters
11 | ```
12 |
13 | Parameters to use for paging & selecting metrics to retrieve.
14 |
15 | ## Properties
16 |
17 | | Property | Type | Summary | Default Value |
18 | |-----------|-----------------------------------------|-------------------------------------------------------|---------------|
19 | | Since | `DateTimeOffset` | The start date for the metric data. | `null` |
20 | | Until | `DateTimeOffset` | The end date for the metric data. | `null` |
21 | | Metrics | `string[]` | An exhaustive list of metrics to retrieve. | `null` |
22 | | Period | [`MetricPeriod`](../Enums/MetricPeriod) | The metrics' period. | `null` |
--------------------------------------------------------------------------------
/src/ThreadSharp/Models/Api/Insights/ThreadsUserInsightViewsData.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json.Serialization;
2 | using ThreadSharp.Converters;
3 |
4 | namespace ThreadSharp.Models.Api.Insights;
5 |
6 | ///
7 | /// Views data for Threads user insights.
8 | ///
9 | public sealed class ThreadsUserInsightViewsData : ThreadsUserInsightDataBase
10 | {
11 | ///
12 | /// List of views insight values.
13 | ///
14 | [JsonPropertyName("values")]
15 | public List? Values { get; set; }
16 |
17 | ///
18 | /// Views insight value item.
19 | ///
20 | public sealed class ThreadsUserInsightViewsValue
21 | {
22 | ///
23 | /// The number of views.
24 | ///
25 | [JsonPropertyName("views")]
26 | public long Views { get; set; }
27 |
28 | ///
29 | /// The end time of the views insight item.
30 | ///
31 | [JsonPropertyName("end_time")]
32 | [JsonConverter(typeof(DateTimeConverter))]
33 | public DateTime EndTime { get; set; }
34 | }
35 | }
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 itsWindows11 & ThreadSharp contributors.
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/src/ThreadSharp/Converters/StringToMetricPeriodConverter.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json.Serialization;
2 | using System.Text.Json;
3 | using ThreadSharp.Enums;
4 |
5 | namespace ThreadSharp.Converters;
6 |
7 | internal class StringToMetricPeriodConverter : JsonConverter
8 | {
9 | public override MetricPeriod Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
10 | {
11 | return reader.GetString() switch
12 | {
13 | "lifetime" => MetricPeriod.Lifetime,
14 | "day" => MetricPeriod.Day,
15 | _ => MetricPeriod.Unknown
16 | };
17 | }
18 |
19 | public override void Write(Utf8JsonWriter writer, MetricPeriod value, JsonSerializerOptions options)
20 | {
21 | switch (value)
22 | {
23 | case MetricPeriod.Lifetime:
24 | writer.WriteStringValue("lifetime");
25 | break;
26 | case MetricPeriod.Day:
27 | writer.WriteStringValue("day");
28 | break;
29 | default:
30 | throw new ArgumentOutOfRangeException(nameof(value), value, null);
31 | }
32 | }
33 | }
--------------------------------------------------------------------------------
/docs/api-reference/ThreadSharp/Enums/ThreadsPublishingStatusCode.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: ThreadsPublishingStatusCode
3 | ---
4 |
5 | # [ThreadSharp](../).[Enums](./).ThreadsPublishingStatusCode
6 |
7 | ## Definition
8 |
9 | ```c#
10 | public enum ThreadsPublishingStatusCode
11 | ```
12 |
13 | Enum representing the status code for publishing a certain media container.
14 |
15 | ## Values
16 |
17 | | Value | Summary | Numeric Value |
18 | |------------|---------------------------------------------------------------------|---------------|
19 | | Expired | The container is expired. | -- |
20 | | Error | An error occurred while creating the container. | -- |
21 | | Finished | The container has finished processing, and is ready for publishing. | -- |
22 | | InProgress | The container creation is in progress. | -- |
23 | | Published | The container is published. | -- |
24 | | Unknown | For undefined values. | -- |
--------------------------------------------------------------------------------
/src/ThreadSharp/Enums/ThreadsPostMediaType.cs:
--------------------------------------------------------------------------------
1 | using System.Runtime.Serialization;
2 |
3 | namespace ThreadSharp.Enums;
4 |
5 | ///
6 | /// Enum representing media types of already published posts.
7 | ///
8 | public enum ThreadsPostMediaType
9 | {
10 | ///
11 | /// For text posts.
12 | ///
13 | [EnumMember(Value = "TEXT_POST")]
14 | TextPost,
15 | ///
16 | /// For image posts.
17 | ///
18 | [EnumMember(Value = "IMAGE")]
19 | Image,
20 | ///
21 | /// For video posts.
22 | ///
23 | [EnumMember(Value = "VIDEO")]
24 | Video,
25 | ///
26 | /// For carousel albums.
27 | ///
28 | [EnumMember(Value = "CAROUSEL_ALBUM")]
29 | CarouselAlbum,
30 | ///
31 | /// For audio posts.
32 | ///
33 | [EnumMember(Value = "AUDIO")]
34 | Audio,
35 | ///
36 | /// For reposts.
37 | ///
38 | [EnumMember(Value = "REPOST_FACADE")]
39 | RepostFacade,
40 | ///
41 | /// Used for unknown/not yet recognized media types by ThreadSharp.
42 | ///
43 | Unknown
44 | }
--------------------------------------------------------------------------------
/docs/api-reference/ThreadSharp/Enums/ThreadsPostMediaType.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: ThreadsPostMediaType
3 | ---
4 |
5 | # [ThreadSharp](../).[Enums](./).ThreadsPostMediaType
6 |
7 | ## Definition
8 |
9 | ```c#
10 | public enum ThreadsPostMediaType
11 | ```
12 |
13 | Enum representing media types of already published posts.
14 |
15 | ## Values
16 |
17 | | Value | Summary | Numeric Value |
18 | |---------------|-----------------------------------------------------------------|---------------|
19 | | TextPost | For text posts. | -- |
20 | | Image | For image posts. | -- |
21 | | Video | For video posts. | -- |
22 | | CarouselAlbum | For carousel albums. | -- |
23 | | Audio | For audio posts. | -- |
24 | | RepostFacade | For reposts. | -- |
25 | | Unknown | Used for unknown/not yet recognized media types by ThreadSharp. | -- |
--------------------------------------------------------------------------------
/src/ThreadSharp/Models/Api/Content/MediaContainerContent.cs:
--------------------------------------------------------------------------------
1 | using Refit;
2 |
3 | namespace ThreadSharp.Models.Api.Content;
4 |
5 | ///
6 | /// Media container content for image or video containers.
7 | ///
8 | public class MediaContainerContent : BaseMediaContainerContent
9 | {
10 | private string? _imageUrl;
11 | private string? _videoUrl;
12 |
13 | ///
14 | /// The URL of the image to use in the media container.
15 | ///
16 | [AliasAs("image_url")]
17 | public string? ImageUrl
18 | {
19 | get => _imageUrl;
20 | set
21 | {
22 | if (VideoUrl != null)
23 | _videoUrl = null;
24 |
25 | _imageUrl = value;
26 | }
27 | }
28 |
29 | ///
30 | /// The URL of the video to use in the media container.
31 | ///
32 | [AliasAs("video_url")]
33 | public string? VideoUrl
34 | {
35 | get => _videoUrl;
36 | set
37 | {
38 | if (ImageUrl != null)
39 | _imageUrl = null;
40 |
41 | _videoUrl = value;
42 | }
43 | }
44 |
45 | ///
46 | /// The alt text for the media.
47 | ///
48 | [AliasAs("alt_text")]
49 | public string? AltText { get; set; }
50 | }
--------------------------------------------------------------------------------
/src/ThreadSharp/Models/Api/ThreadsProfile.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json.Serialization;
2 |
3 | namespace ThreadSharp.Models.Api;
4 |
5 | ///
6 | /// Represents a user's Threads profile.
7 | ///
8 | public sealed class ThreadsProfile : ThreadsIdContainer
9 | {
10 | ///
11 | /// The username.
12 | ///
13 | [JsonPropertyName("username")]
14 | public string? Username { get; set; }
15 |
16 | ///
17 | /// The name or title of the user.
18 | ///
19 | [JsonPropertyName("name")]
20 | public string? Name { get; set; }
21 |
22 | ///
23 | /// The profile picture URL.
24 | ///
25 | [JsonPropertyName("threads_profile_picture_url")]
26 | public string? ProfilePictureUrl { get; set; }
27 |
28 | ///
29 | /// The user's biography.
30 | ///
31 | [JsonPropertyName("threads_biography")]
32 | public string? Biography { get; set; }
33 |
34 | ///
35 | /// Whether or not the user can geo-gate their posts.
36 | ///
37 | [JsonPropertyName("is_eligible_for_geo_gating")]
38 | public bool? IsEligibleForGeoGating { get; set; }
39 |
40 | ///
41 | public override string ToString()
42 | => (Username != null ? "@" + Username : base.ToString())!;
43 | }
--------------------------------------------------------------------------------
/src/ThreadSharp/Models/UserMetricPagingParameters.cs:
--------------------------------------------------------------------------------
1 | using Refit;
2 | using ThreadSharp.Enums;
3 |
4 | namespace ThreadSharp.Models;
5 |
6 | ///
7 | /// Parameters to use for paging & selecting metrics to retrieve.
8 | ///
9 | public sealed class UserMetricPagingParameters
10 | {
11 | private string[]? _metrics;
12 |
13 | ///
14 | /// The start date for the metric data.
15 | ///
16 | [AliasAs("since")]
17 | public DateTimeOffset? Since { get; set; }
18 |
19 | ///
20 | /// The end date for the metric data.
21 | ///
22 | [AliasAs("until")]
23 | public DateTimeOffset? Until { get; set; }
24 |
25 | ///
26 | /// An exhaustive list of metrics to retrieve.
27 | ///
28 | [AliasAs("metric")]
29 | [Query(CollectionFormat.Csv)]
30 | public string[]? Metrics
31 | {
32 | get => _metrics;
33 | set
34 | {
35 | if (value?.Contains("follower_demographics") == true && (Since.HasValue || Until.HasValue))
36 | throw new InvalidOperationException("Cannot retrieve follower demographics with `since` and `until` parameters set.");
37 |
38 | _metrics = value;
39 | }
40 | }
41 |
42 | ///
43 | /// The metrics' period.
44 | ///
45 | [AliasAs("period")]
46 | public MetricPeriod? Period { get; set; }
47 | }
--------------------------------------------------------------------------------
/src/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | [](https://github.com/itsWindows11/ThreadSharp/actions/workflows/nuget.yml)
4 |
5 | ## Documentation
6 |
7 | You can browse the documentation at [itswindows11.github.io/ThreadSharp](https://itswindows11.github.io/ThreadSharp/docs/).
8 |
9 | ## Usage
10 |
11 | You can start by initializing a `ThreadsClient` with an access token:
12 |
13 | ```c#
14 | ThreadsClient threadsClient = new ThreadsClient("access-token");
15 | ```
16 |
17 | ## Building the Code
18 |
19 | ### Prerequisites
20 |
21 | Ensure you have following components:
22 |
23 | - [Git](https://git-scm.com/).
24 | - A .NET IDE of your choice, preferably [Visual Studio 2022](https://visualstudio.microsoft.com/vs/).
25 | - [The .NET SDK](https://dotnet.microsoft.com/en-us/download/visual-studio-sdks).
26 |
27 | ### Git
28 |
29 | Clone the repository:
30 |
31 | ```bash
32 | git clone https://github.com/itsWindows11/ThreadSharp
33 | ```
34 |
35 | ### Build the project
36 |
37 | - Open `ThreadSharp.sln`.
38 | - Build `ThreadSharp.csproj` with `DEBUG` mode activated.
39 |
40 | ## License
41 |
42 | Copyright (c) 2024 itsWindows11 & ThreadSharp contributors.
43 |
44 | Licensed under the [MIT License](https://github.com/itsWindows11/ThreadSharp/blob/main/LICENSE).
45 |
--------------------------------------------------------------------------------
/docs/docs/calling-unsupported-endpoints.md:
--------------------------------------------------------------------------------
1 | # Calling API endpoints unsupported by ThreadSharp
2 |
3 | To futureproof the ThreadSharp library, you can call the [`ThreadsClient.SendRequestAsync(HttpMethod, string endpoint)`](/api-reference/ThreadSharp/ThreadsClient#methods) method for unsupported endpoints.
4 |
5 | ::: tip NOTE
6 | If you frequently use the [`ThreadsClient.SendRequestAsync(HttpMethod, string endpoint)`](/api-reference/ThreadSharp/ThreadsClient#methods) method for endpoints added to the Threads API but not yet supported by ThreadSharp, please report your use cases in the [issues tab](https://github.com/itsWindows11/ThreadSharp/issues) in the repository, and it will be worked on.
7 | :::
8 |
9 | Below is an example for getting the current user's visitor country insights in the form of a result, containing a `Dictionary`:
10 |
11 | ```c#
12 | // GetThreadsClient() is a placeholder method for getting a ThreadsClient.
13 | ThreadsClient threadsClient = GetThreadsClient();
14 |
15 | var result = await threadsClient.SendRequestAsync(HttpMethod.Get, "/me/threads_insights?metric=follower_demographics&breakdown=country");
16 |
17 | if (result.IsSuccessStatusCode && result.Value is not null)
18 | {
19 | Dictionary insightData = result.Value;
20 |
21 | // Operate on specific properties by deserializing the JsonElement into the type you want, or enumerate the properties.
22 | } else
23 | {
24 | // Handle gracefully based on the exception data in the result's "Error" property & the Value if exists.
25 | }
26 | ```
--------------------------------------------------------------------------------
/docs/api-reference/ThreadSharp/Internal/ThreadsInsightsClient.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: ThreadsInsightsClient
3 | ---
4 |
5 | # [ThreadSharp](../).[Internal](./).ThreadsInsightsClient
6 |
7 | ## Definition
8 |
9 | ```c#
10 | public sealed class ThreadsInsightsClient
11 | ```
12 |
13 | Client for all things insight/data related for the current authenticated user.
14 |
15 | ## Methods
16 |
17 | | Method | Summary | Return Value |
18 | |---------------------------------------------------------------------------------------------------------------|--------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------|
19 | | GetForCurrentUserAsync(UserMetricPagingParameters, Breakdown?, CancellationToken cancellationToken = default) | Gets the insights of the currently authenticated user. | The result, containing either a list of [`ThreadsUserInsightDataBase`](../Models/Api/ThreadsUserInsightDataBase) derivatives or an error. |
20 | | GetForPostAsync(string mediaContainerId, string[] metrics, CancellationToken cancellationToken = default) | Gets insights of a specific Thread or media container. | The result, containing either a list of [`ThreadsMediaInsightItem`](../Models/Api/Insights/ThreadsMediaInsightItem)s, or an error. |
--------------------------------------------------------------------------------
/.github/workflows/docs-deploy.yml:
--------------------------------------------------------------------------------
1 | name: Deploy VitePress Site to Pages
2 |
3 | on:
4 | push:
5 | branches: [main]
6 |
7 | workflow_dispatch:
8 |
9 | permissions:
10 | contents: read
11 | pages: write
12 | id-token: write
13 |
14 | concurrency:
15 | group: pages
16 | cancel-in-progress: false
17 |
18 | jobs:
19 | build:
20 | runs-on: ubuntu-latest
21 | steps:
22 | - name: Checkout
23 | uses: actions/checkout@v4
24 | with:
25 | fetch-depth: 0
26 | # Uncomment this if you're using pnpm
27 | # - uses: pnpm/action-setup@v3
28 | # Uncomment this if you're using Bun
29 | # - uses: oven-sh/setup-bun@v1
30 | - name: Setup Node
31 | uses: actions/setup-node@v4
32 | with:
33 | node-version: 20
34 | cache: npm
35 | cache-dependency-path: docs/package-lock.json
36 | - name: Setup Pages
37 | uses: actions/configure-pages@v4
38 | - name: Install dependencies
39 | working-directory: docs
40 | run: npm ci
41 | - name: Build with VitePress
42 | working-directory: docs
43 | run: npm run build
44 | - name: Upload artifact
45 | id: upload
46 | uses: actions/upload-pages-artifact@v3
47 | with:
48 | path: docs/.vitepress/dist
49 |
50 | deploy:
51 | environment:
52 | name: github-pages
53 | url: ${{ steps.deployment.outputs.page_url }}
54 | needs: build
55 | runs-on: ubuntu-latest
56 | steps:
57 | - name: Deploy to GitHub Pages
58 | id: deployment
59 | uses: actions/deploy-pages@v4
--------------------------------------------------------------------------------
/docs/api-reference/ThreadSharp/Models/Api/ThreadsProfile.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: ThreadsProfile
3 | ---
4 |
5 | # [ThreadSharp](../../).[Models](../).[Api](./).ThreadsProfile
6 |
7 | ## Definition
8 |
9 | ```c#
10 | public sealed class ThreadsProfile : ThreadsIdContainer
11 | ```
12 |
13 | Represents a user's Threads profile.
14 |
15 | Derived from: [`ThreadsIdContainer`](./ThreadsIdContainer) --> [`BaseJsonUnrecognizedDataModel`](../BaseJsonUnrecognizedDataModel).
16 |
17 | ## Properties
18 |
19 | | Property | Type | Summary | Default Value |
20 | |------------------------|----------|---------------------------------------------------|---------------|
21 | | Username | `string` | The username. | `null` |
22 | | Name | `string` | The name or title of the user. | `null` |
23 | | ProfilePictureUrl | `string` | The profile picture URL. | `null` |
24 | | Biography | `string` | The user's biography. | `null` |
25 | | IsEligibleForGeoGating | `bool?` | Whether or not the user can geo-gate their posts. | `null` |
26 |
27 | ## Methods
28 |
29 | | Method | Summary | Return Value |
30 | |--------------|---------------------------------------------------------------------------------------|--------------|
31 | | `ToString()` | Returns a string that represents the current object.
**This method is overriden.** | `string` |
--------------------------------------------------------------------------------
/src/ThreadSharp/Enums/ThreadsPublishingErrorCode.cs:
--------------------------------------------------------------------------------
1 | namespace ThreadSharp.Enums;
2 |
3 | ///
4 | /// Enum representing possible error codes for media container publishing.
5 | ///
6 | public enum ThreadsPublishingErrorCode
7 | {
8 | ///
9 | /// The video has failed to download from the Threads API end.
10 | ///
11 | FailedDownloadingVideo,
12 | ///
13 | /// The audio processing has failed.
14 | ///
15 | FailedProcessingAudio,
16 | ///
17 | /// The video processing has failed.
18 | ///
19 | FailedProcessingVideo,
20 | ///
21 | /// The aspect ratio is invalid or unsupported by Threads.
22 | ///
23 | InvalidAspectRatio,
24 | ///
25 | /// The bitrate is invalid or unsupported by Threads.
26 | ///
27 | InvalidBitRate,
28 | ///
29 | /// The video duration is invalid or unsupported by Threads.
30 | ///
31 | InvalidDuration,
32 | ///
33 | /// The framerate is invalid or unsupported by Threads.
34 | ///
35 | InvalidFrameRate,
36 | ///
37 | /// The audio channels are invalid or unsupported by Threads.
38 | ///
39 | InvalidAudioChannels,
40 | ///
41 | /// The audio channel layout are invalid or unsupported by Threads.
42 | ///
43 | InvalidAudioChannelLayout,
44 | ///
45 | /// Used for unrecognized error values, or if the Threads API returns an unknown error.
46 | ///
47 | Unknown
48 | }
--------------------------------------------------------------------------------
/src/ThreadSharp/Models/PostPagingParameters.cs:
--------------------------------------------------------------------------------
1 | using Refit;
2 | using ThreadSharp.Exceptions;
3 |
4 | namespace ThreadSharp.Models;
5 |
6 | ///
7 | /// Parameters for paging posts.
8 | ///
9 | public sealed class PostPagingParameters
10 | {
11 | private DateTimeOffset? _before;
12 | private DateTimeOffset? _after;
13 |
14 | ///
15 | /// Limit of posts to retrieve.
16 | ///
17 | [AliasAs("limit")]
18 | public int Limit { get; set; } = 25;
19 |
20 | ///
21 | /// Since date for posts.
22 | ///
23 | [AliasAs("since")]
24 | public DateTimeOffset? Since { get; set; }
25 |
26 | ///
27 | /// Until date for posts.
28 | ///
29 | [AliasAs("until")]
30 | public DateTimeOffset? Until { get; set; }
31 |
32 | ///
33 | /// The date offset to begin retrieving prior posts.
34 | ///
35 | [AliasAs("before")]
36 | public DateTimeOffset? Before
37 | {
38 | get => _before;
39 | set
40 | {
41 | if (After.HasValue)
42 | throw new ThreadsBeforeAfterException();
43 |
44 | _before = value;
45 | }
46 | }
47 |
48 | ///
49 | /// The date offset to begin retrieving subsequent posts.
50 | ///
51 | [AliasAs("after")]
52 | public DateTimeOffset? After
53 | {
54 | get => _after;
55 | set
56 | {
57 | if (_before.HasValue)
58 | throw new ThreadsBeforeAfterException();
59 |
60 | _after = value;
61 | }
62 | }
63 | }
--------------------------------------------------------------------------------
/docs/api-reference/ThreadSharp/Models/Api/ThreadsMediaContainerStatus.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: ThreadsMediaContainerStatus
3 | ---
4 |
5 | # [ThreadSharp](../../).[Models](../).[Api](./).ThreadsMediaContainerStatus
6 |
7 | ## Definition
8 |
9 | ```c#
10 | public sealed class ThreadsMediaContainerStatus : ThreadsIdContainer
11 | ```
12 |
13 | Represents the status of a media container.
14 |
15 | Derived from: [`ThreadsIdContainer`](ThreadsIdContainer) --> [`BaseJsonUnrecognizedDataModel`](../BaseJsonUnrecognizedDataModel).
16 |
17 | ## Properties
18 |
19 | | Property | Type | Summary | Default Value |
20 | |----------------|--------------------------------------------------------------------------|-----------------------------------------------------------------------------------------|---------------|
21 | | Status | [`ThreadsPublishingStatusCode`](../../Enums/ThreadsPublishingStatusCode) | The status of a media container. Indicates whether the publishing went smoothly or not. | -- |
22 | | ErrorMessage | [`ThreadsPublishingErrorCode`](../../Enums/ThreadsPublishingErrorCode) | The error message, if exists. | `null` |
23 |
24 | ## Methods
25 |
26 | | Method | Summary |
27 | |--------------|---------------------------------------------------------------------------------------|
28 | | `ToString()` | Returns a string that represents the current object.
**This method is overriden.** |
--------------------------------------------------------------------------------
/src/ThreadSharp/Models/Api/Insights/ThreadsMediaInsightItem.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json.Serialization;
2 | using ThreadSharp.Converters;
3 | using ThreadSharp.Enums;
4 |
5 | namespace ThreadSharp.Models.Api.Insights;
6 |
7 | ///
8 | /// An item representing a metric in media insights.
9 | ///
10 | public sealed class ThreadsMediaInsightItem : ThreadsIdContainer
11 | {
12 | ///
13 | /// The insight's name.
14 | ///
15 | [JsonPropertyName("name")]
16 | public required string Name { get; set; }
17 |
18 | ///
19 | /// The insight's title.
20 | ///
21 | [JsonPropertyName("title")]
22 | public required string Title { get; set; }
23 |
24 | ///
25 | /// The insight's description.
26 | ///
27 | [JsonPropertyName("description")]
28 | public required string Description { get; set; }
29 |
30 | ///
31 | /// The insight's values.
32 | ///
33 | [JsonPropertyName("values")]
34 | public List? Values { get; set; }
35 |
36 | ///
37 | /// The insight's period
38 | ///
39 | [JsonPropertyName("period")]
40 | [JsonConverter(typeof(StringToMetricPeriodConverter))]
41 | public MetricPeriod Period { get; set; }
42 |
43 | ///
44 | /// Value item for a media insights metric.
45 | ///
46 | public sealed class ThreadsMediaInsightItemValue
47 | {
48 | ///
49 | /// The value of the metric.
50 | ///
51 | [JsonPropertyName("value")]
52 | public required int Value { get; set; }
53 | }
54 | }
--------------------------------------------------------------------------------
/src/ThreadSharp/Internal/ThreadsSourceGenerationContext.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json;
2 | using System.Text.Json.Serialization;
3 | using ThreadSharp.Models;
4 | using ThreadSharp.Models.Api;
5 | using ThreadSharp.Models.Api.Insights;
6 |
7 | namespace ThreadSharp.Internal;
8 |
9 | [JsonSerializable(typeof(BaseJsonUnrecognizedDataModel))]
10 | [JsonSerializable(typeof(Dictionary))]
11 | [JsonSerializable(typeof(ThreadsManageReplyResult))]
12 | [JsonSerializable(typeof(ThreadsIdContainer))]
13 | [JsonSerializable(typeof(ThreadsProfile))]
14 | [JsonSerializable(typeof(ThreadsPublishingLimitData))]
15 | [JsonSerializable(typeof(ThreadsMediaContainerStatus))]
16 | [JsonSerializable(typeof(List))]
17 | [JsonSerializable(typeof(ThreadsDataContainer>))]
18 | [JsonSerializable(typeof(ThreadsDataContainer>))]
19 | [JsonSerializable(typeof(ThreadsDataContainer>))]
20 | [JsonSerializable(typeof(ThreadsDataContainer>))]
21 | [JsonSerializable(typeof(ThreadsDataContainer>))]
22 | [JsonSerializable(typeof(ThreadsDataContainer>))]
23 | [JsonSerializable(typeof(ThreadsDataContainer>))]
24 | [JsonSerializable(typeof(ThreadsDataContainer>))]
25 | [JsonSerializable(typeof(ThreadsDataContainer>))]
26 | [JsonSerializable(typeof(ThreadsDataContainer>))]
27 | [JsonSerializable(typeof(ThreadsDataContainer>))]
28 | internal sealed partial class ThreadsSourceGenerationContext : JsonSerializerContext
29 | {
30 | }
--------------------------------------------------------------------------------
/src/ThreadSharp/Models/Api/ThreadsPublishingLimitData.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json.Serialization;
2 |
3 | namespace ThreadSharp.Models.Api;
4 |
5 | ///
6 | /// Publishing data information.
7 | ///
8 | public sealed class ThreadsPublishingLimitData : BaseJsonUnrecognizedDataModel
9 | {
10 | ///
11 | /// The post quota usage.
12 | ///
13 | [JsonPropertyName("quota_usage")]
14 | public int QuotaUsage { get; set; }
15 |
16 | ///
17 | /// The reply quota usage.
18 | ///
19 | [JsonPropertyName("reply_quota_usage")]
20 | public int ReplyQuotaUsage { get; set; }
21 |
22 | ///
23 | /// Config for post quota.
24 | ///
25 | [JsonPropertyName("config")]
26 | public required QuotaConfig Config { get; set; }
27 |
28 | ///
29 | /// Config for reply quota.
30 | ///
31 | [JsonPropertyName("reply_config")]
32 | public required QuotaConfig ReplyConfig { get; set; }
33 |
34 | ///
35 | /// Quota config.
36 | ///
37 | public sealed class QuotaConfig
38 | {
39 | ///
40 | /// The total quota.
41 | ///
42 | [JsonPropertyName("quota_total")]
43 | public int QuotaTotal { get; set; }
44 |
45 | ///
46 | /// The duration remaining for the quota to expire.
47 | ///
48 | [JsonPropertyName("quota_duration")]
49 | public int QuotaDuration { get; set; }
50 |
51 | ///
52 | public override string ToString()
53 | {
54 | return $"Total: {QuotaTotal}, Duration: {QuotaDuration}";
55 | }
56 | }
57 | }
--------------------------------------------------------------------------------
/src/ThreadSharp/Models/Api/ThreadsUserInsightDataBase.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json.Serialization;
2 | using ThreadSharp.Converters;
3 | using ThreadSharp.Enums;
4 | using ThreadSharp.Models.Api.Insights;
5 |
6 | namespace ThreadSharp.Models.Api;
7 |
8 | ///
9 | /// Base class for all the potential insight types.
10 | ///
11 | [JsonPolymorphic(TypeDiscriminatorPropertyName = "name", UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)]
12 | [JsonDerivedType(typeof(ThreadsUserInsightViewsData), typeDiscriminator: "views")]
13 | [JsonDerivedType(typeof(ThreadsUserInsightLikesData), typeDiscriminator: "likes")]
14 | [JsonDerivedType(typeof(ThreadsUserInsightRepostsData), typeDiscriminator: "reposts")]
15 | [JsonDerivedType(typeof(ThreadsUserInsightRepliesData), typeDiscriminator: "replies")]
16 | [JsonDerivedType(typeof(ThreadsUserInsightQuotesData), typeDiscriminator: "quotes")]
17 | [JsonDerivedType(typeof(ThreadsUserInsightTotalFollowersData), typeDiscriminator: "followers_count")]
18 | [JsonDerivedType(typeof(ThreadsUserInsightFollowerDemographicsData), typeDiscriminator: "follower_demographics")]
19 | public class ThreadsUserInsightDataBase : ThreadsIdContainer
20 | {
21 | ///
22 | /// The period of the insight/metric.
23 | ///
24 | [JsonPropertyName("period")]
25 | [JsonConverter(typeof(StringToMetricPeriodConverter))]
26 | public required MetricPeriod Period { get; set; }
27 |
28 | ///
29 | /// The metric title.
30 | ///
31 | [JsonPropertyName("title")]
32 | public string? Title { get; set; }
33 |
34 | ///
35 | /// The metric description.
36 | ///
37 | [JsonPropertyName("description")]
38 | public string? Description { get; set; }
39 | }
--------------------------------------------------------------------------------
/docs/api-reference/ThreadSharp/Models/Api/Insights/ThreadsUserInsightLikesData.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: ThreadsUserInsightLikesData
3 | ---
4 |
5 | # [ThreadSharp](../../../).[Models](../../).[Api](../).[Insights](./).ThreadsUserInsightLikesData
6 |
7 | ## Definition
8 |
9 | ```c#
10 | public sealed class ThreadsUserInsightLikesData : ThreadsUserInsightDataBase
11 | ```
12 |
13 | Likes data for Threads user insights.
14 |
15 | Derived from: [`ThreadsUserInsightDataBase`](../ThreadsUserInsightDataBase) --> [`ThreadsIdContainer`](../ThreadsIdContainer) --> [`BaseJsonUnrecognizedDataModel`](../../BaseJsonUnrecognizedDataModel).
16 |
17 | ::: tip NOTE
18 | In the relevant [Threads API reference for user insights](https://developers.facebook.com/docs/threads/reference/insights#get---threads-user-id--threads-insights), the property `name` is omitted as it is used as a type discriminator in the `System.Text.Json`, which is used for (de)serialization in ThreadSharp.
19 | :::
20 |
21 | ## Properties
22 |
23 | | Property | Type | Summary | Default Value |
24 | |------------|-----------------------------------------------------------------|------------------------------|---------------|
25 | | TotalValue | [`ThreadsUserInsightLikesValue`](#threadsuserinsightlikesvalue) | The total value of the data. | `null` |
26 |
27 | ## Nested Classes
28 |
29 | ---
30 |
31 | ### ThreadsUserInsightLikesValue
32 |
33 | #### Definition
34 |
35 | ```c#
36 | public sealed class ThreadsUserInsightLikesValue
37 | ```
38 |
39 | Value of the data.
40 |
41 | #### Properties
42 |
43 | | Property | Type | Summary | Default Value |
44 | |----------|--------|--------------------|---------------|
45 | | Value | `long` | Value of the data. | -- |
--------------------------------------------------------------------------------
/docs/api-reference/ThreadSharp/Models/Api/Insights/ThreadsUserInsightQuotesData.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: ThreadsUserInsightQuotesData
3 | ---
4 |
5 | # [ThreadSharp](../../../).[Models](../../).[Api](../).[Insights](./).ThreadsUserInsightQuotesData
6 |
7 | ## Definition
8 |
9 | ```c#
10 | public sealed class ThreadsUserInsightQuotesData : ThreadsUserInsightDataBase
11 | ```
12 |
13 | Quotes data for Threads user insights.
14 |
15 | Derived from: [`ThreadsUserInsightDataBase`](../ThreadsUserInsightDataBase) --> [`ThreadsIdContainer`](../ThreadsIdContainer) --> [`BaseJsonUnrecognizedDataModel`](../../BaseJsonUnrecognizedDataModel).
16 |
17 | ::: tip NOTE
18 | In the relevant [Threads API reference for user insights](https://developers.facebook.com/docs/threads/reference/insights#get---threads-user-id--threads-insights), the property `name` is omitted as it is used as a type discriminator in the `System.Text.Json`, which is used for (de)serialization in ThreadSharp.
19 | :::
20 |
21 | ## Properties
22 |
23 | | Property | Type | Summary | Default Value |
24 | |------------|-------------------------------------------------------------------|------------------------------|---------------|
25 | | TotalValue | [`ThreadsUserInsightQuotesValue`](#threadsuserinsightquotesvalue) | The total value of the data. | `null` |
26 |
27 | ## Nested Classes
28 |
29 | ---
30 |
31 | ### ThreadsUserInsightQuotesValue
32 |
33 | #### Definition
34 |
35 | ```c#
36 | public sealed class ThreadsUserInsightQuotesValue
37 | ```
38 |
39 | Value of the data.
40 |
41 | #### Properties
42 |
43 | | Property | Type | Summary | Default Value |
44 | |----------|--------|--------------------|---------------|
45 | | Value | `long` | Value of the data. | -- |
--------------------------------------------------------------------------------
/docs/api-reference/ThreadSharp/Models/Api/Insights/ThreadsUserInsightRepliesData.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: ThreadsUserInsightRepliesData
3 | ---
4 |
5 | # [ThreadSharp](../../../).[Models](../../).[Api](../).[Insights](./).ThreadsUserInsightRepliesData
6 |
7 | ## Definition
8 |
9 | ```c#
10 | public sealed class ThreadsUserInsightRepliesData : ThreadsUserInsightDataBase
11 | ```
12 |
13 | Replies data for Threads user insights.
14 |
15 | Derived from: [`ThreadsUserInsightDataBase`](../ThreadsUserInsightDataBase) --> [`ThreadsIdContainer`](../ThreadsIdContainer) --> [`BaseJsonUnrecognizedDataModel`](../../BaseJsonUnrecognizedDataModel).
16 |
17 | ::: tip NOTE
18 | In the relevant [Threads API reference for user insights](https://developers.facebook.com/docs/threads/reference/insights#get---threads-user-id--threads-insights), the property `name` is omitted as it is used as a type discriminator in the `System.Text.Json`, which is used for (de)serialization in ThreadSharp.
19 | :::
20 |
21 | ## Properties
22 |
23 | | Property | Type | Summary | Default Value |
24 | |------------|---------------------------------------------------------------------|------------------------------|---------------|
25 | | TotalValue | [`ThreadsUserInsightrepliesValue`](#threadsuserinsightrepliesvalue) | The total value of the data. | `null` |
26 |
27 | ## Nested Classes
28 |
29 | ---
30 |
31 | ### ThreadsUserInsightRepliesValue
32 |
33 | #### Definition
34 |
35 | ```c#
36 | public sealed class ThreadsUserInsightRepliesValue
37 | ```
38 |
39 | Value of the data.
40 |
41 | #### Properties
42 |
43 | | Property | Type | Summary | Default Value |
44 | |----------|--------|--------------------|---------------|
45 | | Value | `long` | Value of the data. | -- |
--------------------------------------------------------------------------------
/docs/api-reference/ThreadSharp/Models/Api/Insights/ThreadsUserInsightRepostsData.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: ThreadsUserInsightRepostsData
3 | ---
4 |
5 | # [ThreadSharp](../../../).[Models](../../).[Api](../).[Insights](./).ThreadsUserInsightRepostsData
6 |
7 | ## Definition
8 |
9 | ```c#
10 | public sealed class ThreadsUserInsightRepostsData : ThreadsUserInsightDataBase
11 | ```
12 |
13 | Reposts data for Threads user insights.
14 |
15 | Derived from: [`ThreadsUserInsightDataBase`](../ThreadsUserInsightDataBase) --> [`ThreadsIdContainer`](../ThreadsIdContainer) --> [`BaseJsonUnrecognizedDataModel`](../../BaseJsonUnrecognizedDataModel).
16 |
17 | ::: tip NOTE
18 | In the relevant [Threads API reference for user insights](https://developers.facebook.com/docs/threads/reference/insights#get---threads-user-id--threads-insights), the property `name` is omitted as it is used as a type discriminator in the `System.Text.Json`, which is used for (de)serialization in ThreadSharp.
19 | :::
20 |
21 | ## Properties
22 |
23 | | Property | Type | Summary | Default Value |
24 | |------------|---------------------------------------------------------------------|------------------------------|---------------|
25 | | TotalValue | [`ThreadsUserInsightRepostsValue`](#threadsuserinsightrepostsvalue) | The total value of the data. | `null` |
26 |
27 | ## Nested Classes
28 |
29 | ---
30 |
31 | ### ThreadsUserInsightRepostsValue
32 |
33 | #### Definition
34 |
35 | ```c#
36 | public sealed class ThreadsUserInsightRepostsValue
37 | ```
38 |
39 | Value of the data.
40 |
41 | #### Properties
42 |
43 | | Property | Type | Summary | Default Value |
44 | |----------|--------|--------------------|---------------|
45 | | Value | `long` | Value of the data. | -- |
--------------------------------------------------------------------------------
/src/ThreadSharp/Models/ThreadsResult.cs:
--------------------------------------------------------------------------------
1 | using System.Net;
2 | using System.Text.Json;
3 | using ThreadSharp.Exceptions;
4 |
5 | namespace ThreadSharp.Models;
6 |
7 | ///
8 | /// Result class used to wrap Threads API responses.
9 | ///
10 | /// The type of the response body.
11 | public sealed class ThreadsResult
12 | {
13 | ///
14 | /// The value of this result. If successful, isn't null.
15 | ///
16 | public T? Value { get; }
17 |
18 | ///
19 | /// The error of this result, if one occurred.
20 | ///
21 | public ThreadsException? Error { get; }
22 |
23 | ///
24 | /// Additional data about the error, if one occurred.
25 | ///
26 | public Dictionary? ErrorData => Error?.Data as Dictionary;
27 |
28 | ///
29 | /// The status code of the result.
30 | ///
31 | public HttpStatusCode StatusCode { get; }
32 |
33 | ///
34 | /// Gets whether the status code is successful.
35 | ///
36 | public bool IsSuccessStatusCode => (int)StatusCode >= 200 && (int)StatusCode <= 299;
37 |
38 | internal ThreadsResult(T? value, HttpStatusCode statusCode)
39 | {
40 | Value = value;
41 | StatusCode = statusCode;
42 | }
43 |
44 | internal ThreadsResult(ThreadsException error, HttpStatusCode statusCode)
45 | {
46 | Error = error;
47 | StatusCode = statusCode;
48 | }
49 |
50 | ///
51 | public override string ToString()
52 | {
53 | if (Value is null)
54 | return $"{(IsSuccessStatusCode ? "Success" : "Error")} (empty response), {base.ToString()}";
55 |
56 | return $"{(IsSuccessStatusCode ? "Success" : "Error")}, {base.ToString()}";
57 | }
58 | }
--------------------------------------------------------------------------------
/docs/api-reference/ThreadSharp/Models/Api/Insights/ThreadsUserInsightTotalFollowersData.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: ThreadsUserInsightTotalFollowersData
3 | ---
4 |
5 | # [ThreadSharp](../../../).[Models](../../).[Api](../).[Insights](./).ThreadsUserInsightTotalFollowersData
6 |
7 | ## Definition
8 |
9 | ```c#
10 | public sealed class ThreadsUserInsightTotalFollowersData : ThreadsUserInsightDataBase
11 | ```
12 |
13 | Total follower data for Threads user insights.
14 |
15 | Derived from: [`ThreadsUserInsightDataBase`](../ThreadsUserInsightDataBase) --> [`ThreadsIdContainer`](../ThreadsIdContainer) --> [`BaseJsonUnrecognizedDataModel`](../../BaseJsonUnrecognizedDataModel).
16 |
17 | ::: tip NOTE
18 | In the relevant [Threads API reference for user insights](https://developers.facebook.com/docs/threads/reference/insights#get---threads-user-id--threads-insights), the property `name` is omitted as it is used as a type discriminator in the `System.Text.Json`, which is used for (de)serialization in ThreadSharp.
19 | :::
20 |
21 | ## Properties
22 |
23 | | Property | Type | Summary | Default Value |
24 | |------------|-----------------------------------------------------------------------------------|------------------------------|---------------|
25 | | TotalValue | [`ThreadsUserInsightTotalFollowersValue`](#threadsuserinsighttotalfollowersvalue) | The total value of the data. | `null` |
26 |
27 | ## Nested Classes
28 |
29 | ---
30 |
31 | ### ThreadsUserInsightTotalFollowersValue
32 |
33 | #### Definition
34 |
35 | ```c#
36 | public sealed class ThreadsUserInsightTotalFollowersValue
37 | ```
38 |
39 | Value of the data.
40 |
41 | #### Properties
42 |
43 | | Property | Type | Summary | Default Value |
44 | |----------|--------|--------------------|---------------|
45 | | Value | `long` | Value of the data. | -- |
--------------------------------------------------------------------------------
/docs/api-reference/ThreadSharp/Models/Api/ThreadsUserInsightDataBase.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: ThreadsUserInsightDataBase
3 | ---
4 |
5 | # [ThreadSharp](../../).[Models](../).[Api](./).ThreadsUserInsightDataBase
6 |
7 | ## Definition
8 |
9 | ```c#
10 | using System.Text.Json.Serialization;
11 |
12 | [JsonPolymorphic(TypeDiscriminatorPropertyName = "name", UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)]
13 | [JsonDerivedType(typeof(ThreadsUserInsightViewsData), typeDiscriminator: "views")]
14 | [JsonDerivedType(typeof(ThreadsUserInsightLikesData), typeDiscriminator: "likes")]
15 | [JsonDerivedType(typeof(ThreadsUserInsightRepostsData), typeDiscriminator: "reposts")]
16 | [JsonDerivedType(typeof(ThreadsUserInsightRepliesData), typeDiscriminator: "replies")]
17 | [JsonDerivedType(typeof(ThreadsUserInsightQuotesData), typeDiscriminator: "quotes")]
18 | [JsonDerivedType(typeof(ThreadsUserInsightTotalFollowersData), typeDiscriminator: "followers_count")]
19 | [JsonDerivedType(typeof(ThreadsUserInsightFollowerDemographicsData), typeDiscriminator: "follower_demographics")]
20 | public class ThreadsUserInsightDataBase : ThreadsIdContainer
21 | ```
22 |
23 | Base class for all the potential insight types.
24 |
25 | Derived from: [`ThreadsIdContainer`](./ThreadsIdContainer) --> [`BaseJsonUnrecognizedDataModel`](../BaseJsonUnrecognizedDataModel).
26 |
27 | ## Properties
28 |
29 | | Property | Type | Summary | Default Value |
30 | |-------------|--------------------------------------------|-----------------------------------|---------------|
31 | | Period | [`MetricPeriod`](../../Enums/MetricPeriod) | The period of the insight/metric. | -- |
32 | | Title | `string` | The metric title. | `null` |
33 | | Description | `string` | The metric description. | `null` |
--------------------------------------------------------------------------------
/docs/api-reference/ThreadSharp/Enums/ThreadsPublishingErrorCode.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: ThreadsPublishingErrorCode
3 | ---
4 |
5 | # [ThreadSharp](../).[Enums](./).ThreadsPublishingErrorCode
6 |
7 | ## Definition
8 |
9 | ```c#
10 | public enum ThreadsPublishingErrorCode
11 | ```
12 |
13 | Enum representing possible error codes for media container publishing.
14 |
15 | ## Values
16 |
17 | | Value | Summary | Numeric Value |
18 | |---------------------------|-------------------------------------------------------------------------------------|---------------|
19 | | FailedDownloadingVideo | The video has failed to download from the Threads API end. | -- |
20 | | FailedProcessingAudio | The audio processing has failed. | -- |
21 | | FailedProcessingVideo | The video processing has failed. | -- |
22 | | InvalidAspectRatio | The aspect ratio is invalid or unsupported by Threads. | -- |
23 | | InvalidBitRate | The bitrate is invalid or unsupported by Threads. | -- |
24 | | InvalidDuration | The video duration is invalid or unsupported by Threads. | -- |
25 | | InvalidFrameRate | The framerate is invalid or unsupported by Threads. | -- |
26 | | InvalidAudioChannels | The audio channels are invalid or unsupported by Threads. | -- |
27 | | InvalidAudioChannelLayout | The audio channel layout are invalid or unsupported by Threads. | -- |
28 | | Unknown | Used for unrecognized error values, or if the Threads API returns an unknown error. | -- |
--------------------------------------------------------------------------------
/src/ThreadSharp/Converters/StringToThreadsPublishingStatusCodeConverter.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json;
2 | using System.Text.Json.Serialization;
3 | using ThreadSharp.Enums;
4 |
5 | namespace ThreadSharp.Converters;
6 |
7 | internal class StringToThreadsPublishingStatusCodeConverter : JsonConverter
8 | {
9 | public override ThreadsPublishingStatusCode Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
10 | {
11 | return reader.GetString() switch
12 | {
13 | "EXPIRED" => ThreadsPublishingStatusCode.Expired,
14 | "ERROR" => ThreadsPublishingStatusCode.Published,
15 | "FINISHED" => ThreadsPublishingStatusCode.Finished,
16 | "IN_PROGRESS" => ThreadsPublishingStatusCode.Error,
17 | "PUBLISHED" => ThreadsPublishingStatusCode.Published,
18 | _ => ThreadsPublishingStatusCode.Unknown,
19 | };
20 | }
21 |
22 | public override void Write(Utf8JsonWriter writer, ThreadsPublishingStatusCode value, JsonSerializerOptions options)
23 | {
24 | switch (value)
25 | {
26 | case ThreadsPublishingStatusCode.Expired:
27 | writer.WriteStringValue("EXPIRED");
28 | break;
29 | case ThreadsPublishingStatusCode.Error:
30 | writer.WriteStringValue("ERROR");
31 | break;
32 | case ThreadsPublishingStatusCode.Finished:
33 | writer.WriteStringValue("FINISHED");
34 | break;
35 | case ThreadsPublishingStatusCode.InProgress:
36 | writer.WriteStringValue("IN_PROGRESS");
37 | break;
38 | case ThreadsPublishingStatusCode.Published:
39 | writer.WriteStringValue("PUBLISHED");
40 | break;
41 | default:
42 | writer.WriteStringValue("UNKNOWN");
43 | break;
44 | }
45 | }
46 | }
--------------------------------------------------------------------------------
/docs/api-reference/ThreadSharp/Models/Api/Insights/ThreadsUserInsightViewsData.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: ThreadsUserInsightViewsData
3 | ---
4 |
5 | # [ThreadSharp](../../../).[Models](../../).[Api](../).[Insights](./).ThreadsUserInsightViewsData
6 |
7 | ## Definition
8 |
9 | ```c#
10 | public sealed class ThreadsUserInsightViewsData : ThreadsUserInsightDataBase
11 | ```
12 |
13 | Views data for Threads user insights.
14 |
15 | Derived from: [`ThreadsUserInsightDataBase`](../ThreadsUserInsightDataBase) --> [`ThreadsIdContainer`](../ThreadsIdContainer) --> [`BaseJsonUnrecognizedDataModel`](../../BaseJsonUnrecognizedDataModel).
16 |
17 | ::: tip NOTE
18 | In the relevant [Threads API reference for user insights](https://developers.facebook.com/docs/threads/reference/insights#get---threads-user-id--threads-insights), the property `name` is omitted as it is used as a type discriminator in the `System.Text.Json`, which is used for (de)serialization in ThreadSharp.
19 | :::
20 |
21 | ## Properties
22 |
23 | | Property | Type | Summary | Default Value |
24 | |----------|-----------------------------------------------------------------------|-------------------------------|---------------|
25 | | Values | [`List`](#threadsuserinsightviewsvalue) | List of views insight values. | `null` |
26 |
27 | ## Nested Classes
28 |
29 | ---
30 |
31 | ### ThreadsUserInsightViewsValue
32 |
33 | #### Definition
34 |
35 | ```c#
36 | public sealed class ThreadsUserInsightViewsValue
37 | ```
38 |
39 | Value of the data.
40 |
41 | #### Properties
42 |
43 | | Property | Type | Summary | Default Value |
44 | |------------|------------|-----------------------------------------|---------------|
45 | | Views | `long` | Value of the data. | -- |
46 | | EndTime | `DateTime` | The end time of the views insight item. | -- |
--------------------------------------------------------------------------------
/docs/api-reference/ThreadSharp/Models/Api/ThreadsPublishingLimitData.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: ThreadsPublishingLimitData
3 | ---
4 |
5 | # [ThreadSharp](../../).[Models](../).[Api](./).ThreadsPublishingLimitData
6 |
7 | ## Definition
8 |
9 | ```c#
10 | public sealed class ThreadsPublishingLimitData : BaseJsonUnrecognizedDataModel
11 | ```
12 |
13 | Derived from: [`BaseJsonUnrecognizedDataModel`](../BaseJsonUnrecognizedDataModel).
14 |
15 | ## Properties
16 |
17 | | Property | Type | Summary | Default Value |
18 | |------------------|-------------------------------|-------------------------|---------------|
19 | | QuotaUsage | `int` | The post quota usage. | 0 |
20 | | ReplyQuotaUsage | `int` | The reply quota usage. | 0 |
21 | | QuotaConfig | [`QuotaConfig`](#quotaconfig) | Config for post quota. | `null` |
22 | | ReplyQuotaConfig | [`QuotaConfig`](#quotaconfig) | Config for reply quota. | `null` |
23 |
24 | ## Nested Classes
25 |
26 | ---
27 |
28 | ### QuotaConfig
29 |
30 | #### Definition
31 |
32 | ```c#
33 | public sealed class QuotaConfig
34 | ```
35 |
36 | Quota config.
37 |
38 | #### Properties
39 |
40 | | Property | Type | Summary | Default Value |
41 | |---------------|-------|-------------------------------------------------|---------------|
42 | | QuotaTotal | `int` | The total quota. | 0 |
43 | | QuotaDuration | `int` | The duration remaining for the quota to expire. | 0 |
44 |
45 | #### Methods
46 |
47 | | Method | Summary | Return Value |
48 | |--------------|---------------------------------------------------------------------------------------|--------------|
49 | | `ToString()` | Returns a string that represents the current object.
**This method is overriden.** | `string` |
--------------------------------------------------------------------------------
/docs/api-reference/ThreadSharp/Models/ThreadsResult.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: ThreadsResult
3 | ---
4 |
5 | # [ThreadSharp](../).[Models](./).ThreadsResult
6 |
7 | ## Definition
8 |
9 | ```c#
10 | public sealed class ThreadsResult
11 | ```
12 |
13 | Result class used to wrap Threads API responses.
14 |
15 | `T`: The type of the response body.
16 |
17 | ## Constructors
18 |
19 | ```c#
20 | internal ThreadsResult(T? value, HttpStatusCode statusCode)
21 | ```
22 |
23 | ```c#
24 | internal ThreadsResult(ThreadsException error, HttpStatusCode statusCode)
25 | ```
26 |
27 | ## Properties
28 |
29 | | Property | Type | Summary | Default Value |
30 | |---------------------|------------------------------------------------------|------------------------------------------------------|---------------|
31 | | Value | `T` | The value of this result. If successful, isn't null. | `null` |
32 | | Error | [`ThreadsException`](../Exceptions/ThreadsException) | Paging data from the API. | `null` |
33 | | ErrorData | `Dictionary` | Additional data about the error, if one occurred. | `null` |
34 | | StatusCode | `HttpStatusCode` | The status code of the result. | -- |
35 | | IsSuccessStatusCode | `bool` | Gets whether the status code is successful. | -- |
36 |
37 | ## Methods
38 |
39 | | Method | Summary | Return Value |
40 | |--------------|---------------------------------------------------------------------------------------|--------------|
41 | | `ToString()` | Returns a string that represents the current object.
**This method is overriden.** | `string` |
--------------------------------------------------------------------------------
/src/ThreadSharp/Models/ThreadsDataContainer.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json.Serialization;
2 |
3 | namespace ThreadSharp.Models;
4 |
5 | ///
6 | /// Container for Threads responses where there's a `data` child.
7 | ///
8 | /// Type for the data.
9 | public sealed class ThreadsDataContainer
10 | {
11 | ///
12 | /// The data.
13 | ///
14 | [JsonPropertyName("data")]
15 | public required T Data { get; set; }
16 |
17 | ///
18 | /// Paging data from the API.
19 | ///
20 | [JsonPropertyName("paging")]
21 | public ThreadsPagingData? Paging { get; set; }
22 |
23 | ///
24 | /// Paging data from the API.
25 | ///
26 | public sealed class ThreadsPagingData
27 | {
28 | ///
29 | /// The pointer or URL to the next page.
30 | ///
31 | [JsonPropertyName("next")]
32 | public string? Next { get; set; }
33 |
34 | ///
35 | /// The pointer or URL to the previous page.
36 | ///
37 | [JsonPropertyName("previous")]
38 | public string? Previous { get; set; }
39 |
40 | ///
41 | /// Cursor data.
42 | ///
43 | [JsonPropertyName("cursors")]
44 | public ThreadsPagingDataCursors? Cursors { get; set; }
45 |
46 | ///
47 | /// Cursor data.
48 | ///
49 | public sealed class ThreadsPagingDataCursors
50 | {
51 | ///
52 | /// The pointer or URL to the previous page.
53 | ///
54 | [JsonPropertyName("before")]
55 | public string? Before { get; set; }
56 |
57 | ///
58 | /// The pointer or URL to the next page.
59 | ///
60 | [JsonPropertyName("after")]
61 | public string? After { get; set; }
62 | }
63 | }
64 | }
--------------------------------------------------------------------------------
/src/ThreadSharp/Converters/StringToThreadsPostMediaTypeConverter.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json.Serialization;
2 | using System.Text.Json;
3 | using ThreadSharp.Enums;
4 |
5 | namespace ThreadSharp.Converters;
6 |
7 | internal class StringToThreadsPostMediaTypeConverter : JsonConverter
8 | {
9 | public override ThreadsPostMediaType Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
10 | {
11 | return reader.GetString() switch
12 | {
13 | "TEXT_POST" => ThreadsPostMediaType.TextPost,
14 | "IMAGE" => ThreadsPostMediaType.Image,
15 | "VIDEO" => ThreadsPostMediaType.Video,
16 | "CAROUSEL_ALBUM" => ThreadsPostMediaType.CarouselAlbum,
17 | "AUDIO" => ThreadsPostMediaType.Audio,
18 | "REPOST_FACADE" => ThreadsPostMediaType.RepostFacade,
19 | _ => ThreadsPostMediaType.Unknown
20 | };
21 | }
22 |
23 | public override void Write(Utf8JsonWriter writer, ThreadsPostMediaType value, JsonSerializerOptions options)
24 | {
25 | switch (value)
26 | {
27 | case ThreadsPostMediaType.TextPost:
28 | writer.WriteStringValue("TEXT_POST");
29 | break;
30 | case ThreadsPostMediaType.Image:
31 | writer.WriteStringValue("IMAGE");
32 | break;
33 | case ThreadsPostMediaType.Video:
34 | writer.WriteStringValue("VIDEO");
35 | break;
36 | case ThreadsPostMediaType.CarouselAlbum:
37 | writer.WriteStringValue("CAROUSEL_ALBUM");
38 | break;
39 | case ThreadsPostMediaType.Audio:
40 | writer.WriteStringValue("AUDIO");
41 | break;
42 | case ThreadsPostMediaType.RepostFacade:
43 | writer.WriteStringValue("REPOST_FACADE");
44 | break;
45 | default:
46 | throw new ArgumentOutOfRangeException(nameof(value), value, null);
47 | }
48 | }
49 | }
--------------------------------------------------------------------------------
/src/ThreadSharp/Exceptions/ThreadsException.cs:
--------------------------------------------------------------------------------
1 | using System.Collections;
2 | using System.Text.Json;
3 |
4 | namespace ThreadSharp.Exceptions;
5 |
6 | ///
7 | /// Exception thrown when an error occurs in the Threads API, or when an error from the library is thrown.
8 | ///
9 | ///
10 | /// Usually, derivatives are used instead of this class, to allow for easier handling of specific errors.
11 | ///
12 | public class ThreadsException : Exception
13 | {
14 | private const string DefaultErrorMessage = "An unexpected error occurred.";
15 |
16 | ///
17 | ///
18 | /// This contains the response from the Threads API.
19 | ///
20 | public override IDictionary Data { get; } = new Dictionary();
21 |
22 | ///
23 | public override string Message { get; }
24 |
25 | internal ThreadsException(string message)
26 | {
27 | Message = message;
28 | }
29 |
30 | ///
31 | /// Initializes a new instance of the class.
32 | ///
33 | internal ThreadsException() : this(DefaultErrorMessage)
34 | { }
35 |
36 | ///
37 | /// Initializes a new instance of the class.
38 | ///
39 | /// The message to use when throwing the exception.
40 | /// The error data provided by the Threads API.
41 | internal ThreadsException(string message, IEnumerable> errorData) : this(message)
42 | {
43 | Data = errorData.ToDictionary(x => x.Key, x => x.Value.GetString())!;
44 | }
45 |
46 | ///
47 | /// Initializes a new instance of the class.
48 | ///
49 | /// The error data provided by the Threads API.
50 | internal ThreadsException(IEnumerable> errorData)
51 | : this(DefaultErrorMessage, errorData)
52 | { }
53 | }
--------------------------------------------------------------------------------
/docs/docs/insights.md:
--------------------------------------------------------------------------------
1 | # Insights
2 |
3 | This document explains how to get, and work with Threads insights for either the media or the currently authenticated user as a whole.
4 |
5 | ## Media Insights
6 |
7 | To get media insights, you first have to know the media container ID of the thread you want insights for. Then, you can pass this ID to the [`GetForPostAsync()`](/api-reference/ThreadSharp/Internal/ThreadsInsightsClient) method on the [insights client](#insights-client) in order to get insights for the post. This includes metrics like views, likes, quotes, reposts etc.
8 |
9 | You can optionally specify which metrics you want to retrieve by passing an exhaustive list of fields to the `metrics` parameter.
10 |
11 | Below is an example for getting only the like, quote & repost metrics:
12 |
13 | ```c#
14 | // GetThreadsClient() is a placeholder method for getting a ThreadsClient.
15 | var threadsClient = GetThreadsClient();
16 |
17 | var insightsResult = await threadsClient.Insights.GetForPostAsync("[thread ID]", ["likes", "quotes", "reposts"]);
18 |
19 | // Work with the result.
20 | ```
21 |
22 | For more details, refer to the [official docs](https://developers.facebook.com/docs/threads/insights#media-insights).
23 |
24 | ## User Insights
25 |
26 | For getting the user insights, you can use the [`GetForCurrentUserAsync()`](/api-reference/ThreadSharp/Internal/ThreadsInsightsClient#methods) method on the [insights client](#insights-client).
27 |
28 | ::: caution IMPORTANT
29 | If you are retrieving follower demographics, you must specify the `breakdown` parameter, or else an exception will be thrown.
30 | :::
31 |
32 | See the "[Getting User Insights](https://github.com/itsWindows11/ThreadSharp/tree/main/src/Samples/GetInsightsSample)" sample for an idea on how retrieving user insights work.
33 |
34 | For more details, refer to the [official docs](https://developers.facebook.com/docs/threads/insights#user-insights).
35 |
36 | ## Definitions
37 |
38 | ### Insights Client
39 |
40 | An instance of [`ThreadsInsightsClient`](/api-reference/ThreadSharp/Internal/ThreadsInsightsClient), usually obtained from a `ThreadsClient`'s `Insights` property.
--------------------------------------------------------------------------------
/docs/api-reference/ThreadSharp/Models/Api/Insights/ThreadsMediaInsightItem.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: ThreadsMediaInsightItem
3 | ---
4 |
5 | # [ThreadSharp](../../../).[Models](../../).[Api](../).[Insights](./).ThreadsMediaInsightItem
6 |
7 | ## Definition
8 |
9 | ```c#
10 | public sealed class ThreadsMediaInsightItem : ThreadsIdContainer
11 | ```
12 |
13 | An item representing a metric in media insights.
14 |
15 | Derived from: [`ThreadsIdContainer`](../ThreadsIdContainer) --> [`BaseJsonUnrecognizedDataModel`](../../BaseJsonUnrecognizedDataModel).
16 |
17 | ## Properties
18 |
19 | | Property | Type | Summary | Default Value |
20 | |-------------------------------------|------------------------------------------------------------------------|-------------------------------------------|---------------|
21 | | `Name` | `string` | The insight's name. | -- |
22 | | `Title` | `string` | The insight's title. | -- |
23 | | `Description` | `string` | The insight's description. | -- |
24 | | `Values` | [`List?`](#ThreadsMediaInsightItemValue) | The insight's values. | `null` |
25 | | `Period` | [`MetricPeriod`](../../../Enums/MetricPeriod) | The insight's period. | -- |
26 |
27 | ## Nested Classes
28 |
29 | ### ThreadsMediaInsightItemValue
30 |
31 | #### Definition
32 |
33 | ```c#
34 | public sealed class ThreadsMediaInsightItemValue
35 | ```
36 |
37 | #### Properties
38 |
39 | | Property | Type | Summary | Default Value |
40 | |----------|-------|--------------------------|---------------|
41 | | `Value` | `int` | The value of the metric. | `0` |
--------------------------------------------------------------------------------
/docs/api-reference/ThreadSharp/Models/ThreadsDataContainer.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: ThreadsDataContainer
3 | ---
4 |
5 | # [ThreadSharp](../).[Models](./).ThreadsDataContainer
6 |
7 | ## Definition
8 |
9 | ```c#
10 | public sealed class ThreadsDataContainer
11 | ```
12 |
13 | Container for Threads responses where there's a `data` child.
14 |
15 | `T`: Type for the data.
16 |
17 | ## Properties
18 |
19 | | Property | Type | Summary | Default Value |
20 | |----------|-------------------------------------------|----------------------------|---------------|
21 | | Data | `T` | The data. | -- |
22 | | Paging | [`ThreadsPagingData`](#threadspagingdata) | Paging data from the API. | `null` |
23 |
24 | ## Nested Classes
25 |
26 | ---
27 |
28 | ### ThreadsPagingData
29 |
30 | #### Definition
31 |
32 | ```c#
33 | public sealed class ThreadsPagingData
34 | ```
35 |
36 | Paging data from the API.
37 |
38 | #### Properties
39 |
40 | | Property | Type | Summary | Default Value |
41 | |------------|--------------------------------------------------------------------------|-------------------------------------------|---------------|
42 | | Next | `string` | The pointer or URL to the next page. | `null` |
43 | | Previous | `string` | The pointer or URL to the previous page. | `null` |
44 | | Cursors | [`ThreadsPagingDataCursors`](#threadspagingdatathreadspagingdatacursors) | Cursor data. | `null` |
45 |
46 | ### ThreadsPagingData.ThreadsPagingDataCursors
47 |
48 | #### Definition
49 |
50 | ```c#
51 | public sealed class ThreadsPagingDataCursors
52 | ```
53 |
54 | #### Properties
55 |
56 | | Property | Type | Summary | Default Value |
57 | |----------|----------|-------------------------------------------|---------------|
58 | | Before | `string` | The pointer or URL to the previous page. | `null` |
59 | | After | `string` | The pointer or URL to the next page. | `null` |
--------------------------------------------------------------------------------
/.github/SECURITY.md:
--------------------------------------------------------------------------------
1 | # Security Policy
2 |
3 | #### This is our policy for reporting security vulnerabilities and overall guidelines on what you should do upon discovering one!
4 |
5 | ---
6 |
7 |
20 |
21 | ## Reporting Security Vulnerabilities
22 |
23 |
30 |
31 | #### Please use the GitHub Security Advisory "Report a Vulnerability" tab!
32 |
33 | In order to report a security vulnerability, you can use GitHub's built-in tool which easily allows you to calculate an _attack vector/CVSS string_ or attribute to an existing [CVE](https://cve.org) code. This allows us to accurately calculate the severity and/or importance of preventing it.
34 |
35 | ### Spotting secrets in code
36 |
37 | If you spot a secret in the code, please let us know by [contacting us](mailto:itswin11@outlook.com). This helps us quietly remove the secret, and invalidate it.
38 | If you notice that we've accidentally published an app credential file or removed it from the `.gitignore` in the project root, please notify us.
39 |
40 | ## Our Measures
41 | ##### What have we done to keep ThreadSharp safe?
42 |
43 | ### Dependabot
44 |
45 | We have implemented Dependabot alerts to automatically track security vulnerabilities that apply to the repository's dependencies.
46 |
47 | ### Code scanning
48 |
49 | We have enabled GitHub Code Scanning to automatically scan our code for potential GitHub client secrets and other API tokens.
50 |
51 | ### Security advisories
52 |
53 | We have enabled GitHub security advisories to let us know if a potential security problem might affect our repository or if something doesn't look right with any of our other security vulnerability countermeasures. This makes it easy to track potential errors or problems that might expose user credentials publicly or cause other similar problems.
54 |
--------------------------------------------------------------------------------
/src/ThreadSharp/Models/Api/Insights/ThreadsUserInsightFollowerDemographicsData.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json.Serialization;
2 |
3 | namespace ThreadSharp.Models.Api.Insights;
4 |
5 | ///
6 | /// Follower demographic data for Threads user insights.
7 | ///
8 | public sealed class ThreadsUserInsightFollowerDemographicsData : ThreadsUserInsightDataBase
9 | {
10 | ///
11 | /// The total value of the data.
12 | ///
13 | [JsonPropertyName("total_value")]
14 | public ThreadsUserInsightFollowerDemographicsValue? TotalValue { get; set; }
15 |
16 | ///
17 | /// Total value of the data.
18 | ///
19 | public sealed class ThreadsUserInsightFollowerDemographicsValue
20 | {
21 | ///
22 | /// Breakdown data.
23 | ///
24 | [JsonPropertyName("breakdowns")]
25 | public required List BreakdownData { get; set; }
26 |
27 | ///
28 | /// Breakdown data.
29 | ///
30 | public sealed class ThreadsUserInsightFollowerDemographicsBreakdownData
31 | {
32 | ///
33 | /// Dimension keys as specified in the request.
34 | ///
35 | [JsonPropertyName("dimension_keys")]
36 | public required string[] DimensionKeys { get; set; }
37 |
38 | ///
39 | /// The results of the breakdown data.
40 | ///
41 | [JsonPropertyName("results")]
42 | public required List Results { get; set; }
43 |
44 | ///
45 | /// The breakdown data item.
46 | ///
47 | public sealed class ThreadsUserInsightFollowerDemographicsBreakdownDataValue
48 | {
49 | ///
50 | /// The dimension values.
51 | ///
52 | [JsonPropertyName("dimension_values")]
53 | public required string[] DimensionValues { get; set; }
54 |
55 | ///
56 | /// The value of the breakdown data item.
57 | ///
58 | [JsonPropertyName("value")]
59 | public int Value { get; set; }
60 | }
61 | }
62 | }
63 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 | A C# API wrapper for the Threads API.
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | > [!IMPORTANT]
11 | > ThreadSharp is limited by the capabilities of the [Threads public API](https://developers.facebook.com/docs/threads/) and so not all features that you enjoy in the Threads app are available.
12 |
13 | ## Documentation
14 |
15 | You can browse the documentation at [itswindows11.github.io/ThreadSharp](https://itswindows11.github.io/ThreadSharp/docs/).
16 |
17 | ## Contributing
18 |
19 | There are multiple ways to participate in the community:
20 |
21 | - Upvote popular feature requests.
22 | - [Submit a new feature](https://github.com/itsWindows11/ThreadSharp/pulls).
23 | - [File bugs and feature requests](https://github.com/itsWindows11/ThreadSharp/issues/new/choose).
24 | - Review [source code changes](https://github.com/itsWindows11/ThreadSharp/commits).
25 |
26 | ## Usage
27 |
28 | You can start by initializing a `ThreadsClient` with an access token:
29 |
30 | ```c#
31 | ThreadsClient threadsClient = new ThreadsClient("access-token");
32 | ```
33 |
34 | > [!NOTE]
35 | > ThreadSharp doesn't handle authentication manually, so you will have to handle the authentication depending on your platform's OAuth2 library.
36 |
37 | ## Building the Code
38 |
39 | ### Prerequisites
40 |
41 | Ensure you have following components:
42 |
43 | - [Git](https://git-scm.com/).
44 | - A .NET IDE of your choice, preferably [Visual Studio 2022](https://visualstudio.microsoft.com/vs/).
45 | - [The .NET SDK](https://dotnet.microsoft.com/en-us/download/visual-studio-sdks).
46 |
47 | ### Git
48 |
49 | Clone the repository:
50 |
51 | ```bash
52 | git clone https://github.com/itsWindows11/ThreadSharp
53 | ```
54 |
55 | ### Build the project
56 |
57 | - Open `ThreadSharp.sln`.
58 | - Build `ThreadSharp.csproj` with `DEBUG` mode activated.
59 |
60 | ## License
61 |
62 | Copyright (c) 2024 itsWindows11 & ThreadSharp contributors.
63 |
64 | Licensed under the [MIT license](LICENSE).
65 |
--------------------------------------------------------------------------------
/src/Samples/GetCurrentUserProfileAndPrintDetails/Program.cs:
--------------------------------------------------------------------------------
1 | using ReplyToSelfPostSample;
2 | using System.Text.Json;
3 | using ThreadSharp;
4 |
5 | var accessToken = Environment.GetEnvironmentVariable("THREADS_ACCESS_TOKEN");
6 |
7 | if (accessToken is null)
8 | {
9 | Console.WriteLine("ERROR: Cannot run this sample without a Threads access token.");
10 | return;
11 | }
12 |
13 | #pragma warning disable CA1869 // Cache and reuse 'JsonSerializerOptions' instances
14 | var serializerOptions = new JsonSerializerOptions()
15 | {
16 | WriteIndented = true,
17 | TypeInfoResolver = CustomJsonSerializerContext.Default
18 | };
19 | #pragma warning restore CA1869 // Cache and reuse 'JsonSerializerOptions' instances
20 |
21 | var threadsClient = new ThreadsClient(accessToken);
22 |
23 | var currentUserProfileResult = await threadsClient.Me.GetAsync();
24 |
25 | if (currentUserProfileResult.Value is not null)
26 | {
27 | Console.WriteLine("User Info:");
28 | Console.WriteLine("----------------");
29 |
30 | Console.WriteLine($"""
31 | Display Name: "{currentUserProfileResult.Value.Name}"
32 | Username: "@{currentUserProfileResult.Value.Username}"
33 | Biography: "{currentUserProfileResult.Value.Biography}"
34 | Profile picture URL: "{currentUserProfileResult.Value.ProfilePictureUrl}"
35 | User ID: {currentUserProfileResult.Value.Id}
36 | Is eligible for geo-gating: {currentUserProfileResult.Value.IsEligibleForGeoGating}
37 | """);
38 | } else if (currentUserProfileResult.ErrorData is not null)
39 | {
40 | Console.WriteLine("ERROR: Cannot retrieve user info.");
41 | Console.WriteLine("-------------------------------------");
42 |
43 | var errorData = currentUserProfileResult.ErrorData;
44 |
45 | #pragma warning disable IL2026 // Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code
46 | #pragma warning disable IL3050 // Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling.
47 | if (errorData is not null)
48 | Console.WriteLine(JsonSerializer.Serialize(errorData, serializerOptions));
49 | #pragma warning restore IL3050 // Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling.
50 | #pragma warning restore IL2026 // Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code
51 | }
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | ###############################################################################
2 | # Set default behavior to automatically normalize line endings.
3 | ###############################################################################
4 | * text=auto
5 |
6 | ###############################################################################
7 | # Set default behavior for command prompt diff.
8 | #
9 | # This is need for earlier builds of msysgit that does not have it on by
10 | # default for csharp files.
11 | # Note: This is only used by command line
12 | ###############################################################################
13 | #*.cs diff=csharp
14 |
15 | ###############################################################################
16 | # Set the merge driver for project and solution files
17 | #
18 | # Merging from the command prompt will add diff markers to the files if there
19 | # are conflicts (Merging from VS is not affected by the settings below, in VS
20 | # the diff markers are never inserted). Diff markers may cause the following
21 | # file extensions to fail to load in VS. An alternative would be to treat
22 | # these files as binary and thus will always conflict and require user
23 | # intervention with every merge. To do so, just uncomment the entries below
24 | ###############################################################################
25 | #*.sln merge=binary
26 | #*.csproj merge=binary
27 | #*.vbproj merge=binary
28 | #*.vcxproj merge=binary
29 | #*.vcproj merge=binary
30 | #*.dbproj merge=binary
31 | #*.fsproj merge=binary
32 | #*.lsproj merge=binary
33 | #*.wixproj merge=binary
34 | #*.modelproj merge=binary
35 | #*.sqlproj merge=binary
36 | #*.wwaproj merge=binary
37 |
38 | ###############################################################################
39 | # behavior for image files
40 | #
41 | # image files are treated as binary by default.
42 | ###############################################################################
43 | #*.jpg binary
44 | #*.png binary
45 | #*.gif binary
46 |
47 | ###############################################################################
48 | # diff behavior for common document formats
49 | #
50 | # Convert binary document formats to text before diffing them. This feature
51 | # is only available from the command line. Turn it on by uncommenting the
52 | # entries below.
53 | ###############################################################################
54 | #*.doc diff=astextplain
55 | #*.DOC diff=astextplain
56 | #*.docx diff=astextplain
57 | #*.DOCX diff=astextplain
58 | #*.dot diff=astextplain
59 | #*.DOT diff=astextplain
60 | #*.pdf diff=astextplain
61 | #*.PDF diff=astextplain
62 | #*.rtf diff=astextplain
63 | #*.RTF diff=astextplain
64 |
--------------------------------------------------------------------------------
/docs/api-reference/ThreadSharp/Internal/ThreadsThreadManagementClient.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: ThreadsThreadManagementClient
3 | ---
4 |
5 | # [ThreadSharp](../).[Internal](./).ThreadsThreadManagementClient
6 |
7 | ## Definition
8 |
9 | ```c#
10 | public sealed class ThreadsThreadManagementClient
11 | ```
12 |
13 | Client for thread fetching & management.
14 |
15 | ## Methods
16 |
17 | | Method | Summary | Return Value |
18 | |---------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------|
19 | | `GetContainerStatusAsync(string, CancellationToken cancellationToken = default)` | Gets the status of a Threads media container. | The result, containing either the [`ThreadsMediaContainerStatus`](../Models/Api/ThreadsMediaContainerStatus) or an error. |
20 | | `GetThreadAsync(string, string[]?, CancellationToken cancellationToken = default)` | Gets a specific thread with its media container ID. | The result, containing either the [`ThreadsPost`](../Models/Api/ThreadsPost) or an error. |
21 | | `GetRepliesAsync(string, PostPagingParameters?, string[]?, bool reverse = false, CancellationToken cancellationToken = default)` | Gets replies to a specific thread. | The result, containing either a list of [`ThreadsPost`](../Models/Api/ThreadsPost)s or an error. |
22 | | `GetConversationAsync(string, PostPagingParameters?, string[]?, bool reverse = false, CancellationToken cancellationToken = default)` | Gets replies to a specific thread, along with the child replies as separate replies. | The result, containing either a list of [`ThreadsPost`](../Models/Api/ThreadsPost)s or an error. |
23 | | `ManageReplyAsync(string, bool hide, CancellationToken cancellationToken = default)` | Hides or unhides a specific thread. | The result, containing either a [`ThreadsManageReplyResult`](../Models/Api/ThreadsManageReplyResult) or an error. |
--------------------------------------------------------------------------------
/src/ThreadSharp/ThreadSharp.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netstandard2.0;net7.0
5 | 12
6 | enable
7 | enable
8 | True
9 | true
10 | True
11 |
12 | ThreadSharp
13 | © 2024 itsWindows11 & contributors.
14 | ThreadSharp
15 | https://github.com/itsWindows11/ThreadSharp
16 | https://github.com/itsWindows11/ThreadSharp
17 | threads;threads api;instagram;meta;facebook;c#;csharp;
18 |
19 | -- Version 0.7.0 --
20 | Added support for quote posts & reposts.
21 | Added set-only `AccessToken` property to `ThreadsClient`.
22 | Improved documentation.
23 | Updated to System.Text.Json v8.0.5 to fix vulnerabilities.
24 | -- Version 0.6.0 --
25 | Changed media insight retrieval models according to latest Threads API updates, and updated docs.
26 | -- Version 0.5.0 --
27 | Initial release.
28 |
29 | A C# API wrapper for the Threads API.
30 | logo.png
31 | README.md
32 | LICENSE
33 | True
34 | True
35 | snupkg
36 | 0.7.0
37 |
38 |
39 |
40 |
41 | True
42 | \
43 |
44 |
45 | True
46 | \
47 |
48 |
49 | True
50 | \
51 |
52 |
53 |
54 |
55 |
56 | all
57 | runtime; build; native; contentfiles; analyzers; buildtransitive
58 |
59 |
60 |
61 |
62 |
63 |
64 |
--------------------------------------------------------------------------------
/src/Samples/ReplyToPostSample/Program.cs:
--------------------------------------------------------------------------------
1 | using ReplyToSelfPostSample;
2 | using System.Text.Json;
3 | using ThreadSharp;
4 | using ThreadSharp.Enums;
5 | using ThreadSharp.Helpers;
6 |
7 | var accessToken = Environment.GetEnvironmentVariable("THREADS_ACCESS_TOKEN");
8 |
9 | if (accessToken is null)
10 | {
11 | Console.WriteLine("ERROR: Cannot run this sample without a Threads access token.");
12 | return;
13 | }
14 |
15 | var threadsClient = new ThreadsClient(accessToken);
16 |
17 | #pragma warning disable CA1869 // Cache and reuse 'JsonSerializerOptions' instances
18 | var serializerOptions = new JsonSerializerOptions()
19 | {
20 | WriteIndented = true,
21 | TypeInfoResolver = CustomJsonSerializerContext.Default
22 | };
23 | #pragma warning restore CA1869 // Cache and reuse 'JsonSerializerOptions' instances
24 |
25 | var threadsIdContainerResult = await threadsClient.Me.CreateTextPostAsync(
26 | "Hello World from ThreadSharp! This is a C# wrapper for the Threads API.\nThis is a read only post.",
27 | replyControl: ReplyControl.MentionedOnly
28 | );
29 |
30 | if (threadsIdContainerResult.Value is not null)
31 | {
32 | Console.WriteLine("SUCCESS: Root post has been created.");
33 | var rootPostId = threadsIdContainerResult.Value.Id;
34 |
35 | var rootPermalink = (await threadsClient.Threads.GetThreadAsync(rootPostId)).Value!.Permalink;
36 | Console.WriteLine($"PERMALINK: {rootPermalink}");
37 |
38 | var replyResult = await threadsClient.Me.CreateTextPostAsync(
39 | "Hello from a reply using ThreadSharp!",
40 | replyToId: rootPostId
41 | );
42 |
43 | Console.WriteLine("-----------");
44 |
45 | if (replyResult.Value is not null)
46 | {
47 | Console.WriteLine("SUCCESS: Reply post has been created.");
48 |
49 | var replyPermalink = (await threadsClient.Threads.GetThreadAsync(replyResult.Value.Id)).Value!.Permalink;
50 | Console.WriteLine($"PERMALINK: {replyPermalink}");
51 | }
52 | else
53 | {
54 | Console.WriteLine("ERROR: Failed to create reply post.");
55 | }
56 | } else
57 | {
58 | Console.WriteLine("ERROR: Failed to create post.");
59 |
60 | var errorData = threadsIdContainerResult.ErrorData;
61 |
62 | #pragma warning disable IL2026 // Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code
63 | #pragma warning disable IL3050 // Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling.
64 | if (errorData is not null)
65 | Console.WriteLine(JsonSerializer.Serialize(errorData, serializerOptions));
66 | #pragma warning restore IL3050 // Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling.
67 | #pragma warning restore IL2026 // Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code
68 | }
--------------------------------------------------------------------------------
/docs/api-reference/ThreadSharp/ThreadsClient.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: ThreadsClient
3 | ---
4 |
5 | # [ThreadSharp](./).ThreadsClient
6 |
7 | ## Definition
8 |
9 | ```c#
10 | public sealed class ThreadsClient : IDisposable
11 | ```
12 |
13 | The client to use for interacting with the Threads API.
14 |
15 | Derived from: `IDisposable`.
16 |
17 | ## Constructors
18 |
19 | ```c#
20 | public ThreadsClient(string accessToken)
21 | ```
22 |
23 | Creates an instance of [ThreadsClient](./ThreadsClient) which doesn't automatically renew the access token.
24 |
25 | `accessToken`: The access token to call the Threads API with.
26 |
27 | ## Properties
28 |
29 | | Property | Type | Summary | Default Value |
30 | |-------------------------|-----------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------|---------------|
31 | | MaxRetriesOnServerError | `int` | The maximum amount of retries to do when a request fails on the Threads API's end. | 5 |
32 | | AccessToken | `string` | Sets the current user's access token, mostly for emergency purposes or when a new access token is generated. | 5 |
33 | | BackingClient | `HttpClient` | The backing HttpClient for the client.
**Use of the `SendRequestAsync(HttpMethod, string)` method is preferred over using this directly.** | -- |
34 | | Me | [`ThreadsUserClient`](./Internal/ThreadsUserClient) | Client for all things user related for the current authenticated user, including posting threads. | -- |
35 | | Threads | [`ThreadsThreadManagementClient`](./Internal/ThreadsThreadManagementClient) | Client for thread fetching & management. | -- |
36 | | Insights | [`ThreadsInsightsClient`](./Internal/ThreadsInsightsClient) | Client for all things insight/data related for the current authenticated user. | -- |
37 |
38 | ## Methods
39 |
40 | | Method | Summary | Return Value |
41 | |----------------------------------------|-------------------------------------|--------------------------------------------------------|
42 | | `SendRequestAsync(HttpMethod, string)` | Sends a request to the Threads API. | `Task>>` |
--------------------------------------------------------------------------------
/ThreadSharp.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 17
4 | VisualStudioVersion = 17.10.34916.146
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ThreadSharp", "src\ThreadSharp\ThreadSharp.csproj", "{C1338975-9080-4329-BFE5-FDE07D4F9012}"
7 | EndProject
8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Samples", "Samples", "{72E3B5D5-678B-4F7B-8BBA-913E9F5F0199}"
9 | EndProject
10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ReplyToSelfPostSample", "src\Samples\ReplyToPostSample\ReplyToSelfPostSample.csproj", "{965FA02A-BC9E-4554-B03E-5E98D0D97CD2}"
11 | EndProject
12 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GetInsightsSample", "src\Samples\GetInsightsSample\GetInsightsSample.csproj", "{EFCA2A30-D389-4ECD-9C1E-3804AC831A7B}"
13 | EndProject
14 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GetCurrentUserProfileAndPrintDetailsSample", "src\Samples\GetCurrentUserProfileAndPrintDetails\GetCurrentUserProfileAndPrintDetailsSample.csproj", "{DB3BEA94-6DD2-460A-858D-7085822A9BF0}"
15 | EndProject
16 | Global
17 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
18 | Debug|Any CPU = Debug|Any CPU
19 | Release|Any CPU = Release|Any CPU
20 | EndGlobalSection
21 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
22 | {C1338975-9080-4329-BFE5-FDE07D4F9012}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
23 | {C1338975-9080-4329-BFE5-FDE07D4F9012}.Debug|Any CPU.Build.0 = Debug|Any CPU
24 | {C1338975-9080-4329-BFE5-FDE07D4F9012}.Release|Any CPU.ActiveCfg = Release|Any CPU
25 | {C1338975-9080-4329-BFE5-FDE07D4F9012}.Release|Any CPU.Build.0 = Release|Any CPU
26 | {965FA02A-BC9E-4554-B03E-5E98D0D97CD2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
27 | {965FA02A-BC9E-4554-B03E-5E98D0D97CD2}.Debug|Any CPU.Build.0 = Debug|Any CPU
28 | {965FA02A-BC9E-4554-B03E-5E98D0D97CD2}.Release|Any CPU.ActiveCfg = Release|Any CPU
29 | {965FA02A-BC9E-4554-B03E-5E98D0D97CD2}.Release|Any CPU.Build.0 = Release|Any CPU
30 | {EFCA2A30-D389-4ECD-9C1E-3804AC831A7B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
31 | {EFCA2A30-D389-4ECD-9C1E-3804AC831A7B}.Debug|Any CPU.Build.0 = Debug|Any CPU
32 | {EFCA2A30-D389-4ECD-9C1E-3804AC831A7B}.Release|Any CPU.ActiveCfg = Release|Any CPU
33 | {EFCA2A30-D389-4ECD-9C1E-3804AC831A7B}.Release|Any CPU.Build.0 = Release|Any CPU
34 | {DB3BEA94-6DD2-460A-858D-7085822A9BF0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
35 | {DB3BEA94-6DD2-460A-858D-7085822A9BF0}.Debug|Any CPU.Build.0 = Debug|Any CPU
36 | {DB3BEA94-6DD2-460A-858D-7085822A9BF0}.Release|Any CPU.ActiveCfg = Release|Any CPU
37 | {DB3BEA94-6DD2-460A-858D-7085822A9BF0}.Release|Any CPU.Build.0 = Release|Any CPU
38 | EndGlobalSection
39 | GlobalSection(SolutionProperties) = preSolution
40 | HideSolutionNode = FALSE
41 | EndGlobalSection
42 | GlobalSection(NestedProjects) = preSolution
43 | {965FA02A-BC9E-4554-B03E-5E98D0D97CD2} = {72E3B5D5-678B-4F7B-8BBA-913E9F5F0199}
44 | {EFCA2A30-D389-4ECD-9C1E-3804AC831A7B} = {72E3B5D5-678B-4F7B-8BBA-913E9F5F0199}
45 | {DB3BEA94-6DD2-460A-858D-7085822A9BF0} = {72E3B5D5-678B-4F7B-8BBA-913E9F5F0199}
46 | EndGlobalSection
47 | GlobalSection(ExtensibilityGlobals) = postSolution
48 | SolutionGuid = {C52DAFE2-C687-440B-B831-227A4C22F227}
49 | EndGlobalSection
50 | EndGlobal
51 |
--------------------------------------------------------------------------------
/src/ThreadSharp/Converters/StringToThreadsPublishingErrorCodeConverter.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json;
2 | using System.Text.Json.Serialization;
3 | using ThreadSharp.Enums;
4 |
5 | namespace ThreadSharp.Converters;
6 |
7 | internal class StringToThreadsPublishingErrorCodeConverter : JsonConverter
8 | {
9 | public override ThreadsPublishingErrorCode Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
10 | {
11 | return reader.GetString() switch
12 | {
13 | "FAILED_DOWNLOADING_VIDEO" => ThreadsPublishingErrorCode.FailedDownloadingVideo,
14 | "FAILED_PROCESSING_AUDIO" => ThreadsPublishingErrorCode.FailedProcessingAudio,
15 | "FAILED_PROCESSING_VIDEO" => ThreadsPublishingErrorCode.FailedProcessingVideo,
16 | "INVALID_ASPECT_RATIO" => ThreadsPublishingErrorCode.InvalidAspectRatio,
17 | "INVALID_BIT_RATE" => ThreadsPublishingErrorCode.InvalidBitRate,
18 | "INVALID_DURATION" => ThreadsPublishingErrorCode.InvalidDuration,
19 | "INVALID_FRAME_RATE" => ThreadsPublishingErrorCode.InvalidFrameRate,
20 | "INVALID_AUDIO_CHANNELS" => ThreadsPublishingErrorCode.InvalidAudioChannels,
21 | "INVALID_AUDIO_CHANNEL_LAYOUT" => ThreadsPublishingErrorCode.InvalidAudioChannelLayout,
22 | _ => ThreadsPublishingErrorCode.Unknown
23 | };
24 | }
25 |
26 | public override void Write(Utf8JsonWriter writer, ThreadsPublishingErrorCode value, JsonSerializerOptions options)
27 | {
28 | switch (value)
29 | {
30 | case ThreadsPublishingErrorCode.FailedDownloadingVideo:
31 | writer.WriteStringValue("FAILED_DOWNLOADING_VIDEO");
32 | break;
33 | case ThreadsPublishingErrorCode.FailedProcessingAudio:
34 | writer.WriteStringValue("FAILED_PROCESSING_AUDIO");
35 | break;
36 | case ThreadsPublishingErrorCode.FailedProcessingVideo:
37 | writer.WriteStringValue("FAILED_PROCESSING_VIDEO");
38 | break;
39 | case ThreadsPublishingErrorCode.InvalidAspectRatio:
40 | writer.WriteStringValue("INVALID_ASPECT_RATIO");
41 | break;
42 | case ThreadsPublishingErrorCode.InvalidBitRate:
43 | writer.WriteStringValue("INVALID_BIT_RATE");
44 | break;
45 | case ThreadsPublishingErrorCode.InvalidDuration:
46 | writer.WriteStringValue("INVALID_DURATION");
47 | break;
48 | case ThreadsPublishingErrorCode.InvalidFrameRate:
49 | writer.WriteStringValue("INVALID_FRAME_RATE");
50 | break;
51 | case ThreadsPublishingErrorCode.InvalidAudioChannels:
52 | writer.WriteStringValue("INVALID_AUDIO_CHANNELS");
53 | break;
54 | case ThreadsPublishingErrorCode.InvalidAudioChannelLayout:
55 | writer.WriteStringValue("INVALID_AUDIO_CHANNEL_LAYOUT");
56 | break;
57 | case ThreadsPublishingErrorCode.Unknown:
58 | writer.WriteStringValue("UNKNOWN");
59 | break;
60 | default:
61 | throw new ArgumentOutOfRangeException(nameof(value), value, null);
62 | }
63 | }
64 | }
--------------------------------------------------------------------------------
/docs/.vitepress/theme/style.css:
--------------------------------------------------------------------------------
1 | /**
2 | * Customize default theme styling by overriding CSS variables:
3 | * https://github.com/vuejs/vitepress/blob/main/src/client/theme-default/styles/vars.css
4 | */
5 |
6 | /**
7 | * Colors
8 | * -------------------------------------------------------------------------- */
9 |
10 | :root {
11 | --vp-c-brand: #F56040;
12 | --vp-c-brand-light: #f67255;
13 | --vp-c-brand-lighter: #f99a86;
14 | --vp-c-brand-lightest: #fbc2b6;
15 | --vp-c-brand-dark: #aa2509;
16 | --vp-c-brand-darker: #791b06;
17 | --vp-c-brand-dimm: rgba(73, 16, 4, 0.2);
18 |
19 | --vp-c-brand-1: var(--vp-c-brand-dark);
20 | --vp-c-brand-2: var(--vp-c-brand-darker);
21 | --vp-c-brand-3: var(--vp-c-brand-darkest);
22 |
23 | --vp-c-bg: #ffffff;
24 | --vp-c-bg-alt: #f7f6f6;
25 | --vp-c-bg-elv: #ffffff;
26 | --vp-c-bg-soft: #f7f6f6;
27 | }
28 |
29 | /**
30 | * Brand Colors
31 | * -------------------------------------------------------------------------- */
32 |
33 | html.dark {
34 | --vp-c-brand-1: var(--vp-c-brand-light);
35 | --vp-c-brand-2: var(--vp-c-brand-lighter);
36 | --vp-c-brand-3: var(--vp-c-brand-lightest);
37 |
38 | --vp-c-bg: #1f1b1b;
39 | --vp-c-bg-alt: #181616;
40 | --vp-c-bg-elv: #272020;
41 | --vp-c-bg-soft: #272020;
42 | }
43 |
44 | /**
45 | * Component: Button
46 | * -------------------------------------------------------------------------- */
47 |
48 | :root {
49 | --vp-button-brand-border: var(--vp-c-brand-light);
50 | --vp-button-brand-text: var(--vp-c-white);
51 | --vp-button-brand-bg: var(--vp-c-brand);
52 | --vp-button-brand-hover-border: var(--vp-c-brand-light);
53 | --vp-button-brand-hover-text: var(--vp-c-white);
54 | --vp-button-brand-hover-bg: var(--vp-c-brand-light);
55 | --vp-button-brand-active-border: var(--vp-c-brand-light);
56 | --vp-button-brand-active-text: var(--vp-c-white);
57 | --vp-button-brand-active-bg: var(--vp-button-brand-bg);
58 | }
59 |
60 | /**
61 | * Component: Home
62 | * -------------------------------------------------------------------------- */
63 |
64 | :root {
65 | --vp-home-hero-name-color: transparent;
66 | --vp-home-hero-name-background: -webkit-linear-gradient(
67 | -120deg,
68 | #405DE6 0%,
69 | #5B51D8 15%,
70 | #833AB4 36%,
71 | #F56040 64%,
72 | #FFDC80 100%
73 | );
74 | }
75 |
76 | div.VPHero img.VPImage {
77 | -webkit-user-drag: none;
78 | -khtml-user-drag: none;
79 | -moz-user-drag: none;
80 | -o-user-drag: none;
81 | user-drag: none;
82 | }
83 |
84 | @media (min-width: 960px) {
85 | div.VPHero img.VPImage {
86 | margin-top: 50px;
87 | }
88 | }
89 |
90 | /**
91 | * Component: Custom Block
92 | * -------------------------------------------------------------------------- */
93 |
94 | :root {
95 | --vp-custom-block-tip-border: var(--vp-c-brand);
96 | --vp-custom-block-tip-text: var(--vp-c-brand-darker);
97 | --vp-custom-block-tip-bg: var(--vp-c-brand-dimm);
98 | }
99 |
100 | .dark {
101 | --vp-custom-block-tip-border: var(--vp-c-brand);
102 | --vp-custom-block-tip-text: var(--vp-c-brand-lightest);
103 | --vp-custom-block-tip-bg: var(--vp-c-brand-dimm);
104 | }
105 |
106 | /**
107 | * Component: Algolia
108 | * -------------------------------------------------------------------------- */
109 |
110 | .DocSearch {
111 | --docsearch-primary-color: var(--vp-c-brand) !important;
112 | }
--------------------------------------------------------------------------------
/docs/docs/threads/get-current-user-threads.md:
--------------------------------------------------------------------------------
1 | # Getting the Current User's Threads
2 |
3 | This document explains how to get posts and replies created by the currently authenticated user, in a few different ways.
4 |
5 | ## Getting Posts
6 |
7 | To get the user's posts, you can call [`GetThreadsAsync()`](/api-reference/ThreadSharp/Internal/ThreadsUserClient#methods) method on the [user client](#user-client).
8 |
9 | ```c#
10 | ThreadsClient threadsClient = GetThreadsClient();
11 |
12 | var postsResult = await threadsClient.Me.GetThreadsAsync(limit: 10);
13 |
14 | if (postsResult.IsSuccessStatusCode && postsResult.Value is not null)
15 | {
16 | List posts = postsResult.Value;
17 |
18 | // Enumerate the list and/or operate on one of the posts.
19 | } else
20 | {
21 | // Handle gracefully based on the exception data in the result's "Error" property & the Value if exists.
22 | }
23 | ```
24 |
25 | ## Getting Replies
26 |
27 | To get the user's replies, you can call [`GetRepliesAsync()`](/api-reference/ThreadSharp/Internal/ThreadsUserClient#methods) method on the [user client](#user-client). This returns a list of posts that represent each reply, if the request was successful.
28 |
29 | ```c#
30 | ThreadsClient threadsClient = GetThreadsClient();
31 |
32 | var repliesResult = await threadsClient.Me.GetRepliesAsync(limit: 10);
33 |
34 | if (repliesResult.IsSuccessStatusCode && repliesResult.Value is not null)
35 | {
36 | List replies = repliesResult.Value;
37 |
38 | // Enumerate the list and/or operate on one of the replies.
39 | } else
40 | {
41 | // Handle gracefully based on the exception data in the result's "Error" property & the Value if exists.
42 | }
43 | ```
44 |
45 | ## Getting specific fields only
46 |
47 | You can get specific fields to return in your response by setting the `fields` parameter to an exhaustive list of fields to request, if you don't want the extra data that comes by default.
48 |
49 | ::: tip NOTES
50 | - The other fields not included in the list passed to the `fields` parameter will be set to `null`.
51 | - Any fields not supported by ThreadSharp yet will be included in the response's [`UnrecognizedData`](/api-reference/ThreadSharp/Models/BaseJsonUnrecognizedDataModel) property.
52 | - There's no guarantee that all the fields will be returned as is. For example, in some cases, the permalink is omitted.
53 | - The post ID is always included by default.
54 | :::
55 |
56 | Consider the following example to get only the post text as well as the media product type, the author's username and the permalink:
57 |
58 | ```c#
59 | ThreadsClient threadsClient = GetThreadsClient();
60 |
61 | var postsResult = await threadsClient.Me.GetThreadsAsync(fields: ["text", "permalink", "username", "media_product_type"], limit: 10);
62 |
63 | if (postsResult.IsSuccessStatusCode && postsResult.Value is not null)
64 | {
65 | List posts = postsResult.Value;
66 |
67 | // Enumerate the list and/or operate on one of the posts.
68 |
69 | // In this case, only the post's Id, Text, Permalink, Username, MediaProductType properties
70 | // will be non-null unless in specific cases like mentioned in the note above.
71 | } else
72 | {
73 | // Handle gracefully based on the exception data in the result's "Error" property & the Value if exists.
74 | }
75 | ```
76 |
77 | ## Definitions
78 |
79 | ### User Client
80 |
81 | An instance of [`ThreadsUserClient`](/api-reference/ThreadSharp/Internal/ThreadsUserClient), usually obtained from a `ThreadsClient`'s `Me` property.
--------------------------------------------------------------------------------
/docs/api-reference/ThreadSharp/Models/Api/Insights/ThreadsUserInsightFollowerDemographicsData.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: ThreadsUserInsightFollowerDemographicsData
3 | ---
4 |
5 | # [ThreadSharp](../../../).[Models](../../).[Api](../).[Insights](./).ThreadsUserInsightFollowerDemographicsData
6 |
7 | ## Definition
8 |
9 | ```c#
10 | public sealed class ThreadsUserInsightFollowerDemographicsData : ThreadsUserInsightDataBase
11 | ```
12 |
13 | Follower demographic data for Threads user insights.
14 |
15 | Derived from: [`ThreadsUserInsightDataBase`](../ThreadsUserInsightDataBase) --> [`ThreadsIdContainer`](../ThreadsIdContainer) --> [`BaseJsonUnrecognizedDataModel`](../../BaseJsonUnrecognizedDataModel).
16 |
17 | ::: tip NOTE
18 | In the relevant [Threads API reference for user insights](https://developers.facebook.com/docs/threads/reference/insights#get---threads-user-id--threads-insights), the property `name` is omitted as it is used as a type discriminator in the `System.Text.Json`, which is used for (de)serialization in ThreadSharp.
19 | :::
20 |
21 | ## Properties
22 |
23 | | Property | Type | Summary | Default Value |
24 | |------------|-----------------------------------------------------------------------------------------------|------------------------------|---------------|
25 | | TotalValue | [`ThreadsUserInsightFollowerDemographicsValue`](#threadsuserinsightfollowerdemographicsvalue) | The total value of the data. | `null` |
26 |
27 | ## Nested Classes
28 |
29 | ---
30 |
31 | ### ThreadsUserInsightFollowerDemographicsValue
32 |
33 | #### Definition
34 |
35 | ```c#
36 | public sealed class ThreadsUserInsightFollowerDemographicsValue
37 | ```
38 |
39 | Total value of the data.
40 |
41 | #### Properties
42 |
43 | | Property | Type | Summary | Default Value |
44 | |---------------|---------------------------------------------------------------------------------------------------------------------|-----------------|---------------|
45 | | BreakdownData | [`List`](#threadsuserinsightfollowerdemographicsbreakdowndata) | Breakdown data. | -- |
46 |
47 | ### ThreadsUserInsightFollowerDemographicsValue.ThreadsUserInsightFollowerDemographicsBreakdownData
48 |
49 | #### Definition
50 |
51 | ```c#
52 | public sealed class ThreadsUserInsightFollowerDemographicsBreakdownData
53 | ```
54 |
55 | Breakdown data.
56 |
57 | #### Properties
58 |
59 | | Property | Type | Summary | Default Value |
60 | |---------------|----------------------------------------------------------------------|---------------------------------------------|---------------|
61 | | DimensionKeys | `string[]` | Dimension keys as specified in the request. | -- |
62 | | Results | [`List`]() | The results of the breakdown data. | -- |
63 |
64 | ### ThreadsUserInsightFollowerDemographicsValue.ThreadsUserInsightFollowerDemographicsBreakdownData.ThreadsUserInsightFollowerDemographicsBreakdownDataValue
65 |
66 | #### Definition
67 |
68 | ```c#
69 | public sealed class ThreadsUserInsightFollowerDemographicsBreakdownDataValue
70 | ```
71 |
72 | The breakdown data item.
73 |
74 | #### Properties
75 |
76 | | Property | Type | Summary | Default Value |
77 | |-----------------|------------|---------------------------------------|---------------|
78 | | DimensionValues | `string[]` | The dimension values. | -- |
79 | | Value | `int` | The value of the breakdown data item. | -- |
--------------------------------------------------------------------------------
/docs/.vitepress/config.ts:
--------------------------------------------------------------------------------
1 | import { DefaultTheme, defineConfig } from 'vitepress'
2 | import fs from "fs"
3 | import path from 'path';
4 |
5 | const getSidebarItems = (dir: string, allItemsCollapsedByDefault: boolean | undefined = undefined) => {
6 | const items: DefaultTheme.SidebarItem[] = [];
7 | const files = fs.readdirSync(dir, { withFileTypes: true });
8 |
9 | for (const file of files) {
10 | const basename = path.basename(file.name, ".md");
11 |
12 | if (basename.includes("index"))
13 | continue;
14 |
15 | if (file.isDirectory()) {
16 | items.push({
17 | text: basename,
18 | link: `${dir}/${basename}`,
19 | items: getSidebarItems(`${dir}/${basename}`, allItemsCollapsedByDefault),
20 | collapsed: allItemsCollapsedByDefault
21 | });
22 | } else {
23 | items.push({
24 | text: basename,
25 | link: `${dir}/${basename}`
26 | });
27 | }
28 | }
29 |
30 | return items;
31 | };
32 |
33 | function docsItems(): (DefaultTheme.NavItemChildren | DefaultTheme.NavItemWithLink | DefaultTheme.SidebarItem)[] {
34 | return [
35 | {
36 | text: 'Getting Started',
37 | link: '/docs/getting-started'
38 | },
39 | {
40 | text: 'Threads',
41 | collapsed: true,
42 | items: [
43 | {
44 | text: "Getting the current user's threads.",
45 | link: '/docs/threads/get-current-user-threads'
46 | },
47 | {
48 | text: 'Getting threads by ID & replies.',
49 | link: '/docs/threads/getting-threads'
50 | },
51 | {
52 | text: 'Creating a new thread.',
53 | link: '/docs/threads/create-new-thread'
54 | }
55 | ]
56 | },
57 | {
58 | text: "Getting the current user's profile.",
59 | link: '/docs/profile'
60 | },
61 | {
62 | text: "Insights.",
63 | link: '/docs/insights'
64 | },
65 | {
66 | text: "Calling API endpoints unsupported by ThreadSharp.",
67 | link: '/docs/calling-unsupported-endpoints'
68 | }
69 | ]
70 | }
71 |
72 | function samplesItems() {
73 | return [
74 | { text: 'Replying To Self', link: 'https://github.com/itsWindows11/ThreadSharp/tree/main/src/Samples/ReplyToPostSample' },
75 | { text: 'Getting User Profile Details', link: 'https://github.com/itsWindows11/ThreadSharp/tree/main/src/Samples/GetCurrentUserProfileAndPrintDetails' },
76 | { text: 'Getting User Insights', link: 'https://github.com/itsWindows11/ThreadSharp/tree/main/src/Samples/GetInsightsSample' }
77 | ]
78 | }
79 |
80 | // https://vitepress.dev/reference/site-config
81 | export default defineConfig({
82 | title: "ThreadSharp",
83 | description: "A C# API wrapper for the Threads API.",
84 | cleanUrls: true,
85 | lastUpdated: true,
86 | base: "/ThreadSharp/",
87 | sitemap: {
88 | hostname: "https://itswindows11.github.io/ThreadSharp"
89 | },
90 | themeConfig: {
91 | // https://vitepress.dev/reference/default-theme-config
92 | nav: [
93 | { text: 'Home', link: '/' },
94 | {
95 | text: 'Documentation',
96 | items: docsItems() as (DefaultTheme.NavItemChildren | DefaultTheme.NavItemWithLink)[]
97 | },
98 | {
99 | text: 'Samples',
100 | items: samplesItems() as (DefaultTheme.NavItemComponent | DefaultTheme.NavItemChildren | DefaultTheme.NavItemWithLink)[]
101 | },
102 | { text: 'API Reference', link: '/api-reference' },
103 | ],
104 |
105 | sidebar: [
106 | {
107 | text: 'Documentation',
108 | collapsed: false,
109 | items: docsItems() as DefaultTheme.SidebarItem[]
110 | },
111 | {
112 | text: 'Samples',
113 | collapsed: false,
114 | items: samplesItems() as DefaultTheme.SidebarItem[]
115 | },
116 | {
117 | text: 'API Reference',
118 | collapsed: true,
119 | items: getSidebarItems("api-reference/", true)
120 | }
121 | ],
122 |
123 | socialLinks: [
124 | { icon: 'github', link: 'https://github.com/itsWindows11/ThreadSharp' }
125 | ]
126 | }
127 | });
--------------------------------------------------------------------------------
/docs/docs/threads/getting-threads.md:
--------------------------------------------------------------------------------
1 | # Getting Threads by ID & Replies
2 |
3 | This document explains how to get a media container or post by its ID, as well as getting its replies if the post's published.
4 |
5 | ## Getting a thread by ID
6 |
7 | To get a thread from its ID, you can use the [`GetThreadAsync()`](/api-reference/ThreadSharp/Internal/ThreadsThreadManagementClient#methods) method on the [thread management client](#thread-management-client), providing it with a thread or media container ID, and optionally the specific fields you want to retrieve.
8 |
9 | Below is an example that gets a thread with all the available fields (i.e. the `fields` parameter is not set), then prints the post permalink and the text.
10 |
11 | ```c#
12 | // GetThreadsClient() is a placeholder method for getting a ThreadsClient.
13 | var threadsClient = GetThreadsClient();
14 |
15 | var threadResult = await threadsClient.Threads.GetThreadAsync("[thread ID]");
16 |
17 | if (threadResult.IsSuccessStatusCode && threadResult.Value is not null)
18 | {
19 | ThreadsPost post = threadResult.Value;
20 |
21 | // Print the permalink as well as the text.
22 | Console.WriteLine($"Permalink: {post.Permalink}"");
23 | Console.WriteLine($"Text: {post.Text}");
24 | } else
25 | {
26 | // Handle gracefully based on the exception data in the result's "Error" property & the Value if exists.
27 | }
28 | ```
29 |
30 | ## Getting replies of a thread
31 |
32 | With a thread's ID, you can also retrieve replies, or the entire conversation, which is a list of replies with its child replies as separate items. No nesting involved here.
33 |
34 | ### Getting only the top-level replies
35 |
36 | To get only the top-level replies, you can use the [`GetRepliesAsync()`](/api-reference/ThreadSharp/Internal/ThreadsThreadManagementClient#methods) method on the [thread management client](#thread-management-client), providing it with a thread or media container ID, and optionally the specific fields you want to retrieve for each reply.
37 |
38 | Below is an example that retrieves the top-level replies:
39 |
40 | ```c#
41 | // GetThreadsClient() is a placeholder method for getting a ThreadsClient.
42 | var threadsClient = GetThreadsClient();
43 |
44 | var repliesResult = await threadsClient.Threads.GetRepliesAsync("[thread ID]");
45 |
46 | if (repliesResult.IsSuccessStatusCode && repliesResult.Value is not null)
47 | {
48 | List replies = repliesResult.Value;
49 |
50 | // Operate on each reply in the list.
51 | } else
52 | {
53 | // Handle gracefully based on the exception data in the result's "Error" property & the Value if exists.
54 | }
55 | ```
56 |
57 | ### Getting replies w/ children replies
58 |
59 | If you want the top-level replies along with their children, you can use the [`GetConversationAsync()`](/api-reference/ThreadSharp/Internal/ThreadsThreadManagementClient#methods) method on the [thread management client](#thread-management-client), providing it with a thread or media container ID, and optionally the specific fields you want to retrieve for each reply.
60 |
61 | Below is an example that retrieves the replies, along with the children replies, and filters child replies from the top level ones:
62 |
63 | ```c#
64 | // GetThreadsClient() is a placeholder method for getting a ThreadsClient.
65 | var threadsClient = GetThreadsClient();
66 |
67 | var repliesResult = await threadsClient.Threads.GetConversationAsync("[thread ID]");
68 |
69 | if (repliesResult.IsSuccessStatusCode && repliesResult.Value is not null)
70 | {
71 | List replies = repliesResult.Value;
72 |
73 | foreach (var replyPost in replies)
74 | {
75 | if (replyPost.RepliedTo is not null)
76 | // Child reply, use its Id property to get the reply ID.
77 | else
78 | {
79 | // Top-level reply.
80 | }
81 | }
82 | } else
83 | {
84 | // Handle gracefully based on the exception data in the result's "Error" property & the Value if exists.
85 | }
86 | ```
87 |
88 | A recursive reply tree can be created by filtering the replies and seeing if the `RepliedTo`'s `Id` property matches the top-level reply.
89 |
90 | ## Definitions
91 |
92 | ### Thread Management Client
93 |
94 | An instance of [`ThreadsThreadManagementClient`](/api-reference/ThreadSharp/Internal/ThreadsThreadManagementClient), usually obtained from a `ThreadsClient`'s `Threads` property.
--------------------------------------------------------------------------------
/docs/docs/getting-started.md:
--------------------------------------------------------------------------------
1 | # Getting Started
2 |
3 | ## Installing ThreadSharp
4 |
5 | Below is a shield that shows the latest version of ThreadSharp that's available:
6 |
7 | 
8 |
9 | In order to include ThreadSharp in your project, you can install the NuGet package as follows:
10 |
11 | ::: code-group
12 | ```ps [NuGet Package Manager Console (Visual Studio)]
13 | Install-Package ThreadSharp
14 | ```
15 |
16 | ```ps [.NET CLI]
17 | dotnet add package ThreadSharp --version [version]
18 | ```
19 | :::
20 |
21 | Replace `[version]` with the latest version shown in the badge above.
22 |
23 | ## Using ThreadSharp
24 |
25 | ### The Client
26 |
27 | The client for ThreadSharp, [`ThreadsClient`](/api-reference/ThreadSharp/ThreadsClient), contains different sub-clients for different use cases, listed below.
28 |
29 | - For any operations related to the currently authenticated user, including posting threads, you can use the [`ThreadsClient.Me`](/api-reference/ThreadSharp/ThreadsClient#properties) property to access the [`ThreadsUserClient`](/api-reference/ThreadSharp/Internal/ThreadsUserClient).
30 | - For any operations related to thread management (excluding post/media container creation), you can use the [`ThreadsClient.Threads`](/api-reference/ThreadSharp/ThreadsClient#properties) property to access the [`ThreadsThreadManagementClient`](/api-reference/ThreadSharp/Internal/ThreadsThreadManagementClient).
31 | - For any operations related to insights, you can use the [`ThreadsClient.Insights`](/api-reference/ThreadSharp/ThreadsClient#properties) property to access the [`ThreadsInsightsClient`](/api-reference/ThreadSharp/Internal/ThreadsInsightsClient).
32 |
33 | ### Samples
34 |
35 | You can find a list of samples in the menu (accessed through the hamburger icon), which can be helpful for certain use cases.
36 |
37 | Samples can be also accessed [on GitHub](https://github.com/itsWindows11/ThreadSharp/tree/main/src/Samples/).
38 |
39 | ### Nullable Properties in Models
40 |
41 | In most of ThreadSharp's models for responses from the Threads API, all properties are nullable to allow strong typing while also allowing flexibility for choosing which fields to return, since the Threads API is GraphQL-like.
42 |
43 | ### Responses
44 |
45 | ThreadSharp returns responses in the form of a result containing either a model or an exception describing what happened.
46 |
47 | For example, consider the below snippet for getting only the username, profile picture and biography of the current user's Threads profile:
48 |
49 | ```c#
50 | // GetThreadsClient() is a placeholder method for getting a ThreadsClient.
51 | var threadsClient = GetThreadsClient();
52 |
53 | var getProfileResult = await threadsClient.Me.GetAsync(fields: ["username", "name", "threads_profile_picture_url"]);
54 |
55 | if (getProfileResult.IsSuccessStatusCode && getProfileResult.Value is not null)
56 | {
57 | ThreadsProfile profile = getProfileResult.Value;
58 |
59 | // Write the username (aka. handle) as well as name.
60 | Console.WriteLine($"@{profile.Username}: {profile.Name}");
61 | } else
62 | {
63 | // Handle gracefully based on the exception data in the result's "Error" property & the Value if exists.
64 | }
65 | ```
66 |
67 | The result here is handled without throwing any exception, unless there's an error from the library consumer's end. The library consumer can choose to throw the error or handle it like above.
68 |
69 | Each model contained in the responses also supports getting extra data in the JSON which is not yet supported by ThreadSharp, for futureproofing.
70 |
71 | ::: tip NOTE
72 | If you frequently use the `UnrecognizedData` property for fields added to the Threads API but not yet supported by ThreadSharp, please report your use cases in the [issues tab](https://github.com/itsWindows11/ThreadSharp/issues) in the repository, and it will be worked on.
73 | :::
74 |
75 | ### Authentication & Token Refreshing
76 |
77 | The Threads API conforms to the OAuth2 standard for authentication, so the library consumer will have to use the specific OAuth2 library for their platform.
78 |
79 | Token refreshing is not handled automatically by ThreadSharp for security reasons, as a random CSRF token is needed to securely exchange tokens. As a result, the [`ThreadsClient`](/api-reference/ThreadSharp/ThreadsClient) only takes an access token.
80 |
81 | ::: tip NOTE
82 | To handle the case where an access token is expired upon calling an API, check if the result's `Error` contains a [`ThreadsUnauthenticatedException`](/api-reference/ThreadSharp/Exceptions/ThreadsUnauthenticatedException).
83 | :::
--------------------------------------------------------------------------------
/src/ThreadSharp/Models/Api/ThreadsPost.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json.Serialization;
2 | using ThreadSharp.Converters;
3 | using ThreadSharp.Enums;
4 |
5 | namespace ThreadSharp.Models.Api;
6 |
7 | ///
8 | /// Represents a Threads post.
9 | ///
10 | public sealed class ThreadsPost : ThreadsIdContainer
11 | {
12 | ///
13 | /// The media product type. Usually has the value "THREADS".
14 | ///
15 | [JsonPropertyName("media_product_type")]
16 | public string? MediaProductType { get; set; }
17 |
18 | ///
19 | /// The post's media type.
20 | ///
21 | [JsonPropertyName("media_type")]
22 | [JsonConverter(typeof(StringToThreadsPostMediaTypeConverter))]
23 | public ThreadsPostMediaType? MediaType { get; set; }
24 |
25 | ///
26 | /// The media URL of the post, if it contains a single image or video.
27 | ///
28 | [JsonPropertyName("media_url")]
29 | public string? MediaUrl { get; set; }
30 |
31 | ///
32 | /// The attached link's URL, if exists.
33 | ///
34 | [JsonPropertyName("link_attachment_url")]
35 | public string? LinkAttachmentUrl { get; set; }
36 |
37 | ///
38 | /// The post's permalink.
39 | ///
40 | [JsonPropertyName("permalink")]
41 | public string? Permalink { get; set; }
42 |
43 | ///
44 | /// The owner data.
45 | ///
46 | [JsonPropertyName("owner")]
47 | public ThreadsIdContainer? Owner { get; set; }
48 |
49 | ///
50 | /// The post author's username.
51 | ///
52 | [JsonPropertyName("username")]
53 | public string? Username { get; set; }
54 |
55 | ///
56 | /// The post text.
57 | ///
58 | [JsonPropertyName("text")]
59 | public string? Text { get; set; }
60 |
61 | ///
62 | /// The date the post was created.
63 | ///
64 | [JsonPropertyName("timestamp")]
65 | [JsonConverter(typeof(DateTimeConverter))]
66 | public DateTime? Timestamp { get; set; }
67 |
68 | ///
69 | /// The post shortcode.
70 | ///
71 | [JsonPropertyName("shortcode")]
72 | public string? Shortcode { get; set; }
73 |
74 | ///
75 | /// The URL of the post thumbnail, usually from a link.
76 | ///
77 | [JsonPropertyName("thumbnail_url")]
78 | public string? ThumbnailUrl { get; set; }
79 |
80 | ///
81 | /// List of children media container IDs, if the post is a carousel post.
82 | ///
83 | [JsonPropertyName("children")]
84 | public ThreadsDataContainer>? Children { get; set; }
85 |
86 | ///
87 | /// Whether or not the post quotes someone else's post.
88 | ///
89 | [JsonPropertyName("is_quote_post")]
90 | public bool IsQuotePost { get; set; } = false;
91 |
92 | ///
93 | /// Alt text for single image/video post.
94 | ///
95 | [JsonPropertyName("alt_text")]
96 | public string? AltText { get; set; }
97 |
98 | ///
99 | /// Whether or not the post has replies.
100 | ///
101 | [JsonPropertyName("has_replies")]
102 | public bool HasReplies { get; set; } = false;
103 |
104 | ///
105 | /// Whether or not the post is a reply to another post.
106 | ///
107 | [JsonPropertyName("is_reply")]
108 | public bool IsReply { get; set; } = false;
109 |
110 | ///
111 | /// Whether the reply is owned by the currently authenticated user.
112 | ///
113 | [JsonPropertyName("is_reply_owned_by_me")]
114 | public bool IsReplyOwnedByMe { get; set; } = false;
115 |
116 | ///
117 | /// The root post.
118 | ///
119 | [JsonPropertyName("root_post")]
120 | public ThreadsIdContainer? RootPost { get; set; }
121 |
122 | ///
123 | /// The media container ID of the parent post.
124 | ///
125 | [JsonPropertyName("replied_to")]
126 | public ThreadsIdContainer? RepliedTo { get; set; }
127 |
128 | ///
129 | /// The post's hide status.
130 | ///
131 | // TODO: Determine if this should be an enum.
132 | [JsonPropertyName("hide_status")]
133 | public string? HideStatus { get; set; }
134 |
135 | ///
136 | /// The reply audience.
137 | ///
138 | // TODO: Determine if this should be an enum.
139 | [JsonPropertyName("reply_audience")]
140 | public string? ReplyAudience { get; set; }
141 |
142 | ///
143 | public override string ToString()
144 | {
145 | return $"Username: @{Username}, Text: \"{Text}\"";
146 | }
147 | }
--------------------------------------------------------------------------------
/docs/api-reference/ThreadSharp/Models/Api/ThreadsPost.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: ThreadsPost
3 | ---
4 |
5 | # [ThreadSharp](../../).[Models](../).[Api](./).ThreadsPost
6 |
7 | ## Definition
8 |
9 | ```c#
10 | public sealed class ThreadsPost : ThreadsIdContainer
11 | ```
12 |
13 | Represents a Threads post.
14 |
15 | Derived from: [`ThreadsIdContainer`](./ThreadsIdContainer) --> [`BaseJsonUnrecognizedDataModel`](../BaseJsonUnrecognizedDataModel).
16 |
17 | ## Properties
18 |
19 | | Property | Type | Summary | Default Value |
20 | |-------------------|-----------------------------------------------------------------------------|-----------------------------------------------------------------------|---------------|
21 | | MediaProductType | `string` | The media product type. Usually has the value `"THREADS"`. | `null` |
22 | | MediaType | [`ThreadsPostMediaType`](../../Enums/ThreadsPostMediaType) | The post's media type. | `null` |
23 | | MediaUrl | `string` | The media URL of the post, if it contains a single image or video. | `null` |
24 | | LinkAttachmentUrl | `string` | The attached link's URL, if exists. | `null` |
25 | | Permalink | `string` | The post's permalink. | `null` |
26 | | Owner | [`ThreadsIdContainer`](./ThreadsIdContainer) | The owner data. | `null` |
27 | | Username | `string` | The post author's username. | `null` |
28 | | Text | `string` | The post text. | `null` |
29 | | Timestamp | `DateTime?` | The date the post was created. | `null` |
30 | | Shortcode | `string` | The post's shortcode. | `null` |
31 | | ThumbnailUrl | `string` | The URL of the post thumbnail, usually from a link. | `null` |
32 | | Children | [`ThreadsDataContainer>`](../ThreadsDataContainer) | List of children media container IDs, if the post is a carousel post. | `null` |
33 | | IsQuotePost | `bool` | Whether or not the post quotes someone else's post. | `false` |
34 | | AltText | `string` | Alt text for single image/video post. | `null` |
35 | | HasReplies | `bool` | Whether or not the post has replies. | `false` |
36 | | IsReply | `bool` | Whether or not the post is a reply to another post. | `false` |
37 | | IsReplyOwnedByMe | `bool` | Whether the reply is owned by the currently authenticated user. | `false` |
38 | | RootPost | [`ThreadsIdContainer`](./ThreadsIdContainer) | The root post. | `null` |
39 | | RepliedTo | [`ThreadsIdContainer`](./ThreadsIdContainer) | The media container ID of the parent post. | `null` |
40 | | HideStatus | TBD, for now this is `string`. | The post's hide status. | `null` |
41 | | ReplyAudience | TBD, for now this is `string`. | The reply audience. | `null` |
42 |
43 | ## Methods
44 |
45 | | Method | Summary | Return Value |
46 | |--------------|---------------------------------------------------------------------------------------|--------------|
47 | | `ToString()` | Returns a string that represents the current object.
**This method is overriden.** | `string` |
--------------------------------------------------------------------------------
/src/ThreadSharp/ThreadsClient.cs:
--------------------------------------------------------------------------------
1 | using Refit;
2 | using System.Net;
3 | using System.Text.Json;
4 | using ThreadSharp.Internal;
5 | using ThreadSharp.Models;
6 | using ThreadSharp.Exceptions;
7 |
8 | namespace ThreadSharp;
9 |
10 | ///
11 | /// The client to use for interacting with the Threads API.
12 | ///
13 | public sealed class ThreadsClient : IDisposable
14 | {
15 | private string _accessToken;
16 |
17 | private readonly HttpClient _httpClient;
18 | private readonly IThreadSharpRefitClient _refitClient;
19 |
20 | ///
21 | /// The maximum amount of retries to do when a request fails on
22 | /// the Threads API's end.
23 | ///
24 | public int MaxRetriesOnServerError { get; set; } = 5;
25 |
26 | ///
27 | /// Sets the current user's access token, mostly for emergency
28 | /// purposes or when a new access token is generated.
29 | ///
30 | public string AccessToken
31 | {
32 | set => _accessToken = value;
33 | }
34 |
35 | ///
36 | /// The backing for the client.
37 | ///
38 | ///
39 | /// Use of the
40 | /// method is preferred over using this directly.
41 | ///
42 | public HttpClient BackingClient => _httpClient;
43 |
44 | ///
45 | /// Client for all things user related for the current authenticated
46 | /// user, including posting threads.
47 | ///
48 | public ThreadsUserClient Me { get; }
49 |
50 | ///
51 | /// Client for thread fetching & management.
52 | ///
53 | public ThreadsThreadManagementClient Threads { get; }
54 |
55 | ///
56 | /// Client for all things insight/data related for the current
57 | /// authenticated user.
58 | ///
59 | public ThreadsInsightsClient Insights { get; }
60 |
61 | ///
62 | /// Creates an instance of which doesn't
63 | /// automatically renew the access token.
64 | ///
65 | /// The access token to call the Threads API with.
66 | public ThreadsClient(string accessToken)
67 | {
68 | _accessToken = accessToken;
69 |
70 | var httpClientHandler = new HttpClientHandler()
71 | {
72 | AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate
73 | };
74 |
75 | _httpClient = new HttpClient(httpClientHandler)
76 | {
77 | BaseAddress = new Uri("https://graph.threads.net"),
78 | DefaultRequestHeaders =
79 | {
80 | { "Authorization", $"Bearer {accessToken}" },
81 | }
82 | };
83 |
84 | _refitClient = RestService.For(_httpClient, new RefitSettings
85 | {
86 | ContentSerializer = new SystemTextJsonContentSerializer(new JsonSerializerOptions()
87 | {
88 | TypeInfoResolver = ThreadsSourceGenerationContext.Default
89 | })
90 | });
91 |
92 | Me = new(() => _accessToken, _refitClient, () => MaxRetriesOnServerError);
93 | Threads = new(() => _accessToken, _refitClient, () => MaxRetriesOnServerError);
94 | Insights = new(() => _accessToken, _refitClient, () => MaxRetriesOnServerError);
95 | }
96 |
97 | /*///
98 | /// Creates an instance of for fetching other users' info & posts.
99 | ///
100 | /// The user ID to use for retrieving.
101 | /// A client for user info & post retrieval.
102 | public ThreadsUserClient User(string threadsUserId)
103 | => new(threadsUserId, _accessToken, _refreshToken, _refitClient);*/
104 |
105 | ///
106 | /// Sends a request to the Threads API.
107 | ///
108 | /// The method to use for the HTTP request.
109 | /// The path or endpoint you want to call. Prefixed by "https://graph.threads.net/".
110 | /// A result, if successful, contains the dictionary that contains the full response.
111 | public async Task>> SendRequestAsync(HttpMethod method, string path)
112 | {
113 | using var request = new HttpRequestMessage(method, path);
114 |
115 | using var response = await _httpClient.SendAsync(request);
116 |
117 | using var content = await response.Content.ReadAsStreamAsync();
118 | var json = await JsonSerializer.DeserializeAsync(content, ThreadsSourceGenerationContext.Default.DictionaryStringJsonElement);
119 |
120 | if (response.IsSuccessStatusCode)
121 | return new(json, response.StatusCode);
122 |
123 | return new(new ThreadsException(), response.StatusCode);
124 | }
125 |
126 | ///
127 | public void Dispose()
128 | {
129 | _httpClient.Dispose();
130 | }
131 | }
--------------------------------------------------------------------------------
/docs/api-reference/ThreadSharp/Internal/ThreadsUserClient.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: ThreadsUserClient
3 | ---
4 |
5 | # [ThreadSharp](../).[Internal](./).ThreadsUserClient
6 |
7 | ## Definition
8 |
9 | ```c#
10 | public sealed class ThreadsUserClient
11 | ```
12 |
13 | Client for all things user related for the current authenticated user, including posting threads.
14 |
15 | ## Methods
16 |
17 | | Method | Summary | Return Value |
18 | |------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
19 | | `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. |
20 | | `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. |
21 | | `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. |
22 | | `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. |
23 | | `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. |
24 | | `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. |
25 | | `PublishMediaContainerAsync(string mediaContainerId, CancellationToken cancellationToken = default)` | Publishes a media container. | The result, containing either a [`ThreadsIdContainer`](../Models/Api/ThreadsIdContainer) or an error. |
26 | | `RepostAsync(string mediaContainerId, CancellationToken cancellationToken = default)` | Reposts a post on the user's profile ("Reposts" tab). | The result, containing either a or an error. |
--------------------------------------------------------------------------------
/src/ThreadSharp/Helpers/ThreadsPostingHelpers.cs:
--------------------------------------------------------------------------------
1 | using ThreadSharp.Enums;
2 | using ThreadSharp.Internal;
3 | using ThreadSharp.Models;
4 | using ThreadSharp.Models.Api;
5 | using ThreadSharp.Models.Api.Content;
6 |
7 | namespace ThreadSharp.Helpers;
8 |
9 | ///
10 | /// Posting helpers for the Threads API to help make sharing easier, as sharing is caring! :)
11 | ///
12 | public static class ThreadsPostingHelpers
13 | {
14 | ///
15 | /// Creates and publishes a text post without directly dealing with media containers.
16 | ///
17 | /// The client to use for publishing.
18 | /// The text to use in the post.
19 | /// The post/media container ID to reply to.
20 | /// The post/media container ID to quote.
21 | /// An option to restrict replies or open them to everyone.
22 | /// List of valid ISO 3166-1 alpha-2 country codes to restrict viewing the post to.
23 | ///
24 | /// The cancellation token to use in case the caller chooses to cancel the operation.
25 | ///
26 | ///
27 | /// The result, containing either the or an error.
28 | ///
29 | public static async Task> CreateTextPostAsync(
30 | this ThreadsUserClient threadsUserClient,
31 | string text,
32 | string? replyToId = null,
33 | string? quotePostId = null,
34 | ReplyControl replyControl = ReplyControl.Everyone,
35 | string[]? allowlistedCountryCodes = null,
36 | CancellationToken cancellationToken = default
37 | )
38 | {
39 | if (string.IsNullOrWhiteSpace(text))
40 | throw new ArgumentException("Text must not be null or whitespace when posting.", nameof(text));
41 |
42 | var mediaContainerCreationResponse = await threadsUserClient.CreateMediaContainerAsync(
43 | new EmptyContainerContent(),
44 | text,
45 | replyToId,
46 | quotePostId,
47 | replyControl,
48 | allowlistedCountryCodes,
49 | cancellationToken
50 | );
51 |
52 | if (!mediaContainerCreationResponse.IsSuccessStatusCode || mediaContainerCreationResponse.Value is null)
53 | return mediaContainerCreationResponse;
54 |
55 | return await threadsUserClient.PublishMediaContainerAsync(mediaContainerCreationResponse.Value!.Id, cancellationToken);
56 | }
57 |
58 | ///
59 | /// Creates and publishes a carousel post without directly dealing with media containers.
60 | ///
61 | /// The client to use for publishing.
62 | /// The statuses of the media containers to upload.
63 | /// Text to include in the carousel post. Optional.
64 | ///
65 | /// The cancellation token to use in case the caller chooses to cancel the operation.
66 | ///
67 | /// The post/media container ID to reply to.
68 | /// The post/media container ID to quote.
69 | /// An option to restrict replies or open them to everyone.
70 | /// List of valid ISO 3166-1 alpha-2 country codes to restrict viewing the post to.
71 | ///
72 | /// Thrown when any of the media containers aren't successfully uploaded.
73 | ///
74 | ///
75 | /// The result, containing either the or an error.
76 | ///
77 | public static async Task> CreateCarouselPostAsync(
78 | this ThreadsUserClient threadsUserClient,
79 | IEnumerable mediaContainerStatuses,
80 | string? text = null,
81 | string? replyToId = null,
82 | string? quotePostId = null,
83 | ReplyControl replyControl = ReplyControl.Everyone,
84 | string[]? allowlistedCountryCodes = null,
85 | CancellationToken cancellationToken = default
86 | )
87 | {
88 | if (mediaContainerStatuses.Any(x => x.Status != ThreadsPublishingStatusCode.Finished))
89 | throw new ArgumentException($"All media containers must be of status \"{nameof(ThreadsPublishingStatusCode)}.{nameof(ThreadsPublishingStatusCode.Finished)}\" before a carousel post can be created.", nameof(mediaContainerStatuses));
90 |
91 | var mediaContainerCreationResponse = await threadsUserClient.CreateMediaContainerAsync(
92 | new CarouselContainerContent()
93 | {
94 | Children = mediaContainerStatuses.ToList()
95 | },
96 | text,
97 | replyToId,
98 | quotePostId,
99 | replyControl,
100 | allowlistedCountryCodes,
101 | cancellationToken
102 | );
103 |
104 | if (!mediaContainerCreationResponse.IsSuccessStatusCode || mediaContainerCreationResponse.Value is null)
105 | return mediaContainerCreationResponse;
106 |
107 | return await threadsUserClient.PublishMediaContainerAsync(mediaContainerCreationResponse.Value!.Id, cancellationToken);
108 | }
109 | }
--------------------------------------------------------------------------------
/src/Samples/GetInsightsSample/Program.cs:
--------------------------------------------------------------------------------
1 | using ReplyToSelfPostSample;
2 | using System.Text.Json;
3 | using ThreadSharp;
4 | using ThreadSharp.Enums;
5 | using ThreadSharp.Models;
6 | using ThreadSharp.Models.Api.Insights;
7 |
8 | var accessToken = Environment.GetEnvironmentVariable("THREADS_ACCESS_TOKEN");
9 |
10 | if (accessToken is null)
11 | {
12 | Console.WriteLine("ERROR: Cannot run this sample without a Threads access token.");
13 | return;
14 | }
15 |
16 | #pragma warning disable CA1869 // Cache and reuse 'JsonSerializerOptions' instances
17 | var serializerOptions = new JsonSerializerOptions()
18 | {
19 | WriteIndented = true,
20 | TypeInfoResolver = CustomJsonSerializerContext.Default
21 | };
22 | #pragma warning restore CA1869 // Cache and reuse 'JsonSerializerOptions' instances
23 |
24 | var threadsClient = new ThreadsClient(accessToken);
25 |
26 | // Get all insights for the current user.
27 | var insightsResult = await threadsClient.Insights.GetForCurrentUserAsync(new UserMetricPagingParameters()
28 | {
29 | Metrics = [
30 | "views",
31 | "likes",
32 | "reposts",
33 | "quotes",
34 | "replies",
35 | "followers_count",
36 | "follower_demographics"
37 | ],
38 | Period = MetricPeriod.Lifetime
39 | }, Breakdown.Age | Breakdown.Gender);
40 |
41 | if (insightsResult.Value is not null)
42 | {
43 | Console.WriteLine("User Insights:");
44 | Console.WriteLine("----------------");
45 |
46 | foreach (var insight in insightsResult.Value)
47 | {
48 | // There are different types of insights, so we have to handle each of them.
49 | switch (insight)
50 | {
51 | case ThreadsUserInsightViewsData viewsData:
52 | Console.WriteLine("Views:");
53 |
54 | int i = 0;
55 |
56 | // Filter through different views metrics.
57 | foreach (var value in viewsData.Values ?? Enumerable.Empty())
58 | {
59 | Console.WriteLine($" - Item n. {i}");
60 | Console.WriteLine($" Views: {value.Views}");
61 | Console.WriteLine($" End Time: {value.EndTime}");
62 | i++;
63 | }
64 | break;
65 | case ThreadsUserInsightLikesData likesData:
66 | Console.WriteLine($"Likes: {likesData.TotalValue?.Value ?? 0}");
67 | break;
68 | case ThreadsUserInsightRepostsData repostsData:
69 | Console.WriteLine($"Reposts: {repostsData.TotalValue?.Value ?? 0}");
70 | break;
71 | case ThreadsUserInsightQuotesData quotesData:
72 | Console.WriteLine($"Quotes: {quotesData.TotalValue?.Value ?? 0}");
73 | break;
74 | case ThreadsUserInsightRepliesData repliesData:
75 | Console.WriteLine($"Replies: {repliesData.TotalValue?.Value ?? 0}");
76 | break;
77 | case ThreadsUserInsightTotalFollowersData totalFollowersData:
78 | Console.WriteLine($"Total Followers: {totalFollowersData.TotalValue?.Value ?? 0}");
79 | break;
80 | case ThreadsUserInsightFollowerDemographicsData followerDemographicsData:
81 | Console.WriteLine("Follower Demographics:");
82 |
83 | int j = 0;
84 |
85 | // Loop through the breakdown data.
86 | foreach (var value in followerDemographicsData.TotalValue?.BreakdownData ?? Enumerable.Empty())
87 | {
88 | Console.WriteLine($" - Item n. {j}");
89 | Console.WriteLine($" Results:");
90 |
91 | int k = 0;
92 |
93 | // Loop through the results and match values with different dimension keys.
94 | foreach (var result in value.Results)
95 | {
96 | foreach (var (dimensionKey, dimensionValue) in Enumerable.Zip(value.DimensionKeys, value.Results.Select(x => x.DimensionValues).ElementAt(k)))
97 | {
98 | Console.WriteLine($" - {dimensionKey}: {dimensionValue}");
99 | Console.WriteLine($" Value: {value.Results[k].Value}");
100 | }
101 |
102 | k++;
103 | }
104 |
105 | j++;
106 | }
107 | break;
108 | default:
109 | Console.WriteLine("Detected unsupported insight type.\nPlease report this by going to this link and filling the appropriate details: https://github.com/itsWindows11/ThreadSharp/issues/new");
110 | break;
111 | }
112 |
113 | Console.WriteLine("--------");
114 | }
115 | } else if (insightsResult.ErrorData is not null)
116 | {
117 | Console.WriteLine("ERROR: Cannot retrieve user insights.");
118 | Console.WriteLine("----------------------------------------");
119 |
120 | var errorData = insightsResult.ErrorData;
121 |
122 | #pragma warning disable IL2026 // Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code
123 | #pragma warning disable IL3050 // Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling.
124 | if (errorData is not null)
125 | Console.WriteLine(JsonSerializer.Serialize(errorData, serializerOptions));
126 | #pragma warning restore IL3050 // Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling.
127 | #pragma warning restore IL2026 // Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code
128 | } else
129 | {
130 | Console.WriteLine("ERROR: An unknown error occurred while retrieving user insights.");
131 | }
--------------------------------------------------------------------------------
/docs/docs/threads/create-new-thread.md:
--------------------------------------------------------------------------------
1 | # Creating a New Thread
2 |
3 | This document explains how to create a new thread in two ways, using helpers or by creating & publishing media containers.
4 |
5 | For more information regarding media container creation, check the [official docs](https://developers.facebook.com/docs/threads/posts).
6 |
7 | ## Creating a media container
8 |
9 | To create a media container, you can call the [`CreateMediaContainerAsync()`](/api-reference/ThreadSharp/Internal/ThreadsUserClient#methods) method to create a media container.
10 |
11 | There are different types of media containers that can be passed, below is a map of media container content wrappers correspoding to their media types in the Threads API:
12 |
13 | | Content Type | Media Container Wrapper |
14 | |--------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
15 | | `TEXT` | [`EmptyContainerContent`](/api-reference/ThreadSharp/Models/Api/Content/EmptyContainerContent) or [`AttachmentLinkContainerContent`](/api-reference/ThreadSharp/Models/Api/Content/AttachmentLinkContainerContent) if a link attachment is needed. |
16 | | `IMAGE` | [`MediaContainerContent`](/api-reference/ThreadSharp/Models/Api/Content/MediaContainerContent) w/ its `ImageUrl` property set. |
17 | | `VIDEO` | [`MediaContainerContent`](/api-reference/ThreadSharp/Models/Api/Content/MediaContainerContent) w/ its `VideoUrl` property set. |
18 | | `CAROUSEL` | [`CarouselContainerContent`](/api-reference/ThreadSharp/Models/Api/Content/CarouselContainerContent) |
19 |
20 | ::: tip NOTES
21 | - If the media container wrapper is of type [`MediaContainerContent`](/api-reference/ThreadSharp/Models/Api/Content/MediaContainerContent), and you want it to be a part of a carousel post, set the `IsCarouselItem` property to `true` so Threads knows that this item is going to be added to a carousel album.
22 | - No media container wrapper other than [`MediaContainerContent`](/api-reference/ThreadSharp/Models/Api/Content/MediaContainerContent) can be used as a carousel item.
23 | - Adding text to the post is optional if media or a carousel is attached to the post.
24 | :::
25 |
26 | Below are examples of creating media containers:
27 |
28 | ::: code-group
29 | ```c# [Text-only post]
30 | // GetThreadsClient() is a placeholder method for getting a ThreadsClient.
31 | var threadsClient = GetThreadsClient();
32 |
33 | var textOnlyMediaContainerResult = await threadsClient.Me.CreateMediaContainerAsync(new EmptyContainerContent(), "Hello World from Threads API; using ThreadSharp!");
34 |
35 | // Handle the result.
36 | ```
37 |
38 | ```c# [Image-only post]
39 | // GetThreadsClient() is a placeholder method for getting a ThreadsClient.
40 | var threadsClient = GetThreadsClient();
41 |
42 | var imageOnlyMediaContainerResult = await threadsClient.Me.CreateMediaContainerAsync(
43 | new MediaContainerContent()
44 | {
45 | ImageUrl = "https://www.example.com/images/bronz-fonz.jpg"
46 | },
47 | "#BronzFonz"
48 | );
49 |
50 | // Handle the result.
51 | ```
52 | :::
53 |
54 | Optionally, you can also specify which people can reply to your post by setting the `replyControl` parameter, which is of type [`ReplyControl`](/api-reference/ThreadSharp/Enums/ReplyContol).
55 |
56 | ## Publishing a media container
57 |
58 | To publish a media container, it's as straightforward as calling [`PublishMediaContainerAsync()`](/api-reference/ThreadSharp/Internal/ThreadsUserClient#methods) in the [user client](#user-client), along with passing the ID of the media container previously created above.
59 |
60 | ## Helpers
61 |
62 | - A text post can be easily created by using the extension method, `CreateTextPostAsync()`, on the [user client](#user-client).
63 |
64 | - A carousel post can be easily created by using the extension method, `CreateCarouselPostAsync()`, on the [user client](#user-client). This takes a list of media container statuses, which can be retrieved like explained in the "[Checking media container status](#checking-media-container-status)" section.
65 |
66 | ::: tip NOTE
67 | In carousel posts, all media container statuses passed must be of status [`ThreadsPublishingStatusCode.Finished`](/api-reference/ThreadSharp/Enums/ThreadsPublishingStatusCode#values), otherwise the publishing will fail.
68 | :::
69 |
70 | Both of these helpers handle publishing the post behind the scenes, as well as creating the relevant media containers.
71 |
72 | ## Troubleshooting
73 |
74 | ### Quota
75 |
76 | A quota is enforced on creating posts & replies from the Threads API, so you need to monitor the post/reply quota.
77 |
78 | You can do this by calling [`GetPublishingLimitAsync()`](/api-reference/ThreadSharp/Internal/ThreadsUserClient#methods) on the [user client](#user-client).
79 |
80 | ### Checking media container status
81 |
82 | To check the status of a media container with its ID, you can call the [`GetContainerStatusAsync()`](/api-reference/ThreadSharp/Internal/ThreadsThreadManagementClient) method on the [thread management client](#thread-management-client).
83 |
84 | This can be used to create carousel posts with either the helper, or by manually creating & publishing the media containers.
85 |
86 | ## Definitions
87 |
88 | ### Thread Management Client
89 |
90 | An instance of [`ThreadsThreadManagementClient`](/api-reference/ThreadSharp/Internal/ThreadsThreadManagementClient), usually obtained from a `ThreadsClient`'s `Threads` property.
91 |
92 | ### User Client
93 |
94 | An instance of [`ThreadsUserClient`](/api-reference/ThreadSharp/Internal/ThreadsUserClient), usually obtained from a `ThreadsClient`'s `Me` property.
--------------------------------------------------------------------------------
/src/ThreadSharp/IThreadSharpRefitClient.cs:
--------------------------------------------------------------------------------
1 | using Refit;
2 | using ThreadSharp.Enums;
3 | using ThreadSharp.Models;
4 | using ThreadSharp.Models.Api.Content;
5 |
6 | namespace ThreadSharp;
7 |
8 | internal static class Constants
9 | {
10 | internal const string CurrentUserId = "me";
11 | }
12 |
13 | // User part for the Refit client, used as a base for the ThreadSharp wrapper.
14 | // https://developers.facebook.com/docs/threads/reference/user
15 | internal partial interface IThreadSharpRefitClient
16 | {
17 | [Get("/v1.0/{threadsUserId}")]
18 | Task> GetProfileAsync(
19 | [AliasAs("access_token")] string accessToken,
20 | string threadsUserId = Constants.CurrentUserId,
21 | string fields = "id,username,name,threads_profile_picture_url,threads_biography,is_eligible_for_geo_gating",
22 | CancellationToken cancellationToken = default
23 | );
24 |
25 | [Get("/v1.0/{threadsUserId}/threads")]
26 | Task> GetThreadsAsync(
27 | [AliasAs("access_token")] string accessToken,
28 | PostPagingParameters pagingParameters,
29 | string threadsUserId = Constants.CurrentUserId,
30 | string fields = "id,media_product_type,media_type,media_url,permalink,owner,username,text,timestamp,shortcode,thumbnail_url,children,is_quote_post,alt_text,has_replies,reply_audience",
31 | int limit = 25,
32 | CancellationToken cancellationToken = default
33 | );
34 |
35 | [Get("/v1.0/{threadsUserId}/replies")]
36 | Task> GetRepliesAsync(
37 | [AliasAs("access_token")] string accessToken,
38 | PostPagingParameters pagingParameters,
39 | string threadsUserId = Constants.CurrentUserId,
40 | string fields = "id,media_product_type,media_type,media_url,permalink,username,text,timestamp,shortcode,thumbnail_url,children,is_quote_post,has_replies,root_post,replied_to,is_reply,is_reply_owned_by_me,reply_audience",
41 | int limit = 25,
42 | CancellationToken cancellationToken = default
43 | );
44 |
45 | [Get($"/v1.0/{Constants.CurrentUserId}/threads_publishing_limit")]
46 | Task> GetPublishingLimitAsync(
47 | [AliasAs("access_token")] string accessToken,
48 | string fields = "config,quota_usage,reply_config,reply_quota_usage",
49 | CancellationToken cancellationToken = default
50 | );
51 | }
52 |
53 | // Thread management part for the Refit client, used as a base for the ThreadSharp wrapper.
54 | // https://developers.facebook.com/docs/threads/reference/insights,
55 | // https://developers.facebook.com/docs/threads/reference/reply-management
56 | internal partial interface IThreadSharpRefitClient
57 | {
58 | [Get("/v1.0/{threadsMediaId}")]
59 | Task> GetThreadAsync(
60 | [AliasAs("access_token")] string accessToken,
61 | string threadsMediaId,
62 | string fields = "id,media_product_type,media_type,media_url,permalink,owner,username,text,timestamp,shortcode,thumbnail_url,children,is_quote_post,alt_text,has_replies,is_reply,is_reply_owned_by_me,root_post,replied_to,hide_status,reply_audience",
63 | CancellationToken cancellationToken = default
64 | );
65 |
66 | [Get("/v1.0/{threadsMediaId}/insights")]
67 | Task> GetMediaInsightsAsync(
68 | [AliasAs("access_token")] string accessToken,
69 | string metric,
70 | string threadsMediaId,
71 | CancellationToken cancellationToken = default
72 | );
73 |
74 | [Get("/v1.0/{threadsMediaId}/replies")]
75 | Task> GetRepliesAsync(
76 | [AliasAs("access_token")] string accessToken,
77 | string threadsMediaId,
78 | bool reverse,
79 | PostPagingParameters pagingParameters,
80 | string fields = "id,media_product_type,media_type,media_url,permalink,owner,username,text,timestamp,shortcode,thumbnail_url,children,is_quote_post,has_replies,root_post,replied_to,is_reply,is_reply_owned_by_me,hide_status,reply_audience",
81 | CancellationToken cancellationToken = default
82 | );
83 |
84 | [Get("/v1.0/{threadsMediaId}/conversation")]
85 | Task> GetConversationAsync(
86 | [AliasAs("access_token")] string accessToken,
87 | string threadsMediaId,
88 | bool reverse,
89 | PostPagingParameters pagingParameters,
90 | string fields = "id,media_product_type,media_type,media_url,permalink,owner,username,text,timestamp,shortcode,thumbnail_url,children,is_quote_post,has_replies,root_post,replied_to,is_reply,is_reply_owned_by_me,hide_status,reply_audience",
91 | CancellationToken cancellationToken = default
92 | );
93 |
94 | [Post("/v1.0/{threadsReplyId}/manage_reply")]
95 | Task> ManageReplyAsync(
96 | [AliasAs("access_token")] string accessToken,
97 | string threadsReplyId,
98 | bool hide,
99 | CancellationToken cancellationToken = default
100 | );
101 |
102 | [Get("/v1.0/{threadsUserId}/threads_insights")]
103 | Task> GetUserInsightsAsync(
104 | [AliasAs("access_token")] string accessToken,
105 | UserMetricPagingParameters metrics,
106 | string? breakdown = null,
107 | string threadsUserId = Constants.CurrentUserId,
108 | CancellationToken cancellationToken = default
109 | );
110 | }
111 |
112 | // Thread publishing part of the Refit client, used as a base for the ThreadSharp wrapper.
113 | // https://developers.facebook.com/docs/threads/reference/publishing
114 | internal partial interface IThreadSharpRefitClient
115 | {
116 | [Get("/v1.0/{threadsMediaId}")]
117 | Task> GetMediaContainerAsync(
118 | [AliasAs("access_token")] string accessToken,
119 | string threadsMediaId,
120 | string fields = "id",
121 | CancellationToken cancellationToken = default
122 | );
123 |
124 | [Post($"/v1.0/{Constants.CurrentUserId}/threads")]
125 | Task> CreateMediaContainerAsync(
126 | [AliasAs("access_token")] string accessToken,
127 | [AliasAs("media_type")] MediaType mediaType,
128 | BaseMediaContainerContent postContent,
129 | [AliasAs("reply_control")] ReplyControl replyControl = ReplyControl.Everyone,
130 | [AliasAs("reply_to_id")] string? replyToId = null,
131 | [AliasAs("quote_post_id")] string? quotePostId = null,
132 | string? text = null,
133 | [AliasAs("allowlisted_country_codes")] string? allowlistedCountryCodes = null,
134 | CancellationToken cancellationToken = default
135 | );
136 |
137 | [Post($"/v1.0/{Constants.CurrentUserId}/threads_publish")]
138 | Task> PublishMediaContainerAsync(
139 | [AliasAs("access_token")] string accessToken,
140 | [AliasAs("creation_id")] string threadsMediaIdToPublish,
141 | CancellationToken cancellationToken = default
142 | );
143 |
144 | [Post("/v1.0/{threadsPostId}/repost")]
145 | Task> RepostAsync(
146 | [AliasAs("access_token")] string accessToken,
147 | string threadsPostId,
148 | CancellationToken cancellationToken = default
149 | );
150 | }
--------------------------------------------------------------------------------