Raju Komati commited on
Commit
90e7797
1 Parent(s): f43107a

refactored the code and updated Poe class to accept keyword arguments to specify driver type

Browse files
Files changed (3) hide show
  1. quora/README.md +1 -1
  2. quora/__init__.py +233 -222
  3. quora/api.py +22 -55
quora/README.md CHANGED
@@ -59,7 +59,7 @@ from quora import Poe
59
 
60
  # available models: ['Sage', 'GPT-4', 'Claude+', 'Claude-instant', 'ChatGPT', 'Dragonfly', 'NeevaAI']
61
 
62
- poe = Poe(model='ChatGPT')
63
  poe.chat('who won the football world cup most?')
64
 
65
  # new bot creation
 
59
 
60
  # available models: ['Sage', 'GPT-4', 'Claude+', 'Claude-instant', 'ChatGPT', 'Dragonfly', 'NeevaAI']
61
 
62
+ poe = Poe(model='ChatGPT', driver='firefox', cookie_path='cookie.json')
63
  poe.chat('who won the football world cup most?')
64
 
65
  # new bot creation
quora/__init__.py CHANGED
@@ -6,13 +6,14 @@ from pathlib import Path
6
  from random import choice, choices, randint
7
  from re import search, findall
8
  from string import ascii_letters, digits
9
- from typing import Optional
10
  from urllib.parse import unquote
11
 
12
  import selenium.webdriver.support.expected_conditions as EC
 
13
  from pypasser import reCaptchaV3
14
  from requests import Session
15
- from selenium import webdriver
16
  from selenium.webdriver.common.by import By
17
  from selenium.webdriver.support.wait import WebDriverWait
18
  from tls_client import Session as TLS
@@ -20,33 +21,51 @@ from tls_client import Session as TLS
20
  from quora.api import Client as PoeClient
21
  from quora.mail import Emailnator
22
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
23
  # from twocaptcha import TwoCaptcha
24
  # solver = TwoCaptcha('72747bf24a9d89b4dcc1b24875efd358')
25
 
26
  MODELS = {
27
- "Sage": "capybara",
28
- "GPT-4": "beaver",
29
- "Claude+": "a2_2",
30
- "Claude-instant": "a2",
31
- "ChatGPT": "chinchilla",
32
- "Dragonfly": "nutria",
33
- "NeevaAI": "hutia",
34
  }
35
 
36
 
37
  def extract_formkey(html):
38
- script_regex = r"<script>if\(.+\)throw new Error;(.+)</script>"
39
  script_text = search(script_regex, html).group(1)
40
  key_regex = r'var .="([0-9a-f]+)",'
41
  key_text = search(key_regex, script_text).group(1)
42
- cipher_regex = r".\[(\d+)\]=.\[(\d+)\]"
43
  cipher_pairs = findall(cipher_regex, script_text)
44
 
45
- formkey_list = [""] * len(cipher_pairs)
46
  for pair in cipher_pairs:
47
  formkey_index, key_index = map(int, pair)
48
  formkey_list[formkey_index] = key_text[key_index]
49
- formkey = "".join(formkey_list)
50
 
51
  return formkey
52
 
@@ -55,35 +74,35 @@ class PoeResponse:
55
  class Completion:
56
  class Choices:
57
  def __init__(self, choice: dict) -> None:
58
- self.text = choice["text"]
59
  self.content = self.text.encode()
60
- self.index = choice["index"]
61
- self.logprobs = choice["logprobs"]
62
- self.finish_reason = choice["finish_reason"]
63
 
64
  def __repr__(self) -> str:
65
- return f"""<__main__.APIResponse.Completion.Choices(\n text = {self.text.encode()},\n index = {self.index},\n logprobs = {self.logprobs},\n finish_reason = {self.finish_reason})object at 0x1337>"""
66
 
67
  def __init__(self, choices: dict) -> None:
68
  self.choices = [self.Choices(choice) for choice in choices]
69
 
70
  class Usage:
71
  def __init__(self, usage_dict: dict) -> None:
72
- self.prompt_tokens = usage_dict["prompt_tokens"]
73
- self.completion_tokens = usage_dict["completion_tokens"]
74
- self.total_tokens = usage_dict["total_tokens"]
75
 
76
  def __repr__(self):
77
- return f"""<__main__.APIResponse.Usage(\n prompt_tokens = {self.prompt_tokens},\n completion_tokens = {self.completion_tokens},\n total_tokens = {self.total_tokens})object at 0x1337>"""
78
 
79
  def __init__(self, response_dict: dict) -> None:
80
  self.response_dict = response_dict
81
- self.id = response_dict["id"]
82
- self.object = response_dict["object"]
83
- self.created = response_dict["created"]
84
- self.model = response_dict["model"]
85
- self.completion = self.Completion(response_dict["choices"])
86
- self.usage = self.Usage(response_dict["usage"])
87
 
88
  def json(self) -> dict:
89
  return self.response_dict
@@ -91,139 +110,135 @@ class PoeResponse:
91
 
92
  class ModelResponse:
93
  def __init__(self, json_response: dict) -> None:
94
- self.id = json_response["data"]["poeBotCreate"]["bot"]["id"]
95
- self.name = json_response["data"]["poeBotCreate"]["bot"]["displayName"]
96
- self.limit = json_response["data"]["poeBotCreate"]["bot"]["messageLimit"][
97
- "dailyLimit"
98
- ]
99
- self.deleted = json_response["data"]["poeBotCreate"]["bot"]["deletionState"]
100
 
101
 
102
  class Model:
 
103
  def create(
104
  token: str,
105
- model: str = "gpt-3.5-turbo", # claude-instant
106
- system_prompt: str = "You are ChatGPT a large language model developed by Openai. Answer as consisely as possible",
107
- description: str = "gpt-3.5 language model from openai, skidded by poe.com",
108
  handle: str = None,
109
  ) -> ModelResponse:
110
  models = {
111
- "gpt-3.5-turbo": "chinchilla",
112
- "claude-instant-v1.0": "a2",
113
- "gpt-4": "beaver",
114
  }
115
 
116
  if not handle:
117
- handle = f"gptx{randint(1111111, 9999999)}"
118
 
119
  client = Session()
120
- client.cookies["p-b"] = token
121
 
122
- formkey = extract_formkey(client.get("https://poe.com").text)
123
- settings = client.get("https://poe.com/api/settings").json()
124
 
125
  client.headers = {
126
- "host": "poe.com",
127
- "origin": "https://poe.com",
128
- "referer": "https://poe.com/",
129
- "poe-formkey": formkey,
130
- "poe-tchannel": settings["tchannelData"]["channel"],
131
- "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36",
132
- "connection": "keep-alive",
133
- "sec-ch-ua": '"Chromium";v="112", "Google Chrome";v="112", "Not:A-Brand";v="99"',
134
- "sec-ch-ua-mobile": "?0",
135
- "sec-ch-ua-platform": '"macOS"',
136
- "content-type": "application/json",
137
- "sec-fetch-site": "same-origin",
138
- "sec-fetch-mode": "cors",
139
- "sec-fetch-dest": "empty",
140
- "accept": "*/*",
141
- "accept-encoding": "gzip, deflate, br",
142
- "accept-language": "en-GB,en-US;q=0.9,en;q=0.8",
143
  }
