forked from yourChainGod/xAuth
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathxauth.js
More file actions
199 lines (161 loc) · 6.22 KB
/
xauth.js
File metadata and controls
199 lines (161 loc) · 6.22 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
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
const axios = require('axios');
class XAuth {
static TWITTER_AUTHORITY = 'twitter.com';
static TWITTER_ORIGIN = 'https://twitter.com';
static TWITTER_API_BASE = 'https://twitter.com/i/api/2';
static USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36';
static AUTHORIZATION = 'Bearer AAAAAAAAAAAAAAAAAAAAANRILgAAAAAAnNwIzUejRCOuH5E6I8xnZz4puTs%3D1Zv7ttfk8LF81IUq16cHjhLTvJu4FA33AGWWjCpTnA';
static MAX_RETRIES = 3;
static RETRY_INTERVAL = 1000; // milliseconds
static ACCOUNT_STATE = {
32: 'Bad Token',
64: 'SUSPENDED',
141: 'SUSPENDED',
326: 'LOCKED'
};
constructor(authToken) {
this.authToken = authToken;
this.client = this._createAxiosInstance(true);
this.client2 = this._createAxiosInstance(false);
}
_createAxiosInstance(includeTwitterHeaders = true) {
const headers = {
'user-agent': XAuth.USER_AGENT,
'Cookie': `auth_token=${this.authToken}`
};
if (includeTwitterHeaders) {
Object.assign(headers, {
'authority': XAuth.TWITTER_AUTHORITY,
'origin': XAuth.TWITTER_ORIGIN,
'x-twitter-auth-type': 'OAuth2Session',
'x-twitter-active-user': 'yes',
'authorization': XAuth.AUTHORIZATION
});
}
return axios.create({
headers,
timeout: 10000,
validateStatus: null
});
}
async _handleResponse(response, retryFunc) {
if (response.status === 429) {
await new Promise(resolve => setTimeout(resolve, XAuth.RETRY_INTERVAL));
if (retryFunc) {
return await retryFunc();
}
}
}
async getTwitterToken(oauthToken) {
if (!oauthToken) {
throw new Error('oauth_token不能为空');
}
const response = await this.client2.get('https://api.x.com/oauth/authenticate', {
params: { oauth_token: oauthToken }
});
await this._handleResponse(response);
const content = response.data;
if (!content.includes('authenticity_token')) {
if (content.includes('The request token for this page is invalid')) {
throw new Error('请求oauth_token无效');
}
throw new Error('响应中未找到authenticity_token');
}
let token = null;
const tokenMarkers = [
'name="authenticity_token" value="',
'name="authenticity_token" type="hidden" value="'
];
for (const marker of tokenMarkers) {
if (content.includes(marker)) {
token = content.split(marker)[1].split('"')[0];
break;
}
}
if (!token) {
throw new Error('获取到的authenticity_token为空');
}
return token;
}
async oauth1(oauthToken) {
const authenticityToken = await this.getTwitterToken(oauthToken);
const data = new URLSearchParams({
authenticity_token: authenticityToken,
oauth_token: oauthToken
});
const response = await this.client2.post('https://x.com/oauth/authorize', data);
await this._handleResponse(response);
const content = response.data;
if (!content.includes('oauth_verifier')) {
if (content.includes('This account is suspended.')) {
throw new Error('该账户已被封禁');
}
throw new Error('未找到oauth_verifier');
}
const verifier = content.split('oauth_verifier=')[1].split('"')[0];
if (!verifier) {
throw new Error('获取到的oauth_verifier为空');
}
return verifier;
}
async getAuthCode(params) {
if (!params || Object.keys(params).length === 0) {
throw new Error('参数不能为空');
}
const response = await this.client.get(`${XAuth.TWITTER_API_BASE}/oauth2/authorize`, {
params,
headers: this.client.defaults.headers // 确保使用当前的headers
});
await this._handleResponse(response, () => this.getAuthCode(params));
console.log('响应数据:', response.data);
if (!response.data || typeof response.data !== 'object') {
throw new Error('响应格式错误');
}
const data = response.data;
if (data.code === 353) {
const ct0Cookie = response.headers['set-cookie']?.find(cookie => cookie.startsWith('ct0='));
if (ct0Cookie) {
const ct0 = ct0Cookie.split(';')[0].split('=')[1];
this.client.defaults.headers['x-csrf-token'] = ct0;
return await this.getAuthCode(params);
}
throw new Error('未找到ct0 cookie');
}
if (data.errors && data.errors.length > 0) {
const errorCode = data.errors[0].code;
if (XAuth.ACCOUNT_STATE[errorCode]) {
throw new Error(`token状态错误: ${XAuth.ACCOUNT_STATE[errorCode]}`);
}
}
if (!data.auth_code) {
throw new Error('响应中未找到auth_code');
}
return data.auth_code;
}
async oauth2(params) {
const authCode = await this.getAuthCode(params);
const data = new URLSearchParams({
approval: 'true',
code: authCode
});
const response = await this.client.post(
`${XAuth.TWITTER_API_BASE}/oauth2/authorize`,
data,
{
headers: {
...this.client.defaults.headers, // 包含所有现有的headers
'Content-Type': 'application/x-www-form-urlencoded',
'Accept': 'application/json, text/plain, */*'
},
maxRedirects: 5,
validateStatus: null
}
);
await this._handleResponse(response, () => this.oauth2(params));
if (!response.data.includes('redirect_uri')) {
throw new Error('响应中未找到redirect_uri');
}
return authCode;
}
}
module.exports = XAuth;