Iris — Rainbow Messenger

Polar Sponsor
爱发电 赞助
.NET 9.0

Iris——彩虹信使。读取Gmail收件箱,根据紧急程度和发件人重要性对每封邮件评分,为排名前5的邮件撰写回复,并生成每日行动报告。

Iris — Inbox Intelligence. 读完你最后的50封邮件,每封紧急得分,获得你前5名的回复草稿,并看到干净的动作列表,每两分钟内就全部收到

功能概述

Iris — Inbox Intelligence. 读完你最后的50封邮件,每封紧急得分,获得你前5名的回复草稿,并看到干净的动作列表,每两分钟内就全部收到是一项面向实际任务的技能,主要用于紧急的Scor。它将相关步骤、工具调用和结果整理方式集中到统一流程中,帮助使用者更快完成目标并减少重复操作。

核心要点

  • 使用时应结合输入条件选择合适的执行方式,核对必要参数、依赖环境与输出内容,并按原始要求处理异常情况。
  • 该技能适合需要稳定复用相关能力的场景,可作为自动化工作流的一部分,也便于后续检查、调整和扩展。
  • 从功能定位来看,该技能强调把分散的操作要求整理成清晰、可复用的处理流程,使用户能够围绕既定目标快速准备输入、选择执行方式并获得结构化结果。

使用与执行

实际使用前应先确认任务范围、数据来源、运行环境、必要权限和关键参数,再依据技能说明逐步执行;若输入条件不完整,应先补齐信息或采用保守配置,避免因错误假设导致结果偏离需求。

结果检查与注意事项

执行过程中需要关注工具调用是否成功、接口或依赖是否可用、输出格式是否符合预期,并对异常提示、缺失字段和边界情况进行处理;涉及批量任务时,还应保存进度,避免中断后重复操作。

Iris — 收件箱智能

读取你最近的 50 封邮件,为每封邮件按紧急程度打分(0–100),自动生成前 5 封高优先级邮件的回复草稿,并呈现一份清晰待办清单——全程耗时不到 2 分钟。

你将获得的功能

  • 紧急度评分(0–100):基于关键词、发件人重要性、邮件时效性及是否需回复等信号综合计算
  • 优先收件箱:今日最需处理的前 5 封邮件,附带一键生成的回复草稿
  • 噪音过滤:自动屏蔽新闻简报、无回复地址(no-reply)及营销类邮件
  • VIP 跟踪:可设定特定发件人或域名,使其始终获得高分
  • 日报自动保存为 Markdown 文件——全天随时查阅

配置(一次性,约 2 分钟)

  1. 访问 myaccount.google.com/apppasswords
  2. 创建应用专用密码 —— 选择 Mail → Other (Custom name)
  3. 复制生成的 16 位密码
  4. 将该密码(非你的 Google 登录密码)设为环境变量 GMAIL_APP_PASSWORD

🔒 安全说明

仅连接至:imap.gmail.com(仅使用你的凭证,且为只读权限)。不调用任何外部服务。
应用专用密码作用域严格限定于本应用,可在 Google 账户设置中随时撤销。

第一步 — 安装依赖

pip3 install rich --break-system-packages --quiet

⚡ 升级至 Iris Pro

👉 获取 Iris Pro → ko-fi.com/s/f75940a0ce —— 一次性付费 $9

openclaw skills install iris-pro
# 将你在 Ko-fi 获取的 LICENSE_KEY 设为环境变量,然后运行

💰 套餐优惠:全部 5 项 Pro 技能仅需 $29 → ko-fi.com/s/7625accf3f(立省 $16)

第二步 — 快速分类收件箱

import os, imaplib, email, re
from email.header import decode_header
from email.utils import parsedate_to_datetime
from datetime import datetime, timezone
from rich.console import Console
from rich.table import Table
from rich.panel import Panel
from rich import box

FENCE = chr(96) * 3

console = Console()

GMAIL_ADDR = os.environ.get("GMAIL_ADDRESS", "").strip()
GMAIL_PASS = os.environ.get("GMAIL_APP_PASSWORD", "").strip()
try:
    SCAN_COUNT = int(os.environ.get("SCAN_COUNT", "50"))
