unfollow.js
1 import { cli, Strategy } from '@jackwener/opencli/registry'; 2 import { CommandExecutionError } from '@jackwener/opencli/errors'; 3 cli({ 4 site: 'twitter', 5 name: 'unfollow', 6 description: 'Unfollow a Twitter user', 7 domain: 'x.com', 8 strategy: Strategy.UI, 9 browser: true, 10 args: [ 11 { name: 'username', type: 'string', positional: true, required: true, help: 'Twitter screen name (without @)' }, 12 ], 13 columns: ['status', 'message'], 14 func: async (page, kwargs) => { 15 if (!page) 16 throw new CommandExecutionError('Browser session required for twitter unfollow'); 17 const username = kwargs.username.replace(/^@/, ''); 18 await page.goto(`https://x.com/${username}`); 19 await page.wait({ selector: '[data-testid="primaryColumn"]' }); 20 const result = await page.evaluate(`(async () => { 21 try { 22 let attempts = 0; 23 let unfollowBtn = null; 24 25 while (attempts < 20) { 26 // Check if already not following 27 const followBtn = document.querySelector('[data-testid$="-follow"]'); 28 if (followBtn) { 29 return { ok: true, message: 'Not following @${username} (already unfollowed).' }; 30 } 31 32 unfollowBtn = document.querySelector('[data-testid$="-unfollow"]'); 33 if (unfollowBtn) break; 34 35 await new Promise(r => setTimeout(r, 500)); 36 attempts++; 37 } 38 39 if (!unfollowBtn) { 40 return { ok: false, message: 'Could not find Unfollow button. Are you logged in?' }; 41 } 42 43 // Click the unfollow button — this opens a confirmation dialog 44 unfollowBtn.click(); 45 await new Promise(r => setTimeout(r, 1000)); 46 47 // Confirm the unfollow in the dialog 48 const confirmBtn = document.querySelector('[data-testid="confirmationSheetConfirm"]'); 49 if (confirmBtn) { 50 confirmBtn.click(); 51 await new Promise(r => setTimeout(r, 1000)); 52 } 53 54 // Verify 55 const verify = document.querySelector('[data-testid$="-follow"]'); 56 if (verify) { 57 return { ok: true, message: 'Successfully unfollowed @${username}.' }; 58 } else { 59 return { ok: false, message: 'Unfollow action initiated but UI did not update.' }; 60 } 61 } catch (e) { 62 return { ok: false, message: e.toString() }; 63 } 64 })()`); 65 if (result.ok) 66 await page.wait(2); 67 return [{ 68 status: result.ok ? 'success' : 'failed', 69 message: result.message 70 }]; 71 } 72 });