Add X integration skill (#52)
This commit is contained in:
66
.claude/skills/x-integration/scripts/post.ts
Normal file
66
.claude/skills/x-integration/scripts/post.ts
Normal file
@@ -0,0 +1,66 @@
|
||||
#!/usr/bin/env npx tsx
|
||||
/**
|
||||
* X Integration - Post Tweet
|
||||
* Usage: echo '{"content":"Hello world"}' | npx tsx post.ts
|
||||
*/
|
||||
|
||||
import { getBrowserContext, runScript, validateContent, config, ScriptResult } from '../lib/browser.js';
|
||||
|
||||
interface PostInput {
|
||||
content: string;
|
||||
}
|
||||
|
||||
async function postTweet(input: PostInput): Promise<ScriptResult> {
|
||||
const { content } = input;
|
||||
|
||||
const validationError = validateContent(content, 'Tweet');
|
||||
if (validationError) return validationError;
|
||||
|
||||
let context = null;
|
||||
try {
|
||||
context = await getBrowserContext();
|
||||
const page = context.pages()[0] || await context.newPage();
|
||||
|
||||
await page.goto('https://x.com/home', { timeout: config.timeouts.navigation, waitUntil: 'domcontentloaded' });
|
||||
await page.waitForTimeout(config.timeouts.pageLoad);
|
||||
|
||||
// Check if logged in
|
||||
const isLoggedIn = await page.locator('[data-testid="SideNav_AccountSwitcher_Button"]').isVisible().catch(() => false);
|
||||
if (!isLoggedIn) {
|
||||
const onLoginPage = await page.locator('input[autocomplete="username"]').isVisible().catch(() => false);
|
||||
if (onLoginPage) {
|
||||
return { success: false, message: 'X login expired. Run /x-integration to re-authenticate.' };
|
||||
}
|
||||
}
|
||||
|
||||
// Find and fill tweet input
|
||||
const tweetInput = page.locator('[data-testid="tweetTextarea_0"]');
|
||||
await tweetInput.waitFor({ timeout: config.timeouts.elementWait * 2 });
|
||||
await tweetInput.click();
|
||||
await page.waitForTimeout(config.timeouts.afterClick / 2);
|
||||
await tweetInput.fill(content);
|
||||
await page.waitForTimeout(config.timeouts.afterFill);
|
||||
|
||||
// Click post button
|
||||
const postButton = page.locator('[data-testid="tweetButtonInline"]');
|
||||
await postButton.waitFor({ timeout: config.timeouts.elementWait });
|
||||
|
||||
const isDisabled = await postButton.getAttribute('aria-disabled');
|
||||
if (isDisabled === 'true') {
|
||||
return { success: false, message: 'Post button disabled. Content may be empty or exceed character limit.' };
|
||||
}
|
||||
|
||||
await postButton.click();
|
||||
await page.waitForTimeout(config.timeouts.afterSubmit);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: `Tweet posted: ${content.slice(0, 50)}${content.length > 50 ? '...' : ''}`
|
||||
};
|
||||
|
||||
} finally {
|
||||
if (context) await context.close();
|
||||
}
|
||||
}
|
||||
|
||||
runScript<PostInput>(postTweet);
|
||||
Reference in New Issue
Block a user