diff --git a/README.md b/README.md index 50b5bf6..4aab69e 100644 --- a/README.md +++ b/README.md @@ -99,7 +99,7 @@ long failed = client.posts().stream(PostListParams.create().workspaceId(workspac | --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `posts()` | `list`, `autoPaginate`, `stream`, `get`, `create`, `update`, `delete`, `duplicate`, `publish`, `retry`, `cancel`, `preflight`, `deliveries`, `publishRuns`, `analytics`, `bulkShift`, `bulkLabel`, `bulkDelete`, `validateImport`, `commitImport`, `rollbackImport` | | `workspaces()` | `list`, `get`, `create`, `update`, `delete`, `analytics` | -| `accounts()` | `list`, `get`, `create`, `rename`, `move`, `delete`, `healthSummary`, `health`, `togglePrimary`, `validate`, `refreshToken`, `analytics`, `createTelegramConnectCode`, `getTelegramConnectStatus`, `getTelegramBotCommands`, `setTelegramBotCommands`, `deleteTelegramBotCommands`, `listSlackChannels`, `listSlackMembers`, `getSlackIdentity`, `updateSlackIdentity`, `getIceBreakers`, `setIceBreakers`, `deleteIceBreakers`, `getPersistentMenu`, `setPersistentMenu`, `deletePersistentMenu`, `getGreeting`, `setGreeting`, `deleteGreeting`, `getWebhookSubscription`, `resubscribeWebhook`, `communities()`, `listDiscordChannels`, `switchDiscordChannel`, `getDiscordIdentity`, `updateDiscordIdentity`, `listDiscordPins`, `deleteDiscordMessage`, `pinDiscordMessage`, `unpinDiscordMessage`, `crosspostDiscordMessage`, `createDiscordThread`, `sendDiscordDm`, `listDiscordEvents`, `getDiscordEvent`, `createDiscordEvent`, `updateDiscordEvent`, `deleteDiscordEvent`, `listDiscordMembers`, `getDiscordMember`, `listDiscordRoles`, `createDiscordRole`, `updateDiscordRole`, `deleteDiscordRole`, `addDiscordMemberRole`, `removeDiscordMemberRole`, `platformMetrics` | +| `accounts()` | `list`, `get`, `create`, `rename`, `move`, `delete`, `healthSummary`, `health`, `togglePrimary`, `validate`, `refreshToken`, `analytics`, `createTelegramConnectCode`, `getTelegramConnectStatus`, `getTelegramBotCommands`, `setTelegramBotCommands`, `deleteTelegramBotCommands`, `redditSubreddits`, `redditSubredditRules`, `redditFlairs`, `setRedditDefaultSubreddit`, `listSlackChannels`, `listSlackMembers`, `getSlackIdentity`, `updateSlackIdentity`, `getIceBreakers`, `setIceBreakers`, `deleteIceBreakers`, `getPersistentMenu`, `setPersistentMenu`, `deletePersistentMenu`, `getGreeting`, `setGreeting`, `deleteGreeting`, `getWebhookSubscription`, `resubscribeWebhook`, `communities()`, `listDiscordChannels`, `switchDiscordChannel`, `getDiscordIdentity`, `updateDiscordIdentity`, `listDiscordPins`, `deleteDiscordMessage`, `pinDiscordMessage`, `unpinDiscordMessage`, `crosspostDiscordMessage`, `createDiscordThread`, `sendDiscordDm`, `listDiscordEvents`, `getDiscordEvent`, `createDiscordEvent`, `updateDiscordEvent`, `deleteDiscordEvent`, `listDiscordMembers`, `getDiscordMember`, `listDiscordRoles`, `createDiscordRole`, `updateDiscordRole`, `deleteDiscordRole`, `addDiscordMemberRole`, `removeDiscordMemberRole`, `platformMetrics` | | `accountGroups()` | `list`, `get`, `create`, `update`, `delete`, `setMembers` | | `labels()` | `list`, `get`, `create`, `update`, `delete` | | `webhooks()` | `list`, `create`, `update`, `delete`, `test`, `verify` | @@ -108,19 +108,19 @@ long failed = client.posts().stream(PostListParams.create().workspaceId(workspac | `automations()` | `list`, `get`, `create`, `update`, `delete`, `toggle`, `runs`, `run`, `trigger`, `stats` | | `media()` | `list`, `limits`, `upload`, `presign`, `complete`, `uploadDirect`, `delete` | | `ai()` | `credits`, `generateCaption`, `rewrite`, `repurposeUrl` | -| `inbox()` | `list`, `threads`, `conversations`, `unreadCount`, `accounts`, `platforms`, `markThreadRead`, `markConversationRead`, `refresh`, `update`, `editComment`, `reply`, `hide`, `unhide`, `delete`, `like`, `unlike`, `pin`, `unpin`, `react`, `startConversation`, `setTyping`, `passThreadControl`, `takeThreadControl`, `handover`, `listApprovals`, `approveReply`, `rejectReply` | +| `inbox()` | `list`, `threads`, `conversations`, `unreadCount`, `accounts`, `platforms`, `markThreadRead`, `markConversationRead`, `refresh`, `update`, `editComment`, `reply`, `hide`, `unhide`, `delete`, `like`, `unlike`, `vote`, `pin`, `unpin`, `react`, `startConversation`, `setTyping`, `passThreadControl`, `takeThreadControl`, `handover`, `listApprovals`, `approveReply`, `rejectReply` | | `contacts()` | `list`, `get`, `create`, `update`, `delete`, `conversations`, `importCsv`, `listFields`, `createField`, `updateField`, `updateFieldOptions`, `deleteField`, `conversationAnalytics` | | `broadcasts()` | `list`, `get`, `create`, `update`, `delete`, `send`, `cancel`, `recipients` | | `sequences()` | `list`, `get`, `create`, `update`, `delete`, `enroll`, `unenroll`, `enrollments` | | `knowledge()` | `list`, `create`, `createText`, `createUrl`, `createFile`, `update`, `delete`, `sync`, `search` | | `ads()` | `list`, `external`, `boostable`, `connections`, `sources`, `authorizeMeta`, `deleteConnection`, `boost`, `create`, `refresh`, `setStatus`, `delete`, `accountTree`, `createCampaign`, `campaign`, `updateCampaign`, `deleteCampaign`, `duplicateCampaign`, `createAdSet`, `adSet`, `updateAdSet`, `deleteAdSet`, `duplicateAdSet`, `createNetworkAd`, `networkAd`, `updateNetworkAd`, `deleteNetworkAd`, `duplicateNetworkAd`, `bulkSetStatus`, `creatives`, `createCreative`, `creative`, `deleteCreative`, `estimateReach`, `insights`, `adInsights`, `audiences`, `createAudience`, `audience`, `updateAudience`, `deleteAudience`, `addAudienceUsers`, `searchTargeting`, `leadForms`, `createLeadForm`, `leadForm`, `archiveLeadForm`, `leads`, `leadsFeed`, `leadPages`, `subscribeLeadPage`, `unsubscribeLeadPage`, `goals`, `catalogs`, `createCatalog`, `catalog`, `updateCatalog`, `deleteCatalog`, `catalogProducts`, `writeCatalogProducts`, `productFeeds`, `createProductFeed`, `deleteProductFeed`, `feedUploads`, `startFeedUpload`, `productSets`, `createProductSet`, `updateProductSet`, `deleteProductSet`, `reachFrequency`, `createReachFrequency`, `reachFrequencyPrediction`, `reserveReachFrequency`, `cancelReachFrequency`, `library`, `partnershipCreators`, `requestPartnership`, `revokePartnership`, `accountActivity`, `labels`, `createLabel`, `updateLabel`, `deleteLabel`, `applyLabel`, `studies`, `createStudy`, `study`, `deleteStudy`, `iosCampaignLimits`, `highDemandPeriods`, `createHighDemandPeriod`, `deleteHighDemandPeriod`, `valueRuleSets`, `createValueRuleSet`, `deleteValueRuleSet` | -| `inbox()` | `list`, `threads`, `conversations`, `unreadCount`, `accounts`, `platforms`, `markThreadRead`, `markConversationRead`, `refresh`, `update`, `editComment`, `reply`, `hide`, `unhide`, `delete`, `like`, `unlike`, `pin`, `unpin`, `react`, `startConversation`, `setTyping`, `listApprovals`, `approveReply`, `rejectReply` | +| `inbox()` | `list`, `threads`, `conversations`, `unreadCount`, `accounts`, `platforms`, `markThreadRead`, `markConversationRead`, `refresh`, `update`, `editComment`, `reply`, `hide`, `unhide`, `delete`, `like`, `unlike`, `vote`, `pin`, `unpin`, `react`, `startConversation`, `setTyping`, `listApprovals`, `approveReply`, `rejectReply` | | `ads()` | `list`, `external`, `boostable`, `connections`, `sources`, `authorizeMeta`, `deleteConnection`, `boost`, `create`, `refresh`, `setStatus`, `delete`, `accountTree`, `createCampaign`, `campaign`, `updateCampaign`, `deleteCampaign`, `duplicateCampaign`, `createAdSet`, `adSet`, `updateAdSet`, `deleteAdSet`, `duplicateAdSet`, `createNetworkAd`, `networkAd`, `updateNetworkAd`, `deleteNetworkAd`, `duplicateNetworkAd`, `bulkSetStatus`, `creatives`, `createCreative`, `creative`, `deleteCreative`, `estimateReach`, `insights`, `adInsights`, `audiences`, `createAudience`, `audience`, `updateAudience`, `deleteAudience`, `addAudienceUsers`, `searchTargeting`, `leadForms`, `createLeadForm`, `leadForm`, `archiveLeadForm`, `leads`, `leadsFeed`, `leadPages`, `subscribeLeadPage`, `unsubscribeLeadPage` | | `whatsapp()` | `getProfile`, `updateProfile`, `requestDisplayName`, `setUsername`, `listTemplates`, `listTemplateLibrary`, `getTemplate`, `createTemplate`, `importTemplate`, `updateTemplate`, `deleteTemplate`, `listGroups`, `createGroup`, `getGroup`, `updateGroup`, `deleteGroup`, `getGroupInviteLink`, `resetGroupInviteLink`, `removeGroupParticipants`, `listBlocked`, `blockUsers`, `unblockUsers`, `getCommerceSettings`, `updateCommerceSettings`, `linkCatalog`, `listFlows`, `getFlow`, `createFlow`, `updateFlow`, `deleteFlow`, `uploadFlowJson`, `publishFlow`, `deprecateFlow`, `listFlowResponses`, `getEncryptionKeyStatus`, `setEncryptionKey`, `getAccountEvents`, `listSandboxSessions`, `createSandboxSession` | | `ads()` | `list`, `external`, `boostable`, `connections`, `sources`, `providers`, `authorize`, `deleteConnection`, `boost`, `create`, `refresh`, `setStatus`, `delete`, `accountTree`, `createCampaign`, `campaign`, `updateCampaign`, `deleteCampaign`, `duplicateCampaign`, `createAdSet`, `adSet`, `updateAdSet`, `deleteAdSet`, `duplicateAdSet`, `createNetworkAd`, `networkAd`, `updateNetworkAd`, `deleteNetworkAd`, `duplicateNetworkAd`, `bulkSetStatus`, `creatives`, `createCreative`, `creative`, `deleteCreative`, `estimateReach`, `insights`, `adInsights`, `audiences`, `createAudience`, `audience`, `updateAudience`, `deleteAudience`, `addAudienceUsers`, `addAudienceCompanies`, `searchTargeting`, `leadForms`, `createLeadForm`, `leadForm`, `archiveLeadForm`, `leads`, `leadsFeed`, `leadPages`, `subscribeLeadPage`, `unsubscribeLeadPage`, `bidPricing`, `supplyForecast`, `conversionRules`, `createConversionRule`, `conversionRule`, `updateConversionRule`, `deleteConversionRule`, `attachConversionRule`, `detachConversionRule`, `conversionMetrics`, `sendConversionEvents` | | `googleAds()` | `recommendations`, `optimizationScore`, `applyRecommendations`, `dismissRecommendations`, `keywords`, `createKeyword`, `updateKeyword`, `deleteKeyword`, `keywordIdeas`, `keywordMetrics`, `searchTerms`, `bidStrategies`, `createBidStrategy`, `adSchedule`, `setAdSchedule`, `negativeKeywordLists`, `createNegativeKeywordList`, `addNegativeKeywords`, `attachNegativeKeywordList`, `assets`, `createAsset`, `attachAsset`, `deleteAsset`, `assetGroups`, `createAssetGroup`, `updateAssetGroup`, `deleteAssetGroup`, `localServicesLeads`, `conversionActions`, `createConversionAction`, `uploadConversions`, `uploadConversionAdjustments`, `query` | | `googleBusiness()` | `getLocation`, `updateLocation`, `getAttributes`, `updateAttributes`, `getMenus`, `replaceMenus`, `getServices`, `replaceServices`, `listMedia`, `addMedia`, `deleteMedia`, `listPlaceActions`, `createPlaceAction`, `updatePlaceAction`, `deletePlaceAction`, `getVerificationOptions`, `startVerification`, `completeVerification`, `getPerformance`, `getSearchKeywords`, `assign` | -| `validate()` | `post`, `length`, `media` | +| `validate()` | `post`, `length`, `media`, `subreddit` | | `activity()` | `list` | `accounts().communities()` covers the X communities an account can post into: `list`, `sync`, diff --git a/src/main/java/com/fopost/sdk/model/Account.java b/src/main/java/com/fopost/sdk/model/Account.java index 8708d18..416f965 100644 --- a/src/main/java/com/fopost/sdk/model/Account.java +++ b/src/main/java/com/fopost/sdk/model/Account.java @@ -6,8 +6,11 @@ * A connected social account. * *

Named {@code Account} for the platform account, not the billing account. Which fields - * arrive depends on the endpoint: the list gives health and primary state, the detail gives - * timestamps and the owning workspace. {@code name} is the display name when one is set, and + * arrive depends on the endpoint: the list gives health, primary state and + * {@code reconnectRequired}, the detail gives timestamps and the owning workspace. + * + *

{@code reconnectRequired} is true when the account was connected before a permission it + * now needs was asked for; reconnecting it is the fix. {@code name} is the display name when one is set, and * {@code platformName} is always the name from the platform. */ public record Account( diff --git a/src/main/java/com/fopost/sdk/model/InboxItem.java b/src/main/java/com/fopost/sdk/model/InboxItem.java index 3f5660a..f358909 100644 --- a/src/main/java/com/fopost/sdk/model/InboxItem.java +++ b/src/main/java/com/fopost/sdk/model/InboxItem.java @@ -41,10 +41,12 @@ public record InboxItem( Boolean canHide, Boolean canDelete, Boolean liked, + String vote, Boolean pinned, String reaction, Instant editedAt, Boolean canLike, + Boolean canVote, Boolean canPin, Boolean canEdit, Boolean canReact, diff --git a/src/main/java/com/fopost/sdk/model/RedditDefaultSubreddit.java b/src/main/java/com/fopost/sdk/model/RedditDefaultSubreddit.java new file mode 100644 index 0000000..455e788 --- /dev/null +++ b/src/main/java/com/fopost/sdk/model/RedditDefaultSubreddit.java @@ -0,0 +1,8 @@ +package com.fopost.sdk.model; + +/** + * Where posts from a Reddit account go when a post names no subreddit. + * + *

A null {@code subreddit} means the account's own profile page, which always accepts a post. + */ +public record RedditDefaultSubreddit(String subreddit) {} diff --git a/src/main/java/com/fopost/sdk/model/RedditFlair.java b/src/main/java/com/fopost/sdk/model/RedditFlair.java new file mode 100644 index 0000000..acd4f26 --- /dev/null +++ b/src/main/java/com/fopost/sdk/model/RedditFlair.java @@ -0,0 +1,8 @@ +package com.fopost.sdk.model; + +/** + * A post flair, valid only in the subreddit it came from. + * + *

{@code editable} means the label may be replaced with your own text. + */ +public record RedditFlair(String id, String text, Boolean editable) {} diff --git a/src/main/java/com/fopost/sdk/model/RedditFlairs.java b/src/main/java/com/fopost/sdk/model/RedditFlairs.java new file mode 100644 index 0000000..710d320 --- /dev/null +++ b/src/main/java/com/fopost/sdk/model/RedditFlairs.java @@ -0,0 +1,6 @@ +package com.fopost.sdk.model; + +import java.util.List; + +/** The post flairs one subreddit offers. */ +public record RedditFlairs(String subreddit, List flairs) {} diff --git a/src/main/java/com/fopost/sdk/model/RedditSubreddit.java b/src/main/java/com/fopost/sdk/model/RedditSubreddit.java new file mode 100644 index 0000000..3a89fc3 --- /dev/null +++ b/src/main/java/com/fopost/sdk/model/RedditSubreddit.java @@ -0,0 +1,17 @@ +package com.fopost.sdk.model; + +/** + * A subreddit a Reddit account is in, or its own profile page. + * + *

{@code name} carries no {@code r/} prefix. {@code canPost} is false where the account may + * read but not submit, and {@code isDefault} marks where posts go when a post names no subreddit. + */ +public record RedditSubreddit( + String name, + String title, + Long subscribers, + Boolean over18, + Boolean canPost, + Boolean flairEnabled, + String iconUrl, + Boolean isDefault) {} diff --git a/src/main/java/com/fopost/sdk/model/RedditSubredditRule.java b/src/main/java/com/fopost/sdk/model/RedditSubredditRule.java new file mode 100644 index 0000000..e9ebc28 --- /dev/null +++ b/src/main/java/com/fopost/sdk/model/RedditSubredditRule.java @@ -0,0 +1,4 @@ +package com.fopost.sdk.model; + +/** One rule a subreddit publishes. {@code appliesTo} is {@code link}, {@code comment} or {@code all}. */ +public record RedditSubredditRule(String name, String description, String appliesTo) {} diff --git a/src/main/java/com/fopost/sdk/model/RedditSubredditRules.java b/src/main/java/com/fopost/sdk/model/RedditSubredditRules.java new file mode 100644 index 0000000..d5028fd --- /dev/null +++ b/src/main/java/com/fopost/sdk/model/RedditSubredditRules.java @@ -0,0 +1,6 @@ +package com.fopost.sdk.model; + +import java.util.List; + +/** A subreddit's rules, in its own order. Show them before publishing. */ +public record RedditSubredditRules(String subreddit, List rules) {} diff --git a/src/main/java/com/fopost/sdk/model/SubredditCheck.java b/src/main/java/com/fopost/sdk/model/SubredditCheck.java new file mode 100644 index 0000000..f9f5627 --- /dev/null +++ b/src/main/java/com/fopost/sdk/model/SubredditCheck.java @@ -0,0 +1,18 @@ +package com.fopost.sdk.model; + +import com.fasterxml.jackson.annotation.JsonAlias; + +/** + * Whether a subreddit exists and takes a post from a named Reddit account. + * + *

{@code ok} is true when both hold. A private, banned or missing subreddit answers 200 with + * {@code exists} false rather than an error. + */ +public record SubredditCheck( + String subreddit, + Boolean exists, + Boolean canPost, + // Neither the snake strategy nor the camel alias covers a digit boundary. + @JsonAlias("over_18") Boolean over18, + Boolean flairEnabled, + Boolean ok) {} diff --git a/src/main/java/com/fopost/sdk/resource/AccountsResource.java b/src/main/java/com/fopost/sdk/resource/AccountsResource.java index 05fa483..dbb7728 100644 --- a/src/main/java/com/fopost/sdk/resource/AccountsResource.java +++ b/src/main/java/com/fopost/sdk/resource/AccountsResource.java @@ -32,6 +32,10 @@ import com.fopost.sdk.model.InstagramStoryInsights; import com.fopost.sdk.model.LinkedInMention; import com.fopost.sdk.model.PinterestBoard; +import com.fopost.sdk.model.RedditDefaultSubreddit; +import com.fopost.sdk.model.RedditFlairs; +import com.fopost.sdk.model.RedditSubreddit; +import com.fopost.sdk.model.RedditSubredditRules; import com.fopost.sdk.model.SlackChannel; import com.fopost.sdk.model.SlackIdentity; import com.fopost.sdk.model.SlackMember; @@ -554,6 +558,47 @@ private DiscordAck ack(JsonNode body) { return http.convert(ApiClient.unwrap(body), DiscordAck.class); } + /** + * Subreddits a Reddit account is in, busiest first, plus its own profile page. + * + *

A 409 with code {@code reconnect_required} means the grant is short of a permission + * this read needs, and the account has to be reconnected. + */ + public List redditSubreddits(String accountId) { + return http.convertList( + ApiClient.unwrap(http.get("/v1/accounts/" + accountId + "/reddit/subreddits", null)), + RedditSubreddit.class); + } + + /** The rules a subreddit publishes, in its own order. Show them before publishing. */ + public RedditSubredditRules redditSubredditRules(String accountId, String subreddit) { + String path = "/v1/accounts/" + accountId + "/reddit/subreddits/" + subreddit + "/rules"; + return http.convert(ApiClient.unwrap(http.get(path, null)), RedditSubredditRules.class); + } + + /** + * Post flairs one subreddit offers. A flair id is valid only there, and one from elsewhere + * fails preflight. + */ + public RedditFlairs redditFlairs(String accountId, String subreddit) { + return http.convert( + ApiClient.unwrap(http.get("/v1/accounts/" + accountId + "/reddit/flairs", + query("subreddit", subreddit))), + RedditFlairs.class); + } + + /** + * Where posts from this account go when a post names no subreddit. Pass null to fall back to + * the account's own profile page, which always accepts a post. + */ + public RedditDefaultSubreddit setRedditDefaultSubreddit(String accountId, String subreddit) { + Map body = new LinkedHashMap<>(); + body.put("subreddit", subreddit); + return http.convert( + ApiClient.unwrap(http.put("/v1/accounts/" + accountId + "/reddit/default-subreddit", body)), + RedditDefaultSubreddit.class); + } + private static Map query(String key, Object value) { if (value == null) { return null; diff --git a/src/main/java/com/fopost/sdk/resource/InboxResource.java b/src/main/java/com/fopost/sdk/resource/InboxResource.java index 11ad9e1..ef1320f 100644 --- a/src/main/java/com/fopost/sdk/resource/InboxResource.java +++ b/src/main/java/com/fopost/sdk/resource/InboxResource.java @@ -199,6 +199,18 @@ public InboxItem like(String itemId) { return http.convert(ApiClient.unwrap(http.post("/v1/inbox/" + itemId + "/like", null)), InboxItem.class); } + /** + * Vote an item up or down where the network ranks by votes (Reddit). {@code direction} is + * {@code up}, {@code down}, or {@code none} to take an earlier vote back. Only where + * {@code canVote} is true. An upvote is the same call {@code like} makes, so {@code liked} + * moves with it. + */ + public InboxItem vote(String itemId, String direction) { + Map body = new LinkedHashMap<>(); + body.put("direction", direction); + return http.convert(ApiClient.unwrap(http.post("/v1/inbox/" + itemId + "/vote", body)), InboxItem.class); + } + public InboxItem unlike(String itemId) { return http.convert(ApiClient.unwrap(http.post("/v1/inbox/" + itemId + "/unlike", null)), InboxItem.class); } diff --git a/src/main/java/com/fopost/sdk/resource/ValidateResource.java b/src/main/java/com/fopost/sdk/resource/ValidateResource.java index 4e17f85..55ccb50 100644 --- a/src/main/java/com/fopost/sdk/resource/ValidateResource.java +++ b/src/main/java/com/fopost/sdk/resource/ValidateResource.java @@ -4,6 +4,7 @@ import com.fopost.sdk.model.LengthValidation; import com.fopost.sdk.model.MediaValidation; import com.fopost.sdk.model.PostValidation; +import com.fopost.sdk.model.SubredditCheck; import com.fopost.sdk.param.ValidatePostParams; import java.util.LinkedHashMap; import java.util.List; @@ -53,4 +54,17 @@ public MediaValidation media(String url) { body.put("url", url); return http.convert(ApiClient.unwrap(http.post("/v1/validate/media", body)), MediaValidation.class); } + + /** + * Whether a subreddit exists and takes a post from a connected Reddit account. + * + *

The check runs with that account's own token, so {@code accountId} is required. + */ + public SubredditCheck subreddit(String accountId, String name) { + Map query = new LinkedHashMap<>(); + query.put("account_id", accountId); + query.put("name", name); + return http.convert( + ApiClient.unwrap(http.get("/v1/validate/subreddit", query)), SubredditCheck.class); + } } diff --git a/src/test/java/com/fopost/sdk/RedditTest.java b/src/test/java/com/fopost/sdk/RedditTest.java new file mode 100644 index 0000000..a413c34 --- /dev/null +++ b/src/test/java/com/fopost/sdk/RedditTest.java @@ -0,0 +1,97 @@ +package com.fopost.sdk; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import com.fopost.sdk.model.InboxItem; +import com.fopost.sdk.model.RedditDefaultSubreddit; +import com.fopost.sdk.model.RedditFlairs; +import com.fopost.sdk.model.RedditSubreddit; +import com.fopost.sdk.model.RedditSubredditRules; +import com.fopost.sdk.model.SubredditCheck; +import java.util.List; +import org.junit.jupiter.api.Test; + +class RedditTest { + + @Test + void subredditsRulesAndFlairsReadTheCamelCaseWire() { + FakeTransport transport = new FakeTransport() + .enqueue(200, """ + {"data":[{"name":"webdev","title":"Web Development","subscribers":2000000, + "over18":false,"canPost":true,"flairEnabled":true, + "iconUrl":null,"isDefault":true}]}""") + .enqueue(200, """ + {"data":{"subreddit":"webdev","rules":[{"name":"No self promotion", + "description":"Keep it useful","appliesTo":"link"}]}}""") + .enqueue(200, """ + {"data":{"subreddit":"webdev","flairs":[{"id":"flair-1", + "text":"Showoff Saturday","editable":false}]}}"""); + FoPost client = TestSupport.client(transport); + + List subreddits = client.accounts().redditSubreddits("a1"); + assertEquals("https://api.fopost.test/v1/accounts/a1/reddit/subreddits", transport.last().url()); + assertEquals("webdev", subreddits.get(0).name()); + assertTrue(subreddits.get(0).isDefault()); + assertTrue(subreddits.get(0).flairEnabled()); + + RedditSubredditRules rules = client.accounts().redditSubredditRules("a1", "webdev"); + assertEquals( + "https://api.fopost.test/v1/accounts/a1/reddit/subreddits/webdev/rules", + transport.last().url()); + assertEquals("link", rules.rules().get(0).appliesTo()); + + RedditFlairs flairs = client.accounts().redditFlairs("a1", "webdev"); + assertEquals( + "https://api.fopost.test/v1/accounts/a1/reddit/flairs?subreddit=webdev", + transport.last().url()); + assertEquals("flair-1", flairs.flairs().get(0).id()); + } + + @Test + void theDefaultSubredditSendsNullToFallBackToTheProfilePage() { + FakeTransport transport = new FakeTransport().enqueue(200, "{\"data\":{\"subreddit\":null}}"); + FoPost client = TestSupport.client(transport); + + RedditDefaultSubreddit result = client.accounts().setRedditDefaultSubreddit("a1", null); + + assertEquals("PUT", transport.last().method()); + assertEquals("{}", transport.lastBody()); + assertNull(result.subreddit()); + } + + @Test + void validatingASubredditNamesTheAccountItReadsAs() { + FakeTransport transport = new FakeTransport() + .enqueue(200, """ + {"data":{"subreddit":"webdev","exists":true,"can_post":true, + "over_18":false,"flair_enabled":true,"ok":true}}"""); + FoPost client = TestSupport.client(transport); + + SubredditCheck check = client.validate().subreddit("a1", "webdev"); + + assertEquals( + "https://api.fopost.test/v1/validate/subreddit?account_id=a1&name=webdev", + transport.last().url()); + assertTrue(check.ok()); + assertTrue(check.canPost()); + assertEquals(Boolean.FALSE, check.over18()); + } + + @Test + void votingSendsTheDirection() { + FakeTransport transport = new FakeTransport() + .enqueue(200, """ + {"data":{"id":"i1","platform":"reddit","type":"comment","state":"unread", + "vote":"down","canVote":true}}"""); + FoPost client = TestSupport.client(transport); + + InboxItem item = client.inbox().vote("i1", "down"); + + assertEquals("https://api.fopost.test/v1/inbox/i1/vote", transport.last().url()); + assertEquals("{\"direction\":\"down\"}", transport.lastBody()); + assertEquals("down", item.vote()); + assertTrue(item.canVote()); + } +}