diff --git a/README.en.md b/README.en.md index 274fbaf..7277cc8 100644 --- a/README.en.md +++ b/README.en.md @@ -102,7 +102,7 @@ https://mypikpak.com/s/synthetic-demo Password: demo ``` -Share text copied from any PikPak client works in every language it supports, and common synonyms such as `Pass code` or `Access code` are recognized too; see [Copying share text](docs/en/clipboard.md) for the details. +Share text copied from any PikPak client works in every language it supports, and common synonyms such as `Pass code` or `Access code` are recognized too; see [Copying share text](docs/en/clipboard.md) for the details. If the pass code is missing or wrong, the share row shows the reason in red; select the share and press Enter to type the pass code and scan again. The new task appears right away. It is then scanned, saved, downloaded and verified, and the files end up in a folder named after the share title inside your download folder. You can also press `a` to open the paste box, which shows in real time how many shares and pass codes it found: @@ -129,7 +129,7 @@ The icon in front of a name shows where the file is now: | Key | Action | | --- | --- | -| `↑` `↓` / `←` `→` / `Enter` | Select; collapse or expand a share; view file details | +| `↑` `↓` / `←` `→` / `Enter` | Select; collapse or expand a share; view file details; type the pass code for a share waiting for one | | `r` | Retry the selected file or share | | `c` | Cancel (asks for confirmation). Local files, saved progress and cloud copies are kept; press `r` later to resume | | `p` / `P` | Pause or resume the selected item; pause all, or start all when something is paused. Paused state survives a restart | diff --git a/README.ja.md b/README.ja.md index fabc745..89404f8 100644 --- a/README.ja.md +++ b/README.ja.md @@ -102,7 +102,7 @@ https://mypikpak.com/s/synthetic-demo パスワード: demo ``` -PikPak の各言語のクライアントでコピーした共有テキストに対応しており、「パスワード」「パスコード」などのよく使われる表記も認識します。PikPak のウェブ版(日本語)でコピーしたテキストは「パスワード:」の後が空になっているため、パスコードを書き足してください。詳しくは[共有テキストのコピー](docs/ja/clipboard.md)を参照してください。 +PikPak の各言語のクライアントでコピーした共有テキストに対応しており、「パスワード」「パスコード」などのよく使われる表記も認識します。PikPak のウェブ版(日本語)でコピーしたテキストは「パスワード:」の後が空になっているため、パスコードを書き足してください。パスコードがないか正しくない場合、共有の行に理由が赤字で表示されます。共有を選んで Enter を押すとパスコードを入力して再スキャンできます。詳しくは[共有テキストのコピー](docs/ja/clipboard.md)を参照してください。 画面にはすぐ新しいタスクが表示され、スキャン、保存、ダウンロード、検証を順に進めます。最後に、ダウンロード先の中にある共有タイトル名のフォルダにファイルを置きます。`a` を押して貼り付け欄を開くこともでき、認識した共有の数とパスコードの有無がその場で表示されます。 @@ -129,7 +129,7 @@ PikPak の各言語のクライアントでコピーした共有テキストに | キー | 動作 | | --- | --- | -| `↑` `↓` / `←` `→` / `Enter` | 選択、共有の折りたたみ・展開、ファイルの詳細表示 | +| `↑` `↓` / `←` `→` / `Enter` | 選択、共有の折りたたみ・展開、ファイルの詳細表示、パスコード待ちの共有へのパスコード入力 | | `r` | 選択したファイルまたは共有を再試行 | | `c` | 取消(確認あり)。ローカルファイル、進捗、クラウドのコピーはすべて残り、後で `r` で再開できます | | `p` / `P` | 選択項目を一時停止・再開。すべて一時停止、または一時停止中の項目があればすべて開始。一時停止の状態は再起動後も保たれます | diff --git a/README.md b/README.md index 4865575..e681892 100644 --- a/README.md +++ b/README.md @@ -102,6 +102,8 @@ xattr -d com.apple.quarantine ./pikpak-dl-cli-darwin-arm64 提取码:demo ``` +提取码缺失或错误时,分享行以红色显示原因,选中该分享按 Enter 输入提取码即可重新扫描,详见[复制分享文本](docs/clipboard.md)。 + 界面会立即显示新任务,随后依次扫描、转存、下载、校验,最后把文件放进下载目录中以分享标题命名的文件夹里。也可以按 `a` 打开粘贴框,界面会实时提示识别到的分享数和提取码:
@@ -127,7 +129,7 @@ xattr -d com.apple.quarantine ./pikpak-dl-cli-darwin-arm64
| 按键 | 作用 |
| --- | --- |
-| `↑` `↓` / `←` `→` / `Enter` | 选择;折叠或展开分享;查看文件详情 |
+| `↑` `↓` / `←` `→` / `Enter` | 选择;折叠或展开分享;查看文件详情;在等待提取码的分享上输入提取码 |
| `r` | 重试选中的文件或分享 |
| `c` | 取消(需确认);本地文件、断点和云端副本都保留,之后可用 `r` 恢复 |
| `p` / `P` | 暂停或继续选中项;全部暂停,或有暂停时全部开始。暂停状态在重启后保持 |
diff --git a/docs/clipboard.md b/docs/clipboard.md
index b4e7bf8..9bd79ed 100644
--- a/docs/clipboard.md
+++ b/docs/clipboard.md
@@ -14,7 +14,9 @@
标注后可以跟冒号(含全角)、等号、空格或“是/为”,码也可以放在括号或引号里;冒号在行尾时,码可以单独写在下一行,但这一行须是最后一行或后面空一行(否则可能是模板里的文件名,不会当成提取码)。英文等以空格分词的语言,标注与码之间需要冒号、等号或 `is`。解压密码、账号或登录密码、Vault 加密分享的密钥和 Wi-Fi 密码不会被当成提取码。标注后同一行没有内容时按未提供提取码处理;PikPak 网页版的日文界面复制的文本就是这样(“パスワード:”后面是空的),需要补上提取码后重新复制或粘贴。
-也支持链接内明确标记的 `pwd`、`password`、`passcode` 或 `pass_code` 参数;无码分享可以只复制链接。真实缺码、错误码分别已观察到 `PASS_CODE_EMPTY`、`PASS_CODE_ERROR`,应等待新的完整文本,不把它们当成全局账号失效。
+没有任何标注时,只有一种写法会被识别:整段文本只有一行链接和一行单独的提取码(中间可以有空行)。其他没有标注的文字一律不当成提取码。
+
+也支持链接内明确标记的 `pwd`、`password`、`passcode` 或 `pass_code` 参数;无码分享可以只复制链接。真实缺码、错误码分别已观察到 `PASS_CODE_EMPTY`、`PASS_CODE_ERROR`,不会被当成全局账号失效。这时分享行会以红色显示“分享需要提取码”或“提取码错误”:选中该分享按 Enter 输入提取码即可重新扫描,也可以重新复制完整文本。之后单独复制的提取码不会自动配对到等待中的分享。
多个分享可以分别占一行或一段,且每个码明确配对。一个码跟在多条无法区分的链接之后时不会猜测;请分别复制。相似域名、其他 PikPak 页面、单独的提取码和普通文本不会创建任务。原始剪贴板不写入日志或数据库。
diff --git a/docs/en/clipboard.md b/docs/en/clipboard.md
index b0045ed..b75990c 100644
--- a/docs/en/clipboard.md
+++ b/docs/en/clipboard.md
@@ -9,7 +9,9 @@ Password: demo
The pass code can be labelled the way any PikPak client labels it in its own language, or with a common synonym: `Password`, `Pass code`, `Access code`, `提取码`, `密码`, `识别码`, `パスワード`, `パスコード`, `비밀번호`, `Kata sandi`, `Kata laluan`, `Contraseña`, `Senha`, `Пароль`, `كلمة المرور` and more. In English and other languages that separate words with spaces, put a colon, an equals sign or `is` between the label and the code (`Password: demo`, `the password is demo`). The code may be wrapped in brackets or quotes, or sit alone on the next line after a trailing colon, as long as that line is the last one or is followed by a blank line (otherwise it may be a file name from a template and is not taken as the pass code). Archive (unzip) passwords, account or sign-in passwords, Vault share keys and Wi-Fi passwords are never taken as the pass code. A label with nothing after it on the same line counts as no pass code; this is what the PikPak web client produces in Japanese (`パスワード:` is left empty), so add the code and copy or paste the text again.
-Explicitly labelled `pwd`, `password`, `passcode` or `pass_code` parameters in the link are also supported, and for a share without a pass code you can copy just the link. A missing pass code and a wrong pass code have been observed as `PASS_CODE_EMPTY` and `PASS_CODE_ERROR`. In those cases the program waits for a new complete text and does not treat them as the whole account failing.
+Without any label, only one shape is recognized: the whole text is one line with the link and one line with the pass code alone (blank lines in between are fine). Any other unlabelled text is never taken as the pass code.
+
+Explicitly labelled `pwd`, `password`, `passcode` or `pass_code` parameters in the link are also supported, and for a share without a pass code you can copy just the link. A missing pass code and a wrong pass code have been observed as `PASS_CODE_EMPTY` and `PASS_CODE_ERROR`. Neither is treated as the whole account failing. The share row turns red and reads “The share needs a pass code” or “Wrong pass code”: select the share and press Enter to type the pass code and scan again, or copy the complete text again. A pass code copied on its own later is never paired with a waiting share automatically.
Several shares can each take their own line or paragraph, as long as each pass code is clearly paired. A pass code after several links that cannot be told apart is never guessed; copy them separately. Look-alike domains, other PikPak pages, a pass code on its own and ordinary text do not create tasks. The raw clipboard is never written to logs or the database.
diff --git a/docs/ja/clipboard.md b/docs/ja/clipboard.md
index af39fce..5069fa3 100644
--- a/docs/ja/clipboard.md
+++ b/docs/ja/clipboard.md
@@ -11,7 +11,9 @@
PikPak のウェブ版を日本語で使っている場合、コピーされるテキストは「パスワード:」の後が空になっています(ウェブ版の日本語テンプレートの不具合です)。この場合はパスコードなしの共有として受け付け、「パスコードが必要」と表示されるので、パスコードを書き足してからもう一度コピーするか貼り付けてください。
-リンク内で明示された `pwd`、`password`、`passcode`、`pass_code` パラメーターにも対応しています。パスコードのない共有はリンクだけをコピーすれば十分です。実際にパスコードがない場合と誤っている場合には、それぞれ `PASS_CODE_EMPTY`、`PASS_CODE_ERROR` が返ることを確認しています。これらは新しいテキスト全体を待つべき状態であり、アカウント全体の失効とは見なしません。
+ラベルがない場合に認識するのは、テキスト全体がリンクの行とパスコードだけの行の 2 行からなる形だけです(間に空行があってもかまいません)。それ以外のラベルのない文字はパスコードとして扱いません。
+
+リンク内で明示された `pwd`、`password`、`passcode`、`pass_code` パラメーターにも対応しています。パスコードのない共有はリンクだけをコピーすれば十分です。実際にパスコードがない場合と誤っている場合には、それぞれ `PASS_CODE_EMPTY`、`PASS_CODE_ERROR` が返ることを確認しています。どちらもアカウント全体の失効とは見なしません。共有の行は赤字で「共有にパスコードが必要です」または「パスコードが正しくありません」と表示されます。共有を選んで Enter を押すとパスコードを入力して再スキャンできます。テキスト全体をもう一度コピーしてもかまいません。後から単独でコピーしたパスコードが、待機中の共有に自動で対応付けられることはありません。
複数の共有は、それぞれ別の行や段落に分けてかまいません。ただし、各パスコードがどのリンクのものかを明確に対応付けられる必要があります。区別できない複数のリンクの後にパスコードが一つだけある場合は推測しないので、一つずつコピーしてください。類似したドメイン、PikPak のほかのページ、パスコード単独、普通のテキストではタスクを作成しません。クリップボードの元の内容はログにもデータベースにも書き込みません。
diff --git a/docs/releaseNotes/v0.9.2.md b/docs/releaseNotes/v0.9.2.md
new file mode 100644
index 0000000..121c896
--- /dev/null
+++ b/docs/releaseNotes/v0.9.2.md
@@ -0,0 +1,39 @@
+# PikPak DL CLI v0.9.2
+
+发布日期:2026-10-25
+
+提取码缺失或错误时,分享不再一直显示“扫描中”,可以在界面里直接补填提取码;也能识别只有链接和一行提取码的分享文本。
+
+## 改进
+
+- **在界面中补填提取码**:分享因提取码缺失或错误而停下时,选中该分享按 Enter 打开提取码输入框,提交后立即重新扫描。按键提示中的 Enter 会相应显示为“提取码”。提取码只能是 1 到 32 位字母或数字,格式不对时在输入框内说明。
+- **识别单独一行的提取码**:整段文本只有一行链接和一行提取码(中间可以有空行)时,即使没有“提取码:”之类的标注,也能识别。其他没有标注的文字仍不当成提取码。
+
+## 修复
+
+- **扫描失败不再显示为“扫描中”**:提取码被拒绝、分享已失效或扫描多次失败时,分享行以红色显示原因,不再显示“扫描中”和动态图标。提取码问题分为“分享需要提取码”和“提取码错误”两种,分别显示。
+
+## 下载
+
+| 平台 | 文件 |
+| --- | --- |
+| macOS Apple 芯片 | `pikpak-dl-cli-darwin-arm64` |
+| macOS Intel | `pikpak-dl-cli-darwin-x64` |
+| Windows x64 | `pikpak-dl-cli-windows-x64.exe` |
+| Windows arm64 | `pikpak-dl-cli-windows-arm64.exe` |
+| Linux x64(glibc) | `pikpak-dl-cli-linux-x64` |
+| Linux arm64(glibc) | `pikpak-dl-cli-linux-arm64` |
+
+`SHA256SUMS.txt` 列出各文件的校验和。
+
+## 升级说明
+
+直接替换可执行文件即可。任务库结构不变,不需要迁移或重新登录。
+
+升级前已经因提取码停下的分享仍显示“分享需要提取码,或提取码不正确”,同样可以按 Enter 补填提取码。
+
+## 已知问题与待验收
+
+- 提取码输入框尚未在真实终端中手动演练,目前只经过自动化界面测试。
+- 之后单独复制的提取码仍不会自动配对到等待中的分享,需要按 Enter 输入,或重新复制包含链接和提取码的完整文本。
+- v0.9.1 与 v0.8.1 列出的待验收项仍然有效。
diff --git a/openspec/changes/archive/2026-10-25-fix-share-pass-code-waiting/design.md b/openspec/changes/archive/2026-10-25-fix-share-pass-code-waiting/design.md
new file mode 100644
index 0000000..b24dc5f
--- /dev/null
+++ b/openspec/changes/archive/2026-10-25-fix-share-pass-code-waiting/design.md
@@ -0,0 +1,16 @@
+## Context
+
+分享扫描在 `scanShare` 中调用 `/drive/v1/share`;服务端返回 `PASS_CODE_EMPTY` 或 `PASS_CODE_ERROR` 时抛出 `ShareScanError("access_required")`,`finishScan` 把分享的 `scan_status` 记为 `failed`、`last_error` 记为 `access_required`。界面的分享行只区分“已完成扫描”与“其他”,`failed` 也被当成扫描中。
+
+## Goals / Non-Goals
+
+**Goals:** 等待提取码的分享一眼可见,并能在界面中就地补填;识别“链接 + 单独一行提取码”的简单写法。
+
+**Non-Goals:** 不放宽多分享或带说明文字时的配对规则;不把之后单独复制的提取码自动关联到任务(`clipboard-intake` 原有要求保持不变)。
+
+## Decisions
+
+1. **单独一行的提取码只在最简单的形状下识别。** 整段文本去掉空白行后恰好两行:一行只有一个受支持的分享链接,另一行只有 4–32 位字母数字。其他形状(有说明文字、多个链接、带文件名的模板)仍按原规则处理,避免把文件名当成提取码。即使误判,服务端会拒绝,分享进入“提取码错误”,用户可以按 Enter 改正。
+2. **缺码与错码分开记录。** `ShareScanError` 保留 `access_required` 作为类别(文件转存路径仍按它判断),另带 `reason`:`pass_code_missing` 或 `pass_code_wrong`。`finishScan` 与 `scan_waiting` 事件使用 `reason`;访问资料不可读时仍为 `access_required`。
+3. **扫描失败的分享显示错误状态。** `scan_status` 为 `pending` 或 `active` 才算扫描中;`failed` 时用错误色显示原因,没有文件时替代“扫描中”,有文件时附在计数后。
+4. **补填提取码是显式指定目标的输入。** 选中原因为 `pass_code_missing`、`pass_code_wrong` 或 `access_required` 的分享按 Enter 时打开输入框(其他分享仍是展开/折叠)。提交时由 `RuntimeSession` 的新命令 `setPassCode` 交给 `IntakeReceiver.receivePassCode`,复用 `acceptShare`:写入新的访问资料、重置因提取码失败的文件并请求重新扫描。这是用户对选中分享的明确操作,不违反“不自动关联单独复制的提取码”。提取码不写入事件、日志或快照。
diff --git a/openspec/changes/archive/2026-10-25-fix-share-pass-code-waiting/proposal.md b/openspec/changes/archive/2026-10-25-fix-share-pass-code-waiting/proposal.md
new file mode 100644
index 0000000..59951a8
--- /dev/null
+++ b/openspec/changes/archive/2026-10-25-fix-share-pass-code-waiting/proposal.md
@@ -0,0 +1,28 @@
+## Why
+
+复制 `链接 + 空行 + 单独一行的提取码`(不带“密码:”之类的标注)时,解析器认不出提取码,分享按无码接收。服务端返回需要提取码后,分享的扫描记为失败,界面却仍显示“扫描中…”,只有事件区写着“扫描等待:分享需要提取码,或提取码不正确”。用户看不出任务已经停下,只能重新复制完整文本,没有就地补填提取码的办法。
+
+## What Changes
+
+- 解析:整段文本只有一个分享链接(独占一行)和一行单独的提取码时,把这一行识别为该链接的提取码。
+- 扫描结果区分“缺少提取码”(服务端 `PASS_CODE_EMPTY`)与“提取码错误”(`PASS_CODE_ERROR`),分别记录原因。
+- 主面板:扫描失败的分享不再显示“扫描中”,改用错误色显示失败原因;等待提取码的分享显示“按 Enter 输入提取码”。
+- 选中等待提取码的分享按 Enter,打开提取码输入框;提交后更新该分享的访问资料并重新扫描。
+
+## Capabilities
+
+### New Capabilities
+
+(无)
+
+### Modified Capabilities
+
+- `clipboard-intake`:完整分享文本解析支持“单独一行的提取码”;缺失或错误提取码可以在界面中为指定分享补填。
+- `terminal-ui`:扫描失败的分享显示错误状态;新增提取码输入框。
+
+## Impact
+
+- `src/intake/parse.ts`、`src/intake/receiver.ts`
+- `src/pikpak/shares.ts`(区分缺码与错码)、`src/runtime/engine.ts`、`src/runtime/session.ts`(新命令 `setPassCode`)
+- `src/tui/app.tsx`、`main-panel.tsx`、`pages.tsx`、`view-model.ts` 与三种语言的界面文字
+- 测试:`tests/share-text.test.ts`、`tests/shares.test.ts`、界面与会话测试
diff --git a/openspec/changes/archive/2026-10-25-fix-share-pass-code-waiting/specs/clipboard-intake/spec.md b/openspec/changes/archive/2026-10-25-fix-share-pass-code-waiting/specs/clipboard-intake/spec.md
new file mode 100644
index 0000000..5686c67
--- /dev/null
+++ b/openspec/changes/archive/2026-10-25-fix-share-pass-code-waiting/specs/clipboard-intake/spec.md
@@ -0,0 +1,54 @@
+## MODIFIED Requirements
+
+### Requirement: 完整分享文本解析
+
+系统 SHALL 从同一次复制的文本中提取受支持的 PikPak 分享链接及明确对应的提取码,也支持无需提取码的分享。系统 MUST NOT 将任意包含 PikPak 字样的文本、相似域名或无法明确配对的提取码自动作为有效授权输入。系统 SHALL 识别 PikPak 网页版与移动应用在其支持的各种界面语言下复制的分享文本中的提取码标注,以及常见的同义标注与写法(全角冒号、括号或引号包裹、码在标注后的同一行或冒号后的下一行)。解压密码、账号或登录密码、加密分享的密钥等其他用途的密码 MUST NOT 被当作提取码。标注后在同一行内没有值时 SHALL 视为未提供提取码,MUST NOT 把其他行的内容当作提取码。文本去掉空白行后只剩一行仅含一个受支持分享链接的行与一行仅含 4 到 32 位字母数字的行时,系统 SHALL 把后者作为该分享的提取码;其他没有标注的文字 MUST NOT 被当作提取码。
+
+#### Scenario: 分享说明中包含链接和提取码
+- **WHEN** 同一次复制包含一个受支持的分享链接以及明确标记的提取码
+- **THEN** 系统提取规范化分享身份和对应提取码,忽略周围说明文字
+
+#### Scenario: 分享无需提取码
+- **WHEN** 文本没有提取码且服务端确认分享无需提取码
+- **THEN** 系统正常进入分享扫描流程
+
+#### Scenario: 文本包含多个分享
+- **WHEN** 同一次复制包含多个可独立识别且提取码配对明确的分享
+- **THEN** 系统为各分享分别接收请求
+- **AND** 对存在歧义的条目提示用户分别复制完整文本,不猜测提取码归属
+
+#### Scenario: 其他语言的 PikPak 分享文本
+- **WHEN** 用户复制以繁体中文、英文、日文、韩文、印尼文、马来文、西班牙文、葡萄牙文、俄文或阿拉伯文界面生成的 PikPak 分享文本
+- **THEN** 系统识别其中的链接与提取码
+
+#### Scenario: 同义标注
+- **WHEN** 分享说明用“识别码”“访问码”“提取密码”“口令”“パスコード”或“access code”等标注提取码
+- **THEN** 系统识别对应的提取码
+
+#### Scenario: 分享说明附带解压密码
+- **WHEN** 文本同时包含提取码与压缩包的解压密码
+- **THEN** 系统只把提取码作为访问资料,不因解压密码判定配对歧义
+
+#### Scenario: 标注后没有值
+- **WHEN** 文本中的提取码标注后为空,下一行是文件名
+- **THEN** 系统按未提供提取码接收该分享,不把文件名当作提取码
+
+#### Scenario: 链接与单独一行的提取码
+- **WHEN** 文本只有一行分享链接,隔若干空白行后是一行单独的提取码,没有任何标注
+- **THEN** 系统把这一行识别为该分享的提取码
+
+### Requirement: 缺失或错误提取码等待补充
+
+系统 SHALL 在分享需要提取码但输入缺失或提取码被拒绝时,让对应任务等待访问资料补充,并区分“缺少提取码”与“提取码错误”两种原因。系统 MUST NOT 自动把之后单独复制的提取码或其他文本关联到该任务。用户在全屏界面中为选中的等待分享明确输入提取码时,系统 SHALL 按重新复制完整文本的规则更新该分享的访问资料并重新扫描;提取码 MUST NOT 出现在事件、日志或快照中。
+
+#### Scenario: 分两次复制
+- **WHEN** 用户先复制需要提取码的链接,再单独复制一串提取码
+- **THEN** 原任务保持等待补充,并提示重新复制含链接和提取码的完整文本,或在界面中为该分享输入提取码
+
+#### Scenario: 重新复制完整文本
+- **WHEN** 用户重新复制同一分享及有效提取码
+- **THEN** 系统更新原分享请求的访问资料并重新扫描,不受已有缺码或错误状态的去重阻挡
+
+#### Scenario: 在界面中补填提取码
+- **WHEN** 用户选中等待提取码的分享并输入新的提取码
+- **THEN** 系统保存新的访问资料并重新扫描该分享;提取码被拒绝时分享再次显示“提取码错误”
diff --git a/openspec/changes/archive/2026-10-25-fix-share-pass-code-waiting/specs/terminal-ui/spec.md b/openspec/changes/archive/2026-10-25-fix-share-pass-code-waiting/specs/terminal-ui/spec.md
new file mode 100644
index 0000000..6bd87bb
--- /dev/null
+++ b/openspec/changes/archive/2026-10-25-fix-share-pass-code-waiting/specs/terminal-ui/spec.md
@@ -0,0 +1,17 @@
+## ADDED Requirements
+
+### Requirement: 扫描失败与提取码输入
+
+分享的扫描失败时,主面板 SHALL 以错误色显示失败原因,MUST NOT 继续显示“扫描中”或动态图标。分享因缺少或错误的提取码而等待时,分享行 SHALL 提示可按 Enter 输入提取码,按键提示中的 Enter SHALL 相应改为“输入提取码”。选中此类分享按 Enter SHALL 打开提取码输入框:Enter 提交,Esc 取消;提取码不是 1 到 32 位字母数字时 SHALL 在输入框内说明,不提交。其他分享按 Enter 仍为展开或折叠。
+
+#### Scenario: 提取码错误
+- **WHEN** 服务端拒绝分享的提取码
+- **THEN** 分享行以错误色显示“提取码错误”,不再显示“扫描中”
+
+#### Scenario: 输入提取码后重新扫描
+- **WHEN** 用户选中等待提取码的分享,按 Enter,输入提取码并提交
+- **THEN** 输入框关闭,事件区显示已更新提取码并重新扫描该分享
+
+#### Scenario: 其他原因的扫描失败
+- **WHEN** 分享已失效或扫描重试次数用完
+- **THEN** 分享行以错误色显示该原因,按 Enter 仍为展开或折叠
diff --git a/openspec/changes/archive/2026-10-25-fix-share-pass-code-waiting/tasks.md b/openspec/changes/archive/2026-10-25-fix-share-pass-code-waiting/tasks.md
new file mode 100644
index 0000000..53e9e76
--- /dev/null
+++ b/openspec/changes/archive/2026-10-25-fix-share-pass-code-waiting/tasks.md
@@ -0,0 +1,23 @@
+## 1. 解析
+
+- [x] 1.1 `parse.ts`:只有一个链接行与一行单独提取码时识别该提取码。
+- [x] 1.2 `tests/share-text.test.ts`:覆盖用户样本(含不间断空格的空白行)及不应识别的形状。
+
+## 2. 扫描与命令
+
+- [x] 2.1 `ShareScanError` 增加 `reason`,区分 `pass_code_missing` 与 `pass_code_wrong`;`scanShare` 与引擎事件使用它。
+- [x] 2.2 `IntakeReceiver.receivePassCode` 与会话命令 `setPassCode`:校验提取码、更新访问资料并重新扫描。
+- [x] 2.3 测试:扫描原因、`setPassCode` 命令。
+
+## 3. 界面
+
+- [x] 3.1 分享行:扫描失败显示错误状态与原因,等待提取码时提示按 Enter。
+- [x] 3.2 提取码输入框:Enter 打开、提交、Esc 取消,按键提示随选中项变化。
+- [x] 3.3 三种语言的界面文字与原因说明。
+- [x] 3.4 界面测试。
+
+## 4. 收尾
+
+- [x] 4.1 更新 `docs/clipboard.md` 及英文、日文译本。
+- [x] 4.2 运行 `bun run check` 与 `openspec validate fix-share-pass-code-waiting --strict`。
+- [x] 4.3 升级版本号并归档变更。
diff --git a/openspec/specs/clipboard-intake/spec.md b/openspec/specs/clipboard-intake/spec.md
index 9a7092c..a9f351e 100644
--- a/openspec/specs/clipboard-intake/spec.md
+++ b/openspec/specs/clipboard-intake/spec.md
@@ -2,9 +2,7 @@
## Purpose
在前台监听系统剪贴板或接收手动粘贴,从完整的分享文本中可靠地识别分享链接与提取码。重复复制不会产生重复任务,非分享内容不落盘,剪贴板故障对用户可见。
-
## Requirements
-
### Requirement: 前台剪贴板监听
系统 SHALL 在 macOS、Windows 和 Linux 的前台会话(全屏界面或 `watch`)中监听文本剪贴板,除非用户在设置中关闭了监听。Linux 上 SHALL 在 Wayland 会话中经 `wl-paste`、在 X11 会话中经 `xclip` 或 `xsel` 读取文本。启动时 SHALL 检查当前文本一次,之后只处理新的复制事件;下载执行暂停时 SHALL 继续接收可持久化的请求。监听关闭期间系统 MUST NOT 读取剪贴板。
@@ -35,7 +33,7 @@
### Requirement: 完整分享文本解析
-系统 SHALL 从同一次复制的文本中提取受支持的 PikPak 分享链接及明确对应的提取码,也支持无需提取码的分享。系统 MUST NOT 将任意包含 PikPak 字样的文本、相似域名或无法明确配对的提取码自动作为有效授权输入。系统 SHALL 识别 PikPak 网页版与移动应用在其支持的各种界面语言下复制的分享文本中的提取码标注,以及常见的同义标注与写法(全角冒号、括号或引号包裹、码在标注后的同一行或冒号后的下一行)。解压密码、账号或登录密码、加密分享的密钥等其他用途的密码 MUST NOT 被当作提取码。标注后在同一行内没有值时 SHALL 视为未提供提取码,MUST NOT 把其他行的内容当作提取码。
+系统 SHALL 从同一次复制的文本中提取受支持的 PikPak 分享链接及明确对应的提取码,也支持无需提取码的分享。系统 MUST NOT 将任意包含 PikPak 字样的文本、相似域名或无法明确配对的提取码自动作为有效授权输入。系统 SHALL 识别 PikPak 网页版与移动应用在其支持的各种界面语言下复制的分享文本中的提取码标注,以及常见的同义标注与写法(全角冒号、括号或引号包裹、码在标注后的同一行或冒号后的下一行)。解压密码、账号或登录密码、加密分享的密钥等其他用途的密码 MUST NOT 被当作提取码。标注后在同一行内没有值时 SHALL 视为未提供提取码,MUST NOT 把其他行的内容当作提取码。文本去掉空白行后只剩一行仅含一个受支持分享链接的行与一行仅含 4 到 32 位字母数字的行时,系统 SHALL 把后者作为该分享的提取码;其他没有标注的文字 MUST NOT 被当作提取码。
#### Scenario: 分享说明中包含链接和提取码
- **WHEN** 同一次复制包含一个受支持的分享链接以及明确标记的提取码
@@ -66,18 +64,26 @@
- **WHEN** 文本中的提取码标注后为空,下一行是文件名
- **THEN** 系统按未提供提取码接收该分享,不把文件名当作提取码
+#### Scenario: 链接与单独一行的提取码
+- **WHEN** 文本只有一行分享链接,隔若干空白行后是一行单独的提取码,没有任何标注
+- **THEN** 系统把这一行识别为该分享的提取码
+
### Requirement: 缺失或错误提取码等待补充
-系统 SHALL 在分享需要提取码但输入缺失或提取码被拒绝时,让对应任务等待访问资料补充。系统 MUST NOT 自动把之后单独复制的提取码或其他文本关联到该任务。
+系统 SHALL 在分享需要提取码但输入缺失或提取码被拒绝时,让对应任务等待访问资料补充,并区分“缺少提取码”与“提取码错误”两种原因。系统 MUST NOT 自动把之后单独复制的提取码或其他文本关联到该任务。用户在全屏界面中为选中的等待分享明确输入提取码时,系统 SHALL 按重新复制完整文本的规则更新该分享的访问资料并重新扫描;提取码 MUST NOT 出现在事件、日志或快照中。
#### Scenario: 分两次复制
- **WHEN** 用户先复制需要提取码的链接,再单独复制一串提取码
-- **THEN** 原任务保持等待补充,并提示重新复制含链接和提取码的完整文本
+- **THEN** 原任务保持等待补充,并提示重新复制含链接和提取码的完整文本,或在界面中为该分享输入提取码
#### Scenario: 重新复制完整文本
- **WHEN** 用户重新复制同一分享及有效提取码
- **THEN** 系统更新原分享请求的访问资料并重新扫描,不受已有缺码或错误状态的去重阻挡
+#### Scenario: 在界面中补填提取码
+- **WHEN** 用户选中等待提取码的分享并输入新的提取码
+- **THEN** 系统保存新的访问资料并重新扫描该分享;提取码被拒绝时分享再次显示“提取码错误”
+
### Requirement: 复制事件与重复请求去重
系统 SHALL 忽略对同一剪贴板事件的重复读取,并将相同账号下同一分享的并发请求合并。用户新的复制事件即使文本相同,SHALL 可以触发重新扫描;已有扫描或转存正在执行时 SHALL 合并为至多一个后续扫描请求。
@@ -129,3 +135,4 @@
#### Scenario: 只粘贴提取码
- **WHEN** 用户只粘贴提取码而不含链接
- **THEN** 系统说明未识别到分享,不把它配对到任何已有任务
+
diff --git a/openspec/specs/terminal-ui/spec.md b/openspec/specs/terminal-ui/spec.md
index da08b0c..9536178 100644
--- a/openspec/specs/terminal-ui/spec.md
+++ b/openspec/specs/terminal-ui/spec.md
@@ -2,9 +2,7 @@
## Purpose
无参数启动时进入全屏终端界面。界面负责启动检查与登录引导,按本地下载目录分组展示任务、进度与文件位置,并在面板内完成暂停、继续、重试、取消、释放与设置。退出时恢复终端,保存进度。
-
## Requirements
-
### Requirement: 无参数进入交互界面
系统 SHALL 在不带任何参数且标准输入、标准输出均为交互终端时启动全屏界面。标准输入或输出不是交互终端时,系统 SHALL 打印帮助并退出,MUST NOT 输出界面控制序列。带子命令启动时 SHALL 保持子命令原有行为。
@@ -237,3 +235,20 @@
#### Scenario: 启动时授权缺少权限
- **WHEN** 启动时已保存会话的授权范围缺少必需权限
- **THEN** 系统进入登录页,说明缺少哪些权限,提示按 Enter 重新授权,也可以按 Esc 暂时继续使用
+
+### Requirement: 扫描失败与提取码输入
+
+分享的扫描失败时,主面板 SHALL 以错误色显示失败原因,MUST NOT 继续显示“扫描中”或动态图标。分享因缺少或错误的提取码而等待时,分享行 SHALL 提示可按 Enter 输入提取码,按键提示中的 Enter SHALL 相应改为“输入提取码”。选中此类分享按 Enter SHALL 打开提取码输入框:Enter 提交,Esc 取消;提取码不是 1 到 32 位字母数字时 SHALL 在输入框内说明,不提交。其他分享按 Enter 仍为展开或折叠。
+
+#### Scenario: 提取码错误
+- **WHEN** 服务端拒绝分享的提取码
+- **THEN** 分享行以错误色显示“提取码错误”,不再显示“扫描中”
+
+#### Scenario: 输入提取码后重新扫描
+- **WHEN** 用户选中等待提取码的分享,按 Enter,输入提取码并提交
+- **THEN** 输入框关闭,事件区显示已更新提取码并重新扫描该分享
+
+#### Scenario: 其他原因的扫描失败
+- **WHEN** 分享已失效或扫描重试次数用完
+- **THEN** 分享行以错误色显示该原因,按 Enter 仍为展开或折叠
+
diff --git a/package.json b/package.json
index 28dd82b..3b98b10 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "pikpak-dl-cli",
- "version": "0.9.1",
+ "version": "0.9.2",
"author": "rozx",
"homepage": "https://github.com/rozx/pikpak-dl-cli",
"private": true,
diff --git a/src/i18n/messages/en.ts b/src/i18n/messages/en.ts
index bd75828..d07d0fc 100644
--- a/src/i18n/messages/en.ts
+++ b/src/i18n/messages/en.ts
@@ -145,7 +145,9 @@ Usage: pikpak-dl-cli Open the full-screen interface in an inter
looks: { releasing: "Releasing", completedMissing: "Completed · local file is gone", cancelled: "Cancelled", paused: "Paused" },
reasons: {
- access_required: { reason: "The share needs a pass code, or the pass code is wrong", advice: "Copy the complete share text with the link and pass code again, or press a to paste it." },
+ access_required: { reason: "The share needs a pass code, or the pass code is wrong", advice: "Select the share and press Enter to type the pass code, or copy the complete share text with the link and pass code again." },
+ pass_code_missing: { reason: "The share needs a pass code", advice: "Select the share and press Enter to type the pass code, or copy the complete share text with the link and pass code again." },
+ pass_code_wrong: { reason: "Wrong pass code", advice: "Select the share and press Enter to type the correct pass code, or copy the complete share text with the link and pass code again." },
cloud_space: { reason: "Not enough free cloud space; waiting for space that can be reclaimed", advice: "Wait for cloud copies of completed files to be reclaimed, or free up space in PikPak yourself." },
waiting_sync: { reason: "The cloud quota does not reflect the space just released yet", advice: "It will be checked again automatically; nothing to do." },
copy_version_changed: { reason: "The cloud copy was modified and no longer matches the share", advice: "Press r to retry; the file will be saved from the share again." },
@@ -162,6 +164,7 @@ Usage: pikpak-dl-cli Open the full-screen interface in an inter
rate_limited: { reason: "Too many requests; PikPak is rate limiting", advice: "It will retry automatically; nothing to do." },
remote_copy_missing: { reason: "The cloud copy no longer exists", advice: "Press r to retry; it will be saved again." },
remote_request_failed: { reason: "A PikPak request failed", advice: "Press r to retry later." },
+ scan_failed: { reason: "Scan failed", advice: "Press r to retry." },
scan_retry_exhausted: { reason: "Scanning failed several times", advice: "Check your network, then press r to retry." },
share_unavailable: { reason: "The share has expired or cannot be accessed", advice: "Make sure the share link is still valid." },
traffic_quota: { reason: "Your PikPak download traffic quota is used up", advice: "It continues automatically once the quota resets." },
@@ -243,6 +246,7 @@ Usage: pikpak-dl-cli Open the full-screen interface in an inter
connections: "Download connections updated; takes effect from the next file",
activeFiles: "Concurrent files updated; takes effect when a slot frees up",
language: name => `Interface language switched to ${name}`,
+ passCode: target => `Pass code updated for ${target}; scanning again`,
failed: reason => `Operation failed: ${reason}`,
},
@@ -262,6 +266,7 @@ Usage: pikpak-dl-cli Open the full-screen interface in an inter
stopping: "Stopping and saving progress… (press Ctrl+C again to quit now)",
groupShares: count => `${count} share(s)`,
scanning: "Scanning…",
+ enterPassCode: "press Enter to type the pass code",
completed: (done, total) => `${done}/${total} done`,
counts: { downloading: "downloading", transferring: "saving", paused: "paused", queued: "queued", failed: "failed", waiting: "waiting", cancelled: "cancelled", releasing: "releasing", missing: "missing locally" },
shareCancelled: " · share cancelled",
@@ -270,7 +275,7 @@ Usage: pikpak-dl-cli Open the full-screen interface in an inter
},
keys: {
- details: "info", retry: "retry", cancel: "cancel",
+ details: "info", passCode: "code", retry: "retry", cancel: "cancel",
pauseAll: (selectedPaused, anyPaused) => selectedPaused ? "resume/all" : anyPaused ? "pause/run all" : "pause/all", releaseAll: "release/all", folder: "dir",
paste: "paste", settings: "setup", quit: "quit", confirm: "confirm", back: "back", submit: "submit", releaseCopy: "release cloud copy", openFolder: "open folder",
save: "save", discard: "discard", select: "select", adjust: "adjust", view: "view", edit: "edit", toggle: "toggle", reauthorize: "authorize again", logout: "sign out", confirmLogout: "sign out",
@@ -306,6 +311,14 @@ Usage: pikpak-dl-cli Open the full-screen interface in an inter
openFailed: reason => `Cannot open the folder: ${reason}`,
},
+ passCode: {
+ title: share => `Pass code for ${share}`,
+ label: "Pass code: ",
+ hint: "The share is scanned again after you submit",
+ invalid: "A pass code is 1 to 32 letters or digits",
+ rejected: reason => `Not saved: ${reason}`,
+ },
+
paste: {
title: "Paste share text",
hint: "Paste a share text with the link and pass code",
diff --git a/src/i18n/messages/ja.ts b/src/i18n/messages/ja.ts
index c9c3d27..c7f12cd 100644
--- a/src/i18n/messages/ja.ts
+++ b/src/i18n/messages/ja.ts
@@ -147,7 +147,9 @@ PikPak クラウドドライブをダウンロードの中継に使います。
looks: { releasing: "解放中", completedMissing: "完了 · ローカルファイルがありません", cancelled: "キャンセル済み", paused: "一時停止中" },
reasons: {
- access_required: { reason: "共有にパスコードが必要か、パスコードが正しくありません", advice: "リンクとパスコードを含む共有テキスト全体をもう一度コピーするか、a で貼り付けてください。" },
+ access_required: { reason: "共有にパスコードが必要か、パスコードが正しくありません", advice: "共有を選んで Enter でパスコードを入力するか、リンクとパスコードを含む共有テキスト全体をもう一度コピーしてください。" },
+ pass_code_missing: { reason: "共有にパスコードが必要です", advice: "共有を選んで Enter でパスコードを入力するか、リンクとパスコードを含む共有テキスト全体をもう一度コピーしてください。" },
+ pass_code_wrong: { reason: "パスコードが正しくありません", advice: "共有を選んで Enter で正しいパスコードを入力するか、リンクとパスコードを含む共有テキスト全体をもう一度コピーしてください。" },
cloud_space: { reason: "クラウドの空き容量が不足しています。回収できる容量を待っています", advice: "完了したファイルのクラウドのコピーが回収されるのを待つか、PikPak で容量を空けてください。" },
waiting_sync: { reason: "解放した容量がまだクラウドの容量に反映されていません", advice: "しばらくすると自動で再確認します。操作は不要です。" },
copy_version_changed: { reason: "クラウドのコピーが変更され、共有のバージョンと一致しません", advice: "r で再試行すると、共有のファイルを保存し直します。" },
@@ -164,6 +166,7 @@ PikPak クラウドドライブをダウンロードの中継に使います。
rate_limited: { reason: "リクエストが多すぎるため、PikPak が一時的に制限しています", advice: "自動で再試行します。操作は不要です。" },
remote_copy_missing: { reason: "クラウドのコピーがもうありません", advice: "r で再試行すると、保存し直します。" },
remote_request_failed: { reason: "PikPak へのリクエストに失敗しました", advice: "しばらくしてから r で再試行してください。" },
+ scan_failed: { reason: "スキャンに失敗しました", advice: "r で再試行してください。" },
scan_retry_exhausted: { reason: "スキャンが何度も失敗しました", advice: "ネットワークを確認してから r で再試行してください。" },
share_unavailable: { reason: "共有の期限が切れているか、アクセスできません", advice: "共有リンクがまだ有効か確認してください。" },
traffic_quota: { reason: "PikPak のダウンロード転送量を使い切りました", advice: "転送量が回復すると自動で続行します。" },
@@ -245,6 +248,7 @@ PikPak クラウドドライブをダウンロードの中継に使います。
connections: "ダウンロード接続数を更新しました。次のファイルから適用されます",
activeFiles: "同時ダウンロード数を更新しました。枠が空き次第適用されます",
language: name => `表示言語を ${name} に切り替えました`,
+ passCode: target => `${target}のパスコードを更新し、再スキャンします`,
failed: reason => `操作に失敗しました:${reason}`,
},
@@ -264,6 +268,7 @@ PikPak クラウドドライブをダウンロードの中継に使います。
stopping: "停止して進捗を保存しています…(もう一度 Ctrl+C ですぐに終了)",
groupShares: count => `共有 ${count} 件`,
scanning: "スキャン中…",
+ enterPassCode: "Enter でパスコードを入力",
completed: (done, total) => `${done}/${total} 完了`,
counts: { downloading: "ダウンロード中", transferring: "保存中", paused: "一時停止", queued: "待機", failed: "失敗", waiting: "待ち", cancelled: "キャンセル済み", releasing: "解放中", missing: "ローカルなし" },
shareCancelled: " · 共有はキャンセル済み",
@@ -272,7 +277,7 @@ PikPak クラウドドライブをダウンロードの中継に使います。
},
keys: {
- details: "詳細", retry: "再試行", cancel: "取消",
+ details: "詳細", passCode: "パスコード", retry: "再試行", cancel: "取消",
pauseAll: (selectedPaused, anyPaused) => selectedPaused ? "再開/全て開始" : anyPaused ? "停止/全て開始" : "停止/全て", releaseAll: "解放/全て", folder: "フォルダ",
paste: "貼付", settings: "設定", quit: "終了", confirm: "確定", back: "戻る", submit: "送信", releaseCopy: "クラウドのコピーを解放", openFolder: "フォルダを開く",
save: "保存", discard: "破棄", select: "選択", adjust: "調整", view: "表示", edit: "変更", toggle: "切替", reauthorize: "再認可", logout: "ログアウト", confirmLogout: "ログアウトする",
@@ -308,6 +313,14 @@ PikPak クラウドドライブをダウンロードの中継に使います。
openFailed: reason => `フォルダを開けません:${reason}`,
},
+ passCode: {
+ title: share => `${share}のパスコードを入力`,
+ label: "パスコード:",
+ hint: "送信すると共有を再スキャンします",
+ invalid: "パスコードは 1〜32 文字の英数字です",
+ rejected: reason => `保存できませんでした:${reason}`,
+ },
+
paste: {
title: "共有テキストの貼り付け",
hint: "リンクとパスコードを含む共有テキストを貼り付けてください",
diff --git a/src/i18n/messages/zh-CN.ts b/src/i18n/messages/zh-CN.ts
index 86a2c0e..b68e4a6 100644
--- a/src/i18n/messages/zh-CN.ts
+++ b/src/i18n/messages/zh-CN.ts
@@ -149,7 +149,9 @@ export const zhCN = {
looks: { releasing: "释放中", completedMissing: "已完成 · 本地文件已不在", cancelled: "已取消", paused: "已暂停" },
reasons: {
- access_required: { reason: "分享需要提取码,或提取码不正确", advice: "重新复制包含链接和提取码的完整分享文本,或按 a 粘贴。" },
+ access_required: { reason: "分享需要提取码,或提取码不正确", advice: "选中该分享按 Enter 输入提取码,或重新复制包含链接和提取码的完整分享文本。" },
+ pass_code_missing: { reason: "分享需要提取码", advice: "选中该分享按 Enter 输入提取码,或重新复制包含链接和提取码的完整分享文本。" },
+ pass_code_wrong: { reason: "提取码错误", advice: "选中该分享按 Enter 输入正确的提取码,或重新复制包含链接和提取码的完整分享文本。" },
cloud_space: { reason: "云盘剩余空间不足,正在等待可回收的空间", advice: "等待已完成文件的云端副本被回收,或在 PikPak 中自行清理空间。" },
waiting_sync: { reason: "云盘容量尚未反映刚释放的空间", advice: "稍后会自动重新检查,无需操作。" },
copy_version_changed: { reason: "云盘中的副本已被改动,与分享中的版本不一致", advice: "按 r 重试,将重新转存分享中的文件。" },
@@ -166,6 +168,7 @@ export const zhCN = {
rate_limited: { reason: "请求过于频繁,PikPak 暂时限流", advice: "稍后会自动重试,无需操作。" },
remote_copy_missing: { reason: "云盘中的副本已不存在", advice: "按 r 重试,将重新转存。" },
remote_request_failed: { reason: "PikPak 请求失败", advice: "稍后按 r 重试。" },
+ scan_failed: { reason: "扫描失败", advice: "按 r 重试。" },
scan_retry_exhausted: { reason: "扫描多次失败", advice: "检查网络后按 r 重试。" },
share_unavailable: { reason: "分享已失效或无法访问", advice: "确认分享链接仍然有效。" },
traffic_quota: { reason: "PikPak 下载流量额度已用完", advice: "额度恢复后会自动继续。" },
@@ -247,6 +250,7 @@ export const zhCN = {
connections: "下载连接数已更新,从下一个文件开始生效",
activeFiles: "同时下载的文件数已更新,名额空出时生效",
language: (name: string) => `界面语言已切换为 ${name}`,
+ passCode: (target: string) => `已更新 ${target} 的提取码,重新扫描`,
failed: (reason: string) => `操作失败:${reason}`,
},
@@ -266,6 +270,7 @@ export const zhCN = {
stopping: "正在停止并保存进度…(再按 Ctrl+C 立即退出)",
groupShares: (count: number) => `${count} 个分享`,
scanning: "扫描中…",
+ enterPassCode: "按 Enter 输入提取码",
completed: (done: number, total: number) => `${done}/${total} 完成`,
counts: { downloading: "下载中", transferring: "转存中", paused: "暂停", queued: "排队", failed: "失败", waiting: "等待", cancelled: "已取消", releasing: "释放中", missing: "本地缺失" },
shareCancelled: " · 分享已取消",
@@ -274,7 +279,7 @@ export const zhCN = {
},
keys: {
- details: "详情", retry: "重试", cancel: "取消",
+ details: "详情", passCode: "提取码", retry: "重试", cancel: "取消",
// 单项与全部合写:有暂停时 P 是全部开始,写明“全部开始”。
pauseAll: (selectedPaused: boolean, anyPaused: boolean) => `${selectedPaused ? "继续" : "暂停"}/${anyPaused ? "全部开始" : "全部"}`, releaseAll: "释放/全部", folder: "目录",
paste: "粘贴", settings: "设置", quit: "退出", confirm: "确认", back: "返回", submit: "提交", releaseCopy: "释放云端副本", openFolder: "打开目录",
@@ -311,6 +316,14 @@ export const zhCN = {
openFailed: (reason: string) => `无法打开目录:${reason}`,
},
+ passCode: {
+ title: (share: string) => `输入${share}的提取码`,
+ label: "提取码:",
+ hint: "提交后重新扫描该分享",
+ invalid: "提取码应为 1 到 32 位字母或数字",
+ rejected: (reason: string) => `未能保存:${reason}`,
+ },
+
paste: {
title: "粘贴分享文本",
hint: "粘贴包含链接和提取码的分享文本",
diff --git a/src/intake/parse.ts b/src/intake/parse.ts
index 2290ef2..ef89e11 100644
--- a/src/intake/parse.ts
+++ b/src/intake/parse.ts
@@ -74,8 +74,17 @@ function uniqueCode(values: string[]): string | null {
return unique.length <= 1 && unique.every(value => /^[A-Za-z0-9]{1,32}$/.test(value)) ? unique[0] ?? "" : null;
}
-export function parseShares(text: string): ParseResult {
- if (text.length > 65536) return { shares: [], issues: text.includes("https://mypikpak.com/s/") ? ["input_too_large"] : [] };
+// 单独一行的提取码(fix-share-pass-code-waiting 决策 1):去掉空白行后只剩一行链接与一行字母数字时,
+// 补上标注再按常规规则解析;其他没有标注的文字不当成提取码。
+function loneCode(text: string): string {
+ const lines = text.split(/\r?\n/).map(line => line.trim()).filter(Boolean);
+ if (lines.length !== 2 || !/^https?:\/\/\S+$/i.test(lines[0]!) || !/^[A-Za-z0-9]{4,32}$/.test(lines[1]!.normalize("NFKC"))) return text;
+ return `${lines[0]}\n提取码:${lines[1]}`;
+}
+
+export function parseShares(input: string): ParseResult {
+ if (input.length > 65536) return { shares: [], issues: input.includes("https://mypikpak.com/s/") ? ["input_too_large"] : [] };
+ const text = loneCode(input);
const result: ParseResult = { shares: [], issues: [] };
for (const block of blocks(text)) {
const links: Link[] = [];
diff --git a/src/intake/receiver.ts b/src/intake/receiver.ts
index 9e019bf..cd62ec8 100644
--- a/src/intake/receiver.ts
+++ b/src/intake/receiver.ts
@@ -44,4 +44,14 @@ export class IntakeReceiver {
}
return { ids, issues: parsed.issues, failure: null };
}
+ // 界面中为选中的分享补填提取码(fix-share-pass-code-waiting 决策 4):目标由用户明确指定,按重新复制完整文本处理。
+ async receivePassCode(id: string, passCode: string): Promise