diff --git a/docs/typed_api.md b/docs/typed_api.md index 16b001b..e8ee25f 100644 --- a/docs/typed_api.md +++ b/docs/typed_api.md @@ -20,7 +20,7 @@ specific family of responses into owned structs freed by a matching `_free`: - **Graph** — `graph_typed.h` (follows, followers, profiles, search, reposts, likes) - **Embed** — `embed_typed.h` (build owned image / video / record / external embeds) - **Notification** — `agent.h` + `notification` parsers (`wf_agent_list_notifications_typed`) -- **Unspecced** (NEW) — `unspecced_typed.h` (trending topics, tagged/suggestions skeletons, config, age assurance, onboarding starter packs, starter-pack search). See [unspecced.md](unspecced.md). +- **Unspecced** — `unspecced_typed.h` (full coverage: 8 agent-level wrappers with custom cJSON parsers + 22 XRPC-level wrappers using generated atproto_lex.h types). See [unspecced.md](unspecced.md). - **Chat** — `chat_typed.h` (conversation / message views) - **Moderation** — `moderation_typed.h` (label / moderation detail views) - **Feed generator** — `feedgen_typed.h` (feed generator / skeleton parsing) diff --git a/docs/unspecced.md b/docs/unspecced.md index b10b0c9..0947f29 100644 --- a/docs/unspecced.md +++ b/docs/unspecced.md @@ -1,21 +1,16 @@ # Unspecced typed API (`unspecced_typed.h`) The `app.bsky.unspecced` lexicon holds experimental or auxiliary endpoints that -are not part of the stable core API. `unspecced_typed.h` provides *typed* wrappers -that issue the agent call and parse the raw JSON body into owned C structs, plus -lower-level `wf_agent_parse_*` functions for parsing an already-fetched body -(e.g. an offline fixture). - -Covered endpoints: - -- `getTrendingTopics` — trending topics and suggested topics -- `getTaggedSuggestions` — curated actor/feed suggestions by tag -- `getSuggestionsSkeleton` — suggested actors (skeleton) -- `getConfig` — server/live-now configuration for the viewer -- `getAgeAssuranceState` — viewer age-assurance status -- `getOnboardingSuggestedStarterPacks` — full starter-pack views -- `getOnboardingSuggestedStarterPacksSkeleton` — starter-pack at-uris only -- `searchStarterPacksSkeleton` — starter-pack search hits +are not part of the stable core API. `unspecced_typed.h` provides: + +- **Agent-level typed wrappers** (`wf_agent_*_typed`) for 8 endpoints with + custom cJSON parsers (trending topics, tagged suggestions, suggestions + skeleton, config, age assurance, onboarding starter packs, starter-pack + search). +- **XRPC-level convenience wrappers** (`wf_unspecced_*`) for all 30 + `app.bsky.unspecced` endpoints, using the generated atproto_lex.h types. + Each query endpoint has a `wf_unspecced_*_parse` helper that decodes the + raw `wf_response` body into an owned generated output struct. This module mirrors the conventions in `feed_typed.h` / `feedgen_typed.h`: it returns `wf_status` error codes (`WF_ERR_INVALID_ARG`, `WF_ERR_PARSE`, @@ -175,3 +170,42 @@ Available parse functions: `wf_agent_parse_trending_topics`, | `wf_agent_search_starter_packs_list` | `wf_agent_search_starter_packs_free` | All free functions are safe to call with `NULL`. + +## XRPC-level wrappers (`wf_unspecced_*`) + +The 22 remaining `app.bsky.unspecced` endpoints are exposed as thin +XRPC-level wrappers in `unspecced_typed.h`. They follow the `ozone.h` pattern: +the call function validates inputs and delegates to the generated +`wf_lex_app_bsky_unspecced_*_main_call`; the `*_parse` helper wraps the +generated `_output_decode_json`. + +Usage pattern: + +```c +#include "wolfram/unspecced_typed.h" /* also reachable via agent.h */ + +wf_xrpc_client *client = /* … */; +/* Query params */ +wf_lex_app_bsky_unspecced_search_actors_skeleton_main_params params = {0}; +params.q = "alice"; +params.has_limit = true; +params.limit = 25; + +wf_response res = {0}; +wf_status s = wf_unspecced_search_actors_skeleton(client, ¶ms, &res); +if (s != WF_OK) { /* handle error */ } + +/* Decode the response body into an owned typed struct */ +wf_lex_app_bsky_unspecced_search_actors_skeleton_main_output *out = NULL; +s = wf_unspecced_search_actors_skeleton_parse(&res, &out); +if (s == WF_OK && out) { + for (size_t i = 0; i < out->actors.count; ++i) { + printf("actor: %s\n", out->actors.items[i]->did); + } + wf_lex_app_bsky_unspecced_search_actors_skeleton_main_output_free(out); +} +wf_response_free(&res); +``` + +Each NSID is also available as a `WF_UNSPECCED_*_NSID` define for use with +the lower-level XRPC API directly. diff --git a/include/wolfram/unspecced_typed.h b/include/wolfram/unspecced_typed.h index 33be7f5..f5ed712 100644 --- a/include/wolfram/unspecced_typed.h +++ b/include/wolfram/unspecced_typed.h @@ -267,6 +267,98 @@ wf_status wf_agent_search_starter_packs_typed( wf_agent *agent, const char *q, const char *viewer, int limit, const char *cursor, wf_agent_search_starter_packs_list *out); +/* ------------------------------------------------------------------ */ +/* Unspecced — XRPC-level convenience wrappers */ +/* ------------------------------------------------------------------ */ + +#define WF_UNSPECCED_GETONBOARDINGSUGGESTEDUSERSSKELETON_NSID "app.bsky.unspecced.getOnboardingSuggestedUsersSkeleton" +#define WF_UNSPECCED_GETPOPULARFEEDGENERATORS_NSID "app.bsky.unspecced.getPopularFeedGenerators" +#define WF_UNSPECCED_GETPOSTTHREADOTHERV2_NSID "app.bsky.unspecced.getPostThreadOtherV2" +#define WF_UNSPECCED_GETPOSTTHREADV2_NSID "app.bsky.unspecced.getPostThreadV2" +#define WF_UNSPECCED_GETSUGGESTEDFEEDS_NSID "app.bsky.unspecced.getSuggestedFeeds" +#define WF_UNSPECCED_GETSUGGESTEDFEEDSSKELETON_NSID "app.bsky.unspecced.getSuggestedFeedsSkeleton" +#define WF_UNSPECCED_GETSUGGESTEDONBOARDINGUSERS_NSID "app.bsky.unspecced.getSuggestedOnboardingUsers" +#define WF_UNSPECCED_GETSUGGESTEDSTARTERPACKS_NSID "app.bsky.unspecced.getSuggestedStarterPacks" +#define WF_UNSPECCED_GETSUGGESTEDSTARTERPACKSSKELETON_NSID "app.bsky.unspecced.getSuggestedStarterPacksSkeleton" +#define WF_UNSPECCED_GETSUGGESTEDUSERS_NSID "app.bsky.unspecced.getSuggestedUsers" +#define WF_UNSPECCED_GETSUGGESTEDUSERSFORDISCOVER_NSID "app.bsky.unspecced.getSuggestedUsersForDiscover" +#define WF_UNSPECCED_GETSUGGESTEDUSERSFORDISCOVERSKELETON_NSID "app.bsky.unspecced.getSuggestedUsersForDiscoverSkeleton" +#define WF_UNSPECCED_GETSUGGESTEDUSERSFOREXPLORE_NSID "app.bsky.unspecced.getSuggestedUsersForExplore" +#define WF_UNSPECCED_GETSUGGESTEDUSERSFOREXPLORESKELETON_NSID "app.bsky.unspecced.getSuggestedUsersForExploreSkeleton" +#define WF_UNSPECCED_GETSUGGESTEDUSERSFORSEEMORE_NSID "app.bsky.unspecced.getSuggestedUsersForSeeMore" +#define WF_UNSPECCED_GETSUGGESTEDUSERSFORSEEMORESKELETON_NSID "app.bsky.unspecced.getSuggestedUsersForSeeMoreSkeleton" +#define WF_UNSPECCED_GETSUGGESTEDUSERSSKELETON_NSID "app.bsky.unspecced.getSuggestedUsersSkeleton" +#define WF_UNSPECCED_GETTRENDS_NSID "app.bsky.unspecced.getTrends" +#define WF_UNSPECCED_GETTRENDSSKELETON_NSID "app.bsky.unspecced.getTrendsSkeleton" +#define WF_UNSPECCED_INITAGEASSURANCE_NSID "app.bsky.unspecced.initAgeAssurance" +#define WF_UNSPECCED_SEARCHACTORSSKELETON_NSID "app.bsky.unspecced.searchActorsSkeleton" +#define WF_UNSPECCED_SEARCHPOSTSSKELETON_NSID "app.bsky.unspecced.searchPostsSkeleton" + +wf_status wf_unspecced_get_onboarding_suggested_users_skeleton(wf_xrpc_client *client, const wf_lex_app_bsky_unspecced_get_onboarding_suggested_users_skeleton_main_params *params, wf_response *out); +wf_status wf_unspecced_get_onboarding_suggested_users_skeleton_parse(const wf_response *resp, wf_lex_app_bsky_unspecced_get_onboarding_suggested_users_skeleton_main_output **out); + +wf_status wf_unspecced_get_popular_feed_generators(wf_xrpc_client *client, const wf_lex_app_bsky_unspecced_get_popular_feed_generators_main_params *params, wf_response *out); +wf_status wf_unspecced_get_popular_feed_generators_parse(const wf_response *resp, wf_lex_app_bsky_unspecced_get_popular_feed_generators_main_output **out); + +wf_status wf_unspecced_get_post_thread_other_v2(wf_xrpc_client *client, const wf_lex_app_bsky_unspecced_get_post_thread_other_v2_main_params *params, wf_response *out); +wf_status wf_unspecced_get_post_thread_other_v2_parse(const wf_response *resp, wf_lex_app_bsky_unspecced_get_post_thread_other_v2_main_output **out); + +wf_status wf_unspecced_get_post_thread_v2(wf_xrpc_client *client, const wf_lex_app_bsky_unspecced_get_post_thread_v2_main_params *params, wf_response *out); +wf_status wf_unspecced_get_post_thread_v2_parse(const wf_response *resp, wf_lex_app_bsky_unspecced_get_post_thread_v2_main_output **out); + +wf_status wf_unspecced_get_suggested_feeds(wf_xrpc_client *client, const wf_lex_app_bsky_unspecced_get_suggested_feeds_main_params *params, wf_response *out); +wf_status wf_unspecced_get_suggested_feeds_parse(const wf_response *resp, wf_lex_app_bsky_unspecced_get_suggested_feeds_main_output **out); + +wf_status wf_unspecced_get_suggested_feeds_skeleton(wf_xrpc_client *client, const wf_lex_app_bsky_unspecced_get_suggested_feeds_skeleton_main_params *params, wf_response *out); +wf_status wf_unspecced_get_suggested_feeds_skeleton_parse(const wf_response *resp, wf_lex_app_bsky_unspecced_get_suggested_feeds_skeleton_main_output **out); + +wf_status wf_unspecced_get_suggested_onboarding_users(wf_xrpc_client *client, const wf_lex_app_bsky_unspecced_get_suggested_onboarding_users_main_params *params, wf_response *out); +wf_status wf_unspecced_get_suggested_onboarding_users_parse(const wf_response *resp, wf_lex_app_bsky_unspecced_get_suggested_onboarding_users_main_output **out); + +wf_status wf_unspecced_get_suggested_starter_packs(wf_xrpc_client *client, const wf_lex_app_bsky_unspecced_get_suggested_starter_packs_main_params *params, wf_response *out); +wf_status wf_unspecced_get_suggested_starter_packs_parse(const wf_response *resp, wf_lex_app_bsky_unspecced_get_suggested_starter_packs_main_output **out); + +wf_status wf_unspecced_get_suggested_starter_packs_skeleton(wf_xrpc_client *client, const wf_lex_app_bsky_unspecced_get_suggested_starter_packs_skeleton_main_params *params, wf_response *out); +wf_status wf_unspecced_get_suggested_starter_packs_skeleton_parse(const wf_response *resp, wf_lex_app_bsky_unspecced_get_suggested_starter_packs_skeleton_main_output **out); + +wf_status wf_unspecced_get_suggested_users(wf_xrpc_client *client, const wf_lex_app_bsky_unspecced_get_suggested_users_main_params *params, wf_response *out); +wf_status wf_unspecced_get_suggested_users_parse(const wf_response *resp, wf_lex_app_bsky_unspecced_get_suggested_users_main_output **out); + +wf_status wf_unspecced_get_suggested_users_for_discover(wf_xrpc_client *client, const wf_lex_app_bsky_unspecced_get_suggested_users_for_discover_main_params *params, wf_response *out); +wf_status wf_unspecced_get_suggested_users_for_discover_parse(const wf_response *resp, wf_lex_app_bsky_unspecced_get_suggested_users_for_discover_main_output **out); + +wf_status wf_unspecced_get_suggested_users_for_discover_skeleton(wf_xrpc_client *client, const wf_lex_app_bsky_unspecced_get_suggested_users_for_discover_skeleton_main_params *params, wf_response *out); +wf_status wf_unspecced_get_suggested_users_for_discover_skeleton_parse(const wf_response *resp, wf_lex_app_bsky_unspecced_get_suggested_users_for_discover_skeleton_main_output **out); + +wf_status wf_unspecced_get_suggested_users_for_explore(wf_xrpc_client *client, const wf_lex_app_bsky_unspecced_get_suggested_users_for_explore_main_params *params, wf_response *out); +wf_status wf_unspecced_get_suggested_users_for_explore_parse(const wf_response *resp, wf_lex_app_bsky_unspecced_get_suggested_users_for_explore_main_output **out); + +wf_status wf_unspecced_get_suggested_users_for_explore_skeleton(wf_xrpc_client *client, const wf_lex_app_bsky_unspecced_get_suggested_users_for_explore_skeleton_main_params *params, wf_response *out); +wf_status wf_unspecced_get_suggested_users_for_explore_skeleton_parse(const wf_response *resp, wf_lex_app_bsky_unspecced_get_suggested_users_for_explore_skeleton_main_output **out); + +wf_status wf_unspecced_get_suggested_users_for_see_more(wf_xrpc_client *client, const wf_lex_app_bsky_unspecced_get_suggested_users_for_see_more_main_params *params, wf_response *out); +wf_status wf_unspecced_get_suggested_users_for_see_more_parse(const wf_response *resp, wf_lex_app_bsky_unspecced_get_suggested_users_for_see_more_main_output **out); + +wf_status wf_unspecced_get_suggested_users_for_see_more_skeleton(wf_xrpc_client *client, const wf_lex_app_bsky_unspecced_get_suggested_users_for_see_more_skeleton_main_params *params, wf_response *out); +wf_status wf_unspecced_get_suggested_users_for_see_more_skeleton_parse(const wf_response *resp, wf_lex_app_bsky_unspecced_get_suggested_users_for_see_more_skeleton_main_output **out); + +wf_status wf_unspecced_get_suggested_users_skeleton(wf_xrpc_client *client, const wf_lex_app_bsky_unspecced_get_suggested_users_skeleton_main_params *params, wf_response *out); +wf_status wf_unspecced_get_suggested_users_skeleton_parse(const wf_response *resp, wf_lex_app_bsky_unspecced_get_suggested_users_skeleton_main_output **out); + +wf_status wf_unspecced_get_trends(wf_xrpc_client *client, const wf_lex_app_bsky_unspecced_get_trends_main_params *params, wf_response *out); +wf_status wf_unspecced_get_trends_parse(const wf_response *resp, wf_lex_app_bsky_unspecced_get_trends_main_output **out); + +wf_status wf_unspecced_get_trends_skeleton(wf_xrpc_client *client, const wf_lex_app_bsky_unspecced_get_trends_skeleton_main_params *params, wf_response *out); +wf_status wf_unspecced_get_trends_skeleton_parse(const wf_response *resp, wf_lex_app_bsky_unspecced_get_trends_skeleton_main_output **out); + +wf_status wf_unspecced_init_age_assurance(wf_xrpc_client *client, const wf_lex_app_bsky_unspecced_init_age_assurance_main_input *input, wf_response *out); + +wf_status wf_unspecced_search_actors_skeleton(wf_xrpc_client *client, const wf_lex_app_bsky_unspecced_search_actors_skeleton_main_params *params, wf_response *out); +wf_status wf_unspecced_search_actors_skeleton_parse(const wf_response *resp, wf_lex_app_bsky_unspecced_search_actors_skeleton_main_output **out); + +wf_status wf_unspecced_search_posts_skeleton(wf_xrpc_client *client, const wf_lex_app_bsky_unspecced_search_posts_skeleton_main_params *params, wf_response *out); +wf_status wf_unspecced_search_posts_skeleton_parse(const wf_response *resp, wf_lex_app_bsky_unspecced_search_posts_skeleton_main_output **out); + #ifdef __cplusplus } #endif diff --git a/src/unspecced_typed.c b/src/unspecced_typed.c index de76173..c0fb751 100644 --- a/src/unspecced_typed.c +++ b/src/unspecced_typed.c @@ -1062,3 +1062,521 @@ wf_status wf_agent_search_starter_packs_typed( wf_response_free(&res); return status; } + +/* ================================================================== */ +/* Unspecced — generated XRPC-level convenience wrappers */ +/* ================================================================== */ + +wf_status wf_unspecced_get_onboarding_suggested_users_skeleton( + wf_xrpc_client *client, + const wf_lex_app_bsky_unspecced_get_onboarding_suggested_users_skeleton_main_params *params, + wf_response *out) { + if (!client || !params || !out) { + return WF_ERR_INVALID_ARG; + } + return wf_lex_app_bsky_unspecced_get_onboarding_suggested_users_skeleton_main_call(client, params, out); +} + +wf_status wf_unspecced_get_onboarding_suggested_users_skeleton_parse( + const wf_response *resp, + wf_lex_app_bsky_unspecced_get_onboarding_suggested_users_skeleton_main_output **out) { + if (!resp || !out) { + return WF_ERR_INVALID_ARG; + } + *out = NULL; + if (!resp->body) { + return WF_ERR_INVALID_ARG; + } + return wf_lex_app_bsky_unspecced_get_onboarding_suggested_users_skeleton_main_output_decode_json( + resp->body, resp->body_len, out); +} + +wf_status wf_unspecced_get_popular_feed_generators( + wf_xrpc_client *client, + const wf_lex_app_bsky_unspecced_get_popular_feed_generators_main_params *params, + wf_response *out) { + if (!client || !params || !out) { + return WF_ERR_INVALID_ARG; + } + return wf_lex_app_bsky_unspecced_get_popular_feed_generators_main_call(client, params, out); +} + +wf_status wf_unspecced_get_popular_feed_generators_parse( + const wf_response *resp, + wf_lex_app_bsky_unspecced_get_popular_feed_generators_main_output **out) { + if (!resp || !out) { + return WF_ERR_INVALID_ARG; + } + *out = NULL; + if (!resp->body) { + return WF_ERR_INVALID_ARG; + } + return wf_lex_app_bsky_unspecced_get_popular_feed_generators_main_output_decode_json( + resp->body, resp->body_len, out); +} + +wf_status wf_unspecced_get_post_thread_other_v2( + wf_xrpc_client *client, + const wf_lex_app_bsky_unspecced_get_post_thread_other_v2_main_params *params, + wf_response *out) { + if (!client || !params || !out) { + return WF_ERR_INVALID_ARG; + } + return wf_lex_app_bsky_unspecced_get_post_thread_other_v2_main_call(client, params, out); +} + +wf_status wf_unspecced_get_post_thread_other_v2_parse( + const wf_response *resp, + wf_lex_app_bsky_unspecced_get_post_thread_other_v2_main_output **out) { + if (!resp || !out) { + return WF_ERR_INVALID_ARG; + } + *out = NULL; + if (!resp->body) { + return WF_ERR_INVALID_ARG; + } + return wf_lex_app_bsky_unspecced_get_post_thread_other_v2_main_output_decode_json( + resp->body, resp->body_len, out); +} + +wf_status wf_unspecced_get_post_thread_v2( + wf_xrpc_client *client, + const wf_lex_app_bsky_unspecced_get_post_thread_v2_main_params *params, + wf_response *out) { + if (!client || !params || !out) { + return WF_ERR_INVALID_ARG; + } + return wf_lex_app_bsky_unspecced_get_post_thread_v2_main_call(client, params, out); +} + +wf_status wf_unspecced_get_post_thread_v2_parse( + const wf_response *resp, + wf_lex_app_bsky_unspecced_get_post_thread_v2_main_output **out) { + if (!resp || !out) { + return WF_ERR_INVALID_ARG; + } + *out = NULL; + if (!resp->body) { + return WF_ERR_INVALID_ARG; + } + return wf_lex_app_bsky_unspecced_get_post_thread_v2_main_output_decode_json( + resp->body, resp->body_len, out); +} + +wf_status wf_unspecced_get_suggested_feeds( + wf_xrpc_client *client, + const wf_lex_app_bsky_unspecced_get_suggested_feeds_main_params *params, + wf_response *out) { + if (!client || !params || !out) { + return WF_ERR_INVALID_ARG; + } + return wf_lex_app_bsky_unspecced_get_suggested_feeds_main_call(client, params, out); +} + +wf_status wf_unspecced_get_suggested_feeds_parse( + const wf_response *resp, + wf_lex_app_bsky_unspecced_get_suggested_feeds_main_output **out) { + if (!resp || !out) { + return WF_ERR_INVALID_ARG; + } + *out = NULL; + if (!resp->body) { + return WF_ERR_INVALID_ARG; + } + return wf_lex_app_bsky_unspecced_get_suggested_feeds_main_output_decode_json( + resp->body, resp->body_len, out); +} + +wf_status wf_unspecced_get_suggested_feeds_skeleton( + wf_xrpc_client *client, + const wf_lex_app_bsky_unspecced_get_suggested_feeds_skeleton_main_params *params, + wf_response *out) { + if (!client || !params || !out) { + return WF_ERR_INVALID_ARG; + } + return wf_lex_app_bsky_unspecced_get_suggested_feeds_skeleton_main_call(client, params, out); +} + +wf_status wf_unspecced_get_suggested_feeds_skeleton_parse( + const wf_response *resp, + wf_lex_app_bsky_unspecced_get_suggested_feeds_skeleton_main_output **out) { + if (!resp || !out) { + return WF_ERR_INVALID_ARG; + } + *out = NULL; + if (!resp->body) { + return WF_ERR_INVALID_ARG; + } + return wf_lex_app_bsky_unspecced_get_suggested_feeds_skeleton_main_output_decode_json( + resp->body, resp->body_len, out); +} + +wf_status wf_unspecced_get_suggested_onboarding_users( + wf_xrpc_client *client, + const wf_lex_app_bsky_unspecced_get_suggested_onboarding_users_main_params *params, + wf_response *out) { + if (!client || !params || !out) { + return WF_ERR_INVALID_ARG; + } + return wf_lex_app_bsky_unspecced_get_suggested_onboarding_users_main_call(client, params, out); +} + +wf_status wf_unspecced_get_suggested_onboarding_users_parse( + const wf_response *resp, + wf_lex_app_bsky_unspecced_get_suggested_onboarding_users_main_output **out) { + if (!resp || !out) { + return WF_ERR_INVALID_ARG; + } + *out = NULL; + if (!resp->body) { + return WF_ERR_INVALID_ARG; + } + return wf_lex_app_bsky_unspecced_get_suggested_onboarding_users_main_output_decode_json( + resp->body, resp->body_len, out); +} + +wf_status wf_unspecced_get_suggested_starter_packs( + wf_xrpc_client *client, + const wf_lex_app_bsky_unspecced_get_suggested_starter_packs_main_params *params, + wf_response *out) { + if (!client || !params || !out) { + return WF_ERR_INVALID_ARG; + } + return wf_lex_app_bsky_unspecced_get_suggested_starter_packs_main_call(client, params, out); +} + +wf_status wf_unspecced_get_suggested_starter_packs_parse( + const wf_response *resp, + wf_lex_app_bsky_unspecced_get_suggested_starter_packs_main_output **out) { + if (!resp || !out) { + return WF_ERR_INVALID_ARG; + } + *out = NULL; + if (!resp->body) { + return WF_ERR_INVALID_ARG; + } + return wf_lex_app_bsky_unspecced_get_suggested_starter_packs_main_output_decode_json( + resp->body, resp->body_len, out); +} + +wf_status wf_unspecced_get_suggested_starter_packs_skeleton( + wf_xrpc_client *client, + const wf_lex_app_bsky_unspecced_get_suggested_starter_packs_skeleton_main_params *params, + wf_response *out) { + if (!client || !params || !out) { + return WF_ERR_INVALID_ARG; + } + return wf_lex_app_bsky_unspecced_get_suggested_starter_packs_skeleton_main_call(client, params, out); +} + +wf_status wf_unspecced_get_suggested_starter_packs_skeleton_parse( + const wf_response *resp, + wf_lex_app_bsky_unspecced_get_suggested_starter_packs_skeleton_main_output **out) { + if (!resp || !out) { + return WF_ERR_INVALID_ARG; + } + *out = NULL; + if (!resp->body) { + return WF_ERR_INVALID_ARG; + } + return wf_lex_app_bsky_unspecced_get_suggested_starter_packs_skeleton_main_output_decode_json( + resp->body, resp->body_len, out); +} + +wf_status wf_unspecced_get_suggested_users( + wf_xrpc_client *client, + const wf_lex_app_bsky_unspecced_get_suggested_users_main_params *params, + wf_response *out) { + if (!client || !params || !out) { + return WF_ERR_INVALID_ARG; + } + return wf_lex_app_bsky_unspecced_get_suggested_users_main_call(client, params, out); +} + +wf_status wf_unspecced_get_suggested_users_parse( + const wf_response *resp, + wf_lex_app_bsky_unspecced_get_suggested_users_main_output **out) { + if (!resp || !out) { + return WF_ERR_INVALID_ARG; + } + *out = NULL; + if (!resp->body) { + return WF_ERR_INVALID_ARG; + } + return wf_lex_app_bsky_unspecced_get_suggested_users_main_output_decode_json( + resp->body, resp->body_len, out); +} + +wf_status wf_unspecced_get_suggested_users_for_discover( + wf_xrpc_client *client, + const wf_lex_app_bsky_unspecced_get_suggested_users_for_discover_main_params *params, + wf_response *out) { + if (!client || !params || !out) { + return WF_ERR_INVALID_ARG; + } + return wf_lex_app_bsky_unspecced_get_suggested_users_for_discover_main_call(client, params, out); +} + +wf_status wf_unspecced_get_suggested_users_for_discover_parse( + const wf_response *resp, + wf_lex_app_bsky_unspecced_get_suggested_users_for_discover_main_output **out) { + if (!resp || !out) { + return WF_ERR_INVALID_ARG; + } + *out = NULL; + if (!resp->body) { + return WF_ERR_INVALID_ARG; + } + return wf_lex_app_bsky_unspecced_get_suggested_users_for_discover_main_output_decode_json( + resp->body, resp->body_len, out); +} + +wf_status wf_unspecced_get_suggested_users_for_discover_skeleton( + wf_xrpc_client *client, + const wf_lex_app_bsky_unspecced_get_suggested_users_for_discover_skeleton_main_params *params, + wf_response *out) { + if (!client || !params || !out) { + return WF_ERR_INVALID_ARG; + } + return wf_lex_app_bsky_unspecced_get_suggested_users_for_discover_skeleton_main_call(client, params, out); +} + +wf_status wf_unspecced_get_suggested_users_for_discover_skeleton_parse( + const wf_response *resp, + wf_lex_app_bsky_unspecced_get_suggested_users_for_discover_skeleton_main_output **out) { + if (!resp || !out) { + return WF_ERR_INVALID_ARG; + } + *out = NULL; + if (!resp->body) { + return WF_ERR_INVALID_ARG; + } + return wf_lex_app_bsky_unspecced_get_suggested_users_for_discover_skeleton_main_output_decode_json( + resp->body, resp->body_len, out); +} + +wf_status wf_unspecced_get_suggested_users_for_explore( + wf_xrpc_client *client, + const wf_lex_app_bsky_unspecced_get_suggested_users_for_explore_main_params *params, + wf_response *out) { + if (!client || !params || !out) { + return WF_ERR_INVALID_ARG; + } + return wf_lex_app_bsky_unspecced_get_suggested_users_for_explore_main_call(client, params, out); +} + +wf_status wf_unspecced_get_suggested_users_for_explore_parse( + const wf_response *resp, + wf_lex_app_bsky_unspecced_get_suggested_users_for_explore_main_output **out) { + if (!resp || !out) { + return WF_ERR_INVALID_ARG; + } + *out = NULL; + if (!resp->body) { + return WF_ERR_INVALID_ARG; + } + return wf_lex_app_bsky_unspecced_get_suggested_users_for_explore_main_output_decode_json( + resp->body, resp->body_len, out); +} + +wf_status wf_unspecced_get_suggested_users_for_explore_skeleton( + wf_xrpc_client *client, + const wf_lex_app_bsky_unspecced_get_suggested_users_for_explore_skeleton_main_params *params, + wf_response *out) { + if (!client || !params || !out) { + return WF_ERR_INVALID_ARG; + } + return wf_lex_app_bsky_unspecced_get_suggested_users_for_explore_skeleton_main_call(client, params, out); +} + +wf_status wf_unspecced_get_suggested_users_for_explore_skeleton_parse( + const wf_response *resp, + wf_lex_app_bsky_unspecced_get_suggested_users_for_explore_skeleton_main_output **out) { + if (!resp || !out) { + return WF_ERR_INVALID_ARG; + } + *out = NULL; + if (!resp->body) { + return WF_ERR_INVALID_ARG; + } + return wf_lex_app_bsky_unspecced_get_suggested_users_for_explore_skeleton_main_output_decode_json( + resp->body, resp->body_len, out); +} + +wf_status wf_unspecced_get_suggested_users_for_see_more( + wf_xrpc_client *client, + const wf_lex_app_bsky_unspecced_get_suggested_users_for_see_more_main_params *params, + wf_response *out) { + if (!client || !params || !out) { + return WF_ERR_INVALID_ARG; + } + return wf_lex_app_bsky_unspecced_get_suggested_users_for_see_more_main_call(client, params, out); +} + +wf_status wf_unspecced_get_suggested_users_for_see_more_parse( + const wf_response *resp, + wf_lex_app_bsky_unspecced_get_suggested_users_for_see_more_main_output **out) { + if (!resp || !out) { + return WF_ERR_INVALID_ARG; + } + *out = NULL; + if (!resp->body) { + return WF_ERR_INVALID_ARG; + } + return wf_lex_app_bsky_unspecced_get_suggested_users_for_see_more_main_output_decode_json( + resp->body, resp->body_len, out); +} + +wf_status wf_unspecced_get_suggested_users_for_see_more_skeleton( + wf_xrpc_client *client, + const wf_lex_app_bsky_unspecced_get_suggested_users_for_see_more_skeleton_main_params *params, + wf_response *out) { + if (!client || !params || !out) { + return WF_ERR_INVALID_ARG; + } + return wf_lex_app_bsky_unspecced_get_suggested_users_for_see_more_skeleton_main_call(client, params, out); +} + +wf_status wf_unspecced_get_suggested_users_for_see_more_skeleton_parse( + const wf_response *resp, + wf_lex_app_bsky_unspecced_get_suggested_users_for_see_more_skeleton_main_output **out) { + if (!resp || !out) { + return WF_ERR_INVALID_ARG; + } + *out = NULL; + if (!resp->body) { + return WF_ERR_INVALID_ARG; + } + return wf_lex_app_bsky_unspecced_get_suggested_users_for_see_more_skeleton_main_output_decode_json( + resp->body, resp->body_len, out); +} + +wf_status wf_unspecced_get_suggested_users_skeleton( + wf_xrpc_client *client, + const wf_lex_app_bsky_unspecced_get_suggested_users_skeleton_main_params *params, + wf_response *out) { + if (!client || !params || !out) { + return WF_ERR_INVALID_ARG; + } + return wf_lex_app_bsky_unspecced_get_suggested_users_skeleton_main_call(client, params, out); +} + +wf_status wf_unspecced_get_suggested_users_skeleton_parse( + const wf_response *resp, + wf_lex_app_bsky_unspecced_get_suggested_users_skeleton_main_output **out) { + if (!resp || !out) { + return WF_ERR_INVALID_ARG; + } + *out = NULL; + if (!resp->body) { + return WF_ERR_INVALID_ARG; + } + return wf_lex_app_bsky_unspecced_get_suggested_users_skeleton_main_output_decode_json( + resp->body, resp->body_len, out); +} + +wf_status wf_unspecced_get_trends( + wf_xrpc_client *client, + const wf_lex_app_bsky_unspecced_get_trends_main_params *params, + wf_response *out) { + if (!client || !params || !out) { + return WF_ERR_INVALID_ARG; + } + return wf_lex_app_bsky_unspecced_get_trends_main_call(client, params, out); +} + +wf_status wf_unspecced_get_trends_parse( + const wf_response *resp, + wf_lex_app_bsky_unspecced_get_trends_main_output **out) { + if (!resp || !out) { + return WF_ERR_INVALID_ARG; + } + *out = NULL; + if (!resp->body) { + return WF_ERR_INVALID_ARG; + } + return wf_lex_app_bsky_unspecced_get_trends_main_output_decode_json( + resp->body, resp->body_len, out); +} + +wf_status wf_unspecced_get_trends_skeleton( + wf_xrpc_client *client, + const wf_lex_app_bsky_unspecced_get_trends_skeleton_main_params *params, + wf_response *out) { + if (!client || !params || !out) { + return WF_ERR_INVALID_ARG; + } + return wf_lex_app_bsky_unspecced_get_trends_skeleton_main_call(client, params, out); +} + +wf_status wf_unspecced_get_trends_skeleton_parse( + const wf_response *resp, + wf_lex_app_bsky_unspecced_get_trends_skeleton_main_output **out) { + if (!resp || !out) { + return WF_ERR_INVALID_ARG; + } + *out = NULL; + if (!resp->body) { + return WF_ERR_INVALID_ARG; + } + return wf_lex_app_bsky_unspecced_get_trends_skeleton_main_output_decode_json( + resp->body, resp->body_len, out); +} + +wf_status wf_unspecced_init_age_assurance( + wf_xrpc_client *client, + const wf_lex_app_bsky_unspecced_init_age_assurance_main_input *input, + wf_response *out) { + if (!client || !input || !out) { + return WF_ERR_INVALID_ARG; + } + return wf_lex_app_bsky_unspecced_init_age_assurance_main_call(client, input, out); +} + +wf_status wf_unspecced_search_actors_skeleton( + wf_xrpc_client *client, + const wf_lex_app_bsky_unspecced_search_actors_skeleton_main_params *params, + wf_response *out) { + if (!client || !params || !out) { + return WF_ERR_INVALID_ARG; + } + return wf_lex_app_bsky_unspecced_search_actors_skeleton_main_call(client, params, out); +} + +wf_status wf_unspecced_search_actors_skeleton_parse( + const wf_response *resp, + wf_lex_app_bsky_unspecced_search_actors_skeleton_main_output **out) { + if (!resp || !out) { + return WF_ERR_INVALID_ARG; + } + *out = NULL; + if (!resp->body) { + return WF_ERR_INVALID_ARG; + } + return wf_lex_app_bsky_unspecced_search_actors_skeleton_main_output_decode_json( + resp->body, resp->body_len, out); +} + +wf_status wf_unspecced_search_posts_skeleton( + wf_xrpc_client *client, + const wf_lex_app_bsky_unspecced_search_posts_skeleton_main_params *params, + wf_response *out) { + if (!client || !params || !out) { + return WF_ERR_INVALID_ARG; + } + return wf_lex_app_bsky_unspecced_search_posts_skeleton_main_call(client, params, out); +} + +wf_status wf_unspecced_search_posts_skeleton_parse( + const wf_response *resp, + wf_lex_app_bsky_unspecced_search_posts_skeleton_main_output **out) { + if (!resp || !out) { + return WF_ERR_INVALID_ARG; + } + *out = NULL; + if (!resp->body) { + return WF_ERR_INVALID_ARG; + } + return wf_lex_app_bsky_unspecced_search_posts_skeleton_main_output_decode_json( + resp->body, resp->body_len, out); +} diff --git a/tools/wf_gen_unspecced_wrappers.py b/tools/wf_gen_unspecced_wrappers.py new file mode 100644 index 0000000..44e38ba --- /dev/null +++ b/tools/wf_gen_unspecced_wrappers.py @@ -0,0 +1,155 @@ +#!/usr/bin/env python3 +"""Generate XRPC-level convenience wrappers for remaining app.bsky.unspecced endpoints. + +Usage: + python3 tools/wf_gen_unspecced_wrappers.py --header # header declarations + python3 tools/wf_gen_unspecced_wrappers.py --source # source implementations +""" + +import re +import sys + +HEADER_PATH = "include/wolfram/atproto_lex.h" + +# Endpoints that already have wrappers in unspecced_typed.c (skip these) +EXISTING = frozenset({ + "get_age_assurance_state", + "get_config", + "get_onboarding_suggested_starter_packs", + "get_onboarding_suggested_starter_packs_skeleton", + "get_suggestions_skeleton", + "get_tagged_suggestions", + "get_trending_topics", + "search_starter_packs_skeleton", +}) + + +def snake_to_nsid_method(snake): + """Convert snake_case method name to NSID method name (camelCase). + E.g. 'get_popular_feed_generators' -> 'getPopularFeedGenerators' + """ + parts = snake.split("_") + result = parts[0] + for p in parts[1:]: + result += p.capitalize() + return result + + +def read_endpoints(): + """Read all unspecced _main_call endpoint names from atproto_lex.h.""" + with open(HEADER_PATH, "r") as f: + content = f.read() + endpoints = set() + for m in re.finditer( + r'wf_status\s+wf_lex_app_bsky_unspecced_(\w+)_main_call\s*\(', content + ): + name = m.group(1) + if name not in EXISTING: + endpoints.add(name) + return sorted(endpoints) + + +def output_header(endpoints): + """Output header declarations.""" + print("/* ------------------------------------------------------------------ */") + print("/* Unspecced — XRPC-level convenience wrappers */") + print("/* ------------------------------------------------------------------ */") + print() + # NSID defines + for ep in endpoints: + method = snake_to_nsid_method(ep) + define_name = f"WF_UNSPECCED_{method.upper()}_NSID" + nsid = f"app.bsky.unspecced.{method}" + print(f"#define {define_name:60s} \"{nsid}\"") + print() + # Wrapper declarations + for ep in endpoints: + lex_name = f"app_bsky_unspecced_{ep}" + method = snake_to_nsid_method(ep) + has_input = ep == "init_age_assurance" + has_output = ep != "init_age_assurance" + if has_input: + params_type = f"wf_lex_{lex_name}_main_input" + else: + params_type = f"wf_lex_{lex_name}_main_params" + func = f"wf_unspecced_{ep}" + if has_input: + print(f"wf_status {func}(wf_xrpc_client *client, const {params_type} *input, wf_response *out);") + else: + print(f"wf_status {func}(wf_xrpc_client *client, const {params_type} *params, wf_response *out);") + if has_output: + output_type = f"wf_lex_{lex_name}_main_output" + print(f"wf_status {func}_parse(const wf_response *resp, {output_type} **out);") + print() + + +def output_source(endpoints): + """Output source implementations.""" + print("/* ================================================================== */") + print("/* Unspecced — generated XRPC-level convenience wrappers */") + print("/* ================================================================== */") + print() + + for ep in endpoints: + lex_name = f"app_bsky_unspecced_{ep}" + has_input = ep == "init_age_assurance" + has_output = ep != "init_age_assurance" + + if has_input: + params_type = f"wf_lex_{lex_name}_main_input" + call_func = f"wf_lex_{lex_name}_main_call" + else: + params_type = f"wf_lex_{lex_name}_main_params" + call_func = f"wf_lex_{lex_name}_main_call" + + func = f"wf_unspecced_{ep}" + + # Call function + arg_name = "input" if has_input else "params" + print(f"wf_status {func}(") + print(f" wf_xrpc_client *client,") + print(f" const {params_type} *{arg_name},") + print(f" wf_response *out) {{") + print(f" if (!client || !{arg_name} || !out) {{") + print(f" return WF_ERR_INVALID_ARG;") + print(f" }}") + print(f" return {call_func}(client, {arg_name}, out);") + print(f"}}") + print() + + # Parse function + if has_output: + output_type = f"wf_lex_{lex_name}_main_output" + decode_func = f"wf_lex_{lex_name}_main_output_decode_json" + print(f"wf_status {func}_parse(") + print(f" const wf_response *resp,") + print(f" {output_type} **out) {{") + print(f" if (!resp || !out) {{") + print(f" return WF_ERR_INVALID_ARG;") + print(f" }}") + print(f" *out = NULL;") + print(f" if (!resp->body) {{") + print(f" return WF_ERR_INVALID_ARG;") + print(f" }}") + print(f" return {decode_func}(") + print(f" resp->body, resp->body_len, out);") + print(f"}}") + print() + + +def main(): + endpoints = read_endpoints() + if not endpoints: + print("No remaining unspecced endpoints found.", file=sys.stderr) + sys.exit(1) + + if "--header" in sys.argv: + output_header(endpoints) + elif "--source" in sys.argv: + output_source(endpoints) + else: + print(f"Usage: {sys.argv[0]} [--header | --source]", file=sys.stderr) + print(f"Found {len(endpoints)} endpoints", file=sys.stderr) + +if __name__ == "__main__": + main()