144
 
145
  payload = dumps(
146
- separators=(",", ":"),
147
  obj={
148
- "queryName": "CreateBotMain_poeBotCreate_Mutation",
149
- "variables": {
150
- "model": models[model],
151
- "handle": handle,
152
- "prompt": system_prompt,
153
- "isPromptPublic": True,
154
- "introduction": "",
155
- "description": description,
156
- "profilePictureUrl": "https://qph.fs.quoracdn.net/main-qimg-24e0b480dcd946e1cc6728802c5128b6",
157
- "apiUrl": None,
158
- "apiKey": "".join(choices(ascii_letters + digits, k=32)),
159
- "isApiBot": False,
160
- "hasLinkification": False,
161
- "hasMarkdownRendering": False,
162
- "hasSuggestedReplies": False,
163
- "isPrivateBot": False,
164
  },
165
- "query": "mutation CreateBotMain_poeBotCreate_Mutation(\n $model: String!\n $handle: String!\n $prompt: String!\n $isPromptPublic: Boolean!\n $introduction: String!\n $description: String!\n $profilePictureUrl: String\n $apiUrl: String\n $apiKey: String\n $isApiBot: Boolean\n $hasLinkification: Boolean\n $hasMarkdownRendering: Boolean\n $hasSuggestedReplies: Boolean\n $isPrivateBot: Boolean\n) {\n poeBotCreate(model: $model, handle: $handle, promptPlaintext: $prompt, isPromptPublic: $isPromptPublic, introduction: $introduction, description: $description, profilePicture: $profilePictureUrl, apiUrl: $apiUrl, apiKey: $apiKey, isApiBot: $isApiBot, hasLinkification: $hasLinkification, hasMarkdownRendering: $hasMarkdownRendering, hasSuggestedReplies: $hasSuggestedReplies, isPrivateBot: $isPrivateBot) {\n status\n bot {\n id\n ...BotHeader_bot\n }\n }\n}\n\nfragment BotHeader_bot on Bot {\n displayName\n messageLimit {\n dailyLimit\n }\n ...BotImage_bot\n ...BotLink_bot\n ...IdAnnotation_node\n ...botHelpers_useViewerCanAccessPrivateBot\n ...botHelpers_useDeletion_bot\n}\n\nfragment BotImage_bot on Bot {\n displayName\n ...botHelpers_useDeletion_bot\n ...BotImage_useProfileImage_bot\n}\n\nfragment BotImage_useProfileImage_bot on Bot {\n image {\n __typename\n ... on LocalBotImage {\n localName\n }\n ... on UrlBotImage {\n url\n }\n }\n ...botHelpers_useDeletion_bot\n}\n\nfragment BotLink_bot on Bot {\n displayName\n}\n\nfragment IdAnnotation_node on Node {\n __isNode: __typename\n id\n}\n\nfragment botHelpers_useDeletion_bot on Bot {\n deletionState\n}\n\nfragment botHelpers_useViewerCanAccessPrivateBot on Bot {\n isPrivateBot\n viewerIsCreator\n}\n",
166
  },
167
  )
168
 
169
- base_string = payload + client.headers["poe-formkey"] + "WpuLMiXEKKE98j56k"
170
- client.headers["poe-tag-id"] = md5(base_string.encode()).hexdigest()
171
 
172
- response = client.post("https://poe.com/api/gql_POST", data=payload)
173
 
174
- if "success" not in response.text:
175
  raise Exception(
176
- """
177
  Bot creation Failed
178
  !! Important !!
179
  Bot creation was not enabled on this account
180
  please use: quora.Account.create with enable_bot_creation set to True
181
- """
182
  )
183
 
184
  return ModelResponse(response.json())
185
 
186
 
187
  class Account:
 
188
  def create(
189
  proxy: Optional[str] = None,
190
  logging: bool = False,
191
  enable_bot_creation: bool = False,
192
  ):
193
- client = TLS(client_identifier="chrome110")
194
- client.proxies = (
195
- {"http": f"http://{proxy}", "https": f"http://{proxy}"} if proxy else None
196
- )
197
 
198
  mail_client = Emailnator()
199
  mail_address = mail_client.get_mail()
200
 
201
  if logging:
202
- print("email", mail_address)
203
 
204
  client.headers = {
205
- "authority": "poe.com",
206
- "accept": "*/*",
207
- "accept-language": "en,fr-FR;q=0.9,fr;q=0.8,es-ES;q=0.7,es;q=0.6,en-US;q=0.5,am;q=0.4,de;q=0.3",
208
- "content-type": "application/json",
209
- "origin": "https://poe.com",
210
- "poe-tag-id": "null",
211
- "referer": "https://poe.com/login",
212
- "sec-ch-ua": '"Chromium";v="112", "Google Chrome";v="112", "Not:A-Brand";v="99"',
213
- "sec-ch-ua-mobile": "?0",
214
- "sec-ch-ua-platform": '"macOS"',
215
- "sec-fetch-dest": "empty",
216
- "sec-fetch-mode": "cors",
217
- "sec-fetch-site": "same-origin",
218
- "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36",
219
- "poe-formkey": extract_formkey(client.get("https://poe.com/login").text),
220
- "poe-tchannel": client.get("https://poe.com/api/settings").json()[
221
- "tchannelData"
222
- ]["channel"],
223
  }
224
 
225
  token = reCaptchaV3(
226
- "https://www.recaptcha.net/recaptcha/enterprise/anchor?ar=1&k=6LflhEElAAAAAI_ewVwRWI9hsyV4mbZnYAslSvlG&co=aHR0cHM6Ly9wb2UuY29tOjQ0Mw..&hl=en&v=4PnKmGB9wRHh1i04o7YUICeI&size=invisible&cb=bi6ivxoskyal"
227
  )
228
  # token = solver.recaptcha(sitekey='6LflhEElAAAAAI_ewVwRWI9hsyV4mbZnYAslSvlG',
229
  # url = 'https://poe.com/login?redirect_url=%2F',
@@ -233,77 +248,74 @@ class Account:
233
  # action = 'login',)['code']
234
 
235
  payload = dumps(
236
- separators=(",", ":"),
237
  obj={
238
- "queryName": "MainSignupLoginSection_sendVerificationCodeMutation_Mutation",
239
- "variables": {
240
- "emailAddress": mail_address,
241
- "phoneNumber": None,
242
- "recaptchaToken": token,
243
  },
244
- "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",
245
  },
246
  )
247
 
248
- base_string = payload + client.headers["poe-formkey"] + "WpuLMiXEKKE98j56k"
249
- client.headers["poe-tag-id"] = md5(base_string.encode()).hexdigest()
250
 
251
  print(dumps(client.headers, indent=4))
252
 
253
- response = client.post("https://poe.com/api/gql_POST", data=payload)
254
 
255
- if "automated_request_detected" in response.text:
256
- print("please try using a proxy / wait for fix")
257
 
258
- if "Bad Request" in response.text:
259
  if logging:
260
- print("bad request, retrying...", response.json())
261
  quit()
