返回提交历史
Modified
quora/__init__.py
+33
-15
Modified
quora/api.py
+50
-44
Modified
quora/cookies.txt
+2
-0
Modified
testing/poe_test.py
+1
-1
Renamed
testing/t3nsor_test.py
+0
-0
XFEstudio/gpt4free
updated quora(poe) (token creation patched atm)
8c0f5e96
代码差异
5 个文件
+86
-60
@@ -8,6 +8,9 @@ from pathlib import Path
8
8
from random import choice, choices, randint
9
9
from string import ascii_letters, digits
10
10
from urllib import parse
11
from os import urandom
12
from hashlib import md5
13
from json import dumps
11
14
12
15
class PoeResponse:
13
16
@@ -91,6 +94,7 @@ class Model:
91
94
"sec-ch-ua" : "\"Chromium\";v=\"112\", \"Google Chrome\";v=\"112\", \"Not:A-Brand\";v=\"99\"",
92
95
"sec-ch-ua-mobile" : "?0",
93
96
"sec-ch-ua-platform": "\"macOS\"",
97
"content-type" : "application/json",
94
98
"sec-fetch-site" : "same-origin",
95
99
"sec-fetch-mode" : "cors",
96
100
"sec-fetch-dest" : "empty",
@@ -155,6 +159,7 @@ class Account:
155
159
"accept" : "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
156
160
"sec-fetch-site" : "same-origin",
157
161
"sec-fetch-mode" : "navigate",
162
"content-type" : "application/json",
158
163
"sec-fetch-user" : "?1",
159
164
"sec-fetch-dest" : "document",
160
165
"accept-encoding" : "gzip, deflate, br",
@@ -167,17 +172,21 @@ class Account:
167
172
168
173
client.headers["poe-formkey"] = next_data['props']['formkey']
169
174
client.headers["poe-tchannel"] = client.get('https://poe.com/api/settings').json()['tchannelData']['channel']
170
171
payload = {
172
"queryName": "MainSignupLoginSection_sendVerificationCodeMutation_Mutation",
173
"variables": {
174
"emailAddress": mail_address,
175
"phoneNumber" : None
175
176
payload = dumps(separators = (',', ':'), obj = {
177
'queryName': 'MainSignupLoginSection_sendVerificationCodeMutation_Mutation',
178
'variables': {
179
'emailAddress': mail_address,
180
'phoneNumber': None,
181
'recaptchaToken': None,
176
182
},
177
"query": "mutation MainSignupLoginSection_sendVerificationCodeMutation_Mutation(\n $emailAddress: String\n $phoneNumber: String\n) {\n sendVerificationCode(verificationReason: login, emailAddress: $emailAddress, phoneNumber: $phoneNumber) {\n status\n errorMessage\n }\n}\n"
178
}
183
'query': 'mutation MainSignupLoginSection_sendVerificationCodeMutation_Mutation(\n $emailAddress: String\n $phoneNumber: String\n $recaptchaToken: String\n) {\n sendVerificationCode(verificationReason: login, emailAddress: $emailAddress, phoneNumber: $phoneNumber, recaptchaToken: $recaptchaToken) {\n status\n errorMessage\n }\n}\n',
184
})
185
186
base_string = payload + client.headers["poe-formkey"] + 'WpuLMiXEKKE98j56k'
187
client.headers["poe-tag-id"] = md5(base_string.encode()).hexdigest()
179
188
180
response = client.post('https://poe.com/api/gql_POST', json=payload)
189
response = client.post('https://poe.com/api/gql_POST', data=payload)
181
190
if 'Bad Request' in response.text:
182
191
if logging: print('bad request, retrying...' , response.json())
183
192
Account.create(proxy = proxy, logging = logging)
@@ -197,7 +206,7 @@ class Account:
197
206
198
207
if logging: print('code', mail_token)
199
208
200
payload = {
209
payload = dumps(separators = (',', ':'), obj={
201
210
"queryName": "SignupOrLoginWithCodeSection_signupWithVerificationCodeMutation_Mutation",
202
211
"variables": {
203
212
"verificationCode" : mail_token,
@@ -205,9 +214,12 @@ class Account:
205
214
"phoneNumber" : None
206
215
},
207
216
"query": "mutation SignupOrLoginWithCodeSection_signupWithVerificationCodeMutation_Mutation(\n $verificationCode: String!\n $emailAddress: String\n $phoneNumber: String\n) {\n signupWithVerificationCode(verificationCode: $verificationCode, emailAddress: $emailAddress, phoneNumber: $phoneNumber) {\n status\n errorMessage\n }\n}\n"
208
}
217
})
218
219
base_string = payload + client.headers["poe-formkey"] + 'WpuLMiXEKKE98j56k'
220
client.headers["poe-tag-id"] = md5(base_string.encode()).hexdigest()
209
221
210
response = client.post('https://poe.com/api/gql_POST', json = payload)
222
response = client.post('https://poe.com/api/gql_POST', data = payload)
211
223
if logging: print('verify_code', response.json())
212
224
213
225
token = parse.unquote(client.cookies.get_dict()['p-b'])
@@ -216,11 +228,17 @@ class Account:
216
228
f.write(f'{token}\n')
217
229
218
230
if enable_bot_creation:
219
client.post("https://poe.com/api/gql_POST", json = {
231
232
payload = {
220
233
"queryName": "UserProfileConfigurePreviewModal_markMultiplayerNuxCompleted_Mutation",
221
234
"variables": {},
222
235
"query": "mutation UserProfileConfigurePreviewModal_markMultiplayerNuxCompleted_Mutation {\n markMultiplayerNuxCompleted {\n viewer {\n hasCompletedMultiplayerNux\n id\n }\n }\n}\n"
223
})
236
}
237
238
base_string = dumps(payload, separators = (',', ':')) + client.headers["poe-formkey"] + 'WpuLMiXEKKE98j56k'
239
client.headers["poe-tag-id"] = md5(base_string.encode()).hexdigest()
240
241
client.post("https://poe.com/api/gql_POST", json = payload)
224
242
225
243
return token
226
244
@@ -305,4 +323,4 @@ class Completion:
305
323
'completion_tokens' : len(chunk["text"]),
306
324
'total_tokens' : len(prompt) + len(chunk["text"])
307
325
}
308
})
326
})
@@ -1,19 +1,3 @@
1
# ading2210/poe-api: a reverse engineered Python API wrapepr for Quora's Poe
2
# Copyright (C) 2023 ading2210
3
4
# This program is free software: you can redistribute it and/or modify
5
# it under the terms of the GNU General Public License as published by
6
# the Free Software Foundation, either version 3 of the License, or
7
# (at your option) any later version.
8
9
# This program is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
# GNU General Public License for more details.
13
14
# You should have received a copy of the GNU General Public License
15
# along with this program. If not, see <https://www.gnu.org/licenses/>.
16
17
1
import requests
18
2
import re
19
3
import json
@@ -71,14 +55,6 @@ class Client:
71
55
home_url = "https://poe.com"
72
56
settings_url = "https://poe.com/api/settings"
73
57
74
formkey = ""
75
next_data = {}
76
bots = {}
77
active_messages = {}
78
message_queues = {}
79
ws = None
80
ws_connected = False
81
82
58
def __init__(self, token, proxy=None):
83
59
self.proxy = proxy
84
60
self.session = requests.Session()
@@ -90,6 +66,9 @@ class Client:
90
66
}
91
67
logger.info(f"Proxy enabled: {self.proxy}")
92
68
69
self.active_messages = {}
70
self.message_queues = {}
71
93
72
self.session.cookies.set("p-b", token, domain="poe.com")
94
73
self.headers = {
95
74
"User-Agent": user_agent,
@@ -99,10 +78,10 @@ class Client:
99
78
self.ws_domain = f"tch{random.randint(1, 1e6)}"
100
79
101
80
self.session.headers.update(self.headers)
102
self.next_data = self.get_next_data()
81
self.next_data = self.get_next_data(overwrite_vars=True)
103
82
self.channel = self.get_channel_data()
104
83
self.connect_ws()
105
self.bots = self.get_bots()
84
self.bots = self.get_bots(download_next_data=False)
106
85
self.bot_names = self.get_bot_names()
107
86
108
87
self.gql_headers = {
@@ -112,7 +91,7 @@ class Client:
112
91
self.gql_headers = {**self.gql_headers, **self.headers}
113
92
self.subscribe()
114
93
115
def get_next_data(self):
94
def get_next_data(self, overwrite_vars=False):
116
95
logger.info("Downloading next_data...")
117
96
118
97
r = request_with_retries(self.session.get, self.home_url)
@@ -120,8 +99,9 @@ class Client:
120
99
json_text = re.search(json_regex, r.text).group(1)
121
100
next_data = json.loads(json_text)
122
101
123
self.formkey = next_data["props"]["formkey"]
124
self.viewer = next_data["props"]["pageProps"]["payload"]["viewer"]
102
if overwrite_vars:
103
self.formkey = next_data["props"]["formkey"]
104
self.viewer = next_data["props"]["pageProps"]["payload"]["viewer"]
125
105
126
106
return next_data
127
107
@@ -134,17 +114,23 @@ class Client:
134
114
chat_data = r.json()["pageProps"]["payload"]["chatOfBotDisplayName"]
135
115
return chat_data
136
116
137
def get_bots(self):
138
viewer = self.next_data["props"]["pageProps"]["payload"]["viewer"]
139
if not "availableBots" in viewer:
140
raise RuntimeError("Invalid token.")
141
bot_list = viewer["availableBots"]
117
def get_bots(self, download_next_data=True):
118
if download_next_data:
119
next_data = self.get_next_data()
120
else:
121
next_data = self.next_data
122
123
if not "availableBots" in self.viewer:
124
raise RuntimeError("Invalid token or no bots are available.")
125
bot_list = self.viewer["availableBots"]
142
126
143
127
bots = {}
144
128
for bot in bot_list:
145
chat_data = self.get_bot(bot["displayName"].lower())
129
chat_data = self.get_bot(bot["displayName"])
146
130
bots[chat_data["defaultBotObject"]["nickname"]] = chat_data
147
131
132
self.bots = bots
133
self.bot_names = self.get_bot_names()
148
134
return bots
149
135
150
136
def get_bot_names(self):
@@ -170,9 +156,20 @@ class Client:
170
156
171
157
def send_query(self, query_name, variables):
172
158
for i in range(20):
173
payload = generate_payload(query_name, variables)
159
json_data = generate_payload(query_name, variables)
160
payload = json.dumps(json_data, separators=(',', ':'))
161
162
base_string = payload + self.gql_headers['poe-formkey'] + 'WpuLMiXEKKE98j56k'
163
164
from hashlib import md5
165
headers = self.gql_headers |{
166
"content-type": "application/json",
167
"poe-tag-id": md5(base_string.encode()).hexdigest()
168
}
169
174
170
r = request_with_retries(
175
self.session.post, self.gql_url, json=payload, headers=self.gql_headers)
171
self.session.post, self.gql_url, data=payload, headers=headers)
172
176
173
data = r.json()
177
174
if data["data"] == None:
178
175
logger.warn(
@@ -212,6 +209,7 @@ class Client:
212
209
self.ws.run_forever(**kwargs)
213
210
214
211
def connect_ws(self):
212
self.ws_connected = False
215
213
self.ws = websocket.WebSocketApp(
216
214
self.get_websocket_url(),
217
215
header={"User-Agent": user_agent},
@@ -233,9 +231,10 @@ class Client:
233
231
def on_ws_connect(self, ws):
234
232
self.ws_connected = True
235
233
236
def on_ws_close(self, ws, close_status_code):
234
def on_ws_close(self, ws, close_status_code, close_message):
237
235
self.ws_connected = False
238
logger.warn(f"Websocket closed with status {close_status_code}")
236
logger.warn(
237
f"Websocket closed with status {close_status_code}: {close_message}")
239
238
240
239
def on_ws_error(self, ws, error):
241
240
self.disconnect_ws()
@@ -345,22 +344,27 @@ class Client:
345
344
def get_message_history(self, chatbot, count=25, cursor=None):
346
345
logger.info(f"Downloading {count} messages from {chatbot}")
347
346
347
messages = []
348
348
if cursor == None:
349
349
chat_data = self.get_bot(self.bot_names[chatbot])
350
350
if not chat_data["messagesConnection"]["edges"]:
351
351
return []
352
cursor = chat_data["messagesConnection"]["edges"][-1]["cursor"]
352
messages = chat_data["messagesConnection"]["edges"][:count]
353
cursor = chat_data["messagesConnection"]["pageInfo"]["startCursor"]
354
count -= len(messages)
353
355
354
356
cursor = str(cursor)
355
357
if count > 50:
356
358
messages = self.get_message_history(
357
chatbot, count=50, cursor=cursor)
359
chatbot, count=50, cursor=cursor) + messages
358
360
while count > 0:
361
count -= 50
359
362
new_cursor = messages[0]["cursor"]
360
363
new_messages = self.get_message_history(
361
364
chatbot, min(50, count), cursor=new_cursor)
362
365
messages = new_messages + messages
363
count -= 50
366
return messages
367
elif count <= 0:
364
368
return messages
365
369
366
370
result = self.send_query("ChatListPaginationQuery", {
@@ -368,7 +372,9 @@ class Client:
368
372
"cursor": cursor,
369
373
"id": self.bots[chatbot]["id"]
370
374
})
371
return result["data"]["node"]["messagesConnection"]["edges"]
375
query_messages = result["data"]["node"]["messagesConnection"]["edges"]
376
messages = query_messages + messages
377
return messages
372
378
373
379
def delete_message(self, message_ids):
374
380
logger.info(f"Deleting messages: {message_ids}")
@@ -398,4 +404,4 @@ class Client:
398
404
logger.info(f"No more messages left to delete.")
399
405
400
406
401
load_queries()
407
load_queries()
@@ -13,3 +13,5 @@ UP2wQVds17VFHh6IfCQFrA==
13
13
FNgKEpc2r-XqWe0rHBfYpg==
14
14
juCAh6kB0sUpXHvKik2woA==
15
15
nBvuNYRLaE4xE4HuzBPiIQ==
16
oyae3iClomSrk6RJywZ4iw==
17
1Z27Ul8BTdNOhncT5H6wdg==
@@ -6,7 +6,7 @@ print('token', token)
6
6
7
7
sleep(2)
8
8
9
for response in quora.StreamingCompletion.create(model = 'gpt-4',
9
for response in quora.StreamingCompletion.create(model = 'gpt-3.5-turbo',
10
10
prompt = 'hello world',
11
11
token = token):
12
12
此文件没有可显示的逐行差异。