Repository navigation
Conversation
feat: add group support feat: auto cut context
There was a problem hiding this comment.
Sorry @dudu7615, your pull request is larger than the review limit of 150000 diff characters
ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (72)
💤 Files with no reviewable changes (3)
📝 WalkthroughWalkthroughThe application migrates from FastAPI webhooks to QQ WebSocket processing, adds HermesWss protocol and messaging capabilities, introduces QR onboarding and uploads, and replaces the previous AI plugin with configurable AiChat support for person and group conversations. ChangesWebSocket and HermesWss integration
Application and plugin flow
Estimated code review effort: 5 (Critical) | ~120 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 13
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
plugin/Holiday_d/RequApi.py (1)
7-33: 🚀 Performance & Scalability | 🔴 Critical | ⚡ Quick winAvoid blocking the async event loop with synchronous HTTP calls.
updataCacheperforms a synchronoushttpx.getrequest. Since it is called from the asynchronousHoliday.runmethod, this blocking network I/O will halt the entireasyncioevent loop. If the request takes a few seconds or times out, it can prevent the bot from responding to other messages or sending heartbeats, which may cause the WebSocket to disconnect.Convert these functions to use
async/awaitwithhttpx.AsyncClientto avoid blocking the event loop.⚡ Proposed fix
Update
plugin/Holiday_d/RequApi.py:-def updataCache(): +async def updataCache(): url = "https://timor.tech/api/holiday/year" for _ in range(3): try: - resp = httpx.get(url, timeout=10) - if resp.status_code == 200: - reply = resp.json() - if reply["code"] == 0: - for _, holiday in reply["holiday"].items(): - Cache.addCache( - holiday["name"], - datetime.strptime(holiday["date"], "%Y-%m-%d"), - holiday["holiday"], - ) - logger.info("获取节假日信息成功") - return + async with httpx.AsyncClient() as client: + resp = await client.get(url, timeout=10) + if resp.status_code == 200: + reply = resp.json() + if reply["code"] == 0: + for _, holiday in reply["holiday"].items(): + Cache.addCache( + holiday["name"], + datetime.strptime(holiday["date"], "%Y-%m-%d"), + holiday["holiday"], + ) + logger.info("获取节假日信息成功") + return except Exception as e: logger.warning(f"尝试获取节假日信息失败,错误信息:{e}") continue logger.error("获取节假日信息失败,请检查网络连接") return None -def checkAndUpdata(): +async def checkAndUpdata(): cachedYear = Cache.getCachedYear() if cachedYear is None or cachedYear.year != datetime.now().year: - updataCache() + await updataCache()Then, update the caller in
plugin/Holiday.pyaround line 32:if not self._checked: - RequApi.checkAndUpdata() + await RequApi.checkAndUpdata() self._checked = True🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@plugin/Holiday_d/RequApi.py` around lines 7 - 33, Convert updataCache and checkAndUpdata in RequApi.py to async functions, perform requests through httpx.AsyncClient with awaited calls, and preserve the existing retry, caching, and logging behavior. Update the Holiday.run caller to await RequApi.checkAndUpdata() before setting _checked.
🧹 Nitpick comments (4)
CLAUDE.md (1)
30-30: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueSpecify a language for fenced code blocks.
These code blocks omit a language identifier, which degrades syntax highlighting in viewers and triggers markdownlint warnings (MD040).
CLAUDE.md#L30-L30: addtextto the fenced code block opening to specify the language.modules/HermesWss/README.md#L28-L28: addtextto the fenced code block opening to specify the language.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@CLAUDE.md` at line 30, Specify the text language on the fenced code block openings in CLAUDE.md (line 30) and modules/HermesWss/README.md (line 28) by adding the text identifier; no other content changes are needed.Source: Linters/SAST tools
modules/HermesWss/models.py (1)
404-407: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winUse
Any/Awaitablefor these callback aliases.CoroutineisCoroutine[YieldType, SendType, ReturnType], soasyncio.AbstractEventLoopis the wrong first parameter here. UpdatePostMessageFn,OnMessageCallback, andOnInteractionCallbacktoCoroutine[Any, Any, ...](orAwaitable[...]if only awaitability is needed).🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@modules/HermesWss/models.py` around lines 404 - 407, Update the callback type aliases PostMessageFn, OnMessageCallback, and OnInteractionCallback to use Coroutine[Any, Any, ReturnType] or Awaitable[ReturnType] instead of treating asyncio.AbstractEventLoop as the coroutine yield type. Add or reuse the appropriate Any import and preserve each alias’s existing parameter and return types.Source: Coding guidelines
plugin/Weather.py (1)
75-103: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueConsistently use
targetfrom_reply_target.In the
/天气预报and/常住地branches,author["id"]is used instead of thetargetvariable returned by_reply_target. While functionally equivalent for person messages, usingtargetconsistently is cleaner and fully utilizes the helper function (as done in the/今日天气branch).♻️ Proposed refactor
logger.info(f"发送天气信息: {weather}") send_type, target = self._reply_target(message) if send_type == "group": await HttpSender.sendText2Group(weather, message, target) else: - await HttpSender.sendText2Person(weather, message, author["id"]) + await HttpSender.sendText2Person(weather, message, target) elif content.startswith("/常住地"): if len(content.split(" ")) != 2: send_type, target = self._reply_target(message) if send_type == "group": await HttpSender.sendText2Group( "输入格式错误. 请输入 /常住地 <城市名>", message, target ) else: await HttpSender.sendText2Person( - "输入格式错误. 请输入 /常住地 <城市名>", message, author["id"] + "输入格式错误. 请输入 /常住地 <城市名>", message, target ) else: city = content.split(" ")[1] SqlCache.User.add(author["id"], city) send_type, target = self._reply_target(message) if send_type == "group": await HttpSender.sendText2Group( f"设置常住地: {city}", message, target ) else: await HttpSender.sendText2Person( - f"设置常住地: {city}", message, author["id"] + f"设置常住地: {city}", message, target )🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@plugin/Weather.py` around lines 75 - 103, Update the `/天气预报` and `/常住地` branches in the message handler to pass the `target` returned by `_reply_target(message)` to `HttpSender.sendText2Person` instead of `author["id"]`. Preserve the existing group-message behavior and response content.plugin/Weather_d/GetWeather.py (1)
34-38: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low valuePrevent rate-limit race conditions under concurrent load.
The current rate-limiting logic uses a global
_lastCheckvariable without synchronization. If multiple users request weather data simultaneously, they might all bypass thewhileloop at the same time and hit the API concurrently, exceeding the rate limit.Consider using an
asyncio.Lock()to ensure that concurrent requests wait their turn and accurately update_lastCheck.🔒️ Proposed refactor to add a lock
Add a global lock at the top of the file:
import asyncio _rate_limit_lock = asyncio.Lock()Then, wrap the rate-limit check and the update of
_lastCheckinside the lock:- # 防止请求过快 - while datetime.now() - _lastCheck < timedelta(seconds=1.05): - await asyncio.sleep(1) - _lastCheck = datetime.now() + # 防止请求过快 + async with _rate_limit_lock: + while datetime.now() - _lastCheck < timedelta(seconds=1.05): + await asyncio.sleep(0.1) + _lastCheck = datetime.now()(Apply this change to both
getTodayWeatherandgetFutureWeather.)🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@plugin/Weather_d/GetWeather.py` around lines 34 - 38, Protect the shared _lastCheck rate-limit state in both getTodayWeather and getFutureWeather with a module-level asyncio.Lock. Acquire the lock around each method’s wait loop and _lastCheck update so concurrent requests serialize their timing checks while preserving the existing delay behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@config.yaml`:
- Around line 1-2: Remove the exposed clientSecret and any other real
credentials from config.yaml, revoke and rotate the leaked QQ Gateway secret,
and replace repository values with placeholders or environment-variable
references. Update .gitignore to exclude config.yaml so local credentials cannot
be committed again.
In `@modules/HermesWss/chunked_upload.py`:
- Around line 385-387: Update the retry-delay condition in the complete-upload
retry loop around the visible backoff calculation so it also sleeps before the
final retry attempt. Match the boundary behavior used by _upload_single_part and
_PART_UPLOAD_MAX_RETRIES, while preserving the existing exponential delay
calculation.
In `@modules/HermesWss/wss_client.py`:
- Line 56: Replace stdlib logging throughout HermesWss with loguru: in
modules/HermesWss/wss_client.py:56-56, sender.py:32-32, onboard.py:38-38,
keyboards.py:25-25, and chunked_upload.py:30-30, remove each logging import and
getLogger setup and import the module-level logger from loguru; in
onboard.py:38-38 also replace the print block with appropriate logger calls
while preserving its messages.
- Around line 311-317: Update the WebSocket connection setup in the surrounding
ws_connect flow to apply CONNECT_TIMEOUT_SECONDS to the connection-establishment
timeout rather than only the ws_close timeout. Configure the aiohttp timeout
parameter that bounds connect setup, while preserving the existing gateway_url,
headers, and close-timeout behavior.
In `@plugin/AiChat_d/Config.py`:
- Around line 6-18: Replace the unsafe yaml.load call in the Config
configuration-loading flow with yaml.safe_load, and replace the TypedDict Config
definition with a Pydantic model that validates the configuration fields and
types. Instantiate that model from the safely loaded YAML data while preserving
the existing Config field names and Paths.CONFIG_FILE source.
In `@plugin/AiChat_d/config.yaml`:
- Line 1: Remove the hardcoded value from the apiKey configuration in
config.yaml and replace it with the project’s supported environment-variable
reference or a non-secret placeholder. Revoke the exposed API key through the
provider, without adding another secret to the repository.
In `@plugin/AiChat_d/History.py`:
- Line 3: Replace the wildcard import from plugin.AiChat_d.Sql in History.py
with explicit imports for only the Sql symbols used by the module, preserving
existing behavior and avoiding namespace collisions.
In `@plugin/AiChat_d/Sql.py`:
- Around line 147-156: Update the truncation logic in the message cleanup block
to preserve the oldest system prompt at all times: when deleting messages beyond
keep_count from the newest-first all_msgs list, exclude the final element (index
-1) from the deletion slice while still removing excess intermediate messages.
In `@plugin/AiChat_d/sql/init.sql`:
- Around line 1-17: The SQL schema and queries must match the ORM models in
Sql.py. In plugin/AiChat_d/sql/init.sql lines 1-17, rename users/messages to
user/message, rename open_id/updated_at/time to openId/latestChat/createdAt, and
add messageCount to user; update plugin/AiChat_d/sql/message/add.sql lines 1-8
to use message/user with openId/latestChat, plugin/AiChat_d/sql/message/get.sql
lines 1-3 to use message with openId/createdAt, and
plugin/AiChat_d/sql/user/add.sql lines 1 and plugin/AiChat_d/sql/user/check.sql
lines 1-2 to use user and openId.
In `@plugin/AiChat_d/sql/message/clear.sql`:
- Line 1: Update the DELETE statement in the clear SQL script to use valid
DELETE FROM syntax and target the messages table name defined by init.sql, while
preserving the open_id filter.
In `@plugin/AiChat_d/sql/user/del.sql`:
- Line 1: Update the DELETE statement in the user deletion SQL to use valid
DELETE FROM syntax, and replace the positional placeholder with the named
open_id parameter format used by the surrounding SQL files.
In `@plugin/AiChat_d/Types.py`:
- Around line 1-39: Replace the TypedDict definitions in Types.py with Pydantic
BaseModel classes, preserving the existing fields, annotations, nesting, and
defaults or required-field behavior. Convert ApiJson_Messages, ApiJson,
ReplyJson_Choices_Message, ReplyJson_Choices, and ReplyJson consistently, using
BaseModel-compatible nested model types.
In `@plugin/AiChat.py`:
- Line 40: Update the union_id assignment to use safe dictionary lookups for
both author fields, preserving the fallback from union_openid to id without
raising KeyError when either key is absent.
---
Outside diff comments:
In `@plugin/Holiday_d/RequApi.py`:
- Around line 7-33: Convert updataCache and checkAndUpdata in RequApi.py to
async functions, perform requests through httpx.AsyncClient with awaited calls,
and preserve the existing retry, caching, and logging behavior. Update the
Holiday.run caller to await RequApi.checkAndUpdata() before setting _checked.
---
Nitpick comments:
In `@CLAUDE.md`:
- Line 30: Specify the text language on the fenced code block openings in
CLAUDE.md (line 30) and modules/HermesWss/README.md (line 28) by adding the text
identifier; no other content changes are needed.
In `@modules/HermesWss/models.py`:
- Around line 404-407: Update the callback type aliases PostMessageFn,
OnMessageCallback, and OnInteractionCallback to use Coroutine[Any, Any,
ReturnType] or Awaitable[ReturnType] instead of treating
asyncio.AbstractEventLoop as the coroutine yield type. Add or reuse the
appropriate Any import and preserve each alias’s existing parameter and return
types.
In `@plugin/Weather_d/GetWeather.py`:
- Around line 34-38: Protect the shared _lastCheck rate-limit state in both
getTodayWeather and getFutureWeather with a module-level asyncio.Lock. Acquire
the lock around each method’s wait loop and _lastCheck update so concurrent
requests serialize their timing checks while preserving the existing delay
behavior.
In `@plugin/Weather.py`:
- Around line 75-103: Update the `/天气预报` and `/常住地` branches in the message
handler to pass the `target` returned by `_reply_target(message)` to
`HttpSender.sendText2Person` instead of `author["id"]`. Preserve the existing
group-message behavior and response content.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 8caceef4-9ff3-4524-b0c1-63cb946d1d72
⛔ Files ignored due to path filters (1)
uv.lockis excluded by!**/*.lock
📒 Files selected for processing (72)
.github/workflows/black-format.yml.github/workflows/codeql.yml.gitignore.python-version.sourcery.yml.vscode/launch.json.vscode/settings.jsonCLAUDE.mdLICENSEQBot.code-workspaceREADME.mdconfig.yamlmain.pymodules/AutoAuth.pymodules/Config.pymodules/HermesWss/README.mdmodules/HermesWss/__init__.pymodules/HermesWss/chunked_upload.pymodules/HermesWss/constants.pymodules/HermesWss/crypto.pymodules/HermesWss/exceptions.pymodules/HermesWss/keyboards.pymodules/HermesWss/models.pymodules/HermesWss/onboard.pymodules/HermesWss/sender.pymodules/HermesWss/token_manager.pymodules/HermesWss/utils.pymodules/HermesWss/wss_client.pymodules/HttpSender.pymodules/Types.pymodules/Urls.pyplugin/AiChat.pyplugin/AiChat_d/.gitignoreplugin/AiChat_d/Config.pyplugin/AiChat_d/History.pyplugin/AiChat_d/Paths.pyplugin/AiChat_d/RequApi.pyplugin/AiChat_d/Sql.pyplugin/AiChat_d/Types.pyplugin/AiChat_d/config.yamlplugin/AiChat_d/data/personality/xy.mdplugin/AiChat_d/sql/init.sqlplugin/AiChat_d/sql/message/add.sqlplugin/AiChat_d/sql/message/clear.sqlplugin/AiChat_d/sql/message/get.sqlplugin/AiChat_d/sql/user/add.sqlplugin/AiChat_d/sql/user/check.sqlplugin/AiChat_d/sql/user/del.sqlplugin/GlmAi.pyplugin/GlmAi_d/Config.pyplugin/GlmAi_d/config.yamlplugin/Holiday.pyplugin/Holiday_d/.gitignoreplugin/Holiday_d/RequApi.pyplugin/Holiday_d/SqlCache.pyplugin/Holiday_d/test.httpplugin/PluginBase.pyplugin/TestAutoReply.pyplugin/Weather.pyplugin/Weather_d/.gitignoreplugin/Weather_d/GetWeather.pyplugin/Weather_d/SqlCache.pyplugin/Weather_d/config.yamlplugin/Weather_d/test.httpplugin/__init__.pypyproject.tomlrun.shtest/ai.jsontest/aiChat.pytest/passCallback.pytest/recieveHttp.pytest/testSqlmodel.py
💤 Files with no reviewable changes (3)
- plugin/GlmAi_d/config.yaml
- plugin/GlmAi_d/Config.py
- plugin/GlmAi.py
There was a problem hiding this comment.
Caution
Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.
Actionable comments posted: 13
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
plugin/Holiday_d/RequApi.py (1)
7-33: 🚀 Performance & Scalability | 🔴 Critical | ⚡ Quick winAvoid blocking the async event loop with synchronous HTTP calls.
updataCacheperforms a synchronoushttpx.getrequest. Since it is called from the asynchronousHoliday.runmethod, this blocking network I/O will halt the entireasyncioevent loop. If the request takes a few seconds or times out, it can prevent the bot from responding to other messages or sending heartbeats, which may cause the WebSocket to disconnect.Convert these functions to use
async/awaitwithhttpx.AsyncClientto avoid blocking the event loop.⚡ Proposed fix
Update
plugin/Holiday_d/RequApi.py:-def updataCache(): +async def updataCache(): url = "https://timor.tech/api/holiday/year" for _ in range(3): try: - resp = httpx.get(url, timeout=10) - if resp.status_code == 200: - reply = resp.json() - if reply["code"] == 0: - for _, holiday in reply["holiday"].items(): - Cache.addCache( - holiday["name"], - datetime.strptime(holiday["date"], "%Y-%m-%d"), - holiday["holiday"], - ) - logger.info("获取节假日信息成功") - return + async with httpx.AsyncClient() as client: + resp = await client.get(url, timeout=10) + if resp.status_code == 200: + reply = resp.json() + if reply["code"] == 0: + for _, holiday in reply["holiday"].items(): + Cache.addCache( + holiday["name"], + datetime.strptime(holiday["date"], "%Y-%m-%d"), + holiday["holiday"], + ) + logger.info("获取节假日信息成功") + return except Exception as e: logger.warning(f"尝试获取节假日信息失败,错误信息:{e}") continue logger.error("获取节假日信息失败,请检查网络连接") return None -def checkAndUpdata(): +async def checkAndUpdata(): cachedYear = Cache.getCachedYear() if cachedYear is None or cachedYear.year != datetime.now().year: - updataCache() + await updataCache()Then, update the caller in
plugin/Holiday.pyaround line 32:if not self._checked: - RequApi.checkAndUpdata() + await RequApi.checkAndUpdata() self._checked = True🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@plugin/Holiday_d/RequApi.py` around lines 7 - 33, Convert updataCache and checkAndUpdata in RequApi.py to async functions, perform requests through httpx.AsyncClient with awaited calls, and preserve the existing retry, caching, and logging behavior. Update the Holiday.run caller to await RequApi.checkAndUpdata() before setting _checked.
🧹 Nitpick comments (4)
CLAUDE.md (1)
30-30: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueSpecify a language for fenced code blocks.
These code blocks omit a language identifier, which degrades syntax highlighting in viewers and triggers markdownlint warnings (MD040).
CLAUDE.md#L30-L30: addtextto the fenced code block opening to specify the language.modules/HermesWss/README.md#L28-L28: addtextto the fenced code block opening to specify the language.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@CLAUDE.md` at line 30, Specify the text language on the fenced code block openings in CLAUDE.md (line 30) and modules/HermesWss/README.md (line 28) by adding the text identifier; no other content changes are needed.Source: Linters/SAST tools
modules/HermesWss/models.py (1)
404-407: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winUse
Any/Awaitablefor these callback aliases.CoroutineisCoroutine[YieldType, SendType, ReturnType], soasyncio.AbstractEventLoopis the wrong first parameter here. UpdatePostMessageFn,OnMessageCallback, andOnInteractionCallbacktoCoroutine[Any, Any, ...](orAwaitable[...]if only awaitability is needed).🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@modules/HermesWss/models.py` around lines 404 - 407, Update the callback type aliases PostMessageFn, OnMessageCallback, and OnInteractionCallback to use Coroutine[Any, Any, ReturnType] or Awaitable[ReturnType] instead of treating asyncio.AbstractEventLoop as the coroutine yield type. Add or reuse the appropriate Any import and preserve each alias’s existing parameter and return types.Source: Coding guidelines
plugin/Weather.py (1)
75-103: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueConsistently use
targetfrom_reply_target.In the
/天气预报and/常住地branches,author["id"]is used instead of thetargetvariable returned by_reply_target. While functionally equivalent for person messages, usingtargetconsistently is cleaner and fully utilizes the helper function (as done in the/今日天气branch).♻️ Proposed refactor
logger.info(f"发送天气信息: {weather}") send_type, target = self._reply_target(message) if send_type == "group": await HttpSender.sendText2Group(weather, message, target) else: - await HttpSender.sendText2Person(weather, message, author["id"]) + await HttpSender.sendText2Person(weather, message, target) elif content.startswith("/常住地"): if len(content.split(" ")) != 2: send_type, target = self._reply_target(message) if send_type == "group": await HttpSender.sendText2Group( "输入格式错误. 请输入 /常住地 <城市名>", message, target ) else: await HttpSender.sendText2Person( - "输入格式错误. 请输入 /常住地 <城市名>", message, author["id"] + "输入格式错误. 请输入 /常住地 <城市名>", message, target ) else: city = content.split(" ")[1] SqlCache.User.add(author["id"], city) send_type, target = self._reply_target(message) if send_type == "group": await HttpSender.sendText2Group( f"设置常住地: {city}", message, target ) else: await HttpSender.sendText2Person( - f"设置常住地: {city}", message, author["id"] + f"设置常住地: {city}", message, target )🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@plugin/Weather.py` around lines 75 - 103, Update the `/天气预报` and `/常住地` branches in the message handler to pass the `target` returned by `_reply_target(message)` to `HttpSender.sendText2Person` instead of `author["id"]`. Preserve the existing group-message behavior and response content.plugin/Weather_d/GetWeather.py (1)
34-38: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low valuePrevent rate-limit race conditions under concurrent load.
The current rate-limiting logic uses a global
_lastCheckvariable without synchronization. If multiple users request weather data simultaneously, they might all bypass thewhileloop at the same time and hit the API concurrently, exceeding the rate limit.Consider using an
asyncio.Lock()to ensure that concurrent requests wait their turn and accurately update_lastCheck.🔒️ Proposed refactor to add a lock
Add a global lock at the top of the file:
import asyncio _rate_limit_lock = asyncio.Lock()Then, wrap the rate-limit check and the update of
_lastCheckinside the lock:- # 防止请求过快 - while datetime.now() - _lastCheck < timedelta(seconds=1.05): - await asyncio.sleep(1) - _lastCheck = datetime.now() + # 防止请求过快 + async with _rate_limit_lock: + while datetime.now() - _lastCheck < timedelta(seconds=1.05): + await asyncio.sleep(0.1) + _lastCheck = datetime.now()(Apply this change to both
getTodayWeatherandgetFutureWeather.)🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@plugin/Weather_d/GetWeather.py` around lines 34 - 38, Protect the shared _lastCheck rate-limit state in both getTodayWeather and getFutureWeather with a module-level asyncio.Lock. Acquire the lock around each method’s wait loop and _lastCheck update so concurrent requests serialize their timing checks while preserving the existing delay behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@config.yaml`:
- Around line 1-2: Remove the exposed clientSecret and any other real
credentials from config.yaml, revoke and rotate the leaked QQ Gateway secret,
and replace repository values with placeholders or environment-variable
references. Update .gitignore to exclude config.yaml so local credentials cannot
be committed again.
In `@modules/HermesWss/chunked_upload.py`:
- Around line 385-387: Update the retry-delay condition in the complete-upload
retry loop around the visible backoff calculation so it also sleeps before the
final retry attempt. Match the boundary behavior used by _upload_single_part and
_PART_UPLOAD_MAX_RETRIES, while preserving the existing exponential delay
calculation.
In `@modules/HermesWss/wss_client.py`:
- Line 56: Replace stdlib logging throughout HermesWss with loguru: in
modules/HermesWss/wss_client.py:56-56, sender.py:32-32, onboard.py:38-38,
keyboards.py:25-25, and chunked_upload.py:30-30, remove each logging import and
getLogger setup and import the module-level logger from loguru; in
onboard.py:38-38 also replace the print block with appropriate logger calls
while preserving its messages.
- Around line 311-317: Update the WebSocket connection setup in the surrounding
ws_connect flow to apply CONNECT_TIMEOUT_SECONDS to the connection-establishment
timeout rather than only the ws_close timeout. Configure the aiohttp timeout
parameter that bounds connect setup, while preserving the existing gateway_url,
headers, and close-timeout behavior.
In `@plugin/AiChat_d/Config.py`:
- Around line 6-18: Replace the unsafe yaml.load call in the Config
configuration-loading flow with yaml.safe_load, and replace the TypedDict Config
definition with a Pydantic model that validates the configuration fields and
types. Instantiate that model from the safely loaded YAML data while preserving
the existing Config field names and Paths.CONFIG_FILE source.
In `@plugin/AiChat_d/config.yaml`:
- Line 1: Remove the hardcoded value from the apiKey configuration in
config.yaml and replace it with the project’s supported environment-variable
reference or a non-secret placeholder. Revoke the exposed API key through the
provider, without adding another secret to the repository.
In `@plugin/AiChat_d/History.py`:
- Line 3: Replace the wildcard import from plugin.AiChat_d.Sql in History.py
with explicit imports for only the Sql symbols used by the module, preserving
existing behavior and avoiding namespace collisions.
In `@plugin/AiChat_d/Sql.py`:
- Around line 147-156: Update the truncation logic in the message cleanup block
to preserve the oldest system prompt at all times: when deleting messages beyond
keep_count from the newest-first all_msgs list, exclude the final element (index
-1) from the deletion slice while still removing excess intermediate messages.
In `@plugin/AiChat_d/sql/init.sql`:
- Around line 1-17: The SQL schema and queries must match the ORM models in
Sql.py. In plugin/AiChat_d/sql/init.sql lines 1-17, rename users/messages to
user/message, rename open_id/updated_at/time to openId/latestChat/createdAt, and
add messageCount to user; update plugin/AiChat_d/sql/message/add.sql lines 1-8
to use message/user with openId/latestChat, plugin/AiChat_d/sql/message/get.sql
lines 1-3 to use message with openId/createdAt, and
plugin/AiChat_d/sql/user/add.sql lines 1 and plugin/AiChat_d/sql/user/check.sql
lines 1-2 to use user and openId.
In `@plugin/AiChat_d/sql/message/clear.sql`:
- Line 1: Update the DELETE statement in the clear SQL script to use valid
DELETE FROM syntax and target the messages table name defined by init.sql, while
preserving the open_id filter.
In `@plugin/AiChat_d/sql/user/del.sql`:
- Line 1: Update the DELETE statement in the user deletion SQL to use valid
DELETE FROM syntax, and replace the positional placeholder with the named
open_id parameter format used by the surrounding SQL files.
In `@plugin/AiChat_d/Types.py`:
- Around line 1-39: Replace the TypedDict definitions in Types.py with Pydantic
BaseModel classes, preserving the existing fields, annotations, nesting, and
defaults or required-field behavior. Convert ApiJson_Messages, ApiJson,
ReplyJson_Choices_Message, ReplyJson_Choices, and ReplyJson consistently, using
BaseModel-compatible nested model types.
In `@plugin/AiChat.py`:
- Line 40: Update the union_id assignment to use safe dictionary lookups for
both author fields, preserving the fallback from union_openid to id without
raising KeyError when either key is absent.
---
Outside diff comments:
In `@plugin/Holiday_d/RequApi.py`:
- Around line 7-33: Convert updataCache and checkAndUpdata in RequApi.py to
async functions, perform requests through httpx.AsyncClient with awaited calls,
and preserve the existing retry, caching, and logging behavior. Update the
Holiday.run caller to await RequApi.checkAndUpdata() before setting _checked.
---
Nitpick comments:
In `@CLAUDE.md`:
- Line 30: Specify the text language on the fenced code block openings in
CLAUDE.md (line 30) and modules/HermesWss/README.md (line 28) by adding the text
identifier; no other content changes are needed.
In `@modules/HermesWss/models.py`:
- Around line 404-407: Update the callback type aliases PostMessageFn,
OnMessageCallback, and OnInteractionCallback to use Coroutine[Any, Any,
ReturnType] or Awaitable[ReturnType] instead of treating
asyncio.AbstractEventLoop as the coroutine yield type. Add or reuse the
appropriate Any import and preserve each alias’s existing parameter and return
types.
In `@plugin/Weather_d/GetWeather.py`:
- Around line 34-38: Protect the shared _lastCheck rate-limit state in both
getTodayWeather and getFutureWeather with a module-level asyncio.Lock. Acquire
the lock around each method’s wait loop and _lastCheck update so concurrent
requests serialize their timing checks while preserving the existing delay
behavior.
In `@plugin/Weather.py`:
- Around line 75-103: Update the `/天气预报` and `/常住地` branches in the message
handler to pass the `target` returned by `_reply_target(message)` to
`HttpSender.sendText2Person` instead of `author["id"]`. Preserve the existing
group-message behavior and response content.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 8caceef4-9ff3-4524-b0c1-63cb946d1d72
⛔ Files ignored due to path filters (1)
uv.lockis excluded by!**/*.lock
📒 Files selected for processing (72)
.github/workflows/black-format.yml.github/workflows/codeql.yml.gitignore.python-version.sourcery.yml.vscode/launch.json.vscode/settings.jsonCLAUDE.mdLICENSEQBot.code-workspaceREADME.mdconfig.yamlmain.pymodules/AutoAuth.pymodules/Config.pymodules/HermesWss/README.mdmodules/HermesWss/__init__.pymodules/HermesWss/chunked_upload.pymodules/HermesWss/constants.pymodules/HermesWss/crypto.pymodules/HermesWss/exceptions.pymodules/HermesWss/keyboards.pymodules/HermesWss/models.pymodules/HermesWss/onboard.pymodules/HermesWss/sender.pymodules/HermesWss/token_manager.pymodules/HermesWss/utils.pymodules/HermesWss/wss_client.pymodules/HttpSender.pymodules/Types.pymodules/Urls.pyplugin/AiChat.pyplugin/AiChat_d/.gitignoreplugin/AiChat_d/Config.pyplugin/AiChat_d/History.pyplugin/AiChat_d/Paths.pyplugin/AiChat_d/RequApi.pyplugin/AiChat_d/Sql.pyplugin/AiChat_d/Types.pyplugin/AiChat_d/config.yamlplugin/AiChat_d/data/personality/xy.mdplugin/AiChat_d/sql/init.sqlplugin/AiChat_d/sql/message/add.sqlplugin/AiChat_d/sql/message/clear.sqlplugin/AiChat_d/sql/message/get.sqlplugin/AiChat_d/sql/user/add.sqlplugin/AiChat_d/sql/user/check.sqlplugin/AiChat_d/sql/user/del.sqlplugin/GlmAi.pyplugin/GlmAi_d/Config.pyplugin/GlmAi_d/config.yamlplugin/Holiday.pyplugin/Holiday_d/.gitignoreplugin/Holiday_d/RequApi.pyplugin/Holiday_d/SqlCache.pyplugin/Holiday_d/test.httpplugin/PluginBase.pyplugin/TestAutoReply.pyplugin/Weather.pyplugin/Weather_d/.gitignoreplugin/Weather_d/GetWeather.pyplugin/Weather_d/SqlCache.pyplugin/Weather_d/config.yamlplugin/Weather_d/test.httpplugin/__init__.pypyproject.tomlrun.shtest/ai.jsontest/aiChat.pytest/passCallback.pytest/recieveHttp.pytest/testSqlmodel.py
💤 Files with no reviewable changes (3)
- plugin/GlmAi_d/config.yaml
- plugin/GlmAi_d/Config.py
- plugin/GlmAi.py
🛑 Comments failed to post (13)
config.yaml (1)
1-2: 🔒 Security & Privacy | 🔴 Critical | ⚡ Quick win
Do not commit real credentials to the repository.
The
clientSecretappears to be a real, sensitive API key for your QQ Gateway application. Hardcoding secrets in the repository poses a critical security risk. Please revoke and rotate this secret immediately in the QQ Open Platform. Replace the values inconfig.yamlwith placeholder strings, and ensureconfig.yamlis added to.gitignoreto prevent future leaks (or load credentials from environment variables).🛡️ Proposed fix to remove secrets
-appId: "1905194016" -clientSecret: OgyHbvGcyLj8XxOpHkEiDjGnLuT3eGsV +appId: "YOUR_APP_ID" +clientSecret: "YOUR_CLIENT_SECRET"📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.appId: "YOUR_APP_ID" clientSecret: "YOUR_CLIENT_SECRET"🧰 Tools
🪛 Betterleaks (1.6.1)
[high] 2-2: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.
(generic-api-key)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@config.yaml` around lines 1 - 2, Remove the exposed clientSecret and any other real credentials from config.yaml, revoke and rotate the leaked QQ Gateway secret, and replace repository values with placeholders or environment-variable references. Update .gitignore to exclude config.yaml so local credentials cannot be committed again.Source: Linters/SAST tools
modules/HermesWss/chunked_upload.py (1)
385-387: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Backoff skips the delay before the final retry.
With
_COMPLETE_UPLOAD_MAX_RETRIES = 2the loop runs attempts 0,1,2, butattempt < _COMPLETE_UPLOAD_MAX_RETRIES - 1(i.e.< 1) sleeps only after attempt 0. The retry from attempt 1 → 2 fires immediately with no backoff. This diverges from_upload_single_part(Line 332), which correctly usesattempt < _PART_UPLOAD_MAX_RETRIES.🐛 Proposed fix
- if attempt < _COMPLETE_UPLOAD_MAX_RETRIES - 1: + if attempt < _COMPLETE_UPLOAD_MAX_RETRIES: delay: float = _COMPLETE_UPLOAD_BASE_DELAY * (2.0 ** attempt) await asyncio.sleep(delay)📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.if attempt < _COMPLETE_UPLOAD_MAX_RETRIES: delay: float = _COMPLETE_UPLOAD_BASE_DELAY * (2.0 ** attempt) await asyncio.sleep(delay)🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@modules/HermesWss/chunked_upload.py` around lines 385 - 387, Update the retry-delay condition in the complete-upload retry loop around the visible backoff calculation so it also sleeps before the final retry attempt. Match the boundary behavior used by _upload_single_part and _PART_UPLOAD_MAX_RETRIES, while preserving the existing exponential delay calculation.modules/HermesWss/wss_client.py (2)
56-56: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win
Replace standard-library
logging(andloguruacross HermesWss. Repository guidelines requirelogurufor logging output and disallow bothloggingmodule. Each module currently creates alogging.getLogger(__name__)logger; replace the import/logger setup withfrom loguru import loggerand drop thegetLoggercall (loguru exposes a module-levellogger).
modules/HermesWss/wss_client.py#L56-L56: removeimport logging(Line 22) andlogger = logging.getLogger(__name__); usefrom loguru import logger.modules/HermesWss/sender.py#L32-L32: removeimport logging(Line 13) and thegetLoggercall; usefrom loguru import logger.modules/HermesWss/onboard.py#L38-L38: removeimport logging(Line 19) and thegetLoggercall; also convert theprint(...)block (Lines 278-283) tologgercalls.modules/HermesWss/keyboards.py#L25-L25: removeimport logging(Line 9) and thegetLoggercall; usefrom loguru import logger.modules/HermesWss/chunked_upload.py#L30-L30: removeimport logging(Line 17) and thegetLoggercall; usefrom loguru import logger.📍 Affects 5 files
modules/HermesWss/wss_client.py#L56-L56(this comment)modules/HermesWss/sender.py#L32-L32modules/HermesWss/onboard.py#L38-L38modules/HermesWss/keyboards.py#L25-L25modules/HermesWss/chunked_upload.py#L30-L30🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@modules/HermesWss/wss_client.py` at line 56, Replace stdlib logging throughout HermesWss with loguru: in modules/HermesWss/wss_client.py:56-56, sender.py:32-32, onboard.py:38-38, keyboards.py:25-25, and chunked_upload.py:30-30, remove each logging import and getLogger setup and import the module-level logger from loguru; in onboard.py:38-38 also replace the print block with appropriate logger calls while preserving its messages.Source: Coding guidelines
311-317: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🌐 Web query:
aiohttp ClientWSTimeout ws_close ws_receive fields meaning ws_connect timeout💡 Result:
In aiohttp, ClientWSTimeout is a dataclass used to configure timeout settings specifically for WebSocket connections [1][2]. It is passed as the timeout argument to the ws_connect coroutine [1][3]. The fields of ClientWSTimeout are defined as follows: ws_receive: A float (or None) representing the maximum time allowed to receive a complete message [1][2]. It defaults to None, meaning no timeout [1]. ws_close: A float (or None) representing the maximum time allowed for the WebSocket to perform a closing handshake [1][2]. It defaults to 10.0 seconds [1][4]. When using ws_connect, these timeouts manage the behavior of the returned ClientWebSocketResponse object [1][2]. The default configuration is a ClientWSTimeout object with ws_receive=None and ws_close=10.0 [1][4]. Note that the timeout parameter in ws_connect is distinct from the general ClientTimeout structure used for standard HTTP requests [2]. While earlier versions of aiohttp accepted a float for the timeout parameter in ws_connect (which would be used for ws_close), this is now deprecated in favor of explicitly using the ClientWSTimeout class [3][5].
Citations:
- 1: http://docs.aiohttp.org/en/stable/client_reference.html
- 2: https://docs.aiohttp.org/en/latest/client%5Freference.html
- 3: https://docs.aiohttp.org/en/stable/_modules/aiohttp/client.html
- 4: https://docs.aiohttp.org/en/latest/_modules/aiohttp/client_ws.html
- 5: https://docs.aiohttp.org/en/latest/%5Fmodules/aiohttp/client.html
🏁 Script executed:
#!/bin/bash set -euo pipefail printf '\n## wss_client.py around the reported lines\n' sed -n '250,360p' modules/HermesWss/wss_client.py printf '\n## timeout-related symbols in modules/HermesWss/wss_client.py\n' rg -n "ClientTimeout|ClientWSTimeout|ws_connect|CONNECT_TIMEOUT_SECONDS|timeout=" modules/HermesWss/wss_client.py printf '\n## session creation in modules/HermesWss/wss_client.py\n' rg -n "_ws_session|ClientSession|Timeout" modules/HermesWss/wss_client.pyRepository: dudu7615/QBot
Length of output: 5422
🏁 Script executed:
#!/bin/bash set -euo pipefail printf '\n## aiohttp docs in repository references\n' rg -n "ClientWSTimeout|ws_close|ws_receive|ws_connect" -g '!**/.git/**' .Repository: dudu7615/QBot
Length of output: 468
🏁 Script executed:
#!/bin/bash set -euo pipefail echo "### file context" wc -l modules/HermesWss/wss_client.py sed -n '290,340p' modules/HermesWss/wss_client.py echo "### session / timeout references" rg -n "_ws_session|ClientSession|ClientTimeout|ClientWSTimeout|ws_connect|CONNECT_TIMEOUT_SECONDS" modules/HermesWss/wss_client.py echo "### nearby websocket code" sed -n '1,220p' modules/HermesWss/wss_client.pyRepository: dudu7615/QBot
Length of output: 9448
CONNECT_TIMEOUT_SECONDSdoesn't cap WebSocket establishment.ClientWSTimeout(ws_close=...)only covers the close handshake, so thisws_connectcall is still governed by the session's timeout defaults. If the goal is to bound connect setup, apply a connect/session timeout here instead.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@modules/HermesWss/wss_client.py` around lines 311 - 317, Update the WebSocket connection setup in the surrounding ws_connect flow to apply CONNECT_TIMEOUT_SECONDS to the connection-establishment timeout rather than only the ws_close timeout. Configure the aiohttp timeout parameter that bounds connect setup, while preserving the existing gateway_url, headers, and close-timeout behavior.plugin/AiChat_d/Config.py (1)
6-18: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
Use
yaml.safe_loadand define data models with Pydantic.
yaml.loadwithFullLoaderis susceptible to arbitrary code execution vulnerabilities. Additionally, the coding guidelines mandate the use of Pydantic for data models instead ofTypedDict.🛡️ Proposed fix with Pydantic validation
-from typing import TypedDict +from pydantic import BaseModel import yaml from plugin.AiChat_d import Paths -class Config(TypedDict): +class ConfigModel(BaseModel): apiKey: str url: str model: str personality: str max_context_length: int # 超过此消息数时触发上下文截断 keep_recent: int # 截断后保留的最新消息数(含 system prompt) -config: Config = yaml.load( - open(Paths.CONFIG_FILE, "r", encoding="utf8"), - Loader=yaml.FullLoader, -) +with open(Paths.CONFIG_FILE, "r", encoding="utf8") as f: + config = ConfigModel(**yaml.safe_load(f)).model_dump()📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.class ConfigModel(BaseModel): apiKey: str url: str model: str personality: str max_context_length: int # 超过此消息数时触发上下文截断 keep_recent: int # 截断后保留的最新消息数(含 system prompt) with open(Paths.CONFIG_FILE, "r", encoding="utf8") as f: config = ConfigModel(**yaml.safe_load(f)).model_dump()🧰 Tools
🪛 ast-grep (0.44.1)
[warning] 15-15: File path is request-/variable-derived; validate and normalize to prevent path traversal.
Context: open(Paths.CONFIG_FILE, "r", encoding="utf8")
Note: [CWE-22] Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal').(open-filename-from-request)
🪛 OpenGrep (1.25.0)
[ERROR] 15-18: yaml.load() without SafeLoader can execute arbitrary Python code. Use yaml.safe_load() or yaml.load(..., Loader=SafeLoader) instead.
(coderabbit.deserialization.python-yaml-unsafe-load)
🪛 Ruff (0.15.21)
[warning] 12-12: Comment contains ambiguous
((FULLWIDTH LEFT PARENTHESIS). Did you mean((LEFT PARENTHESIS)?(RUF003)
[warning] 12-12: Comment contains ambiguous
)(FULLWIDTH RIGHT PARENTHESIS). Did you mean)(RIGHT PARENTHESIS)?(RUF003)
[error] 17-17: Probable use of unsafe loader
FullLoaderwithyaml.load. Allows instantiation of arbitrary objects. Consideryaml.safe_load.(S506)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@plugin/AiChat_d/Config.py` around lines 6 - 18, Replace the unsafe yaml.load call in the Config configuration-loading flow with yaml.safe_load, and replace the TypedDict Config definition with a Pydantic model that validates the configuration fields and types. Instantiate that model from the safely loaded YAML data while preserving the existing Config field names and Paths.CONFIG_FILE source.Sources: Coding guidelines, Linters/SAST tools
plugin/AiChat_d/config.yaml (1)
1-1: 🔒 Security & Privacy | 🔴 Critical | ⚡ Quick win
Remove hardcoded API key.
The file contains a hardcoded API key (
sk-3PNdKhLiSokJybbnW4O9aER1IZjiQ8lNYJS9c9SBAWjYRxUW). Hardcoding secrets in the codebase poses a severe security risk, even for development configuration files. Please remove this key and use an environment variable or a placeholder instead, and ensure the exposed key is revoked.🛡️ Proposed fix
-apiKey: sk-3PNdKhLiSokJybbnW4O9aER1IZjiQ8lNYJS9c9SBAWjYRxUW +apiKey: "YOUR_API_KEY_HERE"📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.apiKey: "YOUR_API_KEY_HERE"🧰 Tools
🪛 Betterleaks (1.6.1)
[high] 1-1: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.
(generic-api-key)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@plugin/AiChat_d/config.yaml` at line 1, Remove the hardcoded value from the apiKey configuration in config.yaml and replace it with the project’s supported environment-variable reference or a non-secret placeholder. Revoke the exposed API key through the provider, without adding another secret to the repository.Source: Linters/SAST tools
plugin/AiChat_d/History.py (1)
3-3: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Replace wildcard import with explicit imports.
Wildcard imports make it difficult to trace dependencies and can cause namespace collisions.
♻️ Proposed fix
-from plugin.AiChat_d.Sql import * +from plugin.AiChat_d.Sql import Message, MessageRole, User📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.from plugin.AiChat_d.Sql import Message, MessageRole, User🧰 Tools
🪛 Ruff (0.15.21)
[error] 3-3:
from plugin.AiChat_d.Sql import *used; unable to detect undefined names(F403)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@plugin/AiChat_d/History.py` at line 3, Replace the wildcard import from plugin.AiChat_d.Sql in History.py with explicit imports for only the Sql symbols used by the module, preserving existing behavior and avoiding namespace collisions.plugin/AiChat_d/Sql.py (1)
147-156: 🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win
Prevent the system prompt from being deleted during truncation.
The messages are sorted by
createdAt DESC(newest first), which means the system prompt (the oldest message) is located at the end of theall_msgslist. Deletingall_msgs[keep_count:]removes the oldest messages, inherently deleting the vital system prompt. SincemessageCountwill not be zero during the next call, the prompt will never be reloaded, permanently breaking the bot's persona for the user.Adjust the slice bounds to preserve both the most recent messages and the system prompt (the absolute oldest message at index
-1).🐛 Proposed fix to preserve the system prompt
all_msgs: list[Message] = list( session.exec( select(Message) .where(Message.openId == openId) .order_by(text("createdAt DESC")) ) ) if len(all_msgs) > keep_count: - for msg in all_msgs[keep_count:]: + # Delete everything except the first keep_count-1 (newest) and the last 1 (system prompt) + to_delete = all_msgs[keep_count - 1 : -1] if keep_count > 1 else all_msgs[:-1] + for msg in to_delete: session.delete(msg)🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@plugin/AiChat_d/Sql.py` around lines 147 - 156, Update the truncation logic in the message cleanup block to preserve the oldest system prompt at all times: when deleting messages beyond keep_count from the newest-first all_msgs list, exclude the final element (index -1) from the deletion slice while still removing excess intermediate messages.plugin/AiChat_d/sql/init.sql (1)
1-17: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift
Schema mismatch between SQL definitions and ORM models.
The raw SQL files use table and column names that do not match the ORM definitions inSql.py(user,message,openId,latestChat,createdAt). Running these raw SQL queries alongside the ORM will result in missing table or column errors.
plugin/AiChat_d/sql/init.sql#L1-L17: Align the table names (users->user,messages->message) and column names (open_id->openId,updated_at->latestChat,time->createdAt) to match theSql.pymodels. Also, ensure themessageCountcolumn is added to the user table.plugin/AiChat_d/sql/message/add.sql#L1-L8: Update to usemessageandusertables, andopenId,latestChatcolumns.plugin/AiChat_d/sql/message/get.sql#L1-L3: Update to use themessagetable, andopenId,createdAtcolumns.plugin/AiChat_d/sql/user/add.sql#L1-L1: Update to use theusertable andopenIdcolumn.plugin/AiChat_d/sql/user/check.sql#L1-L2: Update to use theusertable andopenIdcolumn.📍 Affects 5 files
plugin/AiChat_d/sql/init.sql#L1-L17(this comment)plugin/AiChat_d/sql/message/add.sql#L1-L8plugin/AiChat_d/sql/message/get.sql#L1-L3plugin/AiChat_d/sql/user/add.sql#L1-L1plugin/AiChat_d/sql/user/check.sql#L1-L2🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@plugin/AiChat_d/sql/init.sql` around lines 1 - 17, The SQL schema and queries must match the ORM models in Sql.py. In plugin/AiChat_d/sql/init.sql lines 1-17, rename users/messages to user/message, rename open_id/updated_at/time to openId/latestChat/createdAt, and add messageCount to user; update plugin/AiChat_d/sql/message/add.sql lines 1-8 to use message/user with openId/latestChat, plugin/AiChat_d/sql/message/get.sql lines 1-3 to use message with openId/createdAt, and plugin/AiChat_d/sql/user/add.sql lines 1 and plugin/AiChat_d/sql/user/check.sql lines 1-2 to use user and openId.plugin/AiChat_d/sql/message/clear.sql (1)
1-1: 🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win
Fix syntax error in
DELETEstatement.The SQL syntax
DELETE * FROMis invalid. The correct syntax isDELETE FROM. Also, note thatinit.sqlnames the tablemessages, notmessage.🐛 Proposed fix
-DELETE * FROM message WHERE open_id = :open_id; +DELETE FROM messages WHERE open_id = :open_id;📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.DELETE FROM messages WHERE open_id = :open_id;🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@plugin/AiChat_d/sql/message/clear.sql` at line 1, Update the DELETE statement in the clear SQL script to use valid DELETE FROM syntax and target the messages table name defined by init.sql, while preserving the open_id filter.plugin/AiChat_d/sql/user/del.sql (1)
1-1: 🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win
Fix syntax error in
DELETEstatement.The SQL syntax
DELETE * FROMis invalid. The correct syntax isDELETE FROM. Also, consider using named parameters (e.g.,:open_id) to stay consistent with the other SQL files.🐛 Proposed fix
-DELETE * FROM users WHERE open_id = ?; +DELETE FROM users WHERE open_id = :open_id;📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.DELETE FROM users WHERE open_id = :open_id;🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@plugin/AiChat_d/sql/user/del.sql` at line 1, Update the DELETE statement in the user deletion SQL to use valid DELETE FROM syntax, and replace the positional placeholder with the named open_id parameter format used by the surrounding SQL files.plugin/AiChat_d/Types.py (1)
1-39: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win
Use Pydantic for data models instead of
TypedDict.As per coding guidelines, define data models and data types with Pydantic. Please convert these
TypedDictdefinitions to PydanticBaseModelclasses.♻️ Proposed refactor to use Pydantic
-from typing import TypedDict +from pydantic import BaseModel -class ApiJson_Messages(TypedDict): +class ApiJson_Messages(BaseModel): role: str # system, user, assistant, tool content: str -class ApiJson(TypedDict): +class ApiJson(BaseModel): model: str # glm-4.5-flash messages: list[ApiJson_Messages] temperature: float # 1 max_tokens: int # 65536 stream: bool # False thinking: dict[str, str] # {"type": "enabled"|"disabled"} do_sample: bool # True top_p: float # 0.95 -class ReplyJson_Choices_Message(ApiJson_Messages): ... +class ReplyJson_Choices_Message(ApiJson_Messages): + pass -class ReplyJson_Choices(TypedDict): +class ReplyJson_Choices(BaseModel): index: int message: ReplyJson_Choices_Message finish_reason: str -class ReplyJson(TypedDict): +class ReplyJson(BaseModel): id: str object: str created: int model: str choices: list[ReplyJson_Choices] usage: dict[str, int] time: str role: str content: str📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.from pydantic import BaseModel class ApiJson_Messages(BaseModel): role: str # system, user, assistant, tool content: str class ApiJson(BaseModel): model: str # glm-4.5-flash messages: list[ApiJson_Messages] temperature: float # 1 max_tokens: int # 65536 stream: bool # False thinking: dict[str, str] # {"type": "enabled"|"disabled"} do_sample: bool # True top_p: float # 0.95 class ReplyJson_Choices_Message(ApiJson_Messages): pass class ReplyJson_Choices(BaseModel): index: int message: ReplyJson_Choices_Message finish_reason: str class ReplyJson(BaseModel): id: str object: str created: int model: str choices: list[ReplyJson_Choices] usage: dict[str, int] time: str role: str content: str🧰 Tools
🪛 Ruff (0.15.21)
[warning] 1-1: The file is executable but no shebang is present
(EXE002)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@plugin/AiChat_d/Types.py` around lines 1 - 39, Replace the TypedDict definitions in Types.py with Pydantic BaseModel classes, preserving the existing fields, annotations, nesting, and defaults or required-field behavior. Convert ApiJson_Messages, ApiJson, ReplyJson_Choices_Message, ReplyJson_Choices, and ReplyJson consistently, using BaseModel-compatible nested model types.Source: Coding guidelines
plugin/AiChat.py (1)
40-40: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
Use
.get()for safe dictionary access.Directly accessing
author["union_openid"]will raise aKeyErrorif the key is missing from the event payload. Use.get()to ensure a safe fallback toauthor["id"].🛠️ Proposed fix
- union_id = author["union_openid"] or author["id"] + union_id = author.get("union_openid") or author.get("id")📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.union_id = author.get("union_openid") or author.get("id")🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@plugin/AiChat.py` at line 40, Update the union_id assignment to use safe dictionary lookups for both author fields, preserving the fallback from union_openid to id without raising KeyError when either key is absent.
Summary by CodeRabbit
New Features
Bug Fixes
Documentation