- Python 66.9%
- Vue 21.3%
- CSS 6.4%
- TypeScript 4.4%
- Dockerfile 0.5%
- Other 0.5%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
| docker | ||
| privflow | ||
| tests | ||
| web | ||
| .dockerignore | ||
| .env.example | ||
| .gitignore | ||
| compose.yaml | ||
| config.example.json | ||
| DEPLOYMENT.md | ||
| Dockerfile | ||
| LICENSE | ||
| pyproject.toml | ||
| README.md | ||
PrivFlow
PrivFlow 是一个面向个人使用的轻量自动化执行器,目标是覆盖个人最常用的 RSS、HTTP、文件和通知流程,而不是复刻 n8n 的通用可视化编辑器。
部署请参阅 DEPLOYMENT.md。
当前 MVP
manual、interval、rss三种触发器- RSS/Atom 轮询与去重
- HTTP 请求与 JSON/文本解析
- JSON 路径、正则提取
pick、模板、数组拼接、JSON 序列化等简单转换- 文件保存、文件下载
- Console、Telegram、SMTP 邮件和 Webhook 输出
- Telegram、SMTP 连接集中配置,多个 flow 可以复用
- SQLite 运行记录、失败信息和调度检查点
- 所有配置使用 JSON,敏感值可用
${ENV_VAR}引用环境变量 - 本地 HTTP API 与 TypeScript Web 配置界面
- 单用户 Passkey 登录;首次注册后所有配置和运行 API 均受保护
快速开始
python -m privflow init privflow.json
python -m privflow validate -c privflow.json
python -m privflow list -c privflow.json
python -m privflow history --db .privflow/state.db
示例中的外部 flow 默认关闭;填入真实地址和凭据后,把对应 flow 的 enabled 改为 true,再执行:
python -m privflow run-once -c privflow.json --force
持续运行调度器:
python -m privflow daemon -c privflow.json --tick 15
Web 界面
前端使用 TypeScript + Vite,Python 继续负责执行器、SQLite 状态和配置 API。前端通过 /api/definitions 获取触发器和动作的字段定义,因此后续新增业务类型时,可以扩展后端定义和执行器,再由通用表单呈现配置。
首次构建并启动:
cd web
pnpm install
pnpm run build
cd ..
python -m privflow init privflow.json # 首次运行时执行一次
python -m privflow serve -c privflow.json --port 8787
然后打开 http://localhost:8787,首次访问时注册第一把 Passkey。之后可在顶部“添加 Passkey”登记备用设备。默认 Passkey 配置仅匹配这个地址;如果以其他域名、端口或 HTTPS 访问,请按 DEPLOYMENT.md 设置 PRIVFLOW_RP_ID、PRIVFLOW_RP_ORIGIN 和 PRIVFLOW_COOKIE_SECURE。
Web 界面当前支持:
- 查看 flow 和运行历史
- 新建、编辑、删除 flow
- 配置手动、定时、RSS 触发器
- 配置 HTTP、通知、保存文件、下载、Webhook、数据转换动作
- 对已保存的 SMTP 连接测试连通性和身份验证(不会发送测试邮件)
- 从 Web 直接运行 flow
- 从 Web 以调试模式运行 flow,并在运行详情中逐步查看每个动作的输出
- 显示后台运行中的任务状态,并自动刷新运行历史
- 概览、flow 编辑和连接配置使用独立 URL,支持浏览器前进后退;例如
/flows/new、/flows/example/edit、/connections
开发前端时,可以让 Python API 运行在 8787 端口,再执行:
cd web
pnpm run dev
配置仍保存为本地 JSON,Web 编辑器只是它的管理入口;这样即使暂时不使用 Web,也可以继续用 CLI 和配置文件。
命令行也可以对指定 flow 开启调试记录:
python -m privflow run-once -c privflow.json --flow my-flow --force --debug
普通运行仍保存原有的动作输出格式;只有调试运行会额外保存每一步的输出、状态和错误信息。
也可以安装成命令:
python -m pip install -e .
privflow list
Flow 示例
一个 RSS 到 Telegram 的 flow。Token 不放在 flow 里,而是引用预先配置的共享连接:
{
"id": "rss-to-telegram",
"trigger": {
"type": "rss",
"url": "https://example.com/feed.xml",
"seconds": 1800,
"process_history": false
},
"steps": [
{
"type": "notify",
"channel": "telegram",
"connection": "telegram-main",
"message": "{{ item.title }}\n{{ item.link }}"
}
]
}
RSS 触发器的 process_history 默认是 false。关闭时,PrivFlow 会记录 flow 从停用变为启用的时间,只处理发布时间晚于该时间的条目;首次启用不会把订阅源已有的历史文章全部发送出来。将它设为 true 才会处理当前订阅源中的历史条目。无法解析发布时间的条目,在默认模式下会跳过;重新停用再启用会产生新的时间边界。
邮件通知使用同样的方式:
{
"type": "notify",
"channel": "email",
"connection": "smtp-main",
"to": "me@example.com",
"subject": "PrivFlow 更新",
"message": "{{ data }}"
}
如果 RSS 描述中包含 HTML,可以使用 item.summary_html:
{
"type": "notify",
"channel": "email",
"connection": "smtp-main",
"format": "html",
"to": "me@example.com",
"subject": "RSS 更新:{{ item.title }}",
"message": "<html><body><h2>{{ item.title }}</h2>{{ item.summary_html }}</body></html>"
}
PrivFlow 会在 RSS 解析阶段和邮件发送阶段各做一次白名单过滤。目前允许 a、img、br、p、div、span、strong、em、列表、标题、引用和代码标签;会移除脚本、iframe、表单、事件属性、样式属性以及非 HTTPS 图片地址。邮件同时包含纯文本降级版本。
模板变量与使用场景
所有动作配置字段都会在执行前递归渲染模板,包括 URL、请求体、文件路径、消息、邮件主题、邮件地址和 Webhook 请求体等。模板使用 {{ expression }},支持点号路径和数组下标,例如 {{ data.items.0.url }}。
通用变量
每次真正执行一个 flow 时,都可以使用以下变量:
| 变量 | 含义 |
|---|---|
run_id |
SQLite 运行记录 ID |
execution_id |
本次执行的 UUID |
flow |
当前 flow 的完整配置,可访问 flow.id、flow.trigger、flow.steps |
trigger |
本次触发信息,可访问 trigger.type、trigger.fired_at |
item |
当前条目;RSS 场景是 RSS 条目,其他普通场景默认为空对象 |
data |
当前数据,会随着每个动作执行不断更新 |
response |
最近一次 HTTP 请求的响应对象 |
steps.<step-id> |
指定动作的输出;没有显式 id 时使用 steps.step_1、steps.step_2 等 |
response 通常包含:response.status、response.headers、response.text 和 response.body。模板中一般优先使用 response.text;JSON 请求解析后的结果会直接放入 data。
变量不存在时会渲染为空字符串。完整表达式会保留原始类型:
{
"body": "{{ data }}",
"message": "当前数据:{{ data }}"
}
当 data 是对象或数组时,第一种写法会保留对象/数组类型,第二种写法会把它转换成 JSON 文本。
各触发场景
RSS 触发器每发现一个新条目执行一次动作,此时可用:
| 变量 | 含义 |
|---|---|
item.id |
条目标识,用于去重 |
item.title |
标题 |
item.link |
原文链接 |
item.summary |
去除 HTML 后的纯文本摘要 |
item.summary_html |
经过白名单过滤的 HTML 摘要 |
item.published |
发布时间;由 RSS/Atom 字段提供 |
item.source |
Feed 地址 |
例如 RSS → 邮件:
{
"type": "notify",
"channel": "email",
"connection": "smtp-main",
"format": "html",
"to": "me@example.com",
"subject": "RSS 更新:{{ item.title }}",
"message": "<h2>{{ item.title }}</h2>{{ item.summary_html }}<p><a href=\"{{ item.link }}\">查看原文</a></p>"
}
手动触发和定时触发没有 RSS 条目,item 和初始 data 是空对象,但仍可使用 flow、trigger、run_id 和 execution_id。
各动作场景
| 动作 | 执行后的 data |
常用变量 |
|---|---|---|
| HTTP 请求 | 根据 parse 得到 JSON 对象、文本或其他响应结果 |
response.status、response.text、response.headers、data |
| 提取数据 | 提取出的字段或正则结果 | data、response.text、steps.<id> |
| 数据转换 | 转换结果 | data、item、steps.<id> |
| 保存文件 | 保存后的本地路径 | data、之前动作的 steps.<id> |
| 下载文件 | 下载后的本地路径 | data、response、item |
| Webhook | Webhook 响应对象 | data.status、data.text、data.headers |
| 通知 | 通知结果对象 | 消息发送前可使用所有当前上下文变量 |
| 规则转换 | 生成后的规则文本;未变化时跳过后续动作 | data、item、steps.<id> |
推荐给需要被后续动作引用的动作设置显式 id:
[
{
"id": "fetch-items",
"type": "http",
"url": "https://example.com/api/items",
"parse": "json"
},
{
"type": "extract",
"from": "steps.fetch-items",
"path": "items.0.url"
},
{
"type": "notify",
"channel": "telegram",
"connection": "telegram-main",
"message": "{{ data }}"
}
]
transform.map_template 会为数组中的每个元素建立局部上下文。在该模板内部,item 和 data 都指向当前元素:
{
"type": "transform",
"operation": "map_template",
"template": "{{ item.url }}"
}
规则转换
rules 是一个内置的无代码动作,适合把按行排列的域名列表转换为规则文本,并记住上一次输入的首行。状态保存于 SQLite,按 flow_id 和 state_key 隔离。
{
"id": "build-rules",
"type": "rules",
"from": "data",
"format": "domain_suffix",
"state_key": "updatedInfo",
"skip_if_unchanged": true,
"comment_prefix": "#",
"entry_prefix": "DOMAIN-SUFFIX,",
"ignore_blank": true,
"strip": false
}
规则转换的行为如下:
- 以输入文本第一行作为变更标识;
- 首次运行或首行变化时生成规则并更新状态;
- 首行未变化时将本次运行标记为
skipped,不执行后续动作; - 以
comment_prefix开头的行原样保留; - 普通非空行添加
entry_prefix; ignore_blank控制是否忽略空行;strip控制是否去除每行首尾空白;- 状态在整个 workflow 成功后才提交,后续保存或发送失败时下一次仍会重试。
状态检查
state_guard 用于比较当前数据和持久化状态,适合“数据没有变化就不继续处理”的场景:
{
"id": "check-version",
"type": "state_guard",
"from": "data.updated_at",
"state_key": "last_updated_at",
"on_unchanged": "skip"
}
动作会读取 from 指定的值,并按当前 flow_id 和 state_key 从 SQLite 查询旧值。首次运行或值发生变化时暂存新值并继续执行;值相同时,skip 会将本次运行标记为 skipped,success 会将本次运行标记为成功,但两者都会结束后续动作。新值只有在整个 flow 成功完成后才提交,后续动作失败时不会消耗这次变化。
连接与安全
privflow.json 的 connections 区域只保存连接元数据和环境变量名,例如:
{
"id": "smtp-main",
"type": "smtp",
"host": "smtp.example.com",
"port": 587,
"username_env": "SMTP_USERNAME",
"password_env": "SMTP_PASSWORD",
"from": "you@example.com",
"starttls": true
}
实际凭据由启动 PrivFlow 的环境提供:
export TELEGRAM_BOT_TOKEN='在密码管理器中读取的 token'
export SMTP_USERNAME='your-account'
export SMTP_PASSWORD='在密码管理器中读取的密码'
Web 界面不会读取或回显这些值;它只显示环境变量是否已配置。API 也会拒绝保存 workflow 或 connection 中的明文 token、password 等字段。生产使用时建议通过系统服务、密码管理器或进程管理工具注入环境变量,不要把真实凭据写入 JSON、代码仓库或命令历史。
在“连接”页面保存 SMTP 配置后,可以点击“测试连接”。测试会建立 SMTP 会话、按配置执行 STARTTLS 并尝试登录,但不会发送邮件。若返回 535 Authentication failed,请重点检查用户名、密码环境变量是否指向正确值,以及服务商是否要求应用专用密码或授权码。
文件动作的相对路径默认位于 storage.base_dir 下。首次使用建议从 config.example.json 复制一份配置,并把真实配置加入 .gitignore,避免提交 Token 或 SMTP 密码。
设计边界
第一版刻意不包含可视化节点画布、插件市场、多用户权限、复杂表达式和分布式队列。先让表单配置和固定的个人流程稳定运行,再根据真实使用频率扩展能力。