Skip to content

feat: support Instagram collaborators on Reels, images, and carousels - #289

Closed
HafizMMoaz wants to merge 3 commits into
trypostit:mainfrom
HafizMMoaz:feat/issue-213-instagram-collaborators
Closed

feat: support Instagram collaborators on Reels, images, and carousels#289
HafizMMoaz wants to merge 3 commits into
trypostit:mainfrom
HafizMMoaz:feat/issue-213-instagram-collaborators

Conversation

@HafizMMoaz

Copy link
Copy Markdown

Summary

Instagram's Content Publishing API accepts a collaborators parameter
(up to 3 usernames) on media container creation. Without it, every Reel
published through TryPost needs a manual re-invite in the Instagram app
afterward - this covers the "smallest useful version" from the issue:
accept the field and pass it through to the container creation call.

Changes

  • PostPlatformMetaRules::rules(): platforms.*.meta.collaborators
    (array, max 3, each entry a string up to 30 chars - Instagram's
    username length cap), following the exact shape suggested in the
    issue.
  • InstagramPublisher: a withCollaborators() helper adds the field
    (JSON-encoded, matching Meta's expected format and the existing
    attached_media JSON-encoding pattern in FacebookPublisher) to
    whichever container actually gets published - single image, Reel, or
    the carousel's parent container (not per-item children, which
    don't accept it). Reads straight from $this->postPlatform->meta
    (same as other instance state already on the class) rather than
    threading a new parameter through every method, which also means it
    survives a carousel publish that resumes after an interruption - the
    parent container isn't created until the resume completes.
  • publishStory() is untouched - Instagram doesn't support collaborators
    on Stories, so it's simply never added there.

What I intentionally left out

  • Composer UI: the issue's suggested scope includes surfacing this
    in the web composer next to Pinterest's board picker etc. I didn't
    add it - it would be the first real usage of the TagsInput UI
    primitive in this app (everywhere else that needs an array-of-strings
    input, like Discord mentions, uses a server-side autocomplete instead),
    and I don't have a way to verify it renders/behaves correctly in a
    browser in this environment. Shipping an unverified new UI pattern
    felt riskier than leaving it for a follow-up where someone can
    actually click through it. The feature is fully usable today via the
    public API and MCP tools.
  • Error-code mapping: the issue suggests mapping a specific Instagram
    error (invalid/private collaborator username) to a friendlier message
    in InstagramPublishException. I didn't add a guessed error_subcode
    entry - the existing fallback already surfaces Meta's own
    error_user_msg when present, and I'd rather not fabricate an
    unconfirmed code mapping (this table is built from Meta's official
    error-code docs, not guesses).
  • Status-check endpoint (invite Accepted/Pending/Declined): explicitly
    called out in the issue as a v2 follow-up, not needed here.
  • Docs site (docs.trypost.it): out of scope for this repo.

Test plan

  • tests/Feature/Services/Social/InstagramPublisherTest.php - 5 new tests: collaborators on a reel container, on a single-image container, omitted when unset, never sent on a story, and present only on the carousel's parent container (not child items)
  • tests/Unit/PostPlatformMetaRulesTest.php, tests/Feature/Api/PostApiPlatformMetaTest.php, tests/Feature/Mcp/PostPlatformMetaToolTest.php - rule presence, cross-platform persistence (API), and MCP create-post coverage, plus a max-3 rejection test in each entry point
  • php artisan test --compact tests/Unit/PostPlatformMetaRulesTest.php tests/Feature/Api/PostApiPlatformMetaTest.php tests/Feature/Mcp/PostPlatformMetaToolTest.php tests/Feature/Services/Social/InstagramPublisherTest.php - 105 passed
  • vendor/bin/pint --dirty --format agent - passed

Addresses #213 (validation + publisher; composer UI and error-message mapping left as follow-ups per above)

Instagram's Content Publishing API accepts a collaborators parameter
(up to 3 usernames) on media container creation, invited automatically
instead of needing a manual re-invite in the app after every publish.
Supported on Reels, single images, and carousels; Instagram ignores it
on Stories.

platforms.*.meta.collaborators validates the field (array, max 3,
each a string up to 30 chars - Instagram's username length cap).
InstagramPublisher reads it straight from the stored PostPlatform
meta (same as other instance state already on the class) rather than
threading it through every method signature, which also means it
carries over correctly if a carousel publish resumes after an
interruption - the parent container isn't created until the resume
completes, and collaborators need to be on that container, not the
per-item children.
Per this project's PostPlatformMetaRules convention, every new
platforms.*.meta field needs coverage in the API and MCP platform-meta
tests (not just the web path) so it can't silently get stripped by
validated() on one entry point while working on another.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds support for Instagram “collaborators” (up to 3 usernames) by accepting the meta field across entry points and passing it through to the correct Instagram media container creation call (Reels, single images, and carousel parent containers only).

Changes:

  • Added platforms.*.meta.collaborators validation to PostPlatformMetaRules (array max 3; each username max 30 chars).
  • Updated InstagramPublisher to attach JSON-encoded collaborators on the publishable container (single image / reel / carousel parent).
  • Added/updated API, MCP, unit, and service tests to ensure persistence and correct request payload behavior.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
app/Support/PostPlatformMetaRules.php Adds validation rules for Instagram meta.collaborators.
app/Services/Social/InstagramPublisher.php Adds withCollaborators() helper and applies it to the appropriate container creation flows.
tests/Unit/PostPlatformMetaRulesTest.php Ensures the shared meta rules include the new collaborators key.
tests/Feature/Services/Social/InstagramPublisherTest.php Adds coverage for collaborators being included/omitted and carousel-parent-only behavior.
tests/Feature/Api/PostApiPlatformMetaTest.php Verifies API persistence and validates max-3 rejection.
tests/Feature/Mcp/PostPlatformMetaToolTest.php Verifies MCP create-post persistence and validates max-3 rejection.
Suppressed comments (1)

tests/Feature/Services/Social/InstagramPublisherTest.php:257

  • This assertion may match the GET polling request to the container (which will never have collaborators), so the test could still pass even if the story container creation request accidentally included collaborators. Filter down to the POST /{ig-id}/media container creation request to avoid false positives.
    Http::assertSent(function ($request) {
        return ! str_contains($request->url(), 'media_publish')
            && ! isset($request['collaborators']);
    });

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.


$this->publisher->publish($this->postPlatform);

Http::assertSent(fn ($request) => ! isset($request['collaborators']));
Copilot review on trypostit#289: these assertSent() calls passed as soon as
any recorded request lacked collaborators (e.g. the status-check GET),
which is true regardless of whether the container-creation POST
itself was correct. Scope both to the actual /media container request,
excluding media_publish.
@HafizMMoaz

Copy link
Copy Markdown
Author

Good catch - fixed both. Scoped the assertions to the actual /ig_123456789/media container-creation request (excluding media_publish), so they no longer trivially pass against the status-check GET request that also lacks collaborators.

@paulocastellano

Copy link
Copy Markdown
Contributor

@HafizMMoaz i'm closing this pull request in favor of #283

It's already done for code review, test, feel free to test it.

I'm planing to merge it today/tomorrow.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants