Add Pyinstaller support, Use curl_cffi in You provider

This commit is contained in:
Heiner Lohaus 2024-03-15 11:46:06 +01:00
parent 95b1b8c025
commit 8cc6000ffb
13 changed files with 106 additions and 50 deletions

View file

@ -406,14 +406,13 @@ async def stream_generate(
except Exception as e: except Exception as e:
if debug.logging: if debug.logging:
print(f"Bing: Failed to create images: {e}") print(f"Bing: Failed to create images: {e}")
response_txt += f"\nhttps://www.bing.com/images/create?q={parse.quote(prompt)}" image_response = f"\nhttps://www.bing.com/images/create?q={parse.quote(prompt)}"
do_read = False
if response_txt.startswith(returned_text): if response_txt.startswith(returned_text):
new = response_txt[len(returned_text):] new = response_txt[len(returned_text):]
if new not in ("", "\n"): if new not in ("", "\n"):
yield new yield new
returned_text = response_txt returned_text = response_txt
if image_response: if image_response is not None:
yield image_response yield image_response
elif response.get('type') == 2: elif response.get('type') == 2:
result = response['item']['result'] result = response['item']['result']

View file

@ -4,14 +4,18 @@ import re
import json import json
import base64 import base64
import uuid import uuid
from asyncio import get_running_loop try:
from aiohttp import ClientSession, FormData, BaseConnector, CookieJar from curl_cffi import CurlMime
has_curl_cffi = True
except ImportError:
has_curl_cffi = False
from ..typing import AsyncResult, Messages, ImageType, Cookies from ..typing import AsyncResult, Messages, ImageType, Cookies
from .base_provider import AsyncGeneratorProvider, ProviderModelMixin 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 ..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): class You(AsyncGeneratorProvider, ProviderModelMixin):
url = "https://you.com" url = "https://you.com"
@ -33,8 +37,6 @@ class You(AsyncGeneratorProvider, ProviderModelMixin):
model_aliases = { model_aliases = {
"claude-v2": "claude-2" "claude-v2": "claude-2"
} }
_args: dict = None
_cookie_jar: CookieJar = None
_cookies = None _cookies = None
_cookies_used = 0 _cookies_used = 0
@ -45,19 +47,12 @@ class You(AsyncGeneratorProvider, ProviderModelMixin):
messages: Messages, messages: Messages,
image: ImageType = None, image: ImageType = None,
image_name: str = None, image_name: str = None,
connector: BaseConnector = None,
webdriver: WebDriver = None,
proxy: str = None, proxy: str = None,
chat_mode: str = "default", chat_mode: str = "default",
**kwargs, **kwargs,
) -> AsyncResult: ) -> AsyncResult:
if cls._args is None: if not has_curl_cffi:
cls._args = get_args_from_browser(cls.url, webdriver, proxy) raise MissingRequirementsError('Install "curl_cffi" package')
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 image is not None: if image is not None:
chat_mode = "agent" chat_mode = "agent"
elif not model or model == cls.default_model: elif not model or model == cls.default_model:
@ -67,10 +62,9 @@ class You(AsyncGeneratorProvider, ProviderModelMixin):
else: else:
chat_mode = "custom" chat_mode = "custom"
model = cls.get_model(model) model = cls.get_model(model)
async with ClientSession( async with StreamSession(
connector=get_connector(connector, proxy), proxy=proxy,
cookie_jar=cls._cookie_jar, impersonate="chrome"
**cls._args
) as session: ) as session:
cookies = await cls.get_cookies(session) if chat_mode != "default" else None 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 "" 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) # and idx < len(questions)
# ] # ]
headers = { headers = {
"accept": "text/event-stream", "Accept": "text/event-stream",
"referer": f"{cls.url}/search?fromSearchBar=true&tbm=youchat", "Referer": f"{cls.url}/search?fromSearchBar=true&tbm=youchat",
} }
data = { data = {
"userFiles": upload, "userFiles": upload,
@ -106,12 +100,12 @@ class You(AsyncGeneratorProvider, ProviderModelMixin):
cookies=cookies cookies=cookies
) as response: ) as response:
await raise_for_status(response) await raise_for_status(response)
async for line in response.content: async for line in response.iter_lines():
if line.startswith(b'event: '): if line.startswith(b'event: '):
event = line[7:-1].decode() event = line[7:].decode()
elif line.startswith(b'data: '): elif line.startswith(b'data: '):
if event in ["youChatUpdate", "youChatToken"]: if event in ["youChatUpdate", "youChatToken"]:
data = json.loads(line[6:-1]) data = json.loads(line[6:])
if event == "youChatToken" and event in data: if event == "youChatToken" and event in data:
yield data[event] yield data[event]
elif event == "youChatUpdate" and "t" in data: elif event == "youChatUpdate" and "t" in data:
@ -122,18 +116,20 @@ class You(AsyncGeneratorProvider, ProviderModelMixin):
yield data["t"] yield data["t"]
@classmethod @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( async with client.get(
f"{cls.url}/api/get_nonce", f"{cls.url}/api/get_nonce",
cookies=cookies, cookies=cookies,
) as response: ) as response:
await raise_for_status(response) await raise_for_status(response)
upload_nonce = await response.text() upload_nonce = await response.text()
data = FormData() #data = FormData()
data.add_field('file', file, filename=filename) #data.add_field('file', file, filename=filename)
multipart = CurlMime()
multipart.addpart(name="file", filename=filename, data=file)
async with client.post( async with client.post(
f"{cls.url}/api/upload", f"{cls.url}/api/upload",
data=data, multipart=multipart,
headers={ headers={
"X-Upload-Nonce": upload_nonce, "X-Upload-Nonce": upload_nonce,
}, },
@ -146,7 +142,7 @@ class You(AsyncGeneratorProvider, ProviderModelMixin):
return result return result
@classmethod @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: if not cls._cookies or cls._cookies_used >= 5:
cls._cookies = await cls.create_cookies(client) cls._cookies = await cls.create_cookies(client)
cls._cookies_used = 0 cls._cookies_used = 0
@ -173,7 +169,7 @@ class You(AsyncGeneratorProvider, ProviderModelMixin):
return f"Basic {auth}" return f"Basic {auth}"
@classmethod @classmethod
async def create_cookies(cls, client: ClientSession) -> Cookies: async def create_cookies(cls, client: StreamSession) -> Cookies:
user_uuid = str(uuid.uuid4()) user_uuid = str(uuid.uuid4())
async with client.post( async with client.post(
"https://web.stytch.com/sdk/v1/passwords", "https://web.stytch.com/sdk/v1/passwords",

View file

@ -572,7 +572,7 @@ this.fetch = async (url, options) => {
while headers is None: while headers is None:
headers = window.evaluate_js("this._headers") headers = window.evaluate_js("this._headers")
await asyncio.sleep(1) 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()] cookies = [list(*cookie.items()) for cookie in window.get_cookies()]
window.destroy() window.destroy()
cls._cookies = dict([(name, cookie.value) for name, cookie in cookies]) cls._cookies = dict([(name, cookie.value) for name, cookie in cookies])

View file

@ -3,4 +3,5 @@ from .providers.types import ProviderType
logging: bool = False logging: bool = False
version_check: bool = True version_check: bool = True
last_provider: ProviderType = None last_provider: ProviderType = None
last_model: str = None last_model: str = None
version: str = None

View file

@ -916,16 +916,16 @@ fileInput.addEventListener('change', async (event) => {
reader.addEventListener('load', async (event) => { reader.addEventListener('load', async (event) => {
fileInput.dataset.text = event.target.result; fileInput.dataset.text = event.target.result;
if (type == "json") { if (type == "json") {
const data = JSON.parse(event.target.result); const data = JSON.parse(fileInput.dataset.text);
if ("g4f" in data.options) { if ("g4f" in data.options) {
Object.keys(data).forEach(key => { Object.keys(data).forEach(key => {
if (key != "options" && !localStorage.getItem(key)) { if (key != "options" && !localStorage.getItem(key)) {
appStorage.setItem(key, JSON.stringify(data[key])); appStorage.setItem(key, JSON.stringify(data[key]));
} }
}); });
fileInput.value = "";
delete fileInput.dataset.text; delete fileInput.dataset.text;
await load_conversations(); await load_conversations();
fileInput.value = "";
} }
} }
}); });

View file

@ -1,3 +1,9 @@
import sys, os
from flask import Flask from flask import Flask
app = Flask(__name__, template_folder='./../client/html') 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)

View file

@ -1,6 +1,12 @@
from flask import render_template, send_file, redirect from flask import render_template, send_file, redirect
from time import time from time import time
from os import urandom 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: class Website:
def __init__(self, app) -> None: def __init__(self, app) -> None:
@ -35,6 +41,6 @@ class Website:
def _assets(self, folder: str, file: str): def _assets(self, folder: str, file: str):
try: try:
return send_file(f"./../client/{folder}/{file}", as_attachment=False) return send_file(f"{assets_folder}/{folder}/{file}", as_attachment=False)
except: except:
return "File not found", 404 return "File not found", 404

View file

@ -70,7 +70,14 @@ class AbstractProvider(BaseProvider):
loop.run_in_executor(executor, create_func), loop.run_in_executor(executor, create_func),
timeout=kwargs.get("timeout") 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 @classmethod
@property @property
def params(cls) -> str: def params(cls) -> str:
@ -83,17 +90,12 @@ class AbstractProvider(BaseProvider):
Returns: Returns:
str: A string listing the supported parameters. 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: def get_type_name(annotation: type) -> str:
return annotation.__name__ if hasattr(annotation, "__name__") else str(annotation) return annotation.__name__ if hasattr(annotation, "__name__") else str(annotation)
args = "" 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): if name in ("self", "kwargs") or (name == "stream" and not cls.supports_stream):
continue continue
args += f"\n {name}" args += f"\n {name}"

View file

@ -12,11 +12,40 @@ except ImportError:
from typing import Type as Session, Type as Response from typing import Type as Session, Type as Response
from .aiohttp import StreamResponse, StreamSession from .aiohttp import StreamResponse, StreamSession
has_curl_cffi = False 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 WebDriver, WebDriverSession
from ..webdriver import bypass_cloudflare, get_driver_cookies from ..webdriver import bypass_cloudflare, get_driver_cookies
from ..errors import MissingRequirementsError, RateLimitError, ResponseStatusError 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( def get_args_from_browser(
url: str, url: str,

View file

@ -1,6 +1,6 @@
from __future__ import annotations 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 typing import AsyncIterator, Any, Optional
from .defaults import DEFAULT_HEADERS 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) connector = ProxyConnector.from_url(proxy, rdns=rdns)
except ImportError: except ImportError:
raise MissingRequirementsError('Install "aiohttp_socks" package for proxy support') raise MissingRequirementsError('Install "aiohttp_socks" package for proxy support')
return connector 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)

View file

@ -16,4 +16,14 @@ DEFAULT_HEADERS = {
"referer": "", "referer": "",
"accept-encoding": "gzip, deflate, br", "accept-encoding": "gzip, deflate, br",
"accept-language": "en-US", "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": "",
} }

View file

@ -6,6 +6,7 @@ from functools import cached_property
from importlib.metadata import version as get_package_version, PackageNotFoundError from importlib.metadata import version as get_package_version, PackageNotFoundError
from subprocess import check_output, CalledProcessError, PIPE from subprocess import check_output, CalledProcessError, PIPE
from .errors import VersionNotFoundError from .errors import VersionNotFoundError
from . import debug
PACKAGE_NAME = "g4f" PACKAGE_NAME = "g4f"
GITHUB_REPOSITORY = "xtekky/gpt4free" GITHUB_REPOSITORY = "xtekky/gpt4free"
@ -64,6 +65,9 @@ class VersionUtils:
VersionNotFoundError: If the version cannot be determined from the package manager, VersionNotFoundError: If the version cannot be determined from the package manager,
Docker environment, or git repository. Docker environment, or git repository.
""" """
if debug.version:
return debug.version
# Read from package manager # Read from package manager
try: try:
return get_package_version(PACKAGE_NAME) return get_package_version(PACKAGE_NAME)

View file

@ -15,7 +15,6 @@ fastapi
uvicorn uvicorn
flask flask
py-arkose-generator py-arkose-generator
async-property
undetected-chromedriver>=3.5.5 undetected-chromedriver>=3.5.5
brotli brotli
beautifulsoup4 beautifulsoup4