@danceiny/gotry 0.0.1-rc.21 → 0.0.1-rc.24

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (327) hide show
  1. package/README.md +102 -194
  2. package/README.zh-CN.md +115 -210
  3. package/bin/gotry-backend.js +53 -0
  4. package/bin/gotry-bootstrap.js +466 -35
  5. package/bin/gotry-inner.js +255 -36
  6. package/bin/gotry-process-liveness.d.ts +27 -0
  7. package/bin/gotry-process-liveness.js +97 -0
  8. package/bin/gotry-runtime-resolution.d.ts +1 -1
  9. package/bin/gotry-runtime-resolution.js +2 -2
  10. package/client/client.js +174 -0
  11. package/cordis.gotry-patch.yml +70 -93
  12. package/dist/capabilities/agent-reach-deep.js +45 -122
  13. package/dist/capabilities/agent-reach.js +63 -148
  14. package/dist/capabilities/anything.js +89 -75
  15. package/dist/capabilities/artifacts.js +123 -104
  16. package/dist/capabilities/channel-health.js +65 -45
  17. package/dist/capabilities/channel-registry.js +108 -87
  18. package/dist/capabilities/doctor.js +357 -235
  19. package/dist/capabilities/effect.js +223 -309
  20. package/dist/capabilities/fact-log.js +28 -14
  21. package/dist/capabilities/flyai.js +245 -156
  22. package/dist/capabilities/ground-transfer.js +509 -0
  23. package/dist/capabilities/hbcli.js +144 -148
  24. package/dist/capabilities/hotelbyte-transaction.js +194 -0
  25. package/dist/capabilities/incident-log.js +131 -61
  26. package/dist/capabilities/model-override.js +30 -10
  27. package/dist/capabilities/opensky.js +54 -43
  28. package/dist/capabilities/resilience.js +50 -32
  29. package/dist/capabilities/session/action-cache.js +50 -44
  30. package/dist/capabilities/session/adapters/ctrip-flight.js +119 -62
  31. package/dist/capabilities/session/adapters/ctrip-hotel.js +125 -110
  32. package/dist/capabilities/session/adapters/dida-portal.js +206 -0
  33. package/dist/capabilities/session/adapters/meituan-local.js +35 -54
  34. package/dist/capabilities/session/adapters/rail-12306.js +169 -112
  35. package/dist/capabilities/session/benchmark.js +85 -149
  36. package/dist/capabilities/session/extension-bridge.js +320 -259
  37. package/dist/capabilities/session/extension-channel.js +57 -76
  38. package/dist/capabilities/session/extension-distribution.js +115 -119
  39. package/dist/capabilities/session/extract.js +31 -21
  40. package/dist/capabilities/session/golden-score.js +55 -33
  41. package/dist/capabilities/session/health-watch.js +146 -154
  42. package/dist/capabilities/session/read-guard.js +55 -47
  43. package/dist/capabilities/session/static-flight-golden.js +49 -36
  44. package/dist/capabilities/session/transport.js +54 -61
  45. package/dist/capabilities/session/wizard.js +63 -93
  46. package/dist/capabilities/session-consent.js +143 -47
  47. package/dist/capabilities/session-login.js +95 -126
  48. package/dist/capabilities/session-search.js +391 -253
  49. package/dist/capabilities/sponsor-plugin.js +341 -0
  50. package/dist/capabilities/visa-policy.js +87 -73
  51. package/dist/capabilities/weather.js +163 -235
  52. package/dist/scripts/action-cache-tests.js +24 -59
  53. package/dist/scripts/agent-planning-turn-deadline-e2e.js +91 -142
  54. package/dist/scripts/agent-planning-turn-deadline-tests.js +80 -176
  55. package/dist/scripts/agent-reach-deep-tests.js +30 -40
  56. package/dist/scripts/agent-reach-tests.js +26 -18
  57. package/dist/scripts/agent-reach-wrapper-tests.js +46 -50
  58. package/dist/scripts/anything-tests.js +58 -62
  59. package/dist/scripts/artifact-client-contract-tests.js +69 -0
  60. package/dist/scripts/artifacts-capability-tests.js +235 -0
  61. package/dist/scripts/async-collect.js +26 -15
  62. package/dist/scripts/benchmark-environment-bridge-e2e.js +287 -684
  63. package/dist/scripts/benchmark-environment-bridge-tests.js +818 -3123
  64. package/dist/scripts/booking-copilot-availability-ledger-binding-tests.js +66 -279
  65. package/dist/scripts/booking-copilot-availability-policy-tests.js +153 -1257
  66. package/dist/scripts/booking-copilot-bin-proof-tests.js +22 -45
  67. package/dist/scripts/booking-copilot-crossrepo-fixture-server.js +64 -76
  68. package/dist/scripts/booking-copilot-dsh-core-proof-tests.js +69 -199
  69. package/dist/scripts/booking-copilot-dsh-planner-proof-tests.js +393 -759
  70. package/dist/scripts/booking-copilot-dsh-plugin-proof-tests.js +33 -75
  71. package/dist/scripts/booking-copilot-event-sequence-concurrency-proof-tests.js +60 -110
  72. package/dist/scripts/booking-copilot-gap-code-contract-proof-tests.js +28 -66
  73. package/dist/scripts/booking-copilot-operation-ledger-concurrency-proof-tests.js +72 -271
  74. package/dist/scripts/booking-copilot-receipt-ledger-concurrency-proof-tests.js +73 -275
  75. package/dist/scripts/booking-copilot-runtime-proof-tests.js +784 -3398
  76. package/dist/scripts/booking-copilot-server-proof-tests.js +63 -168
  77. package/dist/scripts/booking-copilot-startup-proof-tests.js +38 -124
  78. package/dist/scripts/booking-executor-tests.js +80 -0
  79. package/dist/scripts/booking-recovery-chain-tests.js +182 -0
  80. package/dist/scripts/booking-saga-tests.js +61 -123
  81. package/dist/scripts/booking-surface-contract-proof-tests.js +152 -1167
  82. package/dist/scripts/bootstrap-tests.js +1383 -128
  83. package/dist/scripts/build-changelog.js +89 -130
  84. package/dist/scripts/build-metrics-report.js +135 -143
  85. package/dist/scripts/changelog-tests.js +35 -82
  86. package/dist/scripts/channel-probe-tests.js +44 -116
  87. package/dist/scripts/channel-probe.js +84 -167
  88. package/dist/scripts/channel-registry-tests.js +97 -185
  89. package/dist/scripts/companion-tests.js +25 -70
  90. package/dist/scripts/dida-cookie-probe.js +87 -0
  91. package/dist/scripts/dida-sw-probe.js +114 -0
  92. package/dist/scripts/diff-test.js +17 -12
  93. package/dist/scripts/doctor-tests.js +428 -120
  94. package/dist/scripts/dsh-artifact-e2e.js +150 -0
  95. package/dist/scripts/dsh-artifact-web-e2e.js +443 -0
  96. package/dist/scripts/dsh-http-proxy-sse-proof.js +90 -0
  97. package/dist/scripts/dsh-runtime-closure-tests.js +110 -153
  98. package/dist/scripts/dsh-runtime-closure.js +75 -63
  99. package/dist/scripts/dsh-session-v3-migration-proof.js +106 -0
  100. package/dist/scripts/dsh-subprocess-local-proof.js +149 -0
  101. package/dist/scripts/dsh-target-closure-proof.js +178 -0
  102. package/dist/scripts/effect-tests.js +165 -552
  103. package/dist/scripts/engine-run.js +1 -3
  104. package/dist/scripts/engine-tests.js +16 -10
  105. package/dist/scripts/evaluation-cadence-tests.js +69 -242
  106. package/dist/scripts/evaluation-contract-tests.js +193 -499
  107. package/dist/scripts/extension-distribution-cli.js +19 -15
  108. package/dist/scripts/extension-distribution-tests.js +98 -211
  109. package/dist/scripts/extension-tests.js +366 -389
  110. package/dist/scripts/fact-gate-tests.js +725 -263
  111. package/dist/scripts/fixtures/extension-bridge-unref-child.mjs +21 -0
  112. package/dist/scripts/flight-hotel-anchor-363-registered-e2e.js +169 -0
  113. package/dist/scripts/flight-malformed-tests.js +159 -0
  114. package/dist/scripts/flyai-tests.js +325 -68
  115. package/dist/scripts/g5-guard-tests.js +131 -0
  116. package/dist/scripts/g5-guard.js +189 -0
  117. package/dist/scripts/gen-lunar-anchors.js +106 -0
  118. package/dist/scripts/gotry-backend-kernel-tests.js +79 -0
  119. package/dist/scripts/ground-transfer-tests.js +956 -0
  120. package/dist/scripts/hbcli-e2e-tests.js +71 -90
  121. package/dist/scripts/hbcli-live-optin-tests.js +96 -0
  122. package/dist/scripts/hbcli-release-contract-tests.js +122 -0
  123. package/dist/scripts/hbcli-tests.js +41 -115
  124. package/dist/scripts/health-watch-cli.js +42 -28
  125. package/dist/scripts/hotel-date-gate-tests.js +496 -0
  126. package/dist/scripts/hotelbyte-reconcile-tests.js +141 -0
  127. package/dist/scripts/hotelbyte-spawn-e2e-tests.js +373 -0
  128. package/dist/scripts/i18n-tests.js +16 -9
  129. package/dist/scripts/incident-tests.js +281 -87
  130. package/dist/scripts/issue-194-job-id-guard-tests.js +106 -0
  131. package/dist/scripts/issue-233-cancel-refund-commission-tests.js +392 -0
  132. package/dist/scripts/issue-271-liveness-tests.js +547 -0
  133. package/dist/scripts/issue-289-model-retry-real-tests.js +296 -0
  134. package/dist/scripts/issue-289-model-retry-tests.js +441 -0
  135. package/dist/scripts/issue-289-web-retry-e2e.js +630 -0
  136. package/dist/scripts/issue-338-home-city-e2e.js +577 -0
  137. package/dist/scripts/issue343-real-entry-e2e.js +160 -0
  138. package/dist/scripts/journey-tests.js +22 -27
  139. package/dist/scripts/kernel-manifest-evidence.js +109 -0
  140. package/dist/scripts/kernel-manifest-gate.js +126 -0
  141. package/dist/scripts/kernel-manifest-tests.js +212 -0
  142. package/dist/scripts/kernel-manifest-trace-entry.js +7 -0
  143. package/dist/scripts/kernel-manifest-trace-hook.mjs +28 -0
  144. package/dist/scripts/kernel-manifest.js +369 -0
  145. package/dist/scripts/ledger-repair-apply-tests.js +316 -0
  146. package/dist/scripts/ledger-repair-plan-tests.js +375 -0
  147. package/dist/scripts/ledger-tests.js +325 -340
  148. package/dist/scripts/ledger-workflow-crash.js +10 -20
  149. package/dist/scripts/map-tools-vendor-package-proof.js +206 -134
  150. package/dist/scripts/memory-capture-tests.js +242 -61
  151. package/dist/scripts/memory-decay-tests.js +18 -52
  152. package/dist/scripts/memory-lifecycle-tests.js +657 -0
  153. package/dist/scripts/memory-lifecycle.js +196 -0
  154. package/dist/scripts/memory-metrics.js +24 -12
  155. package/dist/scripts/memory-value-report.js +474 -222
  156. package/dist/scripts/memory-value-tests.js +328 -0
  157. package/dist/scripts/metrics-report-tests.js +62 -199
  158. package/dist/scripts/model-override-e2e.js +66 -105
  159. package/dist/scripts/nightly-evidence-tests.js +64 -89
  160. package/dist/scripts/nightly-evidence.js +108 -111
  161. package/dist/scripts/nudge-digest.js +39 -28
  162. package/dist/scripts/onboarding-tests.js +63 -88
  163. package/dist/scripts/opensky-check.js +18 -13
  164. package/dist/scripts/opensky-tests.js +28 -28
  165. package/dist/scripts/persona-surface-guard-tests.js +37 -12
  166. package/dist/scripts/pnpm-dsh-closure-proof.js +2 -5
  167. package/dist/scripts/policy-anchor-359-registered-e2e.js +214 -0
  168. package/dist/scripts/price-drift-tests.js +126 -227
  169. package/dist/scripts/price-drift-watch.js +209 -253
  170. package/dist/scripts/probe-poi-tests.js +52 -45
  171. package/dist/scripts/product-metrics.js +187 -273
  172. package/dist/scripts/publish-preverify.js +47 -41
  173. package/dist/scripts/realtime-pricing-tests.js +37 -76
  174. package/dist/scripts/replay-async.js +20 -18
  175. package/dist/scripts/replay-real.js +16 -18
  176. package/dist/scripts/replay.js +52 -82
  177. package/dist/scripts/session-attach-diagnose.js +16 -12
  178. package/dist/scripts/session-attach-poc.js +34 -37
  179. package/dist/scripts/session-benchmark.js +66 -147
  180. package/dist/scripts/session-dida-live-e2e.js +151 -0
  181. package/dist/scripts/session-extract-tests.js +14 -35
  182. package/dist/scripts/session-login.js +14 -13
  183. package/dist/scripts/session-tests.js +545 -583
  184. package/dist/scripts/sf-live-benchmark.js +92 -110
  185. package/dist/scripts/sf-live-cli-tests.js +2 -9
  186. package/dist/scripts/sf-soft-score-tests.js +19 -60
  187. package/dist/scripts/sf-summary-tests.js +149 -0
  188. package/dist/scripts/sf-summary.js +466 -84
  189. package/dist/scripts/skeleton-check.js +16 -14
  190. package/dist/scripts/skeleton-integration-test.js +11 -10
  191. package/dist/scripts/skills-contract-tests.js +51 -46
  192. package/dist/scripts/smoke-session-gate-tests.js +12 -20
  193. package/dist/scripts/smoke.js +541 -422
  194. package/dist/scripts/sponsor-reuse-tests.js +262 -0
  195. package/dist/scripts/state-cli-tests.js +226 -101
  196. package/dist/scripts/state-cli.js +549 -212
  197. package/dist/scripts/static-golden-tests.js +46 -180
  198. package/dist/scripts/time-eval-tests.js +210 -192
  199. package/dist/scripts/travel-timeline-tests.js +29 -93
  200. package/dist/scripts/turn-handoff-collect-tests.js +46 -93
  201. package/dist/scripts/turn-handoff-collect.js +71 -99
  202. package/dist/scripts/turn-policy-tests.js +28 -29
  203. package/dist/scripts/typed-contract-canary.js +77 -162
  204. package/dist/scripts/tz-resolver-sanity.js +50 -0
  205. package/dist/scripts/tz-resolver-tests.js +584 -0
  206. package/dist/scripts/unified-tests.js +23 -19
  207. package/dist/scripts/visa-policy-tests.js +31 -44
  208. package/dist/scripts/weather-tests.js +121 -543
  209. package/dist/scripts/wish-channel-gate-tests.js +29 -86
  210. package/dist/scripts/write-gate-tests.js +522 -0
  211. package/dist/scripts/z3-lifecycle-fault-tests.js +188 -0
  212. package/dist/scripts/z3-lifecycle-tests.js +170 -0
  213. package/dist/scripts/z3-race-repeat-tests.js +39 -0
  214. package/dist/scripts/z3-race-tests.js +18 -12
  215. package/dist/src/artifact-gate.js +652 -221
  216. package/dist/src/backend/kernel.js +91 -0
  217. package/dist/src/backend/modules/booking-copilot.js +55 -0
  218. package/dist/src/backend/modules/booking-executor.js +124 -0
  219. package/dist/src/backend/modules/session-search.js +194 -0
  220. package/dist/src/benchmark-agent-conformance.js +296 -262
  221. package/dist/src/benchmark-environment-bridge.js +298 -413
  222. package/dist/src/benchmark-headless-child-diagnostics.js +93 -100
  223. package/dist/src/benchmark-tool-isolation.js +94 -67
  224. package/dist/src/bookable-facts.js +299 -174
  225. package/dist/src/booking-saga.js +64 -120
  226. package/dist/src/booking-surface/availability-policy.js +443 -576
  227. package/dist/src/booking-surface/cancel-refund-commission.js +379 -0
  228. package/dist/src/booking-surface/contracts.js +24 -80
  229. package/dist/src/booking-surface/dsh-planner.js +510 -281
  230. package/dist/src/booking-surface/dsh-plugin.js +17 -1
  231. package/dist/src/booking-surface/error-codes.js +82 -10
  232. package/dist/src/booking-surface/index.js +0 -3
  233. package/dist/src/booking-surface/profile.js +10 -54
  234. package/dist/src/booking-surface/recovery-chain.js +367 -0
  235. package/dist/src/booking-surface/runtime.js +765 -891
  236. package/dist/src/booking-surface/server.js +200 -251
  237. package/dist/src/booking-surface/startup.js +58 -54
  238. package/dist/src/booking-surface/validation.js +150 -218
  239. package/dist/src/bridge.js +20 -13
  240. package/dist/src/companions.js +48 -74
  241. package/dist/src/contracts.js +21 -43
  242. package/dist/src/dsh-llm.js +82 -120
  243. package/dist/src/engine.js +253 -239
  244. package/dist/src/evaluation-cadence.js +72 -209
  245. package/dist/src/evaluation-contracts.js +220 -843
  246. package/dist/src/flight-pack-adapter.js +54 -0
  247. package/dist/src/flight-pack-contract.js +2 -0
  248. package/dist/src/gotry-backend.js +70 -0
  249. package/dist/src/hotel-date-gate.js +118 -0
  250. package/dist/src/i18n.js +18 -7
  251. package/dist/src/index.js +1425 -1629
  252. package/dist/src/journey.js +132 -81
  253. package/dist/src/ledger-repair-apply.js +538 -0
  254. package/dist/src/ledger-repair-plan.js +463 -0
  255. package/dist/src/loop.js +257 -166
  256. package/dist/src/memory-capture.js +180 -32
  257. package/dist/src/memory-decay.js +30 -14
  258. package/dist/src/memory-lifecycle.js +1310 -0
  259. package/dist/src/memory-utility.js +27 -40
  260. package/dist/src/mock-llm.js +36 -61
  261. package/dist/src/model.js +81 -36
  262. package/dist/src/realtime-pricing.js +63 -72
  263. package/dist/src/slot-spec.js +84 -106
  264. package/dist/src/state-ledger.js +310 -471
  265. package/dist/src/subagent-job-id-guard.js +58 -0
  266. package/dist/src/time-anchor.js +231 -33
  267. package/dist/src/tool-packet.js +20 -8
  268. package/dist/src/travel-slots.js +58 -35
  269. package/dist/src/travel-timeline.js +44 -63
  270. package/dist/src/turn-deadline.js +121 -127
  271. package/dist/src/turn-policy.js +61 -89
  272. package/dist/src/tz-resolver.js +143 -0
  273. package/dist/src/unified.js +504 -381
  274. package/dist/src/wish-pool.js +39 -14
  275. package/dist/src/write-gate.js +495 -0
  276. package/dist/src/z3-shared.js +273 -7
  277. package/extension/README.md +4 -3
  278. package/extension/background.js +125 -14
  279. package/extension/content-bridge.js +23 -1
  280. package/extension/content-main.js +15 -4
  281. package/extension/manifest.json +16 -8
  282. package/package.json +251 -220
  283. package/ts/capabilities/agent-reach.ts +4 -4
  284. package/ts/capabilities/anything.ts +1 -1
  285. package/ts/capabilities/artifacts.ts +46 -9
  286. package/ts/capabilities/flyai.ts +156 -37
  287. package/ts/capabilities/hbcli.ts +1 -1
  288. package/ts/capabilities/incident-log.ts +88 -59
  289. package/ts/capabilities/session/adapters/ctrip-flight.ts +99 -36
  290. package/ts/capabilities/session/adapters/dida-portal.ts +250 -0
  291. package/ts/capabilities/session/adapters/rail-12306.ts +123 -24
  292. package/ts/capabilities/session/extension-bridge.ts +226 -101
  293. package/ts/capabilities/session/extension-channel.ts +22 -9
  294. package/ts/capabilities/session/wizard.ts +2 -2
  295. package/ts/capabilities/session-consent.ts +98 -14
  296. package/ts/capabilities/session-login.ts +1 -0
  297. package/ts/capabilities/session-search.ts +315 -31
  298. package/ts/dsh-runtime/vendor/README.md +8 -4
  299. package/ts/dsh-runtime/vendor/dsh-map-tools/README.en.md +7 -1
  300. package/ts/dsh-runtime/vendor/dsh-map-tools/README.md +5 -3
  301. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/settings-ns.js +1 -1
  302. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/settings-ns.d.ts +1 -1
  303. package/ts/dsh-runtime/vendor/dsh-map-tools/package.json +6 -6
  304. package/ts/package.json +236 -16
  305. package/ts/scripts/map-tools-vendor-package-proof.ts +152 -31
  306. package/ts/scripts/state-cli.ts +418 -51
  307. package/ts/src/artifact-gate.ts +561 -15
  308. package/ts/src/benchmark-environment-bridge.ts +37 -20
  309. package/ts/src/bookable-facts.ts +183 -7
  310. package/ts/src/booking-surface/cancel-refund-commission.ts +488 -0
  311. package/ts/src/booking-surface/dsh-planner.ts +240 -32
  312. package/ts/src/booking-surface/dsh-plugin.js +17 -1
  313. package/ts/src/booking-surface/error-codes.ts +75 -0
  314. package/ts/src/booking-surface/recovery-chain.ts +409 -0
  315. package/ts/src/booking-surface/server.ts +84 -51
  316. package/ts/src/booking-surface/validation.ts +2 -0
  317. package/ts/src/contracts.ts +12 -2
  318. package/ts/src/dsh-llm.ts +4 -8
  319. package/ts/src/index.ts +609 -90
  320. package/ts/src/loop.ts +116 -7
  321. package/ts/src/memory-capture.ts +227 -3
  322. package/ts/src/mock-llm.ts +4 -8
  323. package/ts/src/model.ts +88 -2
  324. package/ts/src/state-ledger.ts +47 -29
  325. package/ts/src/subagent-job-id-guard.ts +72 -0
  326. package/ts/src/time-anchor.ts +217 -13
  327. package/ts/src/unified.ts +316 -54
@@ -1,137 +1,154 @@
1
+ /**
2
+ * 携程酒店站点适配器(2026-09-03 迪拜 session 复盘:账号会话面此前只有机票,
3
+ * 用户要「携程找酒店」时被迫撞 flyai 429 与打码 web 读页;会话面酒店从
4
+ * 「备份」实装为通道之一,rfc §2.2 同款只读纪律)。
5
+ *
6
+ * - entry:https://hotels.ctrip.com/hotels/list?city=<id>&checkin=&checkout=&adult=N
7
+ * - networkHints:酒店检索 XHR 的 URL 形态(接口名公开资料多版并存,**首个真会话
8
+ * 后校准**——D-13 同款边界);URL hint 未命中时由 content-main 的**形状嗅探**
9
+ * 兜底(响应 JSON 含酒店清单签名即转发),对接口改名免疫
10
+ * - 解析:走形(walk)JSON 找「名字 + 价格」签名的条目数组,归一化最小字段;
11
+ * 页面/接口 JSON 是不可信输入(RFC §3.5)——本层只归形状,语义判定在工具层
12
+ *
13
+ * 只读:适配器不含任何提交/预订语义;URL 直开即零 DOM 交互;ReadGuard 对酒店
14
+ * 页面照常生效(下单/支付面 URL 仍被物理拦截)。
15
+ */
16
+ /** 城市码表(**实测校准 2026-09-03**:每条 id 均经 hotels.ctrip.com list 页
17
+ * <title> 验证——探测法曾纠出 104=平遥县(传猜成都)等 5 处错猜,表内只收
18
+ * 验证过的 id;词表外 unresolved 逐字保留,调用方可显式传 cityId 覆盖) */
1
19
  const HOTEL_CITY_CODES = {
2
- 上海: '2',
3
- 北京: '1',
4
- 广州: '32',
5
- 深圳: '30',
6
- 成都: '28',
7
- 杭州: '17',
8
- 南京: '12',
9
- 苏州: '14',
10
- 无锡: '13',
11
- 扬州: '15',
12
- 镇江: '16',
13
- 青岛: '7',
14
- 厦门: '25',
15
- 珠海: '31',
16
- 三亚: '43',
17
- 海口: '42',
18
- 桂林: '33',
19
- 昆明: '34',
20
- 大理: '36',
21
- 丽江: '37',
22
- 西双版纳: '35',
23
- 贵阳: '38',
24
- 拉萨: '41',
25
- 乌鲁木齐: '39',
26
- 太原: '105',
27
- 长春: '158',
28
- 南通: '82',
29
- 敦煌: '11',
30
- 舟山: '19',
31
- 张家界: '27',
32
- 黄山市: '23',
33
- 九江: '24',
34
- 武夷山: '26',
35
- 迪拜: '220'
20
+ 上海: '2', 北京: '1', 广州: '32', 深圳: '30', 成都: '28',
21
+ 杭州: '17', 南京: '12', 苏州: '14', 无锡: '13', 扬州: '15', 镇江: '16',
22
+ 青岛: '7', 厦门: '25', 珠海: '31', 三亚: '43', 海口: '42',
23
+ 桂林: '33', 昆明: '34', 大理: '36', 丽江: '37', 西双版纳: '35',
24
+ 贵阳: '38', 拉萨: '41', 乌鲁木齐: '39', 太原: '105', 长春: '158', 南通: '82',
25
+ 敦煌: '11', 舟山: '19', 张家界: '27', 黄山市: '23', 九江: '24', 武夷山: '26',
26
+ 迪拜: '220',
36
27
  };
