Skip to content

修复 QQVersionList Rainbow 源#156

Merged
ArcticFoxPro merged 4 commits into
klxiaoniu:masterfrom
ArcticFoxPro:master
May 29, 2026
Merged

修复 QQVersionList Rainbow 源#156
ArcticFoxPro merged 4 commits into
klxiaoniu:masterfrom
ArcticFoxPro:master

Conversation

@ArcticFoxPro

@ArcticFoxPro ArcticFoxPro commented May 29, 2026

Copy link
Copy Markdown
Collaborator

这个 PR 解决了什么问题?

请补充以下信息

更新日志

修复

  • 修复 QQVersionList Rainbow 源

优化

  • Kuikly 图标更新

自检清单

请确认下列所有选项并打勾

  • 此 PR 已实现我的所有预期更改,可以被合并。
  • 我确认此 PR 全部代码仅由本人(或联合作者)编写,代码所有权归本人(或联合作者)所有。
  • 此 PR 更新日志已提供(或无须提供)。
  • Readme 文档无须补充(或已补充)。

请确认下列选项并打勾。如不认可选项内容或存在需备注内容,请在选项旁随附补充说明。

  • 我认可并确认我贡献的代码将以 GNU Affero General Public License Version 3 开源许可进行发行。
  • (单项选择)若后续项目决定更改开源许可:
    • 请通知我并等待我的决定
    • 无需通知,我授权项目所有者自行决定我的贡献的开源许可变更。
    • 其他,请补充说明:_______

Summary by Sourcery

更新 QQ Rainbow 版本来源端点,并刷新 Kuikly 官方图标资源。

Bug 修复:

  • 通过切换到新的 Rainbow 脚本格式,并从 JSON 中提取版本号(而不是使用字符串拆分),修复 QQ Rainbow 版本解析问题。

改进:

  • 将 Kuikly 官方矢量图标更新为新的、更大的 18dp 图标。
Original summary in English

Summary by Sourcery

Update the QQ Rainbow version source endpoint and refresh the Kuikly official icon asset.

Bug Fixes:

  • Fix QQ Rainbow version parsing by switching to the new Rainbow script format and extracting versions from JSON instead of string splitting.

Enhancements:

  • Update the Kuikly official vector drawable to a new, larger 18dp icon.

@sourcery-ai

sourcery-ai Bot commented May 29, 2026

Copy link
Copy Markdown
Contributor
评审者指南(在小型 PR 上折叠)

评审者指南

更新了 Rainbow QQ 版本列表的来源 URL,并将脆弱的字符串拆分解析器替换为健壮的基于 JSON 的实现,同时刷新了 Kuikly 图标矢量资源的尺寸和路径数据。

更新后的 Rainbow QQ 版本列表获取与 JSON 解析时序图

sequenceDiagram
    participant MainActivity
    participant OkHttpClient
    participant Request
    participant Response
    participant VersionUtil
    participant Gson
    participant KotlinJson

    MainActivity->>OkHttpClient: OkHttpClient()
    MainActivity->>Request: Request.Builder().url("https://cdn-go.cn/qq-web/im.qq.com_new/latest/rainbow/androidNewVersion.js").build()
    MainActivity->>OkHttpClient: newCall(request)
    OkHttpClient-->>MainActivity: Call
    MainActivity->>OkHttpClient: execute()
    OkHttpClient-->>MainActivity: Response
    MainActivity->>Response: body
    Response-->>MainActivity: ResponseBody
    MainActivity->>Response: string()
    Response-->>MainActivity: responseData
    MainActivity->>VersionUtil: resolveQQRainbow(viewModel, responseData)

    activate VersionUtil
    VersionUtil->>Gson: fromJson(totalJson, JsonObject::class.java)
    Gson-->>VersionUtil: JsonObject
    VersionUtil->>JsonObject: getAsJsonArray(versions64)
    JsonObject-->>VersionUtil: JsonArray
    loop for each json in JsonArray
        VersionUtil->>KotlinJson: decodeFromString<QQVersionBean>(json.toString())
        KotlinJson-->>VersionUtil: QQVersionBean
        Note right of VersionUtil: Set QQVersionBean.jsonString = json.toString()
    end
    deactivate VersionUtil
Loading

文件级变更

变更 详情 文件
重构 Rainbow QQ 版本列表解析逻辑,从手动字符串拆分改为消费规范 JSON。
  • 调整 substring 起始偏移量,以包含从 '{"versions64",' 开始的完整 JSON 对象,以匹配新的响应格式。
  • 使用 Gson 将提取出的子串解析为 JsonObject,然后直接访问并反转 versions64 数组。
  • 实例化一个 ignoreUnknownKeys=true 的 Kotlinx Json 解析器,并使用它将每个元素解码为 QQVersionBean
  • 在通过现有逻辑标记已安装版本之前,为每个 QQVersionBean 设置 jsonString 为该元素的完整 JSON 字符串。
