Add VK (VKontakte) as a platform - #311
Conversation
VK publishes to a community or profile wall via wall.post with a user access token (scope: wall, photos, groups, video, offline) — VK no longer grants the wall scope to new OAuth apps, so the connect flow accepts a token directly, modeled on the Bluesky credential form: validate the token, list manageable walls (own profile + administered communities), store the chosen wall as the account. Publisher uploads photos via getWallUploadServer/saveWallPhoto and videos via video.save, then attaches them to wall.post. VK reports failures as HTTP 200 with an error object; error 5 maps to TokenExpiredException, rate-limit and permission codes to their categories.
VK joins the analytics page: account level shows the community member count (profile wall: follower count) via groups.getById/users.get; post level pulls views, likes, reposts and comments from wall.getById. Deeper community stats need the stats scope the connect flow doesn't request, so they are deliberately out.
wall.post with a user token is limited to standalone apps; community access tokens publish to their community regardless of the issuing app type. users.get error 27 tells the two apart on the shared token field; verification for community-token accounts goes through groups.getById.
VK has no API to resolve which community a token belongs to (groups.getById without group_ids returns nothing for community tokens), so the form asks for the community link/screen name and groups.getOnlineStatus — callable only with the community's own token — proves the key matches it.
…or 27 users.get is callable with a community access token — without user_ids it returns an empty response instead of a group-auth error, so a successful-but-empty users.get is the community-token signal.
groups.getOnlineStatus fails with 'group messages is disabled' for communities that turned messages off; getCallbackConfirmationCode works with the community's own token regardless and still rejects foreign tokens.
|
@gynsus I'm looking into this; do I need a Russian VPN to install this app? |
|
Should work worldwide, no VPN needed — vk.com and api.vk.com are not geo-restricted. For the easiest test path you don't even need to register a VK app: any VK account (a non-Russian phone number should work for signup) → create a community → Manage → API usage → Access tokens → create a token with photos/wall/manage → paste it into TryPost's Connect VK form. That exercises the recommended community-token flow end to end. The user-token path needs a standalone app of your own, but it's optional — happy to walk you through it if you want to test video upload too. |
|
@gynsus i'm still trying create an account for review it. You can share same VK account with me so i can test it? |
Why
VK (VKontakte) is the dominant social network for Russian-speaking audiences, and self-hosted TryPost users publishing there currently have to do it by hand. This PR adds VK as a full platform: connect, publish (text, photos, video), and analytics.
The connect flow is deliberately token-based, not OAuth. VK stopped granting the
wallscope to newly registered OAuth apps years ago, so a stock "log in with VK" integration cannot post to walls at all. What still works, verified against the live API:wall.postwithowner_id=-<group>+from_group=1is allowed for these regardless of the app type that issued them; this is the recommended path and the one the form suggests first;wall, photos, groups, video, offlinescopes) — needed for profile walls and for video upload, which VK only allows with user tokens.What changed
Platform::Vk+ContentType::VkPost(16,000-char limit, VK's actual cap), lang strings for all 16 locales, platform tile/logo/theme.VkController): one token field for both token kinds. A community token is detected by a successful-but-emptyusers.get(the method accepts community tokens and just returns an empty list withoutuser_ids— it does not error). Three live-API quirks shaped the flow:vk.com/club…, screen name, or bare id — all normalized) and resolves it viagroups.getById.groups.getCallbackConfirmationCode, which only answers for the token's own community. (groups.getOnlineStatuslooked right for this but fails with "group messages is disabled" for communities that turned messages off.)users.get+groups.get(filter=admin,editor)builds a picker of publishable walls (own profile + administered communities).VkPublisher):wall.postwith attachments; photos via the three-stepgetWallUploadServer → upload → saveWallPhotoflow (images optimized first), video viavideo.savewithwallpost=0so the video attaches to our post instead of auto-publishing. A failed single attachment is skipped with a log, not a sunk post. VK reports errors as HTTP 200 +errorobject;VkPublishExceptionmaps codes to the shared error categories (rate limit / permission / media format).VkAnalytics): community member count on the account level; views/likes/reposts/comments per post viawall.getById. Handles both the v5.199 response shape (response.groups[]/response.items[]) and the older flat one.ConnectionVerifier):users.getfor user tokens,groups.getByIdfor community tokens (whichusers.getcan't verify); VK error 5 is the confirmed-dead-token signal.Testing
VkControllerTest(9 tests: both token kinds, community mismatch/unknown address, VK-side errors),VkPublisherTest,VkAnalyticsTest,VkPublishExceptionTest, plusConnectionVerifierTestadditions for both verify paths.What only you can decide
Token-based connect is unusual for this codebase — every other platform is OAuth. We kept the UI to the same two-step popup pattern as LinkedIn's identity picker, but if you'd rather gate VK behind a
SELF_HOSTED-style env flag until it settles, that's a small change on top.