262
 
263
  if logging:
264
- print("send_code", response.json())
265
 
266
  mail_content = mail_client.get_message()
267
  mail_token = findall(r';">(\d{6,7})</div>', mail_content)[0]
268
 
269
  if logging:
270
- print("code", mail_token)
271
 
272
  payload = dumps(
273
- separators=(",", ":"),
274
  obj={
275
- "queryName": "SignupOrLoginWithCodeSection_signupWithVerificationCodeMutation_Mutation",
276
- "variables": {
277
- "verificationCode": str(mail_token),
278
- "emailAddress": mail_address,
279
- "phoneNumber": None,
280
  },
281
- "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",
282
  },
283
  )
284
 
285
- base_string = payload + client.headers["poe-formkey"] + "WpuLMiXEKKE98j56k"
286
- client.headers["poe-tag-id"] = md5(base_string.encode()).hexdigest()
287
 
288
- response = client.post("https://poe.com/api/gql_POST", data=payload)
289
  if logging:
290
- print("verify_code", response.json())
291
 
292
  def get(self):
293
- cookies = (
294
- open(Path(__file__).resolve().parent / "cookies.txt", "r")
295
- .read()
296
- .splitlines()
297
- )
298
  return choice(cookies)
299
 
300
 
301
  class StreamingCompletion:
 
302
  def create(
303
- model: str = "gpt-4",
304
  custom_model: bool = None,
305
- prompt: str = "hello world",
306
- token: str = "",
307
  ):
308
  _model = MODELS[model] if not custom_model else custom_model
309
 
@@ -312,22 +324,22 @@ class StreamingCompletion:
312
  for chunk in client.send_message(_model, prompt):
313
  yield PoeResponse(
314
  {
315
- "id": chunk["messageId"],
316
- "object": "text_completion",
317
- "created": chunk["creationTime"],
318
- "model": _model,
319
- "choices": [
320
  {
321
- "text": chunk["text_new"],
322
- "index": 0,
323
- "logprobs": None,
324
- "finish_reason": "stop",
325
  }
326
  ],
327
- "usage": {
328
- "prompt_tokens": len(prompt),
329
- "completion_tokens": len(chunk["text_new"]),
330
- "total_tokens": len(prompt) + len(chunk["text_new"]),
331
  },
332
  }
333
  )
@@ -335,17 +347,17 @@ class StreamingCompletion:
335
 
336
  class Completion:
337
  def create(
338
- model: str = "gpt-4",
339
  custom_model: str = None,
340
- prompt: str = "hello world",
341
- token: str = "",
342
  ):
343
  models = {
344
- "sage": "capybara",
345
- "gpt-4": "beaver",
346
- "claude-v1.2": "a2_2",
347
- "claude-instant-v1.0": "a2",
348
- "gpt-3.5-turbo": "chinchilla",
349
  }
350
 
351
  _model = models[model] if not custom_model else custom_model
@@ -357,73 +369,68 @@ class Completion:
357
 
358
  return PoeResponse(
359
  {
360
- "id": chunk["messageId"],
361
- "object": "text_completion",
362
- "created": chunk["creationTime"],
363
- "model": _model,
364
- "choices": [
365
  {
366
- "text": chunk["text"],
367
- "index": 0,
368
- "logprobs": None,
369
- "finish_reason": "stop",
370
  }
371
  ],
372
- "usage": {
373
- "prompt_tokens": len(prompt),
374
- "completion_tokens": len(chunk["text"]),
375
- "total_tokens": len(prompt) + len(chunk["text"]),
376
  },
377
  }
378
  )
379
 
380
 
381
  class Poe:
382
- def __init__(self, model: str = "ChatGPT"):
 
 
 
 
 
 
 
383
  # validating the model
384
  if model and model not in MODELS:
385
- raise RuntimeError(
386
- "Sorry, the model you provided does not exist. Please check and try again."
387
- )
388
  self.model = MODELS[model]
389
- self.cookie = self.__load_cookie()
 
390
  self.client = PoeClient(self.cookie)
391
 
392
- def __load_cookie(self) -> str:
393
- if (cookie_file := Path("./quora/cookie.json")).exists():
394
  with cookie_file.open() as fp:
395
  cookie = json.load(fp)
396
- if datetime.fromtimestamp(cookie["expiry"]) < datetime.now():
397
- cookie = self.__register_and_get_cookie()
398
  else:
399
- print("Loading the cookie from file")
400
  else:
401
- cookie = self.__register_and_get_cookie()
402
 
403
- return unquote(cookie["value"])
404
 
405
- @classmethod
406
- def __register_and_get_cookie(cls) -> dict:
407
  mail_client = Emailnator()
408
  mail_address = mail_client.get_mail()
409
 
410
- print(mail_address)
411
- options = webdriver.FirefoxOptions()
412
- # options.add_argument("-headless")
413
- try:
414
- driver = webdriver.Firefox(options=options)
415
-
416
- except Exception:
417
- raise Exception(b'The error message you are receiving is due to the `geckodriver` executable not being found in your system\'s PATH. To resolve this issue, you need to download the geckodriver and add its location to your system\'s PATH.\n\nHere are the steps to resolve the issue:\n\n1. Download the geckodriver for your platform (Windows, macOS, or Linux) from the following link: https://github.com/mozilla/geckodriver/releases\n\n2. Extract the downloaded archive and locate the geckodriver executable.\n\n3. Add the geckodriver executable to your system\'s PATH.\n\nFor macOS and Linux:\n\n- Open a terminal window.\n- Move the geckodriver executable to a directory that is already in your PATH, or create a new directory and add it to your PATH:\n\n```bash\n# Example: Move geckodriver to /usr/local/bin\nmv /path/to/your/geckodriver /usr/local/bin\n```\n\n- If you created a new directory, add it to your PATH:\n\n```bash\n# Example: Add a new directory to PATH\nexport PATH=$PATH:/path/to/your/directory\n```\n\nFor Windows:\n\n- Right-click on "My Computer" or "This PC" and select "Properties".\n- Click on "Advanced system settings".\n- Click on the "Environment Variables" button.\n- In the "System variables" section, find the "Path" variable, select it, and click "Edit".\n- Click "New" and add the path to the directory containing the geckodriver executable.\n\nAfter adding the geckodriver to your PATH, restart your terminal or command prompt and try running your script again. The error should be resolved.')
418
-
419
  driver.get("https://www.poe.com")
420
 
421
  # clicking use email button
422
  driver.find_element(By.XPATH, '//button[contains(text(), "Use email")]').click()
423
 
424
- email = WebDriverWait(driver, 30).until(
425
- EC.presence_of_element_located((By.XPATH, '//input[@type="email"]'))
426
- )
427
  email.send_keys(mail_address)
428
  driver.find_element(By.XPATH, '//button[text()="Go"]').click()
429
 
@@ -434,46 +441,50 @@ class Poe:
434
  EC.presence_of_element_located((By.XPATH, '//input[@placeholder="Code"]'))
435
  )
436
  verification_code.send_keys(code)
437
- verify_button = EC.presence_of_element_located(
438
- (By.XPATH, '//button[text()="Verify"]')
439
- )
440
- login_button = EC.presence_of_element_located(
441
- (By.XPATH, '//button[text()="Log In"]')
442
- )
443
 
444
  WebDriverWait(driver, 30).until(EC.any_of(verify_button, login_button)).click()
445
 
446
- cookie = driver.get_cookie("p-b")
447
 
448
- with open("./quora/cookie.json", "w") as fw:
449
  json.dump(cookie, fw)
450
 
451
  driver.close()
452
  return cookie
453
 
 
 
 
 
 
 
 
 
 
 
 
 
454
  def chat(self, message: str, model: Optional[str] = None) -> str:
455
  if model and model not in MODELS:
456
- raise RuntimeError(
457
- "Sorry, the model you provided does not exist. Please check and try again."
458
- )
459
  model = MODELS[model] if model else self.model
460
  response = None
461
  for chunk in self.client.send_message(model, message):
462
- response = chunk["text"]
463
  return response
464
 
465
  def create_bot(
466
  self,
467
  name: str,
468
  /,
469
- prompt: str = "",
470
- base_model: str = "ChatGPT",
471
- description: str = "",
472
  ) -> None:
473
  if base_model not in MODELS:
474
- raise RuntimeError(
475
- "Sorry, the base_model you provided does not exist. Please check and try again."
476
- )
477
 
478
  response = self.client.create_bot(
479
  handle=name,
 
6
  from random import choice, choices, randint
7
  from re import search, findall
8
  from string import ascii_letters, digits
9
+ from typing import Optional, Union
10
  from urllib.parse import unquote
11
 
12
  import selenium.webdriver.support.expected_conditions as EC
13
+ from fake_useragent import UserAgent
14
  from pypasser import reCaptchaV3
15
  from requests import Session
16
+ from selenium.webdriver import Firefox, Chrome, FirefoxOptions, ChromeOptions
17
  from selenium.webdriver.common.by import By
18
  from selenium.webdriver.support.wait import WebDriverWait
19
  from tls_client import Session as TLS
 
21
  from quora.api import Client as PoeClient
22
  from quora.mail import Emailnator
23
 
24
+ CHROME_DRIVER_URL = 'https://chromedriver.storage.googleapis.com'
25
+ FIREFOX_DRIVER_URL = 'https://api.github.com/repos/mozilla/geckodriver/releases'
26
+
27
+ SELENIUM_WEB_DRIVER_ERROR_MSG = b'''The error message you are receiving is due to the `geckodriver` executable not
28
+ being found in your system\'s PATH. To resolve this issue, you need to download the geckodriver and add its location
29
+ to your system\'s PATH.\n\nHere are the steps to resolve the issue:\n\n1. Download the geckodriver for your platform
30
+ (Windows, macOS, or Linux) from the following link: https://github.com/mozilla/geckodriver/releases\n\n2. Extract the
31
+ downloaded archive and locate the geckodriver executable.\n\n3. Add the geckodriver executable to your system\'s
32
+ PATH.\n\nFor macOS and Linux:\n\n- Open a terminal window.\n- Move the geckodriver executable to a directory that is
33
+ already in your PATH, or create a new directory and add it to your PATH:\n\n```bash\n# Example: Move geckodriver to
34
+ /usr/local/bin\nmv /path/to/your/geckodriver /usr/local/bin\n```\n\n- If you created a new directory, add it to your
35
+ PATH:\n\n```bash\n# Example: Add a new directory to PATH\nexport PATH=$PATH:/path/to/your/directory\n```\n\nFor
36
+ Windows:\n\n- Right-click on "My Computer" or "This PC" and select "Properties".\n- Click on "Advanced system
37
+ settings".\n- Click on the "Environment Variables" button.\n- In the "System variables" section, find the "Path"
38
+ variable, select it, and click "Edit".\n- Click "New" and add the path to the directory containing the geckodriver
39
+ executable.\n\nAfter adding the geckodriver to your PATH, restart your terminal or command prompt and try running
40
+ your script again. The error should be resolved.'''
41
+
42
  # from twocaptcha import TwoCaptcha
43
  # solver = TwoCaptcha('72747bf24a9d89b4dcc1b24875efd358')
44
 
45
  MODELS = {
46
+ 'Sage': 'capybara',
47
+ 'GPT-4': 'beaver',
48
+ 'Claude+': 'a2_2',
49
+ 'Claude-instant': 'a2',
50
+ 'ChatGPT': 'chinchilla',
51
+ 'Dragonfly': 'nutria',
52
+ 'NeevaAI': 'hutia',
53
  }
54
 
55
 
56
  def extract_formkey(html):
57
+ script_regex = r'<script>if\(.+\)throw new Error;(.+)</script>'
58
  script_text = search(script_regex, html).group(1)
59
  key_regex = r'var .="([0-9a-f]+)",'
60
  key_text = search(key_regex, script_text).group(1)
61
+ cipher_regex = r'.\[(\d+)\]=.\[(\d+)\]'
62
  cipher_pairs = findall(cipher_regex, script_text)
63
 
64
+ formkey_list = [''] * len(cipher_pairs)
65
  for pair in cipher_pairs:
66
  formkey_index, key_index = map(int, pair)
67
  formkey_list[formkey_index] = key_text[key_index]
68
+ formkey = ''.join(formkey_list)
69
 
70
  return formkey
71
 
 
74
  class Completion:
75
  class Choices:
76
  def __init__(self, choice: dict) -> None:
77
+ self.text = choice['text']
78
  self.content = self.text.encode()
79
+ self.index = choice['index']
80
+ self.logprobs = choice['logprobs']
81
+ self.finish_reason = choice['finish_reason']
82
 
83
  def __repr__(self) -> str:
84
+ return f'''<__main__.APIResponse.Completion.Choices(\n text = {self.text.encode()},\n index = {self.index},\n logprobs = {self.logprobs},\n finish_reason = {self.finish_reason})object at 0x1337>'''
85
 
86
  def __init__(self, choices: dict) -> None:
87
  self.choices = [self.Choices(choice) for choice in choices]
88
 
89
  class Usage:
90
  def __init__(self, usage_dict: dict) -> None:
91
+ self.prompt_tokens = usage_dict['prompt_tokens']
92
+ self.completion_tokens = usage_dict['completion_tokens']
93
+ self.total_tokens = usage_dict['total_tokens']
94
 
95
  def __repr__(self):
96
+ return f'''<__main__.APIResponse.Usage(\n prompt_tokens = {self.prompt_tokens},\n completion_tokens = {self.completion_tokens},\n total_tokens = {self.total_tokens})object at 0x1337>'''
97
 
98
  def __init__(self, response_dict: dict) -> None:
99
  self.response_dict = response_dict
100
+ self.id = response_dict['id']
101
+ self.object = response_dict['object']
102
+ self.created = response_dict['created']
103
+ self.model = response_dict['model']
104
+ self.completion = self.Completion(response_dict['choices'])
105
+ self.usage = self.Usage(response_dict['usage'])
106
 
107
  def json(self) -> dict:
108
  return self.response_dict
 
110
 
111
  class ModelResponse:
112
  def __init__(self, json_response: dict) -> None:
113
+ self.id = json_response['data']['poeBotCreate']['bot']['id']
114
+ self.name = json_response['data']['poeBotCreate']['bot']['displayName']
115
+ self.limit = json_response['data']['poeBotCreate']['bot']['messageLimit']['dailyLimit']
116
+ self.deleted = json_response['data']['poeBotCreate']['bot']['deletionState']
 
 
117
 
118
 
119
  class Model:
120
+ @staticmethod
121
  def create(
122
  token: str,
123
+ model: str = 'gpt-3.5-turbo', # claude-instant
124
+ system_prompt: str = 'You are ChatGPT a large language model developed by Openai. Answer as consisely as possible',
125
+ description: str = 'gpt-3.5 language model from openai, skidded by poe.com',
126
  handle: str = None,
127
  ) -> ModelResponse:
128
  models = {
129
+ 'gpt-3.5-turbo': 'chinchilla',
130
+ 'claude-instant-v1.0': 'a2',
131
+ 'gpt-4': 'beaver',
132
  }
133
 
134
  if not handle:
135
+ handle = f'gptx{randint(1111111, 9999999)}'
136
 
137
  client = Session()
138
+ client.cookies['p-b'] = token
139
 
140
+ formkey = extract_formkey(client.get('https://poe.com').text)
141
+ settings = client.get('https://poe.com/api/settings').json()
142
 
143
  client.headers = {
144
+ 'host': 'poe.com',
145
+ 'origin': 'https://poe.com',
146
+ 'referer': 'https://poe.com/',
147
+ 'poe-formkey': formkey,
148
+ 'poe-tchannel': settings['tchannelData']['channel'],
149
+ 'user-agent': UserAgent().random,
150
+ 'connection': 'keep-alive',
151
+ 'sec-ch-ua': '"Chromium";v="112", "Google Chrome";v="112", "Not:A-Brand";v="99"',
152
+ 'sec-ch-ua-mobile': '?0',
153
+ 'sec-ch-ua-platform': '"macOS"',
154
+ 'content-type': 'application/json',
155
+ 'sec-fetch-site': 'same-origin',
156
+ 'sec-fetch-mode': 'cors',
157
+ 'sec-fetch-dest': 'empty',
158
+ 'accept': '*/*',
159
+ 'accept-encoding': 'gzip, deflate, br',
160
+ 'accept-language': 'en-GB,en-US;q=0.9,en;q=0.8',
161
  }
162
 
163
  payload = dumps(
164
+ separators=(',', ':'),
165
  obj={
166
+ 'queryName': 'CreateBotMain_poeBotCreate_Mutation',
167
+ 'variables': {
168
+ 'model': models[model],
169
+ 'handle': handle,
170
+ 'prompt': system_prompt,
171
+ 'isPromptPublic': True,
172
+ 'introduction': '',
173
+ 'description': description,
174
+ 'profilePictureUrl': 'https://qph.fs.quoracdn.net/main-qimg-24e0b480dcd946e1cc6728802c5128b6',
175
+ 'apiUrl': None,
176
+ 'apiKey': ''.join(choices(ascii_letters + digits, k=32)),
177
+ 'isApiBot': False,
178
+ 'hasLinkification': False,
179
+ 'hasMarkdownRendering': False,
180
+ 'hasSuggestedReplies': False,
181
+ 'isPrivateBot': False,
182
  },
183
+ 'query': 'mutation CreateBotMain_poeBotCreate_Mutation(\n $model: String!\n $handle: String!\n $prompt: String!\n $isPromptPublic: Boolean!\n $introduction: String!\n $description: String!\n $profilePictureUrl: String\n $apiUrl: String\n $apiKey: String\n $isApiBot: Boolean\n $hasLinkification: Boolean\n $hasMarkdownRendering: Boolean\n $hasSuggestedReplies: Boolean\n $isPrivateBot: Boolean\n) {\n poeBotCreate(model: $model, handle: $handle, promptPlaintext: $prompt, isPromptPublic: $isPromptPublic, introduction: $introduction, description: $description, profilePicture: $profilePictureUrl, apiUrl: $apiUrl, apiKey: $apiKey, isApiBot: $isApiBot, hasLinkification: $hasLinkification, hasMarkdownRendering: $hasMarkdownRendering, hasSuggestedReplies: $hasSuggestedReplies, isPrivateBot: $isPrivateBot) {\n status\n bot {\n id\n ...BotHeader_bot\n }\n }\n}\n\nfragment BotHeader_bot on Bot {\n displayName\n messageLimit {\n dailyLimit\n }\n ...BotImage_bot\n ...BotLink_bot\n ...IdAnnotation_node\n ...botHelpers_useViewerCanAccessPrivateBot\n ...botHelpers_useDeletion_bot\n}\n\nfragment BotImage_bot on Bot {\n displayName\n ...botHelpers_useDeletion_bot\n ...BotImage_useProfileImage_bot\n}\n\nfragment BotImage_useProfileImage_bot on Bot {\n image {\n __typename\n ... on LocalBotImage {\n localName\n }\n ... on UrlBotImage {\n url\n }\n }\n ...botHelpers_useDeletion_bot\n}\n\nfragment BotLink_bot on Bot {\n displayName\n}\n\nfragment IdAnnotation_node on Node {\n __isNode: __typename\n id\n}\n\nfragment botHelpers_useDeletion_bot on Bot {\n deletionState\n}\n\nfragment botHelpers_useViewerCanAccessPrivateBot on Bot {\n isPrivateBot\n viewerIsCreator\n}\n',
184
  },
185
  )
186
 
187
+ base_string = payload + client.headers['poe-formkey'] + 'WpuLMiXEKKE98j56k'
188
+ client.headers['poe-tag-id'] = md5(base_string.encode()).hexdigest()
189
 
190
+ response = client.post('https://poe.com/api/gql_POST', data=payload)
191
 
192
+ if 'success' not in response.text:
193
  raise Exception(
194
+ '''
195
  Bot creation Failed
196
  !! Important !!
197
  Bot creation was not enabled on this account
198
  please use: quora.Account.create with enable_bot_creation set to True
199
+ '''
200
  )
201
 
202
  return ModelResponse(response.json())
203
 
204
 
205
  class Account:
206
+ @staticmethod
207
  def create(
208
  proxy: Optional[str] = None,
209
  logging: bool = False,
210
  enable_bot_creation: bool = False,
211
  ):
212
+ client = TLS(client_identifier='chrome110')
213
+ client.proxies = {'http': f'http://{proxy}', 'https': f'http://{proxy}'} if proxy else None
 
 
214
 
215
  mail_client = Emailnator()
216
  mail_address = mail_client.get_mail()
217
 
218
  if logging:
219
+ print('email', mail_address)
220
 
221
  client.headers = {
222
+ 'authority': 'poe.com',
223
+ 'accept': '*/*',
224
+ 'accept-language': 'en,fr-FR;q=0.9,fr;q=0.8,es-ES;q=0.7,es;q=0.6,en-US;q=0.5,am;q=0.4,de;q=0.3',
225
+ 'content-type': 'application/json',
226
+ 'origin': 'https://poe.com',
227
+ 'poe-tag-id': 'null',
228
+ 'referer': 'https://poe.com/login',
229
+ 'sec-ch-ua': '"Chromium";v="112", "Google Chrome";v="112", "Not:A-Brand";v="99"',
230
+ 'sec-ch-ua-mobile': '?0',
231
+ 'sec-ch-ua-platform': '"macOS"',
232
+ 'sec-fetch-dest': 'empty',
233
+ 'sec-fetch-mode': 'cors',
234
+ 'sec-fetch-site': 'same-origin',
235
+ 'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36',
236
+ 'poe-formkey': extract_formkey(client.get('https://poe.com/login').text),
237
+ 'poe-tchannel': client.get('https://poe.com/api/settings').json()['tchannelData']['channel'],
 
 
238
  }
239
 
240
  token = reCaptchaV3(
241
+ 'https://www.recaptcha.net/recaptcha/enterprise/anchor?ar=1&k=6LflhEElAAAAAI_ewVwRWI9hsyV4mbZnYAslSvlG&co=aHR0cHM6Ly9wb2UuY29tOjQ0Mw..&hl=en&v=4PnKmGB9wRHh1i04o7YUICeI&size=invisible&cb=bi6ivxoskyal'
242
  )
243
  # token = solver.recaptcha(sitekey='6LflhEElAAAAAI_ewVwRWI9hsyV4mbZnYAslSvlG',
244
  # url = 'https://poe.com/login?redirect_url=%2F',
 
248
  # action = 'login',)['code']
249
 
250
  payload = dumps(
251
+ separators=(',', ':'),
252
  obj={
253
+ 'queryName': 'MainSignupLoginSection_sendVerificationCodeMutation_Mutation',
254
+ 'variables': {
255
+ 'emailAddress': mail_address,
256
+ 'phoneNumber': None,
257
+ 'recaptchaToken': token,
258
  },
259
+ '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',
260
  },
261
  )
262
 
263
+ base_string = payload + client.headers['poe-formkey'] + 'WpuLMiXEKKE98j56k'
264
+ client.headers['poe-tag-id'] = md5(base_string.encode()).hexdigest()
265
 
266
  print(dumps(client.headers, indent=4))
267
 
268
+ response = client.post('https://poe.com/api/gql_POST', data=payload)
269
 
270
+ if 'automated_request_detected' in response.text:
271
+ print('please try using a proxy / wait for fix')
272
 
273
+ if 'Bad Request' in response.text:
274
  if logging:
275
+ print('bad request, retrying...', response.json())
276
  quit()
277
 
278
  if logging:
279
+ print('send_code', response.json())
280
 
281
  mail_content = mail_client.get_message()
282
  mail_token = findall(r';">(\d{6,7})</div>', mail_content)[0]
283
 
284
  if logging:
285
+ print('code', mail_token)
286
 
287
  payload = dumps(
288
+ separators=(',', ':'),
289
  obj={
290
+ 'queryName': 'SignupOrLoginWithCodeSection_signupWithVerificationCodeMutation_Mutation',
291
+ 'variables': {
292
+ 'verificationCode': str(mail_token),
293
+ 'emailAddress': mail_address,
294
+ 'phoneNumber': None,
295
  },
296
+ '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',
297
  },
298
  )
299
 
300
+ base_string = payload + client.headers['poe-formkey'] + 'WpuLMiXEKKE98j56k'
301
+ client.headers['poe-tag-id'] = md5(base_string.encode()).hexdigest()
302
 
303
+ response = client.post('https://poe.com/api/gql_POST', data=payload)
304
  if logging:
305
+ print('verify_code', response.json())
306
 
307
  def get(self):
308
+ cookies = open(Path(__file__).resolve().parent / 'cookies.txt', 'r').read().splitlines()
 
 
 
 
309
  return choice(cookies)
310
 
311
 
312
  class StreamingCompletion:
313
+ @staticmethod
314
  def create(
315
+ model: str = 'gpt-4',
316
  custom_model: bool = None,
317
+ prompt: str = 'hello world',
318
+ token: str = '',
319
  ):
320
  _model = MODELS[model] if not custom_model else custom_model
321
 
 
324
  for chunk in client.send_message(_model, prompt):
325
  yield PoeResponse(
326
  {
327
+ 'id': chunk['messageId'],
328
+ 'object': 'text_completion',
329
+ 'created': chunk['creationTime'],
330
+ 'model': _model,
331
+ 'choices': [
332
  {
333
+ 'text': chunk['text_new'],
334
+ 'index': 0,
335
+ 'logprobs': None,
336
+ 'finish_reason': 'stop',
337
  }
338
  ],
339
+ 'usage': {
340
+ 'prompt_tokens': len(prompt),
341
+ 'completion_tokens': len(chunk['text_new']),
342
+ 'total_tokens': len(prompt) + len(chunk['text_new']),
343
  },
344
  }
345
  )
 
347
 
348
  class Completion:
349
  def create(
350
+ model: str = 'gpt-4',
351
  custom_model: str = None,
352
+ prompt: str = 'hello world',
353
+ token: str = '',
354
  ):
355
  models = {
356
+ 'sage': 'capybara',
357
+ 'gpt-4': 'beaver',
358
+ 'claude-v1.2': 'a2_2',
359
+ 'claude-instant-v1.0': 'a2',
360
+ 'gpt-3.5-turbo': 'chinchilla',
361
  }
362
 
363
  _model = models[model] if not custom_model else custom_model
 
369
 
370
  return PoeResponse(
371
  {
372
+ 'id': chunk['messageId'],
373
+ 'object': 'text_completion',
374
+ 'created': chunk['creationTime'],
375
+ 'model': _model,
376
+ 'choices': [
377
  {
378
+ 'text': chunk['text'],
379
+ 'index': 0,
380
+ 'logprobs': None,
381
+ 'finish_reason': 'stop',
382
  }
383
  ],
384
+ 'usage': {
385
+ 'prompt_tokens': len(prompt),
386
+ 'completion_tokens': len(chunk['text']),
387
+ 'total_tokens': len(prompt) + len(chunk['text']),
388
  },
389
  }
390
  )
391
 
392
 
393
  class Poe:
394
+ def __init__(
395
+ self,
396
+ model: str = 'ChatGPT',
397
+ driver: str = 'firefox',
398
+ download_driver: bool = False,
399
+ driver_path: Optional[str] = None,
400
+ cookie_path: str = './quora/cookie.json',
401
+ ):
402
  # validating the model
403
  if model and model not in MODELS:
404
+ raise RuntimeError('Sorry, the model you provided does not exist. Please check and try again.')
 
 
405
  self.model = MODELS[model]
406
+ self.cookie_path = cookie_path
407
+ self.cookie = self.__load_cookie(driver, download_driver, driver_path=driver_path)
408
  self.client = PoeClient(self.cookie)
409
 
410
+ def __load_cookie(self, driver: str, download_driver: bool, driver_path: Optional[str] = None) -> str:
411
+ if (cookie_file := Path(self.cookie_path)).exists():
412
  with cookie_file.open() as fp:
413
  cookie = json.load(fp)
414
+ if datetime.fromtimestamp(cookie['expiry']) < datetime.now():
415
+ cookie = self.__register_and_get_cookie(driver, driver_path=driver_path)
416
  else:
417
+ print('Loading the cookie from file')
418
  else:
419
+ cookie = self.__register_and_get_cookie(driver, driver_path=driver_path)
420
 
421
+ return unquote(cookie['value'])
422
 
423
+ def __register_and_get_cookie(self, driver: str, driver_path: Optional[str] = None) -> dict:
 
424
  mail_client = Emailnator()
425
  mail_address = mail_client.get_mail()
426
 
427
+ driver = self.__resolve_driver(driver, driver_path=driver_path)
 
 
 
 
 
 
 
 
428
  driver.get("https://www.poe.com")
429
 
430
  # clicking use email button
431
  driver.find_element(By.XPATH, '//button[contains(text(), "Use email")]').click()
432
 
433
+ email = WebDriverWait(driver, 30).until(EC.presence_of_element_located((By.XPATH, '//input[@type="email"]')))
 
 
434
  email.send_keys(mail_address)
435
  driver.find_element(By.XPATH, '//button[text()="Go"]').click()
436
 
 
441
  EC.presence_of_element_located((By.XPATH, '//input[@placeholder="Code"]'))
442
  )
