返回提交历史
Added
.github/workflows/unittest.yml
+19
-0
Added
etc/unittest/main.py
+73
-0
Modified
g4f/Provider/Phind.py
+6
-2
Modified
g4f/Provider/base_provider.py
+71
-0
Modified
g4f/Provider/bing/create_images.py
+1
-1
Modified
g4f/Provider/create_images.py
+60
-1
Modified
g4f/gui/client/js/chat.v1.js
+3
-3
Modified
g4f/gui/server/backend.py
+149
-62
Modified
g4f/version.py
+41
-15
Modified
g4f/webdriver.py
+55
-20
XFEstudio/gpt4free
Change doctypes style to Google Fix typo in latest_version Fix Phind Provider Add unittest worklow and main tests
32252def
代码差异
10 个文件
+478
-104
@@ -0,0 +1,19 @@
1
name: Unittest
2
3
on: [push]
4
5
jobs:
6
build:
7
name: Build unittest
8
runs-on: ubuntu-latest
9
steps:
10
- uses: actions/checkout@v4
11
- name: Set up Python
12
uses: actions/setup-python@v4
13
with:
14
python-version: "3.x"
15
cache: 'pip'
16
- name: Install requirements
17
- run: pip install -r requirements.txt
18
- name: Run tests
19
run: python -m etc.unittest.main
@@ -0,0 +1,73 @@
1
import sys
2
import pathlib
3
import unittest
4
from unittest.mock import MagicMock
5
6
sys.path.append(str(pathlib.Path(__file__).parent.parent.parent))
7
8
import g4f
9
from g4f import ChatCompletion, get_last_provider
10
from g4f.gui.server.backend import Backend_Api, get_error_message
11
from g4f.base_provider import BaseProvider
12
13
g4f.debug.logging = False
14
15
class MockProvider(BaseProvider):
16
working = True
17
18
def create_completion(
19
model, messages, stream, **kwargs
20
):
21
yield "Mock"
22
23
async def create_async(
24
model, messages, **kwargs
25
):
26
return "Mock"
27
28
class TestBackendApi(unittest.TestCase):
29
30
def setUp(self):
31
self.app = MagicMock()
32
self.api = Backend_Api(self.app)
33
34
def test_version(self):
35
response = self.api.get_version()
36
self.assertIn("version", response)
37
self.assertIn("latest_version", response)
38
39
class TestChatCompletion(unittest.TestCase):
40
41
def test_create(self):
42
messages = [{'role': 'user', 'content': 'Hello'}]
43
result = ChatCompletion.create(g4f.models.default, messages)
44
self.assertTrue("Hello" in result or "Good" in result)
45
46
def test_get_last_provider(self):
47
messages = [{'role': 'user', 'content': 'Hello'}]
48
ChatCompletion.create(g4f.models.default, messages, MockProvider)
49
self.assertEqual(get_last_provider(), MockProvider)
50
51
def test_bing_provider(self):
52
messages = [{'role': 'user', 'content': 'Hello'}]
53
provider = g4f.Provider.Bing
54
result = ChatCompletion.create(g4f.models.default, messages, provider)
55
self.assertTrue("Bing" in result)
56
57
class TestChatCompletionAsync(unittest.IsolatedAsyncioTestCase):
58
59
async def test_async(self):
60
messages = [{'role': 'user', 'content': 'Hello'}]
61
result = await ChatCompletion.create_async(g4f.models.default, messages, MockProvider)
62
self.assertTrue("Mock" in result)
63
64
class TestUtilityFunctions(unittest.TestCase):
65
66
def test_get_error_message(self):
67
g4f.debug.last_provider = g4f.Provider.Bing
68
exception = Exception("Message")
69
result = get_error_message(exception)
70
self.assertEqual("Bing: Exception: Message", result)
71
72
if __name__ == '__main__':
73
unittest.main()
@@ -59,12 +59,16 @@ class Phind(AsyncGeneratorProvider):
59
59
"rewrittenQuestion": prompt,
60
60
"challenge": 0.21132115912208504
61
61
}
62
async with session.post(f"{cls.url}/api/infer/followup/answer", headers=headers, json=data) as response:
62
async with session.post(f"https://https.api.phind.com/infer/", headers=headers, json=data) as response:
63
63
new_line = False
64
64
async for line in response.iter_lines():
65
65
if line.startswith(b"data: "):
66
66
chunk = line[6:]
67
if chunk.startswith(b"<PHIND_METADATA>") or chunk.startswith(b"<PHIND_INDICATOR>"):
67
if chunk.startswith(b'<PHIND_DONE/>'):
68
break
69
if chunk.startswith(b'<PHIND_WEBRESULTS>') or chunk.startswith(b'<PHIND_FOLLOWUP>'):
70
pass
71
elif chunk.startswith(b"<PHIND_METADATA>") or chunk.startswith(b"<PHIND_INDICATOR>"):
68
72
pass
69
73
elif chunk:
70
74
yield chunk.decode()
@@ -36,6 +36,17 @@ class AbstractProvider(BaseProvider):
36
36
) -> str:
37
37
"""
38
38
Asynchronously creates a result based on the given model and messages.
39
40
Args:
41
cls (type): The class on which this method is called.
42
model (str): The model to use for creation.
43
messages (Messages): The messages to process.
44
loop (AbstractEventLoop, optional): The event loop to use. Defaults to None.
45
executor (ThreadPoolExecutor, optional): The executor for running async tasks. Defaults to None.
46
**kwargs: Additional keyword arguments.
47
48
Returns:
49
str: The created result as a string.
39
50
"""
40
51
loop = loop or get_event_loop()
41
52
@@ -52,6 +63,12 @@ class AbstractProvider(BaseProvider):
52
63
def params(cls) -> str:
53
64
"""
54
65
Returns the parameters supported by the provider.
66
67
Args:
68
cls (type): The class on which this property is called.
69
70
Returns:
71
str: A string listing the supported parameters.
55
72
"""
56
73
sig = signature(
57
74
cls.create_async_generator if issubclass(cls, AsyncGeneratorProvider) else
@@ -90,6 +107,17 @@ class AsyncProvider(AbstractProvider):
90
107
) -> CreateResult:
91
108
"""
92
109
Creates a completion result synchronously.
110
111
Args:
112
cls (type): The class on which this method is called.
113
model (str): The model to use for creation.
114
messages (Messages): The messages to process.
115
stream (bool): Indicates whether to stream the results. Defaults to False.
116
loop (AbstractEventLoop, optional): The event loop to use. Defaults to None.
117
**kwargs: Additional keyword arguments.
118
119
Returns:
120
CreateResult: The result of the completion creation.
93
121
"""
94
122
loop = loop or get_event_loop()
95
123
coro = cls.create_async(model, messages, **kwargs)
@@ -104,6 +132,17 @@ class AsyncProvider(AbstractProvider):
104
132
) -> str:
105
133
"""
106
134
Abstract method for creating asynchronous results.
135
136
Args:
137
model (str): The model to use for creation.
138
messages (Messages): The messages to process.
139
**kwargs: Additional keyword arguments.
140
141
Raises:
142
NotImplementedError: If this method is not overridden in derived classes.
143
144
Returns:
145
str: The created result as a string.
107
146
"""
108
147
raise NotImplementedError()
109
148
@@ -126,6 +165,17 @@ class AsyncGeneratorProvider(AsyncProvider):
126
165
) -> CreateResult:
127
166
"""
128
167
Creates a streaming completion result synchronously.
168
169
Args:
170
cls (type): The class on which this method is called.
171
model (str): The model to use for creation.
172
messages (Messages): The messages to process.
173
stream (bool): Indicates whether to stream the results. Defaults to True.
174
loop (AbstractEventLoop, optional): The event loop to use. Defaults to None.
175
**kwargs: Additional keyword arguments.
176
177
Returns:
178
CreateResult: The result of the streaming completion creation.
129
179
"""
130
180
loop = loop or get_event_loop()
131
181
generator = cls.create_async_generator(model, messages, stream=stream, **kwargs)
@@ -146,6 +196,15 @@ class AsyncGeneratorProvider(AsyncProvider):
146
196
) -> str:
147
197
"""
148
198
Asynchronously creates a result from a generator.
199
200
Args:
201
cls (type): The class on which this method is called.
202
model (str): The model to use for creation.
203
messages (Messages): The messages to process.
204
**kwargs: Additional keyword arguments.
205
206
Returns:
207
str: The created result as a string.
149
208
"""
150
209
return "".join([
151
210
chunk async for chunk in cls.create_async_generator(model, messages, stream=False, **kwargs)
@@ -162,5 +221,17 @@ class AsyncGeneratorProvider(AsyncProvider):
162
221
) -> AsyncResult:
163
222
"""
164
223
Abstract method for creating an asynchronous generator.
224
225
Args:
226
model (str): The model to use for creation.
227
messages (Messages): The messages to process.
228
stream (bool): Indicates whether to stream the results. Defaults to True.
229
**kwargs: Additional keyword arguments.
230
231
Raises:
232
NotImplementedError: If this method is not overridden in derived classes.
233
234
Returns:
235
AsyncResult: An asynchronous generator yielding results.
165
236
"""
166
237
raise NotImplementedError()
@@ -198,7 +198,7 @@ class CreateImagesBing:
198
198
_cookies: Dict[str, str] = {}
199
199
200
200
@classmethod
201
def create_completion(cls, prompt: str, cookies: Dict[str, str] = None, proxy: str = None) -> Generator[str]:
201
def create_completion(cls, prompt: str, cookies: Dict[str, str] = None, proxy: str = None) -> Generator[str, None, None]:
202
202
"""
203
203
Generator for creating imagecompletion based on a prompt.
204
204
@@ -8,13 +8,31 @@ from ..base_provider import BaseProvider, ProviderType
8
8
9
9
system_message = """
10
10
You can generate custom images with the DALL-E 3 image generator.
11
To generate a image with a prompt, do this:
11
To generate an image with a prompt, do this:
12
12
<img data-prompt=\"keywords for the image\">
13
13
Don't use images with data uri. It is important to use a prompt instead.
14
14
<img data-prompt=\"image caption\">
15
15
"""
16
16
17
17
class CreateImagesProvider(BaseProvider):
18
"""
19
Provider class for creating images based on text prompts.
20
21
This provider handles image creation requests embedded within message content,
22
using provided image creation functions.
23
24
Attributes:
25
provider (ProviderType): The underlying provider to handle non-image related tasks.
26
create_images (callable): A function to create images synchronously.
27
create_images_async (callable): A function to create images asynchronously.
28
system_message (str): A message that explains the image creation capability.
29
include_placeholder (bool): Flag to determine whether to include the image placeholder in the output.
30
__name__ (str): Name of the provider.
31
url (str): URL of the provider.
32
working (bool): Indicates if the provider is operational.
33
supports_stream (bool): Indicates if the provider supports streaming.
34
"""
35
18
36
def __init__(
19
37
self,
20
38
provider: ProviderType,
@@ -23,6 +41,16 @@ class CreateImagesProvider(BaseProvider):
23
41
system_message: str = system_message,
24
42
include_placeholder: bool = True
25
43
) -> None:
44
"""
45
Initializes the CreateImagesProvider.
46
47
Args:
48
provider (ProviderType): The underlying provider.
49
create_images (callable): Function to create images synchronously.
50
create_async (callable): Function to create images asynchronously.
51
system_message (str, optional): System message to be prefixed to messages. Defaults to a predefined message.
52
include_placeholder (bool, optional): Whether to include image placeholders in the output. Defaults to True.
53
"""
26
54
self.provider = provider
27
55
self.create_images = create_images
28
56
self.create_images_async = create_async
@@ -40,6 +68,22 @@ class CreateImagesProvider(BaseProvider):
40
68
stream: bool = False,
41
69
**kwargs
42
70
) -> CreateResult:
71
"""
72
Creates a completion result, processing any image creation prompts found within the messages.
73
74
Args:
75
model (str): The model to use for creation.
76
messages (Messages): The messages to process, which may contain image prompts.
77
stream (bool, optional): Indicates whether to stream the results. Defaults to False.
78
**kwargs: Additional keywordarguments for the provider.
79
80
Yields:
81
CreateResult: Yields chunks of the processed messages, including image data if applicable.
82
83
Note:
84
This method processes messages to detect image creation prompts. When such a prompt is found,
85
it calls the synchronous image creation function and includes the resulting image in the output.
86
"""
43
87
messages.insert(0, {"role": "system", "content": self.system_message})
44
88
buffer = ""
45
89
for chunk in self.provider.create_completion(model, messages, stream, **kwargs):
@@ -71,6 +115,21 @@ class CreateImagesProvider(BaseProvider):
71
115
messages: Messages,
72
116
**kwargs
73
117
) -> str:
118
"""
119
Asynchronously creates a response, processing any image creation prompts found within the messages.
120
121
Args:
122
model (str): The model to use for creation.
123
messages (Messages): The messages to process, which may contain image prompts.
124
**kwargs: Additional keyword arguments for the provider.
125
126
Returns:
127
str: The processed response string, including asynchronously generated image data if applicable.
128
129
Note:
130
This method processes messages to detect image creation prompts. When such a prompt is found,
131
it calls the asynchronous image creation function and includes the resulting image in the output.
132
"""
74
133
messages.insert(0, {"role": "system", "content": self.system_message})
75
134
response = await self.provider.create_async(model, messages, **kwargs)
76
135
matches = re.findall(r'(<img data-prompt="(.*?)">)', response)
@@ -652,9 +652,9 @@ observer.observe(message_input, { attributes: true });
652
652
653
653
document.title = 'g4f - gui - ' + versions["version"];
654
654
text = "version ~ "
655
if (versions["version"] != versions["lastet_version"]) {
656
release_url = 'https://github.com/xtekky/gpt4free/releases/tag/' + versions["lastet_version"];
657
text += '<a href="' + release_url +'" target="_blank" title="New version: ' + versions["lastet_version"] +'">' + versions["version"] + ' 🆕</a>';
655
if (versions["version"] != versions["latest_version"]) {
656
release_url = 'https://github.com/xtekky/gpt4free/releases/tag/' + versions["latest_version"];
657
text += '<a href="' + release_url +'" target="_blank" title="New version: ' + versions["latest_version"] +'">' + versions["version"] + ' 🆕</a>';
658
658
} else {
659
659
text += versions["version"];
660
660
}
@@ -1,6 +1,7 @@
1
1
import logging
2
2
import json
3
3
from flask import request, Flask
4
from typing import Generator
4
5
from g4f import debug, version, models
5
6
from g4f import _all_models, get_last_provider, ChatCompletion
6
7
from g4f.image import is_allowed_extension, to_image
@@ -11,60 +12,123 @@ from .internet import get_search_message
11
12
debug.logging = True
12
13
13
14
class Backend_Api:
15
"""
16
Handles various endpoints in a Flask application for backend operations.
17
18
This class provides methods to interact with models, providers, and to handle
19
various functionalities like conversations, error handling, and version management.
20
21
Attributes:
22
app (Flask): A Flask application instance.
23
routes (dict): A dictionary mapping API endpoints to their respective handlers.
24
"""
14
25
def __init__(self, app: Flask) -> None:
26
"""
27
Initialize the backend API with the given Flask application.
28
29
Args:
30
app (Flask): Flask application instance to attach routes to.
31
"""
15
32
self.app: Flask = app
16
33
self.routes = {
17
34
'/backend-api/v2/models': {
18
'function': self.models,
19
'methods' : ['GET']
35
'function': self.get_models,
36
'methods': ['GET']
20
37
},
21
38
'/backend-api/v2/providers': {
22
'function': self.providers,
23
'methods' : ['GET']
39
'function': self.get_providers,
40
'methods': ['GET']
24
41
},
25
42
'/backend-api/v2/version': {
26
'function': self.version,
27
'methods' : ['GET']
43
'function': self.get_version,
44
'methods': ['GET']
28
45
},
29
46
'/backend-api/v2/conversation': {
30
'function': self._conversation,
47
'function': self.handle_conversation,
31
48
'methods': ['POST']
32
49
},
33
50
'/backend-api/v2/gen.set.summarize:title': {
34
'function': self._gen_title,
51
'function': self.generate_title,
35
52
'methods': ['POST']
36
53
},
37
54
'/backend-api/v2/error': {
38
'function': self.error,
55
'function': self.handle_error,
39
56
'methods': ['POST']
40
57
}
41
58
}
42
59
43
def error(self):
60
def handle_error(self):
61
"""
62
Initialize the backend API with the given Flask application.
63
64
Args:
65
app (Flask): Flask application instance to attach routes to.
66
"""
44
67
print(request.json)
45
46
68
return 'ok', 200
47
69
48
def models(self):
70
def get_models(self):
71
"""
72
Return a list of all models.
73
74
Fetches and returns a list of all available models in the system.
75
76
Returns:
77
List[str]: A list of model names.
78
"""
49
79
return _all_models
50
80
51
def providers(self):
52
return [
53
provider.__name__ for provider in __providers__ if provider.working
54
]
81
def get_providers(self):
82
"""
83
Return a list of all working providers.
84
"""
85
return [provider.__name__ for provider in __providers__ if provider.working]
55
86
56
def version(self):
87
def get_version(self):
88
"""
89
Returns the current and latest version of the application.
90
91
Returns:
92
dict: A dictionary containing the current and latest version.
93
"""
57
94
return {
58
95
"version": version.utils.current_version,
59
"lastet_version": version.get_latest_version(),
96
"latest_version": version.get_latest_version(),
60
97
}
61
98
62
def _gen_title(self):
63
return {
64
'title': ''
65
}
99
def generate_title(self):
100
"""
101
Generates and returns a title based on the request data.
102
103
Returns:
104
dict: A dictionary with the generated title.
105
"""
106
return {'title': ''}
66
107
67
def _conversation(self):
108
def handle_conversation(self):
109
"""
110
Handles conversation requests and streams responses back.
111
112
Returns:
113
Response: A Flask response object for streaming.
114
"""
115
kwargs = self._prepare_conversation_kwargs()
116
117
return self.app.response_class(
118
self._create_response_stream(kwargs),
119
mimetype='text/event-stream'
120
)
121
122
def _prepare_conversation_kwargs(self):
123
"""
124
Prepares arguments for chat completion based on the request data.
125
126
Reads the request and prepares the necessary arguments for handling
127
a chat completion request.
128
129
Returns:
130
dict: Arguments prepared for chat completion.
131
"""
68
132
kwargs = {}
69
133
if 'image' in request.files:
70
134
file = request.files['image']
@@ -87,47 +151,70 @@ class Backend_Api:
87
151
messages[-1]["content"] = get_search_message(messages[-1]["content"])
88
152
model = json_data.get('model')
89
153
model = model if model else models.default
90
provider = json_data.get('provider', '').replace('g4f.Provider.', '')
91
provider = provider if provider and provider != "Auto" else None
92
154
patch = patch_provider if json_data.get('patch_provider') else None
93
155
94
def try_response():
95
try:
96
first = True
97
for chunk in ChatCompletion.create(
98
model=model,
99
provider=provider,
100
messages=messages,
101
stream=True,
102
ignore_stream_and_auth=True,
103
patch_provider=patch,
104
**kwargs
105
):
106
if first:
107
first = False
108
yield json.dumps({
109
'type' : 'provider',
110
'provider': get_last_provider(True)
111
}) + "\n"
112
if isinstance(chunk, Exception):
113
logging.exception(chunk)
114
yield json.dumps({
115
'type' : 'message',
116
'message': get_error_message(chunk),
117
}) + "\n"
118
else:
119
yield json.dumps({
120
'type' : 'content',
121
'content': str(chunk),
122
}) + "\n"
123
except Exception as e:
124
logging.exception(e)
125
yield json.dumps({
126
'type' : 'error',
127
'error': get_error_message(e)
128
})
129
130
return self.app.response_class(try_response(), mimetype='text/event-stream')
156
return {
157
"model": model,
158
"provider": provider,
159
"messages": messages,
160
"stream": True,
161
"ignore_stream_and_auth": True,
162
"patch_provider": patch,
163
**kwargs
164
}
165
166
def _create_response_stream(self, kwargs) -> Generator[str, None, None]:
167
"""
168
Creates and returns a streaming response for the conversation.
169
170
Args:
171
kwargs (dict): Arguments for creating the chat completion.
172
173
Yields:
174
str: JSON formatted response chunks for the stream.
175
176
Raises:
177
Exception: If an error occurs during the streaming process.
178
"""
179
try:
180
first = True
181
for chunk in ChatCompletion.create(**kwargs):
182
if first:
183
first = False
184
yield self._format_json('provider', get_last_provider(True))
185
if isinstance(chunk, Exception):
186
logging.exception(chunk)
187
yield self._format_json('message', get_error_message(chunk))
188
else:
189
yield self._format_json('content', str(chunk))
190
except Exception as e:
191
logging.exception(e)
192
yield self._format_json('error', get_error_message(e))
193
194
def _format_json(self, response_type: str, content) -> str:
195
"""
196
Formats and returns a JSON response.
197
198
Args:
199
response_type (str): The type of the response.
200
content: The content to be included in the response.
201
202
Returns:
203
str: A JSON formatted string.
204
"""
205
return json.dumps({
206
'type': response_type,
207
response_type: content
208
}) + "\n"
131
209
132
210
def get_error_message(exception: Exception) -> str:
211
"""
212
Generates a formatted error message from an exception.
213
214
Args:
215
exception (Exception): The exception to format.
216
217
Returns:
218
str: A formatted error message string.
219
"""
133
220
return f"{get_last_provider().__name__}: {type(exception).__name__}: {exception}"
@@ -7,10 +7,16 @@ from .errors import VersionNotFoundError
7
7
8
8
def get_pypi_version(package_name: str) -> str:
9
9
"""
10
Get the latest version of a package from PyPI.
10
Retrieves the latest version of a package from PyPI.
11
11
12
:param package_name: The name of the package.
13
:return: The latest version of the package as a string.
12
Args:
13
package_name (str): The name of the package for which to retrieve the version.
14
15
Returns:
16
str: The latest version of the specified package from PyPI.
17
18
Raises:
19
VersionNotFoundError: If there is an error in fetching the version from PyPI.
14
20
"""
15
21
try:
16
22
response = requests.get(f"https://pypi.org/pypi/{package_name}/json").json()
@@ -20,10 +26,16 @@ def get_pypi_version(package_name: str) -> str:
20
26
21
27
def get_github_version(repo: str) -> str:
22
28
"""
23
Get the latest release version from a GitHub repository.
29
Retrieves the latest release version from a GitHub repository.
30
31
Args:
32
repo (str): The name of the GitHub repository.
33
34
Returns:
35
str: The latest release version from the specified GitHub repository.
24
36
25
:param repo: The name of the GitHub repository.
26
:return: The latest release version as a string.
37
Raises:
38
VersionNotFoundError: If there is an error in fetching the version from GitHub.
27
39
"""
28
40
try:
29
41
response = requests.get(f"https://api.github.com/repos/{repo}/releases/latest").json()
@@ -31,11 +43,16 @@ def get_github_version(repo: str) -> str:
31
43
except requests.RequestException as e:
32
44
raise VersionNotFoundError(f"Failed to get GitHub release version: {e}")
33
45
34
def get_latest_version():
46
def get_latest_version() -> str:
35
47
"""
36
Get the latest release version from PyPI or the GitHub repository.
48
Retrieves the latest release version of the 'g4f' package from PyPI or GitHub.
37
49
38
:return: The latest release version as a string.
50
Returns:
51
str: The latest release version of 'g4f'.
52
53
Note:
54
The function first tries to fetch the version from PyPI. If the package is not found,
55
it retrieves the version from the GitHub repository.
39
56
"""
40
57
try:
41
58
# Is installed via package manager?
@@ -47,14 +64,19 @@ def get_latest_version():
47
64
48
65
class VersionUtils:
49
66
"""
50
Utility class for managing and comparing package versions.
67
Utility class for managing and comparing package versions of 'g4f'.
51
68
"""
52
69
@cached_property
53
70
def current_version(self) -> str:
54
71
"""
55
Get the current version of the g4f package.
72
Retrieves the current version of the 'g4f' package.
73
74
Returns:
75
str: The current version of 'g4f'.
56
76
57
:return: The current version as a string.
77
Raises:
78
VersionNotFoundError: If the version cannot be determined from the package manager,
79
Docker environment, or git repository.
58
80
"""
59
81
# Read from package manager
60
82
try:
@@ -79,15 +101,19 @@ class VersionUtils:
79
101
@cached_property
80
102
def latest_version(self) -> str:
81
103
"""
82
Get the latest version of the g4f package.
104
Retrieves the latest version of the 'g4f' package.
83
105
84
:return: The latest version as a string.
106
Returns:
107
str: The latest version of 'g4f'.
85
108
"""
86
109
return get_latest_version()
87
110
88
111
def check_version(self) -> None:
89
112
"""
90
Check if the current version is up to date with the latest version.
113
Checks if the current version of 'g4f' is up to date with the latest version.
114
115
Note:
116
If a newer version is available, it prints a message with the new version and update instructions.
91
117
"""
92
118
try:
93
119
if self.current_version != self.latest_version:
@@ -21,13 +21,16 @@ def get_browser(
21
21
options: ChromeOptions = None
22
22
) -> WebDriver:
23
23
"""
24
Creates and returns a Chrome WebDriver with the specified options.
24
Creates and returns a Chrome WebDriver with specified options.
25
25
26
:param user_data_dir: Directory for user data. If None, uses default directory.
27
:param headless: Boolean indicating whether to run the browser in headless mode.
28
:param proxy: Proxy settings for the browser.
29
:param options: ChromeOptions object with specific browser options.
30
:return: An instance of WebDriver.
26
Args:
27
user_data_dir (str, optional): Directory for user data. If None, uses default directory.
28
headless (bool, optional): Whether to run the browser in headless mode. Defaults to False.
29
proxy (str, optional): Proxy settings for the browser. Defaults to None.
30
options (ChromeOptions, optional): ChromeOptions object with specific browser options. Defaults to None.
31
32
Returns:
33
WebDriver: An instance of WebDriver configured with the specified options.
31
34
"""
32
35
if user_data_dir is None:
33
36
user_data_dir = user_config_dir("g4f")
@@ -49,10 +52,13 @@ def get_browser(
49
52
50
53
def get_driver_cookies(driver: WebDriver) -> dict:
51
54
"""
52
Retrieves cookies from the given WebDriver.
55
Retrieves cookies from the specified WebDriver.
56
57
Args:
58
driver (WebDriver): The WebDriver instance from which to retrieve cookies.
53
59
54
:param driver: WebDriver from which to retrieve cookies.
55
:return: A dictionary of cookies.
60
Returns:
61
dict: A dictionary containing cookies with their names as keys and values as cookie values.
56
62
"""
57
63
return {cookie["name"]: cookie["value"] for cookie in driver.get_cookies()}
58
64
@@ -60,9 +66,13 @@ def bypass_cloudflare(driver: WebDriver, url: str, timeout: int) -> None:
60
66
"""
61
67
Attempts to bypass Cloudflare protection when accessing a URL using the provided WebDriver.
62
68
63
:param driver: The WebDriver to use.
64
:param url: URL to access.
65
:param timeout: Time in seconds to wait for the page to load.
69
Args:
70
driver (WebDriver): The WebDriver to use for accessing the URL.
71
url (str): The URL to access.
72
timeout (int): Time in seconds to wait for the page to load.
73
74
Raises:
75
Exception: If there is an error while bypassing Cloudflare or loading the page.
66
76
"""
67
77
driver.get(url)
68
78
if driver.find_element(By.TAG_NAME, "body").get_attribute("class") == "no-js":
@@ -86,6 +96,7 @@ class WebDriverSession:
86
96
"""
87
97
Manages a Selenium WebDriver session, including handling of virtual displays and proxies.
88
98
"""
99
89
100
def __init__(
90
101
self,
91
102
webdriver: WebDriver = None,
@@ -95,6 +106,17 @@ class WebDriverSession:
95
106
proxy: str = None,
96
107
options: ChromeOptions = None
97
108
):
109
"""
110
Initializes a new instance of the WebDriverSession.
111
112
Args:
113
webdriver (WebDriver, optional): A WebDriver instance for the session. Defaults to None.
114
user_data_dir (str, optional): Directory for user data. Defaults to None.
115
headless (bool, optional): Whether to run the browser in headless mode. Defaults to False.
116
virtual_display (bool, optional): Whether to use a virtual display. Defaults to False.
117
proxy (str, optional): Proxy settings for the browser. Defaults to None.
118
options (ChromeOptions, optional): ChromeOptions for the browser. Defaults to None.
119
"""
98
120
self.webdriver = webdriver
99
121
self.user_data_dir = user_data_dir
100
122
self.headless = headless
@@ -110,14 +132,17 @@ class WebDriverSession:
110
132
virtual_display: bool = False
111
133
) -> WebDriver:
112
134
"""
113
Reopens the WebDriver session with the specified parameters.
135
Reopens the WebDriver session with new settings.
136
137
Args:
138
user_data_dir (str, optional): Directory for user data. Defaults to current value.
139
headless (bool, optional): Whether to run the browser in headless mode. Defaults to current value.
140
virtual_display (bool, optional): Whether to use a virtual display. Defaults to current value.
114
141
115
:param user_data_dir: Directory for user data.
116
:param headless: Boolean indicating whether to run the browser in headless mode.
117
:param virtual_display: Boolean indicating whether to use a virtual display.
118
:return: An instance of WebDriver.
142
Returns:
143
WebDriver: The reopened WebDriver instance.
119
144
"""
120
user_data_dir = user_data_dir or self.user_data_dir
145
user_data_dir = user_data_data_dir or self.user_data_dir
121
146
if self.default_driver:
122
147
self.default_driver.quit()
123
148
if not virtual_display and self.virtual_display:
@@ -128,8 +153,10 @@ class WebDriverSession:
128
153
129
154
def __enter__(self) -> WebDriver:
130
155
"""
131
Context management method for entering a session.
132
:return: An instance of WebDriver.
156
Context management method for entering a session. Initializes and returns a WebDriver instance.
157
158
Returns:
159
WebDriver: An instance of WebDriver for this session.
133
160
"""
134
161
if self.webdriver:
135
162
return self.webdriver
@@ -141,6 +168,14 @@ class WebDriverSession:
141
168
def __exit__(self, exc_type, exc_val, exc_tb):
142
169
"""
143
170
Context management method for exiting a session. Closes and quits the WebDriver.
171
172
Args:
173
exc_type: Exception type.
174
exc_val: Exception value.
175
exc_tb: Exception traceback.
176
177
Note:
178
Closes the WebDriver and stops the virtual display if used.
144
179
"""
145
180
if self.default_driver:
146
181
try: