From 3ff77edf28fca3a89ce023a30cd069413ecf8406 Mon Sep 17 00:00:00 2001 From: ikaros <327209194@qq.com> Date: Mon, 4 Mar 2024 19:01:49 +0800 Subject: [PATCH 1/2] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E6=9C=AC=E5=9C=B0?= =?UTF-8?q?=E9=97=AE=E7=AD=94=E4=B8=8B=20=E7=94=A8=E6=88=B7=E5=90=8D?= =?UTF-8?q?=E5=8F=98=E6=88=90=E5=8A=A9=E6=92=AD=E7=9A=84bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- utils/my_handle.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/my_handle.py b/utils/my_handle.py index a7be376d..abdb96c0 100644 --- a/utils/my_handle.py +++ b/utils/my_handle.py @@ -606,7 +606,7 @@ def local_qa_handle(self, data): # 假设有多个未知变量,用户可以在此处定义动态变量 variables = { 'cur_time': My_handle.common.get_bj_time(5), - 'username': My_handle.config.get("assistant_anchor", "username") + 'username': user_name } # 使用字典进行字符串替换 From 9b3104a4fc0ef76f78e4ba085687ad40f3a8ad8a Mon Sep 17 00:00:00 2001 From: ikaros <327209194@qq.com> Date: Mon, 4 Mar 2024 19:02:31 +0800 Subject: [PATCH 2/2] =?UTF-8?q?=E8=A1=A5=E5=85=85claude2=E7=9A=84=E6=B5=8B?= =?UTF-8?q?=E8=AF=95demo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/test_claude/claude2.py | 0 tests/test_claude/claude_api.py | 371 ++++++++++++++++++++ tests/test_claude/unofficial-claude2-api.py | 71 ++++ 3 files changed, 442 insertions(+) create mode 100644 tests/test_claude/claude2.py create mode 100644 tests/test_claude/claude_api.py create mode 100644 tests/test_claude/unofficial-claude2-api.py diff --git a/tests/test_claude/claude2.py b/tests/test_claude/claude2.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/test_claude/claude_api.py b/tests/test_claude/claude_api.py new file mode 100644 index 00000000..bc471b13 --- /dev/null +++ b/tests/test_claude/claude_api.py @@ -0,0 +1,371 @@ +import json +import os +import uuid +import requests +from curl_cffi import requests,Curl, CurlOpt +from dotenv import load_dotenv +from common.log import logger +import PyPDF2 +import docx +import re +from io import BytesIO + +load_dotenv() # Load environment variables from .env file +class Client: + + def __init__(self, cookie,use_proxy=False): + self.cookie = cookie + self.use_proxy = use_proxy + self.proxies = self.load_proxies_from_env() + #logger.info("__init__: use_proxy: {}".format(self.use_proxy)) + #logger.info("__init__: proxies: {}".format(self.proxies)) + self.organization_id =self.get_organization_id() + #self.organization_id ="28912dc3-bcd3-43c5-944c-a943a02d19fc" + + def load_proxies_from_env(self): + proxies = {} + if self.use_proxy: + http_proxy = os.getenv('HTTP_PROXY') + https_proxy = os.getenv('HTTPS_PROXY') + socks5_proxy = os.getenv('SOCKS5_PROXY') + if http_proxy: + proxies['http'] = http_proxy + if https_proxy: + proxies['https'] = https_proxy + if socks5_proxy: + proxies['https'] = socks5_proxy + return proxies + + def get_organization_id(self): + url = "https://claude.ai/api/organizations" + + headers = { + 'User-Agent': + 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.5359.125 Safari/537.36', + 'Accept-Language': 'en-US,en;q=0.5', + 'Referer': 'https://claude.ai/chats', + 'Content-Type': 'application/json', + 'Sec-Fetch-Dest': 'empty', + 'Sec-Fetch-Mode': 'navigate', + 'Sec-Fetch-Site': 'same-origin', + 'Connection': 'keep-alive', + 'Cookie': f'{self.cookie}' + } + + response = self.send_request("GET",url,headers=headers) + if response.status_code == 200: + res = json.loads(response.text) + uuid = res[0]['uuid'] + return uuid + else: + print(f"Error: {response.status_code} - {response.text}") + + def get_content_type(self, file_path): + # Function to determine content type based on file extension + extension = os.path.splitext(file_path)[-1].lower() + if extension == '.pdf': + return 'application/pdf' + elif extension == '.txt': + return 'text/plain' + elif extension == '.csv': + return 'text/csv' + # Add more content types as needed for other file types + else: + return 'application/octet-stream' + + # Lists all the conversations you had with Claude + def list_all_conversations(self): + url = f"https://claude.ai/api/organizations/{self.organization_id}/chat_conversations" + + headers = { + 'User-Agent': + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/115.0', + 'Accept-Language': 'en-US,en;q=0.5', + 'Referer': 'https://claude.ai/chats', + 'Content-Type': 'application/json', + 'Sec-Fetch-Dest': 'empty', + 'Sec-Fetch-Mode': 'cors', + 'Sec-Fetch-Site': 'same-origin', + 'Connection': 'keep-alive', + 'Cookie': f'{self.cookie}' + } + + response = self.send_request("GET",url,headers=headers) + conversations = response.json() + + # Returns all conversation information in a list + if response.status_code == 200: + return conversations + else: + print(f"Error: {response.status_code} - {response.text}") + + # Send Message to Claude + def send_message(self, prompt, conversation_id, attachment=None): + url = "https://claude.ai/api/append_message" + #print("send_message,attachment"+attachment) + # Upload attachment if provided + attachments = [] + if attachment: + attachment_response = self.upload_attachment(attachment) + if attachment_response: + attachments = [attachment_response] + else: + return {"Error: Invalid file format. Please try again."} + + # Ensure attachments is an empty list when no attachment is provided + if not attachment: + attachments = [] + + payload = json.dumps({ + "completion": { + "prompt": f"{prompt}", + "timezone": "Asia/Kolkata", + "model": "claude-2" + }, + "organization_uuid": f"{self.organization_id}", + "conversation_uuid": f"{conversation_id}", + "text": f"{prompt}", + "attachments": attachments + }) + + headers = { + 'User-Agent': + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/115.0', + 'Accept': 'text/event-stream, text/event-stream', + 'Accept-Language': 'en-US,en;q=0.5', + 'Referer': 'https://claude.ai/chats', + 'Content-Type': 'application/json', + 'Origin': 'https://claude.ai', + 'DNT': '1', + 'Connection': 'keep-alive', + 'Cookie': f'{self.cookie}', + 'Sec-Fetch-Dest': 'empty', + 'Sec-Fetch-Mode': 'cors', + 'Sec-Fetch-Site': 'same-origin', + 'TE': 'trailers' + } + + #response = self.send_request("POST",url,headers=headers, data=payload, stream=True) + # decoded_data = response.content.decode("utf-8") + # #logger.info("send_message {} decoded_data:".format(decoded_data)) + # decoded_data = re.sub('\n+', '\n', decoded_data).strip() + # data_strings = decoded_data.split('\n') + # completions = [] + # for data_string in data_strings: + # json_str = data_string[6:].strip() + # data = json.loads(json_str) + # if 'completion' in data: + # completions.append(data['completion']) + # + # answer = ''.join(completions) + # logger.info("send_message {} answer:".format(answer)) + buffer = BytesIO() + c = Curl() + def stream_callback(data): + json_str = data.decode('utf-8') + + decoded_data = re.sub('\n+', '\n', json_str).strip() + data_strings = decoded_data.split('\n') + for data_string in data_strings: + json_str = data_string[6:].strip() + _data = json.loads(json_str) + if 'completion' in _data: + buffer.write(str(_data['completion']).encode('utf-8')) + print(_data['completion'], end="") + + + c.setopt(CurlOpt.URL, b'https://claude.ai/api/append_message') + c.setopt(CurlOpt.WRITEFUNCTION, stream_callback) + c.setopt(CurlOpt.HTTPHEADER, headers) + c.setopt(CurlOpt.POSTFIELDS, payload) + c.impersonate("chrome110") + + c.perform() + c.close() + body = buffer.getvalue() + print(body.decode()) + return body + + # Deletes the conversation + def delete_conversation(self, conversation_id): + url = f"https://claude.ai/api/organizations/{self.organization_id}/chat_conversations/{conversation_id}" + + payload = json.dumps(f"{conversation_id}") + headers = { + 'User-Agent': + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/115.0', + 'Accept-Language': 'en-US,en;q=0.5', + 'Content-Type': 'application/json', + 'Content-Length': '38', + 'Referer': 'https://claude.ai/chats', + 'Origin': 'https://claude.ai', + 'Sec-Fetch-Dest': 'empty', + 'Sec-Fetch-Mode': 'cors', + 'Sec-Fetch-Site': 'same-origin', + 'Connection': 'keep-alive', + 'Cookie': f'{self.cookie}', + 'TE': 'trailers' + } + + response = self.send_request("DELETE",url,headers=headers, data=payload) + # Returns True if deleted or False if any error in deleting + if response.status_code == 200: + return True + else: + return False + + # Returns all the messages in conversation + def chat_conversation_history(self, conversation_id): + url = f"https://claude.ai/api/organizations/{self.organization_id}/chat_conversations/{conversation_id}" + + headers = { + 'User-Agent': + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/115.0', + 'Accept-Language': 'en-US,en;q=0.5', + 'Referer': 'https://claude.ai/chats', + 'Content-Type': 'application/json', + 'Sec-Fetch-Dest': 'empty', + 'Sec-Fetch-Mode': 'cors', + 'Sec-Fetch-Site': 'same-origin', + 'Connection': 'keep-alive', + 'Cookie': f'{self.cookie}' + } + + response = self.send_request("GET",url,headers=headers,params={'encoding': 'utf-8'}) + print(type(response)) + + # List all the conversations in JSON + return response.json() + + def generate_uuid(self): + random_uuid = uuid.uuid4() + random_uuid_str = str(random_uuid) + formatted_uuid = f"{random_uuid_str[0:8]}-{random_uuid_str[9:13]}-{random_uuid_str[14:18]}-{random_uuid_str[19:23]}-{random_uuid_str[24:]}" + return formatted_uuid + + def create_new_chat(self): + url = f"https://claude.ai/api/organizations/{self.organization_id}/chat_conversations" + uuid = self.generate_uuid() + + payload = json.dumps({"uuid": uuid, "name": ""}) + headers = { + 'User-Agent': + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/115.0', + 'Accept-Language': 'en-US,en;q=0.5', + 'Referer': 'https://claude.ai/chats', + 'Content-Type': 'application/json', + 'Origin': 'https://claude.ai', + 'DNT': '1', + 'Connection': 'keep-alive', + 'Cookie': self.cookie, + 'Sec-Fetch-Dest': 'empty', + 'Sec-Fetch-Mode': 'cors', + 'Sec-Fetch-Site': 'same-origin', + 'TE': 'trailers' + } + response = self.send_request("POST",url,headers=headers, data=payload) + # Returns JSON of the newly created conversation information + return response.json() + + # Resets all the conversations + def reset_all(self): + conversations = self.list_all_conversations() + + for conversation in conversations: + conversation_id = conversation['uuid'] + delete_id = self.delete_conversation(conversation_id) + + return True + + def upload_attachment(self, file_path): + if file_path.endswith(('.txt', '.pdf', '.csv','.docx','.doc')): + file_name = os.path.basename(file_path) + file_size = os.path.getsize(file_path) + file_type = "text/plain" + file_content = "" + if file_path.endswith('.txt'): + with open(file_path, 'r', encoding='utf-8') as file: + file_content = file.read() + + elif file_path.endswith('.pdf'): + with open(file_path, 'rb') as file: + pdf_reader = PyPDF2.PdfFileReader(file) + for page_num in range(pdf_reader.numPages): + page = pdf_reader.getPage(page_num) + file_content += page.extractText() + + elif file_path.endswith(('.doc', '.docx')): + doc = docx.Document(file_path) + paragraphs = doc.paragraphs + for paragraph in paragraphs: + file_content += paragraph.text + + return { + "file_name": file_name, + "file_type": file_type, + "file_size": file_size, + "extracted_content": file_content + } + + url = 'https://claude.ai/api/convert_document' + headers = { + 'User-Agent': + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/115.0', + 'Accept-Language': 'en-US,en;q=0.5', + 'Referer': 'https://claude.ai/chats', + 'Origin': 'https://claude.ai', + 'Sec-Fetch-Dest': 'empty', + 'Sec-Fetch-Mode': 'cors', + 'Sec-Fetch-Site': 'same-origin', + 'Connection': 'keep-alive', + 'Cookie': f'{self.cookie}', + 'TE': 'trailers' + } + + file_name = os.path.basename(file_path) + content_type = self.get_content_type(file_path) + files = { + 'file': (file_name, open(file_path, 'rb'), content_type), + 'orgUuid': (None, self.organization_id) + } + response = self.send_request(url, "POST",headers=headers, files=files) + if response.status_code == 200: + return response.json() + else: + return False + + # Renames the chat conversation title + def rename_chat(self, title, conversation_id): + url = "https://claude.ai/api/rename_chat" + + payload = json.dumps({ + "organization_uuid": f"{self.organization_id}", + "conversation_uuid": f"{conversation_id}", + "title": f"{title}" + }) + headers = { + 'User-Agent': + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/115.0', + 'Accept-Language': 'en-US,en;q=0.5', + 'Content-Type': 'application/json', + 'Referer': 'https://claude.ai/chats', + 'Origin': 'https://claude.ai', + 'Sec-Fetch-Dest': 'empty', + 'Sec-Fetch-Mode': 'cors', + 'Sec-Fetch-Site': 'same-origin', + 'Connection': 'keep-alive', + 'Cookie': f'{self.cookie}', + 'TE': 'trailers' + } + + response = self.send_request("POST",url,headers=headers, data=payload) + if response.status_code == 200: + return True + else: + return False + + def send_request(self, method, url, headers, data=None, files=None, params=None, stream=False): + if self.use_proxy: + return requests.request(method, url, headers=headers, data=data, files=files, params=params,impersonate="chrome110",proxies=self.proxies,timeout=500) + else: + return requests.request(method, url, headers=headers, data=data, files=files, params=params,impersonate="chrome110",timeout=500) \ No newline at end of file diff --git a/tests/test_claude/unofficial-claude2-api.py b/tests/test_claude/unofficial-claude2-api.py new file mode 100644 index 00000000..39a70df4 --- /dev/null +++ b/tests/test_claude/unofficial-claude2-api.py @@ -0,0 +1,71 @@ +# pip install unofficial-claude2-api +from sys import exit as sys_exit +from claude2_api.client import ( + ClaudeAPIClient, + SendMessageResponse, +) +from claude2_api.session import SessionData, get_session_data +from claude2_api.errors import ClaudeAPIError, MessageRateLimitError, OverloadError + +# Wildcard import will also work the same as above +# from claude2_api import * + +# List of attachments filepaths, up to 5, max 10 MB each +FILEPATH_LIST = [ + "test1.txt", + "test2.txt", +] + +# This function will automatically retrieve a SessionData instance using selenium +# It will auto gather cookie session, user agent and organization ID. +# Omitting profile argument will use default Firefox profile +session: SessionData = get_session_data() + +# Initialize a client instance using a session +# Optionally change the requests timeout parameter to best fit your needs...default to 240 seconds. +client = ClaudeAPIClient(session, timeout=240) + +# Create a new chat and cache the chat_id +chat_id = client.create_chat() +if not chat_id: + # This will not throw MessageRateLimitError + # But it still means that account has no more messages left. + print("\nMessage limit hit, cannot create chat...") + sys_exit(1) + +try: + # Used for sending message with or without attachments + # Returns a SendMessageResponse instance + res: SendMessageResponse = client.send_message( + chat_id, "Hello!", + # attachment_paths=FILEPATH_LIST + ) + # Inspect answer + if res.answer: + print(res.answer) + else: + # Inspect response status code and raw answer bytes + print(f"\nError code {res.status_code}, raw_answer: {res.raw_answer}") +except ClaudeAPIError as e: + # Identify the error + if isinstance(e, MessageRateLimitError): + # The exception will hold these informations about the rate limit: + print(f"\nMessage limit hit, resets at {e.reset_date}") + print(f"\n{e.sleep_sec} seconds left until -> {e.reset_timestamp}") + elif isinstance(e, OverloadError): + print(f"\nOverloaded error: {e}") + else: + print(f"\nGot unknown Claude error: {e}") +finally: + # Perform chat deletion for cleanup + client.delete_chat(chat_id) + +# Get a list of all chats ids +all_chat_ids = client.get_all_chat_ids() +# Delete all chats +for chat in all_chat_ids: + client.delete_chat(chat) + +# Or by using a shortcut utility +client.delete_all_chats() +sys_exit(0) \ No newline at end of file