t.me/xtekky commited on
Commit
16e85ff
2 Parent(s): aacd2cf 4b9875b

Merge pull request #173 from sudouser777/feature/code_refactor

Browse files
Files changed (3) hide show
  1. quora/README.md +1 -1
  2. quora/__init__.py +230 -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', driver_path='path_of_driver')
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,48 @@ 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 +71,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 +107,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 +245,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 +321,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 +344,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 +366,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 +438,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
+ SELENIUM_WEB_DRIVER_ERROR_MSG = b'''The error message you are receiving is due to the `geckodriver` executable not
25
+ being found in your system\'s PATH. To resolve this issue, you need to download the geckodriver and add its location
26
+ to your system\'s PATH.\n\nHere are the steps to resolve the issue:\n\n1. Download the geckodriver for your platform
27
+ (Windows, macOS, or Linux) from the following link: https://github.com/mozilla/geckodriver/releases\n\n2. Extract the
28
+ downloaded archive and locate the geckodriver executable.\n\n3. Add the geckodriver executable to your system\'s
29
+ PATH.\n\nFor macOS and Linux:\n\n- Open a terminal window.\n- Move the geckodriver executable to a directory that is
30
+ already in your PATH, or create a new directory and add it to your PATH:\n\n```bash\n# Example: Move geckodriver to
31
+ /usr/local/bin\nmv /path/to/your/geckodriver /usr/local/bin\n```\n\n- If you created a new directory, add it to your
32
+ PATH:\n\n```bash\n# Example: Add a new directory to PATH\nexport PATH=$PATH:/path/to/your/directory\n```\n\nFor
33
+ Windows:\n\n- Right-click on "My Computer" or "This PC" and select "Properties".\n- Click on "Advanced system
34
+ settings".\n- Click on the "Environment Variables" button.\n- In the "System variables" section, find the "Path"
35
+ variable, select it, and click "Edit".\n- Click "New" and add the path to the directory containing the geckodriver
36
+ executable.\n\nAfter adding the geckodriver to your PATH, restart your terminal or command prompt and try running
37
+ your script again. The error should be resolved.'''
38
+
39
  # from twocaptcha import TwoCaptcha
40
  # solver = TwoCaptcha('72747bf24a9d89b4dcc1b24875efd358')
41
 
42
  MODELS = {
43
+ 'Sage': 'capybara',
44
+ 'GPT-4': 'beaver',
45
+ 'Claude+': 'a2_2',
46
+ 'Claude-instant': 'a2',
47
+ 'ChatGPT': 'chinchilla',
48
+ 'Dragonfly': 'nutria',
49
+ 'NeevaAI': 'hutia',
50
  }
51
 
52
 
53
  def extract_formkey(html):
54
+ script_regex = r'<script>if\(.+\)throw new Error;(.+)</script>'
55
  script_text = search(script_regex, html).group(1)
56
  key_regex = r'var .="([0-9a-f]+)",'
57
  key_text = search(key_regex, script_text).group(1)
58
+ cipher_regex = r'.\[(\d+)\]=.\[(\d+)\]'
59
  cipher_pairs = findall(cipher_regex, script_text)
60
 
61
+ formkey_list = [''] * len(cipher_pairs)
62
  for pair in cipher_pairs:
63
  formkey_index, key_index = map(int, pair)
64
  formkey_list[formkey_index] = key_text[key_index]
65
+ formkey = ''.join(formkey_list)
66
 
67
  return formkey
68
 
 
71
  class Completion:
72
  class Choices:
73
  def __init__(self, choice: dict) -> None:
74
+ self.text = choice['text']
75
  self.content = self.text.encode()
76
+ self.index = choice['index']
77
+ self.logprobs = choice['logprobs']
78
+ self.finish_reason = choice['finish_reason']
79
 
80
  def __repr__(self) -> str:
81
+ 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>'''
82
 
83
  def __init__(self, choices: dict) -> None:
84
  self.choices = [self.Choices(choice) for choice in choices]
85
 
86
  class Usage:
87
  def __init__(self, usage_dict: dict) -> None:
88
+ self.prompt_tokens = usage_dict['prompt_tokens']
89
+ self.completion_tokens = usage_dict['completion_tokens']
90
+ self.total_tokens = usage_dict['total_tokens']
91
 
92
  def __repr__(self):
93
+ 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>'''
94
 
95
  def __init__(self, response_dict: dict) -> None:
96
  self.response_dict = response_dict
97
+ self.id = response_dict['id']
98
+ self.object = response_dict['object']
99
+ self.created = response_dict['created']
100
+ self.model = response_dict['model']
101
+ self.completion = self.Completion(response_dict['choices'])
102
+ self.usage = self.Usage(response_dict['usage'])
103
 
104
  def json(self) -> dict:
105
  return self.response_dict
 
107
 
108
  class ModelResponse:
109
  def __init__(self, json_response: dict) -> None:
110
+ self.id = json_response['data']['poeBotCreate']['bot']['id']
111
+ self.name = json_response['data']['poeBotCreate']['bot']['displayName']
112
+ self.limit = json_response['data']['poeBotCreate']['bot']['messageLimit']['dailyLimit']
113
+ self.deleted = json_response['data']['poeBotCreate']['bot']['deletionState']
 
 
114
 
115
 
116
  class Model:
117
+ @staticmethod
118
  def create(
119
  token: str,
120
+ model: str = 'gpt-3.5-turbo', # claude-instant
121
+ system_prompt: str = 'You are ChatGPT a large language model developed by Openai. Answer as consisely as possible',
122
+ description: str = 'gpt-3.5 language model from openai, skidded by poe.com',
123
  handle: str = None,
124
  ) -> ModelResponse:
125
  models = {
126
+ 'gpt-3.5-turbo': 'chinchilla',
127
+ 'claude-instant-v1.0': 'a2',
128
+ 'gpt-4': 'beaver',
129
  }
130
 
131
  if not handle:
132
+ handle = f'gptx{randint(1111111, 9999999)}'
133
 
134
  client = Session()
135
+ client.cookies['p-b'] = token
136
 
137
+ formkey = extract_formkey(client.get('https://poe.com').text)
138
+ settings = client.get('https://poe.com/api/settings').json()
139
 
140
  client.headers = {
141
+ 'host': 'poe.com',
142
+ 'origin': 'https://poe.com',
143
+ 'referer': 'https://poe.com/',
144
+ 'poe-formkey': formkey,
145
+ 'poe-tchannel': settings['tchannelData']['channel'],
146
+ 'user-agent': UserAgent().random,
147
+ 'connection': 'keep-alive',
148
+ 'sec-ch-ua': '"Chromium";v="112", "Google Chrome";v="112", "Not:A-Brand";v="99"',
149
+ 'sec-ch-ua-mobile': '?0',
150
+ 'sec-ch-ua-platform': '"macOS"',
151
+ 'content-type': 'application/json',
152
+ 'sec-fetch-site': 'same-origin',
153
+ 'sec-fetch-mode': 'cors',
154
+ 'sec-fetch-dest': 'empty',
155
+ 'accept': '*/*',
156
+ 'accept-encoding': 'gzip, deflate, br',
157
+ 'accept-language': 'en-GB,en-US;q=0.9,en;q=0.8',
158
  }
159
 
160
  payload = dumps(
161
+ separators=(',', ':'),
162
  obj={
163
+ 'queryName': 'CreateBotMain_poeBotCreate_Mutation',
164
+ 'variables': {
165
+ 'model': models[model],
166
+ 'handle': handle,
167
+ 'prompt': system_prompt,
168
+ 'isPromptPublic': True,
169
+ 'introduction': '',
170
+ 'description': description,
171
+ 'profilePictureUrl': 'https://qph.fs.quoracdn.net/main-qimg-24e0b480dcd946e1cc6728802c5128b6',
172
+ 'apiUrl': None,
173
+ 'apiKey': ''.join(choices(ascii_letters + digits, k=32)),
174
+ 'isApiBot': False,
175
+ 'hasLinkification': False,
176
+ 'hasMarkdownRendering': False,
177
+ 'hasSuggestedReplies': False,
178
+ 'isPrivateBot': False,
179
  },
180
+ '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',
181
  },
182
  )
183
 
184
+ base_string = payload + client.headers['poe-formkey'] + 'WpuLMiXEKKE98j56k'
185
+ client.headers['poe-tag-id'] = md5(base_string.encode()).hexdigest()
186
 
187
+ response = client.post('https://poe.com/api/gql_POST', data=payload)
188
 
189
+ if 'success' not in response.text:
190
  raise Exception(
191
+ '''
192
  Bot creation Failed
193
  !! Important !!
194
  Bot creation was not enabled on this account
195
  please use: quora.Account.create with enable_bot_creation set to True
196
+ '''
197
  )
198
 
199
  return ModelResponse(response.json())
200
 
201
 
202
  class Account:
203
+ @staticmethod
204
  def create(
205
  proxy: Optional[str] = None,
206
  logging: bool = False,
207
  enable_bot_creation: bool = False,
208
  ):
209
+ client = TLS(client_identifier='chrome110')
210
+ client.proxies = {'http': f'http://{proxy}', 'https': f'http://{proxy}'} if proxy else None
 
 
211
 
212
  mail_client = Emailnator()
213
  mail_address = mail_client.get_mail()
214
 
215
  if logging:
216
+ print('email', mail_address)
217
 
218
  client.headers = {
219
+ 'authority': 'poe.com',
220
+ 'accept': '*/*',
221
+ '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',
222
+ 'content-type': 'application/json',
223
+ 'origin': 'https://poe.com',
224
+ 'poe-tag-id': 'null',
225
+ 'referer': 'https://poe.com/login',
226
+ 'sec-ch-ua': '"Chromium";v="112", "Google Chrome";v="112", "Not:A-Brand";v="99"',
227
+ 'sec-ch-ua-mobile': '?0',
228
+ 'sec-ch-ua-platform': '"macOS"',
229
+ 'sec-fetch-dest': 'empty',
230
+ 'sec-fetch-mode': 'cors',
231
+ 'sec-fetch-site': 'same-origin',
232
+ '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',
233
+ 'poe-formkey': extract_formkey(client.get('https://poe.com/login').text),
234
+ 'poe-tchannel': client.get('https://poe.com/api/settings').json()['tchannelData']['channel'],
 
 
235
  }
236
 
237
  token = reCaptchaV3(
238
+ 'https://www.recaptcha.net/recaptcha/enterprise/anchor?ar=1&k=6LflhEElAAAAAI_ewVwRWI9hsyV4mbZnYAslSvlG&co=aHR0cHM6Ly9wb2UuY29tOjQ0Mw..&hl=en&v=4PnKmGB9wRHh1i04o7YUICeI&size=invisible&cb=bi6ivxoskyal'
239
  )
240
  # token = solver.recaptcha(sitekey='6LflhEElAAAAAI_ewVwRWI9hsyV4mbZnYAslSvlG',
241
  # url = 'https://poe.com/login?redirect_url=%2F',
 
245
  # action = 'login',)['code']
246
 
247
  payload = dumps(
248
+ separators=(',', ':'),
249
  obj={
250
+ 'queryName': 'MainSignupLoginSection_sendVerificationCodeMutation_Mutation',
251
+ 'variables': {
252
+ 'emailAddress': mail_address,
253
+ 'phoneNumber': None,
254
+ 'recaptchaToken': token,
255
  },
256
+ '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',
257
  },
258
  )
259
 
260
+ base_string = payload + client.headers['poe-formkey'] + 'WpuLMiXEKKE98j56k'
261
+ client.headers['poe-tag-id'] = md5(base_string.encode()).hexdigest()
262
 
263
  print(dumps(client.headers, indent=4))
264
 
265
+ response = client.post('https://poe.com/api/gql_POST', data=payload)
266
 
267
+ if 'automated_request_detected' in response.text:
268
+ print('please try using a proxy / wait for fix')
269
 
270
+ if 'Bad Request' in response.text:
271
  if logging:
272
+ print('bad request, retrying...', response.json())
273
  quit()
274
 
275
  if logging:
276
+ print('send_code', response.json())
277
 
278
  mail_content = mail_client.get_message()
279
  mail_token = findall(r';">(\d{6,7})</div>', mail_content)[0]
280
 
281
  if logging:
282
+ print('code', mail_token)
283
 
284
  payload = dumps(
285
+ separators=(',', ':'),
286
  obj={
287
+ 'queryName': 'SignupOrLoginWithCodeSection_signupWithVerificationCodeMutation_Mutation',
288
+ 'variables': {
289
+ 'verificationCode': str(mail_token),
290
+ 'emailAddress': mail_address,
291
+ 'phoneNumber': None,
292
  },
293
+ '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',
294
  },
295
  )
296
 
297
+ base_string = payload + client.headers['poe-formkey'] + 'WpuLMiXEKKE98j56k'
298
+ client.headers['poe-tag-id'] = md5(base_string.encode()).hexdigest()
299
 
300
+ response = client.post('https://poe.com/api/gql_POST', data=payload)
301
  if logging:
302
+ print('verify_code', response.json())
303
 
304
  def get(self):
305
+ cookies = open(Path(__file__).resolve().parent / 'cookies.txt', 'r').read().splitlines()
 
 
 
 
306
  return choice(cookies)
307
 
308
 
309
  class StreamingCompletion:
310
+ @staticmethod
311
  def create(
312
+ model: str = 'gpt-4',
313
  custom_model: bool = None,
314
+ prompt: str = 'hello world',
315
+ token: str = '',
316
  ):
317
  _model = MODELS[model] if not custom_model else custom_model
318
 
 
321
  for chunk in client.send_message(_model, prompt):
322
  yield PoeResponse(
323
  {
324
+ 'id': chunk['messageId'],
325
+ 'object': 'text_completion',
326
+ 'created': chunk['creationTime'],
327
+ 'model': _model,
328
+ 'choices': [
329
  {
330
+ 'text': chunk['text_new'],
331
+ 'index': 0,
332
+ 'logprobs': None,
333
+ 'finish_reason': 'stop',
334
  }
335
  ],
336
+ 'usage': {
337
+ 'prompt_tokens': len(prompt),
338
+ 'completion_tokens': len(chunk['text_new']),
339
+ 'total_tokens': len(prompt) + len(chunk['text_new']),
340
  },
341
  }
342
  )
 
344
 
345
  class Completion:
346
  def create(
347
+ model: str = 'gpt-4',
348
  custom_model: str = None,
349
+ prompt: str = 'hello world',
350
+ token: str = '',
351
  ):
352
  models = {
353
+ 'sage': 'capybara',
354
+ 'gpt-4': 'beaver',
355
+ 'claude-v1.2': 'a2_2',
356
+ 'claude-instant-v1.0': 'a2',
357
+ 'gpt-3.5-turbo': 'chinchilla',
358
  }
359
 
360
  _model = models[model] if not custom_model else custom_model
 
366
 
367
  return PoeResponse(
368
  {
369
+ 'id': chunk['messageId'],
370
+ 'object': 'text_completion',
371
+ 'created': chunk['creationTime'],
372
+ 'model': _model,
373
+ 'choices': [
374
  {
375
+ 'text': chunk['text'],
376
+ 'index': 0,
377
+ 'logprobs': None,
378
+ 'finish_reason': 'stop',
379
  }
380
  ],
381
+ 'usage': {
382
+ 'prompt_tokens': len(prompt),
383
+ 'completion_tokens': len(chunk['text']),
384
+ 'total_tokens': len(prompt) + len(chunk['text']),
385
  },
386
  }
387
  )
388
 
389
 
390
  class Poe:
391
+ def __init__(
392
+ self,
393
+ model: str = 'ChatGPT',
394
+ driver: str = 'firefox',
395
+ download_driver: bool = False,
396
+ driver_path: Optional[str] = None,
397
+ cookie_path: str = './quora/cookie.json',
398
+ ):
399
  # validating the model
400
  if model and model not in MODELS:
401
+ raise RuntimeError('Sorry, the model you provided does not exist. Please check and try again.')
 
 
402
  self.model = MODELS[model]
403
+ self.cookie_path = cookie_path
404
+ self.cookie = self.__load_cookie(driver, download_driver, driver_path=driver_path)
405
  self.client = PoeClient(self.cookie)
406
 
407
+ def __load_cookie(self, driver: str, download_driver: bool, driver_path: Optional[str] = None) -> str:
408
+ if (cookie_file := Path(self.cookie_path)).exists():
409
  with cookie_file.open() as fp:
410
  cookie = json.load(fp)
411
+ if datetime.fromtimestamp(cookie['expiry']) < datetime.now():
412
+ cookie = self.__register_and_get_cookie(driver, driver_path=driver_path)
413
  else:
414
+ print('Loading the cookie from file')
415
  else:
416
+ cookie = self.__register_and_get_cookie(driver, driver_path=driver_path)
417
 
418
+ return unquote(cookie['value'])
419
 
420
+ def __register_and_get_cookie(self, driver: str, driver_path: Optional[str] = None) -> dict:
 
421
  mail_client = Emailnator()
422
  mail_address = mail_client.get_mail()
423
 
424
+ driver = self.__resolve_driver(driver, driver_path=driver_path)
 
 
 
 
 
 
 
 
425
  driver.get("https://www.poe.com")
426
 
427
  # clicking use email button
428
  driver.find_element(By.XPATH, '//button[contains(text(), "Use email")]').click()
429
 
430
+ email = WebDriverWait(driver, 30).until(EC.presence_of_element_located((By.XPATH, '//input[@type="email"]')))
 
 
431
  email.send_keys(mail_address)
432
  driver.find_element(By.XPATH, '//button[text()="Go"]').click()
433
 
 
438
  EC.presence_of_element_located((By.XPATH, '//input[@placeholder="Code"]'))
439
  )
440
  verification_code.send_keys(code)
441
+ verify_button = EC.presence_of_element_located((By.XPATH, '//button[text()="Verify"]'))
442
+ login_button = EC.presence_of_element_located((By.XPATH, '//button[text()="Log In"]'))
 
 
 
 
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(self.cookie_path, 'w') as fw:
449
  json.dump(cookie, fw)
450
 
451
  driver.close()
452
  return cookie
453
 
454
+ @classmethod
455
+ def __resolve_driver(cls, driver: str, driver_path: Optional[str] = None) -> Union[Firefox, Chrome]:
456
+ options = FirefoxOptions() if driver == 'firefox' else ChromeOptions()
457
+ options.add_argument('-headless')
458
+
459
+ if driver_path:
460
+ options.binary_location = driver_path
461
+ try:
462
+ return Firefox(options=options) if driver == 'firefox' else Chrome(options=options)
463
+ except Exception:
464
+ raise Exception(SELENIUM_WEB_DRIVER_ERROR_MSG)
465
+
466
  def chat(self, message: str, model: Optional[str] = None) -> str:
467
  if model and model not in MODELS:
468
+ raise RuntimeError('Sorry, the model you provided does not exist. Please check and try again.')
 
 
469
  model = MODELS[model] if model else self.model
470
  response = None
471
  for chunk in self.client.send_message(model, message):
472
+ response = chunk['text']
473
  return response
474
 
475
  def create_bot(
476
  self,
477
  name: str,
478
  /,
479
+ prompt: str = '',
480
+ base_model: str = 'ChatGPT',
481
+ description: str = '',
482
  ) -> None:
483
  if base_model not in MODELS:
484
+ raise RuntimeError('Sorry, the base_model you provided does not exist. Please check and try again.')
 
 
485
 
486
  response = self.client.create_bot(
487
  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