Fixed trademarks

This commit is contained in:
nsde 2023-06-28 15:21:14 +02:00
parent 6aa22e8c55
commit c0a797599b
10 changed files with 328 additions and 664 deletions

View file

@ -1,5 +1,5 @@
# ☄️ Nova API Server # ☄️ Nova API Server
Reverse proxy server for OpenAI's API. Reverse proxy server for "Closed"AI's API.
## Install ## Install
Assuming you have a new version of Python 3 and pip installed: Assuming you have a new version of Python 3 and pip installed:
@ -28,7 +28,7 @@ pip install .
## `.env` configuration ## `.env` configuration
### `ACTUAL_IPS` (optional) ### `ACTUAL_IPS` (optional)
This is a security measure to make sure a proxy, VPN, Tor or any other IP hiding service is used by the host when accessing OpenAI's API. This is a security measure to make sure a proxy, VPN, Tor or any other IP hiding service is used by the host when accessing "Closed"AI's API.
It is a space separated list of IP addresses that are allowed to access the API. It is a space separated list of IP addresses that are allowed to access the API.
You can also just add the *beginning* of an API address, like `12.123.` to allow all IPs starting with `12.123.`. You can also just add the *beginning* of an API address, like `12.123.` to allow all IPs starting with `12.123.`.

View file