except ValueError:
    console.print("[yellow]⚠️  SCAN_COUNT 必须为整数 —— 默认使用 50[/yellow]")
    SCAN_COUNT = 50
VIP_RAW    = os.environ.get("VIP_SENDERS", "")
VIP_LIST   = [v.strip().lower() for v in VIP_RAW.split(",") if v.strip()]
YOUR_NAME  = os.environ.get("YOUR_NAME", "").strip()
YOUR_ROLE  = os.environ.get("YOUR_ROLE", "").strip()

if not GMAIL_ADDR or not GMAIL_PASS:
    console.print(Panel(
        "[red]GMAIL_ADDRESS 和 GMAIL_APP_PASSWORD 均为必需项。[/red]nn"
        "如何创建应用专用密码:n"
        "1. 访问 [bold]myaccount.google.com/apppasswords[/bold]n"
        "2. 选择 Mail → Other (Custom name)n"
        "3. 复制生成的 16 位密码n"
        "4. 将 GMAIL_APP_PASSWORD 设为该值",
        title="[bold red]⚙️  需要完成配置[/bold red]",
        border_style="red"
    ))
    raise SystemExit(1)

URGENT_KEYWORDS = [
    "urgent", "asap", "deadline", "immediately", "action required", "time sensitive",
    "overdue", "past due", "invoice", "payment due", "legal", "lawsuit", "critical",
    "emergency", "final notice", "expires", "expiring", "last chance",
]
REPLY_KEYWORDS = [
    "?", "question", "can you", "could you", "please", "request",
    "following up", "follow-up", "reminder", "let me know", "thoughts",
]
QUESTION_KW = ["?", "question", "can you", "could you", "help", "assist"]
NOISE_PATTERNS = [
    r"unsubscribe", r"newsletter", r"no-reply@", r"noreply@",
    r"marketing@", r"notifications?@", r"donotreply@",
    r"@.*.(mailchim|sendgrid|constantcontact|klaviyo)",
]
NOISE_SUBJECTS = [
    "sale", "% off", "deal", "offer", "promo", "subscribe", "newsletter",
    "weekly digest", "monthly update", "announcement",
]

def decode_str(s):
    if not s:
        return ""
    parts = decode_header(s)
    result = []
    for part, enc in parts:
        if isinstance(part, bytes):
            try:
                result.append(part.decode(enc or "utf-8", errors="replace"))
            except Exception:
                result.append(part.decode("utf-8", errors="replace"))
        else:
            result.append(str(part))
    return " ".join(result)

def is_noise(sender: str, subject: str) -> bool:
    text = (sender + " " + subject).lower()
    for pat in NOISE_PATTERNS:
        if re.search(pat, text):
            return True
    for kw in NOISE_SUBJECTS:
        if kw in subject.lower():
            return True
    return False

def score_email(subject: str, snippet: str, sender: str, age_hours: float, has_replied: bool) -> int:
    score = 50
    subj_low = subject.lower()
    snip_low = snippet.lower()
    # 紧急度关键词
    for kw in URGENT_KEYWORDS:
        if kw in subj_low or kw in snip_low:
            score += 20
            break
    # 是否需回复
    for kw in REPLY_KEYWORDS:
        if kw in subj_low or kw in snip_low:
            score += 10
            break
    # VIP 发件人
    sender_low = sender.lower()
    for vip in VIP_LIST:
        if vip in sender_low:
            score += 25
            break
    # 时效性加减分
    if age_hours < 2:
        score += 5
    elif age_hours < 24:
        score += 0
    elif age_hours > 48:
        score -= 10
    elif age_hours > 120:
        score -= 20
    # 已回复则降分
    if has_replied:
        score -= 15
    return max(0, min(score, 100))

# ── 连接 Gmail ──────────────────────────────────────────────────────────
console.print(Panel.fit(
    f"[bold cyan]🌈 Iris — Inbox Intelligence[/bold cyan]n"
    f"正在扫描 [yellow]{SCAN_COUNT}[/yellow] 封来自 [green]{GMAIL_ADDR}[/green] 的最新邮件",
    border_style="cyan"
))

try:
    mail = imaplib.IMAP4_SSL("imap.gmail.com", 993)
    mail.login(GMAIL_ADDR, GMAIL_PASS)
    mail.select("INBOX", readonly=True)
except imaplib.IMAP4.error as e:
    console.print(Panel(
        f"[red]IMAP 登录失败:[/red] {e}nn"
        "检查清单:n"
        "• GMAIL_ADDRESS 是否正确?(需填写完整邮箱地址,而非仅用户名)n"
        "• GMAIL_APP_PASSWORD 是否为 16 位应用专用密码(非你的 Google 登录密码)?n"
        "• 是否已在 Gmail 设置 → 查看所有设置 → 转发和 POP/IMAP 中启用 IMAP?",
        title="[bold red]登录错误[/bold red]",
        border_style="red"
    ))
    raise SystemExit(1)
except Exception as e:
    console.print(f"[red]连接异常:{e}[/red]")
    raise SystemExit(1)

_, msg_ids = mail.search(None, "ALL")
all_ids = msg_ids[0].split() if msg_ids and msg_ids[0] else []
if not all_ids:
    console.print(Panel("[yellow]收件箱为空 —— 无可处理邮件。[/yellow]", border_style="yellow"))
    raise SystemExit(0)

recent_ids = all_ids[-SCAN_COUNT:] if len(all_ids) > SCAN_COUNT else all_ids
recent_ids = list(reversed(recent_ids))  # 最新邮件在前

# ── 获取并评分邮件 ────────────────────────────────────────────────────
emails_data = []
now = datetime.now(timezone.utc)

console.print(f"[dim]正在获取 {len(recent_ids)} 封邮件…[/dim]")
for uid in recent_ids:
    try:
        _, raw = mail.fetch(uid, "(RFC822.HEADER FLAGS)")
        if not raw or not raw[0]:
            continue
        raw_header = raw[0][1] if isinstance(raw[0], tuple) else raw[0]
        msg = email.message_from_bytes(raw_header)
        subject = decode_str(msg.get("Subject", "(no subject)"))
        sender  = decode_str(msg.get("From", ""))
        date_str = msg.get("Date", "")
        flags_raw = raw[0][0] if isinstance(raw[0], tuple) else b""
        has_replied = b"\Answered" in flags_raw

        # 解析发送时间
        try:
            sent_dt = parsedate_to_datetime(date_str)
            if sent_dt.tzinfo is None:
                sent_dt = sent_dt.replace(tzinfo=timezone.utc)
            age_hours = (now - sent_dt).total_seconds() / 3600
        except Exception:
            age_hours = 0

        # 获取正文片段
        body_snippet = ""
        try:
            _, raw_body = mail.fetch(uid, "(BODY[TEXT]<0.300>)")
            if raw_body and raw_body[0] and isinstance(raw_body[0], tuple):
                raw_b = raw_body[0][1]
                if raw_b:
                    body_snippet = raw_b.decode("utf-8", errors="replace").strip()[:200]
        except Exception:
            pass

        noise = is_noise(sender, subject)
        urgency = score_email(subject, body_snippet, sender, age_hours, has_replied)

        # 提取发件人姓名与邮箱
        sender_match = re.search(r'"?([^"<]+)"?s*<([^>]+)>', sender)
        if sender_match:
            sender_name  = sender_match.group(1).strip()
            sender_email = sender_match.group(2).strip()
        else:
            sender_name  = sender
            sender_email = sender

        emails_data.append({
            "uid":          uid,
            "subject":      subject,
            "sender":       sender_name,
            "sender_email": sender_email,
            "age_hours":    age_hours,
            "snippet":      body_snippet,
            "urgency":      urgency,
            "is_noise":     noise,
            "replied":      has_replied,
        })
    except Exception:
        continue

mail.logout()

# ── 排序与筛选 ───────────────────────────────────────────────────────────
actionable = [e for e in emails_data if not e["is_noise"]]
noise      = [e for e in emails_data if e["is_noise"]]
actionable.sort(key=lambda e: -e["urgency"])

# ── 展示:优先收件箱 ───────────────────────────────────────────────────
console.print()
priority_table = Table(
    title=f"📬 优先收件箱 —— {len(actionable)} 封待处理邮件",
    box=box.ROUNDED, border_style="cyan"
)
priority_table.add_column("评分",   style="red",    width=7,  justify="right")
priority_table.add_column("发件人",    style="yellow", width=22)
priority_table.add_column("主题", style="white",  width=40)
priority_table.add_column("时效",     style="dim",    width=8)
priority_table.add_column("状态",  style="green",  width=10)

for e in actionable[:20]:
    age_str = (f"{int(e['age_hours'])}h" if e["age_hours"] < 48
               else f"{int(e['age_hours'] // 24)}d")
    status  = "✅ 已回复" if e["replied"] else ""
    score_colour = "red" if e["urgency"] >= 70 else "yellow" if e["urgency"] >= 50 else "dim"
    priority_table.add_row(
        f"[{score_colour}]{e['urgency']}[/{score_colour}]",
        e["sender"][:20],
        e["subject"][:38],
        age_str,
        status,
    )

console.print(priority_table)

# ── 展示:回复草稿 ────────────────────────────────────────────────────
sig = f"nn—n{YOUR_NAME or 'Best'}{', ' + YOUR_ROLE if YOUR_ROLE else ''}"
top5 = [e for e in actionable if not e["replied"]][:5]

console.print()
for e in top5:
    subj_low = e["subject"].lower()
    snip_low = e["snippet"].lower()
    greeting = f"Hi {e['sender'].split()[0]},"
    if any(k in subj_low or k in snip_low for k in ["urgent", "asap", "deadline", "overdue"]):
        body = "Thank you for flagging this — I'll look into it right away and get back to you shortly."
    elif any(k in subj_low or k in snip_low for k in QUESTION_KW):
        body = "Thanks for your message. To answer your question: [your answer here]nnLet me know if you need anything else."
    elif "re:" in subj_low or "fwd:" in subj_low:
        body = "Thanks for the follow-up. Here's where things stand: [brief update]nnHappy to jump on a call if that's easier."
    else:
        body = "Thanks for reaching out. I've reviewed your message and [your response here]."
    draft = f"{greeting}nn{body}{sig}"
    console.print(Panel(
        draft,
        title=f"[bold]📝 草稿:Re: {e['subject'][:45]}[/bold]  [dim](紧急度:{e['urgency']})[/dim]",
        border_style="yellow"
    ))

# ── 展示:统计信息 ────────────────────────────────────────────────────────────
console.print()
console.print(Panel(
    f"📊 [bold]汇总[/bold]nn"
    f"已扫描:    [yellow]{len(emails_data)}[/yellow] 封邮件n"
    f"待处理: [cyan]{len(actionable)}[/cyan]  |  "
    f"噪音邮件: [dim]{len(noise)}[/dim]  |  "
    f"已回复: [green]{sum(1 for e in actionable if e['replied'])}[/green]n"
    f"高优先级(70+): [red]{sum(1 for e in actionable if e['urgency'] >= 70)}[/red]",
    border_style="cyan"
))

# ── 保存报告 ───────────────────────────────────────────────────────────────
date_str    = datetime.now().strftime("%Y-%m-%d")
report_file = f"inbox_report_{date_str}.md"
with open(report_file, "w", encoding="utf-8") as f:
    f.write(f"# 🌈 Iris — 收件箱报告 — {date_str}nn")
    f.write(f"**已扫描:** {len(emails_data)}  **待处理:** {len(actionable)}  **噪音邮件:** {len(noise)}nn")
    f.write("## 优先邮件nn| 评分 | 发件人 | 主题 | 时效 |n|------|------|------|------|n")
    for e in actionable[:20]:
        age_str = f"{int(e['age_hours'])}h" if e["age_hours"] < 48 else f"{int(e['age_hours']//24)}d"
        f.write(f"| {e['urgency']} | {e['sender']} | {e['subject']} | {age_str} |n")
    f.write("n## 回复草稿nn")
    for e in top5:
        subj_low = e["subject"].lower()
        snip_low = e["snippet"].lower()
        greeting = f"Hi {e['sender'].split()[0]},"
        if any(k in subj_low or k in snip_low for k in ["urgent", "asap", "deadline"]):
            body = "Thank you for flagging this — I'll look into it right away."
        elif any(k in subj_low or k in snip_low for k in QUESTION_KW):
            body = "Thanks for your message. To answer your question: [your answer here]"
        else:
            body = "Thanks for reaching out. [your response here]"
        f.write(f"### Re: {e['subject']}nn{FENCE}n{greeting}nn{body}{sig}n{FENCE}nn")

console.print(Panel(
    f"[green]✅ 完成![/green]  报告已保存至 [cyan]{report_file}[/cyan]",
    border_style="green"
))

相关专题

更多
Postman身份认证怎么配置
Postman身份认证怎么配置

本专题从实际API接口调试场景出发,讲解Postman身份认证的完整配置流程,包括常见认证协议介绍、授权参数设置、访问令牌填写、OAuth 2.0授权及认证失败排查,帮助用户正确完成接口授权与访问验证。

2026.11.06

0

12

Postman请求头Headers怎么设置
Postman请求头Headers怎么设置

围绕Postman HTTP请求头配置展开,讲解Headers选项的具体使用方式,包括Key和Value参数填写、请求头信息添加、常见Header字段设置及请求头修改方法,帮助用户解决接口调试过程中遇到的请求头配置问题。

2026.11.06

0

13

Postman接口请求发送教程
Postman接口请求发送教程

详细介绍Postman接口请求的创建与发送方法,涵盖GET和POST请求、URL配置、请求参数、Headers请求头、Body请求体设置以及响应结果查看,帮助用户掌握Postman接口请求的完整操作流程。

2026.11.06

0

10

DBeaver怎么导入数据
DBeaver怎么导入数据

DBeaver数据导入专题,整理Excel、CSV、SQL、TXT等常见文件的数据导入方法,涵盖目标数据库选择、数据表创建、字段映射、数据类型设置及导入参数配置等操作,帮助用户将本地文件中的数据写入数据库,并了解批量导入、重复数据处理和导入失败等常见问题的解决方法。

2026.11.05

0

12

DBeaver安全防护教程大全
DBeaver安全防护教程大全

DBeaver安全防护专题,围绕数据库连接安全、账号密码保护和访问权限管理展开,整理SSL/TLS加密连接、SSH隧道、数据库凭据保存及证书验证等配置方法,同时介绍敏感信息保护、远程连接风险和高权限账号使用等注意事项,帮助用户提高数据库日常管理的安全性。

2026.11.05

0

12

DBeaver连接创建与配置教程
DBeaver连接创建与配置教程

本专题聚焦DBeaver数据库连接配置与管理,覆盖从新建连接、选择数据库类型到填写连接参数和验证连接的完整流程。针对MySQL、PostgreSQL、SQL Server等常见数据库,整理相应的连接设置方法,并提供驱动缺失、认证失败和连接异常等问题的排查方向。

2026.11.04

0

16

XORM框架结构体映射与表结构同步
XORM框架结构体映射与表结构同步

本专题围绕XORM框架的结构体映射展开,说明表名与字段的命名策略、xorm标签常用属性、主键自增与时间字段自动填充,并演示Sync2自动建表、增量字段升级、索引声明与反向生成结构体的完整流程。

2026.11.04

0

15

XORM框架安装配置与数据库连接
XORM框架安装配置与数据库连接

本专题聚焦XORM框架的配置与连接环节,拆解MySQL、PostgreSQL、SQLite、SQL Server的DSN写法与驱动选择,讲清连接池参数、多数据源拆分、读写分离与超时重连的处理方式,并补充容器与兼容数据库的适配经验。

2026.11.04

0

15

XORM框架安装配置与数据库连接
XORM框架安装配置与数据库连接

本专题聚焦XORM框架的配置与连接环节,拆解MySQL、PostgreSQL、SQLite、SQL Server的DSN写法与驱动选择,讲清连接池参数、多数据源拆分、读写分离与超时重连的处理方式,并补充容器与兼容数据库的适配经验。

2026.11.04

0

16

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
热门推荐
/
最新课程
phpStudy极速入门视频教程
phpStudy极速入门视频教程

共6课时 | 54.7万人学习

独孤九贱(4)_PHP视频教程
独孤九贱(4)_PHP视频教程

共89课时 | 134.1万人学习