-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathpageScript.js
More file actions
185 lines (161 loc) · 6.55 KB
/
Copy pathpageScript.js
File metadata and controls
185 lines (161 loc) · 6.55 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
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
// This script runs in the page context to access cookies and make API calls
(function() {
// Store headers from Twitter's own API calls
let twitterHeaders = null;
let headersReady = false;
// Function to capture headers from a request
function captureHeaders(headers) {
if (!headers) return;
const headerObj = {};
if (headers instanceof Headers) {
headers.forEach((value, key) => {
headerObj[key] = value;
});
} else if (headers instanceof Object) {
// Copy all headers
for (const [key, value] of Object.entries(headers)) {
headerObj[key] = value;
}
}
// Replace headers completely (don't merge) to ensure we get auth tokens
twitterHeaders = headerObj;
headersReady = true;
console.log('Captured Twitter API headers:', Object.keys(headerObj));
}
// Intercept fetch to capture Twitter's headers
const originalFetch = window.fetch;
window.fetch = function(...args) {
const url = args[0];
const options = args[1] || {};
// If it's a Twitter GraphQL API call, capture ALL headers
if (typeof url === 'string' && url.includes('x.com/i/api/graphql')) {
if (options.headers) {
captureHeaders(options.headers);
console.log('Captured Twitter headers:', Object.keys(twitterHeaders || {}));
}
}
return originalFetch.apply(this, args);
};
// Also intercept XMLHttpRequest
const originalXHROpen = XMLHttpRequest.prototype.open;
const originalXHRSend = XMLHttpRequest.prototype.send;
XMLHttpRequest.prototype.open = function(method, url, ...rest) {
this._url = url;
return originalXHROpen.apply(this, [method, url, ...rest]);
};
XMLHttpRequest.prototype.send = function(...args) {
if (this._url && this._url.includes('x.com/i/api/graphql')) {
const headers = {};
// Try to get headers from setRequestHeader
if (this._headers) {
Object.assign(headers, this._headers);
}
captureHeaders(headers);
}
return originalXHRSend.apply(this, args);
};
const originalSetRequestHeader = XMLHttpRequest.prototype.setRequestHeader;
XMLHttpRequest.prototype.setRequestHeader = function(header, value) {
if (!this._headers) this._headers = {};
this._headers[header] = value;
return originalSetRequestHeader.apply(this, [header, value]);
};
// Wait a bit for Twitter to make some API calls first
setTimeout(() => {
if (!headersReady) {
console.log('No Twitter headers captured yet, using defaults');
twitterHeaders = {
'Accept': 'application/json',
'Content-Type': 'application/json'
};
headersReady = true;
}
}, 3000);
// Listen for fetch requests from content script via postMessage
window.addEventListener('message', async function(event) {
// Only accept messages from our extension
if (event.data && event.data.type === '__fetchLocation') {
const { screenName, requestId } = event.data;
// Wait for headers to be ready
if (!headersReady) {
let waitCount = 0;
while (!headersReady && waitCount < 30) {
await new Promise(resolve => setTimeout(resolve, 100));
waitCount++;
}
}
try {
const variables = JSON.stringify({ screenName });
const url = `https://x.com/i/api/graphql/XRqGa7EeokUU5kppkh13EA/AboutAccountQuery?variables=${encodeURIComponent(variables)}`;
// Use captured headers or minimal defaults
const headers = twitterHeaders || {
'Accept': 'application/json',
'Content-Type': 'application/json'
};
// Ensure credentials are included
const response = await fetch(url, {
method: 'GET',
credentials: 'include',
headers: headers,
referrer: window.location.href,
referrerPolicy: 'origin-when-cross-origin'
});
let location = null;
if (response.ok) {
const data = await response.json();
console.log(`API response for ${screenName}:`, data);
location = data?.data?.user_result_by_screen_name?.result?.about_profile?.account_based_in || null;
console.log(`Extracted location for ${screenName}:`, location);
// Debug: log the full path to see what's available
if (!location && data?.data?.user_result_by_screen_name?.result) {
console.log('User result available but no location:', {
hasAboutProfile: !!data.data.user_result_by_screen_name.result.about_profile,
aboutProfile: data.data.user_result_by_screen_name.result.about_profile
});
}
} else {
const errorText = await response.text().catch(() => '');
// Handle rate limiting
if (response.status === 429) {
const resetTime = response.headers.get('x-rate-limit-reset');
const remaining = response.headers.get('x-rate-limit-remaining');
const limit = response.headers.get('x-rate-limit-limit');
if (resetTime) {
const resetDate = new Date(parseInt(resetTime) * 1000);
const now = Date.now();
const waitTime = resetDate.getTime() - now;
console.log(`Rate limited! Limit: ${limit}, Remaining: ${remaining}`);
console.log(`Rate limit resets at: ${resetDate.toLocaleString()}`);
console.log(`Waiting ${Math.ceil(waitTime / 1000 / 60)} minutes before retrying...`);
// Store rate limit info for content script
window.postMessage({
type: '__rateLimitInfo',
resetTime: parseInt(resetTime),
waitTime: Math.max(0, waitTime)
}, '*');
}
} else {
console.log(`Twitter API error for ${screenName}:`, response.status, response.statusText, errorText.substring(0, 200));
}
}
// Send response back to content script via postMessage
// Include error status so content script knows not to cache on rate limit
window.postMessage({
type: '__locationResponse',
screenName,
location,
requestId,
isRateLimited: response.status === 429
}, '*');
} catch (error) {
console.error('Error fetching location:', error);
window.postMessage({
type: '__locationResponse',
screenName,
location: null,
requestId
}, '*');
}
}
});
})();