"""
工具函数模块:语言检测、HTML清洗、内容提取、时间解析、AI API调用、文本换行
"""
import re
import requests
from datetime import datetime, timedelta, timezone
from email.utils import parsedate_to_datetime
def is_chinese(text):
"""判断文本是否为中文(汉字占比 > 20%)"""
if not text:
return True
chinese_chars = sum(1 for c in text if '\u4e00' <= c <= '\u9fff')
return chinese_chars > len(text) * 0.2
def clean_html(html_text):
"""清理HTML标签,合并空白字符"""
if not html_text:
return ""
text = re.sub(r'<[^>]+>', ' ', html_text)
text = re.sub(r'\s+', ' ', text)
return text.strip()
def extract_content(entry):
"""从RSS条目提取完整正文内容"""
content = entry.get('summary', '') or entry.get('description', '')
if not content:
content_list = entry.get('content', [{}])
if content_list and isinstance(content_list, list):
content = content_list[0].get('value', '')
return clean_html(content)
def parse_pub_time(entry):
"""解析发布时间,统一转换为北京时间(UTC+8)"""
beijing_tz = timezone(timedelta(hours=8))
time_fields = ['published_parsed', 'updated_parsed', 'created_parsed']
for field in time_fields:
if entry.get(field):
try:
utc_time = datetime(*entry[field][:6], tzinfo=timezone.utc)
return utc_time.astimezone(beijing_tz).replace(tzinfo=None)
except Exception:
continue
for field in ['published', 'updated', 'created']:
if entry.get(field):
try:
dt = parsedate_to_datetime(entry[field])
if dt.tzinfo is not None:
return dt.astimezone(beijing_tz).replace(tzinfo=None)
return dt
except Exception:
continue
return datetime.now()
def call_ai(messages, api_key, base_url, model, temperature=0.3, max_tokens=800, purpose=None):
"""调用 OpenAI 兼容的 Chat Completion API"""
if not api_key:
raise ValueError('OPENAI_API_KEY 未配置')
headers = {
'Content-Type': 'application/json',
'Authorization': f'Bearer {api_key}'
}
data = {
'model': model,
'messages': messages,
'temperature': temperature,
'max_tokens': max_tokens,
}
try:
response = requests.post(
f'{base_url}/chat/completions',
headers=headers,
json=data,
timeout=120
)
response.raise_for_status()
result = response.json()['choices'][0]['message']['content']
_record_api_success(model, purpose, max_tokens)
return result
except Exception:
_record_api_failure(model, purpose, max_tokens)
raise
def wrap_text(text, font, max_width, draw):
"""将长文本按像素宽度自动换行,返回行列表"""
lines = []
current_line = ""
for char in text:
test_line = current_line + char
bbox = draw.textbbox((0, 0), test_line, font=font)
line_width = bbox[2] - bbox[0]
if line_width <= max_width:
current_line = test_line
else:
if current_line:
lines.append(current_line)
current_line = char
if current_line:
lines.append(current_line)
return lines
def _record_api_success(model, purpose, max_tokens):
try:
from .monitor import get_monitor
get_monitor().record_api_call(model or "unknown", purpose or "unknown", max_tokens, True)
except Exception:
pass
def _record_api_failure(model, purpose, max_tokens):
try:
from .monitor import get_monitor
get_monitor().record_api_call(model or "unknown", purpose or "unknown", max_tokens, False)
except Exception:
pass