gogcligog gmail unread:在终端把 Gmail 消息批量标记为未读的完整指南
【免费下载链接】gogcliGoogle Workspace in your terminal.项目地址: https://gitcode.com/GitHub_Trending/gogcl/gogcli
在 Google Workspace 终端化工具 gogcli 中,gog gmail unread是 Gmail「Organize」组下的消息状态管理命令,用于把已读消息重新标记为未读(即给消息重新加上UNREAD标签)。它支持按消息 ID 精确操作,也支持用 Gmail 搜索查询语(query)批量处理,并内置--dry-run预览、JSON/TSV 机器可读输出与--max限额保护。读完本文,你可以掌握该命令的完整用法与参数、它在源码层的执行链路(Users.Messages.BatchModify分批调用、标签名到标签 ID 的解析、分页防死循环保护),以及它与gog gmail mark-read、gog gmail archive等同类批量操作的异同。
命令概览
gog gmail unread的规范用法如下(mail/email与mark-unread分别为gmail与unread的别名,可互换使用):
gog gmail (mail,email) unread (mark-unread) [<messageId> ...] [flags]命令注册在 internal/cmd/gmail.go 中,帮助文本为 "Mark messages as unread",归属 Organize 分组,mark-unread是其显式别名:
// internal/cmd/gmail.go Unread GmailUnreadCmd `cmd:"" name:"unread" aliases:"mark-unread" group:"Organize" help:"Mark messages as unread"`它在 gog gmail 父命令下的完整子命令列表中,与mark-read(标记已读)、archive(归档)、trash(移入垃圾箱)并列,构成一组消息组织操作。
核心参数说明
命令本身只有三个直接参数,全部定义在 internal/cmd/gmail_archive.go 的GmailUnreadCmd结构体中:
// GmailUnreadCmd marks messages as unread. type GmailUnreadCmd struct { MessageIDs []string `arg:"" optional:"" name:"messageId" help:"Message IDs to mark as unread"` Query string `name:"query" short:"q" help:"Mark all messages matching this query as unread"` Max int64 `name:"max" aliases:"limit" help:"Max messages (with --query)" default:"100"` }| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
[<messageId> ...] | 位置参数(可多个,可选) | — | 要标记为未读的消息 ID;与--query二选一,二者都缺省时命令以用法错误退出 |
-q,--query | string | — | 使用 Gmail 搜索查询语法匹配消息,把全部命中结果标记为未读 |
--max,--limit | int64 | 100 | 使用--query时允许处理的最大消息数(别名--limit) |
关键行为约束(来自同一文件的gmailBulkLabelOp实现):
- 必须提供目标:既没有位置消息 ID、也没有
--query时,返回用法错误 "provide message IDs or --query"; --max必须为正:--query模式下--max <= 0会在发起任何 API 请求前就以退出码 2 报 "--max must be > 0"。这一点有测试直接验证(internal/cmd/gmail_archive_test.go 中的TestGmailBulkOps_QueryInvalidMaxFailsBeforeDryRun包含unread zero用例);- 消息 ID 会被归一化:位置参数先经
normalizeGmailMessageID(internal/cmd/webid.go)处理,因此可以容忍 Gmail Web URL 形式的输入而非裸 ID(归档线程模式的测试用例中就传入了完整的https://mail.google.com/...链接并期望其被解析为 thread ID)。
两种典型用法
按消息 ID 标记未读——把之前读过、想重新处理的邮件恢复成未读状态:
gog gmail unread 18ab12cd34ef5678 gog gmail unread 18ab12cd34ef5678 18ab99cd00ef1122 # 支持一次传多个用查询批量标记——例如把收件箱里发件人为某人、且本周内的邮件全部恢复未读:
gog gmail unread --query "in:inbox from:alice@example.com after:2026-09-09" gog gmail unread -q "in:inbox older_than:7d is:read" --max 50 -n # 先预览,前 50 条--query使用的是 Gmail 搜索语法(in:inbox、is:unread、from:、after:等),与gog gmail search使用的查询语法一致。
底层执行链路:从 ID 到UNREAD标签
gog gmail unread的Run方法只有一行核心逻辑,把「加UNREAD标签」委托给共用的批量标签操作函数:
// internal/cmd/gmail_archive.go func (c *GmailUnreadCmd) Run(ctx context.Context, flags *RootFlags) error { return gmailBulkLabelOp(ctx, flags, c.MessageIDs, c.Query, c.Max, []string{"UNREAD"}, nil, "marked as unread", "gmail.unread") }对比同文件中的姊妹命令可以看到差异全部体现在「增/删哪些标签」上:mark-read移除UNREAD标签,trash加TRASH并移除INBOX,archive只移除INBOX,而unread只添加UNREAD、不删除任何标签。dry-run 输出中的操作名gmail.unread也因此在测试 internal/cmd/gmail_archive_test.go 中被断言为added_labels: ["UNREAD"]、removed_labels: []。
gmailBulkLabelOp的完整执行流程如下:
- 归一化与校验:把位置参数归一化为消息 ID,校验「ID 或 query 至少其一」与
--max > 0; - dry-run 拦截:若带
--dry-run(别名--dryrun/--noop/--preview),通过dryRunExit(internal/cmd/dryrun.go)打印包含op、message_ids、query、max、added_labels、removed_labels的结构化请求描述后以退出码 0 结束,不访问网络; - 解析账号并构建 Gmail 客户端:
requireAccount(flags)依据-a/--account(账号邮箱、别名或auto)确定目标账号,gmailService构建服务实例; - 收集消息 ID:
--query模式下调用searchMessageIDs分页搜索,结果与位置参数 ID 合并;搜索为空则输出 "No messages found"(JSON 模式下输出{"action":"marked as unread","count":0}); - 标签名解析为标签 ID:
fetchLabelNameToID(internal/cmd/gmail_labels.go)拉取账号标签列表建立名称→ID 映射,再由resolveLabelIDs(internal/cmd/gmail_labels_utils.go)解析。UNREAD是系统标签,该步对其是幂等确认; - 分批调用 BatchModify:按每批最多 1000 个 ID(Gmail API 的
users.messages.batchModify上限)构造BatchModifyMessagesRequest{Ids, AddLabelIds: [UNREAD]}循环发送,任一失败会报 "batch modify failed at offset N"; - 输出结果:非 JSON 模式打印
Marked as unread N messages(单数消息省略 s);JSON 模式输出action、count、addedLabels、removedLabels四个字段的对象。
搜索分页的两个细节
--query模式下的searchMessageIDs(internal/cmd/gmail_archive.go)有两个值得注意的工程细节:
- 每页最多取 500 条:批大小取
min(remaining, 500),并只请求messages(id),nextPageToken字段,避免拉取消息正文; - 重复分页令牌保护:通过
pageTokenGuard检测服务端异常地重复返回相同nextPageToken(会导致死循环),第 2 次即报错 "repeated page token"。internal/cmd/gmail_archive_test.go 中的TestSearchMessageIDsRejectsRepeatedPageToken、TestSearchMessageIDsRejectsRepeatedEmptyPageToken与TestGmailArchiveCmd_QueryRejectsRepeatedPageToken用假 HTTP 服务专门覆盖了这一场景。
机器可读输出与 Agent 安全开关
gog gmail unread继承 gogcli 全部根级标志,以下与脚本/Agent 场景最相关(完整列表见 docs/commands/gog-gmail-unread.md):
| 标志 | 说明 |
|---|---|
-n,--dry-run(别名--dryrun/--noop/--preview) | 不修改任何数据,打印将要执行的动作并以成功码退出 |
-j,--json(别名--machine) | 以 JSON 输出到 stdout,最适合脚本消费 |
-p,--plain(别名--tsv) | 输出稳定、可解析的 TSV 文本,无颜色 |
--results-only | JSON 模式下只输出主结果,去掉nextPageToken等外层字段 |
--select,--pick(别名--project) | JSON 模式下按逗号分隔的字段路径做 best-effort 选择 |
--wrap-untrusted | 在 JSON/raw 输出中为抓取到的文本字段包裹外部不可信内容标记,供 Agent 消费时防提示注入 |
--readonly | 运行时阻断一切变更类 API 请求;配合auth add还会请求只读 OAuth 作用域 |
--gmail-no-send | 拦截 Gmail 发送类操作(Agent 安全开关) |
-a,--account(别名--acct) | 指定账号邮箱、别名或auto,多账号场景必用 |
--client | 指定 OAuth 客户端名,选择对应的存储凭据与令牌桶 |
--access-token | 直接使用给定的 access token(绕过存储的 refresh token,约 1 小时过期) |
--quota-project | 指定计费 Google Cloud 项目(以X-Goog-User-Project头发送,某些 API 在使用--access-token或 ADC 时必需) |
--enable-commands/--enable-commands-exact/--disable-commands | 按点分路径启用/禁用命令前缀,限制 CLI 可用面 |
--no-input(别名--non-interactive) | 永不交互式提问,出错即失败,适合 CI |
--color(auto\|always\|never)、-v/--verbose、--home | 输出着色、详细日志、覆盖配置/数据根目录(等价GOG_HOME) |
组合示例:在 CI 中安全地批量恢复未读,先预览再执行:
# 预览将影响哪些消息(不联网写操作) gog gmail unread -q "in:inbox from:boss@example.com" --max 100 -n --json # 确认无误后正式执行,仅保留主结果字段 gog gmail unread -q "in:inbox from:boss@example.com" --max 100 --json --results-only与相关命令的关系
gog gmail mark-read:同文件同机制的逆向操作,移除UNREAD标签而非添加;参数结构(messageId...、--query、--max默认 100)完全一致(docs/commands/gog-gmail-mark-read.md);gog gmail archive/gog gmail trash:同样走gmailBulkLabelOp,区别仅在增删的标签组合;archive额外提供--thread按线程操作的模式(该模式不可与--query同时使用,否则以退出码 2 报 "--thread cannot be used with --query");gog gmail search:只读搜索线程;unread --query是「搜索 + 批量改标签」的写操作版本,二者共享 Gmail 查询语法;- 命令树上下文:完整的 Gmail 子命令(get、send、drafts、labels、thread、settings 等)见 docs/commands/gog-gmail.md,全部命令索引见 docs/commands/README.md。
这些文档页均由gog schema --json自动生成(页首注明 "Do not edit this page by hand; runmake docs-commands"),因此文档中的标志定义与源码中的结构体 tag 始终一致。
小结
gog gmail unread的语义模型非常清晰:把「标记未读」实现为「批量给消息添加UNREAD系统标签」,通过Users.Messages.BatchModify按 1000 条一批发送,天然获得幂等性(重复添加同一标签无副作用)与批量效率。配合--query + --max的限额机制、--dry-run预览、JSON/TSV 输出与--readonly等安全开关,它既可以人工交互式使用,也可以安全地嵌入脚本和 Agent 工作流(例如 gogcli 的 Agent 技能gog-inbox-triage就涉及未读邮件的优先级处理,参见 docs/agent-skills.md)。测试文件 internal/cmd/gmail_archive_test.go 对 dry-run 请求内容、--max校验、分页令牌防死循环等均做了断言,为该行为提供了可直接运行的验证依据。
【免费下载链接】gogcliGoogle Workspace in your terminal.项目地址: https://gitcode.com/GitHub_Trending/gogcl/gogcli
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考