-
-
Notifications
You must be signed in to change notification settings - Fork 86
Expand file tree
/
Copy pathkeywordLiker.js
More file actions
148 lines (122 loc) · 5.92 KB
/
Copy pathkeywordLiker.js
File metadata and controls
148 lines (122 loc) · 5.92 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
// Copyright (c) 2024-2026 nich (@nichxbt). Licensed under the Apache License, Version 2.0.
// scripts/keywordLiker.js
// Like only posts containing specific keywords — with a prompt input box
// Paste in DevTools console on x.com/home or any feed/search page
// by nichxbt
(() => {
'use strict';
// =============================================
// ⬇️ PROMPT: ask user for keywords
// =============================================
const input = prompt(
'🔍 Enter keywords to match (comma-separated):\n\nExample: crypto, bitcoin, web3',
'crypto'
);
if (!input) { console.log('❌ Cancelled.'); return; }
const keywords = input.split(',').map(k => k.trim().toLowerCase()).filter(Boolean);
if (keywords.length === 0) { console.log('❌ No keywords provided.'); return; }
// =============================================
// CONFIG — adjust as needed
// =============================================
const CONFIG = {
keywords, // From the prompt above
maxLikes: 50, // Stop after this many likes
delayBetween: [2000, 5000], // Random delay between likes (ms)
scrollRounds: 20, // How many scroll cycles to do
scrollDelay: 2500, // Pause after each scroll (ms)
dryRun: false, // Set true to preview without actually liking
};
const sleep = (ms) => new Promise(r => setTimeout(r, ms));
const rand = (a, b) => Math.floor(a + Math.random() * (b - a));
const SEL = {
tweet: 'article[data-testid="tweet"]',
tweetText: '[data-testid="tweetText"]',
likeBtn: '[data-testid="like"]',
unlikeBtn: '[data-testid="unlike"]',
toast: '[data-testid="toast"]',
};
const matchesKeywords = (text) => {
const lower = text.toLowerCase();
return keywords.some(kw => lower.includes(kw));
};
const isRateLimited = () => {
for (const el of document.querySelectorAll(`${SEL.toast}, [role="alert"]`)) {
if (/rate limit|try again|too many|slow down/i.test(el.textContent)) return true;
}
return false;
};
let liked = 0;
let skipped = 0;
const seen = new Set();
// ── Abort handle ───────────────────────────────────────────
let aborted = false;
window.XActions = window.XActions || {};
window.XActions.stop = () => { aborted = true; console.log('🛑 Stopping after current tweet...'); };
const run = async () => {
console.log('╔════════════════════════════════════════════╗');
console.log('║ 🔑 KEYWORD LIKER ║');
console.log('║ by nichxbt — XActions ║');
console.log('╚════════════════════════════════════════════╝');
console.log(` Keywords: ${keywords.join(', ')}`);
console.log(` Max likes: ${CONFIG.maxLikes}`);
console.log(` Dry run: ${CONFIG.dryRun}`);
console.log(` ℹ️ Type XActions.stop() to abort early\n`);
for (let round = 0; round < CONFIG.scrollRounds && !aborted; round++) {
const articles = document.querySelectorAll(SEL.tweet);
for (const article of articles) {
if (liked >= CONFIG.maxLikes || aborted) break;
// Unique fingerprint
const link = article.querySelector('a[href*="/status/"]')?.href || '';
const textEl = article.querySelector(SEL.tweetText);
const text = textEl ? textEl.textContent.trim() : '';
const id = link || text.slice(0, 80);
if (!id || seen.has(id)) continue;
seen.add(id);
// Already liked?
if (article.querySelector(SEL.unlikeBtn)) continue;
// Keyword filter
if (!matchesKeywords(text)) { skipped++; continue; }
// Rate limit check
if (isRateLimited()) {
console.log('🚨 Rate limited! Waiting 120s...');
await sleep(120000);
if (isRateLimited()) { console.log('🛑 Still limited. Stopping.'); aborted = true; break; }
}
// Scroll into view
article.scrollIntoView({ behavior: 'smooth', block: 'center' });
await sleep(500);
const likeBtn = article.querySelector(SEL.likeBtn);
if (!likeBtn) continue;
if (CONFIG.dryRun) {
console.log(`🏃 [DRY] Would like: "${text.slice(0, 60)}..."`);
} else {
likeBtn.click();
await sleep(400);
// Verify
if (!article.querySelector(SEL.unlikeBtn)) {
console.log(` ⚠️ Like may not have registered.`);
}
}
liked++;
console.log(`❤️ (${liked}/${CONFIG.maxLikes}) "${text.slice(0, 50)}..."`);
const delay = rand(CONFIG.delayBetween[0], CONFIG.delayBetween[1]);
await sleep(delay);
}
if (liked >= CONFIG.maxLikes || aborted) break;
// Scroll for more content
window.scrollBy(0, 1200);
console.log(` 📜 Scroll ${round + 1}/${CONFIG.scrollRounds} — ${liked} liked, ${skipped} skipped`);
await sleep(CONFIG.scrollDelay);
}
// Summary
console.log('\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
console.log(` 🔑 KEYWORD LIKER RESULTS`);
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
console.log(` ❤️ Liked: ${liked}`);
console.log(` ⏭️ Skipped: ${skipped} (no keyword match)`);
console.log(` 🔍 Keywords: ${keywords.join(', ')}`);
if (CONFIG.dryRun) console.log(' 🏃 (Dry run — nothing was liked)');
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n');
};
run();
})();