-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathserver.py
More file actions
168 lines (132 loc) · 5.69 KB
/
Copy pathserver.py
File metadata and controls
168 lines (132 loc) · 5.69 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
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
import random
import anyio
import json5
import uvicorn
from colorama import Fore, init
from motor.motor_asyncio import AsyncIOMotorClient
from starlette.applications import Starlette
from starlette.endpoints import WebSocketEndpoint
from starlette.requests import Request
from starlette.responses import JSONResponse
from starlette.routing import Route, WebSocketRoute
from starlette.staticfiles import StaticFiles
from starlette.templating import Jinja2Templates
from asyncX.x import AsyncX
init(autoreset=True) # Enable colored console output
# ✅ Set up Starlette app & templates
app = Starlette()
config = json5.load(open("config.json5"))
templates = Jinja2Templates(directory="templates")
# ✅ Serve static files (CSS, JS)
app.mount("/static", StaticFiles(directory="static"), name="static")
# ✅ MongoDB Setup
MONGO_URI = config.get("mongo", "mongodb://localhost:27017")
mongo_client = AsyncIOMotorClient(MONGO_URI)
db = mongo_client["followers"]
accounts_collection = db["accounts"]
# ✅ Load all accounts into memory
accounts_cache = []
async def load_accounts():
"""Loads Twitter accounts from MongoDB into memory."""
# global accounts_cache
accounts_cache = await accounts_collection.find({}).to_list(None)
print(Fore.GREEN + f"✅ Loaded {len(accounts_cache)} Twitter accounts.")
async def get_working_account():
"""Randomly selects an account and ensures it is not rate-limited."""
if not accounts_cache:
await load_accounts()
random.shuffle(accounts_cache)
asyncx = AsyncX()
for account in accounts_cache:
print(Fore.YELLOW + f"🔄 Trying account: {account['auth_token'][:10]}...")
await asyncx.authenticate(account)
# ✅ Test if this account is rate-limited
test_rest_id = (
"44196397" # Elon Musk's Twitter ID (or use another known valid ID)
)
if await asyncx.check_followers_rate_limit(test_rest_id):
return account # ✅ This account is usable!
print(Fore.RED + "❌ No working accounts found.")
return None
async def homepage(request):
"""Render the main frontend page."""
return templates.TemplateResponse("index.html", {"request": request})
async def import_accounts(request: Request):
"""Handles Twitter account imports."""
data = await request.json()
accounts = data.get("accounts", [])
if not accounts:
return JSONResponse({"message": "No accounts provided"}, status_code=400)
# ✅ Parse & Store Unique Accounts
existing_auth_tokens = {
acc["auth_token"]
for acc in await accounts_collection.find({}, {"auth_token": 1}).to_list(None)
}
new_accounts = [
acc for acc in accounts if acc["auth_token"] not in existing_auth_tokens
]
if new_accounts:
await accounts_collection.insert_many(new_accounts)
await load_accounts() # ✅ Refresh cache
return JSONResponse(
{"message": f"Imported {len(new_accounts)} accounts successfully!"}
)
else:
return JSONResponse(
{"message": "⚠️ No new accounts added. All were duplicates."}
)
class FollowerScraper(WebSocketEndpoint):
encoding = "text"
async def on_connect(self, websocket):
"""Accepts WebSocket connections."""
await websocket.accept()
print(Fore.GREEN + "✅ WebSocket Connection Established")
async def on_receive(self, websocket, data):
"""Receives a username from the WebSocket and scrapes followers."""
username = data.strip()
print(Fore.CYAN + f"🔍 Scraping followers for @{username}...")
asyncx = AsyncX()
await asyncx.authenticate(
{
"auth_token": "4ac359bfbe7196fe85a9c3e6300659bfef472ade",
"kdt": "bhJXX0dmtlSf1ZgtVp0gCzlHURJlJDHTgcZeRDiC",
"ct0": "4dca27a92fe1d77d9d926e019a5b31326b74b145fa6625d8c01cae9a6e06378095d28534e9eb78e98987c7ad0c31511fd8dfd9a68a03082b5031cc4f56eb7f9ff61cf2c29492c20810dc75612784142a",
}
)
rest_id = await asyncx.usernamesToRestIds(username)
if not rest_id:
await websocket.send_text(
f"❌ Failed to retrieve user rest_id for @{username}"
)
await websocket.close()
return
print(Fore.YELLOW + f"✅ Found rest_id: {rest_id} - Fetching followers...")
async for batch in asyncx.followers(user_ids=rest_id):
# if batch is a dict, it's a ratelimit message
if isinstance(batch, dict):
await websocket.send_text(
f"⏳ Rate limit exceeded! Resets at {batch['reset_time']}."
)
await anyio.sleep(batch["wait_time"])
continue
for follower_id in batch:
await websocket.send_text(follower_id) # ✅ Send each `rest_id` live
print(Fore.BLUE + f"📦 Sent: {follower_id}")
print(Fore.GREEN + f"🎉 Completed! Sent all followers for @{username}")
await websocket.close()
async def on_disconnect(self, websocket, close_code):
"""Handles WebSocket disconnection."""
print(Fore.RED + "❌ WebSocket Disconnected")
# ✅ Define routes
app.routes.extend(
[
Route("/", homepage), # Serve index.html
WebSocketRoute("/ws", FollowerScraper), # WebSocket route
Route(
"/import_accounts", methods=["POST"], endpoint=import_accounts
), # Import accounts
]
)
# ✅ Run the app
if __name__ == "__main__":
uvicorn.run(app, host="0.0.0.0", port=8000)