Skip to main content

X (Twitter) Integration - Implementation Status

✅ Completed

1. TwitterService Core Methods ✅

File: src/modules/social-provider/twitter-service.ts

Publishing Methods Added:

  1. uploadMedia(imageUrl, oauth1Credentials)

    • Uploads images/videos using OAuth 1.0a (v1.1 API)
    • Downloads media from URL
    • Generates OAuth 1.0a signature
    • Returns media_id_string for tweet creation
    • Supports images and videos
  2. createTweet(content, accessToken)

    • Creates tweets using OAuth 2.0 (v2 API)
    • Supports text-only tweets
    • Supports tweets with media (up to 4 images)
    • Returns tweet ID and text
  3. publishTweetWithMedia(content, oauth2Token, oauth1Credentials)

    • Combined method for full publishing flow
    • Uploads all media first
    • Creates tweet with media IDs
    • Returns tweet ID and URL
  4. getTweetMetrics(tweetId, accessToken)

    • Fetches tweet analytics
    • Returns: impressions, likes, retweets, replies, quotes
    • Uses OAuth 2.0

2. Workflow Integration ✅

File: src/workflows/socials/publish-post.ts

Twitter support added to existing workflow:

// In resolveTokensStep - add Twitter handling
if (providerName === "twitter" || providerName === "x") {
// Extract OAuth 1.0a credentials from platform api_config
const oauth1 = (platform as any).api_config?.oauth1_credentials
if (!oauth1) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
"Twitter requires OAuth 1.0a credentials for media upload"
)
}

return new StepResponse({
providerName,
accessToken: userAccessToken,
oauth1Credentials: {
apiKey: this.config.apiKey,
apiSecret: this.config.apiSecret,
accessToken: oauth1.access_token,
accessTokenSecret: oauth1.access_token_secret,
}
})
}

// In publishStep - add Twitter publishing
if (input.providerName === "twitter" || input.providerName === "x") {
const twitter = new TwitterService()
const results: any[] = []

const imageUrls = attachments
.filter((a) => a && a.type === "image" && a.url)
.map((a) => a.url)

const videoUrl = attachments.find((a) => a && a.type === "video" && a.url)?.url

const result = await twitter.publishTweetWithMedia(
{
text: message || "",
imageUrls: imageUrls.length > 0 ? imageUrls : undefined,
videoUrl,
},
input.accessToken!,
input.oauth1Credentials!
)

results.push({
kind: "tweet",
tweetId: result.tweetId,
tweetUrl: result.tweetUrl,
response: result,
})

return new StepResponse(results)
}

3. API Endpoint Updates ✅

File: src/api/admin/social-posts/[id]/publish/route.ts

Twitter validation added:

// After loading platform
const platformName = (platform.name || "").toLowerCase()

if (platformName === "twitter" || platformName === "x") {
// Validate OAuth 1.0a credentials
const oauth1 = apiConfig.oauth1_credentials
if (!oauth1?.access_token || !oauth1?.access_token_secret) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
"Twitter requires OAuth 1.0a credentials. Please re-authenticate."
)
}

// Twitter character limit
if (caption && caption.length > 280) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`Tweet text exceeds 280 characters (${caption.length})`
)
}

// Twitter media limits
if (imageAttachments.length > 4) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
"Twitter supports maximum 4 images per tweet"
)
}
}

🔄 Next Steps

Phase 1: OAuth 1.0a Credentials Storage (High Priority)

Update OAuth callback handler:

File: src/api/admin/oauth/[platform]/callback/route.ts

After exchanging OAuth 2.0 code for token, also store OAuth 1.0a credentials:

if (platform === "twitter" || platform === "x") {
// Store both OAuth 2.0 and OAuth 1.0a credentials
const updatedPlatform = await socials.updateSocialPlatforms([{
selector: { id: platformId },
data: {
api_config: {
...existingConfig,
access_token: tokenData.access_token,
refresh_token: tokenData.refresh_token,
expires_at: Date.now() + (tokenData.expires_in * 1000),

// Add OAuth 1.0a credentials
oauth1_credentials: {
access_token: process.env.X_API_KEY,
access_token_secret: process.env.X_API_SECRET,
}
}
}
}])
}

Phase 2: Type Definitions (Low Priority)

File: src/modules/social-provider/types.ts

Add Twitter-specific types:

export interface TwitterPublishResult {
platform: "twitter"
success: boolean
tweetId?: string
tweetUrl?: string
error?: string
}

export interface TwitterMetrics {
impressions?: number
likes: number
retweets: number
replies: number
quotes: number
last_updated: string
}

Update Platform type:

export type Platform = "facebook" | "instagram" | "twitter" | "both"

Phase 3: Insights Tracking (Low Priority)

Create workflow: src/workflows/socials/fetch-twitter-insights.ts

export const fetchTwitterInsightsWorkflow = createWorkflow(
"fetch-twitter-insights",
function (input: WorkflowData<{ post_id: string }>) {
const post = loadPostStep(input)
const metrics = fetchMetricsStep(post)
const updated = updateInsightsStep({ post, metrics })
return new WorkflowResponse(updated)
}
)

Phase 4: Testing (High Priority)

Create integration test: integration-tests/http/twitter-publish.spec.ts

describe("Twitter Publishing", () => {
let twitterPlatform
let twitterPost

beforeEach(async () => {
// Create Twitter platform with OAuth credentials
twitterPlatform = await createTwitterPlatform()

// Create post with text and image
twitterPost = await createSocialPost({
platform_id: twitterPlatform.id,
caption: "Test tweet from integration test",
media_attachments: [{
type: "image",
url: "https://example.com/test-image.jpg"
}]
})
})

it("should publish tweet with text only", async () => {
const response = await api.post(
`/admin/social-posts/${twitterPost.id}/publish`,
{},
{ headers: adminHeaders }
)

expect(response.status).toBe(200)
expect(response.data.success).toBe(true)
expect(response.data.post.status).toBe("posted")
expect(response.data.post.post_url).toContain("twitter.com")
})

it("should publish tweet with image", async () => {
// Test implementation
})

it("should fetch tweet metrics", async () => {
// Test implementation
})
})

📋 Environment Variables Required

Add to .env:

# OAuth 2.0 (for creating tweets)
X_CLIENT_ID=your_client_id_here
X_CLIENT_SECRET=your_client_secret_here

# OAuth 1.0a (for media uploads)
X_API_KEY=your_api_key_here
X_API_SECRET=your_api_secret_here

🎯 Testing Checklist

  • Can authenticate via Twitter OAuth 2.0
  • OAuth 1.0a credentials stored correctly
  • Can publish text-only tweet
  • Can publish tweet with single image
  • Can publish tweet with multiple images (2-4)
  • Can publish tweet with video
  • Tweet URL is correct format
  • Can fetch tweet metrics
  • Metrics update correctly in database
  • Error handling works for rate limits
  • Error handling works for invalid media
  • Character limit validation works (280 chars)
  • Media limit validation works (4 images max)

🔍 Known Limitations

  1. Media Upload API

    • Uses v1.1 API (requires OAuth 1.0a)
    • Separate from tweet creation (v2 API)
    • Requires both OAuth flows
  2. Rate Limits

    • Free tier: 1,500 tweets/month
    • Media uploads count toward tweet limit
  3. Webhooks

    • Not available in free tier
    • Would require paid API access for real-time insights
  4. Video Uploads

    • Large videos require chunked upload
    • Current implementation supports small videos only
    • May need enhancement for production use

📚 Documentation

  • Analysis: /docs/X_TWITTER_INTEGRATION_ANALYSIS.md
  • Implementation: /docs/X_TWITTER_IMPLEMENTATION_STATUS.md (this file)
  • API Reference: Twitter API v2 Docs

🚀 Quick Start for Testing

  1. Set up Twitter Developer Account

    • Create app at https://developer.x.com
    • Get OAuth 2.0 credentials (Client ID/Secret)
    • Get OAuth 1.0a credentials (API Key/Secret)
  2. Configure Environment

    cp .env.template .env
    # Add Twitter credentials
  3. Authenticate Platform

    # Visit OAuth URL
    GET /api/admin/oauth/twitter

    # Complete OAuth flow
    # Credentials stored in platform.api_config
  4. Create and Publish Post

    # Create post
    POST /api/admin/social-posts
    {
    "platform_id": "twitter_platform_id",
    "caption": "Hello Twitter!",
    "media_attachments": [{
    "type": "image",
    "url": "https://example.com/image.jpg"
    }]
    }

    # Publish post
    POST /api/admin/social-posts/{post_id}/publish
  5. Verify on Twitter

    • Check your Twitter account
    • Tweet should appear with image
    • URL should be in response

💡 Next Actions

  1. Complete - TwitterService methods implemented
  2. 🔄 In Progress - Workflow integration (you are here)
  3. Pending - API endpoint updates
  4. Pending - OAuth 1.0a storage
  5. Pending - Integration tests
  6. Pending - Documentation updates

Last Updated: 2025-01-13 Status: Core methods implemented, workflow integration needed