37
28
  export function buildHotelEntryUrl(q) {
38
29
  const unresolved = [];
39
30
  const cityId = String(q.cityId ?? '').trim() || HOTEL_CITY_CODES[q.to.trim()];
40
- if (!cityId) unresolved.push(q.to);
31
+ if (!cityId)
32
+ unresolved.push(q.to);
41
33
  if (unresolved.length > 0) {
42
- return {
43
- ok: false,
44
- unresolved
45
- };
34
+ return { ok: false, unresolved };
46
35
  }
47
- const params = new URLSearchParams({
48
- city: cityId
49
- });
50
- if (q.checkIn) params.set('checkin', q.checkIn);
51
- if (q.checkOut) params.set('checkout', q.checkOut);
52
- if (q.adults && q.adults > 0) params.set('adult', String(q.adults));
53
- return {
54
- ok: true,
55
- url: `https://hotels.ctrip.com/hotels/list?${params.toString()}`
56
- };
36
+ const params = new URLSearchParams({ city: cityId });
37
+ if (q.checkIn)
38
+ params.set('checkin', q.checkIn);
39
+ if (q.checkOut)
40
+ params.set('checkout', q.checkOut);
41
+ if (q.adults && q.adults > 0)
42
+ params.set('adult', String(q.adults));
43
+ return { ok: true, url: `https://hotels.ctrip.com/hotels/list?${params.toString()}` };
57
44
  }
45
+ /** networkHints:酒店检索 XHR 的 URL 形态(restapi/soa2/34951/fetchHotelList
46
+ * 为 2026-09-03 自 list 页一方确认的现行接口;其余为多版并存口径;命中即读) */
58
47
  export const HOTEL_NETWORK_HINTS = [
59
48
  /hotels\.ctrip\.com\/(hotels\/api|domestic\/pc\/api)/i,
60
49
  /GetHotelListBySOA|GetHotelListByCity|HotelSearch|hotelsearch/i,
61
- /restapi\/soa2\/\d+\/fetchHotelList/i
50
+ /restapi\/soa2\/\d+\/fetchHotelList/i,
62
51
  ];
52
+ /** 形状嗅探签名:URL hint 未命中时的兜底(对接口改名免疫);大小上限由调用方先把关 */
63
53
  export function looksLikeHotelListBody(body) {
64
- if (!body || body.length > 2_000_000) return false;
54
+ if (!body || body.length > 2_000_000)
55
+ return false;
65
56
  return /"hotelList"|"hotelMatchInfos"|"hotelName"/.test(body);
66
57
  }
58
+ /** 酒店 page 域(content_scripts 注入面与 background 白名单的对账源) */
67
59
  export const HOTEL_SITE_HOST = 'hotels.ctrip.com';
60
+ /** 条目签名:像不像一家酒店(名字类字段必有 + 价格类字段至少其一) */
68
61
  function hotelSignature(o) {
69
- const name = o.hotelName ?? o.name ?? o.hotelNameEn;
70
- if (typeof name !== 'string' || !name.trim()) return null;
62
+ const name = (o.hotelName ?? o.name ?? o.hotelNameEn);
63
+ if (typeof name !== 'string' || !name.trim())
64
+ return null;
71
65
  let price;
72
66
  let priceRaw;
73
- if (typeof o.price === 'number' && o.price > 0) price = o.price;
74
- else if (typeof o.price === 'string' && o.price.trim()) priceRaw = o.price.trim();
75
- const probe = (v, seen = new Set())=>{
76
- if (v == null || typeof v !== 'object') return;
77
- if (seen.has(v)) return;
67
+ // 顶层扁平价(数字/字符串直给;字符串价是打码/区间形态,原样保留不数值化)
68
+ if (typeof o.price === 'number' && o.price > 0)
69
+ price = o.price;
70
+ else if (typeof o.price === 'string' && o.price.trim())
71
+ priceRaw = o.price.trim();
72
+ const probe = (v, seen = new Set()) => {
73
+ if (v == null || typeof v !== 'object')
74
+ return;
75
+ if (seen.has(v))
76
+ return;
78
77
  seen.add(v);
79
- for (const [k, child] of Object.entries(v)){
80
- if (/^(avgPrice|totalPrice|price|amount|total|startPrice)$/i.test(k) && typeof child === 'number' && child > 0 && price === undefined) price = child;
81
- else if (/^(price|priceDisplay|amountText)$/i.test(k) && typeof child === 'string' && child.trim() && priceRaw === undefined) priceRaw = child.trim();
82
- else probe(child, seen);
78
+ for (const [k, child] of Object.entries(v)) {
79
+ if (/^(avgPrice|totalPrice|price|amount|total|startPrice)$/i.test(k) && typeof child === 'number' && child > 0 && price === undefined)
80
+ price = child;
81
+ else if (/^(price|priceDisplay|amountText)$/i.test(k) && typeof child === 'string' && child.trim() && priceRaw === undefined)
82
+ priceRaw = child.trim();
83
+ else
84
+ probe(child, seen);
83
85
  }
84
86
  };
85
87
  probe(o.priceInfo);
86
88
  const flatPrice = o.totalPrice ?? o.avgPrice ?? o.minPrice;
87
- if (typeof flatPrice === 'number' && flatPrice > 0 && price === undefined) price = flatPrice;
89
+ if (typeof flatPrice === 'number' && flatPrice > 0 && price === undefined)
90
+ price = flatPrice;
88
91
  const flatRaw = o.priceText ?? o.priceDisplay;
89
- if (typeof flatRaw === 'string' && flatRaw.trim() && priceRaw === undefined) priceRaw = flatRaw.trim();
90
- if (price === undefined && priceRaw === undefined) return null;
92
+ if (typeof flatRaw === 'string' && flatRaw.trim() && priceRaw === undefined)
93
+ priceRaw = flatRaw.trim();
94
+ if (price === undefined && priceRaw === undefined)
95
+ return null;
91
96
  const starRaw = o.star ?? o.level ?? o.hotelStar;
92
97
  const star = typeof starRaw === 'number' && starRaw > 0 ? starRaw : Number.isFinite(Number(starRaw)) && Number(starRaw) > 0 ? Number(starRaw) : undefined;
93
98
  const scoreRaw = o.score ?? o.commentScore ?? o.reviewScore;
94
99
  const score = scoreRaw == null ? undefined : String(scoreRaw);
95
100
  const pos = o.position;
96
101
  const address = (typeof pos?.address === 'string' ? pos.address : undefined) ?? (typeof o.address === 'string' ? o.address : undefined);
97
- const hotelId = (typeof o.hotelId === 'number' || typeof o.hotelId === 'string' ? String(o.hotelId) : undefined) ?? (typeof o.hotelIdStr === 'string' ? o.hotelIdStr : undefined);
98
- return {
99
- name: name.trim(),
100
- price,
101
- priceRaw,
102
- star,
103
- score,
104
- address,
105
- hotelId
106
- };
102
+ const hotelId = (typeof o.hotelId === 'number' || typeof o.hotelId === 'string' ? String(o.hotelId) : undefined)
103
+ ?? (typeof o.hotelIdStr === 'string' ? o.hotelIdStr : undefined);
104
+ return { name: name.trim(), price, priceRaw, star, score, address, hotelId };
107
105
  }
106
+ /** 结构化候选:携程现行 list 形态(**一方校准 2026-09-03**,取自 list 页官方
107
+ * 前端代码反查 + SSR initListData 实测):
108
+ * - 条目 = hotelList[].hotelInfo{summary{hotelId,nameInfo{name,names[]},hotelStar{star},positionInfo},commentInfo{commentScore}}
109
+ * - 价格 = roomInfo[].priceInfo.price(多房型取最低;页面 room_price 即读此);
110
+ * priceToken 在而无 price = 加密价(fetchHotelListPrices 页面内存解密),
111
+ * 嗅探面拿不到明文——如实不报价,summary 指引到 jumpUrl 落地页
112
+ * - displayPrice(如 "¥468")作 priceRaw 兜底;通用 price/amount 子树探测殿后
113
+ * (直连复现 fetchHotelList 被 hotel-spider-defence 反爬 404——「让站点自己
114
+ * 发请求」正是会话桥的设计原因;官方前端为公开静态资产,路径自其中反查) */
108
115
  function ctripStructuredCandidate(item) {
109
116
  const info = item.hotelInfo;
110
- const summary = info?.summary ?? item;
117
+ const summary = (info?.summary ?? item);
111
118
  const nameInfo = summary.nameInfo;
112
119
  const names = nameInfo?.names;
113
120
  const nameRaw = nameInfo?.name ?? (Array.isArray(names) && names.length > 0 ? names[0] : undefined) ?? summary.hotelName ?? summary.name;
114
- if (typeof nameRaw !== 'string' || !nameRaw.trim()) return null;
121
+ if (typeof nameRaw !== 'string' || !nameRaw.trim())
122
+ return null;
123
+ // 价格:roomInfo[].priceInfo 官方路径优先(数值 price 取 min;displayPrice/字符串价作 priceRaw)
115
124
  let price;
116
125
  let priceRaw;
117
126
  let encrypted = false;
118
127
  const rooms = info?.roomInfo ?? item.roomInfo;
119
128
  if (Array.isArray(rooms)) {
120
- for (const room of rooms){
121
- if (room == null || typeof room !== 'object') continue;
129
+ for (const room of rooms) {
130
+ if (room == null || typeof room !== 'object')
131
+ continue;
122
132
  const pi = room.priceInfo;
123
- if (!pi) continue;
124
- if (typeof pi.price === 'number' && pi.price > 0) price = price === undefined ? pi.price : Math.min(price, pi.price);
125
- if (typeof pi.price === 'string' && pi.price.trim() && !priceRaw) priceRaw = pi.price.trim();
126
- if (typeof pi.displayPrice === 'string' && pi.displayPrice.trim() && !priceRaw) priceRaw = pi.displayPrice.trim();
127
- if (pi.priceToken != null && pi.price === undefined) encrypted = true;
133
+ if (!pi)
134
+ continue;
135
+ if (typeof pi.price === 'number' && pi.price > 0)
136
+ price = price === undefined ? pi.price : Math.min(price, pi.price);
137
+ if (typeof pi.price === 'string' && pi.price.trim() && !priceRaw)
138
+ priceRaw = pi.price.trim();
139
+ if (typeof pi.displayPrice === 'string' && pi.displayPrice.trim() && !priceRaw)
140
+ priceRaw = pi.displayPrice.trim();
141
+ if (pi.priceToken != null && pi.price === undefined)
142
+ encrypted = true;
128
143
  }
129
144
  }
145
+ // 兜底:条目子树 price/amount 探测(SSR 形态 productInfo.priceInfo.price 等)
130
146
  if (price === undefined && !priceRaw) {
131
- const priceProbe = (v, depth, seen)=>{
132
- if (price !== undefined || depth > 6 || v == null || typeof v !== 'object' || seen.has(v)) return;
147
+ const priceProbe = (v, depth, seen) => {
148
+ if (price !== undefined || depth > 6 || v == null || typeof v !== 'object' || seen.has(v))
149
+ return;
133
150
  seen.add(v);
134
- for (const [k, child] of Object.entries(v)){
151
+ for (const [k, child] of Object.entries(v)) {
135
152
  if (/^(price|avgPrice|totalPrice|minPrice|amount)$/i.test(k) && typeof child === 'number' && child > 0) {
136
153
  price = child;
137
154
  return;
@@ -145,7 +162,9 @@ function ctripStructuredCandidate(item) {
145
162
  };
146
163
  priceProbe(item, 0, new Set());
147
164
  }
148
- if (price === undefined && !priceRaw && !encrypted) return null;
165
+ // 加密价(priceToken 在而无明文 price):条目保留,价如实为 0——不伪造任何价
166
+ if (price === undefined && !priceRaw && !encrypted)
167
+ return null;
149
168
  const starOf = summary.hotelStar;
150
169
  const starRaw = starOf?.star ?? summary.star;
151
170
  const star = typeof starRaw === 'number' && starRaw > 0 ? starRaw : undefined;
@@ -153,33 +172,31 @@ function ctripStructuredCandidate(item) {
153
172
  const pos = summary.positionInfo;
154
173
  const address = (typeof pos?.address === 'string' ? pos.address : undefined) ?? (typeof summary.address === 'string' ? summary.address : undefined);
155
174
  const hotelId = summary.hotelId != null ? String(summary.hotelId) : undefined;
156
- return {
157
- name: nameRaw.trim(),
158
- price,
159
- priceRaw,
160
- star,
161
- score: scoreRaw == null ? undefined : String(scoreRaw),
162
- address,
163
- hotelId
164
- };
175
+ return { name: nameRaw.trim(), price, priceRaw, star, score: scoreRaw == null ? undefined : String(scoreRaw), address, hotelId };
165
176
  }
177
+ /** 走形:深找第一个酒店签名数组(纯函数,fixture 测试锚点);malformed 一律返空,不抛错 */
166
178
  export function parseCtripHotelList(body, opts = {}) {
167
179
  let raw;
168
180
  try {
169
181
  raw = JSON.parse(body);
170
- } catch {
182
+ }
183
+ catch {
171
184
  return [];
172
185
  }
173
186
  const out = [];
174
187
  const maxItems = opts.maxItems ?? 20;
175
- const walk = (node, depth, seen)=>{
176
- if (out.length >= maxItems || depth > 12 || node == null || typeof node !== 'object' || seen.has(node)) return;
188
+ const walk = (node, depth, seen) => {
189
+ if (out.length >= maxItems || depth > 12 || node == null || typeof node !== 'object' || seen.has(node))
190
+ return;
177
191
  if (Array.isArray(node)) {
178
- for (const item of node){
179
- if (out.length >= maxItems) return;
180
- if (item == null || typeof item !== 'object' || Array.isArray(item)) continue;
192
+ for (const item of node) {
193
+ if (out.length >= maxItems)
194
+ return;
195
+ if (item == null || typeof item !== 'object' || Array.isArray(item))
196
+ continue;
181
197
  const cand = hotelSignature(item) ?? ctripStructuredCandidate(item);
182
- if (!cand) continue;
198
+ if (!cand)
199
+ continue;
183
200
  out.push({
184
201
  name: cand.name,
185
202
  price: cand.price ?? 0,
@@ -188,20 +205,18 @@ export function parseCtripHotelList(body, opts = {}) {
188
205
  score: cand.score,
189
206
  address: cand.address,
190
207
  hotelId: cand.hotelId,
191
- jumpUrl: cand.hotelId ? `https://hotels.ctrip.com/hotel/${cand.hotelId}` : undefined
208
+ jumpUrl: cand.hotelId ? `https://hotels.ctrip.com/hotel/${cand.hotelId}` : undefined,
192
209
  });
193
210
  }
194
211
  return;
195
212
  }
196
213
  seen.add(node);
197
- for (const child of Object.values(node)){
214
+ for (const child of Object.values(node)) {
198
215
  walk(child, depth + 1, seen);
199
- if (out.length >= maxItems) return;
216
+ if (out.length >= maxItems)
217
+ return;
200
218
  }
201
219
  };
202
220
  walk(raw, 0, new Set());
203
221
  return out;
204
222
  }
205
-
206
-
207
- //# sourceURL=ts/capabilities/session/adapters/ctrip-hotel.ts
@@ -0,0 +1,206 @@
1
+ /**
2
+ * Dida 供应商门户站点适配器(2026-09-09 实装;hotel-be portal integration 迁移线:
3
+ * 员工子账号免知凭据访问供应商 portal——登录经 hotel-be portal 自动填充跳板在
4
+ * dida 官网由人完成,gotry 只在用户本人 Chrome 登录态里被动嗅探检索回包)。
5
+ *
6
+ * - entry:https://portal.dida.com/hotel/find(SPA,页内交互触发检索;深链参数
7
+ * 形态未知——D-13 同款边界,首个真会话后校准)
8
+ * - networkHints:portal-webapi.dida.com 实时价/价格监控 XHR(接口名取自
9
+ * hotel-be supplier/integration/dida/portal 已捕获端点,2026-07 抓包校准);
10
+ * URL hint 未命中时由 content-main 的形状嗅探兜底(响应 JSON 含报价签名即
11
+ * 转发),对接口改名免疫
12
+ * - 解析:SearchRealTime 信封(Envelope{Data.HotelPriceList[].RoomTypeList[]
13
+ * .RatePlanList[]})逐报价展平;SearchMonitor 响应形状未捕获前不猜测——
14
+ * 只按 URL 命中转发、解析留给首个真会话校准(D-13)
15
+ *
16
+ * 只读:适配器不含任何提交/预订语义;预订面(PriceConfirm 等)URL 属 hotel-be
17
+ * 适配器边界,gotry 侧不存在对应原语;登录语义红线同全局:gotry 永不读取/存储/
18
+ * 回传 cookie 值,只读票据 cookie 名(CN_M_DidaTravel)存在性。
19
+ */
20
+ /** 票据 cookie 名(hotel-be portal 会话同款 HttpOnly cookie;只读名字,永不读值) */
21
+ export const DIDA_LOGIN_COOKIE_NAMES = ['CN_M_DidaTravel'];
22
+ /** page 域(content_scripts 注入面与 background 白名单的对账源) */
23
+ export const DIDA_SITE_HOST = 'portal.dida.com';
24
+ /** cookie 域(chrome.cookies 过滤器与登录票据检查;did a 站点 cookie 挂 .dida.com 域 cookie) */
25
+ export const DIDA_SITE_DOMAIN = 'dida.com';
26
+ /** 默认检索入口(酒店 find 页;SPA 加载后由页面自身代码发起价格检索) */
27
+ export const DIDA_ENTRY_URL = 'https://portal.dida.com/hotel/find';
28
+ /** entry 构造(v1:固定 find 页 + 显式覆盖守白名单域;URL 直开即零 DOM 交互) */
29
+ export function buildDidaEntryUrl(q = {}) {
30
+ const url = (q.entryUrl ?? '').trim() || DIDA_ENTRY_URL;
31
+ if (!/^https:\/\/portal\.dida\.com\//.test(url))
32
+ return { ok: false };
33
+ return { ok: true, url };
34
+ }
35
+ /** networkHints:portal-webapi.dida.com 实时价/价格监控 XHR(2026-07 抓包口径;
36
+ * 首个真会话后按现行接口校准——D-13 同款边界) */
37
+ export const DIDA_NETWORK_HINTS = [
38
+ /portal-webapi\.dida\.com\/HotelPriceAPI\/SearchRealTime/i,
39
+ /portal-webapi\.dida\.com\/HotelPriceAPI\/SearchMonitor/i,
40
+ // 2026-09-10 UAT 实测校准:find 页加载态自发的是推荐流(非 SearchRealTime——
41
+ // 那是用户点「预订」后的实时价接口)。推荐酒店 + 推荐价格两个接口成对出现。
42
+ /portal-webapi\.dida\.com\/HotelRecommendAPI\/SearchHomepageRecommendHotels/i,
43
+ /portal-webapi\.dida\.com\/HotelRecommendAPI\/SearchHomepageRecommendPrices/i,
44
+ /portal-webapi\.dida\.com\/PopularDestinationAPI\/SearchHotels/i,
45
+ /portal-webapi\.dida\.com\/PopularDestinationAPI\/SearchHotelPrices/i,
46
+ ];
47
+ /** 形状嗅探签名:URL hint 未命中时的兜底(实时价信封报价签名,对接口改名免疫);
48
+ * 大小上限由调用方先把关 */
49
+ export function looksLikeDidaRatesBody(body) {
50
+ if (!body || body.length > 2_000_000)
51
+ return false;
52
+ return /"HotelPriceList"|"RatePlanList"/.test(body);
53
+ }
54
+ /** 解析推荐酒店列表(Data.Hotels[]:HotelID/Name/Name_CN/Name_EN;Current 页加载态无价) */
55
+ export function parseDidaRecommendHotels(body, opts = {}) {
56
+ let raw;
57
+ try {
58
+ raw = JSON.parse(body);
59
+ }
60
+ catch {
61
+ return [];
62
+ }
63
+ const hotels = raw?.Data?.Hotels;
64
+ if (!Array.isArray(hotels))
65
+ return [];
66
+ const out = [];
67
+ for (const item of hotels.slice(0, opts.maxItems ?? 60)) {
68
+ if (item == null || typeof item !== 'object')
69
+ continue;
70
+ const h = item;
71
+ const hotelId = h.HotelID != null ? String(h.HotelID) : '';
72
+ const name = typeof h.Name_CN === 'string' && h.Name_CN.trim() ? h.Name_CN : (typeof h.Name === 'string' ? h.Name : '');
73
+ if (!hotelId || !name)
74
+ continue;
75
+ out.push({ hotelId, name, nameEN: typeof h.Name_EN === 'string' ? h.Name_EN : undefined });
76
+ }
77
+ return out;
78
+ }
79
+ // (parser types continue below)
80
+ /** 解析价格列表:兼容 Recommend Prices(HotelId/Date)与 PopularDestination(HotelID/CheckInDate)两种键形 */
81
+ export function parseDidaPrices(body) {
82
+ let raw;
83
+ try {
84
+ raw = JSON.parse(body);
85
+ }
86
+ catch {
87
+ return [];
88
+ }
89
+ const prices = raw?.Data?.Prices;
90
+ if (!Array.isArray(prices))
91
+ return [];
92
+ const out = [];
93
+ for (const item of prices) {
94
+ if (item == null || typeof item !== 'object')
95
+ continue;
96
+ const p = item;
97
+ const hotelId = p.HotelId != null ? String(p.HotelId) : (p.HotelID != null ? String(p.HotelID) : '');
98
+ const price = typeof p.Price === 'number' ? p.Price : NaN;
99
+ if (!hotelId || !Number.isFinite(price) || price <= 0)
100
+ continue;
101
+ const dateOf = (v) => (typeof v === 'string' && v.length >= 10 ? v.slice(0, 10) : undefined);
102
+ out.push({
103
+ hotelId,
104
+ price,
105
+ originalPrice: typeof p.OriginalPrice === 'number' ? p.OriginalPrice : undefined,
106
+ currency: typeof p.Currency === 'string' ? p.Currency : undefined,
107
+ priceDate: dateOf(p.Date),
108
+ checkInDate: dateOf(p.CheckInDate),
109
+ checkOutDate: dateOf(p.CheckOutDate),
110
+ });
111
+ }
112
+ return out;
113
+ }
114
+ /** 从 RoomTypePrice 展平一条报价候选(RatePlanList 元素为 dida 已知形态);
115
+ * 酒店字段取 HotelPriceItem.Hotel 嵌套(HotelStatic),容忍旧式扁平形态 */
116
+ function rateCandidate(hotel, room, plan) {
117
+ if (plan == null || typeof plan !== 'object')
118
+ return null;
119
+ const ratePlanId = plan.RatePlanID;
120
+ const price = plan.Price;
121
+ const totalPrice = plan.TotalPrice;
122
+ if (typeof ratePlanId !== 'string' || !ratePlanId.trim())
123
+ return null;
124
+ if (typeof price !== 'number' && typeof totalPrice !== 'number')
125
+ return null;
126
+ const hotelStatic = (hotel.Hotel != null && typeof hotel.Hotel === 'object' ? hotel.Hotel : hotel);
127
+ const hotelId = hotelStatic.HotelID;
128
+ const roomTypeId = room.DidaRoomTypeID;
129
+ return {
130
+ hotelId: hotelId != null ? String(hotelId) : undefined,
131
+ hotelName: typeof hotelStatic.Name === 'string' ? hotelStatic.Name : undefined,
132
+ roomTypeId: roomTypeId != null ? String(roomTypeId) : undefined,
133
+ roomName: typeof room.DidaRoomTypeName_CN === 'string' && room.DidaRoomTypeName_CN.trim()
134
+ ? room.DidaRoomTypeName_CN
135
+ : (typeof room.DidaRoomTypeName_EN === 'string' ? room.DidaRoomTypeName_EN : undefined),
136
+ plan,
137
+ };
138
+ }
139
+ /** 走形:SearchRealTime 信封 → 展平报价列表(纯函数,fixture 测试锚点);
140
+ * malformed 一律返空,不抛错——页面/接口 JSON 是不可信输入(RFC §3.5) */
141
+ export function parseDidaRates(body, opts = {}) {
142
+ let raw;
143
+ try {
144
+ raw = JSON.parse(body);
145
+ }
146
+ catch {
147
+ return [];
148
+ }
149
+ const maxItems = opts.maxItems ?? 40;
150
+ const out = [];
151
+ // 定位 HotelPriceList:信封顶层 Data 下;无信封时容忍裸数组形态
152
+ let hotelList;
153
+ if (raw != null && typeof raw === 'object' && !Array.isArray(raw)) {
154
+ const data = raw.Data;
155
+ if (data != null && typeof data === 'object')
156
+ hotelList = data.HotelPriceList;
157
+ }
158
+ else if (Array.isArray(raw)) {
159
+ hotelList = raw;
160
+ }
161
+ if (!Array.isArray(hotelList))
162
+ return out;
163
+ for (const item of hotelList) {
164
+ if (out.length >= maxItems || item == null || typeof item !== 'object')
165
+ continue;
166
+ const hotel = item;
167
+ // RoomTypeList 与 RoomList 是合并关系(hotel-be converter 两表都吃),不是二选一
168
+ const rooms = [
169
+ ...(Array.isArray(hotel.RoomTypeList) ? hotel.RoomTypeList : []),
170
+ ...(Array.isArray(hotel.RoomList) ? hotel.RoomList : []),
171
+ ];
172
+ for (const room of rooms) {
173
+ if (out.length >= maxItems || room == null || typeof room !== 'object')
174
+ continue;
175
+ const plans = room.RatePlanList;
176
+ if (!Array.isArray(plans))
177
+ continue;
178
+ for (const plan of plans) {
179
+ if (out.length >= maxItems)
180
+ return out;
181
+ const cand = rateCandidate(hotel, room, plan);
182
+ if (!cand)
183
+ continue;
184
+ const p = cand.plan;
185
+ out.push({
186
+ hotelId: cand.hotelId,
187
+ hotelName: cand.hotelName,
188
+ roomTypeId: cand.roomTypeId,
189
+ roomName: cand.roomName,
190
+ ratePlanId: typeof p.RatePlanID === 'string' ? p.RatePlanID : undefined,
191
+ price: typeof p.Price === 'number' ? p.Price : 0,
192
+ totalPrice: typeof p.TotalPrice === 'number' ? p.TotalPrice : undefined,
193
+ currency: typeof p.Currency === 'string' ? p.Currency : undefined,
194
+ mealType: typeof p.MealType === 'string' ? p.MealType : undefined,
195
+ breakfastType: typeof p.BreakfastType === 'string' ? p.BreakfastType : undefined,
196
+ bedType: typeof p.BedType === 'string' ? p.BedType : undefined,
197
+ paymentType: typeof p.PaymentType === 'string' ? p.PaymentType : undefined,
198
+ inventory: typeof p.Inventory === 'number' ? p.Inventory : (typeof p.RoomCount === 'number' ? p.RoomCount : undefined),
199
+ referenceNo: typeof p.ReferenceNo === 'string' ? p.ReferenceNo : undefined,
200
+ jumpUrl: cand.hotelId ? `https://portal.dida.com/hotel/find` : undefined,
201
+ });
202
+ }
203
+ }
204
+ }
205
+ return out;
206
+ }
@@ -1,73 +1,54 @@
1
+ /**
2
+ * 美团本地适配器骨架(民宿/门票——官方通道盲区,RFC §3.1)。
3
+ *
4
+ * 实测边界(2026-08-28 tick):匿名实例 hotel.meituan.com 直接 403——三站中最强反爬,
5
+ * **登录态是硬前置**(founder 纠偏「不能匿名实例」在美团侧是 403 级事实,非策略偏好)。
6
+ * 本骨架:entry 城市拼音表 + 登录票据名单 + networkHint 占位;live 接口形状待用户登录态
7
+ * (profile 内用户自登美团)后由心跳轮实测回填——在此之前 parse 返回空、verdict=miss。
8
+ */
9
+ /** 城市拼音表(美团 URL 路径段;与 ctrip-flight 城市码表同界:词表外 unresolved 不猜) */
1
10
  const CITY_SLUGS = {
2
- 上海: 'shanghai',
3
- 北京: 'beijing',
4
- 广州: 'guangzhou',
5
- 深圳: 'shenzhen',
6
- 成都: 'chengdu',
7
- 昆明: 'kunming',
8
- 大理: 'dali',
9
- 丽江: 'lijiang',
10
- 西安: 'xian',
11
- 杭州: 'hangzhou',
12
- 三亚: 'sanya',
13
- 厦门: 'xiamen',
14
- 重庆: 'chongqing',
15
- 青岛: 'qingdao',
16
- 长沙: 'changsha',
17
- 武汉: 'wuhan',
18
- 南京: 'nanjing',
19
- 郑州: 'zhengzhou',
20
- 贵阳: 'guiyang',
21
- 桂林: 'guilin'
11
+ 上海: 'shanghai', 北京: 'beijing', 广州: 'guangzhou', 深圳: 'shenzhen', 成都: 'chengdu',
12
+ 昆明: 'kunming', 大理: 'dali', 丽江: 'lijiang', 西安: 'xian', 杭州: 'hangzhou',
13
+ 三亚: 'sanya', 厦门: 'xiamen', 重庆: 'chongqing', 青岛: 'qingdao', 长沙: 'changsha',
14
+ 武汉: 'wuhan', 南京: 'nanjing', 郑州: 'zhengzhou', 贵阳: 'guiyang', 桂林: 'guilin',
22
15
  };
23
16
  export const SITE_DOMAIN = 'meituan.com';
24
- export const LOGIN_COOKIE_NAMES = [
25
- 'lt',
26
- 'u',
27
- 'token',
28
- 'n'
29
- ];
17
+ /** 登录票据 cookie 名单(公开常识:lt=login ticket/u=uid/token;登录后校准,同 ctrip 侧做法) */
18
+ export const LOGIN_COOKIE_NAMES = ['lt', 'u', 'token', 'n'];
30
19
  export function buildMeituanEntry(city, vertical, keyword) {
31
20
  const slug = CITY_SLUGS[city];
32
- if (!slug) return {
33
- ok: false,
34
- unresolved: [
35
- city
36
- ]
37
- };
21
+ if (!slug)
22
+ return { ok: false, unresolved: [city] };
38
23
  const base = vertical === 'hotel' ? `https://hotel.meituan.com/${slug}/` : `https://minsu.meituan.com/${slug}/`;
39
- return {
40
- ok: true,
41
- url: keyword ? base + `?q=${encodeURIComponent(keyword)}` : base
42
- };
24
+ return { ok: true, url: keyword ? base + `?q=${encodeURIComponent(keyword)}` : base };
43
25
  }
26
+ /** networkHint 占位:登录态实测后回填真实搜索接口 pattern(当前为空=嗅探层不命中,a11y 兜底接管) */
44
27
  export const NETWORK_HINTS = [];
28
+ /** XHR JSON 解析占位:接口形状实测回填前恒返空(调用方走 a11y 兜底) */
45
29
  export function parseMeituanSearch(_body) {
46
30
  void _body;
47
31
  return [];
48
32
  }
33
+ /**
34
+ * a11y 兜底:从快照条目抽「名称+价格」候选(纯函数;供 extract.ts 条目流消费)。
35
+ * 形状规则:名称=非空 heading/listitem/link;价格=条目名内 ¥\d+(宽松元级);不做语义猜测。
36
+ */
49
37
  export function extractListings(entries, opts = {}) {
50
38
  const out = [];
51
- const roles = new Set([
52
- 'heading',
53
- 'listitem',
54
- 'link',
55
- 'article'
56
- ]);
57
- for (const e of entries){
58
- if (out.length >= (opts.max ?? 30)) break;
59
- if (!roles.has(e.role) || !e.name) continue;
39
+ const roles = new Set(['heading', 'listitem', 'link', 'article']);
40
+ for (const e of entries) {
41
+ if (out.length >= (opts.max ?? 30))
42
+ break;
43
+ if (!roles.has(e.role) || !e.name)
44
+ continue;
60
45
  const pm = /¥\s*(\d+)/.exec(e.name);
61
- if (!pm) continue;
46
+ if (!pm)
47
+ continue;
62
48
  const name = e.name.replace(/¥\s*\d+.*$/, '').trim().slice(0, 80);
63
- if (!name) continue;
64
- out.push({
65
- name,
66
- price: Number(pm[1])
67
- });
49
+ if (!name)
50
+ continue;
51
+ out.push({ name, price: Number(pm[1]) });
68
52
  }
69
53
  return out;
70
54
  }
71
-
72
-
73
- //# sourceURL=ts/capabilities/session/adapters/meituan-local.ts