app/src/main/java/com/xiaoniu/qqversionlist/util/VersionUtil.kt
更新用于获取 QQ 版本列表的 Rainbow 源 URL。
  • 在 OkHttp 请求构造器中,将旧的 https://im.qq.com/rainbow/androidQQVersionList 端点替换为 https://cdn-go.cn/qq-web/im.qq.com_new/latest/rainbow/androidNewVersion.js
app/src/main/java/com/xiaoniu/qqversionlist/ui/MainActivity.kt
刷新 Kuikly 官方图标矢量资源为新的尺寸和图形。
  • 将渲染的图标尺寸从 15dp 增大到 18dp,并将 viewport 从 38x38 扩展到 600x600。
  • 用新的、更复杂的路径数据替换之前的心形路径数据,同时保持 fillColor 仍绑定到 colorControlNormal
app/src/main/res/drawable/kuikly_official.xml

提示与命令

与 Sourcery 交互

  • 触发新的评审: 在 pull request 中评论 @sourcery-ai review
  • 继续讨论: 直接回复 Sourcery 的评审评论。
  • 从评审评论生成 GitHub issue: 在某条评审评论下回复,要求 Sourcery 从该评论创建一个 issue。你也可以直接回复 @sourcery-ai issue,从该评论创建 issue。
  • 生成 pull request 标题: 在 pull request 标题的任意位置写上 @sourcery-ai 以在任何时间生成标题。你也可以在 pull request 中评论 @sourcery-ai title 来(重新)生成标题。
  • 生成 pull request 总结: 在 pull request 内容的任意位置写上 @sourcery-ai summary,在你想要的位置生成 PR 总结。你也可以在 pull request 中评论 @sourcery-ai summary 来在任何时间(重新)生成总结。
  • 生成评审者指南: 在 pull request 中评论 @sourcery-ai guide,在任何时间(重新)生成评审者指南。
  • 解决所有 Sourcery 评论: 在 pull request 中评论 @sourcery-ai resolve,以解决所有 Sourcery 评论。适用于你已经处理完所有评论且不再希望看到它们的情况。
  • 关闭所有 Sourcery 评审: 在 pull request 中评论 @sourcery-ai dismiss,以关闭所有现有的 Sourcery 评审。特别适用于你想从新的评审重新开始的情况——别忘了再评论 @sourcery-ai review 来触发新的评审!

自定义你的使用体验

访问你的 控制面板 以:

  • 启用或禁用诸如 Sourcery 生成的 pull request 总结、评审者指南等评审功能。
  • 更改评审语言。
  • 添加、移除或编辑自定义评审说明。
  • 调整其他评审设置。

获取帮助

Original review guide in English
Reviewer's guide (collapsed on small PRs)

Reviewer's Guide

Updates the Rainbow QQ version list source URL and replaces the brittle string-splitting parser with a robust JSON-based implementation, plus refreshes the Kuikly icon vector asset sizing and path data.

Sequence diagram for updated Rainbow QQ version list fetching and JSON parsing

sequenceDiagram
    participant MainActivity
    participant OkHttpClient
    participant Request
    participant Response
    participant VersionUtil
    participant Gson
    participant KotlinJson

    MainActivity->>OkHttpClient: OkHttpClient()
    MainActivity->>Request: Request.Builder().url("https://cdn-go.cn/qq-web/im.qq.com_new/latest/rainbow/androidNewVersion.js").build()
    MainActivity->>OkHttpClient: newCall(request)
    OkHttpClient-->>MainActivity: Call
    MainActivity->>OkHttpClient: execute()
    OkHttpClient-->>MainActivity: Response
    MainActivity->>Response: body
    Response-->>MainActivity: ResponseBody
    MainActivity->>Response: string()
    Response-->>MainActivity: responseData
    MainActivity->>VersionUtil: resolveQQRainbow(viewModel, responseData)

    activate VersionUtil
    VersionUtil->>Gson: fromJson(totalJson, JsonObject::class.java)
    Gson-->>VersionUtil: JsonObject
    VersionUtil->>JsonObject: getAsJsonArray(versions64)
    JsonObject-->>VersionUtil: JsonArray
    loop for each json in JsonArray
        VersionUtil->>KotlinJson: decodeFromString<QQVersionBean>(json.toString())
        KotlinJson-->>VersionUtil: QQVersionBean
        Note right of VersionUtil: Set QQVersionBean.jsonString = json.toString()
    end
    deactivate VersionUtil
Loading

File-Level Changes

