Skip to content

feat: 首页推荐/热点分页、话题榜与话题页、进吧签到状态、可导出诊断日志 - #84

Open
IamNewHands wants to merge 11 commits into
infinityf4p:mainfrom
IamNewHands:feat/hot-feed-topic-and-diagnostics
Open

IamNewHands wants to merge 11 commits into
infinityf4p:mainfrom
IamNewHands:feat/hot-feed-topic-and-diagnostics

Conversation

@IamNewHands

Copy link
Copy Markdown

本 PR 把 fork 上最近几天的四个功能域整理后提交上游,共 11 个 commit。

1. 首页「推荐 / 热点」分页(feat(home))

  • 首页顶部加分段控件:推荐复用原有个性化流,热点读取 /c/f/forum/hotThreadList(cmd=309661)的 protobuf 端点。
  • 热点「全部」由客户端合并:服务端没有「全部」分类码(空码 / all / 0 返回同一份 4 条默认列表),所以「全部」= 默认列表 + 四个分类并发拉取的并集,按服务端下发的 hot_num(182) 降序,同热度按回复数、再按 id 兜底,重复 tid 保留热度更高的一份;任一分类失败只记日志并用其余分类。
  • 话题榜(topic_list)渲染在热点页顶部:排名 + 话题名 + 讨论数,前三名用官方红 / 橙 / 黄。
  • 左右滑动在推荐与热点之间切换(HomeFeedSwipePolicy 只把足够长且明显偏水平的拖动当作翻页,子分页的横向标签栏不受影响)。
  • 两个分页改为常驻视图树,切回不重新请求、保留滚动位置;首页 tab 手势(单击回列表根 / 双击回顶部 / 长按刷新)对当前可见分页生效。

2. 话题页(feat(topic))

  • 新端点 GET /mo/q/newtopic/topicDetail(JSON)。按游标翻页:只给 pn / offset 会返回 no=300000 param invalid,必须带上一页最后一条的 feed_id。
  • 新增 TopicDetailView(话题头部 + 帖子流 + 下拉刷新 + 触底加载),从热点页的热议话题点入。

3. 进吧签到(feat(forumhub) / fix(sign))

  • 关注的贴吧磁贴显示吧内等级与今日是否已签到。
  • 整轮只解析一次写令牌并逐吧复用。原来 signForum 每次都走 refreshedClientTBS,它会先发一次 /c/s/login 才回退到已存 tbs,于是每个吧都多一次登录握手(40 个待签 = 40 次登录 + 40 次签到);现在取不到令牌时才回退到逐吧解析。
  • 跳过已签到的吧,并在签到过程中显示进度。

4. 可导出的应用内诊断日志(feat(diagnostics) / feat(settings))

  • 新增 AppLog(有界内存日志)与设置页的导出入口,记录未公开端点的真实响应:forumGuide 的原始 body 骨架、逐页 error_code、行数与等级直方图;推荐 / 热点流是否已登录、子分页名与前几行的来源吧;ForumHubView 不再用 try? 丢弃状态错误。
  • 每条消息在写入时即抹掉 BDUSS、STOKEN、tbs 等凭据,导出文件可直接发送。
  • 两个 protobuf 信息流的 Cookie 头带上已登录凭据(只发 ka=open 会把请求标记为匿名)。
  • 设置页可完全关闭该日志。

5. 协议修正与生成产物(fix(api) / build(protos))

  • /c/f/forum/forumGuide 的响应是扁平结构:like_forum 与 like_forum_has_more 和 error_code 同级,不存在 data 包装层。原 DTO 按嵌套 data 解码,于是每次成功响应都读到 nil,再被 guard 抛成「服务器返回了空数据,请稍后重试」。改为顶层解码,并把 DataDTO 折叠进 DTO;第 1 页无条目时补一条 ERROR,以区分「0 关注」与「字段名不符」。
  • 热点默认请求此前发空 tab_code,服务端只回 4 条残缺列表;官方实现用 tab_code=all,现在空码一律改写为 HotTab.allCode。服务端 hot_thread_tab_info 只含分类子标签,mapper 固定补一个「全部」入口排在最前(服务端已给 all 则不重复)。
  • HotThreadListResponseData.topic_list = 1(RecomTopicList)与 ThreadInfo.hot_num = 182 此前被当作未知字段丢掉,现在补上声明并同步生成产物。
  • 切标签取消旧请求时 URLError.cancelled 曾被当成分类拉取失败记 ERROR 并合并出残缺列表,现在取消按取消处理。

说明

  • 不含 fork 的 CI / 打包 / 上游同步改动:.github/workflows/* 一行未动,project.yml 与上游逐字节相同(构建号仍是上游的 58)。
  • 新源文件已注册进 TiebaPure.xcodeproj,条目顺序与 UUID 与 XcodeGen 的输出一致。
  • 新增单测:HotThreadListTests、TopicThreadsTests、AppLogTests、FollowedForumStatusTests,以及热点 / 话题线格式、合并排序、签到状态、扁平指南 DTO 等用例。
  • 已在 fork 上用上游的 .github/workflows/ci.yml(本 PR 未改动它)跑通:单元测试全量 + 13 项 UI 冒烟 + iPad 布局 + Release 构建与 bundled notices 校验 —— https://github.com/IamNewHands/TiebaPure-iOS/actions/runs/37020874404

改用 /c/f/forum/forumGuide 一次取回关注贴吧列表里的
level_id 与 is_sign,在磁贴上显示 Lv.N 与已签角标。
状态请求与原关注列表并行发出,失败时只缺少装饰信息,
不影响吧列表本身。
首页顶部分页:推荐复用原有个性化流,热点读取 /c/f/forum/hotThreadList
(cmd=309661) 的 protobuf 端点,显示吧内热帖与子分页。

新增 TiebaHotAPI、HotFeed 与 HotThreadsView;热点响应按实测的线格式解码,
并用逐字段用例固定下来(论坛 id 的变长整数、手写标题字节都由 fixture 覆盖)。
新源文件与生成的 proto 产物注册进 Xcode 工程,条目顺序与 UUID 与 XcodeGen
的输出一致。顺带修掉 FollowedForumStatusTests 里一处非 Equatable 比较。
真机上无法从外部区分两个症状:首页分页显示与官方客户端不一致的帖子,以及
关注贴吧磁贴从不显示等级或签到状态。两处都被吞掉了——ForumHubView 用 try?
丢弃状态错误,followedForumGuide 是唯一从未观察过字段名的调用。

- 新增 AppLog(有界内存日志)与设置页的导出入口,记录未公开端点的真实响应:
  forumGuide 的原始 body 骨架、逐页 error_code、行数与等级直方图;推荐/热点
  流是否已登录、子分页名与前几行的来源吧;ForumHubView 不再丢弃状态错误。
- 每条消息在写入时即抹掉 BDUSS、STOKEN、tbs 等凭据,导出文件可直接发送。
- 两个 protobuf 信息流的 Cookie 头带上已登录凭据:只发 ka=open 会把请求标记
  为匿名,这很可能就是推荐流是通用排序而非官方客户端的关注吧混排的原因。
- 注册新源文件到 Xcode 工程,并修掉 AppLog 的两处编译错误与一处 range 长度读取。
诊断日志证实两处线格式假设错误。

进吧等级:/c/f/forum/forumGuide 的响应是扁平结构,like_forum 与
like_forum_has_more 和 error_code 同级,没有 data 包装层,原 DTO 按嵌套
data 解码后每次都读到 nil,再被 guard 抛成「服务器返回了空数据」。改为顶层
解码(与 aiotieba、tieba-lite-harmony 同一端点同一参数集一致),并把
DataDTO 折叠进 DTO;第 1 页无条目时补一条 ERROR,以区分「0 关注」与
「字段名不符」。

热点:默认请求此前发空 tab_code,服务端只回 4 条残缺列表;官方实现用
tab_code=all,现在空码一律改写为 HotTab.allCode。服务端 hot_thread_tab_info
只含分类子标签,mapper 固定补一个「全部」入口排在最前(服务端已给 all 则
不重复),标签栏初始选中即全部。

诊断:DiagnosticJSON 上限 6 → 40,顶层键列表不再被 …N more 截断;热点日志
改打服务端子标签 name(code) 与原始/保留条数。

测试:扁平指南 DTO(含多余展示键)、缺 like_forum 视为空、空 tab_code → all、
mapper 补全部且不重复、skeleton 不再截断字段列表。
真机反馈:一键签到只有转圈,看不出进行到哪、还剩多少;而且对今天已签到的吧也照样发
一次请求,关注列表长了就很慢。三个改动:

- 签到前先读一次关注吧 guide(forumGuide 的 is_sign),今天已签到的吧直接计入
  今天已签过并跳过请求。关注 100 个吧、其中 90 个已签时,写请求从 100 次降到 10 次。
- guide 读不到时不清空任何吧(重复签到只是无操作的 no-op),并往诊断日志记一条 error,
  避免读状态失败被当成全都签过了。
- 新增 ForumSignProgress(已完成/总数/当前吧名)并发布:进吧签到区显示 3/120 计数、
  正在签到:xx吧 一行、以及取消按钮;结束后清空,结果仍由既有 alert 汇报。设置页说明
  文案同步改为今天已签到的贴吧会直接跳过。

Fixture 新增 .signAlreadyDone 场景驱动 guide 返回签到态;补 3 个测试(跳过已签到、
进度发布并在结束后清空、进度文案)。build number 59 到 60。
热点「全部」要按真实热度合并四个分类,并把话题榜渲染到页面顶部,两者都用到现在
被当作未知字段丢掉的字段:

- HotThreadListResponseData 增加 topic_list = 1(RecomTopicList:topic_id=1 /
  topic_name=2 / discuss_num=4),即官方热点页顶部那块热议话题。
- ThreadInfo 增加 optional hot_num = 182:热点响应每条都带它,且每个分类列表
  本身已按它降序,合并后的「全部」用它排序。
- scripts/generate-ios-protos.sh 的产物清单与 .proto 同步更新;Swift 生成产物
  一并提交(nameMap 字节码无法从现有样例可靠反推,不能手写)。
热点「全部」:服务端没有「全部」分类码(空码、all、0 返回同一份 4 条默认
列表),只能客户端拼:默认列表 + 四个分类列表的并集,按服务端下发的
hot_num(182) 降序,同热度按回复数、再按 id 兜底,重复 tid 保留热度更高的一份;
四个分类并发拉取,任一失败只记 AppLog 并继续用其余分类。话题榜(topic_list)
渲染在热点页顶部:排名 + 话题名 + 讨论数,前三名用官方红/橙/黄。

话题页:新端点 GET /mo/q/newtopic/topicDetail(JSON),匿名探针实测后按游标
翻页(只给 pn/offset 会返回 no=300000 param invalid,必须带上一页最后一条的
feed_id)。新增 TopicDetailView(话题头部 + 帖子流 + 下拉刷新 + 触底加载),
从热点页热议话题进入;HomeNavigationRoute 增加 .topic 路由。v1 限制:media
点击直接进帖子,该接口不返回视频信息。

顺带修复:切标签取消旧请求时 URLError.cancelled 曾被当成分类拉取失败记
ERROR 并合并出残缺列表,现在取消按取消处理。

测试:话题 JSON 解码(字符串/数字混用)、行映射与缺字段、游标、空列表;
话题 wire 解码、话题过滤与顺序、讨论数文案、合并去重/热度排序/同热度兜底。
一键签到时所有吧都已签到,整轮没有任何写请求,但协调器仍然先解析写令牌。
那一步是一次登录握手,本次日志里它独自耗时 20.2s:用户看到的是「无需签到」,
却要盯着转圈等 20 秒才显示完成。现在待签列表为空时直接跳过令牌解析,并记一条
说明跳过原因的日志;有待签贴吧时行为不变。

同时给写令牌解析加慢速分步日志:客户端登录与网页兜底各自超过 3s 才记一条 WARN
(成功的跳记、失败记原因),下一次日志就能指出这 20 秒花在哪一跳,而不是只有一个
总数。正常几百毫秒的解析不产生任何日志,点赞收藏不会刷屏。

夹具新增 signAllDone 场景与写令牌解析计数,新增两个单测:无待签贴吧时解析次数
必须为 0;有待签贴吧时整轮只解析一次(而不是每个吧一次)。

构建号 64 → 65。
…tely

设置 → 诊断 新增「记录诊断日志」开关,默认开启。关闭后 AppLog.record 直接返回,
一条都不再记录(不是隐藏),已记录的内容保留,仍可查看、导出或清空。

开关存在 UserDefaults(dev.infinityf4p.tiebapure.diagnostics.logging-enabled),
未设置过时为开启。AppLog.isEnabled 是不需要 await 的静态读取,所以昂贵的调用点
可以先问再构造:进吧等级那行要把 39KB 响应整份走一遍 DiagnosticJSON.skeleton,
现在关闭时直接跳过,而不是构造完再丢掉。设置页 footer 与诊断日志页的空态文案都
跟着开关变化,避免「已关闭却提示回首页刷新一次」这种误导。

补两个单测:关闭时 record 与 recordError 一条都不记、重新开启立即恢复;未设置过时
默认开启、关闭状态能跨实例读到。构建号 65 → 66。
… long-press refreshes

Split Home reselect into discernible intents driven by gesture recognizers
on the tab bar: single tap collapses detail/search to the feed root, double
tap additionally scrolls to top (no refresh), long press refreshes. HomeView
now observes three tokens (returnToRoot/scrollToTop/refresh) instead of one.
Update HomeNavigationTests and home-tab UI tests to the new gestures.
推荐 和 热点 是首页仅有的两个分页,所以在任一页面上左右滑动都切到另一页:
在推荐上右滑进入热点,在热点上左滑回到推荐。HomeFeedSwipePolicy 只把足够长、
且明显偏水平的拖动当作翻页;手势只挂在滚动内容上,热点子分页的横向标签栏
仍由它自己的横向滚动处理,不会被抢走拖动。分段控件保留,是无障碍和可发现
的主要切换方式。

两个分页改为常驻视图树,切换只改可见性:热点保留已加载列表与滚动位置,不再
每次右滑都重新请求;隐藏分页移出命中测试与无障碍树。HotFeedLoadPolicy 门控
「首次激活才加载」。首页 tab 手势对可见分页生效,与推荐一致:单击返回列表根、
双击回到顶部、长按刷新;HomeFeedGestureTargetPolicy 只把 token 投给当前分页,
避免后台分页被滚动或被偷偷重新请求。HotThreadsView 增加顶部滚动锚点,回顶复用
与推荐相同的无动画 transaction,避免 iOS 26 上懒加载布局在动画中反复更新。
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