summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorH Lohaus <hlohaus@users.noreply.github.com>2024-03-26 21:50:35 +0100
committerGitHub <noreply@github.com>2024-03-26 21:50:35 +0100
commitdd08125bb4740ab5dc1116868ff237ce51ae3d5e (patch)
treeed6339c66505409e7dced6d3499814b00446ff12
parentMerge pull request #1754 from hlohaus/goo (diff)
parentFix load .har files, add hardir to docker, add docs (diff)
downloadgpt4free-dd08125bb4740ab5dc1116868ff237ce51ae3d5e.tar
gpt4free-dd08125bb4740ab5dc1116868ff237ce51ae3d5e.tar.gz
gpt4free-dd08125bb4740ab5dc1116868ff237ce51ae3d5e.tar.bz2
gpt4free-dd08125bb4740ab5dc1116868ff237ce51ae3d5e.tar.lz
gpt4free-dd08125bb4740ab5dc1116868ff237ce51ae3d5e.tar.xz
gpt4free-dd08125bb4740ab5dc1116868ff237ce51ae3d5e.tar.zst
gpt4free-dd08125bb4740ab5dc1116868ff237ce51ae3d5e.zip
-rw-r--r--.gitignore3
-rw-r--r--README.md35
-rw-r--r--docker/Dockerfile9
-rw-r--r--g4f/Provider/bing/conversation.py5
-rw-r--r--g4f/Provider/needs_auth/OpenaiChat.py50
-rw-r--r--g4f/Provider/openai/har_file.py36
-rw-r--r--g4f/gui/server/api.py8
-rw-r--r--g4f/providers/conversation.py2
-rw-r--r--hardir/.gitkeep0
9 files changed, 86 insertions, 62 deletions
diff --git a/.gitignore b/.gitignore
index 71d27a86..a4c228ac 100644
--- a/.gitignore
+++ b/.gitignore
@@ -53,4 +53,5 @@ info.txt
local.py
*.gguf
image.py
-.buildozer \ No newline at end of file
+.buildozer
+hardir \ No newline at end of file
diff --git a/README.md b/README.md
index b0d96918..153922ec 100644
--- a/README.md
+++ b/README.md
@@ -89,7 +89,7 @@ As per the survey, here is a list of improvements to come
```sh
docker pull hlohaus789/g4f
-docker run -p 8080:8080 -p 1337:1337 -p 7900:7900 --shm-size="2g" hlohaus789/g4f:latest
+docker run -p 8080:8080 -p 1337:1337 -p 7900:7900 --shm-size="2g" -v ${PWD}/hardir:/app/hardir hlohaus789/g4f:latest
```
3. Open the included client on: [http://localhost:8080/chat/](http://localhost:8080/chat/)
or set the API base in your client to: [http://localhost:1337/v1](http://localhost:1337/v1)
@@ -218,9 +218,12 @@ See: [/docs/interference](/docs/interference.md)
### Configuration
-##### Cookies / Access Token
+#### Cookies
-For generating images with Bing and for the OpenAI Chat you need cookies or a token from your browser session. From Bing you need the "_U" cookie and from OpenAI you need the "access_token". You can pass the cookies / the access token in the create function or you use the `set_cookies` setter before you run G4F:
+You need cookies for BingCreateImages and the Gemini Provider.
+From Bing you need the "_U" cookie and from Gemini you need the "__Secure-1PSID" cookie.
+Sometimes you doesn't need the "__Secure-1PSID" cookie, but some other auth cookies.
+You can pass the cookies in the create function or you use the `set_cookies` setter before you run G4F:
```python
from g4f.cookies import set_cookies
@@ -228,20 +231,32 @@ from g4f.cookies import set_cookies
set_cookies(".bing.com", {
"_U": "cookie value"
})
-set_cookies("chat.openai.com", {
- "access_token": "token value"
-})
set_cookies(".google.com", {
"__Secure-1PSID": "cookie value"
})
-
...
```
-Alternatively, G4F reads the cookies with `browser_cookie3` from your browser
-or it starts a browser instance with selenium `webdriver` for logging in.
+#### .HAR File for OpenaiChat Provider
+
+##### Generating a .HAR File
+
+To utilize the OpenaiChat provider, a .har file is required from https://chat.openai.com/. Follow the steps below to create a valid .har file:
+
+1. Navigate to https://chat.openai.com/ using your preferred web browser and log in with your credentials.
+2. Access the Developer Tools in your browser. This can typically be done by right-clicking the page and selecting "Inspect," or by pressing F12 or Ctrl+Shift+I (Cmd+Option+I on a Mac).
+3. With the Developer Tools open, switch to the "Network" tab.
+4. Reload the website to capture the loading process within the Network tab.
+5. Initiate an action in the chat which can be capture in the .har file.
+6. Right-click any of the network activities listed and select "Save all as HAR with content" to export the .har file.
+
+##### Storing the .HAR File
+
+- Place the exported .har file in the `./hardir` directory if you are using Docker. Alternatively, you can store it in any preferred location within your current working directory.
+
+Note: Ensure that your .har file is stored securely, as it may contain sensitive information.
-##### Using Proxy
+#### Using Proxy
If you want to hide or change your IP address for the providers, you can set a proxy globally via an environment variable:
diff --git a/docker/Dockerfile b/docker/Dockerfile
index 8b2d5b7b..3e606ae6 100644
--- a/docker/Dockerfile
+++ b/docker/Dockerfile
@@ -81,7 +81,14 @@ WORKDIR $G4F_DIR
COPY requirements.txt $G4F_DIR
# Upgrade pip for the latest features and install the project's Python dependencies.
-RUN pip install --break-system-packages --upgrade pip && pip install --break-system-packages -r requirements.txt
+RUN pip install --break-system-packages --upgrade pip \
+ && pip install --break-system-packages -r requirements.txt
+
+# Install selenium driver and uninstall webdriver
+RUN pip install --break-system-packages \
+ undetected-chromedriver selenium-wire \
+ && pip uninstall -y --break-system-packages \
+ webdriver plyer
# Copy the entire package into the container.
ADD --chown=$G4F_USER:$G4F_USER g4f $G4F_DIR/g4f
diff --git a/g4f/Provider/bing/conversation.py b/g4f/Provider/bing/conversation.py
index de5716b7..e9cdfe31 100644
--- a/g4f/Provider/bing/conversation.py
+++ b/g4f/Provider/bing/conversation.py
@@ -3,8 +3,9 @@ from __future__ import annotations
from aiohttp import ClientSession
from ...requests import raise_for_status
from ...errors import RateLimitError
+from ...providers.conversation import BaseConversation
-class Conversation:
+class Conversation(BaseConversation):
"""
Represents a conversation with specific attributes.
"""
@@ -32,7 +33,7 @@ async def create_conversation(session: ClientSession, headers: dict, tone: str)
Returns:
Conversation: An instance representing the created conversation.
"""
- if tone == "copilot":
+ if tone == "Copilot":
url = "https://copilot.microsoft.com/turing/conversation/create?bundleVersion=1.1634.3-nodesign2"
else:
url = "https://www.bing.com/turing/conversation/create?bundleVersion=1.1626.1"
diff --git a/g4f/Provider/needs_auth/OpenaiChat.py b/g4f/Provider/needs_auth/OpenaiChat.py
index 0aff99a7..72f9f224 100644
--- a/g4f/Provider/needs_auth/OpenaiChat.py
+++ b/g4f/Provider/needs_auth/OpenaiChat.py
@@ -3,10 +3,10 @@ from __future__ import annotations
import asyncio
import uuid
import json
-import os
import base64
import time
from aiohttp import ClientWebSocketResponse
+from copy import copy
try:
import webview
@@ -22,13 +22,13 @@ except ImportError:
pass
from ..base_provider import AsyncGeneratorProvider, ProviderModelMixin
-from ..helper import get_cookies
from ...webdriver import get_browser
from ...typing import AsyncResult, Messages, Cookies, ImageType, Union, AsyncIterator
from ...requests import get_args_from_browser, raise_for_status
from ...requests.aiohttp import StreamSession
from ...image import to_image, to_bytes, ImageResponse, ImageRequest
-from ...errors import MissingRequirementsError, MissingAuthError, ProviderNotWorkingError
+from ...errors import MissingAuthError
+from ...providers.conversation import BaseConversation
from ..openai.har_file import getArkoseAndAccessToken
from ... import debug
@@ -56,11 +56,6 @@ class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin):
prompt: str = None,
model: str = "",
messages: Messages = [],
- history_disabled: bool = False,
- action: str = "next",
- conversation_id: str = None,
- parent_id: str = None,
- image: ImageType = None,
**kwargs
) -> Response:
"""
@@ -89,12 +84,7 @@ class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin):
generator = cls.create_async_generator(
model,
messages,
- history_disabled=history_disabled,
- action=action,
- conversation_id=conversation_id,
- parent_id=parent_id,
- image=image,
- response_fields=True,
+ return_conversation=True,
**kwargs
)
return Response(
@@ -209,7 +199,7 @@ class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin):
} for message in messages]
# Check if there is an image response
- if image_request:
+ if image_request is not None:
# Change content in last user message
messages[-1]["content"] = {
"content_type": "multimodal_text",
@@ -308,10 +298,11 @@ class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin):
history_disabled: bool = True,
action: str = "next",
conversation_id: str = None,
+ conversation: Conversation = None,
parent_id: str = None,
image: ImageType = None,
image_name: str = None,
- response_fields: bool = False,
+ return_conversation: bool = False,
**kwargs
) -> AsyncResult:
"""
@@ -330,7 +321,7 @@ class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin):
conversation_id (str): ID of the conversation.
parent_id (str): ID of the parent message.
image (ImageType): Image to include in the conversation.
- response_fields (bool): Flag to include response fields in the output.
+ return_conversation (bool): Flag to include response fields in the output.
**kwargs: Additional keyword arguments.
Yields:
@@ -387,6 +378,8 @@ class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin):
arkose_token, api_key, cookies = await getArkoseAndAccessToken(proxy)
cls._create_request_args(cookies)
cls._set_api_key(api_key)
+ if arkose_token is None:
+ raise MissingAuthError("No arkose token found in .har file")
try:
image_request = await cls.upload_image(session, cls._headers, image, image_name) if image else None
@@ -396,7 +389,8 @@ class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin):
print(f"{e.__class__.__name__}: {e}")
model = cls.get_model(model).replace("gpt-3.5-turbo", "text-davinci-002-render-sha")
- fields = ResponseFields()
+ fields = Conversation() if conversation is None else copy(conversation)
+ fields.finish_reason = None
while fields.finish_reason is None:
conversation_id = conversation_id if fields.conversation_id is None else fields.conversation_id
parent_id = parent_id if fields.message_id is None else fields.message_id
@@ -409,7 +403,7 @@ class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin):
"conversation_id": conversation_id,
"parent_message_id": parent_id,
"model": model,
- "history_and_training_disabled": history_disabled and not auto_continue,
+ "history_and_training_disabled": history_disabled and not auto_continue and not return_conversation,
"websocket_request_id": websocket_request_id
}
if action != "continue":
@@ -422,8 +416,6 @@ class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin):
}
if need_arkose:
headers["OpenAI-Sentinel-Arkose-Token"] = arkose_token
- headers["OpenAI-Sentinel-Chat-Requirements-Token"] = chat_token
-
async with session.post(
f"{cls.url}/backend-api/conversation",
json=data,
@@ -432,15 +424,15 @@ class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin):
cls._update_request_args(session)
await raise_for_status(response)
async for chunk in cls.iter_messages_chunk(response.iter_lines(), session, fields):
- if response_fields:
- response_fields = False
+ if return_conversation:
+ return_conversation = False
yield fields
yield chunk
if not auto_continue:
break
action = "continue"
await asyncio.sleep(5)
- if history_disabled and auto_continue:
+ if history_disabled and auto_continue and not return_conversation:
await cls.delete_conversation(session, cls._headers, fields.conversation_id)
@staticmethod
@@ -458,7 +450,7 @@ class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin):
cls,
messages: AsyncIterator,
session: StreamSession,
- fields: ResponseFields
+ fields: Conversation
) -> AsyncIterator:
last_message: int = 0
async for message in messages:
@@ -487,7 +479,7 @@ class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin):
break
@classmethod
- async def iter_messages_line(cls, session: StreamSession, line: bytes, fields: ResponseFields) -> AsyncIterator:
+ async def iter_messages_line(cls, session: StreamSession, line: bytes, fields: Conversation) -> AsyncIterator:
if not line.startswith(b"data: "):
return
elif line.startswith(b"data: [DONE]"):
@@ -618,7 +610,7 @@ this.fetch = async (url, options) => {
@classmethod
def _update_request_args(cls, session: StreamSession):
for c in session.cookie_jar if hasattr(session, "cookie_jar") else session.cookies.jar:
- cls._cookies[c.name if hasattr(c, "name") else c.key] = c.value
+ cls._cookies[c.key if hasattr(c, "key") else c.name] = c.value
cls._update_cookie_header()
@classmethod
@@ -631,7 +623,7 @@ this.fetch = async (url, options) => {
def _update_cookie_header(cls):
cls._headers["Cookie"] = cls._format_cookies(cls._cookies)
-class ResponseFields:
+class Conversation(BaseConversation):
"""
Class to encapsulate response fields.
"""
@@ -664,7 +656,7 @@ class Response():
self._generator = None
chunks = []
async for chunk in self._generator:
- if isinstance(chunk, ResponseFields):
+ if isinstance(chunk, Conversation):
self._fields = chunk
else:
yield chunk
diff --git a/g4f/Provider/openai/har_file.py b/g4f/Provider/openai/har_file.py
index 3e8535ad..68fe7420 100644
--- a/g4f/Provider/openai/har_file.py
+++ b/g4f/Provider/openai/har_file.py
@@ -11,11 +11,6 @@ from copy import deepcopy
from .crypt import decrypt, encrypt
from ...requests import StreamSession
-arkPreURL = "https://tcr9i.chat.openai.com/fc/gt2/public_key/35536E1E-65B4-4D96-9D97-6ADB7EFF8147"
-sessionUrl = "https://chat.openai.com/api/auth/session"
-chatArk = None
-accessToken = None
-
class arkReq:
def __init__(self, arkURL, arkBx, arkHeader, arkBody, arkCookies, userAgent):
self.arkURL = arkURL
@@ -25,21 +20,30 @@ class arkReq:
self.arkCookies = arkCookies
self.userAgent = userAgent
+arkPreURL = "https://tcr9i.chat.openai.com/fc/gt2/public_key/35536E1E-65B4-4D96-9D97-6ADB7EFF8147"
+sessionUrl = "https://chat.openai.com/api/auth/session"
+chatArk: arkReq = None
+accessToken: str = None
+cookies: dict = None
+
def readHAR():
dirPath = "./"
harPath = []
chatArks = []
accessToken = None
+ cookies = {}
for root, dirs, files in os.walk(dirPath):
for file in files:
if file.endswith(".har"):
harPath.append(os.path.join(root, file))
+ if harPath:
+ break
if not harPath:
raise RuntimeError("No .har file found")
for path in harPath:
- with open(path, 'r') as file:
+ with open(path, 'rb') as file:
try:
- harFile = json.load(file)
+ harFile = json.loads(file.read())
except json.JSONDecodeError:
# Error: not a HAR file!
continue
@@ -48,11 +52,12 @@ def readHAR():
chatArks.append(parseHAREntry(v))
elif v['request']['url'] == sessionUrl:
accessToken = json.loads(v["response"]["content"]["text"]).get("accessToken")
- if not chatArks:
- RuntimeError("No arkose requests found in .har files")
+ cookies = {c['name']: c['value'] for c in v['request']['cookies']}
if not accessToken:
RuntimeError("No accessToken found in .har files")
- return chatArks.pop(), accessToken
+ if not chatArks:
+ return None, accessToken, cookies
+ return chatArks.pop(), accessToken, cookies
def parseHAREntry(entry) -> arkReq:
tmpArk = arkReq(
@@ -60,7 +65,7 @@ def parseHAREntry(entry) -> arkReq:
arkBx="",
arkHeader={h['name'].lower(): h['value'] for h in entry['request']['headers'] if h['name'].lower() not in ['content-length', 'cookie'] and not h['name'].startswith(':')},
arkBody={p['name']: unquote(p['value']) for p in entry['request']['postData']['params'] if p['name'] not in ['rnd']},
- arkCookies=[{'name': c['name'], 'value': c['value'], 'expires': c['expires']} for c in entry['request']['cookies']],
+ arkCookies={c['name']: c['value'] for c in entry['request']['cookies']},
userAgent=""
)
tmpArk.userAgent = tmpArk.arkHeader.get('user-agent', '')
@@ -81,7 +86,6 @@ def genArkReq(chatArk: arkReq) -> arkReq:
tmpArk.arkBody['bda'] = base64.b64encode(bda.encode()).decode()
tmpArk.arkBody['rnd'] = str(random.random())
tmpArk.arkHeader['x-ark-esync-value'] = bw
- tmpArk.arkCookies = {cookie['name']: cookie['value'] for cookie in tmpArk.arkCookies}
return tmpArk
async def sendRequest(tmpArk: arkReq, proxy: str = None):
@@ -117,8 +121,10 @@ def getN() -> str:
return base64.b64encode(timestamp.encode()).decode()
async def getArkoseAndAccessToken(proxy: str):
- global chatArk, accessToken
+ global chatArk, accessToken, cookies
if chatArk is None or accessToken is None:
- chatArk, accessToken = readHAR()
+ chatArk, accessToken, cookies = readHAR()
+ if chatArk is None:
+ return None, accessToken, cookies
newReq = genArkReq(chatArk)
- return await sendRequest(newReq, proxy), accessToken, newReq.arkCookies \ No newline at end of file
+ return await sendRequest(newReq, proxy), accessToken, cookies \ No newline at end of file
diff --git a/g4f/gui/server/api.py b/g4f/gui/server/api.py
index 11b28dda..da934d57 100644
--- a/g4f/gui/server/api.py
+++ b/g4f/gui/server/api.py
@@ -39,9 +39,9 @@ from g4f.errors import VersionNotFoundError
from g4f.Provider import ProviderType, __providers__, __map__
from g4f.providers.base_provider import ProviderModelMixin
from g4f.Provider.bing.create_images import patch_provider
-from g4f.Provider.Bing import Conversation
+from g4f.providers.conversation import BaseConversation
-conversations: dict[str, Conversation] = {}
+conversations: dict[str, BaseConversation] = {}
class Api():
@@ -230,14 +230,14 @@ class Api():
if first:
first = False
yield self._format_json("provider", get_last_provider(True))
- if isinstance(chunk, Conversation):
+ if isinstance(chunk, BaseConversation):
conversations[conversation_id] = chunk
yield self._format_json("conversation", conversation_id)
elif isinstance(chunk, Exception):
logging.exception(chunk)
yield self._format_json("message", get_error_message(chunk))
else:
- yield self._format_json("content", chunk)
+ yield self._format_json("content", str(chunk))
except Exception as e:
logging.exception(e)
yield self._format_json('error', get_error_message(e))
diff --git a/g4f/providers/conversation.py b/g4f/providers/conversation.py
new file mode 100644
index 00000000..921810d3
--- /dev/null
+++ b/g4f/providers/conversation.py
@@ -0,0 +1,2 @@
+class BaseConversation:
+ ... \ No newline at end of file
diff --git a/hardir/.gitkeep b/hardir/.gitkeep
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/hardir/.gitkeep