|
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299 |
- import io
- import os
- import uuid
- import requests
- from urllib.parse import urlparse
- from PIL import Image
- from common.log import logger
- import oss2,time,json
- from urllib.parse import urlparse, unquote
- from voice.ali.ali_voice import AliVoice
- from voice import audio_convert
-
- from common import redis_helper
-
- from datetime import datetime
-
- def fsize(file):
- if isinstance(file, io.BytesIO):
- return file.getbuffer().nbytes
- elif isinstance(file, str):
- return os.path.getsize(file)
- elif hasattr(file, "seek") and hasattr(file, "tell"):
- pos = file.tell()
- file.seek(0, os.SEEK_END)
- size = file.tell()
- file.seek(pos)
- return size
- else:
- raise TypeError("Unsupported type")
-
-
- def compress_imgfile(file, max_size):
- if fsize(file) <= max_size:
- return file
- file.seek(0)
- img = Image.open(file)
- rgb_image = img.convert("RGB")
- quality = 95
- while True:
- out_buf = io.BytesIO()
- rgb_image.save(out_buf, "JPEG", quality=quality)
- if fsize(out_buf) <= max_size:
- return out_buf
- quality -= 5
-
-
- def split_string_by_utf8_length(string, max_length, max_split=0):
- encoded = string.encode("utf-8")
- start, end = 0, 0
- result = []
- while end < len(encoded):
- if max_split > 0 and len(result) >= max_split:
- result.append(encoded[start:].decode("utf-8"))
- break
- end = min(start + max_length, len(encoded))
- # 如果当前字节不是 UTF-8 编码的开始字节,则向前查找直到找到开始字节为止
- while end < len(encoded) and (encoded[end] & 0b11000000) == 0b10000000:
- end -= 1
- result.append(encoded[start:end].decode("utf-8"))
- start = end
- return result
-
-
- def get_path_suffix(path):
- path = urlparse(path).path
- return os.path.splitext(path)[-1].lstrip('.')
-
-
- def convert_webp_to_png(webp_image):
- from PIL import Image
- try:
- webp_image.seek(0)
- img = Image.open(webp_image).convert("RGBA")
- png_image = io.BytesIO()
- img.save(png_image, format="PNG")
- png_image.seek(0)
- return png_image
- except Exception as e:
- logger.error(f"Failed to convert WEBP to PNG: {e}")
- raise
-
-
- def generate_timestamp():
- # 获取当前时间
- now = datetime.now()
- # 格式化时间字符串为 'yyyyMMddHHmmssSS'
- timestamp = now.strftime('%Y%m%d%H%M%S%f')[:-4]
- return timestamp
-
- def at_extract_content(text):
- # 找到最后一个空格的索引
- last_space_index = text.rfind(" ")
- if last_space_index != -1:
- # 返回空格后面的内容
- return text[last_space_index + 1:]
- return ""
-
- def audio_extract_content(text):
- result = text.split('\n', 1)[1]
- return result
-
-
- def save_to_local_from_url(url):
- '''
- 从url保存到本地tmp目录
- '''
-
- parsed_url = urlparse(url)
- # 从 URL 提取文件名
- filename = os.path.basename(parsed_url.path)
- # tmp_dir = os.path(__file__) # 获取系统临时目录
- # print(tmp_dir)
- tmp_file_path = os.path.join(os.getcwd(),'tmp', filename) # 拼接完整路径
-
- # 检查是否存在同名文件
- if os.path.exists(tmp_file_path):
- logger.info(f"文件已存在,将覆盖:{tmp_file_path}")
-
- # 下载文件并保存到临时目录
- response = requests.get(url, stream=True)
- with open(tmp_file_path, 'wb') as f:
- for chunk in response.iter_content(chunk_size=1024):
- if chunk: # 检查是否有内容
- f.write(chunk)
-
- return tmp_file_path
-
-
- def upload_oss(
- access_key_id,
- access_key_secret,
- endpoint,
- bucket_name,
- file_source,
- prefix,
- expiration_days=7
- ):
- """
- 上传文件到阿里云OSS并设置生命周期规则,同时返回文件的公共访问地址。
-
- :param access_key_id: 阿里云AccessKey ID
- :param access_key_secret: 阿里云AccessKey Secret
- :param endpoint: OSS区域对应的Endpoint
- :param bucket_name: OSS中的Bucket名称
- :param file_source: 本地文件路径或HTTP链接
- :param prefix: 设置规则应用的前缀为文件所在目录
- :param expiration_days: 文件保存天数,默认7天后删除
- :return: 文件的公共访问地址
- """
-
- # 创建Bucket实例
- auth = oss2.Auth(access_key_id, access_key_secret)
- bucket = oss2.Bucket(auth, endpoint, bucket_name)
-
- ### 1. 设置生命周期规则 ###
- rule_id = f'delete_after_{expiration_days}_days' # 规则ID
- # prefix = oss_file_name.split('/')[0] + '/' # 设置规则应用的前缀为文件所在目录
-
-
- # 定义生命周期规则
- rule = oss2.models.LifecycleRule(rule_id, prefix, status=oss2.models.LifecycleRule.ENABLED,
- expiration=oss2.models.LifecycleExpiration(days=expiration_days))
-
- # 设置Bucket的生命周期
- lifecycle = oss2.models.BucketLifecycle([rule])
- bucket.put_bucket_lifecycle(lifecycle)
-
- print(f"已设置生命周期规则:文件将在{expiration_days}天后自动删除")
-
- ### 2. 判断文件来源并上传到OSS ###
- if file_source.startswith('http://') or file_source.startswith('https://'):
- # HTTP 链接,先下载文件
- try:
- response = requests.get(file_source, stream=True)
- response.raise_for_status()
- parsed_url = urlparse(file_source)
- # 提取路径部分并解码
- path = unquote(parsed_url.path)
- # 获取路径的最后一部分作为文件名
- filename = path.split('/')[-1]
- oss_file_name=prefix+'/'+ filename
- bucket.put_object(oss_file_name, response.content)
- print(f"文件从 HTTP 链接上传成功:{file_source}")
- except requests.exceptions.RequestException as e:
- print(f"从 HTTP 链接下载文件失败: {e}")
- return None
- else:
- # 本地文件路径
- try:
- filename=os.path.basename(file_source)
- oss_file_name=prefix+'/'+ filename
- bucket.put_object_from_file(oss_file_name, file_source)
- print(f"文件从本地路径上传成功:{file_source}")
- except oss2.exceptions.OssError as e:
- print(f"从本地路径上传文件失败: {e}")
- return None
-
- ### 3. 构建公共访问URL ###
- file_url = f"http://{bucket_name}.{endpoint.replace('http://', '')}/{oss_file_name}"
-
- print(f"文件上传成功,公共访问地址:{file_url}")
-
- return file_url
-
- def generate_guid_no_dashes():
- """
- 生成一个无分隔符的 GUID
- :return: 返回生成的无分隔符 GUID 字符串
- """
- return str(uuid.uuid4()).replace('-', '')
-
- def dialogue_message(wxid_from:str,wxid_to:str,wx_content:list,is_ai:bool=False):
- """
- 构造消息的 JSON 数据
- :param contents: list,包含多个消息内容,每个内容为字典,如:
- [{"type": "text", "text": "AAAAAAA"},
- {"type": "image_url", "image_url": {"url": "https://AAAAA.jpg"}},
- {"type":"file","file_url":{"url":"https://AAAAA.pdf"}}
- ]
- :return: JSON 字符串
- """
-
- # 获取当前时间戳,精确到毫秒
- current_timestamp = int(time.time() * 1000)
-
- # 获取当前时间,格式化为 "YYYY-MM-DD HH:MM:SS"
- current_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
-
- # 构造 JSON 数据
- data = {
- "message_id": str(current_timestamp),
- "topic": "topic.ai.ops.wx",
- "time": current_time,
- "data": {
- "msg_type": "dialogue",
- "is_ai":is_ai,
- "content": {
- "wxid_from": wxid_from,
- "wxid_to": wxid_to,
- "wx_content":wx_content
- }
- }
- }
-
- return json.dumps(data, separators=(',', ':'), ensure_ascii=False)
-
-
- def wx_voice(text: str):
- try:
- # 将文本转换为语音
- reply_text_voice = AliVoice().textToVoice(text)
- reply_text_voice_path = os.path.join(os.getcwd(), reply_text_voice)
-
- # 转换为 Silk 格式
- reply_silk_path = os.path.splitext(reply_text_voice_path)[0] + ".silk"
- reply_silk_during = audio_convert.any_to_sil(reply_text_voice_path, reply_silk_path)
-
- # OSS 配置(建议将凭证存储在安全的地方)
- oss_access_key_id="LTAI5tRTG6pLhTpKACJYoPR5"
- oss_access_key_secret="E7dMzeeMxq4VQvLg7Tq7uKf3XWpYfN"
- oss_endpoint="http://oss-cn-shanghai.aliyuncs.com"
- oss_bucket_name="cow-agent"
- oss_prefix="cow"
-
- # 上传文件到 OSS
- file_path = reply_silk_path
- file_url = upload_oss(oss_access_key_id, oss_access_key_secret, oss_endpoint, oss_bucket_name, file_path, oss_prefix)
-
- # 删除临时文件
- try:
- os.remove(reply_text_voice_path)
- except FileNotFoundError:
- pass # 如果文件未找到,跳过删除
- try:
- os.remove(reply_silk_path)
- except FileNotFoundError:
- pass # 如果文件未找到,跳过删除
-
- return int(reply_silk_during), file_url
- except Exception as e:
- print(f"发生错误:{e}")
- return None, None # 发生错误时返回 None
-
-
- def save_contacts_brief_to_redis(wxid, friends):
- # 将联系人信息保存到 Redis,使用一个合适的 key
- hash_key = f"__AI_OPS_WX__:CONTACTS_BRIEF:{wxid}"
-
- # 获取缓存中的数据,如果缓存不存在则初始化为空列表
- cache_str = redis_helper.redis_helper.get_hash_field(hash_key, "data")
- cache = json.loads(cache_str) if cache_str else []
-
- # 合并联系人信息
- cache.extend(friends)
-
- # 将合并后的联系人数据保存回 Redis
- redis_helper.redis_helper.update_hash_field(hash_key, "data", {
- "data": json.dumps(cache, ensure_ascii=False)
- })
|