443
  verification_code.send_keys(code)
444
+ verify_button = EC.presence_of_element_located((By.XPATH, '//button[text()="Verify"]'))
445
+ login_button = EC.presence_of_element_located((By.XPATH, '//button[text()="Log In"]'))
 
 
 
 
446
 
447
  WebDriverWait(driver, 30).until(EC.any_of(verify_button, login_button)).click()
448
 
449
+ cookie = driver.get_cookie('p-b')
450
 
451
+ with open(self.cookie_path, 'w') as fw:
452
  json.dump(cookie, fw)
453
 
454
  driver.close()
455
  return cookie
456
 
457
+ @classmethod
458
+ def __resolve_driver(cls, driver: str, driver_path: Optional[str] = None) -> Union[Firefox, Chrome]:
459
+ options = FirefoxOptions() if driver == 'firefox' else ChromeOptions()
460
+ options.add_argument('-headless')
461
+
462
+ if driver_path:
463
+ options.binary_location = driver_path
464
+ try:
465
+ return Firefox(options=options) if driver == 'firefox' else Chrome(options=options)
466
+ except Exception:
467
+ raise Exception(SELENIUM_WEB_DRIVER_ERROR_MSG)
468
+
469
  def chat(self, message: str, model: Optional[str] = None) -> str:
470
  if model and model not in MODELS:
471
+ raise RuntimeError('Sorry, the model you provided does not exist. Please check and try again.')
 
 
472
  model = MODELS[model] if model else self.model
473
  response = None
474
  for chunk in self.client.send_message(model, message):
475
+ response = chunk['text']
476
  return response
477
 
478
  def create_bot(
479
  self,
480
  name: str,
481
  /,
482
+ prompt: str = '',
483
+ base_model: str = 'ChatGPT',
484
+ description: str = '',
485
  ) -> None:
486
  if base_model not in MODELS:
487
+ raise RuntimeError('Sorry, the base_model you provided does not exist. Please check and try again.')
 
 
488
 
489
  response = self.client.create_bot(
490
  handle=name,
quora/api.py CHANGED
@@ -18,23 +18,21 @@
18
  # You should have received a copy of the GNU General Public License
19
  # along with this program. If not, see <https://www.gnu.org/licenses/>.
20
 
21
- import requests
22
- import re
23
  import json
24
- import random
25
  import logging
26
- import time
27
  import queue
 
 
28
  import threading
 
29
  import traceback
30
- import hashlib
31
- import string
32
- import random
33
- import requests.adapters
34
- import websocket
35
  from pathlib import Path
36
  from urllib.parse import urlparse
37
 
 
 
 
38
 
39
  parent_path = Path(__file__).resolve().parent
40
  queries_path = parent_path / "graphql"
@@ -66,7 +64,7 @@ def request_with_retries(method, *args, **kwargs):
66
  if r.status_code == 200:
67
  return r
68
  logger.warn(
69
- f"Server returned a status code of {r.status_code} while downloading {url}. Retrying ({i+1}/{attempts})..."
70
  )
71
 
72
  raise RuntimeError(f"Failed to download {url} too many times.")
@@ -81,9 +79,7 @@ class Client:
81
  def __init__(self, token, proxy=None):
82
  self.proxy = proxy
83
  self.session = requests.Session()
84
- self.adapter = requests.adapters.HTTPAdapter(
85
- pool_connections=100, pool_maxsize=100
86
- )
87
  self.session.mount("http://", self.adapter)
88
  self.session.mount("https://", self.adapter)
89
 
@@ -139,9 +135,7 @@ class Client:
139
  logger.info("Downloading next_data...")
140
 
141
  r = request_with_retries(self.session.get, self.home_url)
142
- json_regex = (
143
- r'<script id="__NEXT_DATA__" type="application\/json">(.+?)</script>'
144
- )
145
  json_text = re.search(json_regex, r.text).group(1)
146
  next_data = json.loads(json_text)
147
 
@@ -213,19 +207,14 @@ class Client:
213
  if channel is None:
214
  channel = self.channel
215
  query = f'?min_seq={channel["minSeq"]}&channel={channel["channel"]}&hash={channel["channelHash"]}'
216
- return (
217
- f'wss://{self.ws_domain}.tch.{channel["baseHost"]}/up/{channel["boxName"]}/updates'
218
- + query
219
- )
220
 
221
  def send_query(self, query_name, variables):
222
  for i in range(20):
223
  json_data = generate_payload(query_name, variables)
224
  payload = json.dumps(json_data, separators=(",", ":"))
225
 
226
- base_string = (
227
- payload + self.gql_headers["poe-formkey"] + "WpuLMiXEKKE98j56k"
228
- )
229
 
230
  headers = {
231
  "content-type": "application/json",
@@ -233,15 +222,11 @@ class Client:
233
  }
234
  headers = {**self.gql_headers, **headers}
235
 
236
- r = request_with_retries(
237
- self.session.post, self.gql_url, data=payload, headers=headers
238
- )
239
 
240
  data = r.json()
241
  if data["data"] == None:
242
- logger.warn(
243
- f'{query_name} returned an error: {data["errors"][0]["message"]} | Retrying ({i+1}/20)'
244
- )
245
  time.sleep(2)
246
  continue
247
 
@@ -304,9 +289,7 @@ class Client:
304
 
305
  def on_ws_close(self, ws, close_status_code, close_message):
306
  self.ws_connected = False
307
- logger.warn(
308
- f"Websocket closed with status {close_status_code}: {close_message}"
309
- )
310
 
311
  def on_ws_error(self, ws, error):
312
  self.disconnect_ws()
@@ -333,11 +316,7 @@ class Client:
333
  return
334
 
335
  # indicate that the response id is tied to the human message id
336
- elif (
337
- key != "pending"
338
- and value == None
339
- and message["state"] != "complete"
340
- ):
341
  self.active_messages[key] = message["messageId"]
342
  self.message_queues[key].put(message)
343
  return
@@ -381,9 +360,7 @@ class Client:
381
  human_message = message_data["data"]["messageEdgeCreate"]["message"]
382
  human_message_id = human_message["node"]["messageId"]
383
  except TypeError:
384
- raise RuntimeError(
385
- f"An unknown error occurred. Raw response data: {message_data}"
386
- )
387
 
388
  # indicate that the current message is waiting for a response
389
  self.active_messages[human_message_id] = None
@@ -418,9 +395,7 @@ class Client:
418
 
419
  def send_chat_break(self, chatbot):
420
  logger.info(f"Sending chat break to {chatbot}")
421
- result = self.send_query(
422
- "AddMessageBreakMutation", {"chatId": self.bots[chatbot]["chatId"]}
423
- )
424
  return result["data"]["messageBreakCreate"]["message"]
425
 
426
  def get_message_history(self, chatbot, count=25, cursor=None):
@@ -437,15 +412,11 @@ class Client:
437
 
438
  cursor = str(cursor)
439
  if count > 50:
440
- messages = (
441
- self.get_message_history(chatbot, count=50, cursor=cursor) + messages
442
- )
443
  while count > 0:
444
  count -= 50
445
  new_cursor = messages[0]["cursor"]
446
- new_messages = self.get_message_history(
447
- chatbot, min(50, count), cursor=new_cursor
448
- )
449
  messages = new_messages + messages
450
  return messages
451
  elif count <= 0:
@@ -523,9 +494,7 @@ class Client:
523
 
524
  data = result["data"]["poeBotCreate"]
525
  if data["status"] != "success":
526
- raise RuntimeError(
527
- f"Poe returned an error while trying to create a bot: {data['status']}"
528
- )
529
  self.get_bots()
530
  return data
531
 
@@ -568,9 +537,7 @@ class Client:
568
 
569
  data = result["data"]["poeBotEdit"]
570
  if data["status"] != "success":
571
- raise RuntimeError(
572
- f"Poe returned an error while trying to edit a bot: {data['status']}"
573
- )
574
  self.get_bots()
575
  return data
576
 
 
18
  # You should have received a copy of the GNU General Public License
19
  # along with this program. If not, see <https://www.gnu.org/licenses/>.
20
 
21
+ import hashlib
 
22
  import json
 
23
  import logging
 
24
  import queue
25
+ import random
26
+ import re
27
  import threading
28
+ import time
29
  import traceback
 
 
 
 
 
30
  from pathlib import Path
31
  from urllib.parse import urlparse
32
 
33
+ import requests
34
+ import requests.adapters
35
+ import websocket
36
 
37
  parent_path = Path(__file__).resolve().parent
38
  queries_path = parent_path / "graphql"
 
64
  if r.status_code == 200:
65
  return r
66
  logger.warn(
67
+ f"Server returned a status code of {r.status_code} while downloading {url}. Retrying ({i + 1}/{attempts})..."
68
  )
69
 
70
  raise RuntimeError(f"Failed to download {url} too many times.")
 
79
  def __init__(self, token, proxy=None):
80
  self.proxy = proxy
81
  self.session = requests.Session()
82
+ self.adapter = requests.adapters.HTTPAdapter(pool_connections=100, pool_maxsize=100)
 
 
83
  self.session.mount("http://", self.adapter)
84
  self.session.mount("https://", self.adapter)
85
 
 
135
  logger.info("Downloading next_data...")
136
 
137
  r = request_with_retries(self.session.get, self.home_url)
138
+ json_regex = r'<script id="__NEXT_DATA__" type="application\/json">(.+?)</script>'
 
 
139
  json_text = re.search(json_regex, r.text).group(1)
140
  next_data = json.loads(json_text)
141
 
 
207
  if channel is None:
208
  channel = self.channel
209
  query = f'?min_seq={channel["minSeq"]}&channel={channel["channel"]}&hash={channel["channelHash"]}'
210
+ return f'wss://{self.ws_domain}.tch.{channel["baseHost"]}/up/{channel["boxName"]}/updates' + query
 
 
 
211
 
212
  def send_query(self, query_name, variables):
213
  for i in range(20):
214
  json_data = generate_payload(query_name, variables)
215
  payload = json.dumps(json_data, separators=(",", ":"))
216
 
217
+ base_string = payload + self.gql_headers["poe-formkey"] + "WpuLMiXEKKE98j56k"
 
 
218
 
219
  headers = {
220
  "content-type": "application/json",
 
222
  }
223
  headers = {**self.gql_headers, **headers}
224
 
225
+ r = request_with_retries(self.session.post, self.gql_url, data=payload, headers=headers)
 
 
226
 
227
  data = r.json()
228
  if data["data"] == None:
229
+ logger.warn(f'{query_name} returned an error: {data["errors"][0]["message"]} | Retrying ({i + 1}/20)')
 
 
230
  time.sleep(2)
231
  continue
232
 
 
289
 
290
  def on_ws_close(self, ws, close_status_code, close_message):
291
  self.ws_connected = False
292
+ logger.warn(f"Websocket closed with status {close_status_code}: {close_message}")
 
 
293
 
294
  def on_ws_error(self, ws, error):
295
  self.disconnect_ws()
 
316
  return
317
 
318
  # indicate that the response id is tied to the human message id
319
+ elif key != "pending" and value == None and message["state"] != "complete":
 
 
 
 
320
  self.active_messages[key] = message["messageId"]
321
  self.message_queues[key].put(message)
322
  return
 
360
  human_message = message_data["data"]["messageEdgeCreate"]["message"]
361
  human_message_id = human_message["node"]["messageId"]
362
  except TypeError:
363
+ raise RuntimeError(f"An unknown error occurred. Raw response data: {message_data}")
 
 
364
 
365
  # indicate that the current message is waiting for a response
366
  self.active_messages[human_message_id] = None
 
395
 
396
  def send_chat_break(self, chatbot):
397
  logger.info(f"Sending chat break to {chatbot}")
398
+ result = self.send_query("AddMessageBreakMutation", {"chatId": self.bots[chatbot]["chatId"]})
 
 
399
  return result["data"]["messageBreakCreate"]["message"]
400
 
401
  def get_message_history(self, chatbot, count=25, cursor=None):
 
412
 
413
  cursor = str(cursor)
414
  if count > 50:
415
+ messages = self.get_message_history(chatbot, count=50, cursor=cursor) + messages
 
 
416
  while count > 0:
417
  count -= 50
418
  new_cursor = messages[0]["cursor"]
419
+ new_messages = self.get_message_history(chatbot, min(50, count), cursor=new_cursor)
 
 
420
  messages = new_messages + messages
421
  return messages
422
  elif count <= 0:
 
494
 
495
  data = result["data"]["poeBotCreate"]
496
  if data["status"] != "success":
497
+ raise RuntimeError(f"Poe returned an error while trying to create a bot: {data['status']}")
 
 
498
  self.get_bots()
499
  return data
500
 
 
537
 
538
  data = result["data"]["poeBotEdit"]
539
  if data["status"] != "success":
540
+ raise RuntimeError(f"Poe returned an error while trying to edit a bot: {data['status']}")
 
 
541
  self.get_bots()
542
  return data
543