Change Details Files
Refactor Rainbow QQ version list parsing to consume proper JSON instead of manual string splitting.
  • Change substring start offset to include the full JSON object starting at '{"versions64"',[ to match new response format.
  • Parse the extracted substring into a JsonObject via Gson, then access and reverse the 'versions64' array directly.
  • Instantiate a Kotlinx Json parser with ignoreUnknownKeys=true and use it to decode each element into QQVersionBean.
  • Set jsonString on each QQVersionBean to the full JSON string of the element before marking installed versions via existing logic.
app/src/main/java/com/xiaoniu/qqversionlist/util/VersionUtil.kt
Update the Rainbow source URL used to fetch the QQ version list. app/src/main/java/com/xiaoniu/qqversionlist/ui/MainActivity.kt
Refresh Kuikly official icon vector to new dimensions and artwork.
  • Increase rendered icon size from 15dp to 18dp and expand viewport from 38x38 to 600x600.
  • Replace the previous heart-shaped path data with new, more complex path data while keeping fillColor tied to colorControlNormal.
app/src/main/res/drawable/kuikly_official.xml

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - 我发现了两个问题,并给出了一些高层面的反馈:

  • resolveQQRainbow 中,建议在调用 substring 之前,对 start/end 索引做一下健全性检查(例如当 indexOf 返回 -1 时),这样即使 Rainbow 返回数据格式异常或发生变更,也不会导致崩溃。
  • 你在每次调用 resolveQQRainbow 时都会新建一个 GsonKotlinJson 实例;如果这个方法被频繁调用,建议把它们移动到可复用的单例/伴生对象属性中,以避免重复分配。
给 AI Agent 的提示词
Please address the comments from this code review:

## Overall Comments
- In `resolveQQRainbow`, consider adding a sanity check for `start`/`end` indices (e.g., when `indexOf` returns -1) before calling `substring`, so a malformed or changed Rainbow response doesn’t cause a crash.
- You construct a new `Gson` and `KotlinJson` instance on every `resolveQQRainbow` call; if this method is invoked frequently, consider moving these to reusable singletons/companion properties to avoid repeated allocations.

## Individual Comments

### Comment 1
<location path="app/src/main/java/com/xiaoniu/qqversionlist/util/VersionUtil.kt" line_range="53" />
<code_context>
     fun resolveQQRainbow(viewModel: MainActivityViewModel, responseData: String) {
-        val start = (responseData.indexOf("versions64\":[")) + 12
+        val start = (responseData.indexOf("{\"versions64\":["))
         val end = (responseData.indexOf(";\n" + "      typeof"))
         val totalJson = responseData.substring(start, end)
-        var qqVersion: List<QQVersionBean> = mutableListOf<QQVersionBean>()
</code_context>
<issue_to_address>
**issue (bug_risk):** The new JS endpoint might not preserve the exact `";\n      typeof"` delimiter, which could break parsing.

If the new CDN script is minified or its whitespace/newlines differ, `indexOf(";\n" + "      typeof")` may return `-1`, making `substring(start, end)` throw. Consider using a more robust delimiter (e.g., look for a structural marker like the closing `}` before `typeof`, or parse via regex/JSON) and at minimum guard against `end == -1` to avoid crashes.
</issue_to_address>

### Comment 2
<location path="app/src/main/java/com/xiaoniu/qqversionlist/util/VersionUtil.kt" line_range="58" />
<code_context>
+        val totalObject = Gson().fromJson(totalJson, JsonObject::class.java)
+        val jsonParser = KotlinJson { ignoreUnknownKeys = true }
+        val qqVersion: List<QQVersionBean> = totalObject.getAsJsonArray("versions64").reversed().map { json ->
             val qqVersionInstall = DataStoreUtil.getStringKV("QQVersionInstall", "")
-            KotlinJson.decodeFromString<QQVersionBean>(json).apply {
-                jsonString = json
</code_context>
<issue_to_address>
**suggestion (performance):** Avoid repeated DataStore lookups inside the `map` loop.

`DataStoreUtil.getStringKV("QQVersionInstall", "")` is called for every item in `versions64`, even though the value is constant. Read it once before the `map { ... }` and store it in a local `val` to avoid repeated I/O/overhead.

Suggested implementation:

```
        val totalObject = Gson().fromJson(totalJson, JsonObject::class.java)
        val jsonParser = KotlinJson { ignoreUnknownKeys = true }
        val qqVersionInstall = DataStoreUtil.getStringKV("QQVersionInstall", "")
        val qqVersion: List<QQVersionBean> = totalObject.getAsJsonArray("versions64").reversed().map { json ->
            jsonParser.decodeFromString<QQVersionBean>(json.toString()).apply {

```

```
                jsonString = json.toString()
                // 标记本机 Android QQ 版本
                this.apply {
                    // use qqVersionInstall here as needed

```

1. Ensure any logic that previously used the locally-defined `qqVersionInstall` inside the `map` still references the new outer-scoped `qqVersionInstall` variable.
2. If `displayInstall` or other properties depend on `qqVersionInstall`, keep that logic unchanged other than relying on the moved variable.
</issue_to_address>

Sourcery 对开源项目免费——如果你觉得我们的评审有帮助,欢迎分享 ✨
帮我变得更有用!请在每条评论上点击 👍 或 👎,我会根据你的反馈改进后续的代码评审。
Original comment in English

Hey - I've found 2 issues, and left some high level feedback:

  • In resolveQQRainbow, consider adding a sanity check for start/end indices (e.g., when indexOf returns -1) before calling substring, so a malformed or changed Rainbow response doesn’t cause a crash.
  • You construct a new Gson and KotlinJson instance on every resolveQQRainbow call; if this method is invoked frequently, consider moving these to reusable singletons/companion properties to avoid repeated allocations.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `resolveQQRainbow`, consider adding a sanity check for `start`/`end` indices (e.g., when `indexOf` returns -1) before calling `substring`, so a malformed or changed Rainbow response doesn’t cause a crash.
- You construct a new `Gson` and `KotlinJson` instance on every `resolveQQRainbow` call; if this method is invoked frequently, consider moving these to reusable singletons/companion properties to avoid repeated allocations.

## Individual Comments

### Comment 1
<location path="app/src/main/java/com/xiaoniu/qqversionlist/util/VersionUtil.kt" line_range="53" />
<code_context>
     fun resolveQQRainbow(viewModel: MainActivityViewModel, responseData: String) {
-        val start = (responseData.indexOf("versions64\":[")) + 12
+        val start = (responseData.indexOf("{\"versions64\":["))
         val end = (responseData.indexOf(";\n" + "      typeof"))
         val totalJson = responseData.substring(start, end)
-        var qqVersion: List<QQVersionBean> = mutableListOf<QQVersionBean>()
</code_context>
<issue_to_address>
**issue (bug_risk):** The new JS endpoint might not preserve the exact `";\n      typeof"` delimiter, which could break parsing.

If the new CDN script is minified or its whitespace/newlines differ, `indexOf(";\n" + "      typeof")` may return `-1`, making `substring(start, end)` throw. Consider using a more robust delimiter (e.g., look for a structural marker like the closing `}` before `typeof`, or parse via regex/JSON) and at minimum guard against `end == -1` to avoid crashes.
</issue_to_address>

### Comment 2
<location path="app/src/main/java/com/xiaoniu/qqversionlist/util/VersionUtil.kt" line_range="58" />
<code_context>
+        val totalObject = Gson().fromJson(totalJson, JsonObject::class.java)
+        val jsonParser = KotlinJson { ignoreUnknownKeys = true }
+        val qqVersion: List<QQVersionBean> = totalObject.getAsJsonArray("versions64").reversed().map { json ->
             val qqVersionInstall = DataStoreUtil.getStringKV("QQVersionInstall", "")
-            KotlinJson.decodeFromString<QQVersionBean>(json).apply {
-                jsonString = json
</code_context>
<issue_to_address>
**suggestion (performance):** Avoid repeated DataStore lookups inside the `map` loop.

`DataStoreUtil.getStringKV("QQVersionInstall", "")` is called for every item in `versions64`, even though the value is constant. Read it once before the `map { ... }` and store it in a local `val` to avoid repeated I/O/overhead.

Suggested implementation:

```
        val totalObject = Gson().fromJson(totalJson, JsonObject::class.java)
        val jsonParser = KotlinJson { ignoreUnknownKeys = true }
        val qqVersionInstall = DataStoreUtil.getStringKV("QQVersionInstall", "")
        val qqVersion: List<QQVersionBean> = totalObject.getAsJsonArray("versions64").reversed().map { json ->
            jsonParser.decodeFromString<QQVersionBean>(json.toString()).apply {

```

```
                jsonString = json.toString()
                // 标记本机 Android QQ 版本
                this.apply {
                    // use qqVersionInstall here as needed

```

1. Ensure any logic that previously used the locally-defined `qqVersionInstall` inside the `map` still references the new outer-scoped `qqVersionInstall` variable.
2. If `displayInstall` or other properties depend on `qqVersionInstall`, keep that logic unchanged other than relying on the moved variable.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread app/src/main/java/com/xiaoniu/qqversionlist/util/VersionUtil.kt
Comment thread app/src/main/java/com/xiaoniu/qqversionlist/util/VersionUtil.kt Outdated
@ArcticFoxPro ArcticFoxPro merged commit c9f2026 into klxiaoniu:master May 29, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant