diff --git a/g4f/Provider/Bing.py b/g4f/Provider/Bing.py index f8b13020..925dae0a 100644 --- a/g4f/Provider/Bing.py +++ b/g4f/Provider/Bing.py @@ -406,14 +406,13 @@ async def stream_generate( except Exception as e: if debug.logging: print(f"Bing: Failed to create images: {e}") - response_txt += f"\nhttps://www.bing.com/images/create?q={parse.quote(prompt)}" - do_read = False + image_response = f"\nhttps://www.bing.com/images/create?q={parse.quote(prompt)}" if response_txt.startswith(returned_text): new = response_txt[len(returned_text):] if new not in ("", "\n"): yield new returned_text = response_txt - if image_response: + if image_response is not None: yield image_response elif response.get('type') == 2: result = response['item']['result'] diff --git a/g4f/Provider/You.py b/g4f/Provider/You.py index 85b60452..8adc8b19 100644 --- a/g4f/Provider/You.py +++ b/g4f/Provider/You.py @@ -4,14 +4,18 @@ import re import json import base64 import uuid -from asyncio import get_running_loop -from aiohttp import ClientSession, FormData, BaseConnector, CookieJar +try: + from curl_cffi import CurlMime + has_curl_cffi = True +except ImportError: + has_curl_cffi = False from ..typing import AsyncResult, Messages, ImageType, Cookies from .base_provider import AsyncGeneratorProvider, ProviderModelMixin -from .helper import format_prompt, get_connector +from .helper import format_prompt from ..image import to_bytes, ImageResponse -from ..requests import WebDriver, raise_for_status, get_args_from_browser +from ..requests import StreamSession, raise_for_status +from ..errors import MissingRequirementsError class You(AsyncGeneratorProvider, ProviderModelMixin): url = "https://you.com" @@ -33,8 +37,6 @@ class You(AsyncGeneratorProvider, ProviderModelMixin): model_aliases = { "claude-v2": "claude-2" } - _args: dict = None - _cookie_jar: CookieJar = None _cookies = None _cookies_used = 0 @@ -45,19 +47,12 @@ class You(AsyncGeneratorProvider, ProviderModelMixin): messages: Messages, image: ImageType = None, image_name: str = None, - connector: BaseConnector = None, - webdriver: WebDriver = None, proxy: str = None, chat_mode: str = "default", **kwargs, ) -> AsyncResult: - if cls._args is None: - cls._args = get_args_from_browser(cls.url, webdriver, proxy) - cls._cookie_jar = CookieJar(loop=get_running_loop()) - else: - if "cookies" in cls._args: - del cls._args["cookies"] - cls._cookie_jar._loop = get_running_loop() + if not has_curl_cffi: + raise MissingRequirementsError('Install "curl_cffi" package') if image is not None: chat_mode = "agent" elif not model or model == cls.default_model: @@ -67,10 +62,9 @@ class You(AsyncGeneratorProvider, ProviderModelMixin): else: chat_mode = "custom" model = cls.get_model(model) - async with ClientSession( - connector=get_connector(connector, proxy), - cookie_jar=cls._cookie_jar, - **cls._args + async with StreamSession( + proxy=proxy, + impersonate="chrome" ) as session: cookies = await cls.get_cookies(session) if chat_mode != "default" else None upload = json.dumps([await cls.upload_file(session, cookies, to_bytes(image), image_name)]) if image else "" @@ -82,8 +76,8 @@ class You(AsyncGeneratorProvider, ProviderModelMixin): # and idx < len(questions) # ] headers = { - "accept": "text/event-stream", - "referer": f"{cls.url}/search?fromSearchBar=true&tbm=youchat", + "Accept": "text/event-stream", + "Referer": f"{cls.url}/search?fromSearchBar=true&tbm=youchat", } data = { "userFiles": upload, @@ -106,12 +100,12 @@ class You(AsyncGeneratorProvider, ProviderModelMixin): cookies=cookies ) as response: await raise_for_status(response) - async for line in response.content: + async for line in response.iter_lines(): if line.startswith(b'event: '): - event = line[7:-1].decode() + event = line[7:].decode() elif line.startswith(b'data: '): if event in ["youChatUpdate", "youChatToken"]: - data = json.loads(line[6:-1]) + data = json.loads(line[6:]) if event == "youChatToken" and event in data: yield data[event] elif event == "youChatUpdate" and "t" in data: @@ -122,18 +116,20 @@ class You(AsyncGeneratorProvider, ProviderModelMixin): yield data["t"] @classmethod - async def upload_file(cls, client: ClientSession, cookies: Cookies, file: bytes, filename: str = None) -> dict: + async def upload_file(cls, client: StreamSession, cookies: Cookies, file: bytes, filename: str = None) -> dict: async with client.get( f"{cls.url}/api/get_nonce", cookies=cookies, ) as response: await raise_for_status(response) upload_nonce = await response.text() - data = FormData() - data.add_field('file', file, filename=filename) + #data = FormData() + #data.add_field('file', file, filename=filename) + multipart = CurlMime() + multipart.addpart(name="file", filename=filename, data=file) async with client.post( f"{cls.url}/api/upload", - data=data, + multipart=multipart, headers={ "X-Upload-Nonce": upload_nonce, }, @@ -146,7 +142,7 @@ class You(AsyncGeneratorProvider, ProviderModelMixin): return result @classmethod - async def get_cookies(cls, client: ClientSession) -> Cookies: + async def get_cookies(cls, client: StreamSession) -> Cookies: if not cls._cookies or cls._cookies_used >= 5: cls._cookies = await cls.create_cookies(client) cls._cookies_used = 0 @@ -173,7 +169,7 @@ class You(AsyncGeneratorProvider, ProviderModelMixin): return f"Basic {auth}" @classmethod - async def create_cookies(cls, client: ClientSession) -> Cookies: + async def create_cookies(cls, client: StreamSession) -> Cookies: user_uuid = str(uuid.uuid4()) async with client.post( "https://web.stytch.com/sdk/v1/passwords", diff --git a/g4f/Provider/needs_auth/OpenaiChat.py b/g4f/Provider/needs_auth/OpenaiChat.py index b501d655..6601f500 100644 --- a/g4f/Provider/needs_auth/OpenaiChat.py +++ b/g4f/Provider/needs_auth/OpenaiChat.py @@ -572,7 +572,7 @@ this.fetch = async (url, options) => { while headers is None: headers = window.evaluate_js("this._headers") await asyncio.sleep(1) - headers["User-Agent"] = window.evaluate_js("window.navigator.userAgent") + headers["User-Agent"] = window.evaluate_js("this.navigator.userAgent") cookies = [list(*cookie.items()) for cookie in window.get_cookies()] window.destroy() cls._cookies = dict([(name, cookie.value) for name, cookie in cookies]) diff --git a/g4f/debug.py b/g4f/debug.py index 69f7d55c..3f08e9e8 100644 --- a/g4f/debug.py +++ b/g4f/debug.py @@ -3,4 +3,5 @@ from .providers.types import ProviderType logging: bool = False version_check: bool = True last_provider: ProviderType = None -last_model: str = None \ No newline at end of file +last_model: str = None +version: str = None \ No newline at end of file diff --git a/g4f/gui/client/js/chat.v1.js b/g4f/gui/client/js/chat.v1.js index 24253b2b..c0d0cb6d 100644 --- a/g4f/gui/client/js/chat.v1.js +++ b/g4f/gui/client/js/chat.v1.js @@ -916,16 +916,16 @@ fileInput.addEventListener('change', async (event) => { reader.addEventListener('load', async (event) => { fileInput.dataset.text = event.target.result; if (type == "json") { - const data = JSON.parse(event.target.result); + const data = JSON.parse(fileInput.dataset.text); if ("g4f" in data.options) { Object.keys(data).forEach(key => { if (key != "options" && !localStorage.getItem(key)) { appStorage.setItem(key, JSON.stringify(data[key])); } }); - fileInput.value = ""; delete fileInput.dataset.text; await load_conversations(); + fileInput.value = ""; } } }); diff --git a/g4f/gui/server/app.py b/g4f/gui/server/app.py index 2b55698c..d9e25ca1 100644 --- a/g4f/gui/server/app.py +++ b/g4f/gui/server/app.py @@ -1,3 +1,9 @@ +import sys, os from flask import Flask -app = Flask(__name__, template_folder='./../client/html') \ No newline at end of file +if getattr(sys, 'frozen', False): + template_folder = os.path.join(sys._MEIPASS, "client/html") +else: + template_folder = "./../client/html" + +app = Flask(__name__, template_folder=template_folder) \ No newline at end of file diff --git a/g4f/gui/server/website.py b/g4f/gui/server/website.py index 2705664d..9aa98b76 100644 --- a/g4f/gui/server/website.py +++ b/g4f/gui/server/website.py @@ -1,6 +1,12 @@ from flask import render_template, send_file, redirect from time import time from os import urandom +import sys, os + +if getattr(sys, 'frozen', False): + assets_folder = os.path.join(sys._MEIPASS, "client") +else: + assets_folder = "./../client" class Website: def __init__(self, app) -> None: @@ -35,6 +41,6 @@ class Website: def _assets(self, folder: str, file: str): try: - return send_file(f"./../client/{folder}/{file}", as_attachment=False) + return send_file(f"{assets_folder}/{folder}/{file}", as_attachment=False) except: return "File not found", 404 \ No newline at end of file diff --git a/g4f/providers/base_provider.py b/g4f/providers/base_provider.py index 32a0c01b..c8397193 100644 --- a/g4f/providers/base_provider.py +++ b/g4f/providers/base_provider.py @@ -70,7 +70,14 @@ class AbstractProvider(BaseProvider): loop.run_in_executor(executor, create_func), timeout=kwargs.get("timeout") ) - + + def get_parameters(cls) -> dict: + return signature( + cls.create_async_generator if issubclass(cls, AsyncGeneratorProvider) else + cls.create_async if issubclass(cls, AsyncProvider) else + cls.create_completion + ).parameters + @classmethod @property def params(cls) -> str: @@ -83,17 +90,12 @@ class AbstractProvider(BaseProvider): Returns: str: A string listing the supported parameters. """ - sig = signature( - cls.create_async_generator if issubclass(cls, AsyncGeneratorProvider) else - cls.create_async if issubclass(cls, AsyncProvider) else - cls.create_completion - ) def get_type_name(annotation: type) -> str: return annotation.__name__ if hasattr(annotation, "__name__") else str(annotation) args = "" - for name, param in sig.parameters.items(): + for name, param in cls.get_parameters(): if name in ("self", "kwargs") or (name == "stream" and not cls.supports_stream): continue args += f"\n {name}" diff --git a/g4f/requests/__init__.py b/g4f/requests/__init__.py index 3bb68e9c..cfc6af42 100644 --- a/g4f/requests/__init__.py +++ b/g4f/requests/__init__.py @@ -12,11 +12,40 @@ except ImportError: from typing import Type as Session, Type as Response from .aiohttp import StreamResponse, StreamSession has_curl_cffi = False +try: + import webview + import asyncio + has_webview = True +except ImportError: + has_webview = False from ..webdriver import WebDriver, WebDriverSession from ..webdriver import bypass_cloudflare, get_driver_cookies from ..errors import MissingRequirementsError, RateLimitError, ResponseStatusError -from .defaults import DEFAULT_HEADERS +from .defaults import DEFAULT_HEADERS, WEBVIEW_HAEDERS + +async def get_args_from_webview(url: str): + if not has_webview: + raise MissingRequirementsError('Install "webview" package') + window = webview.create_window("", url, hidden=True) + await asyncio.sleep(2) + body = None + while body is None: + try: + await asyncio.sleep(1) + body = window.dom.get_element("body:not(.no-js)") + except: + ... + headers = { + **WEBVIEW_HAEDERS, + "User-Agent": window.evaluate_js("this.navigator.userAgent"), + "Accept-Language": window.evaluate_js("this.navigator.language"), + "Referer": window.real_url + } + cookies = [list(*cookie.items()) for cookie in window.get_cookies()] + cookies = dict([(name, cookie.value) for name, cookie in cookies]) + window.destroy() + return {"headers": headers, "cookies": cookies} def get_args_from_browser( url: str, diff --git a/g4f/requests/aiohttp.py b/g4f/requests/aiohttp.py index 6979b20a..505086a1 100644 --- a/g4f/requests/aiohttp.py +++ b/g4f/requests/aiohttp.py @@ -1,6 +1,6 @@ from __future__ import annotations -from aiohttp import ClientSession, ClientResponse, ClientTimeout, BaseConnector +from aiohttp import ClientSession, ClientResponse, ClientTimeout, BaseConnector, FormData from typing import AsyncIterator, Any, Optional from .defaults import DEFAULT_HEADERS @@ -43,4 +43,8 @@ def get_connector(connector: BaseConnector = None, proxy: str = None, rdns: bool connector = ProxyConnector.from_url(proxy, rdns=rdns) except ImportError: raise MissingRequirementsError('Install "aiohttp_socks" package for proxy support') - return connector \ No newline at end of file + return connector + +class CurlMime(FormData): + def addpart(self, name: str, content_type: str = None, filename: str = None, data: bytes = None): + self.add_field(name, data, content_type=content_type, filename=filename) \ No newline at end of file diff --git a/g4f/requests/defaults.py b/g4f/requests/defaults.py index 2457f046..3183eb5a 100644 --- a/g4f/requests/defaults.py +++ b/g4f/requests/defaults.py @@ -16,4 +16,14 @@ DEFAULT_HEADERS = { "referer": "", "accept-encoding": "gzip, deflate, br", "accept-language": "en-US", +} +WEBVIEW_HAEDERS = { + "Accept": "*/*", + "Accept-Encoding": "gzip, deflate, br", + "Accept-Language": "", + "Referer": "", + "Sec-Fetch-Dest": "empty", + "Sec-Fetch-Mode": "cors", + "Sec-Fetch-Site": "same-origin", + "User-Agent": "", } \ No newline at end of file diff --git a/g4f/version.py b/g4f/version.py index 0d85a7f5..eda2b8fe 100644 --- a/g4f/version.py +++ b/g4f/version.py @@ -6,6 +6,7 @@ from functools import cached_property from importlib.metadata import version as get_package_version, PackageNotFoundError from subprocess import check_output, CalledProcessError, PIPE from .errors import VersionNotFoundError +from . import debug PACKAGE_NAME = "g4f" GITHUB_REPOSITORY = "xtekky/gpt4free" @@ -64,6 +65,9 @@ class VersionUtils: VersionNotFoundError: If the version cannot be determined from the package manager, Docker environment, or git repository. """ + if debug.version: + return debug.version + # Read from package manager try: return get_package_version(PACKAGE_NAME) diff --git a/requirements.txt b/requirements.txt index 1a7ded34..def8c7e3 100644 --- a/requirements.txt +++ b/requirements.txt @@ -15,7 +15,6 @@ fastapi uvicorn flask py-arkose-generator -async-property undetected-chromedriver>=3.5.5 brotli beautifulsoup4