├── .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 | ![ThreadSharp banner](https://raw.githubusercontent.com/itsWindows11/ThreadSharp/refs/heads/main/assets/banner.png) 2 | 3 | [![CI Status](https://github.com/itsWindows11/ThreadSharp/actions/workflows/nuget.yml/badge.svg)](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 | ![ThreadSharp banner](https://github.com/itsWindows11/ThreadSharp/blob/main/assets/banner.png?raw=true) 2 |

A C# API wrapper for the Threads API.

3 |

4 | 5 | CI Status 6 | 7 | NuGet 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 | ![NuGet Version](https://img.shields.io/nuget/v/ThreadSharp) 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 | } --------------------------------------------------------------------------------