@ -1,19 +1,16 @@
import os import os
import httpx
import fastapi import fastapi
from keys import Keys
from starlette.requests import Request
from starlette.responses import StreamingResponse from starlette.responses import StreamingResponse
from starlette.background import BackgroundTask from starlette.requests import Request
from fastapi.middleware.cors import CORSMiddleware from fastapi.middleware.cors import CORSMiddleware
from dotenv import load_dotenv from dotenv import load_dotenv
import security import security
import transfer
load_dotenv() load_dotenv()
target_api_client = httpx.AsyncClient(base_url='https://api.openai.com/')
app = fastapi.FastAPI() app = fastapi.FastAPI()
@ -32,9 +29,6 @@ async def startup_event():
security.enable_proxy() security.enable_proxy()
security.ip_protection_check() security.ip_protection_check()
# Setup key cache
Keys()
@app.get('/') @app.get('/')
async def root(): async def root():
"""Returns the root endpoint.""" """Returns the root endpoint."""
@ -46,34 +40,12 @@ async def root():
} }
async def _reverse_proxy(request: Request): async def _reverse_proxy(request: Request):
target_url = f'https://api.openai.com/v1/{request.url.path}' headers = {
key = Keys.get(request.body()['model']) name: value
if not key: for name, value in target_response.headers.items()
return fastapi.responses.JSONResponse( if name.lower() not in EXCLUDED_HEADERS
status_code=400,
content={
'error': 'No API Key for model given, please try again with a valid model.'
} }
)
request_to_api = target_api_client.build_request(
method=request.method,
url=target_url,
headers={
'Authorization': 'Bearer ' + key,
'Content-Type': 'application/json'
},
content=await request.body(),
)
api_response = await target_api_client.send(request_to_api, stream=True) # ...
print(f'[{request.method}] {request.url.path} {api_response.status_code}')
Keys(key).unlock()
return StreamingResponse(
api_response.aiter_raw(),
status_code=api_response.status_code,
headers=api_response.headers,
background=BackgroundTask(api_response.aclose)
)
app.add_route('/{path:path}', _reverse_proxy, ['GET', 'POST', 'PUT', 'DELETE', 'PATCH']) app.add_route('/{path:path}', _reverse_proxy, ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'])

119
api/proxies.py Normal file
View file

@ -0,0 +1,119 @@
"""This module contains the Proxy class, which represents a proxy."""
import os
import httpx
import socket
import asyncio
from sockslib import socks
from rich import print
from dotenv import load_dotenv
load_dotenv()
class Proxy:
"""Represents a proxy. The type can be either http, https, socks4 or socks5."""
def __init__(self,
proxy_type: str='http',
host: str='127.0.0.1',
port: int=8080,
username: str=None,
password: str=None
):
self.proxy_type = proxy_type
self.ip_address = socket.gethostbyname(host)
self.host = host
self.port = port
self.username = username
self.password = password
@property
def auth(self):
"""Returns the authentication part of the proxy URL, if the proxy has a username and password."""
return f'{self.username}:{self.password}@' if all([self.username, self.password]) else ''
@property
def protocol(self):
"""Makes sure the hostnames are resolved correctly using the proxy.
See https://stackoverflow.com/a/43266186
"""
return self.proxy_type# + 'h' if self.proxy_type.startswith('socks') else self.proxy_type
def proxies(self):
"""Returns a dictionary of proxies, ready to be used with the requests library or httpx.
"""
url = f'{self.protocol}://{self.auth}{self.host}:{self.port}'
proxies_dict = {
'http://': url.replace('https', 'http') if self.proxy_type == 'https' else url,
'https://': url.replace('http', 'https') if self.proxy_type == 'http' else url
}
return proxies_dict
def __str__(self):
return f'{self.proxy_type}://{len(self.auth) * "*"}{self.host}:{self.port}'
def __repr__(self):
return f'<Proxy type={self.proxy_type} host={self.host} port={self.port} username={self.username} password={len(self.password) * "*"}>'
active_proxy = Proxy(
proxy_type=os.getenv('PROXY_TYPE', 'http'),
host=os.getenv('PROXY_HOST', '127.0.0.1'),
port=int(os.getenv('PROXY_PORT', 8080)),
username=os.getenv('PROXY_USER'),
password=os.getenv('PROXY_PASS')
)
def activate_proxy() -> None:
socks.set_default_proxy(
proxy_type=socks.PROXY_TYPES[active_proxy.proxy_type.upper()],
addr=active_proxy.host,
port=active_proxy.port,
username=active_proxy.username,
password=active_proxy.password
)
socket.socket = socks.socksocket
def check_proxy():
"""Checks if the proxy is working."""
resp = httpx.get(
url='https://echo.hoppscotch.io/',
timeout=20,
proxies=active_proxy.proxies()
)
resp.raise_for_status()
return resp.ok
async def check_api():
model = 'gpt-3.5-turbo'
messages = [
{
'role': 'user',
'content': 'Explain what a wormhole is.'
},
]
headers = {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + os.getenv('CLOSEDAI_KEY')
}
json_data = {
'model': model,
'messages': messages,
'stream': True
}
async with httpx.AsyncClient(timeout=20) as client:
async with client.stream("POST", 'https://api.openai.com/v1/chat/completions', headers=headers, json=json_data) as response:
response.raise_for_status()
async for chunk in response.aiter_text():
print(chunk.strip())
if __name__ == '__main__':
asyncio.run(check_api())

View file

@ -1,41 +1,26 @@
import os import os
import socket
import httpx import httpx
from sockslib import socks
from rich import print from rich import print
import proxies
from dotenv import load_dotenv
load_dotenv()
is_proxy_enabled = False is_proxy_enabled = False
def enable_proxy(): def enable_proxy():
"""Enables the SOCKS5 proxy.""" """Enables the proxy."""
global is_proxy_enabled global is_proxy_enabled
if all([os.getenv('PROXY_HOST'), os.getenv('PROXY_PORT')]): proxies.activate_proxy()
proxy_type = socks.PROXY_TYPE_HTTP
if '4' in os.getenv('PROXY_TYPE'): print(f'[green]SUCCESS: Proxy enabled: {proxies.active_proxy}[/green]')
proxy_type = socks.PROXY_TYPE_SOCKS4
if '5' in os.getenv('PROXY_TYPE'):
proxy_type = socks.PROXY_TYPE_SOCKS5
socks.set_default_proxy(
proxy_type=proxy_type,
addr=os.getenv('PROXY_HOST'),
port=int(os.getenv('PROXY_PORT')),
username=os.getenv('PROXY_USER'),
password=os.getenv('PROXY_PASS')
)
socket.socket = socks.socksocket
is_proxy_enabled = True is_proxy_enabled = True
else:
print('[yellow]WARNING: PROXY_PORT, PROXY_IP, PROXY_USER, and PROXY_PASS are not set in the .env file or empty. \
Consider configuring a SOCKS5 proxy to improve the security.[/yellow]')
class InsecureIPError(Exception): class InsecureIPError(Exception):
"""Raised when the IP address of the server is not secure.""" """Raised when the IP address of the server is not secure."""
@ -45,17 +30,27 @@ def ip_protection_check():
actual_ips = os.getenv('ACTUAL_IPS', '').split() actual_ips = os.getenv('ACTUAL_IPS', '').split()
if actual_ips: if actual_ips:
detected_ip = httpx.get('https://checkip.amazonaws.com', timeout=5).text.strip() echo_response = httpx.get(
url='https://echo.hoppscotch.io/',
timeout=15
)
response_data = echo_response.json()
response_ip = response_data['headers']['x-forwarded-for']
for actual_ip in actual_ips: for actual_ip in actual_ips:
if detected_ip.startswith(actual_ip): if actual_ip in response_data:
raise InsecureIPError(f'IP {detected_ip} is in the values of ACTUAL_IPS of the\ raise InsecureIPError(f'IP pattern "{actual_ip}" is in the values of ACTUAL_IPS of the\
.env file. Enable a VPN or proxy to continue.') .env file. Enable a VPN or proxy to continue.')
if is_proxy_enabled: if is_proxy_enabled:
print(f'[green]SUCCESS: The IP {detected_ip} was detected, which seems to be a proxy. Great![/green]') print(f'[green]SUCCESS: The IP "{response_ip}" was detected, which seems to be a proxy. Great![/green]')
else: else:
print('[yellow]WARNING: ACTUAL_IPS is not set in the .env file or empty.\ print('[yellow]WARNING: ACTUAL_IPS is not set in the .env file or empty.\
This means that the real IP of the server could be exposed. If you\'re using something\ This means that the real IP of the server could be exposed. If you\'re using something\
like Cloudflare or Repl.it, you can ignore this warning.[/yellow]') like Cloudflare or Repl.it, you can ignore this warning.[/yellow]')
if __name__ == '__main__':
enable_proxy()
ip_protection_check()

View file

@ -1,8 +1,8 @@
""" """
SocksiPy - Python SOCKS module. THE FOLLOWING CODE WAS TAKEN FROM https://raw.githubusercontent.com/m0rtem/CloudFail/master/socks.py
Version 1.5.1
Code from https://github.com/XX-net/XX-Net/blob/master/code/default/lib/noarch/socks.py SocksiPy - Python SOCKS module.
Version 1.5.7
Copyright 2006 Dan-Haim. All rights reserved. Copyright 2006 Dan-Haim. All rights reserved.
@ -54,44 +54,28 @@ Modifications made by Anorov (https://github.com/Anorov)
-Various small bug fixes -Various small bug fixes
""" """
__version__ = "1.5.1" __version__ = "1.5.7"
import os, sys
from base64 import b64encode
import socket import socket
import struct import struct
from errno import EOPNOTSUPP, EINVAL, EAGAIN from errno import EOPNOTSUPP, EINVAL, EAGAIN
from io import BytesIO, SEEK_CUR from io import BytesIO
from os import SEEK_CUR
from base64 import b64encode
try: try:
from collections import Callable
except:
from collections.abc import Callable from collections.abc import Callable
except ImportError:
from six import string_types from collections import Callable
current_path = os.path.dirname(os.path.abspath(__file__))
python_path = os.path.abspath( os.path.join(current_path, os.pardir, os.pardir))
if sys.platform == "win32":
win32_lib = os.path.abspath( os.path.join(python_path, 'lib', 'win32'))
sys.path.append(win32_lib)
import win_inet_pton
inet_pton = win_inet_pton.inet_pton
inet_ntop = win_inet_pton.inet_ntop
else:
inet_pton = socket.inet_pton
inet_ntop = socket.inet_ntop
from . import utils
PROXY_TYPE_SOCKS4 = SOCKS4 = 1 PROXY_TYPE_SOCKS4 = SOCKS4 = 1
PROXY_TYPE_SOCKS5 = SOCKS5 = 2 PROXY_TYPE_SOCKS5 = SOCKS5 = 2
PROXY_TYPE_HTTP = HTTP = 3 PROXY_TYPE_HTTP = HTTP = 3
PRINTABLE_PROXY_TYPES = {SOCKS4: "SOCKS4", SOCKS5: "SOCKS5", HTTP: "HTTP"} PROXY_TYPES = {"SOCKS4": SOCKS4, "SOCKS5": SOCKS5, "HTTP": HTTP}
PRINTABLE_PROXY_TYPES = dict(zip(PROXY_TYPES.values(), PROXY_TYPES.keys()))
_orgsocket = _orig_socket = socket.socket _orgsocket = _orig_socket = socket.socket
class ProxyError(IOError): class ProxyError(IOError):
""" """
socket_err contains original socket.error exception. socket_err contains original socket.error exception.
@ -106,11 +90,6 @@ class ProxyError(IOError):
def __str__(self): def __str__(self):
return self.msg return self.msg
def __repr__(self):
# for %r
return repr(self.msg)
class GeneralProxyError(ProxyError): pass class GeneralProxyError(ProxyError): pass
class ProxyConnectionError(ProxyError): pass class ProxyConnectionError(ProxyError): pass
class SOCKS5AuthError(ProxyError): pass class SOCKS5AuthError(ProxyError): pass
@ -118,7 +97,6 @@ class SOCKS5Error(ProxyError): pass
class SOCKS4Error(ProxyError): pass class SOCKS4Error(ProxyError): pass
class HTTPError(ProxyError): pass class HTTPError(ProxyError): pass
SOCKS4_ERRORS = { 0x5B: "Request rejected or failed", SOCKS4_ERRORS = { 0x5B: "Request rejected or failed",
0x5C: "Request rejected because SOCKS server cannot connect to identd on the client", 0x5C: "Request rejected because SOCKS server cannot connect to identd on the client",
0x5D: "Request rejected because the client program and identd report different user-ids" 0x5D: "Request rejected because the client program and identd report different user-ids"
@ -139,7 +117,6 @@ DEFAULT_PORTS = { SOCKS4: 1080,
HTTP: 8080 HTTP: 8080
} }
def set_default_proxy(proxy_type=None, addr=None, port=None, rdns=True, username=None, password=None): def set_default_proxy(proxy_type=None, addr=None, port=None, rdns=True, username=None, password=None):
""" """
set_default_proxy(proxy_type, addr[, port[, rdns[, username, password]]]) set_default_proxy(proxy_type, addr[, port[, rdns[, username, password]]])
@ -147,44 +124,20 @@ def set_default_proxy(proxy_type=None, addr=None, port=None, rdns=True, username
Sets a default proxy which all further socksocket objects will use, Sets a default proxy which all further socksocket objects will use,
unless explicitly changed. All parameters are as for socket.set_proxy(). unless explicitly changed. All parameters are as for socket.set_proxy().
""" """
proxy_type = utils.bytes2str_only(proxy_type)
addr = utils.to_str(addr)
if isinstance(port, bytes):
port = int(utils.to_str(port))
else:
port = int(port)
username = utils.to_bytes(username)
password = utils.to_bytes(password)
if isinstance(proxy_type, str):
proxy_type = proxy_type.lower()
if "http" in proxy_type:
proxy_type = PROXY_TYPE_HTTP
elif "socks5" in proxy_type:
proxy_type = PROXY_TYPE_SOCKS5
elif "socks4" in proxy_type:
proxy_type = PROXY_TYPE_SOCKS4
else:
raise ProxyError("unknown proxy type:%s" % proxy_type)
socksocket.default_proxy = (proxy_type, addr, port, rdns, socksocket.default_proxy = (proxy_type, addr, port, rdns,
username if username else None, username.encode() if username else None,
password if password else None) password.encode() if password else None)
setdefaultproxy = set_default_proxy setdefaultproxy = set_default_proxy
def get_default_proxy(): def get_default_proxy():
""" """
Returns the default proxy, set by set_default_proxy. Returns the default proxy, set by set_default_proxy.
""" """
return socksocket.default_proxy return socksocket.default_proxy
getdefaultproxy = get_default_proxy getdefaultproxy = get_default_proxy
def wrap_module(module): def wrap_module(module):
""" """
Attempts to replace a module's socket library with a SOCKS socket. Must set Attempts to replace a module's socket library with a SOCKS socket. Must set
@ -199,27 +152,64 @@ def wrap_module(module):
wrapmodule = wrap_module wrapmodule = wrap_module
def create_connection(dest_pair, proxy_type=None, proxy_addr=None, def create_connection(dest_pair, proxy_type=None, proxy_addr=None,
proxy_port=None, proxy_username=None, proxy_port=None, proxy_rdns=True,
proxy_password=None, timeout=None): proxy_username=None, proxy_password=None,
timeout=None, source_address=None,
socket_options=None):
"""create_connection(dest_pair, *[, timeout], **proxy_args) -> socket object """create_connection(dest_pair, *[, timeout], **proxy_args) -> socket object
Like socket.create_connection(), but connects to proxy Like socket.create_connection(), but connects to proxy
before returning the socket object. before returning the socket object.
dest_pair - 2-tuple of (IP/hostname, port). dest_pair - 2-tuple of (IP/hostname, port).
**proxy_args - Same args passed to socksocket.set_proxy(). **proxy_args - Same args passed to socksocket.set_proxy() if present.
timeout - Optional socket timeout value, in seconds. timeout - Optional socket timeout value, in seconds.
source_address - tuple (host, port) for the socket to bind to as its source
address before connecting (only for compatibility)
""" """
sock = socksocket() # Remove IPv6 brackets on the remote address and proxy address.
remote_host, remote_port = dest_pair
if remote_host.startswith('['):
remote_host = remote_host.strip('[]')
if proxy_addr and proxy_addr.startswith('['):
proxy_addr = proxy_addr.strip('[]')
err = None
# Allow the SOCKS proxy to be on IPv4 or IPv6 addresses.
for r in socket.getaddrinfo(proxy_addr, proxy_port, 0, socket.SOCK_STREAM):
family, socket_type, proto, canonname, sa = r
sock = None
try:
sock = socksocket(family, socket_type, proto)
if socket_options is not None:
for opt in socket_options:
sock.setsockopt(*opt)
if isinstance(timeout, (int, float)): if isinstance(timeout, (int, float)):
sock.settimeout(timeout) sock.settimeout(timeout)
sock.set_proxy(proxy_type, proxy_addr, proxy_port,
if proxy_type is not None:
sock.set_proxy(proxy_type, proxy_addr, proxy_port, proxy_rdns,
proxy_username, proxy_password) proxy_username, proxy_password)
sock.connect(dest_pair) if source_address is not None:
sock.bind(source_address)
sock.connect((remote_host, remote_port))
return sock return sock
except socket.error as e:
err = e
if sock is not None:
sock.close()
sock = None
if err is not None:
raise err
raise socket.error("gai returned empty list.")
class _BaseSocket(socket.socket): class _BaseSocket(socket.socket):
"""Allows Python 2's "delegated" methods such as send() to be overridden """Allows Python 2's "delegated" methods such as send() to be overridden
@ -234,11 +224,8 @@ class _BaseSocket(socket.socket):
_savenames = list() _savenames = list()
def _makemethod(name): def _makemethod(name):
return lambda self, *pos, **kw: self._savedmethods[name](*pos, **kw) return lambda self, *pos, **kw: self._savedmethods[name](*pos, **kw)
for name in ("sendto", "send", "recvfrom", "recv"): for name in ("sendto", "send", "recvfrom", "recv"):
method = getattr(_BaseSocket, name, None) method = getattr(_BaseSocket, name, None)
@ -250,7 +237,6 @@ for name in ("sendto", "send", "recvfrom", "recv"):
_BaseSocket._savenames.append(name) _BaseSocket._savenames.append(name)
setattr(_BaseSocket, name, _makemethod(name)) setattr(_BaseSocket, name, _makemethod(name))
class socksocket(_BaseSocket): class socksocket(_BaseSocket):
"""socksocket([family[, type[, proto]]]) -> socket object """socksocket([family[, type[, proto]]]) -> socket object
@ -262,27 +248,18 @@ class socksocket(_BaseSocket):
default_proxy = None default_proxy = None
def __init__(self, family=socket.AF_INET, type=socket.SOCK_STREAM, proto=0, _sock=None): def __init__(self, family=socket.AF_INET, type=socket.SOCK_STREAM, proto=0, *args, **kwargs):
if type not in {socket.SOCK_STREAM, socket.SOCK_DGRAM}: if type not in (socket.SOCK_STREAM, socket.SOCK_DGRAM):
msg = "Socket type must be stream or datagram, not {!r}" msg = "Socket type must be stream or datagram, not {!r}"
raise ValueError(msg.format(type)) raise ValueError(msg.format(type))
_BaseSocket.__init__(self, family, type, proto, *args, **kwargs)
self._proxyconn = None # TCP connection to keep UDP relay alive self._proxyconn = None # TCP connection to keep UDP relay alive
self.resolve_dest = True
if self.default_proxy: if self.default_proxy:
self.proxy = self.default_proxy self.proxy = self.default_proxy
proxy_host = self.proxy[1]
if utils.check_ip_valid6(proxy_host):
family=socket.AF_INET6
elif utils.check_ip_valid4(proxy_host):
family=socket.AF_INET
else: else:
self.proxy = (None, None, None, None, None, None) self.proxy = (None, None, None, None, None, None)
_BaseSocket.__init__(self, family, type, proto, _sock)
self.proxy_sockname = None self.proxy_sockname = None
self.proxy_peername = None self.proxy_peername = None
@ -317,34 +294,9 @@ class socksocket(_BaseSocket):
password - Password to authenticate with to the server. password - Password to authenticate with to the server.
Only relevant when username is also provided. Only relevant when username is also provided.
""" """
proxy_type = utils.bytes2str_only(proxy_type)
addr = utils.to_str(addr)
if isinstance(port, bytes):
port = int(utils.to_str(port))
else:
port = int(port)
username = utils.to_bytes(username)
password = utils.to_bytes(password)
if isinstance(proxy_type, string_types):
proxy_type = proxy_type.lower()
if "http" in proxy_type:
proxy_type = PROXY_TYPE_HTTP
self.resolve_dest = False
elif "socks5" in proxy_type:
if proxy_type == "socks5h":
self.resolve_dest = False
rdns = True
proxy_type = PROXY_TYPE_SOCKS5
elif "socks4" in proxy_type:
proxy_type = PROXY_TYPE_SOCKS4
else:
raise ProxyError("unknown proxy type:%s" % proxy_type)
self.proxy = (proxy_type, addr, port, rdns, self.proxy = (proxy_type, addr, port, rdns,
username if username else None, username.encode() if username else None,
password if password else None) password.encode() if password else None)
setproxy = set_proxy setproxy = set_proxy
@ -559,26 +511,39 @@ class socksocket(_BaseSocket):
and the resolved address as a tuple object. and the resolved address as a tuple object.
""" """
host, port = addr host, port = addr
host = utils.to_str(host)
proxy_type, _, _, rdns, username, password = self.proxy proxy_type, _, _, rdns, username, password = self.proxy
family_to_byte = {socket.AF_INET: b"\x01", socket.AF_INET6: b"\x04"}
if utils.check_ip_valid6(host): # If the given destination address is an IP address, we'll
addr_bytes = inet_pton(socket.AF_INET6, host) # use the IP address request even if remote resolving was specified.
file.write(b"\x04" + addr_bytes) # Detect whether the address is IPv4/6 directly.
elif utils.check_ip_valid4(host): for family in (socket.AF_INET, socket.AF_INET6):
addr_bytes = socket.inet_aton(host) try:
file.write(b"\x01" + addr_bytes) addr_bytes = socket.inet_pton(family, host)
else: file.write(family_to_byte[family] + addr_bytes)
host = socket.inet_ntop(family, addr_bytes)
file.write(struct.pack(">H", port))
return host, port
except socket.error:
continue
# Well it's not an IP number, so it's probably a DNS name.
if rdns: if rdns:
# Resolve remotely # Resolve remotely
host_bytes = host.encode("utf-8") host_bytes = host.encode('idna')
file.write(b"\x03" + chr(len(host_bytes)).encode() + host_bytes) file.write(b"\x03" + chr(len(host_bytes)).encode() + host_bytes)
else: else:
# Resolve locally # Resolve locally
addr_bytes = socket.inet_aton(socket.gethostbyname(host)) addresses = socket.getaddrinfo(host, port, socket.AF_UNSPEC, socket.SOCK_STREAM, socket.IPPROTO_TCP, socket.AI_ADDRCONFIG)
file.write(b"\x01" + addr_bytes) # We can't really work out what IP is reachable, so just pick the
host = socket.inet_ntoa(addr_bytes) # first.
target_addr = addresses[0]
family = target_addr[0]
host = target_addr[4][0]
addr_bytes = socket.inet_pton(family, host)
file.write(family_to_byte[family] + addr_bytes)
host = socket.inet_ntop(family, addr_bytes)
file.write(struct.pack(">H", port)) file.write(struct.pack(">H", port))
return host, port return host, port
@ -590,7 +555,7 @@ class socksocket(_BaseSocket):
length = self._readall(file, 1) length = self._readall(file, 1)
addr = self._readall(file, ord(length)) addr = self._readall(file, ord(length))
elif atyp == b"\x04": elif atyp == b"\x04":
addr = inet_ntop(socket.AF_INET6, self._readall(file, 16)) addr = socket.inet_ntop(socket.AF_INET6, self._readall(file, 16))
else: else:
raise GeneralProxyError("SOCKS5 proxy server sent invalid data") raise GeneralProxyError("SOCKS5 proxy server sent invalid data")
@ -602,7 +567,6 @@ class socksocket(_BaseSocket):
Negotiates a connection through a SOCKS4 server. Negotiates a connection through a SOCKS4 server.
""" """
proxy_type, addr, port, rdns, username, password = self.proxy proxy_type, addr, port, rdns, username, password = self.proxy
dest_addr = utils.to_str(dest_addr)
writer = self.makefile("wb") writer = self.makefile("wb")
reader = self.makefile("rb", 0) # buffering=0 renamed in Python 3 reader = self.makefile("rb", 0) # buffering=0 renamed in Python 3
@ -611,7 +575,7 @@ class socksocket(_BaseSocket):
remote_resolve = False remote_resolve = False
try: try:
addr_bytes = socket.inet_aton(dest_addr) addr_bytes = socket.inet_aton(dest_addr)
except socket.error as e: except socket.error:
# It's a DNS name. Check where it should be resolved. # It's a DNS name. Check where it should be resolved.
if rdns: if rdns:
addr_bytes = b"\x00\x00\x00\x01" addr_bytes = b"\x00\x00\x00\x01"
@ -632,7 +596,7 @@ class socksocket(_BaseSocket):
# NOTE: This is actually an extension to the SOCKS4 protocol # NOTE: This is actually an extension to the SOCKS4 protocol
# called SOCKS4A and may not be supported in all cases. # called SOCKS4A and may not be supported in all cases.
if remote_resolve: if remote_resolve:
writer.write(dest_addr.encode('utf-8') + b"\x00") writer.write(dest_addr.encode('idna') + b"\x00")
writer.flush() writer.flush()
# Get the response from the server # Get the response from the server
@ -657,37 +621,30 @@ class socksocket(_BaseSocket):
reader.close() reader.close()
writer.close() writer.close()
def _negotiate_HTTP(self, dest_host, dest_port): def _negotiate_HTTP(self, dest_addr, dest_port):
""" """
Negotiates a connection through an HTTP server. Negotiates a connection through an HTTP server.
NOTE: This currently only supports HTTP CONNECT-style proxies. NOTE: This currently only supports HTTP CONNECT-style proxies.
""" """
proxy_type, proxy_addr, port, rdns, username, password = self.proxy proxy_type, addr, port, rdns, username, password = self.proxy
# If we need to resolve locally, we do this now # If we need to resolve locally, we do this now
dest_host = utils.to_bytes(dest_host) addr = dest_addr if rdns else socket.gethostbyname(dest_addr)
if b":" not in dest_host and not rdns:
dest_addr = socket.gethostbyname(dest_host)
dest_addr = utils.to_bytes(dest_addr)
else:
dest_addr = dest_host
http_headers = [ http_headers = [
(b"CONNECT " + utils.to_bytes(dest_addr) + b":" b"CONNECT " + addr.encode('idna') + b":" + str(dest_port).encode() + b" HTTP/1.1",
+ str(dest_port).encode() + b" HTTP/1.1"), b"Host: " + dest_addr.encode('idna')
b"Host: " + dest_addr
] ]
if username and password: if username and password:
http_headers.append(b"Proxy-Authorization: basic " http_headers.append(b"Proxy-Authorization: basic " + b64encode(username + b":" + password))
+ b64encode(username + b":" + password))
http_headers.append(b"\r\n") http_headers.append(b"\r\n")
self.sendall(b"\r\n".join(http_headers)) self.sendall(b"\r\n".join(http_headers))
# We just need the first line to check if the connection was successful # We just need the first line to check if the connection was successful
fobj = self.makefile("rb") fobj = self.makefile()
status_line = fobj.readline() status_line = fobj.readline()
fobj.close() fobj.close()
@ -695,11 +652,11 @@ class socksocket(_BaseSocket):
raise GeneralProxyError("Connection closed unexpectedly") raise GeneralProxyError("Connection closed unexpectedly")
try: try:
proto, status_code, status_msg = status_line.split(b" ", 2) proto, status_code, status_msg = status_line.split(" ", 2)
except ValueError: except ValueError:
raise GeneralProxyError("HTTP proxy server sent invalid response") raise GeneralProxyError("HTTP proxy server sent invalid response")
if not proto.startswith(b"HTTP/"): if not proto.startswith("HTTP/"):
raise GeneralProxyError("Proxy server does not appear to be an HTTP proxy") raise GeneralProxyError("Proxy server does not appear to be an HTTP proxy")
try: try:
@ -716,7 +673,7 @@ class socksocket(_BaseSocket):
raise HTTPError(error) raise HTTPError(error)
self.proxy_sockname = (b"0.0.0.0", 0) self.proxy_sockname = (b"0.0.0.0", 0)
self.proxy_peername = dest_addr, dest_port self.proxy_peername = addr, dest_port
_proxy_negotiators = { _proxy_negotiators = {
SOCKS4: _negotiate_SOCKS4, SOCKS4: _negotiate_SOCKS4,
@ -724,27 +681,26 @@ class socksocket(_BaseSocket):
HTTP: _negotiate_HTTP HTTP: _negotiate_HTTP
} }
def connect(self, dest_pair): def connect(self, dest_pair):
""" """
Connects to the specified destination through a proxy. Connects to the specified destination through a proxy.
Uses the same API as socket's connect(). Uses the same API as socket's connect().
To select the proxy server, use set_proxy(). To select the proxy server, use set_proxy().
dest_pair dest_pair - 2-tuple of (IP/hostname, port).
""" """
if len(dest_pair) == 2: if len(dest_pair) != 2 or dest_pair[0].startswith("["):
# IPv4 # Probably IPv6, not supported -- raise an error, and hope
# Happy Eyeballs (RFC6555) makes sure at least the IPv4
# connection works...
raise socket.error("PySocks doesn't support IPv6")
dest_addr, dest_port = dest_pair dest_addr, dest_port = dest_pair
elif len(dest_pair) == 4:
# IPv6
dest_addr, dest_port, st_zero, st_stream = dest_pair
else:
raise GeneralProxyError("Invalid destination-connection (host, port) pair")
if self.type == socket.SOCK_DGRAM: if self.type == socket.SOCK_DGRAM:
if not self._proxyconn: if not self._proxyconn:
self.bind(("", 0)) self.bind(("", 0))
if self.resolve_dest:
dest_addr = socket.gethostbyname(dest_addr) dest_addr = socket.gethostbyname(dest_addr)
# If the host address is INADDR_ANY or similar, reset the peer # If the host address is INADDR_ANY or similar, reset the peer
@ -755,30 +711,33 @@ class socksocket(_BaseSocket):
self.proxy_peername = (dest_addr, dest_port) self.proxy_peername = (dest_addr, dest_port)
return return
proxy_type, proxy_host, proxy_port, rdns, username, password = self.proxy proxy_type, proxy_addr, proxy_port, rdns, username, password = self.proxy
proxy_host = utils.to_bytes(proxy_host)
# Do a minimal input check first # Do a minimal input check first
if not dest_addr or not isinstance(dest_port, int): if (not isinstance(dest_pair, (list, tuple))
or len(dest_pair) != 2
or not dest_addr
or not isinstance(dest_port, int)):
raise GeneralProxyError("Invalid destination-connection (host, port) pair") raise GeneralProxyError("Invalid destination-connection (host, port) pair")
if proxy_type is None: if proxy_type is None:
# Treat like regular socket object # Treat like regular socket object
self.proxy_peername = dest_pair
_BaseSocket.connect(self, (dest_addr, dest_port)) _BaseSocket.connect(self, (dest_addr, dest_port))
return return
proxy_port = proxy_port or DEFAULT_PORTS.get(proxy_type) proxy_addr = self._proxy_addr()
if not proxy_port:
raise GeneralProxyError("Invalid proxy port")
try: try:
# Initial connection to proxy server # Initial connection to proxy server
proxy_ip = socket.gethostbyname(proxy_host) _BaseSocket.connect(self, proxy_addr)
_BaseSocket.connect(self, (proxy_ip, proxy_port))
except socket.error as error: except socket.error as error:
# Error while connecting to proxy # Error while connecting to proxy
self.close() self.close()
proxy_server = "{0}:{1}".format(proxy_host, proxy_port) proxy_addr, proxy_port = proxy_addr
proxy_server = "{0}:{1}".format(proxy_addr, proxy_port)
printable_type = PRINTABLE_PROXY_TYPES[proxy_type] printable_type = PRINTABLE_PROXY_TYPES[proxy_type]
msg = "Error connecting to {0} proxy {1}".format(printable_type, msg = "Error connecting to {0} proxy {1}".format(printable_type,
@ -800,8 +759,12 @@ class socksocket(_BaseSocket):
self.close() self.close()
raise raise
def _proxy_addr(self):
if __name__ == "__main__": """
name = "abc" Return proxy address to connect to as tuple object
name2 = name.encode('idna') """
print(name2) proxy_type, proxy_addr, proxy_port, rdns, username, password = self.proxy
proxy_port = proxy_port or DEFAULT_PORTS.get(proxy_type)
if not proxy_port:
raise GeneralProxyError("Invalid proxy type")
return proxy_addr, proxy_port

View file

@ -1,385 +0,0 @@
""""
Code from https://github.com/XX-net/XX-Net/blob/master/code/default/lib/noarch/utils.py
Copyright (c) [2022], [XX-Net]
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"""
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import re
import os
import threading
from functools import reduce
from six import string_types
ipv4_pattern = re.compile(br'^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$')
ipv6_pattern = re.compile(br"""
^
\s* # Leading whitespace
(?!.*::.*::) # Only a single whildcard allowed
(?:(?!:)|:(?=:)) # Colon iff it would be part of a wildcard
(?: # Repeat 6 times:
[0-9a-f]{0,4} # A group of at most four hexadecimal digits
(?:(?<=::)|(?<!::):) # Colon unless preceeded by wildcard
){6} #
(?: # Either
[0-9a-f]{0,4} # Another group
(?:(?<=::)|(?<!::):) # Colon unless preceeded by wildcard
[0-9a-f]{0,4} # Last group
(?: (?<=::) # Colon iff preceeded by exacly one colon
| (?<!:) #
| (?<=:) (?<!::) : #
) # OR
| # A v4 address with NO leading zeros
(?:25[0-4]|2[0-4]\d|1\d\d|[1-9]?\d)
(?: \.
(?:25[0-4]|2[0-4]\d|1\d\d|[1-9]?\d)
){3}
)
\s* # Trailing whitespace
$
""", re.VERBOSE | re.IGNORECASE | re.DOTALL)
def check_ip_valid4(ip):
"""检查ipv4地址的合法性"""
ip = to_bytes(ip)
ret = ipv4_pattern.match(ip)
if ret is not None:
"each item range: [0,255]"
for item in ret.groups():
if int(item) > 255:
return 0
return 1
else:
return 0
def check_ip_valid6(ip):
"""Copied from http://stackoverflow.com/a/319293/2755602"""
ip = to_bytes(ip)
return ipv6_pattern.match(ip) is not None
def check_ip_valid(ip):
ip = to_bytes(ip)
if b'.' in ip:
return check_ip_valid4(ip)
else:
return check_ip_valid6(ip)
def get_ip_port(ip_str, port=443):
ip_str = to_bytes(ip_str)
if b"." in ip_str:
# ipv4
if b":" in ip_str:
# format is ip:port
ps = ip_str.split(b":")
ip = ps[0]
port = ps[1]
else:
# format is ip
ip = ip_str
else:
# ipv6
if b"[" in ip_str:
# format: [ab01:12:23:34::1]
# format: [ab01:12:23:34::1]:23
p1 = ip_str.find(b"[")
p2 = ip_str.find(b"]")
ip = ip_str[p1 + 1:p2]
port_str = ip_str[p2 + 1:]
if len(port_str) > 0:
port = port_str[1:]
else:
ip = ip_str
return ip, int(port)
domain_allowed = re.compile("(?!-)[A-Z\d-]{1,63}(?<!-)$")
def check_domain_valid(hostname):
if len(hostname) > 255:
return False
if hostname.endswith("."):
hostname = hostname[:-1]
return all(domain_allowed.match(x) for x in hostname.split("."))
def str2hex(data):
data = to_str(data)
return ":".join("{:02x}".format(ord(c)) for c in data)
def get_ip_maskc(ip_str):
head = ".".join(ip_str.split(".")[:-1])
return head + ".0"
def split_ip(strline):
"""从每组地址中分离出起始IP以及结束IP"""
begin = ""
end = ""
if "-" in strline:
num_regions = strline.split(".")
if len(num_regions) == 4:
"xxx.xxx.xxx-xxx.xxx-xxx"
begin = ''
end = ''
for region in num_regions:
if '-' in region:
s, e = region.split('-')
begin += '.' + s
end += '.' + e
else:
begin += '.' + region
end += '.' + region
begin = begin[1:]
end = end[1:]
else:
"xxx.xxx.xxx.xxx-xxx.xxx.xxx.xxx"
begin, end = strline.split("-")
if 1 <= len(end) <= 3:
prefix = begin[0:begin.rfind(".")]
end = prefix + "." + end
elif strline.endswith("."):
"xxx.xxx.xxx."
begin = strline + "0"
end = strline + "255"
elif "/" in strline:
"xxx.xxx.xxx.xxx/xx"
(ip, bits) = strline.split("/")
if check_ip_valid4(ip) and (0 <= int(bits) <= 32):
orgip = ip_string_to_num(ip)
end_bits = (1 << (32 - int(bits))) - 1
begin_bits = 0xFFFFFFFF ^ end_bits
begin = ip_num_to_string(orgip & begin_bits)
end = ip_num_to_string(orgip | end_bits)
else:
"xxx.xxx.xxx.xxx"
begin = strline
end = strline
return begin, end
def generate_random_lowercase(n):
min_lc = ord(b'a')
len_lc = 26
ba = bytearray(os.urandom(n))
for i, b in enumerate(ba):
ba[i] = min_lc + b % len_lc # convert 0..255 to 97..122
# sys.stdout.buffer.write(ba)
return ba
class SimpleCondition(object):
def __init__(self):
self.lock = threading.Condition()
def notify(self):
self.lock.acquire()
self.lock.notify()
self.lock.release()
def wait(self):
self.lock.acquire()
self.lock.wait()
self.lock.release()
def split_domain(host):
host = to_bytes(host)
hl = host.split(b".")
return hl[0], b".".join(hl[1:])
def ip_string_to_num(s):
"""Convert dotted IPv4 address to integer."""
return reduce(lambda a, b: a << 8 | b, list(map(int, s.split("."))))
def ip_num_to_string(ip):
"""Convert 32-bit integer to dotted IPv4 address."""
return ".".join([str(ip >> n & 0xFF) for n in [24, 16, 8, 0]])
private_ipv4_range = [
("10.0.0.0", "10.255.255.255"),
("127.0.0.0", "127.255.255.255"),
("169.254.0.0", "169.254.255.255"),
("172.16.0.0", "172.31.255.255"),
("192.168.0.0", "192.168.255.255")
]
private_ipv6_range = [
("::1", "::1"),
("fc00::", "fdff:ffff:ffff:ffff:ffff:ffff:ffff:ffff")
]
private_ipv4_range_bin = []
for b, e in private_ipv4_range:
bb = ip_string_to_num(b)
ee = ip_string_to_num(e)
private_ipv4_range_bin.append((bb, ee))
def is_private_ip(ip):
ip = to_str(ip)
try:
if "." in ip:
ip_bin = ip_string_to_num(ip)
for b, e in private_ipv4_range_bin:
if b <= ip_bin <= e:
return True
return False
else:
if ip == "::1":
return True
fi = ip.find(":")
if fi != 4:
return False
be = ip[0:2]
if be in ["fc", "fd"]:
return True
else:
return False
except Exception as e:
# print(("is_private_ip(%s), except:%r", ip, e))
return False
import string
printable = set(string.printable)
def get_printable(s):
return [x for x in s if x in printable]
def compare_version(version, reference_version):
try:
p = re.compile(r'([0-9]+)\.([0-9]+)\.([0-9]+)')
m1 = p.match(version)
m2 = p.match(reference_version)
v1 = list(map(int, list(map(m1.group, [1, 2, 3]))))
v2 = list(map(int, list(map(m2.group, [1, 2, 3]))))
if v1 > v2:
return 1
elif v1 < v2:
return -1
else:
return 0
except Exception as e:
print("older_or_equal fail: %s, %s" % (version, reference_version))
raise e
def map_with_parameter(function, datas, args):
l = []
for data in datas:
d_out = function(data, args)
l.append(d_out)
return l
def to_bytes(data, coding='utf-8'):
if isinstance(data, bytes):
return data
if isinstance(data, string_types):
return data.encode(coding)
if isinstance(data, dict):
return dict(map_with_parameter(to_bytes, data.items(), coding))
if isinstance(data, tuple):
return tuple(map_with_parameter(to_bytes, data, coding))
if isinstance(data, list):
return list(map_with_parameter(to_bytes, data, coding))
if isinstance(data, int):
return to_bytes(str(data))
if data is None:
return data
return bytes(data)
def to_str(data, coding='utf-8'):
if isinstance(data, string_types):
return data
if isinstance(data, bytes):
return data.decode(coding)
if isinstance(data, bytearray):
return data.decode(coding)
if isinstance(data, dict):
return dict(map_with_parameter(to_str, data.items(), coding))
if isinstance(data, tuple):
return tuple(map_with_parameter(to_str, data, coding))
if isinstance(data, list):
return list(map_with_parameter(to_str, data, coding))
if isinstance(data, int):
return str(data)
if data is None:
return data
return str(data)
def bytes2str_only(data, coding='utf-8'):
if isinstance(data, bytes):
return data.decode(coding)
if isinstance(data, dict):
return dict(map_with_parameter(bytes2str_only, data.items(), coding))
if isinstance(data, tuple):
return tuple(map_with_parameter(bytes2str_only, data, coding))
if isinstance(data, list):
return list(map_with_parameter(bytes2str_only, data, coding))
else:
return data
def merge_two_dict(x, y):
"""Given two dictionaries, merge them into a new dict as a shallow copy."""
z = x.copy()
z.update(y)
return z
if __name__ == '__main__':
# print(get_ip_port("1.2.3.4", 443))
# print(get_ip_port("1.2.3.4:8443", 443))
print((get_ip_port("[face:ab1:11::0]", 443)))
print((get_ip_port("ab01::1", 443)))
print((get_ip_port("[ab01:55::1]:8444", 443)))

30
api/transfer.py Normal file
View file

@ -0,0 +1,30 @@
import os
import httpx
from dotenv import load_dotenv
from starlette.responses import StreamingResponse
from starlette.background import BackgroundTask
load_dotenv()
EXCLUDED_HEADERS = [
'content-encoding',
'content-length',
'transfer-encoding',
'connection'
]
async def stream_api_response(request, target_endpoint: str='https://api.openai.com/v1'):
async with httpx.AsyncClient(timeout=120) as client:
async with client.stream(
method=request.method,
url=f'{target_endpoint}/{request.url.path}',
headers={
'Authorization': 'Bearer ' + os.getenv('CLOSEDAI_KEY'),
'Content-Type': 'application/json'
},
data=await request.body(),
) as target_response:
target_response.raise_for_status()

View file

@ -1,6 +1,7 @@
fastapi fastapi
httpx httpx[socks]
openai openai
python-dotenv python-dotenv
rich rich
starlette starlette
win_inet_pton

View file

@ -2,7 +2,7 @@
from typing import List from typing import List
import openai import openai as closedai
import httpx import httpx
PORT = 8000 PORT = 8000
@ -43,12 +43,12 @@ def test_api(model: str=MODEL, messages: List[dict]=None) -> dict:
return response.json()['choices'][0] return response.json()['choices'][0]
def test_library(): def test_library():
"""Tests if the endpoint is working with the OpenAI library.""" """Tests if the endpoint is working with the "Closed"AI library."""
openai.api_base = ENDPOINT closedai.api_base = ENDPOINT
openai.api_key = 'nv-LIBRARY-TEST' closedai.api_key = 'nv-LIBRARY-TEST'
completion = openai.ChatCompletion.create( completion = closedai.ChatCompletion.create(
model=MODEL, model=MODEL,
messages=MESSAGES, messages=MESSAGES,
) )

View file

@ -1,31 +0,0 @@
# Credit: @miss_articulate_python on Discord
import configparser
import os
import pathlib
import openai
# creating a config file, so we can store the api key and other settings
config_file = pathlib.Path(__file__).parent / 'config.ini'
config = configparser.ConfigParser()
config.read_dict({
'openai': {
'api_base': 'http://ENDPOINT',
'api_key': '',
'reset_ip_every_request': 'false'
}
})
if config_file.exists():
config.read(config_file)
with open(config_file, 'w', encoding='utf8') as configfile:
config.write(configfile)
# the normal patch that you apply
openai.api_base = config['openai']['api_base']
openai.api_key = config['openai']['api_key']
# many modules lookup these environment variable, so we pre-emptively set them
os.environ['OPENAI_API_KEY'] = config['openai']['api_key']
os.environ['OPENAI_API_BASE'] = config['openai']['api_base']