feat: social video uploads + hero page video + TikTok content

Hero page:
- Replace GIF with squaremcp-hero-loop.mp4 (autoplay, muted, loop)
- Update styles, scripts, tests, Dockerfile, baselines
- Deployed and verified

Social video uploads:
- Twitter/X: uploadVideoAndTweet via v1.1 media/upload + v2 tweets
- Facebook: createVideoPost via Graph API /{pageId}/videos
- Instagram: createReel via Graph API (container → poll → publish)
- TikTok: REST endpoints + OpenAPI schema for video upload

Marketing:
- TikTok content prompts, scripts, and posting schedule

Note: Remotion not mentioned in any user-facing content
This commit is contained in:
Garfield
2026-05-11 13:55:58 -04:00
parent de9d74bb2b
commit ecdf332b78
17 changed files with 854 additions and 54 deletions

View File

@@ -935,6 +935,17 @@ app.post('/api/instagram/post', requireAuth, async (req, res) => {
}
});
app.post('/api/instagram/reel', requireAuth, async (req, res) => {
const { video_url, caption, account } = req.body as Record<string, unknown>;
if (!video_url) { res.status(400).json({ error: 'video_url is required' }); return; }
try {
const result = await handleToolCall('instagram_create_reel', { video_url, caption, account });
res.json(parseToolResult(result));
} catch (err) {
res.status(500).json({ error: (err as Error).message });
}
});
// ── Twitter/X REST endpoints ────────────────────────────────────
app.get('/api/twitter/search', requireAuth, async (req, res) => {
const query = req.query.query as string | undefined;
@@ -985,6 +996,116 @@ app.post('/api/twitter/tweet', requireAuth, async (req, res) => {
}
});
app.post('/api/twitter/video', requireAuth, async (req, res) => {
const { video_url, text, account } = req.body as Record<string, unknown>;
if (!video_url || !text) { res.status(400).json({ error: 'video_url and text are required' }); return; }
try {
const result = await handleToolCall('twitter_upload_video', { video_url, text, account });
res.json(parseToolResult(result));
} catch (err) {
res.status(500).json({ error: (err as Error).message });
}
});
// ── Facebook REST endpoints ─────────────────────────────────────
app.get('/api/facebook/page', requireAuth, async (req, res) => {
const account = req.query.account as string | undefined;
try {
const result = await handleToolCall('facebook_get_page', { account });
res.json(parseToolResult(result));
} catch (err) {
res.status(500).json({ error: (err as Error).message });
}
});
app.get('/api/facebook/posts', requireAuth, async (req, res) => {
const limit = req.query.limit ? parseInt(req.query.limit as string, 10) : undefined;
const account = req.query.account as string | undefined;
try {
const result = await handleToolCall('facebook_get_posts', { limit, account });
res.json(parseToolResult(result));
} catch (err) {
res.status(500).json({ error: (err as Error).message });
}
});
app.post('/api/facebook/post', requireAuth, async (req, res) => {
const { message, link, account } = req.body as Record<string, unknown>;
if (!message) { res.status(400).json({ error: 'message is required' }); return; }
try {
const result = await handleToolCall('facebook_create_post', { message, link, account });
res.json(parseToolResult(result));
} catch (err) {
res.status(500).json({ error: (err as Error).message });
}
});
app.post('/api/facebook/photo', requireAuth, async (req, res) => {
const { image_url, caption, account } = req.body as Record<string, unknown>;
if (!image_url) { res.status(400).json({ error: 'image_url is required' }); return; }
try {
const result = await handleToolCall('facebook_create_photo_post', { image_url, caption, account });
res.json(parseToolResult(result));
} catch (err) {
res.status(500).json({ error: (err as Error).message });
}
});
app.post('/api/facebook/video', requireAuth, async (req, res) => {
const { video_url, description, account } = req.body as Record<string, unknown>;
if (!video_url) { res.status(400).json({ error: 'video_url is required' }); return; }
try {
const result = await handleToolCall('facebook_create_video_post', { video_url, description, account });
res.json(parseToolResult(result));
} catch (err) {
res.status(500).json({ error: (err as Error).message });
}
});
// ── TikTok REST endpoints ───────────────────────────────────────
app.get('/api/tiktok/profile', requireAuth, async (req, res) => {
const account = req.query.account as string | undefined;
try {
const result = await handleToolCall('tiktok_get_profile', { account });
res.json(parseToolResult(result));
} catch (err) {
res.status(500).json({ error: (err as Error).message });
}
});
app.get('/api/tiktok/videos', requireAuth, async (req, res) => {
const max_count = req.query.max_count ? parseInt(req.query.max_count as string, 10) : undefined;
const account = req.query.account as string | undefined;
try {
const result = await handleToolCall('tiktok_get_videos', { max_count, account });
res.json(parseToolResult(result));
} catch (err) {
res.status(500).json({ error: (err as Error).message });
}
});
app.post('/api/tiktok/video', requireAuth, async (req, res) => {
const { video_url, title, description, account } = req.body as Record<string, unknown>;
if (!video_url) { res.status(400).json({ error: 'video_url is required' }); return; }
try {
const result = await handleToolCall('tiktok_create_video', { video_url, title, description, account });
res.json(parseToolResult(result));
} catch (err) {
res.status(500).json({ error: (err as Error).message });
}
});
app.post('/api/tiktok/video/status', requireAuth, async (req, res) => {
const { publish_id, account } = req.body as Record<string, unknown>;
if (!publish_id) { res.status(400).json({ error: 'publish_id is required' }); return; }
try {
const result = await handleToolCall('tiktok_get_video_status', { publish_id, account });
res.json(parseToolResult(result));
} catch (err) {
res.status(500).json({ error: (err as Error).message });
}
});
app.post('/api/pilot-request', async (req, res) => {
const origin = req.get('origin');
if (origin && !SQUAREMCP_ALLOWED_ORIGINS.has(origin)) {