1192 字
6 分钟
Nonebot2 Linux环境配置踩坑笔记:代理设置与文件处理

Nonebot2 插件踩坑笔记:代理设置与文件处理#

引言#

在 Nonebot2 插件开发中,网络请求和文件处理是两个常见的痛点。最近在维护插件时,遇到了几个典型问题:httpx 代理设置改变、图片发送失败等。本文将分享这些问题的解决方案,希望能帮助大家少走弯路。

一、httpx 代理设置更新#

1. 问题起因#

最近很多使用 httpx.AsyncClient 的插件突然报错:

AsyncClient.__init__() got an unexpected keyword argument 'proxies'

这是因为 httpx 库更新后改变了代理设置的方式。让我们看看如何修复这个问题。

2. 实际案例分析#

2.1 nonebot_plugin_cp_broadcast插件的更新#

更新前

async def req_get(url: URLTypes, proxies: Optional[ProxiesTypes] = None) -> str:
    async with AsyncClient(proxies=proxies) as client:
        r: Response = await client.get(url)
    return r.content.decode("utf-8")

更新后

async def req_get(url: URLTypes, proxies: Optional[ProxiesTypes] = None) -> str:
    try:
        transport = AsyncHTTPTransport(proxy=proxies["http"] if proxies else None)
        async with AsyncClient(transport=transport) as client:
            resp = await client.get(url)
            resp.raise_for_status()
            return resp.text
    except Exception as e:
        logger.error(f"请求失败: {e}")
        return ""

2.2 nonebot_plugin_picstatus 插件的更新#

更新前

@bg_provider()
async def loli():
    async with AsyncClient(
        follow_redirects=True,
        proxies=config.proxy,
        timeout=config.ps_req_timeout,
    ) as cli:
        return resp_to_bg_data(
            (await cli.get("https://www.loliapi.com/acg/pe/")).raise_for_status(),
        )

更新后

@bg_provider()
async def loli():
    transport = AsyncHTTPTransport(proxy=config.proxy if config.proxy else None)
    async with AsyncClient(
        transport=transport,
        follow_redirects=True,
        timeout=config.ps_req_timeout
    ) as cli:
        resp = await cli.get("https://www.loliapi.com/acg/pe/")
        resp.raise_for_status()
        return resp_to_bg_data(resp)

二、nonebot-plugin-nai3 插件踩坑实录#

1. 遇到的问题#

在开发过程中遇到了两个主要问题:

# 问题一:代理设置错误
httpx.ProxyError: 代理连接失败

# 问题二:图片发送失败
ActionFailed(retcode=1200, message='文件名解析失败')

2. 解决过程#

2.1 代理问题修复#

# 更新前
proxies = {"http://": nai3_config.nai3_proxy}

# 更新后
proxies = {
    "http://": nai3_config.nai3_proxy,
    "https://": nai3_config.nai3_proxy
}
transport = AsyncHTTPTransport(proxy=proxies["https://"] if proxies else None)

2.2 图片发送问题#

尝试了多种方案:

  1. 直接路径(失败):
await nai3.send(MessageSegment.image(f"file:///{temp_path}"))
  1. 相对路径(失败):
await nai3.send(MessageSegment.image("./data/nai3/temp.png"))
  1. Base64 方案(成功):
import base64
with open(temp_path, "rb") as f:
    image_data = f.read()
    base64_str = base64.b64encode(image_data).decode()
await nai3.send(
    f"种子: {seed}\n" + MessageSegment.image(f"base64://{base64_str}"), 
    at_sender=True
)

三、最佳实践与解决方案#

1. 代理设置通用模板#

from httpx import AsyncClient, AsyncHTTPTransport
from typing import Optional

async def make_request(url: str, proxy: Optional[str] = None):
    transport = AsyncHTTPTransport(proxy=proxy if proxy else None)
    try:
        async with AsyncClient(transport=transport) as client:
            resp = await client.get(url)
            resp.raise_for_status()
            return resp.text
    except Exception as e:
        logger.error(f"请求失败: {e}")
        return None

2. 图片发送最佳实践#

async def send_image(bot, event, image_path: str):
    try:
        # 首先尝试直接发送
        await bot.send(event, MessageSegment.image(f"file:///{image_path}"))
    except Exception as e:
        logger.debug(f"直接发送失败: {e}")
        # 失败则使用 base64
        try:
            with open(image_path, "rb") as f:
                base64_str = base64.b64encode(f.read()).decode()
            await bot.send(event, MessageSegment.image(f"base64://{base64_str}"))
        except Exception as e:
            logger.error(f"图片发送完全失败: {e}")
            raise

3. 错误处理与日志记录#

try:
    # 网络请求
    transport = AsyncHTTPTransport(proxy=proxy if proxy else None)
    async with AsyncClient(transport=transport) as client:
        resp = await client.get(url)
        resp.raise_for_status()
        
    # 文件处理
    with open(temp_path, "wb") as f:
        f.write(resp.content)
        
    # 消息发送
    await send_image(bot, event, temp_path)
        
except httpx.HTTPError as e:
    logger.error(f"HTTP 请求失败: {e}")
except IOError as e:
    logger.error(f"文件操作失败: {e}")
except ActionFailed as e:
    logger.error(f"消息发送失败: {e}")
except Exception as e:
    logger.error(f"未知错误: {e}")

4. 文件权限检查#

$ ls -la /opt/mybot/data/nai3
total 592
drwxr-xr-x 2 root root   4096 Feb 10 02:04 .
drwxr-xr-x 7 root root   4096 Feb  9 04:38 ..
-rwxr-xr-x 1 root root     35 Feb  9 03:13 black_data.json
-rwxr-xr-x 1 root root 591515 Feb 10 03:43 temp.png

权限说明:

  • drwxr-xr-x: 目录权限,所有者可读写执行,其他用户可读执行
  • -rwxr-xr-x: 文件权限,所有者可读写执行,其他用户可读执行

四、经验总结#

1. 代理设置注意事项#

  • 使用新版 AsyncHTTPTransport
  • 同时配置 http 和 https
  • 检查代理地址格式
  • 做好错误处理

2. 图片处理要点#

  • 优先使用 base64 方式
  • 检查文件权限
  • 做好异常处理
  • 添加详细日志

3. 调试技巧#

  • 使用详细的日志记录
  • 分步骤排查问题
  • 准备多个备选方案
  • 注意检查文件权限

五、常见问题解答#

  1. Q: 为什么要更改代理设置方式? A: 新的方式提供了更好的灵活性和可控性,允许更细粒度的传输层配置。

  2. Q: 为什么图片发送会失败? A: 可能是路径解析问题、权限问题或 go-cqhttp 的限制,使用 base64 方式最可靠。

  3. Q: 代理设置要注意什么? A: 需要同时设置 http 和 https,使用新版的 AsyncHTTPTransport 方式。

  4. Q: 如何调试文件权限问题? A: 使用 ls -la 命令查看详细权限,确保运行用户有正确的读写权限。

结语#

通过这些实际案例的分享,我们看到了在 Nonebot2 插件开发中常见的一些问题和解决方案。良好的错误处理和日志记录是排查问题的关键,而使用正确的代理设置和文件处理方式则是避免问题的基础。

希望这篇文章能帮助大家在开发过程中少走弯路。如果你有任何问题或经验分享,欢迎在评论区讨论!

参考资料#

Nonebot2 Linux环境配置踩坑笔记:代理设置与文件处理
https://fuwari.vercel.app/posts/nonebot2qq机器人搭建教程-linux篇1/
作者
陌灬离
发布于
2025-02-10
许可协议
CC BY-NC-SA 4.0