You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

223 lines
7.6 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

import requests
from bs4 import BeautifulSoup
from config.logger import setup_logging
from plugins_func.register import register_function, ToolType, ActionResponse, Action
from core.utils.util import get_ip_info
TAG = __name__
logger = setup_logging()
GET_WEATHER_FUNCTION_DESC = {
"type": "function",
"function": {
"name": "get_weather",
"description": (
"获取某个地点的天气,用户应提供一个位置,比如用户说杭州天气,参数为:杭州。"
"如果用户说的是省份,默认用省会城市。如果用户说的不是省份或城市而是一个地名,默认用该地所在省份的省会城市。"
"如果用户没有指明地点说“天气怎么样””今天天气如何“location参数为空"
),
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "地点名,例如杭州。可选参数,如果不提供则不传",
},
"lang": {
"type": "string",
"description": "返回用户使用的语言code例如zh_CN/zh_HK/en_US/ja_JP等默认zh_CN",
},
},
"required": ["lang"],
},
},
}
HEADERS = {
"User-Agent": (
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
"(KHTML, like Gecko) Chrome/92.0.4515.107 Safari/537.36"
)
}
# 天气代码 https://dev.qweather.com/docs/resource/icons/#weather-icons
WEATHER_CODE_MAP = {
"100": "",
"101": "多云",
"102": "少云",
"103": "晴间多云",
"104": "",
"150": "",
"151": "多云",
"152": "少云",
"153": "晴间多云",
"300": "阵雨",
"301": "强阵雨",
"302": "雷阵雨",
"303": "强雷阵雨",
"304": "雷阵雨伴有冰雹",
"305": "小雨",
"306": "中雨",
"307": "大雨",
"308": "极端降雨",
"309": "毛毛雨/细雨",
"310": "暴雨",
"311": "大暴雨",
"312": "特大暴雨",
"313": "冻雨",
"314": "小到中雨",
"315": "中到大雨",
"316": "大到暴雨",
"317": "暴雨到大暴雨",
"318": "大暴雨到特大暴雨",
"350": "阵雨",
"351": "强阵雨",
"399": "",
"400": "小雪",
"401": "中雪",
"402": "大雪",
"403": "暴雪",
"404": "雨夹雪",
"405": "雨雪天气",
"406": "阵雨夹雪",
"407": "阵雪",
"408": "小到中雪",
"409": "中到大雪",
"410": "大到暴雪",
"456": "阵雨夹雪",
"457": "阵雪",
"499": "",
"500": "薄雾",
"501": "",
"502": "",
"503": "扬沙",
"504": "浮尘",
"507": "沙尘暴",
"508": "强沙尘暴",
"509": "浓雾",
"510": "强浓雾",
"511": "中度霾",
"512": "重度霾",
"513": "严重霾",
"514": "大雾",
"515": "特强浓雾",
"900": "",
"901": "",
"999": "未知",
}
def fetch_city_info(location, api_key, api_host):
url = f"https://{api_host}/geo/v2/city/lookup?key={api_key}&location={location}&lang=zh"
response = requests.get(url, headers=HEADERS).json()
return response.get("location", [])[0] if response.get("location") else None
def fetch_weather_page(url):
response = requests.get(url, headers=HEADERS)
return BeautifulSoup(response.text, "html.parser") if response.ok else None
def parse_weather_info(soup):
city_name = soup.select_one("h1.c-submenu__location").get_text(strip=True)
current_abstract = soup.select_one(".c-city-weather-current .current-abstract")
current_abstract = (
current_abstract.get_text(strip=True) if current_abstract else "未知"
)
current_basic = {}
for item in soup.select(
".c-city-weather-current .current-basic .current-basic___item"
):
parts = item.get_text(strip=True, separator=" ").split(" ")
if len(parts) == 2:
key, value = parts[1], parts[0]
current_basic[key] = value
temps_list = []
for row in soup.select(".city-forecast-tabs__row")[:7]: # 取前7天的数据
date = row.select_one(".date-bg .date").get_text(strip=True)
weather_code = (
row.select_one(".date-bg .icon")["src"].split("/")[-1].split(".")[0]
)
weather = WEATHER_CODE_MAP.get(weather_code, "未知")
temps = [span.get_text(strip=True) for span in row.select(".tmp-cont .temp")]
high_temp, low_temp = (temps[0], temps[-1]) if len(temps) >= 2 else (None, None)
temps_list.append((date, weather, high_temp, low_temp))
return city_name, current_abstract, current_basic, temps_list
@register_function("get_weather", GET_WEATHER_FUNCTION_DESC, ToolType.SYSTEM_CTL)
def get_weather(conn, location: str = None, lang: str = "zh_CN"):
from core.utils.cache.manager import cache_manager, CacheType
api_host = conn.config["plugins"]["get_weather"].get(
"api_host", "mj7p3y7naa.re.qweatherapi.com"
)
api_key = conn.config["plugins"]["get_weather"].get(
"api_key", "a861d0d5e7bf4ee1a83d9a9e4f96d4da"
)
default_location = conn.config["plugins"]["get_weather"]["default_location"]
client_ip = conn.client_ip
# 优先使用用户提供的location参数
if not location:
# 通过客户端IP解析城市
if client_ip:
# 先从缓存获取IP对应的城市信息
cached_ip_info = cache_manager.get(CacheType.IP_INFO, client_ip)
if cached_ip_info:
location = cached_ip_info.get("city")
else:
# 缓存未命中调用API获取
ip_info = get_ip_info(client_ip, logger)
if ip_info:
cache_manager.set(CacheType.IP_INFO, client_ip, ip_info)
location = ip_info.get("city")
if not location:
location = default_location
else:
# 若无IP使用默认位置
location = default_location
# 尝试从缓存获取完整天气报告
weather_cache_key = f"full_weather_{location}_{lang}"
cached_weather_report = cache_manager.get(CacheType.WEATHER, weather_cache_key)
if cached_weather_report:
return ActionResponse(Action.REQLLM, cached_weather_report, None)
# 缓存未命中,获取实时天气数据
city_info = fetch_city_info(location, api_key, api_host)
if not city_info:
return ActionResponse(
Action.REQLLM, f"未找到相关的城市: {location},请确认地点是否正确", None
)
soup = fetch_weather_page(city_info["fxLink"])
if not soup:
return ActionResponse(Action.REQLLM, None, "请求失败")
city_name, current_abstract, current_basic, temps_list = parse_weather_info(soup)
weather_report = f"您查询的位置是:{city_name}\n\n当前天气: {current_abstract}\n"
# 添加有效的当前天气参数
if current_basic:
weather_report += "详细参数:\n"
for key, value in current_basic.items():
if value != "0": # 过滤无效值
weather_report += f" · {key}: {value}\n"
# 添加7天预报
weather_report += "\n未来7天预报\n"
for date, weather, high, low in temps_list:
weather_report += f"{date}: {weather},气温 {low}~{high}\n"
# 提示语
weather_report += "\n(如需某一天的具体天气,请告诉我日期)"
# 缓存完整的天气报告
cache_manager.set(CacheType.WEATHER, weather_cache_key, weather_report)
return ActionResponse(Action.REQLLM, weather_report, None)