Files
last30days-skill/vendor/package/dist/commands/follow.js
T
Matt Van Horn 32992834ee Merge PR #48: feat: add Xiaohongshu source + Reddit public fallback
- Xiaohongshu search via local MCP service (opt-in, zero impact if service not running)
- Reddit public JSON fallback (works with zero API keys)
- Reddit priority: ScrapeCreators -> OpenAI -> public fallback
- Updated env.py: Reddit always available via public fallback

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 16:11:35 -08:00

91 lines
3.7 KiB
JavaScript

import { normalizeHandle } from '../lib/normalize-handle.js';
import { TwitterClient } from '../lib/twitter-client.js';
const ONLY_DIGITS_REGEX = /^\d+$/;
async function resolveUserId(client, usernameOrId, ctx) {
const raw = usernameOrId.trim();
const isNumeric = ONLY_DIGITS_REGEX.test(raw);
// Otherwise, treat as username and look up
const handle = normalizeHandle(raw);
if (handle) {
const lookup = await client.getUserIdByUsername(handle);
if (lookup.success && lookup.userId) {
return { userId: lookup.userId, username: lookup.username };
}
if (!isNumeric) {
console.error(`${ctx.p('err')}Failed to find user @${handle}: ${lookup.error ?? 'Unknown error'}`);
return null;
}
}
if (isNumeric) {
return { userId: raw };
}
console.error(`${ctx.p('err')}Invalid username: ${usernameOrId}`);
return null;
}
export function registerFollowCommands(program, ctx) {
program
.command('follow')
.description('Follow a user')
.argument('<username-or-id>', 'Username (with or without @) or user ID to follow')
.action(async (usernameOrId) => {
const opts = program.opts();
const timeoutMs = ctx.resolveTimeoutFromOptions(opts);
const { cookies, warnings } = await ctx.resolveCredentialsFromOptions(opts);
for (const warning of warnings) {
console.error(`${ctx.p('warn')}${warning}`);
}
if (!cookies.authToken || !cookies.ct0) {
console.error(`${ctx.p('err')}Missing required credentials`);
process.exit(1);
}
const client = new TwitterClient({ cookies, timeoutMs });
const resolved = await resolveUserId(client, usernameOrId, ctx);
if (!resolved) {
process.exit(1);
}
const { userId, username } = resolved;
const displayName = username ? `@${username}` : userId;
const result = await client.follow(userId);
if (result.success) {
const finalName = result.username ? `@${result.username}` : displayName;
console.log(`${ctx.p('ok')}Now following ${finalName}`);
}
else {
console.error(`${ctx.p('err')}Failed to follow ${displayName}: ${result.error}`);
process.exit(1);
}
});
program
.command('unfollow')
.description('Unfollow a user')
.argument('<username-or-id>', 'Username (with or without @) or user ID to unfollow')
.action(async (usernameOrId) => {
const opts = program.opts();
const timeoutMs = ctx.resolveTimeoutFromOptions(opts);
const { cookies, warnings } = await ctx.resolveCredentialsFromOptions(opts);
for (const warning of warnings) {
console.error(`${ctx.p('warn')}${warning}`);
}
if (!cookies.authToken || !cookies.ct0) {
console.error(`${ctx.p('err')}Missing required credentials`);
process.exit(1);
}
const client = new TwitterClient({ cookies, timeoutMs });
const resolved = await resolveUserId(client, usernameOrId, ctx);
if (!resolved) {
process.exit(1);
}
const { userId, username } = resolved;
const displayName = username ? `@${username}` : userId;
const result = await client.unfollow(userId);
if (result.success) {
const finalName = result.username ? `@${result.username}` : displayName;
console.log(`${ctx.p('ok')}Unfollowed ${finalName}`);
}
else {
console.error(`${ctx.p('err')}Failed to unfollow ${displayName}: ${result.error}`);
process.exit(1);
}
});
}
//# sourceMappingURL=follow.js.map