Phind.py 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126
  1. from __future__ import annotations
  2. from urllib import parse
  3. from datetime import datetime
  4. from ..typing import AsyncResult, Messages
  5. from .base_provider import AsyncGeneratorProvider
  6. from ..requests import StreamSession
  7. class Phind(AsyncGeneratorProvider):
  8. url = "https://www.phind.com"
  9. working = True
  10. supports_stream = True
  11. supports_message_history = True
  12. @classmethod
  13. async def create_async_generator(
  14. cls,
  15. model: str,
  16. messages: Messages,
  17. proxy: str = None,
  18. timeout: int = 120,
  19. creative_mode: bool = False,
  20. **kwargs
  21. ) -> AsyncResult:
  22. headers = {
  23. "Accept": "*/*",
  24. "Origin": cls.url,
  25. "Referer": f"{cls.url}/search",
  26. "Sec-Fetch-Dest": "empty",
  27. "Sec-Fetch-Mode": "cors",
  28. "Sec-Fetch-Site": "same-origin",
  29. }
  30. async with StreamSession(
  31. impersonate="chrome110",
  32. proxies={"https": proxy},
  33. timeout=timeout
  34. ) as session:
  35. prompt = messages[-1]["content"]
  36. data = {
  37. "question": prompt,
  38. "question_history": [
  39. message["content"] for message in messages[:-1] if message["role"] == "user"
  40. ],
  41. "answer_history": [
  42. message["content"] for message in messages if message["role"] == "assistant"
  43. ],
  44. "webResults": [],
  45. "options": {
  46. "date": datetime.now().strftime("%d.%m.%Y"),
  47. "language": "en-US",
  48. "detailed": True,
  49. "anonUserId": "",
  50. "answerModel": "GPT-4" if model.startswith("gpt-4") else "Phind Model",
  51. "creativeMode": creative_mode,
  52. "customLinks": []
  53. },
  54. "context": "\n".join([message["content"] for message in messages if message["role"] == "system"]),
  55. }
  56. data["challenge"] = generate_challenge(data)
  57. async with session.post(f"https://https.api.phind.com/infer/", headers=headers, json=data) as response:
  58. new_line = False
  59. async for line in response.iter_lines():
  60. if line.startswith(b"data: "):
  61. chunk = line[6:]
  62. if chunk.startswith(b'<PHIND_DONE/>'):
  63. break
  64. if chunk.startswith(b'<PHIND_BACKEND_ERROR>'):
  65. raise RuntimeError(f"Response: {chunk.decode()}")
  66. if chunk.startswith(b'<PHIND_WEBRESULTS>') or chunk.startswith(b'<PHIND_FOLLOWUP>'):
  67. pass
  68. elif chunk.startswith(b"<PHIND_METADATA>") or chunk.startswith(b"<PHIND_INDICATOR>"):
  69. pass
  70. elif chunk.startswith(b"<PHIND_SPAN_BEGIN>") or chunk.startswith(b"<PHIND_SPAN_END>"):
  71. pass
  72. elif chunk:
  73. yield chunk.decode()
  74. elif new_line:
  75. yield "\n"
  76. new_line = False
  77. else:
  78. new_line = True
  79. def deterministic_stringify(obj):
  80. def handle_value(value):
  81. if isinstance(value, (dict, list)):
  82. if isinstance(value, list):
  83. return '[' + ','.join(sorted(map(handle_value, value))) + ']'
  84. else: # It's a dict
  85. return '{' + deterministic_stringify(value) + '}'
  86. elif isinstance(value, bool):
  87. return 'true' if value else 'false'
  88. elif isinstance(value, (int, float)):
  89. return format(value, '.8f').rstrip('0').rstrip('.')
  90. elif isinstance(value, str):
  91. return f'"{value}"'
  92. else:
  93. return 'null'
  94. items = sorted(obj.items(), key=lambda x: x[0])
  95. return ','.join([f'{k}:{handle_value(v)}' for k, v in items if handle_value(v) is not None])
  96. def simple_hash(s):
  97. d = 0
  98. for char in s:
  99. if len(char) > 1 or ord(char) >= 256:
  100. continue
  101. d = ((d << 5) - d + ord(char[0])) & 0xFFFFFFFF
  102. if d > 0x7FFFFFFF: # 2147483647
  103. d -= 0x100000000 # Subtract 2**32
  104. return d
  105. def generate_challenge(obj):
  106. deterministic_str = deterministic_stringify(obj)
  107. encoded_str = parse.quote(deterministic_str, safe='')
  108. c = simple_hash(encoded_str)
  109. a = (9301 * c + 49297)
  110. b = 233280
  111. # If negativ, we need a special logic
  112. if a < 0:
  113. return ((a%b)-b)/b
  114. else:
  115. return a%b/b